/* ==========================================================================
   redesign.css — laag 1 van het Encore Console redesign
   --------------------------------------------------------------------------
   Laadt NA style.css en raakt geen JS aan. Alles hangt aan bestaande
   classes; gelijke specificiteit + latere cascade = deze regels winnen.
   In index.html, direct onder de style.css-link:
       <link rel="stylesheet" href="/redesign.css">

   Wat deze laag doet:
     1. Chrome wordt donker marineblauw (voorbode van de sidebar in stap 2)
     2. Kaarten: zachter oppervlak, ruimere radius, schaduw i.p.v. harde rand
     3. Typografie: duidelijker hiërarchie (Space Grotesk zwaarder + strakker)
     4. Hoezen: liften bij hover, echte diepte
     5. Rijen/chips/knoppen: rondere, rustigere vormen
   Kleuren komen uit de bestaande tokens, dus dark mode blijft werken.
   ========================================================================== */

:root{
  /* Nieuwe tokens die alleen deze laag gebruikt */
  --r-chrome-1:#152b4f;          /* zijbalk/koptop  */
  --r-chrome-2:#0d1a30;          /* zijbalk/kopvoet */
  --r-chrome-ink:#ffffff;
  --r-chrome-soft:#8ba3c7;       /* secundaire tekst op chrome */
  --r-chrome-line:rgba(255,255,255,0.1);
  --r-radius:14px;
  --r-radius-sm:10px;
  --r-shadow-1:0 1px 2px rgba(16,24,40,0.04), 0 8px 20px rgba(16,24,40,0.06);
  --r-shadow-2:0 2px 4px rgba(16,24,40,0.06), 0 18px 38px rgba(16,24,40,0.12);
  --r-shadow-cover:0 10px 24px rgba(16,24,40,0.18);
  --r-shadow-cover-hi:0 18px 38px rgba(16,24,40,0.26);
  --r-surface:var(--card);
  --r-hairline:var(--line);
}
:root[data-theme="dark"]{
  --r-shadow-1:0 1px 2px rgba(0,0,0,0.3), 0 8px 20px rgba(0,0,0,0.28);
  --r-shadow-2:0 2px 4px rgba(0,0,0,0.35), 0 18px 38px rgba(0,0,0,0.4);
  --r-shadow-cover:0 10px 24px rgba(0,0,0,0.5);
  --r-shadow-cover-hi:0 18px 38px rgba(0,0,0,0.6);
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --r-shadow-1:0 1px 2px rgba(0,0,0,0.3), 0 8px 20px rgba(0,0,0,0.28);
    --r-shadow-2:0 2px 4px rgba(0,0,0,0.35), 0 18px 38px rgba(0,0,0,0.4);
    --r-shadow-cover:0 10px 24px rgba(0,0,0,0.5);
    --r-shadow-cover-hi:0 18px 38px rgba(0,0,0,0.6);
  }
}

/* ── 1. Chrome ─────────────────────────────────────────────────────────────
   De koptekst wordt de donkere balk uit het redesign: geen 2px inktlijn
   meer, maar een marineblauw verloop met een haarlijn eronder. Alle
   kinderen krijgen expliciete kleuren, zodat licht én donker kloppen.

   Elke selector hier is bewust geprefixt met .header-stack (de bestaande
   sticky wrapper uit index.html): style.css richt zich op .header, .logo-text,
   .nav-btn en .btn-add met één klasse, dus twee klassen winnen hier altijd —
   ongeacht waar die regels in style.css staan. */
.header-stack .header{
  background:linear-gradient(180deg,var(--r-chrome-1) 0%,var(--r-chrome-2) 100%);
  border-bottom:1px solid var(--r-chrome-line);
  color:var(--r-chrome-ink);
  padding:12px clamp(16px,4vw,40px);
}
.header-stack .logo-text{color:var(--r-chrome-ink);font-size:19px;letter-spacing:0.06em}
.header-stack .logo-mark{border-radius:9px;box-shadow:0 2px 8px rgba(0,0,0,0.35)}
.header-stack .nav{gap:2px;margin-left:14px}
.header-stack .nav-btn{padding:7px 12px;border-radius:9px}
.header-stack .nav-btn span{
  border-bottom:none;padding-bottom:0;color:var(--r-chrome-soft);
  font-size:13px;font-weight:600;transition:color .15s ease;
}
.header-stack .nav-btn:hover span{color:var(--r-chrome-ink)}
.header-stack .nav-btn.active{background:rgba(255,255,255,0.12)}
.header-stack .nav-btn.active span{border-bottom-color:transparent;color:var(--r-chrome-ink)}
.header-stack .current-user-name{color:var(--r-chrome-soft)}

.header .search-input{
  background:rgba(255,255,255,0.08);border:1px solid var(--r-chrome-line);
  border-radius:9px;color:var(--r-chrome-ink);
}
.header .search-input::placeholder{color:var(--r-chrome-soft)}
.header .search-input:focus{
  background:rgba(255,255,255,0.14);border-color:rgba(255,255,255,0.32);
}
.header .search-clear{color:var(--r-chrome-soft)}
.header .search-clear:hover{color:var(--r-chrome-ink)}

.header .icon-btn{
  background:rgba(255,255,255,0.07);border:1px solid var(--r-chrome-line);
  border-radius:9px;
}
.header .icon-btn svg{stroke:#dbe6f5}
.header .icon-btn:hover{background:rgba(255,255,255,0.15);border-color:rgba(255,255,255,0.28)}
.header .icon-btn.active{
  background:rgba(255,255,255,0.18);border-color:rgba(255,255,255,0.4);
}
.header .icon-btn.active svg{stroke:#fff}
.header .icon-btn.bell.has-unread::after{border-color:var(--r-chrome-2)}
.header-stack .btn-add{
  background:#fff;color:var(--r-chrome-1);border-radius:9px;padding:9px 16px;
  font-size:12.5px;box-shadow:0 2px 8px rgba(0,0,0,0.25);
}
.header-stack .btn-add:hover{background:#e6edf8}
/* De dropdowns hangen ónder de donkere balk in de lichte inhoud — die
   houden hun eigen kaartkleur, dus alleen de radius harmoniseren. */
.year-filter-panel-scroll{border-radius:var(--r-radius-sm);box-shadow:var(--r-shadow-2)}

/* ── 2. Oppervlakken ──────────────────────────────────────────────────────
   Kaarten worden zachter: grotere radius, schaduw als scheiding, rand als
   haarlijn. Panelen liften niet (ze zijn niet klikbaar), hoezen wel. */
/* Alleen top/links/rechts — GEEN padding-bottom. style.css zet .main's
   padding-bottom apart per breakpoint (52px op desktop voor .desktop-version-bar,
   84px + safe-area op mobiel voor de vaste onderbalk). De `padding`-shorthand
   uit de handoff overschreef die en verstopte het laatste item achter de balk;
   drie longhands laten die regels met rust. */
.main{padding-top:clamp(20px,4vw,44px);padding-left:clamp(20px,4vw,44px);padding-right:clamp(20px,4vw,44px);max-width:1240px}
.panel,
.stat-card,
.auth-card,
.install-panel,
.install-hint{
  border-radius:var(--r-radius);
  border-color:var(--r-hairline);
  box-shadow:var(--r-shadow-1);
}
.panel{padding:24px}
.panel-grid{gap:22px}
.stat-card{padding:20px 22px;position:relative;overflow:hidden}
.stat-card::before{
  content:"";position:absolute;inset:0 0 auto;height:3px;
  background:linear-gradient(90deg,var(--accent),transparent 85%);
  opacity:.55;
}
.detail-header-panel{border-radius:var(--r-radius);box-shadow:var(--r-shadow-1)}
.wm-frame{border-radius:var(--r-radius);box-shadow:var(--r-shadow-1)}

/* ── 3. Typografie ────────────────────────────────────────────────────────
   Eén trap steiler: titels zwaarder en strakker, kaartkoppen in Space
   Grotesk, mono-labels kleiner en wijder gespatieerd. */
.page-title,
.detail-title{letter-spacing:-0.02em;line-height:1.04}
.page-head{margin-bottom:22px}
.eyebrow{font-size:10.5px;letter-spacing:0.18em;margin-bottom:8px}
.panel-head{margin-bottom:18px}
.panel-head h2{
  font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:15px;
  letter-spacing:-0.01em;
}
.section-title,
.settings-group-title{font-weight:700;letter-spacing:-0.01em}
.stat-value{font-size:44px;letter-spacing:-0.03em;font-variant-numeric:tabular-nums}
.stat-label{font-size:10px;letter-spacing:0.16em;margin-top:8px}
.meta-label{font-size:9.5px;letter-spacing:0.16em}
.list-section-head{font-size:10px;letter-spacing:0.16em}
.view-all{font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:0.06em}
.empty-state{font-size:13.5px;padding:56px 20px}

/* ── 4. Hoezen ────────────────────────────────────────────────────────────
   De hoes is de hoofdrolspeler: echte schaduw, en liften bij hover. */
.album-cover .image-slot,
.mini-cover .image-slot,
.list-cover .image-slot,
.detail-cover .image-slot{
  border-radius:var(--r-radius-sm);
  box-shadow:var(--r-shadow-cover);
  transition:transform .18s ease, box-shadow .18s ease;
}
.artist-photo .image-slot,
.artist-cover .image-slot,
.fav-mini-thumb.artist-cover .image-slot{border-radius:50%}
.grid-albums{gap:26px}
.album-card:hover .album-cover .image-slot,
.mini-card:hover .mini-cover .image-slot{
  transform:translateY(-5px);box-shadow:var(--r-shadow-cover-hi);
}
.album-card .album-title{margin-top:11px;letter-spacing:-0.01em}
/* De favorietenster zit absoluut op .album-card (naast .album-cover), dus de
   lift van .image-slot hierboven pakt hem niet mee — apart meeliften zodat
   hij niet losgekoppeld boven de hoes blijft hangen. Het witte cirkeltje
   (var(--overlay-bg)) blijft voorlopig staan voor leesbaarheid; fase 3
   (Releases/Artists-grid) vervangt het door de kale gouden ster uit het design. */
.album-card .favorite-star{transition:transform .18s ease}
.album-card:hover .favorite-star{transform:translateY(-5px)}
.detail-cover .image-slot{box-shadow:var(--r-shadow-2)}

/* ── 5. Rijen, chips, knoppen ─────────────────────────────────────────────
   Lijstrijen krijgen een zachte hoverkaart in plaats van alleen een
   onderlijn; filters en segmenten worden pillen. */
/* Geen negatieve marge hier: .list-row wordt ook gebruikt in lijsten met
   sleep-handvatten en in festival-layouts, waar een uitlopende rij tegen de
   kaartrand botst. De radius is alleen zichtbaar bij hover, dat is genoeg. */
.list-row{
  border-bottom:1px solid var(--r-hairline);
  border-radius:var(--r-radius-sm);
  padding:12px 10px;
  transition:background .14s ease;
}
.list-row:hover{background:var(--bg-alt)}
.list-row:last-child{border-bottom:none}
.list-title{letter-spacing:-0.01em}
.mini-concert{padding:11px 8px;margin:0 -8px;border-radius:var(--r-radius-sm);transition:background .14s ease}
.mini-concert:hover{background:var(--bg-alt)}
.fav-mini-row,
.artist-mini-row{border-radius:8px;padding:3px 8px;margin:0 -8px}

.filterbar{gap:8px;margin-bottom:22px;border-radius:12px}
.fbtn{border-radius:999px;padding:8px 15px;box-shadow:var(--r-shadow-1)}
/* Actief filter: gevuld accent (navy in licht) + tekst op --card, zodat het
   in beide thema's leesbaar is (handoff scherm 2). */
.fbtn.is-active{
  padding-right:33px;box-shadow:none;
  background:var(--accent);border-color:var(--accent);color:var(--card);
}
.fbtn.is-active:hover{border-color:var(--accent);filter:brightness(1.08)}
/* De ✕ is een sibling van .fbtn.is-active (beide in .year-filter), absoluut
   over de rechter-padding. Op de gevulde navy pil: witte cirkel + accent-✕,
   altijd zichtbaar. */
.fbtn.is-active + .filter-clear{
  background:var(--card);color:var(--accent);
}
.fbtn.is-active + .filter-clear:hover,
.fbtn.is-active + .filter-clear:focus-visible{
  background:var(--card);color:var(--accent);filter:brightness(0.9);
}
.fav-icon-toggle.active{
  background:var(--accent);border-color:var(--accent);color:var(--card);
}
/* One "Filters" button per page (fase 16, 2026-09-14) — see
   combinedFilterHTML()'s own comment (app.js). .cf-toggle only overrides
   .fbtn.is-active's own padding-right:33px (style.css — space reserved
   for the little ✕-clear icon every *other* .fbtn.is-active shows next to
   it): this button doesn't get one, "Clear all" (clearAllBtnHTML) already
   sits right next to it on every page unchanged. The panel itself widens
   past .filterbar .year-filter-panel's normal 200px chip-hugging cap
   (style.css) — it holds labelled groups now, not a single row of chips. */
.fbtn.cf-toggle.is-active{padding-right:15px}
.filterbar .year-filter-panel.cf-panel{width:280px;max-width:min(280px,calc(100vw - 24px))}
.cf-body{
  background:var(--card);border:1px solid var(--line);border-radius:var(--r-radius-sm);
  box-shadow:var(--r-shadow-2);padding:6px;box-sizing:border-box;
}
/* Accordion header (2026-09-14 follow-up) — a real button now, not a plain
   label: click to expand this group's own options, collapsing whichever
   other one was open (actions.toggleFilterGroup()). Same .chevron-mark
   every other expand/collapse row in the app already uses (Feedback
   threads, Release notes, Help Center articles), just smaller. */
.cf-group-label{
  display:flex;align-items:center;gap:7px;width:100%;
  font-family:'IBM Plex Mono',monospace;font-size:10px;font-weight:700;letter-spacing:0.08em;
  text-transform:uppercase;color:var(--ink-soft);padding:8px 9px;
  background:none;border:none;cursor:pointer;text-align:left;
}
.cf-group-label:hover{color:var(--ink)}
.cf-group-label .chevron-mark{font-size:14px}
.cf-group-label-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cf-group-count{
  flex:none;font-family:'IBM Plex Mono',monospace;font-size:10px;font-weight:600;
  color:var(--ink);background:var(--bg-alt);border-radius:999px;padding:1px 7px;
}
.cf-scroll{max-height:180px;overflow-y:auto}
.cf-row{
  display:flex;align-items:center;gap:9px;width:100%;
  padding:7px 9px;border-radius:7px;cursor:pointer;text-align:left;font-family:inherit;
  font-size:12.5px;color:var(--ink);background:none;border:none;
}
.cf-row:hover{background:var(--bg-alt)}
.cf-dot{
  flex:none;width:16px;height:16px;border-radius:5px;border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;font-size:10px;color:#fff;
}
.cf-row.inc .cf-dot{background:var(--ink);border-color:var(--ink)}
.cf-row.exc{color:var(--danger)}
.cf-row.exc .cf-dot{background:var(--danger);border-color:var(--danger)}
/* Favorites-only row (2026-09-14 follow-up) — a plain star glyph, not the
   check/cross box every other .cf-row uses (nothing to include/exclude,
   just on/off), in the same gold .favorite-star already uses elsewhere
   once active. */
.cf-row.cf-fav .cf-dot.cf-star{background:none;border:none;width:auto;height:auto;font-size:14px;color:var(--ink-soft)}
.cf-row.cf-fav.active{color:var(--ink);font-weight:600}
.cf-row.cf-fav.active .cf-dot.cf-star{color:#e0a850}
.cf-divider{height:1px;background:var(--line);margin:4px 4px}
/* "Click once to include · twice to exclude" (TODO.md item 158, 1A) — one
   quiet line above the groups, with the same boxes as .cf-dot. */
.cf-legend{display:flex;flex-wrap:wrap;gap:4px 12px;align-items:center;padding:6px 9px 4px;font-size:11px;color:var(--ink-soft)}
.cf-legend span{display:inline-flex;align-items:center;gap:5px}
.cf-mini{display:inline-flex;align-items:center;justify-content:center;width:13px;height:13px;border-radius:4px;font-size:8px;font-style:normal;color:#fff}
.cf-mini.inc{background:var(--ink)}
.cf-mini.exc{background:var(--danger)}

/* Collapsible-row component (2026-09-15, mockup series approved by the owner,
   redesign screen audit) — replaces .au-card/.au-head/.au-body (style.css)
   everywhere it was used: Help Center, Release notes, Profile Tags/My
   notes/Private items, Admin Users, Admin/Account Feedback, the History
   modal. .au-card boxed each row individually (1px border, 8px radius)
   inside a panel that was *already* soft-shadowed by the redesign's global
   surface sweep — a box nested inside a already-softened box. .hc-row is
   instead a flat, hairline-divided row with a rounded hover highlight,
   same idiom .notif-row already shipped for Notifications (redesign.css,
   further down this file).
   Two screens (Private items, My notes) had no outer .panel of their own —
   .au-card there wasn't a box nested in a white surface, it *was* the only
   white surface. Flattening it without adding a replacement would have
   dropped the white-card look entirely (caught by the owner mid-review); the
   fix on those two screens also wraps the row list in a new outer .panel,
   same as Tags/Help Center/Admin Users already had.
   .hc-row-toggle deliberately does NOT copy .notif-row's negative-margin
   bleed trick (margin:0 -10px, width left auto to fill it) — .notif-row is
   a <div>, which reliably auto-expands to its container width with that
   trick; .hc-row-toggle is a <button>, and buttons don't reliably get that
   same auto-fill behavior (observed 2026-09-15, the owner: the hover/selected
   background and the row's own right-aligned pill — e.g. the 2FA badge on
   Admin Users — were both shrinking to fit the button's content instead of
   the row's full width). Explicit width:100%, no negative margin, fixes
   it: the toggle now reliably spans the row, and anything with
   margin-left:auto inside it (like .au-pill) lands flush against the true
   right edge. */
.hc-row{border-top:1px solid var(--r-hairline)}
.hc-row:first-child{border-top:none}
.hc-row-toggle{
  display:flex;align-items:center;gap:10px;width:100%;
  background:none;border:none;text-align:left;color:var(--ink);cursor:pointer;font:inherit;
  padding:14px 10px;border-radius:var(--r-radius-sm);
  transition:background .12s ease;
}
.hc-row-toggle:hover{background:var(--bg-alt)}
.hc-row-toggle .chevron-mark{color:var(--accent)}
.hc-row-body{padding:0 10px 16px 32px}

/* Profile → Tags — .tag-row sits directly inside .hc-row (no separate
   toggle wrapper: count/Rename/Delete sit on the row alongside the expand
   toggle, which .hc-row-toggle's "whole row is one button" shape can't
   hold without nesting buttons) — so the hover/rounding that .hc-row-toggle
   gives other screens goes on .tag-row itself here instead. */
.tag-row{display:flex;align-items:center;gap:9px;margin:0 -10px;padding:13px 10px;border-radius:var(--r-radius-sm);transition:background .12s ease}
.tag-row:hover{background:var(--bg-alt)}
.tag-row-toggle{display:flex;align-items:center;gap:9px;flex:1;min-width:0;background:none;border:none;text-align:left;color:var(--ink);cursor:pointer;font:inherit;padding:0}
.tag-row-toggle .chip{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tag-row-toggle .chevron-mark{color:var(--accent)}
.tag-row-edit{cursor:default}
.tag-row-edit:hover{background:none}
.tag-row-edit input{flex:1;min-width:0}
.tag-row-count{flex:none;font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--ink-soft)}
/* Expanded tag items reuse .tracklist/.track-row as-is (the owner, 2026-09-14
   — "dezelfde stijl als tracklist regels bij releases") — kind badge
   borrows the .track-playtime slot, no bespoke row classes needed. My
   notes' rows reuse plain .filter-row the same way (the owner: "dezelfde
   layout als Private items") — no dedicated note-card classes needed. */

/* Add menu ("What are you adding?", item c, 2026-09-14, mockup approved by
   The owner) — .add-choose-modal is scoped to just the "choose" step's own
   .modal (see renderAddModal()); every other step there still gets the old
   thick-ink-border shell until its own redesign pass. */
.modal.add-choose-modal{border:1px solid var(--line);border-radius:var(--r-radius);box-shadow:var(--r-shadow-2);padding:22px}
.choose-option{display:flex;align-items:flex-start;gap:12px;border:none;background:none;border-radius:var(--r-radius-sm);padding:12px;transition:background .12s ease}
.choose-option:hover{background:var(--bg-alt)}
.choose-option-icon{flex:none;width:34px;height:34px;border-radius:10px;background:var(--bg-alt);display:flex;align-items:center;justify-content:center;color:var(--ink)}
.choose-option-icon svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.choose-option-body{flex:1;min-width:0}
/* Same weight as .help-center-q/.filter-row-label — the app's existing
   "icon/chevron + title + one-line description" row convention, which is
   what this option row actually is (not a grid tile, not a page hero). 500
   (not 600) — IBM Plex Sans loads a 500 weight, closer to the mockup's own
   less-heavy title. */
.choose-option-title{display:block;font-weight:500;font-size:14px;color:var(--ink)}
.choose-option-desc{display:block;font-size:12.5px;color:var(--ink-soft);margin-top:2px}
.choose-divider{height:1px;background:var(--line);margin:6px 4px}
/* "What are you adding?" now shares ADMIN's mono/uppercase label style
   (the owner, 2026-09-14 follow-up) instead of the plain .modal h2 every other
   step here still uses — both sized up a step from the original 10px
   ADMIN label, the heading larger than the group label since it's the
   page's own title, not a sub-grouping within it. */
.choose-heading{margin:0 0 14px}
/* Titles (TODO.md item 158 follow-up — the owner, 2026-09-26): two styles,
   by rank, instead of a 13px mono title here and a 24px heavy Space Grotesk
   one there. Pop-ups (every .modal title, and the .choose-heading ones:
   What are you adding?, Who did you go with?, History, Event grouping,
   Manage aliases, Remove …?) are the quieter IBM Plex Sans 600 at 18px; a
   full edit page (Edit release / Edit show, .edit-page-title) uses the page
   titles' Space Grotesk at 28px. The mono uppercase style stays for small
   labels (field names, table heads, card titles), where it belongs. An
   item's own title inside a modal (h2.detail-title) keeps its style. */
.modal h2:not(.detail-title),
.choose-heading{
  font-family:'IBM Plex Sans',sans-serif;font-size:18px;font-weight:600;
  letter-spacing:0;text-transform:none;line-height:1.25;color:var(--ink);
}
.edit-page-title{
  font-family:'Space Grotesk',sans-serif;font-size:28px;font-weight:700;
  letter-spacing:-0.01em;line-height:1.15;color:var(--ink);margin:0 0 16px;
}
.choose-group-label{font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--ink-soft);padding:4px 12px 2px}

/* Add a release/Add a show page-specific elements (2026-09-14, mockup
   approved by the owner) — the Discogs import panel, its two dividers, the
   Format editor rows, and the "Marking as bought" note. Deliberately NOT
   touching .form-input/.meta-label/the submit/cancel buttons' own look or
   the private-checkbox row here — those are shared with Edit release/Add a
   show/Admin forms and the owner asked for that as its own separate pass
   (TODO.md item 118). */
.album-import-panel{border:none;border-radius:var(--r-radius-sm)}
.album-import-label{display:flex;align-items:center;gap:7px}
.album-import-label svg{width:14px;height:14px;stroke:var(--ink-soft);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
/* One line under the panel label saying what the panel does (TODO.md item
   158, 5A): Add fills the form, Edit saves the link at once. */
.album-import-help{font-size:12px;color:var(--ink-soft);margin:-4px 0 10px}
/* "Unlink" as a small text button (TODO.md item 158, 4C) — was the same
   circled-minus icon as "Remove from your releases/shows". */
.btn-outline.unlink-btn{flex:none;padding:3px 11px;font-size:12px}
/* Edit release / Edit show link panel (TODO.md item 154): one row per
   thing that can be linked — the release, or each act — with its own
   actions; "Find or link ›" opens .find-link-modal. */
.link-panel{margin:-6px 0 16px}
.link-panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}
.link-panel-head .album-import-label{margin-bottom:0}
.link-panel-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:8px 0;border-top:1px solid var(--line)}
.link-panel-head + .link-panel-row{border-top:none}
.link-panel-row.link-panel-foot{border-top-style:dashed;padding-bottom:0}
.link-panel-who{flex:1;min-width:160px}
.link-panel-who strong{display:block;font-size:13.5px;font-weight:600}
.link-panel-who small{display:block;font-size:12px;color:var(--ink-soft)}
.link-panel-foot .link-panel-who small{display:inline}
.link-panel-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.btn-outline.link-panel-btn{flex:none;width:auto;padding:6px 12px;font-size:12.5px;margin:0}
.find-link-modal .concert-find-results{margin-top:0;padding-top:0;border-top:none}
.find-link-paste{margin-top:16px;padding-top:12px;border-top:1px solid var(--line)}
.find-link-paste-label{font-family:'IBM Plex Mono',monospace;font-size:10.5px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--ink-soft);margin-bottom:6px}
.find-link-paste-line{display:flex;gap:8px;flex-wrap:wrap}
.find-link-paste-line .form-input{flex:1;min-width:180px}
.find-link-paste-line .btn-outline{flex:none;width:auto}
.find-link-paste .filter-row-sub{margin-top:6px}
.find-link-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:16px}
/* Replaces .barcode-scan-divider/.album-manual-divider (both retired, see
   their own comment in style.css) — one flanking-line divider for both the
   "or" inside the import panel and the "or enter manually" below it. */
.form-divider{display:flex;align-items:center;gap:10px;margin:10px 0;font-family:'IBM Plex Mono',monospace;font-size:10.5px;text-transform:uppercase;letter-spacing:1px;color:var(--ink-soft)}
.form-divider::before,.form-divider::after{content:"";flex:1;height:1px;background:var(--line)}
.format-editor{background:var(--bg-alt);border-radius:var(--r-radius-sm);padding:10px}
/* .suggest-bg/.suggest-border are already the app's own "informational
   accent" tint (see .btn-outline.suggest) — reused here rather than
   inventing a separate warning color the app doesn't otherwise have. */
.locked-note{background:var(--suggest-bg);border:1px solid var(--suggest-border);color:var(--accent);border-radius:var(--r-radius-sm);padding:10px 12px}

/* The shared delete/remove confirmation pop-up (2026-09-14, mockup approved
   by the owner) — renderDeleteModal()/state.deleteConfirm, used by ~20 kinds
   app-wide (Album, Show, Tag, Connection, User, Trash, now Profile photo
   too). Thin border/shadow/rounded corners instead of the old thick-ink
   .modal border, matching .add-choose-modal's own earlier treatment.
   Cancel/Remove deliberately stay equal-width pills side by side (not
   Cancel-narrow/action-wide like the Add flow's own action-row) — an
   irreversible action shouldn't read as more prominent than backing out of
   it. .btn-cancel is scoped to just this modal (not redesigned everywhere
   it's used — TODO.md item 118 covers that); .btn-danger only ever appears
   here, so it's safe to redesign globally. */
.modal.delete-modal{border:1px solid var(--line);border-radius:var(--r-radius);box-shadow:var(--r-shadow-2)}
.delete-modal .btn-cancel{border-radius:999px}
.btn-danger{border-radius:999px}

/* Every other modal (2026-09-15, screen audit) — same shell .add-choose-
   modal/.delete-modal already got, extended to the base .modal class
   itself rather than one more one-off modifier. Catalog item, History,
   Match detail, Alias, Band members, Suggest an edit, Festival grouping,
   Admin edit release/show, the Add flow's barcode/import steps and the
   Photo lightbox all pick this up automatically — none of them override
   border/radius/shadow of their own, so there's nothing here for them to
   conflict with. */
.modal{border:1px solid var(--line);border-radius:var(--r-radius);box-shadow:var(--r-shadow-2)}

.segmented{border-radius:999px;padding:3px;box-shadow:var(--r-shadow-1)}
.segmented button{border-radius:999px;padding:7px 16px}
.select{border-radius:999px;padding:8px 14px;box-shadow:var(--r-shadow-1)}
.year-chip{border-radius:999px;padding:4px 11px}
.btn-outline{border-radius:999px;padding:8px 18px}
.icon-btn,
.fav-icon-toggle,
.private-icon-toggle,
.catalog-link-icon{border-radius:10px}
.badge,
.chip,
.type-badge,
.festival-count-chip{border-radius:999px;padding:3px 10px}
.bar-row-track{border-radius:999px;height:8px}
.bar-row-fill{border-radius:999px}
.yearbar-bar{border-radius:4px 4px 0 0}
.timeline-year-pill{border-radius:999px}
.part-of-festival{border-radius:999px}

/* ══════════════════════════════════════════════════════════════════════════
   FASE 2 — de shell (Encore Console)
   --------------------------------------------------------------------------
   Twee kolommen: een donkere marineblauwe zijbalk (altijd donker, in beide
   thema's — bewuste keuze) en een lichte inhoudskolom met een slanke
   topbalk. Onder 640px wordt de zijbalk een off-canvas drawer (via .sidebar
   .mobile-open, dezelfde klasse die de oude hamburger-dropdown gebruikte —
   de JS wijst nu naar .sidebar) en breekt de iconenrij (#header-actions) uit
   naar de bestaande vaste onderbalk.

   De chrome-regels in blok 1 hierboven (.header-stack .header etc.) mikken
   op .header / .nav-in-de-header en zijn met deze nieuwe markup vanzelf
   no-ops geworden — steigerwerk, blijft staan tot deze laag in style.css
   opgaat.
   ══════════════════════════════════════════════════════════════════════════ */

:root{
  --chrome-1:#152b4f;
  --chrome-2:#0d1a30;
  --chrome-ink:#ffffff;
  --chrome-ink-dim:#a9bad4;
  --chrome-soft:#8ba3c7;
  --chrome-line:rgba(255,255,255,0.10);
  --chrome-active:rgba(255,255,255,0.12);
  --chrome-hover:rgba(255,255,255,0.06);
  --chrome-accent:#7dd3fc;
  --sidebar-w:236px;
}

/* ── Buitenste layout ──────────────────────────────────────────────────── */
/* Reserveer de scrollbar-goot altijd, zodat de inhoudskolom niet ~15px
   verspringt t.o.v. de vaste zijbalk wanneer een korte pagina geen
   scrollbar heeft en een lange wel. */
html{scrollbar-gutter:stable}
.app-shell{display:flex;align-items:flex-start;min-height:100vh}
body.pre-auth .app-shell{display:block}
body.pre-auth .sidebar,
body.pre-auth .content-head{display:none}
/* .main still carries its own padding/max-width (style.css) even with the
   sidebar/topbar hidden (the owner, 2026-09-15) — .auth-screen's navy chrome
   ended up as a rounded rectangle floating on the plain --bg page instead
   of filling the viewport the way the sidebar's own chrome does. Dropped
   here, pre-auth only, so the in-app .main (padded, capped at 1180/1240px)
   is untouched. */
body.pre-auth .main{padding:0;max-width:none;margin:0}

/* ── Zijbalk ───────────────────────────────────────────────────────────── */
.sidebar{
  flex:none;width:var(--sidebar-w);align-self:stretch;
  /* calc(var(--vh) * 100), not a plain 100vh (TODO.md item 75 follow-up,
     see updateViewportUnit()'s own comment, public/app.js) — at Text size
     110%/125%, a plain 100vh gets multiplied by the zoom a *second* time on
     top of already being 100% of the real screen, so the sidebar rendered
     taller than the viewport and its bottom (current-user-row, Log out,
     the version line) fell below the fold (the owner, 2026-09-11 — traced to
     the zoom level after the icon-row-wrap fix in 18064fd turned out not to
     be the actual cause). --vh already cancels that out everywhere else
     that needs a real viewport fraction (modals, the Help Center popup). */
  position:sticky;top:0;height:calc(var(--vh) * 100);overflow-y:auto;overflow-x:hidden;
  background:linear-gradient(180deg,var(--chrome-1) 0%,var(--chrome-2) 100%);
  display:flex;flex-direction:column;gap:3px;
  padding:22px 14px 18px;z-index:50;
}
.sidebar .logo{gap:11px;padding:0 8px 22px;cursor:pointer}
.sidebar .logo-mark{width:30px;height:30px;border-radius:9px;box-shadow:0 2px 8px rgba(0,0,0,0.35)}
/* Own cursor, not the plain pointer .logo (→ Dashboard) gives it — same
   zoom-in cue .image-slot.has-zoom uses for the hero photos this now
   shares its zoom behavior with. */
#logo-icon{cursor:zoom-in}
.sidebar .logo-text{
  font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:14.5px;
  letter-spacing:0.06em;color:var(--chrome-ink);white-space:nowrap;
}

/* Hoofdnavigatie — verticaal, met streepje links en teller rechts. */
.sidebar .nav{display:flex;flex-direction:column;gap:3px;margin:0;width:100%}
.sidebar .nav-btn{
  display:flex;align-items:center;gap:10px;width:100%;
  padding:10px 12px 10px 10px;border-radius:9px;
  font-size:13px;font-weight:600;text-align:left;cursor:pointer;
  color:var(--chrome-ink-dim);background:transparent;border:none;
  transition:background .15s ease,color .15s ease;
}
.sidebar .nav-btn::before{
  content:"";flex:none;width:3px;height:15px;border-radius:2px;
  background:transparent;transition:background .15s ease;
}
.sidebar .nav-btn span{border:none;padding:0;color:inherit;flex:1;min-width:0}
.sidebar .nav-btn .nav-count{
  flex:none;font-size:11px;font-weight:500;color:var(--chrome-soft);
  font-variant-numeric:tabular-nums;
}
.sidebar .nav-btn:hover{background:var(--chrome-hover);color:var(--chrome-ink)}
.sidebar .nav-btn.active{background:var(--chrome-active);color:var(--chrome-ink)}
.sidebar .nav-btn.active::before{background:var(--chrome-accent)}
.sidebar .nav-btn.active .nav-count{color:var(--chrome-ink-dim)}

.sidebar-divider{flex:none;height:1px;background:var(--chrome-line);margin:18px 8px}
/* Newest admin Update post (TODO.md item 116) — right under the divider. */
.sidebar-update{flex:none;display:grid;gap:3px;margin:-8px 4px 0;padding:9px 10px;border-radius:9px;border:none;
  background:var(--chrome-hover);color:var(--chrome-ink);text-align:left;font:inherit;cursor:pointer}
.sidebar-update.hidden{display:none}
.sidebar-update:hover{background:var(--chrome-active)}
.sidebar-update:focus-visible{outline:2px solid var(--chrome-accent);outline-offset:2px}
.sidebar-update-top{display:flex;align-items:center;gap:6px;font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--chrome-ink-dim)}
.sidebar-update-dot{width:7px;height:7px;border-radius:50%;flex:none}
.sidebar-update-title{font-size:12px;line-height:1.4;font-weight:600;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.sidebar-update-more{font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--chrome-accent)}

