/* ==========================================================================
   4planning UI-stijl — nieuwe huisstijl-componentlaag (2026-07)
   --------------------------------------------------------------------------
   Alle componenten zijn opgebouwd op de bestaande theme-vars uit
   brand_css_vars.php (whitelabel + dark-mode-proof). GEEN hardcoded hex —
   kleuren komen altijd uit --primary-color(-rgb), --bs-*-soft, --theme-*.
   Levende galerij: ui-stijl.php (superadmin).
   ========================================================================== */

:root {
  --ui-radius-lg: 1.25rem;
  --ui-radius: 1rem;
  --ui-radius-sm: .75rem;

  /* --------------------------------------------------------------------
     Uitgebreid accentpalet — vaste semantische kleuren (SB Admin Pro-
     familie, NIET per-omgeving; net als --bs-success/--bs-warning die al
     in brand_css_vars.php zitten). Voor categorie-kleuren, tags, grafieken.
     -------------------------------------------------------------------- */
  --bs-indigo: #5800e8;  --bs-indigo-rgb: 88, 0, 232;
  --bs-pink:   #e30059;  --bs-pink-rgb: 227, 0, 89;
  --bs-orange: #f76400;  --bs-orange-rgb: 247, 100, 0;
  --bs-teal:   #00ba94;  --bs-teal-rgb: 0, 186, 148;

  /* Soft-tinten: zelfde formule als de PHP-softs (15% kleur + thema-
     achtergrond), maar live via color-mix — beweegt automatisch mee met
     dark/light omdat --theme-color-body door applyThemeVars() wisselt. */
  --bs-indigo-soft: color-mix(in srgb, var(--bs-indigo) 15%, var(--theme-color-body));
  --bs-pink-soft:   color-mix(in srgb, var(--bs-pink) 15%, var(--theme-color-body));
  --bs-orange-soft: color-mix(in srgb, var(--bs-orange) 15%, var(--theme-color-body));
  --bs-teal-soft:   color-mix(in srgb, var(--bs-teal) 15%, var(--theme-color-body));
  --bs-light-soft:  color-mix(in srgb, var(--bs-light) 15%, var(--theme-color-body));
  --bs-dark-soft:   color-mix(in srgb, var(--bs-dark) 15%, var(--theme-color-body));

  /* Het rood van 4planning zelf — bewust NIET whitelabel. Dit is de kleur waarmee we
     markeren wat alleen wíj kunnen zetten (zie .status-pill-4planning). Zelfde waarde
     als de fallback in includes/loggedout/header.php en css/omgevingen.css. */
  --ui-4planning: #E73144;  --ui-4planning-rgb: 231, 49, 68;
  --ui-4planning-soft: color-mix(in srgb, var(--ui-4planning) 15%, var(--theme-color-body));

  /* --------------------------------------------------------------------
     LEESBARE TEKST OP EEN GEVULDE SEMANTISCHE KLEUR.
     De semantische kleuren (success/warning/danger/info + het accentpalet)
     zijn VAST — ze wisselen niet per omgeving en niet met dark/light. De
     leesbare tekstkleur erop is dus óók vast, en dat is precies waarom er
     twee tokens zijn in plaats van var(--theme-text-color): die laatste
     stond in de themabundel op .btn-danger/.btn-success/.btn-warning, en
     dat leverde in light mode een rode knop met zwarte tekst op en in dark
     mode gele tekst-op-geel. Kies op de kleur, niet op het thema:
     donker vlak (success/danger/purple/…) → wit, licht vlak
     (warning/info/teal) → bijna-zwart. Voor de MERK-kleuren bestaan
     --primary-bg-text-color / --secondary-bg-text-color al; gebruik die. */
  --ui-op-donker: #ffffff;
  --ui-op-licht:  #14181f;

  /* --------------------------------------------------------------------
     VELD-CHROME — één bron voor alles waar je iets in typt of kiest:
     form-control, form-select, textarea, zoekveld, chip-input, de Quill-
     editor, het datum/tijd-veld en de dropzone. Deze waarden stonden eerder
     letterlijk vijf keer herhaald in dit bestand; elk nieuw veldcomponent
     kopieerde ze opnieuw en week dan net iets af (andere radius, andere
     focusring). Gebruik ALTIJD deze vars, nooit de losse waarden.
     -------------------------------------------------------------------- */
  --ui-veld-bg: var(--theme-color-input);
  --ui-veld-bg-actief: var(--theme-color-input-active);
  --ui-veld-rand: var(--theme-color-input-border);
  /* Hover is bewust zwakker dan focus: aanwijzen mag niet even hard schreeuwen
     als "hier staat je cursor". Focus voegt daar bovenop nog de ring toe. */
  --ui-veld-rand-hover: rgba(var(--primary-color-rgb), .35);
  --ui-veld-rand-focus: rgba(var(--primary-color-rgb), .45);
  --ui-veld-ring: 0 0 0 .25rem rgba(var(--primary-color-rgb), .12);
  --ui-veld-radius: var(--ui-radius-sm);
  --ui-veld-padding: .625rem .875rem;
  --ui-veld-overgang: border-color .12s ease, box-shadow .12s ease, background-color .12s ease;
}

/* --------------------------------------------------------------------------
   Card-soft — zachte kaart: grote radius, dunne rand, subtiele schaduw
   -------------------------------------------------------------------------- */
.card-soft {
  background: var(--theme-color-card);
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius-lg);
  box-shadow: 0 .5rem 1.5rem rgba(var(--custom-theme-box-shadow-rgb), .07);
}
.card-soft > .card-body { padding: 1.25rem 1.5rem; }
/* Combinatie met Bootstrap .card op bestaande pagina's: header transparant en
   mee-afgerond. GEEN overflow:hidden op de kaart — dropdowns in de header
   (bv. het filterpaneel) zouden dan geclipt worden. */
.card.card-soft > .card-header {
  background: transparent;
  border-bottom: 1px solid var(--theme-color-3);
  border-radius: calc(var(--ui-radius-lg) - 1px) calc(var(--ui-radius-lg) - 1px) 0 0;
  padding: 1rem 1.5rem;
}
/* Op telefoonformaat vult de kaart het hele scherm: de zij-inset van de pagina
   gaat daar van 1.5rem naar .75rem (zie het APP-GEVOEL-blok in styles-sb.css.php)
   en de kaartpadding mee naar 1rem — anders staat de tekst nog steeds 2,5rem van
   de schermrand. 1rem is bewust dezelfde waarde als de mobiele inspringing van
   `.table-soft-inset` hieronder, zodat een tabel in de kaart op één lijn met de
   tekst erboven blijft staan. De ronding gaat omhoog omdat de hoek dan het enige
   is wat je nog van de kaartvorm ziet (zelfde gedachte als de sheet-hoek van het
   inlogscherm); de header volgt met dezelfde -1px voor de rand. */
@media (max-width: 575.98px) {
  .card-soft { border-radius: 1.5rem; }
  .card-soft > .card-body { padding: 1.15rem 1rem; }
  .card.card-soft > .card-header {
    border-radius: calc(1.5rem - 1px) calc(1.5rem - 1px) 0 0;
    padding: .9rem 1rem;
  }
}

/* --------------------------------------------------------------------------
   Icon-tile — icoon in zacht getint afgerond vierkant (het kernsignaal
   uit de nieuwe stijl). Kleur = merk-soft; semantische varianten voor status.
   -------------------------------------------------------------------------- */
.icon-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: .875rem;
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  flex-shrink: 0;
}
.icon-tile svg { width: 1.25rem; height: 1.25rem; }

.icon-tile-sm { width: 2.25rem; height: 2.25rem; border-radius: .7rem; }
.icon-tile-sm svg { width: 1rem; height: 1rem; }

.icon-tile-lg { width: 3.25rem; height: 3.25rem; border-radius: 1rem; }
.icon-tile-lg svg { width: 1.5rem; height: 1.5rem; }

/* -primary is dezelfde kleur als de kale .icon-tile en bestaat als NAAM, zodat je de
   merktint ook kunt zetten op een element dat zijn eigen maat houdt (bv. de KPI-tegel
   `.stat-ico` op admin-dashboard.php: die is 2.6rem en mag niet de 2.75rem van
   .icon-tile krijgen, maar wil wél de merktint uit een token in plaats van een hex). */
.icon-tile-primary   { background: var(--bs-primary-soft); color: var(--brand-on-surface); }
.icon-tile-secondary { background: var(--bs-secondary-soft); color: var(--secondary-on-surface); }
.icon-tile-success { background: var(--bs-success-soft); color: var(--bs-success); }
.icon-tile-warning { background: var(--bs-warning-soft); color: var(--bs-warning); }
.icon-tile-danger  { background: var(--bs-danger-soft);  color: var(--bs-danger); }
.icon-tile-info    { background: var(--bs-info-soft);    color: var(--bs-info); }
.icon-tile-purple  { background: var(--bs-purple-soft);  color: var(--bs-purple); }
.icon-tile-indigo  { background: var(--bs-indigo-soft);  color: var(--bs-indigo); }
.icon-tile-pink    { background: var(--bs-pink-soft);    color: var(--bs-pink); }
.icon-tile-orange  { background: var(--bs-orange-soft);  color: var(--bs-orange); }
.icon-tile-teal    { background: var(--bs-teal-soft);    color: var(--bs-teal); }
.icon-tile-neutral { background: var(--theme-color-4);   color: var(--theme-text-color-gray); }

/* --------------------------------------------------------------------------
   Status-pill — VERHUISD naar css/styles-sb.css.php (zoek: STATUS-PILL), 2026-08-10.
   --------------------------------------------------------------------------
   Reden, dezelfde als bij .dropdown-soft en .btn-soft-neutral: ui-stijl.css laadt
   PER PAGINA (114 van de 189 root-pagina's), maar status-pill wordt gerenderd door
   centrale helpers — status_badge(), aanvraag_badge(), contributie_status_badge() —
   die vanaf élke pagina aangeroepen kunnen worden, en door AJAX-partials
   (fetch_payments.php, fetch_leads.php, …) die in willekeurige ouders landen.
   Anders dan een .badge, die tenminste nog zijn bg-* toonde, heeft status-pill
   ZONDER de componentlaag geen achtergrond: dan is het onzichtbare tekst.
   Eén bron, geen kopie — voeg hem hier niet opnieuw toe.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Pill-tabs — chips als tabbladen (Gegevens / Contacten / … en
   Komend / Gepland / Afgerond). Actieve chip = gevuld met merkkleur.
   -------------------------------------------------------------------------- */
.pill-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
}
.pill-tab {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .45rem 1.05rem;
  border-radius: 50rem;
  border: 1px solid var(--theme-color-3);
  background: var(--theme-color-card);
  color: var(--theme-text-color);
  font-size: .875rem;
  font-weight: 500;
  line-height: 1.3;
  cursor: pointer;
  text-decoration: none;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.pill-tab:hover {
  background: var(--bs-primary-soft);
  border-color: rgba(var(--primary-color-rgb), .2);
  color: var(--brand-on-surface);
}
.pill-tab.active {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: var(--primary-bg-text-color);
  font-weight: 600;
}
/* Vangnet: SB Admin's `.card .card-header .card-header-tabs .nav-link.active`
   (styles-sb.css.php) overschrijft met een hogere specificiteit de merkkleur-vulling
   door de kaartachtergrond — resultaat: witte tekst op wit. Gebruik `pill-tabs`
   NOOIT samen met `card-header-tabs`; mocht het toch gebeuren, wint dit weer. */
.card .card-header .card-header-tabs .nav-link.pill-tab.active {
  background-color: var(--primary-color);
  border-color: var(--primary-color);
  color: var(--primary-bg-text-color);
}

/* Volle breedte, gelijk verdeeld (segmented, mockup Onderhoudsmomenten) */
.pill-tabs-fill { flex-wrap: nowrap; }
.pill-tabs-fill .pill-tab { flex: 1; justify-content: center; }

/* --------------------------------------------------------------------------
   Data-list — lijstrijen: icon-tile + titel/subtitel + meta rechts + chevron
   (Onderhoudsmomenten, werkmomenten, voorraadrijen)
   -------------------------------------------------------------------------- */
.data-list { display: flex; flex-direction: column; }
.data-list-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .875rem;
  row-gap: .5rem;
  padding: .875rem .5rem;
  margin: 0 -.5rem;
  border-top: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius-sm);
  color: inherit;
  text-decoration: none;
}
.data-list-row:first-child { border-top: 0; }
/* Rijen zijn vaak <a>-elementen: nooit underline, tekstkleur blijft neutraal */
a.data-list-row,
a.data-list-row:hover,
a.data-list-row:focus {
  text-decoration: none;
  color: inherit;
}
a.data-list-row:hover {
  background: rgba(var(--theme-color-3-rgb), .35);
  border-top-color: transparent;
}
a.data-list-row:hover + .data-list-row { border-top-color: transparent; }
/* Rij met een eigen actie (knop/formulier) kan geen <a> zijn — een <form> of <button>
   mag niet in een link staan. Dan: .data-list-row-clickable op de rij + een lege
   <a class="data-list-row-overlay"> die de hele rij afdekt. De overlay vangt de klik op
   de tekst/lege ruimte; echte interactieve elementen worden er met z-index bovenuit
   getild zodat hun eigen klik gewoon werkt (chevron blijft dan een <span>).
   Navigeert de rij niet maar OPENT hij iets in de pagina zelf (een paneel, een andere
   weergave in dezelfde modal)? Gebruik dan een <button class="data-list-row-overlay">:
   dat is de juiste semantiek en de reset hieronder haalt de knop-chrome weg. */
.data-list-row-clickable { position: relative; }
.data-list-row-clickable:hover,
.data-list-row-clickable:focus-within {
  background: rgba(var(--theme-color-3-rgb), .35);
  border-top-color: transparent;
}
.data-list-row-clickable:hover + .data-list-row,
.data-list-row-clickable:focus-within + .data-list-row { border-top-color: transparent; }
.data-list-row-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  /* De overlay mag ook een <button> zijn: bij een actie BINNEN de pagina (een paneel dat
     opengaat) is dat semantisch juister dan een <a> met een nep-href. Zonder deze reset
     legt de browser dan zijn eigen knop-chrome — een grijs vlak met rand — over de hele
     rij. Op een <a> verandert er niets. */
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
  appearance: none;
}
.data-list-row-overlay:focus-visible {
  outline: 2px solid var(--primary-color);
  outline-offset: -2px;
}
.data-list-row-clickable :is(a, button, input, select, textarea, label):not(.data-list-row-overlay) {
  position: relative;
  z-index: 2;
}
/* Basis van 11rem: de tekstkolom is het ENIGE krimpbare item in de rij (date-cell,
   meta en chevron staan op flex-shrink:0), dus zonder ondergrens werd hij door een
   brede meta (avatars + pill + actieknop) tot min-content geperst — dan brak elk
   woord op een eigen regel. Met deze basis + flex-wrap op de rij wipt de meta netjes
   naar een tweede regel i.p.v. de titel/subtitel te vermorzelen. */
.data-list-main { min-width: 0; flex: 1 1 11rem; }
.data-list-title {
  font-weight: 600;
  color: var(--theme-text-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Titel die MAG afbreken i.p.v. af te kappen. De ellipsis hierboven is juist voor een
   lijst van ITEMS (een naam, een titel — je herkent hem aan het begin). Bij een lijst van
   INSTELLINGEN is de titel de vraag zelf ("Herinnering bij onbeantwoord evenement"), en
   een afgekapte vraag is niet te beantwoorden: op een telefoon houdt de tekstkolom naast
   een switch nog ~30 tekens over. Zet deze class dan op de titel. */
.data-list-title-wrap { overflow: visible; text-overflow: clip; white-space: normal; }
.data-list-sub { font-size: .85rem; color: var(--theme-text-color-gray); }
.data-list-meta {
  margin-left: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .3rem;
  font-size: .85rem;
  color: var(--theme-text-color-gray);
  text-align: right;
  flex-shrink: 0;
}
.data-list-chevron { color: var(--theme-text-color-gray); flex-shrink: 0; }
.data-list-chevron svg { width: 1.1rem; height: 1.1rem; }

/* --------------------------------------------------------------------------
   Info-list — dossierrijen: icon-tile + label (klein, grijs) boven waarde
   (Klantdossier: Bedrijfsnaam / Contactpersonen / Afgenomen producten)
   -------------------------------------------------------------------------- */
.info-list { display: flex; flex-direction: column; }
.info-row {
  display: flex;
  align-items: center;
  gap: .875rem;
  padding: .875rem 0;
  border-top: 1px solid var(--theme-color-3);
}
.info-row:first-child { border-top: 0; }
.info-label {
  font-size: .8125rem;
  color: var(--theme-text-color-gray);
  margin-bottom: .1rem;
}
.info-value { font-weight: 600; color: var(--theme-text-color); }

/* --------------------------------------------------------------------------
   Timeline-list — verticale tijdlijn met merk-stippen
   (Contactmomenten, planning & werkmomenten)
   -------------------------------------------------------------------------- */
.timeline-list {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
}
.timeline-list::before {
  content: "";
  position: absolute;
  left: .3rem;
  top: 1rem;
  bottom: 1rem;
  width: 2px;
  background: rgba(var(--primary-color-rgb), .25);
}
.timeline-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: .875rem;
  padding: .75rem 0 .75rem 1.75rem;
}
.timeline-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: .65rem;
  height: .65rem;
  border-radius: 50%;
  background: var(--primary-color);
}
.timeline-main { min-width: 0; }
.timeline-title { font-weight: 600; color: var(--theme-text-color); }
.timeline-sub { font-size: .85rem; color: var(--theme-text-color-gray); }
.timeline-meta {
  margin-left: auto;
  flex-shrink: 0;
  text-align: right;
  font-size: .85rem;
  color: var(--theme-text-color-gray);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .3rem;
}

/* --------------------------------------------------------------------------
   Stat-strip — horizontale KPI-voetstrook onder een kaart
   (4 momenten | 2 deze week | 1 open)
   -------------------------------------------------------------------------- */
.stat-strip {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: .75rem;
  container-type: inline-size;
}
.stat-strip-item {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: 0 1.25rem;
  border-left: 1px solid var(--theme-color-3);
}
.stat-strip-item:first-child { border-left: 0; padding-left: 0; }

/* Verticale variant: KPI's onder elkaar, allemaal op dezelfde linkerlijn. Gebruik deze
   wanneer de strip als lijstje beter leest dan als balk — de scheidingsstreep vervalt,
   want in een kolom zou die het onderste blok laten inspringen. */
.stat-strip-stacked { flex-direction: column; align-items: flex-start; }
.stat-strip-stacked .stat-strip-item { border-left: 0; padding: 0; }

/* Te smalle container: automatisch dezelfde gestapelde look. Zonder dit houdt een
   gewrapte flexregel de border-left + padding-left van de kolom ervóór, waardoor het
   tweede blok ingesprongen onder het eerste komt te staan.

   LET OP — het stapelen loopt via de ITEMS, niet via `.stat-strip` zelf. Een element
   kan zijn EIGEN container niet queryen: @container kijkt naar de dichtstbijzijnde
   container-VOOROUDER, en `container-type` staat hier op `.stat-strip`. Een regel
   `.stat-strip { flex-direction: column }` binnen deze query heeft daarom nooit gevuurd
   (die zou alleen een stat-strip ÍN een stat-strip raken) — de item-regels wél, en het
   netto-effect was het omgekeerde van de bedoeling: de blokken raakten hun scheiding
   kwijt en plakten zonder ruimte tegen elkaar aan ("Ingeroosterd 1Afgemeld 0" op
   lid.php › Aanwezigheid). `flex: 1 0 100%` op de items geeft elk item een eigen regel
   en levert dus dezelfde look als `.stat-strip-stacked`. */
@container (max-width: 24rem) {
  .stat-strip-item { border-left: 0; padding: 0; flex: 1 0 100%; }
}
.stat-strip-value {
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--theme-text-color);
}
.stat-strip-label { font-size: .8125rem; color: var(--theme-text-color-gray); }

/* --------------------------------------------------------------------------
   Knoppen — op een huisstijl-pagina is ELKE .btn een pill. Dit dekt ook
   knoppen in modal-bodies, tabelcellen en JS-gerenderde markup (bv.
   sportlink-inbox.js) zonder per-knop classes. Bootstrap's input-group/
   btn-group-regels hebben hogere specificiteit en houden hun binnenhoeken
   zelf strak — alleen de buitenranden worden rond.
   .btn-pill blijft de ruimere padding-variant voor primaire CTA's.
   -------------------------------------------------------------------------- */
.btn { border-radius: 50rem; }

/* Icoon + label in een knop: ruimte ertussen, en verticaal gecentreerd.
   Zonder dit plakt een Feather-svg tegen de tekst aan én zakt hij onder de
   basislijn (de bekende Feather-uitlijnvalkuil uit CLAUDE.md) — .btn-pill had
   de flexrij al, een gewone .btn niet, dus dezelfde markup zag er per knop
   anders uit. Bewust gestuurd op een svg NAAST een <span>, het huispatroon:
   de oude variant `<i …></i>&nbsp;Label` heeft een tekstnode i.p.v. een span
   en blijft dus buiten schot — anders kreeg die de nbsp én de gap. */
.btn:has(> svg + span),
.btn:has(> span + svg) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
}

/* --------------------------------------------------------------------------
   Split-button — primaire actie + caret met extra opties in één pill
   --------------------------------------------------------------------------
   Bootstrap's .btn-group plat ALLE binnenhoeken en laat de buitenhoeken op de
   standaard btn-radius staan; met de pill-regel hierboven levert dat een halve
   pill op. Bovendien is het laatste kind van de groep de <ul class="dropdown-menu">,
   dus `:last-child` treft de caret NIET — daarom expliciet op de twee knoppen.
   Gebruik: <div class="btn-split btn-group"> <button class="btn btn-primary">…
            <button class="btn btn-primary dropdown-toggle dropdown-toggle-split"
                    data-bs-toggle="dropdown" [hidden]>… <ul class="dropdown-menu dropdown-soft">
   Is de caret hidden, dan wordt de hoofdknop automatisch weer een hele pill. */
.btn-split > .btn:first-child {
    border-top-left-radius: 50rem;
    border-bottom-left-radius: 50rem;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}
.btn-split > .btn.dropdown-toggle-split {
    border-top-right-radius: 50rem;
    border-bottom-right-radius: 50rem;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    padding-left: .8rem;
    padding-right: .9rem;
    /* De caret erft in deze themabundel géén achtergrond — hij kwam als een kaal
       pijltje naast een gevulde knop te staan. Dezelfde reparatie stond al
       pagina-lokaal op de zwevende save-bar ("erfde geen bg door theme-var-gaten");
       hier generiek, via de Bootstrap-variabelen die de kleurklasse (.btn-primary,
       .btn-outline-primary, …) zelf al zet. Zo klopt het voor élke variant: bij een
       outline-knop is --bs-btn-bg transparant en blijft de caret dus ook transparant. */
    background-color: var(--bs-btn-bg);
    border-color: var(--bs-btn-border-color);
    color: var(--bs-btn-color);
    /* Dunne scheiding in de tekstkleur van de knop: werkt op elke merkkleur en in
       dark mode, anders dan een vaste witte lijn. */
    border-left: 1px solid rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .35);
}
.btn-split > .btn.dropdown-toggle-split:hover,
.btn-split > .btn.dropdown-toggle-split:focus,
.btn-split > .btn.dropdown-toggle-split:active,
.btn-split > .btn.dropdown-toggle-split.show {
    background-color: var(--bs-btn-hover-bg, var(--bs-btn-bg));
    border-color: var(--bs-btn-hover-border-color, var(--bs-btn-border-color));
    color: var(--bs-btn-hover-color, var(--bs-btn-color));
    /* Scheidingslijn behouden bij hover — anders lijkt de knop even te versmelten. */
    border-left-color: rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .35);
}
/* Caret verborgen → hoofdknop weer een volledige pill. */
.btn-split:has(> .dropdown-toggle-split[hidden]) > .btn:first-child {
    border-radius: 50rem;
}
.btn.btn-pill {
  border-radius: 50rem;
  padding: .625rem 1.5rem;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
}
.btn.btn-pill svg { width: 1.05rem; height: 1.05rem; }

/* --------------------------------------------------------------------------
   Pagina-knop — de CTA-knop die in EIGEN PAGINA'S (Instellingen › Eigen
   Pagina's) in de Quill-inhoud kan staan, naast het documentkaartje
   (.quill-document-link, dat page-local in pagina.php staat omdat het een
   heel eigen kaartvorm is). Deze is wél in ui-stijl.css opgenomen: het is een
   gewone merk-pill-knop, geen apart kaartpatroon, dus hoort hij bij de
   generieke knoppenfamilie hierboven, herbruikbaar op elke plek waar
   opgeslagen pagina-HTML gerenderd wordt (pagina.php + de live preview in
   paginas-beheer.php). Toegestaan door de HTMLPurifier-config in
   functions/purify_functions.php (Attr.AllowedClasses) — een nieuwe class
   voor dit component moet DAAR ook toegevoegd worden, anders filtert de
   purifier hem eruit.
   Markup: <a class="pagina-knop" href="…">Label</a>
   -------------------------------------------------------------------------- */
.pagina-knop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .625rem 1.5rem;
  margin: .25rem 0;
  border-radius: 50rem;
  background-color: var(--primary-color);
  color: var(--primary-bg-text-color);
  font-weight: 600;
  text-decoration: none;
  border: 0;
  transition: filter .15s ease-in-out, transform .15s ease-in-out;
}
.pagina-knop:hover,
.pagina-knop:focus {
  filter: brightness(1.08);
  color: var(--primary-bg-text-color);
  text-decoration: none;
  transform: translateY(-1px);
}

/* --------------------------------------------------------------------------
   SEMANTISCHE KNOPPEN — drie sterktes, één betekenis-tabel
   --------------------------------------------------------------------------
   De betekenis van elke kleur staat in de statuskleuren-tabel van de skill en
   is hier identiek: success = positief eindpunt, danger = onomkeerbaar/
   negatief, warning = aandacht nodig, info = neutraal-informatief. Wat je
   kiest is alleen hoe HARD de knop roept:

     .btn-soft-<kleur>     ← DE STANDAARD. Zachte tint + tekst in de kleur.
                             Zelfde palet als .status-pill-<kleur>, dus een
                             rij met een danger-pill en een Verwijderen-knop
                             valt niet uiteen in twee roden.
     .btn-outline-<kleur>  Bootstrap-standaard; voor een secundaire actie die
                             wél om de rand moet vragen.
     .btn-<kleur>          Vol vlak. Alleen voor de ENE bevestigende actie in
                             een modal-footer ("Ja, definitief verwijderen").
                             Twee volvlak-knoppen naast elkaar betekent dat er
                             geen hoofdactie meer is.

   Waarom soft de standaard is: een lijst met per rij een rode Verwijderen-knop
   is een pagina die schreeuwt terwijl er niets aan de hand is — precies de
   reden dat .danger-zone ook géén rode vlakvulling heeft. De soft-variant
   houdt het signaal (rood = onomkeerbaar) en haalt het volume eruit.
   -------------------------------------------------------------------------- */

/* 1. Volvlak: leesbare tekstkleur pinnen. De themabundel zet hier
      var(--theme-text-color) — zie de toelichting bij --ui-op-donker. */
.btn-success, .btn-danger, .btn-purple, .btn-indigo, .btn-pink, .btn-orange {
  --bs-btn-color: var(--ui-op-donker);
  --bs-btn-hover-color: var(--ui-op-donker);
  --bs-btn-active-color: var(--ui-op-donker);
  --bs-btn-disabled-color: var(--ui-op-donker);
}
.btn-warning, .btn-info, .btn-teal {
  --bs-btn-color: var(--ui-op-licht);
  --bs-btn-hover-color: var(--ui-op-licht);
  --bs-btn-active-color: var(--ui-op-licht);
  --bs-btn-disabled-color: var(--ui-op-licht);
}
/* .btn-secondary draagt WEL de merkkleur van de omgeving, dus die volgt de
   bestaande contrastvar en niet een van de twee vaste tokens hierboven. */
.btn-secondary {
  --bs-btn-color: var(--secondary-bg-text-color);
  --bs-btn-hover-color: var(--secondary-bg-text-color);
  --bs-btn-active-color: var(--secondary-bg-text-color);
  --bs-btn-disabled-color: var(--secondary-bg-text-color);
}

