/* ============================================================
   FIELD NOTES — design system
   serif voice:  Fraunces (display) + Newsreader (body)
   mono voice:   IBM Plex Mono (labels, dates, data)
   light: warm paper & ink & deep-water teal & seal vermilion
   dark:  abyss & bone & bioluminescent cyan
   ============================================================ */

/* kai-script seal glyphs (俞翌阳) — installed kai fonts win (Kaiti SC on
   macOS, KaiTi on Windows); the 1.2KB LXGW WenKai subset only downloads
   where none exists (iOS ships no kai font → the seals fell back to sans). */
@font-face {
    font-family: 'Seal Kai';
    src: local('Kaiti SC'), local('Kaiti SC Regular'), local('STKaitiSC-Regular'),
         local('STKaiti'), local('KaiTi'), local('BiauKai'), local('DFKai-SB'),
         url('assets/fonts/seal-kai.woff2') format('woff2');
    font-display: swap;
    unicode-range: U+4FDE, U+7FCC, U+9633;
}

:root {
    --paper:      #f7f3ec;
    --ink:        #1f1b16;
    --ink-soft:   #6e6759;
    --line:       #d9d2c4;
    --accent:     #16687a;
    --accent-ink: #0e4e5c;
    --seal:       #b13a2c;
    --surface:    rgba(31, 27, 22, 0.04);

    /* code syntax — warm, on-palette */
    --syn-keyword: #b13a2c;
    --syn-string:  #4f7a3a;
    --syn-number:  #9a6a1e;
    --syn-func:    #0e4e5c;
    --syn-type:    #16687a;
    --syn-comment: #9f968a;
    --syn-attr:    #6d5a9c;

    --font-display: 'Fraunces', Georgia, serif;
    --font-body:    'Newsreader', Georgia, serif;
    --font-mono:    'IBM Plex Mono', 'Fira Code', monospace;

    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --content-w: 880px;
    interpolate-size: allow-keywords;   /* lets height:auto animate (e.g. collapsible TOC) */
}

html.dark {
    --paper:      #0a0f16;
    --ink:        #e8e3d8;
    --ink-soft:   #8b94a3;
    --line:       #232c38;
    --accent:     #5fd4e0;
    --accent-ink: #8ce4ed;
    --seal:       #e05a48;
    --surface:    rgba(232, 227, 216, 0.05);

    --syn-keyword: #e5795f;
    --syn-string:  #a9cc86;
    --syn-number:  #d8ad63;
    --syn-func:    #8ce4ed;
    --syn-type:    #5fd4e0;
    --syn-comment: #6b7688;
    --syn-attr:    #baa2e6;
}

* { box-sizing: border-box; }

/* every themed surface eases on the same clock, so light/dark flips as one
   piece instead of the header snapping ahead of the page. Elements with their
   own (snappier) hover transition still win on specificity — see those rules. */
*, *::before, *::after {
    transition-property: background-color, border-color, color, fill, stroke;
    transition-duration: 0.45s;
    transition-timing-function: ease;
}

html { scroll-behavior: smooth; transition: background-color 0.45s ease; }

body {
    margin: 0;
    font-family: var(--font-body);
    font-size: 1.1rem;
    font-weight: 350;
    line-height: 1.65;
    color: var(--ink);
    background-color: var(--paper);
    transition: background-color 0.45s ease, color 0.45s ease;
    -webkit-font-smoothing: antialiased;
}

.mono {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    font-weight: 400;
    letter-spacing: 0.04em;
}

a { color: var(--accent-ink); text-decoration: none; }

main a, .site-footer a {
    background-image: linear-gradient(currentColor, currentColor);
    background-size: 100% 1px;
    background-position: 0 100%;
    background-repeat: no-repeat;
    transition: color 0.25s ease;
}
main a:hover { color: var(--seal); }

::selection { background: var(--accent); color: var(--paper); }

/* ---------- atmosphere ---------- */

#ocean {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
}

/* ---------- conductor mode (the "polymorph" egg) ---------- */

/* "clean stage": a veil of the page background dims the content so the school
   reads unobstructed. The veil is `main::before` (not opacity on main, and not
   a body-level layer) on purpose — `main` is a stacking context
   (position:relative; z-index:10), so a veil INSIDE it lets the game slot poke
   back above the dim while the rest of main sinks, keeping the eggs
   dismissable. The school, header and console ride above it all. */
main::before {
    content: '';
    position: fixed;
    inset: 0;
    background: var(--paper);
    opacity: 0;
    pointer-events: none;
    z-index: 1;
    transition: opacity 0.55s var(--ease-out);
}
.stage-clean main::before { opacity: 0.94; }
.stage-clean #ocean { z-index: 12; }           /* the school above the veil (main is z10) */
.stage-clean main { pointer-events: none; }    /* faded content is inert… */
.stage-clean .game-phrase {                     /* …but the game slot stays live */
    position: relative;
    z-index: 2;
    pointer-events: auto;
}

/* "pointing": on for the whole life of any pointer.point(), spotlight or not.
   It lifts the school above <main> and does NOTHING else — no veil, no
   pointer-events change — because a bare point must leave the page fully
   readable and clickable. Without it a current aimed across the About section
   swims behind the opaque portrait, and a formation hiding behind a photo is
   not pointing at anything. Composes with the two stages below: they set the
   same z-index, so any combination lands on 12. */
.stage-point #ocean { z-index: 12; }

/* "spotlight": clean stage with a hole in it. When the school points at
   something (pointer.js), the page dims a little less than a full clean stage
   and the pointed-at element is lifted back above the veil, so it reads as lit
   rather than merely un-dimmed. Same mechanism, same reason: the veil is
   `main::before` INSIDE main's stacking context, so anything in main only needs
   a z-index of 2 to poke through — never `opacity` on <main>, which would trap
   the game slot under the dim and make the egg undismissable. An element in a
   stacking context we cannot lift just stays dim; the pointing still works. */
.stage-spot main::before { opacity: 0.86; }
.stage-spot #ocean { z-index: 12; }            /* the school above the veil (main is z10) */
.stage-spot main { pointer-events: none; }
.stage-spot .game-phrase,
.stage-spot .spotlit {
    position: relative;
    z-index: 2;
    pointer-events: auto;
}
/* both stages at once (an egg dimmed the page, then the school pointed): keep
   the darker veil so the combination reads as one deliberate state */
.stage-clean.stage-spot main::before { opacity: 0.94; }

/* a soft halo of paper bleeding out from the lit element, so its edges don't
   cut hard against the veil */
.stage-spot .spotlit {
    border-radius: 6px;
    box-shadow: 0 0 0 10px var(--paper),
                0 0 46px 22px color-mix(in srgb, var(--paper) 70%, transparent);
    transition: box-shadow 0.55s var(--ease-out);
}

/* the lone red fish is a live control — clicking it opens the ministeve avatar.
   A pointer cursor while hovering it (cursor inherits, so setting it on <html>
   propagates over the water), and a one-time "ask me about steve?" ripple that
   blooms near it after the visitor has been idle a moment. */
html.fish-clickable, html.fish-clickable body { cursor: pointer; }

