/* ==========================================================================
   UI-BASIS — componenten die in 4planning in styles-sb.css.php staan
   ==========================================================================

   Deze vier blokken horen inhoudelijk bij css/ui-stijl.css, maar zijn in
   4planning naar de hoofdstylesheet verhuisd omdat ze door GEDEELDE includes en
   centrale helpers gerenderd worden (top-nav, notify.js, status_badge()) en dus
   ook moeten werken op pagina's die de componentlaag niet laden.

   Hier zijn ze apart gehouden en NIET in ui-stijl.css geplakt, zodat dat bestand
   byte-identiek blijft aan de 4planning-versie en je het later kunt hersyncen.

   Laadvolgorde: css/styles.css -> ui-tokens.css -> ui-basis.css -> ui-stijl.css

   STATUS 2026-08-13: dit bestand laadt APP-BREED (includes/loggedin/header.php,
   samen met ui-tokens.css) — vooruitlopend op de rest van de componentlaag,
   specifiek omdat het BOTTOM-SHEET-blok hierin zit. Dat blok verandert het
   gedrag van ELKE modal onder 576px, app-breed (opt-out per modal met
   .modal-no-sheet). Dat is precies de bedoeling — een gecentreerde
   Bootstrap-modal positioneert zich t.o.v. de layout-viewport, die in de
   WebView hoger is dan het zichtbare gebied, waardoor de footer met de knoppen
   wegvalt. Sleepgedrag + scrollvergrendeling + toetsenbord-compensatie:
   js/modal-sheet.js + js/modal-viewport.js + js/modal-stack.js, alle drie
   sindsdien in includes/loggedin/libraries.php (ook app-breed).
   De rest van dit bestand (btn-soft-neutral, dropdown-soft, status-pill,
   count-chip, dagswipe, de AI-glow-motor) rijdt mee omdat het in hetzelfde
   bestand zit — dat is onschadelijk, want geen van die classes bestaat al
   ELDERS in de app (geverifieerd 2026-08-13: geen enkele pagina gebruikte deze
   namen al), dus er is niets om mee te botsen. `ui-stijl.css` zelf (pills,
   veldchrome, kaarten) blijft WEL opt-in — zie fase 1/2 in de skill.

   Bron: 4planning public_html/css/styles-sb.css.php, regels 31388-31611 en
   31685-32109 (overgenomen 2026-08-12).
   ========================================================================== */

/* ============================================================
   BTN-SOFT-NEUTRAL — app-breed, om dezelfde reden als de bottom-sheet hieronder
   (2026-08-10)

   De soft-knopfamilie hoort in css/ui-stijl.css, maar die laadt PER PAGINA — en
   75 van de 189 root-pagina's laden hem (nog) niet. Twee app-brede modules
   injecteren wél btn-soft-neutral op élke pagina: js/notify.js (de generieke
   confirmAction-dialoog) en js/gezin-agenda.js, allebei via libraries.php.
   Zonder deze regel is die Annuleren-knop daar onzichtbare tekst — een knop
   zonder achtergrond én zonder eigen kleur.

   Alleen DEZE ene variant staat hier, bewust niet de hele familie: de rest wordt
   uitsluitend in pagina-markup gebruikt en die pagina's laden ui-stijl.css. Zet
   er dus geen tweede kopie van btn-soft-success/-danger/… naast — zo ontstonden
   ooit de vier concurrerende safe-area-blokken. Waarden identiek aan ui-stijl.css;
   wijzig je ze daar, wijzig ze hier mee.
   ============================================================ */
.btn-soft-neutral {
--bs-btn-color: var(--theme-text-color);
--bs-btn-bg: var(--theme-color-4);
--bs-btn-border-color: rgba(var(--theme-text-color-rgb), .22);
--bs-btn-hover-color: var(--theme-text-color);
--bs-btn-hover-bg: var(--theme-color-3);
--bs-btn-hover-border-color: rgba(var(--theme-text-color-rgb), .38);
--bs-btn-active-color: var(--theme-text-color);
--bs-btn-active-bg: var(--theme-color-3);
--bs-btn-active-border-color: rgba(var(--theme-text-color-rgb), .38);
--bs-btn-active-shadow: none;
--bs-btn-disabled-color: var(--theme-text-color);
--bs-btn-disabled-bg: var(--theme-color-4);
--bs-btn-disabled-border-color: rgba(var(--theme-text-color-rgb), .22);
--bs-btn-focus-shadow-rgb: var(--theme-text-color-rgb);
font-weight: 600;
}

/* ============================================================
   BOTTOM-SHEET — modals op mobiel (< 576px)      (S9, 2026-08-01)

   Onder 576px is ELKE modal een bottom-sheet. Dat is geen stijlkeuze maar een
   structurele fix: een gecentreerde Bootstrap-modal wordt verticaal
   gepositioneerd t.o.v. de LAYOUT-viewport, die in de WebView hoger is dan het
   zichtbare gebied. Daarom viel de footer met de knoppen weg en bestaat
   js/modal-viewport.js (dat meet hoeveel er onder de vouw ligt en compenseert).
   Een sheet is verankerd aan de ONDERKANT: er ís geen "eronder" meer.

   UITZONDERINGEN
   - `modal-fullscreen*` op de .modal-dialog → blijft fullscreen.
   - `.modal-no-sheet` op de .modal → opt-out voor een modal die een eigen vaste
     hoogte nodig heeft (kaart, canvas, viewer-iframe). Gebruik 'm spaarzaam en
     zet erbij WAAROM.

   LET OP — de :not() hoort op de .modal-DIALOG, niet op de .modal. De klasse
   `modal-fullscreen-sm-down` staat namelijk op het dialoog. Stond de :not() op
   de modal (zoals in de eerste versie van dit blok), dan was de test altijd waar
   en vuurde de uitzondering NOOIT — fullscreen-modals kregen dan wél het
   sheet-uiterlijk maar geen sleepgedrag, want js/modal-sheet.js keek wél naar
   het dialoog. Die inconsistentie is precies hoe je 'm herkent.

   Dit is NIET de valkuil uit CLAUDE.md ("max-height + overflow:hidden op
   .modal-content knipt de footer af"). Die trap ontstaat als de inhoud groter is
   dan de cap zonder scroll-eigenaar. Hier is .modal-content een flex-KOLOM waarin
   alleen de body scrollt (min-height:0) en header/footer flex-shrink:0 zijn — de
   footer kan dus per definitie niet wegvallen.

   Gedrag (slepen om te sluiten + scrollvergrendeling): js/modal-sheet.js.
   ============================================================ */
@media (max-width: 575.98px) {
.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) {
position: fixed;
left: 0;
right: 0;
/* Tot de fysieke onderrand: de sheet-achtergrond dekt de home-indicator-zone.
   --app-kb (js/modal-viewport.js) tilt hem boven een open toetsenbord. */
bottom: var(--app-kb, 0px);
width: 100%;
max-width: 100%;
/* !important omdat Bootstrap-SPACING-UTILITIES zelf !important dragen. Twee modals hebben
   `class="modal-dialog mb-10"` (margin-bottom: 6rem) als oude "ruimte voor de footer"-truc;
   op een sheet die aan bottom:0 hangt duwt zo'n marge hem 96px omhoog en zweeft hij
   halverwege het scherm. De sheet maakt die truc overbodig, dus nul — en met !important
   breekt een volgende marge-utility op een modal-dialog het niet opnieuw stil.
   LET OP: schrijf hier nooit een sterretje gevolgd door een schuine streep (de
   utility-patronen dus voluit) — dat sluit dit commentaar voortijdig, en CSS slikt dan
   stilzwijgend de declaratie eronder in. Precies dat ging hier al een keer mis. */
margin: 0 !important;
/* De safe-area zit in de FOOTER, niet hieronder — anders ontstaat er een gat
   tussen de sheet en de schermrand. Overschrijft de generieke modal-dialog-
   padding hierboven (die is voor gecentreerde modals en heeft daar !important). */
padding-bottom: 0 !important;
/* Bootstrap-varianten neutraliseren. Zonder dit is de sheet KAPOT:
   .modal-dialog-centered zet `display:flex; align-items:center` +
   `min-height: calc(100% - …)`, .modal-dialog-scrollable zet
   `height: calc(100% - …)`. Allebei maken ze het dialoog schermvullend, waardoor
   de "sheet" halverwege het scherm zweeft met afgeronde hoeken in het niets.
   Dit is de meerderheid van de modals, niet een randgeval. */
display: block;
align-items: initial;
min-height: 0;
height: auto;
}

