/* maybemattis.de – base styles (own minimal CSS, no framework).
   Design tokens as custom properties; shared by the public site and the
   inventory screens. Phase 19 gave the tokens a second set of values for the
   dark theme — style through the tokens, never with a literal colour, or the
   element will keep its light-theme colour on a dark ground. */

/* Type ---------------------------------------------------------------------
   Self-hosted, not linked from Google: a German site must not hand every
   visitor's IP address to a third party for a font (E59). Both faces are
   variable, so one file per script covers every weight we use — Archivo also
   brings a width axis, which the wordmark uses.

   Paths are relative to this file on purpose: collectstatic rewrites them to
   the hashed names, so nothing here has to know about {% static %}. */

@font-face {
    font-family: "Archivo";
    font-style: normal;
    font-weight: 400 700;
    font-stretch: 62% 125%;
    font-display: swap;
    src: url("../fonts/archivo-latin.de2f1fe81758.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
        U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Archivo";
    font-style: normal;
    font-weight: 400 700;
    font-stretch: 62% 125%;
    font-display: swap;
    src: url("../fonts/archivo-latin-ext.589f7bc0fac4.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

@font-face {
    font-family: "Newsreader";
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url("../fonts/newsreader-latin.f94a697b1650.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
        U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Newsreader";
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url("../fonts/newsreader-latin-ext.2e01db0610b1.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

@font-face {
    font-family: "Newsreader";
    font-style: italic;
    font-weight: 300 700;
    font-display: swap;
    src: url("../fonts/newsreader-italic-latin.e97067eb043b.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
        U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Newsreader";
    font-style: italic;
    font-weight: 300 700;
    font-display: swap;
    src: url("../fonts/newsreader-italic-latin-ext.43736a955d60.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

/* Tokens: the light theme ---------------------------------------------------
   The bare :root carries the complete palette. The dark blocks further down
   redefine only what changes, so a token that is missing there still resolves. */

:root {
    color-scheme: light;

    /* Palette. The ground is a cool paper rather than a warm one: blue, green
       and red have to sit on it side by side, and a warm ground pulls the green
       towards olive. */
    --color-bg: #f2f2f5;
    --color-surface: #ffffff;
    --color-surface-2: #e9e9ef;
    --color-border: #dcdce4;
    --color-text: #1b1b21;
    --color-text-muted: #63636f;
    --color-accent: #54468c;
    --color-accent-strong: #3d3268;
    --color-accent-soft: #eae7f4;
    /* Text on a filled accent surface — flips to near-black in the dark theme,
       where the accent itself is the light colour. */
    --color-on-accent: #ffffff;
    --color-danger: #a3341f;
    --color-danger-soft: #fdf1ee;
    --color-ok: #2f7d4f;
    --color-ok-soft: #e4f2e9;
    /* The three verdict bands (E118). Green, amber and rust are the same three
       the rest of the page already speaks in — a mark is not a fourth palette.
       Every one of them is dark enough here to carry white type, which is what
       --color-on-verdict is for. */
    --color-verdict-high: #286d43;
    --color-verdict-mid: #8a5a12;
    --color-verdict-low: #a3341f;
    --color-on-verdict: #ffffff;
    /* A hairline that reads as a rule, not as a box edge. */
    --color-rule: #cfcfda;

    /* Typography. Archivo sets everything that is looked at — headings, the
       wordmark, labels, numbers; Newsreader sets everything that is read. */
    --font-display: "Archivo", system-ui, -apple-system, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
    --font-body: "Newsreader", Georgia, "Times New Roman", serif;
    --font-sans: var(--font-display);
    --font-mono: ui-monospace, "SFMono-Regular", "Cascadia Code", Consolas,
        "Liberation Mono", monospace;

    /* Type scale, a fifth apart at the top and a whole tone at the bottom. */
    --text-xs: 0.78rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.375rem;
    --text-2xl: 1.75rem;
    --text-3xl: clamp(2rem, 1.4rem + 2.4vw, 3.1rem);

    /* Spacing / shape */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-6: 1.5rem;
    --space-8: 2rem;
    --space-10: 2.5rem;
    --space-12: 3rem;
    --radius: 8px;
    --radius-sm: 5px;
    --shadow: 0 1px 2px rgba(20, 20, 30, 0.06), 0 2px 8px rgba(20, 20, 30, 0.04);
    /* The page is as wide as the site; running text is as wide as one can
       comfortably read. Everything else hangs between the two. */
    --content-width: 76rem;
    --measure: 34rem;
    /* The sticky header's own height — what anything else that sticks has to
       clear. Logo (2.25rem) plus its padding and the hairline. */
    --header-height: 4.35rem;
}

/* Tokens: the dark theme ----------------------------------------------------
   Two blocks with identical bodies, and both are needed. The first serves the
   reader who never touched the switch and whose system says dark — guarded so
   an explicit "light" still wins. The second serves the switch itself. */

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;
        --color-bg: #131318;
        --color-surface: #1b1b22;
        --color-surface-2: #24242d;
        --color-border: #33333f;
        --color-text: #e7e7ee;
        --color-text-muted: #9a9aa8;
        --color-accent: #a99ae0;
        --color-accent-strong: #c5b9f0;
        --color-accent-soft: #262034;
        --color-on-accent: #16161c;
        --color-danger: #ef9384;
        --color-danger-soft: #2f1a16;
        --color-ok: #6fc08e;
        --color-ok-soft: #15291d;
        --color-verdict-high: #6fc08e;
        --color-verdict-mid: #d3a44e;
        --color-verdict-low: #ef9384;
        --color-on-verdict: #16161c;
        --color-rule: #3a3a47;
        --shadow: none;
    }
}

:root[data-theme="dark"] {
    color-scheme: dark;
    --color-bg: #131318;
    --color-surface: #1b1b22;
    --color-surface-2: #24242d;
    --color-border: #33333f;
    --color-text: #e7e7ee;
    --color-text-muted: #9a9aa8;
    --color-accent: #a99ae0;
    --color-accent-strong: #c5b9f0;
    --color-accent-soft: #262034;
    --color-on-accent: #16161c;
    --color-danger: #ef9384;
    --color-danger-soft: #2f1a16;
    --color-ok: #6fc08e;
    --color-ok-soft: #15291d;
    --color-verdict-high: #6fc08e;
    --color-verdict-mid: #d3a44e;
    --color-verdict-low: #ef9384;
    --color-on-verdict: #16161c;
    --color-rule: #3a3a47;
    --shadow: none;
}

/* Section colour worlds -----------------------------------------------------
   MaybeGames reads blue, MaybeMusic green, MaybeMe red. Only the three section
   pages and their reviews carry a tint (base.html sets data-section there); the
   frontpage, toplists and inventory keep the neutral accent. The selector is
   not tied to <body>: on the mixed frontpage grid each card opens its own
   world, so blue and green stand next to each other. Every accented
   element derives from these three tokens, so nav, links, badges, buttons and
   focus rings shift together with the world.

   Each world needs its dark values too: a mid-tone blue that reads well on
   paper disappears on a dark ground, so the dark set is lifted, not inverted. */

/* Brand hex, straight from Mattis: games blue, music green, me red. Light mode
   takes them as given — the strong/soft pair is the usual 0.77x-darken and
   high-L tint of that same hue. */
[data-section="maybegames"] {
    --color-accent: #1f5fbf;
    --color-accent-strong: #184993;
    --color-accent-soft: #e3e9f2;
}

[data-section="maybemusic"] {
    --color-accent: #357960;
    --color-accent-strong: #295d4a;
    --color-accent-soft: #e3f2ed;
}

[data-section="maybeme"] {
    --color-accent: #9b1c1c;
    --color-accent-strong: #771616;
    --color-accent-soft: #f2e3e3;
}

/* The brand hex themselves fail here (2.3–3.6:1 against the dark ground) — a
   mid-tone blue, green or red that reads well on paper disappears on a dark
   ground, so the dark set is the same hue lifted, not the light set inverted. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) [data-section="maybegames"] {
        --color-accent: #90acd5;
        --color-accent-strong: #bccde6;
        --color-accent-soft: #1b2432;
    }

    :root:not([data-theme="light"]) [data-section="maybemusic"] {
        --color-accent: #90d5bc;
        --color-accent-strong: #bce6d7;
        --color-accent-soft: #1b3229;
    }

    :root:not([data-theme="light"]) [data-section="maybeme"] {
        --color-accent: #d59090;
        --color-accent-strong: #e6bcbc;
        --color-accent-soft: #321b1b;
    }
}

:root[data-theme="dark"] [data-section="maybegames"] {
    --color-accent: #90acd5;
    --color-accent-strong: #bccde6;
    --color-accent-soft: #1b2432;
}

:root[data-theme="dark"] [data-section="maybemusic"] {
    --color-accent: #90d5bc;
    --color-accent-strong: #bce6d7;
    --color-accent-soft: #1b3229;
}

:root[data-theme="dark"] [data-section="maybeme"] {
    --color-accent: #d59090;
    --color-accent-strong: #e6bcbc;
    --color-accent-soft: #321b1b;
}

/* Base --------------------------------------------------------------------- */

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

body {
    margin: 0;
    /* The interface is set in Archivo; the places where something is actually
       read — a review, a teaser, a description — ask for --font-body. */
    font-family: var(--font-sans);
    font-size: 16px;
    line-height: 1.5;
    color: var(--color-text);
    background: var(--color-bg);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

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

a:hover {
    text-decoration: underline;
}

h1,
h2,
h3 {
    font-family: var(--font-display);
    line-height: 1.15;
    letter-spacing: -0.015em;
    text-wrap: balance;
    margin: 0 0 var(--space-3);
}

h1 {
    font-size: var(--text-2xl);
    font-weight: 700;
}

h2 {
    font-size: var(--text-xl);
    font-weight: 600;
}

/* Digits that belong in a column stay in a column. */
.rating,
.eyebrow,
td,
th {
    font-variant-numeric: tabular-nums;
}

/* Keyboard users get to the content without walking the whole nav. It only
   shows up once it has focus. */
.skip-link {
    position: absolute;
    left: var(--space-2);
    top: -4rem;
    z-index: 10;
    background: var(--color-surface);
    color: var(--color-accent);
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    transition: top 120ms ease-out;
}

.skip-link:focus {
    top: var(--space-2);
}

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

/* Motion on the pictures ---------------------------------------------------
   A picture that answers the pointer makes a grid of covers feel like a shelf
   you can pick from. It is one slow zoom inside a fixed frame, not a preview
   that starts playing: the page is a magazine, and something moving on its own
   would compete with the reading (E98). Only for pointers that can hover, so a
   touch screen does not get a state it cannot leave. */
@media (hover: hover) {
    .card-review__frame,
    .lead__frame,
    .card-album__frame {
        overflow: hidden;
        border-radius: var(--radius-sm);
    }

    .lead__frame {
        border-radius: var(--radius);
    }

    .card-review__image,
    .card-review__art,
    .lead__image,
    .lead__art,
    .card-album__cover {
        transition: transform 700ms cubic-bezier(0.2, 0.7, 0.2, 1);
    }

    .card-review:hover .card-review__image,
    .card-review:hover .card-review__art,
    .lead__media:hover .lead__image,
    .lead__media:hover .lead__art,
    .card-album__link:hover .card-album__cover {
        transform: scale(1.06);
    }

    /* The blurred ground moves the other way, so the artwork appears to come
       forward instead of the whole tile swelling. */
    .card-review__backdrop,
    .lead__backdrop {
        transition: transform 700ms cubic-bezier(0.2, 0.7, 0.2, 1);
    }

    .card-review:hover .card-review__backdrop,
    .lead__media:hover .lead__backdrop {
        transform: scale(1.28);
    }
}

/* The global reduced-motion rule only shortens transitions; a zoom would still
   arrive, just instantly. Here it is the movement itself that is unwanted. */
@media (prefers-reduced-motion: reduce) {
    .card-review:hover .card-review__image,
    .card-review:hover .card-review__art,
    .lead__media:hover .lead__image,
    .lead__media:hover .lead__art,
    .card-album__link:hover .card-album__cover {
        transform: none;
    }

    .card-review:hover .card-review__backdrop,
    .lead__media:hover .lead__backdrop {
        transform: scale(1.2);
    }
}

/* Layout ------------------------------------------------------------------ */

/* The header sits on the page's own ground and is held by a rule, not by a
   surface: on a reading page the chrome should not look like a second card.
   It stays put while the page scrolls, so the nav and the theme switch are
   always in reach — a solid background keeps content from showing through. */
.site-header {
    position: sticky;
    top: 0;
    z-index: 10;
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-rule);
}

.site-header__inner {
    max-width: var(--content-width);
    margin: 0 auto;
    padding: var(--space-4);
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-6);
}

/* The wordmark is the site's own logo, so it is an image rather than type. It
   sits on the baseline of the row it shares with the navigation. */
.site-header__brand {
    display: block;
    line-height: 0;
    /* Two lines of type in one image: it has no useful baseline to share with
       the navigation, so it centres against the row instead. */
    align-self: center;
}

.site-header__brand:hover {
    text-decoration: none;
}

.site-header__logo {
    display: block;
    width: auto;
    height: 2.25rem;
}

/* Black on light ground, white on dark. Both files are in the page; the choice
   is made here, so the theme switch needs no second request. Written for all
   three theme states: bare :root is light, the media query covers the system
   default, the attribute covers an explicit choice (E59). */
.site-header__logo--dark {
    display: none;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .site-header__logo--light {
        display: none;
    }

    :root:not([data-theme="light"]) .site-header__logo--dark {
        display: block;
    }
}

:root[data-theme="dark"] .site-header__logo--light {
    display: none;
}

:root[data-theme="dark"] .site-header__logo--dark {
    display: block;
}

.site-nav {
    display: flex;
    gap: var(--space-4);
    align-items: baseline;
    /* In the middle widths — a logged-in staff user around 800px — the entries
       can still outgrow their share of the row. They scroll inside it rather
       than pushing the page sideways. */
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 2px;
}

.site-nav::-webkit-scrollbar {
    display: none;
}

/* On a phone the entries do not fit beside the wordmark at all, and a row that
   silently scrolls hides "Reihen" with nothing to say so. The nav takes a line
   of its own under the wordmark — after the switch, which stays on the first
   line — and wraps there. */
@media (max-width: 46rem) {
    .site-nav {
        order: 2;
        flex: 1 0 100%;
        flex-wrap: wrap;
        row-gap: var(--space-2);
        overflow-x: visible;
    }
}

.site-nav a {
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    white-space: nowrap;
    padding-bottom: var(--space-1);
    border-bottom: 2px solid transparent;
}

.site-nav a:hover {
    color: var(--color-text);
    text-decoration: none;
}

/* The section's own nav entry stays marked while its world is tinted. */
.site-nav a[aria-current="page"] {
    color: var(--color-accent);
    border-bottom-color: var(--color-accent);
}

/* The search field in the header (E85). It shrinks to the magnifier's width
   on a phone and grows into the row's spare space from tablet up. */
.site-search {
    display: flex;
    align-items: stretch;
    gap: var(--space-1);
    align-self: center;
}

.site-search__input {
    width: 7rem;
    min-width: 0;
    padding: var(--space-1) var(--space-2);
    border: 1px solid var(--color-rule);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    font-family: var(--font-display);
    font-size: var(--text-sm);
}

@media (min-width: 52rem) {
    .site-search__input {
        width: 11rem;
    }
}

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

.site-search__submit {
    display: flex;
    align-items: center;
    padding: 0 var(--space-1);
    border: 0;
    background: none;
    color: var(--color-text-muted);
    cursor: pointer;
}

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

.site-search__submit svg {
    width: 1.1rem;
    height: 1.1rem;
}

/* The same field, page-sized, for a reader who arrived on /suche/ directly. */
.search-form {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-8);
}

.search-form__input {
    flex: 1 1 18rem;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--color-rule);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    font-family: var(--font-display);
    font-size: var(--text-lg);
}

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

.site-header__spacer {
    margin-left: auto;
}

.site-header__user {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

.site-header__lang a {
    font-size: var(--text-sm);
}

/* The theme switch. It is a three-way control — system, light, dark — but only
   ever shows the state it would move to, so it stays one small button. */
.theme-toggle {
    background: none;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    font: inherit;
    font-size: var(--text-sm);
    line-height: 1;
    padding: var(--space-2);
    cursor: pointer;
}

.theme-toggle:hover {
    color: var(--color-text);
    border-color: var(--color-text-muted);
}

.theme-toggle__icon {
    display: block;
    width: 1rem;
    height: 1rem;
}

/* Only one of the two is ever shown, and which one depends on the theme that
   is actually in force — including the un-stamped "system" state. */
.theme-toggle__icon--moon {
    display: none;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-toggle__icon--sun {
        display: none;
    }

    :root:not([data-theme="light"]) .theme-toggle__icon--moon {
        display: block;
    }
}

:root[data-theme="dark"] .theme-toggle__icon--sun {
    display: none;
}

:root[data-theme="dark"] .theme-toggle__icon--moon {
    display: block;
}

/* Without JavaScript the button could not do anything, so it is not there. */
:root:not(.has-js) .theme-toggle {
    display: none;
}

.linkbutton {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--color-accent);
    cursor: pointer;
}

.linkbutton:hover {
    text-decoration: underline;
}

main {
    max-width: var(--content-width);
    margin: 0 auto;
    padding: var(--space-8) var(--space-4) var(--space-12);
}

.site-footer {
    border-top: 1px solid var(--color-rule);
    margin-top: var(--space-12);
}

.site-footer__inner {
    max-width: var(--content-width);
    margin: 0 auto;
    padding: var(--space-6) var(--space-4);
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--space-4) var(--space-6);
}

.site-footer__nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

/* Messages ---------------------------------------------------------------- */

.messages {
    list-style: none;
    margin: 0 0 var(--space-4);
    padding: 0;
}

.messages li {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--color-accent-soft);
    margin-bottom: var(--space-2);
}

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

.button {
    display: inline-block;
    padding: var(--space-2) var(--space-4);
    background: var(--color-accent);
    color: var(--color-on-accent);
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-sm);
    font: inherit;
    cursor: pointer;
    text-align: center;
}

.button:hover {
    background: var(--color-accent-strong);
    border-color: var(--color-accent-strong);
    text-decoration: none;
}

.button--ghost {
    background: transparent;
    color: var(--color-accent);
}

.button--ghost:hover {
    background: var(--color-accent-soft);
    color: var(--color-accent-strong);
}

input[type="text"],
input[type="search"],
input[type="password"],
select {
    font: inherit;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
}

/* One focus ring for everything that takes focus — including the plain
   <button>s and the FAQ's <summary>, which lost the UA outline along with the
   marker. */
input:focus,
select:focus,
textarea:focus,
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 1px;
}