.fish-hint {
    position: fixed;
    z-index: 40;
    transform: translate(-50%, -150%) scale(0.92);
    transform-origin: 50% 140%;
    font-family: var(--font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.01em;
    line-height: 1;
    color: var(--seal);
    background: color-mix(in srgb, var(--paper) 86%, transparent);
    border: 1px solid color-mix(in srgb, var(--seal) 38%, transparent);
    border-radius: 999px;
    padding: 6px 13px;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    box-shadow: 0 8px 24px -14px color-mix(in srgb, var(--seal) 65%, transparent);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out);
}
.fish-hint.show    { opacity: 1; transform: translate(-50%, -162%) scale(1); }
.fish-hint.leaving { opacity: 0; transform: translate(-50%, -178%) scale(0.96); }
/* a small seal-ink nub, like a speech ripple rising off the fish */
.fish-hint::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: -5px;
    width: 7px;
    height: 7px;
    transform: translateX(-50%) rotate(45deg);
    background: color-mix(in srgb, var(--paper) 86%, transparent);
    border-right: 1px solid color-mix(in srgb, var(--seal) 38%, transparent);
    border-bottom: 1px solid color-mix(in srgb, var(--seal) 38%, transparent);
}

@media (prefers-reduced-motion: reduce) {
    .fish-hint { transition: opacity 0.4s ease; transform: translate(-50%, -162%); }
    .fish-hint.show,
    .fish-hint.leaving { transform: translate(-50%, -162%); }
}

.conductor {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    width: min(460px, calc(100vw - 28px));
    z-index: 70;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 12px 14px;
    box-shadow: 0 10px 34px rgba(15, 12, 8, 0.18);
    font-family: var(--font-mono);
}

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

.conductor-label {
    font-size: 0.68rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.conductor-x {
    background: none;
    border: none;
    color: var(--ink-soft);
    font-size: 1.1rem;
    line-height: 1;
    padding: 2px 6px;
    cursor: pointer;
}
.conductor-x:hover { color: var(--seal); }

/* clean-stage switch — a small track + knob with a mono label */
.conductor-stage {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-left: auto;
    margin-right: 12px;
    cursor: pointer;
    user-select: none;
}
.conductor-stage-in {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}
.conductor-stage-track {
    position: relative;
    width: 30px;
    height: 16px;
    border-radius: 8px;
    background: var(--line);
    transition: background 0.2s var(--ease-out);
    flex: none;
}
.conductor-stage-track::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--paper);
    box-shadow: 0 1px 2px rgba(15, 12, 8, 0.3);
    transition: transform 0.2s var(--ease-out);
}
.conductor-stage-in:checked + .conductor-stage-track { background: var(--accent); }
.conductor-stage-in:checked + .conductor-stage-track::after { transform: translateX(14px); }
.conductor-stage-in:focus-visible + .conductor-stage-track {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.conductor-stage-text {
    font-size: 0.68rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
}
.conductor-stage-in:checked ~ .conductor-stage-text { color: var(--accent); }

.conductor-in {
    width: 100%;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 8px 10px;
    font-family: var(--font-mono);
    font-size: 16px;             /* ≥16px: keeps iOS from zooming the field */
    color: var(--ink);
    outline: none;
}
.conductor-in:focus { border-color: var(--accent); }
.conductor-in::placeholder { color: var(--ink-soft); opacity: 0.7; }

.conductor-row {
    display: flex;
    align-items: baseline;
    justify-content: flex-start;
    gap: 10px;
    margin-top: 8px;
}

.conductor-img {
    background: none;
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 4px 10px;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    color: var(--ink-soft);
    cursor: pointer;
    white-space: nowrap;
}
.conductor-img:hover { color: var(--accent); border-color: var(--accent); }

.conductor-hint {
    font-size: 0.72rem;
    color: var(--seal);
    text-align: right;
    min-height: 1em;
    margin-left: auto;
}

/* 3D cloud picker — a row of shape buttons; the school forms a rotating object */
.conductor-3d-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}
.conductor-3d-label {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    color: var(--ink-soft);
    margin-right: 2px;
}
.conductor-3d {
    background: none;
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 3px 9px;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    color: var(--ink-soft);
    cursor: pointer;
}
.conductor-3d:hover { color: var(--accent); border-color: var(--accent); }
.conductor-3d.is-on { color: var(--accent); border-color: var(--accent); }

/* ---------- ministeve · the chat avatar ---------- */
/* Reuses the conductor's clean-stage veil + head/switch; this is the chat shell:
   a bottom console with a transcript and a question box. The school spells the
   replies; the transcript is the readable, accessible copy. */
.ministeve {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    width: min(460px, calc(100vw - 28px));
    z-index: 70;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 12px 14px;
    box-shadow: 0 10px 34px rgba(15, 12, 8, 0.18);
    font-family: var(--font-mono);
}

.ministeve-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}
.ministeve-label {
    font-size: 0.68rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* show/hide the transcript so the fish get the stage */
.ministeve-toggle {
    background: none;
    border: none;
    margin-left: 12px;
    padding: 2px 4px;
    font-family: var(--font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-soft);
    cursor: pointer;
    position: relative;
}
.ministeve-toggle:hover { color: var(--accent); }
/* a small dot when there's unread text behind a collapsed log */
.ministeve-toggle.has-unread::after {
    content: '';
    position: absolute;
    top: 0;
    right: -2px;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--seal);
}

/* the transcript — text bubbles, capped height, scrolls */
.ministeve.collapsed .ministeve-log { display: none; }
.ministeve-log {
    max-height: 172px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 7px;
    margin-bottom: 9px;
    padding-right: 2px;
    -webkit-overflow-scrolling: touch;
}
.ministeve-msg {
    font-family: var(--font-body);
    font-size: 0.9rem;
    line-height: 1.4;
    max-width: 82%;
    padding: 7px 11px;
    border-radius: 13px;
    white-space: pre-wrap;
    word-break: break-word;
}
.ministeve-user {
    align-self: flex-end;
    background: var(--accent);
    color: var(--paper);
    border-bottom-right-radius: 4px;
}
.ministeve-bot {
    align-self: flex-start;
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--line);
    border-bottom-left-radius: 4px;
}
.ministeve-bot.is-typing {
    color: var(--ink-soft);
    letter-spacing: 0.18em;
}

.ministeve-row {
    display: flex;
    gap: 8px;
    align-items: stretch;
}
.ministeve-in {
    flex: 1 1 auto;
    min-width: 0;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 8px 10px;
    font-family: var(--font-mono);
    font-size: 16px;             /* ≥16px: keeps iOS from zooming the field */
    color: var(--ink);
    outline: none;
}
.ministeve-in:focus { border-color: var(--accent); }
.ministeve-in::placeholder { color: var(--ink-soft); opacity: 0.7; }

.ministeve-send {
    flex: none;
    background: var(--accent);
    border: 1px solid var(--accent);
    border-radius: 6px;
    padding: 0 14px;
    font-family: var(--font-mono);
    font-size: 0.74rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--paper);
    cursor: pointer;
}
.ministeve-send:hover { background: var(--seal); border-color: var(--seal); }
.ministeve-send:active { transform: translateY(1px); }

