:root {
    color-scheme: dark;
    /* Default theme (Ash): neutral charcoal with the brand green + amber. A theme picked in
       the picker sets data-theme on <html>, overriding these below. */
    --bg:          #111111;
    --bg-raised:   #252525;
    --bg-input:    #111111;
    --text:        #e0ddd8;
    --subtle:      #808080;
    --leaf:        #5a8a4a;
    --leaf-light:  #7aaa6a;
    --sky:         #88aacc;
    --sky-hover:   #6a8aac;
    --plum:        #6d5868;
    --gold:        #b89050;
    --error:       #d97070;
    --success:     #7ab870;
    --radius:      0;
    --max-w:       480px;
    --font-display: 'Playfair Display', Georgia, serif;
    --font-body:    'DM Sans', system-ui, sans-serif;
    /* Role fonts: change these to restyle one role site-wide. The logo and numerals (card
       rank, stats) stay on --font-display as brand accents. Playfair is loaded at 400/700. */
    --font-heading:    var(--font-display);
    --font-card-title: var(--font-display);
}

/* ── Themes ── */
/* Earth: the original default — warm browns with the brand green + amber. */
:root[data-theme="earth"] {
    --bg:         #16100d;
    --bg-raised:  #3e2d24;
    --bg-input:   #16100d;
    --text:       #ede0cf;
    --subtle:     #9e8070;
    --leaf:       #5a8a4a;
    --leaf-light: #7ab560;
    --sky:        #7abfcf;
    --sky-hover:  #5aafc0;
    --gold:       #c8892a;
    --error:      #d97070;
}
:root[data-theme="ocean"] {
    --bg:         #0b1520;
    --bg-raised:  #162535;
    --bg-input:   #0b1520;
    --text:       #c8d8e8;
    --subtle:     #5a7a9a;
    --leaf:       #2a8a7a;
    --leaf-light: #4aaa9a;
    --sky:        #5aafc0;
    --sky-hover:  #4a9fb0;
    --gold:       #c8a840;
    --error:      #d97070;
}
:root[data-theme="void"] {
    --bg:         #0e0a18;
    --bg-raised:  #1e1530;
    --bg-input:   #0e0a18;
    --text:       #d8cff4;
    --subtle:     #7060a0;
    --leaf:       #7050d0;
    --leaf-light: #9070f0;
    --sky:        #70c0d0;
    --sky-hover:  #50a0b0;
    --gold:       #d080c0;
    --error:      #e06878;
}
:root[data-theme="ember"] {
    --bg:         #120e08;
    --bg-raised:  #2a1e10;
    --bg-input:   #120e08;
    --text:       #f0e0c8;
    --subtle:     #806040;
    --leaf:       #d06020;
    --leaf-light: #f08040;
    --sky:        #c0a030;
    --sky-hover:  #a08020;
    --gold:       #e8c050;
    --error:      #e06050;
}
/* Moss: forest-floor greens balanced by non-green notes (parchment text, stone-blue links,
   amber accent) so it doesn't read as all-green / an off monitor. */
:root[data-theme="moss"] {
    --bg:         #0e130f;
    --bg-raised:  #1c261e;
    --bg-input:   #0e130f;
    --text:       #e8e4d6;
    --subtle:     #8a9684;
    --leaf:       #6f9a4f;
    --leaf-light: #94bf6c;
    --sky:        #8db4c6;
    --sky-hover:  #74a0b4;
    --gold:       #d6a052;
    --error:      #d97070;
}
:root[data-theme="dusk"] {
    --bg:         #0e0818;
    --bg-raised:  #1e1428;
    --bg-input:   #0e0818;
    --text:       #e0d0f0;
    --subtle:     #705890;
    --leaf:       #c040a0;
    --leaf-light: #e060c0;
    --sky:        #8090e0;
    --sky-hover:  #6070c0;
    --gold:       #e0a040;
    --error:      #e06878;
}
:root[data-theme="slate"] {
    --bg:         #0a0e14;
    --bg-raised:  #141e28;
    --bg-input:   #0a0e14;
    --text:       #c8d8e8;
    --subtle:     #4a6080;
    --leaf:       #4080c0;
    --leaf-light: #6090d0;
    --sky:        #40c0d0;
    --sky-hover:  #30a0b0;
    --gold:       #c0a840;
    --error:      #d97070;
}
/* Rosewood: deep wine, with sage links and an amber accent so it isn't all red. */
:root[data-theme="rosewood"] {
    --bg:         #150d0f;
    --bg-raised:  #2a1b1f;
    --bg-input:   #150d0f;
    --text:       #efe2dc;
    --subtle:     #a08a8e;
    --leaf:       #b04a58;
    --leaf-light: #d06c78;
    --sky:        #9fc0b0;
    --sky-hover:  #86ab9a;
    --gold:       #d9a85e;
    --error:      #e08a70;
}

/* Paper: the one light theme — a soft parchment (not white) with ink text. Cards and the
   square-art title band go light too; only titles drawn directly over cover images keep light
   text (with a shadow), since the art behind them is usually dark. */
