/* Aniintel.
   Two hues carry the whole thing: aizome azure for anything you can act on,
   sakura for anything the catalogue itself says — tags, languages, counts.
   Jade and amber stay reserved for status so they never argue with either.
   Dark is the ground this kind of site is usually read on, so it is designed
   first and light is the deliberate second, not an afterthought. */

/* No borders anywhere. This is the whole of that decision, in one place: the
   file below sets no edges, and this stops the browser's own from showing
   through where a rule used to say `border: 0` to suppress them — buttons,
   inputs, fieldsets and tables all carry one by default.

   What separation used to come from a line now comes from fill and space:
   surfaces sit on a darker page, fields sit sunk into their surface, and rows
   are objects with room around them rather than things divided by rules. */
*,
*::before,
*::after {
    border-style: none;
}

:root {
    color-scheme: light;

    --paper: #f4f6fb;
    --paper-deep: #e9edf6;
    --surface: #ffffff;
    --surface-raised: #ffffff;
    --sunk: #f0f3f9;
    /* How a title-tinted placeholder is washed: pale ground, deep ink. */
    --tint-wash: 45%;
    --tint-ground: 88%;
    --tint-ink-wash: 40%;
    --tint-ink: 30%;

    --ink: #131722;
    --ink-muted: #5b6478;
    --ink-faint: #8b93a6;
    --line: #dde2ed;
    --line-strong: #c6cddd;

    --accent: #3f63e8;
    --accent-strong: #2c4bc4;
    --accent-ink: #ffffff;
    --accent-tint: #e7ecfe;
    --accent-glow: rgba(63, 99, 232, 0.22);

    --sakura: #d6417c;
    --sakura-ink: #ffffff;
    --sakura-tint: #fdeaf2;
    --sakura-line: #f4c2d8;

    --success: #12654a;
    --success-tint: #e2f3ec;
    --success-line: #b2ddca;
    --warning: #8a4c07;
    --warning-tint: #fcefda;
    --warning-line: #ecca9c;
    --danger: #b3283f;
    --danger-tint: #fdeaee;
    --danger-line: #f2c2cc;

    --go: #12734c;
    --go-strong: #0d5c3c;
    --go-ink: #ffffff;
    /* Filled rather than white. With no edge to draw it, a white button on a
       white card is a sentence, not something you can press. Dark mode was
       already a step off its surface and needs no change. */
    --plain: #e9edf6;
    --plain-ink: #131722;
    --plain-hover: #dde2ed;

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

    --shadow-card: 0 1px 2px rgba(19, 23, 34, 0.05), 0 6px 20px rgba(19, 23, 34, 0.06);
    --shadow-lift: 0 2px 4px rgba(19, 23, 34, 0.06), 0 16px 36px rgba(19, 23, 34, 0.12);
    --shadow-raised: 0 12px 40px rgba(19, 23, 34, 0.18);
    --shadow-bar: 0 1px 0 var(--line);

    --font-ui: "Inter var", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
    :root {
        color-scheme: dark;

        --paper: #0b0e15;
        --paper-deep: #070910;
        --surface: #141926;
        --surface-raised: #1a2131;
        --sunk: #10151f;
    /* Dark ground, pale ink: the same hue, the other way up. */
    --tint-wash: 30%;
    --tint-ground: 22%;
    --tint-ink-wash: 50%;
    --tint-ink: 82%;

        --ink: #e9ecf5;
        --ink-muted: #9aa4bc;
        --ink-faint: #6d7793;
        --line: #242c3d;
        --line-strong: #333d54;

        --accent: #7b9bff;
        --accent-strong: #9db4ff;
        --accent-ink: #0b0e15;
        --accent-tint: #1b2440;
        --accent-glow: rgba(123, 155, 255, 0.28);

        --sakura: #ff85b3;
        --sakura-ink: #170c12;
        --sakura-tint: #2c1723;
        --sakura-line: #4a2337;

        --success: #6fd0a3;
        --success-tint: #12261d;
        --success-line: #27543d;
        --warning: #e6b169;
        --warning-tint: #2a2013;
        --warning-line: #4d3a1e;
        --danger: #ff8095;
        --danger-tint: #2c161c;
        --danger-line: #55232e;

        --go: #2fa877;
        --go-strong: #3dbd88;
        --go-ink: #04140c;
        --plain: #232c3e;
        --plain-ink: #e9ecf5;
        --plain-hover: #2c374d;

        --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 6px 20px rgba(0, 0, 0, 0.3);
        --shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.45), 0 16px 36px rgba(0, 0, 0, 0.5);
        --shadow-raised: 0 12px 40px rgba(0, 0, 0, 0.6);
    }
}

:root[data-theme="dark"] {
    color-scheme: dark;

    --paper: #0b0e15;
    --paper-deep: #070910;
    --surface: #141926;
    --surface-raised: #1a2131;
    --sunk: #10151f;
    /* Dark ground, pale ink: the same hue, the other way up. */
    --tint-wash: 30%;
    --tint-ground: 22%;
    --tint-ink-wash: 50%;
    --tint-ink: 82%;

    --ink: #e9ecf5;
    --ink-muted: #9aa4bc;
    --ink-faint: #6d7793;
    --line: #242c3d;
    --line-strong: #333d54;

    --accent: #7b9bff;
    --accent-strong: #9db4ff;
    --accent-ink: #0b0e15;
    --accent-tint: #1b2440;
    --accent-glow: rgba(123, 155, 255, 0.28);

    --sakura: #ff85b3;
    --sakura-ink: #170c12;
    --sakura-tint: #2c1723;
    --sakura-line: #4a2337;

    --success: #6fd0a3;
    --success-tint: #12261d;
    --success-line: #27543d;
    --warning: #e6b169;
    --warning-tint: #2a2013;
    --warning-line: #4d3a1e;
    --danger: #ff8095;
    --danger-tint: #2c161c;
    --danger-line: #55232e;

    --go: #2fa877;
    --go-strong: #3dbd88;
    --go-ink: #04140c;
    --plain: #232c3e;
    --plain-ink: #e9ecf5;
    --plain-hover: #2c374d;

    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 6px 20px rgba(0, 0, 0, 0.3);
    --shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.45), 0 16px 36px rgba(0, 0, 0, 0.5);
    --shadow-raised: 0 12px 40px rgba(0, 0, 0, 0.6);
}

:root[data-theme="light"] {
    color-scheme: light;

    --paper: #f4f6fb;
    --paper-deep: #e9edf6;
    --surface: #ffffff;
    --surface-raised: #ffffff;
    --sunk: #f0f3f9;
    /* How a title-tinted placeholder is washed: pale ground, deep ink. */
    --tint-wash: 45%;
    --tint-ground: 88%;
    --tint-ink-wash: 40%;
    --tint-ink: 30%;

    --ink: #131722;
    --ink-muted: #5b6478;
    --ink-faint: #8b93a6;
    --line: #dde2ed;
    --line-strong: #c6cddd;

    --accent: #3f63e8;
    --accent-strong: #2c4bc4;
    --accent-ink: #ffffff;
    --accent-tint: #e7ecfe;
    --accent-glow: rgba(63, 99, 232, 0.22);

    --sakura: #d6417c;
    --sakura-ink: #ffffff;
    --sakura-tint: #fdeaf2;
    --sakura-line: #f4c2d8;

    --success: #12654a;
    --success-tint: #e2f3ec;
    --success-line: #b2ddca;
    --warning: #8a4c07;
    --warning-tint: #fcefda;
    --warning-line: #ecca9c;
    --danger: #b3283f;
    --danger-tint: #fdeaee;
    --danger-line: #f2c2cc;

    --go: #12734c;
    --go-strong: #0d5c3c;
    --go-ink: #ffffff;
    /* Filled rather than white. With no edge to draw it, a white button on a
       white card is a sentence, not something you can press. Dark mode was
       already a step off its surface and needs no change. */
    --plain: #e9edf6;
    --plain-ink: #131722;
    --plain-hover: #dde2ed;

    --shadow-card: 0 1px 2px rgba(19, 23, 34, 0.05), 0 6px 20px rgba(19, 23, 34, 0.06);
    --shadow-lift: 0 2px 4px rgba(19, 23, 34, 0.06), 0 16px 36px rgba(19, 23, 34, 0.12);
    --shadow-raised: 0 12px 40px rgba(19, 23, 34, 0.18);
}

body {
    margin: 0;
    /* A faint wash of each hue at the top corners, so the page has a horizon
       rather than being a flat slab. */
    background:
        radial-gradient(70rem 30rem at 12% -12%, var(--accent-tint), transparent 70%),
        radial-gradient(50rem 24rem at 92% -8%, var(--sakura-tint), transparent 72%),
        var(--paper);
    background-attachment: fixed;
    color: var(--ink);
    font-family: var(--font-ui);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

/* An author rule that sets display beats the user agent's [hidden] rule no
   matter the specificity, so anything given a display — .avatar, .lang-picker —
   stayed on screen while marked hidden. Stated once here rather than remembered
   at each of them. */
[hidden] {
    display: none !important;
}

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

/* Out of the way until a keyboard finds it, then the first thing on the page.
   Sitting off the top rather than hidden outright: `display: none` and
   `visibility: hidden` both take an element out of the tab order, which would
   make a skip link nothing but markup. */
/* The two banner slots: one above the header, one under the nav.
   
   Their height is fixed rather than grown to fit, because a slot that sizes
   itself to its contents moves the whole page down the moment a picture
   arrives — and the thing that gets moved is whatever somebody was reading.
   728x90 is the size every banner is drawn at; below the fold of a phone it
   becomes 320x50, which is the other one. */
.ad {
    /* Three columns so the banner sits in the middle of the page rather than
       in the middle of what is left after the label — the label is on the
       left and the third column is the empty weight that balances it. */
    display: grid;
    /* minmax(0, auto), not auto: an `auto` track is sized to its content and
       refuses to shrink below it, so on a phone the middle column stayed
       wider than the screen and took the whole page sideways with it — four
       pixels of horizontal scroll on every page. */
    grid-template-columns: 1fr minmax(0, auto) 1fr;
    align-items: center;
    gap: 12px;
    height: 90px;
    padding: 0 clamp(14px, 4vw, 44px);
    background: var(--sunk);
}

/* Under the nav rather than under nothing: the header is translucent and
   sticky, so a slot directly beneath it scrolls up behind the blur. Sitting
   it on the page's own ground keeps the seam invisible. */
.ad--nav {
    background: color-mix(in srgb, var(--sunk) 60%, transparent);
}

/* Said quietly, and never left off. A paid placement that does not say it is
   one is the thing every rule about advertising exists to stop. */
.ad__label {
    justify-self: start;
    color: var(--ink-muted);
    font-family: var(--font-ui);
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.ad__link {
    grid-column: 2;
    display: flex;
    align-items: center;
    max-width: 100%;
    height: 90px;
}

.ad__image {
    max-width: 100%;
    height: auto;
    max-height: 90px;
    object-fit: contain;
}

/* Unsold space, carrying something of ours. Deliberately unlike an
   advertisement to look at: it is the site talking, not a buyer. */
.ad__house {
    grid-column: 2;
    display: flex;
    align-items: center;
    gap: 16px;
    max-width: min(728px, 100%);
}

/* ---------- The editor on a phone ---------- */

/* Same form, less air. Every gap here is comfortable on a desktop and adds
   up to a lot of scrolling on a 390px screen, where the field under the one
   you are filling in should be the next thing you see rather than the next
   thing you scroll to. Only the spacing changes — nothing is hidden, and
   nothing is smaller to read. */
@media (max-width: 560px) {
    .entry-native :where(fieldset) {
        margin: 0 0 12px;
        padding: 14px 14px;
    }

    .entry-native :where(fieldset fieldset) {
        margin: 10px 0 4px;
    }

    .entry-native :where(label:not([class])) {
        margin: 10px 0 4px;
    }

    .entry-native > button {
        margin: 3px 0;
    }
}

/* A call to action that has wrapped onto two lines has stopped looking like
   one. */
.ad__house .button {
    flex: none;
    white-space: nowrap;
}

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

.ad__title {
    font-family: var(--font-ui);
    font-size: 0.9rem;
    font-weight: 700;
}

.ad__body {
    color: var(--ink-muted);
    font-size: 0.8rem;
    line-height: 1.4;
}

/* A phone gets the short banner, and the house copy loses its second line
   rather than pushing the page off the screen. */
@media (max-width: 700px) {
    .ad,
    .ad__link {
        height: 50px;
    }

    .ad {
        grid-template-columns: auto minmax(0, 1fr);
        gap: 8px;
    }

    .ad__label {
        font-size: 0.52rem;
    }

    /* Our own notice gives up its label for the width, since nothing requires
       one; a paid placement keeps it whatever the screen, since everything
       does. */
    .ad--house {
        grid-template-columns: minmax(0, 1fr);
    }

    .ad--house .ad__label {
        display: none;
    }

    .ad__house,
    .ad__link {
        grid-column: 2;
    }

    /* The house variant is one column wide — its label is hidden just above
       — so placing its content in column 2 asked the grid for a column that
       was not declared. An implicit track is sized to its content and will
       not shrink, so the slot stood four pixels wider than a 390px screen
       and gave every page a sideways scroll, with the heading and its button
       wrapping onto two lines inside a slot 50px tall. */
    .ad--house .ad__house,
    .ad--house .ad__link {
        grid-column: 1;
    }

    .ad__image {
        max-height: 50px;
    }

    .ad__body {
        display: none;
    }

    /* One line, cut where it runs out. A title wrapping to three lines in a
       50px slot spills out of the slot and over the page under it. */
    .ad__title {
        overflow: hidden;
        font-size: 0.82rem;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .ad__house {
        width: 100%;
        gap: 10px;
    }
}

/* Nobody wants a banner on paper. */
@media print {
    .ad {
        display: none;
    }
}

.skip-link {
    position: fixed;
    top: 0;
    left: 12px;
    z-index: 100;
    padding: 10px 16px;
    border-radius: 0 0 10px 10px;
    background: var(--accent);
    color: #fff;
    font-family: var(--font-ui);
    font-size: 0.86rem;
    font-weight: 700;
    text-decoration: none;
    transform: translateY(-120%);
    transition: transform 0.12s ease;
}

.skip-link:focus {
    transform: translateY(0);
}

/* The page itself takes focus on a route change so the keyboard follows the
   reader. It is not something anybody chose to focus, so it draws nothing. */
#root:focus {
    outline: none;
}

:where(.app-header, .page, .dialog, .toast) :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
}

/* ---------- Work in progress tape ---------- */

/* Deliberately the same in both themes: the pattern works because it looks
   like the tape on a barrier, and a dark-mode warning stripe reads as
   decoration instead. */
.wip-banner {
    --hazard-yellow: #f5c518;
    --hazard-black: #16181d;

    display: flex;
    align-items: center;
    justify-content: center;
    /* A ribbon rather than a band: the warning is the words, and the stripe
       only has to be tall enough to carry them. */
    padding: 3px 12px;
    background: repeating-linear-gradient(
        -45deg,
        var(--hazard-yellow) 0 16px,
        var(--hazard-black) 16px 32px
    );
}

.wip-banner__text {
    padding: 2px 12px;
    border-radius: var(--pill);
    background: var(--hazard-black);
    color: var(--hazard-yellow);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-align: center;
    text-transform: uppercase;
}

/* ---------- Header ---------- */

.app-header {
    position: sticky;
    top: 0;
    z-index: 20;
    padding: 10px clamp(14px, 4vw, 44px) 0;
    /* Translucent so the page moves under it rather than behind a lid. */
    background: color-mix(in srgb, var(--surface) 82%, transparent);
    backdrop-filter: blur(14px) saturate(1.4);
}

/* Two layers: who you are above the line, where you can go below it. */
.app-header__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 10px;
}

/* The line between the layers. A filled element rather than a border, because
   nothing on this site draws borders. */
.app-header__rule {
    height: 1px;
    background: var(--line-strong);
}

.app-header__browse {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 6px 0;
}

/* The drawer handle exists only below the breakpoint. */
.nav-drawer-toggle {
    display: none;
}

.icon--menu,
.icon--exit {
    /* An svg with only a viewBox has no size of its own, and a flex row under
       pressure crushes it to nothing; flex: none keeps the drawn width. */
    flex: none;
    width: 20px;
    height: 20px;
    stroke-width: 2;
}

/* ---------- Search, in the browse row ---------- */

.nav-search {
    display: flex;
    flex: none;
    align-items: center;
    gap: 4px;
    margin-left: auto;
    padding: 3px 4px 3px 14px;
    border-radius: var(--pill);
    background: var(--sunk);
}

.nav-search:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.nav-search__input {
    width: clamp(7rem, 16vw, 13rem);
    padding: 4px 0;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 0.9rem;
}

.nav-search__input:focus {
    outline: none;
}

.nav-search__input::placeholder {
    color: var(--ink-faint);
}