/* 2. Soft. Gedeelde basis; per variant alleen de drie kleur-vars.
      De hover-tint is de soft-formule met een hogere mengverhouding (26% i.p.v.
      15%), zodat hover in dark mode net zo goed oplicht als in light — een
      vaste donkerdere hex zou in dark mode juist wegvallen. */
.btn-soft-primary, .btn-soft-secondary, .btn-soft-success, .btn-soft-warning,
.btn-soft-danger, .btn-soft-info, .btn-soft-neutral {
  --bs-btn-color: var(--ui-soft-tekst);
  --bs-btn-bg: var(--ui-soft-vlak);
  /* Heel lichte rand in de VOLLE kleur: geeft de knop een contour op een
     achtergrond die bijna even licht is als de soft-vulling (card-soft-tint,
     een tabelrij op hover) zonder dat hij als outline-knop gaat lezen.
     Als rgba() van dezelfde -rgb die de focusring gebruikt, dus hij volgt
     automatisch elke variant én de merkkleur bij -primary/-secondary. */
  --bs-btn-border-color: rgba(var(--ui-soft-rgb), .22);
  --bs-btn-hover-color: var(--ui-soft-tekst);
  --bs-btn-hover-bg: var(--ui-soft-vlak-hover);
  /* Iets sterker op hover, anders verdwijnt de rand in de zwaardere vulling. */
  --bs-btn-hover-border-color: rgba(var(--ui-soft-rgb), .38);
  --bs-btn-active-color: var(--ui-soft-tekst);
  --bs-btn-active-bg: var(--ui-soft-vlak-hover);
  --bs-btn-active-border-color: rgba(var(--ui-soft-rgb), .38);
  --bs-btn-active-shadow: none;
  --bs-btn-disabled-color: var(--ui-soft-tekst);
  --bs-btn-disabled-bg: var(--ui-soft-vlak);
  --bs-btn-disabled-border-color: rgba(var(--ui-soft-rgb), .22);
  --bs-btn-focus-shadow-rgb: var(--ui-soft-rgb);
  font-weight: 600;
}
.btn-soft-primary {
  --ui-soft-tekst: var(--brand-on-surface);
  --ui-soft-vlak: var(--bs-primary-soft);
  --ui-soft-vlak-hover: color-mix(in srgb, var(--primary-color) 26%, var(--theme-color-body));
  --ui-soft-rgb: var(--primary-color-rgb);
}
.btn-soft-secondary {
  --ui-soft-tekst: var(--secondary-on-surface);
  --ui-soft-vlak: var(--bs-secondary-soft);
  --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-secondary) 26%, var(--theme-color-body));
  --ui-soft-rgb: var(--bs-secondary-rgb);
}
.btn-soft-success {
  --ui-soft-tekst: var(--bs-success);
  --ui-soft-vlak: var(--bs-success-soft);
  --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-success) 26%, var(--theme-color-body));
  --ui-soft-rgb: var(--bs-success-rgb);
}
.btn-soft-warning {
  --ui-soft-tekst: var(--bs-warning);
  --ui-soft-vlak: var(--bs-warning-soft);
  --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-warning) 26%, var(--theme-color-body));
  --ui-soft-rgb: var(--bs-warning-rgb);
}
.btn-soft-danger {
  --ui-soft-tekst: var(--bs-danger);
  --ui-soft-vlak: var(--bs-danger-soft);
  --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-danger) 26%, var(--theme-color-body));
  --ui-soft-rgb: var(--bs-danger-rgb);
}
.btn-soft-info {
  --ui-soft-tekst: var(--bs-info);
  --ui-soft-vlak: var(--bs-info-soft);
  --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-info) 26%, var(--theme-color-body));
  --ui-soft-rgb: var(--bs-info-rgb);
}
/* Neutraal: "geen oordeel" — voor een derde knop naast twee gekleurde. */
.btn-soft-neutral {
  --ui-soft-tekst: var(--theme-text-color);
  --ui-soft-vlak: var(--theme-color-4);
  --ui-soft-vlak-hover: var(--theme-color-3);
  --ui-soft-rgb: var(--theme-text-color-rgb);
}

/* 3. De LEGACY SB-Admin-klassen .btn-success-soft / .btn-danger-soft / … zijn
      hetzelfde idee met vaste hexen (#f1e0e3 met zwarte tekst) — dus lichtroze
      met zwarte letters, en in dark mode een lichtroze vlek. Ze wijzen hier naar
      dezelfde tokens, zodat een pagina die ze al gebruikt automatisch meedoet
      zonder dat je de markup hoeft aan te raken. Schrijf in NIEUW werk
      .btn-soft-<kleur>. */
.btn-primary-soft   { --ui-soft-tekst: var(--brand-on-surface);     --ui-soft-vlak: var(--bs-primary-soft);   --ui-soft-vlak-hover: color-mix(in srgb, var(--primary-color) 26%, var(--theme-color-body)); --ui-soft-rgb: var(--primary-color-rgb); }
.btn-secondary-soft { --ui-soft-tekst: var(--secondary-on-surface); --ui-soft-vlak: var(--bs-secondary-soft); --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-secondary) 26%, var(--theme-color-body)); --ui-soft-rgb: var(--bs-secondary-rgb); }
.btn-success-soft   { --ui-soft-tekst: var(--bs-success);           --ui-soft-vlak: var(--bs-success-soft);   --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-success) 26%, var(--theme-color-body)); --ui-soft-rgb: var(--bs-success-rgb); }
.btn-warning-soft   { --ui-soft-tekst: var(--bs-warning);           --ui-soft-vlak: var(--bs-warning-soft);   --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-warning) 26%, var(--theme-color-body)); --ui-soft-rgb: var(--bs-warning-rgb); }
.btn-danger-soft    { --ui-soft-tekst: var(--bs-danger);            --ui-soft-vlak: var(--bs-danger-soft);    --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-danger) 26%, var(--theme-color-body)); --ui-soft-rgb: var(--bs-danger-rgb); }
.btn-info-soft      { --ui-soft-tekst: var(--bs-info);              --ui-soft-vlak: var(--bs-info-soft);      --ui-soft-vlak-hover: color-mix(in srgb, var(--bs-info) 26%, var(--theme-color-body)); --ui-soft-rgb: var(--bs-info-rgb); }
.btn-primary-soft, .btn-secondary-soft, .btn-success-soft,
.btn-warning-soft, .btn-danger-soft, .btn-info-soft {
  --bs-btn-color: var(--ui-soft-tekst);
  --bs-btn-bg: var(--ui-soft-vlak);
  --bs-btn-border-color: rgba(var(--ui-soft-rgb), .22);
  --bs-btn-hover-color: var(--ui-soft-tekst);
  --bs-btn-hover-bg: var(--ui-soft-vlak-hover);
  --bs-btn-hover-border-color: rgba(var(--ui-soft-rgb), .38);
  --bs-btn-active-color: var(--ui-soft-tekst);
  --bs-btn-active-bg: var(--ui-soft-vlak-hover);
  --bs-btn-active-border-color: rgba(var(--ui-soft-rgb), .38);
  --bs-btn-active-shadow: none;
  --bs-btn-disabled-color: var(--ui-soft-tekst);
  --bs-btn-disabled-bg: var(--ui-soft-vlak);
  --bs-btn-disabled-border-color: rgba(var(--ui-soft-rgb), .22);
  --bs-btn-focus-shadow-rgb: var(--ui-soft-rgb);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   BADGES — .badge bg-success/bg-danger/… is de solid-variant en hoort op een
   overzichtspagina bijna nooit. Voor een STATUS bestaat .status-pill (soft,
   zelfde palet); voor een TELLER .count-chip. Deze regels zijn er alleen zodat
   een badge die nog wél solid is, leesbare tekst heeft in beide thema's — het
   is geen uitnodiging om er nieuwe bij te zetten.
   -------------------------------------------------------------------------- */
.badge.bg-success, .badge.bg-danger, .badge.bg-purple,
.badge.bg-indigo, .badge.bg-pink, .badge.bg-orange { color: var(--ui-op-donker) !important; }
.badge.bg-warning, .badge.bg-info, .badge.bg-teal  { color: var(--ui-op-licht)  !important; }

/* --------------------------------------------------------------------------
   Notice-card — vriendelijke herinnering-kaart met accentwoorden
   (Afspraakherinneringen: "Morgen staan we om 09:00 …")
   -------------------------------------------------------------------------- */
.notice-card-text {
  font-size: 1.05rem;
  line-height: 1.55;
  color: var(--theme-text-color);
}
.text-brand {
  color: var(--brand-on-surface);
  font-weight: 700;
}
.notice-card-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem 1.25rem;
  margin-top: .875rem;
  padding-top: .75rem;
  border-top: 1px solid var(--theme-color-3);
  font-size: .85rem;
  color: var(--theme-text-color-gray);
}
.notice-card-meta > span {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
}
.notice-card-meta svg { width: .95rem; height: .95rem; color: var(--brand-on-surface); }

/* --------------------------------------------------------------------------
   Formulieren — GELDT OVERAL waar dit bestand geladen is
   --------------------------------------------------------------------------
   Was tot 2026-08-06 opt-in via .ui-form. Gevolg: van de 71 pagina's die deze
   CSS laden gebruikten er maar 24 die wrapper, dus op de rest zagen dezelfde
   velden er kaal en anders uit — precies de inconsistentie die deze laag moest
   wegnemen. De regels hangen nu direct aan .form-control/.form-select/…, zodat
   een veld niet eerst in de juiste wrapper hoeft te staan om er goed uit te zien.
   BLAST RADIUS: alleen pagina's die css/ui-stijl.css laden; de rest van de app
   is ongemoeid (die laadt dit bestand niet). .ui-form blijft bestaan als
   documenterende wrapper en voor .form-text/labels — hem weghalen mag, maar
   hoeft niet.
   -------------------------------------------------------------------------- */
.form-label,
.ui-form .form-label,
.modal-soft .modal-body .form-label,
.action-bar .form-label {
  font-weight: 600;
  font-size: .875rem;
  color: var(--theme-text-color);
  margin-bottom: .35rem;
}
.form-control,
.form-select,
textarea.form-control {
  border-radius: var(--ui-veld-radius);
  background: var(--ui-veld-bg);
  border: 1px solid var(--ui-veld-rand);
  color: var(--theme-text-color);
  padding: var(--ui-veld-padding);
  transition: var(--ui-veld-overgang);
}
/* Hover ontbrak volledig: een veld voelde dood tot je erin klikte. `:not(:focus)`
   voorkomt dat de zwakkere hover-rand de sterkere focus-rand overschrijft zolang
   de muis blijft hangen. */
.form-control:hover:not(:focus):not(:disabled):not([readonly]),
.form-select:hover:not(:focus):not(:disabled) {
  border-color: var(--ui-veld-rand-hover);
}
.form-control:focus,
.form-select:focus {
  background: var(--ui-veld-bg-actief);
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
}
/* Placeholder en uitgeschakelde velden: dezelfde gedempte tekstkleur als de rest
   van de huisstijl, i.p.v. de browser-standaard die per browser verschilt. */
.form-control::placeholder { color: var(--theme-text-color-gray); opacity: 1; }
.form-control:disabled,
.form-select:disabled,
.form-control[readonly] {
  background: var(--theme-color-4);
  color: var(--theme-text-color-gray);
  cursor: not-allowed;
}
.form-text { color: var(--theme-text-color-gray); }
/* Compacte velden (…-sm) behouden hun kleinere maat. form-select-sm hoort er
   expliciet bij: zonder deze regel drukt de padding hierboven een `sm`-select
   terug naar volle hoogte (de actiebalk gebruikt uitsluitend sm-velden). */
.form-control-sm,
.form-select-sm { padding: .35rem 2rem .35rem .65rem; font-size: .875rem; }
.form-control-sm { padding-right: .65rem; }

/* --------------------------------------------------------------------------
   Search-pill — zoekveld als pill met icoon links
   -------------------------------------------------------------------------- */
.search-pill { position: relative; }
.search-pill > svg {
  position: absolute;
  left: .75rem;
  top: 50%;
  transform: translateY(-50%);
  width: .95rem;
  height: .95rem;
  color: var(--theme-text-color-gray);
  pointer-events: none;
}
.search-pill .form-control {
  border-radius: 50rem;   /* enige afwijking: een zoekveld is bewust een pill */
  background: var(--ui-veld-bg);
  border: 1px solid var(--ui-veld-rand);
  color: var(--theme-text-color);
  /* links = icoon (.75rem) + icoonbreedte (.95rem) + .3rem ademruimte */
  padding: .625rem 1rem .625rem 2rem;
  transition: var(--ui-veld-overgang);
}
/* Zoekveld ZONDER het loepje: dan hoort de linkerpadding symmetrisch te zijn aan de
   rechter, anders staat er 2rem gereserveerde ruimte voor een icoon dat er niet is en
   leest dat als een gat vóór de placeholder. Gebruik dit alleen waar het icoon niets
   toevoegt omdat de context het al zegt — in het select-paneel staat er een kop boven
   en is dit het enige invoerveld. Een losstaand zoekveld op een overzichtspagina houdt
   het icoon: daar is het juist het signaal dát je kunt zoeken. */
.search-pill-kaal .form-control { padding-left: 1rem; }
.search-pill .form-control:hover:not(:focus) { border-color: var(--ui-veld-rand-hover); }
.search-pill .form-control:focus {
  background: var(--ui-veld-bg-actief);
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
}

/* --------------------------------------------------------------------------
   Btn-icon-tile — icoon-only knop (o.a. het "…"-overflow-menu op kaarten)
   -------------------------------------------------------------------------- */
.btn-icon-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: .75rem;
  border: 1px solid transparent;
  background: transparent;
  color: var(--theme-text-color-gray);
  padding: 0;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.btn-icon-tile:hover,
.btn-icon-tile[aria-expanded="true"] {
  background: var(--theme-color-4);
  color: var(--theme-text-color);
}
.btn-icon-tile svg { width: 1.15rem; height: 1.15rem; }
.btn-icon-tile::after { display: none; } /* geen Bootstrap-caret */

/* Danger-variant: dezelfde tint als .btn-soft-danger, alleen in de vierkante
   icon-tile-vorm. Voor een icoon-only "haal deze regel weg" in een formulier of
   lijstrij — het rode kruisje in een reeks-dagrij, een regel van een offerte.
   MOET ná .btn-icon-tile staan: die zet background/color op transparant/grijs met
   dezelfde specificiteit, dus een eerdere definitie zou stil verliezen. Om die reden
   werkt `btn-soft-danger btn-icon-tile` combineren óók niet — vandaar deze variant.
   Kleuren komen uit hetzelfde danger-palet als de soft-knoppen en de status-pills, dus
   een rij met een danger-pill en dit kruisje valt niet in twee roden uiteen. */
.btn-icon-tile-danger {
  background: var(--bs-danger-soft);
  color: var(--bs-danger);
  border-color: rgba(var(--bs-danger-rgb), .22);
}
.btn-icon-tile-danger:hover,
.btn-icon-tile-danger:focus-visible,
.btn-icon-tile-danger[aria-expanded="true"] {
  background: color-mix(in srgb, var(--bs-danger) 26%, var(--theme-color-body));
  color: var(--bs-danger);
  border-color: rgba(var(--bs-danger-rgb), .38);
}

/* --------------------------------------------------------------------------
   Dropdown-soft — VERHUISD naar css/styles-sb.css.php (zoek: DROPDOWN-SOFT).
   Reden: de top-nav is een gedeelde include en staat dus op alle ~331 pagina's,
   terwijl ui-stijl.css er maar op ~111 geladen wordt. Zolang het component hier
   stond, kon de top-nav het niet gebruiken zonder op tweederde van de app een
   randloos, schaduwloos menu op te leveren. Definieer hem NIET opnieuw hier —
   ui-stijl.css laadt ná de themabundel, dus een tweede kopie wint stil en gaat
   op termijn uit de pas lopen. Alle tokens die hij gebruikt zijn globaal
   (brand_css_vars.php); alleen --ui-radius was lokaal en heeft daar een fallback.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Group-picker — selectie van genodigde/toegankelijke groepen
   -------------------------------------------------------------------------- */
.ui-group-picker {
  padding: .9rem;
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius);
  background: var(--theme-color-4);
}
.ui-group-picker-title {
  display: flex;
  align-items: center;
  gap: .65rem;
  margin-bottom: .8rem;
  color: var(--theme-text-color);
  font-size: .9rem;
  font-weight: 700;
}
.ui-group-picker-option {
  position: relative;
  min-height: 2.5rem;
  padding: .55rem .8rem .55rem 2.55rem;
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius-sm);
  background: var(--theme-color-card);
  transition: background-color .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.ui-group-picker-option:has(.form-check-input:checked) {
  border-color: rgba(var(--primary-color-rgb), .35);
  background: var(--bs-primary-soft);
  box-shadow: 0 .2rem .65rem rgba(var(--custom-theme-box-shadow-rgb), .08);
}
.ui-group-picker-option:has(.form-check-input:focus-visible) {
  outline: 2px solid rgba(var(--primary-color-rgb), .4);
  outline-offset: 2px;
}
.ui-group-picker-option .form-check-input {
  position: absolute;
  top: 50%;
  left: .8rem;
  float: none;
  margin: 0;
  transform: translateY(-50%);
}
.ui-group-picker-option .form-check-label {
  color: var(--theme-text-color);
  cursor: pointer;
  font-size: .875rem;
  font-weight: 600;
}
.ui-group-picker-option .text-muted { color: var(--theme-text-color-gray) !important; font-weight: 500; }

/* Avatarstrook die een volledige ledenlijst opent. */
.ui-invited-members-trigger {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  min-height: 2.5rem;
  padding: .3rem .7rem .3rem .4rem;
  border: 1px solid var(--theme-color-3);
  border-radius: 50rem;
  background: var(--theme-color-card);
  color: var(--theme-text-color-gray);
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, box-shadow .15s ease, color .15s ease;
}
.ui-invited-members-trigger:hover,
.ui-invited-members-trigger:focus-visible {
  border-color: rgba(var(--primary-color-rgb), .35);
  background: var(--bs-primary-soft);
  box-shadow: 0 .2rem .65rem rgba(var(--custom-theme-box-shadow-rgb), .08);
  color: var(--brand-on-surface);
}
.ui-invited-members-trigger:focus-visible { outline: 2px solid rgba(var(--primary-color-rgb), .4); outline-offset: 2px; }
.ui-invited-members-trigger > svg { width: 1rem; height: 1rem; }
.ui-member-list { display: grid; gap: .45rem; }
/* De INLINE variant (lijst klapt uit onder de strook, `'inline' => true`) krijgt een
   eigen scrollgebied. Zonder cap groeide die lijst gewoon door: bij een vereniging met
   318 ontvangers is dat ~19.000px die de kanaalpreviews en de verzendknop uit beeld
   duwen — precies in de "Leden informeren"-modal, waar #evMeldingModal een gewone
   `modal-dialog modal-lg` is (géén modal-dialog-scrollable), dus de hele PAGINA scrollt.
   De MODAL-variant krijgt bewust GEEN cap: die zit in een `modal-dialog-scrollable`,
   waar de .modal-body al de scroll-eigenaar is — een tweede scroller daarin is alleen
   maar verwarrend.
   De helper zet daarom een expliciete modifier-klasse op de inline lijst; beide
   varianten dragen namelijk dezelfde `data-doelgroep-lijst`.
   `overscroll-behavior: contain` houdt de scroll in de lijst; js/modal-sheet.js is
   daarnaast aangepast zodat slepen-om-te-sluiten deze geneste scroller niet kaapt. */
.ui-member-list-inline {
  max-height: min(20rem, 40vh);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Ruimte voor de scrollbar, zodat de rijen niet tegen de balk plakken. */
  padding-right: .15rem;
}
.ui-member-list-item {
  display: flex;
  align-items: center;
  gap: .75rem;
  min-height: 3.25rem;
  padding: .45rem .6rem;
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius-sm);
  background: var(--theme-color-card);
  color: var(--theme-text-color);
  font-size: .9rem;
  font-weight: 600;
}
.ui-member-list-loading { color: var(--theme-text-color-gray); font-size: .9rem; }

/* --------------------------------------------------------------------------
   Modal-soft — modal in de zachte stijl (icon-tile in de titel)
   -------------------------------------------------------------------------- */
.modal-soft .modal-content {
  border-radius: var(--ui-radius-lg);
  border: 1px solid var(--theme-color-3);
  background: var(--theme-color-card);
  box-shadow: 0 1.5rem 3rem rgba(var(--custom-theme-box-shadow-rgb), .2);
}
/* …maar NIET als het dialoog fullscreen is. Bootstrap zet daar `border-radius: 0`,
   alleen: die regel staat in styles-sb.css.php en dit bestand laadt LATER met dezelfde
   specificiteit (0,2,0) — dus won de ronding, en kreeg een schermvullende modal
   afgeronde hoeken met een randje in het niets. Deze override is specifieker (0,3,0).
   Rand en schaduw horen er evenmin: die zijn er om een zwevende kaart van de
   achtergrond te scheiden, en een fullscreen-modal zweeft niet. */
.modal-soft .modal-dialog[class*="modal-fullscreen"] .modal-content {
  border-radius: 0;
  border: 0;
  box-shadow: none;
}
/* De achtergrond van een fullscreen-modal loopt tot de schermrand (dat is het contract:
   achtergrond bleedt), maar de INHOUD van de header moet wijken voor de statusbalk —
   anders staat de titel onder de Dynamic Island. styles-sb.css.php zet daarvoor
   `[class*="modal-fullscreen"] .modal-header { padding-top: calc(1rem + var(--sa-t)) }`,
   maar `.modal-soft .modal-header` hierboven is specifieker, laadt later, én gebruikt de
   `padding`-SHORTHAND — die reset de padding-top dus stilzwijgend. Vandaar deze regel. */
.modal-soft .modal-dialog[class*="modal-fullscreen"] .modal-header {
  padding-top: calc(1.25rem + var(--sa-t));
}
.modal-soft .modal-dialog[class*="modal-fullscreen"] .modal-footer {
  padding-bottom: calc(1rem + var(--sa-b));
}
.modal-soft .modal-header {
  border-bottom: 1px solid var(--theme-color-3);
  padding: 1.25rem 1.5rem;
}
.modal-soft .modal-title {
  display: flex;
  align-items: center;
  gap: .875rem;
  font-weight: 700;
}
.modal-soft .modal-body { padding: 1.25rem 1.5rem; }
.modal-soft .modal-footer {
  border-top: 1px solid var(--theme-color-3);
  padding: 1rem 1.5rem;
}
/* Footer-knoppen automatisch als pills — markup hoeft alleen .modal-soft te krijgen */
.modal-soft .modal-footer .btn {
  border-radius: 50rem;
  font-weight: 600;
}
/* Tabellen in een soft-modal doen automatisch mee met de table-soft-look */
.modal-soft .modal-body .table thead th {
  font-size: .8125rem;
  font-weight: 600;
  color: var(--theme-text-color-gray);
  border-bottom: 1px solid var(--theme-color-3);
}
.modal-soft .modal-body .table tbody td {
  border-bottom: 1px solid var(--theme-color-3);
  vertical-align: middle;
}
.modal-soft .modal-body .table tbody tr:last-child td { border-bottom: 0; }

/* Het bottom-sheet-patroon (modals < 576px) staat NIET hier maar in
   css/styles-sb.css.php (zoek: BOTTOM-SHEET). Reden: het geldt app-breed voor
   ELKE modal, en ui-stijl.css wordt per pagina geladen. Eén bron — geen tweede
   kopie hier, dat is precies hoe de safe-area-regels ooit vier keer bestonden.
   Gedrag: js/modal-sheet.js. Opt-out: .modal-no-sheet op de .modal. */

/* --------------------------------------------------------------------------
   Disclosure — uitklapbare kaart: kop is de hele knop (icon-tile + titel +
   optionele count-chip + draaiende chevron), inhoud eronder achter een divider.
   Draait op Bootstrap collapse; de kop is een <button> (toetsenbord + a11y),
   nooit een <a href="#..."> — dat sprong bij een klik naar boven.
   -------------------------------------------------------------------------- */
.disclosure-soft { overflow: hidden; }
.disclosure-toggle {
  display: flex;
  align-items: center;
  gap: .875rem;
  width: 100%;
  padding: 1.1rem 1.35rem;
  border: 0;
  background: transparent;
  border-radius: inherit;
  color: var(--theme-text-color);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color .15s ease;
}
.disclosure-toggle:hover,
.disclosure-toggle:focus-visible { background: rgba(var(--theme-color-3-rgb), .35); }
.disclosure-title {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  font-size: 1rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.disclosure-sub {
  display: block;
  font-size: .85rem;
  font-weight: 400;
  color: var(--theme-text-color-gray);
  white-space: normal;
}
.disclosure-chevron {
  flex-shrink: 0;
  color: var(--theme-text-color-gray);
  transition: transform .25s ease;
}
.disclosure-toggle[aria-expanded="true"] .disclosure-chevron { transform: rotate(180deg); }
/* Inhoud: standaard met kaartpadding, of -flush als een tabel/data-list tot de rand loopt */
.disclosure-panel {
  padding: 1.1rem 1.35rem 1.35rem;
  border-top: 1px solid var(--theme-color-3);
}
.disclosure-panel-flush { padding: 0; }

/* --------------------------------------------------------------------------
   Empty state — lege lijst: icoon + titel + uitleg + CTA
   -------------------------------------------------------------------------- */
.empty-state { text-align: center; padding: 2.5rem 1.5rem; }
.empty-state .icon-tile { margin-bottom: 1rem; }
.empty-state-title {
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--theme-text-color);
  margin-bottom: .35rem;
}
.empty-state-sub {
  color: var(--theme-text-color-gray);
  font-size: .9rem;
  max-width: 26rem;
  margin: 0 auto 1.25rem;
}

/* --------------------------------------------------------------------------
   Alert-soft — VERHUISD naar css/styles-sb.css.php (zoek: ALERT-SOFT), 2026-08-10.
   --------------------------------------------------------------------------
   Zelfde reden als status-pill, count-chip en dropdown-soft: ui-stijl.css laadt per
   pagina, maar includes/loggedin/show-message.php is de centrale flash-renderer en
   draait op ELKE pagina. Zonder de componentlaag zou een melding daar een kleurloos
   blok worden. Eén bron, geen kopie.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Table-soft — echte tabel in de zachte stijl (naast .data-list)
   -------------------------------------------------------------------------- */
.table-soft { margin-bottom: 0; --bs-table-bg: transparent; }
.table-soft thead th {
  font-size: .8125rem;
  font-weight: 600;
  color: var(--theme-text-color-gray);
  border-bottom: 1px solid var(--theme-color-3);
  padding: .6rem .75rem;
}
.table-soft tbody td {
  padding: .875rem .75rem;
  border-bottom: 1px solid var(--theme-color-3);
  vertical-align: middle;
  color: var(--theme-text-color);
}
.table-soft tbody tr:last-child td { border-bottom: 0; }
/* Sorteerbare kop: Feather-chevron i.p.v. het oude unicode ▲/▼. feather.replace()
   maakt er een 24px-SVG van en erft de class — hier terugbrengen naar kop-formaat.
   Alleen svg.sort-arrow: de <span class="sort-arrow">▲</span> op nog niet omgezette
   pagina's (o.a. fetch_users.php) blijft ongemoeid. */
svg.sort-arrow {
  width: .85rem;
  height: .85rem;
  vertical-align: middle;
  color: var(--brand-on-surface);
}
/* Sorteerkop als LINK (i.p.v. de oudere `onclick`-variant op de <th> zelf): werkt zonder
   JS, is deelbaar/bookmarkbaar en houdt de sortering in de URL — nodig omdat deze lijsten
   gepagineerd zijn en client-side sorteren dan alleen de zichtbare pagina zou ordenen.
   Flex zodat de chevron netjes naast de tekst staat: een feather-SVG staat op baseline en
   zakt anders weg onder de kop (het terugkerende icoon-uitlijnprobleem). */
