/*
 * GameRack — direction contract
 *
 * THESIS: A game library is a shelf of boxes and the paper that came inside them.
 * This refuses the streaming-service card grid — dark tiles, rounded corners,
 * hover-lift, pill chips — and rebuilds the catalog as printed matter.
 *
 * OWN-WORLD: Late-Showa Japanese boxed software. Flat spot inks on card stock:
 * warm shell black or kraft chipboard, one rationed vermilion, three source-band
 * colours. One Japanese gothic at four weights. Zero radius everywhere. Rules do
 * the work borders and shadows do elsewhere. Two registers only — the shelf
 * (boxes, head bands, cover art, shelf lips) and the manual (ruled tables, index
 * sections, caution panels, legends).
 *
 * STORY: Ben opens a library too large to hold in his head, is offered five real
 * ways in, and either takes one or narrows through the index until a box is worth
 * pulling off the shelf.
 *
 * FIRST VIEWPORT: Printed masthead band with the logotype, live library counts and
 * a ruled search field; beneath it the contents strip of five ways in; then the
 * shelf itself, boxes standing on their lips under source head bands.
 *
 * FORM: "The Shelf and the Manual", candidate 6 of the grounded list, assigned by
 * concept-seed key bb99b9bb. The dealt staging (threshold relay) was declined —
 * it models flow, and this product is a collection. See DESIGN.md.
 */

/* --- Faces --------------------------------------------------------------
   Zen Kaku Gothic New, self-hosted because the CSP is font-src 'self'.
   Latin subset only, ~9.5KB per weight. Licence in assets/fonts/OFL.txt. */

@font-face {
    font-family: "Zen Kaku Gothic New";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("../fonts/zen-kaku-gothic-new-400-U4PhKqO.woff2") format("woff2");
}

@font-face {
    font-family: "Zen Kaku Gothic New";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("../fonts/zen-kaku-gothic-new-500-bNt1IYr.woff2") format("woff2");
}

@font-face {
    font-family: "Zen Kaku Gothic New";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("../fonts/zen-kaku-gothic-new-700-pKfVnJ_.woff2") format("woff2");
}

@font-face {
    font-family: "Zen Kaku Gothic New";
    font-style: normal;
    font-weight: 900;
    font-display: swap;
    src: url("../fonts/zen-kaku-gothic-new-900-yRnElNZ.woff2") format("woff2");
}

/* Japanese, subset to exactly the glyphs the bilingual chrome uses and no more —
   4KB a weight instead of the ~2MB a full JP family costs. The unicode-range is
   what keeps these faces off Latin text entirely.
   Glyphs: ゲームラック ライブリ 目次 表示 並び替え 注意 絞り込み */

@font-face {
    font-family: "Zen Kaku Gothic New";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("../fonts/zen-kaku-gothic-new-ja-500-VH3nUXU.woff2") format("woff2");
    unicode-range: U+3048, U+3072-3073, U+307f, U+308a, U+3099, U+30a4, U+30af-30b2, U+30c3, U+30d5-30d6, U+30e0, U+30e9-30ea, U+30fc, U+4e26, U+610f, U+66ff, U+6b21, U+6ce8, U+76ee, U+793a, U+7d5e, U+8868, U+8fbc;
}

@font-face {
    font-family: "Zen Kaku Gothic New";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("../fonts/zen-kaku-gothic-new-ja-700-ZN7YiL4.woff2") format("woff2");
    unicode-range: U+3048, U+3072-3073, U+307f, U+308a, U+3099, U+30a4, U+30af-30b2, U+30c3, U+30d5-30d6, U+30e0, U+30e9-30ea, U+30fc, U+4e26, U+610f, U+66ff, U+6b21, U+6ce8, U+76ee, U+793a, U+7d5e, U+8868, U+8fbc;
}