:root[data-theme="paper"] {
    color-scheme: light;
    --bg:         #e0d9cc;
    --bg-raised:  #d3cabb;
    --bg-input:   #ebe5da;
    --text:       #2a2420;
    --subtle:     #5e544c;
    --leaf:       #4a7339;
    --leaf-light: #3a602c;
    --sky:        #255f75;
    --sky-hover:  #1a4a5c;
    --gold:       #8a5210;
    --error:      #a13a36;
    --success:    #3a6e36;
}
/* Title band under square art: a slightly darker plate, debossed (light edge below the text). */
:root[data-theme="paper"] .grid-slot--square-art::before {
    background-color: #c9bfae;
    background-image: radial-gradient(color-mix(in srgb, var(--text) 14%, transparent) 0.7px, transparent 0.9px);
    box-shadow: inset 0 3px 5px -3px rgba(0, 0, 0, 0.35);
}
:root[data-theme="paper"] .grid-slot--square-art .slot-title--cover {
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
}
/* Titles and rank numbers drawn over cover art stay light. */
:root[data-theme="paper"] .grid-slot--cover:not(.grid-slot--square-art):not(.grid-slot--place):not(.cover-failed) .slot-title--cover {
    color: #f6f1e8;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}
:root[data-theme="paper"] .grid-slot--cover:not(.grid-slot--place):not(.cover-failed) .slot-rank--cover {
    color: #f0c070;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
}
/* On paper a glow reads as a smudge: cards get a soft drop shadow instead (green on hover). */
:root[data-theme="paper"] .grid-slot--filled {
    border-color: #b7ab97;
    box-shadow: 0 2px 6px rgba(60, 45, 30, 0.28);
}
:root[data-theme="paper"] .grid-slot--filled:hover {
    border-color: var(--leaf);
    box-shadow: 0 3px 10px rgba(60, 45, 30, 0.35);
}
/* Empty and blank slots: an outline that shows against the page, numbers in a mid tone. */
:root[data-theme="paper"] .grid-slot--empty,
:root[data-theme="paper"] .grid-slot--blank { border-color: #a89c88; }
:root[data-theme="paper"] .grid-slot--empty:hover { border-color: var(--leaf); }
:root[data-theme="paper"] .grid-slot--empty .slot-rank,
:root[data-theme="paper"] .grid-slot--blank .slot-rank { color: var(--subtle); }
/* Hover pencils sit on a dark chip; keep their icon light. */
:root[data-theme="paper"] .hover-edit-btn { color: #e8b868; }

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }

body {
    font-family: var(--font-body);
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}
h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }
/* Base link color: any link without its own rule follows the theme (never the browser's
   default blue, or purple once visited). Component rules below still win. */
a { color: var(--sky); }
a:hover { color: var(--sky-hover); }

main {
    flex: 1;
}

/* ── Header ── */
header {
    /* 3-column grid: equal 1fr side columns flank an auto-width middle, so the
       tools cluster stays pinned to true center no matter the logo/nav widths. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    column-gap: 1.5rem;
    align-items: center;
    padding: 0.9rem 2rem;
    background: var(--bg-raised);
    box-shadow: 0 4px 6px color-mix(in srgb, var(--leaf) 45%, transparent);
    position: relative;
}
.logo { justify-self: start; }
nav { justify-self: end; }

.logo {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 1.5rem;
    text-decoration: none;
    color: var(--text);
    letter-spacing: 0.02em;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.logo-icon {
    color: var(--leaf);
    height: 2.1rem;
    width: auto;
    flex-shrink: 0;
}

nav { display: flex; gap: 1.25rem; align-items: center; }
nav a {
    text-decoration: none;
    color: var(--subtle);
    font-size: 0.9rem;
    font-weight: 500;
    white-space: nowrap;
    transition: color 0.15s;
}
nav a:hover { color: var(--sky); }

/* Hamburger button — hidden on desktop, shown at the mobile breakpoint below. */
.nav-toggle { display: none; }

/* Desktop: give each header nav link a hit area that fills the full header
   height (top edge to bottom edge), not just its tight text box. The link
   stretches to the row, then padding + matching negative margin extend it into
   the header's own 0.9rem vertical padding without growing the bar. */
@media (min-width: 901px) {
    header nav { align-self: stretch; }
    header nav a {
        align-self: stretch;      /* only the links fill the height, not the theme picker */
        display: flex;
        align-items: center;
        box-sizing: content-box;
        padding-block: 0.9rem;
        margin-block: -0.9rem;
    }
}

/* ── Mobile header (≤900px): logo + randomizer stay in the bar; nav collapses
   into a hamburger dropdown, with the theme picker as an inline accordion. ── */
@media (max-width: 900px) {
    header { padding: 0.6rem 1rem; }

    /* Drop the utility hash chip on mobile (kept the randomizer, per choice A). */
    .header-tools .node-id-chip { display: none; }

    .nav-toggle {
        display: flex;
        flex-direction: column;
        justify-content: center;
        justify-self: end;
        gap: 4px;
        width: 2.4rem;
        height: 2.4rem;
        padding: 0.5rem;
        background: none;
        border: 1px solid var(--bg-raised);
        cursor: pointer;
        flex-shrink: 0;
    }
    .nav-toggle-bar {
        display: block;
        width: 100%;
        height: 2px;
        background: var(--text);
        transition: transform 0.2s, opacity 0.2s;
    }
    header.nav-open .nav-toggle-bar:nth-child(1) { transform: translateY(6px) rotate(45deg); }
    header.nav-open .nav-toggle-bar:nth-child(2) { opacity: 0; }
    header.nav-open .nav-toggle-bar:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

    /* Nav becomes a full-width dropdown panel under the header. Scoped to the
       header nav so it never touches the footer's <nav class="footer-nav">. */
    header nav {
        display: none;
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        padding: 0.25rem 1rem 1rem;
        background: var(--bg-raised);
        box-shadow: 0 6px 8px color-mix(in srgb, var(--leaf) 30%, transparent);
        z-index: 300;
    }
    header.nav-open nav { display: flex; }
    header nav a {
        padding: 0.85rem 0.25rem;
        font-size: 1rem;
        border-top: 1px solid color-mix(in srgb, var(--subtle) 25%, transparent);
    }

    /* Theme picker: inline accordion instead of a floating overlay. */
    .theme-picker { width: 100%; }
    .theme-trigger { width: 100%; justify-content: space-between; padding: 0.85rem 0.25rem; }
    .theme-dropdown {
        position: static;
        min-width: 0;
        padding: 0.25rem 0 0.5rem;
        background: none;
        border: none;
    }
}

/* ── Footer ── */
footer {
    margin-top: 3rem;
    padding: 2rem;
    text-align: center;
    border-top: 1px solid var(--bg-raised);
}
footer p { margin: 0; }
footer a {
    color: var(--sky);
    text-decoration: none;
    font-size: 0.9rem;
}
footer a:hover {
    text-decoration: underline;
}

/* ── Content detail ── */
.content-detail { max-width: 700px; }
.content-cover {
    float: left;
    margin: 0.25rem 1.75rem 1rem 0;
    max-width: 200px;
    width: 100%;
    box-shadow: 4px 4px 0 rgba(0,0,0,0.5);
    display: block;
}
.content-detail h1 {
    font-family: var(--font-heading);
    font-size: clamp(1.5rem, 4vw, 2.5rem);
    color: var(--text);
    margin-bottom: 0.35rem;
}
.content-meta {
    font-size: 0.85rem;
    color: var(--subtle);
    margin-bottom: 0.75rem;
    letter-spacing: 0.03em;
}
.content-description {
    color: var(--text);
    line-height: 1.7;
    font-size: 0.95rem;
    white-space: pre-wrap;
}
.content-attribution {
    margin-top: 1rem;
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.8rem;
    color: var(--subtle);
    letter-spacing: 0.03em;
}
.content-attribution { flex-wrap: wrap; }
.content-attribution a:not(.bgg-attribution),
.list-sources a:not(.bgg-attribution) { color: var(--sky); text-decoration: none; }
.content-attribution a:not(.bgg-attribution):hover,
.list-sources a:not(.bgg-attribution):hover { text-decoration: underline; }
/* "Sources:" line under a list's grid (includes/sources.php). */
.list-sources {
    margin-top: 1.25rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8rem;
    color: var(--subtle);
}
.list-source-bgg { display: inline-flex; align-items: center; gap: 0.4rem; }
.list-sources .bgg-attribution { display: inline-flex; line-height: 0; }
.list-sources .bgg-attribution img {
    height: 1.5rem;
    width: auto;
    background: #e2e2e2;
    padding: 0.15rem 0.3rem;
    border-radius: var(--radius);
}
.content-attribution .bgg-attribution { display: inline-flex; line-height: 0; }
/* BGG's logo has a dark-navy wordmark; a soft-white chip keeps it legible on every theme
   without recoloring the official mark. Uses the project's off-white (#e2e2e2), not pure white. */
.content-attribution .bgg-attribution img {
    height: 1.85rem;
    width: auto;
    background: #e2e2e2;
    padding: 0.2rem 0.35rem;
    border-radius: var(--radius);
}

/* ── Owner remark (list backstory / per-item note) ──
   A bottom-of-page author note, deliberately bordered in a strong accent so it reads as
   commentary, not part of the content. Collapsed to 3 lines; JS adds .remark-body--expanded
   and reveals the toggle only when the text actually overflows. Absent → not rendered, so
   there's no empty box or leftover margin. */
.remark-block {
    margin-top: 2rem;
    padding: 1rem 1.25rem;
    border: 2px solid var(--gold);
    border-radius: var(--radius);
    background: var(--bg-raised);
}
.remark-label {
    margin: 0 0 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--gold);
}
.remark-body {
    color: var(--text);
    line-height: 1.6;
    font-size: 0.95rem;
    /* Clamp to 3 lines until expanded; nl2br <br>s count as line breaks within the box. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.remark-body--expanded {
    display: block;
    -webkit-line-clamp: unset;
    overflow: visible;
}
.remark-expand {
    margin-top: 0.5rem;
    padding: 0;
    background: none;
    border: none;
    color: var(--sky);
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}
.remark-expand:hover { text-decoration: underline; }
.remark-edit-btn { margin-top: 0.75rem; }
.remark-add-row  { margin-top: 1.5rem; }
.remark-form { margin-top: 1.5rem; flex-direction: column; align-items: stretch; gap: 0.6rem; }
.remark-form textarea {
    width: 100%;
    resize: vertical;
    font: inherit;
    line-height: 1.6;
    padding: 0.6rem 0.75rem;
    color: var(--text);
    background: var(--bg-raised);
    border: 1px solid var(--subtle);
    border-radius: var(--radius);
}
.remark-form-actions { display: flex; gap: 0.5rem; }

/* ── Theme picker ── */
.theme-picker { position: relative; display: flex; flex-direction: column; }
.theme-trigger { display: flex; align-items: center; gap: 0.3rem; cursor: pointer; }
.theme-toggle {
    background: none;
    border: 1px solid var(--bg-raised);
    color: var(--subtle);
    cursor: pointer;
    padding: 0.3rem;
    line-height: 1;
    display: flex;
    align-items: center;
    transition: color 0.15s, border-color 0.15s;
}
.theme-toggle:hover { color: var(--text); border-color: var(--subtle); }
.theme-dropdown {
    position: absolute;
    right: 0;
    top: calc(100% + 0.5rem);
    background: var(--bg-raised);
    border: 1px solid var(--leaf);
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.4rem;
    z-index: 200;
    min-width: 140px;
}
.theme-swatch-btn {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: none;
    border: 1px solid transparent;
    color: var(--text);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 0.85rem;
    padding: 0.35rem 0.5rem;
    text-align: left;
    width: 100%;
    transition: border-color 0.15s;
}
.theme-swatch-btn:hover { border-color: var(--subtle); }
.theme-swatch-btn.active { border-color: var(--leaf); }
.swatch-preview {
    display: flex;
    width: 48px;
    height: 18px;
    flex-shrink: 0;
    overflow: hidden;
    border: 1px solid rgba(255,255,255,0.1);
}
.swatch-inline { cursor: pointer; }
.swatch-seg { flex: 1; }
.swatch-label { color: var(--text); }

/* ── Main ── */
main {
    max-width: 680px;
    margin: 3rem auto;
    padding: 0 1.5rem;
}

/* ── Hero ── */
.hero {
    text-align: center;
    margin-bottom: 2.75rem;
}
.hero h1 {
    font-family: var(--font-heading);
    font-size: clamp(2rem, 8vw, 3.5rem);
    font-weight: 700;
    line-height: 1.15;
    margin-bottom: 0.875rem;
    color: var(--text);
    white-space: nowrap;
}
.hero p {
    color: var(--subtle);
    font-size: 1.05rem;
}

/* ── Auth card ── */
.auth-section {
    background: var(--bg-raised);
    border: 3px solid var(--leaf);
    border-radius: var(--radius);
    overflow: hidden;
    max-width: var(--max-w);
    margin: 0 auto;
}

.auth-tabs {
    display: flex;
    border-bottom: 2px solid var(--leaf);
}

.tab {
    flex: 1;
    padding: 0.85rem;
    background: none;
    border: none;
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--subtle);
    transition: color 0.15s, background 0.15s;
}
.tab:hover { color: var(--text); }
.tab.active {
    color: var(--sky);
    background: var(--bg);
    font-weight: 600;
    border-bottom: 2px solid var(--sky);
    margin-bottom: -2px;
}

.auth-panel {
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
    padding: 1.75rem;
}

.auth-forgot { align-self: center; font-size: 0.85rem; color: var(--sky); text-decoration: none; }
.auth-forgot:hover { text-decoration: underline; }
/* Password reset page (reset.php) reuses the sign-in box. */
.reset-heading { font-family: var(--font-heading); font-size: 1.4rem; padding: 1.5rem 1.75rem 0; }
.reset-hint { font-size: 0.85rem; color: var(--subtle); line-height: 1.5; margin: 0; }
.reset-back { padding: 0 1.75rem 1.5rem; font-size: 0.85rem; }
.reset-back a { color: var(--sky); text-decoration: none; }
.reset-back a:hover { text-decoration: underline; }

/* ── Inputs ── */
input[type="text"],
input[type="email"],
input[type="password"] {
    width: 100%;
    padding: 0.65rem 0.875rem;
    background: var(--bg-input);
    color: var(--text);
    border: 2px solid color-mix(in srgb, var(--leaf) 50%, transparent);
    border-radius: var(--radius);
    font-family: var(--font-body);
    font-size: 0.95rem;
    outline: none;
    transition: border-color 0.15s;
}
input::placeholder { color: var(--subtle); }
input:focus { border-color: var(--leaf); }

/* ── Buttons ── */
.btn-primary {
    width: 100%;
    padding: 0.7rem;
    background: var(--leaf);
    color: #fff;
    border: none;
    border-radius: var(--radius);
    font-family: var(--font-body);
    font-size: 0.95rem;
    font-weight: 600;
    cursor: pointer;
    letter-spacing: 0.02em;
    transition: background 0.15s;
}
.btn-primary:hover:not(:disabled) { background: var(--leaf-light); }
/* Disabled buttons (sending, sent, nothing to save) look inactive and don't react to hover. */
.btn-primary:disabled,
.btn-small:disabled { opacity: 0.5; cursor: default; }

/* ── Messages ── */
.error-msg   { color: var(--error);   font-size: 0.875rem; }
.success-msg { color: var(--success); font-size: 0.875rem; }

/* ── Welcome (authed) ── */
.welcome h1 {
    font-family: var(--font-heading);
    font-size: 1.75rem;
    margin-bottom: 0.5rem;
}
.welcome a { color: var(--sky); text-decoration: none; }
.welcome a:hover { text-decoration: underline; }

/* ── Node page ── */
.node-page {
    width: 100%;
    max-width: 900px;
    margin: 0 auto;
    padding: 1.5rem 1.5rem;
}
.node-header {
    margin-bottom: 0.75rem;
    padding-bottom: 0.5rem;
}
.node-type-label {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--subtle);
    margin-bottom: 0.4rem;
}
.node-header h1 {
    font-family: var(--font-heading);
    font-size: 2rem;
    color: var(--text);
    margin-bottom: 0.5rem;
}
.node-description {
    color: var(--subtle);
    font-size: 0.95rem;
    margin-top: 0.4rem;
}
.empty-list {
    color: var(--subtle);
    font-style: italic;
    padding: 2rem 0;
}
.node-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.node-card {
    display: flex;
    align-items: center;
    gap: 1rem;
    background: var(--bg-raised);
    border: 2px solid transparent;
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    transition: border-color 0.15s;
}
.node-card:hover { border-color: var(--leaf); }
.node-rank {
    font-family: var(--font-display);
    font-size: 1.1rem;
    color: var(--gold);
    min-width: 1.8rem;
    text-align: right;
    flex-shrink: 0;
}
.node-card-link {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    text-decoration: none;
    flex: 1;
}
.node-card-title {
    color: var(--text);
    font-weight: 500;
}
.node-card-subtype {
    font-size: 0.75rem;
    color: var(--subtle);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

/* ── Avatar ── */
.avatar-img {
    display: block;
    image-rendering: pixelated;
    border: 2px solid var(--bg-raised);
    border-radius: var(--radius);
}
.profile-avatar-wrap {
    position: relative;
    flex-shrink: 0;
    display: inline-block;
}
/* Hover-reveal pencil buttons (avatar + bio) */
.hover-edit-btn {
    position: absolute;
    top: 6px;
    right: 6px;
    background: rgba(22, 16, 13, 0.75);
    border: 1px solid var(--bg-raised);
    border-radius: var(--radius);
    color: var(--gold);
    cursor: pointer;
    padding: 4px;
    line-height: 1;
    opacity: 0;
    z-index: 10;
    transition: opacity 0.15s, color 0.15s;
}
.hover-edit-btn:hover { color: var(--gold); }
.hover-edit-btn.visible { opacity: 1; }
.profile-top {
    display: flex;
    gap: 1.5rem;
    align-items: flex-start;
    margin-bottom: 1.5rem;
}
.flex-fill {
    flex: 1;
    min-width: 0;
}
.avatar-editor { margin-bottom: 1.5rem; }
/* Shared layout for every createPaintEditor() surface: profile avatar, list-art modal,
   /dev template-art tool. */
.paint-editor-top {
    /* Establish a query container so the palette swatches can size off THIS row's own
       width (cqw), not the viewport. The canvas (square, flex-fills the remainder) and
       the palette then keep matched heights at any container width — full-page /dev panel
       or the narrower list-art modal — instead of the canvas shrinking while the fixed
       palette stays tall and leaves dead space below it. */
    container-type: inline-size;
    --swatch: clamp(17px, 4.6cqw, 28px);
    --swatch-gap: clamp(2px, 0.65cqw, 4px);
    display: flex;
    gap: 1rem;
    align-items: flex-start;
    flex-wrap: nowrap;
}
.paint-canvas {
    background: var(--bg);         /* empty cells are transparent: the theme shows through */
    border: 2px solid var(--leaf);
    border-radius: var(--radius);
    cursor: crosshair;
    flex: 1 1 auto;                /* fill the width left of the palette… */
    min-width: 0;
    max-width: 480px;              /* …but never larger than the palette column's height */
    aspect-ratio: 1 / 1;           /* canvas is square (480×480 buffer), so it scales 1:1 */
    width: auto;
    height: auto;
    image-rendering: pixelated;    /* crisp nearest-neighbor scaling, no HiDPI blur */
}
.paint-palette {
    display: grid;
    grid-template-columns: repeat(5, var(--swatch));
    gap: var(--swatch-gap);
    flex-shrink: 0;                /* keep the palette beside the canvas, not wrapped below */
}
.palette-swatch {
    width: var(--swatch);
    height: var(--swatch);
    border: 2px solid transparent;
    border-radius: var(--radius);
    cursor: pointer;
    padding: 0;
    transition: border-color 0.1s;
}
.palette-swatch:hover   { border-color: var(--text); }
.palette-swatch.selected { border-color: var(--leaf-light); outline: 2px solid var(--leaf-light); }

/* Tool row (pen / fill / undo / redo) appended below the palette swatches. Spans the
   full palette grid width and lays its icon buttons out in a row. */
.paint-tools {
    grid-column: 1 / -1;          /* full width under the 5-column swatch grid */
    /* Mirror the swatch grid exactly: 5 columns of var(--swatch), same gap. With 5
       tool buttons this lines each one up under a swatch and makes the row width
       follow the palette width precisely, scaling together via the cqw --swatch. */
    display: grid;
    grid-template-columns: repeat(5, var(--swatch));
    gap: var(--swatch-gap);
    margin-top: var(--swatch-gap);
}
.paint-tool {
    width: var(--swatch);
    height: var(--swatch);        /* exact swatch size; scales with the palette */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 2px solid var(--leaf);
    border-radius: var(--radius);
    background: var(--bg-raised);
    color: var(--text);
    cursor: pointer;
    transition: border-color 0.1s, background 0.1s;
}
.paint-tool:hover:not(:disabled) { border-color: var(--leaf-light); }
.paint-tool.selected { border-color: var(--leaf-light); background: var(--leaf); color: var(--bg); }
.paint-tool:disabled { opacity: 0.4; cursor: default; }
.paint-tool-icon {
    display: block;
    width: 64%; height: 64%;      /* scales with the swatch-sized button */
    background-color: currentColor;
    -webkit-mask-size: contain; mask-size: contain;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
}
.paint-tool[data-tool="pen"]  .paint-tool-icon { -webkit-mask-image: url('/assets/pencil.svg');                  mask-image: url('/assets/pencil.svg'); }
.paint-tool[data-tool="fill"] .paint-tool-icon { -webkit-mask-image: url('/assets/paint-bucket.svg');            mask-image: url('/assets/paint-bucket.svg'); }
.paint-tool[data-tool="erase"] .paint-tool-icon { -webkit-mask-image: url('/assets/eraser.svg');                 mask-image: url('/assets/eraser.svg'); }
.paint-tool[data-tool="undo"] .paint-tool-icon { -webkit-mask-image: url('/assets/arrow-counter-clockwise.svg'); mask-image: url('/assets/arrow-counter-clockwise.svg'); }
.paint-tool[data-tool="redo"] .paint-tool-icon { -webkit-mask-image: url('/assets/arrow-clockwise.svg');         mask-image: url('/assets/arrow-clockwise.svg'); }

/* ── Side-by-side tracer (/trace.php) ──
   Full-width dev page: a reference image (left) under a 32×32 grid overlay, beside the
   shared paint editor (right). The reference square mirrors the canvas's 32×32 grid so a
   cell on the left maps to the same cell on the right; an uploaded image is fit (contain,
   no distortion) into an inner box inset by --pad grid cells on every side. */
.trace-main {
    /* Override the base `main` 680px cap — this page is intentionally full-width. */
    max-width: none;
    margin: 0;
    padding: 1.5rem 2rem 3rem;
}
.trace-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2rem;
    align-items: start;
    /* One shared square size for BOTH panels so they match. The right canvas only gets its
       column minus the palette (~172px) and the inter-panel gap, so size off that smaller
       budget: half the viewport (minus page padding 64px + gap 32px = 96px → 48px/side)
       minus the palette column. Bounded by 82vh so a square can't exceed the screen height. */
    --panel: min(82vh, calc(50vw - 48px - 172px));
}
/* Both panels resolve to --panel: the reference fills its column then caps here; the canvas
   wrapper flex-grows into its column-minus-palette then caps at the same value → equal squares.
   The wrapper carries the border + size cap so the grid overlay can sit at inset:0 and align
   cell-for-cell with the canvas content. */