.th-sort {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  color: inherit;
  text-decoration: none;
  /* De client-side variant (js/tabel-sorteer.js) is een <button> in plaats van een <a>,
     want daar valt niets te navigeren. Zonder deze reset tekent de browser er zijn eigen
     grijze knop-chrome omheen en staat er een rijtje vakjes in de tabelkop. Font/kleur
     erven, zodat beide varianten er identiek uitzien. */
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.th-sort:hover { color: var(--primary-color); }
.th-sort-actief { color: var(--primary-color); }
th[aria-sort] { white-space: nowrap; }
/* Hover: én background-color én de inset-box-shadow die Bootstrap 5.3 zelf
   gebruikt voor rij-tinten (background alleen wordt door de table-vars
   overschilderd), met genoeg alpha om zichtbaar te zijn. */
.table-soft tbody tr:hover td {
  background-color: rgba(var(--theme-color-3-rgb), .5);
  box-shadow: inset 0 0 0 9999px rgba(var(--theme-color-3-rgb), .5);
}
/* Hele rij klikbaar. Een <tr> mag geen <a> zijn en mag er ook geen omhullen
   (ongeldige HTML), dus dit is bewust JS-gedreven: de pagina delegeert de klik
   en negeert klikken op echte controls (knop/link/formulier in de rij, zoals
   het kebab-menu). Deze class doet dan ook alleen de affordance. De rij zelf
   krijgt GEEN tabindex/role — dat sloopt de tabelsemantiek; de toetsenbord-
   route blijft de actieknop in de rij. */
.table-soft tbody tr.table-row-clickable { cursor: pointer; }
.table-soft tbody tr.table-row-clickable :is(a, button, input, select, textarea, label) {
  cursor: auto;
}
.table-soft tbody tr.table-row-clickable :is(a, button) { cursor: pointer; }

/* --------------------------------------------------------------------------
   Table-fixed-cols — vaste kolombreedtes (table-layout: fixed + <colgroup>)
   MET een bodembreedte, zodat de tabel op smalle schermen horizontaal scrollt
   i.p.v. zijn kolommen tot min-content te knijpen.

   Waarom de min-width erbij hoort: `table-layout: fixed` negeert de inhoud
   volledig, dus een tabel met width:100% wordt op een telefoon nooit breder
   dan de viewport. De percentages uit de colgroup worden dan minieme kolommen
   (11% van 375px ≈ 40px) en alles wat niet afbreekt — een status-pill, een
   bedrag met &nbsp;, een datum — loopt buiten zijn cel dóór de buurkolom heen.
   Met deze min-width wordt de omhullende .table-responsive weer breder dan het
   scherm en doet die gewoon zijn werk (horizontaal scrollen).

   Gebruik: <div class="table-responsive"><table class="table … table-fixed-cols"
   style="--tbl-min: 48rem"> + een <colgroup> die optelt tot 100%.
   Stem --tbl-min af op het aantal kolommen (vuistregel ~7rem per kolom).
   -------------------------------------------------------------------------- */
.table-fixed-cols {
  table-layout: fixed;
  min-width: var(--tbl-min, 44rem);
}

/* --------------------------------------------------------------------------
   Avatar-stack — overlappende avatars ("wie doet mee") + rest-teller
   (avatars zelf altijd via renderUserAvatar() / window.Avatar)
   -------------------------------------------------------------------------- */
.avatar-stack { display: inline-flex; align-items: center; }
.avatar-stack > * { flex-shrink: 0; border: 2px solid var(--theme-color-card); border-radius: 50%; }
.avatar-stack > * + * { margin-left: -.55rem; }
.avatar-stack-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--theme-color-4);
  color: var(--theme-text-color-gray);
  font-size: .75rem;
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   Badge-teller — dot/teller op iconen + inline count-chip in tabs
   -------------------------------------------------------------------------- */
.badge-anchor { position: relative; display: inline-flex; }
.badge-count {
  position: absolute;
  top: -.4rem;
  right: -.4rem;
  min-width: 1.2rem;
  height: 1.2rem;
  padding: 0 .3rem;
  border-radius: 50rem;
  background: var(--primary-color);
  color: var(--primary-bg-text-color);
  font-size: .7rem;
  font-weight: 700;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--theme-color-card);
}
.badge-count-danger { background: var(--bs-danger); color: #fff; }
.badge-dot {
  position: absolute;
  top: -.15rem;
  right: -.15rem;
  width: .65rem;
  height: .65rem;
  border-radius: 50%;
  background: var(--bs-danger);
  border: 2px solid var(--theme-color-card);
}
/* .count-chip is VERHUISD naar css/styles-sb.css.php (zoek: COUNT-CHIP), 2026-08-10 —
   zelfde reden als .status-pill: tellers worden ook gerenderd door AJAX-partials
   (fetch_payments.php) en op pagina's die ui-stijl.css niet laden. Eén bron, geen kopie.
   De .pill-tab.active-variant blijft hieronder, want pill-tabs zijn wél pagina-gebonden. */
.pill-tab.active .count-chip {
  background: rgba(var(--primary-bg-text-color-rgb), .22);
  color: var(--primary-bg-text-color);
}

/* --------------------------------------------------------------------------
   Line-items — genummerde regels met bedragen + totaalregel (offertes)
   -------------------------------------------------------------------------- */
.line-items { display: flex; flex-direction: column; }
.line-item {
  display: flex;
  align-items: center;
  gap: .875rem;
  padding: .75rem 0;
  border-top: 1px solid var(--theme-color-3);
}
.line-item:first-child { border-top: 0; }
.line-item-num {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: .55rem;
  background: var(--theme-color-4);
  color: var(--theme-text-color-gray);
  font-size: .78rem;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.line-item-desc { min-width: 0; font-weight: 500; color: var(--theme-text-color); }
.line-item-sub { font-size: .8125rem; color: var(--theme-text-color-gray); }
.line-item-amount {
  margin-left: auto;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Subtotalen tussen de regels en de totaalregel (btw, servicekosten, korting) */
.line-items-subtotals {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding-top: .75rem;
  margin-top: .25rem;
  border-top: 1px solid var(--theme-color-3);
  font-size: .9rem;
  color: var(--theme-text-color-gray);
}
.line-items-subtotals > div {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}
.line-items-subtotals > div > span:last-child {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.line-items-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: .875rem;
  margin-top: .25rem;
  border-top: 2px solid var(--theme-color-3);
  font-weight: 800;
  font-size: 1.1rem;
  color: var(--theme-text-color);
}
.line-items-total > span:last-child { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   Progress-soft — voortgangsbalk (quota, aanwezigheid, doelen)
   -------------------------------------------------------------------------- */
.progress-soft {
  height: .5rem;
  border-radius: 50rem;
  background: var(--theme-color-4);
  overflow: hidden;
}
.progress-soft .progress-bar {
  background: var(--primary-color);
  border-radius: 50rem;
}
.progress-labeled {
  display: flex;
  justify-content: space-between;
  font-size: .8125rem;
  color: var(--theme-text-color-gray);
  margin-bottom: .4rem;
}
.progress-labeled strong { color: var(--theme-text-color); }

/* Progress-stapel — ÉÉN balk met meerdere statussen naast elkaar (een lijst die in
   fasen doorloopt: campagne-items, aanmeldingen, import-batches). Anders dan
   progress-soft toont dit niet "hoe ver", maar "waar zit alles nu" — en dat is precies
   wat je wilt zien zodra 'nog niet begonnen' en 'wacht op mij' verschillende dingen zijn.
   REGEL: de segmenten moeten samen 100% dekken, dus elk item van de lijst hoort in
   precies één segment; ontbreekt er een status, dan liegt de balk over de rest. De
   volgorde is de reis van links (klaar) naar rechts (nog niet begonnen), zodat de balk
   bij voortgang van rechts naar links vult. Kleur = de statuskleuren-tabel, dus dezelfde
   betekenis als de status-pills. De legenda (progress-legenda) hoort erbij: zonder
   benoeming is een gekleurd blokje een raadsel. */
.progress-stapel {
  display: flex;
  height: .5rem;
  border-radius: 50rem;
  background: var(--theme-color-4);
  /* BEWUST GEEN overflow:hidden: de hover-uitleg hieronder is een ::after ván een segment
     en zou daar tegen de baan aan worden afgeknipt (0,5rem hoog — je zag er niets van).
     De ronde uiteinden komen daarom van het eerste en laatste segment zelf. */
}
.progress-stapel-deel { height: 100%; min-width: 0; position: relative; }
.progress-stapel-deel:first-child { border-radius: 50rem 0 0 50rem; }
.progress-stapel-deel:last-child  { border-radius: 0 50rem 50rem 0; }
/* Bij hover dimt de rest: dat koppelt de uitleg zichtbaar aan hét segment waar je op staat
   (de balk is maar 8px hoog, dus zonder die koppeling raad je welk stukje je te pakken hebt)
   en het vertelt meteen dat er iets te halen valt. */
.progress-stapel:hover .progress-stapel-deel { opacity: .45; }
.progress-stapel .progress-stapel-deel:hover { opacity: 1; }
.progress-stapel-deel { transition: opacity .12s ease; }

/* Hover-uitleg op een segment — puur CSS, via data-uitleg.
   WAAROM GEEN `title`: de browser-tooltip komt pas na ~1s, is niet te stijlen en toont in
   een WebView soms niets. WAAROM GEEN Bootstrap-tooltip: die moet je per pagina initialiseren
   (en na een AJAX-injectie opnieuw) — precies het soort stap dat je vergeet, en dan is er
   niets. Dit werkt zodra de CSS er is.
   DIT MAG NOOIT DE ENIGE PLEK ZIJN WAAR DE INFORMATIE STAAT: hover bestaat niet op een
   telefoon. De legenda onder de balk draagt dezelfde tekst (label, aantal, percentage) en is
   de echte bron; de hover is de sneltoets voor wie een muis heeft. */
.progress-stapel-deel[data-uitleg]::after,
.progress-stapel-deel[data-uitleg]::before {
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease .04s;
}
.progress-stapel-deel[data-uitleg]::after {
  content: attr(data-uitleg);
  position: absolute;
  bottom: calc(100% + .55rem);
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  white-space: nowrap;
  padding: .3rem .55rem;
  border-radius: .5rem;
  font-size: .8125rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--theme-text-color);
  /* Zelfde familie als toast-soft/popover-soft: doorschijnende rand in de tekstkleur, de
     schaduw doet het zweefwerk. */
  background: var(--theme-color-card);
  border: 1px solid rgba(var(--theme-text-color-rgb), .1);
  box-shadow: 0 .5rem 1.25rem rgba(var(--custom-theme-box-shadow-rgb), .22);
}
.progress-stapel-deel[data-uitleg]::before {
  content: "";
  position: absolute;
  bottom: calc(100% + .3rem);
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  z-index: 4;
  width: .5rem;
  height: .5rem;
  background: var(--theme-color-card);
  border-right: 1px solid rgba(var(--theme-text-color-rgb), .1);
  border-bottom: 1px solid rgba(var(--theme-text-color-rgb), .1);
}
.progress-stapel-deel[data-uitleg]:hover::after,
.progress-stapel-deel[data-uitleg]:hover::before { opacity: 1; }
/* Randgevallen: bij een SMAL eerste/laatste segment zou een gecentreerde ballon links of
   rechts buiten de kaart vallen — en html/body clippen horizontaal (overflow-x: clip), dus
   dat wordt afgesneden in plaats van scrollbaar. Daarom lijnt de ballon daar uit op de rand
   van het segment. Het pijltje schuift mee naar diezelfde kant, want een pijl die naast zijn
   eigen ballon zweeft is erger dan een pijl die niet precies in het midden staat. */
.progress-stapel-deel[data-uitleg]:first-child::after { left: 0; transform: none; }
.progress-stapel-deel[data-uitleg]:first-child::before { left: .9rem; transform: rotate(45deg); }
.progress-stapel-deel[data-uitleg]:last-child::after { left: auto; right: 0; transform: none; }
.progress-stapel-deel[data-uitleg]:last-child::before { left: auto; right: .9rem; transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
  .progress-stapel-deel,
  .progress-stapel-deel[data-uitleg]::after,
  .progress-stapel-deel[data-uitleg]::before { transition: none; }
}
.progress-legenda {
  display: flex;
  flex-wrap: wrap;
  gap: .15rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: .8125rem;
  color: var(--theme-text-color-gray);
}
.progress-legenda li { display: flex; align-items: center; gap: .4rem; }
.progress-legenda strong { color: var(--theme-text-color); }
.progress-legenda-stip {
  width: .6rem;
  height: .6rem;
  flex: 0 0 auto;
  border-radius: 50rem;
}
.progress-stapel-deel.is-success, .progress-legenda-stip.is-success { background: var(--bs-success); }
.progress-stapel-deel.is-primary, .progress-legenda-stip.is-primary { background: var(--primary-color); }
.progress-stapel-deel.is-warning, .progress-legenda-stip.is-warning { background: var(--bs-warning); }
.progress-stapel-deel.is-info,    .progress-legenda-stip.is-info    { background: var(--bs-info); }
.progress-stapel-deel.is-danger,  .progress-legenda-stip.is-danger  { background: var(--bs-danger); }
/* "Nog niet begonnen" is bewust bijna de baan zelf: het is de rest, geen prestatie.
   Het legenda-stipje krijgt wél een randje, anders is het op een lichte kaart onzichtbaar. */
.progress-stapel-deel.is-leeg,    .progress-legenda-stip.is-leeg    { background: var(--bs-info-soft); }
.progress-legenda-stip.is-leeg { box-shadow: inset 0 0 0 1px rgba(var(--theme-text-color-rgb), .18); }

/* --------------------------------------------------------------------------
   Stepper — stap 1→2→3-indicator voor aanmaak-/wizardflows
   -------------------------------------------------------------------------- */
.stepper { display: flex; align-items: center; }
.stepper-step { display: flex; align-items: center; gap: .6rem; }
.stepper-dot {
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: .85rem;
  border: 2px solid var(--theme-color-3);
  color: var(--theme-text-color-gray);
  background: var(--theme-color-card);
  flex-shrink: 0;
}
.stepper-dot svg { width: 1rem; height: 1rem; }
.stepper-step.is-done .stepper-dot {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: var(--primary-bg-text-color);
}
.stepper-step.is-active .stepper-dot {
  border-color: var(--primary-color);
  color: var(--brand-on-surface);
  background: var(--bs-primary-soft);
}
.stepper-label { font-size: .85rem; font-weight: 600; color: var(--theme-text-color-gray); }
.stepper-step.is-active .stepper-label,
.stepper-step.is-done .stepper-label { color: var(--theme-text-color); }
.stepper-line {
  flex: 1;
  height: 2px;
  background: var(--theme-color-3);
  margin: 0 .75rem;
  min-width: 1.5rem;
}
.stepper-line.is-done { background: rgba(var(--primary-color-rgb), .45); }

/* --------------------------------------------------------------------------
   Accordion-soft — Bootstrap-accordion in de zachte stijl
   -------------------------------------------------------------------------- */
.accordion-soft { --bs-accordion-bg: transparent; --bs-accordion-border-color: transparent; }
.accordion-soft .accordion-item {
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius) !important;
  background: var(--theme-color-card);
  overflow: hidden;
}
.accordion-soft .accordion-item + .accordion-item { margin-top: .75rem; }
.accordion-soft .accordion-button {
  background: transparent;
  color: var(--theme-text-color);
  font-weight: 600;
  padding: 1rem 1.25rem;
  box-shadow: none;
  gap: .875rem;
}
.accordion-soft .accordion-button:not(.collapsed) { color: var(--brand-on-surface); }
.accordion-soft .accordion-button:focus { box-shadow: none; }
.accordion-soft .accordion-body {
  padding: .25rem 1.25rem 1.25rem;
  color: var(--theme-text-color-gray);
  font-size: .9rem;
}

/* --------------------------------------------------------------------------
   Toast-soft — meldingsstijl (doel-stijl voor showNotification bij uitrol)
   -------------------------------------------------------------------------- */
.toast-soft {
  display: flex;
  align-items: center;
  gap: .875rem;
  padding: .875rem 1rem;
  border-radius: var(--ui-radius);
  background: var(--theme-color-card);
  /* Subtiele rand in dezelfde toon als het sluitkruisje (tekstkleur, sterk
     doorschijnend) — de schaduw doet het "zweef"-werk, niet de rand. */
  border: 1px solid rgba(var(--theme-text-color-rgb), .1);
  box-shadow: 0 .875rem 2.25rem rgba(var(--custom-theme-box-shadow-rgb), .22);
  max-width: 22rem;
}
.toast-soft-title { font-weight: 700; font-size: .9rem; color: var(--theme-text-color); }
.toast-soft-text { font-size: .85rem; color: var(--theme-text-color-gray); }

/* --------------------------------------------------------------------------
   Pagination-pill — paginatie als pills
   -------------------------------------------------------------------------- */
.pagination-pill { display: flex; gap: .35rem; margin: 0; }
.pagination-pill .page-item .page-link {
  border-radius: 50rem;
  border: 1px solid var(--theme-color-3);
  background: var(--theme-color-card);
  color: var(--theme-text-color);
  min-width: 2.25rem;
  height: 2.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 .6rem;
  font-weight: 600;
  font-size: .875rem;
  box-shadow: none;
}
.pagination-pill .page-item .page-link:hover {
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  border-color: rgba(var(--primary-color-rgb), .2);
}
.pagination-pill .page-item.active .page-link {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: var(--primary-bg-text-color);
}
.pagination-pill .page-item.disabled .page-link { opacity: .45; pointer-events: none; }
.pagination-pill .page-link svg { width: 1rem; height: 1rem; }

/* --------------------------------------------------------------------------
   Skeleton — pulserende placeholders tijdens AJAX-laden
   -------------------------------------------------------------------------- */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--theme-color-4);
  border-radius: .5rem;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(var(--theme-text-color-rgb), .07), transparent);
  transform: translateX(-100%);
  animation: skeleton-shimmer 1.4s infinite;
}
@keyframes skeleton-shimmer { 100% { transform: translateX(100%); } }
.skeleton-line { height: .8rem; }
.skeleton-line-sm { height: .6rem; }
.skeleton-circle { border-radius: 50%; }
.skeleton-tile { border-radius: .875rem; }
@media (prefers-reduced-motion: reduce) {
  .skeleton::after { animation: none; }
}

/* --------------------------------------------------------------------------
   Mini-seg — segmented mini-filter voor in een kaart-header
   (kleine broer van .pill-tabs; iOS-stijl segment op neutraal vlak)
   -------------------------------------------------------------------------- */
.mini-seg {
  display: inline-flex;
  background: var(--theme-color-4);
  border-radius: 50rem;
  padding: .2rem;
  gap: .15rem;
}
.mini-seg-btn {
  border: 0;
  background: transparent;
  border-radius: 50rem;
  padding: .3rem .8rem;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--theme-text-color-gray);
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
  white-space: nowrap;
}
.mini-seg-btn:hover { color: var(--theme-text-color); }
.mini-seg-btn.active {
  background: var(--theme-color-card);
  color: var(--theme-text-color);
  box-shadow: 0 1px 3px rgba(var(--custom-theme-box-shadow-rgb), .18);
}

/* Ja/nee-variant: de actieve stand kleurt SEMANTISCH in plaats van neutraal-wit.
   Voor een schakelaar met precies twee uitkomsten waar de uitkomst zelf iets betekent —
   aanwezig/afwezig bij het afvinken van een training, en straks hetzelfde bij een bardienst
   of een evenement. De neutrale mini-seg is een VIEW-switcher ("welk lijstje zie ik"); daar
   zou groen/rood liegen, want er is geen goed of fout. Hier is dat er juist wel, en zonder
   kleur moet je per rij het icoontje lezen om te zien wat er staat.

   De tint wordt zelf gemengd met --theme-color-CARD en komt niet uit --bs-*-soft: die zijn
   gemengd met de BODY-kleur, en in dark mode is de body (#14191F) donkerder dan de kaart
   (#282F3B) — een "soft" vlak op een kaart wordt daar dus donkerder in plaats van lichter
   (zie de valkuil in de ui-stijl-skill). De niet-actieve stand blijft bewust grijs: twee
   gekleurde knoppen naast elkaar leest als twee waarschuwingen in plaats van als een keuze. */
.mini-seg-ja-nee .mini-seg-btn.active.is-ja {
  background: color-mix(in srgb, var(--bs-success) 18%, var(--theme-color-card));
  color: var(--bs-success);
}
.mini-seg-ja-nee .mini-seg-btn.active.is-nee {
  background: color-mix(in srgb, var(--bs-danger) 18%, var(--theme-color-card));
  color: var(--bs-danger);
}
/* Compacte maat voor gebruik ín een lijstrij: het raakvlak blijft ruim genoeg voor een duim
   (36px hoog), maar de knop claimt niet de halve rijbreedte. */
.mini-seg-sm .mini-seg-btn { padding: .35rem .6rem; }
.mini-seg-sm .mini-seg-btn .feather { width: 16px; height: 16px; display: block; }

/* --------------------------------------------------------------------------
   Chip-input — tag-/ledenselectie als chips in een invoerveld
   (visuele doelstijl voor het LidPicker-patroon)
   -------------------------------------------------------------------------- */
.chip-input {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
  padding: .45rem .6rem;
  border-radius: var(--ui-veld-radius);
  background: var(--ui-veld-bg);
  border: 1px solid var(--ui-veld-rand);
  cursor: text;
  transition: var(--ui-veld-overgang);
}
.chip-input:hover:not(:focus-within) { border-color: var(--ui-veld-rand-hover); }
.chip-input:focus-within {
  background: var(--ui-veld-bg-actief);
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
}
.chip-input > input {
  border: 0;
  outline: 0;
  background: transparent;
  flex: 1;
  min-width: 6rem;
  padding: .2rem;
  color: var(--theme-text-color);
  font-size: .875rem;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .3rem .4rem .3rem .7rem;
  border-radius: 50rem;
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  font-size: .8125rem;
  font-weight: 600;
  line-height: 1.2;
}
.chip .user-avatar { margin-left: -.35rem; }
/* Op getinte vlakken (card-soft-tint / card-inset-tint) valt de soft-chip weg
   tegen dezelfde soft-achtergrond → daar krijgt de chip kaartkleur + randje,
   zodat hij als pill leesbaar blijft (werkt in licht én donker thema). */
.card-soft-tint .chip,
.card-inset-tint .chip {
  background: var(--theme-color-card);
  border: 1px solid rgba(var(--primary-color-rgb), .25);
}
.card-soft-tint .chip-remove,
.card-inset-tint .chip-remove { background: rgba(var(--primary-color-rgb), .12); }
.chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  border: 0;
  padding: 0;
  background: rgba(var(--primary-color-rgb), .15);
  color: inherit;
  cursor: pointer;
}
.chip-remove:hover { background: rgba(var(--primary-color-rgb), .3); }
.chip-remove svg { width: .7rem; height: .7rem; }

/* --------------------------------------------------------------------------
   Dropzone-soft — file-upload met stippellijn
   (het <input type="file"> zelf blijft form-control-file w-100 — huisregel)
   -------------------------------------------------------------------------- */
.dropzone-soft {
  /* Dashed blijft: dat is het signaal "hier mag je slepen". Kleur + achtergrond
     komen wel uit de veld-tokens, zodat de zone bij de andere velden hoort. */
  border: 2px dashed var(--ui-veld-rand);
  border-radius: var(--ui-radius);
  padding: 2rem 1.5rem;
  text-align: center;
  background: var(--ui-veld-bg);
  cursor: pointer;
  transition: var(--ui-veld-overgang);
}
.dropzone-soft:hover,
.dropzone-soft.is-dragover {
  border-color: var(--ui-veld-rand-focus);
  background: var(--bs-primary-soft);
}
.dropzone-soft .icon-tile { margin-bottom: .75rem; }
.dropzone-title { font-weight: 600; color: var(--theme-text-color); }
.dropzone-sub { font-size: .85rem; color: var(--theme-text-color-gray); }

/* Meervoudsstand (data-dropzone-multi): de gekozen bestanden staan als data-list
   ONDER de zone. Die lijst is niet decoratief — hij is de enige plek waar zichtbaar
   wordt dát je er meerdere hebt, en de enige weg terug bij een verkeerde keuze.
   Rijen zijn <div>'s (geen links), dus geen hover: er valt niets te openen. */
.dropzone-lijst { margin-top: .75rem; }
.dropzone-lijst .data-list-row { padding: .5rem .75rem; cursor: default; }
.dropzone-lijst .data-list-title {
  /* Een lange bestandsnaam mag de rij niet uit elkaar duwen; de verwijderknop
     moet altijd op zijn plek blijven staan. */
  overflow-wrap: anywhere;
}
.dropzone-lijst .btn-icon-tile { flex-shrink: 0; }

/* --------------------------------------------------------------------------
   Profielfoto wijzigen — kiezen → bijsnijden → opslaan, in één modal-soft.
   Drie bouwstenen, waarvan er maar één nieuw is:
     1. kiezen     = dropzone-soft (+ icon-tile, dropzone-title/-sub) — hergebruik
     2. bijsnijden = .crop-stage    — het Cropper.js-canvas, ronde crop-box
     3. bedienen   = .crop-controls — roteren/zoomen met btn-icon-tile-knoppen
   Het gedrag zit in js/mijn-account-foto.js. Die module bindt op VASTE id's
   (#cropPhotoModal, #cropFileInput, #cropImage, #cropStep1/#cropStep2,
   #cropFooter1/#cropFooter2, #cropPickArea, #cropPickBtn, #cropRotL/#cropRotR,
   #cropZoomIn/#cropZoomOut, #cropBack, #cropSaveBtn/#cropSaveLbl,
   #cropUploadForm/#cropUploadFile) en post naar window.location.pathname — dus
   elke pagina met deze markup + een $_FILES['profileImage']-handler werkt.
   Productie: lid-bewerken.php (huisclasses) en mijn-account.php (nog op de oude
   ma-*-namen — die lopen hieronder als alias mee; bij de volgende aanraking van
   die pagina omzetten en de aliassen laten vallen).
   De DONKERE stage is een bewuste uitzondering op tokens-only: een bijsnij-
   canvas hoort donker te zijn zodat de foto het licht opeist — zelfde soort
   uitzondering als de push-preview, die de OS-lockscreen nabootst.
   -------------------------------------------------------------------------- */
.crop-stage,
.ma-crop-wrap {
  height: 300px;
  overflow: hidden;
  background: #1a1a1a;
  position: relative;
}
/* Ronde crop-box — een profielfoto wordt in de hele app rond getoond */
.crop-stage .cropper-view-box,
.crop-stage .cropper-face,
.ma-crop-wrap .cropper-view-box,
.ma-crop-wrap .cropper-face { border-radius: 50%; }
.crop-stage .cropper-view-box,
.ma-crop-wrap .cropper-view-box { outline-color: rgba(255, 255, 255, .75); }

.crop-controls,
.ma-crop-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .75rem;
  padding: .875rem 1rem;
  background: var(--theme-color-card);
  border-top: 1px solid var(--theme-color-3);
}
.crop-controls-sep,
.ma-crop-controls-sep {
  width: 1px;
  height: 1.5rem;
  background: var(--theme-color-3);
  margin: 0 .25rem;
}

/* Avatar + acties naast elkaar (profielfoto-rij boven een formulier) */
.avatar-edit-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   Quill-soft — rich-text editor (Quill 2 snow) in de huisstijl.
   Wrapper om de mount: <div class="quill-soft"><div data-rich-editor …></div></div>
   (init altijd via js/rich-editor.js — huismodule; vendor Quill per pagina laden).
   Overschrijft de globale Quill-regels in styles-sb.css.php, incl. de daar
   hardcoded #444-icoonkleuren (in dark mode vrijwel onzichtbaar) en de
   hardcoded blauwe hover — hier: thema-tekstkleur + merkkleur.
   -------------------------------------------------------------------------- */
.quill-soft { transition: var(--ui-veld-overgang); }
.quill-soft .ql-toolbar {
  background: var(--theme-color-4) !important;
  border: 1px solid var(--ui-veld-rand) !important;
  border-radius: var(--ui-veld-radius) var(--ui-veld-radius) 0 0;
  transition: var(--ui-veld-overgang);
  padding: .5rem .6rem;
}
.quill-soft .ql-container {
  background: var(--ui-veld-bg) !important;
  border: 1px solid var(--ui-veld-rand) !important;
  border-top: 0 !important;
  border-radius: 0 0 var(--ui-veld-radius) var(--ui-veld-radius);
  transition: var(--ui-veld-overgang);
  color: var(--theme-text-color) !important;
  font-size: .95rem;
}
.quill-soft .ql-editor {
  min-height: 9rem;
  background: transparent !important;
  color: var(--theme-text-color) !important;
}
.quill-soft .ql-editor.ql-blank::before {
  color: var(--theme-text-color-gray) !important;
  font-style: normal;
}
/* Hover op de hele editor, net als bij een gewoon invoerveld. */
.quill-soft:hover:not(:focus-within) .ql-toolbar,
.quill-soft:hover:not(:focus-within) .ql-container {
  border-color: var(--ui-veld-rand-hover) !important;
}
.quill-soft:focus-within {
  border-radius: var(--ui-veld-radius);
  box-shadow: var(--ui-veld-ring);
}
.quill-soft:focus-within .ql-toolbar,
.quill-soft:focus-within .ql-container {
  border-color: var(--ui-veld-rand-focus) !important;
}