label {
    font-size: 0.85rem;
    color: var(--color-text-muted);
    display: block;
    margin-bottom: var(--space-1);
}

/* Card -------------------------------------------------------------------- */

.card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: var(--space-6);
}

/* Login ------------------------------------------------------------------- */

.auth {
    max-width: 24rem;
    margin: var(--space-8) auto;
}

.auth .card {
    display: grid;
    gap: var(--space-3);
}

.auth p {
    margin: 0;
}

.auth input {
    width: 100%;
}

.form-error {
    color: var(--color-danger);
}

.form-success {
    color: var(--color-ok);
}

.form-hint,
.auth .form-hint {
    font-size: 0.8rem;
    color: var(--color-text-muted);
    margin: var(--space-1) 0 0;
}

/* Single sign-on (phase 16). Only rendered while the feature is switched on. */

.auth__sso {
    display: block;
    width: 100%;
}

/* Qualified with .auth on purpose: `.auth p { margin: 0 }` above is more
   specific than a bare class and would otherwise eat the spacing. */

.auth .auth__divider {
    text-align: center;
    font-size: 0.85rem;
    color: var(--color-text-muted);
    margin: var(--space-4) 0;
}

.auth .auth__aside {
    font-size: 0.85rem;
    color: var(--color-text-muted);
    margin-top: var(--space-4);
    text-align: center;
}