.trace-canvas-wrap {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    max-width: var(--panel);
    aspect-ratio: 1 / 1;
    border: 2px solid var(--leaf);
    border-radius: var(--radius);
    overflow: hidden;
}
.trace-main .paint-canvas {
    /* Fills the wrapper, which now owns the border + max-width cap. */
    max-width: none;
    width: 100%;
    height: 100%;
    border: none;
    border-radius: 0;
    display: block;
}
.trace-canvas-grid {
    position: absolute;
    inset: 0;
    pointer-events: none;
    display: none;                 /* shown when the wrapper has .show-grid */
    --cell: calc(100% / 32);
    /* Same overlay as .trace-ref-grid: fine line every cell, heavier every 8. */
    background-image:
        repeating-linear-gradient(to right,  rgba(255,255,255,0.28) 0 1px, transparent 1px calc(100% / 4)),
        repeating-linear-gradient(to bottom, rgba(255,255,255,0.28) 0 1px, transparent 1px calc(100% / 4)),
        repeating-linear-gradient(to right,  rgba(255,255,255,0.12) 0 1px, transparent 1px var(--cell)),
        repeating-linear-gradient(to bottom, rgba(255,255,255,0.12) 0 1px, transparent 1px var(--cell));
}
.trace-canvas-wrap.show-grid .trace-canvas-grid { display: block; }
.trace-grid-toggle { cursor: pointer; }
/* The reference square hugs the right edge of its column (beside the canvas); its heading
   and controls follow it so all three share one left edge. (Doubled class so it outranks
   the .trace-col h3 margin reset.) */
.trace-col.trace-col--ref > * {
    max-width: var(--panel);
    margin-left: auto;
}
.trace-col h3 {
    margin: 0 0 0.75rem;
}
.trace-ref {
    position: relative;
    aspect-ratio: 1 / 1;
    width: 100%;
    max-width: var(--panel);
    margin-left: auto;             /* hug the right edge of its column, beside the canvas */
    --cell: calc(100% / 32);
    --pad: 0;
    border: 2px solid var(--leaf);
    border-radius: var(--radius);
    background: var(--bg-raised);
    overflow: hidden;
}
.trace-ref-img {
    position: absolute;
    /* Inset the image by --pad cells on all four sides; contain keeps aspect (non-square
       images fit their bounding box without stretching). */
    inset: calc(var(--pad) / 32 * 100%);
    width: calc(100% - var(--pad) / 32 * 200%);
    height: calc(100% - var(--pad) / 32 * 200%);
    object-fit: contain;
    image-rendering: auto;
    display: none;                 /* shown once a file is chosen */
}
.trace-ref.has-img .trace-ref-img { display: block; }
.trace-ref-grid {
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* Fine line every cell, heavier line every 8 cells (100%/4). */
    background-image:
        repeating-linear-gradient(to right,  rgba(255,255,255,0.28) 0 1px, transparent 1px calc(100% / 4)),
        repeating-linear-gradient(to bottom, rgba(255,255,255,0.28) 0 1px, transparent 1px calc(100% / 4)),
        repeating-linear-gradient(to right,  rgba(255,255,255,0.12) 0 1px, transparent 1px var(--cell)),
        repeating-linear-gradient(to bottom, rgba(255,255,255,0.12) 0 1px, transparent 1px var(--cell));
}
.trace-ref-hover {
    /* Hint box over the reference cell matching the canvas cell under the mouse (trace.php
       sets --hx/--hy). The faint dark ring keeps the white outline visible on light images. */
    position: absolute;
    left: calc(var(--hx) * var(--cell));
    top:  calc(var(--hy) * var(--cell));
    width: var(--cell);
    height: var(--cell);
    box-sizing: border-box;
    border: 2px solid rgba(255,255,255,0.75);
    box-shadow: 0 0 0 1px rgba(0,0,0,0.3);
    pointer-events: none;
}
.trace-ref-empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--subtle);
    font-size: 0.9rem;
    text-align: center;
    padding: 1rem;
}
.trace-ref.has-img .trace-ref-empty { display: none; }
.trace-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1rem;
}
.trace-controls label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--subtle);
    font-size: 0.9rem;
}
.trace-pad-input {
    width: 4rem;
}
@media (max-width: 760px) {
    /* Stacked: each panel gets the full column, so size off width alone (no palette to
       subtract from a half-viewport). */
    .trace-grid { grid-template-columns: 1fr; --panel: min(100%, 82vh); }
}