/* Iconen: altijd zichtbaar, in beide thema's */
.quill-soft .ql-toolbar button { border-radius: .45rem; }
.quill-soft .ql-toolbar button svg .ql-stroke,
.quill-soft .ql-toolbar button svg .ql-even,
.quill-soft .ql-toolbar .ql-picker-label svg .ql-stroke {
  stroke: var(--theme-text-color) !important;
  fill: none !important;
}
.quill-soft .ql-toolbar button svg .ql-fill,
.quill-soft .ql-toolbar .ql-picker-label svg .ql-fill {
  fill: var(--theme-text-color) !important;
  stroke: none !important;
}
.quill-soft .ql-toolbar .ql-picker-label { color: var(--theme-text-color) !important; }

/* Hover/actief: merkkleur (geen hardcoded blauw) */
.quill-soft .ql-toolbar button:hover,
.quill-soft .ql-toolbar button.ql-active {
  background: var(--bs-primary-soft) !important;
}
.quill-soft .ql-toolbar button:hover svg .ql-stroke,
.quill-soft .ql-toolbar button.ql-active svg .ql-stroke,
.quill-soft .ql-toolbar .ql-picker-label:hover svg .ql-stroke {
  stroke: var(--brand-on-surface) !important;
  fill: none !important;
}
.quill-soft .ql-toolbar button:hover svg .ql-fill,
.quill-soft .ql-toolbar button.ql-active svg .ql-fill,
.quill-soft .ql-toolbar .ql-picker-label:hover svg .ql-fill {
  fill: var(--brand-on-surface) !important;
  stroke: none !important;
}
.quill-soft .ql-toolbar .ql-picker-label:hover,
.quill-soft .ql-toolbar .ql-picker.ql-expanded .ql-picker-label {
  color: var(--brand-on-surface) !important;
}

/* Picker-dropdowns in dropdown-soft-stijl */
.quill-soft .ql-toolbar .ql-picker-options {
  background: var(--theme-color-card) !important;
  border: 1px solid var(--theme-color-3) !important;
  border-radius: .75rem;
  box-shadow: 0 .75rem 2rem rgba(var(--custom-theme-box-shadow-rgb), .15);
  padding: .35rem;
}
.quill-soft .ql-picker-options .ql-picker-item {
  border-radius: .5rem;
  color: var(--theme-text-color) !important;
}
.quill-soft .ql-picker-options .ql-picker-item:hover {
  background: var(--bs-primary-soft) !important;
  color: var(--brand-on-surface) !important;
}
/* Kleur-swatches houden hun eigen kleur en compacte vorm */
.quill-soft .ql-color-picker .ql-picker-item { border-radius: .3rem; padding: 0; }

/* Link-tooltip */
.quill-soft .ql-tooltip {
  background: var(--theme-color-card) !important;
  border: 1px solid var(--theme-color-3) !important;
  border-radius: .75rem;
  box-shadow: 0 .75rem 2rem rgba(var(--custom-theme-box-shadow-rgb), .15);
  color: var(--theme-text-color) !important;
  z-index: 10;
}
.quill-soft .ql-tooltip input[type="text"] {
  background: var(--ui-veld-bg);
  border: 1px solid var(--ui-veld-rand);
  border-radius: .5rem;
  color: var(--theme-text-color);
  transition: var(--ui-veld-overgang);
}
.quill-soft .ql-tooltip input[type="text"]:focus {
  outline: 0;
  background: var(--ui-veld-bg-actief);
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
}

/* --------------------------------------------------------------------------
   PUSH-PREVIEW — hoe een pushmelding er op het TOESTEL uitziet.
   Bewust NIET thema-gestuurd: dit is een device-mock (iOS/Android-lockscreen),
   geen app-UI. Alleen de app-icoontegel toont de omgeving. Vaste kleuren zijn
   hier dus correct — het is een nabootsing van het besturingssysteem.
   -------------------------------------------------------------------------- */
.push-preview {
  max-width: 25rem;
  margin-inline: auto;
  padding: 1.5rem 1rem 1.75rem;
  border-radius: 1.75rem;
  /* Lockscreen-sfeer: neutrale gradient, onafhankelijk van het app-thema */
  background: linear-gradient(160deg, #3a4a63 0%, #1f2735 100%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08);
}
.push-preview-time {
  text-align: center;
  color: #fff;
  margin-bottom: 1.25rem;
  text-shadow: 0 1px 12px rgba(0, 0, 0, .35);
}
.push-preview-time strong {
  display: block;
  font-size: 2.75rem;
  font-weight: 300;
  line-height: 1;
  letter-spacing: -.02em;
}
.push-preview-time span { font-size: .8125rem; opacity: .85; }

/* De notificatiekaart zelf (glassmorphism, zoals iOS/Android) */
.push-card {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .875rem;
  border-radius: 1.125rem;
  background: rgba(255, 255, 255, .92);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .18);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  text-align: left;
}
.push-card + .push-card { margin-top: .6rem; }
.push-card-icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: .7rem;
  object-fit: cover;
  flex-shrink: 0;
  background: #fff;
}
.push-card-main { min-width: 0; flex: 1; }
.push-card-head {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  margin-bottom: .1rem;
}
.push-card-app {
  font-size: .6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #6b7280;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.push-card-time { margin-left: auto; font-size: .6875rem; color: #9aa1ab; flex-shrink: 0; }
.push-card-title {
  font-size: .9375rem;
  font-weight: 600;
  color: #111;
  line-height: 1.3;
  word-break: break-word;
}
.push-card-body {
  font-size: .875rem;
  color: #333;
  line-height: 1.4;
  word-break: break-word;
  /* max 4 regels, zoals het OS zelf afkapt */
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Donkere systeemvariant (Android/iOS dark) — los te kiezen, niet automatisch */
.push-preview-dark .push-card { background: rgba(30, 32, 38, .92); }
.push-preview-dark .push-card-title { color: #f5f5f7; }
.push-preview-dark .push-card-body { color: #c9ccd2; }
.push-preview-dark .push-card-app { color: #9aa1ab; }

/* --------------------------------------------------------------------------
   MAIL-PREVIEW — hoe een e-mail er in de mailbox uitziet. Ook een mock: de
   mail-HTML zelf is licht (mailclients kennen ons thema niet), alleen de
   accentkleur volgt de omgeving via --primary-color. Voor een ECHTE preview
   van de verstuurde mail blijft een <iframe sandbox> met de mailtemplate de
   juiste keuze (zie nieuws-aanmaken.php); dit component is de chrome eromheen.
   -------------------------------------------------------------------------- */
.mail-preview {
  max-width: 34rem;
  margin-inline: auto;
  border-radius: var(--ui-radius);
  overflow: hidden;
  border: 1px solid var(--theme-color-3);
  box-shadow: 0 .75rem 2rem rgba(var(--custom-theme-box-shadow-rgb), .12);
  background: #fff;
}
/* Mailclient-kop: afzender + onderwerp (wel thema-gestuurd: dit is onze UI) */
.mail-preview-head {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .875rem 1rem;
  background: var(--theme-color-4);
  border-bottom: 1px solid var(--theme-color-3);
}
.mail-preview-avatar {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  object-fit: contain;
  background: #fff;
  padding: .2rem;
  flex-shrink: 0;
  box-shadow: 0 1px 3px rgba(var(--custom-theme-box-shadow-rgb), .12);
}
.mail-preview-meta { min-width: 0; flex: 1; }
.mail-preview-from {
  font-weight: 700;
  font-size: .875rem;
  color: var(--theme-text-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mail-preview-address { font-size: .75rem; color: var(--theme-text-color-gray); }
.mail-preview-date { font-size: .75rem; color: var(--theme-text-color-gray); flex-shrink: 0; }
.mail-preview-subject {
  padding: .75rem 1rem;
  background: var(--theme-color-card);
  border-bottom: 1px solid var(--theme-color-3);
  font-weight: 700;
  color: var(--theme-text-color);
}

/* De mail-body: nabootsing van standard_mail.html (altijd licht). Bewust GEEN eigen
   max-height/overflow: elke plek waar dit component staat zit al in een modal (de
   activatiemail-preview en de onboarding-reeks-previews), en .modal-body is daar al de
   scroll-eigenaar (zie de sheet-CSS verderop in dit bestand). Een tweede scrollgebied
   binnen de eerste voelde als een kapotte modal — je scrollde de mail zelf, niet de modal.
   (nieuws-aanmaken.php/poll-aanmaken.php gebruiken hier geen .mail-preview-body: die tonen
   de echte mail via een <iframe> met eigen vaste hoogte, css/nieuws-editor.css.) */
.mail-preview-body {
  background: #f2f2f7;
  padding: 1.5rem 1rem;
}
.mail-preview-logo {
  display: block;
  max-width: 9rem;
  max-height: 3rem;
  margin: 0 auto 1.25rem;
  object-fit: contain;
}
.mail-preview-card {
  background: #fff;
  border-radius: .75rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .05);
  padding: 1.75rem;
  text-align: left;
}
.mail-preview-title {
  font-size: 1.35rem;
  font-weight: 900;
  color: var(--primary-color);
  margin: 0 0 .4rem;
  line-height: 1.25;
}
.mail-preview-subtitle {
  font-size: .9rem;
  color: #555;
  font-style: italic;
  margin-bottom: 1.25rem;
}
.mail-preview-text {
  font-size: .95rem;
  line-height: 1.6;
  color: #01165A;
}
.mail-preview-btn {
  display: inline-block;
  margin-top: 1.25rem;
  padding: .75rem 1.5rem;
  border-radius: 50rem;
  background: var(--primary-color);
  color: var(--primary-bg-text-color);
  font-weight: 700;
  font-size: .9rem;
  text-decoration: none;
}
.mail-preview-btn:hover { color: var(--primary-bg-text-color); text-decoration: none; }
.mail-preview-footer {
  border-top: 1px solid #eee;
  margin-top: 1.75rem;
  padding-top: 1rem;
  font-size: .8rem;
  color: #777;
  line-height: 1.5;
}
.mail-preview-footer a { color: var(--primary-color); }

/* --------------------------------------------------------------------------
   Lid-select — adaptieve persoon-selector.
   Weinig personen (≤10): avatar-tegels, tik om te (de)selecteren.
   Veel personen (>10): chip-input + zoek-als-je-typt (LidPicker-gedrag).
   Avatars altijd via renderUserAvatar() / window.Avatar.el().
   -------------------------------------------------------------------------- */
.lid-select-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
  gap: .6rem;
}
.lid-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .45rem;
  padding: .85rem .5rem .7rem;
  border-radius: var(--ui-radius);
  border: 1px solid var(--theme-color-3);
  background: var(--theme-color-card);
  cursor: pointer;
  text-align: center;
  user-select: none;
  transition: border-color .15s ease, background-color .15s ease;
}
.lid-tile:hover {
  background: var(--bs-primary-soft);
  border-color: rgba(var(--primary-color-rgb), .2);
}
.lid-tile.is-selected {
  background: var(--bs-primary-soft);
  border-color: var(--primary-color);
}
.lid-tile-avatar { position: relative; }
.lid-tile-check {
  position: absolute;
  right: -.25rem;
  bottom: -.25rem;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  background: var(--primary-color);
  color: var(--primary-bg-text-color);
  display: none;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--theme-color-card);
}
.lid-tile-check svg { width: .6rem; height: .6rem; }
.lid-tile.is-selected .lid-tile-check { display: inline-flex; }
.lid-tile-name {
  font-size: .8125rem;
  font-weight: 600;
  color: var(--theme-text-color);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* -- v2-paneel (UX-review 2026-07-22): zoekveld + snelkeuze-chips + lijst +
      sticky selectiebalk (WhatsApp-patroon). Rijen ≥48px, hele rij tikbaar. -- */
.lid-panel { display: flex; flex-direction: column; gap: .75rem; }
.lid-panel-search { position: relative; }
.lid-search-clear {
  position: absolute;
  right: .6rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1.5rem;
  height: 1.5rem;
  border: 0;
  border-radius: 50%;
  background: var(--theme-color-4);
  color: var(--theme-text-color-gray);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}
.lid-search-clear svg { width: .7rem; height: .7rem; }
.lid-search-clear:hover { color: var(--theme-text-color); }

/* Snelkeuze-chips: outline = uit, teller "3/8" = deels, gevuld + vinkje = vol */
.lid-quick { display: flex; flex-wrap: wrap; gap: .5rem; }
.lid-chip {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .45rem .9rem;
  border-radius: 50rem;
  border: 1px solid var(--theme-color-3);
  background: var(--theme-color-card);
  color: var(--theme-text-color);
  font-size: .875rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.lid-chip svg { width: .95rem; height: .95rem; }
.lid-chip:hover { background: var(--bs-primary-soft); border-color: rgba(var(--primary-color-rgb), .2); color: var(--brand-on-surface); }
.lid-chip .lid-chip-count {
  font-size: .75rem;
  font-weight: 700;
  color: var(--theme-text-color-gray);
  background: var(--theme-color-4);
  border-radius: 50rem;
  padding: .1rem .45rem;
}
.lid-chip.is-full {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: var(--primary-bg-text-color);
}
.lid-chip.is-full .lid-chip-count {
  background: rgba(var(--primary-bg-text-color-rgb), .22);
  color: var(--primary-bg-text-color);
}
.lid-chip-check { display: inline-flex; }
.lid-chip-check svg { width: .8rem; height: .8rem; }
/* "Meer groepen"-chip: secundair, geen selectie-actie */
.lid-chip-more {
  border-style: dashed;
  color: var(--theme-text-color-gray);
  font-weight: 600;
}

/* Ledenlijst: rijen met avatar + naam + vinkje, hele rij toggle't */
.lid-panel-list { max-height: 19rem; overflow-y: auto; }
/* ...behalve in TEGELmodus. Daar staan er hoogstens `threshold` personen (9 = 3
   rijen van 3 op mobiel), dus die passen gewoon. Een scrollbalkje om drie rijen
   is onrust: je ziet dan niet in één oogopslag wie er zijn, en dat is nou juist
   waaróm er tegels zijn. De LIJSTmodus (> threshold) houdt zijn cap wél — daar
   kunnen honderden personen in staan. */
.lid-select-grid.lid-panel-list { max-height: none; overflow: visible; }
.lid-list { display: flex; flex-direction: column; }
.lid-row {
  display: flex;
  align-items: center;
  gap: .75rem;
  min-height: 48px;
  padding: .5rem .6rem;
  border-radius: var(--ui-radius-sm);
  cursor: pointer;
  user-select: none;
}
.lid-row:hover { background: rgba(var(--theme-color-3-rgb), .4); }
.lid-row.is-selected { background: var(--bs-primary-soft); }
.lid-row-main { min-width: 0; flex: 1; }
.lid-row-name {
  font-weight: 600;
  color: var(--theme-text-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lid-row-sub {
  font-size: .78rem;
  color: var(--theme-text-color-gray);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lid-row-check {
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  border: 2px solid var(--theme-color-3);
  color: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.lid-row-check svg { width: .75rem; height: .75rem; }
.lid-row.is-selected .lid-row-check {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: var(--primary-bg-text-color);
  animation: lid-check-in .18s ease;
}
@keyframes lid-check-in {
  from { transform: scale(.6); }
  to { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .lid-row.is-selected .lid-row-check { animation: none; }
}

/* Kopregel bij zoekresultaten */
.lid-row-selectall {
  border: 0;
  background: transparent;
  color: var(--brand-on-surface);
  font-size: .85rem;
  font-weight: 700;
  text-align: left;
  padding: .35rem .6rem;
  cursor: pointer;
  border-radius: .5rem;
}
.lid-row-selectall:hover { background: var(--bs-primary-soft); }

/* Sticky selectiebalk onderin het paneel */
.lid-selbar {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .6rem .25rem .25rem;
  border-top: 1px solid var(--theme-color-3);
  background: var(--theme-color-card);
}

/* --------------------------------------------------------------------------
   Medewerker-kiezer — lid-select als FORMULIERVELD (medewerker_kiezer() in
   includes/loggedin/ui_helpers.php + js/medewerker-kiezer.js).
   Draagt de veld-tokens (--ui-veld-*), want dit IS een veld: label erboven,
   verborgen input eronder, hover- en focusrand net als een form-control.

   NIET in een .card-inset mounten. Dat is een flex-RIJ (icoon + titel + meta
   + pill naast elkaar); LidSelect's zoekveld, tegelgrid en selectiebalk worden
   dan drie kolommen naast elkaar in plaats van gestapeld — met vervormde
   avatars en losgeslagen vinkjes tot gevolg. Vandaar deze eigen wrapper, die
   bewust een BLOK is.
   -------------------------------------------------------------------------- */
.mw-kiezer { display: block; }
.mw-kiezer > .form-label { display: block; }
.mw-kiezer-paneel {
  display: block;
  padding: .75rem .85rem;
  border-radius: var(--ui-veld-radius);
  border: 1px solid var(--ui-veld-rand);
  background: var(--ui-veld-bg);
  transition: var(--ui-veld-overgang);
}
.mw-kiezer-paneel:hover:not(:focus-within) { border-color: var(--ui-veld-rand-hover); }
.mw-kiezer-paneel:focus-within {
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
  background: var(--ui-veld-bg-actief);
}
/* De sticky selectiebalk erft de kaartkleur; binnen het veld moet hij op de
   veldachtergrond staan, anders zie je een blok in een andere tint meescrollen. */
.mw-kiezer-paneel .lid-selbar { background: var(--ui-veld-bg); }
.mw-kiezer-paneel:focus-within .lid-selbar { background: var(--ui-veld-bg-actief); }
.lid-selbar-count {
  border: 0;
  background: transparent;
  padding: .25rem .4rem;
  border-radius: .5rem;
  font-size: .85rem;
  font-weight: 700;
  color: var(--theme-text-color);
  cursor: pointer;
  white-space: nowrap;
}
.lid-selbar-count:hover,
.lid-selbar.is-filtering .lid-selbar-count {
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
}
.lid-selbar-count.pulse { animation: lid-count-pulse .25s ease; }
@keyframes lid-count-pulse {
  50% { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  .lid-selbar-count.pulse { animation: none; }
}
.lid-selbar-stack { cursor: pointer; }
.lid-selbar-stack > * { border: 2px solid var(--theme-color-card); }
.lid-stack-more { width: 28px; height: 28px; font-size: .68rem; }
.lid-selbar-clear {
  border: 0;
  background: transparent;
  color: var(--theme-text-color-gray);
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  padding: .25rem .4rem;
  border-radius: .5rem;
}
.lid-selbar-clear:hover { background: var(--bs-danger-soft); color: var(--bs-danger); }

.lid-select-empty {
  padding: .75rem;
  font-size: .85rem;
  color: var(--theme-text-color-gray);
  text-align: center;
}

/* --------------------------------------------------------------------------
   AI-patronen (Planny). Eén AI-signaal in de app: de Planny-glow. De rand
   hergebruikt de bestaande glow-motor uit styles-sb.css.php
   (--planny-glow-angle @property + keyframes planny-glow-idle) — geen
   nieuwe gradient verzinnen. Iconen Feather-only; avatar = AI-signaal.
   -------------------------------------------------------------------------- */

/* ── De Planny-avatar ALS icoontegel ─────────────────────────────────────────
   In plaats van een Feather-icoon in een getinte tegel: het gezicht zelf, op de
   maat van een icon-tile. Dit is het enige signaal in de rij hieronder dat geen
   effect is maar een merk — je hoeft niet te leren dat een bewegende gradient
   "AI" betekent, want er kijkt letterlijk een Planny naar je terug.

   LET OP bij gebruik: de avatar is per omgeving instelbaar (whitelabel). Haal de
   src uit planny_settings($conn, $omgevingId)['avatar_url'] en val alleen daarna
   terug op asset('images/planny-icon.png') — hardcoded is hij fout bij elke
   omgeving met een eigen icoon. */
.planny-tegel {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  flex-shrink: 0;
  display: block;
  object-fit: cover;
}
.planny-tegel-sm { width: 2.25rem; height: 2.25rem; }

/* ── AI-signaal op een hele KAART ────────────────────────────────────────────
   Zelfde randtruc als .input-planny, maar om een card-soft heen. Bewust
   ingetogen (dunnere rand, tragere rotatie dan een invoerveld): een kaart staat
   permanent op de pagina, een invoerveld heeft de focus. Zonder die demping
   trekt hij de aandacht weg van waar de gebruiker mee bezig is. */
.card-planny-glow {
  position: relative;
  /* De rand van card-soft blijft STAAN maar wordt doorzichtig: `border: 0` zou de
     kaart 2px smaller maken dan zijn buren, en dat zie je in een rij kaarten. */
  border-color: transparent;
}
.card-planny-glow::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(from var(--planny-glow-angle),
      #ff5fa2, #b15cff, #5b8bff, #36d9ff, #7a7bff, #ff9d5c, #ff5fa2);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: planny-glow-idle 22s linear infinite;
  pointer-events: none;
  z-index: 1;
}
/* De inhoud moet bovenop de rand liggen, anders vangt de pseudo-laag de klikken
   op de card-header (die is de collapse-toggle). */
.card-planny-glow > * { position: relative; z-index: 2; }

@media (prefers-reduced-motion: reduce) {
  .card-planny-glow::before { animation: none; }
}

/* AI-invoerveld met roterende glow-rand (wrapper om een textarea/input) */
.input-planny {
  position: relative;
  border-radius: var(--ui-radius);
  background: var(--theme-color-card);
}
.input-planny::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 2px;
  background: conic-gradient(from var(--planny-glow-angle),
      #ff5fa2, #b15cff, #5b8bff, #36d9ff, #7a7bff, #ff9d5c, #ff5fa2);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: planny-glow-idle 14s linear infinite;
  pointer-events: none;
}
.input-planny > textarea,
.input-planny > input {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  border-radius: inherit;
  padding: .875rem 1rem;
  color: var(--theme-text-color);
  font-size: .95rem;
  resize: vertical;
}
.input-planny:focus-within { box-shadow: 0 0 0 .3rem rgba(var(--primary-color-rgb), .08); }
@media (prefers-reduced-motion: reduce) {
  .input-planny::before { animation: none; }
}

/* Slanke AI-hintbalk ("Planny kan automatisch …") */
.ai-hint {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .55rem .9rem;
  border-radius: .75rem;
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  font-size: .875rem;
  font-weight: 500;
}
.ai-hint svg { width: 1rem; height: 1rem; flex-shrink: 0; }
.ai-hint img { width: 1.15rem; height: 1.15rem; border-radius: 50%; flex-shrink: 0; }

/* Attributie-strip ("Automatisch voorgesteld door Planny") */
.ai-attribution {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .6rem .9rem;
  border-radius: .75rem;
  background: var(--theme-color-4);
  color: var(--theme-text-color-gray);
  font-size: .85rem;
}
.ai-attribution img { width: 1.1rem; height: 1.1rem; border-radius: 50%; }
.ai-attribution svg { width: .95rem; height: .95rem; }

/* --------------------------------------------------------------------------
   Icon-circle — icoon in cirkel op kaartkleur; juist voor op getinte
   ondergronden (binnen .card-inset-tint / .card-soft-tint)
   -------------------------------------------------------------------------- */
.icon-circle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--theme-color-card);
  color: var(--brand-on-surface);
  box-shadow: 0 1px 3px rgba(var(--custom-theme-box-shadow-rgb), .12);
  flex-shrink: 0;
}
.icon-circle svg { width: 1.2rem; height: 1.2rem; }
.icon-circle-sm { width: 2.25rem; height: 2.25rem; }
.icon-circle-sm svg { width: 1rem; height: 1rem; }
.icon-circle-success { color: var(--bs-success); }
.icon-circle-warning { color: var(--bs-warning); }
.icon-circle-danger  { color: var(--bs-danger); }
.icon-circle-neutral { color: var(--theme-text-color-gray); }

/* --------------------------------------------------------------------------
   Card-inset — getinte binnen-kaart (uitgelicht item of samenvatting
   bínnen een card-soft): icon-circle + titel + meta + pill rechts
   -------------------------------------------------------------------------- */
.card-inset {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border-radius: var(--ui-radius);
  background: var(--theme-color-4);
}
.card-inset-tint { background: var(--bs-primary-soft); }
/* Blok-variant: zelfde getinte binnen-kaart, maar de inhoud stapelt. Nodig zodra
   je er iets anders in zet dan de rij "icoon + titel + meta + pill" — een klein
   formulier bijvoorbeeld. Zonder deze variant worden die velden flex-items en
   komen ze naast elkaar te staan.

   Hij draagt de HELE box zelf (padding, radius, achtergrond) en niet alleen
   `display: block`. Dat was hij tot 2026-08-20 wél, en een variant die alleen een
   display omzet is stil kapot zodra iemand hem ZONDER `.card-inset` schrijft — en dat
   is precies wat er overal gebeurde, ook in de galerij zelf: de drie kaartjes onder
   "Knoppen — welke variant wanneer", het huisstijl- en kwaliteitsblok in de
   campagne-goedkeurpoort en de duels in het toernooi-schema stonden allemaal zonder
   achtergrond, met de tekst tegen de rand van de kaart eronder. De naam belooft een
   kaart, dus geeft de class ook een kaart. Naast `.card-inset` gezet (zoals
   js/klant-zoeker.js doet) verandert er niets: dezelfde waarden. */
.card-inset-blok {
  display: block;
  padding: 1rem 1.25rem;
  border-radius: var(--ui-radius);
  background: var(--theme-color-4);
}

/* --------------------------------------------------------------------------
   Uitleg-knop — het vraagtekentje naast een label of hint dat een voorbeeld of
   toelichting opent. Icoon-only, dus het label zit in aria-label/title.
   Bewust klein en gedempt: het is een aanbod, geen actie in de flow. Vervangt
   de inline `style="width:14px;height:14px;vertical-align:-2px"` die op zes
   plekken los rondslingerde.
   -------------------------------------------------------------------------- */
.uitleg-knop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: 50%;
  color: var(--theme-text-color-gray);
  cursor: pointer;
  vertical-align: -.2em;
  transition: color .12s ease, background-color .12s ease;
}
.uitleg-knop svg { width: .9rem; height: .9rem; }

/* Chrome om de mail-voorbeeldweergave (js/afspraak-mail-preview.js). Het kader
   volgt het thema; de iframe zelf blijft wit, want de mail wordt door de
   mailclient getekend en niet door ons thema — zelfde regel als bij
   `mail-preview`. Onder 576px is de modal een sheet met beperkte hoogte, dus
   daar mag de iframe meekrimpen. */
.afspraak-preview-body { background: var(--theme-color-2); }
.afspraak-preview-frame {
  display: block;
  width: 100%;
  height: 70vh;
  border: 0;
  background: #fff;
}
@media (max-width: 575.98px) {
  .afspraak-preview-frame { height: 60vh; }
}
.uitleg-knop:hover,
.uitleg-knop:focus-visible {
  color: var(--brand-on-surface);
  background: var(--bs-primary-soft);
  text-decoration: none;
}
.card-inset-main { min-width: 0; flex-grow: 1; }
.card-inset-title { font-weight: 700; color: var(--theme-text-color); }
.card-inset-sub { font-size: .85rem; color: var(--theme-text-color-gray); }
.card-inset-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .35rem 1rem;
  font-size: .85rem;
  color: var(--theme-text-color-gray);
  margin-top: .15rem;
}
.card-inset-meta > span { display: inline-flex; align-items: center; gap: .4rem; }
.card-inset-meta svg { width: .9rem; height: .9rem; color: var(--brand-on-surface); }

/* Hele kaart als "positieve zone" (bv. Liever-wel vs. Liever-niet) */
.card-soft-tint {
  background: var(--bs-primary-soft);
  border-color: rgba(var(--primary-color-rgb), .15);
}

/* --------------------------------------------------------------------------
   Date-cell — gestapelde dag/datum/tijd-cel links in een lijstrij
   -------------------------------------------------------------------------- */
.date-cell {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  min-width: 5.75rem;
  flex-shrink: 0;
}
.date-cell-day { font-weight: 700; color: var(--theme-text-color); }
.date-cell-date { font-size: .85rem; color: var(--theme-text-color-gray); }
.date-cell-time { font-size: .8125rem; color: var(--theme-text-color-gray); }

/* --------------------------------------------------------------------------
   Status-inline — icoon + gedempte tekst, rustiger dan een pill
   -------------------------------------------------------------------------- */
.status-inline {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .875rem;
  font-weight: 500;
  color: var(--theme-text-color-gray);
}
.status-inline svg { width: 1rem; height: 1rem; }
.status-inline-success { color: var(--bs-success); }
.status-inline-danger { color: var(--bs-danger); }

/* Status-pill met icoontje ("✓ 2 van 2 ingevuld") */
.status-pill svg { width: .85rem; height: .85rem; flex-shrink: 0; }

/* Ingetogen link (mailto:/tel: in tabellen) — vervangt hardcoded grijs (#69707A),
   dat in dark mode vrijwel onleesbaar werd. */
.link-muted { color: var(--theme-text-color-gray); }
.link-muted:hover,
.link-muted:focus { color: var(--brand-on-surface); }

/* Bedrag in merkkleur (ticketprijzen e.d.) */
.price {
  color: var(--brand-on-surface);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Btn-filter-pill — dropdown-trigger in kaart-header ("Deze week ⌄");
   grotere broer van .mini-seg, voor open-einde keuzes via .dropdown-soft
   -------------------------------------------------------------------------- */
.btn-filter-pill {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .45rem 1rem;
  border-radius: 50rem;
  border: 1px solid transparent;
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  font-size: .875rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .15s ease, background-color .15s ease;
}
.btn-filter-pill:hover,
.btn-filter-pill[aria-expanded="true"] { border-color: rgba(var(--primary-color-rgb), .3); }
.btn-filter-pill svg { width: 1rem; height: 1rem; }
.btn-filter-pill svg.caret { width: .85rem; height: .85rem; }
.btn-filter-pill::after { display: none; } /* geen Bootstrap-caret */

/* --------------------------------------------------------------------------
   Card-footer-link — gecentreerde "Bekijk alle … ›"-voet onder een kaart
   -------------------------------------------------------------------------- */
.card-footer-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  padding: .875rem 1rem;
  border-top: 1px solid var(--theme-color-3);
  border-radius: 0 0 var(--ui-radius-lg) var(--ui-radius-lg);
  color: var(--brand-on-surface);
  font-weight: 600;
  font-size: .9rem;
  text-decoration: none;
}
.card-footer-link:hover,
.card-footer-link:focus {
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  text-decoration: none;
}
.card-footer-link svg { width: 1rem; height: 1rem; }

/* ── Handtekening-vlak (sig-pad) ─────────────────────────────────────────────
   Tekenveld voor een krabbel op scherm (werkbon, later ook akkoordering).
   touch-action: none is FUNCTIONEEL, geen opsmuk: zonder die regel scrollt de
   WebView mee terwijl iemand tekent en krijg je een halve streep. De canvas
   krijgt zijn pixelmaat van de JS (devicePixelRatio), niet van CSS — anders is
   de lijn op een telefoon blokkerig. */
.sig-pad {
  position: relative;
  border: 1px dashed var(--theme-color-3);
  border-radius: var(--ui-radius-md, .75rem);
  background: var(--theme-color-body);
  overflow: hidden;
}
.sig-pad-canvas {
  display: block;
  width: 100%;
  height: 9rem;
  touch-action: none;
  cursor: crosshair;
}
.sig-pad-leeg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  color: var(--theme-text-color-gray);
  font-size: .875rem;
}
.sig-pad.is-getekend .sig-pad-leeg { display: none; }
.sig-pad-wissen {
  position: absolute;
  top: .35rem;
  right: .35rem;
}
/* Bekeken (bevroren) handtekening: zelfde kader, geen tekenaffordantie. */
.sig-beeld {
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius-md, .75rem);
  background: var(--theme-color-body);
  padding: .5rem;
}
.sig-beeld img { display: block; max-width: 100%; height: auto; }

/* ── Wachtwoordsterkte (pw-meter) ────────────────────────────────────────────
   Eisen-checklist + sterktebalk onder een "kies een nieuw wachtwoord"-veld.
   Gedrag: js/wachtwoord-sterkte.js, markup: wachtwoord_sterkte_meter() in
   includes/loggedin/ui_helpers.php. De checklist staat er ALTIJD (ook bij een
   leeg veld) — dat is precies het punt: iemand moet vóór het typen al zien
   waar het wachtwoord aan moet voldoen. Niet "pas tonen bij invoer" maken. */
.pw-meter { margin-top: .75rem; }
.pw-meter-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  font-size: .8125rem;
}
.pw-meter-caption { color: var(--theme-text-color-gray); }
.pw-meter-oordeel { font-weight: 600; color: var(--theme-text-color-gray); }

.pw-meter-bar { display: flex; gap: .25rem; margin: .35rem 0 .65rem; }
.pw-meter-bar span {
  flex: 1 1 0;
  height: .3rem;
  border-radius: 50rem;
  background: var(--theme-color-3);
  transition: background-color .15s ease;
}
/* Niveau 1 = Zwak (danger) … 4 = Sterk (success). Alleen het aantal gevulde
   segmenten verschilt tussen Goed en Sterk — zelfde kleur, meer balk. */
.pw-meter[data-niveau="1"] .pw-meter-oordeel { color: var(--bs-danger); }
.pw-meter[data-niveau="2"] .pw-meter-oordeel { color: var(--bs-warning); }
.pw-meter[data-niveau="3"] .pw-meter-oordeel,
.pw-meter[data-niveau="4"] .pw-meter-oordeel { color: var(--bs-success); }
.pw-meter[data-niveau="1"] .pw-meter-bar span:nth-child(-n+1) { background: var(--bs-danger); }
.pw-meter[data-niveau="2"] .pw-meter-bar span:nth-child(-n+2) { background: var(--bs-warning); }
.pw-meter[data-niveau="3"] .pw-meter-bar span:nth-child(-n+3) { background: var(--bs-success); }
.pw-meter[data-niveau="4"] .pw-meter-bar span { background: var(--bs-success); }

.pw-eisen {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .3rem;
}
.pw-eis {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: .8125rem;
  color: var(--theme-text-color-gray);
}
.pw-eis svg { width: .95rem; height: .95rem; flex: 0 0 auto; }
.pw-eis-icon { display: inline-flex; align-items: center; }
.pw-eis-ok, .pw-eis-fout { display: none; }
.pw-eis.is-ok { color: var(--theme-text-color); }
.pw-eis.is-ok .pw-eis-open { display: none; }
.pw-eis.is-ok .pw-eis-ok { display: inline-flex; color: var(--bs-success); }
.pw-eis.is-fout { color: var(--bs-danger); }
.pw-eis.is-fout .pw-eis-open { display: none; }
.pw-eis.is-fout .pw-eis-fout { display: inline-flex; color: var(--bs-danger); }

.pw-meter-tip {
  margin: .65rem 0 0;
  font-size: .8125rem;
  color: var(--theme-text-color-gray);
}

/* Oog-knop in het invoerveld. De wrapper wordt door de JS om het bestaande
   veld heen gezet, dus dit werkt op .form-control én op de .ma-input van
   mijn-account.php — geen aannames over de input-class. */
.pw-veld { position: relative; display: block; }
.pw-veld > input { padding-right: 2.75rem; }
.pw-veld-toggle {
  position: absolute;
  top: 50%;
  right: .4rem;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 50rem;
  background: transparent;
  color: var(--theme-text-color-gray);
  cursor: pointer;
}
.pw-veld-toggle:hover,
.pw-veld-toggle:focus {
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
}
.pw-veld-toggle svg { width: 1.05rem; height: 1.05rem; }

/* --------------------------------------------------------------------------
   Action-bar — vastgeplakte actiebalk onderaan een bevestig-/selectieflow
   (import-preview, bulkselectie). Plaats hem als LAATSTE kind binnen de
   content-container: hij is `position: sticky`, niet `fixed`, zodat hij nooit
   over de side-nav valt en er geen spacer onder de pagina nodig is.
   Onderrand volgt de safe-area (native).
   -------------------------------------------------------------------------- */
.action-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  background: var(--theme-color-card);
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius-lg);
  box-shadow: 0 -.5rem 1.5rem rgba(var(--custom-theme-box-shadow-rgb), .12);
  padding: .875rem 1.25rem calc(.875rem + var(--sa-b, 0px));
}
.action-bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .75rem;
}
.action-bar-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem;
}
.action-bar-summary { font-size: .9rem; color: var(--theme-text-color-gray); }
.action-bar-summary strong { color: var(--theme-text-color); }
/* Keuzeveld mét zichtbaar label in de balk. Drie kale selects naast elkaar
   zeggen niets — label boven het veld, in de compacte maat van de balk. */