.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-content {
display: flex;
flex-direction: column;
/* Houd de bovenkant van een hoge sheet op zijn plek als het toetsenbord opent.
   Alleen `bottom: --app-kb` verplaatste voorheen de VOLLE 92dvh-sheet omhoog,
   waardoor vooral lange formulieren (zoals Snel afspraak inplannen) honderden
   pixels versprongen. --app-vvh is de werkelijk zichtbare viewporthoogte; door
   de sheet tegelijk te verkleinen blijft de header staan en wordt alleen de
   modal-body kleiner en scrollbaar. De 92dvh-cap behoudt het uiterlijk zonder
   toetsenbord, de 12rem-vloer houdt extreme landscape/keyboard-combinaties bruikbaar. */
max-height: clamp(12rem, calc(var(--app-vvh, 100dvh) - 4rem), 92dvh);
border-radius: 1.25rem 1.25rem 0 0;
border-bottom: 0;
/* overflow:hidden clipt de bovenhoeken; veilig omdat de footer flex-shrink:0 is. */
overflow: hidden;
/* Terugveren na een sleep die niet ver genoeg was (js/modal-sheet.js). Bewust op
   .modal-content en niet op .modal-dialog: die laatste draagt Bootstrap's eigen
   open/dicht-animatie. Twee lagen, zodat ze elkaars transform niet overschrijven. */
transition: transform .28s cubic-bezier(.32, .72, 0, 1);
}

/* Sleep-affordance. Het visuele signaal; het gedrag zit in js/modal-sheet.js. */
.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-content::before {
content: "";
flex: 0 0 auto;
width: 2.25rem;
height: .25rem;
margin: .625rem auto 0;
border-radius: 50rem;
background: rgba(var(--theme-text-color-rgb), .2);
}

/* Heeft de kop een merkkleurige achtergrond (.modal-header.bg-primary, zoals
   dashboard.php/klant.php/nieuws.php gebruiken)? Dan kleurt de strook ERBOVEN
   — waar de sleepgreep in staat — mee. Zonder dit bleef die strook wit (de
   basiskleur van .modal-content, zie css/styles.css), met een harde witte
   rand tussen de ronde tophoek van de sheet en de oranje kop erdirect onder.
   .modal-content zelf NIET herkleuren: die achtergrond schijnt ook door de
   modal-body/-footer heen (die hebben zelf geen eigen achtergrond), dus dat
   zou de hele sheet oranje maken i.p.v. alleen de greep-strook. In plaats
   daarvan wordt ::before zelf de volle breedte en de kleurdrager: twee
   achtergrondlagen — de sleepgreep (een klein lichte balkje, gepositioneerd
   via background-position/-size) BOVENOP een merkkleurig vlak. */
.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-content:has(> .modal-header.bg-primary)::before {
width: 100%;
height: 1rem;
margin: 0;
border-radius: 0;
background:
  linear-gradient(rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .55), rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .55)) center .375rem / 2.25rem .25rem no-repeat,
  var(--primary-color);
}

/* Bootstrap rondt de kop zelf ook nog af (border-top-left/right-radius via
   --bs-modal-inner-border-radius) — logisch zolang de kop het bovenste
   element is, maar dat is hij nu niet meer: de sleepgreep-strook hierboven
   is dat. Die eigen ronding van de kop bleef staan en liet in de hoeken een
   sliver van .modal-content's witte achtergrond door, precies waar de rechte
   onderrand van de strook op de ronde bovenrand van de kop botste. De kop
   hoeft zelf niet meer af te ronden; de strook erboven (en de overflow:hidden
   + border-radius van .modal-content zelf) doen dat al voor de hele sheet. */
.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-content:has(> .modal-header.bg-primary) > .modal-header.bg-primary {
border-radius: 0;
}

.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-header {
flex: 0 0 auto;
border-bottom: 0;
padding-bottom: .75rem;
}

.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-body {
flex: 1 1 auto;
min-height: 0;              /* zonder dit scrollt een flex-item niet */
overflow-y: auto;
/* Zonder contain scrollt de pagina ACHTER de sheet mee zodra de body op z'n eind
   is — dat leest als "de overlay is kapot". js/modal-sheet.js vergrendelt het
   document daarnaast ook echt; dit is de goedkope eerste verdedigingslinie. */
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}

/* Een <form> TUSSEN .modal-content en .modal-body is een veelgebruikt patroon (73
   modals in deze app): het formulier moet zowel de velden als de knoppen in de
   footer omvatten. Zo'n form is een gewoon blok en breekt daarmee de flex-keten —
   .modal-body is dan geen flex-item van .modal-content meer, dus de flex/min-height
   hierboven doen NIETS. De body groeit tot volle inhoudshoogte en overflow:hidden
   op .modal-content knipt de rest eraf: je kunt niet scrollen en de knoppen zijn
   onbereikbaar. Alleen zichtbaar bij een modal die hoger is dan 92dvh, wat lang
   niemand had — tot de medewerkerkiezer erbij kwam.
   Maak de tussenliggende wrapper daarom zelf óók een flex-kolom die de resthoogte
   vult; dan werken de regels hierboven weer zoals bedoeld. */
.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-content > form {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
}

.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-footer {
flex: 0 0 auto;
display: flex;
flex-wrap: wrap;
gap: .5rem;
/* De home-indicator/gesturebalk ligt ONDER de knoppen, binnen het sheet-oppervlak.
   Vandaar hier de safe-area en niet onder het dialoog. */
padding-bottom: calc(1rem + var(--sa-b));
}

/* Knoppen DELEN de rij gelijk (twee knoppen = 50/50). Rechtsonder naast elkaar in hun
   eigen breedte is een desktop-patroon: op een sheet levert dat twee smalle knoppen op
   aan de kant waar je duim niet is. Gelijke helften zijn allebei raakbaar en je hoeft
   niet te mikken.
   BEDOELD GEDRAG, niet een halve implementatie — de comment die hier stond beloofde
   "primaire actie op volle breedte" en dat heeft dit blok nooit gedaan (geverifieerd
   2026-08-12, bewust zo gelaten).
   Bij DRIE knoppen delen ze de rij in derden zolang ze passen; passen ze niet, dan zorgt
   `flex-wrap: wrap` op de footer hierboven ervoor dat de laatste op een eigen regel valt
   en daar de volle breedte pakt. Wil je één knop bewust vol, geef die dan `w-100` — niet
   deze regel aanpassen, hij raakt elke modal in de app. */
.modal:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) .modal-footer > .btn {
flex: 1 1 auto;
margin: 0;
}

/* Enter/exit: omhoog schuiven i.p.v. Bootstrap's val-van-boven. De curve is de
   iOS-sheet-curve; die timing is het verschil tussen "web-modal" en "app". */
.modal.fade:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) {
transform: translateY(100%);
transition: transform .32s cubic-bezier(.32, .72, 0, 1);
}
.modal.show:not(.modal-no-sheet) .modal-dialog:not([class*="modal-fullscreen"]) {
transform: translateY(0);
}