.ministeve-foot { margin-top: 6px; min-height: 1em; }
.ministeve-hint { text-align: left; display: block; }

.grain {
    position: fixed;
    inset: 0;
    z-index: 50;
    pointer-events: none;
    opacity: 0.3;
    mix-blend-mode: multiply;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.10'/%3E%3C/svg%3E");
}
html.dark .grain { mix-blend-mode: screen; opacity: 0.18; }

/* ---------- header ---------- */

.site-header {
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    max-width: var(--content-w);
    margin: 0 auto;
    padding: 18px 24px;
    background: color-mix(in srgb, var(--paper) 82%, transparent);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--line);
}

.wordmark {
    font-family: var(--font-mono);
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--ink);
    letter-spacing: 0.02em;
}

.cursor-blink {
    color: var(--seal);
    animation: blink 1.2s steps(2) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

.site-nav { display: flex; gap: 22px; }

.site-nav a {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
    position: relative;
}
.site-nav a::before { content: '/'; color: var(--line); margin-right: 5px; }
.site-nav a:hover { color: var(--accent-ink); }

.theme-toggle {
    display: flex;
    align-items: center;
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px;
    color: var(--ink-soft);
    transition: color 0.45s ease, transform 0.4s var(--ease-out);
}
.theme-toggle svg { width: 18px; height: 18px; display: block; }
.theme-toggle .sun-icon { display: none; }
html.dark .theme-toggle .sun-icon { display: block; }
html.dark .theme-toggle .moon-icon { display: none; }
.theme-toggle:hover { color: var(--accent-ink); transform: rotate(18deg); }

/* ---------- layout ---------- */

main {
    position: relative;
    z-index: 10;
    max-width: var(--content-w);
    margin: 0 auto;
    padding: 0 24px;
}

/* ---------- hero ---------- */

.hero { padding: 12vh 0 12vh; }

.kicker {
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.18em;
    font-size: 0.7rem;
    margin: 0 0 28px;
}

.hero-name {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 144, 'WONK' 1;
    font-weight: 460;
    font-size: clamp(3rem, 9.5vw, 6.2rem);
    line-height: 1.02;
    letter-spacing: -0.015em;
    margin: 0 0 36px;
}
.hero-name .line { display: block; }
.hero-name em {
    font-family: var(--font-body);
    font-style: italic;
    font-weight: 300;
    color: var(--ink-soft);
    font-size: 0.62em;
    vertical-align: 0.12em;
    letter-spacing: 0;
}

/* the seal — 印章 */
.seal {
    display: inline-flex;
    flex-direction: column;
    vertical-align: 0.18em;
    margin-left: 0.28em;
    padding: 0.16em 0.18em;
    background: var(--seal);
    color: #f7f3ec;
    border-radius: 3px;
    font-family: 'Seal Kai', 'Kaiti SC', 'STKaiti', 'KaiTi', 'BiauKai', 'DFKai-SB', serif;
    font-size: 0.16em;
    line-height: 1.18;
    letter-spacing: 0;
    transform: rotate(4deg);
    box-shadow: 0 1px 0 rgba(0,0,0,0.15) inset;
    transition: transform 0.4s var(--ease-out), background-color 0.45s ease, color 0.45s ease;
    cursor: default;
}
.hero-name:hover .seal { transform: rotate(-3deg) scale(1.06); }

.hero-bio {
    max-width: 34em;
    font-size: 1.22rem;
    margin: 0 0 44px;
}

.hero-links {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 10px 30px;
    padding: 0;
    margin: 0 0 56px;
}
.hero-links .idx { color: var(--seal); margin-right: 8px; font-size: 0.68rem; }
.hero-links a { color: var(--ink); background: none; }
.hero-links a:hover { color: var(--accent-ink); }

.hero-hint {
    color: var(--ink-soft);
    opacity: 0.7;
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    margin: 0;
}

/* ---------- sequence divider ---------- */

.sequence-divider {
    max-width: var(--content-w);
    margin: 0 auto;
    padding: 0 24px;
    overflow: hidden;
    white-space: nowrap;
    color: var(--line);
    font-size: 0.65rem;
    letter-spacing: 0.35em;
    user-select: none;
    position: relative;
    z-index: 10;
}
.sequence-divider .hot { color: var(--seal); }

/* ---------- sections ---------- */

.section { padding: 90px 0 30px; }

.section-head {
    display: flex;
    align-items: baseline;
    gap: 16px;
    margin-bottom: 44px;
}
.section-no { color: var(--seal); font-size: 0.75rem; }
.section-head h2 {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 60;
    font-weight: 480;
    font-size: 1.9rem;
    letter-spacing: -0.01em;
    margin: 0;
}
.section-head .rule { flex: 1; height: 1px; background: var(--line); }

/* ---------- about ---------- */

.about-grid {
    display: grid;
    grid-template-columns: 1.6fr 1fr;
    gap: 56px;
    align-items: start;
}

.lede {
    font-size: 1.3rem;
    line-height: 1.5;
    margin-top: 0;
}

.about-text p { margin-bottom: 1.1em; }

.cjk { font-family: 'Songti SC', 'Noto Serif CJK SC', serif; }

.fieldnote {
    margin-top: 30px;
    border-top: 1px dashed var(--line);
    border-bottom: 1px dashed var(--line);
    padding: 14px 0;
}
.fieldnote summary {
    cursor: pointer;
    color: var(--seal);
    list-style: none;
    letter-spacing: 0.08em;
}
.fieldnote summary::-webkit-details-marker { display: none; }
.fieldnote summary::after { content: ' +'; }
.fieldnote[open] summary::after { content: ' −'; }
.fieldnote p { margin: 12px 0 4px; color: var(--ink-soft); }

.about-portrait {
    margin: 0;
    transform: rotate(1.2deg);
}
.about-portrait img {
    width: 100%;
    display: block;
    border-radius: 4px;
    filter: saturate(0.92) contrast(1.02);
    box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.35);
    transition: opacity 0.4s ease;
}
/* the molecule egg fades the portrait so the fish can form in its place */
/* while the molecule is up, the portrait fades AND stops intercepting the
   pointer, so grabbing the portrait area drags the fish behind it, not the img */
.about-portrait.is-molecule img { opacity: 0; pointer-events: none; }

/* clickable "protein" / "DNA" words in the About paragraph */
.mol-word {
    cursor: pointer;
    color: var(--accent-ink);
    border-bottom: 1px dotted color-mix(in srgb, var(--accent) 60%, transparent);
    transition: color 0.2s ease, border-color 0.2s ease;
}
.mol-word:hover,
.mol-word:focus-visible {
    color: var(--seal);
    border-bottom-color: var(--seal);
    outline: none;
}
.mol-word.is-active { color: var(--seal); border-bottom-color: var(--seal); }
.about-portrait figcaption {
    margin-top: 12px;
    color: var(--ink-soft);
    font-size: 0.68rem;
    letter-spacing: 0.08em;
}

/* ---------- log ---------- */