.action-bar-field {
  display: flex;
  flex-direction: column;
  gap: .2rem;
}
.action-bar-field > .form-label {
  margin-bottom: 0;
  font-size: .75rem;
  font-weight: 600;
  color: var(--theme-text-color-gray);
}

/* --------------------------------------------------------------------------
   Mini-kalender — maandkalender die per dag laat zien wat er al staat.
   Los bruikbaar (MiniKalender.init) én als stap 1 in het datum/tijd-paneel.
   Markup + gedrag: js/mini-kalender.js. Kleuren komen uit de theme-tokens;
   de dagknoppen zijn bewust rond en ruim (touch-doel ≥ 38px).
   -------------------------------------------------------------------------- */
.mini-kalender-kop {
  display: grid;
  grid-template-columns: 2.25rem 1fr 2.25rem;
  align-items: center;
  margin-bottom: .5rem;
}
.mini-kalender-titel {
  text-align: center;
  font-size: .9375rem;
  font-weight: 600;
  color: var(--theme-text-color);
  text-transform: capitalize;
}

/* Het blad = alles onder de kop (het dagraster, of de maand-/jaarblokken). Dit is het
   ENIGE deel dat meebeweegt als je veegt of op ‹ › tikt: de kop blijft staan (anders
   schuiven de pijltjes onder je vinger vandaan) en de weekdagnamen ook (elke maand
   dezelfde, dus beweging zonder informatie). Het gebaar zelf komt uit js/dag-swipe.js —
   de track eromheen is `.dag-track.dag-track--strak`, zie styles-sb.css.php. */
.mini-kalender-blad { min-width: 0; }

.mini-kalender-grid,
.mini-kalender-dagnamen {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
}
.mini-kalender-dagnamen {
  margin-bottom: .25rem;
  color: var(--theme-text-color-gray);
  font-size: .6875rem;
  font-weight: 700;
  text-align: center;
  text-transform: uppercase;
}
.mini-kalender-dagnamen span { padding: .35rem 0; }

.mini-kalender-cel {
  display: grid;
  min-width: 0;
  min-height: 2.7rem;
  place-items: center;
}
.mini-kalender-dag {
  position: relative;
  display: inline-grid;
  width: 2.375rem;
  height: 2.375rem;
  padding: 0;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 50%;
  background: transparent;
  color: var(--theme-text-color);
  font-size: .875rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
}
.mini-kalender-dag:hover { background: var(--bs-primary-soft); color: var(--brand-on-surface); }
.mini-kalender-dag.is-vandaag { border-color: rgba(var(--primary-color-rgb), .45); font-weight: 700; }
.mini-kalender-dag.is-gekozen {
  background: var(--primary-color);
  border-color: transparent;
  color: var(--primary-bg-text-color);
  font-weight: 700;
}
.mini-kalender-dag:focus-visible {
  outline: 0;
  box-shadow: var(--ui-veld-ring);
}
/* Bereik (periode-modi): de dagen ertussen vormen één doorlopende balk, de twee
   uiteinden blijven ronde knoppen. De TINT zit op de cel, niet op de knop — tussen
   twee ronde knoppen zou anders een gaatje in de balk vallen. De klassen worden in
   js/mini-kalender.js op de cel gezet; bewust geen `:has()`, zodat dit niet aan een
   browserversie hangt. */
.mini-kalender-cel.in-bereik { background: var(--bs-primary-soft); }
.mini-kalender-cel.bereik-start { border-radius: 50rem 0 0 50rem; }
.mini-kalender-cel.bereik-eind { border-radius: 0 50rem 50rem 0; }
.mini-kalender-cel.bereik-start.bereik-eind { border-radius: 50rem; }
.mini-kalender-dag.is-bereik { color: var(--brand-on-surface); font-weight: 600; }

/* Bezettingsstippen onder het dagnummer: één stip per item (max 3). */
.mini-kalender-stippen {
  position: absolute;
  bottom: .3rem;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  gap: .12rem;
}
.mini-kalender-stip {
  display: inline-block;
  width: .3rem;
  height: .3rem;
  border-radius: 50%;
  background: var(--theme-text-color-gray); /* geweest/afgerond = geen actie meer */
}
.mini-kalender-stip-gepland { background: var(--primary-color); }
.mini-kalender-stip-zelf { background: var(--bs-warning); }
.mini-kalender-stip-attentie { background: var(--bs-warning); }
.mini-kalender-stip-geannuleerd { background: var(--bs-danger); }
.mini-kalender-dag.is-gekozen .mini-kalender-stip { background: var(--primary-bg-text-color); }

.mini-kalender-legenda {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem 1rem;
  margin-top: .85rem;
  padding-top: .8rem;
  border-top: 1px solid var(--theme-color-3);
  font-size: .75rem;
  color: var(--theme-text-color-gray);
}
.mini-kalender-legenda span {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
}

/* --------------------------------------------------------------------------
   DTK-paneel — de popover van het datum/tijd-veld, in drie genummerde stappen
   (datum → tijd → acties). Op smalle schermen wordt hij een bottom-sheet: een
   paneel van 26rem past niet naast een veld op een telefoon, en onderaan het
   scherm is hij met één duim te bedienen.
   -------------------------------------------------------------------------- */
/* De paneel-chrome is GEDEELD met het select-veld: zelfde zweefkaart, zelfde
   sheet-omslag, zelfde backdrop. Alleen de INHOUD verschilt (kalender vs. lijst).
   Eén selectorlijst i.p.v. twee kopieën van ~60 regels sheet-CSS. */
.dtk-paneel,
.select-paneel {
  position: absolute;
  top: calc(100% + .4rem);
  left: 0;
  z-index: 1065;              /* zie de laagvolgorde bij .dtk-backdrop hieronder */
  width: 26rem;
  max-width: calc(100vw - 2rem);
  /* 80vh en GEEN vaste rem-cap erbij. Hier stond `min(80vh, 40rem)`, vanuit de
     gedachte dat 40rem (640px) ruim genoeg is voor een paneel met eindige inhoud.
     Gemeten in Chromium klopt die aanname niet: het datum/tijd-paneel is 730px hoog
     met alleen een datum en 963px met datum + tijd, en een optielijst is per definitie
     onbegrensd. De cap was dus geen vangnet maar de reden dat je op een groot scherm
     een scrollbalk in de kalender kreeg terwijl er ruimte zat was. 80vh doet wat een
     cap hoort te doen — voorkomen dat het paneel het hele scherm claimt — en schaalt
     mee met het venster; de JS knijpt hem daarnaast nog op de ruimte die er bij dit
     specifieke veld werkelijk is (zie plaatsPopover in mini-kalender.js). */
  max-height: 80vh;
  overflow: auto;
  overscroll-behavior: contain;
  background: var(--theme-color-card);
  border: 1px solid rgba(var(--theme-text-color-rgb), .1);
  border-radius: var(--ui-radius-lg);
  box-shadow: 0 1.5rem 3rem rgba(var(--custom-theme-box-shadow-rgb), .22);
}
.dtk-paneel[hidden],
.select-paneel[hidden] { display: none; }
/* Loopt hij rechts uit beeld, dan hangt hij aan de rechterkant van de anker. */
.dtk-paneel-rechts,
.select-paneel-rechts { left: auto; right: 0; }
/* Loopt hij ONDER uit beeld en is er boven meer ruimte, dan klapt hij om naar boven —
   JS bepaalt de kant in open() (mini-kalender.js/select-veld.js) en klemt de hoogte
   daar meteen op wat er werkelijk beschikbaar is. Zonder dit stak een lang paneel
   (kalender + tijdsectie) onder de viewport uit, en dat was niet met de PAGINA te
   herstellen: de muis staat dan al boven het paneel, en dat is zelf een
   scrollcontainer (`overflow: auto`) — de browser vangt het scrollwiel af op het
   paneel in plaats van de pagina, dus het onzichtbare stuk bleef onbereikbaar. */
.dtk-paneel-boven,
.select-paneel-boven { top: auto; bottom: calc(100% + .4rem); }
/* Het UITZONDERINGSgeval: het paneel staat in een voorouder met `overflow` anders dan
   `visible` (bv. `.offai-reveal > div`, die dat draagt voor zijn inklap-animatie) en
   zou daar altijd door geclipt worden — ongeacht z-index. JS (open() in
   mini-kalender.js/select-veld.js) verhuist het paneel dan TIJDELIJK naar
   `.closest('.modal') || document.body` met `position: fixed`; top/left/right/bottom
   komen als inline stijl mee (percentages die op de wrapper leunen kloppen niet meer
   zodra het paneel daarbuiten hangt). Zie `vindKnellendeVoorouder()` voor de volledige
   uitleg — dit trof écht de "sjabloon"-select op evenement-aanmaken.php. */
.dtk-paneel-zwevend,
.select-paneel-zwevend { position: fixed; }
/* Het select-paneel is een LIJST, geen raster van 26rem — hij mag meebewegen met
   de breedte van het veld, met een ondergrens zodat een smal veld in een actiebalk
   geen kolom van 6rem opent. */
.select-paneel { width: max(100%, 18rem); }

/* --------------------------------------------------------------------------
   Uitklap-animatie van de POPOVER-stand (≥576px). De sheet-stand animeert al
   (translateY op de iOS-curve, zie de media-query verderop); op desktop verscheen
   het paneel tot nu toe hard, en dat leest als een sprong in plaats van als iets
   dat opengaat.

   ZONDER JS, en dat is hier geen luxe maar noodzaak: `mini-kalender.js` zet de
   klasse `-open` ALLEEN in de sheet-tak. Een animatie die op die klasse stuurt zou
   het datum/tijd-paneel op desktop dus permanent op `opacity: 0` zetten — onzichtbaar
   in plaats van mooi. Vandaar `@starting-style` + `transition-behavior:
   allow-discrete`, die sturen op het `hidden`-attribuut dat beide componenten al
   zetten. Een browser die dit niet kent toont het paneel gewoon meteen: precies het
   gedrag van vóór deze regels, dus de terugval is de oude situatie.

   `transition-behavior` staat BEWUST als eigen declaratie en niet in de shorthand:
   een onbekende waarde binnen de `transition`-lijst maakt de HELE declaratie
   ongeldig, en dan zouden opacity en transform ook wegvallen.

   GEEN scale, hoe verleidelijk ook: `open()` meet in beide componenten direct na het
   tonen `getBoundingClientRect().left` om te bepalen of het paneel rechts moet
   uithangen. Een scale verandert die box (een rect is de GETRANSFORMEERDE box), dus
   een schaal van .98 op 26rem verschuift de meting ~8px terwijl de marge in die test
   16px is — dat kan de links/rechts-beslissing stil doen omslaan. Een translateY
   raakt `left` niet en is dus veilig.
   -------------------------------------------------------------------------- */
@media (min-width: 576px) {
  .dtk-paneel,
  .select-paneel {
    transition: opacity .16s ease-out, transform .16s cubic-bezier(.32, .72, 0, 1), display .16s;
    transition-behavior: allow-discrete;
  }
  /* Beginstand (bij openen) en eindstand (bij sluiten) zijn dezelfde waarden — daarom
     staan ze hier twee keer: @starting-style geldt alleen voor de eerste stijl-
     berekening, [hidden] voor het uitfaden. */
  @starting-style {
    .dtk-paneel:not([hidden]),
    .select-paneel:not([hidden]) { opacity: 0; transform: translateY(-.4rem); }
  }
  .dtk-paneel[hidden],
  .select-paneel[hidden] { opacity: 0; transform: translateY(-.4rem); }
  .dtk-paneel:not([hidden]),
  .select-paneel:not([hidden]) { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .dtk-paneel,
  .select-paneel { transition: none; }
}

/* Backdrop — ALLEEN in de sheet-stand (js/mini-kalender.js maakt hem daar aan).
   Een sheet die de pagina eronder al vergrendelt maar niet verduistert leest als
   "de pagina hangt", precies zoals een modal zonder backdrop. In de POPOVER-stand
   (≥576px) hoort hij er bewust NIET: die hangt aan het veld, blijft in de context
   van het formulier staan en een verduisterd scherm zou hem tot modal promoveren.
   Dezelfde opacity en curve als de modal-backdrop, zodat het één beweging is. */
.dtk-backdrop,
.select-backdrop {
  position: fixed;
  inset: 0;
  /* LAAGVOLGORDE — de backdrop moet boven ELK paneel liggen dat hij hoort te
     verduisteren, en het datum-/selectpaneel er net boven:
        1050 modal-backdrop · 1055 modal · 1060 filterpaneel-sheet
        1064 deze backdrop  · 1065 .dtk-paneel / .select-paneel
     Staat dit veld in een MODAL, dan hangt de backdrop ín die modal (die is een
     eigen stacking context) en telt alleen de onderlinge 1064/1065. Staat het in
     een ANDER sheet dat geen modal is — het filterpaneel, z-index 1060 — dan
     hangt hij aan de body, en met de oude 1059 lag hij daar ONDER: het filter
     bleef onverduisterd, dus je keek naar een lichte sheet op een even lichte
     sheet zonder enig hoogteverschil. */
  z-index: 1064;
  background: #000;
  opacity: 0;
  transition: opacity .32s cubic-bezier(.32, .72, 0, 1);
}
.dtk-backdrop-open,
.select-backdrop-open { opacity: .5; }
@media (prefers-reduced-motion: reduce) {
  .dtk-backdrop,
  .select-backdrop { transition: none; }
}

.dtk-kop {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.1rem 1.15rem .9rem;
  background: var(--theme-color-card);
  border-bottom: 1px solid var(--theme-color-3);
}
.dtk-titel { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--theme-text-color); }
.dtk-sub { margin: .15rem 0 0; font-size: .8125rem; color: var(--theme-text-color-gray); }

.dtk-sectie { padding: 1.15rem; border-bottom: 1px solid var(--theme-color-3); }
.dtk-sectie:last-child { border-bottom: 0; }

.dtk-stapkop {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  margin-bottom: 1rem;
}
.dtk-stapkop h3 { margin: 0; font-size: .95rem; font-weight: 700; color: var(--theme-text-color); }
.dtk-stapkop p { margin: .1rem 0 0; font-size: .8125rem; color: var(--theme-text-color-gray); }
/* Het stapnummer is een getinte cirkel in de merkkleur — zelfde familie als
   icon-tile, maar rond en klein omdat het een volgnummer is, geen icoon. */
.dtk-stap {
  display: inline-grid;
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  place-items: center;
  border-radius: 50%;
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  font-size: .75rem;
  font-weight: 700;
}

/* Tijdkeuze: chips als radiogroep. Bewust <button role="radio"> en geen echte
   <input type="radio">: dit paneel staat BINNEN het formulier van de pagina,
   dus radio's zouden meePOSTen. De waarde gaat via de verborgen input. */
/* Twee tijdblokken naast elkaar (Begint om / Eindigt om). Onder elkaar zou de
   samenhang wegvallen: het is één beslissing met twee grenzen. */
.dtk-tijdblokken { display: grid; gap: 1rem; }
@media (min-width: 30rem) {
  .dtk-tijdblokken.is-duo { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.dtk-tijdblok-kop {
  display: block;
  margin-bottom: .4rem;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--theme-text-color);
}
.dtk-tijden {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: .4rem;
}
/* Naast elkaar is er minder ruimte: dan twee chips per rij. */
.dtk-tijdblokken.is-duo .dtk-tijden { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* Schakelaar "duurt meerdere dagen" — huispatroon: de switch alleen in de
   .form-check, het bijschrift als sibling (een label BINNEN de form-switch
   overlapt de knop). */
.dtk-schakelaar {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin-top: .9rem;
  padding-top: .85rem;
  border-top: 1px solid var(--theme-color-3);
  font-size: .875rem;
  color: var(--theme-text-color);
}
.dtk-tijd {
  display: flex;
  min-height: 2.6rem;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  min-width: 0;
  padding: 0 .6rem;
  background: var(--ui-veld-bg);
  border: 1px solid var(--ui-veld-rand);
  border-radius: var(--ui-veld-radius);
  color: var(--theme-text-color);
  font-size: .875rem;
  font-weight: 600;
  cursor: pointer;
  transition: var(--ui-veld-overgang);
}
.dtk-tijd:hover { border-color: var(--ui-veld-rand-hover); background: var(--bs-primary-soft); }
.dtk-tijd[aria-checked="true"] {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: var(--primary-bg-text-color);
}
.dtk-tijd:focus-visible { outline: 0; box-shadow: var(--ui-veld-ring); }
.dtk-tijd svg { width: 1rem; height: 1rem; flex-shrink: 0; }
.dtk-tijd-anders { grid-column: span 2; }
.dtk-tijdblokken.is-duo .dtk-tijd-anders { grid-column: span 2; }

.dtk-eigentijd {
  margin-top: .8rem;
  padding: .85rem;
  background: var(--theme-color-2);
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius-sm);
}
.dtk-eigentijd[hidden] { display: none; }
.dtk-eigentijd > label {
  display: block;
  margin-bottom: .5rem;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--theme-text-color);
}
.dtk-tijdrij { display: flex; align-items: center; gap: .5rem; }
.dtk-tijdrij input {
  width: 4.5rem;
  height: 2.6rem;
  padding: 0 .75rem;
  text-align: center;
  font-weight: 600;
  background: var(--ui-veld-bg);
  border: 1px solid var(--ui-veld-rand);
  border-radius: var(--ui-veld-radius);
  color: var(--theme-text-color);
  transition: var(--ui-veld-overgang);
}
.dtk-tijdrij input:hover:not(:focus) { border-color: var(--ui-veld-rand-hover); }
.dtk-tijdrij input:focus {
  outline: 0;
  background: var(--ui-veld-bg-actief);
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
}
.dtk-tijdrij.is-fout input { border-color: var(--bs-danger); }
.dtk-tijdrij-scheiding { color: var(--theme-text-color-gray); font-weight: 700; }
.dtk-fout {
  margin: .5rem 0 0;
  font-size: .75rem;
  font-weight: 600;
  color: var(--bs-danger);
}
.dtk-fout[hidden] { display: none; }

/* "Je keuze" — de enige plek waar datum én tijd samen te lezen zijn; staat
   in een aria-live-regio zodat een screenreader elke wijziging meekrijgt. */