/* ── Profile detail view ── */
.profile-detail {
    max-width: 700px;
}
.profile-back {
    margin-bottom: 1.5rem;
    font-size: 0.9rem;
}
.profile-back a { color: var(--sky); text-decoration: none; }
.profile-back a:hover { text-decoration: underline; }
.profile-detail h1 {
    font-family: var(--font-heading);
    font-size: clamp(1.4rem, 4vw, 2.5rem);
    color: var(--text);
    margin-bottom: 0.35rem;
    overflow-wrap: anywhere;   /* usernames have no spaces */
}
.profile-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 1.25rem;
}
.profile-tagline {
    font-size: 0.95rem;
    color: var(--gold);
    margin-top: 0.15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
/* Owner-only placeholder on the profile page when no tagline is set; clicking it opens the editor. */
.tagline-empty { font-style: italic; color: var(--subtle); cursor: pointer; }
.tagline-empty:hover { color: var(--text); }
.tagline-wrap {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    max-width: 100%;
}
.tagline-hover-btn {
    position: static;
    top: auto;
    right: auto;
    flex-shrink: 0;
}
.profile-joined {
    font-size: 0.85rem;
    color: var(--subtle);
}
.profile-bio {
    position: relative;
    margin-top: 0.5rem;
}
.profile-bio p {
    color: var(--text);
    line-height: 1.6;
    white-space: pre-wrap;
}
.bio-empty { color: var(--subtle); font-style: italic; }
.profile-bio p.bio-empty { color: var(--subtle); }
.bio-add { cursor: pointer; }
.profile-bio p.bio-add:hover { color: var(--text); }
/* Delete account: a quiet link at the foot of your own profile, expanding to a confirm form. */
.account-delete { margin-top: 3rem; font-size: 0.85rem; }
.account-delete > summary { cursor: pointer; color: var(--subtle); width: fit-content; }
.account-delete > summary:hover { color: var(--error); }
.account-delete-panel {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.75rem;
    padding: 1rem;
    max-width: 28rem;
    border: 1px solid color-mix(in srgb, var(--error) 45%, transparent);
    border-radius: var(--radius);
}
.account-delete-panel p { margin: 0; color: var(--text); line-height: 1.5; }
.account-delete-panel label { color: var(--subtle); }
.bio-edit-form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.bio-edit-form textarea {
    background: var(--bg-input);
    border: 2px solid var(--leaf);
    border-radius: var(--radius);
    color: var(--text);
    font-family: var(--font-body);
    font-size: 0.9rem;
    padding: 0.5rem 0.6rem;
    resize: vertical;
    width: 100%;
}
.bio-edit-form textarea:focus { outline: none; border-color: var(--leaf-light); }

/* ── Profile header ── */
.profile-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.375rem;
    padding-bottom: 0.5rem;
}
.profile-header-top {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.4rem;
}
/* Let the name column shrink beside the avatar (flex items default to min-width: auto),
   so a long username wraps instead of pushing the page wider on phones. */
.profile-identity, .profile-header-top > div, .profile-top > div { min-width: 0; }
.profile-header-top .avatar-img {
    border-radius: var(--radius);
    flex-shrink: 0;
}
.profile-identity h1 {
    font-family: var(--font-heading);
    font-size: clamp(1.25rem, 4vw, 2rem);
    color: var(--text);
    margin-bottom: 0.25rem;
    overflow-wrap: anywhere;   /* usernames have no spaces */
}
.profile-nudge { margin-top: 0.5rem; font-size: 0.875rem; }
.profile-nudge a { color: var(--sky); text-decoration: none; }
.profile-nudge a:hover { text-decoration: underline; }
.list-controls { display: flex; align-items: center; gap: 0.5rem; margin-top: 1.25rem; margin-bottom: 0.2rem; }
#compress-btn { display: inline-flex; align-items: center; gap: 0.3rem; }
.compress-icon {
    display: block;
    width: 13px; height: 13px;
    background-color: currentColor;
    -webkit-mask-image: url('/assets/arrow-up-left.svg');
    mask-image: url('/assets/arrow-up-left.svg');
    -webkit-mask-size: contain; mask-size: contain;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
    flex-shrink: 0;
}
.inline-form {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.inline-form input {
    flex: 1;
    min-width: 0;
    background: var(--bg-input);
    border: 2px solid var(--leaf);
    border-radius: var(--radius);
    color: var(--text);
    font-family: var(--font-body);
    font-size: 0.9rem;
    padding: 0.35rem 0.6rem;
}
.inline-form input:focus { outline: none; border-color: var(--leaf-light); }
.inline-form input.shake,
.modal-input.shake {
    border-color: var(--error);
    animation: shake 0.35s ease;
}
.shake { animation: shake 0.35s ease-in-out; }
@keyframes shake {
    0%, 100% { transform: translateX(0); }
    20%       { transform: translateX(-6px); }
    40%       { transform: translateX(6px); }
    60%       { transform: translateX(-4px); }
    80%       { transform: translateX(4px); }
}
.btn-small {
    background: var(--leaf);
    color: var(--text);
    border: none;
    border-radius: var(--radius);
    font-family: var(--font-body);
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0.35rem 0.75rem;
    cursor: pointer;
    transition: background 0.15s;
}
.btn-small:hover:not(:disabled) { background: var(--leaf-light); }
.btn-cancel { background: var(--bg-raised); border: 1px solid var(--subtle); }
.btn-cancel:hover:not(:disabled) { background: var(--subtle); }
.btn-danger { background: var(--error); }
.btn-danger:hover:not(:disabled) { background: var(--error); opacity: 0.85; }
.profile-stats {
    flex-shrink: 0;
    text-align: right;
}
.stat {
    font-size: 0.875rem;
    color: var(--subtle);
}
.stat strong {
    font-size: 1.5rem;
    font-family: var(--font-display);
    color: var(--gold);
    display: block;
    line-height: 1;
}

/* ── Trace reference inside the user paint editors (js/trace-reference.js) ──
   Off: the wrapper is a plain block and the editor row lays out as usual. On: the editor row
   dissolves (display: contents) so the reference, canvas and palette share ONE grid. Tracing
   never shrinks the canvas: both squares stay at the canvas's full 480px with the full-size
   palette, and the host widens to fit (see the :has() rules below). Where even that won't fit,
   the reference stacks above the canvas instead of squeezing the pair side by side. */
.paint-trace { container-type: inline-size; }
.paint-trace:not(.is-tracing) .paint-trace-controls,
.paint-trace:not(.is-tracing) .paint-trace-ref { display: none; }
.paint-trace.is-tracing .paint-trace-grid {
    display: grid;
    grid-template-columns: 480px 480px auto;
    column-gap: 1rem;
    align-items: start;
}
.paint-trace.is-tracing .paint-editor-top {
    display: contents;
    container-type: normal;        /* so cqw below resolves against .paint-trace */
    --swatch: 28px;
    --swatch-gap: 4px;
}
.paint-trace.is-tracing .paint-trace-controls { grid-column: 1 / -1; margin-bottom: 0.75rem; }
.paint-trace.is-tracing .paint-trace-ref { max-width: 480px; margin-left: 0; }
.paint-trace.is-tracing .paint-canvas { width: 100%; height: auto; }
/* Narrower than two 480px squares + palette (2×480 + 156 + 2×16 = 1148px): stack the
   reference directly above the canvas, same column so they still match in size; the palette
   sizes as it normally does beside the canvas. */
@container (max-width: 1147px) {
    .paint-trace.is-tracing .paint-trace-grid {
        grid-template-columns: minmax(0, 480px) auto;
        row-gap: 0.75rem;
    }
    .paint-trace.is-tracing .paint-editor-top {
        --swatch: clamp(17px, 4.6cqw, 28px);
        --swatch-gap: clamp(2px, 0.65cqw, 4px);
    }
    .paint-trace.is-tracing .paint-trace-ref { grid-column: 1; grid-row: 2; }
    .paint-trace.is-tracing .paint-canvas    { grid-column: 1; grid-row: 3; }
    .paint-trace.is-tracing .paint-palette   { grid-column: 2; grid-row: 3; }
}
/* Hosts widen while tracing so the side-by-side layout fits: the profile page (normally a
   700px column) and the list-art modal. On smaller screens they cap at the viewport and the
   stacked layout above takes over. */
.node-page:has(.avatar-editor .paint-trace.is-tracing) { max-width: 1240px; }
.profile-detail:has(.avatar-editor .paint-trace.is-tracing) { max-width: none; }
.modal-box--art:has(.paint-trace.is-tracing) { width: min(1200px, calc(100vw - 2rem)); }

/* ── 5×5 grid ── */
/* ── List layout: profile header + card grid in one auto-fill grid ──
   The header and the grid each span the full column block (1 / -1), so the block
   stays centered while the header's left edge always lands on the grid's first
   column — kept consistent at every width. (A plain centered wrapper can't do this:
   an auto-fill grid inside a shrink-to-fit box collapses to one column.) */
.list-layout {
    --card-w: 154px;
    display: grid;
    grid-template-columns: repeat(auto-fill, var(--card-w));
    justify-content: center;
    column-gap: 1.25rem;
    row-gap: 0;
}
.list-layout > * { grid-column: 1 / -1; min-width: 0; }

.grid-25 {
    --card-w: 154px;
    --card-h: 231px;
    display: grid;
    /* Fixed-size cards (locked width + height ⇒ locked 2:3 ratio). Columns reflow
       to fewer-per-row as the container narrows, rather than squeezing the cards. */
    grid-template-columns: repeat(auto-fill, var(--card-w));
    grid-auto-rows: var(--card-h);
    /* Left-align the tracks so the grid's left edge sits at the container's left
       padding — the same line the profile header/Edit sit on — at every width. */
    justify-content: start;
    gap: 1.25rem;
}
/* Video Game lists: IGDB covers are 264×374 (0.706), a touch wider than the 2:3 card, so
   object-fit: cover trimmed their sides. 154 / 0.706 ≈ 218px fits them exactly. Only in
   game lists: a grid shares one row height, and mixed lists keep the standard card. */
.grid-25--game-covers { --card-h: 218px; }
.grid-slot {
    border-radius: var(--radius);
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 0.35rem;
    position: relative;
    text-decoration: none;
    font-family: var(--font-body);
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}
.slot-top {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.slot-bottom {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.slot-rank {
    position: absolute;
    top: 0.25rem;
    left: 0.25rem;
    font-family: var(--font-display);
    font-size: 0.9rem;
    color: var(--gold);
    line-height: 1;
}
.grid-slot--filled {
    background: var(--bg-raised);
    border: 2px solid color-mix(in srgb, var(--leaf) 30%, transparent);
    box-shadow: 0 0 10px color-mix(in srgb, var(--leaf) 80%, transparent);
}
.grid-slot--filled:hover {
    border-color: color-mix(in srgb, var(--leaf) 60%, transparent);
    box-shadow: 0 0 14px color-mix(in srgb, var(--leaf) 100%, transparent);
}
.slot-title {
    color: var(--text);
    font-family: var(--font-card-title);
    font-size: 0.95rem;
    font-weight: 700;
    line-height: 1.3;
    text-align: center;
    word-break: break-word;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.slot-subtype {
    font-size: 0.8rem;
    color: var(--subtle);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.slot-subtype--typed { color: var(--gold); }
.typed-list-badge { color: var(--gold); text-transform: uppercase; letter-spacing: 0.08em; }
.template-badge { color: var(--gold); line-height: 1; vertical-align: middle; margin-right: 0.2rem; }
.list-title-wrap { display: inline-flex; align-items: center; gap: 0.5rem; max-width: 100%; }
.list-title-btn { position: static; top: auto; right: auto; flex-shrink: 0; opacity: 1; color: var(--subtle); }
.list-title-btn:hover { color: var(--gold); }
.slot-template-icon { color: var(--gold); line-height: 1; }
.modal-custom-label { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--subtle); cursor: pointer; user-select: none; }
.modal-custom-label input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    width: 15px;
    height: 15px;
    border: 2px solid var(--subtle);
    border-radius: var(--radius);
    background: var(--bg-input);
    cursor: pointer;
    flex-shrink: 0;
    position: relative;
    transition: background 0.15s, border-color 0.15s;
}
.modal-custom-label input[type="checkbox"]:checked {
    background: var(--leaf);
    border-color: var(--leaf);
}
.modal-custom-label input[type="checkbox"]:checked::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 45%;
    width: 4px;
    height: 8px;
    border: 2px solid var(--bg);
    border-top: none;
    border-left: none;
    transform: translate(-50%, -50%) rotate(45deg);
}
select.modal-input {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='%239e8070'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.6rem center;
    padding-right: 2rem;
    cursor: pointer;
}
.modal-sub-tabs { display: flex; border-bottom: 1px solid var(--bg-raised); margin-bottom: 0.5rem; }
#modal-list-create-panel, #modal-list-follow-panel { display: flex; flex-direction: column; gap: 0.75rem; }
.sub-tab {
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--subtle);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 0.8rem;
    font-weight: 500;
    padding: 0.3rem 0.75rem;
    margin-bottom: -1px;
    transition: color 0.15s, border-color 0.15s;
}
.sub-tab:hover { color: var(--text); }
.sub-tab.active { color: var(--sky); border-bottom-color: var(--sky); }

.modal-list-type { display: flex; flex-direction: column; gap: 0.4rem; }
.modal-list-type-label { font-size: 0.8rem; color: var(--subtle); }
.grid-slot--empty {
    background: transparent;
    border: 2px dashed var(--bg-raised);
    color: inherit;
    align-items: center;
    justify-content: center;
}
.grid-slot--empty:hover {
    border-color: var(--leaf);
    background: var(--bg-raised);
}
.grid-slot--empty .slot-rank {
    top: 0.25rem;
    left: 0.25rem;
}
.slot-add {
    font-size: 1.5rem;
    color: var(--subtle);
    line-height: 1;
    transition: color 0.15s;
}
.grid-slot--empty:hover .slot-add { color: var(--leaf); }
.grid-slot--blank {
    background: transparent;
    border: 2px solid var(--bg-raised);
    opacity: 0.4;
}
.grid-slot--cover {
    padding: 0;
    overflow: hidden;
    position: relative;
}
.slot-delete-btn {
    position: absolute;
    top: 0.2rem;
    right: 0.2rem;
    z-index: 10;
    background: var(--gold);
    border: none;
    color: var(--bg);
    cursor: pointer;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0.15rem 0.4rem;
    display: none;
}
.grid-25.edit-mode .slot-delete-btn { display: block; }
/* Social-list art pencil: shown in edit mode alongside the delete button (top-left, so it
   clears the delete button at top-right). Not shown during normal viewing of your own lists. */
.slot-art-edit-btn {
    position: absolute;
    top: 0.2rem;
    left: 0.2rem;
    z-index: 10;
    background: var(--gold);
    border: none;
    color: var(--bg);
    cursor: pointer;
    line-height: 0;
    padding: 0.25rem;
    border-radius: var(--radius);
    display: none;
}
.slot-art-edit-btn svg { display: block; }
.grid-25.edit-mode .slot-art-edit-btn { display: block; }
.modal-box--sm { width: min(340px, calc(100vw - 2rem)); }
/* Wide enough to show the paint canvas (480px) beside the palette (~156px) + gap,
   matching the full-size /dev art editor instead of cramping the canvas. Tighter gap
   keeps the heading/canvas/actions stack compact like the dev panel. */
.confirm-remove-heading { margin: 0; font-family: var(--font-heading); font-size: 1.1rem; }
.confirm-remove-sub { margin: 0; font-size: 0.85rem; color: var(--subtle); }
.confirm-remove-sub a { color: var(--gold); }
.grid-slot--ghost { opacity: 0.3; border: 2px solid var(--leaf) !important; }
.grid-slot--filled { position: relative; }
.slot-cover-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center top;
    display: block;
}
/* Deezer art (artist/album/song) is 1:1 — fit it whole and anchor to the top of
   the portrait card instead of cropping; the leftover space falls to the bottom. */