/* Filter bar -------------------------------------------------------------- */

.filters {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: var(--space-4);
    margin-bottom: var(--space-6);
}

.filters__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--space-3);
    align-items: end;
}

.filters__grid input,
.filters__grid select {
    width: 100%;
}

.filters__actions {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-3);
}

.result-meta {
    color: var(--color-text-muted);
    font-size: 0.9rem;
    margin-bottom: var(--space-3);
}

/* Table ------------------------------------------------------------------- */

.table-wrap {
    overflow-x: auto;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

table.games {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.92rem;
}

table.games th,
table.games td {
    text-align: left;
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--color-border);
    vertical-align: middle;
    white-space: nowrap;
}

table.games th {
    background: var(--color-surface-2);
    font-weight: 600;
    color: var(--color-text-muted);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

table.games tbody tr:hover {
    background: var(--color-accent-soft);
}

table.games td.name {
    white-space: normal;
    min-width: 14rem;
    font-weight: 500;
}

/* Badges ------------------------------------------------------------------ */

.badge {
    display: inline-block;
    padding: 0.05rem var(--space-2);
    border-radius: 999px;
    font-size: 0.75rem;
    line-height: 1.5;
    background: var(--color-surface-2);
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
}

.badge--accent {
    background: var(--color-accent-soft);
    color: var(--color-accent-strong);
    border-color: transparent;
}

.badge--ok {
    background: var(--color-ok-soft);
    color: var(--color-ok);
    border-color: transparent;
}

.badge-row {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--space-1);
}

.muted {
    color: var(--color-text-muted);
}

.rating {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* Pagination -------------------------------------------------------------- */

.pagination {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    justify-content: center;
    margin-top: var(--space-6);
}

.pagination__current {
    color: var(--color-text-muted);
    font-size: 0.9rem;
}

/* Detail ------------------------------------------------------------------ */

.detail-header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-3);
    margin-bottom: var(--space-2);
}

.detail-header .year {
    color: var(--color-text-muted);
    font-size: 1.2rem;
    font-weight: 400;
}

/* A year without a day: the record is stale until the date arrives (E128). */
.year--stale {
    color: var(--color-text-muted);
    text-decoration: underline dotted;
    text-underline-offset: 0.2em;
}

.detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-4);
}

.detail-section {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: var(--space-4);
}

.detail-section h2 {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-text-muted);
}

.deflist {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--space-2) var(--space-4);
    margin: 0;
}

.deflist dt {
    color: var(--color-text-muted);
}

.deflist dd {
    margin: 0;
}

.play-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    font: inherit;
    cursor: pointer;
}

.play-toggle[aria-pressed="true"] {
    background: var(--color-ok-soft);
    border-color: transparent;
    color: var(--color-ok);
    font-weight: 600;
}

.back-link {
    display: inline-block;
    margin-bottom: var(--space-4);
    font-size: 0.9rem;
}

/* Responsive: stacked table on narrow screens ----------------------------- */

@media (max-width: 640px) {
    table.games thead {
        display: none;
    }

    table.games,
    table.games tbody,
    table.games tr,
    table.games td {
        display: block;
        width: 100%;
    }

    table.games tr {
        border: 1px solid var(--color-border);
        border-radius: var(--radius);
        margin-bottom: var(--space-3);
        padding: var(--space-2);
    }

    table.games td {
        border: none;
        white-space: normal;
        display: flex;
        justify-content: space-between;
        gap: var(--space-4);
        padding: var(--space-1) var(--space-2);
    }

    table.games td::before {
        content: attr(data-label);
        color: var(--color-text-muted);
        font-size: 0.8rem;
        text-transform: uppercase;
        letter-spacing: 0.03em;
    }

    table.games td.name::before {
        content: "";
    }
}

/* Blog: frontpage, feeds and cards (phase 19) -------------------------------
   No boxes. A card is a picture, a line of small caps, a title and a sentence,
   held apart by space; the grid does the aligning. Where something has to be
   fenced off it gets a rule, not a border and a shadow. */

/* The small line above a title: what kind of thing this is, and when. */
.eyebrow {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2) var(--space-3);
    margin: 0 0 var(--space-2);
    font-family: var(--font-display);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.eyebrow__kind {
    color: var(--color-accent);
}

/* The verdict — a word for a game, a number for an album. Off the picture and
   in the flow (E68 follow-up), it wants a magazine pull-quote's weight to
   still read as a verdict and not just another line of body copy. */
.verdict {
    margin: var(--space-3) 0 0;
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    /* The accent is the fallback, not the rule: a mark this site recognises
       gets its band colour below, and only an unknown one stays violet. */
    color: var(--color-accent);
}

/* The three bands (E118). Off a picture they colour the type; on one they fill
   the seal, which is why the fill is set on the same class further down. The
   number carries the exact value — the colour only says which third it is in,
   so this never has to be read as a fourth digit. */
.verdict--high {
    color: var(--color-verdict-high);
}

.verdict--mid {
    color: var(--color-verdict-mid);
}

.verdict--low {
    color: var(--color-verdict-low);
}

/* The number itself drops the uppercase tracking — that spacing is for
   letters, not digits — and sets the scale of the whole line. Without the
   "/5" beside it, it is the whole mark and can carry a little more weight. */
.verdict .rating {
    font-size: var(--text-2xl);
    font-weight: 800;
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
}

/* Stamped into the corner of the picture instead of set below the teaser (E68).
   One family, two shapes: a number gets a disc, a word gets a tab, because
   "Nicht empfohlen" cannot be squeezed into a circle. It is the one filled
   surface the public pages keep — a mark on a picture, not a panel (E60). */
.verdict--seal {
    position: absolute;
    right: var(--space-3);
    bottom: var(--space-3);
    z-index: 1;
    margin: 0;
    background: var(--color-accent);
    color: var(--color-bg);
    /* A picture can be any colour behind it; the ring in the page ground keeps
       the seal readable wherever it happens to land. The soft shadow after it
       is what actually lifts the seal off the picture — the ring alone reads
       as a flat sticker. */
    box-shadow:
        0 0 0 3px var(--color-bg),
        0 4px 10px rgb(0 0 0 / 0.28);
    transition: transform 0.15s ease;
}

.verdict--seal:not(.verdict--word) {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 3.5rem;
    height: 3.5rem;
    border-radius: 50%;
    line-height: 1;
    letter-spacing: 0;
}

/* On a picture the band is the fill, not the type — a coloured word on a
   coloured disc would be unreadable, so the type flips to the on-verdict
   colour and the disc carries the judgement. */
.verdict--seal.verdict--high {
    background: var(--color-verdict-high);
}

.verdict--seal.verdict--mid {
    background: var(--color-verdict-mid);
}

.verdict--seal.verdict--low {
    background: var(--color-verdict-low);
}

.verdict--seal.verdict--high,
.verdict--seal.verdict--mid,
.verdict--seal.verdict--low {
    color: var(--color-on-verdict);
}

.verdict--seal .rating {
    font-size: var(--text-xl);
    font-weight: 700;
}

/* A cover is a good deal smaller than a review picture, so the seal comes down
   with it rather than sitting on the artwork like a sticker. */
.verdict--seal-sm {
    right: var(--space-2);
    bottom: var(--space-2);
    width: 2.6rem;
    height: 2.6rem;
    box-shadow:
        0 0 0 2px var(--color-bg),
        0 3px 8px rgb(0 0 0 / 0.28);
}

.verdict--seal-sm .rating {
    font-size: var(--text-base);
}

.verdict--word.verdict--seal {
    max-width: 8rem;
    padding: 0.45rem 0.7rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    line-height: 1.25;
    text-align: center;
}

/* The picture and its seal share this box. The frame inside stays untouched,
   because its :empty outline is what marks a review without a picture. */
.card-review__media,
.lead__media {
    position: relative;
}

/* Page heading for the feeds: title and count on one baseline, over a rule. */
.page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-8);
    border-bottom: 2px solid var(--color-accent);
}

.page-head h1 {
    margin: 0;
    font-size: var(--text-3xl);
}

.page-head__count {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-sm);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

/* A line of orientation under a page's rule: what this index is, for the
   reader who arrived on it from a link rather than from the front page. It
   sits outside .page-head because that is a baseline row of two. */