.dtk-keuze {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-top: 1rem;
  padding: .8rem .85rem;
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  border-radius: var(--ui-radius-sm);
}
.dtk-keuze svg { width: 1.35rem; height: 1.35rem; flex-shrink: 0; }
.dtk-keuze-label {
  display: block;
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.dtk-keuze strong { display: block; font-size: .875rem; }

/* --------------------------------------------------------------------------
   Tijdstijlen 'typen' en 'raster' — de twee alternatieven voor de snelkeuze-chips
   (zie de vergelijking in ui-stijl.php en de noot bij tijdInvoerHtml in
   js/mini-kalender.js). Opt-in via `tijdstijl`; zonder die optie krijg je de chips
   en verandert er niets.
   -------------------------------------------------------------------------- */

/* 'typen': dezelfde twee cijfervelden als het "Ander tijdstip"-vak, maar meteen open
   en groter — dit is nu het hoofdpad, geen uitwijkmogelijkheid. */
.dtk-tijdrij-groot input {
  font-size: 1.35rem;
  font-variant-numeric: tabular-nums;   /* cijfers springen niet bij het typen */
  text-align: center;
  min-height: 3.2rem;
  /* pan-x laat horizontaal scrollen met rust maar geeft ons de VERTICALE sleep — dat is
     de stapper (js/mini-kalender.js, touchmove op [data-dtk-invoer]). Zonder deze regel
     scrolt de browser het paneel weg terwijl je probeert te stappen. Tikken om te
     focussen blijft gewoon werken; touch-action raakt alleen scroll-/zoomgebaren. */
  touch-action: pan-x;
  cursor: ns-resize;                    /* verklapt dat je hier kunt slepen */
}
.dtk-tijdrij-groot .dtk-tijdrij-scheiding { font-size: 1.35rem; }

/* Pijltjes boven en onder elk getal. Ze zitten IN de kolom (grid van drie rijen), zodat
   het getal optisch gecentreerd blijft en de knoppen niet aan de zijkant gaan zwemmen. */
.dtk-stepper {
  display: grid;
  grid-template-rows: auto auto auto;
  justify-items: stretch;
  gap: .2rem;
}
.dtk-stap-knop {
  display: grid;
  place-items: center;
  min-height: 2rem;                     /* raakvlak; het getal ertussen is 3,2rem */
  padding: 0;
  border: 1px solid var(--ui-veld-rand);
  border-radius: var(--ui-veld-radius);
  background: var(--ui-veld-bg);
  color: var(--theme-text-color-gray);
  cursor: pointer;
  transition: var(--ui-veld-overgang);
}
.dtk-stap-knop:hover {
  border-color: var(--ui-veld-rand-hover);
  color: var(--theme-text-color);
}
.dtk-stap-knop:active {
  background: var(--ui-veld-bg-actief);
  border-color: var(--ui-veld-rand-focus);
}
.dtk-stap-knop svg { width: 1.1rem; height: 1.1rem; }
@media (prefers-reduced-motion: reduce) {
  .dtk-stap-knop { transition: none; }
}

/* 'raster': alle 24 uren als tegel. Vier kolommen op smal, zes daarboven — zo blijft
   het blok in een bottom-sheet even hoog als de chips die het vervangt. */
.dtk-uurraster {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .3rem;
}
@media (min-width: 400px) {
  .dtk-uurraster { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
.dtk-uur,
.dtk-min {
  min-height: 2.4rem;                   /* ruim boven de 24px-norm voor raakvlakken */
  padding: .3rem .2rem;
  border: 1px solid var(--ui-veld-rand);
  border-radius: var(--ui-veld-radius);
  background: var(--ui-veld-bg);
  color: var(--theme-text-color);
  font-size: .875rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: var(--ui-veld-overgang);
}
.dtk-uur:hover,
.dtk-min:hover { border-color: var(--ui-veld-rand-hover); }
.dtk-uur[aria-checked="true"],
.dtk-min[aria-checked="true"] {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: var(--primary-bg-text-color);
  font-weight: 600;
}
.dtk-minrij {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .3rem;
  margin-top: .5rem;
}

.dtk-slot .dtk-keuze { margin-top: 0; }
.dtk-acties { display: grid; gap: .5rem; margin-top: 1rem; }
.dtk-acties .btn { width: 100%; }

@media (max-width: 575.98px) {
  /* Bottom-sheet: vast aan de onderrand, volle breedte, alleen bovenhoeken
     rond. `--app-kb` (js/modal-viewport.js) houdt hem boven het toetsenbord —
     zelfde var als de bottom-sheet-modals, zodat gedrag niet uiteenloopt. */
  .dtk-paneel,
  .select-paneel {
    position: fixed;
    top: auto;
    right: 0;
    bottom: var(--app-kb, 0px);
    left: 0;
    width: auto;
    max-width: none;
    max-height: 88vh;
    border-radius: var(--ui-radius-lg) var(--ui-radius-lg) 0 0;
    padding-bottom: var(--sa-b);
    /* Zelfde beweging en curve als de modal-sheets (styles-sb.css.php): omhoog
       schuiven i.p.v. verschijnen, en terugveren na een sleep die niet ver genoeg
       was. De klasse wordt gezet door js/mini-kalender.js; het slepen zelf komt uit
       js/modal-sheet.js (window.SheetGedrag), zodat er één implementatie is. */
    transform: translateY(100%);
    transition: transform .32s cubic-bezier(.32, .72, 0, 1);
  }
  .dtk-paneel-open,
  .select-paneel-open { transform: translateY(0); }
  @media (prefers-reduced-motion: reduce) {
    .dtk-paneel,
    .select-paneel { transition: none; }
  }
  /* Sleep-affordance. Hij hangt aan de STICKY kop en niet aan het paneel: het paneel
     is zelf de scroller, dus een handle daarin zou wegscrollen zodra je de tijdlijst
     omlaag schuift — precies op het moment dat je hem nodig hebt. */
  .dtk-paneel .dtk-kop,
  .select-paneel .select-kop { padding-top: 1.35rem; }
  .dtk-paneel .dtk-kop::before,
  .select-paneel .select-kop::before {
    content: "";
    position: absolute;
    top: .5rem;
    left: 50%;
    transform: translateX(-50%);
    width: 2.25rem;
    height: .25rem;
    border-radius: 50rem;
    background: rgba(var(--theme-text-color-rgb), .2);
  }
  .dtk-tijden { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dtk-tijd-anders { grid-column: auto; }
}

/* --------------------------------------------------------------------------
   Datumtijd-veld — ONZE eigen vervanger van <input type="datetime-local">.
   De native picker ziet er in elke browser en in de WebView anders uit en is op
   mobiel een los OS-scherm; dit veld is overal hetzelfde, toont de bezetting per
   dag (mini-kalender) en houdt exact hetzelfde POST-formaat ('Y-m-d\TH:i'), zodat
   serverkant niets verandert.
   Markup ALTIJD via datumtijd_veld() in includes/loggedin/ui_helpers.php;
   gedrag via js/mini-kalender.js (MiniKalender.veldenAuto()).
   -------------------------------------------------------------------------- */
/* De trigger is GEDEELD met het select-veld (zie die sectie verderop): allebei zijn
   het "een knop die eruitziet als een veld en een paneel opent". Eén selectorlijst
   i.p.v. twee kopieën, zodat ze niet uit de pas kunnen lopen — dat is precies hoe
   de datumtijd-knop ooit zijn eigen radius/rand/ring kreeg. */
.datumtijd-veld,
.select-veld { position: relative; display: inline-block; }
.datumtijd-trigger,
.select-trigger {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  min-height: calc(1.5em + .5rem + 2px);   /* gelijk aan .form-control-sm */
  padding: .25rem .65rem;
  /* Exact de veld-tokens: dit veld staat naast gewone invoervelden en moet er
     niet als een knop uitzien. Eerder had het een eigen radius/randkleur/ring —
     precies de drift die deze tokens moeten voorkomen. */
  border: 1px solid var(--ui-veld-rand);
  border-radius: var(--ui-veld-radius);
  background: var(--ui-veld-bg);
  color: var(--theme-text-color);
  font-size: .875rem;
  line-height: 1.5;
  cursor: pointer;
  transition: var(--ui-veld-overgang);
}
.datumtijd-trigger:hover:not([aria-expanded="true"]),
.select-trigger:hover:not([aria-expanded="true"]):not(:disabled) { border-color: var(--ui-veld-rand-hover); }
.datumtijd-trigger:focus-visible,
.datumtijd-trigger[aria-expanded="true"],
.select-trigger:focus-visible,
.select-trigger[aria-expanded="true"] {
  outline: 0;
  background: var(--ui-veld-bg-actief);
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
}
.datumtijd-trigger.is-leeg,
.select-trigger.is-leeg { color: var(--theme-text-color-gray); }
.datumtijd-trigger svg,
.select-trigger svg { width: 1rem; height: 1rem; flex-shrink: 0; }
.datumtijd-caret,
.select-caret { color: var(--theme-text-color-gray); margin-left: .1rem; }
.select-trigger:disabled {
  background: var(--theme-color-4);
  color: var(--theme-text-color-gray);
  cursor: not-allowed;
}

/* Volle breedte in een formulier-grid (kolom), i.p.v. de inline maat die in een
   actiebalk hoort. Zet de modifier via `'blok' => true` op datumtijd_veld(). */
.datumtijd-blok,
.select-veld-blok { display: block; }
.datumtijd-blok .datumtijd-trigger,
.select-veld-blok .select-trigger {
  width: 100%;
  justify-content: flex-start;
  /* Exact even hoog als een .form-control in .ui-form: zelfde padding-token en
     dezelfde font-size, anders staat dit veld net iets hoger of lager in een rij
     naast een gewoon tekstveld — precies wat er mis was. */
  padding: var(--ui-veld-padding);
  min-height: calc(1.5em + 1.25rem + 2px);
  font-size: 1rem;
}
.datumtijd-blok .datumtijd-caret,
.select-veld-blok .select-caret { margin-left: auto; }

/* KLEINE maat — de tegenhanger van Bootstraps `.form-select-sm`/`.form-control-sm`,
   en nodig omdat de blok-variant hierboven ook de MAAT vastzet.
   Waarom dit er moest komen: `SelectVeld.upgradeAlles()` neemt élke native <select>
   over en gaf hem altijd de blok-maat, óók als de pagina expliciet
   `class="form-select form-select-sm"` had gevraagd. Op content-hub-artikel.php stond
   daardoor in één actiebalk een statuskeuze van 46px naast een datumveld van 31px —
   dezelfde twee componenten, twee maten. De upgrade zet nu deze klasse door zodra de
   bron `form-select-sm` (of `form-control-sm`) draagt.
   De waarden zijn EXACT die van de compacte trigger bovenaan deze sectie, niet
   opnieuw gekozen: zo staat een klein select-veld op de pixel gelijk aan een klein
   datum/tijd-veld ernaast. Alleen de maat verandert — display/breedte blijven van
   `select-veld-blok`, dus een veld in een formulierkolom blijft even breed. */
.select-veld-sm .select-trigger {
  min-height: calc(1.5em + .5rem + 2px);
  padding: .25rem .65rem;
  font-size: .875rem;
}
/* De labeltekst is het enige dat mag krimpen; zonder min-width:0 duwt een lange
   optienaam de caret het veld uit (zie de 1fr/min-content-valkuil in de skill). */
.select-trigger-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}

/* --------------------------------------------------------------------------
   SELECT-VELD — ONZE eigen vervanger van <select>, naar het model van het
   datumtijd-veld.

   WAAROM: de DICHTE stand van een <select> is al onze stijl (de --ui-veld-*-regels
   bovenaan dit bestand pakken .form-select gewoon mee). De UITGEKLAPTE lijst niet:
   die tekent het besturingssysteem. <option>/<optgroup> zijn praktisch onstijlbaar
   — Chrome laat wat toe, Safari niets, en op iOS is het een wiel-picker onderaan
   het scherm. Er is geen CSS-route naar een lijst in onze vormtaal, dus net als bij
   datum/tijd is de enige oplossing VERVANGEN in plaats van stylen: een <button> als
   trigger, ons eigen paneel, en een verborgen input die de waarde draagt met
   dezelfde `name` — zodat de serverkant niets merkt.

   NIET VOOR ELKE SELECT. Een select met 3 opties is met de OS-picker sneller
   bediend, en die geeft toetsenbord- en screenreadergedrag gratis. Dit component
   verdient zichzelf zodra de lijst lang wordt (zoeken) of de opties meer dan een
   woord zijn (een subregel per optie). Vandaar opt-in per veld, geen globale
   vervanging van alle 209 selects.

   Markup ALTIJD via select_veld() in includes/loggedin/ui_helpers.php; gedrag via
   js/select-veld.js (SelectVeld.veldenAuto(), start zichzelf op).

   Trigger, paneel, sheet-omslag en backdrop zijn GEDEELD met het datumtijd-veld —
   zie de selectorlijsten daar. Hieronder alleen wat eigen is aan de lijst.
   -------------------------------------------------------------------------- */
/* De kop is sticky en draagt ook het zoekveld: bij 200 opties moet het zoekveld in
   beeld blijven terwijl je de lijst doorscrollt, anders moet je terug naar boven om
   te kunnen filteren. In de sheet-stand hangt de sleep-handle er ook aan (zie de
   media-query hierboven) — daarom `position: relative` voor die ::before. */
.select-kop {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 1.1rem 1.15rem .9rem;
  background: var(--theme-color-card);
  border-bottom: 1px solid var(--theme-color-3);
}
.select-kop-rij {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}
.select-titel { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--theme-text-color); }
.select-kop .search-pill { margin-top: .8rem; }

.select-lijst { padding: .5rem; }
/* Een groepkop is een LABEL, geen optie: geen hover, geen focus, niet klikbaar.
   Dit vervangt <optgroup>, dat in geen enkele browser te stijlen is. */
.select-groepkop {
  padding: .65rem .75rem .35rem;
  font-size: .75rem;
  font-weight: 700;
  text-transform: none;                    /* zie de tabelkop-regel: geen uppercase */
  color: var(--theme-text-color-gray);
}
.select-optie {
  display: flex;
  align-items: center;
  gap: .65rem;
  width: 100%;
  /* 2.75rem = dezelfde raakvlakmaat als een data-list-rij; onder ~44px wordt een
     lijst op een telefoon een gokspel. */
  min-height: 2.75rem;
  padding: .5rem .75rem;
  border: 0;
  border-radius: var(--ui-radius-sm);
  background: transparent;
  color: var(--theme-text-color);
  font-size: .9375rem;
  text-align: left;
  cursor: pointer;
  transition: background .15s ease-in-out;
}
/* `.is-actief` = de toetsenbord-markering. Die staat NAAST hover en niet in plaats
   daarvan: de focus blijft in het zoekveld staan (aria-activedescendant), dus de
   optie krijgt zelf nooit :focus en zou zonder deze class onzichtbaar meebewegen
   met de pijltjestoetsen. */
.select-optie:hover,
.select-optie.is-actief,
.select-optie:focus-visible {
  outline: 0;
  /* Mengen met --theme-color-CARD en niet met de body: het paneel ís een kaart, en
     in dark mode is de body juist donkerder — dan zou "soft" hier een gat worden.
     Zie de --bs-*-soft-valkuil in de skill. */
  background: color-mix(in srgb, var(--theme-text-color) 8%, var(--theme-color-card));
}
.select-optie[aria-selected="true"] {
  background: color-mix(in srgb, var(--primary-color) 14%, var(--theme-color-card));
  color: var(--brand-on-surface);
  font-weight: 600;
}
/* De markering moet ook zichtbaar zijn op de AL GEKOZEN optie — die heeft zelf al een
   getinte achtergrond, dus een tint erbij zegt niets. Een inset-ring werkt in beide
   gevallen en verschuift niets (geen border die de rij 2px hoger maakt). Staat ná de
   aria-selected-regel, anders wint die en zie je tijdens pijltjesnavigatie niet waar
   je bent. */
.select-optie.is-actief {
  box-shadow: inset 0 0 0 2px rgba(var(--primary-color-rgb), .45);
}
.select-optie:disabled {
  color: var(--theme-text-color-gray);
  cursor: not-allowed;
  background: transparent;
}
/* Kolom, want label en sub zijn allebei een <span> en dus inline: zonder deze regel
   plakken ze aan elkaar op één regel ("ConceptNog niet zichtbaar voor leden").
   min-width:0 hoort erbij, anders weigert het flex-item te krimpen en kapt de
   ellipsis hieronder nooit af. */
.select-optie-main {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}
.select-optie-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select-optie-sub {
  font-size: .8125rem;
  font-weight: 400;
  color: var(--theme-text-color-gray);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Het vinkje houdt ALTIJD zijn ruimte (visibility i.p.v. display): anders schuift
   de hele lijst een centimeter op zodra je iets kiest. */
.select-optie-vink { flex: 0 0 auto; visibility: hidden; color: var(--brand-on-surface); }
.select-optie[aria-selected="true"] .select-optie-vink { visibility: visible; }
.select-optie-vink svg,
.select-optie > svg:first-child { width: 1rem; height: 1rem; }
.select-optie > svg:first-child { flex: 0 0 auto; color: var(--theme-text-color-gray); }

/* De overgenomen native <select>. Hij BLIJFT in de DOM staan — hij draagt de waarde,
   POST't mee, en alle bestaande JS (.value, .options, selectedIndex, een inline
   onchange) blijft op hem werken. Alleen onzichtbaar en uit de tabvolgorde, want de
   knop ernaast is nu de bediening. display:none mag hier omdat SelectVeld.upgrade()
   het `required`-attribuut eraf haalt en zelf handhaaft: een verborgen required veld
   laat de browser struikelen met "not focusable" en dan verstuurt het formulier
   helemaal niet meer. */
.select-veld-native {
  display: none !important;
}

/* Zelfde verhaal voor een overgenomen <input type="date|time|datetime-local">: de input
   BLIJFT staan als waardedrager (POST, .value, inline onchange) en alleen de weergave
   verhuist naar de knop ernaast. display:none mag omdat MiniKalender.upgrade() het
   `required`-attribuut eraf haalt en zelf handhaaft — een verborgen required veld laat
   de browser struikelen met "not focusable" en dan verstuurt het formulier niet meer. */
.datumtijd-veld-native {
  display: none !important;
}

.select-leeg {
  padding: 1.25rem .75rem;
  text-align: center;
  font-size: .875rem;
  color: var(--theme-text-color-gray);
}

/* --------------------------------------------------------------------------
   Keuzevelden (checkbox, radio, switch) — dezelfde rand + focusring als de rest,
   zodat een formulier één geheel is en niet half Bootstrap-blauw oplicht.
   -------------------------------------------------------------------------- */
.form-check-input {
  border-color: var(--ui-veld-rand);
  transition: var(--ui-veld-overgang);
}
.form-check-input:hover:not(:checked):not(:disabled) {
  border-color: var(--ui-veld-rand-hover);
}
.form-check-input:focus {
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
}
.form-check-input:checked {
  background-color: var(--primary-color);
  border-color: var(--primary-color);
}

/* Bestandsveld: de huisregel is `form-control-file w-100` (NOOIT `form-control`),
   maar dan stond het er wel kaal bij naast de andere velden. */
.form-control-file {
  border-radius: var(--ui-veld-radius);
  background: var(--ui-veld-bg);
  border: 1px solid var(--ui-veld-rand);
  color: var(--theme-text-color);
  padding: .5rem .75rem;
  transition: var(--ui-veld-overgang);
}
.form-control-file:hover:not(:focus) { border-color: var(--ui-veld-rand-hover); }
.form-control-file:focus {
  outline: 0;
  background: var(--ui-veld-bg-actief);
  border-color: var(--ui-veld-rand-focus);
  box-shadow: var(--ui-veld-ring);
}

/* De "Bestand kiezen"-knop MOET meegestyled worden, anders zweeft hij in het veld.
   Dit was zichtbaar op élk bestandsveld in de app: de knop stond niet op volle hoogte
   en had lucht links, boven en onder. Oorzaak = twee lagen die elk een ander deel
   stylen en nooit samen zijn ontworpen:
     - ui-stijl.css gaf het VELD padding (.5rem .75rem) + rand + radius;
     - een legacy-blok in styles-sb.css.php stylet de KNOP (`input[type="file"]::
       -webkit-file-upload-button`) met een eigen radius en padding, in de veronderstelling
       dat die in de hoek van een ONGEPadde input zit — wat vóór de componentlaag klopte.
   Samen: de knop zakt de padding in, houdt zijn eigen afronding en raakt de rand niet meer.
   Het veld werd er bovendien 52px hoog van, tegen 39px voor een gewoon tekstveld.

   De negatieve marge spiegelt exact de veldpadding, zodat de knop weer tot de rand loopt
   (zelfde truc als Bootstrap bij `.form-control`). `margin-inline-end` zet daarna de ruimte
   tussen knop en bestandsnaam terug.

   LET OP de selector: `input[type="file"]` staat erbij om de specificiteit boven het
   legacy-blok te tillen (dat is 0,1,1 — een kale `.form-control-file` van 0,1,0 verliest
   het en dan blijven de oude padding/radius/achtergrond gewoon staan). Haal dat deel er
   dus niet uit "omdat het overbodig lijkt". Beide pseudo-elementen zijn in Chromium
   hetzelfde ding; `::file-selector-button` is de standaardnaam. */
input[type="file"].form-control-file {
  /* Ook hier hoger dan het legacy `input[type="file"] { background-color: … }`, dat het
     bestandsveld anders een ándere achtergrond gaf dan alle velden ernaast. */
  background: var(--ui-veld-bg);
  border-radius: var(--ui-veld-radius);
}
input[type="file"].form-control-file::file-selector-button {
  /* Verticale padding iets ruimer dan die van het veld: de knop bepaalt de hoogte van
     het hele bestandsveld, en met .5rem bleef het 3px lager dan een tekstveld ernaast. */
  padding: .625rem .9rem;
  margin: -.5rem -.75rem;
  margin-inline-end: .75rem;
  border: 0;
  border-inline-end: 1px solid var(--ui-veld-rand);
  /* Links de veldradius volgen, rechts vlak: anders steekt een vierkante knophoek
     door de afgeronde hoek van het veld heen. */
  border-radius: var(--ui-veld-radius) 0 0 var(--ui-veld-radius);
  background: var(--primary-color);
  color: var(--primary-bg-text-color);
  cursor: pointer;
  transition: var(--ui-veld-overgang);
}
.form-control-file:hover:not(:disabled)::file-selector-button {
  background: rgba(var(--primary-color-rgb), .88);
}

/* Tussenstand van een bereik-veld: "kies nu de einddatum". Zonder deze regel is
   niet te begrijpen waarom er na één dagklik nog niets compleet is. */
.dtk-bereikhint {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin: .8rem 0 0;
  padding: .5rem .7rem;
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  border-radius: var(--ui-radius-sm);
  font-size: .8125rem;
  font-weight: 600;
}
.dtk-bereikhint[hidden] { display: none; }
.dtk-bereikhint svg { width: 1rem; height: 1rem; flex-shrink: 0; }

/* Uitgeschakeld datum/tijd-veld (readonly-equivalent): de knop is niet bedienbaar,
   de verborgen input POST't zijn waarde gewoon mee. */
.datumtijd-trigger:disabled {
  background: var(--theme-color-4);
  color: var(--theme-text-color-gray);
  cursor: not-allowed;
}
.datumtijd-trigger:disabled:hover { border-color: var(--ui-veld-rand); }

/* Jaar- en maandkeuze in de kalender. De titel in de kop is een knop die een niveau
   omhoog gaat (dagen → maanden → jaren); zonder die route is een geboortedatum
   onbereikbaar — 40 jaar terug is honderden keren op ‹ klikken. */
.mini-kalender-titel-knop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .25rem;
  padding: .2rem .5rem;
  border: 0;
  border-radius: .5rem;
  background: transparent;
  color: var(--theme-text-color);
  font: inherit;
  font-size: .9375rem;
  font-weight: 600;
  text-transform: capitalize;
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
}
.mini-kalender-titel-knop:hover { background: var(--bs-primary-soft); color: var(--brand-on-surface); }
.mini-kalender-titel-knop:focus-visible { outline: 0; box-shadow: var(--ui-veld-ring); }
.mini-kalender-titel-knop svg { width: .9rem; height: .9rem; opacity: .6; }

.mini-kalender-blokken {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: .4rem;
  margin-top: .4rem;
}
.mini-kalender-blok {
  padding: .6rem .4rem;
  border: 1px solid transparent;
  border-radius: var(--ui-veld-radius);
  background: transparent;
  color: var(--theme-text-color);
  font-size: .875rem;
  line-height: 1.2;
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
}
.mini-kalender-blok:hover { background: var(--bs-primary-soft); color: var(--brand-on-surface); }
.mini-kalender-blok.is-vandaag { border-color: rgba(var(--primary-color-rgb), .45); font-weight: 700; }
.mini-kalender-blok.is-gekozen {
  background: var(--primary-color);
  border-color: transparent;
  color: var(--primary-bg-text-color);
  font-weight: 700;
}
.mini-kalender-blok:focus-visible { outline: 0; box-shadow: var(--ui-veld-ring); }

/* ── Gevarenzone ──────────────────────────────────────────────────────────────
   Onomkeerbare acties (alle tickets opnieuw genereren, teksten resetten, een
   sleutel of achtergrond verwijderen) horen NIET tussen de gewone knoppen. Ze
   staan onderaan, visueel apart, met de gevolgzin erbij — zodat "even opslaan"
   nooit per ongeluk "alles resetten" wordt. Bewust géén rode vlakvulling: dat
   schreeuwt bij elke pageload. Een dunne rand + rode kop is genoeg signaal. */
.danger-zone {
  border: 1px solid rgba(var(--bs-danger-rgb), .35);
  border-radius: var(--ui-radius, 1rem);
  padding: 1.25rem;
  margin-top: 2rem;
}
.danger-zone-title {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-weight: 700;
  color: var(--bs-danger);
  margin-bottom: .25rem;
}
.danger-zone-title svg { width: 1.1rem; height: 1.1rem; }
.danger-zone-sub {
  font-size: .9rem;
  color: var(--theme-text-color-gray);
  margin-bottom: 1rem;
}
/* Elke actie is een eigen regel: label + gevolg links, knop rechts. */
.danger-zone-item {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .75rem 0;
  border-top: 1px solid rgba(var(--bs-danger-rgb), .15);
}
.danger-zone-item:first-of-type { border-top: 0; }
.danger-zone-item-main { flex: 1 1 14rem; min-width: 0; }
.danger-zone-item-title { font-weight: 600; }
.danger-zone-item-sub { font-size: .85rem; color: var(--theme-text-color-gray); }
.danger-zone-item .btn, .danger-zone-item form { flex-shrink: 0; }

/* ── Veldlijst ────────────────────────────────────────────────────────────────
   Instellingenformulieren stonden historisch in een <table>: label links, veld
   rechts, beide 50%. Op een breed scherm gaapt daar 600px tussen wat bij elkaar
   hoort, en een keuze met twee opties krijgt een veld van 430px.

   Deze component presenteert zo'n tabel als een VELDLIJST: label boven het veld,
   veld begrensd, alles als één blokje bij elkaar. De markup blijft voorlopig een
   tabel — die formulieren zijn nog niet op productie getest en het zijn er zeven;
   ze allemaal herschrijven zou daar een tweede onzekerheid bovenop stapelen.
   Semantisch is het sowieso geen tabel maar een formulier, dus deze CSS toont
   wat het is. Bouw je zo'n scherm NIEUW, gebruik dan gewoon labels en divs.

   Let op: dit geldt bewust NIET voor de functietabel (45 features aan/uit) en de
   groepenlijst — dat zijn wél echte tabellen, en daar werkt label-links prima. */
.veldlijst,
.veldlijst > tbody,
.veldlijst > tbody > tr,
.veldlijst > tbody > tr > td {
  display: block;
  width: 100%;
}
.veldlijst { border-collapse: collapse; margin-bottom: 0; }
.veldlijst > tbody > tr {
  padding: 1rem 0;
  border-top: 1px solid var(--theme-color-3);
}
.veldlijst > tbody > tr:first-child { border-top: 0; padding-top: 0; }
.veldlijst > tbody > tr:last-child  { padding-bottom: 0; }

/* Het label: de eerste cel. */
.veldlijst > tbody > tr > td:first-child {
  padding: 0 0 .4rem 0;
  font-weight: 600;
  color: var(--theme-text-color);
  border: 0;
}
/* Uitleg onder een label blijft ingetogen. */
.veldlijst > tbody > tr > td:first-child .text-muted,
.veldlijst > tbody > tr > td:first-child small { font-weight: 400; }

/* Het veld: de tweede cel. Begrensd, want een select met twee opties heeft geen
   halve schermbreedte nodig. */
.veldlijst > tbody > tr > td:last-child {
  padding: 0;
  max-width: 28rem;
  border: 0;
}
/* Een rij met colspan (tussenkop) is geen veld en mag de volle breedte houden. */
.veldlijst > tbody > tr > td[colspan] { max-width: none; padding: 0; }
/* Wat na een veldlijst komt (meestal de opslaan-knop) krijgt lucht: zonder deze
   regel plakt de knop tegen het laatste veld, want de laatste rij heeft bewust
   geen onderpadding. */
.veldlijst + *,
.table-responsive:has(> .veldlijst) + * { margin-top: 1.25rem; }

/* ── Instellingen-overzicht: categoriekop + tegel ─────────────────────────────
   Het overzicht was een muur van gelijkwaardige tegels; je moest ze allemaal
   lezen om te weten in welke hoek je moest zoeken. Nu staan ze gebundeld onder
   een kopje, twee per rij, met een chevron die zegt "hier ga je heen". */
.instellingen-cat-kop {
  font-size: .95rem;
  font-weight: 700;
  color: var(--theme-text-color);
  margin: 1.75rem 0 .75rem;
}
.instellingen-cat-kop:first-child { margin-top: 0; }

.instellingen-tegel {
  display: flex;
  align-items: center;
  gap: 1rem;
  height: 100%;
  padding: 1.1rem 1.25rem;
  background: var(--theme-color-card);
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius, 1rem);
  text-decoration: none;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
/* De globale a:hover geeft anders een underline door de hele tegel. */
.instellingen-tegel:hover,
.instellingen-tegel:focus-visible {
  text-decoration: none;
  border-color: rgba(var(--primary-color-rgb), .45);
  box-shadow: 0 .5rem 1.2rem rgba(0, 0, 0, .08);
  transform: translateY(-2px);
}
.instellingen-tegel:focus-visible { outline: 0; box-shadow: var(--ui-veld-ring); }

/* min-width:0 is hier niet cosmetisch: zonder die regel weigert de tekstkolom
   smaller te worden dan zijn langste woord en loopt de tegel buiten de kaart. */
.instellingen-tegel-tekst { flex: 1 1 auto; min-width: 0; display: block; }
.instellingen-tegel-titel {
  display: block;
  font-weight: 700;
  color: var(--theme-text-color);
  margin-bottom: .15rem;
}
.instellingen-tegel-sub {
  display: block;
  font-size: .875rem;
  color: var(--theme-text-color-gray);
  line-height: 1.35;
}
.instellingen-tegel-chevron {
  flex-shrink: 0;
  color: var(--theme-text-color-gray);
  display: flex;
  align-items: center;
}
.instellingen-tegel-chevron svg { width: 1.1rem; height: 1.1rem; }
.instellingen-tegel .icon-tile { flex-shrink: 0; }
/* De contentcontainer wordt met mt-n10 over de donkere paginakop getrokken (huispatroon:
   kaarten die op de kop "zweven"). Op het instellingen-overzicht landt daardoor de eerste
   categoriekop halverwege die kop en is hij nauwelijks te lezen. Deze marge duwt de
   tegellijst er net onderuit, zonder het zwevende zoekveld kwijt te raken. */
#instellingenGroepen { margin-top: 2.25rem; }
/* Het zoekveld en zijn resultaten zweven over de donkere paginakop (de container wordt
   met mt-n10 omhooggetrokken). Een kale .data-list heeft daar geen eigen achtergrond,
   dus stond donkere tekst op een donkere kop — onleesbaar. Dit geeft het paneel hetzelfde
   oppervlak als een card-soft, met een schaduw zodat het duidelijk bóven de kop hangt. */
.zoek-paneel {
  background: var(--theme-color-card);
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius, 1rem);
  box-shadow: 0 8px 28px rgba(33, 40, 50, .18);
  overflow: hidden;
  padding: .25rem;
}
.zoek-paneel .data-list-row { border-radius: var(--ui-radius-sm, .75rem); }
/* De rijen zijn <a>'s: zonder dit pakken titel en subtitel de linkkleur. */
.zoek-paneel .data-list-title { color: var(--theme-text-color); }
.zoek-paneel .data-list-sub   { color: var(--theme-text-color-gray); }
.zoek-paneel .data-list-row:hover { background: var(--theme-color-4); }
/* De tegel is een <a>, dus een pill erin pikt anders de linkkleur op. Een tag is
   een label, geen link — dus de kleur hier expliciet vastzetten. */
.instellingen-tegel .status-pill-neutral   { color: var(--theme-text-color-gray); }
.instellingen-tegel .status-pill-4planning { color: #fff; }

/* De aanvraag-tegel ("andere integratie") is geen bestaande instelling maar een
   uitnodiging. Gestippelde rand + glas: hij hoort zichtbaar in dezelfde rij thuis,
   maar leest niet als "dit heb je al". Volledig transparant was te ver — dan viel de
   tegel weg tegen de pagina. color-mix i.p.v. een vaste rgba(), zodat hij in dark én
   light het juiste oppervlak mengt. */
.instellingen-tegel-plus {
  border-style: dashed;
  background: color-mix(in srgb, var(--theme-color-card) 45%, transparent);
  -webkit-backdrop-filter: blur(10px) saturate(1.4);
  backdrop-filter: blur(10px) saturate(1.4);
}
.instellingen-tegel-plus:hover {
  background: color-mix(in srgb, var(--theme-color-card) 70%, transparent);
}
.instellingen-tegel-plus .instellingen-tegel-titel { color: var(--primary-color); }
.instellingen-tegel-plus .icon-tile {
  background: var(--bs-primary-soft);
  color: var(--primary-color);
}

/* --------------------------------------------------------------------------
   GENERATOR (gen-*) — een lijst met lege plekken die in één run gevuld worden
   --------------------------------------------------------------------------
   Voor werk dat de server in één keer kan uitrekenen (scheidsrechters indelen,
   bardiensten vullen, taken verdelen) maar dat de gebruiker moet KUNNEN
   controleren en gedeeltelijk zelf invullen. Drie gedragsregels zitten in het
   component zelf, omdat ze alle drie een echte valkuil afdekken:

   1. **Je kunt vooraf zelf plekken invullen.** Wat je zelf koos krijgt een
      slotje (`gen-slot is-vast`) en wordt door de generator met rust gelaten.
      Zonder dat is genereren alles-of-niets en dus eng.
   2. **De landing is de wachttijd, geen show erbovenop.** De echte run duurt
      1-3s; die tijd verdeel je over de rijen met `--gen-delay` (JS zet 'm per
      rij) in plaats van er een apart wachtscherm van te maken.
   3. **De tweede run is géén herhaling van de eerste.** Zet `data-gen-ronde`
      op de lijst: bij ronde >1 animeert alleen wat er echt verandert
      (`item-nieuw-rij`, het huiscomponent), de rest staat er meteen. Anders is de
      tiende keer irritant.

   Het AI-signaal is de Planny-avatar + de bestaande glow (`btn-planny`) — geen
   tweede gradient-systeem. `prefers-reduced-motion` zet alle beweging uit; het
   resultaat blijft dan identiek, alleen zonder overgang.
   -------------------------------------------------------------------------- */

/* De kopbalk: samenvatting links, generatie-actie rechts. */
.gen-balk {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
}
.gen-balk-main { min-width: 0; flex: 1 1 12rem; }
.gen-balk-titel { font-weight: 600; }
.gen-balk-sub { font-size: .8125rem; color: var(--theme-text-color-gray); }

/* Meters: een stat-strip waarvan de items KLIKBAAR zijn en de lijst filteren.
   Bewust een <button>, zodat toetsenbord en screenreader hem ook krijgen. */
.gen-meter {
  border: 0;
  background: transparent;
  padding: 0 0 0 1rem;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: .5rem;
  transition: opacity .15s ease;
}
.gen-meter:hover { opacity: .75; }
.gen-meter:focus-visible {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}
.gen-meter[aria-pressed="true"] .stat-strip-value { color: var(--primary-color); }
.gen-meter[aria-pressed="true"] .icon-tile {
  box-shadow: 0 0 0 2px var(--primary-color);
}
/* Het getal telt op na een run (JS), dus vaste cijferbreedte — anders springt
   de hele strip tijdens het tellen. */
.gen-meter .stat-strip-value { font-variant-numeric: tabular-nums; }

/* De plek die gevuld wordt. Vier standen: leeg → bezig → gevuld → vast. */
.gen-slot {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  min-height: 1.75rem;
}
.gen-slot.is-leeg {
  min-width: 7rem;
  padding: .15rem .6rem;
  border: 1px dashed var(--theme-color-3);
  border-radius: 999px;
  color: var(--theme-text-color-gray);
  font-size: .75rem;
}
.gen-slot.is-bezig {
  min-width: 7rem;
  height: 1.5rem;
  border-radius: 999px;
  border: 0;
}
.gen-slot.is-vast .gen-slot-lock {
  color: var(--theme-text-color-gray);
  display: inline-flex;
}
.gen-slot-lock svg { width: .8rem; height: .8rem; }
.gen-slot-uitleg {
  display: block;
  font-size: .75rem;
  color: var(--theme-text-color-gray);
  margin-top: .15rem;
}

/* De landing. --gen-delay komt per rij uit JS; de duur is bewust kort (de
   beweging mag de aandacht niet langer vasthouden dan de rij waard is). */
.gen-land {
  animation: gen-land .45s cubic-bezier(.22, 1, .36, 1) var(--gen-delay, 0ms) both;
}
@keyframes gen-land {
  from { opacity: 0; transform: translateY(-.5rem) scale(.92); }
  to   { opacity: 1; transform: none; }
}
/* Vanaf ronde 2 markeren we alleen wat veranderde — één korte tint, geen dans.
   Die tint is nu het huiscomponent `.item-nieuw-rij` (zie het blok hieronder): het was
   byte-voor-byte dezelfde keyframe, dezelfde 1.4s en dezelfde kleur. `gen-land` blijft
   wél apart: dat is geen "dit is nieuw" maar het UITDELEN van een vers gegenereerd
   voorstel — van bovenaf, met scale, getrapt per slot. Ander moment, andere beweging. */

@media (prefers-reduced-motion: reduce) {
  .gen-land { animation: none; }
}

/* ── Item-animatie: een lijst die verandert, laat dat zien ────────────────────
   Drie momenten, drie classes. Ze horen bij elkaar maar staan los, want een lijst
   kan er twee van gebruiken zonder de derde.

     .item-verschijnt + .is-zichtbaar  — item schuift omhoog in beeld bij scrollen
     .item-nieuw                       — ring om iets dat JIJ zojuist toevoegde
     .item-weg                         — schuift opzij en vervaagt, dan pas weghalen

   WAAROM DIT BESTAAT: dit patroon stond twee keer los in de app (het bouwjournaal
   als `bj-post`/`bj-pulse`/`bj-removing`, de scheidsgenerator als `gen-land`/
   `gen-rij.is-nieuw`) met net andere tijden en een ander "nieuw"-signaal. Allebei zijn ze op
   2026-08-12 hierheen gemigreerd; wat daar bewust apart bleef staat in die bestanden zelf. Precies de
   drift waar deze laag voor bestaat. Nieuwe lijsten gebruiken deze; die twee mogen
   migreren wanneer je ze toch aanraakt (niet blind — hun timing is bewust gekozen).

   VERSCHIJNEN vs NIEUW is niet hetzelfde en dat verwar je makkelijk. Verschijnen gaat
   over iets dat er al wás en nu in beeld komt: rustig, geen aandacht vragen. Nieuw
   gaat over het gevolg van jouw eigen klik: dat mag je één keer aanwijzen. Zet ze
   nooit samen op één element — dan fade't het item in terwijl het ook nog pulseert,
   en dat leest als een storing. Iets dat je zelf toevoegt begint dus meteen op
   `.is-zichtbaar`. */
.item-verschijnt {
  opacity: 0;
  transform: translateY(12px);
  /* De vertraging komt per item uit JS (ItemAnimatie.observeer) en is gecapt, zodat
     een lijst van 200 niet twaalf seconden staat te druppelen. */
  transition: opacity .45s ease var(--item-vertraging, 0ms),
              transform .45s ease var(--item-vertraging, 0ms);
}
.item-verschijnt.is-zichtbaar {
  opacity: 1;
  transform: none;
}

/* Een ring die uitdijt en vervaagt — werkt op alles wat een eigen vlak heeft
   (kaart, tegel, list-group-item). Bewust een ring en geen kleurvlak: de inhoud
   blijft leesbaar terwijl het signaal loopt. */
.item-nieuw {
  animation: item-nieuw-ring 1.3s ease-out 1;
}
@keyframes item-nieuw-ring {
  0%   { box-shadow: 0 0 0 0 rgba(var(--primary-color-rgb), .55); }
  100% { box-shadow: 0 0 0 14px rgba(var(--primary-color-rgb), 0); }
}

/* Op een TABELRIJ kan dat niet: een <tr> draagt geen betrouwbare box-shadow (de
   cellen tekenen eroverheen). Daar is het signaal een korte achtergrondtint op de
   cellen zelf — vandaar `> *` en niet de rij. */
.item-nieuw-rij > * {
  animation: item-nieuw-tint 1.4s ease-out both;
}
@keyframes item-nieuw-tint {
  0%   { background-color: rgba(var(--primary-color-rgb), .16); }
  100% { background-color: transparent; }
}

/* Weggaan mag sneller dan komen: de beslissing is al genomen. Haal het element pas
   ná deze 250ms uit de DOM, anders zie je de animatie niet (ItemAnimatie.verwijder
   doet dat voor je). */
.item-weg {
  opacity: 0;
  transform: translateX(20px);
  transition: opacity .25s ease, transform .25s ease;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .item-verschijnt { opacity: 1; transform: none; transition: none; }
  .item-nieuw,
  .item-nieuw-rij > * { animation: none; }
  .item-weg { transition: none; }
}

/* ── Beeld-veld (logo/achtergrond/favicon/app-icoon) ──────────────────────────
   Alle vier de beelden delen één opbouw: vaste previewhoogte links, uitleg in het
   midden, wijzig-knop rechts. Zonder die vaste hoogte springt de kaart bij elke
   tabwissel mee met het formaat van het plaatje. */
.beeld-veld {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  padding: 1rem;
  border: 1px solid var(--theme-color-3);
  border-radius: var(--ui-radius, 1rem);
}
.beeld-veld-preview {
  flex-shrink: 0;
  width: 8rem;
  height: 6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: .5rem;
  background: var(--theme-color-4);
  border-radius: .75rem;
}
.beeld-veld-preview img { max-width: 100%; max-height: 100%; object-fit: contain; }
/* min-width:0 — anders weigert de tekstkolom te krimpen en loopt het blok uit de kaart. */
.beeld-veld-tekst { flex: 1 1 12rem; min-width: 0; }

/* De segmented control kan ook LINKS bevatten i.p.v. knoppen — bij een weergave
   die server-side rendert (deelbaar, bookmarkbaar, kleine HTML) is dat het
   juiste element. De globale a:hover geeft anders underline door de hele pill. */
a.mini-seg-btn { text-decoration: none; display: inline-block; }
a.mini-seg-btn:hover,
a.mini-seg-btn:focus { text-decoration: none; }

/* --------------------------------------------------------------------------
   WEEKROOSTER (weekrooster-*) — "wanneer kun je meestal?"
   --------------------------------------------------------------------------
   Een raster van 7 dagen × N dagdelen waarin je vakjes aan- en uitklikt. Bedoeld
   voor terugkerende beschikbaarheid (scheidsrechters, bardiensten, vrijwilligers).

   Waarom een rooster en geen lijst: de onderliggende opslag is een BLOCK-list
   ("ik kan niet op…"), maar mensen denken in "wanneer kan ik wél". Met losse
   regels toevoegen kost "alleen zaterdagochtend" twintig handelingen; hier is het
   één veeg. Het rooster leest dus als wel/niet en slaat op als block-list — een
   UI-antwoord op een datamodel-klacht, zonder migratie.

   Elk vakje is een echte <button aria-pressed>, dus toetsenbord en screenreader
   doen mee. De dag- en dagdeelkoppen zijn <span>: die zijn label, geen knop.
   -------------------------------------------------------------------------- */
.weekrooster {
  display: grid;
  grid-template-columns: auto repeat(7, minmax(0, 1fr));
  gap: .25rem;
  align-items: center;
}
.weekrooster-dagnaam,
.weekrooster-rijlabel {
  font-size: .75rem;
  color: var(--theme-text-color-gray);
  text-align: center;
  padding-bottom: .15rem;
}
.weekrooster-rijlabel { text-align: left; padding-right: .5rem; white-space: nowrap; }
.weekrooster-vak {
  aspect-ratio: 1 / .6;
  min-height: 2rem;
  border: 1px solid var(--theme-color-3);
  background: var(--theme-color-4);
  border-radius: .5rem;
  cursor: pointer;
  transition: background-color .12s ease, border-color .12s ease;
  padding: 0;
}
.weekrooster-vak:hover { border-color: var(--primary-color); }
.weekrooster-vak[aria-pressed="true"] {
  background: var(--bs-success-soft, rgba(var(--primary-color-rgb), .16));
  border-color: rgba(var(--primary-color-rgb), .5);
}
.weekrooster-vak:focus-visible {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}
.weekrooster-vak[disabled] { opacity: .5; cursor: default; }

/* De samenvattende regel eronder: zonder die zin moet je het rooster "lezen". */
.weekrooster-samenvatting {
  font-size: .8125rem;
  color: var(--theme-text-color-gray);
  margin-top: .6rem;
}

/* Uitklapkaart met een EXTRA actie in de kop (bijvoorbeeld een AI-knop naast de
   chevron). De kop kan dan niet één grote knop zijn — een <button> in een
   <button> is ongeldige HTML en de browser sloopt de opmaak. Vandaar een rij:
   de toggle links (titel + stand), de actie ernaast, en een eigen chevron-knop
   die dezelfde collapse bedient. Twee triggers voor één paneel is prima; ze
   krijgen allebei aria-expanded van Bootstrap, dus de chevron draait gewoon mee. */
.disclosure-kop {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding-right: 1.35rem;
}
.disclosure-kop .disclosure-toggle { flex: 1 1 auto; min-width: 0; }
.disclosure-kop .disclosure-chevron-btn {
  flex-shrink: 0;
  border: 0;
  background: transparent;
  color: var(--theme-text-color-gray);
  padding: .35rem;
  border-radius: 50%;
  cursor: pointer;
  display: inline-flex;
  transition: background-color .15s ease;
}
/* Hover tint de HELE kop, niet alleen de linkerhelft. De toggle en de chevron doen
   precies hetzelfde (allebei de collapse open/dicht), dus het hoort één doelgebied te
   zijn; twee hovervlakken die elkaar raken lezen als twee verschillende knoppen.
   De AI-knop die er tussenin kan staan is bewust uitgezonderd — die doet iets ánders
   en heeft zijn eigen hover.
   Browsers zonder :has() vallen terug op het oude gedrag (alleen het deelvlak tint),
   wat lelijker is maar niet kapot. */
.disclosure-kop:has(> .disclosure-toggle:hover),
.disclosure-kop:has(> .disclosure-toggle:focus-visible),
.disclosure-kop:has(> .disclosure-chevron-btn:hover),
.disclosure-kop:has(> .disclosure-chevron-btn:focus-visible) {
  background: rgba(var(--theme-color-3-rgb), .35);
}
/* Het deelvlak dan NIET óók tinten: twee lagen over elkaar wordt zichtbaar donkerder
   op de helft waar je muis staat, en dat is precies de naad die we wilden weghalen. */
.disclosure-kop:has(> .disclosure-toggle) > .disclosure-toggle:hover,
.disclosure-kop:has(> .disclosure-toggle) > .disclosure-toggle:focus-visible {
  background: transparent;
}
.disclosure-kop .disclosure-chevron-btn:hover { background: transparent; }
.disclosure-kop .disclosure-chevron-btn:focus-visible {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}
.disclosure-kop .disclosure-chevron-btn svg { transition: transform .25s ease; }
.disclosure-kop .disclosure-chevron-btn[aria-expanded="true"] svg { transform: rotate(180deg); }
/* Onder 576px past titel + actieknop + chevron niet op één regel: de titel werd tot
   "Servicek…" afgekapt terwijl de stand-regel in een kolom van ~80px acht regels werd en
   het icoon los in het midden zweefde. Op mobiel dus wrappen: toggle op een volle regel,
   actie eronder. De titel mag daar ook over twee regels — een afgekapte titel in een kop
   die je moet openklappen is erger dan een titel van twee regels. */
@media (max-width: 575.98px) {
  .disclosure-kop { flex-wrap: wrap; padding-right: .5rem; }
  .disclosure-kop > .disclosure-toggle { flex: 1 1 100%; }
  .disclosure-kop > .disclosure-chevron-btn { order: 3; margin-left: auto; }
  .disclosure-kop > :not(.disclosure-toggle):not(.disclosure-chevron-btn) { order: 2; margin: 0 0 .6rem 1.35rem; }
  .disclosure-title { white-space: normal; }
}

/* Tabel die tot de kaartrand loopt, maar wél op één lijn met de kaartinhoud.
   Zonder dit begint de eerste kolom op .75rem terwijl de tekst in de card-body
   op 1.5rem staat — een verspringing van driekwart centimeter die je in elke
   kaart met zowel tekst als een tabel ziet. */
.table-soft-inset > thead > tr > th:first-child,
.table-soft-inset > tbody > tr > td:first-child { padding-left: 1.5rem; }
.table-soft-inset > thead > tr > th:last-child,
.table-soft-inset > tbody > tr > td:last-child { padding-right: 1.5rem; }
@media (max-width: 575.98px) {
  /* Op een telefoon is de kaartpadding zelf kleiner; anders eet de inspringing
     de halve kolombreedte op. */
  .table-soft-inset > thead > tr > th:first-child,
  .table-soft-inset > tbody > tr > td:first-child { padding-left: 1rem; }
  .table-soft-inset > thead > tr > th:last-child,
  .table-soft-inset > tbody > tr > td:last-child { padding-right: 1rem; }
}

/* De Planny-avatar op tekstformaat, buiten een knop of een ai-hint — bijvoorbeeld
   als kop van een invoerblok ("Vertel Planny wat er speelt"). Binnen `.ai-hint`
   is dit al geregeld (`.ai-hint img`); deze class is voor de losse gevallen, zodat
   niemand er een eigen width/height bij verzint. */
.planny-mini {
  width: 1.35rem;
  height: 1.35rem;
  border-radius: 50%;
  flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   STAP-SLIDER — een schuif met VASTE stops i.p.v. een vrij getal
   --------------------------------------------------------------------------
   Voor waarden die je niet vrij wilt laten invullen maar uit een afgesproken
   reeks wilt kiezen (opslagpakketten, ledenstaffels, prijstreden). De schuif
   loopt over de INDEX van de reeks, niet over de waarde zelf — daardoor staan
   de stops op gelijke afstand ook als de reeks ongelijk oploopt (1, 5, 20, 25,
   50 GB) en kan de server simpelweg de index tegen dezelfde lijst valideren.
   Dat is meteen de reden om dit boven een vrij invoerveld te verkiezen: er
   bestaat geen ongeldige waarde, dus ook geen typefout die een omgeving op
   0 MB zet.

   Markup (zie de galerij):
     <div class="stap-slider">
       <div class="stap-slider-kop"><span>Label</span><output>20 GB</output></div>
       <input type="range" class="stap-slider-range" min="0" max="6" step="1">
       <div class="stap-slider-schaal"><span>1 GB</span><span>100 GB</span></div>
     </div>
   -------------------------------------------------------------------------- */
.stap-slider-kop {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: .5rem;
}
.stap-slider-kop output {
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--primary-color);
  font-variant-numeric: tabular-nums;   /* springt niet bij wisselende cijferbreedte */
}
.stap-slider-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 1.5rem;                       /* raakvlak, niet de dikte van de balk */
  background: transparent;
  cursor: pointer;
}
/* De baan is GEVULD tot de knop. Zonder die vulling zie je tijdens het slepen alleen een
   bolletje op een grijze streep en moet je het getal bovenaan lezen om te weten waar je zit.
   --stap-pct wordt door de JS gezet (0-100). */
.stap-slider-range::-webkit-slider-runnable-track {
  height: .5rem;
  border-radius: 1rem;
  background: linear-gradient(to right,
    var(--primary-color) 0 var(--stap-pct, 0%),
    var(--theme-color-3) var(--stap-pct, 0%) 100%);
}
.stap-slider-range::-moz-range-track {
  height: .5rem;
  border-radius: 1rem;
  background: linear-gradient(to right,
    var(--primary-color) 0 var(--stap-pct, 0%),
    var(--theme-color-3) var(--stap-pct, 0%) 100%);
}
.stap-slider-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: -.375rem;                 /* centreert de knop op de baan */
  border-radius: 50%;
  background: var(--primary-color);
  border: 2px solid var(--theme-color-card);
  box-shadow: 0 1px 4px rgba(33, 40, 50, .3);
  transition: transform .12s ease;
}
.stap-slider-range::-moz-range-thumb {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--primary-color);
  border: 2px solid var(--theme-color-card);
  box-shadow: 0 1px 4px rgba(33, 40, 50, .3);
  transition: transform .12s ease;
}
.stap-slider-range:active::-webkit-slider-thumb { transform: scale(1.15); }
.stap-slider-range:active::-moz-range-thumb     { transform: scale(1.15); }
/* Zichtbare focus is hier extra belangrijk: met het toetsenbord is de pijltjestoets
   de enige manier om een stop te kiezen. */