.log-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.log-list li {
    display: flex;
    align-items: baseline;
    gap: 14px;
    padding: 13px 0;
    border-bottom: 1px solid var(--line);
}
.log-list li:last-child { border-bottom: none; }
.log-date {
    color: var(--accent-ink);
    flex-shrink: 0;
    width: 64px;
}
.log-leader {
    flex-shrink: 0;
    width: 28px;
    border-bottom: 1px dotted var(--line);
    align-self: center;
}
.log-entry { font-size: 1.02rem; }

/* ---------- work ---------- */

.work-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.work-item {
    display: flex;
    align-items: baseline;
    gap: 26px;
    padding: 30px 14px;
    margin: 0 -14px;
    border-bottom: 1px solid var(--line);
    border-radius: 6px;
    transition: background-color 0.3s ease;
}
.work-item:hover { background: var(--surface); }
.work-no {
    color: var(--seal);
    font-size: 1.05rem;
    flex-shrink: 0;
    width: 26px;
    transition: transform 0.3s var(--ease-out), color 0.45s ease;
}
.work-item:hover .work-no { transform: translateX(4px); }
.work-body { flex: 1; }
.work-body h3 {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 40;
    font-weight: 500;
    font-size: 1.35rem;
    letter-spacing: -0.01em;
    margin: 0 0 8px;
    transition: color 0.25s ease;
}
.work-item:hover h3 { color: var(--accent-ink); }
.work-body p { margin: 0 0 10px; color: var(--ink-soft); font-size: 1rem; }
.work-links { display: flex; gap: 18px; }
.work-links a::before { content: '['; color: var(--line); }
.work-links a::after  { content: ']'; color: var(--line); }
.work-links a { background: none; }
.work-tag {
    flex-shrink: 0;
    color: var(--ink-soft);
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 3px 11px;
}

/* ---------- contact / game ---------- */

.contact { padding-bottom: 80px; }

.contact-line {
    font-size: 1.3rem;
    margin-top: 0;
}

/* the blank and its predicate never separate */
.game-phrase { white-space: nowrap; }
@media (max-width: 400px) {
    .game-phrase { white-space: normal; }
}

/* the blank IS the input — typed letters land on the underscores behind them */
.game-slot {
    position: relative;
    display: inline-block;
    margin: 0 6px;
    font-size: 0.8em;
    letter-spacing: 0.32em;
    line-height: 1.4;
    vertical-align: baseline;
}
/* the ghost underscores are the slot's in-flow text: plain glyphs with fixed
   metrics define the baseline, so nothing the <input> does can shift the line.
   (iOS derives a text field's baseline differently empty vs filled, which
   nudged the whole slot after a delete-and-retype when the input was in-flow.) */
.game-ghost {
    display: inline-block;
    font: inherit;
    letter-spacing: inherit;
    color: var(--accent-ink);
    opacity: 0.5;
    pointer-events: none;
    transition: opacity 0.3s ease;
}
.game-slot:focus-within .game-ghost { opacity: 0.85; }
/* blinking invitation — replaced by the native caret on focus */
.game-caret {
    position: absolute;
    left: 1px;
    top: 50%;
    transform: translateY(-58%);
    width: 2px;
    height: 1.05em;
    background: var(--seal);
    pointer-events: none;
    animation: blink 1.2s steps(2) infinite;
}
.game-slot:focus-within .game-caret,
.game-slot.has-text .game-caret { display: none; }
.game-blank {
    /* overlaid on the ghost, out of flow — the field can't affect layout */
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    background: transparent;
    border: none;
    outline: none;
    padding: 0;
    margin: 0;
    font: inherit;
    letter-spacing: inherit;
    text-transform: uppercase;
    color: var(--accent-ink);
    caret-color: var(--seal);
    -webkit-appearance: none;
    appearance: none;
    border-radius: 0;
    box-sizing: border-box;
    line-height: 1.4;
}

.contact-cta {
    display: inline-flex;
    align-items: baseline;
    gap: 14px;
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 60;
    font-style: italic;
    font-weight: 480;
    font-size: 2.1rem;
    color: var(--ink);
    background: none;
    border-bottom: 2px solid var(--seal);
    padding-bottom: 4px;
    transition: color 0.3s ease;
}
.contact-cta:hover { color: var(--seal); }
.cta-arrow {
    font-style: normal;
    font-size: 0.7em;
    transition: transform 0.35s var(--ease-out);
}
.contact-cta:hover .cta-arrow { transform: translateX(8px); }

#game-result {
    margin: 0 0 34px;
    min-height: 1.4em;
    color: var(--seal);
    letter-spacing: 0.05em;
}

/* ---------- footer ---------- */

.site-footer {
    position: relative;
    z-index: 10;
    max-width: var(--content-w);
    margin: 0 auto;
    padding: 26px 24px 34px;
    display: flex;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    color: var(--ink-soft);
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    border-top: 1px solid var(--line);
}
.footer-seq { opacity: 0.65; }

/* ============================================================
   SUBPAGES — the catalog (projects) & notes (blog)
   ============================================================ */

.site-nav a.here { color: var(--seal); }

.page-head { padding: 72px 0 8px; }
.page-title {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 144, 'WONK' 1;
    font-weight: 460;
    font-size: clamp(2.6rem, 6vw, 4rem);
    letter-spacing: -0.015em;
    margin: 0 0 12px;
}
.page-sub {
    color: var(--ink-soft);
    font-size: 1.08rem;
    max-width: 36em;
    margin: 0 0 40px;
}

/* mono bracket filters */
.filters {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    margin: 0 0 14px;
}
.filter-btn {
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px 0;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
    transition: color 0.25s ease;
}
.filter-btn::before { content: '[ '; color: var(--line); }
.filter-btn::after  { content: ' ]'; color: var(--line); }
.filter-btn:hover { color: var(--accent-ink); }
.filter-btn.active { color: var(--seal); }
.filter-btn.active::before, .filter-btn.active::after { color: var(--seal); opacity: 0.5; }

.catalog-count {
    color: var(--ink-soft);
    opacity: 0.7;
    font-size: 0.68rem;
    letter-spacing: 0.1em;
    margin: 0 0 26px;
    min-height: 1em;
}

/* floating specimen image on row hover */
.hover-specimen {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 60;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.3s ease;
    will-change: transform;
}
.hover-specimen img {
    display: block;
    width: 250px;
    height: 250px;
    object-fit: cover;
    border-radius: 4px;
    box-shadow: 0 26px 55px -18px rgba(0, 0, 0, 0.45);
    filter: saturate(0.92);
}
.hover-specimen.on { opacity: 1; }

.row-thumb { display: none; }
@media (hover: none) {
    .hover-specimen { display: none; }
    .row-thumb {
        display: block;
        width: 100%;
        max-width: 340px;
        border-radius: 4px;
        margin: 4px 0 12px;
    }
}

.catalog-link { text-align: right; margin: 28px 0 0; }
.catalog-link a { background: none; color: var(--ink); }
.catalog-link a:hover { color: var(--accent-ink); }

/* ---------- notes (blog index) ---------- */