.page-lede {
    max-width: var(--measure);
    margin: calc(var(--space-6) * -1) 0 var(--space-8);
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.5;
    color: var(--color-text-muted);
}

/* The way back out of a column — it has no section page above it to return
   to, so it says so itself. */
.review__back {
    margin: var(--space-10) auto 0;
    font-family: var(--font-display);
    font-size: var(--text-sm);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* The frontpage's opening words (E77). Not a hero: two lines of type over the
   lead, in the voice of the person whose site this is, so that a reader who
   arrived from a search result learns where they are before the first review. */
/* The introduction lives in the sidebar (Mattis), so it is sized for a
   column of 18rem, not for the measure of a text. */
.site-intro {
    max-width: var(--measure);
    margin: 0 0 var(--space-8);
    padding-bottom: var(--space-6);
    border-bottom: 1px solid var(--color-rule);
}

.site-intro__lede {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 700;
    line-height: 1.3;
    text-wrap: balance;
}

.site-intro__text {
    margin: var(--space-3) 0 0;
    font-family: var(--font-body);
    line-height: 1.55;
    color: var(--color-text-muted);
}

.site-intro__actions {
    margin: var(--space-4) 0 0;
}

/* Frontpage lead: the newest review, picture and text side by side. */
.lead {
    display: grid;
    gap: var(--space-6);
    align-items: center;
    margin-bottom: var(--space-12);
    padding-bottom: var(--space-8);
    border-bottom: 1px solid var(--color-rule);
}

@media (min-width: 52rem) {
    .lead {
        grid-template-columns: 1.15fr 1fr;
        gap: var(--space-8);
    }
}

.lead__frame {
    display: block;
}

.lead__image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: var(--radius);
}

.lead__title {
    margin: 0 0 var(--space-4);
    font-size: var(--text-3xl);
}

.lead__title a {
    color: var(--color-text);
}

.lead__title a:hover {
    color: var(--color-accent);
    text-decoration: none;
}

.lead__teaser {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.55;
    color: var(--color-text-muted);
    max-width: var(--measure);
}

/* A block of the frontpage: heading, content, and air after it. */
.shelf {
    margin-bottom: var(--space-12);
}

.shelf__head {
    margin: 0 0 var(--space-6);
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--color-rule);
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.shelf__head a {
    color: inherit;
}

.shelf__head a:hover {
    color: var(--color-accent);
    text-decoration: none;
}

.shelf__more {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
    margin: var(--space-8) 0 0;
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.04em;
}

/* The grid every feed uses. Cards find their own width; nothing is stretched
   to fill a row that has run out of them.

   Two grid rows per card, and every card borrows them from the grid: the
   pictures share one row, the texts share the next. So the titles in a row
   start on the same line whatever the pictures do — and a review that has no
   picture leaves an empty space rather than a grey placeholder. */
/* Vollbild shows all eight fetched cards (2×4); Halbiert steps down to six
   (2×3) and mobile to four (1×4) — the query always fetches the Vollbild
   count, narrower widths just fold extra cards away with nth-child. */
.grid-reviews {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8) var(--space-6);
    margin-bottom: var(--space-6);
}

.grid-reviews > :nth-child(n + 5) {
    display: none;
}

@media (min-width: 40rem) {
    .grid-reviews {
        grid-template-columns: repeat(3, 1fr);
    }

    .grid-reviews > :nth-child(n + 5) {
        display: grid;
    }

    .grid-reviews > :nth-child(n + 7) {
        display: none;
    }
}

@media (min-width: 64rem) {
    .grid-reviews {
        grid-template-columns: repeat(4, 1fr);
    }

    .grid-reviews > :nth-child(n + 7) {
        display: grid;
    }
}

.card-review {
    display: grid;
    grid-row: span 2;
    grid-template-rows: subgrid;
    gap: var(--space-3);
}

.card-review__text {
    display: flex;
    flex-direction: column;
}

.card-review__frame {
    display: block;
    align-self: start;
}

/* A review without a picture still occupies the picture row — that is what
   keeps the titles of a row on one line. An outline instead of nothing, so the
   gap reads as an empty slot and not as a broken image. No fill: the page has
   no grey boxes. */
.card-review__frame:empty {
    aspect-ratio: 16 / 9;
    border: 1px solid var(--color-rule);
    border-radius: var(--radius-sm);
}

.card-review__image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: var(--radius-sm);
}

/* A review with no preview anywhere shows its box or album art instead (E97).
   Cropping a 350x500 box to 16:9 would cut the picture in half, so the artwork
   keeps its own shape and stands on a blurred, enlarged copy of itself — the
   frame stays 16:9, which is what the grid needs, and nothing is lost. */
.card-review__frame--art,
.lead__frame--art {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: var(--radius-sm);
}

.lead__frame--art {
    border-radius: var(--radius);
}

.card-review__backdrop,
.lead__backdrop {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Scaled up because a blur samples beyond its own edges and would
       otherwise fade to the page ground in the corners. */
    transform: scale(1.2);
    filter: blur(24px) saturate(1.15) brightness(0.85);
}

.card-review__art,
.lead__art {
    position: relative;
    display: block;
    height: 100%;
    width: auto;
    max-width: 100%;
    object-fit: contain;
}

.card-review__title {
    margin: 0 0 var(--space-2);
    font-size: var(--text-lg);
    letter-spacing: -0.01em;
}

.card-review__title a {
    color: var(--color-text);
}

.card-review__title a:hover {
    color: var(--color-accent);
    text-decoration: none;
}

.card-review__teaser {
    margin: 0;
    font-family: var(--font-body);
    line-height: 1.55;
    color: var(--color-text-muted);
}

/* The year of the work, set beside the title the way the old site had it.
   Lighter and unlinked: it dates the game, it is not a way to somewhere. */
.card-review__year,
.lead__year {
    font-weight: 400;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.lead__year {
    font-size: 0.7em;
}

/* Whose music it is (E78). A byline, so it sits directly under the title and
   above the teaser — and every name in it is a way to that composer's page. */
.composer-line {
    margin: 0 0 var(--space-2);
    font-family: var(--font-display);
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.composer-line a {
    color: inherit;
    text-decoration-color: var(--color-rule);
}

.composer-line a:hover {
    color: var(--color-accent);
}

/* Frontpage shelves: toplists as square covers, five wide at full width,
   three at Halbiert, two on a phone — the query only ever fetches five, so a
   phone never has anything left over to fold away. */
.strip-toplists {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-6);
}

@media (min-width: 40rem) {
    .strip-toplists {
        grid-template-columns: repeat(3, 1fr);
    }

    .strip-toplists > :nth-child(n + 4) {
        display: none;
    }
}

@media (min-width: 64rem) {
    .strip-toplists {
        grid-template-columns: repeat(5, 1fr);
    }

    .strip-toplists > :nth-child(n + 4) {
        display: block;
    }
}

.strip-toplists__image {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-2);
}

.strip-toplists__title {
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--color-text);
}

.strip-toplists__title:hover {
    color: var(--color-accent);
    text-decoration: none;
}

/* The way straight to the playlist, as the old front page had it (E84). Small
   and set apart from the title, because it leaves the site. */
.strip-toplists__playlist {
    display: block;
    margin-top: var(--space-1);
    font-family: var(--font-display);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

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

/* Frontpage layout: the reviews and toplists shelves stack in the main
   column; the series register moves beside them as a sidebar once there is
   room, so it stays in view while the shelves scroll rather than being one
   more row to pass on the way down (phase 19 follow-up — it used to be a
   fourth shelf of wrapped pills, which read as an afterthought). */
.frontpage-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

/* Below the sidebar breakpoint there is no sidebar to sit in: the aside
   dissolves into the page, so the introduction can still open the front page
   and the two registers still trail it. */
@media (max-width: 63.999rem) {
    .frontpage-layout__aside {
        display: contents;
    }

    .frontpage-layout__aside .site-intro {
        order: -1;
        border-bottom: 0;
        padding-bottom: 0;
        margin-bottom: 0;
    }
}

@media (min-width: 64rem) {
    .frontpage-layout {
        grid-template-columns: minmax(0, 1fr) 18rem;
        align-items: start;
        gap: var(--space-12);
    }

    /* In the sidebar the lede is a caption beside the reading, not a headline
       over it — one step down from the display size it had on its own. */
    .site-intro__lede {
        font-size: var(--text-lg);
    }

    .frontpage-layout__aside {
        position: sticky;
        top: calc(var(--header-height) + var(--space-6));
        /* Two registers can outgrow the viewport; scroll them, do not cut. */
        max-height: calc(100vh - var(--header-height) - var(--space-8));
        overflow-y: auto;
    }
}

.index-list--tight .index-list__item {
    padding: var(--space-2) 0;
}

/* Section bar: the old hover menus, printed --------------------------------
   Sits directly under the site header on a section's index pages and carries
   that section's accent, so a reader who lands on /alben/ from a search result
   still learns which half of the site they are in. */

.section-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-3) var(--space-6);
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-8);
    border-bottom: 1px solid var(--color-rule);
}

.section-bar__name {
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-accent);
}

.section-bar__links {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    margin: 0;
    padding: 0;
}

