/* ==========================================================================
   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(--kaart-bg);
  border: 1px solid var(--kaart-rand);
  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-invisible — dezelfde vorm/opmaak als card-soft (kop, body-padding,
   icon-tile, empty-state — alle interne markup blijft gewoon werken), maar
   zonder eigen achtergrond/rand/schaduw. Voor een pagina die maar ÉÉN kaart
   heeft: het paneel eromheen (.kv-vel) is dan al de enige "doos" die je nodig
   hebt, en een kaart-in-een-kaart is dubbele chrome. Dit is BEWUST altijd
   onzichtbaar (mobiel én desktop) — anders dan .kv-blok.card, dat alleen op
   mobiel oplost en op desktop juist weer een rand/schaduw krijgt (zie
   css/voorbeeld/kop-en-vel.css, "HET BLOK: KAART OP DESKTOP, NIETS OP
   MOBIEL"). Gebruik card-soft nog steeds overal waar meerdere kaarten naast
   of onder elkaar staan; card-invisible alleen voor het ene omhulsel op een
   verder lege/enkelvoudige pagina. */
.card-invisible {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.card-invisible > .card-body { padding: 1.25rem 1.5rem; }
.card.card-soft > .card-header {
  background: transparent;
  border-bottom: 1px solid var(--kaart-rand);
  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: var(--rij-hover-bg);
  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: var(--rij-hover-bg);
  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; }

/* Sleepgreep voor een herordenbare data-list-row (SortableJS met handle-optie): staat
   BOVEN de .data-list-row-overlay (z-index 2, zie hierboven) zodat het gebaar er start
   i.p.v. de rij te "klikken", en touch-action:none voorkomt dat de browser het slepen
   zelf als scroll interpreteert. Zonder eigen greep is de HELE rij het sleepvlak, en dan
   wint op mobiel een verticale veeg altijd van scrollen — precies de bug die dit oplost. */
.data-list-drag-handle {
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  color: var(--theme-text-color-gray);
  cursor: grab;
  touch-action: none;
}
.data-list-drag-handle svg { width: 1.1rem; height: 1.1rem; }
.sortable-ghost .data-list-drag-handle,
.data-list-drag-handle:active { cursor: grabbing; }

/* Fase-rij (project-detail.php): meta (status-pill) en chevron horen als één blok mee
   te wippen, nooit los van elkaar — de standaard .data-list-row is flex-wrap:wrap voor
   een ZWARE meta (avatars + pill + knop) die zelf al breekt. Onze meta is één klein
   pilletje, dus zonder nowrap paste hij nét wel maar de chevron nét niet meer: die
   viel dan als enige naar een tweede regel, en landde daar linksonder (flex-start) los
   van de rest van de rij. data-list-title vangt de ruimte op met de bestaande ellipsis. */
.phase-item { flex-wrap: nowrap; }
/* Kalendericoon en datum in de deadline-regel: een los <i> zonder flex staat op de
   tekst-baseline (net iets lager dan het cijfer ernaast), vandaar de flex-uitlijning. */
.phase-deadline-label { display: inline-flex; align-items: center; gap: .3rem; }

/* --------------------------------------------------------------------------
   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. */
@container (max-width: 24rem) {
  .stat-strip { flex-direction: column; align-items: flex-start; }
  .stat-strip-item { border-left: 0; padding: 0; }
}
.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; }

/* --------------------------------------------------------------------------
   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 */

/* --------------------------------------------------------------------------
   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;
}
.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). De tint komt uit --rij-hover-bg, zodat elke hoverbare rij in
   de app er hetzelfde uitziet. */
.table-soft tbody tr:hover td {
  background-color: var(--rij-hover-bg);
  box-shadow: inset 0 0 0 9999px var(--rij-hover-bg);
}
/* 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; }

/* Systeem-avatar: het We4media-beeldmerk waar géén persoon achter een regel zit
   (automatisch vastgelegd, of de gebruiker is verwijderd). Het logo heeft een
   transparante achtergrond en zou zonder eigen vlak "zwevend" in de rij hangen,
   dus krijgt hij hetzelfde ronde vlak als een echte avatar — met wat lucht
   eromheen, want een beeldmerk tot de rand loopt leest als een afgeknipte foto. */
.avatar-systeem .avatar-img {
  background: var(--theme-color-body);
  border: 1px solid var(--theme-color-3);
  padding: .2rem;
  object-fit: contain;
}