@keyframes pulseDot{0%,100%{opacity:1}50%{opacity:0.25}}

.sidebar-spacer{flex:1 1 auto;min-height:14px}

/* Onderin: user-menu (notifications / connections / account / admin / help /
   logout) + versie — one section, same markup and CSS on both breakpoints
   now (fase 13, 2026-09-14): the old mobile-only fixed bottom bar is gone,
   see the .header-actions override in the mobile block below. */
.sidebar .header-actions{flex:none;display:flex;flex-direction:column;gap:8px;padding:0}

/* User menu — avatar+name button (#user-menu-trigger) that opens a small
   popover (#user-menu-panel) holding all six items (fase 13 follow-up,
   "option B", 2026-09-14 — the first pass kept Notifications/Connections as
   standing icons above this; the owner asked for everything behind the avatar
   instead). #user-menu-unread-dot on the avatar carries the "something
   needs you" signal while the menu itself is closed — see
   #user-menu-notif-count for the exact count once it's open. Opens upward
   (bottom:100%) since it sits at the very bottom of the sidebar. */
.sidebar .user-menu{position:relative;width:100%}
.sidebar .user-menu-trigger{
  display:flex;align-items:center;gap:8px;width:100%;
  background:transparent;border:none;padding:5px 4px;border-radius:8px;
  cursor:pointer;text-align:left;transition:background .15s ease;
}
.sidebar .user-menu-trigger:hover{background:var(--chrome-hover)}
.sidebar .user-menu-chevron{
  flex:none;width:14px;height:14px;stroke:var(--chrome-soft);fill:none;
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;
  transition:transform .15s ease;
}
.sidebar .user-menu.open .user-menu-trigger{background:var(--chrome-hover)}
.sidebar .user-menu.open .user-menu-chevron{transform:rotate(180deg)}
/* Avatar sizing (the owner, 2026-09-11, carried over from the old
   #current-user-row) — connInitialAvatar()'s plain (unsized) output, so
   it's sized here rather than via one of its usual conn-av--sm/md/lg
   classes. The wrap (not the avatar itself) is the dot's positioning
   context — see its own comment in index.html for why. */
.sidebar .user-menu-avatar-wrap{position:relative;flex:none;line-height:0}
.sidebar .current-user-avatar .conn-av{width:22px;height:22px;font-size:10.5px}
.sidebar .user-menu-unread-dot{
  position:absolute;top:-1px;right:-1px;width:8px;height:8px;border-radius:50%;
  background:var(--danger);border:1.5px solid var(--chrome-2);
}
.sidebar .current-user-name{color:var(--chrome-ink);font-size:12.5px;font-weight:600;min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sidebar .user-menu-panel{
  display:none;position:absolute;left:0;right:0;bottom:calc(100% + 8px);
  background:#1c3560;border:1px solid var(--chrome-line);border-radius:10px;
  padding:6px;box-shadow:0 8px 24px rgba(0,0,0,0.35);z-index:10;
}
.sidebar .user-menu.open .user-menu-panel{display:block}
.sidebar .user-menu-item{
  display:flex;align-items:center;gap:9px;width:100%;
  background:transparent;border:none;padding:8px;border-radius:7px;
  font-size:12.5px;font-weight:500;color:#e7edf7;cursor:pointer;text-align:left;
}
.sidebar .user-menu-item svg{
  flex:none;width:16px;height:16px;stroke:currentColor;fill:none;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;
}
.sidebar .user-menu-item:hover{background:rgba(255,255,255,0.10)}
.sidebar .user-menu-item.active{background:rgba(255,255,255,0.10)}
.sidebar .user-menu-item.is-danger{color:#f0999a}
/* Unread count (Notifications) — inline, not the old absolute corner pill,
   since a full-width menu row has room to just show the real number. */
.sidebar .user-menu-item-count{
  margin-left:auto;flex:none;background:var(--danger);color:#fff;
  font-family:'IBM Plex Mono',monospace;font-size:10px;font-weight:600;line-height:1;
  border-radius:999px;padding:2px 6px;min-width:14px;text-align:center;
}
.sidebar .user-menu-divider{height:1px;background:var(--chrome-line);margin:4px 4px}

.sidebar .desktop-version-bar:not(.hidden){
  display:block;position:static;left:auto;right:auto;bottom:auto;width:auto;z-index:auto;
  background:none;border:none;text-align:left;padding:6px 8px 0;
  font-family:'IBM Plex Mono',monospace;font-size:10px;color:#7d95b8;cursor:pointer;
}
/* Admin-only click (the owner, 2026-09-14) — non-admins still see the version
   text, just can't open release notes from it (updateHeader() disables the
   button and drops its data-action for them). opacity:1 cancels the
   default dimming a disabled <button> otherwise picks up — this isn't a
   broken control, it's simply not a link for this viewer. */
.sidebar .desktop-version-bar:disabled{cursor:default;opacity:1}
.sidebar .desktop-version-bar:not(.hidden):not(:disabled):hover{color:var(--chrome-ink-dim);text-decoration:underline}

/* ── Inhoudskolom ─────────────────────────────────────────────────────── */
/* display:block, GEEN flex — .main krijgt van style.css `margin:0 auto`, en
   op een flex-item betekent `margin:auto` "krimp naar inhoud en centreer",
   waardoor elke pagina een andere breedte kreeg en horizontaal versprong
   (Grid→List, Songs). Als gewoon blok centreert .main netjes op volle
   breedte tot z'n max-width. `flex:1` blijft — dat is .content-col's eigen
   groei binnen .app-shell, niet z'n interne layout. */
.content-col{flex:1 1 auto;min-width:0;display:block;background:var(--bg)}
.content-head{position:sticky;top:0;z-index:40}
.content-head .header-stack{position:static;z-index:auto}

.content-topbar{
  display:flex;align-items:center;gap:12px;
  /* min-height, not just the padding — Account/Admin hide the search box +
     Add button (real settings pages, not content, see updateHeader()'s
     showHeaderTools), which were the tallest things in this row. With no
     floor the row shrank to fit just the breadcrumb label's line-height —
     64px elsewhere, 41px there (measured, the owner 2026-09-11: "minder hoog",
     not the width — the box itself is the same width already). Pinned to
     the height the search input/icon buttons already give every other
     page, so it reads as one consistent topbar everywhere. */
  min-height:64px;
  padding:13px 28px;background:var(--card);
  border-bottom:1px solid var(--line);
}
.topbar-page-label{
  flex:none;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;
  color:var(--ink-soft);
}
/* Detailpagina: broodkruimel "SONGS / <naam>". De naam niet in caps/wijd
   gespatieerd, en afkappen zodat een lange titel de topbalk niet opblaast. */
.topbar-page-label.is-crumb{
  display:flex;align-items:baseline;gap:7px;min-width:0;max-width:min(46vw,520px);
}
/* De ouder is een echte link naar de lijstpagina (broodkruimel-conventie);
   de "← BACK"-knop in de hero doet los daarvan de browser-terug. */
.topbar-page-label .crumb-parent{
  flex:none;background:none;border:0;padding:0;margin:0;cursor:pointer;
  font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;
}
.topbar-page-label .crumb-parent:hover{color:var(--ink)}
.topbar-page-label .crumb-parent:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
.topbar-page-label .crumb-sep{flex:none;opacity:0.5}
.topbar-page-label .crumb-name{
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  text-transform:none;letter-spacing:0.01em;font-weight:600;color:var(--ink);
}
.content-topbar .header-right{
  flex:1 1 auto;min-width:0;display:flex;align-items:center;justify-content:flex-end;gap:10px;
}
.content-topbar .search-input{width:300px;max-width:100%;border-radius:9px}
.content-topbar .icon-btn{width:34px;height:34px;border-radius:9px}
.content-topbar .btn-add{border-radius:9px;padding:9px 17px;box-shadow:none}

/* Viewing-as-balk — globaal, blijft amber, rand-tot-rand onder de topbalk. */
.content-head .viewing-bar-inner{padding:9px 28px}

.main{max-width:1180px;margin:0 auto;padding-top:30px;padding-left:28px;padding-right:28px}
@media (min-width:641px){ .main{padding-bottom:44px} }
/* Dashboard only (the owner, 2026-09-14): its side margins now match .main's
   own 30px header-to-hero gap above, instead of the slightly narrower 28px
   every other page still uses. :has(> .dash) scopes this to the Dashboard
   specifically — .dash is renderDashboard()'s own outer wrapper — rather
   than widening .main's side padding everywhere. Desktop only, wrapped the
   same as .main's padding-bottom rule just above: mobile's own tighter
   16px side padding (mobile block below) is deliberate and untouched. */
@media (min-width:641px){ .main:has(> .dash){padding-left:30px;padding-right:30px} }

/* ── Mobiel (<640px) — zijbalk wordt een off-canvas drawer ─────────────── */
/* Zelfde samengestelde conditie als style.css' hamburger-regel
   (.icon-btn.mobile-nav-toggle, ~regel 1920) — niet alleen max-width:640px
   (item 10, landscape-follow-up, 2026-09-16, the owner). Die twee liepen tot
   nu toe uit de pas: op een telefoon in liggende stand (breed genoeg om
   >640px te zijn, maar kort) toonde style.css al wél de hamburger (zijn
   eigen regel kijkt ook naar hoogte/hover/pointer), terwijl de zijbalk hier
   nog in de permanente desktop-stand bleef staan — hamburger én volledige
   zijbalk tegelijk in beeld, en de hamburger deed niets zichtbaars (hij
   schakelt .sidebar.mobile-open, dat alleen onder déze media query iets
   voorstelt). Bedoeld gedrag stond al gedocumenteerd bij style.css' eigen
   regel: liggende stand krijgt exact dezelfde behandeling als staande
   stand, niet een eigen ontwerp — deze zijbalk-breakpoint miste 'm nog. */
@media (max-width:640px), (hover:none) and (max-height:500px), (pointer:coarse) and (max-height:500px){
  .app-shell{display:block}
  .sidebar{
    position:fixed;top:0;bottom:0;left:0;height:auto;
    width:min(82vw,300px);align-self:auto;
    left:-320px;transition:left .25s ease;z-index:200;
  }
  .sidebar.mobile-open{left:0;box-shadow:0 0 0 100vmax rgba(0,0,0,0.45)}
  /* .nav zit nu altijd in de drawer — de zichtbaarheid regelt .sidebar's
     positie, niet meer .nav's eigen display. Overrulet style.css' mobiele
     .nav-regels: display:none, order:10 (schoof nav achter de spacer) en
     de witte-kaart-actiefstijl. */
  .sidebar .nav{display:flex;order:0;margin-top:0}
  .sidebar .nav-btn.active{border:none;background:var(--chrome-active)}
  /* Fase 13 (2026-09-14) — header-actions used to break out of the drawer
     into its own position:fixed bottom bar here (style.css's now-superseded
     mobile block still sets this; position/background/border-top are the
     only properties of that old bar which still take effect once its media
     query is live — left/right/bottom/z-index/transform only matter
     together with position:fixed, so they're inert once position is reset
     and don't need overriding here too). Back to plain flow, so it just
     sits at the bottom of the drawer's own column, same as desktop. */
  .sidebar .header-actions{position:static;background:none;border-top:none}

  .content-col{display:block}
  .content-topbar{padding:11px 16px;gap:10px;flex-wrap:wrap}
  .content-topbar .search-input{width:100%}
  .content-topbar .header-right{gap:8px}
  /* Search + Add always on their own second row on a phone (2026-09-26,
     the owner) — before, a short page name ("Shows", "Songs") left room
     for them on the first row and a long one didn't, so the header looked
     different page to page. The name (or breadcrumb) keeps row one and is
     cut off with "…" when it's too long. */
  .content-topbar .header-right:not(.empty){flex:1 1 100%}
  .content-topbar .topbar-page-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .topbar-page-label.is-crumb{max-width:none}
  .content-head .viewing-bar-inner{padding:9px 16px}
  /* padding-bottom, not just left/right — style.css's own mobile block
     still reserves 84px here for the fixed bottom bar that's gone (fase
     13), which without an override would leave a big dead gap under every
     page's last item. */
  .main{padding-left:16px;padding-right:16px;padding-bottom:24px}
}

/* ══════════════════════════════════════════════════════════════════════════
   FASE 3 — pagina's
   ══════════════════════════════════════════════════════════════════════════ */

/* Kleine subregel onder een paginatitel (naast de standaard resultaatteller). */
.page-sub{font-size:12.5px;color:var(--ink-soft);margin-top:3px}

/* In-pagina anker-doelen moeten onder de sticky topbalk uitkomen, niet
   erachter (scrollIntoView / jumpToSongLetter). */
[id^="song-letter-"]{scroll-margin-top:72px}

/* ── Songs (handoff scherm 5) ─────────────────────────────────────────── */
.songs-grid{display:grid;grid-template-columns:44px minmax(0,1fr);gap:18px;align-items:start}

.song-rail{
  position:sticky;top:70px;flex:none;
  display:flex;flex-direction:column;align-items:stretch;gap:2px;
  background:var(--card);border:1px solid var(--line);
  border-radius:12px;box-shadow:var(--r-shadow-1);padding:12px 0;
}
.song-rail-letter{
  background:none;border:none;cursor:pointer;
  font-family:'IBM Plex Mono',monospace;font-size:10.5px;font-weight:600;
  color:var(--accent);padding:2px 0;text-align:center;line-height:1.5;
  transition:color .12s ease;
}
.song-rail-letter:hover:not(:disabled){color:var(--accent-dark)}
.song-rail-letter:disabled{cursor:default;font-weight:400;color:color-mix(in srgb,var(--ink) 22%,var(--card))}

.song-list-card.panel{padding:4px 24px 16px}

/* Volle-breedte letter-divider boven elk blok. Getint + accentkleur +
   mono-onderschrift-formaat zodat het duidelijk géén song is maar een
   scheiding. Draagt het jump-anker (#song-letter-X). */
.srow-head{
  display:flex;align-items:center;gap:8px;
  font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:13px;
  letter-spacing:0.04em;color:var(--accent);
  background:var(--suggest-bg);
  border-radius:7px;padding:6px 12px;
  margin:12px -12px 4px;
}
.srow-head:first-child{margin-top:2px}
.srow-head::after{
  content:"";flex:1;height:1px;
  background:var(--suggest-border);
}

/* Eén regel per song:
   titel · artiest ............ N versions · N releases · N shows */
.srow{
  display:flex;align-items:baseline;gap:6px;
  padding:10px 12px;margin:0 -12px;
  border-radius:9px;cursor:pointer;transition:background .12s ease;
}
.srow + .srow{border-top:1px solid var(--line)}
.srow:hover{background:var(--bg-alt)}
.srow-title{
  font-size:14.5px;font-weight:600;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:0 1 auto;min-width:0;
}
.srow-artist{font-size:13px;color:var(--ink-soft);white-space:nowrap;flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}
.srow-meta{
  margin-left:auto;flex:none;
  font-size:10.5px;color:var(--ink-soft);white-space:nowrap;
}
/* Tag chips + favourite star on a row (redesign fase 3). Chips sit just after
   the artist; the star trails the meta. margin-left:auto stays on .srow-meta,
   so on mobile (meta hidden) the star needs its own auto-margin — see below. */
.srow-chips{display:flex;gap:5px;flex:0 1 auto;min-width:0;overflow:hidden}
.srow-chips .chip{
  font-family:'IBM Plex Mono',monospace;font-size:9px;letter-spacing:0.03em;
  text-transform:uppercase;padding:2px 6px;border-radius:999px;white-space:nowrap;
}
.srow .favorite-star{flex:none;align-self:center;font-size:14px;padding:0 2px}
.srow-foot{
  border-top:1px solid var(--line);margin-top:4px;padding:12px 2px 2px;
  font-size:13px;color:var(--ink-soft);
}
@media (max-width:640px){
  /* minmax(0,1fr), not a bare 1fr (the owner, 2026-09-14 — the page could be
     scrolled sideways on mobile) — a grid track's default min-width is
     auto, so without the clamp this one column stretches to fit its
     widest un-wrapped content (.srow-title/.srow-artist's white-space:
     nowrap, ellipsis or not) instead of the viewport, dragging the whole
     page wider than the screen. The desktop rule above already gets this
     right (44px minmax(0,1fr)) — this mobile override just dropped it
     when collapsing to one column. */
  .songs-grid{grid-template-columns:minmax(0,1fr)}
  .song-rail{display:none}
  /* Titel + artiest houden de regel; de N-versions/releases/shows-meta
     past er niet bij op een telefoon — die staat voluit op de songdetail.
     De tag-chips zakken naar een tweede regel (mockup). */
  .srow-meta{display:none}
  .srow{flex-wrap:wrap;row-gap:6px}
  .srow .favorite-star{order:2;margin-left:auto}
  .srow-chips{order:3;flex-basis:100%;overflow:visible;flex-wrap:wrap}
}

/* ── Releases — Grid-weergave (fase 3, handoff scherm 2) ──────────────────
   Alleen op .grid-albums.cover-grid (de Releases-grid). Wantlist, Artists
   en de artiestdetail-grids houden de bestaande look tot ze aan de beurt
   zijn. */
.cover-grid{gap:22px}
.cover-grid .album-cover{
  position:relative;border-radius:8px;overflow:hidden;
  box-shadow:0 10px 22px rgba(16,24,40,0.16);
  transition:transform .18s ease, box-shadow .18s ease;
}
.cover-grid .album-card:hover .album-cover{
  transform:translateY(-8px) scale(1.02);
  box-shadow:0 22px 40px rgba(16,24,40,0.30);
}
/* radius + schaduw zitten nu op de wrapper, niet op de image-slot (die
   kreeg dat van fase 1); en de fase-1-lift op de image-slot uitzetten. */
.cover-grid .album-cover .image-slot{border-radius:0;box-shadow:none}
.cover-grid .album-card:hover .album-cover .image-slot{transform:none}
/* leesbaarheidsverloop van onderaf */
.cover-grid .album-cover::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,rgba(9,14,26,0) 46%,rgba(9,14,26,0.78) 100%);
}
.cover-grid .cover-meta{
  position:absolute;left:9px;right:9px;bottom:8px;z-index:2;
  font-size:9px;letter-spacing:0.1em;text-transform:uppercase;
  color:rgba(255,255,255,0.92);text-shadow:0 1px 3px rgba(0,0,0,0.7);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* ster: kaal, geen wit cirkeltje; goud als favoriet, wit-transparant als niet */
.cover-grid .album-card .favorite-star{
  position:absolute;top:7px;right:9px;z-index:3;
  background:none;border-radius:0;width:auto;height:auto;padding:0;
  font-size:17px;line-height:1;color:rgba(255,255,255,0.9);
  text-shadow:0 1px 4px rgba(0,0,0,0.55);
}
.cover-grid .album-card .favorite-star.active{color:#e0a850}
.cover-grid .album-card:hover .favorite-star{transform:translateY(-8px)}
/* titel + artiest onder de hoes — rustiger dan de rest van de grids */
.cover-grid .album-card .album-title{
  font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:14px;
  letter-spacing:0;margin-top:10px;
}
.cover-grid .album-card .album-artist{font-size:12.5px;color:var(--ink-soft);margin-top:2px}
@media (max-width:640px){
  /* Handoff: hoezengrids 6 → 4 → 2 kolommen. De basis-.grid-albums valt op
     een telefoon terug op 1 grote hoes per rij; de Releases-grid pakt 2. */
  .cover-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
  .cover-grid .cover-meta{font-size:8px;left:6px;right:6px;bottom:6px}
  .cover-grid .album-card .favorite-star{font-size:15px;top:5px;right:6px}
}

/* ── Song-detail (fase 3 → herzien in fase 4-stijl) ───────────────────── */
/* De fase-3 "ingekaderde kaart / optie A"-kop is teruggedraaid: Song-detail
   deelt nu één-op-één de full-bleed hero van Release-detail — .rel-hero /
   .rel-hero-bg / .rel-hero-inner / .rel-hero-cols / .rel-hero-body /
   .rel-hero-back / .rel-hero-meta én .rel-body / .rel-side, allemaal uit
   het FASE 4-blok hieronder. renderView() zet .view-bleed zodra de markup
   een ":scope > .rel-detail, :scope > .song-detail" is. Alleen de
   song-eigen onderdelen staan hier: de hoezen-fan met auto-doordraai, en de
   mono-eyebrow blokkoppen. De eerste release-hoes vult .rel-hero-bg. */

/* Blokkoppen — Versions / Heard live / Where it turns up — als mono-eyebrow,
   gelijk aan TRACKLIST / RELEASE / MY NOTE op Release-detail. */
.song-detail .panel-head h2{
  font-family:'IBM Plex Mono',monospace;font-weight:400;font-size:12.5px;
  letter-spacing:0.16em;text-transform:uppercase;color:var(--ink-soft);margin:0;
}
.song-panel-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px}
.song-panel-note{font-size:10px;letter-spacing:0.12em;color:var(--ink-soft)}

/* Hoezen-fan in de hero. align-self:flex-end lijnt 'm uit op de onderkant
   van de heroteksten (.rel-hero-cols is align-items:stretch). Tot 8 hoezen;
   bij 2+ krijgt de container .is-cycling en draait syncSongFan() ze door.
   Klik op een hoes opent de betreffende release (juiste formaatgroep via
   data-format-key, net als de Versions-lijst — item 104).
   Alle offsets zijn % van de hoesbreedte, zodat mobiel (kleinere .song-fan-
   cover) automatisch meeschaalt zonder aparte keyframes. */
.song-hero-fan{
  position:relative;width:196px;height:156px;flex:none;align-self:flex-end;
}
.song-hero-fan .song-fan-cover{
  position:absolute;left:0;bottom:0;width:112px;height:112px;cursor:pointer;
  transition:transform .6s cubic-bezier(.22,1,.36,1),opacity .45s ease,box-shadow .45s ease;
}
.song-hero-fan .song-fan-cover .image-slot{
  width:100%;height:100%;border-radius:8px;box-shadow:0 18px 40px rgba(0,0,0,0.5);
}
.song-hero-fan .song-fan-cover .image-slot img{width:100%;height:100%;object-fit:cover}

/* Rust-/pre-JS-/reduced-motion-toestand: de eerste drie gewaaierd, de rest
   onzichtbaar er direct achter. syncSongFan() zet .js-fan en stuurt daarna
   met .ff-front / .ff-mid / .ff-back / .ff-park; de vertrekkende hoes krijgt
   .ff-sending (de boog-keyframe). */
.song-hero-fan:not(.js-fan) .song-fan-cover{transform:translate(0,4%) rotate(-8deg);opacity:0;z-index:0}
.song-hero-fan:not(.js-fan) .song-fan-cover:nth-child(1){transform:translate(64%,0) rotate(0);opacity:1;z-index:3;box-shadow:0 22px 46px rgba(0,0,0,0.55)}
.song-hero-fan:not(.js-fan) .song-fan-cover:nth-child(2){transform:translate(32%,6%) rotate(4deg);opacity:0.94;z-index:2}
.song-hero-fan:not(.js-fan) .song-fan-cover:nth-child(3){transform:translate(0,3%) rotate(-8deg);opacity:0.84;z-index:1}
.song-hero-fan.js-fan .song-fan-cover{transform:translate(0,4%) rotate(-8deg);opacity:0;z-index:0}
.song-hero-fan .song-fan-cover.ff-front{transform:translate(64%,0) rotate(0);opacity:1;z-index:3;box-shadow:0 22px 46px rgba(0,0,0,0.55)}
.song-hero-fan .song-fan-cover.ff-mid{transform:translate(32%,6%) rotate(4deg);opacity:0.94;z-index:2}
.song-hero-fan .song-fan-cover.ff-back{transform:translate(0,3%) rotate(-8deg);opacity:0.84;z-index:1}
.song-hero-fan .song-fan-cover.ff-park{transform:translate(0,3%) rotate(-8deg);opacity:0;z-index:0}

/* De voorste hoes wordt opgetild, óver de fan getild en achteraan de stapel
   neergelegd (keuze van de eigenaar A / D, 2026-09-09). De z-index-sprong halverwege
   is wat "achter de stapel gestopt" verkoopt — daarom een keyframe en geen
   transitie. songFanArc eindigt onzichtbaar geparkeerd (3+ hoezen);
   songFanArcPair eindigt zichtbaar als de nieuwe achterste hoes (precies 2).
   Het 100%-frame is identiek aan .ff-park resp. .ff-back, dus geen sprong als
   syncSongFan() na animationend de statische class terugzet. */
.song-hero-fan .song-fan-cover.ff-sending{
  animation:songFanArc 0.95s cubic-bezier(0.5,0,0.2,1) forwards;
  will-change:transform;
}
.song-hero-fan.is-pair .song-fan-cover.ff-sending{
  animation-name:songFanArcPair;animation-duration:1s;
}
@keyframes songFanArc{
  0%   {transform:translate(64%,0) rotate(0) scale(1);z-index:9;opacity:1}
  34%  {transform:translate(86%,-46%) rotate(-5deg) scale(1.05);z-index:9;opacity:1}
  56%  {transform:translate(16%,-34%) rotate(-9deg) scale(0.99);z-index:0;opacity:0.92}
  100% {transform:translate(0,3%) rotate(-8deg) scale(1);z-index:0;opacity:0}
}
@keyframes songFanArcPair{
  0%   {transform:translate(64%,0) rotate(0) scale(1);z-index:9;opacity:1}
  36%  {transform:translate(84%,-44%) rotate(-6deg) scale(1.04);z-index:9;opacity:1}
  58%  {transform:translate(14%,-30%) rotate(-9deg) scale(1);z-index:0;opacity:0.95}
  100% {transform:translate(0,3%) rotate(-8deg) scale(1);z-index:0;opacity:0.84}
}
@media (prefers-reduced-motion:reduce){
  .song-hero-fan .song-fan-cover{transition:none}
  .song-hero-fan .song-fan-cover.ff-sending{animation:none}
}

/* Eén hoes → exact de Release-detail-hoes (.rel-hero-cover uit het FASE 4-blok:
   216px, rand-tot-rand tegen de kolomstart, object-fit:cover, radius 10, grote
   schaduw). Klikbaar naar de release, met dezelfde hover-lift als de fan. */
.song-hero-cover{cursor:pointer;align-self:flex-end}
.song-hero-cover .image-slot{transition:transform .18s ease,box-shadow .18s ease}
.song-hero-cover:hover .image-slot{transform:translateY(-4px);box-shadow:0 30px 64px rgba(0,0,0,0.6)}

/* Versierij: naam + aantal links, hoezen rechts uitgelijnd. Overschrijft de
   flex/vaste-kolom-layout uit style.css (.song-detail wint met 2 klassen). */
.song-versions.panel{padding:20px 24px 12px}
.song-detail .song-version-row{
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:20px;align-items:center;
  padding:13px 12px;margin:0 -12px;
  border-top:1px solid var(--r-hairline);border-bottom:none;
  border-radius:var(--r-radius-sm);transition:background .12s ease;
}
.song-detail .song-version-row:hover{background:var(--bg-alt)}
.song-detail .song-version-name{font-weight:400;font-size:inherit}
.song-version-label{font-size:14.5px;font-weight:600;letter-spacing:-0.01em}
.song-version-count{
  display:block;margin-top:4px;font-size:10px;letter-spacing:0.1em;color:var(--ink-soft);
}
.song-detail .song-version-covers{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end;align-items:center;max-width:420px}
.song-detail .song-version-cover{width:38px;height:38px;flex:none;cursor:pointer}
.song-detail .song-version-cover .image-slot{
  border-radius:5px;box-shadow:var(--r-shadow-cover);
  transition:transform .16s ease,box-shadow .16s ease;
}
.song-detail .song-version-cover:hover{transform:none}
.song-detail .song-version-cover:hover .image-slot{transform:translateY(-4px);box-shadow:var(--r-shadow-cover-hi)}
/* Hover over een type in "Where it turns up": de hoezen van dát type
   blijven zoals ze zijn, de rest dimt (grijs + doorzichtig). */
.song-detail .song-version-cover .image-slot{transition:transform .16s ease,box-shadow .16s ease,opacity .14s ease,filter .14s ease}
.song-detail .song-version-cover.is-dim .image-slot{opacity:0.24;filter:grayscale(0.6)}
.song-detail .song-version-cover.is-dim:hover .image-slot{opacity:1;filter:none}

/* Live-gehoord-tijdlijn. */
.song-live-row{
  display:flex;gap:14px;cursor:pointer;padding:10px;margin:0 -10px;
  border-radius:var(--r-radius-sm);transition:background .12s ease;
}
.song-live-row:hover{background:var(--bg-alt)}
.song-live-row + .song-live-row{border-top:1px solid var(--r-hairline)}
.song-live-date{flex:none;text-align:center;border-right:1px solid var(--r-hairline);padding-right:14px}
.song-live-year{
  display:block;font-family:'Space Grotesk',sans-serif;font-weight:800;
  font-size:20px;line-height:1;color:var(--accent);
}
.song-live-day{display:block;margin-top:4px;font-size:9.5px;letter-spacing:0.1em;color:var(--ink-soft)}
.song-live-title{font-size:14px;font-weight:600}
.song-live-place{font-size:12.5px;color:var(--ink-soft);margin-top:3px}

/* Spreiding over releasetypes — hergebruikt .bar-row-track/.bar-row-fill.
   Die zijn <span>'s hier (inline → hoogte/breedte negeren), dus block.
   Hover over een rij → de hoezen van dat type links springen eruit (de rest
   dimt); de andere rijen dimmen mee. */
.song-spread-sub{font-size:12.5px;color:var(--ink-soft);margin:-8px 0 14px}
.song-spread{display:flex;flex-direction:column;gap:6px}
.song-spread-row{
  display:grid;grid-template-columns:86px minmax(0,1fr) 26px;gap:10px;align-items:center;
  padding:5px 6px;margin:0 -6px;border-radius:7px;
  transition:background .12s ease,opacity .12s ease;
}
.song-spread-row:hover{background:var(--bg-alt)}
.song-spread-row:hover .song-spread-label{color:var(--ink);font-weight:600}
.song-spread-row.is-off{opacity:0.4}
.song-spread-label{font-size:12.5px;color:var(--ink-soft);transition:color .12s ease}
.song-spread-n{font-size:11px;color:var(--ink-soft);text-align:right}
.song-spread .bar-row-track{display:block;height:8px;background:var(--bg-alt);overflow:hidden}
.song-spread .bar-row-fill{display:block;height:100%;background:var(--accent);min-width:4px}

/* .rel-body klapt zelf naar één kolom bij 1100px, en .rel-hero-cols breekt
   bij 900px (beide FASE 4-blok). De fan-offsets zijn % van de hoesbreedte,
   dus hier alleen de hoezen kleiner maken — de boog schaalt mee. */
@media (max-width:900px){
  .song-hero-fan{width:176px;height:140px}
  .song-hero-fan .song-fan-cover{width:100px;height:100px}
}
@media (max-width:640px){
  /* Stacks above the title too (item 23, option C, 2026-09-16) — same
     .rel-hero-cols flex-direction:column as Release/Show; both the fan
     (2+ covers) and the single-cover case need their own align-self so
     they keep a compact top-of-stack size instead of stretching full-width
     (the fan) or staying bottom-aligned (.song-hero-cover's own
     align-self:flex-end, meant for the row-direction desktop layout). */
  .song-hero-fan{align-self:flex-start;width:150px;height:120px}
  .song-hero-cover{align-self:flex-start}
  .song-hero-fan .song-fan-cover{width:88px;height:88px}
  /* De hint-noot bij "Versions" past niet naast de kop op een telefoon. */
  .song-panel-note{display:none}
  /* Hoezen onder de naam i.p.v. ernaast op een telefoon. */
  .song-detail .song-version-row{grid-template-columns:1fr;gap:10px}
  .song-detail .song-version-covers{justify-content:flex-start;max-width:none}
}

/* ── Release-detail (fase 4) ──────────────────────────────────────────── */
/* Full-bleed hero: op een detailpagina laat #view (renderView() zet er
   .view-bleed op zodra de gerenderde markup een .rel-detail is) zijn
   max-width én padding los, zodat de hero de zijbalk en de topbalk raakt —
   ook op brede schermen, waar .main anders met auto-marges gecentreerd
   staat. .rel-hero-inner en .rel-body brengen de leesbreedte (1180px)
   terug voor de inhoud; de hero-achtergrond blijft rand-tot-rand.
   Let op: overflow:hidden zit op .rel-hero-bg, NIET op .rel-hero — het
   tag-popover moet buiten de hero kunnen hangen. */
#view.view-bleed{max-width:none;padding:0}
/* z-index:1, not just position:relative (the owner, 2026-09-14 — bug: the Edit
   icon's dropdown (.artist-edit-menu, absolutely positioned inside here)
   rendered *behind* the artist-detail My Note panel, but in front of
   Songs you heard live). Root cause: .rel-note-panel is itself
   position:relative (so its own pencil-icon button can sit
   position:absolute inside it) — a plain z-index:auto positioned sibling
   like that paints in DOM order among every OTHER positioned z-index:auto
   element sharing this stacking context, .rel-hero included, regardless
   of isolation:isolate (isolation only protects what's *inside* .rel-hero,
   not .rel-hero's own slot in its parent's stacking order). Since
   .rel-note-panel sits later in the DOM than .rel-hero, z-index:auto vs
   z-index:auto let it win. An explicit z-index here always outranks any
   z-index:auto/0 sibling regardless of DOM order — the actual, general
   fix (not just for .rel-note-panel; the same class of conflict with any
   other position:relative content further down the page is closed too),
   not a one-off bump on .rel-note-panel itself. */
.rel-hero{position:relative;isolation:isolate;z-index:1}
.rel-hero-bg{
  position:absolute;inset:0;z-index:-1;overflow:hidden;
  background:var(--accent) center/cover no-repeat;
}
.rel-hero-bg::before{
  content:"";position:absolute;inset:-40px;
  background:inherit;filter:blur(38px) saturate(1.15);transform:scale(1.08);
}
.rel-hero-bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(9,14,26,0.45) 0%,rgba(9,14,26,0.9) 100%);
}
/* padding-top 34px→44px (2026-09-16, mobile walkthrough) — .rel-hero-back
   floats at top:15px over the hero background, independent of the content
   below it; that content has no min-height (removed 2026-09-13, see the
   .artist-hero comment further down — a min-height left dead space above a
   short column instead). Without one, a hero with little content (no tags,
   a single-release cover, a short title) is exactly as tall as its content
   plus this padding — 34px left barely more room than the back-link's own
   line height, so the cover/title ended up visually touching it. A few
   more px of fixed top padding costs nothing when there IS more content
   (tags etc. push the same amount further down either way), but reliably
   clears the back-link when there isn't. */
