/* Alpine.js: verbergt elementen tot Alpine ze geïnitialiseerd heeft, zodat er
   geen onbewerkte {{ }}-achtige flits te zien is bij een trage verbinding. */
[x-cloak] { display: none !important; }

:root {
  --page-plane:      #0d0d0d;
  --surface-1:       #1a1a19;
  --surface-2:       #232322;
  --text-primary:    #ffffff;
  --text-secondary:  #c3c2b7;
  --text-muted:      #898781;
  --border-hairline: rgba(255, 255, 255, 0.10);
  --gridline:        #2c2c2a;
  --accent:          #3987e5;
  --status-good:     #0ca30c;
  --status-critical: #d03b3b;
  --status-warning:  #fab219;
  /* Standaard hetzelfde systeemlettertype als hieronder bij body — theme_css.php
     (zie admin/theme.php) overschrijft dit als de eigenaar een ander
     lettertype kiest; zonder die keuze verandert er dus niets. */
  --font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--page-plane);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
}

/* Sticky footer: alleen op pagina's die een .site-footer renderen (public.php,
   public_stream.php) — andere pagina's (dashboard.php, login.php, ...) delen
   dit stylesheet maar hebben geen footer, dus die blijven ongemoeid. De body
   wordt een kolom van minstens de volledige viewporthoogte; margin-top: auto
   op de footer duwt 'm naar beneden als de inhoud korter is dan het scherm,
   en laat 'm gewoon meestromen zodra de inhoud lang genoeg is. */
body:has(.site-footer) {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
body:has(.site-footer) .site-footer {
  margin-top: auto;
}

/* StreamTop-merkbanner als vaste (fixed) paginaachtergrond — blijft zichtbaar
   achter de kaarten terwijl je scrolt, niet alleen bovenin. Kaarten (.public-card
   e.d.) hebben zelf een ondoorzichtige achtergrond, dus de leesbaarheid van de
   inhoud verandert niet; alleen de ruimte eromheen toont de foto. Gebruikt op
   public.php/public_stream.php/dashboard.php — zie hun <body class="page-bg-banner">. */
body.page-bg-banner {
  background:
    linear-gradient(180deg, rgba(8, 12, 22, 0.6) 0%, rgba(8, 12, 22, 0.75) 100%),
    url('img/header-banner.jpg') center top / cover no-repeat fixed;
}

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

.container {
  max-width: 960px;
  margin: 0 auto;
  padding: 32px 20px 64px;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-hairline);
  background: var(--surface-1);
}

.topbar a { color: var(--text-primary); text-decoration: none; font-weight: 600; }
.topbar nav a { color: var(--text-secondary); font-weight: 400; margin-left: 20px; }
.topbar nav a:hover { color: var(--text-primary); }
.brand-link { text-decoration: none; }