.year-group { margin: 0 0 56px; }
.year-mark {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 90;
    font-weight: 420;
    font-size: 2.3rem;
    letter-spacing: -0.01em;
    margin: 0 0 6px;
    color: var(--ink);
}
.year-mark::after { content: '.'; color: var(--seal); }

.note-list { list-style: none; padding: 0; margin: 0; }
.note-row {
    display: flex;
    align-items: baseline;
    gap: 22px;
    padding: 17px 0;
    border-bottom: 1px solid var(--line);
    color: var(--ink);
    background: none;
}
.note-row:hover { color: var(--ink); }
.note-date { color: var(--accent-ink); flex-shrink: 0; width: 56px; }
.note-title {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 32;
    font-weight: 480;
    font-size: 1.2rem;
    line-height: 1.3;
    flex: 1;                       /* the title gets all the room */
    transition: color 0.25s ease;
}
.note-row:hover .note-title { color: var(--accent-ink); font-style: italic; }
.note-rt { color: var(--ink-soft); font-size: 0.72rem; flex-shrink: 0; }
.notes-empty { color: var(--ink-soft); font-style: italic; }

/* ---------- article (blog post) ---------- */

.article {
    max-width: 700px;
    margin: 0 auto;
    padding: 56px 0 30px;
}
.back-link {
    display: inline-block;
    color: var(--ink-soft);
    background: none;
    margin-bottom: 44px;
    letter-spacing: 0.08em;
}
.back-link:hover { color: var(--accent-ink); }

.article-head { margin-bottom: 46px; }
.article-head h1 {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 110, 'WONK' 1;
    font-weight: 460;
    font-size: clamp(2.1rem, 5vw, 3.1rem);
    line-height: 1.12;
    letter-spacing: -0.015em;
    margin: 14px 0 18px;
}
.article-meta { color: var(--ink-soft); letter-spacing: 0.08em; }
.article-meta .sep { color: var(--line); margin: 0 7px; }
.article-meta .tag-link { color: var(--accent-ink); background: none; }

.article-body { counter-reset: sec; }
.article-body p { margin: 1.25em 0; }
.article-body > p:first-of-type::first-letter {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 3.3em;
    line-height: 0.82;
    float: left;
    padding: 0.06em 0.12em 0 0;
    color: var(--seal);
}
.article-body h2 {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 40;
    font-weight: 500;
    font-size: 1.5rem;
    letter-spacing: -0.01em;
    margin: 2.4em 0 0.8em;
    color: var(--ink);
}
.article-body h2::before {
    counter-increment: sec;
    content: '0' counter(sec);
    font-family: var(--font-mono);
    font-size: 0.72rem;
    font-weight: 400;
    color: var(--seal);
    letter-spacing: 0.1em;
    margin-right: 14px;
    vertical-align: 0.35em;
}
.article-body ul { padding-left: 1.3em; }
.article-body li { margin: 0.5em 0; }
.article-body li::marker { color: var(--seal); }
.article-body blockquote {
    margin: 1.8em 0;
    padding: 0.1em 0 0.1em 1.4em;
    border-left: 2px solid var(--seal);
    font-style: italic;
    font-size: 1.14rem;
    color: var(--ink-soft);
}
.article-body code {
    font-family: var(--font-mono);
    font-size: 0.84em;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 1px 6px;
}
.article-body pre {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 18px 20px;
    overflow-x: auto;
    line-height: 1.65;
    margin: 1.6em 0;
}
.article-body pre code {
    background: none;
    border: none;
    padding: 0;
    font-size: 0.8rem;
}
.article-body figure { margin: 2em 0; }
.article-body figcaption {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    color: var(--ink-soft);
    letter-spacing: 0.08em;
    margin-top: 10px;
}

/* article ends with the seal, like a signed painting */
.end-seal {
    display: flex;
    justify-content: center;
    margin: 70px 0 0;
}
.end-seal span {
    display: inline-flex;
    flex-direction: column;
    padding: 7px 8px;
    background: var(--seal);
    color: #f7f3ec;
    border-radius: 3px;
    font-family: 'Seal Kai', 'Kaiti SC', 'STKaiti', 'KaiTi', 'BiauKai', 'DFKai-SB', serif;
    font-size: 0.8rem;
    line-height: 1.2;
    transform: rotate(3deg);
}

@media (max-width: 760px) {
    .page-head { padding: 44px 0 8px; }
    .note-row { flex-wrap: wrap; gap: 8px 16px; }
    .note-title { flex-basis: 100%; order: 3; }
    .note-rt { margin-left: auto; }
}

/* ---------- reveal animation ---------- */

.reveal {
    opacity: 0;
    transform: translateY(22px);
    transition: opacity 0.8s var(--ease-out), transform 0.8s var(--ease-out);
    transition-delay: var(--d, 0s);
}
.reveal.visible {
    opacity: 1;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .reveal { opacity: 1; transform: none; transition: none; }
    .orbit-container, .cursor-blink { animation: none; }
}

/* ---------- responsive ---------- */

@media (max-width: 760px) {
    .site-header { padding: 14px 16px; gap: 10px; }
    .site-nav { display: none; }   /* replaced by the 俞 seal FAB on phones */
    .site-nav a { font-size: 0.72rem; letter-spacing: 0.03em; }
    .site-nav a::before { display: none; }
    .wordmark { font-size: 0.88rem; }

    main { padding: 0 18px; }
    .hero { padding: 8vh 0 9vh; }
    .hero-bio { font-size: 1.08rem; }
    .hero-links { flex-direction: column; gap: 12px; }

    .about-grid { grid-template-columns: 1fr; gap: 32px; }
    .about-portrait {
        order: -1;            /* photo leads the section on phones */
        max-width: 280px;
        margin: 0 auto;       /* centered */
        text-align: center;
    }
    .about-portrait figcaption { text-align: center; }

    .log-list li { flex-wrap: wrap; }
    .log-leader { display: none; }
    .log-entry { width: 100%; }

    /* tag on its own row (compact), then a fixed greek column so all titles line up */
    .work-item {
        display: grid;
        grid-template-columns: 28px 1fr;
        grid-template-areas: "tag tag" "no body";
        column-gap: 12px;
        row-gap: 12px;
        align-items: baseline;
    }
    .work-tag { grid-area: tag; justify-self: start; margin: 0; }
    .work-no { grid-area: no; width: auto; }
    .work-body { grid-area: body; }

    .contact-cta { font-size: 1.6rem; }
}

/* ============================================================
   BLOG SYSTEM — reading experience + index at scale
   (all generated by redesign/build.mjs from notes/*.md)
   ============================================================ */

/* ---------- reading progress bar ---------- */
.read-progress {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 2px;
    z-index: 200;
    pointer-events: none;
    background: transparent;
}
.read-progress span {
    display: block;
    height: 100%;
    width: 0;
    background: var(--seal);
    transform-origin: left;
    transition: width 0.1s linear;
}