.stap-slider-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 var(--ui-veld-ring, 3px) rgba(var(--primary-color-rgb), .25); }
.stap-slider-range:focus-visible::-moz-range-thumb     { box-shadow: 0 0 0 var(--ui-veld-ring, 3px) rgba(var(--primary-color-rgb), .25); }
/* ALLE stops zichtbaar, niet alleen de uiteinden. Dat was de eigenlijke klacht: met alleen
   "1 GB … 100 GB" onder de baan weet je tijdens het slepen niet op welke trede je staat. Elke
   stop is nu een streepje met zijn label; de actieve licht op in de merkkleur. */
.stap-slider-stops {
  display: flex;
  justify-content: space-between;
  margin-top: .375rem;
}
.stap-slider-stop {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .25rem;
  font-size: .6875rem;
  line-height: 1.2;
  color: var(--theme-text-color-gray);
  text-align: center;
  font-variant-numeric: tabular-nums;
  transition: color .12s ease;
}
/* De uiterste labels zouden anders half buiten de kaart vallen: die lijnen we uit op hun kant. */
.stap-slider-stop:first-child { align-items: flex-start; text-align: left; }
.stap-slider-stop:last-child  { align-items: flex-end;   text-align: right; }
.stap-slider-stop::before {
  content: '';
  width: 2px;
  height: .375rem;
  border-radius: 1px;
  background: var(--theme-color-3);
  transition: background .12s ease, height .12s ease;
}
.stap-slider-stop.is-actief {
  color: var(--primary-color);
  font-weight: 700;
}
.stap-slider-stop.is-actief::before {
  background: var(--primary-color);
  height: .5rem;
}
/* Onder de stops is al gepasseerd: dat leest als "hier kom je vandaan". */
.stap-slider-stop.is-gehad::before { background: rgba(var(--primary-color-rgb), .45); }

/* Op een smal scherm passen 7-8 labels niet naast elkaar; dan blijven de streepjes staan
   (positie afleesbaar) en tonen we alleen de uiteinden en de actieve stop als tekst. */
@media (max-width: 420px) {
  .stap-slider-stop span { display: none; }
  .stap-slider-stop:first-child span,
  .stap-slider-stop:last-child span,
  .stap-slider-stop.is-actief span { display: block; }
}

/* Nog in gebruik door oudere markup: alleen de twee uiteinden. */
.stap-slider-schaal {
  display: flex;
  justify-content: space-between;
  margin-top: .25rem;
  font-size: .8125rem;
  color: var(--theme-text-color-gray);
}

/* Kleine variant van de beeld-preview, voor in een data-list-regel. */
.beeld-veld-preview-sm { width: 3.5rem; height: 2.5rem; padding: .25rem; border-radius: .5rem; }

/* ==========================================================================
   Page-header-4planning — de paginakop in ONZE merkkleur i.p.v. die van de
   omgeving. Voor schermen die niet bij één club horen maar bij het platform:
   de omgevingskiezer, uitgelogde/registratie-schermen, platform-admin.
   Gebruik: class="page-header page-header-dark page-header-4planning"
   — dus in plaats van het gebruikelijke `bg-primary`, niet ernaast.
   Drie dingen zitten bewust IN de class:
   1. VLAKKE kleur, geen verloop. Een verloop is hier geprobeerd en weer
      teruggedraaid: de kop is per pagina verschillend hoog, dus hij eindigt
      onderaan ook in een andere tint, en dan lijkt dezelfde kop per scherm een
      andere kleur. Eén tint is bovendien wat de rest van de app doet (`bg-primary`).
   2. --primary-bg-text-color(-rgb) wordt hier op wit gezet. De kop erft die
      var normaal van de OMGEVING; op een pagina mét een geladen omgeving zou
      een lichte clubkleur dus donkere tekst en donkere btn-header-action-
      knoppen op ons rood geven. Rood vraagt altijd wit — dus pinnen we het,
      en werken de bestaande page-header-dark- en btn-header-action-regels
      (die allebei op deze var draaien) vanzelf goed.
   3. Dezelfde grote "4" als op het inlogscherm (.login-vier, css/login-2026.css)
      staat er sterk uitvergroot doorheen: zó groot dat je alleen nog de vormen
      ziet, niet het logo.
   ========================================================================== */