.rel-hero-inner{max-width:1180px;margin:0 auto;padding:44px 28px 30px}
/* align-items:stretch + justify-content:flex-end op .rel-hero-body: alle
   drie de kolommen zijn even hoog (de hoogste bepaalt de rij), en tekst +
   icoonrij staan onderaan uitgelijnd met de onderkant van de hoes — ook als
   titel/featuring de tekstkolom hoger maken dan de standaard-216px. */
.rel-hero-cols{display:flex;align-items:stretch;gap:30px}
.rel-hero-cover{width:216px;min-height:216px;flex:none;position:relative}
/* De hoes vult z'n vak exact (absoluut) — geen 1px-rand + lichte
   .image-slot-vulling die op de donkere hero als een witte lijst leest,
   en niet-vierkante hoezen worden vierkant bijgesneden (object-fit:cover)
   i.p.v. een lichte letterbox rond het beeld. */
.rel-hero-cover .image-slot{
  position:absolute;inset:0;width:auto;height:auto;
  border:none;background:transparent;border-radius:10px;
  box-shadow:0 24px 55px rgba(0,0,0,0.55);
}
.rel-hero-cover .image-slot img{width:100%;height:100%;object-fit:cover;display:block}
.rel-hero-cover .image-slot:not(.has-image){
  background:rgba(255,255,255,0.06);border:1px dashed rgba(255,255,255,0.28);
}
.rel-hero-cover .image-slot:not(.has-image) .slot-label{color:rgba(255,255,255,0.6)}
.rel-hero-body{
  min-width:0;flex:1;color:#fff;
  display:flex;flex-direction:column;justify-content:flex-end;
}
/* Terugknop: helemaal linksboven in de full-bleed hero, absoluut zodat de
   hoes/tekstrij eronder een regel hoger begint. */