.nav-search__go {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: none;
    color: var(--ink-muted);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.nav-search__go:hover {
    background: var(--accent-tint);
    color: var(--accent);
}

.icon--search {
    width: 17px;
    height: 17px;
}

@supports not (backdrop-filter: blur(1px)) {
    .app-header {
        background: var(--surface);
    }
}

.brand {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 10px;
    padding: 6px 12px 6px 8px;
    border-radius: var(--pill);
    background: transparent;
    color: var(--ink);
    font: inherit;
    cursor: pointer;
    transition: background 0.15s ease;
}

.brand:hover {
    background: var(--accent-tint);
}

.brand__name {
    font-size: 1.08rem;
    font-weight: 750;
    letter-spacing: -0.02em;
}

.icon {
    display: block;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
}

.icon--brand {
    width: 24px;
    height: 24px;
    color: var(--accent);
}

.icon--plus {
    width: 20px;
    height: 20px;
    stroke-width: 2.2;
}

.nav {
    display: flex;
    flex: none;
    align-items: center;
    gap: 8px;
}

.nav__add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: linear-gradient(140deg, var(--accent), var(--accent-strong));
    color: var(--accent-ink);
    cursor: pointer;
    box-shadow: 0 4px 14px var(--accent-glow);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.nav__add:hover {
    transform: translateY(-1px);
    box-shadow: 0 8px 22px var(--accent-glow);
}

.nav__add:active {
    transform: translateY(0);
}

.nav__add[aria-current="page"] {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.nav__account {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* ---------- Making something, from the + ---------- */

.nav__add-wrap {
    position: relative;
    display: inline-flex;
}

/* Hangs from the + rather than from the row, so it stays under the button
   whatever else is in the header. */
/* Hung from the + rather than centred under it. Centred, the menu reached far
   enough left to cover the browse tabs on the row below — and since those open
   on hover, choosing something from this menu left the pointer resting on a
   tab and flung its panel open underneath. */
.nav__add-menu {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    z-index: 40;
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: max-content;
    max-width: min(20rem, calc(100vw - 24px));
    padding: 8px;
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-raised);
}

.nav__add-menu[hidden] {
    display: none;
}

.nav__add-item {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background 0.12s ease;
}

.nav__add-item:hover {
    background: var(--accent-tint);
}

.nav__add-item:hover .nav__add-name {
    color: var(--accent);
}

.nav__add-name {
    font-weight: 700;
}

/* What each one is, so the three are told apart without opening them. */
.nav__add-hint {
    color: var(--ink-muted);
    font-size: 0.78rem;
}

/* The word on a wide screen; the doorway alone on a narrow one, where a word
   would cost more of the row than leaving deserves. Both live in the button
   and the stylesheet shows one at a time. */
.nav__logout {
    display: inline-flex;
    align-items: center;
}

.nav__logout .icon--exit {
    display: none;
}

@media (max-width: 760px) {
    .nav__logout {
        justify-content: center;
        width: 38px;
        height: 38px;
        padding: 0;
        border-radius: 50%;
    }

    .nav__logout .icon--exit {
        display: block;
    }

    .nav__logout-word {
        display: none;
    }
}

/* ---------- Mail, in the nav bar ---------- */

/* Quieter than the create button beside it: mail is something to notice when
   there is some, not something to be invited to do. */
.nav__mail {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border-radius: 50%;
    background: var(--surface);
    color: var(--ink-muted);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.nav__mail:hover,
.nav__mail[aria-current="page"],
.nav__notices:hover,
.nav__notices[aria-expanded="true"] {
    color: var(--accent);
}

/* The bell wears the same round button as mail — they sit side by side and
   are the same kind of thing: something addressed to you. */
.nav__notices {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border-radius: 50%;
    background: var(--surface);
    color: var(--ink-muted);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

.notices {
    position: relative;
    display: inline-flex;
}

/* Hung off the button rather than pushed into the page: what is new is read
   where it was noticed, and a route change to read one line would lose
   whatever was being looked at. */
.notices__panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 40;
    width: min(360px, calc(100vw - 24px));
    max-height: min(70vh, 520px);
    overflow-y: auto;
    padding: 10px;
    border-radius: var(--radius-lg);
    /* Raised by shadow rather than outlined. Nothing on this site draws a
       border — own-shelf.mjs walks the whole page and fails on one — and the
       + menu beside it is lifted the same way. */
    background: var(--surface-raised);
    box-shadow: var(--shadow-raised);
}

.notices__heading {
    margin: 2px 6px 8px;
    color: var(--ink-muted);
    font-size: 0.74rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.notices__list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.notices__row {
    display: block;
    width: 100%;
    padding: 8px 10px;
    border-radius: 10px;
    background: none;
    text-align: left;
    cursor: pointer;
}

.notices__row:hover {
    background: var(--sunk);
}

/* Unread is a mark rather than a colour wash: the list is read top to bottom
   and a wall of highlight says nothing about which one is new. */
.notices__row--new {
    box-shadow: inset 3px 0 0 var(--accent);
}

.notices__title {
    display: block;
    font-size: 0.86rem;
    font-weight: 650;
    line-height: 1.35;
}

.notices__said {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 2px;
    color: var(--ink-muted);
    font-size: 0.8rem;
    line-height: 1.45;
}

.notices__when {
    display: block;
    margin-top: 3px;
    color: var(--ink-muted);
    font-size: 0.72rem;
    font-weight: 600;
}

.notices__empty {
    margin: 6px;
    color: var(--ink-muted);
    font-size: 0.82rem;
    line-height: 1.55;
}

.icon--mail,
.icon--bell,
.icon--chat {
    width: 19px;
    height: 19px;
}

/* The count rides the corner of whatever it belongs to, so one glance at the
   header answers "is there anything for me" without reading any of it. */
.nav__badge,
.notices__badge,
.pm__badge {
    position: absolute;
    top: -3px;
    right: -3px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: var(--pill);
    background: var(--danger);
    color: #fff;
    font-size: 0.66rem;
    font-weight: 800;
    line-height: 17px;
    text-align: center;
}

.nav__user {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 4px 12px 4px 4px;
    border-radius: var(--pill);
    background: none;
    color: var(--ink-muted);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease;
}

.nav__user:hover {
    background: var(--plain-hover);
    color: var(--ink);
}

.nav__user[aria-current="page"] {
    color: var(--ink);
}

/* Name on top, role under it, so the badge reads as a label on the account
   rather than as another button in the row. */
.nav__user-identity {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    line-height: 1.2;
    text-align: left;
}

.nav__user-name {
    max-width: 14ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav__user-role {
    color: var(--sakura);
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

.avatar {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    object-fit: cover;
    background: linear-gradient(140deg, var(--accent), var(--sakura));
    color: #ffffff;
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 0.02em;
}

.avatar--large {
    width: 84px;
    height: 84px;
    font-size: 1.7rem;
}

/* On a phone the marks stand for the words: the brand icon for the wordmark,
   the avatar for the handle. The words are what pushed the end of the row —
   the log out button — off the edge. After the base rules above, so these win
   the cascade. */
@media (max-width: 760px) {
    .brand__name {
        display: none;
    }

    .nav__user {
        padding: 4px;
    }

    .nav__user-identity {
        display: none;
    }

    .app-header__top {
        gap: 8px;
    }

    .nav {
        gap: 6px;
    }
}

/* ---------- Browse menus ---------- */

.browse-nav {
    flex: 1 1 auto;
    display: flex;
    justify-content: flex-start;
    min-width: 0;
}

.browse-nav__list {
    display: flex;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.browse-nav__item {
    position: relative;
}

.browse-nav__top {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 9px 14px;
    border-radius: var(--pill);
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-weight: 650;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.browse-nav__top:hover,
.browse-nav__top[aria-expanded="true"] {
    background: var(--accent-tint);
    color: var(--accent);
}

.browse-nav__caret {
    font-size: 0.66em;
    line-height: 1;
    opacity: 0.65;
}

/* A sibling rather than part of the label, so reading the button's name never
   picks it up. */
.browse-nav__label {
    white-space: nowrap;
}

.browse-menu {
    position: absolute;
    top: calc(100% + 8px);
    left: 50%;
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: max-content;
    padding: 18px;
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-raised);
    transform: translateX(-50%);
}

.browse-menu[hidden] {
    display: none;
}

.browse-menu__columns {
    display: flex;
    gap: 30px;
    align-items: flex-start;
}

.browse-menu__column {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 9rem;
}

.browse-menu__heading {
    padding: 2px 0 8px;
    background: none;
    color: var(--sakura);
    font: inherit;
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.11em;
    text-align: left;
    text-transform: uppercase;
    cursor: pointer;
}

.browse-menu__heading:hover {
    color: var(--accent);
}

.browse-menu__list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.browse-menu__link {
    display: block;
    width: 100%;
    padding: 8px 12px;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--ink);
    font: inherit;
    font-weight: 550;
    text-align: left;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}

.browse-menu__link:hover {
    background: var(--accent-tint);
    color: var(--accent);
}

/* An item that explains itself needs room to: the create items carry a line
   of the visitor's own words under this site's word for the thing. */
.browse-menu__link--wide {
    max-width: 20rem;
    white-space: normal;
}

.browse-menu__hint {
    display: block;
    margin-top: 2px;
    color: var(--ink-faint);
    font-size: 0.76rem;
    font-weight: 450;
    line-height: 1.35;
}

.browse-menu__link:hover .browse-menu__hint {
    color: inherit;
}

.browse-menu__views {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding-top: 14px;
}

.browse-menu__view {
    padding: 6px 14px;
    border-radius: var(--pill);
    background: transparent;
    color: var(--ink-muted);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 650;
    white-space: nowrap;
    cursor: pointer;
    transition: all 0.15s ease;
}

.browse-menu__view:hover {
    background: var(--accent-tint);
    color: var(--accent);
}

.browse__crumb {
    margin: 0 0 6px;
    color: var(--ink-muted);
    font-size: 0.88rem;
}

/* On a narrow screen the tabs live behind three lines on the left, and the
   search box keeps the right of the row to itself. */
@media (max-width: 760px) {
    .app-header__browse {
        position: relative;
    }

    .nav-drawer-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 38px;
        height: 38px;
        border-radius: 50%;
        background: none;
        color: var(--ink);
        cursor: pointer;
        transition: background 0.15s ease;
    }

    .nav-drawer-toggle:hover,
    .nav-drawer-toggle[aria-expanded="true"] {
        background: var(--accent-tint);
        color: var(--accent);
    }

    .browse-nav {
        display: none;
    }

    /* The drawer: the same tabs, stacked, hanging from the row. */
    .app-header__browse--open .browse-nav {
        display: block;
        position: absolute;
        top: calc(100% + 4px);
        left: 0;
        right: 0;
        z-index: 40;
        max-height: min(70vh, 30rem);
        padding: 10px;
        border-radius: var(--radius-lg);
        background: var(--surface-raised);
        box-shadow: var(--shadow-raised);
        overflow-y: auto;
    }

    .browse-nav__list {
        flex-direction: column;
        align-items: stretch;
    }

    .browse-nav__item {
        position: static;
    }

    .browse-nav__top {
        width: 100%;
        justify-content: space-between;
    }

    /* Panels open in place, accordion-fashion, rather than floating: a drawer
       is already the floating thing. */
    .browse-menu {
        position: static;
        min-width: 0;
        margin: 2px 0 8px;
        padding: 12px 14px;
        background: var(--sunk);
        box-shadow: none;
        transform: none;
    }

    .browse-menu__columns {
        flex-wrap: wrap;
        gap: 14px 30px;
    }

    .nav-search {
        flex: 1 1 auto;
    }

    .nav-search__input {
        width: 100%;
        flex: 1 1 auto;
    }
}

/* ---------- Page shell and cards ---------- */

/* The entry page appends its fieldsets straight to the container rather than
   through a .page wrapper, so the shell lives here and every route gets it. */
#root {
    display: block;
    max-width: 70rem;
    margin: 0 auto;
    padding: clamp(20px, 4vw, 44px) clamp(14px, 4vw, 40px) 96px;
}

.page {
    display: flex;
    flex-direction: column;
    gap: 22px;
}

.page--narrow {
    max-width: 27rem;
    margin: clamp(8px, 4vh, 44px) auto 0;
}

.page__lead--invite {
    max-width: 44rem;
}

.page__intro {
    margin-bottom: 28px;
}

.page__title {
    margin: 0 0 8px;
    font-size: clamp(1.9rem, 4.5vw, 2.6rem);
    font-weight: 800;
    letter-spacing: -0.035em;
    line-height: 1.08;
    text-wrap: balance;
}

.page__title--compact {
    font-size: clamp(1.35rem, 3vw, 1.7rem);
    margin: 0;
}

.page__lead {
    max-width: 60ch;
    margin: 0;
    color: var(--ink-muted);
    font-size: 1.02rem;
}

/* Questions offered under a lead, for somebody who was asked what they think
   and is now looking at an empty box. Quieter than the lead: they are a way
   in, not the instruction. */
.page__prompts {
    max-width: 60ch;
    margin: 12px 0 0;
    padding-left: 20px;
    color: var(--ink-muted);
    font-size: 0.92rem;
    line-height: 1.7;
}

.card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 20px;
    padding: 24px;
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-card);
}

.card--wide {
    align-items: stretch;
}


.card__title {
    margin: 0;
    font-size: 1.12rem;
    font-weight: 700;
    letter-spacing: -0.015em;
}

.card__body {
    max-width: 62ch;
    margin: 0;
    color: var(--ink-muted);
}

.card__hint {
    margin: 0;
    color: var(--ink-faint);
    font-size: 0.88rem;
}

/* ---------- Buttons ---------- */

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 20px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
    font-weight: 650;
    line-height: 1.2;
    cursor: pointer;
    transition: transform 0.12s ease, box-shadow 0.15s ease, background 0.15s ease;
}

.button:active {
    transform: translateY(1px);
}

.button--primary {
    background: linear-gradient(140deg, var(--accent), var(--accent-strong));
    color: var(--accent-ink);
    box-shadow: 0 4px 14px var(--accent-glow);
}

.button--primary:hover {
    box-shadow: 0 8px 22px var(--accent-glow);
}

.button--quiet {
    background: transparent;
    color: var(--ink-muted);
}

.button--quiet:hover {
    background: var(--sunk);
    color: var(--ink);
}

.button--danger {
    background: var(--danger-tint);
    color: var(--danger);
}

.button--danger:hover {
    background: var(--danger);
    color: #ffffff;
}

/* Anything that destroys something says so in red, wherever it sits and
   whatever else it is — the Delete under a draft, the bin on a tracked card,
   the Remove beside a volume, the cross on a tag.

   A data attribute rather than a class, because the entry form's buttons are
   styled through :where(button:not([class])) and giving them a class would
   strip the styling they already have. This adds the colour and leaves
   everything else where it was.

   .button--danger is excluded: it is already the filled red pill, and its
   hover fills solid, which this would otherwise override with a paler wash. */
[data-destructive]:not(.button--danger) {
    color: var(--danger);
}

[data-destructive]:not(.button--danger):hover:not(:disabled),
[data-destructive]:not(.button--danger):focus-visible {
    background: var(--danger-tint);
    color: var(--danger);
}

.button--block {
    width: 100%;
}

.button--small {
    padding: 6px 14px;
    font-size: 0.84rem;
}

.button[disabled] {
    opacity: 0.55;
    cursor: not-allowed;
    transform: none;
}

.button--go {
    background: var(--go);
    color: var(--go-ink);
}

.button--go:hover {
    background: var(--go-strong);
}

.button--plain {
    background: var(--plain);
    color: var(--plain-ink);
}

.button--plain:hover {
    background: var(--plain-hover);
}

.button--admin {
    background: var(--sakura-tint);
    color: var(--sakura);
}

.button--admin:hover,
.button--admin[aria-current="page"] {
    background: var(--sakura);
    color: var(--sakura-ink);
}

.button--go[aria-current="page"],
.button--plain[aria-current="page"] {
    box-shadow: inset 0 -2px 0 currentColor;
}

.link-button {
    padding: 0;
    background: none;
    color: var(--accent);
    font: inherit;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}

.link-button:hover {
    color: var(--accent-strong);
}

/* Reporting and blocking: real actions, but rare ones, and a page where every
   post carries a red button reads as a page expecting trouble. Grey and small,
   until pointed at. */
.link-button--quiet {
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 600;
    text-decoration: none;
}

.link-button--quiet:hover {
    color: var(--accent);
    text-decoration: underline;
}

/* ---------- Forms ---------- */

.card--auth {
    align-items: stretch;
    width: 100%;
    padding: 30px;
}

.card--auth .page__title {
    font-size: 1.7rem;
}

.card--auth .page__lead {
    font-size: 0.95rem;
}

.form {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-top: 6px;
}

.form__notice {
    margin: 0;
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    background: var(--accent-tint);
    color: var(--accent-strong);
    font-size: 0.9rem;
}

.form__banner {
    margin: 0;
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    background: var(--danger-tint);
    color: var(--danger);
    font-size: 0.9rem;
}

.field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.field__label {
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.field__input {
    width: 100%;
    padding: 11px 14px;
    border-radius: var(--radius-sm);
    /* Sunk rather than surfaced. With no edge to draw, the only thing that can
       say "you can type here" is the fill being darker than what it sits on. */
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
    box-sizing: border-box;
    transition: background 0.15s ease, box-shadow 0.15s ease;
}

.field__input:focus-visible {
    outline: none;
    box-shadow: 0 0 0 4px var(--accent-glow);
}

.field__input[aria-invalid="true"] {
    background: var(--danger-tint);
}

.field__select {
    width: 100%;
}

.field__hint,
.field__error {
    margin: 0;
    font-size: 0.84rem;
}

.field__hint {
    color: var(--ink-faint);
}

.field__error {
    color: var(--danger);
    font-weight: 600;
}

.form__alternate {
    margin: 0;
    color: var(--ink-muted);
    font-size: 0.9rem;
    text-align: center;
}

.form__divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 0;
    color: var(--ink-faint);
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.form__divider::before,
.form__divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--line);
}

.passkey-action {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 4px;
}

.passkey-action__status {
    margin: 0;
    color: var(--danger);
    font-size: 0.86rem;
}

/* ---------- Dialog ---------- */

.dialog {
    max-width: 26rem;
    padding: 24px;
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    color: var(--ink);
    box-shadow: var(--shadow-raised);
}

.dialog::backdrop {
    background: rgba(6, 8, 14, 0.55);
    backdrop-filter: blur(3px);
}

.dialog__title {
    margin: 0 0 8px;
    font-size: 1.2rem;
    font-weight: 700;
}

.dialog__message {
    margin: 0 0 20px;
    color: var(--ink-muted);
}

.dialog__actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

/* ---------- Toasts ---------- */

/* The entry page's dock shares the bottom-right corner with the toasts and
   the messages button. `--entry-dock` is how tall the page measured it, set
   on the document by entryPage.js; it is read only while a dock is showing,
   so whatever value it leaves behind on the way out does no harm. */
body:has(.entry-dock:not([hidden])) {
    --entry-lift: var(--entry-dock, 0px);
}

.toast-region {
    position: fixed;
    right: clamp(12px, 3vw, 28px);
    bottom: calc(clamp(12px, 3vw, 28px) + var(--entry-lift, 0px));
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: 10px;
    align-items: flex-end;
    pointer-events: none;
}

.toast {
    max-width: 26rem;
    padding: 12px 18px;
    border-radius: var(--radius-md);
    background: var(--surface-raised);
    color: var(--ink);
    font-size: 0.92rem;
    font-weight: 550;
    box-shadow: var(--shadow-lift);
    animation: toast-in 0.22s ease;
}

.toast--success {
    background: var(--success-tint);
    color: var(--success);
}

.toast--warning {
    background: var(--warning-tint);
    color: var(--warning);
}

.toast--repeat {
    animation: toast-nudge 0.24s ease;
}

.toast--leaving {
    animation: toast-out 0.2s ease forwards;
}

@keyframes toast-in {
    from { opacity: 0; transform: translateY(10px) scale(0.98); }
    to { opacity: 1; transform: none; }
}

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

@keyframes toast-nudge {
    50% { transform: translateX(-4px); }
}

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

/* ---------- Entry page chrome ---------- */

.entry-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 20px;
}

.entry-header .page__title--compact {
    margin: 0;
}

/* The page's own buttons, docked to the bottom of the window. Sticky within
   #root: it rides the bottom edge while the page runs past the window, and
   settles into the flow — a card above the footer — once the end scrolls up.
   z-index 30 keeps it under the menus and hints that open off the form at 40,
   which have to stay readable when they open at the bottom of the window. */
.entry-dock {
    position: sticky;
    bottom: 12px;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 24px;
    padding: 12px 16px;
    border-radius: var(--radius-lg);
    /* Translucent like the header, so the form moves under it rather than
       stopping at a lid. */
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    backdrop-filter: blur(14px) saturate(1.4);
    box-shadow: var(--shadow-lift);
}

.entry-status {
    flex: 1 1 auto;
    margin: 0;
    color: var(--ink-muted);
    font-size: 0.86rem;
}

.entry-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    flex-wrap: wrap;
    margin-left: auto;
}

/* What can be done to the whole document — Delete, or Report — at the end of
   the form, closing the document the way "Delete this entry" closes each
   entry. Empty until the server has said who this is to. */
.entry-footer {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin: 16px 0 4px;
}

.entry-footer:empty {
    display: none;
}

/* In map view the bar stands down. The workbench runs taller than many
   windows, and a bar riding the bottom edge lay over the end of the rail —
   the note chips — and the canvas beneath it, where a press landed on the bar
   instead. The page is short in this view, so the bar sits in the flow under
   the map instead, a short scroll away. */
.entry-views-host[data-view="map"] .entry-dock {
    position: static;
}

/* The entry forms are deliberately left on user-agent styling, so this is
   spacing rather than decoration: enough room that the button reads as the end
   of the entry above it and not as another of its fields. */
.entry-remove {
    display: block;
    margin: 16px 0 4px;
}

/* ---------- Saved list ---------- */

.saved {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
}

.saved__row {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    /* Rows were told apart by a rule between them. Now they are told apart by
       being objects with space around them. */
    margin-bottom: 6px;
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    background: var(--sunk);
}

.saved__open {
    flex: 1;
    min-width: 12rem;
    padding: 0;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 1rem;
    font-weight: 650;
    text-align: left;
    cursor: pointer;
}

.saved__open:hover {
    color: var(--accent);
}

.saved__meta {
    color: var(--ink-faint);
    font-size: 0.85rem;
}

.saved__delete {
    flex: none;
    padding: 6px 14px;
    font-size: 0.84rem;
}

/* ---------- Standalone creators ---------- */

.standalone {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 18px;
}

.standalone__item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
}

.sound-link {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin: 8px 0;
    padding: 10px 14px;
    border-radius: var(--radius-md);
    background: var(--sunk);
}

.sound-link__title {
    font-family: var(--font-ui);
    font-size: 0.94rem;
    font-weight: 650;
    color: var(--accent);
}

/* The row reads across rather than down, so its label sits beside the box it
   names instead of stacking above it the way the rest of the form does. */
.entry-native .sound-link :where(label:not([class])) {
    margin: 0;
}

/* ---------- Verify banner ---------- */

.verify-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 20px;
    padding: 16px 20px;
    border-radius: var(--radius-md);
    background: var(--warning-tint);
}

.verify-banner__text {
    margin: 0;
    color: var(--warning);
    font-size: 0.92rem;
}

/* ---------- History ---------- */

.history-panel {
    margin-top: 24px;
    padding: 20px 24px;
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-card);
}

.history__title {
    margin: 0 0 4px;
    font-size: 1.05rem;
    font-weight: 700;
}

.history__status {
    margin: 0;
    color: var(--ink-faint);
    font-size: 0.86rem;
}

.history {
    display: flex;
    flex-direction: column;
    margin: 8px 0 0;
    padding: 0;
    list-style: none;
}

.history__row {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    padding: 12px 0;
}

.history__summary {
    flex: 1;
    min-width: 14rem;
    font-size: 0.94rem;
}

.history__meta {
    color: var(--ink-faint);
    font-size: 0.82rem;
}