/* ---------- table-of-contents rail ---------- */
.toc {
    margin: 0 0 40px;
    padding: 16px 20px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--surface);
}
.toc-title {
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 0.66rem;
    margin: 0 0 10px;
}
/* collapsible: the TOC is a <details>; the summary toggles it */
.toc summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    user-select: none;
    -webkit-user-select: none;
    transition: color 0.2s ease;
}
.toc summary::-webkit-details-marker { display: none; }
.toc summary:hover { color: var(--accent-ink); }
.toc summary::after {
    content: "";
    margin-left: 9px;
    width: 6px;
    height: 6px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: translateY(-2px) rotate(45deg);
    opacity: 0.6;
    transition: transform 0.2s var(--ease-out);
}
.toc:not([open]) summary::after { transform: translateY(0) rotate(-45deg); }
/* smooth expand/collapse of the list (Chromium/Safari; instant fallback elsewhere) */
.toc::details-content {
    height: 0;
    overflow: clip;
    opacity: 0;
    transition: height 0.3s var(--ease-out), opacity 0.24s ease, content-visibility 0.3s;
    transition-behavior: allow-discrete;
}
.toc[open]::details-content {
    height: auto;
    opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
    .toc::details-content { transition: none; }
}
.toc ol { list-style: none; margin: 0; padding: 0; }
.toc li { margin: 0; line-height: 1.4; }
.toc li + li { margin-top: 7px; }
.toc a {
    display: block;
    font-family: var(--font-body);      /* Newsreader — far easier to read than mono here */
    font-size: 0.92rem;
    font-weight: 350;
    letter-spacing: 0;
    line-height: 1.32;
    color: var(--ink-soft);
    padding: 2px 0;
    border-left: 2px solid transparent;
    transition: color 0.2s ease, border-color 0.2s ease;
}
.toc a:hover { color: var(--accent-ink); }
.toc li.toc-l3 { padding-left: 14px; }
.toc a.active { color: var(--accent-ink); }

/* on wide screens the rail floats to the left of the text column */
@media (min-width: 1240px) {
    .toc {
        position: fixed;
        top: 132px;
        left: 50%;
        margin-left: -600px;
        width: 208px;
        max-height: 74vh;
        overflow-y: auto;
        padding: 0 0 0 16px;
        border: none;
        border-radius: 0;
        background: none;
    }
    .toc a.active { border-left-color: var(--seal); padding-left: 10px; margin-left: -12px; }
    .toc li.toc-l3 a.active { padding-left: 24px; }
}
@media (min-width: 1240px) and (max-width: 1360px) {
    /* narrower gap on mid-wide screens so the rail never collides */
    .toc { margin-left: -540px; width: 168px; }
}

/* ---------- article body refinements ---------- */
.article-body :where(h2, h3) { scroll-margin-top: 92px; }
.article-body h3 {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 30;
    font-weight: 500;
    font-size: 1.18rem;
    margin: 1.8em 0 0.6em;
    color: var(--ink);
}
.article-body a:not(.tag-link) {
    color: var(--accent-ink);
    text-decoration: underline;
    text-decoration-color: var(--line);
    text-underline-offset: 3px;
    transition: text-decoration-color 0.2s ease;
}
.article-body a:not(.tag-link):hover { text-decoration-color: var(--accent-ink); }
.article-body strong { font-weight: 560; }
.article-body hr {
    border: none;
    border-top: 1px solid var(--line);
    margin: 2.6em 0;
}
.article-body img { max-width: 100%; height: auto; border-radius: 4px; display: block; }

/* display math scrolls instead of blowing out the layout on phones */
.katex-display { overflow-x: auto; overflow-y: hidden; padding: 6px 2px; }
.article-body :where(h2, h3) .katex { font-size: inherit; }

/* ---------- code blocks: language label + copy button ---------- */
.code-block {
    position: relative;
    margin: 1.6em 0;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--surface);
    overflow: hidden;
}
.code-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 7px 12px 7px 16px;
    border-bottom: 1px solid var(--line);
    font-family: var(--font-mono);
    font-size: 0.64rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-soft);
}
.code-copy {
    font-family: var(--font-mono);
    font-size: 0.64rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-soft);
    background: none;
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 3px 9px;
    cursor: pointer;
    transition: color 0.2s ease, border-color 0.2s ease;
}
.code-copy:hover { color: var(--accent-ink); border-color: var(--accent-ink); }
.code-copy.copied { color: var(--seal); border-color: var(--seal); }
.code-block pre {
    margin: 0;
    border: none;
    border-radius: 0;
    background: none;
}

/* ---------- syntax highlighting (highlight.js tokens, site palette) ---------- */
.hljs-comment, .hljs-quote { color: var(--syn-comment); font-style: italic; }
.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-doctag,
.hljs-name, .hljs-section, .hljs-meta .hljs-keyword { color: var(--syn-keyword); }
.hljs-string, .hljs-regexp, .hljs-addition, .hljs-symbol, .hljs-bullet,
.hljs-meta .hljs-string { color: var(--syn-string); }
.hljs-number { color: var(--syn-number); }
.hljs-title, .hljs-title.function_, .hljs-selector-id, .hljs-selector-class { color: var(--syn-func); }
.hljs-built_in, .hljs-type, .hljs-class .hljs-title, .hljs-title.class_,
.hljs-selector-pseudo { color: var(--syn-type); }
.hljs-attr, .hljs-attribute, .hljs-property, .hljs-variable,
.hljs-template-variable, .hljs-params { color: var(--syn-attr); }
.hljs-meta, .hljs-tag { color: var(--ink-soft); }
.hljs-deletion { color: var(--seal); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: 600; }

/* ---------- footnotes ---------- */
.footnotes {
    margin-top: 64px;
    padding-top: 22px;
    border-top: 1px solid var(--line);
    font-size: 0.94rem;
    color: var(--ink-soft);
}
.footnotes h2 {
    font-family: var(--font-mono) !important;
    font-variation-settings: normal;
    font-size: 0.7rem !important;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin: 0 0 12px !important;
}
.footnotes h2::before { content: none !important; }   /* no section number on "Notes" */
.footnotes ol { padding-left: 1.2em; }
.footnotes li { margin: 0.4em 0; }
.footnotes li::marker { color: var(--seal); }
.footnote-backref { margin-left: 6px; text-decoration: none; }

/* ---------- prev / next ---------- */
.post-nav {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin-top: 64px;
    padding-top: 32px;
    border-top: 1px solid var(--line);
}
.post-nav a {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 16px 18px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: none;
    transition: border-color 0.25s ease, background 0.25s ease;
}
.post-nav a:hover { border-color: var(--accent); background: var(--surface); }
.post-nav .pn-prev { text-align: right; }
.post-nav .pn-empty { border: none; }
.pn-dir { color: var(--ink-soft); font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; }
.pn-title {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 30;
    font-weight: 480;
    font-size: 1.05rem;
    line-height: 1.25;
    color: var(--ink);
}
.post-nav a:hover .pn-title { color: var(--accent-ink); }