.section-bar__links a {
    font-family: var(--font-display);
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.section-bar__links a:hover {
    color: var(--color-text);
}

/* The page you are on is named twice — in the bar and in the heading below.
   Marking it keeps the bar honest instead of looking like four equal offers. */
.section-bar__links a[aria-current] {
    color: var(--color-text);
    text-decoration: underline;
    text-underline-offset: 0.3em;
}

/* Public filter bar --------------------------------------------------------
   Same markup as the inventory's, without the panel: the public pages have no
   grey boxes (E60), so the controls sit on the page and a rule closes them. */

.filters--open {
    background: none;
    border: 0;
    border-bottom: 1px solid var(--color-rule);
    border-radius: 0;
    padding: 0 0 var(--space-4);
}

.filters--single {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: var(--space-3);
}

.filters--single > div:first-child {
    flex: 1 1 18rem;
}

.filters--single .filters__actions {
    margin-top: 0;
}

/* Album grid ---------------------------------------------------------------
   A record is a square, so the grid is one of squares: cover, title, the two
   numbers that matter. Denser than the review grid — there are a thousand. */

.grid-albums {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
    gap: var(--space-6) var(--space-4);
}

.card-album__link {
    display: block;
    color: var(--color-text);
}

.card-album__link:hover {
    text-decoration: none;
}

/* The cover carries the seal, so it needs to be the box it is positioned in. */
.card-album__frame {
    display: block;
    position: relative;
}

.card-album__cover {
    display: block;
    width: 100%;
    /* The width/height attributes are a presentational hint, so without this the
       500px from the markup would win over the aspect ratio. */
    height: auto;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-sm);
}

/* Same reasoning as the review card: an outline reads as an empty slot, a
   grey fill reads as a broken image. */
.card-album__cover--empty {
    border: 1px solid var(--color-rule);
}

.card-album__title {
    display: block;
    margin-top: var(--space-2);
    font-family: var(--font-display);
    font-size: var(--text-sm);
    line-height: 1.3;
}

.card-album__link:hover .card-album__title {
    color: var(--color-accent);
}

.card-album__meta {
    display: flex;
    gap: var(--space-2);
    margin: var(--space-1) 0 0;
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    color: var(--color-text-muted);
}

/* Index list ---------------------------------------------------------------
   Names, aliases and counts — a register, not a set of cards. */

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

.index-list__item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-rule);
}

.index-list__item a {
    font-family: var(--font-display);
    font-size: var(--text-base);
    color: var(--color-text);
}

.index-list__item a:hover {
    color: var(--color-accent);
}

.index-list__note {
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.index-list__count {
    margin-left: auto;
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    color: var(--color-text-muted);
}

/* Frontpage: who writes here -----------------------------------------------
   A closing note, not a masthead. Three people at most, so a flexible row that
   folds to one per line on a phone rather than a grid that would leave holes. */

.people__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6) var(--space-8);
}

.person {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    /* Enough for a portrait plus the longest role on one line; below that the
       three simply stack. */
    flex: 1 1 18rem;
    max-width: 26rem;
}

.person__portrait {
    flex: 0 0 auto;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: 50%;
    object-fit: cover;
}

.person__text > * {
    margin: 0;
}

.person__name {
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--color-text);
}

.person__role {
    margin-top: var(--space-1);
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.person__bio {
    margin-top: var(--space-1);
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

/* Blog: review detail ------------------------------------------------------
   The fresh cut (phase 19): the words open the page, the picture follows as a
   quotation. Everything sits on one reading measure, centred in the page; the
   picture and whatever the body embeds are allowed to break out of it. */

.review {
    /* Not a percentage: a percentage would resolve against whatever element
       uses it — and everything in here already sits on the narrow measure. */
    --break-out: min(58rem, calc(100vw - 3rem));
}

.review > * {
    max-width: var(--measure);
    margin-left: auto;
    margin-right: auto;
}

.review__head {
    margin-bottom: var(--space-8);
}

.review__title {
    margin: 0 0 var(--space-4);
    padding-bottom: var(--space-4);
    border-bottom: 2px solid var(--color-accent);
    font-size: var(--text-3xl);
}

/* The one sentence the review comes down to, set larger than the body. */
.review__standfirst {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--text-xl);
    line-height: 1.45;
    color: var(--color-text-muted);
}

.review__head .verdict,
.review__head .entry-links {
    margin-top: var(--space-4);
}

.review__figure {
    position: relative;
    max-width: var(--break-out);
    margin: 0 auto var(--space-8);
}

.review__header-image {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--radius);
}

/* What a page points at: the album, the game, who made it, when it came out.
   A rail, not a grey box — the two hairlines fence it off, and the labels are
   set small enough that the values carry the line. Used by the review, the
   game hub and the album page alike, so the same fact looks the same
   everywhere. Each line is its own flex row: a long list of platforms wraps
   under itself instead of running back under the label. */
/* Anything a link can point at has to clear the sticky header, or the browser
   scrolls the target exactly under it (the album chapters carried their own
   offset from before the header was sticky; now everything shares one). */
[id] {
    scroll-margin-top: calc(var(--header-height) + var(--space-4));
}

/* The footnote under a mark: it points at the FAQ chapter that explains the
   scale (E87). Deliberately quiet — one size down, in the muted ink — so it
   reads as an aside to the seal rather than a second verdict. */
.verdict-note {
    margin: 0 auto var(--space-8);
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

/* Pulled up under the picture: the figure's own bottom band would otherwise
   park this note a whole section away from the seal it explains. */
.review__figure + .verdict-note {
    margin-top: calc(var(--space-6) * -1);
}

.verdict-note a {
    color: inherit;
    text-decoration-color: var(--color-rule);
    text-underline-offset: 0.2em;
}

.verdict-note a:hover {
    color: var(--color-accent);
    text-decoration-color: currentColor;
}

.facts {
    /* `auto` on the sides, not 0: inside a review this rail is centred on the
       measure by `.review > *`, and a shorthand would undo that. */
    margin: 0 auto var(--space-8);
    padding: var(--space-3) 0;
    border-top: 1px solid var(--color-rule);
    border-bottom: 1px solid var(--color-rule);
    font-size: var(--text-sm);
}

.facts p {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--space-2);
    margin: 0 0 var(--space-2);
}

.facts p:last-child {
    margin-bottom: 0;
}

.facts__label {
    flex: 0 0 auto;
    min-width: 6.5rem;
    font-family: var(--font-display);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.facts__value {
    flex: 1 1 12rem;
}

/* An outbound link in the rail is named by its destination (E117), so it also
   has to say that it leaves the site — the arrow does that in the width of a
   character, where a second line of text would not fit. */
.facts__link::after {
    content: "\2197";
    margin-left: 0.2em;
    font-size: 0.85em;
    text-decoration: none;
    opacity: 0.7;
}

/* The facts as a sidebar (E94). Only from here on is there room for a second
   column next to the reading measure — below this width the band stays above
   the text, which is why it keeps its place in the markup and is moved by the
   grid rather than by a second copy in the template.

   `span 20` is the trick that makes the sticky work: the aside has to own a
   grid area as tall as the article, and `-1` would only reach the last
   *explicit* row line — the rows the article's own children create are
   implicit. Twenty is far more rows than a review has; the surplus are empty
   rows of zero height. */
@media (min-width: 78rem) {
    .review:has(> .review__aside) {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 16rem;
        column-gap: var(--space-8);
        align-items: start;
    }

    .review:has(> .review__aside) > * {
        grid-column: 1;
    }

    .review > .review__aside {
        grid-column: 2;
        grid-row: 1 / span 20;
        align-self: start;
        position: sticky;
        top: calc(var(--header-height) + var(--space-4));
        max-width: none;
        margin: 0;
        /* Standing on its own it needs a top rule as much as a bottom one, but
           not the full-width fence of the in-flow band: the type is the same,
           the box is quieter. */
        padding: var(--space-4) 0 0;
        border-bottom: 0;
        /* A long series makes the column taller than the screen; without this
           it would cut itself off and the last part stay unreachable. */
        max-height: calc(100vh - var(--header-height) - var(--space-8));
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    .review > .review__aside p {
        display: block;
    }

    .review > .review__aside .facts__label {
        display: block;
        min-width: 0;
        margin-bottom: 0.15rem;
    }
}

/* The series rail (E115): the parts in release order, this one marked. It sits
   inside the facts box and takes its type from there — a list, not a menu. */
.rail {
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-rule);
}

.rail__name {
    margin: 0 0 var(--space-2);
    font-weight: 600;
}

.rail__list {
    list-style: none;
    margin: 0;
    padding: 0;
    /* The spine the current part is marked on. */
    border-left: 2px solid var(--color-rule);
}

.rail__item,
.rail__gap {
    position: relative;
    padding: 0.2rem 0 0.2rem var(--space-3);
    line-height: 1.35;
}

.rail__gap {
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}

.rail__item--current {
    /* The mark is the spine going solid, not a second colour in the box. */
    box-shadow: -2px 0 0 var(--color-accent);
    font-weight: 600;
}

.rail__year {
    margin-left: 0.35em;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}

/* What to read next (E114). Each entry is a thumbnail, a title and the one line
   that says why it is here — the reason is the point, so it is never clipped. */
.suggest {
    margin: var(--space-8) auto 0;
    /* Beside the sidebar the article is a grid, and a grid item with auto side
       margins is sized by its content — without a width of its own the block
       would shrink to its longest title instead of holding the measure. */
    width: min(100%, var(--measure));
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-rule);
}

.suggest__head {
    margin: 0 0 var(--space-3);
}

.suggest__list {
    list-style: none;
    display: grid;
    gap: var(--space-3);
    margin: 0;
    padding: 0;
}