/* De .modal zelf is de touch-laag rond de sheet (scrim). Zonder containment ketent
   een veeg daar door naar het document → pagina scrollt mee, bovenaan zelfs
   pull-to-refresh (= herladen met een open modal, dus werk kwijt). */
.modal:not(.modal-no-sheet) {
overscroll-behavior: contain;
}

/* Scrim: iets lichter dan Bootstrap's .5 zwart, met een lichte blur. Een te zware
   scrim laat de app "dood" aanvoelen achter de sheet. */
.modal-backdrop.show {
opacity: 1;
background: rgba(0, 0, 0, .4);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
}
}

@media (prefers-reduced-motion: reduce) {
.modal.fade .modal-dialog { transition: none; }
.modal .modal-content { transition: none; }
}

/* ============================================================
   MODAL-CENTERED-DESKTOP — centreren dat de sheet niet sloopt

   Bootstraps `.modal-dialog-centered` centreert op ELK formaat, en onder 576px
   botst dat frontaal met de sheet hierboven: die hangt aan de onderrand, en
   `align-items:center` + `min-height` zetten hem halverwege het scherm met
   afgeronde hoeken in het niets. Daarom mag een dialoog die op desktop
   gecentreerd hoort te staan die klasse niet zomaar dragen.

   Deze variant doet hetzelfde werk, maar pas vanaf 576px — dus precies buiten
   het bereik van de sheet-regels. Gebruikt door js/dialogen.js (confirmDialog /
   promptDialog), waar de markup in JS ontstaat en er dus geen server-side
   "is dit mobiel"-keuze te maken valt. Ook bruikbaar in gewone markup.
   ============================================================ */
@media (min-width: 576px) {
.modal-dialog.modal-centered-desktop {
display: flex;
align-items: center;
min-height: calc(100% - var(--bs-modal-margin, 1.75rem) * 2);
}
.modal-dialog.modal-centered-desktop > .modal-content {
width: 100%;
}
}


/* ==========================================================================
   DROPDOWN-SOFT — het huis-dropdownmenu (was css/ui-stijl.css)
   ==========================================================================
   Staat hier en niet in ui-stijl.css omdat de top-nav een gedeelde include is:
   die zit op alle ~331 pagina's, ui-stijl.css maar op ~111. Eén definitie, één
   plek — voeg er geen tweede kopie aan ui-stijl.css toe.

   Het blok staat bewust ONDERAAN: de legacy-regels hierboven (.dropdown-item:hover,
   .dropdown-item-custom:hover) dragen !important, dus deze moeten later komen om
   bij gelijke kracht te winnen.

   --ui-radius is het enige token dat in ui-stijl.css leeft en niet globaal is;
   vandaar de fallback, zodat het menu ook zonder ui-stijl.css de juiste ronding heeft. */
/* ============================================================
   STATUS-PILL — zachte pill met gekleurde tekst (was css/ui-stijl.css, 2026-08-10)

   App-breed om dezelfde reden als .dropdown-soft en .btn-soft-neutral hieronder:
   ui-stijl.css laadt per pagina (114 van de 189 root-pagina's), maar deze 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 achtergrond niets: dan is het onzichtbare tekst.
   Kleurbetekenis staat in .claude/skills/ui-stijl/SKILL.md (statuskleuren-conventie).
   ============================================================ */
.status-pill {
display: inline-flex;
align-items: center;
gap: .4rem;
padding: .3rem .8rem;
border-radius: 50rem;
font-size: .8125rem;
font-weight: 600;
line-height: 1.2;
white-space: nowrap;
}
.status-pill svg { width: .85rem; height: .85rem; flex-shrink: 0; }
.status-pill-primary { background: var(--bs-primary-soft); color: var(--brand-on-surface); }
.status-pill-secondary { background: var(--bs-secondary-soft); color: var(--secondary-on-surface); }
.status-pill-success { background: var(--bs-success-soft); color: var(--bs-success); }
.status-pill-warning { background: var(--bs-warning-soft); color: var(--bs-warning); }
.status-pill-danger  { background: var(--bs-danger-soft);  color: var(--bs-danger); }
.status-pill-info    { background: var(--bs-info-soft);    color: var(--bs-info); }
.status-pill-purple  { background: var(--bs-purple-soft);  color: var(--bs-purple); }
.status-pill-indigo  { background: var(--bs-indigo-soft);  color: var(--bs-indigo); }
.status-pill-pink    { background: var(--bs-pink-soft);    color: var(--bs-pink); }
.status-pill-orange  { background: var(--bs-orange-soft);  color: var(--bs-orange); }
.status-pill-teal    { background: var(--bs-teal-soft);    color: var(--bs-teal); }
.status-pill-neutral { background: var(--theme-color-4);   color: var(--theme-text-color-gray); }
/* "4planning" — markeert alles wat ALLEEN wij kunnen zetten (functies aan/uit, het
   app-icoon, de mailserver, koppelingen die wij inrichten). Bewust NIET de merkkleur
   van de omgeving: dit label gaat over ons, niet over de klant. */