.page-header.page-header-4planning {
  --primary-bg-text-color: #fff;
  --primary-bg-text-color-rgb: 255, 255, 255;
  /* Drie knoppen om aan te draaien als de uitsnede anders moet: */
  --ui-4pl-merk-hoogte: 300%;   /* t.o.v. de HOOGTE van de kop, niet de breedte */
  /* Afstand vanaf de RECHTERRAND: 0 = het masker zelf (incl. zijn eigen lege rand)
     recht tegen de kop-rand, positief = verder naar binnen, negatief = erover-
     heen. De bron-PNG heeft intern zelf al ~10% lege ruimte rondom de vorm (het is
     hetzelfde bestand als op het inlogscherm, niet tot de vorm bijgesneden), dus
     0 oogt NIET als "vlak tegen de rand" — vandaar de -6rem hieronder: die schuift
     precies die eigen marge het beeld uit, zodat de vorm zelf de rand raakt.
     ÉÉN waarde volstaat voor alle breedtes (geen aparte desktop-regel meer nodig):
     de rendermaat van het masker hangt aan de HOOGTE van de kop (--ui-4pl-merk-hoogte
     hierboven), niet aan de breedte van het scherm, dus deze compensatie is dat ook
     niet. Vanaf de rechterrand posities geven is precies daarom handiger dan vanaf
     links: links zou een schermbreedte-afhankelijk (en dus per breekpunt ander)
     getal vragen, rechts niet. */
  --ui-4pl-merk-rechts: -16rem;
  --ui-4pl-merk-sterkte: .10;   /* zelfde waarde als .login-vier op het inlogscherm */
  position: relative;
  background-color: var(--ui-4planning);
}

/* Losse desktop-waarden voor het merkteken — overschrijft alleen de vars, de rest
   van de regel hierboven (masker, positie-mechaniek) blijft ongewijzigd. */
@media (min-width: 992px) {
  .page-header.page-header-4planning {
    --ui-4pl-merk-hoogte: 740%;
    --ui-4pl-merk-rechts: -20rem;
  }
}

/* Het merkteken: exact dezelfde bron als de 4 op het inlogscherm
   (images/4planning-mobile-login-background.png, zie .login-vier in login-2026.css),
   zodat het inloggen en het kiezen van een omgeving één beeld vormen.
   Drie keuzes die elk een alternatief afwijzen:
   · MASK, geen <img> of background-image. Via het masker telt alleen de alfa, dus
     de kleur van de bron-PNG doet er niet toe en de sterkte is één var — bij een
     gewone achtergrondafbeelding kun je de laag niet apart doorzichtig maken.
   · Een PSEUDO-ELEMENT, geen extra <div>: dan werkt de class overal waar hij staat,
     zonder dat elke pagina markup moet toevoegen.
   · GEEN overflow:hidden op de kop om de overhang te klippen — daar zit op
     omgevingen.php het user-dropdownmenu in, dat zou dan afgesneden worden. Een
     masker wordt sowieso al door het eigen vlak van dit element geklipt.
   Maat is gekoppeld aan de HOOGTE van de kop, zodat een lage en een hoge kop
   dezelfde uitsnede-verhouding tonen in plaats van de ene een detail en de andere
   het hele logo. */
.page-header.page-header-4planning::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-color: #fff;
  opacity: var(--ui-4pl-merk-sterkte);
  -webkit-mask-image: url("../images/4planning-mobile-login-background.png");
          mask-image: url("../images/4planning-mobile-login-background.png");
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  /* GEEN `right <lengte> center` (de background-position-edge-offset-syntax) —
     `mask-position` accepteert die vorm niet zoals `background-position` dat wel
     doet; de browser verwerpt de hele declaratie dan stil en valt terug op de
     default `0% 0%` (linksboven), zonder waarschuwing. Geverifieerd: exact dát
     gebeurde hier (de "4" stond linksboven i.p.v. rechts). `calc(100% - X)` is wél
     geldig: bij X=0 staat de rechterrand van het masker precies tegen de kop-rand,
     positieve X duwt naar binnen, negatieve X schuift erover (zelfde teken-
     afspraak als hierboven bij --ui-4pl-merk-rechts). `center` verticaal i.p.v.
     een percentage: bij een masker dat groter is dan het vlak keren percentage-
     offsets van richting om, waardoor de uitsnede per kophoogte zou verspringen. */
  -webkit-mask-position: calc(100% - var(--ui-4pl-merk-rechts)) center;
          mask-position: calc(100% - var(--ui-4pl-merk-rechts)) center;
  -webkit-mask-size: auto var(--ui-4pl-merk-hoogte);
          mask-size: auto var(--ui-4pl-merk-hoogte);
}

/* De inhoud van de kop boven het merkteken tillen. Bewust op de directe kinderen
   (dat is de `.container-xl` van een gewone kop én de `.lob-wrap` van omgevingen.php)
   en niet via een stacking context op de kop zelf: dan zou een dropdownmenu ín de kop
   opgesloten raken op het niveau van de kop en achter latere pagina-inhoud kunnen
   vallen. Nu ligt zo'n menu juist op niveau 1 en dus overal bovenop. */
.page-header.page-header-4planning > * {
  position: relative;
  z-index: 1;
}

/* ── Uitleg-knop ─────────────────────────────────────────────────────────────
   Klein vraagteken naast een veldlabel dat één zin uitleg opent (Bootstrap-popover).
   Markup ALTIJD via uitleg_knop() in includes/loggedin/ui_helpers.php.
   Bewust een popover en geen tooltip: een tooltip hangt aan hover en bestaat dus niet
   op een telefoon, en de app draait ook als WebView. */
.uitleg-knop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  margin-left: .25rem;
  vertical-align: text-bottom;
  background: none;
  border: 0;
  border-radius: 50%;
  color: var(--theme-text-color-gray);
  cursor: pointer;
  transition: color var(--ui-veld-overgang, .15s ease);
}
.uitleg-knop:hover,
.uitleg-knop:focus-visible { color: var(--primary-color); }
.uitleg-knop:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; }
.uitleg-knop svg { width: 1.15rem; height: 1.15rem; }

/* ── Popover in de huisstijl ─────────────────────────────────────────────────
   Zet `data-bs-custom-class="popover-soft"` op de trigger (uitleg_knop() doet dat al).
   Bootstrap 5.3 stuurt de hele popover — inclusief het pijltje — via `--bs-popover-*`,
   dus dit is een variabelen-override en geen her-implementatie: geen enkele regel hoeft
   te weten hoe Popper het pijltje positioneert.

   Familie: `toast-soft`. Allebei zweven ze boven de pagina, dus allebei een DOORSCHIJNENDE
   rand in tekstkleur + een schaduw die het werk doet — een harde rand laat zo'n vlak
   uitgeknipt lijken. De basis in styles-sb.css.php zet `--bs-popover-bg` op
   `--theme-text-color` (de TEKSTkleur); dat is precies wat hier rechtgezet wordt. */
.popover-soft {
  --bs-popover-max-width: 20rem;
  --bs-popover-bg: var(--theme-color-card);
  --bs-popover-border-width: 1px;
  --bs-popover-border-color: rgba(var(--theme-text-color-rgb), .1);
  --bs-popover-border-radius: var(--ui-radius);
  --bs-popover-inner-border-radius: calc(var(--ui-radius) - 1px);
  --bs-popover-box-shadow: 0 .875rem 2.25rem rgba(var(--custom-theme-box-shadow-rgb), .22);
  --bs-popover-body-padding-x: 1rem;
  --bs-popover-body-padding-y: .875rem;
  --bs-popover-body-color: var(--theme-text-color-gray);
  --bs-popover-font-size: .85rem;
  --bs-popover-arrow-width: .9rem;
  --bs-popover-arrow-height: .45rem;
  --bs-popover-arrow-border: var(--bs-popover-border-color);
  /* De basisregel zet een eigen box-shadow niet toe; hier wél, want dit vlak zweeft. */
  box-shadow: var(--bs-popover-box-shadow);
  line-height: 1.55;
}
.popover-soft .popover-body { color: var(--bs-popover-body-color); }
/* Een popover met kop houdt dezelfde toon: geen grijs balkje uit de themabundel. */
.popover-soft .popover-header {
  background: transparent;
  border-bottom: 1px solid var(--bs-popover-border-color);
  color: var(--theme-text-color);
  font-weight: 700;
  font-size: .9rem;
}

/* ==========================================================================
   FOTOVIEWER — één foto schermvullend, doorbladeren met ‹ ›, toetsen of vegen.
   Gedrag: js/fotoviewer.js (window.Fotoviewer). Vervangt op termijn de twee losse
   kopieën `chat-lightbox` (css/chat.css) en `bj-lightbox` (bouwjournaal) — die staan er
   nog en migreren wanneer je die bestanden tóch aanraakt.

   BEWUST GEEN THEMA-TOKENS voor de achtergrond: een fotoviewer is in élk thema donker,
   net als de push-preview een lockscreen nabootst. De foto moet het enige zijn dat kleur
   heeft; een lichte overlay in light mode zou de randen van een screenshot laten wegvallen.
   ========================================================================== */
.fotoviewer {
  position: fixed;
  inset: 0;
  z-index: 1080;                    /* boven modals (1055) en gestapelde lagen (1075) */
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, .92);
  padding: calc(var(--sa-t, 0px)) calc(var(--sa-r, 0px)) calc(var(--sa-b, 0px)) calc(var(--sa-l, 0px));
}
.fotoviewer.is-open { display: flex; }
/* Pagina eronder vastzetten zolang de viewer open staat. */
html.fotoviewer-open, html.fotoviewer-open body { overflow: hidden; }

.fotoviewer-track { width: 100vw; height: 88vh; }
.fotoviewer-blad {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fotoviewer-blad img {
  max-width: 96vw;
  max-height: 100%;
  object-fit: contain;
  border-radius: .5rem;
}

.fotoviewer-knop,
.fotoviewer-nav {
  position: absolute;
  /* BOVEN de track. Het blad is een dag-paneel dat de VOLLE track vult (100vw x 88vh), dus
     zonder deze z-index ligt het over de knoppen heen: een klik op ‹ landt dan op het blad
     en de buitenklik-regel sluit de viewer in plaats van te bladeren. Gemeten met
     tools/browser-harness — een DOM-test ziet dit niet, die heeft geen layout. */
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  cursor: pointer;
  transition: background .15s ease;
}
.fotoviewer-knop:hover,
.fotoviewer-nav:hover { background: rgba(255, 255, 255, .28); }
.fotoviewer-knop { width: 2.5rem; height: 2.5rem; top: calc(1rem + var(--sa-t, 0px)); }
.fotoviewer-sluit { right: calc(1rem + var(--sa-r, 0px)); }
.fotoviewer-dl    { right: calc(4rem + var(--sa-r, 0px)); }
.fotoviewer-nav {
  width: 2.75rem;
  height: 2.75rem;
  top: 50%;
  transform: translateY(-50%);
}
.fotoviewer-vorige   { left:  calc(.75rem + var(--sa-l, 0px)); }
.fotoviewer-volgende { right: calc(.75rem + var(--sa-r, 0px)); }
.fotoviewer-teller {
  position: absolute;
  z-index: 2;
  pointer-events: none;          /* mag nooit een klik van de foto of een knop opvangen */
  top: calc(1.5rem + var(--sa-t, 0px));
  left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, .82);
  font-size: .85rem;
  font-weight: 600;
  letter-spacing: .02em;
}

/* ============================================================
   HELP-KNOP + HELP-DRAWER — het kennisbankartikel IN de app
   ------------------------------------------------------------
   Het vraagteken in de PAGINAKOP dat een heel kennisbankartikel opent,
   plus het paneel waarin dat artikel verschijnt. Niet te verwarren met
   `uitleg-knop`: die legt één veld uit in drie zinnen en is een popover.

   Vervangt de eenmalige .help-icon-btn in styles-sb.css.php, die een
   hardcoded #E73144 droeg — buiten het thema én buiten de merkkleur van
   de omgeving — en naar de OUDE Intercom-site kennisbank.4planning.nl
   wees in plaats van naar help.4planning.nl.

   WAAROM EEN PANEEL EN GEEN LINK NAAR BUITEN: een externe link laat de
   beheerder de app verlaten precies op het moment dat hij vastloopt.
   Op iOS opent dat Safari, en op Android laadt de WebView de externe
   site IN de app — dan zit je op een uitgelogde helpsite en kom je er
   alleen met de hardware-backknop weer uit. Een paneel schuift naast de
   pagina waar de vraag ontstond en sluiten brengt je exact terug.
   ============================================================ */

.help-knop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  margin-left: .6rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 50%;
  background: var(--bs-primary-soft);
  color: var(--brand-on-surface);
  vertical-align: middle;
  cursor: pointer;
  transition: var(--ui-veld-overgang);
}
.help-knop:hover,
.help-knop:focus-visible {
  background: color-mix(in srgb, var(--primary-color) 26%, var(--theme-color-card));
  color: var(--brand-on-surface);
}
.help-knop svg {
  display: block;
  width: 1rem;
  height: 1rem;
  margin: 0;
}

/* In de paginakop ligt de knop OP de merkkleur, dus daar kan een soft-tint van
   diezelfde merkkleur niet — die verdwijnt in de achtergrond. Zelfde behandeling
   als btn-header-action: doorschijnend wit-op-merk, met het contrast uit
   --primary-bg-text-color, zodat een lichte clubkleur ook klopt. */
.page-header .help-knop {
  background: rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .15);
  border-color: rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .4);
  color: var(--primary-bg-text-color, #fff);
}
.page-header .help-knop:hover,
.page-header .help-knop:focus-visible {
  background: rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .3);
  color: var(--primary-bg-text-color, #fff);
}

/* --- Het paneel ------------------------------------------------------------
   Bootstrap-offcanvas als basis: backdrop, Escape en focus-afhandeling zijn dan
   gratis en identiek aan de rest van de app. Alleen vorm en typografie zijn van
   ons. Drie zones: een rode kop, een lichte leeszone en een vaste voet. */
.help-drawer.offcanvas {
  width: min(30rem, 100%);
  background: var(--theme-color-card);
  color: var(--theme-text-color);
  /* GEEN border-left. Die stond er eerst wel (1px --theme-color-3) en dat gaf een
     lichtgrijze streep van boven tot onder pal náást de rode kop — als een wit randje
     dat er niet hoort. Op een kaart valt zo'n rand weg tegen de achtergrond, maar hier
     ligt hij tegen een verzadigd vlak en springt hij er juist uit. Een schaduw doet het
     werk dat de rand moest doen (het paneel van de pagina scheiden) zonder die lijn —
     zelfde keuze als bij toast-soft en dropdown-soft. */
  box-shadow: -12px 0 32px rgba(var(--custom-theme-box-shadow-rgb), .22);
}

/* De KOP is 4planning-rood, niet de merkkleur van de omgeving — net als
   page-header-4planning en pill4planning. Dat is een bewuste keuze: de
   kennisbank is van ONS, niet van de club, en dat verschil hoort zichtbaar te
   zijn zodra je uit de app-inhoud in de uitleg stapt. Rood vraagt altijd wit,
   dus de contrastvar wordt hier gepind in plaats van geërfd. */
.help-drawer .offcanvas-header {
  --primary-bg-text-color: #fff;
  --primary-bg-text-color-rgb: 255, 255, 255;
  /* Zelfde drie knoppen als page-header-4planning, andere waarden: het paneel is
     smal en hoog, dus de "4" mag kleiner en dichter bij de rand staan. */
  --ui-4pl-merk-hoogte: 400%;
  --ui-4pl-merk-rechts: -5rem;
  --ui-4pl-merk-sterkte: .12;
  position: relative;
  overflow: hidden;               /* hier WEL: er zit geen dropdown in deze kop */
  display: block;
  flex-shrink: 0;                 /* kop en voet staan vast, alleen de body scrollt */
  background: var(--ui-4planning);
  color: #fff;
  /* Native: de kop verft door tot de bovenrand, de INHOUD blijft binnen de rand. */
  padding: calc(1.15rem + var(--sa-t, 0px)) calc(1.15rem + var(--sa-r, 0px)) 1.15rem 1.15rem;
}
/* Het merkteken: exact dezelfde bron en dezelfde masker-mechaniek als
   page-header-4planning (zie de uitleg daar — mask i.p.v. background-image, en
   calc(100% - X) omdat mask-position de `right <lengte>`-vorm stil verwerpt). */
.help-drawer .offcanvas-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-color: #fff;
  opacity: var(--ui-4pl-merk-sterkte);
  -webkit-mask-image: url("../images/4planning-mobile-login-background.png");
          mask-image: url("../images/4planning-mobile-login-background.png");
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: calc(100% - var(--ui-4pl-merk-rechts)) center;
          mask-position: calc(100% - var(--ui-4pl-merk-rechts)) center;
  -webkit-mask-size: auto var(--ui-4pl-merk-hoogte);
          mask-size: auto var(--ui-4pl-merk-hoogte);
}
.help-drawer .offcanvas-header > * {
  position: relative;
  z-index: 1;
}

/* Bovenregel: het witte logo + "Kennisbank" links, de sluitknop rechts. */
.help-drawer-merk {
  display: flex;
  align-items: center;
  gap: .55rem;
  margin-bottom: .55rem;
}
.help-drawer-merk img {
  height: 1.05rem;
  width: auto;
  display: block;
}
.help-drawer-merk-label {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .75);
  /* Dun streepje vóór het label, zodat logo en woord niet aan elkaar plakken. */
  padding-left: .55rem;
  border-left: 1px solid rgba(255, 255, 255, .3);
}
.help-drawer .offcanvas-title {
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1.3;
  color: #fff;
  padding-right: 2.25rem;         /* ruimte voor de sluitknop rechtsboven */
}
/* De sluitknop hangt absoluut in de kop i.p.v. in de flexrij: dan blijft hij op
   dezelfde plek staan of de titel nu één of drie regels is. */
.help-drawer .btn-close {
  position: absolute;
  z-index: 2;
  top: calc(1.05rem + var(--sa-t, 0px));
  right: calc(1rem + var(--sa-r, 0px));
  padding: .5rem;
  opacity: .85;
}
.help-drawer .btn-close:hover { opacity: 1; }

/* --- De leeszone ---------------------------------------------------------- */
.help-drawer .offcanvas-body {
  padding: 1.25rem;
  padding-right: calc(1.25rem + var(--sa-r, 0px));
}

/* --- De voet --------------------------------------------------------------
   Dit is de ONTSNAPPING en hij staat er in ÁLLE standen, ook als het artikel
   goed laadt: bij een mislukte fetch is dit de enige weg naar de inhoud, en een
   paneel dat leeg blijft zonder uitweg is erger dan een gewone link. */
.help-drawer-voet {
  flex-shrink: 0;
  display: flex;
  gap: .6rem;
  border-top: 1px solid var(--theme-color-3);
  background: var(--theme-color-card);
  padding: .85rem 1rem;
  padding-right: calc(1rem + var(--sa-r, 0px));
  padding-bottom: calc(.85rem + var(--sa-b, 0px));
}
/* Twee gelijke helften. flex-basis 0 (en niet `flex: 1`) zodat de verdeling niet
   afhangt van de lengte van de labels: "Kennisbank" en "Sluiten" verschillen sterk,
   en met auto-basis zou de linkerhelft merkbaar breder worden dan de rechter. */
.help-drawer-voet > .btn {
  flex: 1 1 0;
  min-width: 0;
}
/* De knop draagt hetzelfde rood als de kop, zodat kop en voet het paneel
   visueel omsluiten en de herkomst van de inhoud op beide plekken hetzelfde
   merk is. Bewust GEEN btn-soft-primary: dat zou de merkkleur van de club zijn
   voor iets dat van ons is. */
.help-drawer-bronknop.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Krap, zodat "4planning kennisbank" als ÉÉN naam leest en niet als een logo
     met een los woord ernaast. */
  gap: .3rem;
  padding: .6rem 1rem;
  background: var(--ui-4planning);
  border: 1px solid var(--ui-4planning);
  color: #fff;
  font-weight: 600;
}
.help-drawer-bronknop.btn:hover,
.help-drawer-bronknop.btn:focus-visible {
  background: color-mix(in srgb, #000 12%, var(--ui-4planning));
  border-color: color-mix(in srgb, #000 12%, var(--ui-4planning));
  color: #fff;
}
.help-drawer-bronknop img {
  height: .95rem;
  width: auto;
  display: block;
}
.help-drawer-bronknop svg {
  width: .95rem;
  height: .95rem;
  opacity: .8;
  /* Het extern-icoon hoort NIET bij de naam — het zegt "dit opent buiten de app".
     Iets meer lucht ervoor houdt die twee signalen uit elkaar. */
  margin-left: .2rem;
}
/* Even hoog als de kennisbank-knop ernaast: die is een <a> met eigen padding, dus
   zonder dit staan er twee knoppen van verschillende hoogte naast elkaar. */
.help-drawer-sluit.btn {
  padding: .6rem 1rem;
  font-weight: 600;
}

/* --- Artikeltypografie ----------------------------------------------------
   Strak gescoped, zodat de HTML van de kennisbank niets van de app kan raken en
   de app-CSS niets van het artikel. */
.help-drawer-artikel { font-size: .92rem; line-height: 1.6; }
.help-drawer-artikel > :first-child { margin-top: 0; }
.help-drawer-artikel > :last-child { margin-bottom: 0; }
.help-drawer-artikel h2 {
  font-size: 1rem;
  font-weight: 700;
  margin: 1.6rem 0 .5rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--theme-color-3);
}
.help-drawer-artikel > h2:first-child { padding-top: 0; border-top: 0; margin-top: 0; }
.help-drawer-artikel h3 { font-size: .92rem; font-weight: 700; margin: 1.2rem 0 .4rem; }
.help-drawer-artikel p { margin-bottom: .8rem; }
.help-drawer-artikel ul,
.help-drawer-artikel ol { margin: 0 0 .9rem; padding-left: 1.15rem; }
.help-drawer-artikel li { margin-bottom: .4rem; }
.help-drawer-artikel li::marker { color: var(--theme-text-color-gray); font-weight: 600; }
.help-drawer-artikel a { color: var(--brand-on-surface); }
.help-drawer-artikel img {
  max-width: 100%;
  height: auto;
  border-radius: var(--ui-radius-sm);
  border: 1px solid var(--theme-color-3);
  margin: .3rem 0 1rem;
}
.help-drawer-artikel code {
  font-size: .85em;
  padding: .1rem .3rem;
  border-radius: .3rem;
  background: color-mix(in srgb, var(--theme-text-color) 8%, var(--theme-color-card));
  color: var(--theme-text-color);
}
.help-drawer-artikel blockquote {
  margin: 0 0 .9rem;
  padding: .7rem .9rem;
  border-left: 3px solid var(--primary-color);
  border-radius: 0 var(--ui-radius-sm) var(--ui-radius-sm) 0;
  background: color-mix(in srgb, var(--theme-text-color) 5%, var(--theme-color-card));
  font-size: .88rem;
}
/* Een tabel uit een artikel is onze breedte niet — laat hem in zijn eigen
   scroller staan, anders duwt hij het paneel buiten beeld (de overflow-regel
   uit CLAUDE.md). */
.help-drawer-tabelwrap { overflow-x: auto; margin-bottom: .9rem; }
.help-drawer-artikel table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.help-drawer-artikel th,
.help-drawer-artikel td {
  padding: .45rem .6rem;
  border-bottom: 1px solid var(--theme-color-3);
  text-align: left;
}
.help-drawer-artikel th { font-weight: 600; white-space: nowrap; }

/* Verwante artikelen onder het artikel. Zonder deze lijst is het paneel een
   doodlopende weg zodra het antwoord nét in een ander artikel staat — en dan
   is de enige uitweg de knop naar de kennisbank, dus alsnog de app uit. */
.help-drawer-verwant-kop {
  margin: 1.75rem 0 .5rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--theme-color-3);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--theme-text-color-gray);
}
.help-drawer [data-help-verwant-lijst] .data-list-row {
  padding-left: .75rem;
  padding-right: .75rem;
}

/* De Planny-bel staat op z-index 1045 — exact dezelfde laag als een Bootstrap-
   offcanvas — en komt later in de DOM, dus hij wint en zweeft over de voet van het
   paneel heen, precies over de Kennisbank-knop. Hem verbergen zolang het paneel
   open staat is de juiste oplossing en niet alleen de nettste: het paneel dekt de
   rechterkant af, dus die bel is daar toch onbereikbaar. Niet met display:none
   (dan springt hij hard terug bij het sluiten) maar met opacity, zodat hij mee
   uitfadet met het paneel. */
body.help-drawer-open .planny-fab {
  /* !important is hier NIET optioneel en ook geen luiheid: .planny-fab draait
     `animation: planny-fab-in … both`, en een animatie met fill-mode `both` houdt
     zijn eindwaarde vast — die wint volgens de cascade van elke gewone declaratie.
     Alleen een !important-declaratie staat bóven animaties. Zonder dit meet je een
     opacity van 1 terwijl de regel er wel degelijk staat (precies zo gemeten). */
  opacity: 0 !important;
  pointer-events: none;
  transition: opacity .2s ease;
}

/* Terug naar de keuzelijst. Staat in de rode KOP, onder de titel — dus wit-op-rood
   en niet de soft-familie: een tint van het merkrood op het merkrood zelf is geen
   contrast. Klein en ingetogen, want het is een navigatiestap en geen actie. */
.help-drawer-terug {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  margin: .5rem 0 0;
  padding: .2rem .65rem .2rem .4rem;
  border: 1px solid rgba(255, 255, 255, .35);
  border-radius: 50rem;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
.help-drawer-terug:hover,
.help-drawer-terug:focus-visible {
  background: rgba(255, 255, 255, .3);
  border-color: rgba(255, 255, 255, .55);
  color: #fff;
}
.help-drawer-terug svg { width: .95rem; height: .95rem; }
.help-drawer-terug[hidden] { display: none; }

.help-drawer [data-help-lijst-items] .data-list-row {
  padding-left: .75rem;
  padding-right: .75rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   KAART-MERKTEKEN — één groot, ingetogen icoon rechts in de achtergrond van een
   kaart, dat zegt WAT VOOR SOORT item dit is.

   Waarvoor: een lijst waarin verschillende soorten items door elkaar staan (de agenda:
   evenementen, trainingen, wedstrijden, bardiensten, afspraken). Een pill met het woord
   erop werkt ook, maar die concurreert met de echte statuspills op dezelfde kaart —
   "Training" naast "Afgemeld" zijn twee pills die twee heel verschillende dingen zeggen.
   Het merkteken zit in de ACHTERGROND en concurreert dus met niets; de pills blijven vrij
   voor wat er met JOU aan de hand is.

   WANNEER NIET: op een kaart met een foto (de foto ís al het beeld), en op een lijst waarin
   alle items van hetzelfde soort zijn — dan zegt het merkteken op elke kaart hetzelfde en is
   het puur decor.

   Het icoon mag over de rand hangen; `.agc` klipt zelf al (`overflow: hidden`). Op smalle
   schermen krimpt het mee, anders loopt het door de tekst heen.
   Toegankelijkheid: het icoon is `aria-hidden` — het is een herhaling van iets dat ook in
   tekst op de kaart staat, en een screenreader die "training" twee keer voorleest is erger
   dan een die het één keer doet. ══════════════════════════════════════════════════════ */
.kaart-merkteken {
  position: absolute;
  right: -1.25rem;
  top: 50%;
  transform: translateY(-50%);
  width: 7.5rem;
  height: 7.5rem;
  color: var(--primary-color);
  opacity: .14;
  pointer-events: none;
  z-index: 0;
}
.kaart-merkteken svg { width: 100%; height: 100%; stroke-width: 1.25; }

/* De inhoud moet erbovenop blijven: het merkteken staat op z-index 0, dus alles met een
   eigen positie wint vanzelf — behalve blokken die zelf géén stacking hebben. */
.kaart-merkteken ~ * { position: relative; z-index: 1; }

@media (max-width: 420px) {
  .kaart-merkteken { width: 5.75rem; height: 5.75rem; right: -1rem; }
}