/* --- Inks ---------------------------------------------------------------
   Dark is the default: this is used at a desk in the evening, and 1,100 pieces
   of cover art read better on a dark ground. Dark is the black-plastic shell
   with a printed label; light is the manual open on the desk. */

:root {
    color-scheme: dark light;

    --ground: #141210;
    --rail: #1e1a16;
    --box: #262019;
    --rule: #3a322a;
    --rule-strong: #584c3e;
    --ink: #f0e7d6;
    --ink-dim: #a9997f;

    /* Vermilion is the app's own voice: actions, cautions, active state.
       Never a data category. --accent-text is the value that clears 4.5:1
       as text on --ground; --accent is the fill. */
    --accent: #e8532e;
    --accent-text: #f2724f;
    --accent-ink: #1a1410;

    /* Source bands. Always paired with a printed label — never colour alone. */
    --src-steam: #5b90c6;
    --src-retro: #d9a441;
    --src-manual: #4fa396;
    --src-ink: #16120f;

    /* The lip a box stands on. It has to read clearly lighter than the box face
       in dark and clearly darker in light, or the shelf disappears and the cards
       flatten into tiles. */
    --shelf: #6b5942;
    --shelf-dark: #080706;
    --shelf-cast: rgba(0, 0, 0, 0.45);

    --s1: 4px;
    --s2: 8px;
    --s3: 12px;
    --s4: 16px;
    --s5: 24px;
    --s6: 32px;
    --s7: 48px;

    /* The one page-title step. Detail, manual entry, login and the empty state all
       use it, so the system has exactly one size above the headline. */
    --type-page-title: clamp(1.5rem, 3vw, 2rem);

    --font: "Zen Kaku Gothic New", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --font-code: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    /* Squared off. There is no other radius in this system. */
    --radius: 0px;

    --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-color-scheme: light) {
    :root {
        --ground: #dcd2be;
        --rail: #cfc3ab;
        --box: #f2ebdc;
        --rule: #b3a488;
        --rule-strong: #8a7a5e;
        --ink: #1e1915;
        /* Dim ink has to clear 4.5:1 against --rail, not just --ground: the index
           and the masthead sit on the darker of the two grounds. */
        --ink-dim: #574a3b;

        --accent: #c13a18;
        --accent-text: #a82e10;
        --accent-ink: #f7f1e4;

        --src-steam: #2f5f92;
        --src-retro: #8a6415;
        --src-manual: #2c6a60;
        --src-ink: #f7f1e4;

        --shelf: #7d6647;
        --shelf-dark: #5c4a33;
        --shelf-cast: rgba(60, 48, 30, 0.32);
    }
}

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

body {
    margin: 0;
    min-height: 100vh;
    background: var(--ground);
    color: var(--ink);
    font-family: var(--font);
    font-size: 0.9375rem;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
}

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

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

h1,
h2,
h3 {
    font-weight: 900;
    letter-spacing: 0.01em;
}

/* The printed legend — the system's most recognisable type gesture. */
.legend,
.stat-label,
.filter-group > legend,
.tri-label,
.game-table thead th,
.way-name,
.inline-label,
.page-indicator,
.notice-fields,
.back-link,
.field > span {
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1.1;
}

.muted {
    color: var(--ink-dim);
}

/* --- Bilingual chrome ----------------------------------------------------
   Japanese packaging pairs kana and Latin constantly, and the pairing is the
   look. These are decorative companions to the English beside them, never the
   only carrier of a meaning — so they are aria-hidden, and the interface reads
   identically with the glyphs missing. */

.ja {
    font-weight: 500;
    letter-spacing: 0.08em;
    color: var(--ink-dim);
}

/* Inline companion: WAYS IN 目次 */
.legend > .ja,
.notice-tag > .ja,
.inline-label > .ja,
.filter-summary-label > .ja {
    margin-left: 0.6em;
    font-size: 0.9em;
}

.notice-tag > .ja {
    color: var(--accent-ink);
    opacity: 0.72;
}