.rel-hero-back{
  position:absolute;top:15px;left:22px;z-index:1;
  font-family:'IBM Plex Mono',monospace;font-size:10px;
  letter-spacing:0.16em;color:rgba(255,255,255,0.7);text-decoration:none;
}
.rel-hero-back:hover{color:#fff}
.rel-hero-badges{display:flex;flex-wrap:wrap;gap:7px;align-items:center;margin:0 0 12px}
/* Alle badge-typen (tag/systeem/gebruikerstag/showtype) op één pil-vorm —
   .tag heeft namelijk geen basis-regel buiten `.badge-row .tag`, dus hier
   viel-ie terug op kale tekst met een tint. .type-badge kreeg deze sizing
   eerst niet mee (alleen .tag/.badge/.chip-tag) — viel terug op zijn eigen,
   net iets andere padding/line-height uit style.css, waardoor de Days-pil
   hoger oogde dan de showtype-pil ernaast (the owner, 2026-09-13). */
.rel-hero-badges .tag,
.rel-hero-badges .badge,
.rel-hero-badges .chip-tag,
.rel-hero-badges .type-badge{
  font-family:'IBM Plex Mono',monospace;font-size:9.5px;letter-spacing:0.06em;
  text-transform:uppercase;line-height:1.4;border-radius:999px;padding:3px 8px;
  display:inline-block;white-space:nowrap;margin:0;box-sizing:border-box;
}
/* .type-badge.upcoming leunt op een 1px rand voor zijn omlijnde look (style.css)
   — de gedeelde regel hierboven zet geen border:none, dus die rand blijft
   intact; alleen de padding is nu gelijk zodat de pil-hoogte niet verspringt.
   Format (.badge) en release-type (.tag) op dezelfde solide --bdg-system als
   de showtype-badge — was een doorschijnend wit vlak, wat de format-/type-
   badges op Release-detail een andere kleur gaf dan de showtype-badge op
   Show-detail (the owner, 2026-09-13). .chip-tag houdt zijn eigen --bdg-tag. */
.rel-hero-badges .tag,
.rel-hero-badges .badge{background:var(--bdg-system);color:#fff}
.rel-hero-badges .chip-tag{background:var(--bdg-tag);color:#fff}
.rel-hero-body .detail-title{color:#fff;font-size:clamp(32px,3.8vw,50px)}
/* Status marks after the title (★ / lock / ticket, TODO.md item 158) centre
   on the title's capital letters (the owner: on the lowercase centre they
   looked sunk). The title block gets the title's own size and face, so
   vertical-align:middle lands on the title's x-height centre at every size;
   top:-0.1em then lifts them to the cap-height centre — (cap − x-height)/2
   is 0.094–0.110em for Space Grotesk across the title's 32–50px range. */
.rel-hero-body .detail-title-block{font-size:clamp(32px,3.8vw,50px);font-family:'Space Grotesk',sans-serif;line-height:1.04}
.rel-hero-body .detail-title-adorn{vertical-align:middle;position:relative;top:-0.1em}
/* Ster + tag-icoon op donker. De actieve ster houdt zijn goud (style.css:
   .favorite-star.active) — alleen de inactieve ☆ (default --ink-soft) is te
   dof op het donkere verloop. Het tag-beheer-icoon is een .icon-btn met
   kaartwitte vulling + --ink stroke uit style.css; op de hero doortrekken
   naar dezelfde ronde witte-outline-stijl als de actiekolom. */
.rel-hero-body .favorite-star:not(.active){color:rgba(255,255,255,0.72)}
.rel-hero-body .detail-title-adorn .icon-btn{
  background:transparent;border-color:rgba(255,255,255,0.35);
}
.rel-hero-body .detail-title-adorn .icon-btn svg{stroke:#fff}
.rel-hero-body .detail-title-adorn .icon-btn:hover{
  background:rgba(255,255,255,0.16);border-color:rgba(255,255,255,0.35);
}
.rel-hero-body .artist-link{color:#fff;margin-top:8px;font-size:17px;font-weight:600}
.rel-hero-feat{font-size:13.5px;margin-top:6px;color:rgba(255,255,255,0.78)}
.rel-hero-feat span{color:rgba(255,255,255,0.6)}
.rel-hero-feat .name-link{color:#fff;font-weight:600}
.rel-hero-meta{
  display:flex;flex-wrap:wrap;gap:8px 24px;margin-top:18px;
  font-size:11px;letter-spacing:0.04em;text-transform:uppercase;
  color:rgba(255,255,255,0.82);
}
/* Actiekolom: sinds TODO.md item 158 (2C, 2026-09-25) een knop met tekst
   (Edit / Suggest an edit) plus een "⋯"-menu (.hero-toolbar, zie verderop);
   de ronde witte stijl hieronder geldt voor de "⋯"-knop en de icoonknoppen
   van de connectie-/catalogusweergave. */
/* align-self:stretch + justify-content:flex-end: de actiekolom vult de volle
   hoogte van de hoezenrij en houdt z'n inhoud onderaan. Zo blijft de
   icoonrij exact op z'n plek als de privé-notitie erboven verschijnt. */
.rel-hero-tools{
  display:flex;flex-direction:column;gap:9px;
  align-items:flex-end;justify-content:flex-end;align-self:stretch;
  width:auto;min-width:184px;flex:none; /* groeit mee met "Suggest an edit" + "⋯" */
}
.rel-hero-icons{display:flex;gap:8px;justify-content:flex-end}
.rel-hero-icons .icon-btn,
.rel-hero-icons .private-icon-toggle{
  width:36px;height:36px;border-radius:999px;
  border:1px solid rgba(255,255,255,0.35);background:transparent;color:#fff;
}
.rel-hero-icons .icon-btn svg,
.rel-hero-icons .private-icon-toggle svg{stroke:#fff}
.rel-hero-icons .icon-btn:hover,
.rel-hero-icons .private-icon-toggle:hover{background:rgba(255,255,255,0.16)}
/* Actieve privé-toggle: goud (zoals de favorietenster en de "coming up"-stip
   elders in deze laag). `--vinyl` is in de light-tokens juist heel donker
   marineblauw — op de donkere hero onzichtbaar — dus hier de vaste
   redesign-goudtint, met donkere stroke voor contrast erop. */
.rel-hero-icons .private-icon-toggle.active{background:#e0a850;border-color:transparent}
.rel-hero-icons .private-icon-toggle.active svg{stroke:#1b1206}
.rel-hero-icons .rel-hero-unlink{border-color:rgba(255,255,255,0.22)}
.rel-hero-icons .rel-hero-unlink svg{stroke:rgba(255,255,255,0.85)}
/* Hero toolbar (TODO.md item 158, option 2C): a labelled Edit / Suggest an
   edit button plus a "⋯" menu with labelled rows (heroToolbarHTML(), app.js).
   Same white-outline-on-hero look as the round icon buttons above. */
.hero-toolbar{align-items:center}
.hero-pill{
  display:inline-flex;align-items:center;gap:7px;height:36px;padding:0 15px;
  border-radius:999px;border:1px solid rgba(255,255,255,0.35);background:transparent;
  color:#fff;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;
}
.hero-pill:hover{background:rgba(255,255,255,0.16)}
.hero-pill svg{width:16px;height:16px;stroke:#fff;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.hero-pill:focus-visible,.hero-more-btn:focus-visible{outline:2px solid #fff;outline-offset:2px}
.year-filter-panel.hero-more-panel{
  min-width:220px;background:var(--card);color:var(--ink);border:1px solid var(--line);
  border-radius:var(--r-radius-sm);box-shadow:var(--r-shadow-2);padding:5px;box-sizing:border-box;
}
.hero-more-row{
  display:flex;align-items:center;gap:9px;width:100%;padding:8px 10px;border:none;background:none;
  border-radius:7px;font-family:inherit;font-size:13px;color:var(--ink);cursor:pointer;text-align:left;white-space:nowrap;
}
.hero-more-row:hover,.hero-more-row:focus-visible{background:var(--bg-alt);outline:none}
.hero-more-row svg{flex:none;width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.hero-more-row.danger{color:var(--danger)}
.hero-more-divider{height:1px;background:var(--line);margin:4px 2px}
label.hero-more-row{box-sizing:border-box}
.hero-more-sub{margin-left:auto;padding-left:12px;font-size:11px;color:var(--ink-soft)}
/* A private item's menu row: gold closed lock + "Private · make visible"
   (item 158 follow-up). --warn-ink is the app's amber text tone, legible on
   the menu's card in both themes. */
.hero-more-row.priv{color:var(--warn-ink);font-weight:600}
/* Status marks after the star (privateMarkHTML / wantLiveMarkHTML): a small
   gold round lock — the padlock of the "Private" section tag — and the
   filled Want to see live ticket. Status only, not buttons. */
.hero-private-mark{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:22px;height:22px;border-radius:999px;background:#e0a850;margin-left:4px;vertical-align:middle;
}
.hero-private-mark svg{width:12px;height:12px;stroke:#1b1206;fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
span.want-live-icon{cursor:default;margin-left:2px}
/* Favorite mark (item 158, round 3): a filled gold star, drawn as SVG so it
   centres on the same line as the lock and ticket (a ★ text glyph sat low in
   its own box, which made the other marks look too high). */
.hero-fav-mark{display:inline-flex;align-items:center;justify-content:center;flex:none;width:24px;height:24px}
.hero-fav-mark svg{width:22px;height:22px;fill:#e0a850;stroke:#e0a850;stroke-width:1;stroke-linejoin:round}

/* Viewing-as. everythingViewToolbarHTML() (admin) wraps its Edit+History in
   .rel-hero-icons now too (2026-09-14 — used to hang them straight in the
   column-direction .rel-hero-tools, which stacked them instead of side by
   side like every other icon row here). viewAddRowHTML() (bezoeker) still
   hangs its own .view-add-row directly in .rel-hero-tools — it's a single
   button/link, not an icon row, nothing to lay out side by side. Both carry
   plain .icon-btn/.catalog-link-icon/.btn-outline styling from style.css;
   on the dark hero that gets the same round white-outline treatment as the
   real action column. */
.rel-hero-tools{color:#fff}
.rel-hero-tools > .icon-btn,
.rel-hero-tools .catalog-link-icon{
  width:36px;height:36px;border-radius:999px;flex:none;
  border:1px solid rgba(255,255,255,0.35);background:transparent;
}
.rel-hero-tools > .icon-btn svg,
.rel-hero-tools .catalog-link-icon svg{stroke:#fff}
.rel-hero-tools > .icon-btn:hover,
.rel-hero-tools .catalog-link-icon:hover,
.rel-hero-tools .view-add-row .btn-outline:hover{
  background:rgba(255,255,255,0.16);border-color:rgba(255,255,255,0.35);
}
.rel-hero-tools .view-add-row{flex-wrap:wrap;justify-content:flex-end}
.rel-hero-tools .view-add-label{color:rgba(255,255,255,0.82)}
.rel-hero-tools .view-add-row .btn-outline{
  color:#fff;border-color:rgba(255,255,255,0.35);background:transparent;
}
/* Boven de icoonrij (niet eronder) zodat het aanzetten van Privé de
   icoontjes niet omhoog duwt — de rij blijft onderaan tegen de hoes.
   Breder dan de 184px-icoonkolom (steekt naar links uit, weg van de
   icoontjes) zodat de tekst op 2 regels past i.p.v. 3. */
.rel-hero-note{
  font-size:9px;letter-spacing:0.08em;line-height:1.55;text-align:right;
  color:rgba(255,255,255,0.62);margin:0 0 7px;width:max-content;max-width:238px;
}

.rel-body{
  max-width:1180px;margin:0 auto;
  padding:28px 28px 40px;display:grid;
  grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:26px;align-items:start;
}
.rel-side{display:flex;flex-direction:column;gap:18px}
.rel-panel-head{display:flex;align-items:center;justify-content:space-between;gap:16px}
/* "+ Add tracklist" / "+ Add setlist" in a panel head (TODO.md item 153) —
   only shown while that list is empty; the hero's Edit button is the way in
   otherwise. */
.panel-add-link{margin-top:0;font-size:12.5px;white-space:nowrap}
.rel-panel-title{display:flex;align-items:center;gap:10px}
.rel-panel-note{font-size:10.5px;letter-spacing:0.08em;color:var(--ink-soft)}
.rel-empty{font-size:13.5px;color:var(--ink-soft)}

/* Blokkoppen — Tracklist / Release / My note — allemaal in één stijl:
   mono-eyebrow, maar duidelijk groter (12,5px / 0.16em) dan de 9,5px
   veldlabels binnen het Release-blok (RECORD LABEL etc.) zodat de
   hiërarchie klopt. (Optie A, keuze van de eigenaar 2026-09-08.) */
.rel-tracklist-panel .panel-head h2,
.rel-side-label,
.rel-note-panel .section-title{
  font-family:'IBM Plex Mono',monospace;font-weight:400;font-size:12.5px;
  letter-spacing:0.16em;text-transform:uppercase;color:var(--ink-soft);
  margin:0;
}
.rel-side .meta-grid{
  grid-template-columns:1fr 1fr;gap:16px 18px;
  border:none;padding:0;margin:16px 0 0;
}
.rel-side .meta-wide{grid-column:1 / -1}
.rel-side .meta-value.is-empty{font-weight:400;color:var(--ink-soft)}

/* myNoteSectionHTML's wrapper droeg een inline margin-top:24px; nu een
   klasse zodat de kaart-variant hier 'm op 0 kan zetten. De niet-
   heringerichte Show-/Artiestpagina's houden zo exact hun huidige ruimte. */
.my-note-section{margin-top:24px}
.rel-note-panel{position:relative}
.rel-note-panel .my-note-section{margin-top:0}
.rel-note-panel .section-head{margin:0 0 12px;align-items:center;padding-right:36px}
/* The personal-section padlock takes its title's colour (TODO.md item 158,
   option F): the panel labels on release/show/artist pages (My note, My
   memory, Who did you go with?) are the quiet mono --ink-soft. */
.rel-note-panel .personal-section-tag,
.sd-panel .personal-section-tag{color:var(--ink-soft)}
.rel-note-panel .personal-section-tag svg,
.sd-panel .personal-section-tag svg{width:12px;height:12px}

/* My note — lees-/bewerkstanden (myNoteSectionHTML readEdit-variant). */
.rel-note-panel .my-note-text{
  font-size:13.5px;line-height:1.6;color:var(--ink);margin:0;white-space:pre-wrap;
}
/* Edit-trigger: een icon-btn (the owner, 2026-09-13 — terug van een tekstlinkje
   naar het notitie-icoontje, zelfde glyph als de oude altijd-actieve
   textarea's eigen Save-knop), rechtsboven op de kaart net als Line-up/
   Setlist/Companion's eigen icoon (zie .sd-panel hieronder) in plaats van
   ín de kopregel naast de titel. */
/* Edit an existing note: a small pencil right after the title's padlock
   (TODO.md item 158 follow-up, option D — the owner, 2026-09-26), replacing
   the boxed note-page icon in the card's corner. In the accent (link)
   colour so it reads as clickable next to the grey, non-clickable lock. */
.my-note-edit-pencil{display:inline-flex;align-items:center;justify-content:center;flex:none;width:24px;height:24px;margin-left:-2px;padding:0;border:none;border-radius:6px;background:none;color:var(--accent);cursor:pointer}
.my-note-edit-pencil svg{width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
.my-note-edit-pencil:hover{background:var(--bg-alt)}
.my-note-edit-pencil:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.rel-note-panel .my-note-del{
  font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:0.06em;
  text-transform:uppercase;background:none;border:none;cursor:pointer;padding:2px 4px;
  color:var(--danger);margin-left:auto;
}
.rel-note-panel textarea{margin-top:2px}
.rel-note-panel .my-note-actions{display:flex;align-items:center;gap:8px;margin-top:10px}
.rel-note-panel .my-note-hint{font-size:12px;line-height:1.5;color:var(--ink-soft);margin-top:8px}
.rel-note-panel .btn-mini-primary,
.rel-note-panel .btn-mini{
  font-size:12.5px;font-weight:600;padding:7px 15px;
  border-radius:var(--r-radius-sm);cursor:pointer;
}
.rel-note-panel .btn-mini-primary{background:var(--accent);color:#fff;border:none}
.rel-note-panel .btn-mini-primary[disabled]{opacity:.6;cursor:default}
.rel-note-panel .btn-mini{background:none;border:1px solid var(--line);color:var(--ink)}
.rel-note-panel .btn-mini:hover{border-color:var(--ink-soft)}

/* Disc-/format-koppen in de tracklist als mono-eyebrow (prototype). */
.rel-tracklist-panel .track-heading{
  font-family:'IBM Plex Mono',monospace;font-size:9.5px;letter-spacing:0.16em;
  text-transform:uppercase;color:var(--ink-soft);
}

/* Hoes-zoom: klik op de hero-hoes → een fixed-position kloon tweent van de
   hoes-box naar een grote gecentreerde box (en recht terug bij klik/Escape).
   left/top/width/height-transitie = rechtlijnige beweging, symmetrisch. */
.image-slot.has-zoom{cursor:zoom-in}
.img-zoom-backdrop{
  position:fixed;inset:0;z-index:300;
  background:rgba(9,14,26,0);transition:background .3s ease;cursor:zoom-out;
}
.img-zoom-backdrop.is-open{background:rgba(9,14,26,0.86)}
.img-zoom-img{
  position:fixed;object-fit:cover;
  border-radius:10px;box-shadow:0 40px 90px rgba(0,0,0,0.6);
}
.img-zoom-img.is-animating{
  transition:left .34s cubic-bezier(.22,1,.36,1),top .34s cubic-bezier(.22,1,.36,1),
    width .34s cubic-bezier(.22,1,.36,1),height .34s cubic-bezier(.22,1,.36,1);
}
@media (prefers-reduced-motion:reduce){
  .img-zoom-backdrop,.img-zoom-img.is-animating{transition:none}
}

@media (max-width:1100px){
  /* minmax(0,1fr), same reasoning as .songs-grid's own mobile override
     (redesign.css) — a bare 1fr track's default min-width:auto lets it
     stretch to its widest un-wrapped content instead of the viewport. */
  .rel-body{grid-template-columns:minmax(0,1fr)}
}
@media (max-width:900px){
  .rel-hero-cols{flex-wrap:wrap;align-items:flex-end}
  /* justify-content:flex-end (2026-09-16, mobile walkthrough) — on desktop
     .rel-hero-tools stays content-width, pushed to the row's right edge by
     .rel-hero-body's own flex:1 taking the rest of .rel-hero-cols; once
     wrapped here onto its own full-width row (width:100%, no longer
     sharing a row with .rel-hero-body), nothing was left to push its
     single child (.rel-hero-icons) rightward, so it sat at the row's
     default flex-start — the Edit menu landing bottom-left under the
     photo on Artist detail instead of bottom-right like desktop. */
  .rel-hero-tools{width:100%;flex-direction:row;flex-wrap:wrap;align-items:center;justify-content:flex-end}
  .rel-hero-note{text-align:left;flex:1 1 100%}
}
@media (max-width:640px){
  .rel-hero-inner{padding:34px 16px 24px}
  .rel-hero-back{top:12px;left:16px}
  /* Stacked (item 23, option C, 2026-09-16 — the owner picked this over the
     smaller-cover option for Release/Show/Song alike) — cover moves above
     the title instead of squeezing beside it. flex-direction:column
     switches .rel-hero-cols' main axis to vertical; align-items:stretch
     alongside it makes .rel-hero-body/.rel-hero-tools stretch to the full
     width on their own (only the cover needs its own align-self so it
     keeps a real size instead of stretching too).
     align-items:stretch had to be spelled out here (2026-09-16 follow-up,
     item v round 2 — the owner's screenshots: "Seoul" shifted right with a
     big gap on the left, "Akoesteren" looked fine) — the base rule's own
     align-items:stretch (line ~1105) isn't actually what was reaching this
     breakpoint. The unscoped 900px rule above sets align-items:flex-end
     for every hero type (wrapped .rel-hero-tools row staying bottom-
     anchored there), and with no reset here that stayed in effect at 640px
     too, same as .rel-hero-cols{align-items:stretch} had to be spelled out
     again for .sd-hero/.artist-hero at their own 640px blocks. In column
     mode, flex-end shrinks .rel-hero-body to its own content width and
     right-anchors it instead of stretching it full-width — invisible on a
     title long enough to nearly fill the row (Akoesteren), obvious on a
     short one (Seoul, big gap on the left). Song shares this same
     .rel-hero-cols/.rel-hero-body pair (its own hero only swaps in
     .song-hero-fan/.song-hero-cover for .rel-hero-cover), so the same fix
     covers it too. */
  .rel-hero-cols{flex-direction:column;align-items:stretch;gap:16px}
  .rel-hero-cover{align-self:flex-start;width:120px;min-height:120px}
  .rel-body{padding:20px 16px 32px;gap:18px}
  .rel-hero-icons .icon-btn,
  .rel-hero-icons .private-icon-toggle,
  .rel-hero-tools > .icon-btn,
  .rel-hero-tools .catalog-link-icon{width:44px;height:44px}
  /* The hero's Tags button matches the 44px ⋯ next to it (TODO.md item 158
     follow-up — it stayed 36px and read lower than ⋯ on a phone). */
  .rel-hero-icons .hero-pill{height:44px}
  /* The left-align-everything-once-stacked override (item v follow-up)
     lives further down, in the unscoped @media(max-width:640px) block
     after the Show section — same "duplicate .rel-hero-tools rule, later
     one wins" cascade-order reasoning as the 900px pair above, so putting
     it *here* would just get silently beaten by that later rule again. */
}

/* ── Artiest-detail (fase 5) ──────────────────────────────────────────── */
/* Deelt de full-bleed hero + .rel-body / .rel-side van Release-detail. Alleen
   de ronde foto, de lichtere overlay met lichtval, de aka/leden-regel en de
   "Edit"-menuknop zijn artiest-specifiek. renderView() matcht .artist-detail
   (die ook .rel-detail draagt). Geen datamodel-wijziging. */
.artist-hero .rel-hero-bg::after{
  background:linear-gradient(180deg,rgba(9,14,26,0.22) 0%,rgba(9,14,26,0.88) 100%);
}
.artist-hero-light{
  position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(circle at 78% 18%,rgba(255,255,255,0.24),transparent 55%);
}
/* Iets forser dan de Release-hero — het is de identiteitssplash van de
   artiest. Foto en hero-hoogte opgeschaald (the owner, 2026-09-11: de ronde
   foto oogde klein naast de 216px vierkante Release-hoes, en liet veel
   lege ruimte boven de hero over — een cirkel op gelijke diameter oogt
   sowieso al kleiner qua oppervlak dan een vierkant, dus 190px in plaats
   van simpelweg 216px). Inhoud tegen de onderkant. Rand/schaduw verder
   gelijk aan .rel-hero-cover (geen witte ring, zelfde schaduwdiepte) —
   de eerdere 3px witte rand + lichtere schaduw waren een eigen behandeling
   die niet met de Release-hoes overeenkwam (the owner, 2026-09-13). */
/* Geen eigen min-height meer (was 320px) — dat dwong de hero hoger dan de
   Release-hero, die zijn hoogte gewoon uit de inhoud haalt (hoes + padding,
   geen vloerwaarde), en liet lege ruimte boven de foto ontstaan (the owner,
   2026-09-13). flex-column/justify-end blijft staan voor als de foto ooit
   weer smaller wordt dan de tekstkolom ernaast, maar doet nu niets. */
.artist-hero .rel-hero-inner{display:flex;flex-direction:column;justify-content:flex-end}
.artist-hero .rel-hero-cols{align-items:flex-end}
.artist-hero-photo{
  width:190px;height:190px;flex:none;position:relative;border-radius:999px;
  box-shadow:0 24px 55px rgba(0,0,0,0.55);
}
.artist-hero-photo .image-slot{
  position:absolute;inset:0;width:auto;height:auto;border:none;background:rgba(255,255,255,0.06);
  border-radius:999px;overflow:hidden;box-shadow:none;
}
.artist-hero-photo .image-slot img{width:100%;height:100%;object-fit:cover;display:block}
.artist-hero-photo .image-slot:not(.has-image){border:1px dashed rgba(255,255,255,0.32)}
.artist-hero-photo .image-slot .slot-label,
.artist-hero-photo .image-slot .slot-icon{color:rgba(255,255,255,0.6);font-size:10px;text-align:center;padding:6px}

.artist-hero-rel{
  font-size:11.5px;color:rgba(255,255,255,0.72);margin-top:6px;line-height:1.5;
}
.artist-hero-rel .name-link{color:#fff;font-weight:600;cursor:pointer}
.artist-hero-sep{margin:0 8px;opacity:0.5}

.artist-edit-item{
  display:block;width:100%;text-align:left;background:none;border:none;cursor:pointer;
  font-family:inherit;font-size:13px;color:var(--ink);padding:8px 10px;border-radius:var(--r-radius-sm);
}
.artist-edit-item:hover{background:var(--bg-alt)}
.artist-edit-item.is-danger{color:var(--danger)}

/* Body — linkerkolom. */
.artist-main{display:flex;flex-direction:column;gap:18px;min-width:0}
.artist-panel{padding:20px 22px}

.artist-relgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;margin-top:14px}
.artist-relcard{cursor:pointer;min-width:0}
.artist-relcard .album-cover .image-slot{border-radius:8px}
.artist-relcard-t{font-size:13px;font-weight:600;color:var(--ink);margin-top:9px;line-height:1.3}
.artist-relcard-a{font-size:12px;color:var(--ink-soft);margin-top:2px}
.artist-relcard-m{font-size:9px;letter-spacing:0.04em;text-transform:uppercase;color:var(--ink-soft);margin-top:4px}
.artist-relcard:hover .album-cover .image-slot{transform:translateY(-4px);box-shadow:var(--r-shadow-cover-hi)}

/* Shows-tijdlijn — zelfde vorm als .song-live-row. */
.artist-shows{margin-top:14px}
.artist-show-row{
  display:grid;grid-template-columns:64px minmax(0,1fr) auto;gap:16px;align-items:center;
  padding:12px 10px;margin:0 -10px;border-radius:var(--r-radius-sm);cursor:pointer;
  transition:background .12s ease;
}
.artist-show-row + .artist-show-row{border-top:1px solid var(--r-hairline)}
.artist-show-row:hover{background:var(--bg-alt)}
.artist-show-date{flex:none;text-align:center;border-right:1px solid var(--r-hairline);padding-right:14px}
.artist-show-year{
  display:block;font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:18px;line-height:1;color:var(--accent);
}
.artist-show-day{display:block;margin-top:4px;font-size:9px;letter-spacing:0.1em;color:var(--ink-soft)}
.artist-show-title{font-size:14px;font-weight:700;color:var(--ink)}
.artist-show-place{font-size:12px;color:var(--ink-soft);margin-top:2px}
.artist-show-badge{margin-top:0;flex:none}

/* Zijkolom-kaarten. */
.artist-live-songs{margin-top:12px}
.artist-live-song{
  display:flex;align-items:center;gap:12px;padding:10px 8px;margin:0 -8px;
  border-radius:var(--r-radius-sm);cursor:pointer;transition:background .12s ease;
}
.artist-live-song + .artist-live-song{border-top:1px solid var(--r-hairline)}
.artist-live-song:hover{background:var(--bg-alt)}
.artist-live-song-t{flex:1;font-size:13.5px;font-weight:600;color:var(--ink)}
.artist-live-song-n{font-size:11px;color:var(--ink-soft)}

@media (max-width:900px){
  .artist-hero-photo{width:156px;height:156px}
  .artist-relgrid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:640px){
  /* Stacks now too (item 23/v follow-up, 2026-09-16) — originally excluded
     in favor of Artist's existing side-by-side mobile treatment (round
     photo left, name/stats right), but the owner asked for Release/Show/Song's
     stacked layout here as well for consistency. .rel-hero-cols'
     flex-direction:column (base 640px rule, style shared across all four
     hero types) already applies unmodified; align-items needs its own
     override the same way .sd-hero got (see its comment) — the base
     .artist-hero .rel-hero-cols{align-items:flex-end} above is a *row*-mode
     bottom-anchor (desktop: photo/text bottom-aligned), which would read as
     a *right*-anchor once the main axis flips to vertical here. */
  .artist-hero .rel-hero-cols{align-items:stretch}
  .artist-hero-photo{width:132px;height:132px;align-self:flex-start}
}

/* ── Connections (fase 3, handoff scherm 8) ───────────────────────────────
   Herbouw van renderConnections(): "Add new connection" opent een inline
   zoekpaneel (geen modal), platte connectie-rijen met een tandwiel dat de
   per-connectie-sharing openklapt (Private/Visible/Default als pill-group),
   en Sharing & discovery als schuifschakelaars. Tweekoloms zoals de handoff:
   connecties + verzoeken links, Sharing & discovery rechts. */
.conn-page .conn-title{margin-bottom:6px}
.conn-lede{
  font-size:13.5px;line-height:1.55;color:var(--ink-soft);margin:0 0 22px;max-width:64ch;
}
.conn-layout{display:grid;grid-template-columns:1.4fr 1fr;gap:22px;align-items:start}
.conn-col-main,.conn-col-side{display:flex;flex-direction:column;gap:18px;min-width:0}
.conn-page .panel{padding:22px 24px}

/* Avatar — initials (badge-familiekleuren, inline style van
   connInitialAvatar()) of, als de connectie een profielfoto heeft, de foto
   zelf als <img class="conn-av">. Zelfde maatklassen voor allebei. */
.conn-av{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  border-radius:999px;color:#fff;font-weight:600;line-height:1;
  font-family:'IBM Plex Sans',sans-serif;
}
img.conn-av{object-fit:cover;background:var(--bg-alt)}
.conn-av--sm{width:34px;height:34px;font-size:13px}
.conn-av--md{width:38px;height:38px;font-size:14px}
.conn-av--lg{width:44px;height:44px;font-size:15px}

/* Your connections — kop met "Add new connection" rechts */
.conn-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:8px}
.conn-head .section-title{margin:0;flex:1;min-width:0}
.conn-add-btn{
  display:inline-flex;align-items:center;gap:7px;border-radius:999px;white-space:nowrap;
}
.conn-add-btn .conn-add-plus{font-size:15px;line-height:0;font-weight:600}
.conn-add-btn.is-open{border-color:var(--accent);color:var(--accent);background:var(--suggest-bg)}

/* Inline "Add new connection" paneel */
.conn-find{
  margin:4px 0 8px;padding:16px;border:1px solid var(--line);
  border-radius:var(--r-radius-sm);background:var(--bg-alt);
}
.conn-find-top{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}
.conn-find-h{font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:15px;margin:0;flex:1}
.conn-find-close{
  background:none;border:none;font-family:'IBM Plex Mono',monospace;font-size:11px;
  letter-spacing:0.04em;color:var(--ink-soft);cursor:pointer;padding:2px;
}
.conn-find-close:hover{color:var(--ink)}
.conn-find .conn-find-row{margin:0}
.conn-find-hint{font-size:11.5px;color:var(--ink-soft);margin-top:8px;line-height:1.5}
.conn-find-results{margin-top:12px;border-top:1px solid var(--line)}
.cfr-row{
  display:flex;align-items:center;gap:12px;padding:9px 2px;
  border-bottom:1px solid var(--r-hairline);
}
.cfr-name{flex:1;min-width:0;font-size:13.5px;font-weight:600;color:var(--ink)}
.cfr-status{
  font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:0.04em;
  text-transform:uppercase;color:var(--ink-soft);text-align:right;
}
.cfr-count{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--ink-soft);margin-top:10px}
.conn-find-empty{font-size:12.5px;color:var(--ink-soft);margin-top:12px;line-height:1.5}

/* Filter over bestaande connecties (>4) — bewust rustiger dan "Add new" */
.conn-list-filter{margin:10px 0 4px}
.conn-list-filter .search-field{max-width:none}
.conn-list-filter .form-input{background:var(--bg-alt);font-size:12.5px}
.conn-empty{font-size:12.5px;color:var(--ink-soft);padding:6px 0 2px}

/* Platte connectie-rij */
.conn-row{border-top:1px solid var(--r-hairline);padding:14px 0}
.conn-row:first-of-type{border-top:none}
.conn-connections .conn-find + .conn-row,
.conn-connections .conn-list-filter + .conn-row{border-top:none}
.conn-row-head{display:flex;align-items:center;gap:14px}
.conn-row-body{flex:1;min-width:0}
.conn-row-name{font-size:15px;font-weight:700;color:var(--ink)}
.conn-row-sub{font-size:12px;color:var(--ink-soft);margin-top:2px}
.conn-row-scopes{display:flex;gap:6px;margin-top:7px;flex-wrap:wrap}
.conn-scope{
  font-family:'IBM Plex Mono',monospace;font-size:9px;letter-spacing:0.04em;
  text-transform:uppercase;border-radius:999px;padding:3px 7px;
}
.conn-scope.on{background:var(--bdg-system);color:#fff}
.conn-scope.off{border:1px solid var(--line);color:var(--ink-soft)}
.conn-row-actions{display:flex;align-items:center;gap:8px;flex:none}
.conn-gear{
  width:32px;height:32px;flex:none;border:1px solid var(--line);border-radius:8px;
  background:var(--card);cursor:pointer;display:flex;align-items:center;justify-content:center;
}
.conn-gear svg{width:15px;height:15px;stroke:var(--ink-soft);fill:none;stroke-width:2}
.conn-gear:hover{border-color:var(--ink)}
.conn-gear:hover svg{stroke:var(--ink)}
.conn-gear.is-open{border-color:var(--accent);background:var(--suggest-bg)}
.conn-gear.is-open svg{stroke:var(--accent)}
.conn-view-btn{border-radius:999px;white-space:nowrap}

/* Per-connectie uitklap */
.conn-row-expand{margin-top:14px;padding-top:14px;border-top:1px dashed var(--line)}
.conn-share-grp-t{font-size:12.5px;font-weight:600;color:var(--ink)}
.conn-share-note{font-size:11.5px;color:var(--ink-soft);margin:3px 0 12px}
.conn-share-row{display:flex;align-items:center;gap:14px;padding:6px 0;flex-wrap:wrap}
.conn-share-lbl{width:76px;flex:none;font-size:12.5px;font-weight:600;color:var(--ink)}
.conn-remove{margin-top:14px}

/* Pill-group — 3-weg (Private/Visible/Default). Track background matches
   .segmented's own now (var(--card), white) — was var(--bg-alt), the same
   blue-grey-tinted background item 121 moved every other input/field off
   of; the owner caught it here too once the buttons themselves stopped
   drawing the eye away from it (2026-09-16). */
.pill-group{
  display:inline-flex;gap:4px;background:var(--card);border:1px solid var(--line);
  padding:3px;border-radius:999px;
}
.pill-group button{
  border:none;background:none;cursor:pointer;font-family:inherit;
  border-radius:999px;padding:6px 15px;font-size:11.5px;font-weight:600;color:var(--ink-soft);
  transition:background .12s ease,color .12s ease;
}
.pill-group button:hover{color:var(--ink)}
/* --ink fill (2026-09-16) — was var(--accent) for Private/Visible and
   var(--olive) for Default; both still read as blue (--olive is a blue-
   slate, #3f5e82 in light mode, not actually olive-toned) — same problem
   The owner flagged a second time once they saw "Default" (the state every
   connection starts on, so the one actually on screen most often) was
   still colored. Both now match every other selected segment/pill in the
   app (.segmented button.active, .dash-fav-pill.active) exactly — no more
   separate .is-default color. */
.pill-group button.active{background:var(--ink);color:var(--bg)}

/* Requests */
.conn-req-eyebrow{
  font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:0.16em;
  text-transform:uppercase;color:var(--accent);margin-bottom:4px;
}
.conn-page .conn-req{
  display:flex;align-items:center;gap:13px;padding:13px 0;
  border-top:1px solid var(--r-hairline);
}
.conn-page .conn-req:first-of-type{border-top:none}
.conn-req-body{flex:1;min-width:0}
.conn-page .conn-req-name{font-size:14.5px;font-weight:700}
.conn-page .conn-req-sub{font-size:12px;color:var(--ink-soft);margin:2px 0 0}
.conn-page .conn-req-actions{flex:none;margin:0;align-items:center}
.conn-req-primary{background:var(--accent);border-color:var(--accent);color:#fff}
.conn-req-sent{border-top:1px solid var(--line);margin-top:8px;padding-top:2px}
.conn-req-sent-head{
  font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:0.14em;
  text-transform:uppercase;color:var(--ink-soft);margin-top:8px;
}
.conn-requests{border-color:var(--accent)}

/* Sharing & discovery — .segmented Off/On (2026-09-16, matches Push
   notifications' own toggle now; was a sliding pill switch, .rd-switch,
   removed here — see sharingRowHTML()/discoverableRowHTML()'s own
   comments). */
.conn-page .conn-toggle-row{flex-direction:row;align-items:center;gap:14px}
.conn-tr-text{flex:1;min-width:0}
.conn-page .conn-disc-body{max-width:none}

/* Viewing-bar — vollere variant op de Connections-pagina (optie B) */
.viewing-bar-inner--full{gap:13px}
.viewing-bar-full-who{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1}
.viewing-bar-full-name{font-size:13.5px}
.viewing-bar-full-name strong{font-weight:600}
.viewing-bar-full-sub{font-size:11.5px;opacity:0.82}

@media (max-width:980px){
  /* minmax(0,1fr), same reasoning as .songs-grid's own mobile override
     (redesign.css) — a bare 1fr track's default min-width:auto lets it
     stretch to its widest un-wrapped content instead of the viewport. */
  .conn-layout{grid-template-columns:minmax(0,1fr)}
}
@media (max-width:560px){
  .conn-row-head{flex-wrap:wrap}
  /* Indented to line up under the name/first pill, not the avatar
     (2026-09-16, mobile walkthrough) — width:100% used to start this row
     flush at the row's own left edge once it wrapped, landing the gear +
     View button under the 44px .conn-av--lg photo instead of under
     .conn-row-body's text. 58px = that avatar's width (44px) + .conn-row-head's
     own gap (14px), the same offset .conn-row-body already sits at. */
  .conn-row-actions{width:calc(100% - 58px);margin-left:58px;justify-content:flex-start}
  .conn-share-lbl{width:100%}
}
@media (prefers-reduced-motion:reduce){
  .pill-group button{transition:none}
}

/* ── Notifications (fase 3, handoff scherm 9) ─────────────────────────────
   Herbouw van renderProfileNotifications() als eigen pagina (was een tab
   onder Profile + een losse pop-up dropdown, allebei vervallen — zelfde
   verhuizing als Connections eerder kreeg, TODO item 104). Tweekoloms:
   lijst + filterpillen links, het (echte, al bestaande) push-paneel rechts
   — the owner koos bewust tegen de 4 fictieve per-type-toggles uit het
   prototype, zie TODO item 113. */
.notif-page .page-title{margin-bottom:2px}
.notif-head{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-bottom:22px}
.notif-lede{font-size:13.5px;color:var(--ink-soft);margin-top:6px}
.notif-head-actions{display:flex;gap:8px;flex-wrap:wrap}
/* Named areas, not just two flex columns in a row (the owner, 2026-09-14) —
   .notif-pills is its own area above .notif-col-main only, so the Push
   notifications card (.notif-col-side) lines up with the Notifications
   list card below it instead of with the pills row above it. "." is the
   deliberately-empty cell that leaves the pills row's own height out of
   .notif-col-side's alignment. */
.notif-layout{
  display:grid;grid-template-columns:1.5fr 1fr;
  grid-template-areas:"pills ." "main side";
  column-gap:22px;row-gap:14px;align-items:start;
}
.notif-col-main{grid-area:main;display:flex;flex-direction:column;gap:14px;min-width:0}
.notif-col-side{grid-area:side;display:flex;flex-direction:column;gap:14px;min-width:0}

/* Filterpillen — zelfde pilpatroon als straks Shows' jaarfilter (handoff):
   losse pillen met rand, niet de gevulde pill-group-track van Connections. */
.notif-pills{grid-area:pills;display:flex;gap:6px;flex-wrap:wrap}
.notif-pill{
  border:1px solid var(--line);background:var(--card);color:var(--ink-soft);
  border-radius:999px;padding:6px 15px;font-size:12.5px;font-weight:600;
  cursor:pointer;transition:all .12s ease;font-family:inherit;
}
.notif-pill:hover{border-color:var(--ink)}
.notif-pill.active{background:var(--ink);border-color:var(--ink);color:var(--card)}
.notif-pill .section-count{color:inherit;opacity:0.7}

.notif-list{padding:6px 18px 10px}
.notif-row{
  display:grid;grid-template-columns:10px 38px 1fr auto;gap:14px;align-items:center;
  padding:14px 10px;margin:0 -10px;border-top:1px solid var(--r-hairline);
  border-radius:10px;cursor:pointer;transition:background .12s ease;
}
.notif-row:first-child{border-top:none}
.notif-row:hover{background:var(--bg-alt)}
.notif-row.unread{background:var(--suggest-bg);position:relative}
.notif-row.unread::before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:2px;background:var(--accent)}
.notif-dot{width:7px;height:7px;border-radius:999px;background:var(--danger);justify-self:center}
.notif-dot.read{background:transparent}
.notif-body{min-width:0}
.notif-row-title{font-size:14.5px;font-weight:600;color:var(--ink);line-height:1.4}
.notif-row-title b{font-weight:600}
.notif-time{font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:var(--ink-soft);margin-top:3px}
.notif-del{
  background:none;border:none;color:var(--ink-soft);cursor:pointer;font-size:16px;
  line-height:1;padding:4px;flex:none;
}
.notif-del:hover{color:var(--danger)}
.notif-empty{padding:20px 8px;color:var(--ink-soft);font-size:12.5px;text-align:center}
/* First line inside the card, like My notes' Expand all / Collapse all (the
   owner, 2026-09-27): Mark all read left, Clear read · Clear all right; the
   first row's hairline below it separates it from the list. */
.notif-list-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:4px 0 6px;color:var(--ink-soft);font-size:12.5px}
.notif-list-actions-gap{flex:1}
.notif-list-actions + .notif-row{border-top:1px solid var(--r-hairline)}
/* Mark all read — the "safe" action, accent (the owner's option A); the
   Clear actions red. Same text-button size as those. */
.notif-mark-read{background:none;border:none;padding:8px 0;font:inherit;font-size:12.5px;font-weight:600;color:var(--accent);cursor:pointer}
.notif-mark-read:hover{text-decoration:underline}

@media (max-width:980px){
  /* Single column — pills, then the list, then Push notifications, each
     its own full-width row. Without this override the "pills ." / "main
     side" areas above would still try to carve out a 2nd (now nonexistent)
     column, leaving .notif-col-side stranded. minmax(0,1fr), not a bare
     1fr — same reasoning as .songs-grid's own mobile override just above:
     a grid track's default min-width:auto would otherwise let it stretch
     past the viewport to fit its widest un-wrapped content. */
  .notif-layout{grid-template-columns:minmax(0,1fr);grid-template-areas:"pills" "main" "side"}
}
@media (max-width:560px){
  .notif-row{grid-template-columns:8px 34px 1fr auto;gap:10px}
}

/* ── Account & Admin — tabbladenpagina (fase 6) ────────────────────────────
   De rail is één kaart (zelfde idioom als .song-rail op de Songs-pagina) in
   plaats van losse knoppen op de paginakleur. Actief = getinte rij met een
   accentstreepje links, exact de taal van .sidebar .nav-btn.active in de
   shell — niet een witte kaart, want wit betekent in deze shell "inhoud".
   Op mobiel wordt de rail één horizontaal scrollende chiprij. */
.profile-layout{gap:26px}
.profile-sidebar{
  width:212px;min-width:0;position:sticky;top:70px;
  background:var(--card);border:1px solid var(--line);border-radius:12px;
  box-shadow:var(--r-shadow-1);padding:10px 8px;gap:2px;
}
.acct-rail-group{
  font-family:'IBM Plex Mono',monospace;font-size:9.5px;letter-spacing:0.16em;
  text-transform:uppercase;color:var(--ink-soft);padding:13px 10px 5px;
}
.acct-rail-group:first-child{padding-top:3px}
.profile-side-btn{
  display:flex;align-items:center;gap:9px;width:100%;
  padding:8px 10px 8px 8px;border-radius:8px;
  font-size:13px;font-weight:600;text-align:left;cursor:pointer;
  color:var(--ink-soft);background:none;border:none;
  transition:background .15s ease,color .15s ease;
}
.profile-side-btn::before{
  content:"";flex:none;width:3px;height:14px;border-radius:2px;
  background:transparent;transition:background .15s ease;
}
.acct-tab-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Help Center's Updates/Replay the tour (the owner, 2026-09-14) — the one
   place a rail row still reads as an action rather than a plain tab. */
.acct-tab-label.is-caps{text-transform:uppercase;letter-spacing:0.03em}
/* Plain rule between the category list and Updates/Replay the tour — not
   an .acct-rail-group label, neither of those is really "articles". */
.profile-rail-divider{border:none;border-top:1px solid var(--line);margin:8px 6px}
.profile-side-btn:hover{background:var(--bg-alt);color:var(--ink);text-decoration:none}
/* Overschrijft style.css' .profile-side-btn.active (witte kaart + rand). */
.profile-side-btn.active{background:var(--bg-alt);border:none;color:var(--ink)}
.profile-side-btn.active::before{background:var(--accent)}

/* Kop boven de tabinhoud. .acct-head-actions is een lege haak voor een
   tabbrede knop, mocht een tab die later nodig hebben. */
.acct-head{
  display:flex;align-items:flex-end;justify-content:space-between;
  gap:14px;flex-wrap:wrap;margin:0 0 16px;
}
.acct-head h2{
  font-family:'Space Grotesk',sans-serif;font-size:19px;font-weight:700;
  margin:0;color:var(--ink);line-height:1.2;
}
.acct-head-sub{font-size:12.5px;color:var(--ink-soft);margin-top:3px}
.acct-head-actions{display:flex;align-items:center;gap:8px;flex:none;color:var(--ink-soft);font-size:12.5px}

/* Search + Expand all/Collapse all on one row, inside the panel (TODO.md
   item 121 follow-up, 2026-09-16, the owner) — Release notes and Help Center
   both used to float their search field above the panel, with Expand/
   Collapse pinned to its own flex-end row inside it. Search now sits left,
   the actions group right, sharing this one row; .acct-head-actions keeps
   its own layout unchanged; it's just moved in here as this row's second
   child instead of getting flex-end/margin inline on its own. */
.panel-search-row{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:14px}
.panel-search-row .search-field{flex:1 1 220px;max-width:400px}

/* Lege staat als volwaardige kaart, zodat de inhoudskolom nooit een losse
   zin op de achtergrond is. */
.acct-panel-empty{
  background:var(--card);border:1px solid var(--line);border-radius:12px;
  box-shadow:var(--r-shadow-1);padding:34px 24px;
  text-align:center;font-size:13.5px;color:var(--ink-soft);
}

@media (max-width:640px){
  .profile-layout{gap:16px}
  /* Loskomen van .main's 16px paginamarge (redesign.css regel 484) zodat de
     scrollrail écht van rand tot rand loopt, net als de onderbalk — anders
     oogt hij als een kaart die binnen de pagina zweeft. Binnenpadding van
     dezelfde 16px zodat de eerste chip nog uitlijnt met de paginatitel
     erboven. Kaartchroom (achtergrond/rand/radius/schaduw, van de
     desktop-regel hierboven) vervalt voor een platte strook met alleen een
     onderrand als scheiding. Gemeld door the owner (2026-09-13). */
  .profile-sidebar{
    position:static;width:auto;flex-direction:row;flex-wrap:nowrap;
    overflow-x:auto;overscroll-behavior-x:contain;
    margin:0 -16px;padding:8px 16px;gap:4px;scrollbar-width:none;
    background:none;border:none;border-radius:0;box-shadow:none;
    border-bottom:1px solid var(--line);
  }
  .profile-sidebar::-webkit-scrollbar{display:none}
  .acct-rail-group{display:none}
  .profile-rail-divider{display:none}
  .profile-side-btn{width:auto;flex:none;padding:7px 12px;white-space:nowrap}
  .profile-side-btn::before{display:none}
  .profile-side-btn.active{background:var(--accent);color:#fff}
  .acct-head h2{font-size:17px}
}

/* ── Help Center (fase 14, 2026-09-14) ─────────────────────────────────────
   Reuses the same .profile-layout/.profile-sidebar rail as Account/Admin
   above — no layout of its own left (see style.css's own comment on what's
   left there: just the article/search styling, page-agnostic). A width
   cap on .profile-content here (fase 15) was dropped again the same day —
   The owner's call after noticing it left the page's own right margin much
   wider than every other page's: the .panel/.au-card content now just
   stretches full width like Account/Admin's own tab content already does. */

/* ── Artiesten-lijst (fase 7) ──────────────────────────────────────────────
   Ronde portretten, tegenover de vierkante hoezen van Releases: vierkant is
   iets wat je bezit, rond is iemand die het gemaakt heeft. Verder identiek
   aan .cover-grid (Releases) — zelfde grid-basis (minmax 170px, gap 22px,
   anders paste er door een net iets kleinere minmax+gap één kolom extra in
   dan bij Releases, the owner 2026-09-13), zelfde box-shadow/hover, geen eigen
   randbehandeling: width:100% zonder cap zodat de cirkel net als de hoes
   zijn hele grid-cel vult. (De eerdere 158px-cap, opgeschaald vanaf 126px op
   2026-09-11, loste alleen het gevolg op — de cirkel oogde klein — niet de
   oorzaak, die extra kolom.) */
.artist-grid{grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:22px}
.artist-card{position:relative;text-align:center;min-width:0}
.artist-card .album-cover.artist-cover{
  width:100%;margin:0 auto;aspect-ratio:1;
  border-radius:999px;overflow:hidden;position:relative;
  box-shadow:0 10px 22px rgba(16,24,40,0.16);
  transition:transform .18s ease,box-shadow .18s ease;
}
.artist-card .album-cover.artist-cover .image-slot{
  border-radius:999px;box-shadow:none;width:100%;height:100%;
}
.artist-card .album-cover.artist-cover .image-slot img{width:100%;height:100%;object-fit:cover;display:block}
.artist-card:hover .album-cover.artist-cover{
  transform:translateY(-8px) scale(1.02);
  box-shadow:0 22px 40px rgba(16,24,40,0.30);
}
.artist-card:hover .album-cover.artist-cover .image-slot{transform:none}
/* Ster in de hoek náást de cirkel — daar is toch lege ruimte, en op de
   cirkel zelf zou hij het portret aansnijden. */
.artist-card .favorite-star{
  position:absolute;top:0;right:2px;z-index:3;
  background:none;border:none;border-radius:0;width:auto;height:auto;padding:2px;
  font-size:16px;line-height:1;
}
.artist-card-n{
  font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:13.5px;
  color:var(--ink);margin-top:12px;line-height:1.3;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
.artist-card-m{font-size:11.5px;color:var(--ink-soft);margin-top:3px}
/* .chip-row, not .tag-chips — tagChipsHTML() (public/app.js) emits
   .chip-row/.chip; verified against the spec's own "check, don't assume"
   note (fase7-artiesten-wishlist.md §5). */
.artist-card .chip-row{justify-content:center;margin-top:7px}

/* List-weergave: dezelfde rij als Releases, alleen de thumb wordt rond. */
.artist-list .list-cover.artist-cover,
.artist-list .list-cover.artist-cover .image-slot{border-radius:999px;overflow:hidden}
.artist-list .list-cover.artist-cover{box-shadow:0 0 0 1px var(--line)}

@media (max-width:640px){
  .artist-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:18px 12px}
  .artist-card-n{font-size:12.5px;margin-top:9px}
  .artist-card-m{font-size:10.5px}
}

/* ── Wishlist (fase 7) ─────────────────────────────────────────────────────
   Erft .cover-grid van Releases — het zijn releases, alleen nog niet van
   jou. Drie eigen dingen: de × op de hoes in plaats van erboven, de notitie
   afgekapt op twee regels, en "Mark as bought" als voet van de kaart. */
.wl-grid .album-card{display:flex;flex-direction:column}
/* × linksboven op de hoes, tegenover de ster. Op een muisapparaat pas bij
   hover — het is een weggooiknop, die hoeft niet permanent te staan schreeuwen.
   Op touch (geen hover) altijd zichtbaar, anders is hij onbereikbaar. */
.wl-grid .wantlist-remove-btn{
  position:absolute;top:6px;left:7px;z-index:3;
  width:24px;height:24px;border-radius:999px;padding:0;
  display:flex;align-items:center;justify-content:center;
  font-size:15px;line-height:1;
  background:rgba(9,14,26,0.55);color:#fff;border:1px solid rgba(255,255,255,0.35);
  opacity:0;transition:opacity .15s ease,background .15s ease;
}
.wl-grid .album-card:hover .wantlist-remove-btn,
.wl-grid .wantlist-remove-btn:focus-visible{opacity:1}
.wl-grid .wantlist-remove-btn:hover{background:var(--danger);border-color:transparent}
@media (hover:none){.wl-grid .wantlist-remove-btn{opacity:1}}

.wl-note{
  font-size:12px;color:var(--ink-soft);margin-top:6px;line-height:1.45;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
.wl-grid .wantlist-cat{font-size:10.5px;color:var(--ink-soft);margin-top:4px;letter-spacing:0.04em}
.wl-grid .wantlist-bought-btn{
  width:100%;margin-top:auto;padding:7px 10px;font-size:12px;
}
.wl-grid .chip-row{margin-top:7px}

/* ── Admin (fase 8) ────────────────────────────────────────────────────────
   Eén verschil met fase 6: de teller heeft twee betekenissen. Een
   werkvoorraad (goedkeuringen, open feedback, bevindingen) kan naar nul en
   verdient aandacht; inventaris (hoeveel accounts, hoeveel updates) niet.
   Amber uit de --warn-familie, dezelfde taal als de viewing-balk — geen rood:
   dit is werk, geen fout. */
/* Padding/border op de kale variant zijn bewust hetzelfde bokmodel als
   .is-due, alleen onzichtbaar (transparant) — anders schuift het cijfer van
   een amber badge (met eigen binnenruimte) niet gelijk op met een kaal
   getal (zonder binnenruimte) in de rij eronder. Gemeld door the owner
   (2026-09-13): het cijfer bij Feedback stond merkbaar linker dan bij User
   management, exact dit verschil. */
.acct-tab-count{
  flex:none;font-size:11px;font-weight:500;color:var(--ink-soft);
  font-variant-numeric:tabular-nums;
  border:1px solid transparent;border-radius:999px;padding:1px 7px;
}
.acct-tab-count.is-due{
  background:var(--warn-bg);color:var(--warn-ink);
  border-color:var(--warn-border);font-weight:600;
}
.profile-side-btn.active .acct-tab-count{color:var(--ink)}
.profile-side-btn.active .acct-tab-count.is-due{color:var(--warn-ink)}

/* De Admin-tabs dragen langere labels dan Account ("Pending approvals",
   "User management") — iets meer ruimte, anders kapt elk label af. Alleen
   desktop: op mobiel is de rail een horizontale rij (width:auto) — deze
   regel stond eerst ongeclausuleerd en versloeg dat door zijn hogere
   specificiteit (twee classes vs. één), waardoor de scroller nooit de
   volle breedte kreeg. Gemeld door the owner (2026-09-13). */
@media (min-width:641px){
  .profile-layout .profile-sidebar{width:218px}
}

@media (max-width:640px){
  .profile-side-btn.active .acct-tab-count.is-due{
    background:rgba(255,255,255,0.92);border-color:transparent;
  }
  /* The plain (non-due) count's desktop active colour is var(--ink) — near-
     black in light mode (#101828). On mobile the active chip fills with
     --accent, itself a dark navy in light mode (#16305c): near-black on
     dark navy is unreadable. Match the button's own #fff instead, same fix
     fase 6 already had for it before this rule's box-model change (see the
     comment on .acct-tab-count above) replaced it wholesale. Reported by
     The owner (2026-09-13), User management's "12" invisible once active. */
  .profile-side-btn.active .acct-tab-count:not(.is-due){color:rgba(255,255,255,0.82)}
}

/* ── Shows-tijdlijn (fase 9, richting 3d) ─────────────────────────────────
   De rug loopt door de rechterkolom van een 104px/1fr-grid. Elke rij én elke
   markering gebruikt datzelfde grid, zodat de lijn nooit verspringt.
   .timeline* uit style.css blijft staan maar wordt niet meer gerenderd. */
.tl{display:flex;flex-direction:column}
.tl-row,.tl-mark{display:grid;grid-template-columns:104px minmax(0,1fr)}

.tl-when{text-align:right;padding:16px 22px 16px 0}
/* Iets lichter dan --ink (the owner, 2026-09-13: de datum trok te veel aandacht
   naast de titel) — --ink-mid, tussen --ink en de nog lichtere --ink-soft van
   een toekomstige datum in. */
.tl-when-date{
  font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:15px;
  line-height:1.15;color:var(--ink-mid);
}
.tl-when-rel{
  font-family:'IBM Plex Mono',monospace;font-size:9.5px;letter-spacing:0.12em;
  color:var(--ink-soft);margin-top:4px;
}
.tl-row.upcoming .tl-when-date{color:var(--ink-soft)}
/* .tl-when's 16px padding-top was tuned for the two-line case (date +
   .tl-when-rel) so that block's vertical centre lines up with .tl-dot
   (top:26px, so its own centre sits at 32px from the row's top). A past
   show has no .tl-when-rel any more — dropped in an earlier round, the owner
   2026-09-13 — so the lone date line, still starting at that same 16px, now
   centres a good 7-8px above the dot instead of level with it. Extra
   padding-top here, scoped to rows without the second line, re-centres it
   on the same 32px instead of touching the shared two-line case. */
.tl-row:not(.upcoming) .tl-when{padding-top:23px}

/* De rug is accentkleurig voor alles wat al gebeurd is (dezelfde kleur als
   de vandaag-lijn — het verleden is "waargemaakte tijd"), gestippeld en
   gedimd voor wat nog moet komen. Dots idem: iets groter dan voorheen
   (10px→12px) en in dezelfde accentkleur als hun eigen rugstuk, anders
   verdwenen ze tegen een even donkere lijn als voorheen tegen de neutrale
   (the owner, 2026-09-13). */
.tl-track{position:relative;border-left:2px solid var(--accent);padding:8px 0 8px 26px}
.tl-row.upcoming .tl-track{border-left-color:var(--line);border-left-style:dashed}
.tl-dot{
  position:absolute;left:-7px;top:26px;width:12px;height:12px;border-radius:999px;
  background:var(--accent);border:2px solid var(--bg);box-shadow:0 0 0 2px var(--bg);
}
.tl-row.upcoming .tl-dot{background:var(--bg);border-color:var(--ink-soft)}
/* Approximate date (TODO.md item 140): dashed amber card and hollow dot,
   "approx." under the margin label. */
.tl-row.approx .tl-dot{background:var(--bg);border:2px dashed var(--warn-ink)}
.tl-row.approx .tl-card{border-style:dashed;border-color:var(--warn-border)}
/* "Did you go?" (item 43): amber dot + border while unanswered; a missed
   show fades out. */
.tl-row.to-confirm .tl-dot{background:var(--warn-ink)}
.tl-row.to-confirm .tl-card{border-color:var(--warn-border)}
.tl-row.missed .tl-card{opacity:.6}
.tl-row.missed .tl-dot{background:var(--bg);border-color:var(--line)}
.tl-when-approx{color:var(--warn-ink)}

.tl-card{
  background:var(--card);border:1px solid var(--line);border-radius:var(--r-radius-sm);
  padding:15px 18px 14px;cursor:pointer;
  transition:box-shadow .15s ease,transform .15s ease;
}
.tl-card:hover{box-shadow:var(--r-shadow-2);transform:translateY(-2px)}
.tl-title-row{display:flex;align-items:center;flex-wrap:wrap;gap:9px}
.tl-title-row .type-badge{margin-top:0}
.tl-title-end{margin-left:auto;display:flex;align-items:center;gap:8px;flex:none}
.tl-title-end .favorite-star{font-size:16px}
.tl-artist{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:17px;line-height:1.2;color:var(--ink)}
.tl-row.upcoming .tl-artist{color:var(--ink-soft)}
.tl-venue{font-size:12.5px;color:var(--ink-soft);margin-top:5px;line-height:1.45}
.tl-days{display:flex;flex-direction:column;margin-top:7px}
.tl-day{padding:3px 8px;margin:0 -8px;border-radius:6px;line-height:1.45}
.tl-day:hover{background:var(--bg-alt)}
.tl-day-date{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--ink-soft)}
.tl-day-acts{font-size:12.5px;color:var(--ink)}
.tl-row.upcoming .tl-day-acts{color:var(--ink-soft)}
.tl-card > .chip-row{margin-top:9px}

/* Jaarmarkering — groot, rustig, en niet in de kantlijn maar op de rug, zodat
   de kantlijn één betekenis houdt (de datum van déze show). Kleur volgt de
   datum eronder (the owner, 2026-09-13) — --ink-mid voor een verleden jaar
   (gelijk aan .tl-when-date; bijgesteld van --ink naar --ink-mid dezelfde
   sessie, te aanwezig naast de titel), --ink-soft voor een aankomend jaar. */
.tl-year .tl-year-num{
  font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:30px;line-height:1;
  color:var(--ink-mid);text-align:right;padding-right:22px;
}
.tl-year.upcoming .tl-year-num{color:var(--ink-soft)}
.tl-year{align-items:center}
/* Zelfde accent/gestippeld-onderscheid als .tl-track hierboven, zodat de rug
   nooit van kleur/stijl wisselt op een jaarmarkering (the owner, 2026-09-13). */
.tl-year .tl-rule{border-left:2px solid var(--accent);display:block;padding:12px 0 12px 26px}
.tl-year.upcoming .tl-rule{border-left-color:var(--line);border-left-style:dashed}
.tl-year .tl-rule::after{content:"";display:block;border-top:1px solid var(--r-hairline)}

/* Vandaag — de enige plek op de pagina met de accentkleur als vlak. Zelfde
   kantlijn/rug/dot-opbouw als een showrij (the owner, 2026-09-13: het label
   hoort links van de rug, de dot erop, de datum rechts ervan — net als elke
   showrij, in plaats van een eigen los pilletje met de datum erin gebakken
   en geen echte dot op de rug zelf). */
.tl-today{align-items:center}
.tl-today .tl-when{display:flex;justify-content:flex-end;padding-top:0;padding-bottom:0}
.tl-today-pill{
  display:inline-flex;align-items:center;
  background:var(--accent);color:#fff;border-radius:999px;padding:6px 14px;
  font-family:'IBM Plex Mono',monospace;font-size:10.5px;letter-spacing:0.1em;white-space:nowrap;
}
/* .tl-today-track needs no override any more — .tl-track's own base colour
   is accent now too. .tl-today-dot only needs to re-centre vertically: its
   row has no card, so it's shorter than .tl-dot's fixed top:26px assumes;
   everything else (colour, size) already matches the base dot. */
.tl-today-dot{top:50%;transform:translateY(-50%);box-shadow:0 0 0 2px var(--accent)}
/* No show literally today (the owner, 2026-09-13): the rug at the today-marker
   itself reads as "hasn't happened" like the rest of the future, dashed and
   dim — same treatment .tl-row.upcoming .tl-track already gets. The dot
   marks "today" regardless, so it's deliberately left out of this override. */
.tl-today.no-show .tl-today-track{border-left-color:var(--line);border-left-style:dashed}
/* Outline-pil naast de gevulde TODAY-pil (the owner, 2026-09-13) — zelfde vorm,
   lichter gewicht zodat de twee niet als twee even zware blokken naast
   elkaar staan. */
.tl-today-date{
  display:inline-flex;align-items:center;
  background:var(--card);color:var(--accent);border:1px solid var(--accent);
  border-radius:999px;padding:5px 12px;
  font-family:'IBM Plex Mono',monospace;font-weight:700;font-size:11px;letter-spacing:0.08em;
}

/* Stilte — de rug houdt op en gaat gestippeld verder. */
.tl-quiet .tl-quiet-stub{
  grid-column:2;width:2px;height:28px;margin-left:-1px;justify-self:start;
  background:repeating-linear-gradient(180deg,var(--line) 0 4px,transparent 4px 9px);
}
.tl-quiet{align-items:center}
.tl-quiet .tl-quiet-label{
  grid-column:2;justify-self:start;margin-left:12px;
  font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:0.14em;color:var(--ink-soft);
}

@media (max-width:640px){
  .tl-row,.tl-mark{grid-template-columns:66px minmax(0,1fr)}
  .tl-when{padding:14px 12px 14px 0}
  .tl-row:not(.upcoming) .tl-when{padding-top:24px}
  .tl-when-date{font-size:13px}
  .tl-when-rel{font-size:9px;letter-spacing:0.08em}
  .tl-track{padding-left:18px}
  .tl-year .tl-year-num{font-size:22px;padding-right:12px}
  .tl-year .tl-rule{padding-left:18px}
  .tl-card{padding:13px 14px 12px}
  .tl-artist{font-size:15.5px}
  .tl-today-pill{font-size:9.5px;padding:5px 11px;letter-spacing:0.06em}
  .tl-today-date{font-size:10px}
}

/* ── Shows: Years-weergave (fase 10, richting 4a) ─────────────────────────
   Vast grid: jaarkolom + twaalf gelijke maandkolommen. Op smalle schermen
   schuift het horizontaal (.yr-scroll) met de jaarkolom sticky, zodat je
   nooit kwijt bent welk jaar je leest. */
.yr-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
.yr-grid{
  min-width:760px;background:var(--card);border:1px solid var(--line);
  border-radius:var(--r-radius);padding:16px 20px 8px;
}
.yr-head,.yr-row{display:grid;grid-template-columns:120px repeat(12,minmax(0,1fr)) minmax(0,0.8fr);gap:6px;align-items:center}
.yr-head{padding-bottom:10px;border-bottom:1px solid var(--r-hairline)}
.yr-head-cell{
  text-align:center;font-family:'IBM Plex Mono',monospace;font-size:9.5px;
  letter-spacing:0.08em;color:var(--ink-soft);padding:3px 0;border-radius:5px;
}
.yr-head-year{text-align:left;letter-spacing:0.14em}
.yr-head-cell.is-now{background:var(--accent);color:#fff}

.yr-row{border-bottom:1px solid var(--r-hairline);padding:4px 0}
.yr-row:last-child{border-bottom:none}
.yr-year{display:flex;align-items:baseline;gap:8px;min-width:0}
.yr-year-num{font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:22px;line-height:1;color:var(--ink)}
.yr-year-count{font-family:'IBM Plex Mono',monospace;font-size:9px;letter-spacing:0.06em;color:var(--ink-soft)}
.yr-row.is-quiet .yr-year-num{color:var(--ink-soft);opacity:0.6}

.yr-cell{height:58px;display:flex;align-items:center;justify-content:center;gap:2px;border-radius:8px}
.yr-cell.is-now{box-shadow:inset 0 0 0 1px var(--accent)}
.yr-cell.is-multi .yr-disc{margin:0 -5px}
.yr-empty{width:10px;border-top:1px solid var(--line);opacity:0.8}

.yr-disc{
  position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;
  background:none;border:none;padding:2px;cursor:pointer;
  transition:transform .15s ease;
}
.yr-disc:hover{transform:translateY(-3px)}
.yr-disc-img,.yr-disc-ini{
  width:30px;height:30px;border-radius:999px;display:block;box-shadow:var(--r-shadow-1);
  border:2px solid transparent;
}
.yr-disc-ini{
  display:flex;align-items:center;justify-content:center;color:#fff;
  font-family:'IBM Plex Sans',sans-serif;font-size:12px;font-weight:600;line-height:1;
}
.yr-disc-img{object-fit:cover;background:var(--bg-alt)}
.yr-disc.upcoming .yr-disc-img,.yr-disc.upcoming .yr-disc-ini{border:2px dashed var(--ink-soft);background:var(--bg)}
.yr-disc.approx .yr-disc-img,.yr-disc.approx .yr-disc-ini{outline:2px dashed var(--warn-ink);outline-offset:2px}
.yr-head-unknown{color:var(--warn-ink)}
.yr-disc.is-sel .yr-disc-img,.yr-disc.is-sel .yr-disc-ini{border-color:var(--accent);box-shadow:var(--r-shadow-2)}
.yr-disc:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:10px}
.yr-day{font-family:'IBM Plex Mono',monospace;font-size:9px;color:var(--ink-soft)}
.yr-disc.is-sel .yr-day{color:var(--ink);font-weight:600}
.yr-days{
  position:absolute;right:-4px;top:20px;min-width:15px;height:15px;border-radius:999px;
  background:var(--ink);color:var(--card);border:1.5px solid var(--card);
  font-family:'IBM Plex Mono',monospace;font-size:8.5px;
  display:flex;align-items:center;justify-content:center;
}
.yr-more{font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--ink-soft)}

.yr-legend{
  display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin-top:12px;
  font-family:'IBM Plex Mono',monospace;font-size:9.5px;letter-spacing:0.08em;color:var(--ink-soft);
}
.yr-legend-item{display:inline-flex;align-items:center;gap:7px}
.yr-legend-now{margin-left:auto}
.yr-key{width:12px;height:12px;border-radius:999px;display:inline-block}
.yr-key-past{background:var(--bdg-system)}
.yr-key-up{background:var(--bg);border:2px dashed var(--ink-soft)}
.yr-key-days{
  width:15px;height:15px;background:var(--ink);color:var(--card);font-size:8px;
  display:inline-flex;align-items:center;justify-content:center;
}
.yr-key-none{width:12px;height:0;border-top:1px solid var(--line);border-radius:0}

.yr-detail-head{
  display:flex;align-items:center;gap:10px;margin:22px 0 10px;
  font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:0.16em;color:var(--ink-soft);
}
.yr-detail-head::after{content:"";flex:1;border-top:1px solid var(--line)}
.yr-detail{
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:20px;align-items:flex-start;
  background:var(--card);border:1px solid var(--line);border-radius:var(--r-radius);padding:20px 22px;
}
.yr-detail-disc .yr-disc-img,.yr-detail-disc .yr-disc-ini{width:58px;height:58px;font-size:20px;box-shadow:var(--r-shadow-cover)}

@media (max-width:900px){
  .yr-head,.yr-row{grid-template-columns:96px repeat(12,minmax(44px,1fr)) minmax(40px,0.8fr)}
  .yr-year{flex-direction:column;align-items:flex-start;gap:2px}
  .yr-grid{position:relative}
  .yr-year,.yr-head-year{position:sticky;left:0;background:var(--card);z-index:1;padding-right:8px}
}
@media (max-width:640px){
  .yr-detail{grid-template-columns:auto minmax(0,1fr);gap:14px}
  .yr-detail-side{grid-column:1 / -1}
  .yr-detail-side .btn-outline{width:100%}
  .yr-cell{height:52px}
}

/* ── Show-detail (fase 11) ──────────────────────────────────────────────────
   Deelt de full-bleed hero + .rel-body / .rel-side van Release-detail (fase 4).
   Showspecifiek: het datumblok in plaats van een hoes, de dagkiezer, de
   line-up-rijen, de tweekoloms setlist en het fotogrid in de zijkolom.
   renderView() matcht .show-detail (die ook .rel-detail draagt). */

/* Iets lichtere overlay dan een release: de hero-achtergrond is hier een
   showfoto of een artiestfoto, geen hoes — die mag je zien. Onderin toch
   dezelfde diepte, want de titel staat erop. */
.sd-hero .rel-hero-bg::after{
  background:linear-gradient(180deg,rgba(9,14,26,0.35) 0%,rgba(9,14,26,0.9) 100%);
}
.sd-hero .rel-hero-cols{align-items:flex-end}

/* Datumblok — zelfde rol als de 216px-hoes op de releasepagina, maar het is
   een label, geen beeld: dus geen 216px, wel dezelfde onderlijn. */
.sd-date{
  flex:none;text-align:center;align-self:flex-end;
  background:rgba(255,255,255,0.14);border:1px solid rgba(255,255,255,0.3);
  border-radius:12px;padding:12px 18px;min-width:112px;color:#fff;
}
.sd-date-month{font-size:10px;letter-spacing:0.14em;color:rgba(255,255,255,0.8)}
.sd-date-day{
  font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:32px;
  line-height:1.05;white-space:nowrap;
}
.sd-date-year{font-size:10px;color:rgba(255,255,255,0.8)}
.sd-date-approx{border-style:dashed}
.sd-date-approx .sd-date-day{font-size:26px}

.sd-place{font-size:15px;margin-top:8px;color:rgba(255,255,255,0.9);line-height:1.45}
/* Approximate date (TODO.md item 140, phase 2) — note + "Find the exact
   show" on the hero, same translucent-white language as .sd-date. */
.sd-approx-note{display:inline-flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px;font-size:12.5px;color:rgba(255,255,255,0.85);border:1px dashed rgba(255,255,255,0.45);border-radius:8px;padding:6px 10px}
.sd-approx-find{background:rgba(255,255,255,0.16);border:1px solid rgba(255,255,255,0.4);color:#fff;border-radius:6px;padding:4px 10px;font:600 12px 'IBM Plex Sans',sans-serif;cursor:pointer}
.sd-approx-find:hover{background:rgba(255,255,255,0.26)}
/* "Did you go?" on the show hero (item 43). */
.sd-confirm{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px;border:1px dashed rgba(255,255,255,0.55);border-radius:8px;padding:8px 10px;font-size:12.5px;max-width:560px}
.sd-confirm-q{flex:1 1 160px;color:#fff;font-weight:600}
.sd-confirm-btn{border:1px solid rgba(255,255,255,0.5);background:rgba(255,255,255,0.14);color:#fff;border-radius:6px;padding:4px 11px;font:600 12px 'IBM Plex Sans',sans-serif;cursor:pointer}
.sd-confirm-btn.yes{background:#fff;color:var(--accent);border-color:#fff}

/* Dagkiezer. Eén pil per dag van de editie; de dag waar je staat is wit en
   `disabled` (nergens heen), een dag die je zelf niet hebt gelogd is
   uitgegrijsd en ook disabled — zichtbaar dat de editie langer was dan jouw
   bezoek, zonder dode klik. */
.sd-days{display:flex;flex-wrap:wrap;gap:6px;margin-top:20px}
.sd-day{
  display:inline-flex;align-items:center;gap:8px;cursor:pointer;
  font-family:inherit;font-size:12.5px;font-weight:600;
  border-radius:999px;padding:6px 16px;
  background:transparent;border:1px solid rgba(255,255,255,0.35);color:#fff;
  transition:background .15s ease;
}
.sd-day:hover:not([disabled]){background:rgba(255,255,255,0.16)}
.sd-day .sd-day-date{font-size:10px;letter-spacing:0.08em;opacity:0.82;font-weight:400}
.sd-day.is-active{background:#fff;border-color:#fff;color:#101828;cursor:default}
.sd-day[disabled]:not(.is-active){
  border-color:rgba(255,255,255,0.18);color:rgba(255,255,255,0.55);cursor:default;
}

/* Body. Dezelfde twee kolommen als fase 4; de linkerkolom is een kolom
   kaarten (Line-up, Setlist) in plaats van één lange kaart. */
.sd-main{display:flex;flex-direction:column;gap:18px;min-width:0}
.sd-panel{padding:22px 26px 24px;position:relative}
/* Geen .concert-section-override nodig (in tegenstelling tot wat de spec-CSS
   hier meegaf): renderConcertDetail() wikkelt elke sectie nu in .sd-panel,
   niet meer in .concert-section — die klasse komt op deze pagina al niet
   meer voor. De kaartrand van .sd-panel doet het scheidingswerk dat
   .concert-section's bovenrand vroeger deed. */
.sd-panel .section-head{margin-top:0}
/* Kaart-iconen rechtsboven i.p.v. in de kopregel naast de titel (the owner,
   2026-09-13) — Line-up's billing-picker, Setlist's edit-potlood en
   Companion's beheer-icoon staan zo op één vaste plek, ongeacht hoe lang de
   titel/teller ernaast wordt. padding-right op de kopregel voorkomt overlap
   met een lange teller ("4 of 8 seen").
   Line-up/Companion targetten hier tot 2026-09-16 nog .section-head >
   .year-filter — de wrapper om het knopje heen die het uitklappaneel nodig
   had om er relatief aan te openen. Die wrapper is verdwenen toen de
   companion-/line-up-picker een gecentreerde modal werden (item i/ii
   follow-up, zelfde sessie): de trigger is nu een kaal .icon-btn, dus deze
   regel matchte niets meer en het icoon viel terug in de normale
   kopregel-flow — the owner's "icoontjes zijn verschoven" bugreport. Nu op
   .icon-btn zelf, exact hetzelfde patroon als Setlist er al naast heeft. */
.sd-panel--lineup .section-head,
.sd-panel--setlist .section-head,
.sd-panel--companion .section-head{padding-right:40px}
.sd-panel--lineup .section-head > .icon-btn,
.sd-panel--setlist .section-head > .icon-btn,
.sd-panel--companion .section-head > .icon-btn{position:absolute;top:20px;right:20px}
.sd-panel .section-title{
  font-family:'IBM Plex Mono',monospace;font-weight:400;font-size:12.5px;
  letter-spacing:0.16em;text-transform:uppercase;color:var(--ink-soft);
}
.sd-panel .section-count{font-size:10.5px;letter-spacing:0.08em;color:var(--ink-soft)}

/* Shared by the Setlist.fm section (show detail) and the Discogs section
   (release detail, 2026-09-21 follow-up) — one or more outbound links
   (stacked when there's more than one, e.g. a festival's several attended
   entries) plus an optional "Import …" retroactive-fetch button, on the
   same row and right-aligned against the link(s). flex-wrap (not a fixed
   row) is what makes the button drop to its own line under the link(s) on
   a narrow/mobile width instead of overflowing or squeezing — no separate
   mobile rule needed. */
.sd-ext-row{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px 14px;margin-top:12px}
.sd-ext-links{display:flex;flex-direction:column;gap:8px;font-size:13px}
.sd-ext-link{color:var(--accent);font-weight:600}
.sd-ext-row .btn-outline{flex:none}
/* Tour name(s), TODO.md item 130 follow-up (2026-09-21) — one line per
   linked entry that actually has one, same muted meta-label styling the
   rest of a detail page's side panels use for a field's own value. */
.sd-ext-tours{display:flex;flex-direction:column;gap:4px;margin-top:10px;font-size:13px;color:var(--ink-soft)}
.sd-ext-tour strong{color:var(--ink)}

/* Line-up-rijen. De statusbadge draagt de betekenis in woorden; de rij-tint
   doet het tweede keer, niet het eerste. */
.sd-lu{display:flex;flex-direction:column;margin-top:12px}
.sd-lu-row{
  display:grid;grid-template-columns:40px minmax(0,1fr) auto;gap:14px;align-items:center;
  padding:11px 12px;margin:0 -12px;border-radius:var(--r-radius-sm);
  cursor:pointer;transition:background .12s ease;
}
.sd-lu-row + .sd-lu-row{border-top:1px solid var(--r-hairline)}
.sd-lu-row:hover{background:var(--bg-alt)}
.sd-lu-disc{display:block;width:40px;height:40px;flex:none}
.sd-lu-disc .yr-disc-img,.sd-lu-disc .yr-disc-ini{
  width:40px;height:40px;border-radius:999px;display:flex;align-items:center;
  justify-content:center;font-family:'Space Grotesk',sans-serif;font-weight:700;
  font-size:15px;color:#fff;object-fit:cover;box-shadow:none;border:none;
}
.sd-lu-body{min-width:0;display:block}
.sd-lu-name{display:block;font-size:15px;font-weight:700;color:var(--ink)}
.sd-lu-role{display:block;font-size:12.5px;color:var(--ink-soft);margin-top:2px}
.sd-lu-badge{
  font-size:9.5px;letter-spacing:0.06em;text-transform:uppercase;white-space:nowrap;
  border-radius:999px;padding:3px 9px;border:1px solid transparent;
}
.sd-lu--came-for .sd-lu-badge{background:var(--bdg-system);color:#fff}
.sd-lu--also-saw .sd-lu-badge{background:var(--bdg-count);color:#fff}
.sd-lu--didnt-see .sd-lu-badge{background:transparent;border-color:var(--line);color:var(--ink-soft)}
.sd-lu--didnt-see .sd-lu-name{color:var(--ink-soft)}
.sd-lu--didnt-see .sd-lu-disc{opacity:0.45}

/* Setlist in twee kolommen. CSS-kolommen, niet een grid: de DOM-orde blijft
   1,2,3… (screenreader en toetsenbord lopen de setlist in de juiste volgorde
   door) en de kolommen vullen zich gebalanceerd, precies zoals in de mockup.
   De performer-chiprij staat erboven en mag niet mee in de kolommen. */
.sd-panel--setlist .year-filter-grid{margin-top:12px}
.sd-panel--setlist .setlist-row,
.sd-panel--setlist .track-heading{break-inside:avoid}
@media (min-width:900px){
  .sd-panel--setlist .setlist-row,
  .sd-panel--setlist .track-heading{column-span:none}
  .sd-panel--setlist{column-count:1}
  .sd-setlist-cols{columns:2;column-gap:30px}
  /* Niet in de oorspronkelijke spec-CSS, maar wél nodig om de eigen tekst
     ("de chiprij staat erboven en mag niet mee in de kolommen") waar te
     maken: zonder column-span:all valt de chiprij zelf ook binnen de
     kolomindeling (ze zit immers ín .sd-setlist-cols) en verschijnt hij
     alleen bovenaan de eerste kolom, niet over de volle breedte erboven. */
  .sd-setlist-cols .year-filter-grid{column-span:all}
}

/* My memory in de zijkolom: een 2×-grid in plaats van de swipe-strip. De
   strip-navigatie (pijlen, stippen) is hier zinloos — alles staat al in
   beeld — dus uit. De tegels en het uploadgedrag blijven de bestaande
   .image-slot/.detail-photo. */
.sd-panel--memory .memory-strip{
  display:grid;grid-template-columns:1fr 1fr;gap:10px;overflow:visible;
}
.sd-panel--memory .photo-tile{width:auto;flex:none;scroll-snap-align:none}
.sd-panel--memory .detail-photo{aspect-ratio:1;height:auto;border-radius:9px;overflow:hidden}
.sd-panel--memory .detail-photo .image-slot{width:100%;height:100%}
.sd-panel--memory .detail-photo .image-slot img{width:100%;height:100%;object-fit:cover}
.sd-panel--memory .gallery-nav,
.sd-panel--memory .memory-dots{display:none}

/* Who did you go with?: rijen met avatar in plaats van chips. */
.sd-panel--companion .chip-row{display:flex;flex-direction:column;gap:0;margin-top:10px}
.sd-panel--companion .chip-companion{
  display:flex;align-items:center;gap:12px;background:none;color:var(--ink);
  border:none;border-radius:0;padding:10px 0;font-size:14px;font-weight:600;
  text-transform:none;letter-spacing:0;
}
.sd-panel--companion .chip-companion + .chip-companion{border-top:1px solid var(--r-hairline)}

@media (max-width:1100px){
  .sd-main{gap:16px}
}
@media (max-width:900px){
  .sd-hero .rel-hero-cols{flex-wrap:wrap}
  /* Duplicate of the un-scoped .rel-hero-tools rule above (same
     breakpoint, same selector, no .sd-hero scoping on either) — this one
     comes later in the file, so it's the one that actually wins for every
     hero type, not just Show. justify-content:flex-end here too, same fix
     and same reasoning as the other rule's own comment. */
  .rel-hero-tools{width:100%;flex-direction:row;flex-wrap:wrap;align-items:center;justify-content:flex-end}
  .sd-hero .rel-hero-note{text-align:left;flex:1 1 100%}
}
@media (max-width:640px){
  /* Stacks above the title now too (item 23, option C) — .rel-hero-cols'
     own flex-direction:column (its base 640px rule) already applies here,
     shared with Release/Song. .sd-hero's own base rule sets align-items:
     flex-end for the (row-direction) desktop/tablet layout — in a column
     direction that would right-align every child instead of stretching
     them full-width, so it's put back to stretch here specifically.
     .sd-date then needs its own align-self so it keeps a compact size
     instead of also stretching full-width like .sd-place/.sd-days do. */
  .sd-hero .rel-hero-cols{align-items:stretch}
  .sd-date{align-self:flex-start;min-width:0;padding:9px 14px}
  .sd-date-day{font-size:26px}
  .sd-place{font-size:13.5px}
  .sd-day{padding:8px 14px;min-height:44px}
  .sd-panel{padding:18px 18px 20px}
  .sd-lu-row{padding:12px 10px;margin:0 -10px;min-height:44px}
  .sd-panel--memory .memory-strip{grid-template-columns:1fr 1fr}
}
/* Left-align everything once stacked (item v follow-up, 2026-09-16) —
   unscoped, so it covers Release/Show/Song/Artist alike, same as the
   900px .rel-hero-tools duplicate above. Has to come after that rule in
   the file (same breakpoint-overlap logic: both @media blocks match at
   ≤640px, so source order — not which media query is "narrower" — decides
   the tie) or it would lose the cascade to it right back.
   .rel-hero-tools{justify-content:flex-end} (900px rule) is right for the
   un-stacked 641–900px range (confirmed working — the Edit icon sitting
   under the photo's own right edge, matching desktop), but once
   .rel-hero-cols goes to a single column at 640px, that same right-anchor
   puts the icon row across the screen from the now-left-aligned cover/
   title instead of under them. .rel-hero-icons/.view-add-row's own
   justify-content:flex-end (unconditional base rules, sized to their own
   content either way) never needed a matching override — only the
   full-width .rel-hero-tools box that positions *them* did. */
@media (max-width:640px){
  .rel-hero-tools{justify-content:flex-start}
}

/* ===== Fase 12 — Dashboard ============================================ */

/* Niet meer full-bleed (the owner, 2026-09-13, follow-up ronde) — een gewone
   kaart zoals elk ander paneel, dus een radius; #view's eigen padding
   (renderView()'s view-bleed-lijst mist .dash nu) zet 'm al binnen de
   normale kolombreedte.
   De hele Dashboard-kolom (hero + alles eronder) is wél breder dan .main's
   eigen max-width:1180px+padding:28px zou geven (zelfde ronde, twee latere
   instructies) — die 1180px+centreer-marge laat op een breed scherm ruimte
   over die de design-mockup niet heeft: dáár vult de inhoudskolom altijd de
   volle ruimte náást de zijbalk. Eerst kreeg alleen de hero deze
   negative-margin-truc; the owner vroeg daarna expliciet om de kaarten eronder
   uit te lijnen op diezelfde breedte, dus nu staat 'm op .dash zelf, de
   gezamenlijke ouder van .dash-hero én .dash-body. 24px eigen marge aan
   weerskanten (was: rechts 24px, links per ongeluk 0 — daardoor zat de hero
   vast tegen de zijbalk, the owner's eerste opmerking deze ronde), dus "bijna"
   paginabreed, nooit helemaal. Alleen vanaf 641px: onder de mobiele drempel
   is de zijbalk off-canvas en telt hij niet mee, zie de eigen mobiele regel
   verderop (renderView() geeft .dash niet ook nog .view-bleed — dat blijft
   uit deze lijst, zie de eigen opmerking daar). */
@media (min-width:641px){
  .dash{
    /* calc(var(--vw) * 100), not a plain 100vw (TODO.md item 75 follow-up,
       see updateViewportUnit()'s own comment, public/app.js) — at Text
       size 110%/125%, a plain 100vw gets multiplied by the zoom a *second*
       time on top of already being 100% of the real screen, same bug as
       the sidebar's own height used to have with 100vh. That overshot
       --hero-pane, so the negative margin below pulled this column too far
       left, sliding its content under the sidebar (the owner, 2026-09-16). */
    --hero-pane:calc(var(--vw) * 100 - var(--sidebar-w));
    --hero-gap:calc((var(--hero-pane) - min(var(--hero-pane), 1180px)) / 2);
    --hero-edge:24px;
    margin-left:calc(-28px - var(--hero-gap) + var(--hero-edge));
    width:calc(var(--hero-pane) - (2 * var(--hero-edge)));
  }
}
/* De radial-gradient glow rechtsboven (::after) is weg (the owner, 2026-09-13,
   volgende ronde) — "de lichte cirkels kunnen weg" nu het grote logo daar
   zelf al aandacht trekt. */
.dash-hero{position:relative;overflow:hidden;padding:36px 38px 32px;border-radius:var(--r-radius);
  background:linear-gradient(135deg,#16305c 0%,#0b1c38 100%);color:#fff}
.dash-hero-inner{position:relative;display:grid;grid-template-columns:1fr auto;
  gap:44px;align-items:center}
.dash-hero-inner > *{min-width:0}
.dash-hero-eyebrow{font-family:"IBM Plex Mono",monospace;font-size:10.5px;
  letter-spacing:.2em;text-transform:uppercase;color:#9dbceb}
/* Alle-tijd-totalen i.p.v. een "dit jaar"-verhaal (the owner, 2026-09-13) —
   dezelfde vier getallen die .dash-stats (nu verwijderd) toonde, hier op de
   hero-achtergrond. Stijl (follow-up, zelfde dag): niet langer een 2×2-grid
   met een klein mono-label onder elk getal, maar één grote regel per stat —
   getal én label in dezelfde Space Grotesk 800, zoals het origineel
   verworpen "dit jaar"-verhaal in de design-mockup ("15 avonden. 18
   artiesten. …") er al uitzag; alleen de inhoud bleef de alle-tijd-totalen,
   niet dat verhaal. playtimeStatValueHTML() blijft ongewijzigd formatteren;
   ".stat-unit"/".pt-min" hier opnieuw op maat, net als eerder bij
   .dash-stat-value/.dash-hero-stat-value. */
.dash-hero-stats{display:flex;flex-direction:column;gap:3px;margin-top:16px}
/* 46px, up from 34px (the owner, 2026-09-13, follow-up round) — three lines now
   instead of four (Artists Seen Live dropped, see the JS comment on
   dashHeroHTML), so there's room to go bigger, closer to the 52px the
   design mockup's own three-line version used. */
.dash-hero-line{font-family:"Space Grotesk",sans-serif;font-weight:800;
  font-size:46px;line-height:1.14;letter-spacing:-.01em;color:#fff}
.dash-hero-line .stat-unit{font-size:25px;font-weight:700;color:#9dbceb;margin-left:1px}
.dash-hero-line .pt-min{display:inline}
.dash-hero-actions{display:flex;gap:10px;margin-top:22px;flex-wrap:wrap}
/* Beide knoppen gelijk gewicht (was: "To the timeline" gevuld, "Open your
   crate" omlijnd) — the owner, 2026-09-13: allebei omlijnd in rust, gevuld
   zodra je erover hovert, ongeacht welke van de twee. flex:none +
   white-space:nowrap — zonder die twee kromp de nu smallere hero-linkerkolom
   de knoppen tot hun tekst intern ging omklappen, wat er met
   border-radius:999px als twee ronde blobjes uitzag i.p.v. pillen. */
.dash-hero-actions a{border-radius:999px;padding:8px 16px;font-size:12.5px;
  font-weight:600;color:#fff;border:1px solid rgba(255,255,255,.22);
  background:none;text-decoration:none;transition:background .15s ease;
  flex:none;white-space:nowrap}
.dash-hero-actions a:hover{background:rgba(255,255,255,.14)}
/* Merk rechts in de hero (follow-up ronde, 2026-09-13) — vervangt "Shows per
   year", dat nu zijn eigen kaart is (dashShowsPerYearHTML, verderop in het
   Dashboard). Hergebruikt de zijbalk's eigen logo-asset (icon-192.png)
   i.p.v. 'm opnieuw te tekenen — een stuk groter dan de zijbalk's eigen
   30px (72px in de eerste versie van dit rondje, daarna 220px: bijna zo
   hoog als de hero zelf), met een kleiner mobiel formaat verderop.
   "CRATE & ENCORE" ernaast is er weer bij (een latere ronde zelfde dag) —
   twee regels, alleen op desktop (zie de media-queries verderop: op mobiel
   is er domweg geen ruimte voor zowel dit blok als de grote statregels
   ernaast, dus het hele blok — tekst én plaatje — vervalt daar, niet alleen
   het plaatje). Stond eerst links van het plaatje, nu rechts (nog een
   latere ronde, zelfde dag) — text-align mee gedraaid van right naar left,
   nu de tekst na het plaatje begint in plaats van ervoor. aria-hidden op de
   JS-kant: puur decoratief, de werkende nav/merknaam staat al in de
   zijbalk. */
.dash-hero-brand{display:flex;align-items:center;gap:20px;flex:none}
/* 44px, up from 20px (the owner, 2026-09-13, follow-up round: "mag groter,
   misschien iets kleiner houden dan het logo") — a real jump, but still
   clearly under the 220px mark next to it rather than competing with it. */
.dash-hero-brand-text{
  font-family:"Space Grotesk",sans-serif;font-weight:800;font-size:44px;
  line-height:1.08;letter-spacing:.01em;color:#fff;text-align:left;
}
.dash-hero-brand-mark{
  width:220px;height:220px;border-radius:36px;object-fit:cover;flex:none;
  box-shadow:0 14px 32px rgba(0,0,0,.35);
}

/* Geen eigen horizontale padding meer — #view/.main levert die nu zelf,
   voor de hero en de rest van .dash-body gelijk (de hero is geen full-bleed
   uitzondering meer). Verticale ruimte tussen hero en de rest blijft. */
.dash-body{padding:24px 0 16px}
/* Titel-kop die niet de kleine mono-eyebrow gebruikt maar een grotere Space
   Grotesk 800/19px, zelfde stijl als "Through the crate"'s eigen
   .dash-crate-title (the owner, 2026-09-13) — alleen Favorites. Rechtstreeks op
   de <h3> zelf, geen los .section-title erbinnen (dat was de bug: twee
   gelijk-specifieke ".dash .x"-selectors, en de latere in het bestand won
   altijd, ongeacht bedoeling) — deze titel deelt dus bewust geen classes met
   ".dash .section-title" hieronder. */
.dash .dash-title-lg{
  font-family:"Space Grotesk",sans-serif;font-weight:800;font-size:19px;
  letter-spacing:normal;text-transform:none;color:var(--ink);margin:0;
}

/* Sectiekoppen (§3.7) — Coming up en Favorites' teller, gescoped op .dash
   zodat geen andere pagina's .section-head/.section-title meekrijgen.
   Releases/Shows breakdown en hun chevron (dashboardSectionHeadHTML) zijn
   weg (the owner, 2026-09-13, follow-up ronde) — geen in-/uitklappen meer op
   het Dashboard, en de kaarten erachter staan nu los zonder tussenkop, zie
   .dash-section-gap voor de ruimte die de kop anders leverde. */
.dash .section-head{display:flex;align-items:center;margin:30px 0 14px}
.dash .section-head::after{content:"";flex:1;border-top:1px solid var(--r-hairline);margin-left:10px}
/* Favorites got its own white .panel (TODO.md item 123, 2026-09-16) — the
   30px top margin above was tuned for a bare section sitting straight in
   .dash-body; inside the panel's own padding that read as a big dead gap
   above the heading, so it's zeroed here. margin-bottom on the panel itself
   replaces the spacing the section-head used to hand off to whatever came
   next, same value .dash-crate already uses above it. */
.dash-fav-panel{margin-bottom:24px}
.dash-fav-panel .section-head{margin-top:0}
.dash .section-title{
  font-family:"IBM Plex Mono",monospace;font-weight:400;font-size:10px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink-soft);
}
.dash .section-count{margin-left:10px}
/* Vervangt de ruimte die eerder van de verwijderde "Releases breakdown"/
   "Shows breakdown"-koppen kwam, nu die koppen zelf weg zijn maar de
   kaarten erachter blijven staan. */
.dash-section-gap{margin-top:30px}

/* Coming up (§3.3) hergebruikt nu .dash-recent-list/.dash-recent-row exact
   (the owner, 2026-09-13, follow-up ronde) — zelfde rijstijl als Recent shows
   verderop, met de aftel-tekst ("Today"/"Tomorrow"/"In N days") op de plek
   van Recent shows' datum en de echte datum als het kleine mono-regeltje
   eronder. Alleen de puls en de "&middot; N days"-suffix zijn nog eigen.
   De puls zat eerst ná de datumtekst, binnen dezelfde kolom (.dash-up-when)
   — the owner wilde 'm in het midden, tussen de datumtekst en de afbeelding in
   (2026-09-16). .has-pulse-col geeft die ene rij (alleen Upcoming shows, niet
   Recent shows — die deelt dezelfde .dash-recent-row) een extra, smalle
   gridkolom; .dash-up-pulse-cell blijft ook leeg meetellen op de rijen zonder
   puls (i !== 0), zodat de kolommen niet verspringen verderop in de lijst. */
.dash-recent-row.has-pulse-col{grid-template-columns:74px 16px 1fr auto}
.dash-up-pulse-cell{display:flex;align-items:center;justify-content:center}
.dash-up-pulse{flex:none;width:6px;height:6px;border-radius:50%;background:#e0a850;
  animation:pulseDot 2.4s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){.dash-up-pulse{animation:none}}
.dash-up-days{font-weight:400;color:var(--ink-soft)}

/* ── Rank list (Most collected artists / Most seen live / Most frequent
   companions), §3.4 — companions joined this list in a follow-up round
   (the owner, 2026-09-13): same markup, just .conn-av (Connections' own round
   avatar, photo-or-initial) sized in for the avatar slot instead of
   .artist-cover, and its own .dash-companion-row selector dropped from the
   shared list below (the whole point was to stop it looking different). */
.dash-rank{display:flex;flex-direction:column}
.dash-rank-row,.dash-recent-row{cursor:pointer;
  border-radius:var(--r-radius-sm);transition:background .12s ease}
.dash-rank-row:hover,.dash-recent-row:hover{background:var(--bg)}
.dash-rank-row{
  display:grid;grid-template-columns:26px 28px 1fr auto;gap:14px;align-items:center;
  padding:10px;margin:0 -10px;border-top:1px solid var(--r-hairline);
}
.dash-rank-row:first-child{border-top:none}
.dash-rank-row.first{
  grid-template-columns:26px 52px 1fr auto;padding:4px 10px 14px;border-top:none;
}
.dash-rank-num{font-family:"IBM Plex Mono",monospace;font-size:11px;color:var(--ink-soft)}
.dash-rank-row .artist-cover,.dash-rank-row .conn-av{width:28px;height:28px;font-size:11px}
.dash-rank-row.first .artist-cover,.dash-rank-row.first .conn-av{width:52px;height:52px;font-size:19px}
/* A photo-less artist's ♪ mark (imageSlotHTML) ignored this row's own
   28px/52px sizing above — .slot-icon carries its own fixed font-size
   (style.css), sized for a much roomier cover, so it filled almost the
   whole 28px circle on #2-5 (the owner, screenshot: "#3 🎵 Sigur Rós").
   Scaled down and re-centered per row size, 2026-09-16. */
.dash-rank-row .artist-cover .slot-icon{font-size:12px}
.dash-rank-row.first .artist-cover .slot-icon{font-size:20px}
.dash-rank-body{min-width:0}
.dash-rank-name{font-size:13.5px;font-weight:600;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dash-rank-row.first .dash-rank-name{font-size:16px;font-weight:700}
.dash-rank-sub{font-size:12.5px;color:var(--ink-soft);margin-top:2px}
.dash-rank-count{font-family:"IBM Plex Mono",monospace;font-size:11.5px;color:var(--ink)}
.dash-rank-big{font-family:"Space Grotesk",sans-serif;font-weight:800;font-size:22px;
  line-height:1;color:var(--ink);text-align:right}
.dash-rank-unit{font-family:"IBM Plex Mono",monospace;font-size:9px;letter-spacing:.12em;
  color:var(--ink-soft);text-align:right;margin-top:2px}

/* ── Most common formats, §3.6 ───────────────────────────────────────────
   Donut, not a stacked bar (the owner, 2026-09-13 — "Option A" of four
   mockups). dash-fmt-donut-row puts the ring and the legend side by side;
   the ring itself is plain <circle> arcs (dashFormatStackHTML in app.js),
   coloured with the same DASH_FORMAT_STACK_COLORS the old stack used.
   .dash-fmt-panel (same round, follow-up feedback: "veel wit") — this
   card's content is a lot shorter than its .panel-grid neighbour ("Most
   collected artists", 5 rows), and .panel-grid stretches every panel to
   that same height (grid default align-items:stretch — deliberately kept,
   see .favorites-grid elsewhere for the alternative), which used to dump
   all the leftover space below the donut as a flat block of blank card.
   Sizing the ring up a little closes some of that gap outright; centering
   the row in the remaining space (flex column + margin:auto on the row)
   splits whatever's left evenly above/below instead of stacking it at the
   bottom, so it reads as "the card is short" rather than "the card is
   empty". */
.dash-fmt-panel{display:flex;flex-direction:column}
.dash-fmt-donut-row{display:flex;align-items:center;gap:24px;margin:auto 0}
.dash-fmt-donut{flex:none;width:152px;height:152px}
.dash-fmt-donut-total{font-family:"Space Grotesk",sans-serif;font-weight:800;
  font-size:28px;fill:var(--ink)}
.dash-fmt-donut-label{font-family:"IBM Plex Mono",monospace;font-size:8.5px;
  letter-spacing:.1em;fill:var(--ink-soft)}
.dash-fmt-legend{display:flex;flex-direction:column;gap:11px;min-width:0}
.dash-fmt-legend-item{display:inline-flex;align-items:center;gap:7px;cursor:pointer}
.dash-fmt-key{width:9px;height:9px;border-radius:999px;display:inline-block;flex:none}
.dash-fmt-legend-label{font-family:"IBM Plex Mono",monospace;font-size:11px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-soft)}
.dash-fmt-legend-item:hover .dash-fmt-legend-label{color:var(--ink)}
.dash-fmt-legend-count{font-family:"IBM Plex Mono",monospace;font-size:11px;
  font-weight:600;color:var(--ink)}
@media (max-width:420px){.dash-fmt-donut-row{flex-wrap:wrap}}

/* Hover-highlight (follow-up round, 2026-09-13 — "laat het format waar je
   over hovert actief, de rest grijs") — hovering either the arc or its
   legend row dims every *other* arc/legend row, leaving the hovered one at
   full strength. Matched purely by data-i (set on both the <circle> and
   its legend item in dashFormatStackHTML, app.js), not by colour or label —
   five static rules, not a dynamic selector, because the row is already
   capped at 5 formats everywhere else on this card. :has() reaches across
   the .dash-fmt-donut/.dash-fmt-legend split (arcs and legend items are
   siblings' descendants, not siblings themselves). */
.dash-fmt-donut circle,.dash-fmt-legend-item{transition:opacity .15s ease}
.dash-fmt-donut-row:has([data-i="0"]:hover) :is(circle,.dash-fmt-legend-item):not([data-i="0"]),
.dash-fmt-donut-row:has([data-i="1"]:hover) :is(circle,.dash-fmt-legend-item):not([data-i="1"]),
.dash-fmt-donut-row:has([data-i="2"]:hover) :is(circle,.dash-fmt-legend-item):not([data-i="2"]),
.dash-fmt-donut-row:has([data-i="3"]:hover) :is(circle,.dash-fmt-legend-item):not([data-i="3"]),
.dash-fmt-donut-row:has([data-i="4"]:hover) :is(circle,.dash-fmt-legend-item):not([data-i="4"]){opacity:.3}

/* ── Favorites tile grid + filter pills, §4 ─────────────────────────────── */
/* The total used to live in its own row here (.dash-fav-head/.dash-fav-total)
   — moved into the section-head next to the title instead (the owner,
   2026-09-13, follow-up round), so only the pills are left in this row. */
.dash-fav-pills{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:16px}
.dash-fav-pill{
  border-radius:999px;padding:5px 13px;font-size:12px;font-weight:600;cursor:pointer;
  background:var(--card);border:1px solid var(--r-hairline);color:var(--ink-soft);
  transition:border-color .12s ease,color .12s ease;
}
.dash-fav-pill:hover{border-color:var(--accent);color:var(--accent)}
.dash-fav-pill.active{background:var(--ink);border-color:var(--ink);color:var(--card)}
/* Size swapped with Through the crate (2026-09-16, the owner) — was a 150px
   floor; Through the crate's old 118px/92px(mobile) sizes now used here. */
.dash-fav-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(118px,1fr));gap:18px}
.dash-fav-tile{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;
  box-shadow:var(--r-shadow-cover);transition:transform .18s ease,box-shadow .18s ease}
.dash-fav-item:hover .dash-fav-tile{transform:translateY(-8px);box-shadow:var(--r-shadow-cover-hi)}
.dash-fav-item.artist .dash-fav-tile{border-radius:999px}
/* The outer tile clips to a circle for an artist, but .image-slot itself
   still carries its own base border-radius:8px (style.css) — without this,
   a photo-less artist's dashed "no image" border draws as a rounded square
   that the circular clip then cuts off at an angle, instead of the dashed
   ring reading as one clean circle (the owner, 2026-09-16). */
.dash-fav-item.artist .image-slot{border-radius:50%}
/* A shape per kind (TODO.md item 161, option A). Show: a ticket — the
   headliner photo on top, a tear-off stub with the date below, two notches
   at the perforation (a mask, so the notches show the page behind). */
.dash-fav-item.show .dash-fav-tile{display:flex;flex-direction:column;background:var(--card);
  /* Four cut-outs, intersected: the two top corners (the owner, 2026-09-26
     — "more like a ticket") and the two perforation notches. */
  --tk:radial-gradient(circle 9px at 0 72%,#0000 98%,#000),radial-gradient(circle 9px at 100% 72%,#0000 98%,#000),
       radial-gradient(circle 10px at 0 0,#0000 98%,#000),radial-gradient(circle 10px at 100% 0,#0000 98%,#000);
  -webkit-mask:var(--tk);-webkit-mask-composite:source-in;
          mask:var(--tk);mask-composite:intersect}
.dash-fav-ticket-pic{flex:1;min-height:0;position:relative}
.dash-fav-ticket-pic .image-slot{position:absolute;inset:0;width:100%;height:100%;border-radius:0}
.dash-fav-ticket-stub{flex:none;height:28%;border-top:2px dashed var(--line);display:flex;align-items:center;justify-content:space-between;
  gap:6px;padding:0 10px;font-size:10px;color:var(--ink-soft);white-space:nowrap;overflow:hidden}
.dash-fav-ticket-stub b{color:var(--ink);font-weight:600}
/* Song: a record — grooves, the cover as the centre label, a spindle hole. */
.dash-fav-item.song .dash-fav-tile{border-radius:50%;
  background:repeating-radial-gradient(circle at 50% 50%,#161a22 0 2px,#2a303b 2px 3px)}
.dash-fav-disc-label{position:absolute;inset:30%;border-radius:50%;overflow:hidden}
.dash-fav-disc-label .image-slot{position:absolute;inset:0;width:100%;height:100%;border-radius:50%}
.dash-fav-disc-label::after{content:"";position:absolute;left:50%;top:50%;width:14%;height:14%;margin:-7% 0 0 -7%;border-radius:50%;background:var(--card);z-index:1}
.dash-fav-item{cursor:pointer}
.dash-fav-title{font-size:13.5px;font-weight:600;color:var(--ink);margin-top:10px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.dash-fav-sub{font-size:12.5px;color:var(--ink-soft);margin-top:2px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ── Row layout, §5 ─────────────────────────────────────────────────────
   .dash-shows-split/.dash-shows-side (Recent shows + a side column of two
   more cards) is gone — the follow-up round's reshuffle (the owner,
   2026-09-13) paired every card off differently: Upcoming shows next to
   Recent shows (plain .panel-grid, same as it's used elsewhere), then
   Shows per year wide next to Most common formats narrow (needs its own
   ratio, .dash-row-split below), then Most collected artists / Most seen
   live / Most frequent companions three across (plain .panel-grid again —
   auto-fit already gives 3 columns when there's room, 2 or 1 otherwise). */
/* align-items default (stretch), not :start (the owner, 2026-09-13 — wanted
   both cards the same height) — both panels now stretch to match whichever
   is taller; .dash-year-panel below centers the year chart vertically in
   that extra space the same way .dash-fmt-panel already centers the donut,
   so a stretch never just dumps blank space at the bottom of either. */
/* min-width:0 on both columns — same grid-refuses-to-shrink-below-content
   bug as the hero's yearbar earlier this session (the owner, 2026-09-13,
   mobile pass): without it, Shows per year's many-years .yearbar-wrap
   (and, to a lesser extent, Most common formats' legend) kept the whole
   card — and with it the row, and with it the page — from ever shrinking
   below their own intrinsic content width, so both cards rendered wider
   than the screen instead of the year chart just scrolling inside itself
   the way .yearbar-wrap's own overflow-x:auto already allows. */
.dash-row-split{display:grid;grid-template-columns:2fr 1fr;gap:22px}
.dash-row-split > *{min-width:0}
@media (max-width:1100px){.dash-row-split{grid-template-columns:1fr}}
.dash-year-panel{display:flex;flex-direction:column}
.dash-year-panel > .nav-wrap{margin:auto 0}

/* Recent shows — timeline rows (§5, replaces miniShowLineHTML on this page). */
.dash-recent-list{display:flex;flex-direction:column}
.dash-recent-row{
  display:grid;grid-template-columns:74px 1fr auto;gap:16px;align-items:center;
  padding:12px 10px;margin:0 -10px;border-top:1px solid var(--r-hairline);
}
.dash-recent-row:first-child{border-top:none}
.dash-recent-date{font-family:"IBM Plex Mono",monospace;font-size:11.5px;color:var(--ink)}
.dash-recent-type{font-size:9px;letter-spacing:.12em;color:var(--ink-soft);margin-top:2px}
.dash-recent-info{display:flex;align-items:center;gap:10px;min-width:0}
.dash-recent-thumb{flex:none;width:34px;height:34px}
.dash-recent-thumb .yr-disc-img,.dash-recent-thumb .yr-disc-ini{width:34px;height:34px;font-size:13px}
/* dashShowDiscHTML()'s own ♪ fallback (item 123 follow-up, 2026-09-16) —
   same oversized-icon issue as .dash-rank-row above, same fix: .slot-icon's
   own fixed font-size (style.css) was sized for a much bigger cover. */
.dash-recent-thumb .slot-icon{font-size:14px}
.dash-recent-body{min-width:0}
.dash-recent-title{font-size:14.5px;font-weight:600;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dash-recent-place{font-size:12.5px;color:var(--ink-soft);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

@media (max-width:1100px){
  .dash-hero-inner{grid-template-columns:1fr;gap:28px;justify-items:start}
  .dash-hero-line{font-size:36px}
  .dash-hero-brand-mark{width:140px;height:140px}
  .dash-hero-brand-text{font-size:32px}
}
@media (max-width:760px){
  .dash-hero{padding:26px 20px 24px}
  .dash-hero-line{font-size:28px}
  /* Het hele merkblok weg op mobiel (the owner, 2026-09-13, mobiele ronde) —
     op dit formaat is er geen ruimte meer voor zowel de tekst als het
     plaatje naast de grote statregels; de statregels winnen. Het hele
     .dash-hero-brand-blok (wordmark + plaatje), niet alleen het plaatje —
     de wordmark kwam pas in een latere ronde terug, dezelfde afweging geldt. */
  .dash-hero-brand{display:none}
  .dash-body{padding:18px 0 24px}
  .dash-recent-row{grid-template-columns:56px 1fr;row-gap:4px}
  /* Was repositioned onto its own row under the title (grid-column:2) —
     The owner's mobile walkthrough call (2026-09-16): drop it there entirely
     rather than have it wrap onto a visible second line. Applies to both
     Recent shows and Upcoming shows — both share .dash-recent-row. */
  .dash-recent-row .type-badge{display:none}
  /* Shows per year breekt op mobiel uit .panel's eigen 24px binnenmarge, zodat
     de scrollstrook echt tot de randen van de kaart loopt in plaats van
     binnen de kaartpadding te blijven hangen — zelfde swipe-strook-gevoel als
     de Account/Admin-tabbladen op dit formaat (.profile-sidebar hierboven),
     al blijft dit binnen de kaart (geen paginabrede uitbraak, het is geen
     paginanavigatie). .yearbar-wrap scrollt zelf al met verborgen
     scrollbalk — dit verandert alleen wáár die rand ligt. */
  .dash-year-panel .yearbar-wrap{margin-left:-24px;margin-right:-24px;padding-left:24px;padding-right:24px}
  /* Through the crate's old mobile tile size (92px), swapped over with the
     rest of the size swap above. */
  .dash-fav-grid{grid-template-columns:repeat(auto-fill,minmax(92px,1fr))}
}
@media (prefers-reduced-motion:reduce){
  .dash-fav-item:hover .dash-fav-tile{transform:none}
}

/* ── "Door de bak" / "Through the crate" marquee ─────────────────────────
   Niet in fase12-dashboard.md (die stelde 'm uit tot een eigen fase), maar
   wél in README.md's oorspronkelijke schermbeschrijving (scherm 1, blok b)
   én in the owner's eigen geëxporteerde ontwerp voor deze sessie — de latere
   spec-tekst is na dat exportmoment nooit bijgewerkt. Opgebouwd uit de
   README's eigen specificatie voor dit blok, inclusief de al bestaande
   @keyframes crateScroll-naam uit README's tokentabel. */
.dash-crate{margin-bottom:24px}
/* Kop is nu .section-head (zelfde structuur als Coming Up: titel, teller er
   direct achter, dan de haarlijn) i.p.v. een eigen title+meta-rij — the owner,
   2026-09-13. De titel zelf blijft ongewijzigd op zijn eigen
   .dash-crate-title in plaats van het gedeelde .section-title — dat laatste
   zou 'm (per .dash .section-title verderop) terugzetten op de kleine
   mono-eyebrow, en the owner wilde deze titel juist met rust gelaten zien. */
.dash-crate-title{font-family:"Space Grotesk",sans-serif;font-weight:800;font-size:19px;color:var(--ink);margin:0}
/* padding on the track (not just margin) — follow-up round, 2026-09-13,
   "laat de hoes waar je overheen hovert eruit springen zoals bij
   favorites": a hover lift needs headroom to lift *into*, and
   .dash-crate-track's overflow:hidden (essential — it's what hides the
   doubled row's seams as it scrolls) would otherwise clip the top of a
   lifted tile flush against the track's own edge. */
.dash-crate-track{overflow:hidden;border-radius:12px;margin-top:4px;padding:10px 0 4px}
.dash-crate-row{display:flex;width:max-content;gap:14px;animation:crateScroll 46s linear infinite}
/* Pausing on hover (same rule) — a tile that's still sliding away the
   instant it lifts defeats the point of a hover effect. */
.dash-crate-row:hover{animation-play-state:paused}
/* Size swapped with Favorites (2026-09-16, the owner) — was 118px/92px
   (mobile), Favorites' own 150px floor now used here too, no separate
   mobile size (Favorites never had one either). 150px confirmed good on
   mobile (the owner); desktop bumped further below to 170px, matching
   Releases' own grid-view cover floor (.cover-grid, minmax(170px,1fr)) —
   that grid has no single fixed size either (1fr lets it grow), so its
   floor is the one concrete number to match here. */
.dash-crate-tile{flex:none;width:150px;height:150px;border-radius:6px;overflow:hidden;
  box-shadow:0 8px 18px rgba(16,24,40,.18);cursor:pointer;
  transition:transform .18s ease,box-shadow .18s ease}
@media (min-width:761px){
  .dash-crate-tile{width:170px;height:170px}
}
.dash-crate-tile[aria-hidden]{cursor:default}
/* Same lift the Favorites grid uses (.dash-fav-item:hover .dash-fav-tile) —
   :not([aria-hidden]) excludes the seamless-loop echo copies, which were
   already cursor:default (non-interactive, nothing to pop). z-index+relative
   so the enlarged tile paints over its flex neighbours instead of tucking
   behind whichever one comes later in source order. */
.dash-crate-tile:not([aria-hidden]):hover{
  transform:translateY(-8px) scale(1.05);box-shadow:0 14px 28px rgba(16,24,40,.3);
  position:relative;z-index:1;
}
.dash-crate-tile .image-slot{width:100%;height:100%}
.dash-crate-tile .image-slot img{width:100%;height:100%;object-fit:cover;display:block}
@keyframes crateScroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  .dash-crate-row{animation:none}
  .dash-crate-tile:not([aria-hidden]):hover{transform:none}
}

/* ── Screen audit follow-ups (2026-09-15) ─────────────────────────────────
   A few small, independent fixes that came out of going screen-by-screen
   through the redesign — grouped here rather than scattered near each
   screen's own section, since none of them share a home with anything
   above. */

/* Admin → Data integrity: duplicate/near-match groups (renderAdminChecks(),
   public/app.js) were each their own hand-written inline
   style="border:1px solid var(--line);border-radius:10px" box — the same
   box-in-a-panel mismatch as .au-card, just never given a shared class.
   Replaces that with a flush, hairline-divided sub-section inside the
   already-soft-shadowed .panel — no nested border at all — plus a small
   mono label above each group instead of repeating the title/artist (or
   title/date) on every row. */
@media (max-width:560px){
  .di-audit-row{flex-direction:column;align-items:flex-start;gap:2px}
}
/* Data integrity: Orphaned catalog entries (2026-09-16, mobile walkthrough)
   — label wraps its own text now instead of pushing the button around;
   the button keeps its natural width so its own two words never wrap. */
.di-orphan-row{align-items:center;gap:10px}
.di-orphan-row .filter-row-label{min-width:0;overflow-wrap:break-word}
.di-orphan-row button{flex:none;white-space:nowrap}
/* Data integrity: Similar artist/festival names (2026-09-16, mobile
   walkthrough) — name pair + its two action buttons used to share one row
   (.filter-row), cramped on a phone. Stack on mobile instead: label on its
   own line, actions full-width below. */
.di-name-pair{flex-direction:row}
@media (max-width:560px){
  .di-name-pair{flex-direction:column;align-items:stretch;gap:8px}
  .di-name-pair-actions{width:100%}
}
/* Similar track titles: true 50/50 columns — see the inline comment at the
   call site for why (justify-content:space-between on two unsized flex
   items was letting either side's length push the other around). */
.di-track-title,.di-track-release{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ck-group{margin-bottom:18px}
.ck-group:last-child{margin-bottom:0}
.ck-group-label{
  font-family:'IBM Plex Mono',monospace;font-size:10px;text-transform:uppercase;
  letter-spacing:0.08em;color:var(--ink-soft);padding:10px 0 6px;border-top:1px solid var(--r-hairline);
}
.ck-group:first-child .ck-group-label{border-top:none;padding-top:0}
.ck-row{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  margin:0 -8px;padding:10px 8px;border-radius:var(--r-radius-sm);
  cursor:pointer;transition:background .12s ease;
}
.ck-row:hover{background:var(--bg-alt)}
.ck-group-foot{display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:8px 8px 2px}

/* Bulk-import / alias checklist tables (.import-table-wrap, style.css) —
   still the pre-redesign 6px radius with no shadow, the one thing left
   over from Table view once List/Grid were confirmed already fixed.
   Reused by Table view, the Alias modal's name checklist, and Import
   review's bulk-add tables — one rule, not a special case per screen.
   background added 2026-09-16 (TODO.md item 123) — the border+shadow were
   already here, but neither the wrap nor .import-table/td (style.css) ever
   set a background, so it sat on var(--bg), the plain page colour, not
   actually a white card despite looking framed like one. Every other use
   of this class already lives inside a white .modal, so this is a visual
   no-op there — only Releases' Table view (bare on the page) changes. */
.import-table-wrap{border-radius:var(--r-radius-sm);box-shadow:var(--r-shadow-1);background:var(--card)}
/* Rounds just a hovered row's outer edges — valid on real <td> boundaries,
   no markup change needed. Matches the rounded-hover idiom used everywhere
   else (.notif-row, .hc-row-toggle, .ck-row, …) without giving up the
   table's own dense grid, which (unlike a card list) is the right shape for
   ten columns of collection data. */
.data-table tbody tr.clickable:hover td:first-child{border-radius:8px 0 0 8px}
.data-table tbody tr.clickable:hover td:last-child{border-radius:0 8px 8px 0}

/* Discogs pressing-picker cards (barcode scan / URL import results) — flat
   6px boxes with no shadow. Purely cosmetic softening to match every other
   card-shaped thing in the app; the barcode scanner viewfinder right next
   to these is already its own distinct, already-fine look and isn't
   touched here. */
.discogs-result{border-radius:var(--r-radius-sm);box-shadow:var(--r-shadow-1)}

/* Auth/onboarding screens (TODO.md item 120, 2026-09-15) — the one group
   the redesign never reached; all eleven screens (renderSetup/renderLogin/
   renderTwoFactorPrompt/renderForgotPassword/renderResetPassword/
   renderMustChangePassword/renderSignup/renderVerifyEmail/
   renderChooseDisplayName/renderConfirmEmailChange/renderPendingApproval)
   share this one wrapper. No sidebar/dark chrome exists pre-login to
   "match the rest of the app" against, so rather than leave it as a plain
   grey page with a bordered box floating on it, .auth-screen borrows the
   sidebar's own navy gradient — --r-chrome-1/--r-chrome-2, the same fixed,
   non-theme-dependent colors the sidebar itself uses (see "1. Chrome"
   above), so this reads the same regardless of the viewer's light/dark
   setting — plus a wordmark, so the brand is the first thing seen instead
   of a bare "Log in" heading with nothing above it.
   Each screen's own .auth-card already gets the redesign's global radius/
   shadow sweep (.panel, .stat-card, .auth-card, … in "2. Oppervlakken"
   above) for free. Deliberately not touched any further here:
   .form-input/.submit-btn/.form-grid/the private-checkbox row are TODO.md
   item 118's own separate pass, shared with every form in the app, not
   just these eleven — redoing them here first would mean redoing them
   again once 118 lands. */
.auth-screen{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:24px;
  background:linear-gradient(160deg,var(--r-chrome-1) 0%,var(--r-chrome-2) 100%);
  min-height:100vh;padding:40px 24px;
}
.auth-wordmark{
  font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:22px;
  color:var(--r-chrome-ink);letter-spacing:0.5px;
}

/* Shared form primitives (TODO.md items 117 + 118, 2026-09-15) — every
   text field, dropdown, submit/cancel button and the private-checkbox row
   across the whole app, all pure CSS on already-shared classes (no markup
   changes needed anywhere, unlike the .au-card pass above). Extends a
   direction already shipped once: .delete-modal .btn-cancel is already
   pill-shaped, with its own comment saying the base .btn-cancel wasn't
   redesigned everywhere "since TODO.md item 118 covers that" — this is
   that follow-through, not a new proposal. */
.form-input,
.select{
  border-radius:var(--r-radius-sm);
  transition:border-color .15s ease, box-shadow .15s ease;
}
.form-input:focus{
  box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.meta-label{letter-spacing:0.08em}
.submit-btn,
.btn-cancel{border-radius:999px}
/* Native checkboxes app-wide (the release/show "Private" toggle is the
   main one, but this covers any other checkbox too) — tints the browser's
   own control with the app's accent instead of leaving it default blue.
   No custom-built control, so focus/keyboard/screen-reader behavior is
   untouched. */
input[type="checkbox"]{accent-color:var(--accent)}

/* Search boxes (TODO.md item 117) — three drifted shapes converge to one.
   .help-center-search retired in favor of .form-input's own look directly
   (same radius/background now, so there's nothing left to differ); Admin/
   Filters/Release notes search fields already use plain .form-input, so
   they picked up the change above for free. .search-input (the header
   box) already used the same var(--card) background this converged onto
   below (item 121) — just gets the same radius for shape consistency with
   everything else.
   Follow-up (TODO.md item 121, 2026-09-16): .form-input's background went
   var(--bg) → var(--card) app-wide — the grey "recessed" look read as
   dated next to the header search's white. .help-center-search mirrors
   that here since it's a separate rule, not a shared class. Two effects,
   both intended: fields inside a white .panel/.modal now separate from
   their surface by border alone (var(--line) is faint, but this was a
   deliberate trade against the busier "white + stronger border" mockup
   option); fields with no .panel of their own — Release notes, Help
   Center — go from blending into var(--bg) invisibly to standing out as
   a white box on the page, same visual idiom the header search already
   used. Deliberately not var(--card) via a literal white: staying on the
   token keeps dark mode correct (var(--card) is a dark surface there, not
   a jarring white box) and keeps every field matched to whatever surface
   actually sits behind it. */
.help-center-search{background:var(--card);border-radius:var(--r-radius-sm)}
.search-input{border-radius:var(--r-radius-sm)}

/* Add forms (2026-09-26, the owner — TODO.md item 155): the top panel says
   typing searches too, and what it finds carries a fixed "Found on
   Discogs / Setlist.fm" label; "Already in Crate & Encore" is the other
   kind of match (after Add). */
.import-type-along{display:flex;gap:9px;align-items:flex-start;margin-top:12px;padding-top:10px;border-top:1px solid var(--line);font-size:12px;color:var(--ink-soft)}
.import-type-along svg{flex:none;width:15px;height:15px;margin-top:1px;stroke:var(--accent);fill:none;stroke-width:1.8;stroke-linecap:round}
.import-type-along strong{color:var(--ink);font-weight:600}
.match-src{display:inline-block;font-family:'IBM Plex Mono',monospace;font-size:9.5px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;color:var(--accent);background:var(--suggest-bg, rgba(22,48,92,0.07));border-radius:5px;padding:2px 6px;margin-right:4px;vertical-align:1px}
/* Add/Edit show sections (what, when, where, tours, title) — a hairline,
   no boxes, so the form stays calm. */
.form-grid.form-section{margin-bottom:0;padding:14px 0}
.show-type-row + .form-grid.form-section{padding-top:0}
.form-grid.form-section + .form-grid.form-section,
#festival-hint-slot + .form-grid.form-section{border-top:1px solid var(--line)}
.form-grid.form-section.release-first-section{padding-top:0}
.form-grid.form-section > .span-2 > .format-edit-row:first-child{margin-top:0}
/* Required fields (2026-09-26, the owner — mockup "Formulierregels" i A):
   the * on a field or on a group ("Where *"), and above the Add / Save
   button what is still missing while it's disabled. */
.req-group-note{font-family:'IBM Plex Sans',sans-serif;text-transform:none;letter-spacing:0;font-size:11.5px;font-weight:400;color:var(--ink-soft)}
.form-missing{font-size:12px;color:var(--warn-ink, #8a5a00);background:var(--warn-bg, #fff4d6);border-radius:8px;padding:7px 10px;margin:10px 0 12px} /* 12px: Add has only 4px above its buttons (Edit has 20, margins collapse) */
/* Private + the buttons + "* Required" form the form's own last section
   (2026-09-26, the owner): a hairline above Private, the legend under the
   buttons. */
.form-private-row{display:flex;align-items:center;gap:8px;font-size:13px;border-top:1px solid var(--line);padding-top:14px;margin-top:2px}
.form-required-legend{margin:10px 0 0}

/* Services page (TODO.md item 156) — Discogs, Setlist.fm, Ticketmaster. */
.services-lede{font-size:13px;color:var(--ink-soft);margin:0 0 18px;max-width:62ch}
.services-kind{font-family:'IBM Plex Mono',monospace;font-size:10.5px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--ink-soft);margin:8px 0 -4px}
.cs-body{margin-top:16px;padding-top:14px;border-top:1px solid var(--line)}
/* Artists → Want to see live: Tour alerts status, links to Services. */
.tour-status-line{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:12.5px;color:var(--ink-soft);border:1px solid var(--line);border-radius:9px;padding:8px 12px;margin:0 0 14px}
.tour-status-line strong{color:var(--ink);font-weight:600}
.tour-status-line .link-hint{margin-left:auto}
/* Add release: the less-used fields folded (TODO.md item 147 step 1a). */
.more-details-btn{width:100%;display:flex;align-items:center;gap:6px;border:1px dashed var(--line);border-radius:8px;background:none;padding:9px 12px;font:inherit;font-size:12.5px;color:var(--accent);cursor:pointer;text-align:left}
.more-details-btn small{color:var(--ink-soft);font-size:12px}
.more-details-btn span{margin-left:auto;font-size:15px}
.more-details-btn:hover{background:var(--bg-alt)}
.more-details-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Admin → Usage "Emails sent" (2026-09-26): kind · 30 days · all time. */
.filter-row.uc-email-row{display:grid;grid-template-columns:1fr 70px 70px;gap:10px;align-items:center}
.filter-row.uc-email-row > :not(:first-child){text-align:right}
.filter-row.uc-email-head{padding-bottom:4px}
/* Dashboard "Coming up": tour dates of artists you want to see (TODO.md
   item 157) under your own upcoming shows. */
.dash-tour-head{display:flex;align-items:center;gap:6px;margin:16px 0 4px;padding-top:12px;border-top:1px solid var(--line);font-family:'IBM Plex Mono',monospace;font-size:10px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--ink-soft)}
.dash-tour-head svg{width:14px;height:14px;fill:none;stroke:#e0843f;stroke-width:1.7}
.dash-tour-row{display:grid;grid-template-columns:44px 1fr auto;gap:12px;align-items:center;padding:8px 0;border-top:1px solid var(--line)}
.dash-tour-list .dash-tour-row:first-child{border-top:none}
.dash-tour-info{background:none;border:0;padding:0;margin:0;text-align:left;font:inherit;color:inherit;cursor:pointer;min-width:0}
.dash-tour-info strong{display:block;font-size:13.5px;font-weight:600}
.dash-tour-info small{display:block;font-size:12px;color:var(--ink-soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dash-tour-info:hover strong{text-decoration:underline}
.dash-tour-info:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
.dash-tour-more{font-size:12px;color:var(--ink-soft);margin-top:6px}
.dash-tour-acts{display:flex;align-items:center;gap:6px}
.icon-btn.dash-tour-skip{width:28px;height:28px;border-radius:7px;font-size:16px;color:var(--ink-soft)}
.tour-date.is-not-going .tour-date-block,.tour-date.is-not-going .tour-date-info{opacity:.45}
.icon-btn.dash-tour-skip svg{width:14px;height:14px}
.btn-outline.tour-date-go{padding:5px 11px;font-size:12px}
/* A text button that sits inside a sentence (2026-09-26) — .link-hint is a
   flex block and broke "Alerts via Ticketmaster: X (change)" onto two lines. */
.inline-link{display:inline;background:none;border:0;padding:0;margin:0;font:inherit;color:var(--accent);font-weight:600;cursor:pointer}
.inline-link:hover{text-decoration:underline}
.inline-link:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
.tour-match-change{color:var(--ink-soft)}
.tour-date-sect .tour-date-links{margin-top:8px}
.tour-date-sect > .tour-date-links:first-child{margin-top:0}
/* Tour date "not going" label, next to the place (option B). */
.tour-date-ng{font-family:'IBM Plex Mono',monospace;font-size:9.5px;text-transform:uppercase;letter-spacing:0.04em;background:var(--bg-alt);color:var(--ink-soft);border-radius:4px;padding:1px 6px;margin-left:4px;vertical-align:middle;opacity:1}
.tour-date-acts .year-filter{position:relative}
/* View picker at the end of the filter bar (2026-09-26, option A of
   "Werkbalk lijstpagina's"); the list switch sits under the title. */
.list-scope-toggle{margin:0 0 14px}
/* Filter bar, left to right (2026-09-26, the owner): View · Sort · Filters
   · Clear all — nothing pushed right. View and Sort are pills with a menu,
   like Filters; on a phone their menus go full width under the bar, the
   same as the Filters panel (keepDropdownsOnScreen()'s narrow branch). */
.fb-group{display:inline-flex;align-items:center;gap:8px;flex:none}
.fb-menu-wrap{position:relative}
.fb-menu-btn{font:inherit;font-size:13px;color:var(--ink);background:var(--card);border:1px solid var(--line);cursor:pointer;white-space:nowrap}
.year-filter-panel.fb-menu{min-width:200px;background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:var(--r-radius-sm);box-shadow:var(--r-shadow-2);padding:5px;box-sizing:border-box}
.fb-menu .hero-more-row svg{stroke:var(--ink-soft)}
.fb-menu .hero-more-row.is-current{font-weight:600}
.fb-menu-check{margin-left:auto;color:var(--accent);font-size:12px}
.filterbar .fb-sort{margin-left:0}
/* Room between Private and Cancel / Add when there's no match box between. */
.form-private-row{margin-bottom:14px}
/* Add show: the Setlist note sits under the line, above Private (the owner,
   2026-09-27) — the line moves from Private to the block both are in. */
.form-private-sect{border-top:1px solid var(--line);padding-top:14px;margin-top:2px}
.form-private-sect .form-private-row{border-top:0;padding-top:0;margin-top:0}
.form-setlist-note{margin:0 0 12px}
/* One weight in the filter bar: View, Sort and an unused Filters pill read
   the same; Filters only turns bold (and filled) once a filter is on. */
.filterbar .fbtn:not(.is-active){font-weight:400;font-size:13px}
/* List tabs under the title (2026-09-26, "Lijst-tabbladen" A). */
.list-tabs{display:flex;gap:26px;border-bottom:1px solid var(--line);margin:6px 0 16px;overflow-x:auto;scrollbar-width:none}
.list-tab{position:relative;background:none;border:0;padding:8px 0 10px;margin:0;cursor:pointer;white-space:nowrap;
  font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:17px;color:var(--ink-soft)}
.list-tab:hover{color:var(--ink)}
.list-tab.active{color:var(--ink)}
.list-tab.active::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:3px;border-radius:3px 3px 0 0;background:var(--accent)}
.list-tab:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
@media (max-width:480px){ .list-tabs{gap:20px} .list-tab{font-size:16px} }
/* Filter bar without its card (2026-09-26, "Lijst-tabbladen" B — on trial,
   the owner may want it back): only the pills remain, flush with the page's
   content edge so nothing floats; the list tabs above carry the weight. To
   revert: remove this rule. */
.filterbar{background:none;border:0;padding:0;border-radius:0;box-shadow:none}

/* ── The app's own select (TODO.md item 163) ─────────────────────────────
   appSelectHTML(): looks like every other .form-input, opens the filter
   bar's menu. The native <select> stays in the DOM as the value holder. */
.app-select{position:relative;display:block;min-width:0}
/* In a .field-flagwrap row (a field that can carry an "imported" flag, e.g.
   Country) it takes the row's width like a text field does. */
.field-flagwrap > .app-select{flex:1}
/* Artist fields (item 172): the field with its suggestion list, and the hint
   under it — hidden while you type in the field, shown once you leave it. */
.artist-suggest-wrap{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
/* Only while the text field itself has focus — not :focus-within, which also
   hid the hint the moment its own "Use …" button took focus on mousedown. */
.artist-suggest-wrap:has(.place-suggest-wrap input:focus) .artist-hint-slot{display:none}
.artist-hint-slot:empty{display:none}
/* Several names as a list (item 173): a show's acts (one line each, with
   role and tour) and a release's Featuring (chips), each in one box that
   looks like a field, with an "Add …" field at the end. */
.name-list-box{box-sizing:border-box;width:100%;border:1px solid var(--line);border-radius:var(--r-radius-sm, 8px);background:var(--card);padding:6px 8px;display:flex;flex-direction:column;gap:6px}
.name-list-box:focus-within{border-color:var(--accent)}
.name-list-box.chips-box{flex-direction:row;flex-wrap:wrap;align-items:center}
.act-line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:30px}
.name-chip{display:inline-flex;align-items:center;gap:4px;background:var(--bg-alt);border:1px solid transparent;border-radius:999px;padding:3px 4px 3px 11px;font-size:13.5px;color:var(--ink);max-width:100%}
.name-chip.warn{background:var(--warn-bg);border:1px dashed var(--warn-border);color:var(--warn-ink)}
.name-chip-x{border:none;background:none;color:var(--ink-soft);font-size:15px;line-height:1;padding:0 5px;cursor:pointer}
.name-chip-x:hover{color:var(--danger)}
.act-role{border:1px solid var(--line);border-radius:999px;background:var(--card);padding:3px 10px;font:inherit;font-size:11.5px;font-weight:600;color:var(--accent);cursor:pointer}
.act-tour{border:none;background:none;padding:0;font:inherit;font-size:12.5px;color:var(--ink);cursor:pointer;text-align:left}
.act-tour::before{content:"· ";color:var(--ink-soft)}
.act-tour-add{border:none;background:none;padding:0;font:inherit;font-size:12px;font-weight:600;color:var(--accent);cursor:pointer}
.act-tour-input{flex:1;min-width:140px;padding:5px 9px;font-size:12.5px}
.name-add{flex:1;min-width:140px}
.name-add-input{width:100%;box-sizing:border-box;border:none;outline:none;background:transparent;font:inherit;font-size:14px;color:var(--ink);padding:5px 2px}
.acts-box .act-line ~ .name-add{border-top:1px dashed var(--line);padding-top:4px}
.list-hint{margin:0 0 0 22px}
.chips-box + .list-hint, .list-hint + .list-hint{margin-top:6px}
.chips-box ~ .list-hint{margin-left:0}
.act-orphan{margin-top:6px}
.artist-hint-slot{display:flex;flex-direction:column;gap:4px}
.artist-hint-slot .festival-hint{margin:0}
.hint-btn{margin-left:6px;border:1px solid currentColor;border-radius:999px;padding:1px 9px;font:inherit;font-size:11.5px;font-weight:600;background:var(--card);color:inherit;cursor:pointer}
.hint-link{background:none;border:none;padding:0;margin-left:8px;font:inherit;font-size:11.5px;color:var(--ink-soft);text-decoration:underline;cursor:pointer}
.app-select-native{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;overflow:hidden;clip:rect(0 0 0 0)}
.app-select-btn{display:flex;align-items:center;gap:8px;width:100%;text-align:left;cursor:pointer;color:var(--ink);font:inherit;font-size:14px}
.app-select-btn .app-select-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.app-select-btn:disabled{opacity:.55;cursor:default}
.app-select-btn[aria-expanded="true"]{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent)}
.app-select-chev{flex:none;width:7px;height:7px;border-right:1.6px solid var(--ink-soft);border-bottom:1.6px solid var(--ink-soft);transform:rotate(45deg) translate(-2px,-2px)}
.app-select-panel{min-width:100%;width:max-content;max-width:min(340px,calc(100vw - 32px))}
.app-select-rows{max-height:min(320px,50vh);overflow-y:auto}
.app-select-row[disabled]{opacity:.45;cursor:default}
.app-select-row[disabled]:hover{background:none}
.app-select-row .app-select-label{overflow:hidden;text-overflow:ellipsis}
.app-select-search{width:100%;margin:0 0 5px;padding:7px 10px;font-size:13px}
.app-select-group{font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-soft);padding:8px 10px 2px}
.app-select-none{padding:8px 10px;font-size:12.5px;color:var(--ink-soft)}
.app-select-row.hidden,.app-select-group.hidden,.app-select-none.hidden{display:none}
/* Compact variant for pill-sized filter rows (Admin → Feedback) and dense tables. */
.app-select.compact .app-select-btn{padding:6px 10px;font-size:12.5px;font-weight:600}

/* In a filter bar the menu reads as a pill, like the bar's own Sort/View. */
.filterbar .app-select-btn{border-radius:999px;width:auto}

/* ── Country list + Venue/City suggestions (TODO.md item 160) ───────────── */
.country-flag{flex:none;width:20px;text-align:center;font-size:15px;line-height:1}
.place-suggest-wrap{position:relative;width:100%}
.place-suggest-panel{position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:40;background:var(--card);color:var(--ink);
  border:1px solid var(--line);border-radius:var(--r-radius-sm);box-shadow:var(--r-shadow-2);padding:5px;max-height:300px;overflow-y:auto}
.place-suggest-panel[hidden]{display:none}
.place-suggest-row{white-space:normal}
.place-suggest-row.is-active{background:var(--bg-alt)}
.place-suggest-main{display:flex;flex-direction:column;min-width:0}
.place-suggest-sub{font-size:11.5px;color:var(--ink-soft)}
.place-suggest-new{color:var(--accent);font-weight:600}

/* A searchable appSelectHTML() menu on a phone: a panel at the top of the
   visible screen, sized to what's above the keyboard (--vv-top/--vv-h). */
.app-select-backdrop{position:fixed;inset:0;z-index:199;background:rgba(10,16,28,.45)}
.app-select-panel.sheet{position:fixed;left:12px;right:12px;top:calc(var(--vv-top, 0px) + 12px);bottom:auto;
  height:calc(var(--vv-h, 100dvh) - 24px);max-width:none;width:auto;min-width:0;z-index:200;
  display:flex;flex-direction:column;padding:8px}
.app-select-panel.sheet .app-select-rows{flex:1;min-height:0;max-height:none}
.app-select-sheet-head{display:flex;align-items:center;justify-content:space-between;padding:2px 4px 8px;font-weight:700;font-size:14px}
.app-select-sheet-close{background:none;border:none;font-size:22px;line-height:1;color:var(--ink-soft);cursor:pointer;padding:2px 6px}
.app-select-panel.sheet .app-select-search{font-size:16px} /* ≥16px: iOS doesn't zoom in on focus */

/* The page's fade-up entry animation moves (transform) its content for a
   quarter second on every render — a fixed sheet inside would slide with it. */
.fade-up:has(.app-select-panel.sheet){animation:none}