/* De affordance hoort NIET aan .table-soft te hangen: een pagina met een gewone
   .table krijgt anders wel het klikgedrag maar geen enkele hint. Zelfde tint als
   elke andere rij-hover (--rij-hover-bg); de box-shadow erbij omdat Bootstrap 5.3
   celtinten via een inset-shadow tekent en een kale background-color dus wordt
   overschilderd. */
tr.table-row-clickable { cursor: pointer; }
tr.table-row-clickable:hover > * {
  background-color: var(--rij-hover-bg);
  box-shadow: inset 0 0 0 9999px var(--rij-hover-bg);
}
/* De hover is een RIJ-signaal, geen link-signaal: een link in de rij mag dus niet
   apart oplichten als je hem net wél raakt — dat leest als twee verschillende
   hovers in dezelfde rij. Kleur van links en tekst blijft daarom staan; alleen
   het rijvlak verandert. */
tr.table-row-clickable:hover a { color: inherit; }

/* --------------------------------------------------------------------------
   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); }

/* --------------------------------------------------------------------------
   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); }

/* De tekstkolom is het enige krimpbare item in de rij; zonder min-width:0 duwt een
   lange melding zonder spaties de sluitknop uit de toast. */
.toast-soft > .flex-grow-1 { min-width: 0; }
.toast-soft-text { overflow-wrap: anywhere; }

/* --- Zwevende variant: de toast zoals showNotification() hem plaatst ---------
   De galerijdemo hierboven is een STATISCH kaartje in de pagina; dit is dezelfde
   toast in de .toast-wrapper (de vaste stapel rechtsboven, linksonder op mobiel,
   gerenderd door includes/notification.php). De wrapper staat op
   `pointer-events: none` zodat hij de pagina eronder niet blokkeert — de toast
   zelf moet dus expliciet klikbaar terug. */
.toast-soft-zweeft {
  pointer-events: auto;
  position: relative;
  overflow: hidden;                 /* houdt de voortgangsbalk binnen de radius */
  width: 350px;                     /* zelfde breedte als de server-flash ernaast */
  max-width: 100%;
  align-items: flex-start;          /* met knoppen eronder is centreren scheef */
}
.toast-soft-zweeft .btn-close {
  flex: 0 0 auto;
  padding: .25rem;
  opacity: .5;
}
.toast-soft-zweeft .btn-close:hover { opacity: 1; }

/* Knoppenrij in een toast (o.a. de inkomende-oproepmelding: "Klant openen"). */
.toast-soft-acties {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .625rem;
}
.toast-soft-acties .btn { flex: 1 1 auto; }

/* Aflooptijd zichtbaar maken. Dezelfde rol als de balk op de server-flash-toast
   (.notification .progress-bar in styles.css), hier in de merkkleur. */
.toast-soft-progress {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  width: 100%;
  background: var(--primary-color);
  opacity: .5;
  animation-name: toastSoftAflopen;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}
@keyframes toastSoftAflopen { to { width: 0%; } }
@media (prefers-reduced-motion: reduce) {
  .toast-soft-progress { animation: none; }
}

/* --------------------------------------------------------------------------
   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);
}

/* --------------------------------------------------------------------------
   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. */
.card-inset-blok { display: block; }

/* --------------------------------------------------------------------------
   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); }
.link-muted:has(> svg) { display: inline-flex; align-items: center; gap: .35rem; }

/* 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. */
.datumtijd-veld-sm .datumtijd-trigger,
.select-veld-sm .select-trigger {
  min-height: calc(1.5em + .5rem + 2px);
  padding: .25rem .65rem;
  font-size: .875rem;
}

/* Icoon-only trigger — voor een compacte werkbalk waar de gekozen datum al ELDERS
   staat. Voorbeeld: de dagbladeraar op het dashboard (< kalender >), waar de datum in
   de kaarttitel staat en een tweede keer in de knop alleen ruimte kost.
   Bestaat omdat daar eerst een native <input type="date"> van 1×1 pixel stond met een
   `showPicker()`-knop ernaast: dat opende juist wél de OS-picker die dit component
   overal vervangt. Gebruik dit ALLEEN als de datum elders zichtbaar is — een knop met
   alleen een kalendericoontje vertelt niet wát er gekozen is. */
.datumtijd-veld-icoon .datumtijd-trigger {
  min-width: 2rem;
  padding: .25rem .45rem;
  justify-content: center;
}
.datumtijd-veld-icoon .datumtijd-trigger [data-dtk-label],
.datumtijd-veld-icoon .datumtijd-caret { display: none; }
/* 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(--rij-hover-bg); }
/* 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;
}