/* Stacked companion: the logotype's kana sits under the wordmark, the way a
   maker's name sits under its logo on a box. */
.brand-ja {
    display: block;
    margin-top: 3px;
    font-size: 0.5625rem;
    font-weight: 500;
    letter-spacing: 0.32em;
    color: var(--ink-dim);
}

.masthead-counts dt > .ja {
    margin-left: 0.5em;
    font-size: 0.9em;
    opacity: 0.75;
}

/* A drawn reference mark. Never a glyph — ※ and ■ are outside the Latin subset
   and would fall back to a tofu box. */
.mark::before {
    content: "";
    display: inline-block;
    width: 6px;
    height: 6px;
    margin-right: var(--s2);
    background: var(--accent);
    vertical-align: 0.08em;
}

/* --- Shell --------------------------------------------------------------- */

.app {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* --- Masthead: a printed band across the top of the box ------------------ */

.app-header {
    display: flex;
    align-items: center;
    gap: var(--s5);
    flex-wrap: wrap;
    padding: var(--s4) var(--s5);
    background: var(--rail);
    border-bottom: 3px solid var(--accent);
}

.brand {
    text-decoration: none;
    color: var(--ink);
}

.brand-mark {
    display: block;
    font-size: clamp(1.375rem, 2.4vw, 1.875rem);
    font-weight: 900;
    letter-spacing: -0.03em;
    line-height: 1;
}

.masthead-counts {
    display: flex;
    gap: var(--s5);
    margin: 0;
    padding: 0;
}

.masthead-counts > div {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
}

.masthead-counts dt {
    color: var(--ink-dim);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1.1;
}

.masthead-counts dd {
    margin: 0;
    font-size: 1.0625rem;
    font-weight: 900;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.search-form {
    display: flex;
    align-items: stretch;
    margin-left: auto;
}

.search-form input[type="search"] {
    min-width: 15rem;
}

.logout-form {
    margin: 0;
}

/* --- Controls ------------------------------------------------------------ */

input[type="text"],
input[type="search"],
input[type="password"],
input[type="number"],
input[type="date"],
select,
textarea {
    font-family: var(--font);
    font-size: 0.9375rem;
    color: var(--ink);
    background: var(--ground);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 9px 12px;
    min-width: 0;
}

input::placeholder {
    color: var(--ink-dim);
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    border-color: var(--accent);
}

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    font-family: var(--font);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    padding: 10px 20px;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--accent-ink);
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease);
}

.btn:hover {
    background: transparent;
    color: var(--accent-text);
}

.btn-ghost {
    background: transparent;
    color: var(--ink);
    border-color: var(--rule-strong);
}

.btn-ghost:hover {
    background: transparent;
    border-color: var(--accent);
    color: var(--accent-text);
}

/* Sits flush against the search field it submits. */
.btn-inline {
    border-left-width: 0;
}

.btn-danger {
    background: transparent;
    color: var(--accent-text);
    border-color: var(--accent);
}

.btn-danger:hover {
    background: var(--accent);
    color: var(--accent-ink);
}

.link-button {
    font-family: var(--font);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    padding: 8px 14px;
    background: transparent;
    color: var(--ink);
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius);
    cursor: pointer;
}

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

/* Square printed checkboxes and radios. */
input[type="checkbox"],
input[type="radio"] {
    appearance: none;
    width: 14px;
    height: 14px;
    flex: none;
    margin: 0;
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    background: var(--ground);
    display: inline-grid;
    place-items: center;
    cursor: pointer;
}

input[type="checkbox"]:checked,
input[type="radio"]:checked {
    background: var(--accent);
    border-color: var(--accent);
}

input[type="checkbox"]:checked::after {
    content: "";
    width: 7px;
    height: 4px;
    border-left: 2px solid var(--accent-ink);
    border-bottom: 2px solid var(--accent-ink);
    transform: translateY(-1px) rotate(-45deg);
}