/* ---------- index toolbar: search + tag filter (scales to many posts) ---------- */
.notes-toolbar { margin: 0 0 40px; }
.notes-search-wrap {
    display: flex;
    align-items: center;
    gap: 10px;
    border-bottom: 1px solid var(--line);
    padding: 8px 2px;
    transition: border-color 0.25s ease;
}
.notes-search-wrap:focus-within { border-color: var(--accent); }
.notes-search-wrap .s-icon { color: var(--ink-soft); flex-shrink: 0; }
.notes-search {
    flex: 1;
    border: none;
    background: none;
    outline: none;
    color: var(--ink);
    font-family: var(--font-mono);
    font-size: 0.86rem;
    letter-spacing: 0.01em;
    padding: 2px 0;
}
.notes-search::placeholder { color: var(--ink-soft); opacity: 0.7; }
.tag-filter {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}
.tag-chip {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
    background: none;
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 4px 12px;
    cursor: pointer;
    transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.tag-chip:hover { color: var(--accent-ink); border-color: var(--accent); }
.tag-chip.active {
    color: var(--paper);
    background: var(--accent-ink);
    border-color: var(--accent-ink);
}
.notes-count {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
    margin: 16px 0 0;
}
.note-num { color: var(--seal); flex-shrink: 0; width: 30px; opacity: 0.85; }
mark.hit { background: rgba(177, 58, 44, 0.16); color: inherit; border-radius: 2px; padding: 0 1px; }
html.dark mark.hit { background: rgba(224, 90, 72, 0.22); }

@media (max-width: 760px) {
    .post-nav { grid-template-columns: 1fr; }
    .post-nav .pn-prev { text-align: left; }
    .post-nav .pn-empty { display: none; }
    .note-num { display: none; }
}

/* ============================================================
   mobile radial nav — a 俞 seal that fans the nav out (phones only)
   ============================================================ */
.fab-nav { display: none; }

@media (max-width: 760px) {
    .fab-nav {
        display: block;
        position: fixed;
        right: 20px;
        bottom: 20px;
        z-index: 300;
        transition: opacity 0.25s ease, transform 0.3s var(--ease-out);
    }
    /* An open egg console (funthings / ministeve) spans nearly the full width
       on phones, so its send button lands right under the FAB. Retire the FAB
       while a console is open (both set html.conductor-on) — it fades back in
       when the console closes. The reel (html.reel-on) plays full-bleed and its
       own controls sit in the same corner, so retire the FAB there too. */
    html.conductor-on .fab-nav,
    html.reel-on .fab-nav {
        opacity: 0;
        transform: scale(0.85) translateY(6px);
        pointer-events: none;
    }
    .fab-toggle {
        position: relative;
        z-index: 2;
        width: 54px;
        height: 54px;
        padding: 0;
        border: none;
        border-radius: 50%;
        background: var(--seal);
        color: #f7f3ec;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow: 0 6px 18px rgba(0, 0, 0, 0.20), inset 0 2px 0 rgba(0, 0, 0, 0.14);
        transition: transform 0.4s var(--ease-out), box-shadow 0.3s ease;
        -webkit-tap-highlight-color: transparent;
    }
    .fab-toggle:active { transform: scale(0.94); }
    .fab-nav.open .fab-toggle { transform: rotate(-8deg) scale(0.95); }  /* pressed stamp */
    .fab-seal {
        font-family: 'Seal Kai', 'Kaiti SC', 'STKaiti', 'KaiTi', 'BiauKai', serif;
        font-size: 1.5rem;
        line-height: 1;
        /* splits the optical-centre difference between Kaiti SC and the
           LXGW subset — keeps both within ~1px of the circle's centre */
        transform: translate(-1px, -0.5px);
    }

    .fab-items {
        position: absolute;
        right: 27px;      /* the FAB's centre */
        bottom: 27px;
    }
    /* each label is a fan blade: anchored by its right-centre at the FAB's
       centre, rotated onto its spoke, then pushed out --r along it — so the
       text runs along the radius, reading inward toward the seal */
    .fab-item {
        --r: 84px;        /* centre → inner edge of the label */
        --angle: calc(92deg + var(--i) * (86deg / (var(--n) - 1)));
        position: absolute;
        right: 0;
        bottom: 0;
        transform-origin: 100% 50%;
        transform: translateY(50%) rotate(88deg) translateX(-16px) scale(0.4);
        opacity: 0;
        pointer-events: none;
        white-space: nowrap;
        font-family: var(--font-mono);
        font-size: 0.78rem;
        letter-spacing: 0.03em;
        color: var(--ink);
        background: var(--paper);
        border: 1px solid var(--line);
        border-radius: 999px;
        padding: 5px 12px;
        box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
        transition: transform 0.45s var(--ease-out), opacity 0.3s ease;
    }
    /* the rib — a hairline from just outside the seal to the label */
    .fab-item::before {
        content: '';
        position: absolute;
        top: 50%;
        right: calc(34px - var(--r));
        width: calc(var(--r) - 42px);
        height: 1px;
        background: var(--line);
    }
    .fab-item.here { color: var(--seal); border-color: var(--seal); }
    .fab-nav.open .fab-item {
        opacity: 1;
        pointer-events: auto;
        transform: translateY(50%) rotate(calc(180deg - var(--angle))) translateX(calc(-1 * var(--r))) scale(1);
        transition-delay: calc(var(--i) * 0.04s);
    }
    @media (prefers-reduced-motion: reduce) {
        .fab-toggle, .fab-item { transition: opacity 0.2s ease; }
        .fab-nav.open .fab-item { transition-delay: 0s; }
    }
}

/* ========================================================================
   REEL PLAYER — a slide "video" told by the fish school (see js/reel.js).
   The overlay is transparent; the school (raised over the clean-stage veil,
   #ocean z12) forms each slide's graphic, and this layer only draws the
   caption band + control bar on top. Matches the conductor's mono chrome.
   ======================================================================== */

.reel {
    position: fixed;
    inset: 0;
    z-index: 60;                 /* above the raised ocean (z12), below console (z70) */
    pointer-events: none;        /* the water shows through; only the bar is live */
    font-family: var(--font-mono);
}

/* caption band — lower-centre, above the control bar. Fraunces for the line,
   IBM Plex Mono for the small sub-label, on the light/dark paper palette. */
.reel-caption {
    position: absolute;
    left: 50%;
    bottom: calc(96px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    width: min(760px, calc(100vw - 40px));
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
}
.reel-caption-main {
    font-family: var(--font-display);
    font-optical-sizing: auto;
    font-weight: 480;
    font-size: clamp(1.15rem, 3.4vw, 1.7rem);
    line-height: 1.3;
    color: var(--ink);
    text-wrap: balance;
    text-shadow: 0 1px 18px color-mix(in srgb, var(--paper) 82%, transparent);
    transition: opacity 0.32s var(--ease-out), transform 0.32s var(--ease-out);
}
.reel-caption-sub {
    font-size: 0.72rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-soft);
    transition: opacity 0.32s var(--ease-out);
}
.reel-caption-sub.is-seal {
    font-family: 'Seal Kai', 'Kaiti SC', 'STKaiti', 'KaiTi', 'BiauKai', serif;
    letter-spacing: 0.24em;
    text-transform: none;
    font-size: 0.95rem;
    color: var(--seal);
}
/* a tappable keyword inside a reel caption (slide `eggLink`, e.g. "ministeve"
   on the closing slide) — fires the egg and hands the school over, same exit
   as clicking the red fish */
.reel-caption-egg {
    font: inherit;
    color: var(--accent-ink);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    pointer-events: auto;        /* the reel overlay is click-through; only this word is live */
    text-decoration: underline;
    text-decoration-style: dotted;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.16em;
}
.reel-caption-egg:hover { color: var(--seal); }

/* cross-fade: reel.js adds .is-fading to dip the caption before swapping text */
.reel.is-fading .reel-caption-main { opacity: 0; transform: translateY(4px); }
.reel.is-fading .reel-caption-sub { opacity: 0; }

/* control bar — pinned bottom-centre, the one live surface in the overlay */
.reel-bar {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: calc(100vw - 24px);
    padding: 8px 12px;
    background: color-mix(in srgb, var(--paper) 90%, transparent);
    border: 1px solid var(--line);
    border-radius: 999px;
    box-shadow: 0 10px 34px rgba(15, 12, 8, 0.18);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}

.reel-btn {
    background: none;
    border: none;
    color: var(--ink-soft);
    font-family: var(--font-mono);
    font-size: 1.05rem;
    line-height: 1;
    padding: 4px 6px;
    min-width: 26px;
    cursor: pointer;
    border-radius: 6px;
    transition: color 0.2s ease, background-color 0.2s ease;
}
.reel-btn:hover { color: var(--seal); }
.reel-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.reel-play { color: var(--accent); font-size: 0.95rem; }
.reel-close { font-size: 1.2rem; }

/* discrete, segmented slider — one notch per slide (NOT a time scrubber) */
.reel-track {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 8px 4px;
    cursor: pointer;
    touch-action: none;
}
.reel-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
.reel-notch {
    -webkit-appearance: none;
    appearance: none;
    border: none;
    padding: 0;
    width: 12px;
    height: 6px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--ink-soft) 34%, transparent);
    cursor: pointer;
    transition: background-color 0.22s ease, width 0.22s var(--ease-out), transform 0.22s var(--ease-out);
}
.reel-notch:hover { background: color-mix(in srgb, var(--seal) 55%, transparent); }
.reel-notch.is-seen { background: color-mix(in srgb, var(--accent) 55%, transparent); }
.reel-notch.is-on {
    width: 26px;
    background: var(--seal);
}
.reel-notch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.reel-count {
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
    white-space: nowrap;
    min-width: 44px;
    text-align: center;
}