.suggest__link {
    display: grid;
    grid-template-columns: 5rem minmax(0, 1fr);
    gap: var(--space-3);
    align-items: center;
    text-decoration: none;
    color: inherit;
}

.suggest__thumb {
    width: 5rem;
    height: 3.35rem;
    object-fit: cover;
    border-radius: var(--radius);
}

.suggest__title {
    display: block;
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1.25;
}

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

.suggest__reason {
    display: block;
    margin-top: 0.15rem;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}

/* The running text itself — the only place on the site set in Newsreader at
   full size, and the reason the measure exists. */
.review__body,
.review__conclusion {
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.65;
}

.review__body h2,
.review__conclusion h2,
.review__body h3 {
    font-family: var(--font-display);
    margin-top: var(--space-8);
}

.review__conclusion {
    margin-top: var(--space-8);
    padding-top: var(--space-6);
    border-top: 1px solid var(--color-rule);
    /* Portrait beside the verdict, not above it: the picture is an aside to
       the text, and a face on its own line would read as a new chapter. */
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--space-6);
    align-items: start;
}

.review__signature {
    margin: 0;
    width: 5rem;
    text-align: center;
}

.review__portrait {
    width: 5rem;
    height: 5rem;
    border-radius: 50%;
    object-fit: cover;
}

.review__signature-name {
    margin-top: var(--space-2);
    font-family: var(--font-sans);
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}

/* Under the measure of a phone the portrait would leave the text a column of
   three words, so it steps aside and sits above the heading instead. */
@media (max-width: 34rem) {
    .review__conclusion {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-4);
    }

    .review__signature {
        display: flex;
        align-items: center;
        gap: var(--space-3);
        width: auto;
        text-align: left;
    }

    .review__portrait {
        width: 3rem;
        height: 3rem;
    }

    .review__signature-name {
        margin-top: 0;
    }
}

.review__conclusion h2 {
    margin-top: 0;
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-accent);
}

/* Pictures, videos and album cards inside a review body break the measure —
   that is what keeps a long text from reading as one grey column. */
/* Pictures in the body stay on the measure: a review is mostly screenshots,
   and blowing every one of them up to full width would turn the text into a
   caption. What does break out is what is meant to interrupt — an embedded
   video, an album chapter, a carousel. */
.review__body > .video-embed,
.review__body > .media-embed,
.review__body > .album-card,
.review__body > .carousel {
    width: var(--break-out);
    max-width: var(--break-out);
    margin-left: calc(50% - var(--break-out) / 2);
}

.review__body p > img {
    display: block;
    width: 100%;
    height: auto;
}


/* The albums under a review (E103) ------------------------------------------
   A shelf of records at the end of the text, not cards wedged into it: closed
   they are a list you can read in one look, open they hold their own chapter
   and their tracklist. Same surface as the album card they replace, so the
   page keeps one vocabulary for "this is a record". */
.review-albums {
    margin-top: var(--space-8);
    padding-top: var(--space-6);
    border-top: 1px solid var(--color-rule);
}

.review-albums__head {
    margin: 0 0 var(--space-4);
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-accent);
}

.album-chapter {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    margin-bottom: var(--space-3);
    /* The anchor from the body still points here (E3), and a heading landing
       flush against the header edge reads as cut off. */
    scroll-margin-top: calc(var(--header-height) + var(--space-4));
}

.album-chapter[open] {
    box-shadow: var(--shadow);
}

.album-chapter__summary {
    display: flex;
    gap: var(--space-4);
    align-items: center;
    padding: var(--space-3) var(--space-4);
    cursor: pointer;
    /* The default marker sits on the first line of a flex row and drifts away
       from the title; the rotating caret below replaces it. */
    list-style: none;
}

.album-chapter__summary::-webkit-details-marker {
    display: none;
}

.album-chapter__summary::after {
    content: "";
    flex: 0 0 auto;
    margin-left: auto;
    width: 0.55rem;
    height: 0.55rem;
    border-right: 2px solid var(--color-text-muted);
    border-bottom: 2px solid var(--color-text-muted);
    transform: rotate(45deg);
    transition: transform 0.15s ease;
}

.album-chapter[open] .album-chapter__summary::after {
    transform: rotate(-135deg);
}

.album-chapter__summary:hover .album-chapter__title,
.album-chapter__summary:focus-visible .album-chapter__title {
    color: var(--color-accent);
}

.album-chapter__cover {
    flex: 0 0 auto;
    width: 3.5rem;
    height: 3.5rem;
    object-fit: cover;
    border-radius: var(--radius-sm);
}

.album-chapter__cover--empty {
    display: block;
    background: var(--color-surface-2);
}

.album-chapter__head {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

.album-chapter__title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    line-height: 1.2;
}

.album-chapter__year {
    color: var(--color-text-muted);
    font-weight: 400;
}

.album-chapter__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    font-family: var(--font-sans);
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}

.album-chapter__body {
    padding: 0 var(--space-4) var(--space-4);
    border-top: 1px solid var(--color-border);
}

/* The chapter is prose like the review above it, so it is set like the review
   above it — one size down, because it is an aside to the main text. */
.album-chapter__text {
    font-family: var(--font-body);
    font-size: var(--text-base);
    line-height: 1.6;
}

.album-chapter__links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    align-items: baseline;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
}

@media (max-width: 34rem) {
    .album-chapter__summary {
        align-items: flex-start;
    }

    .album-chapter__cover {
        width: 2.75rem;
        height: 2.75rem;
    }
}

/* The same chapter, read from the album's side (E103). It is prose on a page
   that is otherwise a record sleeve and a table, so it gets the reading measure
   the review page gives it. */
.album-review {
    margin-top: var(--space-8);
}

.album-review__text {
    max-width: var(--measure);
    font-family: var(--font-body);
    font-size: var(--text-base);
    line-height: 1.65;
}


/* --- Detail pages that are not reviews ------------------------------------
   A game hub, an album, a toplist and a series page all open the same way a
   review does: an eyebrow saying what this is, a big title over an accent
   rule, and then the facts. One publication, whatever kind of page you land
   on. */

.entry-head {
    margin-bottom: var(--space-8);
}

.entry-head__title {
    margin: 0 0 var(--space-4);
    padding-bottom: var(--space-4);
    border-bottom: 2px solid var(--color-accent);
    font-size: var(--text-3xl);
}

/* The year belongs to the title but must not compete with it. */
.entry-head__year {
    font-weight: 400;
    color: var(--color-text-muted);
}

.entry-head__standfirst {
    margin: 0 0 var(--space-4);
    max-width: var(--measure);
    font-family: var(--font-body);
    font-size: var(--text-xl);
    line-height: 1.45;
    color: var(--color-text-muted);
}

.entry-head__standfirst:last-child {
    margin-bottom: 0;
}

/* A picture at the top of such a page: a quotation, like the review's. */
.entry-figure {
    max-width: 58rem;
    margin: 0 0 var(--space-8);
}

.entry-figure img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--radius);
}

/* --- Toplists ------------------------------------------------------------- */

/* Entries are separated by a rule and by air, not fenced into cards; the rank
   is the loudest thing on the line because the ranking is the point. */
.toplist-entries {
    list-style: none;
    padding: 0;
    margin: 0 auto;
    display: grid;
    gap: var(--space-8);
}

.toplist-entry {
    padding-top: var(--space-8);
    border-top: 1px solid var(--color-rule);
}

.toplist-entry:first-child {
    padding-top: 0;
    border-top: 0;
}

.toplist-entry__title {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    margin: 0 0 var(--space-3);
    font-size: var(--text-xl);
}

.toplist-entry__rank {
    flex: 0 0 auto;
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--color-accent);
}

.toplist-entry__preview {
    display: block;
    width: 100%;
    height: auto;
    margin-bottom: var(--space-4);
}

.toplist-entry__description {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.6;
}

.toplist-entry__tracks {
    margin: var(--space-3) 0 0;
    padding-left: var(--space-4);
    font-size: var(--text-sm);
}

/* --- Series pages --------------------------------------------------------- */

.series-header__description {
    max-width: var(--measure);
    margin-bottom: var(--space-8);
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.6;
}

/* The games of a series: one line each, held by a hairline. The reviews that
   exist for a game follow its name as small caps, so the eye can see at a
   glance which entries of a series have been written about. */
.series-games__list,
.series-index {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    max-width: 46rem;
}

.series-games__item,
.series-index__item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2) var(--space-4);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-rule);
}

.series-games__name,
.series-index__name {
    font-family: var(--font-display);
    font-weight: 600;
}

/* The banner leads the row (E96). Centred, not on the baseline: a 16:9 picture
   next to a single line of text has no baseline worth sharing. */
.series-index__item {
    align-items: center;
}

.series-index__link {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    min-width: 0;
}

.series-index__image {
    flex: none;
    display: block;
    width: 7rem;
    height: auto;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: var(--radius-sm);
}

/* Same rule as the review cards: an outline, never a grey box. */
.series-index__image--empty {
    border: 1px solid var(--color-rule);
}