input[type="radio"]:checked::after {
    content: "";
    width: 4px;
    height: 4px;
    background: var(--accent-ink);
}

.checkbox {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: 3px 0;
    cursor: pointer;
}

.checkbox .count {
    margin-left: auto;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink-dim);
}

.checkbox.subtle {
    color: var(--ink-dim);
    font-size: 0.8125rem;
}

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

.app-body {
    flex: 1;
    display: grid;
    grid-template-columns: clamp(15rem, 20vw, 18rem) minmax(0, 1fr);
    max-width: 108rem;
    width: 100%;
    margin: 0 auto;
}

.results-area {
    padding: var(--s5);
    min-width: 0;
}

/* --- Ways in: the manual's contents page ---------------------------------
   Every tile is a plain link carrying real query parameters. A tile the
   catalog cannot genuinely express is not written. */

.ways-in {
    padding-bottom: var(--s5);
    margin-bottom: var(--s5);
    border-bottom: 1px solid var(--rule);
}

.ways-in > .legend {
    display: block;
    color: var(--ink-dim);
    margin-bottom: var(--s3);
}

/* 10.5rem, not 12: at a 1280px window the results area leaves ~185px a tile once
   the rail and gaps are taken, and a 12rem floor broke the five into 4 + 1. */
.way-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
    gap: var(--s3);
    list-style: none;
    margin: 0;
    padding: 0;
}

.way {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--s3);
    height: 100%;
    padding: var(--s3);
    background: var(--box);
    border: 1px solid var(--rule);
    border-top: 2px solid var(--rule-strong);
    text-decoration: none;
    color: var(--ink);
    transition: border-color 0.15s var(--ease);
}

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

.way-index {
    font-size: 0.8125rem;
    font-weight: 900;
    font-variant-numeric: tabular-nums;
    color: var(--accent-text);
    line-height: 1.4;
}

.way-name {
    display: block;
    margin-bottom: var(--s1);
}

.way:hover .way-name {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.way-note {
    display: block;
    font-size: 0.75rem;
    line-height: 1.4;
    color: var(--ink-dim);
}

/* --- The index (filter panel) -------------------------------------------- */

.sidebar {
    background: var(--rail);
    border-right: 1px solid var(--rule);
    padding: var(--s5) var(--s4);
    align-self: start;
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow-y: auto;
}

.filter-summary {
    display: none;
}

.filter-form {
    display: flex;
    flex-direction: column;
    gap: var(--s5);
}

.filter-actions {
    display: flex;
    gap: var(--s2);
}

.filter-actions .btn {
    flex: 1;
}

.filter-group {
    margin: 0;
    padding: 0;
    border: 0;
    border-top: 2px solid var(--rule-strong);
}

.filter-group > legend {
    display: block;
    width: 100%;
    padding: var(--s3) 0 var(--s2);
    color: var(--ink);
}

.filter-group .unit {
    color: var(--ink-dim);
    letter-spacing: 0.08em;
}

.range-inputs,
.playtime-inputs {
    display: flex;
    align-items: center;
    gap: var(--s2);
}

.range-inputs input,
.playtime-inputs input {
    width: 100%;
    padding: 6px 8px;
    font-size: 0.8125rem;
}

/* Date fields stack. Two side by side do not fit the index rail once Firefox's
   picker button is accounted for, and the overflow takes the button with it. */
.date-inputs {
    display: grid;
    gap: var(--s2);
}

.date-field {
    display: grid;
    grid-template-columns: 2.5rem minmax(0, 1fr);
    align-items: center;
    gap: var(--s2);
}

.date-field > .legend {
    color: var(--ink-dim);
}

.date-field input[type="date"] {
    width: 100%;
    padding: 6px 8px;
    font-size: 0.8125rem;
}

.dash {
    color: var(--ink-dim);
}

.filter-note {
    margin: var(--s2) 0 0;
    font-size: 0.75rem;
    line-height: 1.45;
    color: var(--ink-dim);
}

.facet-list {
    max-height: 15rem;
    overflow-y: auto;
}

.tri-state {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    padding: var(--s1) 0;
}

.tri-label {
    color: var(--ink-dim);
}

.tri-options {
    display: flex;
    gap: var(--s3);
    flex-wrap: wrap;
}

.tri-options label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.8125rem;
    cursor: pointer;
}