/* the hero entry point — a subtle mono cue that opens the reel */
.reel-cue {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    margin-top: 6px;
    background: none;
    border: none;
    padding: 2px 0;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    letter-spacing: 0.02em;
    color: var(--ink-soft);
    cursor: pointer;
    border-bottom: 1px solid transparent;
    transition: color 0.2s ease, border-color 0.2s ease;
}
.reel-cue::before { content: '▶'; font-size: 0.66em; color: var(--seal); }
.reel-cue:hover { color: var(--seal); border-bottom-color: color-mix(in srgb, var(--seal) 40%, transparent); }
.reel-cue:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* small screens: let the bar breathe, hide the counter's slack */
@media (max-width: 560px) {
    .reel-bar { gap: 6px; padding: 7px 10px; }
    .reel-notch { width: 9px; }
    .reel-notch.is-on { width: 20px; }
    .reel-count { min-width: 38px; font-size: 0.68rem; }
    .reel-caption { bottom: calc(86px + env(safe-area-inset-bottom, 0px)); }
}

@media (prefers-reduced-motion: reduce) {
    .reel-caption-main, .reel-caption-sub { transition: opacity 0.2s ease; }
    .reel-caption-main { transform: none !important; }
    .reel-notch { transition: background-color 0.2s ease; }
}

/* ---------- mirror · the webcam fish-mirror ---------- */
/* An exclusive fish-formation egg: the webcam, redrawn as the school. Reuses the
   conductor's clean-stage veil; this is just its small control strip, centred at
   the bottom like the other consoles. */
.mirror-panel {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    z-index: 70;
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    max-width: calc(100vw - 28px);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 9px 14px;
    box-shadow: 0 10px 34px rgba(15, 12, 8, 0.18);
    font-family: var(--font-mono);
}
.mirror-panel[hidden] { display: none; }
.mirror-label {
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    color: var(--ink-soft);
}
.mirror-controls { display: flex; align-items: center; gap: 8px; }
.mirror-btn {
    font-family: var(--font-mono);
    font-size: 0.72rem;
    line-height: 1;
    padding: 5px 9px;
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 6px;
    color: var(--ink-soft);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease;
}
.mirror-btn:hover { color: var(--accent); border-color: var(--accent); }
.mirror-btn.is-on { color: var(--accent); border-color: var(--accent); }
.mirror-x { font-size: 1rem; padding: 2px 8px; }
.mirror-x:hover { color: var(--seal); border-color: var(--seal); }
.mirror-msg {
    font-size: 0.72rem;
    color: var(--ink-soft);
    flex-basis: 100%;
}

/* ---------- music · persistent music mini-player ---------- */
/* Bottom-LEFT so it never overlaps or gets torn down by the centred consoles
   (conductor / ministeve / mirror). Deliberately independent — it plays on. */
.music-player {
    position: fixed;
    left: calc(16px + env(safe-area-inset-left, 0px));
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    z-index: 60;
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: min(320px, calc(100vw - 32px));
    background: color-mix(in srgb, var(--paper) 92%, transparent);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 6px 12px 6px 8px;
    box-shadow: 0 8px 24px rgba(15, 12, 8, 0.16);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    font-family: var(--font-mono);
}
.music-player[hidden] { display: none; }
.music-btn {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    line-height: 1;
    padding: 5px 7px;
    min-width: 26px;
    background: transparent;
    border: none;
    border-radius: 50%;
    color: var(--ink-soft);
    cursor: pointer;
    transition: color 0.15s ease, background-color 0.15s ease;
}
.music-btn:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.music-x:hover { color: var(--seal); background: color-mix(in srgb, var(--seal) 12%, transparent); }
.music-title {
    font-size: 0.72rem;
    letter-spacing: 0.01em;
    color: var(--ink-soft);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 190px;
}
/* On phones (same 760px breakpoint where .site-nav → the 俞 FAB), the bottom-left
   corner gets crowded by the FAB (bottom-right) and the egg consoles (bottom-
   centre), so the player moves UP into the header's free centre — the gap the
   desktop nav used to fill, between the wordmark (left) and theme toggle (right).
   Fixed + top-centred + width-capped so it never reaches either. z-index above
   the sticky header (100) so it stays tappable; still coexists with every egg. */
@media (max-width: 760px) {
    .music-player {
        left: 50%;
        right: auto;
        bottom: auto;
        top: calc(9px + env(safe-area-inset-top, 0px));
        transform: translateX(-50%);
        z-index: 101;
        gap: 4px;
        max-width: calc(100vw - 156px);
        padding: 3px 8px 3px 5px;
        box-shadow: 0 4px 16px rgba(15, 12, 8, 0.14);
    }
    .music-player .music-btn { padding: 4px 5px; min-width: 22px; font-size: 0.74rem; }
    .music-player .music-title { max-width: 88px; }
}