.series-games__reviews {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    font-family: var(--font-display);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.series-index__counts {
    font-family: var(--font-display);
    font-size: var(--text-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    white-space: nowrap;
}

/* --- Markdown content (rendered body texts) --- */

.review__body img,
.toplist-entry__description img {
    max-width: 100%;
    border-radius: var(--radius);
}

.video-embed {
    margin: var(--space-6) 0;
}

.video-embed iframe {
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    border-radius: var(--radius);
}

.video-embed--invalid {
    border: 1px dashed var(--color-danger);
    border-radius: var(--radius);
    padding: var(--space-2) var(--space-4);
    color: var(--color-danger);
}

.video-embed--invalid::before {
    content: "⚠ Ungültiger YouTube-Link";
}

/* The album player (E113). YouTube keeps the 16/9 of a video; Spotify and
   SoundCloud ship a fixed height with their embed and carry it in the
   attribute, so only the width is set here. */
.player {
    margin: var(--space-4) 0;
}

.player__frame {
    display: block;
    width: 100%;
    border: 0;
    border-radius: var(--radius);
}

.player--youtube .player__frame {
    aspect-ratio: 16 / 9;
}

.player__note {
    margin: var(--space-2) 0 0;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}

/* Album chapter card (::: album directive, package D). The section carries the
   id="album-<slug>" anchor (E3); scroll-margin keeps it clear of the top edge. */
.album-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: var(--space-4) var(--space-6);
    margin: var(--space-6) 0;
}

.album-card--missing {
    border-style: dashed;
    color: var(--color-text-muted);
}

.album-card__placeholder {
    margin: 0;
}

.album-card__head {
    display: flex;
    gap: var(--space-4);
    align-items: flex-start;
}

.album-card__cover {
    flex: 0 0 auto;
    width: 9rem;
    max-width: 30%;
    border-radius: var(--radius-sm);
}

.album-card__cover--empty {
    aspect-ratio: 1;
    background: var(--color-surface-2);
}

.album-card__title {
    margin: 0 0 var(--space-1);
    font-size: 1.25rem;
}

.album-card__year {
    color: var(--color-text-muted);
    font-weight: 400;
}

.album-card__composers {
    margin: 0 0 var(--space-2);
}

.album-card__ratings {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    margin: var(--space-2) 0 0;
}

.album-card__tracklist {
    margin-top: var(--space-3);
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-3);
}

.album-card__tracklist summary {
    cursor: pointer;
    color: var(--color-text-muted);
}

.album-card__tracklist table {
    width: 100%;
    margin-top: var(--space-2);
    border-collapse: collapse;
    font-size: 0.875rem;
}

.album-card__tracklist td {
    padding: var(--space-1) var(--space-2);
    border-top: 1px solid var(--color-border);
}

.album-card__track-no,
.album-card__track-duration {
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.album-card__track-rating {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The tracks tied at the album's best rating stand out in the accent color. */
.album-card__top-track .album-card__track-rating {
    color: var(--color-accent-strong);
    font-weight: 600;
}

/* Inline track chip ([Text](track:<pk>)) — no page behind it (E3), but a
   tooltip with album + rating (title attribute set by the renderer). */
.track-ref {
    color: var(--color-accent-strong);
    font-weight: 500;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
    cursor: help;
}

/* CSS-only carousel: each image paragraph becomes a snap-scrolled slide. */
.gallery {
    display: flex;
    gap: var(--space-2);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: var(--space-2);
    margin: var(--space-6) 0;
}

.gallery p {
    margin: 0;
    flex: 0 0 auto;
    scroll-snap-align: start;
}

.gallery img {
    display: block;
    height: 14rem;
    width: auto;
    border-radius: var(--radius);
}

/* --- Long-form pages: FAQ, Impressum, MaybeMe -----------------------------
   Running text in the body face on the reading measure. Headings and tables
   keep the full width — a legal notice with a source table is unreadable
   squeezed into 34rem, but its paragraphs are unreadable stretched to 76. */

.prose {
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.65;
}

.prose > p,
.prose > ul,
.prose > ol {
    max-width: var(--measure);
}

/* A chapter rule that runs on for another 30rem past the widest thing beneath
   it looks like a mistake, so the heading is held to the width of the widest
   block it can head: the FAQ list. Tables keep the full width — they set it
   themselves and are not `> h2` children. */
.prose h2 {
    max-width: 46rem;
    margin: var(--space-12) 0 var(--space-4);
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--color-rule);
    font-family: var(--font-display);
    font-size: var(--text-xl);
}

.prose h3 {
    margin: var(--space-8) 0 var(--space-2);
    font-family: var(--font-display);
    font-size: var(--text-lg);
}

/* The headings bring their own rule; a second one right above them was the
   old page's way of separating chapters and is now noise. */
.prose hr {
    display: none;
}

.prose table {
    font-family: var(--font-display);
    font-size: var(--text-sm);
}

/* --- FAQ ------------------------------------------------------------------ */

.faq-entry {
    max-width: 46rem;
    border-bottom: 1px solid var(--color-rule);
}

.faq-entry > summary {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-4);
    padding: var(--space-3) 0;
    cursor: pointer;
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 600;
    list-style: none;
}

.faq-entry > summary::-webkit-details-marker {
    display: none;
}

/* A sign that the question opens, without an icon font or an SVG per row. */
.faq-entry > summary::after {
    content: "+";
    flex: 0 0 auto;
    font-weight: 700;
    color: var(--color-accent);
}

.faq-entry[open] > summary::after {
    content: "\2013";
}

/* The entry a link points at. faq.js opens it; this says which one it was, so
   a reader who followed "Wie ich werte" lands on an answer that is visibly the
   answer and not just the next question in a row of them. */
.faq-entry:target {
    background: var(--color-accent-soft);
    box-shadow:
        -0.75rem 0 0 var(--color-accent-soft),
        0.75rem 0 0 var(--color-accent-soft);
}

.faq-entry__answer {
    max-width: var(--measure);
    padding-bottom: var(--space-4);
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.65;
}

/* --- MaybeMe: portfolio entries + timeline -------------------------------- */

.maybeme-intro {
    margin-bottom: var(--space-12);
}

.portfolio-group {
    margin-bottom: var(--space-12);
}

.portfolio-card {
    display: grid;
    gap: var(--space-4);
    margin: 0;
    padding-top: var(--space-8);
    border-top: 1px solid var(--color-rule);
}

.portfolio-card:first-of-type {
    padding-top: 0;
    border-top: 0;
}

@media (min-width: 46rem) {
    .portfolio-card {
        grid-template-columns: 12rem 1fr;
        align-items: start;
    }
}

.portfolio-card__image {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--radius);
}

.portfolio-card__body {
    max-width: var(--measure);
}

.portfolio-card__body h3 {
    margin: 0 0 var(--space-1);
    font-size: var(--text-xl);
}

.portfolio-card__date {
    margin: 0 0 var(--space-3);
    font-family: var(--font-display);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.09em;
    color: var(--color-text-muted);
}

.portfolio-card__teaser,
.portfolio-card__context {
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.6;
}

.portfolio-card__credits {
    margin-bottom: 0;
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.timeline__list {
    list-style: none;
    padding: 0;
    margin: 0;
    max-width: var(--measure);
}

/* The rail is the timeline: a hairline down the left with a dot per year. */
.timeline__item {
    position: relative;
    padding-left: var(--space-8);
    padding-bottom: var(--space-8);
    margin-left: var(--space-2);
    border-left: 1px solid var(--color-rule);
}

.timeline__item:last-child {
    border-left-color: transparent;
    padding-bottom: 0;
}

.timeline__item::before {
    content: "";
    position: absolute;
    left: -5px;
    top: 0.45rem;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--color-accent);
}

.timeline__year,
.timeline__kind {
    font-family: var(--font-display);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.timeline__year {
    color: var(--color-accent);
}

.timeline__kind {
    margin-left: var(--space-3);
    color: var(--color-text-muted);
}

.timeline__title {
    margin: var(--space-2) 0;
    font-size: var(--text-xl);
}

.timeline__item p {
    font-family: var(--font-body);
    font-size: var(--text-lg);
    line-height: 1.6;
}

/* --- Self-hosted media (::: video / audio / download, SFTP uploads) ------- */

.media-embed {
    margin: var(--space-6) 0;
}

.media-embed--video video {
    width: 100%;
    max-height: 32rem;
    border-radius: var(--radius);
    background: #000;
}

.media-embed--audio audio {
    width: 100%;
}

.media-embed--invalid {
    border: 1px dashed var(--color-danger);
    border-radius: var(--radius);
    padding: var(--space-2) var(--space-4);
    color: var(--color-danger);
}

.media-embed--invalid::before {
    content: "⚠ Ungültige Medien-Angabe";
}

.download {
    margin: var(--space-4) 0;
}

.download__link {
    font-weight: 600;
}

.download__meta {
    color: var(--color-text-muted);
    font-size: 0.9rem;
}

/* --- Album & game detail pages ------------------------------------------- */

/* The album opens with its cover beside the facts: a record has a face, and
   hiding it under the text would be the one place where a picture really is
   the subject. Below 46rem the two simply stack. */
.album-page__top {
    display: grid;
    gap: var(--space-6);
    margin-bottom: var(--space-8);
}

@media (min-width: 46rem) {
    .album-page__top {
        grid-template-columns: 14rem 1fr;
        align-items: start;
    }
}

/* No forced square: plenty of these covers are box art, and cropping them to
   a record sleeve loses the top of the artwork. */
.album-page__cover {
    display: block;
    width: 100%;
    max-width: 14rem;
    height: auto;
    border-radius: var(--radius);
}

.album-page__cover--empty {
    aspect-ratio: 1 / 1;
    background: var(--color-surface-2);
}

.album-page__side .facts {
    margin-bottom: var(--space-4);
}

/* Plain outbound links under a head — a playlist, a shop. No buttons: they
   are references, not actions. */
.entry-links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.04em;
}

/* --- Tracklist ------------------------------------------------------------ */

.tracklist {
    width: 100%;
    max-width: 46rem;
    border-collapse: collapse;
}