.tri-options-wide {
    flex-direction: column;
    gap: var(--s1);
}

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

.results-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s4);
    flex-wrap: wrap;
    padding-bottom: var(--s3);
    border-bottom: 2px solid var(--rule-strong);
    margin-bottom: var(--s5);
}

.results-count {
    font-size: 1.0625rem;
    font-weight: 900;
    font-variant-numeric: tabular-nums;
}

.results-count .legend {
    display: block;
    color: var(--ink-dim);
    margin-bottom: var(--s1);
}

.sort-form {
    display: flex;
    align-items: center;
    gap: var(--s2);
    flex-wrap: wrap;
}

.inline-label {
    display: flex;
    align-items: center;
    gap: var(--s2);
    color: var(--ink-dim);
}

.sort-form select {
    padding: 6px 10px;
    font-size: 0.8125rem;
}

/* --- The caution panel ---------------------------------------------------
   The manual's ruled warning box. This is the loudest device in the system and
   it belongs to exactly one message: games hidden because a source has no data
   for a field you filtered on. */

.notice {
    background: var(--box);
    border: 1px solid var(--rule);
    border-top: 3px solid var(--accent);
    padding: var(--s4);
    margin-bottom: var(--s5);
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s3) var(--s5);
    animation: stamp 0.24s var(--ease) both;
}

.notice-tag {
    grid-column: 1 / -1;
    justify-self: start;
    background: var(--accent);
    color: var(--accent-ink);
    padding: 4px 10px;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

.notice-body {
    margin: 0;
    max-width: 68ch;
}

.notice-count {
    font-size: 1.375rem;
    font-weight: 900;
    font-variant-numeric: tabular-nums;
    color: var(--accent-text);
    vertical-align: -0.05em;
}

.notice-fields {
    color: var(--ink);
}

.inline-form {
    justify-self: end;
}

@media (max-width: 40rem) {
    .notice {
        grid-template-columns: minmax(0, 1fr);
    }

    .inline-form {
        justify-self: start;
    }
}

/* --- The shelf ----------------------------------------------------------- */

.game-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11.5rem, 1fr));
    gap: 20px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.game-card {
    animation: settle 0.26s var(--ease) both;
}

/* Stagger the opening rows only: enough to read as one settling motion, short
   enough that paging never feels held up. */
.game-card:nth-child(1) { animation-delay: 0ms; }
.game-card:nth-child(2) { animation-delay: 14ms; }
.game-card:nth-child(3) { animation-delay: 28ms; }
.game-card:nth-child(4) { animation-delay: 42ms; }
.game-card:nth-child(5) { animation-delay: 56ms; }
.game-card:nth-child(6) { animation-delay: 70ms; }
.game-card:nth-child(7) { animation-delay: 84ms; }
.game-card:nth-child(8) { animation-delay: 98ms; }

.game-card > a {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--box);
    border: 1px solid var(--rule);
    border-bottom: 4px solid var(--shelf);
    box-shadow: 0 4px 0 var(--shelf-dark), 0 9px 14px var(--shelf-cast);
    color: var(--ink);
    text-decoration: none;
    transition: border-color 0.15s var(--ease);
}

.game-card > a:hover,
.game-card > a:focus-visible {
    border-color: var(--accent);
}

/* The publisher's band across the head of the box. Colour AND label, always. */
.source-band {
    padding: 4px var(--s2);
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--src-ink);
    background: var(--rule-strong);
}