/* Merkbeeld (icoon + wordmark), volledig in HTML/CSS — geen losse afbeelding */
.brand-mark { display: inline-flex; flex-direction: column; gap: 2px; }
.brand-mark-row { display: flex; align-items: center; gap: 10px; }
.brand-mark-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, #79bbff, #1c4e8c 75%);
  color: #fff;
  box-shadow: 0 2px 10px rgba(57, 135, 229, 0.5), inset 0 1px 2px rgba(255, 255, 255, 0.5);
  flex-shrink: 0;
}
.brand-mark-text {
  font-family: 'Poppins', system-ui, sans-serif;
  font-weight: 800;
  letter-spacing: -0.01em;
  white-space: nowrap;
  line-height: 1;
}
.bm-stream { color: #fff; }
.bm-pulse {
  background: linear-gradient(90deg, #6db8ff, #b79cf7);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.brand-mark-sm .brand-mark-text { font-size: 17px; }
.brand-mark-md .brand-mark-text { font-size: 23px; }
.brand-mark-lg .brand-mark-text { font-size: 30px; }
.brand-mark-tagline {
  font-family: 'Dancing Script', cursive;
  font-size: 17px;
  color: #a9cdf5;
  margin-left: 64px;
  margin-top: -4px;
}
.auth-brand { text-align: center; margin-bottom: 18px; }

/* Glanzende rechthoekige nav-knoppen (bijv. Inloggen/Registreren op de publieke pagina) */
.nav-btn {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 9px 18px;
  border-radius: 10px;
  font-weight: 700;
  font-size: 13px;
  font-family: inherit;
  text-decoration: none;
  color: #fff;
  border: 1px solid rgba(0, 0, 0, 0.4);
  position: relative;
  overflow: hidden;
  cursor: pointer;
  white-space: nowrap;
  box-shadow:
    0 3px 6px rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    inset 0 -8px 10px rgba(0, 0, 0, 0.2);
  transition: transform 0.12s ease, filter 0.12s ease;
}
.nav-btn::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 60%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.nav-btn:hover { transform: translateY(-2px); filter: brightness(1.08); color: #fff; }
.nav-btn-primary { background: linear-gradient(180deg, #6bb3ff 0%, #2f6fd6 55%, #1e4f8f 100%); }
.nav-btn-ghost { background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.04) 55%, rgba(255, 255, 255, 0.02)); color: #dce8fb; border-color: rgba(255, 255, 255, 0.3); }
/* flex-wrap:wrap is bewust toegevoegd (was er niet): zonder wrap bleven alle
   knoppen in ÉÉN ononderbroken rij staan (white-space:nowrap op .nav-btn, en
   een flex-item krimpt standaard niet onder zijn eigen inhoud), wat op de
   gewone paginaheaders met maar 2-4 knoppen toevallig nooit opviel maar bij
   het beheerpaneel (adminNavHtml() in includes/functions.php, tot 11
   knoppen) op een telefoon een eindeloze horizontaal scrollende rij
   veroorzaakte i.p.v. gewoon netjes af te breken naar een volgende regel. */
.header-auth { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

h1 { font-size: 22px; margin: 0 0 4px; }
h2 { font-size: 17px; margin: 0 0 12px; }
p.muted, .muted { color: var(--text-muted); font-size: 13px; }

.card {
  background: var(--surface-1);
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 16px;
}

/* width i.p.v. een vaste max-width: schaalt mee met (heel) brede schermen
   i.p.v. altijd hetzelfde aantal pixels te blijven, en blijft dankzij
   min()/94vw ook op een telefoon netjes binnen de rand. */
.auth-card {
  width: min(94vw, 720px);
  margin: 64px auto;
  padding: 48px;
}
/* Ruimere invoervelden dan de rest van de site (die delen dit stylesheet,
   bewust ongemoeid) — past beter bij deze bredere kaart. */
.auth-card .field input[type="text"],
.auth-card .field input[type="email"],
.auth-card .field input[type="password"] {
  padding: 14px 16px;
  font-size: 16px;
}
.auth-card .field label { font-size: 14px; }
.auth-card button[type="submit"] { padding: 13px 20px; font-size: 15px; }

/* Dun laadbalkje bovenaan zowat elke pagina — vult bij het laden van de
   pagina en opnieuw bij het versturen van een formulier (zie
   assets/page-progress.js, gedeeld door alle pagina's die dit gebruiken).
   Bewust nooit tot 100% bij versturen: de pagina navigeert normaal
   gesproken weg zodra de server klaar is. */
#page-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 0%;
  background: linear-gradient(90deg, var(--accent), #7c3aed);
  box-shadow: 0 0 8px rgba(57, 135, 229, 0.6);
  z-index: 9999;
  transition: width 0.25s ease, opacity 0.4s ease;
}
#page-progress.is-done { opacity: 0; }

/* Wachtwoordsterkte-balk onder het wachtwoordveld op register.php. */
.password-strength {
  height: 5px;
  border-radius: 3px;
  background: var(--surface-2);
  margin-top: 8px;
  overflow: hidden;
}
.password-strength-fill {
  height: 100%;
  width: 0%;
  border-radius: 3px;
  transition: width 0.25s ease, background-color 0.25s ease;
}
.password-strength-label {
  font-size: 11px;
  color: var(--text-muted);
  margin: 4px 0 0;
  min-height: 14px;
}

/* Kleinere kaarten naast elkaar in een grid i.p.v. onder elkaar (admin.php) —
   alleen grote kaarten (grafiek, grote tabellen) blijven los/vol-breed. */
.admin-card-grid { display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 24px; }
.admin-card-grid > .card { flex: 1 1 380px; min-width: 0; margin-bottom: 0; }
.admin-card-grid > .card table { display: block; overflow-x: auto; }

/* Gekleurde bovenrand per kaart (bijv. admin/analytics.php) — puur om elke
   sectie in één oogopslag uit elkaar te houden, zelfde kaart verder ongemoeid.
   Optioneel te gebruiken, dus geen effect op kaarten zonder deze klasse. */
.card-accent-blue   { border-top: 3px solid #3987e5; }
.card-accent-purple { border-top: 3px solid #7c3aed; }
.card-accent-teal   { border-top: 3px solid #17a398; }
.card-accent-orange { border-top: 3px solid #e5822f; }
.card-accent-pink   { border-top: 3px solid #e5399e; }
.card-accent-amber  { border-top: 3px solid #d1a219; }
.card-accent-green  { border-top: 3px solid #0ca30c; }
.card-accent-discord { border-top: 3px solid #5865f2; }

/* KPI-blokken (admin/users.php, admin/analytics.php) — op verzoek van de
   eigenaar ("mooier met meerdere blokken"): de belangrijkste kerncijfers
   bovenaan een beheerpagina niet als platte tekst of een pil-vormige chip,
   maar als eigen kaartje met kleuraccent + icoon, zodat ze in één oogopslag
   uit elkaar te houden zijn — hergebruikt bewust .card/.card-accent-*
   hieronder (zelfde achtergrond/rand/radius als de rest van het beheerpaneel)
   plus het bolletje-icoon van .stat-icon, i.p.v. een compleet nieuwe stijl. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 14px;
  margin-bottom: 24px;
}
.kpi-block {
  padding: 16px 18px;
  margin-bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.kpi-block-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.kpi-block-icon { width: 34px; height: 34px; }
.kpi-block-icon svg { width: 16px; height: 16px; }
.kpi-block-value {
  font-size: 25px;
  font-weight: 800;
  line-height: 1.15;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.kpi-block-label { font-size: 12px; color: var(--text-muted); }
/* Icoonkleur per kaart-accentkleur — zelfde 8 kleurverlopen als .stat-chip
   hierboven, nu gekoppeld aan de .card-accent-* klasse van het blok i.p.v.
   aan de n-de positie in de rij (zo blijft de kleur ook kloppen als een
   optioneel blok, bijv. "meeste bezoekers uit", er wel of niet bij staat). */
.card-accent-blue    .kpi-block-icon { background: linear-gradient(180deg, #6bb3ff, #2f6fd6 55%, #1e4f8f); }
.card-accent-purple  .kpi-block-icon { background: linear-gradient(180deg, #c9a6ff, #7c3aed 55%, #4c1d95); }
.card-accent-teal    .kpi-block-icon { background: linear-gradient(180deg, #7de6e0, #17a398 55%, #0b6158); }
.card-accent-orange  .kpi-block-icon { background: linear-gradient(180deg, #ffbb66, #e5822f 55%, #a85416); }
.card-accent-pink    .kpi-block-icon { background: linear-gradient(180deg, #ff9ecf, #e5399e 55%, #921f66); }
.card-accent-amber   .kpi-block-icon { background: linear-gradient(180deg, #ffe27a, #d1a219 55%, #8a6c11); }
.card-accent-green   .kpi-block-icon { background: linear-gradient(180deg, #7de07d, #2fb02f 55%, #158715); }
.card-accent-discord .kpi-block-icon { background: linear-gradient(180deg, #8b93ff, #5865f2 55%, #333aa8); }

/* Subtiele zebra/hover op tabellen binnen een kaart — maakt rijen iets
   makkelijker te volgen zonder fel te worden ("netter" i.p.v. drukker). */
.card table.plain tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.025); }
.card table.plain tbody tr:hover { background: rgba(255, 255, 255, 0.05); }

.field { margin-bottom: 14px; }
/* Twee .field's naast elkaar i.p.v. onder elkaar (bijv. verzoek-link +
   telefoonnummer) — flex-basis + wrap zorgt dat ze vanzelf weer onder elkaar
   vallen op een smal scherm, zonder aparte media query nodig te hebben. */
.field-row { display: flex; gap: 16px; flex-wrap: wrap; }
.field-row > .field { flex: 1 1 220px; min-width: 0; }
.field label { display: block; font-size: 13px; color: var(--text-secondary); margin-bottom: 4px; }
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field select,
.field textarea {
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--border-hairline);
  border-radius: 6px;
  font-size: 14px;
  background: var(--surface-2);
  color: var(--text-primary);
}
.field select { cursor: pointer; }
.field textarea { resize: vertical; font-family: inherit; }
.checkbox-field label { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-secondary); }
.checkbox-field input { width: auto; }

/* Categorieën: checkbox-grid i.p.v. een <select>, zodat je er tot 3 kunt
   kiezen (zie MAX_CATEGORIES_PER_STREAM). Uitgeschakelde checkboxen (limiet
   bereikt, zie de JS onderaan add_stream.php/edit_stream.php) worden
   visueel gedimd zodat duidelijk is waarom ze niet meer aan te vinken zijn. */
.category-check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 6px 12px;
  padding: 10px 12px;
  border: 1px solid var(--border-hairline);
  border-radius: 6px;
  background: var(--surface-2);
}
.category-check { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--text-secondary); cursor: pointer; }
.category-check input { width: auto; }
.category-check:has(input:disabled) { opacity: 0.4; cursor: not-allowed; }

button, .btn {
  display: inline-block;
  background: var(--accent);
  color: #fff;
  border: none;
  padding: 9px 16px;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}
button:hover, .btn:hover { opacity: 0.9; }
button:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-secondary { background: transparent; color: var(--text-secondary); border: 1px solid var(--border-hairline); }
/* Vrolijker, opvallende variant (i.p.v. de neutrale .btn-secondary) voor de
   "Stemmen, hits & deel-opties"-knop op dashboard.php/manage/edit.php —
   zelfde oranje verloop als .icon-megaphone, zodat het bij het icoon past. */
.btn-megaphone {
  background: linear-gradient(180deg, #ffc57a 0%, #f59e0b 55%, #b06b06 100%);
  color: #fff;
  border: 1px solid rgba(0, 0, 0, 0.35);
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.4), inset 0 -6px 8px rgba(0, 0, 0, 0.2);
}
.btn-megaphone:hover { filter: brightness(1.08); opacity: 1; }
.btn-megaphone svg { filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)); }
.btn-link { background: none; border: none; color: var(--text-muted); font-size: 12px; padding: 0; cursor: pointer; text-decoration: underline; font-weight: 400; }
.btn-link:hover { color: var(--text-primary); }
.btn-link-danger:hover { color: var(--status-critical); }

.errors { background: rgba(208, 59, 59, 0.15); border: 1px solid rgba(208, 59, 59, 0.4); color: #ff9b9b; border-radius: 8px; padding: 10px 14px; margin-bottom: 16px; font-size: 13px; }

/* Waarschuwing/melding in dezelfde stijl als .errors, maar amber i.p.v. rood
   — o.a. voor de "verifieer je e-mailadres"-banner op het dashboard. */
.notice-warning { background: rgba(245, 166, 35, 0.15); border: 1px solid rgba(245, 166, 35, 0.4); color: #f5c877; border-radius: 8px; padding: 12px 14px; margin-bottom: 16px; font-size: 13px; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.notice-warning form { margin: 0; }

/* Meldingen als losse "toast"-kaartjes, bovenaan gecentreerd i.p.v. een vast
   blok bovenaan de pagina — voor foutmeldingen (bv. bij opslaan) én
   bevestigingen (bv. "Opgeslagen!"). Gedeelde implementatie voor het hele
   systeem: zie showToast() in assets/toast.js — niet meer los per pagina. */
.toast-container {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 999;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: min(360px, calc(100vw - 32px));
  width: 100%;
  padding: 0 16px;
  box-sizing: border-box;
}
.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  box-sizing: border-box;
  background: var(--surface-2);
  border: 1px solid var(--border-hairline);
  border-left: 4px solid var(--accent);
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 13px;
  color: var(--text-primary);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  cursor: pointer;
  animation: toast-in 0.2s ease-out;
}
.toast.toast-success { border-left-color: var(--status-good); }
.toast.toast-error   { border-left-color: var(--status-critical); }
.toast.toast-out      { animation: toast-out 0.2s ease-in forwards; }
.toast-text { flex: 1; }
.toast-close {
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: var(--text-secondary);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
  opacity: 0.65;
}
.toast-close:hover { opacity: 1; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes toast-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-10px); }
}

.stream-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 0;
  border-bottom: 1px solid var(--gridline);
  gap: 16px;
}
.stream-row:last-child { border-bottom: none; }

.status-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: 8px;
  vertical-align: middle;
}
.status-dot.online { background: var(--status-good); box-shadow: 0 0 6px rgba(12, 163, 12, 0.6); }
.status-dot.offline { background: var(--status-critical); }

/* Uptime-tijdlijnbalk (stream_detail.php) — één balkje per dag, laatste 30
   dagen, zelfde idee als de uptimebalk van bekende statuspagina's
   (UptimeRobot/Cachet e.d.). Kleur per dag komt van het percentage online-
   metingen die dag; grijs/gestippeld = geen meting die dag. */
.uptime-bar { display: flex; gap: 2px; }
.uptime-seg {
  flex: 1 1 0;
  min-width: 3px;
  height: 28px;
  border-radius: 3px;
  cursor: default;
  transition: transform 0.12s ease;
}
.uptime-seg:hover { transform: scaleY(1.12); }
.uptime-good { background: linear-gradient(180deg, #7de07d 0%, var(--status-good) 100%); }
.uptime-warn { background: linear-gradient(180deg, #ffe27a 0%, var(--status-warning) 100%); }
.uptime-bad  { background: linear-gradient(180deg, #ff9a9a 0%, var(--status-critical) 100%); }
.uptime-none { background: var(--surface-2); border: 1px dashed var(--border-hairline); }

.stream-name a { color: var(--text-primary); text-decoration: none; font-weight: 600; }
.stream-name a:hover { color: var(--accent); }
.stream-meta { color: var(--text-secondary); font-size: 13px; margin-top: 2px; }
.stream-stats { text-align: right; font-size: 13px; color: var(--text-secondary); }
.stream-stats strong { color: var(--text-primary); font-size: 15px; }
.row-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 8px; }

.badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 4px;
  margin-left: 6px;
  vertical-align: middle;
}
.badge-public { background: rgba(12, 163, 12, 0.18); color: #6fdc6f; }
.badge-private { background: rgba(137, 135, 129, 0.2); color: var(--text-muted); }
.badge-type { background: rgba(57, 135, 229, 0.18); color: #8fbdf2; }
.badge-format { background: var(--surface-2); color: var(--text-secondary); }

/* Verkeersbron-badges (admin/analytics.php) — elke bron een herkenbare
   kleur i.p.v. platte tekst in de tabel. */
.badge-referrer-direct { background: var(--surface-2); color: var(--text-secondary); }
.badge-referrer-search { background: rgba(57, 135, 229, 0.18); color: #8fbdf2; }
.badge-referrer-social { background: rgba(229, 57, 158, 0.18); color: #f28fd0; }
.badge-referrer-site   { background: rgba(23, 163, 152, 0.18); color: #7de6e0; }
.badge-referrer-other  { background: rgba(137, 135, 129, 0.2); color: var(--text-muted); }

.empty-state { text-align: center; padding: 40px 0; color: var(--text-muted); }

table.plain { width: 100%; border-collapse: collapse; }
table.plain th, table.plain td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--gridline); font-size: 13px; }
table.plain th { color: var(--text-muted); font-weight: 500; }

/* Wrapper voor brede tabellen (admin/users.php, stream_detail.php) — bewust
   NIET display:block op de table zelf (dat breekt de kolomlayout en laat
   vrije tekst verticaal wrappen i.p.v. de tabel horizontaal te laten
   scrollen, zoals eerder deze sessie ontdekt op stream_detail.php). Deze div
   laat alleen de OMLIJSTING scrollen, de table erbinnen blijft gewoon een
   table. */
.table-scroll { width: 100%; overflow-x: auto; }
.table-scroll table.plain { min-width: max-content; }

/* Help-pagina (help.php): FAQ als native <details>/<summary> — geen extra JS
   nodig, alleen een likje verf zodat het bij de rest van de stijl past. */
.card details { border-top: 1px solid var(--gridline); padding-top: 10px; }
.card details:first-of-type { border-top: none; padding-top: 0; }
.card details + details { margin-top: 10px; }
.card details summary { color: var(--text-primary); cursor: pointer; font-weight: 600; }
.card details summary::marker { color: var(--accent); }
.card details[open] summary { margin-bottom: 4px; }

/* Publiek dashboard */
.public-header {
  position: sticky;
  top: 0;
  z-index: 40;
  overflow: hidden;
  background:
    repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.025) 0 2px, transparent 2px 42px),
    linear-gradient(120deg, #1a2f52 0%, #14213d 40%, #0a0f1c 100%);
  padding: 28px 20px 24px;
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.45);
}
/* De StreamTop-merkbanner als doorlopende paginaachtergrond (zie
   body.page-bg-banner hieronder) i.p.v. los in de header: zo is 'm ook
   zichtbaar achter de kaarten verderop op de pagina, niet alleen in de
   header-strook. De header zelf wordt op zo'n pagina transparant (met
   alleen zijn eigen donkere verloop) zodat dezelfde vaste achtergrond er
   naadloos doorheen loopt — geen aparte/dubbele foto, geen naad. */
body.page-bg-banner .public-header {
  background: linear-gradient(180deg, rgba(8, 12, 22, 0.35) 0%, rgba(8, 12, 22, 0.55) 100%);
  box-shadow: none;
}
body.page-bg-banner .public-header::before,
body.page-bg-banner .public-header::after {
  display: none;
}
.public-header::before,
.public-header::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  filter: blur(50px);
  pointer-events: none;
}
.public-header::before {
  width: 380px; height: 380px;
  background: radial-gradient(circle, rgba(57, 135, 229, 0.55), transparent 70%);
  top: -220px; left: 4%;
}
.public-header::after {
  width: 320px; height: 320px;
  background: radial-gradient(circle, rgba(124, 58, 237, 0.45), transparent 70%);
  top: -190px; right: 8%;
}
.public-header-bar {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--accent), #7c3aed, var(--status-good));
}
.public-header-inner {
  position: relative;
  max-width: 1220px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px;
}
.public-brand { text-decoration: none; }
.public-stats { display: flex; gap: 12px; flex-wrap: wrap; }
.stat-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  padding: 8px 16px 8px 8px;
  border-radius: 999px;
  backdrop-filter: blur(6px);
  text-decoration: none;
}
/* "Stream van de dag" (public.php) — klikbaar chipje, dus met een eigen
   goudige rand/gloed om 'm als iets bijzonders te laten voelen t.o.v. de
   twee neutrale statistiekjes ernaast. */
.stat-chip-sotd {
  border-color: rgba(229, 195, 57, 0.4);
  transition: transform 0.12s ease, border-color 0.12s ease;
}
.stat-chip-sotd:hover { transform: translateY(-1px); border-color: rgba(229, 195, 57, 0.7); }
.stat-icon-sotd { background: linear-gradient(180deg, #ffe27a 0%, #e5c339 55%, #ad8f1f 100%); }
.stat-chip-sotd-name {
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.stat-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  border: 1px solid rgba(0, 0, 0, 0.35);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), inset 0 -6px 8px rgba(0, 0, 0, 0.2);
}
.stat-icon::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 55%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0));
}
.stat-icon svg { position: relative; z-index: 1; width: 15px; height: 15px; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3)); }
.stat-chip:nth-child(1) .stat-icon { background: linear-gradient(180deg, #6bb3ff, #2f6fd6 55%, #1e4f8f); }
.stat-chip:nth-child(2) .stat-icon { background: linear-gradient(180deg, #7de07d, #2fb02f 55%, #158715); }
/* Chip 3 t/m 8: zelfde opzet, elk een eigen kleur — zodat een rij met meer
   dan 2 statistiekjes (bijv. admin/analytics.php) niet grotendeels grijs
   blijft. Werkt overal waar .stat-chip binnen dezelfde ouder staat. */
.stat-chip:nth-child(3) .stat-icon { background: linear-gradient(180deg, #c9a6ff, #7c3aed 55%, #4c1d95); }
.stat-chip:nth-child(4) .stat-icon { background: linear-gradient(180deg, #ffbb66, #e5822f 55%, #a85416); }
.stat-chip:nth-child(5) .stat-icon { background: linear-gradient(180deg, #7de6e0, #17a398 55%, #0b6158); }
.stat-chip:nth-child(6) .stat-icon { background: linear-gradient(180deg, #ff9ecf, #e5399e 55%, #921f66); }
.stat-chip:nth-child(7) .stat-icon { background: linear-gradient(180deg, #ffe27a, #d1a219 55%, #8a6c11); }
.stat-chip:nth-child(8) .stat-icon { background: linear-gradient(180deg, #ff9b8a, #e0503b 55%, #96301f); }
.stat-chip strong { display: block; font-size: 18px; line-height: 1.1; color: #fff; }
.stat-chip span { font-size: 11px; color: var(--text-muted); white-space: nowrap; }

/* Trendpijltje naast een stat-chip-getal (zie admin/analytics.php) — los van
   .stat-chip strong's witte tekstkleur, zodat op/neer/nieuw meteen opvalt. */
.trend-badge { display: inline-block; font-size: 12px; font-weight: 600; vertical-align: middle; margin-left: 2px; }
.trend-up   { color: var(--status-good); }
.trend-down { color: var(--status-critical); }
.trend-flat { color: var(--text-secondary); font-weight: 400; }

/* "Nu online"-paneel (zie admin/analytics.php) — pulserende live-stip en een
   iets prominentere kaartrand, op verzoek van de eigenaar ("groter/
   prominenter"). Kleur komt overeen met .badge-public hierboven. */
.live-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--status-good);
  box-shadow: 0 0 0 0 rgba(12, 163, 12, 0.55);
  animation: live-pulse 1.8s infinite;
}
@keyframes live-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(12, 163, 12, 0.55); }
  70%  { box-shadow: 0 0 0 8px rgba(12, 163, 12, 0); }
  100% { box-shadow: 0 0 0 0 rgba(12, 163, 12, 0); }
}
.live-visitors-card {
  border: 1px solid rgba(12, 163, 12, 0.35);
  background: linear-gradient(180deg, rgba(12, 163, 12, 0.07), transparent 65%);
}
/* Ingelogde bezoekers in "Nu online" — accountnaam als klikbare chip (naar
   admin/user_detail.php), en de hele rij iets opgelicht zodat ze tussen de
   gasten meteen opvallen. */
.live-row-user td { background: rgba(57, 135, 229, 0.07); }
.live-user-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px 3px 8px;
  border-radius: 999px;
  background: rgba(57, 135, 229, 0.16);
  border: 1px solid rgba(57, 135, 229, 0.4);
  color: #cfe2fb;
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.live-user-chip:hover { background: rgba(57, 135, 229, 0.26); color: #fff; }
.live-user-chip svg { width: 13px; height: 13px; flex: none; }
.live-user-role {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #ffd27a;
}
.live-user-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: rgba(57, 135, 229, 0.16);
  color: #cfe2fb;
  border: 1px solid rgba(57, 135, 229, 0.4);
  font-size: 12px;
}
.live-user-badge svg { width: 12px; height: 12px; }

.public-hero {
  max-width: 1220px;
  margin: 0 auto;
  padding: 22px 20px 4px;
  text-align: center;
}
.public-hero h1 {
  font-size: 26px;
  margin: 0 0 6px;
  background: linear-gradient(90deg, #fff, #9fc3ef);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.public-hero p { margin: 0; font-size: 14px; }

/* 3-koloms feature-strip onder de hero-tekst op public.php — één afgeronde
   kaart, kolommen gescheiden door een dunne lijn, elk klikbaar naar de
   bijbehorende plek (de streamslijst hieronder, registreren, of Topstations). */
.feature-strip {
  display: flex;
  max-width: 900px;
  margin: 18px auto 0;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 16px;
  overflow: hidden;
  text-align: left;
}
.feature-strip-item {
  flex: 1 1 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 14px;
  text-decoration: none;
  color: inherit;
  text-align: left;
  border-right: 1px solid rgba(255, 255, 255, 0.1);
  transition: background 0.15s ease;
}
.feature-strip-item:last-child { border-right: none; }
.feature-strip-item:hover { background: rgba(255, 255, 255, 0.05); }
.feature-strip-icon {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  border: 1px solid rgba(0, 0, 0, 0.35);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), inset 0 -8px 10px rgba(0, 0, 0, 0.2);
}
.feature-strip-icon::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 55%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.feature-strip-icon svg { position: relative; z-index: 1; width: 14px; height: 14px; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3)); }
.feature-strip-icon-blue   { background: linear-gradient(180deg, #6bb3ff 0%, #2f6fd6 55%, #1e4f8f 100%); }
.feature-strip-icon-orange { background: linear-gradient(180deg, #ffd873 0%, #f5a623 55%, #c9790e 100%); }
.feature-strip-icon-gold   { background: linear-gradient(180deg, #ffe27a 0%, #e5c339 55%, #ad8f1f 100%); }
.feature-strip-text { display: flex; flex-direction: column; gap: 0; min-width: 0; }
.feature-strip-title { font-size: 13px; font-weight: 800; color: #fff; white-space: nowrap; }
.feature-strip-sub { font-size: 11px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 620px) {
  .feature-strip { flex-direction: column; }
  .feature-strip-item { border-right: none; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
  .feature-strip-item:last-child { border-bottom: none; }

  /* login.php/register.php/forgot_password.php/reset_password.php/
     2fa_verify.php/verify_email.php delen deze kaart — op een telefoon mag
     de ruime desktop-padding wat minder zijn, en hoeft de kaart niet zo ver
     van de bovenkant te beginnen. */
  .auth-card { margin: 24px auto; padding: 24px; }

  /* Header (logo + statistiekjes + login/registreren) én footer staan op
     desktop bewust vastgeplakt (position:sticky) — daar is genoeg
     schermhoogte over. Op een telefoon wrapt de header-inhoud al snel naar
     2-3 regels, en samen met de vastgeplakte footer bleef er dan nauwelijks
     nog ruimte over voor de streamkaarten zelf. Op smalle schermen laten we
     ze daarom gewoon normaal meescrollen i.p.v. permanent op het scherm te
     blijven staan — de footer blijft via de flex-opmaak hierboven wel
     onderaan de pagina staan zodra de inhoud korter is dan het scherm. */
  .public-header { position: static; padding: 18px 16px 16px; }
  .site-footer { position: static; }

  /* Het beheerpaneel (adminNavHtml() in includes/functions.php) heeft tot 11
     navigatieknoppen in dezelfde .header-auth als de gewone paginaheaders
     (die er maar 2-4 hebben) — die knoppen wrappen dankzij flex-wrap
     hierboven nu netjes naar meerdere regels, maar op telefoonbreedte is de
     desktop-maat (9px/18px padding, 13px tekst) dan nog steeds onnodig groot
     voor zoveel knoppen tegelijk. Hier alleen verkleind, niet op de gewone
     headers (die blijven met 2-4 knoppen prima leesbaar op hun normale
     grootte). */
  .app-header .header-auth { justify-content: center; gap: 6px; }
  .app-header .nav-btn { padding: 6px 11px; font-size: 11.5px; }

  /* KPI-blokken (zie .kpi-grid/.kpi-block hierboven) — iets kleinere
     minimumbreedte/afstand/tekst zodat er op een telefoon nog 2 per rij
     passen i.p.v. steeds maar 1 breed blok. */
  .kpi-grid { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin-bottom: 18px; }
  .kpi-block { padding: 12px 14px; gap: 6px; }
  .kpi-block-value { font-size: 20px; }
}

.container:has(.public-grid) { max-width: 1220px; }

/* Bredere container speciaal voor admin/users.php (body.page-users) — met
   zoveel kolommen (Gebruiker/Woonplaats/Streams/Publiek limiet/Mail/
   Beveiliging/Rol/Laatste login/Geregistreerd/Opties) paste de tabel op de
   standaard .container-breedte (960px) bijna nooit zonder horizontale
   scrollbar. Samen met het verplaatsen van de losse rij-acties naar de
   "Opties"-modal (zie .modal-overlay#user-options-overlay in
   admin/users.php) scheelt dit genoeg om 'm op een gewoon/breed scherm
   zonder scrollbar te laten passen. Alleen op deze pagina, niet globaal op
   .container, want andere pagina's (auth, dashboard, ...) zijn bewust smal
   gehouden voor de leesbaarheid. */
.page-users .container { max-width: 1300px; }

/* Bredere Opties-modal, alleen op admin/users.php — sinds de modal ook een
   streams-lijst (naam + status-badge + URL-veld + Test/Opslaan-knoppen) laat
   zien is de standaard .modal-card-form-breedte (560px) te smal geworden om
   dat zonder gedrang te tonen. */
.page-users #user-options-overlay .modal-card { max-width: 720px; }

.site-footer {
  position: sticky;
  bottom: 0;
  z-index: 30;
  margin-top: 56px;
  /* Compact: dit is nu een permanent zichtbare, vastgeplakte balk (zie
     position:sticky hierboven) i.p.v. alleen een stuk onderaan de pagina,
     dus mag 'm niet te veel schermruimte blijven innemen. */
  padding: 8px 20px;
  border-top: 1px solid var(--border-hairline);
  background: var(--page-plane);
  box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.4);
  text-align: center;
}
.site-footer-inner {
  max-width: 1220px;
  margin: 0 auto;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 18px;
}
.site-footer-inner .public-brand { display: flex; align-items: center; }
.site-footer-links { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; }
.site-footer-copy { margin: 0; }
.site-footer-links a { color: var(--text-secondary); text-decoration: none; font-size: 12px; }
.site-footer-links a:hover { color: var(--text-primary); }
.site-footer-copy { font-size: 12px; color: var(--text-muted); margin: 0; }
/* Promotekst vóór de "Stream aanvragen"-modaltrigger (zie
   streamRequestModalHtml() in includes/functions.php) — staat als laatste
   item in de flex-rij .site-footer-links; flex-basis:100% duwt 'm naar een
   eigen volledige regel ónder de gewone navigatielinks i.p.v. ertussen. */
.footer-stream-promo {
  flex-basis: 100%;
  text-align: center;
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 4px;
}
.footer-stream-promo a { color: var(--text-secondary); text-decoration: underline; font-size: 12px; }
.footer-stream-promo a:hover { color: var(--text-primary); }
.public-filterbar {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.public-search,
.public-category-filter {
  padding: 9px 12px;
  border: 1px solid var(--border-hairline);
  border-radius: 8px;
  background: var(--surface-2);
  color: var(--text-primary);
  font-size: 14px;
}
.public-search { flex: 1 1 220px; min-width: 0; }
.public-category-filter { flex: 0 1 200px; cursor: pointer; }

.public-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  margin-top: 8px;
  /* CSS Grid rekt items standaard al uit tot de hoogte van hun rij (initial
     align-items:normal gedraagt zich als stretch binnen een grid) — hier
     expliciet gezet zodat kaarten met minder badges/knoppen (bijv. geen
     stemknop, of maar 1 categorie i.p.v. 3) toch even hoog blijven als hun
     buren in dezelfde rij, ongeacht hoeveel content ze hebben. */
  align-items: stretch;
}
@media (min-width: 620px) {
  .public-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1000px) {
  .public-grid { grid-template-columns: repeat(4, 1fr); }
}
.public-card {
  background: linear-gradient(180deg, var(--surface-1), var(--surface-2));
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  padding: 18px;
  position: relative;
  /* flex-column + margin-top:auto op .icon-row hieronder duwt de knoppenrij
     (en het telefoonnummer eronder) altijd naar de onderkant van de kaart —
     zonder dit zou een kaart met bijv. 3 categorie-badges (2 regels) de
     knoppen lager laten staan dan een buurkaart met maar 1 badge (1 regel),
     ook al zijn de kaarten zelf (dankzij align-items:stretch hierboven) even
     hoog. Zo staan de knoppenrijen altijd op gelijke hoogte, ongeacht hoeveel
     badges/stats een station heeft.  */
  display: flex;
  flex-direction: column;
  /* Bewust GEEN overflow:hidden meer: dat clipte het downloadmenu (.dl-menu-popup)
     halverwege af zodra het uit de kaart uitstak. De bovenbalk hieronder krijgt
     in plaats daarvan zelf afgeronde bovenhoeken, zodat die nog steeds netjes
     binnen de kaartrand blijft ondanks overflow:visible.
     Belangrijke bijwerking daarvan: overflow:hidden zorgde er in een CSS-grid
     óók (impliciet) voor dat een kaart mocht krimpen onder de breedte van zijn
     langste stukje tekst (de "nu speelt"-titel, die met white-space:nowrap
     nooit zelf afbreekt). Zonder overflow:hidden pakt de browser weer de
     content-breedte als minimum, waardoor kolommen ongelijk breed worden
     zodra een titel lang is — én de marquee-animatie niet meer aanslaat
     omdat de kaart dan toch "breed genoeg" lijkt. min-width:0 hieronder
     herstelt het krimpgedrag zonder dat overflow:hidden nodig is. */
  min-width: 0;
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.public-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  border-radius: 14px 14px 0 0;
  background: var(--gridline);
}
.public-card.is-online::before { background: linear-gradient(90deg, var(--status-good), var(--accent)); }
.public-card:hover { transform: translateY(-3px); box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4); border-color: rgba(255, 255, 255, 0.18); }

.pc-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.pc-avatar {
  position: relative;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
}
/* Het logo/fallback-icoon + glansoverlay zitten in een eigen binnenste
   cirkel (.pc-avatar-inner) die zelf overflow:hidden gebruikt om zich
   rond te knippen. Dat is bewust een APARTE laag van .pc-avatar zelf
   (die geen overflow:hidden meer heeft): zo kan het "nu speelt"-badge
   hieronder (.pc-avatar-cover) er gewoon overheen/buiten de rand steken
   zonder zelf ook afgesneden te worden door diezelfde cirkelclip. */
.pc-avatar-inner {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: linear-gradient(180deg, #6bb3ff 0%, #2f6fd6 55%, #1e4f8f 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  overflow: hidden;
  border: 1px solid rgba(0, 0, 0, 0.35);
  box-shadow:
    0 0 0 3px rgba(57, 135, 229, 0.15),
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    inset 0 -8px 10px rgba(0, 0, 0, 0.2);
}
.pc-avatar-inner::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 55%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.pc-avatar-inner svg { position: relative; z-index: 1; width: 20px; height: 20px; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3)); }
.pc-avatar-inner img { position: relative; z-index: 1; width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
.pc-avatar-lg { width: 56px; height: 56px; }
.pc-avatar-lg svg { width: 25px; height: 25px; }
.pc-avatar-xl { width: 64px; height: 64px; }
.pc-avatar-xl svg { width: 28px; height: 28px; }
/* Albumhoesje van het huidige nummer. Heeft het station een eigen logo (een
   echte <img> i.p.v. het ICON_HEADPHONES-fallback-icoon), dan mag dat logo
   herkenbaar blijven: het albumhoesje wordt dan een los "nu speelt"-badge
   rechtsboven in de hoek (tegenover de live-stip rechtsonder, dus geen
   overlap) i.p.v. het logo helemaal te bedekken. Zit als sibling van
   .pc-avatar-inner, dus buiten diens cirkelclip — vandaar dat 'ie gewoon
   over de rand mag steken. Hover vergroot 'm even, zodat je de hoes goed
   kunt bekijken zonder dat 'ie de kaart permanent groter maakt. Heeft een
   station GEEN eigen logo (alleen het fallback-icoon), dan is er niets te
   beschermen — dan vult het albumhoesje gewoon de hele cirkel, zoals
   voorheen (zie de :has()-override hieronder). */
.pc-avatar-cover {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 62%;
  height: 62%;
  object-fit: cover;
  border-radius: 50%;
  z-index: 2;
  border: 2px solid var(--surface-1);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.pc-avatar:hover .pc-avatar-cover {
  transform: scale(1.35);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.6);
  z-index: 4;
}
.pc-avatar:has(.pc-avatar-inner svg) .pc-avatar-cover {
  top: 0;
  right: 0;
  width: 100%;
  height: 100%;
  border: none;
  box-shadow: none;
}
.pc-avatar:has(.pc-avatar-inner svg):hover .pc-avatar-cover {
  transform: none;
  box-shadow: none;
}
.pc-live-dot {
  position: absolute;
  bottom: -1px;
  right: -1px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--status-good);
  border: 2px solid var(--surface-1);
  animation: live-pulse 2s infinite;
  z-index: 3;
}
@keyframes live-pulse {
  0% { box-shadow: 0 0 0 0 rgba(12, 163, 12, 0.55); }
  70% { box-shadow: 0 0 0 7px rgba(12, 163, 12, 0); }
  100% { box-shadow: 0 0 0 0 rgba(12, 163, 12, 0); }
}
.pc-headinfo { flex: 1; min-width: 0; }
.public-card-name { color: var(--text-primary); text-decoration: none; font-weight: 700; font-size: 15px; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.public-card-name:hover { color: var(--accent); }
/* Trofee-icoon bij de #1 van de ranglijst (zie ICON_TROPHY/public.php) —
   zonder deze regel zou de inline SVG (geen expliciete width/height, alleen
   een viewBox) op de browserstandaard van 300x150px renderen. */
.public-card-name svg { width: 14px; height: 14px; vertical-align: -2px; margin-right: 2px; color: #e5c339; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)); }
/* Zelfde trofee-icoon, hier gebruikt in de "populairste stream"-regel op admin.php. */
.top-stream-trophy svg { width: 16px; height: 16px; vertical-align: -3px; color: #e5c339; }
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 3px 10px 3px 8px;
  border-radius: 999px;
  margin-top: 3px;
  border: 1px solid transparent;
}
.status-pill::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.status-pill.online {
  background: rgba(12, 163, 12, 0.15);
  color: #6fdc6f;
  border-color: rgba(12, 163, 12, 0.35);
}
.status-pill.online::before { background: var(--status-good); animation: live-pulse 2s infinite; }
.status-pill.offline {
  background: rgba(137, 135, 129, 0.12);
  color: var(--text-muted);
  border-color: var(--border-hairline);
}
.status-pill.offline::before { background: var(--text-muted); }
.pc-listeners {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--surface-2);
  padding: 5px 10px;
  border-radius: 999px;
  flex-shrink: 0;
}
.pc-listeners svg { width: 13px; height: 13px; color: var(--accent); flex-shrink: 0; }
.public-card-badges { margin-bottom: 8px; }
.public-card-badges .badge { margin-left: 0; margin-right: 6px; }
.public-card-nowplaying { display: flex; align-items: center; gap: 6px; margin-bottom: 14px; min-height: 16px; }
.public-card-art { width: 18px; height: 18px; border-radius: 4px; object-fit: cover; flex: none; }
.public-card-title {
  font-size: 12px;
  color: var(--text-muted);
  min-height: 16px;
  margin-bottom: 0;
  flex: 1;
  min-width: 0;
}
.marquee-wrap { overflow: hidden; white-space: nowrap; position: relative; mask-image: linear-gradient(90deg, #000 92%, transparent); }
.marquee-track { display: inline-flex; will-change: transform; }
.marquee-track.animate { animation-name: marquee-scroll; animation-timing-function: linear; animation-iteration-count: infinite; }
.marquee-item { padding-right: 40px; }
@keyframes marquee-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
.icon-row { display: flex; gap: 9px; margin-top: 4px; flex-wrap: wrap; }
.icon-btn {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  padding: 0;
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 10px;
  position: relative;
  overflow: hidden;
  border: 1px solid rgba(0, 0, 0, 0.4);
  cursor: pointer;
  text-decoration: none;
  color: #fff;
  flex-shrink: 0;
  box-shadow:
    0 3px 6px rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    inset 0 -8px 10px rgba(0, 0, 0, 0.2);
  transition: transform 0.12s ease, filter 0.12s ease, box-shadow 0.12s ease;
}
.icon-btn::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 60%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.icon-btn svg { position: relative; z-index: 1; width: 16px; height: 16px; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)); }
/* Een geüpload icoon vult de HELE knop (absolute, inset:0, border-radius
   overgenomen van de knop) i.p.v. een klein plaatje middenin — daardoor is
   de gekleurde gradient-achtergrond van de knop volledig aan het zicht
   onttrokken en is, zoals gewenst, alleen nog het eigen icoon zichtbaar. */
.icon-btn img.icon-btn-img { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
.icon-btn:hover:not(:disabled) { transform: translateY(-2px) scale(1.04); filter: brightness(1.08); }
.icon-btn:active:not(:disabled) { transform: translateY(0) scale(0.97); }
.icon-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.icon-play { background: linear-gradient(180deg, #6bb3ff 0%, #2f6fd6 55%, #1e4f8f 100%); }
.icon-play.is-playing { background: linear-gradient(180deg, #6fe06f 0%, #1f9e1f 55%, #0b6e0b 100%); }
.icon-download { background: linear-gradient(180deg, #ffd873 0%, #f5a623 55%, #c9790e 100%); }
.icon-request { background: linear-gradient(180deg, #c3aefb 0%, #8b5cf6 55%, #6231c9 100%); }
.icon-phone { background: linear-gradient(180deg, #7de07d 0%, #2fb02f 55%, #158715 100%); }
.icon-whatsapp { background: linear-gradient(180deg, #6fe08a 0%, #25d366 55%, #0f7d38 100%); }
.icon-vote { background: linear-gradient(180deg, #ffe27a 0%, #ffc738 55%, #d69a06 100%); }
.icon-edit { background: linear-gradient(180deg, #8fd9ff 0%, #2f9fd6 55%, #1e6f8f 100%); }
.icon-toggle { background: linear-gradient(180deg, #c3aefb 0%, #8b5cf6 55%, #6231c9 100%); }
.icon-trash { background: linear-gradient(180deg, #ff9a9a 0%, #d6432f 55%, #8f1e1e 100%); }
.icon-refresh { background: linear-gradient(180deg, #7de07d 0%, #2fb02f 55%, #158715 100%); }
.icon-stats { background: linear-gradient(180deg, #7de0d9 0%, #2fb0a4 55%, #157a72 100%); }
.icon-vote-green { background: linear-gradient(180deg, #8fe89a 0%, #2fb050 55%, #157a30 100%); }
.icon-hits-out { background: linear-gradient(180deg, #ffb37a 0%, #f5822f 55%, #c9540e 100%); }
.icon-shield { background: linear-gradient(180deg, #a5b4fc 0%, #6366f1 55%, #4338ca 100%); }
.icon-megaphone { background: linear-gradient(180deg, #ffc57a 0%, #f59e0b 55%, #b06b06 100%); }
/* Volg-knop (bel-icoon) op public.php/public_stream.php — staat nu compact
   naast de stemknop i.p.v. in een eigen volle knoppenrij. Zelfde "actief =
   groen"-taal als .icon-play.is-playing zodra je een station daadwerkelijk
   volgt. */
.icon-follow { background: linear-gradient(180deg, #c3aefb 0%, #8b5cf6 55%, #6231c9 100%); }
.icon-follow.is-following { background: linear-gradient(180deg, #6fe06f 0%, #1f9e1f 55%, #0b6e0b 100%); }

/* Kleinere, niet-klikbare variant van .icon-btn voor icoonverwijzingen MIDDEN
   in lopende tekst (bijv. help.php) — dezelfde gekleurde badge-stijl als de
   normale 32px-knoppen, maar kleiner en op de tekstregel uitgelijnd i.p.v. als
   losse knop. Specificiteit .icon-btn.icon-chip (i.p.v. alleen .icon-chip) zorgt
   dat deze altijd wint van de basis .icon-btn-afmetingen, ongeacht bronvolgorde. */
.icon-btn.icon-chip {
  width: 22px;
  height: 22px;
  border-radius: 7px;
  vertical-align: -6px;
  margin: 0 1px;
  cursor: default;
}
.icon-btn.icon-chip svg { width: 12px; height: 12px; }
.icon-btn.icon-chip:hover { transform: none; filter: none; }

/* Gekleurde badge vóór een <h2>, gebruikt op de Help-pagina zodat de anders
   tekstzware secties ook wat kleur en visuele herkenning krijgen. */
.help-h2 { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.help-h2 .icon-btn { flex-shrink: 0; cursor: default; }
.help-h2 .icon-btn:hover { transform: none; filter: none; }

/* Eigen tooltips i.p.v. het trage/lelijke systeem-tooltipje van title="".
   Gebruik: data-tooltip="..." op het element zelf (badges, statustekst, ...),
   of — voor een .icon-btn/.nav-btn, die allebei al hun EIGEN ::after
   gebruiken voor het glansje én overflow:hidden hebben (dat zou de tooltip
   afsnijden) — op een omringende <span class="tooltip-wrap" data-tooltip="...">.
   Zet bij icoon-only knoppen/links altijd ook aria-label="..." op het element
   zelf; data-tooltip is puur visueel en wordt door screenreaders genegeerd.
   Puur CSS, geen JS nodig: de transition-delay hieronder (0.1s) is nog altijd
   een fractie van de ~1 seconde die browsers standaard voor title="" aanhouden.
   data-tooltip-pos="bottom" laat 'm onder het element openen i.p.v. erboven —
   voor elementen vlak boven in de pagina (bijv. de header), waar "erboven"
   van het scherm af zou vallen. */
.tooltip-wrap { position: relative; display: inline-flex; }
[data-tooltip]::after,
[data-tooltip]::before {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
  z-index: 60;
}
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--surface-2);
  color: var(--text-primary);
  border: 1px solid var(--border-hairline);
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
  width: max-content;
  max-width: 220px;
  text-align: center;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
}
[data-tooltip]::before {
  content: '';
  position: absolute;
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--surface-2);
}
[data-tooltip]:hover::after, [data-tooltip]:hover::before,
[data-tooltip]:focus-visible::after, [data-tooltip]:focus-visible::before {
  opacity: 1;
  visibility: visible;
  transition-delay: 0.1s;
}
[data-tooltip]:hover::after, [data-tooltip]:focus-visible::after {
  transform: translateX(-50%) translateY(0);
}
[data-tooltip-pos="bottom"]::after { bottom: auto; top: calc(100% + 9px); }
[data-tooltip-pos="bottom"]::before {
  bottom: auto;
  top: calc(100% + 4px);
  border-top-color: transparent;
  border-bottom-color: var(--surface-2);
}

/* Downloadknop met keuzemenu (.m3u / .pls) i.p.v. twee identieke icoontjes.
   Opent naar boven, gecentreerd op de knop — .public-card heeft bewust geen
   overflow:hidden (meer), dus dit menu wordt nooit meer halverwege afgesneden
   ongeacht waar de downloadknop in de icoonrij staat. */
.dl-menu { position: relative; }
.dl-menu-popup {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--surface-2);
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  padding: 6px;
  min-width: 200px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.dl-menu-popup a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  text-decoration: none;
}
.dl-menu-popup a:hover { background: rgba(255, 255, 255, 0.08); }
.dl-menu-popup a span { font-size: 11px; font-weight: 400; color: var(--text-muted); }
/* min-height houdt deze regel altijd even hoog, ook als 'ie leeg is (zie
   public.php: bewust altijd gerenderd, nooit weggelaten) — anders zou een
   kaart zonder telefoonnummer een fractie lager/hoger uitkomen dan een
   buurkaart mét, wat de van-onderen-uitgelijnde knoppenrij erboven weer
   scheeftrekt. */
.phone-line { font-size: 12px; color: var(--text-muted); margin-top: 8px; min-height: 15px; }

/* Stemknop-widget */
.vote-widget { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; position: relative; }
/* Binnen een .public-card (flex-column, zie hierboven) duwt dit de stemknop
   + knoppenrij + telefoonnummer altijd als groep naar de onderkant van de
   kaart, zodat ze bij elke kaart in een rij op dezelfde hoogte staan — de
   eventuele "extra ruimte" komt dan boven deze groep te staan (na de
   badges/stats), in plaats van dat de knoppen zelf verspringen. */
.public-card .vote-widget { margin-top: auto; padding-top: 10px; }
.vote-btn {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: inherit;
  font-weight: 700;
  font-size: 12px;
  color: #fff;
  border: 1px solid rgba(0, 0, 0, 0.4);
  border-radius: 999px;
  padding: 6px 14px 6px 10px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  /* Was geel/goud, maar dat las onduidelijk (leek meer op een waarschuwing
     dan een uitnodigende stemknop, en viel niet als "actie" te herkennen
     naast de blauwe afspeelknop). Nu dezelfde blauwe merkkleur als de
     primaire knoppen elders (bijv. .nav-btn-primary): duidelijk twee
     statussen — blauw = "stem nu", groen (.has-voted hieronder) = "gestemd". */
  background: linear-gradient(180deg, #6bb3ff 0%, #2f6fd6 55%, #1e4f8f 100%);
  box-shadow:
    0 3px 6px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    inset 0 -8px 10px rgba(0, 0, 0, 0.2);
  transition: transform 0.12s ease, filter 0.12s ease;
}
.vote-btn::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 60%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.vote-btn svg { width: 14px; height: 14px; position: relative; z-index: 1; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)); }
.vote-btn span { position: relative; z-index: 1; }
.vote-btn:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.08); }
.vote-btn:disabled { opacity: 0.6; cursor: default; }
.vote-btn.has-voted { background: linear-gradient(180deg, #7de07d 0%, #2fb02f 55%, #158715 100%); }
.vote-count { font-size: 12px; color: var(--text-secondary); font-weight: 600; }
.vote-msg {
  font-size: 11px;
  color: var(--text-muted);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.vote-msg.show { opacity: 1; }

/* Generiek modern dialoogvenster (bijv. "bedankt voor je stem" op public.php,
   en de stem-bevestiging op vote_in.php) — één overlay + kaart, herbruikbaar
   voor elke korte bevestigingsmelding. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 500;
}
.modal-card {
  background: var(--surface-1);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 16px;
  padding: 28px 26px 22px;
  max-width: 360px;
  width: 100%;
  text-align: center;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
}
/* Bredere, links uitgelijnde variant van .modal-card voor een modal met een
   formulier erin (bijv. de "Stream aanvragen"-modal, zie
   streamRequestModalHtml() in includes/functions.php) i.p.v. de smalle,
   gecentreerde bevestigingsdialoog (.modal-card zonder deze modifier). */
.modal-card-form { max-width: 560px; text-align: left; }
.modal-card-form .modal-title { text-align: left; }
.modal-card-form .modal-text { text-align: left; }
/* De confirmatie-modal (.modal-card zonder deze modifier) is altijd kort
   genoeg om verticaal gecentreerd te passen, maar een formuliermodal (bv.
   "Stream aanvragen") kan op een laag scherm (mobiel, of een kort browser-
   venster) hoger worden dan de viewport — zonder aparte regel hiervoor zou
   .modal-overlay's align-items:center 'm dan symmetrisch boven ÉN onder de
   viewport laten uitsteken zonder dat je naar het afgesneden bovenstuk kunt
   scrollen (een gecentreerde flex-child kan niet naar zijn eigen top
   scrollen). Dus alleen voor een overlay met een formuliermodal erin: boven
   uitlijnen + de overlay zelf laten scrollen i.p.v. centreren. */
.modal-overlay:has(.modal-card-form) {
  align-items: flex-start;
  overflow-y: auto;
  padding-block: 40px;
}
/* (Root oorzaak gevonden van een eerdere header-boven-modal-bug: de
   "Stream aanvragen"-modal zat in de HTML genest in .site-footer, en die
   footer heeft zelf position:sticky + z-index:30 — dat maakt .site-footer
   een eigen stacking context, waardoor de geneste modal (position:fixed;
   z-index:500) alleen met díe 30 meetelt in de paginabrede stapeling, ver
   onder de header (z-index:40). Geen CSS-regel lost dat structureel op
   zolang de modal daar genest blijft — daarom wordt 'ie nu met Alpine's
   x-teleport="body" (zie streamRequestModal(), includes/functions.php)
   simpelweg buiten de footer geplaatst, direct als kind van <body>, zodat
   'ie gewoon in de root-stapeling meedoet. Zie git-geschiedenis voor de
   eerder geprobeerde (en ontoereikende) position:static-workaround.) */
/* Kleine ronde laadspinner — gebruikt in de "we controleren je stream-URL"-
   modal bij Stream toevoegen/bewerken (zie manage/add.php, manage/edit.php),
   binnenin dezelfde generieke .modal-overlay/.modal-card hierboven. */
.spinner {
  width: 40px;
  height: 40px;
  margin: 0 auto 14px;
  border: 4px solid rgba(255, 255, 255, 0.15);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spinner-rotate 0.8s linear infinite;
}
@keyframes spinner-rotate {
  to { transform: rotate(360deg); }
}

.modal-icon {
  position: relative;
  overflow: hidden;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 14px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), inset 0 -8px 10px rgba(0, 0, 0, 0.2), 0 4px 10px rgba(0, 0, 0, 0.4);
}
.modal-icon::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 55%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.modal-icon svg { position: relative; z-index: 1; width: 26px; height: 26px; color: #fff; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)); }
.modal-icon-good { background: linear-gradient(180deg, #7de07d 0%, #2fb02f 55%, #158715 100%); }
.modal-icon-warn { background: linear-gradient(180deg, #ffe27a 0%, var(--status-warning) 55%, #ad8f1f 100%); }
.modal-title { font-size: 18px; font-weight: 800; margin: 0 0 8px; color: #fff; }
.modal-text { font-size: 13px; line-height: 1.5; color: var(--text-secondary); margin: 0 0 20px; }
.modal-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

/* Admin-dashboard (ingelogd) — herbruikt .public-header/.public-card in compactere vorm */
.app-header { padding: 18px 20px 16px; }
.app-header .header-auth { gap: 8px; }
.admin-intro { max-width: 1220px; margin: 0 auto; padding: 24px 20px 0; }
.admin-card-stats { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; align-items: center; }
.admin-card-stats strong { color: #fff; font-size: 14px; }
.admin-card-stats svg { width: 12px; height: 12px; vertical-align: -1px; margin-right: 1px; color: var(--text-muted); }
/* "in" en "uit" horen bij elkaar: als groep laten wrappen i.p.v. los, zodat
   ze nooit los van elkaar op een eigen regel terechtkomen (zie public.php). */
.stat-inout-group { display: inline-flex; gap: 12px; flex-wrap: nowrap; white-space: nowrap; }
.admin-card-link { display: block; font-size: 11px; margin-bottom: 10px; text-decoration: none; }
.admin-card-link:hover { color: var(--text-primary); }

/* Compacte cijfer-grid i.p.v. losse tekstregels (dashboard.php streamkaarten). */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(70px, 1fr)); gap: 6px; margin-bottom: 8px; }
.stat-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  background: var(--surface-2, rgba(255, 255, 255, 0.04));
  border: 1px solid var(--border-hairline);
  border-radius: 8px;
  padding: 7px 4px 6px;
}
.stat-cell svg { width: 13px; height: 13px; color: var(--text-muted); margin: 0; }
.stat-cell strong { display: block; font-size: 15px; line-height: 1.15; color: #fff; }
.stat-cell span { font-size: 10px; line-height: 1.2; color: var(--text-muted); white-space: nowrap; }
.stat-grid-caption { font-size: 11px; margin: -2px 0 10px; }
/* margin-top:auto (i.p.v. een vaste waarde) duwt de knoppenrij altijd naar de
   onderkant van de kaart — zelfde truc als .public-card .vote-widget hierboven.
   Zonder dit stond de knoppenrij op een kaart met extra statregels (bijv. de
   "beluisterd (24u)"-regel) lager dan op een buurkaart met minder stats, ook al
   zijn de kaarten zelf (dankzij .public-grid's align-items:stretch) even hoog. */
.admin-actions { margin-top: auto; padding-top: 10px; }

/* Zwevende "nu speelt"-kaart, rechtsboven, zoals een FM-ontvangst-widget */
.now-playing-card {
  position: fixed;
  bottom: 16px;
  right: 16px;
  width: 300px;
  max-width: calc(100vw - 24px);
  background: linear-gradient(160deg, #1c3a63, #0f1f38);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  padding: 14px 16px 16px;
  box-shadow: 0 14px 32px rgba(0, 0, 0, 0.5);
  z-index: 200;
}
.np-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #8fbdf2;
  text-transform: uppercase;
  margin-bottom: 6px;
}
.np-body { display: flex; align-items: center; gap: 10px; }
.np-art {
  position: relative;
  width: 56px;
  height: 56px;
  flex: none;
  border-radius: 10px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
}
.np-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.np-art .np-art-icon { color: #8fbdf2; width: 22px; height: 22px; }
.np-art .np-art-icon svg { width: 100%; height: 100%; }
/* Draaiend rondje over de hoes/het logo zolang de stream laadt (zie
   $store.player.loading in public.php). */
.np-art-spinner {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(8, 18, 34, 0.55);
}
.np-art-spinner .btn-spinner { width: 22px; height: 22px; border-width: 3px; }
.np-status.is-loading { color: #8fbdf2; }
.np-text { min-width: 0; flex: 1; }
.np-name { font-size: 19px; font-weight: 800; color: #fff; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.np-track { font-size: 13px; color: #b9d2f0; margin-top: 2px; min-height: 17px; }
.np-controls { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.np-status { flex: 1; font-size: 13px; font-weight: 600; color: var(--text-muted); }
.np-status.is-live { color: #7be27b; }
.np-stop {
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: var(--text-secondary);
  font-size: 12px;
  font-weight: 600;
  padding: 5px 12px;
  border-radius: 999px;
  cursor: pointer;
  /* Zelfde reden als bij .np-popout hieronder: zonder overflow:hidden kan de
     rechthoekige tap-highlight buiten deze pilvorm uitsteken op mobiel. */
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}
.np-stop:hover { color: #fff; border-color: rgba(255, 255, 255, 0.4); }
.np-stop:active { background: rgba(255, 255, 255, 0.12); }
/* Losse "open in eigen venster"-knop op de zwevende "nu speelt"-kaart — bewust
   subtieler dan de blauwe hp-toggle (play/pauze) en de Stop-knop, dit is een
   secundaire actie. */
.np-popout {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 50%;
  color: var(--text-secondary);
  cursor: pointer;
  flex-shrink: 0;
  padding: 0;
  /* Zonder overflow:hidden negeert de browser de border-radius bij het
     tekenen van de standaard tap-highlight (mobiel/touch) — die highlight is
     altijd een rechthoek, dus die stak buiten de cirkel uit en zag er zo uit
     als een "blauw vierkant" op deze knop i.p.v. een subtiel grijs randje
     (bug gemeld door gebruiker). .icon-btn (play/download/aanvraag-knoppen)
     had dit al en toonde het probleem daarom niet. -webkit-tap-highlight-
     color:transparent zet 'm helemaal uit; de :active-regel hieronder geeft
     in plaats daarvan een eigen, subtiele druk-feedback terug. */
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}
.np-popout:hover { color: #fff; border-color: rgba(255, 255, 255, 0.4); }
.np-popout:active { background: rgba(255, 255, 255, 0.12); }
.np-popout svg { width: 13px; height: 13px; }
.now-playing-card audio { width: 100%; margin-top: 12px; border-radius: 8px; }

/* Klein laad-rondje — op de play-knoppen (.icon-play op public.php/
   public_stream.php/widget_stream.php), de play/pauze-knop in de zwevende
   speler en over de hoes daar. Op een .icon-btn staat 'ie standaard
   verborgen en verschijnt 'ie (i.p.v. het play/pauze-icoon) zodra de knop
   .is-loading krijgt; elders regelt x-show de zichtbaarheid. Hergebruikt
   de bestaande spinner-rotate-keyframes van .spinner. */
.btn-spinner {
  display: inline-block;
  width: 16px;
  height: 16px;
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spinner-rotate 0.7s linear infinite;
  position: relative;
  z-index: 1;
  flex: none;
}
.icon-btn > .btn-spinner { display: none; }
.icon-btn.is-loading > .btn-spinner { display: block; }
.icon-btn.is-loading > :not(.btn-spinner) { display: none !important; }
.icon-btn.is-loading { cursor: progress; }
.hp-toggle { display: inline-flex; align-items: center; justify-content: center; }
.hp-toggle .btn-spinner { width: 14px; height: 14px; }

/* Nette 404-pagina (zie renderNotFoundPage() in includes/functions.php) —
   voor stations die niet (meer) bestaan en voor elke onbekende URL. */
.notfound-card {
  width: min(94vw, 760px);
  margin: 56px auto;
  padding: 44px 40px 36px;
  text-align: center;
  background: linear-gradient(180deg, rgba(57, 135, 229, 0.10), transparent 55%), var(--surface-1);
}
.notfound-visual {
  position: relative;
  width: 132px;
  height: 132px;
  margin: 0 auto 18px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.notfound-icon {
  position: relative;
  z-index: 1;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: linear-gradient(180deg, #6bb3ff 0%, #2f6fd6 55%, #1e4f8f 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 10px 24px rgba(47, 111, 214, 0.35);
  filter: grayscale(0.35);
}
.notfound-icon svg { width: 34px; height: 34px; }
/* "Zoekend signaal": ringen die vanuit het icoon uitdijen en wegvagen. */
.notfound-wave {
  position: absolute;
  inset: 30px;
  border-radius: 50%;
  border: 2px solid rgba(143, 189, 242, 0.45);
  animation: notfound-wave 2.6s ease-out infinite;
}
.notfound-wave-2 { animation-delay: 0.85s; }
.notfound-wave-3 { animation-delay: 1.7s; }
@keyframes notfound-wave {
  0%   { transform: scale(0.9); opacity: 0.9; }
  100% { transform: scale(1.9); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .notfound-wave { animation: none; opacity: 0.25; }
}
.notfound-code {
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8fbdf2;
}
.notfound-title { font-size: 28px; font-weight: 800; margin: 0 0 10px; text-wrap: balance; }
.notfound-text { max-width: 480px; margin: 0 auto 24px; color: var(--text-secondary); font-size: 15px; }
.notfound-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.notfound-actions .btn { padding: 11px 20px; }
.notfound-suggest {
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid var(--border-hairline);
  text-align: left;
}
.notfound-suggest h2 { font-size: 15px; margin: 0 0 14px; color: var(--text-secondary); }
.notfound-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 10px;
}
.notfound-station {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border-hairline);
  color: var(--text-primary);
  text-decoration: none;
  transition: border-color 0.15s, transform 0.15s;
  min-width: 0;
}
.notfound-station:hover { border-color: rgba(143, 189, 242, 0.5); transform: translateY(-1px); }
.notfound-station-avatar {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 8px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.06);
  color: #8fbdf2;
}
.notfound-station-avatar img { width: 100%; height: 100%; object-fit: cover; }
.notfound-station-avatar svg { width: 20px; height: 20px; }
.notfound-station-info { min-width: 0; display: flex; flex-direction: column; }
.notfound-station-info strong { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.notfound-station-live { font-size: 12px; color: #7be27b; display: flex; align-items: center; gap: 6px; }
.notfound-station-live .live-dot { width: 7px; height: 7px; }
.notfound-station-off { font-size: 12px; color: var(--text-muted); }
@media (max-width: 620px) {
  .notfound-card { margin: 28px auto; padding: 32px 20px 26px; }
  .notfound-title { font-size: 23px; }
}

/* Cookiemelding (cookieNoticeHtml(), includes/functions.php) op de publieke,
   niet-ingelogde pagina's — bewust linksonder i.p.v. gecentreerd/rechts, zodat
   'ie niet over de rechtsonder "nu speelt"-kaart (.now-playing-card hierboven)
   heen valt op public.php/public_stream.php. */
.cookie-notice {
  display: none;
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: 480;
  max-width: 380px;
  background: var(--surface-1);
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  padding: 14px 16px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.4);
}
.cookie-notice.is-visible { display: block; }
.cookie-notice p { margin: 0 0 10px; font-size: 12.5px; line-height: 1.5; color: var(--text-secondary); }
.cookie-notice a { color: var(--accent); }
.cookie-notice-actions { display: flex; justify-content: flex-end; }
@media (max-width: 620px) {
  .cookie-notice { left: 10px; right: 10px; max-width: none; bottom: 10px; }
}

/* Header-speler met animatie (EQ-balkjes), altijd zichtbaar zodra iets speelt */
.header-player {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface-1);
  border: 1px solid var(--border-hairline);
  border-radius: 999px;
  padding: 6px 16px 6px 6px;
}
.hp-toggle {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  padding: 0;
  margin: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: linear-gradient(180deg, #6bb3ff 0%, #2f6fd6 55%, #1e4f8f 100%);
  color: #fff;
  border: 1px solid rgba(0, 0, 0, 0.35);
  cursor: pointer;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), inset 0 -6px 8px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.4);
}
.hp-toggle::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 55%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.hp-info { display: flex; flex-direction: column; line-height: 1.2; }
.hp-name { font-weight: 700; font-size: 13px; color: var(--text-primary); max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#hp-format { margin: 0 0 2px; align-self: flex-start; }

.eq {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 18px;
  padding: 2px 3px;
  background: rgba(0, 0, 0, 0.35);
  border-radius: 3px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
}
.eq span {
  display: block;
  width: 3px;
  background: linear-gradient(0deg, #37e37a 0%, #37e37a 55%, #ffd24d 78%, #ff5050 100%);
  border-radius: 1px;
  height: 4px;
  animation: eq-bounce 1s ease-in-out infinite;
  animation-play-state: paused;
}
.eq.playing span { animation-play-state: running; }
.eq span:nth-child(1) { animation-delay: 0s; }
.eq span:nth-child(2) { animation-delay: 0.15s; }
.eq span:nth-child(3) { animation-delay: 0.3s; }
.eq span:nth-child(4) { animation-delay: 0.45s; }
@keyframes eq-bounce {
  0%, 100% { height: 4px; }
  50% { height: 16px; }
}

/* Topstations-ranglijst (top.php). */

/* .public-hero h1 gebruikt background-clip:text + color:transparent voor het
   verlooptitel-effect — een SVG met fill="currentColor" zou daardoor onzichtbaar
   worden, dus krijgt het icoon hier een eigen, ondoorzichtige kleur terug. */
.hero-icon {
  display: inline-flex;
  vertical-align: -4px;
  color: #e5c339;
}
.hero-icon svg { width: 24px; height: 24px; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4)); }

.leaderboard-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 8px;
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
  transition: background 0.12s ease;
}
.leaderboard-row:hover { background: var(--surface-2); }
.leaderboard-row + .leaderboard-row { border-top: 1px solid var(--border-hairline); }

.leaderboard-rank {
  flex: 0 0 30px;
  text-align: center;
  font-weight: 700;
  font-size: 14px;
  color: var(--text-muted);
}

/* Zelfde ronde-badge-opbouw (gradient + glansoverlay) als .icon-btn/.pc-avatar,
   maar met een medaillekleur per positie i.p.v. een functietype. */
.medal {
  position: relative;
  flex: 0 0 30px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45), inset 0 -6px 8px rgba(0, 0, 0, 0.2), 0 2px 5px rgba(0, 0, 0, 0.35);
}
.medal::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 55%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.medal svg { position: relative; z-index: 1; width: 15px; height: 15px; color: #fff; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)); }
.medal-gold   { background: linear-gradient(180deg, #ffe27a 0%, #e5c339 55%, #ad8f1f 100%); }
.medal-silver { background: linear-gradient(180deg, #f1f3f5 0%, #b9c2cc 55%, #85909c 100%); }
.medal-bronze { background: linear-gradient(180deg, #e3a26b 0%, #c47a3f 55%, #8f5726 100%); }

.pc-avatar.pc-avatar { flex: 0 0 auto; }

.leaderboard-info { flex: 1; min-width: 0; }
.leaderboard-name {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-weight: 700;
  font-size: 14px;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.leaderboard-name .status-pill { margin-top: 0; }
.leaderboard-meta { margin-top: 4px; }
.leaderboard-meta .badge { margin-left: 0; margin-right: 6px; }

.leaderboard-stats {
  display: flex;
  gap: 14px;
  flex-shrink: 0;
}
.leaderboard-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  cursor: default;
}
.leaderboard-stat strong {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-primary);
  text-transform: none;
  letter-spacing: normal;
}

@media (max-width: 560px) {
  .leaderboard-stats { display: none; }
}

/* Statistieken (admin/analytics.php, body.page-analytics) op een telefoon —
   bewust gescoped op deze pagina i.p.v. globaal op .container/.card, zodat
   andere schermen die dit stylesheet delen ongemoeid blijven. Zorgt voor:
   soepel vegen door brede tabellen op iOS, iets minder opvulling zodat er
   meer breedte overblijft voor tabellen/grafieken, en compactere KPI-blokken
   zodat er niet meteen maar 1 per regel past. */
.page-analytics .table-scroll { -webkit-overflow-scrolling: touch; }

@media (max-width: 480px) {
  .page-analytics .container { padding: 24px 12px 48px; }
  .page-analytics .card { padding: 14px; }
  .page-analytics .admin-card-grid { gap: 14px; }
  .page-analytics .admin-intro { padding: 20px 14px 0; }
  .page-analytics .kpi-grid { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
  .page-analytics .kpi-block { padding: 10px 12px; gap: 6px; }
  .page-analytics .kpi-block-icon { width: 26px; height: 26px; }
  .page-analytics .kpi-block-icon svg { width: 12px; height: 12px; }
  .page-analytics .kpi-block-value { font-size: 17px; }
  .page-analytics .live-visitors-card h2 { font-size: 15px; }
  /* Titel + teller mogen op een telefoon vóór het "ververst automatisch"-
     tekstje blijven staan; de datum-/periodeknoppen bovenaan de pagina
     worden op deze breedte anders al snel te dicht op elkaar. */
  .page-analytics .admin-intro > div { gap: 6px; }
  .page-analytics .admin-intro .btn-secondary { padding: 6px 11px; font-size: 13px; }
}

/* Donkere variant van het laad-rondje (op lichte/neutrale ondergrond, bv.
   in badges en muted tekst). */
.btn-spinner-dark { width: 12px; height: 12px; border-color: rgba(143, 189, 242, 0.25); border-top-color: #8fbdf2; vertical-align: -2px; }

/* "Werkt je stream niet? Zoek automatisch een werkende URL" onder het
   stream-URL-veld (manage/add.php, manage/edit.php) — zie
   streamUrlSuggestionsHtml() in includes/stream_suggest.php. */
.url-suggest { margin-top: 8px; }
.url-suggest .url-suggest-find { font-size: 12px; }
.url-suggest-results:empty { display: none; }
.url-suggest-results {
  margin-bottom: 6px;
  padding: 10px 12px;
  border-radius: 8px;
  background: rgba(12, 163, 12, 0.07);
  border: 1px solid rgba(12, 163, 12, 0.35);
}
.url-suggest-title { margin: 0 0 6px; font-size: 13px; color: var(--text-secondary); }
.url-suggest-title.url-suggest-bad { color: #f5c877; margin: 0; }
.url-suggest-results:has(.url-suggest-bad) { background: rgba(245, 166, 35, 0.08); border-color: rgba(245, 166, 35, 0.35); }
.url-suggest ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.url-suggest li { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12px; }
.url-suggest li code { color: #7be27b; word-break: break-all; }
.url-suggest li .muted { font-size: 11px; }
.url-suggest li button { padding: 4px 10px; font-size: 12px; margin-left: auto; }

/* Streamcontrole (admin/stream_health.php). */
.health-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.health-toolbar .muted { font-size: 12px; }
.health-table td { vertical-align: top; }
.health-url {
  width: 100%;
  min-width: 220px;
  padding: 6px 8px;
  font-size: 12px;
  font-family: Consolas, "SFMono-Regular", monospace;
  background: var(--surface-2);
  border: 1px solid var(--border-hairline);
  border-radius: 6px;
  color: var(--text-primary);
}
.health-detail { font-size: 11px; margin-top: 4px; max-width: 260px; }
.health-badge { white-space: nowrap; display: inline-flex; align-items: center; gap: 5px; }
.health-badge-bad { color: #f28b82 !important; border-color: rgba(242, 139, 130, 0.4) !important; }
.health-btn { padding: 4px 10px !important; font-size: 12px !important; text-decoration: none; display: inline-block; margin: 0 2px 4px 0; }
.health-suggest { font-size: 12px; margin-top: 6px; }
.health-suggest:empty { display: none; }
.health-suggest-title { color: #7be27b; font-weight: 600; margin-bottom: 3px; }
.health-suggest-item { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 4px; }
.health-suggest-item code { color: #7be27b; word-break: break-all; font-size: 11px; }
.health-suggest-item .muted { font-size: 11px; }
.health-apply { padding: 2px 8px !important; font-size: 11px !important; }

/* Geplande taken (admin.php). */
.cron-card-warn { border-color: rgba(245, 166, 35, 0.45); }
.cron-card h2 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cron-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.cron-item { display: flex; gap: 10px; align-items: flex-start; }
.cron-dot { flex: none; width: 10px; height: 10px; border-radius: 50%; margin-top: 6px; background: var(--text-muted); }
.cron-ok .cron-dot { background: var(--status-good); box-shadow: 0 0 0 3px rgba(12, 163, 12, 0.18); }
.cron-late .cron-dot, .cron-never:not(.cron-optional) .cron-dot { background: var(--status-warning); box-shadow: 0 0 0 3px rgba(250, 178, 25, 0.18); }
.cron-info { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cron-info strong { font-size: 14px; }
.cron-info > .muted { font-size: 12px; }
.cron-warn-text { color: #f5c877; }
.cron-help summary { cursor: pointer; font-size: 12px; color: var(--text-muted); }
.cron-help summary:hover { color: var(--text-primary); }
.cron-help p { font-size: 12px; margin: 6px 0; }
.cron-cmd { display: block; font-size: 11px; padding: 6px 8px; background: var(--surface-2); border-radius: 6px; word-break: break-all; user-select: all; }

/* Logboek (admin/audit_log.php). */
.audit-table td { vertical-align: top; font-size: 13px; }
.audit-badge { white-space: nowrap; font-size: 11px; }
.audit-user_delete, .audit-bulk_delete_users { color: #f28b82 !important; }
.audit-user_toggle_admin { color: #ffd27a !important; }
.audit-stream_url_changed { color: #8fbdf2 !important; }
.audit-details { margin-top: 4px; font-size: 12px; }
.audit-details summary { cursor: pointer; color: var(--text-muted); }
.audit-details code { font-size: 11px; word-break: break-all; }
.audit-details pre { margin: 4px 0 0; font-size: 11px; white-space: pre-wrap; word-break: break-all; color: var(--text-secondary); }
.audit-pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 16px; }

/* Heatmap "Beste uitzendtijden" (stream_detail.php, zie
   listenerHeatmapHtml() in includes/stream_insights.php). Sequentieel,
   één tint (blauw), 6 stappen: donker = weinig, licht = veel luisteraars —
   op het donkere kaartoppervlak zakken lage waarden zo weg en springen de
   drukke uren eruit. Gecontroleerd met de dataviz-validator (ordinaal,
   dark, oppervlak #1a1a19): monotone lichtheid, zichtbare stapgrootte, en
   ook de donkerste stap haalt 2:1 contrast met het kaartoppervlak. "Geen
   metingen" is bewust neutraal grijs met een stippelrand, niet de
   laagste blauwstap: geen data is iets anders dan 0 luisteraars. */
.hm-grid { display: flex; flex-direction: column; gap: 3px; }
.hm-row { display: grid; grid-template-columns: 26px repeat(24, minmax(6px, 1fr)); gap: 3px; align-items: center; }
.hm-daylabel { font-size: 11px; color: var(--text-muted); }
.hm-hour { font-size: 10px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.hm-cell { position: relative; display: block; height: 20px; border-radius: 3px; cursor: default; }
/* Tooltips aan de randen links/rechts uitlijnen i.p.v. gecentreerd, zodat
   ze niet buiten de kaart/het scherm vallen (zie $tipAttrs in
   listenerHeatmapHtml()). */
.hm-cell[data-tooltip-align="left"]::after { left: 0; transform: translateY(4px); }
.hm-cell[data-tooltip-align="right"]::after { left: auto; right: 0; transform: translateY(4px); }
.hm-cell[data-tooltip-align]:hover::after,
.hm-cell[data-tooltip-align]:focus-visible::after { transform: none; }
@media (max-width: 620px) {
  .hm-grid, .hm-row { gap: 2px; }
  .hm-row { grid-template-columns: 22px repeat(24, minmax(5px, 1fr)); }
  .hm-cell { height: 15px; border-radius: 2px; }
  .hm-hour { font-size: 9px; }
}
.hm-cell:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
.hm-cell:hover { box-shadow: 0 0 0 2px #fff inset; }
.hm-none { background: transparent; border: 1px dashed rgba(255, 255, 255, 0.14); }
.hm-0 { background: #184f95; }
.hm-1 { background: #256abf; }
.hm-2 { background: #3987e5; }
.hm-3 { background: #6da7ec; }
.hm-4 { background: #9ec5f4; }
.hm-5 { background: #cde2fb; }
.hm-legend { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; font-size: 12px; }
.hm-legend-bar { display: inline-flex; gap: 2px; }
.hm-legend-bar span { width: 22px; height: 10px; border-radius: 2px; }
.hm-legend-none { display: inline-flex; align-items: center; gap: 6px; margin-left: 10px; color: var(--text-muted); }
.hm-legend-none .hm-cell { width: 14px; height: 10px; }
.hm-best { margin: 10px 0 0; font-size: 13px; color: var(--text-secondary); }
.hm-best strong { color: var(--text-primary); }

/* "Net gespeeld" op public_stream.php (zie getRecentTracks() in
   includes/stream_insights.php) — compacte lijst met kleine hoesjes;
   zonder deze regels werden de albumhoezen op volle grootte getoond. */
.recent-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.recent-item {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
  border-top: 1px solid var(--border-hairline);
}
.recent-item:first-child { border-top: 0; padding-top: 0; }
.recent-item.is-current .recent-text strong { color: var(--accent); }
.recent-art {
  width: 44px;
  height: 44px;
  border-radius: 6px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.05);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  flex: none;
}
.recent-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.recent-art svg { width: 18px; height: 18px; }
.recent-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.3;
}
.recent-text strong,
.recent-text .muted {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.recent-text strong { font-size: 14px; }
.recent-text .muted { font-size: 12px; }
.recent-time {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: right;
}
.recent-now {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--status-good);
  font-weight: 600;
}
.recent-now .live-dot { width: 7px; height: 7px; }