.grid-slot--square-art .slot-cover-img { object-fit: contain; }
/* Square-art titles sit in the gap below the art — allow up to 3 lines (like plain
   cards) before ellipsis, rather than the 2-line clamp other cover cards use. */
.grid-slot--square-art .slot-title--cover { -webkit-line-clamp: 3; }
/* Reserve the square (its height == card width) with padding-top, then center the
   title in the leftover gap below it. Rank/follow are absolute, so unaffected. */
.grid-slot--square-art .slot-cover-overlay {
    padding-top: 100%;
    justify-content: center;
}
/* Title band under square art: a recessed label plate (darker, with a soft shadow falling from
   the art's edge) with a fine halftone dot pattern, and letterpress titles (a dark shadow below,
   a faint highlight above). The band is card height minus card width (the art is a square the
   card's width; the borders cancel out). Chosen via a local comparison lab. */
.grid-slot--square-art::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(var(--card-h) - var(--card-w));
    background-color: color-mix(in srgb, #000 28%, var(--bg-raised));
    background-image: radial-gradient(color-mix(in srgb, var(--leaf) 30%, transparent) 0.7px, transparent 0.9px);
    background-size: 4px 4px;
    box-shadow: inset 0 4px 6px -3px rgba(0, 0, 0, 0.75), inset 0 -1px 0 rgba(255, 255, 255, 0.04);
}
.grid-slot--square-art .slot-title--cover {
    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.85), 0 -1px 0 rgba(255, 255, 255, 0.07);
}
/* Short artist names read small at the default size (grid_slot.php picks the tier by length). */
.grid-slot--title-lg .slot-title--cover { font-size: 1.35rem; -webkit-line-clamp: 2; }
/* Usernames have no spaces: let a long one wrap at any character rather than be cut off. */
.grid-slot--user .slot-title--cover { overflow-wrap: anywhere; }
.grid-slot--title-md .slot-title--cover { font-size: 1.15rem; -webkit-line-clamp: 2; }
/* Board games (grid_slot.php): the box art whole and centered, over a blurred, dimmed copy
   of itself scaled to fill the card. scale() pushes the blur's soft edges out of view. In
   mixed lists the title overlays the bottom, so a scrim keeps it legible over busy art. */
.grid-slot--blur-fill .slot-cover-img { object-fit: contain; object-position: center; }
.slot-cover-blur {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: blur(14px) brightness(0.38) saturate(1.9) contrast(1.15);
    transform: scale(1.2);
}
.grid-slot--blur-fill .slot-cover-overlay {
    background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, transparent 45%);
}
.grid-25--hide-titles .grid-slot--blur-fill:not(.cover-failed) .slot-cover-overlay { background: none; }
.cover-failed .slot-cover-blur { display: none; }
.slot-avatar-img {
    inset: auto;
    width: 75%;
    height: 75%;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    object-fit: cover;
    image-rendering: pixelated;
}
.user-list-img {
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    object-position: top;
    transform: none;
}
.slot-cover-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: 0.35rem;
    gap: 0.2rem;
}
.slot-rank--cover {
    position: absolute;
    top: 0.25rem;
    left: 0.25rem;
    font-family: var(--font-display);
    font-size: 0.9rem;
    color: var(--gold);
    line-height: 1;
}
.slot-title--cover {
    color: var(--text);
    font-size: 0.9rem;
    font-weight: 700;
    line-height: 1.25;
    text-align: center;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.grid-slot--yt-video .slot-title--cover { -webkit-line-clamp: 3; }
/* Social-list titles are short category names ("Books", "Video Games"), so they get a
   larger face that stays readable on phones; 2 lines leaves room for the longest ("Board Games"). */
.grid-slot--social-list .slot-title--cover {
    font-size: 1.45rem;
    -webkit-line-clamp: 2;
}
/* Owner-renamed titles over 16 chars (grid_slot.php) drop back to the regular size, 3 lines. */
.grid-slot--social-list.grid-slot--long-title .slot-title--cover {
    font-size: 0.95rem;
    -webkit-line-clamp: 3;
}
.grid-25--hide-titles .slot-title--cover {
    display: none;
}
.grid-25--hide-titles .cover-failed .slot-title--cover {
    display: block;
}
/* PLACE cards have no photo (Google forbids storing place images), so instead of an <img> they
   reserve the same top square as avatars/music/YT cards (grid-slot--square-art) and center a
   tinted pushpin glyph in it (both axes). The title + category vertically center in the gap
   below the square, courtesy of the shared square-art overlay rule (padding-top: 100%). */
.grid-slot--place .slot-place-art {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    aspect-ratio: 1 / 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--gold);
}
.grid-slot--place .slot-place-art svg {
    width: 42%;
    height: auto;
    opacity: 0.9;
}
.slot-place-type {
    text-align: center;
    font-size: 0.7rem;
    color: var(--subtle);
    line-height: 1.15;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* PLACE detail header: title on top, then the pushpin glyph at left paired with the type +
   location stacked in two rows (pin-next-to-address pattern). */
.place-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 0.75rem;
}
.place-hero {
    display: inline-flex;
    flex: none;
    color: var(--gold);
}
.place-hero svg { width: 2.4rem; height: 2.4rem; }
.place-meta { display: flex; flex-direction: column; }
.place-meta .node-type-label { margin-bottom: 0.2rem; }   /* row gap to the location below */
.place-meta .content-meta { margin-bottom: 0; }            /* place-head provides trailing space */
/* Click-to-load map: a framed, iframe-sized box shows a map-like skeleton behind a centered
   "Show map" button. On click the whole box's contents are replaced by the iframe, which fills
   it 1:1 (same border/radius/size), so the swap reads as the placeholder "becoming" the map. */