.source-band.is-steam { background: var(--src-steam); }
.source-band.is-retroachievements { background: var(--src-retro); }
.source-band.is-manual { background: var(--src-manual); }

.cover {
    aspect-ratio: 3 / 4;
    background: var(--rail);
    display: grid;
    place-items: center;
    overflow: hidden;
}

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

.cover-fallback {
    font-size: 2.5rem;
    font-weight: 900;
    color: var(--ink-dim);
}

.card-body {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    padding: var(--s3);
    flex: 1;
}

.card-body h3 {
    margin: 0;
    font-size: 0.9375rem;
    font-weight: 700;
    line-height: 1.28;
    letter-spacing: 0;
}

.game-card > a:hover h3 {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* The spec strip: the manual's table, shrunk onto the box. */
.spec-strip {
    margin: auto 0 0;
    padding-top: var(--s2);
    border-top: 1px solid var(--rule);
    display: grid;
    gap: 2px;
}

.spec-strip > div {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s2);
}

.spec-strip dt {
    font-size: 0.5625rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-dim);
}

.spec-strip dd {
    margin: 0;
    font-size: 0.75rem;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.spec-strip .is-null {
    color: var(--ink-dim);
    font-weight: 400;
}

/* --- The spec table (table view) ----------------------------------------- */

.game-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.875rem;
}

.game-table thead th {
    text-align: left;
    padding: 0 var(--s3) var(--s2);
    color: var(--ink-dim);
    border-bottom: 2px solid var(--rule-strong);
}

.game-table thead th.num,
.game-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.game-table td {
    padding: var(--s2) var(--s3);
    border-bottom: 1px solid var(--rule);
}

.game-table tbody tr:hover {
    background: var(--box);
}

.game-table a {
    color: var(--ink);
    text-decoration: none;
    font-weight: 700;
}

.game-table a:hover {
    color: var(--accent-text);
    text-decoration: underline;
    text-underline-offset: 3px;
}

.game-table .is-null {
    color: var(--ink-dim);
}

/* --- Badges: a printed tag, not a pill ----------------------------------- */

.badge {
    display: inline-block;
    padding: 3px var(--s2);
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    border: 1px solid var(--rule-strong);
    color: var(--ink);
    text-decoration: none;
    background: transparent;
}

a.badge:hover {
    border-color: var(--accent);
    color: var(--accent-text);
}

.badge.is-steam { background: var(--src-steam); border-color: var(--src-steam); color: var(--src-ink); }
.badge.is-retroachievements { background: var(--src-retro); border-color: var(--src-retro); color: var(--src-ink); }
.badge.is-manual { background: var(--src-manual); border-color: var(--src-manual); color: var(--src-ink); }

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

.pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--s4);
    margin-top: var(--s6);
    padding-top: var(--s4);
    border-top: 2px solid var(--rule-strong);
}

.page-indicator {
    font-variant-numeric: tabular-nums;
    color: var(--ink-dim);
}

/* --- Detail page: the back of the box, and the manual spread ------------- */

.detail {
    max-width: 68rem;
    width: 100%;
    margin: 0 auto;
    padding: var(--s5);
}

.back-link {
    display: inline-block;
    text-decoration: none;
    color: var(--ink-dim);
    margin-bottom: var(--s5);
}

.back-link:hover {
    color: var(--accent-text);
}

.detail-head {
    display: grid;
    grid-template-columns: 16rem minmax(0, 1fr);
    gap: var(--s6);
    padding-bottom: var(--s6);
    border-bottom: 2px solid var(--rule-strong);
    margin-bottom: var(--s6);
}

.detail-cover {
    width: 100%;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    border: 1px solid var(--rule);
    border-bottom: 4px solid var(--shelf);
    box-shadow: 0 4px 0 var(--shelf-dark), 0 9px 14px var(--shelf-cast);
}