.tracklist td {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--color-rule);
    vertical-align: baseline;
}

.tracklist td:first-child {
    padding-left: 0;
}

.tracklist td:last-child {
    padding-right: 0;
}

.tracklist__no,
.tracklist__rating,
.tracklist__duration {
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.tracklist__rating,
.tracklist__duration {
    text-align: right;
}

/* The tracks worth the detour. Marked by weight and by the accent on the
   rating, not by a coloured row — the table stays a table. */
.tracklist__top {
    font-weight: 600;
}

.tracklist__top .tracklist__rating {
    color: var(--color-accent);
    font-weight: 700;
}

.tracklist__artists {
    display: block;
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

/* The albums that belong to a game: cover, title, year, rating on one line. */
.album-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    max-width: 46rem;
}

.album-list__item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-rule);
}

.album-list__cover {
    width: 3rem;
    height: 3rem;
    object-fit: cover;
    border-radius: var(--radius-sm);
}

.album-list__title {
    font-family: var(--font-display);
    font-weight: 600;
}

.album-list__year {
    color: var(--color-text-muted);
}

.album-list__item .rating {
    margin-left: auto;
    font-family: var(--font-display);
    color: var(--color-accent);
}

.media-library__table {
    width: 100%;
    border-collapse: collapse;
    margin-top: var(--space-4);
}

.media-library__table th,
.media-library__table td {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--color-border);
    text-align: left;
}

.media-library__url,
.media-library__snippet {
    font-family: var(--font-mono);
    font-size: 0.85rem;
    white-space: pre;
}

.media-library__size {
    white-space: nowrap;
}

/* Inventory maintenance forms (phase 11) ---------------------------------- */

.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
}

.detail-header__actions {
    display: flex;
    gap: var(--space-2);
    margin-left: auto;
}

.gameform__section {
    margin-bottom: var(--space-4);
}

.gameform__section h2 {
    margin-top: 0;
}

.gameform__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: var(--space-3);
    align-items: start;
}

.gameform__checkgroups {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--space-3);
    margin-top: var(--space-3);
}

.gameform__checkgroups ul {
    list-style: none;
    margin: 0;
    padding: 0;
    columns: 2;
}

.gameform__checkgroups label,
.gameform__checkrow label {
    display: inline;
    color: var(--color-text);
    font-size: 0.95rem;
}

.gameform__checkrow {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-bottom: var(--space-3);
}

.gameform__checkrow .field {
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    gap: var(--space-2);
}

.gameform__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-top: var(--space-4);
}

.field input[type="number"],
.field input[type="date"],
.field select,
.field textarea {
    font: inherit;
    width: 100%;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
}

.field input[type="text"] {
    width: 100%;
}

.field--error input,
.field--error select,
.field--error textarea {
    border-color: var(--color-danger);
}

.field__help {
    margin: var(--space-1) 0 0;
    font-size: 0.8rem;
}

.form-error {
    margin: var(--space-1) 0 0;
    padding-left: var(--space-4);
}

.formset {
    width: 100%;
    border-collapse: collapse;
}

.formset th {
    text-align: left;
    font-size: 0.85rem;
    color: var(--color-text-muted);
    font-weight: 500;
    padding-bottom: var(--space-1);
}

.formset td {
    padding: var(--space-1) var(--space-2) var(--space-1) 0;
    vertical-align: top;
}

.formset select {
    width: 100%;
}

@media (max-width: 640px) {
    .gameform__checkgroups ul {
        columns: 1;
    }
}

/* Game image slots (phase 12) ------------------------------------------- */

.imageform__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--space-4);
}

.imageform__summary {
    display: inline;
    font-size: 1.15rem;
}

.imageslot {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg);
}

.imageslot__title {
    margin: 0;
    font-size: 1rem;
}

/* One fixed box for every format, so a 350x500 cover and a 1600x667 header
   sit in the same grid without the rows jumping. */
.imageslot__preview {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 8rem;
    max-height: 12rem;
    overflow: hidden;
    background: var(--color-surface);
    border-radius: var(--radius-sm);
}

/* The link is a flex item, and a flex item does not shrink below its content
   by default — a 1600 px header would be clipped by the overflow above instead
   of scaled down. */
.imageslot__preview a {
    display: block;
    min-width: 0;
    max-width: 100%;
}

.imageslot__preview img {
    display: block;
    max-width: 100%;
    max-height: 12rem;
    object-fit: contain;
}

.imageslot__meta {
    margin: 0;
    font-size: 0.8rem;
}

.imageslot__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.imageslot--broken .imageslot__preview {
    border: 1px dashed var(--color-danger);
}

.imageslot input[type="file"],
.field input[type="file"] {
    font: inherit;
    width: 100%;
    font-size: 0.85rem;
}

/* IGDB lookup (phase 13) ------------------------------------------------- */

.igdb-candidates {
    display: grid;
    gap: var(--space-3);
    margin: var(--space-4) 0 0;
    padding: 0;
    list-style: none;
}

.igdb-candidate {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
}

.igdb-candidate__cover {
    flex: 0 0 auto;
    width: 6rem;
    height: auto;
    border-radius: var(--radius-sm);
}

/* min-width: 0 so a long IGDB summary wraps instead of stretching the card. */
.igdb-candidate__body {
    min-width: 0;
}

/* `.badge-row` is inline-flex, and `game_list.html` needs it that way. Here it
   is the last thing above the button whenever IGDB has neither a description
   nor platforms for an entry — and then the button crept up beside the badge.
   Block-level flex only inside a candidate keeps both happy. */
.igdb-candidate__body .badge-row {
    display: flex;
}

.igdb-candidate__body h2 {
    margin-top: 0;
    font-size: 1.1rem;
}

/* The comparison table is the one wide thing on the page; let it scroll on its
   own rather than pushing the whole layout sideways on a phone. */
.igdb-diff {
    display: block;
    overflow-x: auto;
    width: 100%;
    border-collapse: collapse;
    margin: var(--space-3) 0;
}

.igdb-diff th,
.igdb-diff td {
    padding: var(--space-2);
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--color-border);
}

.igdb-diff__row--blocked {
    opacity: 0.6;
}

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

/* IGDB matching (phase 14, temporary) -------------------------------------
   Delete this block together with the screen it styles; the removal list is in
   TODOs/14_curation_followup.md. */

.match__intro {
    max-width: 60ch;
    margin-bottom: var(--space-6);
}

table.match th,
table.match td {
    vertical-align: top;
    white-space: normal;
}

/* The row header is a title, not a column label — undo table.games th. */
table.match th[scope="row"] {
    width: 32%;
    background: transparent;
    color: var(--color-text);
    font-size: 0.92rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: normal;
}

.match__links {
    display: block;
    margin-top: var(--space-1);
    font-size: 0.8rem;
    font-weight: 400;
    color: var(--color-text-muted);
}

.match__form {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
}

.match__form input {
    flex: 1 1 18rem;
    min-width: 12rem;
}

.match__action p {
    margin: var(--space-2) 0 0;
    font-size: 0.85rem;
}

.match__error {
    color: var(--color-danger);
}

.match__row--error {
    background: var(--color-danger-soft);
}

.match__suggest {
    width: 34%;
    font-size: 0.85rem;
}

.match__waiting {
    margin: 0;
    font-size: 0.85rem;
}

/* The main-game suggestions: a short list of names to press, not a menu. */
.match__parents {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.match__parents form {
    display: flex;
    gap: var(--space-2);
    align-items: baseline;
}

.match__parents .linkbutton {
    text-align: left;
}

.match__state {
    margin: 0 0 var(--space-2);
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.match__state--sure {
    color: var(--color-ok);
}

.match__state--unsure {
    color: var(--color-text-muted);
}

.match__picks {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: var(--space-2);
}

.match__pick {
    padding: var(--space-1) var(--space-2);
    border-left: 3px solid transparent;
    background: var(--color-surface-2);
    border-radius: var(--radius-sm);
}

.match__pick--fits {
    border-left-color: var(--color-ok);
}

/* A link in behaviour, a button in markup: it changes the field, it does not
   navigate — so it must be reachable by keyboard and announced as a control. */
.match__pickname {
    display: block;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 600;
    color: var(--color-text);
    text-align: left;
    cursor: pointer;
}

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

.match__meta {
    display: block;
    color: var(--color-text-muted);
    font-size: 0.78rem;
}

/* The strip of open piles, shown on every curation screen. Each tab carries
   its count, so a pile that filled up while somebody worked on another one
   says so instead of waiting to be found. */
.match__tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}

.match__tab {
    display: inline-flex;
    align-items: baseline;
    gap: 0.4em;
    padding: 0.35em 0.8em;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    color: var(--color-text-muted);
    font-size: 0.85rem;
    text-decoration: none;
}

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

.match__tab--active {
    border-color: var(--color-accent);
    background: var(--color-accent);
    color: var(--color-on-accent, #fff);
    font-weight: 600;
}

.match__tab--empty {
    opacity: 0.55;
}

.match__tabcount {
    font-variant-numeric: tabular-nums;
    font-size: 0.8rem;
}

/* The two years side by side, with the gap that made the row a question. */
.match__years {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.6em;
    font-variant-numeric: tabular-nums;
}

.match__gap {
    color: var(--color-text-muted);
    font-size: 0.8rem;
}

.match__id {
    font-variant-numeric: tabular-nums;
}

@media (max-width: 40rem) {
    table.match th[scope="row"],
    .match__suggest {
        width: auto;
    }
}