.place-map {
    position: relative;
    width: 100%;
    height: 320px;
    margin-top: 1rem;
    border: 1px solid var(--bg-raised);
    border-radius: var(--radius);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Abstract "map" loading state: a faint green block (park) + a few streets, gently pulsing. */
.place-map-skeleton {
    position: absolute;
    inset: 0;
    background-color: var(--bg-raised);
    background-image:
        radial-gradient(circle at 76% 32%, color-mix(in srgb, var(--leaf) 16%, transparent) 0 11%, transparent 11.5%),
        linear-gradient(118deg, transparent 0 45%, color-mix(in srgb, var(--subtle) 20%, transparent) 45% 47.5%, transparent 47.5%),
        linear-gradient(0deg, transparent 0 68%, color-mix(in srgb, var(--subtle) 14%, transparent) 68% 69.5%, transparent 69.5%),
        linear-gradient(90deg, transparent 0 28%, color-mix(in srgb, var(--subtle) 14%, transparent) 28% 29.5%, transparent 29.5%);
    animation: place-map-pulse 2.2s ease-in-out infinite;
}
@keyframes place-map-pulse { 0%, 100% { opacity: 0.65; } 50% { opacity: 1; } }
.place-map-show { position: relative; z-index: 1; }   /* sit above the absolute skeleton */
.place-map-frame {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}
.slot-follow-badge--cover {
    position: absolute;
    top: 0.3rem;
    right: 0.3rem;
}
.slot-follow-badge {
    position: absolute;
    top: 0.3rem;
    right: 0.3rem;
    color: var(--sky);
    line-height: 1;
    pointer-events: none;
    opacity: 0.8;
}
.slot-type-icon {
    color: var(--leaf-light);
    line-height: 1;
    flex-shrink: 0;
    pointer-events: none;
}

@media (max-width: 500px) {
    /* Trim page gutters so two fixed-width cards fit on a phone; auto-fill then
       reflows to 1 column on very narrow screens. */
    .node-page { padding: 1rem 0.75rem; }
}

/* ── Node ID chip ── */
.node-id-chip {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    background: none;
    border: 1px solid var(--subtle);
    border-radius: var(--radius);
    color: var(--subtle);
    font-family: 'Courier New', monospace;
    font-size: 1.125rem;
    letter-spacing: 0.05em;
    padding: 0.3rem 0.75rem;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}
.node-id-chip:hover { color: var(--text); border-color: var(--subtle); }
.node-id-chip.copied { color: var(--success); border-color: var(--success); }
.node-id-hash { color: var(--gold); }

/* ── Header tools: hash chip + dice randomizer ──
   In normal flow as the middle flex child (logo · tools · nav), so the header's
   space-between distribution keeps it roughly centered while letting all three
   groups compress together as the viewport narrows. It was absolutely pinned to
   screen-center, which took it out of flow and let the nav slide under it. */
.header-tools {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}
/* Undo the chip's standalone absolute-centering so it sits inline in the tools
   flex row, on one line, with all 6 hex characters visible. */
.header-tools .node-id-chip {
    position: static;
    left: auto;
    transform: none;
    flex-shrink: 0;
    white-space: nowrap;
}

.randomizer { display: flex; align-items: center; gap: 0.4rem; }

.dice-btn {
    position: relative;
    width: 34px;
    height: 30px;
    background: none;
    border: 1px solid var(--subtle);
    border-radius: var(--radius);
    color: var(--gold);
    cursor: pointer;
    padding: 0;
    transition: border-color 0.15s, background 0.15s;
}
.dice-btn:hover { border-color: var(--text); background: color-mix(in srgb, var(--gold) 12%, transparent); }
/* Two staggered dice to read as a thrown pair. */
.dice {
    position: absolute;
    width: 16px;
    height: 16px;
    background-color: currentColor;
    -webkit-mask-size: contain; mask-size: contain;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
}
.dice--a {
    -webkit-mask-image: url('/assets/dice-two.svg'); mask-image: url('/assets/dice-two.svg');
    left: 4px; top: 2px; transform: rotate(-14deg);
}
.dice--b {
    -webkit-mask-image: url('/assets/dice-five.svg'); mask-image: url('/assets/dice-five.svg');
    right: 3px; bottom: 2px; transform: rotate(13deg); opacity: 0.9;
}
/* The roll animation is driven by the Web Animations API in randomizer.js. */

.rand-toggles { display: flex; gap: 0.1rem; }
.rand-toggle {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;                   /* small space between icon and indicator */
    width: 26px;
    height: 28px;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--leaf-light);
    cursor: pointer;
    padding: 0;
    transition: color 0.15s, opacity 0.15s;
}
.rand-toggle:hover { color: var(--text); }
.rand-toggle:not(.active) { color: var(--subtle); opacity: 0.4; }   /* type excluded */
/* Colored tab indicator beneath the icon, shown only when the type is enabled.
   Always present (transparent when off) so toggling doesn't shift the icon. */
.rand-toggle::after {
    content: "";
    width: 14px;                /* match the icon width */
    height: 2px;
    background: transparent;
    transition: background 0.15s;
}
.rand-toggle.active::after { background: var(--gold); }

/* Guest state: visible but inert, nudging toward sign-in. */
.randomizer[data-authed="0"] .dice-btn { opacity: 0.45; }
.randomizer[data-authed="0"] .rand-toggle { opacity: 0.3; cursor: default; pointer-events: none; }
.dice-btn--disabled { opacity: 0.4; cursor: not-allowed; }

/* ── LIST node ranked list ── */
.node-list--ranked { list-style: none; }
.node-card--empty { opacity: 0.45; }
.node-card--empty .node-rank { color: var(--subtle); }
.node-card-add {
    background: none;
    border: none;
    color: var(--subtle);
    font-size: 1.1rem;
    cursor: pointer;
    padding: 0 0.5rem;
    flex: 1;
    text-align: left;
    transition: color 0.15s;
}
.node-card-add:hover { color: var(--leaf); }
.node-card-blank { color: var(--bg-raised); flex: 1; padding: 0 0.5rem; }

/* ── Add-to-slot modal ── */
.modal {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
}
.modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(10, 8, 6, 0.8);
}
.modal-box {
    position: relative;
    background: var(--bg-raised);
    border: 2px solid var(--leaf);
    border-radius: var(--radius);
    padding: 1.5rem;
    width: min(500px, calc(100vw - 2rem));
    max-height: calc(100vh - 4rem);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
/* Art editor modal: override the base .modal-box width/padding so its editor row is
   as wide as the full-page /dev panel (≈636px inner), making the canvas + palette render
   at the same size. Must follow .modal-box in source order to win (equal specificity). */
.modal-box--art { width: min(700px, calc(100vw - 2rem)); gap: 0.6rem; padding: 1rem; }
.modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.modal-header h2 { font-family: var(--font-heading); font-size: 1.2rem; }
.modal-close {
    background: none;
    border: none;
    color: var(--subtle);
    font-size: 1rem;
    cursor: pointer;
    padding: 0.2rem 0.4rem;
    line-height: 1;
    transition: color 0.15s;
}
.modal-close:hover { color: var(--text); }
.modal-type-pills { display: flex; gap: 0.5rem; }
.type-pill {
    flex: 1;
    padding: 0.4rem 0.75rem;
    background: var(--bg);
    border: 2px solid transparent;
    border-radius: var(--radius);
    color: var(--subtle);
    font-family: var(--font-body);
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}
.type-pill:hover { color: var(--text); }
.type-pill.active { color: var(--sky); border-color: var(--sky); }
.modal-type-pills--locked .type-pill { flex: 0 0 auto; }
.type-pill.active:disabled { color: var(--sky); border-color: var(--sky); cursor: default; }
.modal-section { display: flex; flex-direction: column; gap: 0.75rem; }
.category-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.cat-chip {
    padding: 0.3rem 0.65rem;
    background: var(--bg);
    border: 2px solid transparent;
    border-radius: var(--radius);
    color: var(--subtle);
    font-family: var(--font-body);
    font-size: 0.8rem;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}
.cat-chip:hover { color: var(--text); }
.cat-chip.active { color: var(--leaf-light); border-color: var(--leaf); }
.cat-chip:disabled { cursor: default; opacity: 1; }
.modal-typed-hint { font-size: 0.8rem; color: var(--subtle); font-style: italic; }
.modal-input {
    width: 100%;
    padding: 0.6rem 0.75rem;
    background: var(--bg);
    border: 2px solid color-mix(in srgb, var(--leaf) 40%, transparent);
    border-radius: var(--radius);
    color: var(--text);
    font-family: var(--font-body);
    font-size: 0.9rem;
    outline: none;
    transition: border-color 0.15s;
}
.modal-input:focus { border-color: var(--leaf); }
/* Search loading state (js/search-status.js): the border becomes a conic gradient (shown only
   in the border box; the padding box stays --bg) whose bright arc rotates via the registered
   --search-angle, starting from the top-left corner (--search-origin, measured by the script;
   the arc's peak sits at 85% = 306deg of the gradient, hence the offset). */
@property --search-angle { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
.modal-input.is-searching {
    border-color: transparent;
    background:
        linear-gradient(var(--bg), var(--bg)) padding-box,
        conic-gradient(from calc(var(--search-origin, 0deg) - 306deg + var(--search-angle)),
            color-mix(in srgb, var(--leaf) 30%, transparent) 0% 60%,
            var(--leaf-light) 85%,
            color-mix(in srgb, var(--leaf) 30%, transparent) 100%) border-box;
    animation: search-spin 0.9s linear infinite;
}
@keyframes search-spin { to { --search-angle: 360deg; } }
@media (prefers-reduced-motion: reduce) {
    .modal-input.is-searching { animation: none; background: var(--bg); border-color: var(--leaf-light); }
}
.modal-input::placeholder { color: var(--subtle); }
.modal-search-link { font-size: 0.85rem; color: var(--sky); text-decoration: none; }
.modal-search-link:hover { text-decoration: underline; }
.modal-search-results { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.25rem; }
.search-result-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    background: var(--bg);
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--text);
    font-family: var(--font-body);
    font-size: 0.875rem;
    padding: 0.4rem 0.75rem;
    text-align: left;
    cursor: pointer;
    width: 100%;
    transition: border-color 0.15s;
}
/* Book results: "Other editions (n)" under a result opens a strip of those editions' covers. */
.search-editions-toggle {
    align-self: flex-start;
    background: none;
    border: none;
    color: var(--sky);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 0.75rem;
    padding: 0 0.75rem 0.3rem 2.6rem;   /* line up under the result's title, past its thumbnail */
}
.search-editions-toggle:hover { color: var(--sky-hover); text-decoration: underline; }
.search-editions-toggle::after { content: ' ▸'; }
.search-editions-toggle.open::after { content: ' ▾'; }
.search-editions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.25rem 0.75rem 0.6rem 2.6rem;
}
.search-edition {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    width: 4.25rem;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--subtle);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 0.65rem;
    line-height: 1.2;
    padding: 0.2rem;
    text-align: center;
}
.search-edition:hover { border-color: var(--leaf); color: var(--text); }
.search-edition img { width: 44px; height: 66px; object-fit: cover; display: block; }
.search-edition span { overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }

.search-result-item:hover { border-color: var(--leaf); }
.search-result-nodeid { color: var(--subtle); font-family: 'Courier New', monospace; font-size: 0.75rem; margin-left: auto; }
.modal-search-empty { font-size: 0.85rem; color: var(--subtle); font-style: italic; }
.user-preview-inner {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: var(--bg);
    border: 1px solid var(--leaf);
    border-radius: var(--radius);
    padding: 0.5rem 0.75rem;
}
.user-preview-icon { color: var(--leaf-light); line-height: 1; flex-shrink: 0; }
.user-preview-name { font-weight: 500; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.user-preview-nodeid { font-family: 'Courier New', monospace; font-size: 0.75rem; color: var(--subtle); margin-left: auto; }
.modal-error-wrap { min-height: 1.25em; }
.modal-actions { display: flex; align-items: center; gap: 0.5rem; justify-content: flex-end; margin-top: 0.25rem; }
/* Art editor modal left-aligns its actions to match the full-page /dev art tool. */
.modal-box--art .modal-actions { justify-content: flex-start; }
.modal-success { color: var(--success); font-size: 0.875rem; margin-right: auto; }

/* ── Footer ── */
footer {
    padding: 2rem;
    display: flex;
    justify-content: center;
    align-items: center;
}
.footer-nav {
    display: flex;
    gap: 2rem;
    justify-content: center;
}
.footer-nav a {
    color: var(--text);
    text-decoration: none;
    font-size: 0.9rem;
}
.footer-nav a:hover {
    text-decoration: underline;
}

/* ── YouTube ── */
.youtube-embed-wrapper {
    position: relative;
    padding-bottom: 56.25%;
    height: 0;
    overflow: hidden;
    margin: 1rem 0 1.25rem;
    border-radius: var(--radius);
}
.youtube-embed-wrapper iframe {
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    border: 0;
    border-radius: var(--radius);
}
.content-detail--channel { display: flex; flex-direction: column; align-items: flex-start; }
.channel-thumbnail {
    width: 96px;
    height: 96px;
    border-radius: var(--radius);
    object-fit: cover;
    margin-bottom: 0.75rem;
}
.external-link-trigger { margin-top: 1.25rem; }

/* ── YouTube channel card: the icon + 2×2 thumb grid are packed into the same square
   art area used by avatars/list/music cards (grid-slot--cover + square-art), with the
   title in the shared cover overlay. The thumb grid fills the square's full width at the
   bottom; the icon shrinks into whatever space remains above it. ── */
.slot-yt-square {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    aspect-ratio: 1 / 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    overflow: hidden;
}
.slot-yt-channel-logo {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    padding: 0.5rem;
    box-sizing: border-box;
}
.slot-yt-channel-placeholder {
    max-width: 48%;
    max-height: 72%;
    width: auto;
    height: auto;
    object-fit: contain;
    opacity: 0.92;
}
.slot-yt-channel-thumbs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    width: 100%;
    flex-shrink: 0;
}
/* Each cell sizes itself to a 16:9 box matching the thumbnail's own ratio, so the
   grid never forces a squarer cell that would crop the image under object-fit. */
.slot-yt-channel-thumbs img {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    display: block;
}

/* ── YouTube grid card variants ── */
.grid-slot--yt-video .slot-cover-img {
    object-fit: contain;
    object-position: center center;
    background: #000;
}
.grid-slot--yt-video .slot-cover-overlay {
    background: linear-gradient(to top, rgba(0,0,0,0.72) 0%, transparent 55%);
}
.slot-yt-play {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -60%);
    pointer-events: none;
    line-height: 1;
    opacity: 0.8;
    transition: opacity 0.15s;
}
.grid-slot--yt-video:hover .slot-yt-play { opacity: 1; }
.grid-slot--yt-channel { background: #000; }

/* ── Feedback & Dev pages ── */
.content-page {
    max-width: 700px;
    width: 100%;
    margin: 2rem auto;
    padding: 0 2rem;
    box-sizing: border-box;
}
.dev-tab-panel {
    width: 100%;
    box-sizing: border-box;
}
.dev-art-hint { color: var(--subtle); font-size: 0.85rem; margin: 0.5rem 0 1rem; max-width: 48ch; }
.dev-art-hint code { background: var(--bg-raised); padding: 0.05rem 0.3rem; border-radius: 3px; }
.dev-art-status { color: var(--leaf-light); font-size: 0.85rem; align-self: center; }
.feedback-page .content-container h1,
.dev-page .content-container h1 {
    font-family: var(--font-heading);
    font-size: 2rem;
    margin: 0 0 1.5rem 0;
    color: var(--text);
}
.feedback-section {
    margin-bottom: 2.5rem;
}
.feedback-section h2 {
    font-family: var(--font-heading);
    font-size: 1.25rem;
    margin: 0 0 0.5rem 0;
    color: var(--text);
}
.feedback-intro {
    font-size: 0.9rem;
    color: var(--text);
    opacity: 0.7;
    margin: 0 0 1rem 0;
}
.feedback-empty {
    font-size: 0.9rem;
    color: var(--text);
    opacity: 0.5;
}

/* Feature voting rows */
.feature-list { display: flex; flex-direction: column; gap: 0.75rem; }
.feature-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    background: var(--bg-raised);
    border: 1px solid color-mix(in srgb, var(--leaf) 20%, transparent);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
}
.feature-info { display: flex; flex-direction: column; gap: 0.2rem; flex: 1; min-width: 0; }
.feature-title { font-weight: 600; font-size: 0.95rem; color: var(--text); }
.feature-desc  { font-size: 0.85rem; color: var(--text); opacity: 0.7; }
.feature-attr  { font-size: 0.8rem; color: var(--text); opacity: 0.55; }
.feature-attr a { color: var(--sky); text-decoration: none; }
.feature-attr a:hover { text-decoration: underline; }

.feature-votes { display: flex; gap: 0.4rem; align-items: center; flex-shrink: 0; }
.vote-btn {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    background: var(--bg-raised);
    border: 1px solid color-mix(in srgb, var(--leaf) 25%, transparent);
    color: var(--text);
    border-radius: var(--radius);
    padding: 0.3rem 0.55rem;
    font-size: 0.85rem;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.vote-btn:disabled { opacity: 0.4; cursor: default; }
.vote-btn--up:hover:not(:disabled)   { border-color: var(--leaf); color: var(--leaf); }
.vote-btn--down:hover:not(:disabled) { border-color: var(--error); color: var(--error); }
.vote-btn--up.vote-btn--active   { border-color: var(--leaf); color: var(--leaf); background: color-mix(in srgb, var(--leaf) 12%, transparent); }
.vote-btn--down.vote-btn--active { border-color: var(--error); color: var(--error); background: color-mix(in srgb, var(--error) 12%, transparent); }

/* Feedback form */
.feedback-type-select { margin-bottom: 0.5rem; }
.feedback-textarea {
    width: 100%;
    min-height: 6rem;
    resize: vertical;
    margin-bottom: 0.5rem;
    box-sizing: border-box;
    font-family: var(--font-body);
}
.feedback-actions { display: flex; gap: 0.5rem; align-items: center; }

.feedback-status { margin-top: 1rem; }
.feedback-reply {
    background: var(--bg-raised);
    border-left: 3px solid var(--leaf);
    padding: 0.6rem 0.8rem;
    border-radius: var(--radius);
    margin-top: 0.5rem;
}
.feedback-reply-label { font-size: 0.8rem; font-weight: 600; color: var(--leaf); display: block; margin-bottom: 0.25rem; }
.feedback-reply p { margin: 0; font-size: 0.9rem; color: var(--text); }
.feedback-read-receipt { font-size: 0.85rem; color: var(--leaf); opacity: 0.8; margin: 0; }

/* Dev page */
.dev-tabs { display: flex; gap: 0; margin-bottom: 1.5rem; border-bottom: 2px solid color-mix(in srgb, var(--leaf) 20%, transparent); }
.dev-tabs .tab {
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    padding: 0.5rem 1.2rem;
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 0.95rem;
    color: var(--text);
    opacity: 0.6;
    transition: opacity 0.12s, border-color 0.12s;
}
.dev-tabs .tab.active { opacity: 1; border-bottom-color: var(--leaf); }
.dev-tabs .tab:hover  { opacity: 0.9; }
.dev-unread-badge {
    display: inline-block;
    background: var(--error);
    color: #fff;
    border-radius: var(--radius);
    font-size: 0.7rem;
    padding: 0 0.4rem;
    margin-left: 0.3rem;
    vertical-align: middle;
    line-height: 1.5;
}

.dev-feedback-list { display: flex; flex-direction: column; gap: 1rem; }
.dev-feedback-item {
    background: var(--bg-raised);
    border: 1px solid color-mix(in srgb, var(--leaf) 20%, transparent);
    border-radius: var(--radius);
    padding: 0.85rem 1rem;
}
.dev-feedback-item--read { opacity: 0.65; }
.dev-feedback-meta {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-bottom: 0.5rem;
    font-size: 0.85rem;
}
.dev-feedback-user  { font-weight: 600; color: var(--text); }
.dev-feedback-type  { background: color-mix(in srgb, var(--gold) 18%, transparent); color: var(--gold); border-radius: var(--radius); padding: 0.1rem 0.4rem; font-size: 0.75rem; }
.dev-feedback-date  { color: var(--text); opacity: 0.5; }
.dev-feedback-read-mark { color: var(--leaf); opacity: 0.7; font-size: 0.8rem; }
.dev-feedback-comment { margin: 0 0 0.75rem 0; font-size: 0.9rem; color: var(--text); white-space: pre-wrap; }
.dev-reply-area { display: flex; gap: 0.5rem; align-items: flex-start; }
.dev-reply-input { flex: 1; resize: vertical; font-family: var(--font-body); font-size: 0.85rem; }

/* Feedback / Features: a user's own items (feedback history, "Your requests") */
.my-feedback-list { display: flex; flex-direction: column; gap: 0.75rem; }
.my-feedback-item {
    background: var(--bg-raised);
    border: 1px solid color-mix(in srgb, var(--leaf) 20%, transparent);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
/* Item type scale matches the Features cards: 0.9rem text, 0.8rem details, 0.75rem badges. */
.my-feedback-meta { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; font-size: 0.8rem; }
.my-feedback-meta .feedback-read-receipt { font-size: inherit; }
/* Edit / Delete are secondary: smaller than the item's own text. */
.my-feedback-actions .btn-small { font-size: 0.8rem; font-weight: 500; padding: 0.2rem 0.6rem; }
.my-feedback-text { margin: 0; font-size: 0.9rem; color: var(--text); white-space: pre-wrap; }
.my-feedback-item .feedback-reply { margin-top: 0; }
.feedback-intro--small { font-size: 0.85rem; margin: 0; }
.feedback-intro--small a, .feedback-intro a { color: var(--sky); }

/* Review state of a feature request */
.request-state { border-radius: var(--radius); padding: 0.1rem 0.4rem; font-size: 0.75rem; font-weight: 600; }
.request-state--pending     { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--subtle); }
.request-state--listed      { background: color-mix(in srgb, var(--sky) 18%, transparent); color: var(--sky); }
.request-state--implemented { background: color-mix(in srgb, var(--leaf) 18%, transparent); color: var(--leaf); }
.request-state--declined    { background: color-mix(in srgb, var(--error) 14%, transparent); color: var(--error); }

/* Collapsible panels under the feature list (Implemented, Request, Your requests) */
.feature-panel { margin-top: 1.5rem; }
.feature-panel > summary {
    /* Same face as the page's h2 section headings (.feedback-section h2): summary isn't a
       heading, so it would otherwise get Playfair's regular weight, not the bold h2 uses. */
    cursor: pointer;
    font-family: var(--font-heading);
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--text);
    margin-bottom: 0.75rem;
}
.feature-panel > summary:hover { color: var(--gold); }
.feature-request-form { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.5rem; }
.feature-row--implemented { opacity: 0.8; }