.detail h1 {
    margin: 0 0 var(--s3);
    font-size: var(--type-page-title);
    font-weight: 900;
    letter-spacing: -0.02em;
    line-height: 1.15;
}

.detail h2 {
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-dim);
    padding-top: var(--s3);
    border-top: 2px solid var(--rule-strong);
    margin: var(--s6) 0 var(--s3);
}

.detail .meta {
    display: flex;
    align-items: center;
    gap: var(--s3);
    flex-wrap: wrap;
    margin: 0 0 var(--s4);
}

.summary {
    max-width: 68ch;
    color: var(--ink-dim);
    margin: 0;
}

/* Ruled spec rows in two columns rather than a tile grid: a partial last row of
   tiles left a rule that stopped halfway across the page, which read as a bug.
   Label left, figure right — the manual's own table. */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    column-gap: var(--s7);
    border-top: 2px solid var(--rule-strong);
}

.stat {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s4);
    padding: var(--s3) 0;
    border-bottom: 1px solid var(--rule);
}

.stat-label {
    color: var(--ink-dim);
}

.stat-value {
    font-size: 1.0625rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    text-align: right;
}

.stat-unknown {
    color: var(--ink-dim);
    font-weight: 400;
}

.tag-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    list-style: none;
    margin: 0;
    padding: 0;
}

.sync-info {
    margin-top: var(--s7);
    padding-top: var(--s3);
    border-top: 1px solid var(--rule);
    font-size: 0.75rem;
}

.sync-info p {
    margin: 0;
}

/* --- Manual entry -------------------------------------------------------- */

.manual {
    max-width: 46rem;
    width: 100%;
    margin: 0 auto;
    padding: var(--s5);
}

.manual h1 {
    font-size: var(--type-page-title);
    letter-spacing: -0.02em;
    margin: 0 0 var(--s5);
}

.manual-form,
.manual-search {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s4);
}

/* Fields fill the column; buttons do not. A full-bleed vermilion bar is far more
   weight than "Search" is asking for. */
.manual-form > .btn,
.manual-search > .btn {
    align-self: flex-start;
}

.field {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
}

.field > span {
    color: var(--ink-dim);
}

.manual-delete {
    margin-top: var(--s6);
    padding-top: var(--s4);
    border-top: 2px solid var(--rule-strong);
}

.match-list {
    display: grid;
    gap: var(--s2);
    list-style: none;
    margin: 0;
    padding: 0;
}

.match {
    display: grid;
    grid-template-columns: 3rem minmax(0, 1fr);
    gap: var(--s3);
    align-items: center;
    padding: var(--s2);
    background: var(--box);
    border: 1px solid var(--rule);
}

.match.chosen {
    border-color: var(--accent);
}

.match-art {
    width: 3rem;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    border: 1px solid var(--rule);
}

.match-text {
    min-width: 0;
}

/* --- States: empty, flash, error ----------------------------------------- */

.empty-state {
    max-width: 42rem;
    margin: var(--s7) auto;
    padding: var(--s6);
    background: var(--box);
    border: 1px solid var(--rule);
    border-top: 3px solid var(--accent);
}

.empty-state h2 {
    margin: 0 0 var(--s3);
    font-size: var(--type-page-title);
    letter-spacing: -0.02em;
}

.empty-state pre {
    background: var(--ground);
    border: 1px solid var(--rule);
    padding: var(--s3);
    overflow-x: auto;
    font-family: var(--font-code);
    font-size: 0.8125rem;
}

code {
    font-family: var(--font-code);
    font-size: 0.875em;
}

.empty {
    padding: var(--s7) 0;
    text-align: center;
}

.flash {
    margin: 0;
    padding: var(--s3) var(--s5);
    background: var(--box);
    border-bottom: 2px solid var(--accent);
    font-size: 0.875rem;
}