.history__current {
    padding: 3px 12px;
    border-radius: var(--pill);
    background: var(--accent-tint);
    color: var(--accent);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.history__restore {
    padding: 6px 14px;
    font-size: 0.84rem;
}

/* ---------- Personal settings ---------- */

.settings__avatar {
    display: flex;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
}

.settings__avatar-controls {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.settings__status {
    margin: 0;
    flex-basis: 100%;
}

.settings__preview {
    margin: 0;
    padding: 8px 16px;
    border-radius: var(--pill);
    background: var(--sakura-tint);
    color: var(--sakura);
    font-size: 0.98rem;
    font-weight: 700;
    align-self: flex-start;
}

.settings__actions {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

.log__scroll {
    overflow-x: auto;
    margin: 8px 0;
}

.log {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
    white-space: nowrap;
}

.log th,
.log td {
    padding: 10px 16px 10px 0;
    text-align: left;
}

.log th {
    color: var(--ink-faint);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.log__row--alarming td:nth-child(2) {
    color: var(--danger);
    font-weight: 650;
}

/* ---------- Admin ---------- */

.figures {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(108px, 1fr));
    gap: 1px;
    overflow: hidden;
    margin: 4px 0 10px;
    border-radius: var(--radius-md);
    background: var(--line);
}

.figure {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 14px 16px;
    background: var(--surface);
}

.figure__value {
    font-size: 1.5rem;
    font-weight: 750;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    line-height: 1.1;
}

.figure__value--alarming {
    color: var(--danger);
}

.figure__label {
    color: var(--ink-faint);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.admin-search {
    max-width: 22rem;
    margin-bottom: 6px;
}

/* Making an account: the two boxes side by side with the button under them,
   and one under the other once the card is too narrow for both. */
.admin-invite {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 12px 16px;
}

.admin-invite__submit,
.admin-agent__submit {
    grid-column: 1 / -1;
    justify-self: start;
}

.admin-table__scroll {
    overflow-x: auto;
}

.admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

.admin-table th,
.admin-table td {
    padding: 12px 16px 12px 0;
    text-align: left;
    vertical-align: top;
    white-space: nowrap;
}

.admin-table th {
    color: var(--ink-faint);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.admin-row--locked .admin-user__handle {
    color: var(--danger);
}

.admin-user {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.admin-user__handle {
    font-weight: 650;
}

.admin-user__email {
    color: var(--ink-faint);
    font-size: 0.82rem;
}

.admin-state {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    min-height: 1px;
}

.admin-tag {
    padding: 3px 11px;
    border-radius: var(--pill);
    font-size: 0.72rem;
    font-weight: 700;
}

.admin-tag--admin {
    background: var(--sakura-tint);
    color: var(--sakura);
}

.admin-tag--locked {
    background: var(--danger-tint);
    color: var(--danger);
}

.admin-tag--pending {
    background: var(--warning-tint);
    color: var(--warning);
}

.admin-tag--quiet {
    background: var(--success-tint);
    color: var(--success);
}

.admin-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.admin-actions__self {
    color: var(--ink-faint);
    font-size: 0.82rem;
    font-style: italic;
}

/* ---------- Tag field ---------- */

/* One box that reads as a single field, with the tags inside it. Sakura rather
   than azure: a tag is something the catalogue says, not something you press. */
.tag-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 10px 0;
    max-width: 46rem;
}

.tag-field__label {
    color: var(--ink-muted);
    font-family: var(--font-ui);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.tag-field__box {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 7px;
    min-height: 3.4rem;
    padding: 9px;
    border-radius: var(--radius-md);
    background: var(--surface);
    transition: background 0.15s ease, box-shadow 0.15s ease;
}

.tag-field__box:focus-within {
    box-shadow: 0 0 0 4px var(--sakura-tint);
}

.tag-chip {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 4px 5px 4px 12px;
    border-radius: var(--pill);
    background: var(--sakura-tint);
}

.tag-chip__input {
    min-width: 4rem;
    background: none;
    color: var(--sakura);
    font-family: var(--font-ui);
    font-size: 0.86rem;
    font-weight: 600;
    outline: none;
}

.tag-chip__input::placeholder {
    color: var(--ink-faint);
    font-weight: 500;
}

.tag-chip__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border-radius: 50%;
    background: none;
    color: var(--sakura);
    font-family: var(--font-ui);
    font-size: 0.95rem;
    line-height: 1;
    opacity: 0.65;
    cursor: pointer;
}

.tag-chip__remove:hover {
    background: var(--sakura);
    color: var(--sakura-ink);
    opacity: 1;
}

.tag-field__add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border-radius: 50%;
    background: none;
    color: var(--sakura);
    font-family: var(--font-ui);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    transition: all 0.15s ease;
}

.tag-field__add:hover {
    background: var(--sakura-tint);
}

.tag-field__add:focus-visible,
.tag-chip__remove:focus-visible {
    outline: 2px solid var(--sakura);
    outline-offset: 2px;
}

/* ---------- Credit hints ---------- */

/* Appears under an OP or ED field when the catalogue already knows the song or
   the performer, offering the other half. Anchored to the body rather than the
   field, so a table cell's overflow cannot clip it. */
.credit-hint {
    position: absolute;
    z-index: 40;
    max-width: 22rem;
    padding: 8px;
    border-radius: var(--radius-md);
    background: var(--surface-raised);
    box-shadow: var(--shadow-raised);
}

.credit-hint__heading {
    margin: 0 0 6px;
    padding: 0 6px;
    color: var(--sakura);
    font-family: var(--font-ui);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.credit-hint__list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 0;
    padding: 0;
    max-height: 13rem;
    overflow-y: auto;
    list-style: none;
}

.credit-hint__option {
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    color: var(--ink);
    font-family: var(--font-ui);
    font-size: 0.9rem;
    cursor: pointer;
}

.credit-hint__option:hover,
.credit-hint__option--active {
    background: var(--sakura-tint);
    color: var(--sakura);
    font-weight: 600;
}

/* ---------- Language strip ---------- */

.lang-strip {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 4px 0 16px;
}

.lang-tabs {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.lang-tab {
    padding: 6px 15px;
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--ink-muted);
    font: inherit;
    font-size: 0.86rem;
    font-weight: 650;
    cursor: pointer;
    transition: all 0.15s ease;
}

.lang-tab:hover {
    color: var(--accent);
}

.lang-tab[aria-selected="true"] {
    background: linear-gradient(140deg, var(--accent), var(--accent-strong));
    color: var(--accent-ink);
}

.lang-add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border-radius: 50%;
    background: none;
    color: var(--accent);
    font: inherit;
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
}

.lang-add:hover:not([disabled]) {
    background: var(--accent-tint);
}

.lang-add[disabled] {
    opacity: 0.4;
    cursor: not-allowed;
}

.lang-remove {
    padding: 5px 12px;
    border-radius: var(--pill);
    background: var(--danger-tint);
    color: var(--danger);
    font: inherit;
    font-size: 0.8rem;
    font-weight: 650;
    cursor: pointer;
}

.lang-remove:hover {
    background: var(--danger);
    color: #ffffff;
}

.lang-picker {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 8px 12px;
    border-radius: var(--radius-md);
    background: var(--sunk);
}

.lang-picker__label {
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.lang-picker__select {
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
}

.lang-picker__confirm,
.lang-picker__cancel {
    padding: 7px 15px;
    border-radius: var(--pill);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 650;
    cursor: pointer;
}

.lang-picker__confirm {
    background: var(--accent);
    color: var(--accent-ink);
}

.lang-picker__cancel {
    background: transparent;
    color: var(--ink);
}

/* ---------- Image fields ---------- */

.image-field {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    margin: 12px 0;
}

.image-field__preview {
    max-width: 190px;
    max-height: 260px;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
}

.image-field__status {
    margin: 0;
    color: var(--ink-faint);
    font-size: 0.82rem;
}

/* ---------- Generated entry forms ---------- */

/* entry.js emits plain markup with no classes of its own, so it is styled by
   element here. Everything uses :where() so its specificity stays at zero,
   which keeps the [hidden] rule at the bottom — and every class above —
   winning without a single !important. */

/* Every fieldset in here is one the entry form generated; nothing else puts
   one on the page. This used to say `fieldset:not([class])`, which meant the
   moment anything added a class to one — folding it, or naming it after a
   jump — it lost its card and became a bare legend on the page background. */
.entry-native :where(fieldset) {
    /* Browsers give fieldsets min-width: min-content, which quietly forbids
       them from ever being narrower than their widest line — so the Episodes
       table forced every fieldset above it to 1365px and the whole page
       scrolled sideways on a tablet, even after the table itself learned to
       scroll. This is the one line that lets a fieldset actually shrink. */
    min-width: 0;
    /* The fold's own button hangs off the top corner. */
    position: relative;
    margin: 0 0 20px;
    padding: 22px 24px;
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-card);
}

.entry-native :where(fieldset fieldset) {
    margin: 16px 0 4px;
    background: var(--sunk);
    box-shadow: none;
}

.entry-native :where(legend:not([class])) {
    padding: 4px 14px;
    border-radius: var(--pill);
    background: linear-gradient(140deg, var(--accent), var(--accent-strong));
    color: var(--accent-ink);
    font-size: 0.76rem;
    font-weight: 750;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

/* Named after what was typed into it rather than after the slot it sits in,
   so it stops being a label and starts being a title. */
.entry-native [data-entry-named] > legend {
    max-width: min(100%, 640px);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.86rem;
    letter-spacing: 0;
    text-transform: none;
}

.entry-native :where(fieldset fieldset > legend:not([class])) {
    background: var(--sakura-tint);
    color: var(--sakura);
}

/* Labels above their control rather than jammed against it, which is what made
   the old forms read as a wall. */
.entry-native :where(label:not([class])) {
    display: block;
    margin: 14px 0 5px;
    color: var(--ink-muted);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

/* A checkbox or radio keeps its label beside it, where it belongs. */
.entry-native :where(input[type="checkbox"]:not([class]), input[type="radio"]:not([class])) + :where(label),
.entry-native :where(label:has(input[type="checkbox"]), label:has(input[type="radio"])) {
    display: inline-block;
    margin: 0 16px 0 6px;
    color: var(--ink);
    font-size: 0.95rem;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    vertical-align: middle;
}

/* Sunken against the white fieldset. The border used to draw these boxes;
   with nothing on this site drawing borders, the fill is the whole of what
   says "write here". */
.entry-native :where(input:not([class]), textarea:not([class]), select:not([class])) {
    padding: 10px 13px;
    border-radius: var(--radius-sm);
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
    max-width: 100%;
    transition: background 0.15s ease, box-shadow 0.15s ease;
}

/* And the polarity flips one level down: nested fieldsets are themselves
   sunken, so their boxes come back up to the surface to stay visible. */
.entry-native :where(fieldset fieldset) :where(input:not([class]), textarea:not([class]), select:not([class])) {
    background: var(--surface);
}

.entry-native :where(input:not([class]):focus, textarea:not([class]):focus, select:not([class]):focus) {
    outline: none;
    box-shadow: 0 0 0 4px var(--accent-glow);
}

.entry-native :where(input[type="text"]:not([class]), input[type="number"]:not([class]), input:not([type]):not([class])) {
    min-width: 16rem;
}

.entry-native :where(input[type="number"]:not([class])) {
    min-width: 8rem;
}

/* Inside a table, narrower: six columns of 16rem inputs is a table wider than
   most screens, and the episode list was pushing its Description column off
   the right edge of a tablet. The inputs grow with their cell when there is
   room; this only lowers the floor. */
.entry-native :where(td) :where(input[type="text"]:not([class]), input:not([type]):not([class])) {
    width: 8rem;
    min-width: 8rem;
}

.entry-native :where(td) :where(input[type="number"]:not([class])) {
    width: 5rem;
    min-width: 5rem;
}

.entry-native :where(td) :where(input[type="datetime-local"]:not([class])) {
    width: 11rem;
    min-width: 0;
}

.entry-native :where(td) :where(textarea:not([class])) {
    width: 11rem;
    min-height: 4.5rem;
}

.entry-native :where(textarea:not([class])) {
    width: min(100%, 44rem);
    min-height: 7rem;
    resize: vertical;
}

.entry-native :where(input[type="checkbox"]:not([class]), input[type="radio"]:not([class])) {
    width: 17px;
    height: 17px;
    min-width: 0;
    padding: 0;
    accent-color: var(--accent);
    vertical-align: middle;
}

.entry-native :where(input[type="file"]:not([class])) {
    padding: 8px;
    background: var(--sunk);
}

.entry-native :where(button:not([class])) {
    padding: 8px 16px;
    margin: 4px 6px 4px 0;
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-size: 0.86rem;
    font-weight: 650;
    cursor: pointer;
    transition: all 0.15s ease;
}

.entry-native :where(button:not([class]):hover) {
    background: var(--accent-tint);
    color: var(--accent);
}

.entry-native :where(table:not([class])) {
    /* Sized by what is in it, not stretched to the page. At full width a
       three-column chapter table put the number, the title and Remove in
       three different corners of the screen, with nothing but distance
       saying they belong to one row.

       Block display so a table that is genuinely wider than the screen —
       the episode list, on a tablet — scrolls inside its own box instead of
       pushing the whole page sideways. The columns still hug their content:
       the internal table sizes itself, and the block is only the window it
       sits in. */
    display: block;
    max-width: 100%;
    overflow-x: auto;
    margin: 10px 0;
    border-collapse: collapse;
    font-size: 0.92rem;
}

.entry-native :where(th:not([class]), td:not([class])) {
    padding: 9px 9px 9px 0;
    text-align: left;
    vertical-align: middle;
}

.entry-native :where(th:not([class])) {
    color: var(--ink-faint);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* entry.js separates rows with <br>, which now only adds slack between
   already-spaced blocks. */
.entry-native :where(br) {
    line-height: 0.6;
}

/* Higher specificity than everything above, which is all :where(), so hidden
   columns and collapsed pickers stay hidden. */
.entry-native [hidden] {
    display: none;
}

/* ---------- Ratings ---------- */

/* Seven steps: black, purple, red, orange, yellow, green, blue. Written once
   with light-dark() rather than repeated across the four theme blocks at the
   top of this file — the ramp is one idea, and thirty lines of duplication
   would be four places to get it wrong. Every one of those blocks sets
   color-scheme, which is what light-dark() reads. A true black would vanish
   against a dark page, so the low end lifts to a grey rather than disappearing. */
:root {
    --rating-black: light-dark(#1c1d24, #8b90a3);
    --rating-purple: light-dark(#7b3fd4, #b088f2);
    --rating-red: light-dark(#d92f36, #ff7378);
    --rating-orange: light-dark(#cf7016, #ffa348);
    --rating-yellow: light-dark(#a8880a, #f2cf3c);
    --rating-green: light-dark(#1f9d51, #4fd184);
    --rating-blue: light-dark(#2b6fd6, #6fa5ff);
}

[data-rating-colour="black"] { --rating: var(--rating-black); }
[data-rating-colour="purple"] { --rating: var(--rating-purple); }
[data-rating-colour="red"] { --rating: var(--rating-red); }
[data-rating-colour="orange"] { --rating: var(--rating-orange); }
[data-rating-colour="yellow"] { --rating: var(--rating-yellow); }
[data-rating-colour="green"] { --rating: var(--rating-green); }
[data-rating-colour="blue"] { --rating: var(--rating-blue); }

.score {
    display: inline-flex;
    align-items: baseline;
    gap: 7px;
    color: var(--rating);
    font-family: var(--font-ui);
    font-weight: 850;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.score--large {
    font-size: 2.1rem;
}

.score--empty {
    color: var(--ink-faint);
    font-size: 1.15rem;
    font-weight: 650;
}

.score__star {
    font-size: 0.86em;
}

/* Quieter than the number: it is the scale, not the answer. */
.score__outof {
    color: var(--ink-muted);
    font-size: 0.42em;
    font-weight: 650;
}

/* On a poster, where it has to hold its own against the picture behind it.
   Top right, opposite the form badge — the bottom belongs to the tags. */
.score--small {
    position: absolute;
    top: 8px;
    right: 8px;
    gap: 4px;
    padding: 3px 8px;
    border-radius: var(--pill);
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    backdrop-filter: blur(6px);
    font-size: 0.78rem;
    box-shadow: var(--shadow-card);
}

/* Each step wears its own colour before anything is chosen, so the scale reads
   as a ramp rather than seven identical boxes. */
.vote-scale {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 8px 0 6px;
}

.vote-scale__step {
    width: 2.7rem;
    height: 2.7rem;
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--rating) 11%, var(--surface));
    color: var(--rating);
    font-family: var(--font-ui);
    font-size: 1rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.12s ease, box-shadow 0.15s ease;
}

/* The step you chose is excluded rather than left to be overridden below:
   :not(:disabled) counts towards specificity, so a plain [aria-pressed] rule
   loses to this one and hovering your own choice washed it out to a pale tint
   with near-white text still on it. */
.vote-scale__step:hover:not(:disabled, [aria-pressed="true"]) {
    background: color-mix(in srgb, var(--rating) 22%, var(--surface));
    transform: translateY(-2px);
}

.vote-scale__step[aria-pressed="true"] {
    background: var(--rating);
    color: var(--paper);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--rating) 24%, transparent);
}

.vote-scale__step:disabled {
    cursor: progress;
    opacity: 0.7;
}

/* ---------- Posters ---------- */

.poster {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Two by three, the shape a cover is printed at. */
    aspect-ratio: 2 / 3;
    width: 100%;
    overflow: hidden;
    border-radius: var(--radius-lg);
    background: var(--sunk);
    box-shadow: var(--shadow-card);
}

.poster__image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.poster__empty {
    padding: 0 12px;
    color: var(--ink-faint);
    font-size: 0.78rem;
    text-align: center;
}

/* A cover that has no picture yet: a panel tinted from the title, carrying
   the title. The hue is the card's own (`--tint`, set by tint.js); how light
   and how strong it is are the theme's, so the same series is the same
   colour in both and legible in both. */
.poster__empty--tinted {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding: 14px 12px;
    overflow: hidden;
    background: hsl(var(--tint) var(--tint-wash) var(--tint-ground));
    color: hsl(var(--tint) var(--tint-ink-wash) var(--tint-ink));
    font-family: var(--font-ui);
    font-size: 0.92rem;
    font-weight: 700;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

/* The top left of the picture answers "what am I looking at": the form of
   the thing, and where the series stands — stacked, form first. */
.poster__badges {
    position: absolute;
    top: 8px;
    left: 8px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    max-width: calc(100% - 16px);
}

.poster__form,
.poster__original,
.poster__status {
    max-width: 100%;
    overflow: hidden;
    padding: 3px 8px;
    border-radius: var(--pill);
    color: #fff;
    font-size: 0.64rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-overflow: ellipsis;
    text-transform: uppercase;
    white-space: nowrap;
    backdrop-filter: blur(4px);
}

/* Deliberately the darkest thing on the card. */
.poster__form {
    background: rgb(15 19 31 / 78%);
}

/* Somebody's own work, said in the site's own colour rather than the form
   badge's grey: it is the one thing on a 同人本 card a reader looks for. */
.poster__original {
    background: var(--accent);
    color: var(--accent-ink);
}

/* The present tense wears the present's colour; the rest stand back. */
.poster__status--airing {
    background: var(--accent);
    color: var(--accent-ink);
}

.poster__status--soon {
    background: rgb(15 19 31 / 55%);
}

.poster__status--finished {
    background: rgb(15 19 31 / 55%);
    color: rgb(255 255 255 / 75%);
}

/* The largest tag shares, along the bottom edge of the picture itself. A
   scrim underneath keeps them readable on a light cover; always dark, since
   they sit on an image rather than on the page. */
.poster__tags {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 26px 8px 8px;
    background: linear-gradient(transparent, rgb(10 13 22 / 82%));
    pointer-events: none;
}

.poster__tag {
    max-width: 100%;
    overflow: hidden;
    padding: 1px 7px;
    border-radius: var(--pill);
    background: rgb(255 255 255 / 16%);
    color: #fff;
    font-size: 0.66rem;
    font-weight: 650;
    line-height: 1.5;
    text-overflow: ellipsis;
    white-space: nowrap;
    backdrop-filter: blur(3px);
}

/* ---------- Recently added ---------- */

.recent {
    /* One row, however many are on it, scrolled sideways. A shelf is a shelf:
       it runs off the edge and you push along it, rather than wrapping into a
       block that pushes everything below it down the page. */
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 9.5rem;
    gap: 18px;
    margin: 4px 0 0;
    padding: 0;
    list-style: none;
    overflow-x: auto;
    /* Lands on a whole card rather than halfway through one, which is what
       made the clipped edge look like a mistake rather than a shelf. */
    scroll-snap-type: x mandatory;
    /* The native bar is hidden and drawn again below it instead. See
       shelfBar.js: the one the browser gives us is invisible until you are
       already scrolling on half the machines that will see this page. */
    scrollbar-width: none;
}

.recent::-webkit-scrollbar {
    display: none;
}

.recent__card {
    scroll-snap-align: start;
}

/* A group of shelves that arrives as data. The wrapper is invisible to
   layout, so its sections space themselves like everything else on the page. */
.page__cluster {
    display: contents;
}

/* ---------- Tracking ---------- */

/* One drop list rather than a row of eight chips: the options were crowding
   out the shelves they exist to feed. */
.track-follow {
    margin: 2px 0 6px;
}

.track-follow__select {
    max-width: 100%;
    padding: 8px 14px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 650;
    cursor: pointer;
}

.track-follow__select:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.track-follow__select:disabled {
    cursor: progress;
    opacity: 0.7;
}

.track-shelves__title {
    margin: 18px 0 0;
    color: var(--ink-muted);
    font-family: var(--font-ui);
    font-size: 0.8rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* Where you stand in a tracked thing, under its card: on which unit against
   the newest out, and the button that completes the next one. */
.track-progress {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    margin-top: 8px;
}

.track-progress__standing {
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 600;
}

/* Carrying on and giving up, side by side. */
.track-progress__actions {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Quieter than the button it sits beside until you reach for it: dropping
   something is a thing you do once, not the thing this card is for. */
/* Shape only. The colour comes from [data-destructive] with everything else
   that throws something away, which is the point of marking them all. */
.track-progress__drop {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: none;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.icon--trash {
    flex: none;
    width: 16px;
    height: 16px;
    stroke-width: 1.9;
}

.track-progress__done {
    padding: 4px 12px;
    border-radius: var(--pill);
    background: var(--accent-tint);
    color: var(--accent);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.track-progress__done:hover:not(:disabled) {
    background: var(--accent);
    color: var(--accent-ink);
}

.track-progress__done:disabled {
    cursor: progress;
    opacity: 0.7;
}

.track-progress__caught-up {
    padding: 4px 12px;
    border-radius: var(--pill);
    background: var(--success-tint);
    color: var(--success);
    font-size: 0.78rem;
    font-weight: 700;
}

/* ---------- Discussion, on everything inside a catalogue ---------- */

.info__discussion {
    margin-top: 26px;
}

/* A drop list rather than a wall of headings: a long manga is thousands of
   chapters, and a page that drew them all would be unreadable before it was
   slow. */
.discuss__chooser {
    margin: 10px 0 18px;
}

.discuss__select {
    max-width: 100%;
    min-width: min(22rem, 100%);
    padding: 9px 14px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
    font-weight: 650;
    cursor: pointer;
}

.discuss__select:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.discuss__title {
    margin: 0 0 6px;
    font-family: var(--font-ui);
    font-size: 1.05rem;
    font-weight: 750;
}

.discuss__rating {
    margin-bottom: 14px;
}

.discuss__score {
    margin: 0 0 4px;
    color: var(--ink-muted);
    font-size: 0.9rem;
    font-weight: 650;
}

.discuss__comments {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 14px 0 0;
    padding: 0;
    list-style: none;
}

.discuss__comment {
    position: relative;
    padding: 12px 14px;
    border-radius: var(--radius-md);
    background: var(--sunk);
}

/* The same arrows the forum uses, laid along the row rather than stacked.
   On a post the stack sits in a column beside the text and has the height to
   fill; under a comment it is a footer, and a two-line tower of arrows there
   would be taller than most of the comments it sits under. */
.discuss__comment .forum-vote {
    flex-direction: row;
    align-items: center;
    gap: 6px;
    width: auto;
    margin-top: 8px;
}

.discuss__by {
    margin: 0 0 4px;
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 650;
}

/* Somebody's own line breaks are part of what they said, but a wall of
   pasted text still has to wrap rather than push the page sideways. */
.discuss__text {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.discuss__delete {
    position: absolute;
    top: 8px;
    right: 8px;
    padding: 2px 10px;
    font-size: 0.76rem;
}

.discuss__composer {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    margin-top: 16px;
}

.discuss__field {
    width: min(100%, 44rem);
    padding: 10px 13px;
    border-radius: var(--radius-sm);
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
    resize: vertical;
}

.discuss__field:focus {
    outline: none;
    box-shadow: 0 0 0 4px var(--accent-glow);
}

/* ---------- A fan animation's player ---------- */

/* The maker's upload, played through its site's own frame. A dark box the
   shape of the film until the reader presses play, so the page does not open
   somebody else's player on every visit; then the frame fills the box. */
.info__video {
    margin-top: 20px;
}

.info__video-frame {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 9;
    max-width: 100%;
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--ink);
}

.info__video-frame iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* The video's own picture behind the button, dimmed so the button reads. */
.info__video-picture {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: brightness(0.55);
}

.info__video-play {
    position: relative;
}

/* What the site says it is: the title, the maker, the length. */
.info__video-caption {
    margin: 8px 4px 0;
    color: var(--ink-muted);
    font-size: 0.86rem;
}

.info__video-caption strong {
    color: var(--ink);
    font-weight: 650;
}

/* ---------- Where to watch it, where to read it ---------- */

.where-block {
    margin: 8px 0 0;
    padding: 9px 14px 11px;
    border-radius: var(--radius-md);
    background: var(--sunk);
}

.where__heading {
    margin: 0 0 6px;
    color: var(--ink-muted);
    font-size: 0.76rem;
    font-weight: 750;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* Who this list is being read by, and its picker: the question is asked
   where the answer is wanted rather than only down in the footer. */
.where__standing {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0 0 8px;
    font-size: 0.82rem;
    color: var(--ink-muted);
}

.where--where,
.where-you-are--where {
    padding: 3px 6px;
    font-size: 0.78rem;
}

/* A row the reader can actually use, called out rather than filtered to:
   what a service does not carry where you are is still a fact about the
   work. */
.where__row[data-reaches="true"] {
    background: var(--accent-tint);
}

/* The reader's own country's rating, called out among the rest. */
.entry-facts__name[data-here="true"],
.entry-facts__value[data-here="true"] {
    color: var(--ink);
    font-weight: 750;
}

.where__here {
    padding: 1px 7px;
    border-radius: var(--pill);
    background: var(--accent);
    color: var(--on-accent, #fff);
    font-size: 0.68rem;
    font-weight: 750;
    white-space: nowrap;
}

.where {
    display: flex;
    flex-direction: column;
    gap: 5px;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Name first and hard to miss, the rest trailing after it: somebody scanning
   this is looking for a service they have, then checking the small print. */
.where__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
}

.where__service {
    color: var(--ink);
    font-size: 0.88rem;
    font-weight: 700;
    text-decoration: none;
}

a.where__service {
    color: var(--accent);
}

a.where__service:hover,
a.where__service:focus-visible {
    text-decoration: underline;
}

.where__detail {
    color: var(--ink-faint);
    font-size: 0.79rem;
}

/* In the editor: one row of the availability list. Wrapped rather than a
   table, because seven fields across is a horizontal scrollbar on anything
   narrower than a desk. */
.where-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    margin-bottom: 10px;
    /* Each row on its own ground. Seven controls wrapping over three lines
       run into the next row otherwise, and it stops being obvious which
       format belongs to which service. */
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    background: var(--surface);
    max-width: 100%;
}

/* Capped so three of them fit a line and wrap to two when they do not. Left to
   size themselves, the service list alone is wide enough to push the format
   off the edge of the page. */
.where-row > select,
.where-row > input {
    min-width: 0;
    max-width: min(100%, 15rem);
}

/* Two fields and a cross, so it stays on one line rather than taking the
   where-row's whole block treatment. */
.age-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    margin-bottom: 8px;
}

.age-row > select,
.age-row > input {
    min-width: 0;
    max-width: min(100%, 15rem);
}

.field-hint {
    margin: 0 0 8px;
    color: var(--ink-muted);
    font-size: 0.82rem;
}

/* Several languages, not one. The chosen ones sit as chips beside the drop
   list that adds the next, so a row can say a service carries twelve subtitle
   tracks without twelve controls.

   Each one takes a whole line of the row it sits in: nine chips beside five
   other controls is a row wider than the page, and everything after it ends up
   off the edge. */
.where-row > .lang-choice__label,
.where-row > .lang-choice {
    flex: 1 1 100%;
    min-width: 0;
}

.lang-choice {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 5px;
}

.lang-chip {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 3px 5px 3px 10px;
    border-radius: var(--pill);
    background: var(--accent-tint);
    font-size: 0.8rem;
    font-weight: 650;
}

.lang-chip__remove {
    padding: 0 5px;
    background: none;
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
}

.lang-choice__add {
    padding: 6px 10px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink-muted);
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
}

/* The old regions, kept as shortcuts: one press ticks every country in one.
   Their own line under the chips, because there are fifteen of them and they
   are the least important thing on the row. */
.country-shortcuts {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    flex: 1 1 100%;
    margin-top: 2px;
}

.country-shortcuts__one {
    padding: 3px 8px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink-muted);
    font-size: 0.72rem;
}

/* ---------- Where everybody stands with an entry ---------- */

.standing {
    margin: 8px 0 2px;
    padding: 10px 14px 12px;
    border-radius: var(--radius-md);
    background: var(--sunk);
}

.standing__heading {
    margin: 0 0 7px;
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 750;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

/* Four across where there is room, two by two on a phone, and never a row of
   four slivers too narrow to read the numbers in. */
.standing__answers {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
    gap: 6px;
}

.standing__answer {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 7px 11px;
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.standing__answer:hover:not(:disabled) {
    background: var(--accent-tint);
}

.standing__answer:disabled {
    cursor: default;
    opacity: 0.6;
}

/* The one you gave, in the colour that answer is drawn in everywhere else on
   the bar. */
.standing__answer[aria-pressed="true"] {
    background: var(--accent-tint);
    box-shadow: inset 3px 0 0 0 var(--standing-colour);
}

.standing__name {
    font-size: 0.84rem;
    font-weight: 700;
}

.standing__count {
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 650;
}

/* One colour per answer, shared by the button that gives it and the slice of
   the strip that counts it. */
[data-standing="planned"] { --standing-colour: #6c7bd8; }
[data-standing="active"]  { --standing-colour: #2f9e6b; }
[data-standing="current"] { --standing-colour: #d99b25; }
[data-standing="dropped"] { --standing-colour: var(--danger); }

/* The same numbers as a shape. An empty rail when nobody has answered, rather
   than four slivers of nothing. */
.standing__strip {
    display: flex;
    overflow: hidden;
    height: 7px;
    margin-top: 9px;
    border-radius: var(--pill);
    background: var(--surface);
}

.standing__part {
    background: var(--standing-colour);
}

.standing__total {
    margin: 7px 0 0;
    color: var(--ink-muted);
    font-size: 0.76rem;
    font-weight: 650;
}

/* ---------- The note only you can read ---------- */

/* Folded shut by default. A private thing should not be sitting open on a
   public page, and forty chapters of open notes would bury the conversation
   under a scratchpad. */
.note {
    margin: 14px 0 4px;
    padding: 10px 14px;
    border-radius: var(--radius-md);
    background: var(--sunk);
}

.note__summary {
    color: var(--ink-muted);
    font-size: 0.85rem;
    font-weight: 700;
    cursor: pointer;
    list-style: none;
}

.note__summary::-webkit-details-marker {
    display: none;
}

/* Its own marker, so it reads the same in every browser and turns when the
   note opens. */
.note__summary::before {
    content: '▸';
    display: inline-block;
    margin-right: 7px;
    transition: transform 0.15s ease;
}

.note[open] .note__summary::before {
    transform: rotate(90deg);
}

.note__summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* Sunk against sunk would vanish, so the field takes the card's own colour
   back. */
.note__field {
    width: min(100%, 44rem);
    margin-top: 10px;
    padding: 10px 13px;
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    resize: vertical;
}

.note__field:focus {
    outline: none;
    box-shadow: 0 0 0 4px var(--accent-glow);
}

.note__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}

.note__state {
    margin: 8px 0 0;
    font-size: 0.8rem;
}

/* ---------- The airing calendar ---------- */

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

.cal__day {
    margin: 0 0 26px;
}

.cal__date {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 10px;
    color: var(--ink-muted);
    font-family: var(--font-ui);
    font-size: 0.86rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    /* Landing here should not tuck the heading under the sticky header. */
    scroll-margin-top: 130px;
}

/* The "you are here" of the whole page. */
.cal__now {
    padding: 3px 10px;
    border-radius: var(--pill);
    background: var(--accent);
    color: var(--accent-ink);
    font-size: 0.68rem;
    letter-spacing: 0.08em;
}

.cal__list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.cal__row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 8px 12px;
    border-radius: var(--radius-md);
    background: var(--surface);
}

/* Aired and gone reads quieter than still to come. */
.cal__row[data-state="aired"] {
    opacity: 0.62;
}

.cal__time {
    flex: none;
    width: 3.2rem;
    color: var(--ink-muted);
    font-family: var(--font-ui);
    font-weight: 750;
    font-variant-numeric: tabular-nums;
}

.cal__open {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: 12px;
    min-width: 0;
    padding: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.cal__open:hover .cal__title {
    color: var(--accent);
}

.cal__thumb {
    flex: none;
    width: 30px;
    height: 45px;
    border-radius: var(--radius-sm);
    object-fit: cover;
    background: var(--sunk);
}

/* No cover: the series' tint and its first letter, too small for more. */
.cal__thumb--empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: hsl(var(--tint) var(--tint-wash) var(--tint-ground));
    color: hsl(var(--tint) var(--tint-ink-wash) var(--tint-ink));
    font-family: var(--font-ui);
    font-size: 0.9rem;
    font-weight: 700;
}

.cal__title {
    overflow: hidden;
    font-weight: 650;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cal__episode {
    flex: none;
    margin-left: auto;
    color: var(--ink-muted);
    font-size: 0.86rem;
}

.cal__quiet {
    margin: 0;
    padding: 8px 12px;
    color: var(--ink-faint);
    font-size: 0.9rem;
}

/* The loading edges. Empty while there is more to come — the sentinel the
   scroll watcher trips — and a quiet full stop once there is not. */
.cal__edge {
    min-height: 1px;
    margin: 0;
    color: var(--ink-faint);
    font-size: 0.85rem;
    text-align: center;
}

/* ---------- Search results ---------- */

/* Results wrap rather than scroll: a search is read top to bottom until the
   answer appears, not pushed along sideways. Fixed tracks, so two results
   are two cards and not two half-page posters. */
.results {
    display: grid;
    grid-template-columns: repeat(auto-fill, 9.5rem);
    gap: 18px;
    margin: 4px 0 0;
    padding: 0;
    list-style: none;
}

/* The bar we draw ourselves, sitting under the row it belongs to. */
.shelf-bar {
    height: 10px;
    margin: 14px 0 0;
    border-radius: var(--pill);
    background: var(--sunk);
    /* A rail is a control, not a selection: dragging along it should push the
       row rather than highlight the page. */
    user-select: none;
    touch-action: none;
    cursor: pointer;
}

.shelf-bar__thumb {
    height: 100%;
    border-radius: var(--pill);
    background: var(--line-strong);
}

.shelf-bar__thumb:hover,
.shelf-bar__thumb:active {
    background: var(--ink-faint);
}

.recent__open {
    display: flex;
    flex-direction: column;
    gap: 9px;
    width: 100%;
    padding: 0;
    background: none;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.recent__open .poster {
    transition: transform 0.16s ease, box-shadow 0.16s ease;
}

.recent__open .poster__image,
.recent__open .poster__empty {
    transition: transform 0.25s ease;
}

.recent__open:hover .poster {
    transform: translateY(-4px);
    box-shadow: var(--shadow-lift);
}

/* The picture leans in when pointed at. The frame clips it, so the card keeps
   its footprint and only the cover moves. The empty frame joins in, so a card
   without a picture yet answers the pointer the same way. */
.recent__open:hover .poster__image,
.recent__open:focus-visible .poster__image,
.recent__open:hover .poster__empty,
.recent__open:focus-visible .poster__empty {
    transform: scale(1.07);
}

.recent__open:focus-visible {
    outline: none;
}

.recent__open:focus-visible .poster {
    outline: 3px solid var(--accent);
    outline-offset: 3px;
}

/* A character is a name, who voices them, and what they are — no picture of
   their own — so the Character shelves are rows rather than poster cards. The
   media's cover stands in as the face, because it is the one picture there is
   and it is also the way back into the catalogue. */
.character-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.character-row__open {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) auto;
    grid-template-areas: "face name tags" "face from tags";
    align-items: center;
    column-gap: 12px;
    width: 100%;
    padding: 8px 10px;
    border-radius: 12px;
    background: none;
    text-align: left;
    cursor: pointer;
}

.character-row__open:hover {
    background: var(--sunk);
}

.character-row__face {
    grid-area: face;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    width: 44px;
    height: 44px;
    border-radius: 10px;
    background: var(--sunk);
    color: var(--ink-muted);
    font-family: var(--font-ui);
    font-size: 0.8rem;
    font-weight: 800;
}

.character-row__poster {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.character-row__name {
    grid-area: name;
    overflow: hidden;
    font-family: var(--font-ui);
    font-size: 0.92rem;
    font-weight: 650;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.character-row__from {
    grid-area: from;
    overflow: hidden;
    color: var(--ink-muted);
    font-size: 0.78rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.character-row__tags {
    grid-area: tags;
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
    justify-content: flex-end;
    max-width: 40%;
}

/* The face is what goes first when there is no room for everything. */
@media (max-width: 560px) {
    .character-row__tags {
        display: none;
    }
}

/* The end of a listing that is not the end of the listing. Centred under the
   grid, so it reads as the bottom of what is above it rather than as a
   control belonging to the page. */
.listing-more {
    display: flex;
    justify-content: center;
    margin-top: 20px;
}

/* Why a search turned this up, when the title alone does not say. Quiet, and
   a footnote to the card rather than a second title — but two lines rather
   than one: a card is a poster's width, and "has a character called …" cut
   off before the name explains nothing at all. */
/* The line under a title: why a search matched, what kind of musician, or
   how many volumes and chapters there are. One look for all of them. */
.recent__matched,
.recent__meta {
    font-family: var(--font-ui);
    font-size: 0.74rem;
    line-height: 1.35;
    color: var(--ink-muted);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.recent__title {
    font-family: var(--font-ui);
    font-size: 0.86rem;
    font-weight: 650;
    line-height: 1.35;
    color: var(--ink);
    /* Two lines and then an ellipsis: a long title must not push the shelf out
       of line with its neighbours. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ---------- Index information page ---------- */

.info__actions {
    display: flex;
    gap: 10px;
    margin-bottom: 18px;
}

.info__body {
    display: grid;
    grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
    gap: 36px;
    align-items: start;
    /* In from the side rather than against it: the poster is on the left, but
       with the page's own breathing room to its left as well. */
    padding-inline: clamp(0px, 4vw, 60px);
}

.info__body > .poster {
    max-width: 17rem;
    justify-self: center;
}

.info__facts {
    min-width: 0;
}

.info__title {
    margin: 0;
}

.info__original {
    margin: 6px 0 0;
    color: var(--ink-muted);
    font-size: 1.05rem;
}

.info__credit {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 14px 0 0;
}

.info__credit-role {
    padding: 3px 10px;
    border-radius: var(--pill);
    background: var(--sakura-tint);
    color: var(--sakura);
    font-size: 0.7rem;
    font-weight: 750;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.info__credit-name {
    font-weight: 650;
}

.info__rating {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin: 24px 0 0;
    flex-wrap: wrap;
}

.info__votes {
    color: var(--ink-faint);
    font-size: 0.85rem;
}

.info__vote {
    margin: 18px 0 0;
}

.info__vote-heading {
    margin: 0;
    color: var(--ink-muted);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.info__vote-hint,
.info__empty {
    margin: 0;
    color: var(--ink-faint);
    font-size: 0.82rem;
}

.info__heading {
    margin: 28px 0 10px;
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.info__description {
    margin: 26px 0 0;
    max-width: 44rem;
    line-height: 1.65;
    white-space: pre-wrap;
}

/* ---------- Tags people back ---------- */

.tag-cloud {
    display: flex;
    flex-wrap: wrap;
    gap: 9px;
}

/* Genres sit right above the tags and are deliberately not the same shape.
   They are one press each — no score, no backing, nothing to vote on — so a
   flatter outlined pill says "this is a link to a shelf" where the filled tag
   chip says "this is an opinion with a number on it". */
.genre-cloud {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.genre-chip {
    padding: 4px 12px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    background: none;
    color: var(--ink);
    font-family: var(--font-ui);
    font-size: 0.86rem;
    font-weight: 600;
    cursor: pointer;
}

.genre-chip:hover,
.genre-chip:focus-visible {
    border-color: var(--accent);
    color: var(--accent);
}

/* The ceiling on a chip field, said beside the drop list it replaces. */
.lang-choice__full {
    margin: 4px 0 0;
}

.tag-vote {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 3px 4px 3px 3px;
    border-radius: var(--pill);
    background: var(--sunk);
}

.tag-vote__label {
    padding: 0 4px 0 6px;
    font-family: var(--font-ui);
    font-size: 0.86rem;
    font-weight: 600;
}

.tag-vote__score {
    min-width: 1.6rem;
    padding: 2px 7px;
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

/* A tag you back takes the accent, so which ones are yours reads across the
   cloud without going button by button. */
.tag-vote[data-mine="true"] {
    background: var(--accent-tint);
}

.tag-vote[data-mine="true"] .tag-vote__score {
    color: var(--accent);
}

.tag-vote__button {
    width: 1.7rem;
    height: 1.7rem;
    padding: 0;
    border-radius: 50%;
    background: none;
    color: var(--ink-faint);
    font-size: 0.72rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.14s ease, color 0.14s ease;
}

/* Pressed has to outrank hover: hovering the one you already back must not
   wash the colour back out from under you. */
.tag-vote__button:hover:not(:disabled, [aria-pressed="true"]) {
    background: var(--surface);
    color: var(--ink);
}

.tag-vote__button--back[aria-pressed="true"] {
    background: var(--accent-tint);
    color: var(--accent);
}

.tag-add {
    display: flex;
    gap: 8px;
    margin-top: 14px;
    flex-wrap: wrap;
}

.tag-add__field {
    flex: 1 1 12rem;
    min-width: 0;
    max-width: 20rem;
    padding: 9px 13px;
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
}

.tag-add__field:focus {
    outline: none;
    box-shadow: 0 0 0 4px var(--accent-glow);
}

/* On a narrow screen the poster goes to the top and sits in the middle, which
   is the only place it can be when there is no column beside it. */
@media (max-width: 820px) {
    .info__body {
        grid-template-columns: minmax(0, 1fr);
        gap: 26px;
        padding-inline: 0;
    }

    .info__body > .poster {
        width: min(58vw, 14rem);
        justify-self: center;
    }

    .info__rating {
        margin-top: 18px;
    }
}

/* ---------- Who wrote it, and what is in it ---------- */

.info__credits {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    margin: 12px 0 0;
}

.info__by {
    color: var(--ink-faint);
    font-size: 0.82rem;
}

/* Who has worked on a media, and how much of it is theirs.
   A row is a name, a bar as wide as their share, and the figures — so the
   split reads at a glance and still says the numbers for anybody who wants
   them. The bar is a background rather than a child element because it is
   decoration for a fact the row already states in words. */
.info__contributors {
    margin: 12px 0 0;
}

.contributors {
    display: grid;
    gap: 4px;
    margin: 6px 0 0;
    padding: 0;
    list-style: none;
}

.contributors__person {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 90px auto;
    align-items: center;
    gap: 10px;
    font-size: 0.82rem;
    color: var(--ink-muted);
}

.contributors__name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.contributors__bar {
    height: 6px;
    border-radius: var(--pill);
    background: linear-gradient(
        to right,
        var(--accent) var(--share, 0%),
        var(--sunk) var(--share, 0%)
    );
}

.contributors__share {
    color: var(--ink-faint);
    white-space: nowrap;
}

/* On a phone the bar is the first thing to go: the percentage is already
   written next to it, so the row loses decoration rather than information. */
@media (max-width: 560px) {
    .contributors__person {
        grid-template-columns: minmax(0, 1fr) auto;
    }

    .contributors__bar {
        display: none;
    }
}

.attached {
    display: grid;
    gap: 8px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.attached__open {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 11px 15px;
    border-radius: var(--radius-md);
    background: var(--surface);
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: transform 0.14s ease;
}

.attached__open:hover {
    transform: translateX(3px);
}

/* A cover on the row turns the list into a shelf; the words sit beside it
   in a column of their own so a two-line title does not push it about. */
.attached__cover {
    flex: none;
    width: 42px;
    height: 60px;
    border-radius: var(--radius-sm, 6px);
    object-fit: cover;
    background: var(--sunk);
}

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

/* The title and its score share a line, and the score is allowed to keep its
   width while a long title gives way — a number that wraps under its own
   title reads as a number belonging to the next row. */
.attached__line {
    display: flex;
    align-items: baseline;
    gap: 9px;
    min-width: 0;
}

.attached__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 650;
}

.attached__score {
    flex: none;
    gap: 3px;
    font-size: 0.85rem;
    font-weight: 700;
}

/* ---------- Categories, which are the tags people used ---------- */

.category-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 14px;
}

.category-list__chip {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    padding: 6px 14px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.12s ease, color 0.12s ease;
}

.category-list__chip:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* How many things wear this tag. Quiet, because the word is what is being
   scanned for and the number only breaks a tie between two of them. */
.category-list__count {
    color: var(--ink-faint);
    font-size: 0.78rem;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
}

.category-list__chip:hover .category-list__count {
    color: inherit;
}

/* ---------- The tabs across a media's information page ---------- */

/* Four questions one media answers, and one of them at a time. The bar
   scrolls sideways rather than wrapping: four items on two lines stop
   reading as one row of choices. */
.info-tabs {
    display: flex;
    gap: 6px;
    margin: 6px 0 14px;
    padding-bottom: 2px;
    overflow-x: auto;
    scrollbar-width: none;
}

.info-tabs::-webkit-scrollbar {
    display: none;
}

.info-tabs__tab {
    flex: none;
    padding: 6px 15px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    background: none;
    color: var(--ink-muted);
    font-size: 0.92rem;
    cursor: pointer;
}

.info-tabs__tab:hover {
    color: var(--ink);
}

.info-tabs__tab--on {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    color: var(--accent);
    font-weight: 700;
}

/* ---------- What a media is made of, piece by piece ---------- */

.chapter-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0 0 16px;
    padding: 0;
    list-style: none;
}

.chapter-list__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    width: 100%;
    padding: 7px 11px;
    border: none;
    border-radius: var(--radius-sm, 6px);
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.chapter-list__row:hover {
    background: var(--sunk);
}

/* A volume is the shelf its chapters sit on, so it carries the weight and
   they hang under it. */
.chapter-list__row--volume {
    margin-top: 8px;
    font-weight: 700;
}

/* A row's own picture. Small: four hundred chapters at poster size is a page
   nobody scrolls, and the thing being answered here is "which one is this",
   which a thumbnail answers. */
.chapter-list__picture {
    flex: none;
    align-self: center;
    width: 44px;
    height: 44px;
    border-radius: 4px;
    background: var(--sunk);
    object-fit: cover;
}

/* The room a picture would take, on a row that has none. */
.chapter-list__picture--none {
    background: none;
}

.chapter-list__name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Sits where a row would, so the list does not end in a shape unlike
   everything above it. */
.chapter-list__more {
    margin-top: 6px;
}

.chapter-list__said {
    flex: none;
    color: var(--ink-faint);
    font-size: 0.8rem;
}

/* ---------- The language picker ---------- */
/* One control, two homes: beside the account buttons and among the footer
   links. Dressed like the quiet controls around it in both. */

.lingo {
    padding: 6px 8px;
    border: none;
    border-radius: var(--radius-sm, 6px);
    background: var(--sunk);
    color: inherit;
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}

/* Worn like the search box beside it: the same pill, the same ground, the
   same height, and a chevron of its own in place of the browser's. A bare
   native select in a row of rounded controls was the one thing in the
   header that looked like it came from somewhere else. */
.lingo--header {
    appearance: none;
    -webkit-appearance: none;
    height: 36px;
    padding: 0 30px 0 14px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink);
    font-family: var(--font-ui);
    font-size: 0.9rem;
}

.lingo--header:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* The chevron, drawn rather than loaded: a select takes no pseudo-element,
   and a data: image is one the content security policy refuses, so the
   wrapper carries a rotated corner in the theme's muted ink. */
.lingo-wrap {
    position: relative;
    display: inline-flex;
    align-self: center;
}

.lingo-wrap::after {
    content: '';
    position: absolute;
    top: 50%;
    right: 14px;
    width: 7px;
    height: 7px;
    margin-top: -5px;
    border-right: 1.6px solid var(--ink-muted);
    border-bottom: 1.6px solid var(--ink-muted);
    transform: rotate(45deg);
    pointer-events: none;
}

.lingo--footer {
    margin-left: 12px;
}

/* A crowded phone header gets the short version: the control stays, the
   longer native names ellipsize inside it. */
@media (max-width: 700px) {
    .lingo--header {
        max-width: 96px;
    }
}

/* Where the reader is: the language picker's twin, and worn the same so the
   two read as one pair of settings rather than two unrelated controls. */
.where-you-are {
    padding: 6px 8px;
    border: none;
    border-radius: var(--radius-sm, 6px);
    background: var(--sunk);
    color: inherit;
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}

.where-you-are--footer {
    margin-left: 12px;
}

/* ---------- The media's music ---------- */

.music-list {
    margin: 6px 0 0;
    padding: 0;
    list-style: none;
}

.music-list__row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    width: 100%;
    padding: 6px 11px;
    border: none;
    border-radius: var(--radius-sm, 6px);
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.music-list__row:hover {
    background: var(--sunk);
}

.music-list__name {
    flex: none;
    font-weight: 600;
}

.music-list__said {
    min-width: 0;
    overflow: hidden;
    color: var(--ink-faint);
    font-size: 0.85rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* An episode's second line: the OP and ED, an unusual length, a line of
   description. Under the name rather than beside it, so a row with all four
   still reads. */
/* A line drawn on the map, said from this row's side. Below the row's own
   facts and quieter than them: what a chapter is called and when it came out
   are the chapter's; what somebody joined it to is somebody's reading of it. */
.chapter-list__joined {
    flex-basis: 100%;
    order: 4;
    min-width: 0;
    overflow: hidden;
    color: var(--ink-muted);
    font-size: 0.8rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Lines drawn on the map, on the media page. Not buttons: both ends of a
   connection are parts of entries rather than pages of their own, and a row
   that looks pressable and goes nowhere is worse than one that never offered.
   A whole sentence per row, so it wraps rather than being clipped. */
.connection-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.connection-list__row {
    padding: 6px 11px;
    border-bottom: 1px solid var(--line);
}

.connection-list__row:last-child {
    border-bottom: none;
}

.connection-list__said {
    margin: 0;
    font-weight: 600;
}

.connection-list__note {
    margin: 2px 0 0;
    color: var(--ink-faint);
    font-size: 0.85rem;
}

.chapter-list__detail {
    flex-basis: 100%;
    order: 3;
    min-width: 0;
    overflow: hidden;
    color: var(--ink-faint);
    font-size: 0.8rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- Who is in it ---------- */

.cast {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 10px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.cast__row {
    padding: 11px 13px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
}

/* A face at the top of the card, the shape a cast list is usually read in.
   Cropped rather than letterboxed: these are photographs and drawings of
   wildly different proportions and a grid of them should still be a grid. */
.cast__picture {
    display: block;
    width: 100%;
    height: 140px;
    margin-bottom: 8px;
    border-radius: var(--radius-sm, 6px);
    background: var(--sunk);
    object-fit: cover;
}

.cast__name {
    margin: 0;
    font-weight: 700;
}

/* An arc's picture, in the line that names it. Small enough to sit in text,
   because that is where it sits. */
.entry-facts__picture {
    width: 34px;
    height: 34px;
    margin-right: 6px;
    border-radius: 4px;
    background: var(--sunk);
    object-fit: cover;
    vertical-align: middle;
}

.cast__facts {
    margin: 3px 0 0;
    color: var(--ink-faint);
    font-size: 0.82rem;
}

.cast__tags {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin: 8px 0 0;
}

/* A card is a column, so a button in one stretches the whole width unless it
   is told not to. */
.community__start {
    align-self: flex-start;
    margin-top: 14px;
}

.cast__tag {
    padding: 2px 8px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink-muted);
    font-size: 0.75rem;
}

.attached__meta {
    color: var(--ink-faint);
    font-size: 0.8rem;
}

/* ---------- Reading a catalogue you are not signed in to edit ---------- */

/* A disabled fieldset greys its contents out, which is right for a form that
   is switched off and wrong for a page being read. The values are the content
   here, so they keep their colour. */
.entry-page--reading :where(input, textarea, select):disabled {
    opacity: 1;
    color: var(--ink);
    -webkit-text-fill-color: var(--ink);
    background: var(--sunk);
    cursor: default;
}

.entry-page--reading :where(fieldset:disabled) {
    opacity: 1;
}

/* Nothing to press on a page nobody signed in is reading. */
.entry-page--reading :where(.lang-add, .lang-remove, .tag-field__add, .tag-chip__remove) {
    display: none;
}

/* And the same on the map, which is the same document drawn differently. The
   ⋯ opened a panel that writes to the form and offered Delete this note; the
   ports pull a wire between two boxes. Both are edits, and neither could ever
   have been saved from here. The boxes and the wires already drawn stay:
   reading the map is the whole reason a stranger is sent to it. */
.entry-page--reading :where(.map-node__menu, .map-node__port) {
    display: none;
}

/* The one entry an information page pointed at, so arriving at a catalogue of
   nine of them lands somewhere rather than at the top. */
/* ---------- Jumping to a part of the document ---------- */

/* One press per fieldset, above the form. Scrolls sideways rather than
   wrapping: a document with six entries would otherwise put a second row of
   buttons between the header and the form. */
.entry-jump {
    display: flex;
    gap: 6px;
    margin: 0 0 18px;
    padding-bottom: 2px;
    overflow-x: auto;
    scrollbar-width: none;
}

.entry-jump::-webkit-scrollbar {
    display: none;
}

.entry-jump__link {
    flex: none;
    padding: 5px 13px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--ink-muted);
    font-size: 0.82rem;
    font-weight: 650;
    cursor: pointer;
}

.entry-jump__link:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* ---------- Folding an entry away ---------- */

/* The fold is a grid of one row, and the row goes from its content's height to
   nothing. No height is ever measured, which is what makes it survive a volume
   being added or a picture loading while the entry is open. */
.entry-fold {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows 260ms ease;
}

.entry-fold__inner {
    /* Both of these are load-bearing: without them the row refuses to go below
       its content's height and nothing appears to happen. */
    min-height: 0;
    overflow: hidden;
}

.entry-fold--shut {
    grid-template-rows: 0fr;
}

/* A folded entry is a bar with a legend on it, so it loses the padding that
   was holding a form. */
.entry-native :where(fieldset.entry--folded) {
    padding-bottom: 10px;
}

.entry-fold-toggle {
    position: absolute;
    top: 16px;
    right: 18px;
    padding: 2px 10px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--ink-muted);
    font-size: 0.8rem;
    line-height: 1.4;
    cursor: pointer;
    transition: transform 260ms ease, color 120ms ease;
}

.entry-fold-toggle:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.entry-fold-toggle[aria-expanded="false"] {
    transform: rotate(-90deg);
}

@media (prefers-reduced-motion: reduce) {
    .entry-fold,
    .entry-fold-toggle {
        transition: none;
    }
}

.entry--named {
    outline: 3px solid var(--accent);
    outline-offset: 6px;
    border-radius: var(--radius-lg);
    animation: entry-named-fade 2.4s ease forwards;
}

@keyframes entry-named-fade {
    0%, 55% { outline-color: var(--accent); }
    100% { outline-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
    .entry--named {
        animation: none;
    }
}

/* ---------- Tags people back ---------- */

/* The share is the number that matters, so it carries the weight the running
   total used to. */
.tag-vote__score {
    font-variant-numeric: tabular-nums;
}

/* Set aside: still on the page, so it can be backed again, but plainly not one
   of the tags on the thing. */
.info__subheading {
    margin: 22px 0 8px;
    color: var(--ink-faint);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.tag-cloud--rejected .tag-vote {
    background: none;
    opacity: 0.75;
}

.tag-cloud--rejected .tag-vote__label {
    text-decoration: line-through;
    text-decoration-color: var(--ink-faint);
}

/* Tags already in use, offered while typing one. */
.tag-suggest__option {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
}

.tag-suggest__uses {
    color: var(--ink-faint);
    font-size: 0.74rem;
    white-space: nowrap;
}

/* ---------- Tags on a shelf card ---------- */

/* Tags used to sit under the poster in their own row; they live inside its
   bottom edge now (.poster__tags), so every card is the same height. */

/* ---------- Private messages, in the corner ---------- */

/* A round button at the bottom right, over the page rather than in it. A PM is
   something you send while doing something else, so it has to be reachable
   without leaving whatever prompted it. */
.pm {
    position: fixed;
    right: 20px;
    bottom: calc(20px + var(--entry-lift, 0px));
    z-index: 40;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 10px;
}

.pm__launcher {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    padding: 0;
    border-radius: 50%;
    background: linear-gradient(140deg, var(--accent), var(--accent-strong));
    color: var(--accent-ink);
    cursor: pointer;
    box-shadow: 0 6px 20px var(--accent-glow);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.pm__launcher:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 28px var(--accent-glow);
}

.pm__launcher .icon {
    width: 24px;
    height: 24px;
}

/* The panel is one fixed height whatever is on it, because it is three screens
   that swap — the list, a conversation, a new message — and a box that grew
   and shrank as they did would be a box that jumps. */
.pm__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    width: min(360px, calc(100vw - 40px));
    height: min(70vh, 600px);
    overflow: hidden;
    border-radius: 16px;
    background: var(--surface);
    box-shadow: 0 18px 50px rgb(0 0 0 / 0.22);
}

/* Back on the left, the name in the middle, + and Close on the right: the top
   of a messages screen, laid out where people already expect to find it. */
.pm__head {
    display: flex;
    flex: none;
    align-items: center;
    gap: 4px;
    padding: 8px 8px 8px 10px;
    /* A filled line rather than a border, because nothing here draws borders. */
    box-shadow: 0 1px 0 var(--line);
}

.pm__heading {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    font-size: 0.95rem;
    font-weight: 700;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pm__back,
.pm__new {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border-radius: 50%;
    background: none;
    color: var(--accent);
    cursor: pointer;
}

.pm__back {
    font-size: 1.7rem;
    line-height: 1;
    /* The chevron glyph sits low in its box; lifted to the optical centre. */
    padding-bottom: 4px;
}

.pm__back:hover,
.pm__new:hover {
    background: var(--sunk);
}

.pm__new .icon {
    width: 20px;
    height: 20px;
    stroke-width: 2.2;
}

/* What the three screens draw into. */
.pm__view {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
}

/* ----- The list of conversations ----- */

.pm__list {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
    padding: 6px;
    overflow-y: auto;
}

.pm__conversation {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    border-radius: 12px;
    background: none;
    color: var(--ink);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.pm__conversation:hover {
    background: var(--sunk);
}

/* A picture when they have one, their first letters when they do not. */
.pm__avatar {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    overflow: hidden;
    border-radius: 50%;
    background: linear-gradient(140deg, var(--accent), var(--sakura));
    color: #fff;
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.02em;
}

.pm__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.pm__conversation-text {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.pm__conversation-top {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.pm__who {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    font-size: 0.88rem;
    font-weight: 700;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pm__when {
    flex: none;
    color: var(--ink-muted);
    font-size: 0.72rem;
}

.pm__preview {
    overflow: hidden;
    color: var(--ink-muted);
    font-size: 0.8rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Unread is said by the dot at the left and the preview in full ink, the way a
   phone says it; the words stay as readable as the ones already opened. */
.pm__conversation--unread .pm__preview {
    color: var(--ink);
    font-weight: 600;
}

.pm__dot {
    flex: none;
    width: 9px;
    height: 9px;
    margin-left: -2px;
    border-radius: 50%;
    background: var(--accent);
}

.pm__status {
    margin: 0;
    padding: 12px 16px;
    color: var(--ink-muted);
    font-size: 0.8rem;
    text-align: center;
}

/* ----- One conversation, as bubbles ----- */

.pm__thread {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 2px;
    min-height: 0;
    padding: 10px 12px 8px;
    overflow-y: auto;
}

.pm__stamp {
    margin: 10px 0 6px;
    color: var(--ink-muted);
    font-size: 0.7rem;
    font-weight: 600;
    text-align: center;
}

.pm__item {
    display: flex;
    flex-direction: column;
    max-width: 82%;
}

.pm__item--mine {
    align-self: flex-end;
    align-items: flex-end;
}

.pm__item--theirs {
    align-self: flex-start;
    align-items: flex-start;
}

/* A change of speaker gets a little air; the same one carrying on does not. */
.pm__item--mine + .pm__item--theirs,
.pm__item--theirs + .pm__item--mine {
    margin-top: 8px;
}

.pm__body {
    margin: 0;
    padding: 8px 13px;
    border-radius: 18px;
    font-size: 0.88rem;
    line-height: 1.4;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.pm__item--mine .pm__body {
    border-bottom-right-radius: 5px;
    background: linear-gradient(140deg, var(--accent), var(--accent-strong));
    color: var(--accent-ink);
}

.pm__item--mine .pm__body a {
    color: inherit;
    text-decoration: underline;
}

.pm__item--theirs .pm__body {
    border-bottom-left-radius: 5px;
    background: var(--sunk);
    color: var(--ink);
}

/* Under a bubble: the time, and on theirs, Report. A row with a gap, so the
   two can never run together into one word. */
.pm__meta {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 2px 6px 0;
    color: var(--ink-muted);
    font-size: 0.68rem;
}

.pm__meta .link-button--quiet {
    font-size: 0.68rem;
}

/* ----- Writing ----- */

.pm__compose {
    display: flex;
    flex: none;
    align-items: flex-end;
    gap: 8px;
    padding: 8px 10px 10px;
    box-shadow: 0 -1px 0 var(--line);
}

.pm__write {
    flex: 1 1 auto;
    min-width: 0;
    max-height: 132px;
    padding: 8px 14px;
    border-radius: 18px;
    font-size: 0.88rem;
    line-height: 1.35;
    resize: none;
}

.pm__send {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border-radius: 50%;
    background: linear-gradient(140deg, var(--accent), var(--accent-strong));
    color: var(--accent-ink);
    cursor: pointer;
}

.pm__send:disabled {
    opacity: 0.5;
    cursor: default;
}

.pm__send .icon {
    width: 18px;
    height: 18px;
    stroke-width: 2.4;
}

/* A new message is the same form with a To box and the people to choose from
   above the writing box. It fills the panel so the list has room to scroll. */
.pm__compose--new {
    display: grid;
    flex: 1 1 auto;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto 1fr auto;
    align-items: end;
    min-height: 0;
    padding: 0;
    box-shadow: none;
}

.pm__compose--new .pm__write,
.pm__compose--new .pm__send {
    margin: 0 0 10px;
}

.pm__compose--new .pm__write {
    margin-left: 10px;
}

.pm__compose--new .pm__send {
    margin-right: 10px;
}

.pm__to-row {
    display: flex;
    grid-column: 1 / -1;
    align-items: center;
    gap: 8px;
    padding: 10px 12px 8px;
    box-shadow: 0 1px 0 var(--line);
}

.pm__to-label {
    flex: none;
    color: var(--ink-muted);
    font-size: 0.85rem;
    font-weight: 600;
}

.pm__to {
    flex: 1 1 auto;
    padding: 7px 12px;
}

.pm__friends {
    display: flex;
    grid-column: 1 / -1;
    flex-direction: column;
    gap: 2px;
    align-self: stretch;
    min-height: 0;
    padding: 6px 8px;
    overflow-y: auto;
}

.pm__friend {
    padding: 7px 10px;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 0.86rem;
    text-align: left;
    cursor: pointer;
}

.pm__friend:hover {
    background: var(--sunk);
}

.pm__empty {
    text-align: left;
}

@media (max-width: 600px) {
    .pm {
        right: 12px;
        bottom: calc(12px + var(--entry-lift, 0px));
    }

    /* The footer wraps to three rows on a phone and grew up into the
       button. Room under its last row for the button and its margin, only
       while there is a button to make room for. */
    body:has(.pm__launcher) .app-footer {
        padding-bottom: 88px;
    }
}

/* ---------- Mail ---------- */

.mail {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.mail__item {
    position: relative;
    padding: 14px 16px;
    border-radius: 14px;
    background: var(--sunk);
}

.mail__item--unread {
    background: var(--accent-tint);
}

.mail__subject {
    margin: 0 0 3px;
    font-size: 0.98rem;
    font-weight: 700;
}

.mail__meta {
    margin: 0 0 9px;
    color: var(--ink-muted);
    font-size: 0.76rem;
    font-weight: 600;
}

.mail__body {
    margin: 0;
    line-height: 1.65;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.mail__read {
    margin-top: 10px;
}

.mail__read-all {
    align-self: flex-start;
    margin-bottom: 12px;
}

.mail-compose {
    max-width: 640px;
}

/* ---------- The report queue ---------- */

.reports {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.report {
    padding: 14px 16px;
    border-radius: 14px;
    background: var(--sunk);
}

.report__title {
    margin: 0 0 6px;
    font-size: 0.95rem;
    font-weight: 700;
}

/* What was said, as it stood when it was reported: set apart from the
   reporter's own words so the two are never read as one. */
/* Set apart by ground rather than by a rule, like everything else here. */
.report__quote {
    margin: 0 0 8px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--sunk);
    color: var(--ink-muted);
    line-height: 1.6;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.report__reason {
    margin: 0 0 8px;
    line-height: 1.6;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.report__meta {
    margin: 0 0 12px;
    color: var(--ink-muted);
    font-size: 0.76rem;
    font-weight: 600;
}

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

/* Somewhere to write inside a dialog that otherwise only asks yes or no. */
.dialog__field {
    width: 100%;
    margin-bottom: 16px;
}

/* ---------- Drafts ---------- */

/* Above everything, and not dismissible. Which of the two states a catalogue
   is in decides whether the world can read it, and somebody who has been
   writing for an hour should never have to wonder which one they are in. */
.entry-draft {
    margin: 0 0 20px;
    padding: 11px 14px;
    border-radius: 10px;
    background: var(--sunk);
    color: var(--ink-muted);
    font-size: 0.86rem;
}

/* Not a draft notice, though it looks like one and sits in the same place: a
   draft says who can read this, and this says what pressing Save will do.
   Its own class rather than a modifier so that counting draft notices counts
   draft notices. */
.entry-waiting {
    margin: 0 0 20px;
    padding: 11px 14px;
    border-radius: 10px;
    background: var(--sunk);
    color: var(--ink-muted);
    font-size: 0.86rem;
}

.entry-publish {
    white-space: nowrap;
}

/* The saved list is the only place a draft can be found, so it says so there
   rather than leaving the row looking like everything around it. */
.saved__draft {
    padding: 2px 8px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink-muted);
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* ---------- The site-wide log ---------- */

.log-tabs {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;
}

/* Pressed has to outrank hover, or hovering the tab you are already on washes
   the accent back out from under you. */
.log-tab[aria-pressed="true"] {
    background: var(--accent-tint);
    color: var(--accent);
}

.log-table td {
    vertical-align: top;
    font-size: 0.84rem;
}

/* A title that opens the thing it names, without looking like a button in a
   table of plain text. */
.button--link {
    padding: 0;
    background: none;
    color: var(--accent);
    font: inherit;
    font-weight: 650;
    text-align: left;
    cursor: pointer;
}

.button--link:hover {
    text-decoration: underline;
}

/* ---------- Privacy ---------- */

/* On every page, quiet, and out of the way of everything above it. A notice
   reachable from only some pages is not reachable. */
.app-footer {
    display: flex;
    /* Seven things on one row with nowhere to go: on a phone the row was
       squeezed until every link became a column one word wide, so "Tell us
       what to fix" read downwards, and the two pickers appended after them
       were pushed off the right edge entirely. Wrapping is what a row of
       links is supposed to do when it runs out of width. */
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 12px 16px;
    padding: 28px 20px 36px;
}

.app-footer__link {
    color: var(--ink-faint);
    font-size: 0.8rem;
    text-decoration: none;
    /* A link wraps to the next line whole or not at all. Breaking one across
       lines is what turned these into columns. */
    white-space: nowrap;
}

/* The language and region pickers sit on the end of the same row and wrap
   with it. Bounded so a long country name cannot push the row wider than the
   screen and bring the sideways scroll back with it. */
.app-footer .lingo--footer,
.app-footer .where-you-are--footer {
    max-width: min(14rem, 45vw);
}

.app-footer__link:hover {
    color: var(--accent);
    text-decoration: underline;
}

/* The two small lines under the signup form: what the address is for, and
   what making an account accepts. Same look, separate names, because each is
   addressed on its own. */
.form__privacy,
.form__terms {
    color: var(--ink-muted);
    font-size: 0.78rem;
    line-height: 1.55;
}

/* A claim and what stands behind it, over and over: the privacy notice and the
   how-this-was-built page are both that shape, so they share the list rather
   than each growing one. */
/* Ask an AI to write an entry: the prompt in a box tall enough to read
   whole, the pasted answer under it, and what the reader made of it. */
.import .field__label {
    display: block;
    margin-top: 14px;
}

.import__prompt,
.import__document {
    margin-top: 6px;
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 0.82rem;
    line-height: 1.45;
    resize: vertical;
}

.import__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 12px;
}

.import .button--primary {
    margin-top: 12px;
}

.import__actions .button {
    margin-top: 0;
}

.import__found {
    margin: 12px 0 0;
    font-weight: 600;
}

.import__warnings {
    margin: 8px 0 0;
    padding-left: 20px;
    color: var(--ink-muted);
    font-size: 0.86rem;
}

.import__refusal {
    margin: 12px 0 0;
    color: var(--danger);
    font-weight: 600;
}

.stated-list {
    margin: 0;
}

.stated-list dt {
    margin-top: 16px;
    font-weight: 700;
}

.stated-list dt:first-child {
    margin-top: 0;
}

.stated-list dd {
    margin: 4px 0 0;
    color: var(--ink-muted);
    line-height: 1.65;
}

/* What each shelf holds, under the chips on the Genre view: name, then the
   sentence, in two columns where there is room for them. */
.category-list__legend {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 6px 14px;
    margin: 18px 0 0;
    font-size: 0.86rem;
}

.category-list__legend dt {
    font-weight: 700;
}

.category-list__legend dd {
    margin: 0;
    color: var(--ink-muted);
    line-height: 1.5;
}

/* The same list in the editor, behind a press, and the sentence said under
   the field as each genre is chosen. */
.lang-choice__meaning {
    flex-basis: 100%;
    margin: 6px 0 0;
}

.lang-choice__glossary {
    flex-basis: 100%;
    margin-top: 8px;
    font-size: 0.86rem;
}

.lang-choice__glossary summary {
    color: var(--accent);
    font-weight: 600;
    cursor: pointer;
}

.lang-choice__meanings {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 4px 12px;
    margin: 8px 0 0;
}

.lang-choice__meanings dt {
    font-weight: 700;
}

.lang-choice__meanings dd {
    margin: 0;
    color: var(--ink-muted);
}

@media (max-width: 600px) {
    .category-list__legend,
    .lang-choice__meanings {
        grid-template-columns: 1fr;
    }

    .category-list__legend dd,
    .lang-choice__meanings dd {
        margin-bottom: 6px;
    }
}

/* Download, delete, and read about it — a destructive action beside a harmless
   one, so they are not the same shape. */
.data-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 16px;
}.saved__draft--pending {
    color: var(--accent);
}

/* An administrator's two answers, on the page the thing was written on. Above
   the form and impossible to miss: whoever is here is here to decide. */
.entry-review {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin: 0 0 20px;
    padding: 14px 16px;
    border-radius: 12px;
    background: var(--accent-tint);
}

.entry-review__heading {
    flex: 1;
    margin: 0;
    font-size: 0.88rem;
    font-weight: 700;
}

/* Which of the two private states a card on your own shelf is in. Pinned to
   the poster rather than sitting under the title, because the poster is what
   the eye lands on and the state is the reason the card is on this shelf. */
.recent__state {
    position: absolute;
    top: 8px;
    left: 8px;
    padding: 3px 8px;
    border-radius: var(--pill);
    background: var(--ink);
    color: var(--surface);
    font-size: 0.64rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.recent__state--pending {
    background: var(--accent);
    color: var(--accent-ink);
}

/* ---------- The home page has no cards, only sections ---------- */

/* Everywhere else a card is an object you act on — a form, a row of controls —
   and being a panel says so. The home page is not that: it is one column of
   things to read, in order, and eight stacked outlines made it look like eight
   unrelated widgets rather than one page. The heading of each section already
   says where it begins.

   Scoped to .page--home rather than done globally, because the panels are
   right on every other page. */
.page--home .card {
    /* The padding was what held them apart, so the space it gave comes back
       as a gap between sections instead. */
    margin-bottom: 40px;
    padding: 0;
    background: none;
    box-shadow: none;
}

.page--home .card:last-child {
    margin-bottom: 0;
}

/* ---------- The site status charts ---------- */

/* What is wrong right now, in the same words the daily mail puts in its
   subject line. Loud on purpose: it is above the charts because a number
   buried under twenty-four bar charts is a number nobody acts on. */
.status-alarms {
    margin: 0 0 16px;
    padding: 12px 14px;
    border-radius: 8px;
    border: 1px solid var(--danger-line);
    background: var(--danger-tint);
    color: var(--danger);
}

.status-alarms p {
    margin: 0 0 8px;
    line-height: 1.5;
}

.status-alarms p:last-child {
    margin-bottom: 0;
}

/* The reporting half, said every day whether or not it is a problem. */
.status-backup {
    margin: 16px 0 0;
    color: var(--ink-muted);
    font-size: 0.9rem;
}

/* --- The row of numbers across the top --- */

/* Ten tiles, the day in one line: the question this page is opened with is
   "is anything wrong", and nine bar charts answer it last.

   Three of them carry a health colour, because three of them have a number
   behind them that means something — the disk alarm the daily mail shouts
   with, and the line the machine gauges have always turned red at. The rest
   are deliberately plain. A tile that is green because nothing is known to
   be wrong with it teaches somebody to read green as "fine", and then the
   day one of them matters they have stopped looking. */
.status-tiles {
    display: grid;
    /* Wide enough that "Failed sign-ins" and "Entries created" fit rather
       than ellipsing — a truncated label is worse than a shorter row — and
       so the row breaks five and five rather than seven and a stranded
       two. */
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    gap: 8px;
    margin: 0 0 18px;
}

.status-tile {
    display: grid;
    grid-template-columns: 1fr auto;
    align-content: start;
    gap: 2px 8px;
    padding: 10px 12px 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--sunk);
}

.status-tile__name {
    margin: 0;
    color: var(--ink-muted);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.02em;
}

/* How far apart the readings behind the number are — "24h" over a day's
   total, "now" over a reading taken this second. Without it a tile is a
   number with no period, which is half a fact. */
.status-tile__when {
    justify-self: end;
    padding: 1px 6px;
    border-radius: var(--pill);
    background: var(--line);
    color: var(--ink-muted);
    font-size: 0.66rem;
    font-weight: 700;
}

.status-tile__figure {
    grid-column: 1 / -1;
    display: flex;
    align-items: baseline;
    gap: 2px;
    margin: 2px 0 0;
    color: var(--ink);
    line-height: 1;
}

.status-tile__number {
    font-size: clamp(1.7rem, 3.2vw, 2.4rem);
    font-weight: 800;
    /* So a column of tiles does not jitter as the digits change under it. */
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
}

.status-tile__unit {
    font-size: 1rem;
    font-weight: 700;
}

.status-tile__note {
    grid-column: 1 / -1;
    margin: 4px 0 0;
    color: var(--ink-muted);
    font-size: 0.72rem;
    line-height: 1.35;
}

.status-tile .status-gauge {
    grid-column: 1 / -1;
    margin-top: 8px;
    height: 5px;
}

/* The tinted grounds rather than the solid blocks the page this is modelled
   on uses: a saturated green panel is right on a dashboard that is nothing
   else, and wrong inside a light page full of white cards. The number itself
   takes the full colour, which is the half that has to carry across a room. */
.status-tile--ok {
    border-color: var(--success-line);
    background: var(--success-tint);
}

.status-tile--ok .status-tile__figure {
    color: var(--success);
}

.status-tile--warn {
    border-color: var(--warning-line);
    background: var(--warning-tint);
}

.status-tile--warn .status-tile__figure {
    color: var(--warning);
}

.status-tile--hot {
    border-color: var(--danger-line);
    background: var(--danger-tint);
}

.status-tile--hot .status-tile__figure {
    color: var(--danger);
}

/* --- The hours behind the numbers --- */

/* A wall of small multiples rather than a column of nine full-width strips:
   nine charts read side by side are a shape, and nine stacked are a scroll. */
.status-charts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
    gap: 10px;
}

.status-chart__row {
    /* Without it a scrolling child sets the track's floor and one long chart
       pushes every other column off the card. */
    min-width: 0;
    padding: 10px 12px 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--sunk);
}

.status-chart__label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin: 0;
}

.status-chart__name {
    color: var(--ink);
    font-size: 0.84rem;
    font-weight: 700;
}

.status-chart__when {
    flex: 0 0 auto;
    padding: 1px 6px;
    border-radius: var(--pill);
    background: var(--line);
    color: var(--ink-muted);
    font-size: 0.66rem;
    font-weight: 700;
}

.status-chart__said {
    margin: 3px 0 6px;
    color: var(--ink-muted);
    font-size: 0.76rem;
}

/* Fixed-width columns so the per-hour numbers stay legible; the pane
   scrolls sideways instead of squeezing two days into the panel. */
.status-chart__scroll {
    overflow-x: auto;
}

.status-chart {
    display: block;
    height: 88px;
}

/* --line, not --sunk: --sunk is now the panel's own ground, so an axis
   drawn in it is an axis drawn in the background. */
.status-chart__axis {
    stroke: var(--line);
    stroke-width: 2;
}

/* 0, 50 and 100 on the share charts. Faint enough to sit behind the bars
   and present enough to give them a scale to be read against. */
.status-chart__rule {
    stroke: var(--line);
    stroke-width: 1;
    stroke-dasharray: 2 3;
}

.status-chart__value {
    fill: var(--ink-muted);
    font-size: 10px;
    font-weight: 700;
}

.status-chart__hour {
    fill: var(--ink-muted);
    font-size: 9px;
}

.status-gauge {
    overflow: hidden;
    width: 100%;
    height: 10px;
    border-radius: var(--pill);
    /* On a tinted tile the rail has to be darker than the tile rather than
       the page: --sunk is the tile's own ground, so the empty half of the
       bar would vanish into it. */
    border: 1px solid var(--line);
    background: var(--surface);
}

.status-gauge__fill {
    height: 100%;
    border-radius: var(--pill);
    background: var(--accent);
}

.status-gauge__fill--hot {
    background: var(--danger);
}

/* --- The forum --- */

.forum-start {
    align-self: flex-start;
    margin-bottom: 10px;
}

.forum-hint {
    margin: 6px 0 10px;
    color: var(--ink-muted);
}

.forum-thread {
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    margin-bottom: 8px;
    background: var(--surface);
}

.forum-thread--popular {
    border-color: var(--accent);
}

.forum-thread__title {
    display: block;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ink);
    font-size: 1.02rem;
    font-weight: 700;
    text-align: left;
    cursor: pointer;
}

.forum-thread__title:hover {
    text-decoration: underline;
}

.forum-thread__meta {
    margin: 4px 0 0;
    color: var(--ink-muted);
    font-size: 0.82rem;
}

.forum-entries {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}

.forum-entries__chip {
    padding: 3px 10px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink);
    font-size: 0.8rem;
    cursor: pointer;
}

.forum-entries__chip:hover {
    border-color: var(--accent);
}

.forum-picker__results {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.forum-picker__option {
    padding: 6px 10px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink);
    text-align: left;
    cursor: pointer;
}

.forum-picker__option:hover {
    border-color: var(--accent);
}

.forum-new {
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    margin-bottom: 14px;
}

.forum-posts {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 10px;
}

.forum-post {
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface);
}

.forum-post__who {
    margin: 0 0 4px;
    color: var(--ink-muted);
    font-size: 0.82rem;
}

.forum-post__delete {
    margin-left: 8px;
}

.forum-post__body {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.forum-reply {
    margin-top: 12px;
}

.forum-back {
    padding-left: 0;
    margin-bottom: 4px;
}

/* --- Groups --- */

.group-about {
    margin: 6px 0 0;
    color: var(--ink);
    font-size: 0.9rem;
}

.group-door {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 0;
    border: 0;
    min-width: 0;
}

.group-door__choice {
    color: var(--ink);
    font-size: 0.9rem;
    cursor: pointer;
}

.group-asked {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 6px 0;
}

.group-asked__button {
    padding: 4px 12px;
}

/* --- Character gender cells --- */

.gender-cell {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 9rem;
}

/* Red only while the answer is missing, not for having been asked. */
.gender-cell input[required]:placeholder-shown {
    border-color: var(--danger);
}

/* Inside the character table the column header already says Tags, so the
   field's own label would say it twice. */
.entry-native td .tag-field__label {
    display: none;
}

.entry-native td .tag-field {
    min-width: 11rem;
}

/* --- Reddit-shaped forum: votes, sorts, the tree --- */

.forum-thread {
    display: flex;
    gap: 10px;
}

.forum-thread__content {
    min-width: 0;
    flex: 1;
}

.forum-vote {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    flex: none;
    width: 2.2rem;
}

.forum-vote__arrow {
    padding: 0 6px;
    border: 0;
    background: none;
    color: var(--ink-muted);
    font-size: 0.9rem;
    line-height: 1.2;
    cursor: pointer;
}

.forum-vote__arrow--on {
    color: var(--accent);
}

.forum-vote__score {
    color: var(--ink);
    font-size: 0.85rem;
    font-weight: 700;
}

.forum-sorts {
    display: flex;
    gap: 6px;
    margin-bottom: 8px;
}

.forum-sorts__tab {
    padding: 3px 12px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    background: none;
    color: var(--ink-muted);
    font-size: 0.85rem;
    cursor: pointer;
}

.forum-sorts__tab--on {
    border-color: var(--accent);
    color: var(--accent);
    font-weight: 700;
}

.forum-post__top {
    display: flex;
    gap: 10px;
}

.forum-post__content {
    min-width: 0;
    flex: 1;
}

.forum-post__children {
    padding-left: 14px;
    border-left: 2px solid var(--line);
    margin: 8px 0 0 10px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.discuss__text--hidden {
    color: var(--ink-muted);
    font-style: italic;
}

.forum-post__body--removed {
    color: var(--ink-muted);
    font-style: italic;
}

.forum-post__reply {
    margin-left: 8px;
}

/* The server-rendered account of a series, standing in until the app boots
   (and standing alone when JavaScript is off). */
.series-static {
    max-width: 44rem;
    padding: 16px;
    margin: 0 auto;
}

.series-static__source {
    color: var(--ink-muted);
    font-size: 0.85rem;
}

/* Sub-comment threads: the way on when the page stops stepping right. */
.forum-post__continue {
    margin: 6px 0 0 10px;
    padding-left: 0;
    color: var(--accent);
    font-size: 0.85rem;
}

.forum-branch {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    margin-bottom: 10px;
    background: var(--sunk);
    color: var(--ink-muted);
    font-size: 0.88rem;
}

/* Folding a comment shut, and what stays visible when it is. */
.forum-post__fold {
    padding: 0 6px 0 0;
    border: 0;
    background: none;
    color: var(--ink-muted);
    font-family: monospace;
    font-size: 0.85rem;
    cursor: pointer;
}

.forum-post__fold:hover {
    color: var(--accent);
}

.forum-post__hidden {
    display: none;
    font-style: italic;
}

.forum-post--collapsed > .forum-post__top .forum-post__body,
.forum-post--collapsed > .forum-post__top .forum-vote,
.forum-post--collapsed > .forum-post__top .forum-reply,
.forum-post--collapsed > .forum-post__top .forum-post__reply,
.forum-post--collapsed > .forum-post__top .forum-post__delete,
.forum-post--collapsed > .forum-post__children,
.forum-post--collapsed > .forum-post__continue {
    display: none;
}

.forum-post--collapsed > .forum-post__top .forum-post__hidden {
    display: inline;
}

/* A folded comment is one line, so it should not keep a card's padding. */
.forum-post--collapsed {
    padding-top: 6px;
    padding-bottom: 6px;
}

.forum-post--collapsed .forum-post__who {
    margin: 0;
}

/* --- Reply and Delete as words on the byline, not chunks of furniture --- */

.forum-post__reply,
.forum-post__delete {
    padding: 0;
    border: 0;
    margin-left: 8px;
    background: none;
    font-size: inherit;
    font-weight: 700;
    cursor: pointer;
}

.forum-post__reply {
    color: var(--accent);
}

.forum-post__delete {
    color: var(--danger);
}

.forum-post__reply:hover,
.forum-post__delete:hover {
    text-decoration: underline;
}

/* --- The one box everything is written in --- */

.forum-composer {
    gap: 0;
    margin-top: 12px;
}

.forum-composer__bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 10px;
    border: 1px solid var(--line);
    border-bottom: 0;
    border-radius: 10px 10px 0 0;
    /* A shade darker than the box under it, so it reads as a label on the
       thing rather than part of what is being typed. */
    background: var(--paper-deep);
    color: var(--ink-muted);
    font-size: 0.82rem;
}

.forum-composer__drop {
    padding: 0 4px;
    border: 0;
    background: none;
    color: var(--ink-muted);
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
}

.forum-composer__drop:hover {
    color: var(--danger);
}

.forum-composer__row {
    display: flex;
    align-items: flex-end;
    gap: 8px;
}

.forum-composer__stack {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
}

.forum-composer__box {
    width: 100%;
    /* Padding inside the height rather than added to it, or "one line" is
       one line plus however much room the padding wants. */
    box-sizing: border-box;
    /* One line to start with; the height is set as it is typed into. */
    height: 2.6rem;
    max-height: 220px;
    padding: 9px 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    line-height: 1.3;
    resize: none;
}

.forum-composer__bar:not([hidden]) + .forum-composer__box {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

.forum-composer__send {
    flex: none;
    height: 2.6rem;
    padding: 0 18px;
}

/* --- One entry's own page --- */

.entry-info__edit {
    align-self: flex-start;
    margin-bottom: 12px;
}

.entry-facts {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px 14px;
    margin: 12px 0;
}

.entry-facts__name {
    color: var(--ink-muted);
    font-size: 0.85rem;
    font-weight: 700;
}

.entry-facts__value {
    margin: 0;
}

/* Switching what a tracked media is counted in. Quiet: it is a preference,
   not the thing pressed every time an episode is finished. */
.track-progress__counting {
    padding: 2px 8px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    background: none;
    color: var(--ink-muted);
    font-size: 0.75rem;
    cursor: pointer;
}

.track-progress__counting:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* --- People, lists and friends --- */

.person__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
}

.person__avatar {
    display: inline-flex;
    overflow: hidden;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    font-weight: 800;
}

.person__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ---------- Finding a friend by name ---------- */

.friend-finder {
    margin-bottom: 20px;
}

.friend-finder__input {
    display: block;
    width: 100%;
    max-width: 28rem;
    margin: 8px 0 6px;
}

.friend-finder__found {
    margin-top: 10px;
}

.person__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 12px 0;
}

.person__about {
    max-width: 44rem;
}

.person__counts {
    color: var(--ink-muted);
    font-size: 0.88rem;
}

.person__friends {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.person__list-open {
    margin-top: 10px;
}

/* The add-to-list menu on a media page. */
.list-button {
    position: relative;
}

.list-menu {
    position: absolute;
    z-index: 20;
    top: calc(100% + 6px);
    left: 0;
    display: flex;
    flex-direction: column;
    min-width: 12rem;
    padding: 6px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface);
    box-shadow: 0 10px 30px rgb(0 0 0 / 18%);
}

.list-menu__row {
    padding: 6px 10px;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--ink);
    text-align: left;
    cursor: pointer;
}

.list-menu__row:hover {
    background: var(--sunk);
}

.list-menu__row--on {
    color: var(--accent);
    font-weight: 700;
}

.list-menu__row--new {
    border-top: 1px solid var(--line);
    margin-top: 4px;
    color: var(--ink-muted);
}

.settings__check {
    display: block;
    margin: 10px 0;
}

.info__votes--language {
    margin-left: 6px;
}

/* A browse listing is a grid rather than a scrolling row: it is the whole of
   one form, not a shelf of highlights. */
.recent--grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 16px;
    overflow: visible;
}

/* ---------- Fast import ---------- */

/* Shut by default: a document is how some people start an entry and how most
   never do, and an open textarea at the top of the form would read as the
   first thing to fill in. */
.import {
    margin: 0 0 14px;
    padding: 12px 14px;
    border-radius: var(--radius-md);
    background: var(--sunk);
}

.import > summary {
    font-weight: 750;
    cursor: pointer;
}

.import__box {
    display: block;
    width: 100%;
    margin: 8px 0;
    padding: 10px;
    border: none;
    border-radius: var(--radius-sm, 6px);
    background: var(--card);
    color: inherit;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.82rem;
    resize: vertical;
}

.import__tools,
.import__applying {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.import__applying:not(:empty) {
    margin-top: 10px;
}

.import__found {
    margin: 10px 0 0;
    font-weight: 650;
}

/* What could not be understood, said plainly and in full. Not a toast: a
   toast goes away, and these are the lines somebody has to go and fix. */
.import__warnings {
    margin: 6px 0 0;
    padding-left: 20px;
    color: var(--ink-muted);
    font-size: 0.85rem;
}

.import__warnings:empty {
    display: none;
}

.import__found[data-tone="bad"] {
    color: var(--danger, #b3261e);
}

/* Where a pending draft's facts came from. Quiet: it is a thing to know
   before reading, not an accusation. */
.report__badge {
    padding: 1px 7px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink-muted);
    font-size: 0.68rem;
    font-weight: 750;
    vertical-align: middle;
}

/* Where an other release lives on the web. Sits with the facts rather than
   apart from them: it is one more thing known about the release. */
.entry-link {
    color: var(--accent);
    font-weight: 650;
    text-decoration: underline;
    overflow-wrap: anywhere;
}

/* A link inside something somebody wrote. Wraps rather than overflowing:
   a long URL in a narrow reply would otherwise push the whole thread
   sideways. */
.said-link {
    color: var(--accent);
    text-decoration: underline;
    overflow-wrap: anywhere;
}

/* ---------- Levels ---------- */

/* What level somebody is, beside their name. Quiet on purpose: it is a
   thank-you, not a rank badge, and it sits in the middle of conversations. */
.level-badge {
    padding: 1px 7px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink-muted);
    font-size: 0.68rem;
    font-weight: 750;
    white-space: nowrap;
    vertical-align: middle;
}

/* The rank chips, beside the level and quieter than it.
   Two ladders, two chips, never added together: E is what somebody's work is
   multiplied by, C is what their commentary is. Only drawn above rank one,
   because everybody starts at one and naming it everywhere would be noise
   everywhere. Outlined rather than filled so a row with both still reads as a
   name with a level on it rather than a row of badges. */
.rank-chip {
    padding: 0 5px;
    border: 1px solid var(--line);
    border-radius: var(--pill);
    color: var(--ink-faint);
    font-size: 0.64rem;
    font-weight: 750;
    letter-spacing: 0.02em;
    white-space: nowrap;
    vertical-align: middle;
}

.rank-chip--speaks {
    border-style: dashed;
}

/* The higher the level, the warmer it reads. Enough to notice across a
   thread, not enough to shout.
   Banded in tens by the badge rather than written out ninety-nine times, and
   the bands are wide because the curve is not linear: a level 40 is already
   twenty-four thousand points of work. */
.level-badge[data-band="3"],
.level-badge[data-band="4"],
.level-badge[data-band="5"] {
    background: var(--accent-tint);
    color: var(--ink);
}

.level-badge[data-band="6"],
.level-badge[data-band="7"],
.level-badge[data-band="8"],
.level-badge[data-band="9"] {
    background: var(--accent);
    color: var(--on-accent, #fff);
}

/* How far along, on a profile and nowhere else. */
.level-ladder {
    flex: 1 1 100%;
    margin-top: 6px;
}

.level-ladder__said {
    margin: 0 0 4px;
    color: var(--ink-muted);
    font-size: 0.8rem;
}

.level-ladder__track {
    height: 6px;
    border-radius: var(--pill);
    background: var(--sunk);
    overflow: hidden;
}

.level-ladder__filled {
    height: 100%;
    border-radius: var(--pill);
    background: var(--accent);
}

/* --- The map: the same document drawn as boxes --- */

/* Which of the two views is showing. The form is hidden by this rather than by
   the hidden attribute, which narrowTo already uses on fieldsets for its own
   purpose — two features setting one flag is how one starts undoing the other. */
.entry-views-host[data-view="map"] .entry-native,
.entry-views-host[data-view="map"] .import-panel,
.entry-views-host[data-view="map"] .entry-jump {
    display: none;
}

.entry-views-host[data-view="form"] .map-workbench {
    display: none;
}

.entry-views {
    display: flex;
    gap: 0.25rem;
    margin: 0.75rem 0 0.5rem;
}

.entry-views__tab {
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--ink);
    border-radius: 999px;
    padding: 0.3rem 0.9rem;
    font-size: 0.9rem;
    cursor: pointer;
}

.entry-views__tab--on {
    background: var(--ink);
    color: var(--surface);
    border-color: var(--ink);
}

.map {
    position: relative;
    /* `clip`, not `hidden`. A hidden box is still a scroll container, and the
       browser scrolls one to reveal a focused child — so pressing a box near
       the right of a wide map scrolled the frame sideways by several hundred
       pixels, and every conversion between a map point and a screen point,
       all of which measure from the frame's own corner, was then wrong by
       exactly that much. `clip` makes the same cut without creating anything
       that can scroll. `hidden` first, for anything that does not know
       `clip`. */
    overflow: hidden;
    overflow: clip;
    height: min(70vh, 640px);
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--paper);
    /* The grid is the only thing that says the surface moved when it holds
       nothing yet: panning an empty map with no texture looks like a map that
       does not pan. */
    background-image:
        radial-gradient(circle at 1px 1px, var(--line) 1px, transparent 0);
    background-size: 24px 24px;
    touch-action: none;
    cursor: grab;
}

.map[data-panning="true"] {
    cursor: grabbing;
}

.map__stage {
    position: absolute;
    inset: 0;
    /* Every box and every wire is positioned from this corner, so one
       transform moves all of them together and they cannot drift apart. */
    transform-origin: 0 0;
    will-change: transform;
}

.map__wires {
    position: absolute;
    /* Big enough to hold a wide document without clipping a wire; the layer is
       transparent and catches nothing, so its size costs nothing. */
    width: 8000px;
    height: 8000px;
    overflow: visible;
}

.map-wire {
    fill: none;
    stroke: var(--line-strong);
    stroke-width: 2;
}

.map-wire--holds {
    stroke-dasharray: 5 4;
}

.map-node {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.15rem;
    box-sizing: border-box;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface);
    cursor: pointer;
    text-align: left;
}

.map-node:hover {
    border-color: var(--ink);
}

.map-node__badge {
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-muted);
    opacity: 0.7;
}

.map-node__name {
    font-size: 0.95rem;
    /* A long title must not push the box out of the layout it was placed in. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.map-node[data-kind="media"] {
    border-width: 2px;
    border-color: var(--ink);
}

/* Top-right, not bottom-right: .toast-region is fixed to the bottom-right of
   the window and every page on the site uses it, so the map is the thing that
   moves out of the way. */
.map__view {
    position: absolute;
    right: 0.75rem;
    top: 0.75rem;
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
}

.map__view-button {
    min-width: 2rem;
    border: 0;
    background: transparent;
    color: var(--ink);
    border-radius: 999px;
    padding: 0.25rem 0.5rem;
    font-size: 1rem;
    cursor: pointer;
}

.map__view-button--wide {
    font-size: 0.85rem;
}

.map__view-button:hover {
    background: var(--paper);
}

.map__zoom {
    min-width: 3.4rem;
    text-align: center;
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

/* The rail and the canvas, side by side. */
/* ---------- Diagrams somebody drew for themselves ---------- */

/* The canvas is the page here rather than one of two views of a form, so it
   gets the room the editor's map cannot have. */
.page--diagram .map {
    min-height: min(78vh, 760px);
}

.diagram-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.9rem;
}

.diagram-state {
    padding: 2px 10px;
    border-radius: var(--pill);
    background: var(--sunk);
    color: var(--ink-muted);
    font-family: var(--font-ui);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.diagram-state--public {
    background: var(--accent);
    color: #fff;
}

.diagram-box {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface);
    box-shadow: 0 1px 2px rgb(0 0 0 / 8%);
    /* The canvas arbitrates the gestures; a box must not scroll the page out
       from under a drag. The same rule the editor's map settled on. */
    touch-action: none;
}

/* A box whose subject has been unpublished, edited away or deleted. Kept
   rather than removed, and drawn as an outline so the shape of the argument
   survives the hole in it. */
.diagram-box--gone {
    border-style: dashed;
    background: none;
    color: var(--ink-muted);
}

.diagram-box__body {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 8px 10px;
    text-align: left;
    cursor: grab;
}

.diagram-box__picture {
    width: 100%;
    max-height: 120px;
    object-fit: cover;
    border-radius: 6px;
}

.diagram-box__title {
    margin: 0;
    font-family: var(--font-ui);
    font-size: 0.9rem;
    font-weight: 700;
    line-height: 1.25;
}

.diagram-box__under {
    margin: 0;
    color: var(--ink-muted);
    font-size: 0.8rem;
    line-height: 1.3;
}

/* The ⋯ is the only thing on the corner of a box now. It used to share the
   corner with a + that started a line — which also collided with the canvas's
   own + zoom button, close enough that a script aiming for one hit the other.
   Joining moved onto this menu, where the choice can be made from a list
   without both boxes having to be on screen at once. */
.diagram-box__menu {
    position: absolute;
    top: -10px;
    right: -10px;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 50%;
    background: var(--surface);
    color: var(--ink-muted);
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
}

.diagram-box__menu:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* The lines.

   These were 1.5px of --ink-muted, drawn inside the stage that the zoom
   scales — so at 41% they rendered at well under a pixel and the diagram
   looked like boxes floating unconnected. non-scaling-stroke is the fix that
   matters: the stroke keeps its width in screen pixels whatever the zoom, so
   a line is exactly as readable zoomed out as zoomed in. */
.diagram-wire {
    fill: none;
    stroke: var(--ink-muted);
    stroke-width: 2.5;
    vector-effect: non-scaling-stroke;
    stroke-linecap: round;
}

.diagram-wire__head {
    fill: var(--ink-muted);
}

/* The width you actually have to hit, under the width you see. Aiming at
   2.5px is hard with a mouse and hopeless with a thumb, and it was worse
   before: the press had to land on the hairline itself. */
.diagram-wire__grab {
    fill: none;
    stroke: transparent;
    stroke-width: 18;
    vector-effect: non-scaling-stroke;
    cursor: pointer;
}

.diagram-wire__grab:hover + .diagram-wire {
    stroke: var(--accent);
}

.diagram-wire__grab:hover + .diagram-wire + .diagram-wire__note {
    fill: var(--accent);
}

/* The label sits on the line and on the grid behind it, so it is given a
   halo of the page's own background rather than a box: painting the stroke
   under the fill leaves the words legible over whatever they cross. */
.diagram-wire__note {
    fill: var(--ink);
    stroke: var(--surface);
    stroke-width: 4;
    paint-order: stroke fill;
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    text-anchor: middle;
    paint-order: stroke;
}

.diagram-menu__list {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 16rem;
    /* Joining picks from every other box on the canvas, and a canvas can hold
       a lot of them — so the list scrolls rather than growing off the screen
       and taking its last few options with it. */
    max-height: min(60vh, 26rem);
    overflow-y: auto;
}

.diagram-menu__item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.1rem;
    text-align: left;
}

.diagram-menu__title {
    margin: 0 0 0.15rem;
    font-size: 0.95rem;
    font-weight: 700;
}

.diagram-menu__hint {
    margin: 0 0 0.6rem;
    color: var(--ink-muted);
    font-size: 0.8rem;
}

.diagram-menu__name {
    font-weight: 600;
}

/* What a box points at, under its name — two boxes called "Chapter 1" are
   told apart by what is under them and by nothing else. */
.diagram-menu__under {
    overflow: hidden;
    max-width: 20rem;
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 400;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.diagram-controls {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    margin: 0 0 0.8rem;
}

.diagram-controls .diagram-pick__search {
    flex: 1;
    min-width: 12rem;
    margin: 0;
}

.diagram-sort {
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
}

/* The picker: find the work, then say which part of it. */
.diagram-pick {
    width: min(30rem, 92vw);
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface);
    color: var(--ink);
}

.diagram-pick__search {
    width: 100%;
    margin: 0.6rem 0;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
}

.diagram-pick__list {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    max-height: 22rem;
    overflow-y: auto;
}

.diagram-pick__row {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 0.45rem 0.6rem;
    border: 0;
    border-radius: 8px;
    background: none;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.diagram-pick__row:hover,
.diagram-pick__row:focus-visible {
    background: var(--sunk);
}

.diagram-pick__name {
    font-family: var(--font-ui);
    font-weight: 600;
}

.diagram-pick__under,
.diagram-pick__hint {
    color: var(--ink-muted);
    font-size: 0.82rem;
}

.diagram-pick__actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 0.7rem;
}

.map-workbench {
    display: flex;
    gap: 0.75rem;
    align-items: stretch;
}

.map-workbench .map {
    flex: 1;
    /* Was overflowing the content column: the frame is a flex child now and
       takes what is left rather than claiming a width of its own. */
    min-width: 0;
}

.map-palette {
    flex: 0 0 auto;
    width: 9.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    padding: 0.7rem 0.6rem;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface);
    overflow-y: auto;
    max-height: min(70vh, 640px);
}

.map-palette__group {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.map-palette__heading {
    margin: 0 0 0.15rem;
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

.map-palette__item {
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--paper);
    color: var(--ink);
    padding: 0.38rem 0.55rem;
    font-size: 0.85rem;
    text-align: left;
    cursor: grab;
    /* A press here is a drag, so the browser must not decide it is a scroll. */
    touch-action: none;
    user-select: none;
}

.map-palette__item:hover {
    border-color: var(--ink);
}

.map-palette__item:active {
    cursor: grabbing;
}

/* What follows the pointer while something is being dragged out of the rail.
   Never under the pointer itself — elementFromPoint has to see the box being
   aimed at, not the thing doing the aiming. */
.map-palette__ghost {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 60;
    pointer-events: none;
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--ink);
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink);
    font-size: 0.85rem;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
    transform: translate(-9999px, -9999px);
}

/* While a row is being dragged, every box says whether it would take it —
   not only the one under the pointer. Answering "where does this go" before
   somebody has to aim is the whole reason to say anything at all. */
.map-node--can {
    border-color: var(--accent);
}

.map-node--cannot {
    opacity: 0.4;
}

/* And the one actually being aimed at. */
.map-node--target {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-glow);
}

.map-node--moving {
    cursor: grabbing;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
}

.map-node {
    /* A press on a box is a drag or a jump, never a scroll. */
    touch-action: none;
    user-select: none;
}

/* The handle a wire is pulled out of. Only on the two kinds the form can
   join — a port that pulls a wire nothing accepts is a crueller no than no
   port at all. */
.map-node__port {
    position: absolute;
    right: -7px;
    top: 50%;
    width: 14px;
    height: 14px;
    margin-top: -7px;
    border: 2px solid var(--accent);
    border-radius: 50%;
    background: var(--surface);
    cursor: crosshair;
    touch-action: none;
}

.map-node__port:hover {
    background: var(--accent);
}

/* What adapts what, against the dashed lines that say what holds what. */
.map-wire--adapts {
    stroke: var(--accent);
    stroke-width: 2;
}

/* Invisible and thick, over the thin visible line: a two-pixel curve is not
   something anybody can reliably hit, and a wire nobody can press is a wire
   nobody can cut. */
.map-wire__grab {
    fill: none;
    stroke: transparent;
    stroke-width: 14;
    cursor: pointer;
}

.map-wire__grab:hover + .map-wire--adapts,
.map-wire--adapts:hover {
    stroke-width: 3;
}

/* The one being pulled right now. */
.map-wire--pulling {
    stroke: var(--accent);
    stroke-width: 2;
    stroke-dasharray: 6 4;
}

.map-link__choices {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin: 0.6rem 0;
}

.map-link__choice {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    cursor: pointer;
}

.map-link__choice:hover {
    border-color: var(--ink);
}

.map-link__note {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-bottom: 0.4rem;
}

/* The box's three controls. The Open button fills it, so pressing anywhere
   that is not the port or the menu does what pressing the box always did. */
.map-node__open {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.15rem;
    width: 100%;
    padding: 0.5rem 1.6rem 0.5rem 0.7rem;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: inherit;
    text-align: left;
    cursor: pointer;
    font: inherit;
}

/* A row's box. Same shape as an entry's, quieter: a chapter is not a claim
   about the document the way a book is, and two hundred of them at an entry's
   weight would drown the handful of entries they belong to. */
.map-node[data-kind="volume"],
.map-node[data-kind="chapter"],
.map-node[data-kind="episode"],
.map-node[data-kind="arc"],
.map-node[data-kind="staff"],
.map-node[data-kind="character"] {
    background: var(--paper);
    border-style: dashed;
}

.map-node[data-kind="volume"] .map-node__name,
.map-node[data-kind="chapter"] .map-node__name,
.map-node[data-kind="episode"] .map-node__name,
.map-node[data-kind="arc"] .map-node__name,
.map-node[data-kind="staff"] .map-node__name,
.map-node[data-kind="character"] .map-node__name {
    font-size: 0.85rem;
}

/* A box with a picture in it. The picture runs down the left edge and the
   badge and the name sit beside it, so the box reads the way a shelf does.
   The width matches PICTURE_WIDTH in mapPlace.js, which is what the layout
   made room for. */
.map-node--pictured .map-node__open {
    flex-direction: row;
    align-items: stretch;
    gap: 0;
    padding: 0;
}

.map-node__picture {
    display: block;
    flex: none;
    width: 84px;
    height: 100%;
    border-radius: 9px 0 0 9px;
    background: var(--paper);
    /* Cropped rather than squashed: a cover is portrait, an episode still is
       landscape, and neither should be stretched into the other's shape. */
    object-fit: cover;
    pointer-events: none;
}

/* The words, as a column beside the picture. `min-width: 0` is what lets the
   name inside it truncate: without it a flex item refuses to shrink below its
   content and the name pushes the box open instead of ending in an ellipsis.
   The room on the right is what keeps a long name out from under the menu. */
.map-node__said {
    display: flex;
    flex: 1;
    min-width: 0;
    flex-direction: column;
    justify-content: center;
    gap: 0.15rem;
    padding: 0 1.6rem 0 0.6rem;
}

/* Over a picture rather than over the surface, so it is still findable. */
.map-node--pictured .map-node__menu {
    background: var(--surface);
}

/* Changing a box where it sits. Two or three fields and a picture — the panel
   is deliberately small, and looking like the form would be a promise it does
   not keep. */
/* An episode holds nine fields and a character seven, which is more than a
   dialog can show at once on a laptop. So the fields scroll and the name above
   them and the buttons below them stay put: scrolling the whole panel put Save
   out of sight, and a panel whose only way out is to scroll for it reads as a
   panel with no way out. */
.map-edit {
    display: flex;
    flex-direction: column;
    max-height: min(84vh, 44rem);
    overflow: hidden;
}

/* `min-height: 0` is what lets it shrink and scroll: a flex item will not go
   below its content's height without it, so the panel would grow past the
   ceiling instead of scrolling inside it. */
.map-edit__body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.map-edit .dialog__actions {
    flex: none;
    margin-top: 1rem;
    padding-top: 0.9rem;
    border-top: 1px solid var(--line);
}

.map-edit__field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-top: 0.7rem;
}

.map-edit__field[hidden] {
    display: none;
}

.map-edit textarea.field__input {
    resize: vertical;
}

.map-edit__heading {
    margin: 1rem 0 0.4rem;
    font-size: 0.8rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.map-edit__frame {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 120px;
    margin-bottom: 0.5rem;
    border: 1px dashed var(--line);
    border-radius: 10px;
    background: var(--paper);
    overflow: hidden;
}

.map-edit__preview {
    display: block;
    max-width: 100%;
    max-height: 220px;
}

.map-edit__empty {
    color: var(--ink-faint);
    font-size: 0.9rem;
}

/* The file input itself is never shown: its own button says "No file chosen"
   next to whatever label it is given, which is a second, contradictory answer
   to a question the preview above already answers. The label is the button. */
.map-edit__file {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.map-edit__choose {
    display: inline-block;
    cursor: pointer;
}

/* Visible to a keyboard, which reaches the hidden input rather than the label
   standing in for it. */
.map-edit__file:focus-visible + .map-edit__choose,
.map-edit__choose:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.map-edit__clear {
    margin-left: 0.5rem;
}

.map-edit__status {
    margin-top: 0.4rem;
    min-height: 1.2em;
    font-size: 0.85rem;
    color: var(--ink-muted);
}

/* A note: a box that is nothing but what somebody wrote on it. Tinted so a
   remark about the document never reads as part of the document. */
.map-node[data-kind="note"],
.map-node[data-kind="imageNote"] {
    background: var(--accent-tint);
    border-color: var(--accent);
    border-style: solid;
}

.map-node[data-kind="note"] .map-node__open,
.map-node[data-kind="imageNote"] .map-node__open {
    justify-content: flex-start;
    align-items: stretch;
    gap: 0.3rem;
}

/* The words themselves, wrapped as written. The box's height was computed
   from this text, so what will not fit is clipped rather than overflowing
   onto the canvas. */
.map-node__note {
    white-space: pre-wrap;
    overflow-wrap: break-word;
    overflow: hidden;
    font-size: 0.85rem;
    line-height: 19px;
}

.map-node__note-picture {
    display: block;
    flex: none;
    width: 100%;
    height: 132px;
    border-radius: 6px;
    background: var(--paper);
    object-fit: cover;
    pointer-events: none;
}

.map-node__note-empty {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    height: 132px;
    border: 1px dashed var(--accent);
    border-radius: 6px;
    color: var(--ink-faint);
    font-size: 0.8rem;
}

/* A port on every side of a note. The base port sits on the right edge; these
   move the other three to the middles of theirs. */
.map-node__port--n {
    top: -7px;
    right: auto;
    left: 50%;
    margin: 0 0 0 -7px;
}

.map-node__port--s {
    top: auto;
    bottom: -7px;
    right: auto;
    left: 50%;
    margin: 0 0 0 -7px;
}

.map-node__port--w {
    right: auto;
    left: -7px;
}

/* An entry to one of its own rows. The same dashed line as the media's, drawn
   lighter and thinner: on a real series there are two hundred of these against
   a handful of the others, and at equal weight they would be the only thing on
   the map anybody could see. */
.map-wire--holds-row {
    stroke-width: 1;
    opacity: 0.45;
}

/* A connection the document had no field for. Drawn unlike an adaptation on
   purpose: an adaptation is something the catalogue models, and one of these
   is something a person asserted. */
.map-wire--says {
    stroke: var(--ink-muted);
    stroke-width: 2;
    stroke-dasharray: 2 5;
    fill: none;
    opacity: 0.85;
}

.map-node__menu {
    position: absolute;
    top: 2px;
    right: 4px;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--ink-faint);
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
}

.map-node__menu:hover {
    background: var(--paper);
    color: var(--ink);
}

.map-menu__actions {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-top: 0.8rem;
}

.map-menu__action {
    width: 100%;
    text-align: left;
}

/* The bar that says something is waiting to be connected. A mode nobody can
   see they are in is a mode nobody can get out of. */
.map-linking {
    position: absolute;
    left: 50%;
    top: 0.75rem;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.4rem 0.5rem 0.4rem 0.8rem;
    border: 1px solid var(--accent);
    border-radius: 999px;
    background: var(--surface);
    font-size: 0.85rem;
    white-space: nowrap;
    z-index: 5;
}

.map-linking__stop {
    border: 0;
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink);
    padding: 0.2rem 0.6rem;
    font-size: 0.8rem;
    cursor: pointer;
}

/* The one waiting for a partner. */
.map-node--linking {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-glow);
}

/* On a narrow screen the rail becomes a strip above the canvas that scrolls
   sideways. Not a sheet: a sheet needs opening and closing, and it would
   cover the very canvas somebody is dragging onto. */
@media (max-width: 700px) {
    .map-workbench {
        flex-direction: column;
    }

    .map-palette {
        width: auto;
        max-height: none;
        flex-direction: row;
        gap: 0.5rem;
        overflow-x: auto;
        overflow-y: hidden;
        /* A strip is swiped along, so the browser may have this axis. */
        touch-action: pan-x;
    }

    .map-palette__group {
        flex-direction: row;
        align-items: center;
        gap: 0.3rem;
    }

    .map-palette__heading {
        margin: 0;
        position: sticky;
        left: 0;
    }

    .map-palette__item {
        white-space: nowrap;
    }

    .map {
        height: min(60vh, 460px);
    }
}

/* Full screen: the workbench covers the viewport, rail and canvas together —
   a palette you cannot reach is not a full-screen editor.

   Covering the viewport rather than the Fullscreen API, which paints only the
   fullscreen element and what is inside it. The toast region and the dialogs
   this map opens both live outside it, and both are load-bearing here.

   z-index 50 is settled by the stack rather than picked: .toast-region and
   .map-palette__ghost are at 60 and have to stay above; .pm is at 40 and the
   page chrome below that, and both belong behind. */
.map-workbench--full {
    position: fixed;
    inset: 0;
    z-index: 50;
    padding: 0.75rem;
    background: var(--paper);
    box-sizing: border-box;
}

.map-workbench--full .map {
    height: auto;
    align-self: stretch;
    border-radius: 10px;
}

.map-workbench--full .map-palette {
    max-height: none;
}

/* The page behind must not scroll under it. */
body.map-is-full {
    overflow: hidden;
}

@media (max-width: 700px) {
    .map-workbench--full .map {
        height: auto;
        flex: 1;
    }
}

/* The pages of a 同人本 — the one place on this site where the thing being
   catalogued is also the thing being shown.

   One page at a time in a frame that never grows past the viewport, and a
   strip of thumbnails under it, because a three-hundred-page book cannot be
   navigated by pressing Next three hundred times. The frame's background is
   dark rather than the page's paper: a scan is a photograph, and a white
   surround makes an off-white page look grubby. */
.doujin-pages {
    margin-top: 1.25rem;
}

.doujin-pages__heading {
    margin: 0 0 0.75rem;
    font-size: 1.15rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.doujin-pages__original {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    background: var(--accent-tint);
    color: var(--accent);
}

.doujin-pages__adult {
    font-size: 0.75rem
    font-weight: 600;
    letter-spacing: 0.02em;
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    background: var(--sakura-tint);
    color: var(--sakura);
    border: 1px solid var(--sakura-line);
}

.doujin-pages__frame {
    display: flex;
    justify-content: center;
    align-items: center;
    background: #1b1e26;
    border-radius: 10px;
    padding: 0.5rem;
    /* Tall enough to read and short enough that the bar under it stays on
       screen: a reader who has to scroll to reach Next is reading a website
       rather than a book. */
    max-height: 80vh;
    overflow: hidden;
}

.doujin-pages__sheet {
    max-width: 100%;
    max-height: 78vh;
    object-fit: contain;
    border-radius: 4px;
}

.doujin-pages__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    margin: 0.6rem 0;
}

/* Two pages at once, the way a book opens: each sheet gets half the frame
   and the pair sits together in the middle with a hair of a gutter. */
.doujin-pages__frame--spread {
    gap: 0.3rem;
}

.doujin-pages__frame--spread .doujin-pages__sheet {
    max-width: calc(50% - 0.15rem);
}

/* A written page: prose on the page's own ground rather than a scan on the
   dark one, at a measure that reads, scrolling inside the frame so the bar
   under it stays where a reader's thumb expects it. */
.doujin-pages__frame--text {
    align-items: flex-start;
    justify-content: center;
    background: var(--card);
    overflow-y: auto;
}

.doujin-pages__text {
    width: 100%;
    max-width: 40rem;
    padding: 1.5rem 1.25rem;
    color: var(--ink);
    font-size: 1.05rem;
    line-height: 1.75;
}

.doujin-pages__text p {
    margin: 0 0 1em;
}

/* A serial's way through its chapters, under the page bar. */
.doujin-pages__chapters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    margin: 0 0 0.6rem;
}

/* A written page's place in the strip: its number, since there is no
   picture to shrink. */
.doujin-pages__thumb-text {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background: var(--sunk);
    color: var(--ink-muted);
    font-family: var(--font-ui);
    font-size: 0.8rem;
    font-weight: 700;
}

/* Where the reader is: on the page bar by page, on the chapter line by
   chapter, worn the same. */
.doujin-pages__count,
.doujin-pages__chapter {
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
    min-width: 8rem;
    text-align: center;
}

.doujin-pages__strip-holder {
    margin-top: 0.2rem;
}

/* Sideways rather than wrapped: a strip that wraps into a wall of thumbnails
   is a contact sheet, and what is wanted here is a place in a sequence. */
.doujin-pages__strip {
    display: flex;
    gap: 0.4rem;
    overflow-x: auto;
    padding: 0.35rem 0;
    align-items: flex-start;
}

.doujin-pages__thumb {
    flex: 0 0 auto;
    width: 56px;
    height: 78px;
    padding: 0;
    border: 2px solid var(--line);
    border-radius: 4px;
    background: var(--sunk);
    overflow: hidden;
    cursor: pointer;
    /* The strip is dragged on a phone to reorder, so the browser must not
       take the gesture for a scroll first. The strip itself still scrolls
       with a flick anywhere it is not a thumbnail. */
    touch-action: none;
}

.doujin-pages__thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.doujin-pages__thumb--on {
    border-color: var(--accent);
}

.doujin-pages__thumb--carried {
    opacity: 0.5;
}

.doujin-pages__thumb--target {
    border-color: var(--sakura);
}

.doujin-pages__remove {
    color: var(--sakura);
}

.doujin-pages__report {
    display: block;
    margin-top: 0.5rem;
}

.doujin-pages__upload {
    margin-top: 0.9rem;
    padding: 0.9rem;
    border: 1px dashed var(--line-strong);
    border-radius: 10px;
    text-align: center;
}

.doujin-pages__upload--over {
    border-color: var(--accent);
    background: var(--accent-tint);
}

.doujin-pages__hint,
.doujin-pages__progress,
.doujin-pages__empty {
    margin: 0.5rem 0 0;
    color: var(--ink-muted);
    font-size: 0.9rem;
}

/* The refusal. Deliberately plain: it is not an error, it is the site
   declining to send something, and it says so in the server's own words. */
.doujin-pages__shut {
    padding: 1.25rem;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--sunk);
    text-align: center;
}

.doujin-pages__shut-line {
    margin: 0;
    font-weight: 600;
}

.doujin-pages__shut-count {
    margin: 0.35rem 0 0.6rem;
    color: var(--ink-muted);
}

@media (max-width: 700px) {
    .doujin-pages__bar {
        gap: 0.5rem;
    }

    .doujin-pages__count {
        min-width: 0;
        font-size: 0.85rem;
    }
}

/* The story's own order, behind a press.
   The one section on this site that hides itself. Closed it is a heading, a
   line saying what is behind it, and a button — enough to decide with, and
   nothing that spoils anything. */
.storyline__lead {
    color: var(--ink-muted);
    margin: 0 0 12px;
    max-width: 62ch;
}

.storyline__reveal {
    align-self: flex-start;
}

.storyline__held[hidden] {
    display: none;
}

.storyline__beats {
    display: flex;
    flex-direction: column;
    gap: 2px;
    list-style: none;
    margin: 16px 0 0;
    padding: 0;
    counter-reset: beat;
}

/* Numbered by the story's own order rather than by the row's place in the
   list, because those are the same number here and the number is the point. */
.storyline__beat {
    align-items: baseline;
    border-top: 1px solid var(--line);
    counter-increment: beat;
    display: grid;
    gap: 2px 14px;
    padding: 10px 0 10px 34px;
    position: relative;
}

.storyline__beat::before {
    color: var(--ink-faint);
    content: counter(beat);
    font-size: 0.82rem;
    font-variant-numeric: tabular-nums;
    left: 0;
    position: absolute;
    top: 10px;
}

@media (min-width: 620px) {
    .storyline__beat {
        grid-template-columns: 190px 1fr;
    }
}

/* The story's clock, in whatever words it was written in. */
.storyline__when {
    color: var(--ink-muted);
    font-size: 0.86rem;
    line-height: 1.5;
}

.storyline__what {
    line-height: 1.55;
}

/* Only drawn where the telling and the happening disagree, so it is always
   carrying information rather than repeating the row number back. */
.storyline__told {
    color: var(--ink-faint);
    font-size: 0.78rem;
    grid-column: 2;
}

@media (max-width: 619px) {
    .storyline__told {
        grid-column: 1;
    }
}

/* A beat that carries more than its name: how it happens, who is in it, and a
   picture of the moment. */
.storyline__picture {
    border-radius: 6px;
    display: block;
    grid-column: 2;
    margin-bottom: 8px;
    max-height: 220px;
    max-width: 100%;
    object-fit: cover;
}

@media (max-width: 619px) {
    .storyline__picture {
        grid-column: 1;
    }
}

.storyline__about {
    color: var(--ink-muted);
    font-size: 0.9rem;
    line-height: 1.55;
    margin: 4px 0 0;
    max-width: 60ch;
}

/* Who it happens to, off the wires somebody drew on the map. */
.storyline__who {
    color: var(--ink-faint);
    font-size: 0.8rem;
    margin: 6px 0 0;
}

.storyline__name {
    color: var(--ink-muted);
    font-weight: 600;
}

.storyline__note {
    font-style: italic;
}

.storyline__title {
    display: block;
    line-height: 1.55;
}

/* ---------- Fan art ---------- */

/* A grid of pictures rather than a shelf of covers: a drawing is any shape,
   and squares side by side read as a gallery. */
.art {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 14px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.art__open {
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 100%;
    padding: 0;
    background: none;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.art__picture {
    display: block;
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-md);
    background: var(--sunk);
    transition: transform 0.16s ease, box-shadow 0.16s ease;
}

.art__open:hover .art__picture {
    transform: translateY(-3px);
    box-shadow: var(--shadow-lift);
}

.art__title {
    font-family: var(--font-ui);
    font-size: 0.86rem;
    font-weight: 650;
    line-height: 1.35;
}

.art__meta,
.art__withheld {
    font-family: var(--font-ui);
    font-size: 0.74rem;
    line-height: 1.35;
    color: var(--ink-muted);
}

.art__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 4px;
}

.art__withheld {
    margin: 12px 0 0;
}

/* The picture large, on the dark ground scans get. */
.art__light {
    max-width: min(92vw, 1100px);
    padding: 12px;
    border: none;
    border-radius: var(--radius-lg);
    background: #1b1e26;
    color: #fff;
}

.art__light::backdrop {
    background: rgb(0 0 0 / 70%);
}

.art__light img {
    display: block;
    max-width: 100%;
    max-height: 80vh;
    margin: 0 auto;
    object-fit: contain;
    border-radius: 4px;
}

.art__light-caption {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 10px;
    font-size: 0.9rem;
}

/* The small form that adds one, under the grid. */
.art__form {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 12px 16px;
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px dashed var(--line-strong);
}

.art__form-actions {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 16px;
}

.art__adult {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.9rem;
}

.cast__art {
    margin-top: 4px;
}

/* --- The public status page --- */

/* The five services first, then the numbers, then the pictures: the rows
   keep the admin page's tiles and charts and only the order is the page's
   own. */
.status-page > .status-tiles + .status-tiles {
    margin-top: -6px;
}

/* The machine, folded: the question the page answers is whether the site
   works, and how busy the box is answers it last. */
.status-group {
    margin-top: 14px;
    border-top: 1px solid var(--line);
    padding-top: 8px;
}

.status-group__summary {
    cursor: pointer;
    padding: 6px 0 10px;
    font-weight: 600;
    color: var(--ink-soft, inherit);
}

.status-group[open] .status-group__summary {
    margin-bottom: 4px;
}

/* --- Agent accounts --- */

/* Said beside the name wherever the name has a level beside it. Filled and
   a little louder than the rank chips, because a reader should know at a
   glance that a machine wrote this. */
.agent-chip {
    padding: 1px 7px;
    border-radius: var(--pill);
    background: var(--warning-tint);
    color: var(--warning);
    font-size: 0.66rem;
    font-weight: 750;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    vertical-align: middle;
}

.admin-tag--agent {
    background: var(--warning-tint);
    color: var(--warning);
}

/* A key, shown once. */
.admin-key {
    margin-top: 14px;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--sunk);
}

.admin-key__said {
    margin: 0 0 6px;
    font-size: 0.92rem;
    color: var(--ink-muted);
}

.admin-key__value {
    display: block;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.9rem;
    word-break: break-all;
    user-select: all;
}

.person__run-by {
    margin-top: 4px;
}