.dev-feedback-filters { margin-bottom: 1rem; flex-wrap: wrap; }
.dev-feedback-item[hidden] { display: none; }
.dev-reply-area { flex-wrap: wrap; }

/* Dev Themes tab */
.dev-theme-table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
.dev-theme-table th { text-align: left; font-weight: 600; color: var(--subtle); font-size: 0.8rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid color-mix(in srgb, var(--leaf) 30%, transparent); }
.dev-theme-table td { padding: 0.5rem; border-bottom: 1px solid color-mix(in srgb, var(--leaf) 12%, transparent); color: var(--text); vertical-align: middle; }
.dev-theme-table td:first-child { display: flex; align-items: center; gap: 0.6rem; }
.dev-theme-table .swatch-preview { width: 3.5rem; height: 1rem; }
.dev-theme-none td { color: var(--subtle); font-style: italic; }
.dev-theme-bar-cell { width: 40%; white-space: nowrap; color: var(--subtle); }
.dev-theme-bar { display: inline-block; height: 0.6rem; background: var(--leaf); vertical-align: middle; min-width: 1px; }

/* Dev features */
.dev-add-feature-form { margin-bottom: 1.5rem; padding: 1rem; background: var(--bg-raised); border-radius: var(--radius); }
.dev-add-feature-form button[type="submit"] { width: auto; }
.dev-add-feature-form h3 { margin: 0 0 0.75rem 0; font-size: 1rem; color: var(--text); }
.dev-add-feature-form .modal-input { margin-bottom: 0.5rem; width: 100%; box-sizing: border-box; }
.dev-user-search-row { position: relative; }
.dev-attr-selected { font-size: 0.85rem; color: var(--leaf); margin-left: 0.5rem; }
.dev-user-results {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--bg-raised);
    border: 1px solid color-mix(in srgb, var(--leaf) 30%, transparent);
    border-radius: var(--radius);
    list-style: none;
    margin: 0;
    padding: 0;
    z-index: 50;
    max-height: 180px;
    overflow-y: auto;
}
.dev-user-results li { padding: 0.5rem 0.75rem; cursor: pointer; font-size: 0.9rem; color: var(--text); }
.dev-user-results li:hover { background: color-mix(in srgb, var(--leaf) 10%, transparent); }

.dev-feature-list { display: flex; flex-direction: column; gap: 0.6rem; }
.dev-feature-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    background: var(--bg-raised);
    border: 1px solid color-mix(in srgb, var(--leaf) 20%, transparent);
    border-radius: var(--radius);
    padding: 0.6rem 0.8rem;
}
.dev-feature-info { display: flex; flex-direction: column; gap: 0.15rem; flex: 1; min-width: 0; }
.dev-feature-votes { display: flex; align-items: center; gap: 0.6rem; flex-shrink: 0; font-size: 0.85rem; }
.vote-up-label   { color: var(--leaf); }
.vote-down-label { color: var(--error); }
/* API kill switches (APIs tab) — same card rhythm as feature rows; off state goes red. */
.dev-api-switches { display: flex; flex-direction: column; gap: 0.6rem; max-width: 32rem; }
.dev-api-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    background: var(--bg-raised);
    border: 1px solid color-mix(in srgb, var(--leaf) 20%, transparent);
    border-radius: var(--radius);
    padding: 0.6rem 0.8rem;
}
.dev-api-row--off { border-color: color-mix(in srgb, var(--error) 35%, transparent); }
.dev-api-info { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.dev-api-name { font-weight: 600; }
.dev-api-cost { font-size: 0.8rem; color: var(--subtle); }
.dev-api-toggle { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; flex-shrink: 0; }
.dev-api-state { font-size: 0.8rem; color: var(--subtle); min-width: 4.5rem; text-align: right; }
.dev-api-row--off .dev-api-state { color: var(--error); }
.dev-api-switch { width: 1.1rem; height: 1.1rem; accent-color: var(--leaf); cursor: pointer; }
.btn-danger {
    background: color-mix(in srgb, var(--error) 15%, transparent);
    color: var(--error);
    border: 1px solid color-mix(in srgb, var(--error) 35%, transparent);
}
.btn-danger:hover:not(:disabled) { background: color-mix(in srgb, var(--error) 25%, transparent); }

/* Analytics */
.analytics-controls {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-bottom: 1rem;
}
.analytics-toggle-group {
    display: flex;
    border: 1px solid color-mix(in srgb, var(--leaf) 30%, transparent);
    border-radius: var(--radius);
    overflow: hidden;
}
.analytics-toggle {
    background: transparent;
    color: var(--subtle);
    border: none;
    padding: 0.35rem 0.85rem;
    font-size: 0.85rem;
    cursor: pointer;
    font-family: var(--font-body);
    transition: background 0.15s, color 0.15s;
}
.analytics-toggle + .analytics-toggle {
    border-left: 1px solid color-mix(in srgb, var(--leaf) 30%, transparent);
}
.analytics-toggle.active {
    background: color-mix(in srgb, var(--leaf) 20%, transparent);
    color: var(--text);
}
.analytics-toggle:hover:not(.active) {
    background: color-mix(in srgb, var(--leaf) 10%, transparent);
    color: var(--text);
}
.analytics-label {
    font-size: 0.8rem;
    color: var(--subtle);
    margin: 0 0 0.75rem;
}
.analytics-chart-wrap {
    position: relative;
    width: 100%;
}

/* ── Social-list similarity panel (owner only) ── */
.list-similarity {
    max-width: 700px;
    margin: 2rem auto 0;
    padding: 1.25rem 1.5rem;
    background: var(--bg-raised);
}
.similarity-heading {
    font-family: var(--font-heading);
    font-size: 1.25rem;
    margin: 0 0 1rem;
}
.similarity-empty {
    color: var(--subtle);
    font-size: 0.9rem;
    margin: 0;
}
.similarity-mainstream {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.35rem 0.6rem;
    padding-bottom: 1rem;
    margin-bottom: 1rem;
    border-bottom: 1px solid var(--border);
}
.similarity-mainstream-pct {
    font-family: var(--font-display);
    font-size: 1.6rem;
    font-weight: 700;
    color: var(--gold);
    line-height: 1;
}
.similarity-mainstream-label {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--subtle);
}
.similarity-info {
    cursor: help;
    color: var(--subtle);
}
.similarity-mainstream-sub {
    flex-basis: 100%;
    font-size: 0.8rem;
    color: var(--subtle);
}
.similarity-list {
    display: flex;
    flex-direction: column;
}
.similarity-row {
    --row-accent: var(--subtle);   /* moderate match by default */
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0.4rem 0.6rem 0.75rem;
    border-bottom: 1px solid color-mix(in srgb, var(--row-accent) 35%, transparent);
    transition: background 0.15s;
}
/* Accent bar as a pseudo-element (not a border) so it doesn't miter-join the
   bottom rule into a taper at the corner. */
.similarity-row::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: var(--row-accent);
}
.similarity-row:last-child { border-bottom-color: transparent; }
.similarity-row:hover { background: color-mix(in srgb, var(--row-accent) 8%, transparent); }
/* Match-strength colour scale: green (near-exact) → gold (strong) → muted. */
.similarity-row--exact  { --row-accent: var(--leaf-light); }
.similarity-row--strong { --row-accent: var(--gold); }
.similarity-row-main {
    flex: 1;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.2rem 0.5rem;
    min-width: 0;
}
.similarity-title {
    color: var(--link);
    text-decoration: none;
    font-size: 0.95rem;
}
.similarity-title:hover { text-decoration: underline; }
.similarity-user {
    color: var(--subtle);
    text-decoration: none;
    font-size: 0.85rem;
}
a.similarity-user:hover { color: var(--leaf-light); text-decoration: underline; }
/* Near-exact matches: surface the person, since the lists barely differ. */
.similarity-row--exact .similarity-user {
    color: var(--leaf-light);
    font-weight: 600;
    font-size: 0.95rem;
}
.similarity-row--exact .similarity-title {
    color: var(--subtle);
    font-size: 0.85rem;
}
.similarity-row-pct {
    flex-shrink: 0;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--text);
    background: color-mix(in srgb, var(--row-accent) 20%, transparent);
    padding: 0.15rem 0.5rem;
    border-radius: var(--radius);
}
.similarity-more-wrap {
    margin-top: 1rem;
    text-align: center;
}