.alert {
    margin: 0;
    padding: var(--s3);
    background: var(--box);
    border: 1px solid var(--accent);
    font-size: 0.875rem;
}

/* --- Login: the back of the box ------------------------------------------ */

.login-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: var(--s5);
}

.login-card {
    width: 100%;
    max-width: 22rem;
    display: flex;
    flex-direction: column;
    gap: var(--s4);
    padding: var(--s6);
    background: var(--box);
    border: 1px solid var(--rule);
    border-top: 3px solid var(--accent);
    box-shadow: 0 3px 0 var(--shelf-dark), 0 6px 10px var(--shelf-cast);
}

.login-card h1 {
    margin: 0;
    font-size: var(--type-page-title);
    font-weight: 900;
    letter-spacing: -0.03em;
}

.subtitle {
    margin: calc(var(--s2) * -1) 0 0;
    font-size: 0.8125rem;
    color: var(--ink-dim);
}

/* --- Motion --------------------------------------------------------------
   One authored moment: the shelf settling. Everything else is a border colour. */

@keyframes settle {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
}

@keyframes stamp {
    from {
        opacity: 0;
        transform: scale(1.015);
    }
}

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

/* --- Narrow -------------------------------------------------------------- */

@media (max-width: 60rem) {
    .app-body {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Explicit rows, because a wrapping flex row put sign-out ahead of the search
       field and squeezed the input to nothing. */
    .app-header {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: var(--s3);
        padding: var(--s3) var(--s4);
    }

    .brand {
        grid-column: 1;
        grid-row: 1;
        align-self: center;
    }

    .logout-form {
        grid-column: 2;
        grid-row: 1;
        justify-self: end;
    }

    .search-form {
        grid-column: 1 / -1;
        margin-left: 0;
    }

    .search-form input[type="search"] {
        min-width: 0;
        width: 100%;
        flex: 1;
    }

    .masthead-counts {
        grid-column: 1 / -1;
        flex-wrap: wrap;
        gap: var(--s3) var(--s5);
    }

    .sidebar {
        position: static;
        max-height: none;
        border-right: 0;
        border-bottom: 1px solid var(--rule);
        padding: 0;
    }

    /* Native <details>: collapses with no script, stays keyboard accessible. */
    .filter-summary {
        display: flex;
        align-items: center;
        gap: var(--s2);
        padding: var(--s4);
        cursor: pointer;
        list-style: none;
        font-size: 0.6875rem;
        font-weight: 700;
        letter-spacing: 0.14em;
        text-transform: uppercase;
    }

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

    .filter-summary-active {
        padding: 2px 6px;
        background: var(--accent);
        color: var(--accent-ink);
        font-size: 0.5625rem;
    }

    .filter-summary-chevron {
        margin-left: auto;
        width: 8px;
        height: 8px;
        border-right: 2px solid var(--ink-dim);
        border-bottom: 2px solid var(--ink-dim);
        transform: rotate(45deg) translate(-2px, -2px);
    }

    .filter-panel[open] .filter-summary-chevron {
        transform: rotate(-135deg) translate(-2px, -2px);
    }

    .filter-panel-body {
        padding: 0 var(--s4) var(--s5);
    }

    .results-area {
        padding: var(--s4);
    }

    .game-grid {
        grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
        gap: var(--s3);
    }

    .detail-head {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--s4);
    }

    .detail-cover {
        width: 12rem;
    }
}

/* On desktop the panel is permanently open and its summary is hidden entirely.
   filter_panel_controller.js sets [open] directly; this is the no-JavaScript
   fallback, and it has to fight the <details> element's own hiding twice —
   older engines hide the non-summary children, newer Chromium hides
   ::details-content instead. */
@media (min-width: 60.0625rem) {
    .filter-panel > .filter-panel-body {
        display: block;
    }

    .filter-panel::details-content {
        content-visibility: visible;
        block-size: auto;
    }
}