.status-pill-4planning { background: var(--ui-4planning); color: #fff; }
.status-pill-4planning img { height: .8rem; width: auto; display: block; }
/* Optioneel status-stipje vóór de tekst */
.status-pill-dot::before {
content: "";
width: .45rem;
height: .45rem;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}

/* --------------------------------------------------------------------------
 Alert-soft — inline melding/banner in soft-tint
 -------------------------------------------------------------------------- */
.alert-soft {
display: flex;
align-items: center;
gap: .875rem;
padding: 1rem 1.25rem;
border-radius: var(--ui-radius);
border: 1px solid transparent;
}
.alert-soft > svg { flex-shrink: 0; width: 1.25rem; height: 1.25rem; }
.alert-soft-title { font-weight: 700; color: var(--theme-text-color); margin-bottom: .1rem; }
.alert-soft-text { font-size: .9rem; color: var(--theme-text-color); }
.alert-soft-primary { background: var(--bs-primary-soft); border-color: rgba(var(--primary-color-rgb), .25); }
.alert-soft-primary > svg { color: var(--brand-on-surface); }
.alert-soft-info    { background: var(--bs-info-soft);    border-color: rgba(var(--bs-info-rgb), .25); }
.alert-soft-info > svg { color: var(--bs-info); }
.alert-soft-success { background: var(--bs-success-soft); border-color: rgba(var(--bs-success-rgb), .25); }
.alert-soft-success > svg { color: var(--bs-success); }
.alert-soft-warning { background: var(--bs-warning-soft); border-color: rgba(var(--bs-warning-rgb), .25); }
.alert-soft-warning > svg { color: var(--bs-warning); }
.alert-soft-danger  { background: var(--bs-danger-soft);  border-color: rgba(var(--bs-danger-rgb), .25); }
.alert-soft-danger > svg { color: var(--bs-danger); }
.alert-soft-secondary { background: var(--bs-secondary-soft); border-color: rgba(var(--bs-secondary-rgb), .25); }
.alert-soft-secondary > svg { color: var(--bs-secondary); }

/* ============================================================
   COUNT-CHIP — de teller (was css/ui-stijl.css, 2026-08-10)

   Hoort bij .status-pill hierboven en verhuist om dezelfde reden mee: een teller in
   een tab-label of een attentie-rij wordt ook gerenderd door AJAX-partials
   (fetch_payments.php) en op pagina's die de componentlaag niet laden.
   ONDERSCHEID: een STATUS is een status-pill, een AANTAL is een count-chip. Een
   getal in een gekleurde pill leest als een oordeel ("gevaar"), terwijl het gewoon
   een hoeveelheid is — daarom heeft de chip één neutrale merktint en géén
   semantische kleurvarianten.
   De .pill-tab.active-variant staat bewust nog in ui-stijl.css: pill-tabs zijn wél
   pagina-gebonden.
   ============================================================ */
.count-chip:empty { display: none; } /* teller pas tonen zodra JS hem vult */
.count-chip {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.35rem;
height: 1.35rem;
padding: 0 .35rem;
border-radius: 50rem;
font-size: .72rem;
font-weight: 700;
background: var(--bs-primary-soft);
color: var(--brand-on-surface);
}

/* CURSOR OP KLIKBARE DINGEN DIE GEEN LINK ZIJN (2026-08-10).
   Een <a> ZONDER href is voor de browser geen link en krijgt dus cursor: default;
   een <button> ook. Bootstrap zet zelf geen cursor op .dropdown-item, dus een
   menu-item dat een modal opent (`<a class="dropdown-item" data-bs-toggle="modal">`,
   57 stuks in deze app) voelde dood aan terwijl het item ernaast mét href wél een
   handje toonde — precies het verschil dat op ledenlijst opviel tussen
   "Gebruikers importeren" en "Gebruiker aanmaken".
   Op .dropdown-item generiek (niet alleen .dropdown-soft): een menu-item is per
   definitie aanklikbaar, en zo doen alle 66 dropdowns in de app mee. Disabled items
   houden hun eigen cursor doordat Bootstrap daar pointer-events: none zet.
   De tweede regel dekt hetzelfde patroon buiten dropdowns: een <a> zonder href die
   via een Bootstrap-toggle of onclick iets doet. */
.dropdown-item,
a:not([href])[data-bs-toggle],
a:not([href])[onclick] {
cursor: pointer;
}

.dropdown-menu.dropdown-soft {
border-radius: var(--ui-radius, 1rem);
border: 1px solid var(--theme-color-3);
background: var(--theme-color-card);
box-shadow: 0 .75rem 2rem rgba(var(--custom-theme-box-shadow-rgb), .15);
padding: .5rem;
min-width: 11rem;
}
.dropdown-soft .dropdown-item {
display: flex;
align-items: center;
gap: .6rem;
border-radius: .6rem;
padding: .55rem .75rem;
color: var(--theme-text-color);
font-size: .9rem;
font-weight: 500;
transition: background-color .15s ease, color .15s ease;
}
.dropdown-soft .dropdown-item svg { width: 1rem; height: 1rem; color: var(--theme-text-color-gray); }
/* !important nodig: de legacy .dropdown-item:hover hierboven zet zelf al !important op de
   tekstkleur (secondary-bg-text-color) — zonder gelijke kracht wint die altijd, en loopt
   het icoon (dat wél de bedoelde --brand-on-surface volgt) uit de pas met de tekst. */
.dropdown-soft .dropdown-item:hover,
.dropdown-soft .dropdown-item:focus,
.dropdown-soft .dropdown-item:active {
/* Twee mengingen, en beide zijn nodig — dit was var(--bs-primary-soft) en dat gaf in
   drie gevallen geen zichtbare hover:
   (1) MENG MET --theme-color-CARD, niet met --theme-color-BODY. De soft-vars zijn met
       body gemengd, maar dit menu ligt op card — en in dark mode is card (#282F3B)
       LICHTER dan body (#14191F), dus de hover werd donkerder dan het menu eromheen.
   (2) VOEG EEN NEUTRALE LIFT TOE. Een merkkleur-tint alleen kan geen contrast
       garanderen: een ontkleurde merkkleur (omgeving 139 draait op #ababab) geeft op
       wit nauwelijks verschil, en een dónkere merkkleur (#2C3E50) verdwijnt juist in
       een donkere kaart. --theme-text-color is per thema het tegenovergestelde van de
       kaart, dus 12% daarvan beweegt ALTIJD de goede kant op.
   Nagerekend over grijs/donkerblauw/rood/turquoise × light/dark: de zwakste combinatie
   (donkerblauw in dark) gaat van Δluminantie 0.019 naar 0.030. Het merk-aandeel blijft
   met 16% dicht bij de 15% van de huis-softformule, zodat de tint niet uit de toon valt
   naast pill-tabs en icon-tiles. */
background: color-mix(in srgb, var(--primary-color) 16%,
            color-mix(in srgb, var(--theme-text-color) 12%, var(--theme-color-card))) !important;
/* De globale a:hover (bovenin dit bestand) zet text-decoration: underline. Een
   dropdown-item dat een <a> is kreeg daardoor een streep door zijn label op hover;
   data-list-row heft dat om dezelfde reden al op. */
text-decoration: none !important;
color: var(--brand-on-surface) !important;
border-radius: .6rem;
}
.dropdown-soft .dropdown-item:hover svg,
.dropdown-soft .dropdown-item:focus svg,
.dropdown-soft .dropdown-item:active svg { color: var(--brand-on-surface) !important; }
.dropdown-soft .dropdown-item.text-danger svg { color: var(--bs-danger); }
.dropdown-soft .dropdown-item.text-danger:hover { background: var(--bs-danger-soft) !important; color: var(--bs-danger) !important; }
.dropdown-soft .dropdown-item.text-danger:hover svg { color: var(--bs-danger) !important; }
.dropdown-soft .dropdown-divider { border-color: var(--theme-color-3); margin: .4rem .25rem; }
/* Attentie-item: permanent getint menu-item voor een actie die alleen verschijnt
   als er iets te doen is (bv. "12 leden nog nooit ingelogd"). Default = warning;
   .dropdown-item-alert-info/-danger voor de andere semantische kleuren. */
.dropdown-soft .dropdown-item.dropdown-item-alert {
background: var(--bs-warning-soft);
color: var(--bs-warning);
margin-top: .15rem;
}
.dropdown-soft .dropdown-item.dropdown-item-alert svg { color: inherit; }
.dropdown-soft .dropdown-item.dropdown-item-alert:hover {
background: color-mix(in srgb, var(--bs-warning) 25%, var(--theme-color-body));
color: var(--bs-warning);
}
.dropdown-soft .dropdown-item.dropdown-item-alert:hover svg { color: inherit; }
.dropdown-soft .dropdown-item.dropdown-item-alert-info { background: var(--bs-info-soft); color: var(--bs-info); }
.dropdown-soft .dropdown-item.dropdown-item-alert-info:hover { background: color-mix(in srgb, var(--bs-info) 25%, var(--theme-color-body)); color: var(--bs-info); }
.dropdown-soft .dropdown-item.dropdown-item-alert-danger { background: var(--bs-danger-soft); color: var(--bs-danger); }
.dropdown-soft .dropdown-item.dropdown-item-alert-danger:hover { background: color-mix(in srgb, var(--bs-danger) 25%, var(--theme-color-body)); color: var(--bs-danger); }
/* Teller rechts in het item (count-chip erft de kleur van het attentie-item) */
.dropdown-soft .dropdown-item .count-chip { margin-left: auto; }
.dropdown-soft .dropdown-item.dropdown-item-alert .count-chip {
background: rgba(var(--theme-text-color-rgb), .08);
color: inherit;
}

/* ── Top-nav-menu's: dropdown-soft + een eigen kop ─────────────────────────
   De vier menu's in de top-nav (bel, talen, snel aanmelden, gebruiker) hebben
   iets wat een gewoon overflow-menu niet heeft: een titelkop. Die kop hing aan
   `bg-light` + `text-dark` in de markup en aan een hardgecodeerde #7c7c7c
   hierboven — in dark mode grijs-op-donkergrijs, en de talen-kop kreeg via
   bg-light een ANDERE tint dan de andere menu's. Nu één regel op tokens. */
.dropdown-soft .dropdown-header {
background-color: transparent !important;
color: var(--theme-text-color-gray) !important;
font-size: .7rem;
font-weight: 700;
letter-spacing: .02em;
padding: .5rem .75rem;
}
/* De gebruikerskop toont naam + e-mail en is dus geen labelkop maar een blok:
   normale tekstkleur, en een scheidslijn naar de items eronder. */
.dropdown-soft .dropdown-header.dropdown-header-user {
color: var(--theme-text-color) !important;
font-size: .875rem;
letter-spacing: normal;
border-bottom: 1px solid var(--theme-color-3);
margin-bottom: .35rem;
padding: .35rem .5rem .6rem;
}
/* Scrollende menu's (bel, talen) — de padding van dropdown-soft blijft staan,
   alleen de inhoud scrollt. */
.dropdown-soft.dropdown-scroll {
max-height: 450px;
overflow-y: auto;
}

/* Legacy-items in een dropdown-soft: het item is nu een flexrij met `gap`, dus de
   oude eigen marges van de icoon-wrapper en de vlag-afbeelding komen daar BOVENOP
   en geven dubbele ruimte. Nul zetten in plaats van de markup herschrijven — die
   klassen worden ook buiten de top-nav gebruikt (index.php, omgevingen.php,
   iframe-*.php) en daar moeten ze onaangeroerd blijven. */
.dropdown-soft .dropdown-item .dropdown-item-icon { margin-right: 0; line-height: 0; }
.dropdown-soft .dropdown-item .dropdown-item-icon svg { height: 1rem; width: 1rem; }

/* ── Topnavigatie: zoeken ──────────────────────────────────────────────────
   De top-nav is een gedeelde include; deze verfijning hoort daarom hier en niet
   in ui-stijl.css. Hij bouwt voort op de gedeelde `search-pill`-component,
   maar maakt de desktopvariant rustiger en compacter dan een formulier in de
   pagina-inhoud. */
@media (min-width: 992px) {
    .topnav-search {
        width: clamp(16rem, 24vw, 26rem);
        margin-bottom: 0;
    }
    .topnav-search .form-control {
        height: 2.45rem;
        padding-right: 3rem;
        background: var(--theme-color-4);
    }
    .topnav-search .form-control::placeholder { color: var(--theme-text-color-gray); }
    .topnav-search:focus-within .form-control { background: var(--ui-veld-bg-actief); }
    .topnav-search-submit {
        position: absolute;
        top: 50%;
        right: .32rem;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 1.9rem;
        height: 1.9rem;
        padding: 0;
        border: 0;
        border-radius: 50%;
        transform: translateY(-50%);
        background: transparent;
        color: var(--theme-text-color-gray);
        transition: background .16s ease, color .16s ease, transform .16s ease;
    }
    .topnav-search-submit:hover,
    .topnav-search-submit:focus-visible {
        background: rgba(var(--primary-color-rgb), .12);
        color: var(--primary-color);
        outline: 0;
    }
    .topnav-search-submit:active { transform: translateY(-50%) scale(.94); }
    .topnav-search-submit svg { width: 1rem; height: 1rem; }
}
.topnav-search-menu { min-width: min(19rem, calc(100vw - 1.5rem)); }
.dropdown-soft .dropdown-item-custom img { margin-right: 0; width: 1.15rem; height: auto; border-radius: 2px; }
/* Meldingsitems (notifications_panel.php) houden hun EIGEN opbouw: het type-icoon is
   daar betekenisdragend en op formaat, en de hele inhoud zit al in één .d-flex-kind.
   Zonder deze uitzondering krimpt de 1rem-svg-regel hierboven dat icoon en maakt het
   grijs — dat is een ander component, geen menu-item met een bijicoontje. */
.dropdown-soft .dropdown-item .page-header-icon svg { width: 24px; height: 24px; color: inherit; }
/* Een gecentreerd item ("Markeer alles gelezen") is nu een flexrij, dus text-align
   doet niets meer — centreren gaat via de hoofdas. */
.dropdown-soft .dropdown-item.text-center { justify-content: center; }

/* Het blok "APP-GEVOEL OP TELEFOON" (4planning styles-sb.css.php, kaartpadding +
   -ronding app-breed op elke .card/.container.px-4 onder 576px) is HIER BEWUST
   NIET meegenomen. Het zat in het brede regelbereik dat voor dit bestand is
   gekopieerd, maar is een aparte productbeslissing (elke kaart in de hele app
   herrondt en verliest zijmarge op mobiel) die niemand heeft gevraagd toen
   ui-basis.css app-breed ging — dat was puur voor de bottom-sheet. Wil je dit
   alsnog, haal het op uit 4planning's bron hierboven en behandel het als een
   eigen stap, niet als bijvangst van deze laag. */

/* ==========================================================================
   PAGINAVORM OP TELEFOON — body.kaartvorm-vol (<576px)
   --------------------------------------------------------------------------
   Instelbaar per omgeving: `omgevingen.mobiele_kaartvorm` ('vol' = standaard |
   'ingesprongen'), gelezen in loggedin/setup.php, als body-class gezet in
   loggedin/header.php, te kiezen op Instellingen › Uitstraling. Alles hieronder
   geldt UITSLUITEND onder 576px: op een breed scherm is een kaart die het hele
   venster vult geen kaart meer maar een balk, dus daar zijn beide standen gelijk.
   'ingesprongen' heeft geen eigen regels — dat IS de basisstijl.

   Wat 'vol' doet: de kaart houdt zijn vorm (rand, schaduw, ronding) maar
   verliest zijn zijmarge. Rand en ronding blijven bewust staan — haal je de
   zijranden weg, dan loopt de bovenrand halverwege de bocht dood; en een blok
   dat onderaan recht wordt afgesneden leest als afgekapte inhoud in plaats van
   als een afgerond geheel.

   DE VALKUIL DIE HIER IS OPGELOST (niet "vereenvoudigen"): de inset komt van de
   container-padding, maar een `.row` daarbinnen hangt met `margin: 0 -.75rem`
   aan die padding vast en de kolommen compenseren dat met `padding: 0 .75rem`.
   Zet je alleen de container op 0, dan steekt die row aan beide kanten .75rem
   BUITEN het scherm — sinds `overflow-x: clip` op html/body scrollt dat niet
   meer weg maar wordt het stil afgekapt. Daarom worden de directe row én zijn
   volle-breedte-kolommen hier meegenomen. Kolommen die op telefoon NAAST elkaar
   staan (col-6, col-4) houden hun padding juist wél, anders plakken die kaarten
   tegen elkaar; die krijgen dus een kleine inset in plaats van volle breedte.
   Alleen `main > .container-xl` — de paginakop en de footer houden hun inset,
   want een titel of een copyright tegen de schermrand leest als een fout.
   ========================================================================== */
@media (max-width: 575.98px) {
body.kaartvorm-vol {
/* Eén plek voor de twee maten. --kv-lucht is bewust gekoppeld aan de ronding:
   hoe ronder de hoek, hoe verder de inhoud ervan af moet blijven, anders lijkt
   de eerste regel tekst de bocht in te duiken. */
--kv-radius: 1.85rem;
--kv-lucht: 1.6rem;
}

body.kaartvorm-vol main > .container-xl.px-4,
body.kaartvorm-vol main > .container-fluid.px-4 {
padding-left: 0 !important;
padding-right: 0 !important;
}

body.kaartvorm-vol main > .container-xl.px-4 > .row,
body.kaartvorm-vol main > .container-fluid.px-4 > .row {
margin-left: 0;
margin-right: 0;
}

/* WELKE kolommen leveren hun gutter in? Alle kolommen die op telefoonformaat tóch
   de volle breedte zijn — en dat zijn er meer dan je denkt. Binnen deze media
   query (<576px) doet ALLEEN een kale `col-1`…`col-11` of `col-auto` een kolom
   smaller worden; elke breakpoint-variant (`col-md-6`, `col-xl-9`, `col-xxl-9`)
   geldt pas vanaf zijn eigen breedte en is hier dus gewoon volle breedte. Een
   eerdere versie testte op `col-12` in de class-lijst en sloeg daardoor precies
   de grootste pagina's over: `evenement.php` schrijft `col-xxl-9 col-xl-9`
   zonder `col-12`, en hield zo als enige pagina zijn inset. Daarom nu andersom
   geredeneerd: iedereen mee, behálve wat aantoonbaar naast iets anders staat.
   De :not()'s staan bewust los geketend in plaats van als lijst — `:not(a, b)`
   vraagt Safari 15+ / Chrome 88+, en dit moet ook op oudere toestellen kloppen. */
body.kaartvorm-vol main > .container-xl.px-4 > .row > [class*="col-"]:not(.col-1):not(.col-2):not(.col-3):not(.col-4):not(.col-5):not(.col-6):not(.col-7):not(.col-8):not(.col-9):not(.col-10):not(.col-11):not(.col-auto),
body.kaartvorm-vol main > .container-xl.px-4 > .row > .col:only-child,
body.kaartvorm-vol main > .container-fluid.px-4 > .row > [class*="col-"]:not(.col-1):not(.col-2):not(.col-3):not(.col-4):not(.col-5):not(.col-6):not(.col-7):not(.col-8):not(.col-9):not(.col-10):not(.col-11):not(.col-auto),
body.kaartvorm-vol main > .container-fluid.px-4 > .row > .col:only-child {
padding-left: 0;
padding-right: 0;
}

/* De ronding: via de Bootstrap-vars voor een gewone .card (die declareert ze op
   zichzelf, dus een var op een ouder wint daar niet) én rechtstreeks voor
   .card-soft, dat zijn radius uit --ui-radius-lg haalt. */
body.kaartvorm-vol main > .container-xl.px-4 .card,
body.kaartvorm-vol main > .container-fluid.px-4 .card {
--bs-card-border-radius: var(--kv-radius);
--bs-card-inner-border-radius: var(--kv-radius);
}

body.kaartvorm-vol main > .container-xl.px-4 .card.card-soft,
body.kaartvorm-vol main > .container-fluid.px-4 .card.card-soft {
border-radius: var(--kv-radius);
}

/* Lucht boven en onder IN de kaart. De zijkant blijft op 1rem — daar is de
   schermrand de begrenzing, niet de bocht. Kop en voet krijgen iets minder dan
   de body: die hebben zelf al een scheidslijn die ruimte suggereert. */
body.kaartvorm-vol main > .container-xl.px-4 .card > .card-body,
body.kaartvorm-vol main > .container-fluid.px-4 .card > .card-body {
padding-top: var(--kv-lucht);
padding-bottom: var(--kv-lucht);
}

body.kaartvorm-vol main > .container-xl.px-4 .card > .card-header,
body.kaartvorm-vol main > .container-xl.px-4 .card > .card-footer,
body.kaartvorm-vol main > .container-fluid.px-4 .card > .card-header,
body.kaartvorm-vol main > .container-fluid.px-4 .card > .card-footer {
padding-top: calc(var(--kv-lucht) - .35rem);
padding-bottom: calc(var(--kv-lucht) - .35rem);
}

/* Een kaart die EINDIGT in een tabel heeft onderaan geen card-body, dus de
   regel hierboven doet daar niets — en dan steekt de laatste rij bij hover met
   vierkante hoeken door de ronding heen. `overflow: hidden` op de kaart is hier
   NIET de oplossing (dat klipt dropdowns in de kaartkop); de onderste twee
   cellen krijgen de bocht dus zelf mee. */
body.kaartvorm-vol main .card > .table:last-child > tbody > tr:last-child > td:first-child {
border-bottom-left-radius: calc(var(--kv-radius) - 1px);
}

body.kaartvorm-vol main .card > .table:last-child > tbody > tr:last-child > td:last-child {
border-bottom-right-radius: calc(var(--kv-radius) - 1px);
}
}


/* ==========================================================================
   DAGSWIPE — vloeiend wisselen tussen dagen of tabs met een veegbeweging
   ==========================================================================
   Gedrag: js/dag-swipe.js (gedeelde module). Hier alleen de vorm.
   Overgenomen uit 4planning css/styles-sb.css.php regels 28424-28508.
   Demo: de sectie "Swipe" in ui-stijl.php.
   ========================================================================== */

/* ══════════════════════════════════════════════════════════════
   DAGSWIPE — het gestuurde vlak op wedstrijden.php
   Gedrag: js/dag-swipe.js (gedeelde module). Hier alleen de vorm.
   ══════════════════════════════════════════════════════════════ */

.dag-track {
  position: relative;
  overflow: hidden;
  /* Verticaal scrollen blijft van de BROWSER; de module claimt alleen de
     horizontale as en doet pas preventDefault ná de asbeslissing. De track is
     zelf een scroll-container (overflow), dus een geneste horizontale scroller
     (.mijn-wed-scroll) houdt zijn eigen touch-action en blijft gewoon scrollen. */
  touch-action: pan-y;
  transition: filter 90ms linear;
  /* Spiegelt de overhang van .mijn-wed-scroll (22px boven / 38px onder) plus
     ruimte voor de kaartschaduw opzij. Zonder deze marge knipt de overflow
     precies de schaduw van de hero-kaarten af. Netto layout-effect is nul:
     de negatieve marge haalt de padding er weer af. */
  --dag-pad-x: 14px;
  --dag-pad-t: 22px;
  --dag-pad-b: 38px;
  padding: var(--dag-pad-t) var(--dag-pad-x) var(--dag-pad-b);
  margin: calc(var(--dag-pad-t) * -1) calc(var(--dag-pad-x) * -1) calc(var(--dag-pad-b) * -1);
}

.dag-paneel { min-width: 0; }

/* `--kaal`: zonder de grote, asymmetrische overhang hierboven (die spiegelt specifiek de
   hero-scroller van wedstrijden.php). Maar de `overflow: hidden` van de track knipt óók hier
   de schaduw van de kaarten eraf — precies op de rand van het paneel, zichtbaar als een
   afgesneden schaduw links/rechts van de agendakaarten op home.php en agenda.php. Dus wél
   dezelfde truc, alleen bescheiden en symmetrisch: `--dag-schaduwruimte` padding rondom
   (daar mag de schaduw in staan) en dezelfde waarde als NEGATIEVE marge eraf, zodat het
   netto layout-effect nul is en de kaarten geen millimeter opschuiven. De waarde moet onder
   de horizontale padding van de omliggende .card-body blijven (1rem op telefoon), anders
   steekt de schaduwruimte buiten de kaart uit.
   LET OP: de zij-padding maakt het buurpaneel 14px zichtbaar zodra dat op exact 100% staat —
   daarom heeft .dag-rail hieronder een `gap` die grOter is dan deze waarde, en rekent
   DagSwipe.initVast die gap mee in de transform. Verklein er dus niet één zonder de ander. */
.dag-track--kaal {
  --dag-schaduwruimte: 14px;
  --dag-pad-x: var(--dag-schaduwruimte);
  --dag-pad-t: var(--dag-schaduwruimte);
  --dag-pad-b: var(--dag-schaduwruimte);
}

/* Een grote, instant scrollcorrectie tussen vaste tabs wordt kort verduisterd.
   brightness werkt op de al gerenderde pixels en kan daardoor geen witte
   compositing-flits veroorzaken zoals opacity dat in Chromium soms doet. */
.dag-track--maskeer { filter: brightness(.55); }
@media (prefers-reduced-motion: reduce) {
  .dag-track--maskeer { filter: none; transition: none; }
}

/* Compacte overhang voor inhoud zonder kaartschaduw, zoals het maandblad van
   de mini-kalender. Vier pixels houdt de focusring aan de rand zichtbaar. */
.dag-track--strak {
  --dag-pad-x: 4px;
  --dag-pad-t: 4px;
  --dag-pad-b: 4px;
}

/* Vaste-panelen-variant (DagSwipe.initVast — agenda.php's Aankomend/Afgelopen): in
   tegenstelling tot .dag-paneel worden deze kinderen NOOIT aangemaakt/verwijderd, ze
   staan allebei permanent naast elkaar in de rail en de JS verschuift enkel de transform.
   Bootstrap's eigen `.tab-pane { display:none }` (actief via de .active-klasse, die we
   laten staan voor de a11y-/CSS-haakjes van home.js) moet hier dus overruled worden —
   "onzichtbaar" is hier "buiten de track geschoven", niet display:none. */
.dag-rail {
  display: flex;
  /* NOOIT het default `stretch`: dat zou elk paneel (ook de niet-actieve) uitrekken tot de
     hoogte die de JS zet, waardoor `panelen[i].offsetHeight` niet meer de eigen inhoud meet
     maar de opgelegde hoogte teruggeeft — een lus die zichzelf in stand houdt op de hoogte
     van het EERSTE gemeten paneel. flex-start laat elk paneel zijn eigen hoogte houden; de
     JS (DagSwipe.initVast) zet daarna zelf `rail.style.height` op de hoogte van het actieve
     paneel — zonder dat bepaalt de rij anders de hoogte van het HOOGSTE kind, dus een lege
     tab (bv. Polls zonder polls) zou naast een volle tab (Aankomend) even hoog ogen. */
  align-items: flex-start;
  width: 100%;
  /* Tussenruimte tussen de panelen — GEEN cosmetiek: de track laat zijn schaduwruimte
     (--dag-schaduwruimte) opzij uitsteken, dus zonder gap piept het buurpaneel precies die
     breedte in beeld. De gap is daarom ruim het dubbele, zodat ook de schaduw van de eerste
     kaart van het buurpaneel buiten het zichtbare gebied blijft. DagSwipe.initVast leest
     deze waarde uit de computed style en telt hem op bij elke stap; hem hier weghalen
     betekent dus niet "geen ruimte" maar "buurpaneel in beeld". */
  gap: calc(var(--dag-schaduwruimte, 0px) * 2);
  /* Transition-timing zit uitsluitend in de JS (DagSwipe.initVast), net als bij .dag-paneel
     hierboven — niet hier ook nog eens definiëren, dat zijn twee plekken om uit de pas te
     laten lopen. */
}
.dag-rail > .tab-pane {
  flex: 0 0 100%;
  min-width: 0;
  display: block !important;
}

/* Een tabel die het hele swipepaneel horizontaal scrolt mag het veeggebaar op
   telefoon niet opsluiten. Binnen een dag-rail wrapt zo'n tabel daarom. */
@media (max-width: 575.98px) {
  .dag-rail .table-responsive { overflow-x: visible; }
  .dag-rail .table-responsive > table { table-layout: fixed; width: 100%; }
  .dag-rail .table-responsive > table th,
  .dag-rail .table-responsive > table td {
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* Het inkomende paneel ligt over het huidige heen; `top`/`left`/`right` zijn
   gelijk aan de padding van de track, want een absolute positie rekent vanaf
   de padding-box en zou anders 14px verschoven staan. */
.dag-paneel.is-inkomend {
  position: absolute;
  top: var(--dag-pad-t, 22px);
  left: var(--dag-pad-x, 14px);
  right: var(--dag-pad-x, 14px);
  will-change: transform;
}


/* ==========================================================================
   AI-SIGNAAL — de glow-motor
   ==========================================================================
   css/ui-stijl.css bevat de AI-patronen (`card-planny-glow`, `input-planny`,
   `planny-tegel`) maar NIET de motor eronder: de getypeerde hoek-variabele en de
   keyframes zitten bij 4planning in styles-sb.css.php. Zonder dit blok verwijst
   `animation: planny-glow-idle` naar een keyframes-regel die niet bestaat — de
   rand staat dan stil en `.btn-planny` heeft helemaal geen vorm. Vandaar dat het
   hier alsnog meekomt.

   NAAMGEVING — lees dit voordat je iets bouwt.
   "Planny" is de AI-assistent van 4planning; wij hebben die naam niet. De
   klassen heten hieronder nog zo omdat `ui-stijl.css` byte-identiek is gehouden
   aan het origineel, en `card-planny-glow` / `input-planny` DAAR staan — die kun
   je hier niet hernoemen zonder dat bestand te raken. Hernoemen is dus een
   bewuste personalisatiestap over beide bestanden tegelijk (`planny` -> onze
   eigen naam), niet iets wat je er half naast zet.

   HET SIGNAAL ZELF is een keuze die nog openstaat. Bij 4planning is het de
   mascotte-avatar: je hoeft niet te leren dat een bewegende gradient "AI"
   betekent, want er kijkt letterlijk iemand naar je terug. Wij hebben geen
   mascotte, dus draagt de glow alleen het signaal en zetten we er een
   Feather-icoon in. Wat NIET mag, in beide apps: een sparkle-glyph (✨) als
   AI-markering.

   Waar dit hier toe doet: de AI-vergadernotities op klant.php (Whisper +
   gpt-4o-mini via api/meeting_process.php). Dat is vandaag het enige AI-moment
   in de app en dus de plek waar dit patroon hoort te landen.

   Bron: 4planning css/styles-sb.css.php regels 29279-29285, 29386-29398 en
   30348-30461.
   ========================================================================== */

/* @property maakt --planny-glow-angle "typed" → de browser kan de hoek vloeiend
   (120fps) animeren. Zonder ondersteuning blijft de gradient simpelweg statisch. */
@property --planny-glow-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

@keyframes planny-glow-intro { from { --planny-glow-angle: -140deg; } to { --planny-glow-angle: 90deg; } }
@keyframes planny-glow-idle  { from { --planny-glow-angle: 90deg; }   to { --planny-glow-angle: 450deg; } }
/* Driftende aurora: elke blob (background-laag) krijgt een eigen begin/eind-positie;
   `alternate` laat het geheel organisch heen-en-weer vloeien (nooit een harde loop). */
@keyframes planny-aurora-flow {
  0%   { background-position:   0% 50%, 100%  0%,  50% 100%, 100% 50%,   0%  0%; }
  100% { background-position:  65%  0%,  15% 80%,   0%  15%,  35% 100%,  85% 60%; }
}
/* Subtiel "ademen": de halo zwelt licht op en neemt af. */
@keyframes planny-breathe {
  0%   { transform: scale(1);    }
  100% { transform: scale(1.06); }
}

/* --planny-ease staat bij 4planning in het chat-blok; hier lokaal, want de
   btn-planny-transitie is de enige gebruiker die meekwam. */
:root { --planny-ease: cubic-bezier(.22, 1, .36, 1); }

.btn-planny {
  --planny-btn-border: 2.5px;      /* dikte van de glow-rand */
  --planny-btn-idle: 6s;           /* rand-rotatie in rust */
  --planny-btn-fast: 2.4s;         /* rand-rotatie bij hover (sneller) */
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  padding: .62rem 1.25rem;
  border: 0;
  border-radius: 999px;
  background: var(--theme-color-card, #fff);
  color: var(--theme-text-color, #1f2937);
  font-weight: 700;
  font-size: .92rem;
  line-height: 1.15;
  cursor: pointer;
  isolation: isolate;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  transition: color .28s var(--planny-ease, ease), background-color .28s ease,
              transform .18s ease, box-shadow .28s ease;
}
.btn-planny > * { position: relative; z-index: 2; }

/* Roterende conic-gradient glow-rand (alleen de rand-dikte zichtbaar via mask). */
.btn-planny::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: var(--planny-btn-border);
  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 var(--planny-btn-idle) linear infinite;
  z-index: 1;
}

/* Aurora-vulling BINNEN de rand — onzichtbaar in rust, stroomt bij hover over
   de primary-kleur. De blobs staan bewust naar de randen zodat het midden
   (waar de tekst staat) rustig blijft → tekst leesbaar. */
.btn-planny::after {
  content: "";
  position: absolute;
  inset: var(--planny-btn-border);
  border-radius: inherit;
  background:
      radial-gradient(40% 62% at 22% 26%, rgba(255, 95,162,.95), transparent 70%),
      radial-gradient(44% 66% at 80% 30%, rgba(177, 92,255,.92), transparent 72%),
      radial-gradient(48% 68% at 50% 96%, rgba( 54,217,255,.92), transparent 72%),
      radial-gradient(42% 62% at 90% 78%, rgba(122,123,255,.90), transparent 72%),
      radial-gradient(38% 60% at 10% 82%, rgba(255,157, 92,.88), transparent 72%);
  background-size: 165% 165%;
  filter: saturate(1.25);
  opacity: 0;
  z-index: 1;
  pointer-events: none;
  transition: opacity .3s ease;
}

.btn-planny__avatar {
  width: 20px; height: 20px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
  background: #fff;
}

.btn-planny:hover,
.btn-planny:focus-visible {
  background-color: var(--primary-color);
  /* Bewust hardcoded wit, niet --primary-bg-text-color: de bewegende aurora-
     glow erachter is altijd donker/verzadigd genoeg om wit leesbaar te
     houden, ongeacht welke tekstkleur de primary-kleur zelf zou vragen. */
  color: #fff;
  transform: translateY(-1px);
  box-shadow: 0 10px 26px rgba(var(--primary-color-rgb), .28);
}
.btn-planny:hover::before,
.btn-planny:focus-visible::before { animation-duration: var(--planny-btn-fast); }
.btn-planny:hover::after,
.btn-planny:focus-visible::after {
  opacity: .62;
  animation: planny-aurora-flow 5s ease-in-out infinite alternate;
}
.btn-planny:active { transform: translateY(0); }
.btn-planny:focus-visible { outline: 3px solid rgba(var(--primary-color-rgb), .35); outline-offset: 3px; }

.btn-planny:disabled,
.btn-planny.is-disabled {
  cursor: default;
  opacity: .55;
  transform: none;
  box-shadow: none;
}
.btn-planny:disabled::after,
.btn-planny.is-disabled::after { opacity: 0; }

/* Volle breedte (bv. in smalle kolommen / op mobiel). */
.btn-planny.btn-planny--block { display: flex; width: 100%; }
/* Compacte variant (in drukke toolbars). */
.btn-planny.btn-planny--sm { padding: .42rem .9rem; font-size: .82rem; gap: .4rem; }
.btn-planny.btn-planny--sm .btn-planny__avatar { width: 17px; height: 17px; }

@media (prefers-reduced-motion: reduce) {
  .btn-planny::before { animation: none; }
  .btn-planny:hover::after,
  .btn-planny:focus-visible::after { animation: none; }
}

/* Het icoon in een AI-knop. Bij 4planning is dit een <img class="btn-planny__avatar">
   met de mascotte; wij zetten er een Feather-svg in, die op dezelfde maat moet
   uitkomen als die avatar — anders springt de knophoogte. */
.btn-planny > svg {
  width: 19px;
  height: 19px;
  flex-shrink: 0;
  position: relative;
  z-index: 2;
}


/* ==========================================================================
   HEADER-ACTIEKNOPPEN — .page-header-actions + .btn-header-action
   ==========================================================================
   Pill-knop op een merkkleurige paginakop. Stond wél in de ui-stijl-skill als
   "bestaand component" maar ontbrak hier: het blok zit bij 4planning in
   styles-sb.css.php (regels 22086-22120) en viel buiten de regelbereiken die
   voor dit bestand zijn overgenomen. Zonder deze regels valt een
   `btn btn-header-action` terug op de kale Bootstrap-.btn: witte tekst op een
   oranje kop met een onzichtbare rand. Alsnog overgenomen 2026-08-13.

   Markup:
     <div class="page-header-actions">
       <button class="btn btn-header-action"><i data-feather="…"></i><span>Label</span></button>
     </div>

   De kleuren komen uit --primary-bg-text-color (de contrastkleur ÓP de
   merkkleur), niet uit een vaste wit-waarde: zo klopt de knop ook als de
   merkkleur ooit lichter wordt.

   De legacy `.page-header .btn.lidaanmaken`-compat uit de 4planning-bron is
   NIET meegenomen — die class bestaat hier niet.
   ========================================================================== */
.page-header-actions {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
}

.btn-header-action.btn {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  border-radius: 999px;
  padding: .5rem 1.1rem;
  font-weight: 600;
  background: rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .15);
  border: 1px solid rgba(var(--primary-bg-text-color-rgb, 255, 255, 255), .4);
  color: var(--primary-bg-text-color, #fff);
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.btn-header-action.btn:hover,
.btn-header-action.btn:focus {
  background: var(--primary-bg-text-color, #fff);
  border-color: var(--primary-bg-text-color, #fff);
  color: var(--primary-color);
}

.btn-header-action.btn svg {
  width: 18px;
  height: 18px;
}

/* ==========================================================================
   GEEN ONDERSTREEP OP HOVER — KAART-/KNOP-/TAB-ACHTIGE <a>'s
   ==========================================================================
   Bootstrap zet in css/styles.css globaal `a:hover { text-decoration:
   underline; }`. Prima voor lopende tekstlinks (.link-muted, mailto/tel-
   links, de footer-achtige links in het accountmenu) — daar hoort een
   hover-onderstreep juist bij. Maar een <a> die eruitziet als kaart, knop,
   tab of pill (geen lopende tekst) hoort NOOIT te onderstrepen, en dat ging
   mis: elke component hieronder heeft zelf al `text-decoration: none;` op
   zijn KALE basisregel staan, maar niet nogmaals in zijn eigen `:hover`-
   regel. Bij gelijke of hogere specificiteit wint Bootstrap's `a:hover`
   dan alsnog, want die combineert een elementselector (a) MET :hover — dat
   weegt zwaarder dan een kale klasse zonder :hover op diezelfde eigenschap.
   Vandaar één centrale lijst i.p.v. het in elk los bestand te patchen.

   Nieuw component dat een <a> als kaart/knop/tab/pill gebruikt? Zet 'm hier
   bij, of herhaal `text-decoration: none;` gewoon zelf in de eigen
   :hover-regel — beide werken, maar hier verzamelen voorkomt dat het
   nogmaals wordt vergeten. */
.pill-tab:hover,
.pill-tab:focus,
.kv-terug:hover,
.kv-terug:focus,
.kv-kop-pill:hover,
.kv-kop-pill:focus,
.kv-ondernav-item:hover,
.kv-ondernav-item:focus,
.kv-menu-links > a:hover,
.kv-menu-links > a:focus,
.th-sort:hover,
.th-sort:focus,
.mini-seg-btn:hover,
.mini-seg-btn:focus,
.ui-dashboard-stat:hover,
.ui-dashboard-stat:focus,
.ui-aanvragen-stat:hover,
.ui-aanvragen-stat:focus,
.ui-bellijst-stats a:hover,
.ui-bellijst-stats a:focus {
  text-decoration: none;
}
