/**
 * Poolster UI-systeem — Laag 2b: de UI-elementen zelf.
 *
 * Alles wat de stijlgids als "element" toont staat hier, één keer, en wordt
 * door elke pagina gedeeld. Herkomst: de <style>-blok van de vroegere
 * styleguide/index.html (2026-07-26) — dat bestand droeg zowel de elementen
 * als zijn eigen paginalayout in één blok van ~300 regels. Bij de opsplitsing
 * in vier pagina's is dat gescheiden:
 *
 *   tokens.css      Laag 1 — waarden
 *   base.css        Laag 2 — reset, typografie, tooltip, info-knop, drawer, side-nav
 *   components.css  Laag 2b — DIT BESTAND: de UI-elementen (button, badge, veld, kaart, ...)
 *   styleguide.css  de layout van de stijlgids-pagina's zelf, geen product-UI
 *   components.js   Laag 3 — <pw-*>-componenten
 *
 * Waarom "2b" en niet meteen Laag 3: deze elementen zijn nog CSS-klassen, geen
 * <pw-*>-componenten. Ze worden gradueel omgezet (Owner-beslissing 2026-07-26 —
 * eerst opsplitsen, dan componentiseren). Zolang dat loopt is dit de enige
 * plek waar hun opmaak staat, en dat is precies wat R-001 vraagt.
 *
 * Verbruikt uitsluitend tokens uit tokens.css. Rauwe kleurwaarden zijn hier
 * niet toegestaan; #fff is de enige uitzondering (staat zo ook in de
 * Stylelint-config, als ignoreValue).
 */

/* ===== buttons ===== */
.btn{font-family:var(--font-body); font-size:14px; font-weight:600; border-radius:var(--radius-sm); padding:10px 18px; border:1px solid transparent; cursor:pointer; display:inline-flex; align-items:center; gap:8px; transition:.15s;}
.btn-primary{background:var(--color-primary); color:#fff;}
.btn-primary:hover{background:var(--color-primary-hover);}
.btn-secondary{background:#fff; color:var(--color-primary); border-color:var(--color-primary);}
.btn-secondary:hover{background:var(--color-surface-alt);}
.btn-tertiary{background:transparent; color:var(--color-primary);}
.btn-tertiary:hover{background:var(--color-surface-alt);}
.btn-danger{background:var(--color-danger); color:#fff;}
.btn-danger:hover{background:var(--color-danger-hover);}
.btn-icon{width:38px; height:38px; padding:0; justify-content:center; background:#fff; border:1px solid var(--color-border); color:var(--color-primary); border-radius:var(--radius-full);}
.btn-icon:hover{background:var(--color-surface-alt);}
.btn[disabled]{opacity:.4; cursor:not-allowed;}
/* Subtiele "bezig"-bevestiging voor een icoon-only actieknop (bv. Ververs,
   → Els boeken.js) — <pw-data-grid> had dit al als eigen, shadow-DOM-lokale
   kopie; hier als gedeelde, algemene regel zodat elke light-DOM-pagina 'm
   ook kan gebruiken zonder te herhalen. */
@keyframes pw-spin{to{transform:rotate(360deg);}}
.btn-icon.is-loading{pointer-events:none; opacity:.7;}
.btn-icon.is-loading pw-icon{animation:pw-spin .8s linear infinite; display:inline-flex;}
.btn[aria-busy="true"]{opacity:.75; cursor:wait;}

/* ===== badges & context-chip — → R-004 (onderwerp in tekst, status in kleur, detail in hover) ===== */
.badge{display:inline-flex; align-items:center; gap:6px; font-size:12px; font-weight:700; padding:4px 10px; border-radius:var(--radius-full);}
.badge-neutral{background:var(--color-surface-alt); color:var(--color-text-muted);}
.badge-info{background:var(--color-info-bg); color:var(--color-info);}
.badge-success{background:var(--color-success-bg); color:var(--color-success);}
.badge-warning{background:var(--color-warning-bg); color:var(--color-warning);}
.badge-danger{background:var(--color-danger-bg); color:var(--color-danger);}
.badge-count{background:var(--color-accent); color:var(--color-accent-contrast); min-width:20px; height:20px; justify-content:center; padding:0 6px; border-radius:var(--radius-full); font-size:11px;}
.dot{width:7px; height:7px; border-radius:50%; background:currentColor;}
.chip-scope{display:inline-flex; align-items:center; gap:5px; box-sizing:border-box; min-width:0; max-width:100%; padding:4px 9px; border-radius:var(--radius-full); background:var(--color-surface-alt); color:var(--color-text-muted); border:1px solid var(--color-border); font-size:12px; font-weight:700; white-space:nowrap; overflow:hidden;}
/* Zelfde reden als .field[hidden] hierboven: author-CSS (display:inline-flex)
   wint altijd van de UA-stylesheet's `[hidden]{display:none}`, dus zonder
   deze regel bleef een verborgen pill zichtbaar (leeg, zonder tekst) i.p.v.
   weg te vallen. */
.chip-scope[hidden]{display:none;}
.chip-scope .dot{width:6px; height:6px; border-radius:50%; background:var(--color-accent); flex:none;}
.chip-scope > :not(.dot){min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.chip-scope.system{background:transparent; border-style:dashed;}

/* ===== paneel-sidebar — verticale tekstlijst, single-select, top-level secties binnen één scherm ===== */
.side-nav{flex:0 0 168px; display:flex; flex-direction:column; gap:var(--sp-1);}
.side-nav-link{display:flex; align-items:center; box-sizing:border-box; min-width:0; height:calc(var(--sp-6) + var(--sp-2)); padding:0 14px; border-radius:var(--radius-sm); border:0; border-left:3px solid transparent; background:none; color:var(--color-text-muted); font-family:inherit; font-size:13px; font-weight:600; line-height:1; text-align:left; white-space:nowrap; overflow:hidden; cursor:pointer;}
.side-nav-link:hover{background:var(--color-surface-alt); color:var(--color-text);}
.side-nav-link.active{background:var(--color-info-bg); color:var(--color-primary); border-left-color:var(--color-primary);}

/* Op mobiel is een verticale kolom van vier losstaande regels de duurste
   plek op het scherm om vier gelijkwaardige secties te tonen (elk een volle
   breedte, met scrollen tot gevolg — zichtbaar in de admin-app's Systeem-
   scherm, waar de consumer's eigen `.admin-shell{display:block}`-breekpunt
   de kolom al had losgemaakt van de contentkolom, maar de kolom zelf bleef
   verticaal). .side-nav is zelf verantwoordelijk voor zijn eigen
   layoutrichting, ongeacht wat de omringende pagina op mobiel doet — vandaar
   dit eigen breekpunt i.p.v. dat aan elke consumer over te laten. */
@media(max-width:640px){
  .side-nav{flex:0 0 auto; flex-direction:row; flex-wrap:wrap;}
  .side-nav-link{flex:0 0 auto; height:calc(var(--sp-5) + var(--sp-1));}
}

/* ===== formuliervelden ===== */
.field{position:relative; display:flex; flex-direction:column; gap:6px;}
/* Author-CSS wint altijd van de UA-stylesheet se `[hidden]{display:none}`,
   ongeacht specificiteit — dus de kale `hidden`-attribuut op een .field
   rendert zonder deze regel als flex, niet als none. Consumers liepen hier
   al meermaals tegenaan (Els boeken.js/admin.css, telkens lokaal opgelost
   met een eigen compound-selector); <pw-book-selector> se editie/versie-
   velden (2026-08-18, `ask`-attribuut) zijn de eerste die dit BINNEN de
   library zelf nodig hebben, dus hier gefixt i.p.v. weer een consumer-
   workaround. */
.field[hidden]{display:none;}
.field label{font-size:12.5px; font-weight:700; display:flex; align-items:center; gap:6px; color:var(--color-text);}
.field input[type=text],.field input[type=date],.field input[type=search],.field input[type=password],.field input[type=url],.field input[type=number],.field input[type=email],.field input[type=tel],.field select{border:1px solid var(--color-border); border-radius:var(--radius-sm); padding:10px 12px; font-size:14px; font-family:var(--font-body); background:#fff; color:var(--color-text); width:100%;}
.field input:focus,.field select:focus{outline:2px solid var(--color-accent); outline-offset:1px; border-color:var(--color-primary);}
/* Korte, ondergeschikte toelichting bij een veld — bestond al lokaal
   (.auth-card .field-hint) maar niet als algemene regel; hier gepromoot
   zodat elke pagina hem zonder herhaling kan gebruiken. */
.field-hint{font-size:12px; color:var(--color-text-muted); margin:6px 0 0;}
/* Zelfde als .field-hint, maar voor een onomkeerbaarheids-waarschuwing
   ("Dit kan niet ongedaan worden gemaakt.") — hergebruikt bewust exact
   .alert-danger's kleurtokens (2026-08-20, Rik: "smaakvol rode achtergrond,
   zelfde kleur als alert feedback") i.p.v. een nieuwe rode tint te verzinnen. */
.field-hint-danger{font-size:12.5px; font-weight:600; color:var(--color-danger-text); background:var(--color-danger-bg); border:1px solid var(--color-danger-border); border-radius:var(--radius-sm); padding:8px 10px; margin:10px 0 0;}
.check-row{display:flex; align-items:center; gap:8px; font-size:14px;}
.switch{width:38px; height:22px; border-radius:var(--radius-full); background:var(--color-border); position:relative; cursor:pointer; display:inline-block; flex:none;}
.switch::after{content:""; position:absolute; width:18px; height:18px; border-radius:50%; background:#fff; top:2px; left:2px; box-shadow:var(--shadow-sm); transition:.15s;}
.switch.on{background:var(--color-primary);}
.switch.on::after{left:18px;}
.switch:focus-visible{outline:2px solid var(--color-accent); outline-offset:3px;}
.switch[aria-disabled=true]{cursor:not-allowed; opacity:.55;}
.field-input-wrap{position:relative; display:flex; align-items:center;}
.field-input-wrap input{width:100%;}
.field-clearable input[type=text]{padding-right:34px;}
.field-clear-btn{position:absolute; right:6px; top:50%; transform:translateY(-50%); width:24px; height:24px; border:none; background:none; color:var(--color-text-muted); cursor:pointer; display:flex; align-items:center; justify-content:center; opacity:0; transition:.15s; border-radius:var(--radius-full);}
.field-clear-btn:hover{color:var(--color-text); background:var(--color-surface-alt);}
.pw-toggle{position:absolute; right:6px; top:50%; transform:translateY(-50%); width:24px; height:24px; border:none; background:none; color:var(--color-text-muted); cursor:pointer; display:flex; align-items:center; justify-content:center; border-radius:var(--radius-full);}
.pw-toggle:hover{color:var(--color-text); background:var(--color-surface-alt);}
.field-input-wrap input[type=password]{padding-right:34px;}
.field-search-icon{position:absolute; left:12px; top:50%; transform:translateY(-50%); color:var(--color-text-muted); pointer-events:none; display:flex;}
.field-search input[type=text]{padding-left:34px;}

/* Custom select — vervangt native <select>: daarvan is de hover-state niet consistent te stylen over browsers heen */
.select-custom{position:relative;}
.select-trigger{width:100%; display:flex; align-items:center; justify-content:space-between; gap:8px; border:1px solid var(--color-border); border-radius:var(--radius-sm); padding:10px 12px; font-size:14px; font-family:var(--font-body); background:#fff; color:var(--color-text); cursor:pointer;}
.select-trigger:hover{border-color:var(--color-primary);}
.select-trigger:focus-visible,.select-opt:focus-visible{outline:2px solid var(--color-accent); outline-offset:2px;}
.select-trigger:disabled{cursor:not-allowed; opacity:.55;}
.select-trigger .icon-sm{color:var(--color-text-muted);}
/* position/top/left/width komen sinds 2026-08-18 NIET meer uit deze regel —
   pw-select.js zet die inline, want .select-menu wordt bij het openen naar
   document.body verplaatst (portal) en daar met position:fixed geplaatst.
   Reden: een <pw-select> die in een overflow:hidden-voorouder zit (bv.
   <pw-data-grid>'s .data-grid, of elke toekomstige zo'n container) knipte
   het menu anders af, ongeacht z-index — position:absolute/fixed voorkomt
   dat niet, enkel het daadwerkelijk uit die voorouder halen doet dat. Was al
   meermaals herhaald zonder ooit de eigenlijke oorzaak op te lossen (zie
   git-geschiedenis van deze regel en els docs/CHANGELOG.md v1.5). z-index
   160: hoger dan .modal-backdrop (150) — als portal-kind van <body> is het
   nu een echte sibling van een open modal, en moet er dus boven kunnen
   verschijnen (bv. een <pw-select> binnen een <pw-edit-dialog>-formulier). */
/* transition met EXPLICIETE eigenschapsnamen, niet de kale ".15s"-shorthand
   (= transition-property:all): #reposition() (pw-select.js) zet top/left/
   width nu ook via inline style bij elke open/scroll/resize, en "all" zou
   ook DIE properties meeanimeren — een 150ms trage, zichtbaar verspringende
   dropdown i.p.v. een instant volgende, en (ontdekt tijdens het testen van
   deze fix) een computed style die bij een synchrone lezing vlak na het
   zetten nog de oude waarde toont. position zelf is niet animeerbaar en was
   dus nooit het probleem. */
.select-menu{position:fixed; top:0; left:0; background:#fff; border:1px solid var(--color-border); border-radius:var(--radius-sm); box-shadow:var(--shadow-md); opacity:0; visibility:hidden; transform:translateY(-4px); transition:opacity .15s, visibility .15s, transform .15s; z-index:160; overflow:hidden;}
.select-menu.open{opacity:1; visibility:visible; transform:translateY(0);}
.select-opt{display:block; width:100%; text-align:left; background:none; border:none; padding:9px 12px; font-size:13.5px; color:var(--color-text); cursor:pointer;}
.select-opt:hover{background:var(--color-surface-alt);}
.select-opt.active{color:var(--color-primary); font-weight:700;}

/* Segmented control — precies één keuze, onmiddellijk effect */
/* align-self: flex-start — anders rekt een omringende flex-column (bv. .field)
   dit inline-flex element over de volle breedte uit; het kader zou dan tot
   voorbij de knoppen doorlopen. */
.segmented{display:inline-flex; align-self:flex-start; border:1px solid var(--color-border); border-radius:var(--radius-sm); overflow:hidden;}
.seg-btn{background:#fff; border:none; padding:8px 14px; font-size:13px; font-weight:600; color:var(--color-text-muted); cursor:pointer; border-right:1px solid var(--color-border); font-family:var(--font-body);}
.seg-btn:last-child{border-right:none;}
.seg-btn.active{background:var(--color-primary); color:#fff;}
.seg-btn:hover:not(.active){background:var(--color-surface-alt);}

/* Tone-varianten — een segmented control waarvan de betekenis van elke optie
   zelf al een kleur draagt (bv. status: gepland/bezig/afgewerkt), i.p.v. de
   neutrale .segmented waar alle opties gelijkwaardig zijn en enkel "actief"
   het onderscheid maakt. Zelfde kleurenset als .badge-*, zodat een pill-badge
   en een tone-segmented altijd hetzelfde kleurkoppel gebruiken voor dezelfde
   betekenis. Klasse komt op .seg-btn zelf (niet op .segmented), zodat elke
   knop zijn eigen tone draagt; alleen de .active-knop toont de kleur, de
   overige knoppen blijven de neutrale .seg-btn-staat.*/
.seg-btn.tone-neutral.active{background:var(--color-surface-alt); color:var(--color-text-muted);}
.seg-btn.tone-info.active{background:var(--color-info); color:#fff;}
.seg-btn.tone-success.active{background:var(--color-success); color:#fff;}
.seg-btn.tone-warning.active{background:var(--color-warning); color:#fff;}
.seg-btn.tone-danger.active{background:var(--color-danger); color:#fff;}

/* Toggle-chip-groep — meerdere keuzes, onmiddellijk effect. Zelfde gesloten balkvorm
   als .segmented (hoort visueel bij dezelfde familie: "kies uit deze opties"), maar met
   een vinkje per optie i.p.v. een effen gevulde actieve staat — dát vinkje is het signaal
   "hier kunnen er meerdere tegelijk aan staan", wat segmented control nooit toont. */
.chipgroup{display:inline-flex; border:1px solid var(--color-border); border-radius:var(--radius-sm); overflow:hidden;}
.chip-toggle{display:inline-flex; align-items:center; gap:7px; background:#fff; border:none; border-right:1px solid var(--color-border); border-radius:0; padding:8px 14px 8px 10px; font-size:13px; font-weight:600; color:var(--color-text-muted); cursor:pointer; font-family:var(--font-body);}
.chip-toggle:last-child{border-right:none;}
.chip-toggle .chk{width:15px; height:15px; border-radius:4px; border:1.5px solid var(--color-border); display:flex; align-items:center; justify-content:center; flex:none; font-size:10px; line-height:1; color:transparent; transition:.15s; background:#fff;}
.chip-toggle.active{background:var(--color-info-bg); color:var(--color-text);}
.chip-toggle.active .chk{background:var(--color-primary); border-color:var(--color-primary); color:#fff;}
.chip-toggle:hover:not(.active){background:var(--color-surface-alt);}

/* Beide balken zijn inline-flex zonder wrap, dus vanaf een handvol opties lopen ze
   op een telefoon buiten het scherm — zichtbaar op de categoriefilter van het
   regelboek, waar "Content" en "Proces" simpelweg wegvielen. Onder 640px wikkelen
   ze naar een tweede regel. Prijs: de scheidingslijn rechts van de laatste knop op
   een regel blijft staan; dat is minder erg dan opties die onbereikbaar zijn. */
@media(max-width:640px){
  .segmented,.chipgroup{flex-wrap:wrap;}
}

/* <pw-segmented> — licht-DOM-opmaak (this.append(wrap)), dus deze CSS
   bereikt het net als bij <pw-select>. De bron-<option>-kinderen blijven
   permanent onderdrukt (niet via het element se eigen `hidden`-attribuut,
   want dát is hier het CONSUMER-gestuurde zichtbaarheidssignaal — zie
   pw-segmented.js se docblock). */
pw-segmented>option{display:none;}

/* <pw-scale-picker> — exclusieve keuze uit een kleine, geordende reeks
   eenheden waarvan de relatieve GROOTTE zelf betekenis draagt (bv.
   dag/week/maand/jaar). Elke optie krijgt een balkje waarvan de hoogte
   meeschaalt met --scale-ratio (0..1, door pw-scale-picker.js per optie
   gezet) — de schaal is zo letterlijk zichtbaar i.p.v. enkel de labeltekst. */
pw-scale-picker>option{display:none;}
.scale-track{display:flex; align-items:flex-end; gap:var(--sp-2); height:64px;}
.scale-opt{display:flex; flex-direction:column; align-items:center; justify-content:flex-end; gap:5px; flex:1 1 0; border:none; background:transparent; padding:0 0 2px; font-size:11px; font-weight:600; color:var(--color-text-muted); cursor:pointer; font-family:var(--font-body);}
.scale-bar{display:block; width:100%; max-width:26px; border-radius:4px 4px 0 0; background:var(--color-border); transition:background .2s; height:calc(14px + var(--scale-ratio,1) * 42px);}
.scale-opt[aria-checked="true"]{color:var(--color-text);}
.scale-opt[aria-checked="true"] .scale-bar{background:var(--color-accent);}
.scale-opt:hover:not([aria-checked="true"]) .scale-bar{background:var(--color-text-muted);}
.scale-opt:focus-visible{outline:2px solid var(--color-primary); outline-offset:2px; border-radius:4px;}

/* ===== veldenrij — twee of drie velden naast elkaar op één regel.
   Klapt onder 520px terug naar onder elkaar; een veld dat te smal wordt is
   onbruikbaar, dus dit is geen vaste kolomindeling maar een minimumbreedte. ===== */
.field-row{display:flex; gap:var(--sp-4); flex-wrap:wrap;}
.field-row > .field{flex:1 1 180px; min-width:0;}

/* ===== knoppenrij — meerdere knoppen naast elkaar, met de tussenruimte in de rij.
   De ruimte hoort bewust HIER en niet in .btn zelf: een knop in een tabelcel, in
   een modal-voet of alleen op een regel wil die marge niet, en zou hem dan telkens
   moeten wegwerken. Zelfde redenering als R-015 — afstand hoort bij wat samenstelt,
   niet bij het onderdeel. ===== */
.btn-row{display:flex; gap:var(--sp-3); flex-wrap:wrap;}
.btn-row > .btn{flex:1 1 auto; justify-content:center;}
.btn-row.end{justify-content:flex-end;}
.btn-row.end > .btn{flex:0 0 auto;}

/* ===== navigatie ===== */
/* TOPLIJN — de vaste kop van elke app: naam links, menu in het midden, gebruiker
 * met rol en de uitlog-knop rechts. Heette tot 2026-07-26 .demo-topbar: een
 * demo-naam voor CSS die in werkelijkheid product-UI is, terwijl het dashboard
 * ernaast zijn eigen .app-topbar in een <style>-blok had staan. Twee definities
 * van dezelfde balk, precies wat de Owner wilde wegnemen.
 *
 * De markup komt uit <pw-topbar> (components/pw-topbar.js) en de inhoud uit
 * app-shell.json — één plek om een menu-item te wijzigen. Deze CSS blijft wél
 * hier en niet in het component: het component rendert bewust in de light DOM,
 * zodat R-001 blijft gelden en de opmaak op één plek staat.
 *
 * Volle breedte en vastgeplakt bovenaan is de PRODUCT-vorm. De stijlgids toont
 * hem in een kaartje en zet daar zelf position/radius om — dat staat in
 * styleguide.css, want dat is omhulsel, geen product-UI. */
pw-topbar{display:block;}
.app-topbar{display:flex; align-items:center; justify-content:space-between; gap:var(--sp-4); background:var(--color-primary); color:#fff; padding:12px var(--sp-6); position:sticky; top:0; z-index:50; box-shadow:var(--shadow-md);}
.app-topbar .brand{font-weight:700; font-size:15px; display:flex; align-items:center; gap:8px; color:#fff; text-decoration:none; flex:none;}
.app-topbar nav{display:flex; gap:var(--sp-5); font-size:13.5px; flex-wrap:wrap;}
.app-topbar nav a{color:var(--color-on-dark); text-decoration:none; padding:6px 2px; border-bottom:2px solid transparent;}
.app-topbar nav a:hover{color:#fff;}
.app-topbar nav a.active{color:#fff; font-weight:700; border-bottom-color:var(--color-accent);}
.app-topbar .topbar-right{display:flex; align-items:center; gap:var(--sp-3); flex:none;}
.app-topbar .user-chip{display:inline-flex; align-items:center; gap:6px; font-size:13px; color:var(--color-on-dark); white-space:nowrap;}
/* Uitloggen: vaste bewoner, uiterst rechts, icoon-only met tooltip (→ i-logout,
   nooit i-close — dat sluit een overlay).
   GEEN rondje eromheen (Owner 2026-07-27): enkel het icoon. Een cirkel met rand
   leest als een knop-met-nadruk, en dat is uitloggen niet — het is de minst
   opvallende actie in de balk, niet de meest opvallende. Het raakvlak blijft wél
   38×38 via padding, want een icoon van 14px is op een aanraakscherm te klein om
   te mikken. Kleurwissel bij hover, geen vulling. */
.topbar-logout{color:var(--color-on-dark); display:flex; align-items:center; justify-content:center; flex:none; text-decoration:none; background:none; border:0; padding:10px; margin:-10px; cursor:pointer;}
.topbar-logout:hover{color:#fff;}
/* De tooltip uit base.css opent naar BOVEN. Op een balk die tegen de bovenrand
   van het venster geplakt zit, valt hij daarmee buiten het scherm — dus hier
   omgeklapt, pijltje incluis. */
.app-topbar [data-tip]::after{top:calc(100% + 10px); bottom:auto;}
.app-topbar [data-tip]::before{top:100%; bottom:auto; border-top-color:transparent; border-bottom-color:var(--color-text);}
/* Onder 640px zakt het menu naar een eigen regel onder naam en gebruiker: naast
   elkaar houdt geen van de drie genoeg breedte over (→ R-016). */
@media(max-width:640px){
  .app-topbar{padding:10px var(--sp-4); flex-wrap:wrap;}
  .app-topbar nav{order:3; flex:1 1 100%; gap:var(--sp-4); font-size:13px;}
  .app-topbar .user-chip{font-size:12px;}
}
.demo-sidebar{display:flex; flex-direction:column; gap:4px; width:64px; background:var(--color-surface-alt); border-radius:var(--radius-md); padding:10px;}
.side-icon{width:44px; height:44px; border-radius:var(--radius-sm); display:flex; align-items:center; justify-content:center; color:var(--color-text-muted); cursor:pointer;}
.side-icon.active{background:var(--color-primary); color:#fff;}
.side-icon:hover{background:#fff;}
.tabs [role=tab]{border:none; background:none; padding:10px 4px; margin-right:22px; font-size:14px; font-weight:600; color:var(--color-text-muted); cursor:pointer; border-bottom:2px solid transparent;}
.tabs [role=tab][aria-selected=true]{color:var(--color-primary); border-color:var(--color-accent);}
.tabpanel{padding-top:calc(var(--sp-3) * 1.5); font-size:13.5px; color:var(--color-text-muted);}
/* Zelfde verruiming (→ Owner-instructie 2026-08-13, +50%), maar ook voor
   tabpanels die de .tabpanel-klasse niet dragen — zoals .tool-cat in els's
   Gereedschap — zodat de ruimte onder <pw-tabs> overal gelijk is, niet enkel
   in de stijlgids-demo. Geen typografie hier: die blijft eigen aan .tabpanel. */
[role="tabpanel"]{padding-top:calc(var(--sp-3) * 1.5);}
.breadcrumb{display:flex; gap:6px; font-size:13px; color:var(--color-text-muted); align-items:center; margin-bottom:var(--sp-5);}
.breadcrumb a{color:var(--color-text-muted); text-decoration:none;}
.breadcrumb a:hover{color:var(--color-primary); text-decoration:underline;}
.breadcrumb .current{color:var(--color-text); font-weight:600;}
.pagination{display:flex; gap:6px; align-items:center;}
.page-btn{width:32px; height:32px; border-radius:var(--radius-sm); border:1px solid var(--color-border); background:#fff; font-size:13px; display:flex; align-items:center; justify-content:center; cursor:pointer;}
.page-btn.active{background:var(--color-primary); color:#fff; border-color:var(--color-primary);}

/* ===== kaarten & lijsten ===== */
.card{background:#fff; border:1px solid var(--color-border); border-radius:var(--radius-md); overflow:hidden; box-shadow:var(--shadow-sm);}
.card-media{height:90px; background:linear-gradient(120deg,var(--color-info),var(--color-primary)); display:flex; align-items:center; justify-content:center; color:#fff;}
.card-body{padding:var(--sp-4);}
.card-body h4{font-size:15px; margin-bottom:2px;}
.card-body p{font-size:13px; margin:0;}

/* pw-book-selector (→ S02-boek selector): boek-kaarten op .card gebouwd,
   verticaal (drawer) of horizontaal (pagina) opstelbaar. */
.book-selector{display:flex; flex-wrap:wrap; gap:var(--sp-4);}
.book-selector[data-orientation="vertical"]{flex-direction:column; flex-wrap:nowrap;}
.book-card{width:140px; cursor:pointer; border:none; padding:0; text-align:left; font:inherit; color:inherit;}
.book-selector[data-orientation="vertical"] .book-card{width:100%;}
.book-card:hover, .book-card:focus-visible{box-shadow:0 0 0 2px var(--color-accent), var(--shadow-sm); outline:none;}
/* aspect-ratio i.p.v. een vaste px-hoogte (2026-08-17, Rik-melding: covers
   pasten niet mooi in de vaste 120px-hoogte) — schaalt mee met de kaartbreedte
   en is instelbaar via pw-book-selector's cover-ratio-attribuut (default
   4/3, zie dat element se eigen bovenschrift), i.p.v. altijd hetzelfde
   landschapsformaat te forceren op een portretcover.

   GEEN max-height meer (2026-08-17, Rik: "in tegenstelling tot mijn
   eerdere instructies: maak de covers tóch vullend") — een max-height-cap
   samen met volle breedte brak eerder de verhouding (landscape i.p.v.
   portret). i.p.v. daarvan: width:50% (2026-08-17, Rik: "container...
   kleiner, ong 50% van hoogte, hou zelfde verhouding") — de hele kaart
   (breedte én hoogte) halveert samen, aspect-ratio blijft exact behouden
   omdat enkel de breedte-basis kleiner wordt, niet de ratio zelf.
   margin:0 auto centreert de nu smallere kaart. */
.book-card .card-media{width:50%; aspect-ratio:var(--book-cover-ratio, 4/3); height:auto; margin:0 auto; background:var(--color-surface-alt); padding:0;}
.book-card .card-media img{width:100%; height:100%; object-fit:cover; display:block;}
.book-card .card-media.is-fallback{background:linear-gradient(120deg,var(--color-info),var(--color-primary)); color:#fff; display:flex; align-items:center; justify-content:center; padding:var(--sp-3); text-align:center;}
.book-card .card-media.is-fallback span{font-size:13px; font-weight:600;}
.hero-card{position:relative; border-radius:var(--radius-lg); overflow:hidden; box-shadow:var(--shadow-md); border:1px solid var(--color-border); background:#fff; cursor:pointer; transition:.15s;}
.hero-card:hover{transform:translateY(-3px); box-shadow:var(--shadow-lg);}
.hero-card.active{box-shadow:0 0 0 3px var(--color-accent), var(--shadow-lg);}
.hero-art{height:100px; position:relative; background:linear-gradient(180deg,var(--color-info) 0%,var(--color-primary) 75%);}
.hero-art svg{position:absolute; inset:0; width:100%; height:100%; display:block;}
.hero-badges{position:absolute; top:10px; left:10px; display:flex; gap:6px; z-index:2;}
.hero-flag{position:absolute; top:10px; right:10px; width:24px; height:24px; border-radius:50%; background:var(--color-accent); color:var(--color-accent-contrast); display:flex; align-items:center; justify-content:center; font-size:12px; box-shadow:var(--shadow-sm); z-index:2;}
.hero-body{padding:14px 16px;}
.hero-body h4{font-size:15px; margin-bottom:2px;}
.hero-body .id{font-size:12px; color:var(--color-text-muted); font-family:ui-monospace,Consolas,monospace;}
.hero-card.ghost{border-style:dashed; box-shadow:none; background:transparent; cursor:default;}
.hero-card.ghost:hover{box-shadow:none; border-color:var(--color-border); transform:none;}
.ghost-inner{text-align:center; color:var(--color-text-muted); display:flex; flex-direction:column; align-items:center; gap:var(--sp-2);}
.stat-row{display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:var(--sp-4);}
@media(max-width:1000px){.stat-row{grid-template-columns:repeat(2,minmax(0,1fr));}}
.stat-tile{min-width:0; background:var(--color-surface); border:1px solid var(--color-border); border-radius:var(--radius-md); padding:var(--sp-4); box-shadow:var(--shadow-sm);}
.stat-tile .stat-val{font-size:26px; font-weight:700; color:var(--color-primary);}
/* .stat-val .sub is voor twee getallen die enkel door context bij elkaar horen,
   niet voor een deel-van-geheel-paar. Voorbeeld wél: "12 / 21 dagen" bij
   "tot vertrek / reisduur" — twee onafhankelijke tellingen die toevallig samen
   op één regel staan; het tweede getal moet duidelijk bijzaak blijven. Voorbeeld
   niét: "3 / 50 familieleden" — dat IS een breuk (3 van de 50 gebruikt), en
   hoort dus in twee gelijk-grote cijfers te blijven staan, anders leest het
   alsof "50" minder telt dan "3", terwijl het net het kader is waarbinnen "3"
   betekenis krijgt. Kortom: ongelijk wanneer de twee getallen apart zouden
   kunnen bestaan, gelijk wanneer het ene getal het andere pas laat kloppen. */
.stat-tile .stat-val .sub{font-size:14px; font-weight:600; color:var(--color-text-muted);}
.stat-tile .stat-label{font-size:12px; color:var(--color-text-muted); display:flex; align-items:center; gap:6px;}
.stat-tile .stat-caption{margin-top:6px; padding-top:6px; border-top:1px solid var(--color-border); font-size:11px; color:var(--color-text-muted);}
/* .stat-tile-success/-danger zien er verder exact als .stat-tile uit — enkel
   de hoofdtekst kleurt mee. Het "waarom" (bv. "0 verschillen" of "eerste van
   3: token abc123") hoort niet in een permanent zichtbare onderregel maar in
   de hover-info (.info-btn.tile-info + .popover, → R-003), dus beide dragen
   position:relative als anker en heropenen hun popover naar links — dezelfde
   reden en oplossing als .action-tile-wrap hierboven: de knop staat tegen de
   rechterrand van de tegel. */
.stat-tile-success{position:relative;}
.stat-tile-success .stat-val{color:var(--color-success);}
.stat-tile-danger{position:relative;}
.stat-tile-danger .stat-val{color:var(--color-danger);}
.stat-tile-success .popover,.stat-tile-danger .popover{left:auto; right:0;}
.info-pill-lg{display:inline-flex; align-items:center; gap:7px; background:#fff; border:1px solid var(--color-border); border-radius:var(--radius-full); padding:7px 16px; font-size:12.5px; box-shadow:var(--shadow-sm);}
/* pw-chart: host bepaalt de hoogte (default 260px, override via
   --pw-chart-height), Chart.js zelf staat op responsive+!maintainAspectRatio
   in de configuratie die de consumer meegeeft aan setData() — deze regel is
   enkel het canvas op de volle hostgrootte laten meeschalen. */
pw-chart{display:block; position:relative; width:100%; height:var(--pw-chart-height,260px);}
pw-chart .pw-chart-canvas{width:100%!important; height:100%!important;}
.info-pill-lg .l{color:var(--color-text-muted); font-weight:600;}
.info-pill-lg .v{color:var(--color-text); font-weight:700;}
.list-item{display:flex; align-items:center; gap:12px; padding:12px 4px; border-bottom:1px solid var(--color-border);}
.list-item:last-child{border:0;}
.list-item .grow{flex:1;}
.avatar{width:36px; height:36px; border-radius:50%; background:var(--color-info-bg); color:var(--color-info); display:flex; align-items:center; justify-content:center; font-weight:700; font-size:13px; flex:none;}
.list-item .meta{font-size:12px; color:var(--color-text-muted);}
.list-item .kebab{background:none; border:none; color:var(--color-text-muted); cursor:pointer; width:28px; height:28px; display:flex; align-items:center; justify-content:center; border-radius:var(--radius-full);}
.list-item .kebab:hover{background:var(--color-surface-alt); color:var(--color-primary);}

/* ===== pw-tree — boomstructuur (rijlabel + één acties-menu op
   <wa-tree-item>'s default slot, → elements/pw-tree.js).

   Rik 2026-08-19: eerst 2-4 losse icoonknoppen per rij geprobeerd — bij
   meerdere insprongniveaus oogde dat als een puinhoop (elke rij duwt zijn
   eigen icoongroep naar de rand van zijn eigen, per diepte smallere
   content-vak, dus geen enkele rechterrand valt samen). Consolidatie naar
   één "⋮"-knop (hergebruikt de bestaande .dropdown/.dropdown-menu-opmaak,
   → "knoppenrij"-sectie) lost dat volledig op: exact één klein element per
   rij, altijd op dezelfde plek t.o.v. zijn eigen rij, geen uitlijnprobleem
   meer mogelijk. btn-icon is 38px, te groot naast een boomrij-label — de
   modifier hieronder voegt enkel maatvoering toe (→ R-015), geen nieuwe
   kleur/vorm. */
pw-tree{display:block;}
.pw-tree-row{display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3);}
.pw-tree-label{display:flex; align-items:center; gap:6px; min-width:0;}
.pw-tree-label pw-icon{flex:none; color:var(--color-text-muted);}
.pw-tree-label-text{overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;}
.pw-tree-actions{flex:none;}
/* Rik 2026-08-20: 38px .btn-icon-rand oogde te zwaar naast een boomrij-
   label — lichter, transparant tot hover, zelfde taal als <pw-data-grid>'s
   rij-actieknoppen (.actions .icon-btn). */
.pw-tree-menu-btn{width:26px; height:26px; border-color:transparent; background:transparent;}
.pw-tree-menu-btn:hover{background:var(--color-surface-alt);}

/* Rik 2026-08-20: het "⋮"-menu wordt bij het OPENEN naar <body> verplaatst
   (position:fixed, zie #buildActionMenu in pw-tree.js) i.p.v. als kind van
   zijn eigen boomrij te blijven staan. Reden: <wa-tree-item> bleek een
   eigen stacking-context te vormen (vermoedelijk CSS containment), waardoor
   een absoluut gepositioneerd menu binnen een EERDERE rij altijd ONDER een
   LATERE zusterrij bleef renderen, ongeacht z-index — zichtbaar doordat het
   menu van de eerste boomknoop optisch "achter" de uitgeklapte kinderen
   daaronder verscheen, en klikken op een menu-item in werkelijkheid de
   onderliggende boomrij raakte (per ongeluk in-/uitklappen, geen enkele
   actie voerde uit). Een <body>-kind zit buiten elke boomrij zijn eigen
   stacking-context, dus dit is structureel robuust i.p.v. een hogere
   z-index die het probleem enkel had kunnen verplaatsen. */
.pw-tree-menu-portal{min-width:190px;}
.pw-tree-menu-portal button{display:flex; align-items:center; gap:8px; padding:9px 14px; font-size:13.5px; color:var(--color-text); background:none; border:none; width:100%; text-align:left; cursor:pointer; font-family:var(--font-body);}
.pw-tree-menu-portal button:hover{background:var(--color-surface-alt);}
.pw-tree-menu-portal hr{border:none; border-top:1px solid var(--color-border); margin:4px 0;}

/* Rik 2026-08-19 (Els S16, D-06): `muted`/`badges` zijn generieke,
   business-agnostische weergavehints (→ elements/pw-tree.js docblock) —
   deze CSS interpreteert enkel "gedimd" resp. "klein icoontje vóór de
   menuknop", nooit wat een specifieke badge betekent. Zelfde voor `icon`
   (2026-08-20): een optioneel, host-bepaald icoontje vóór het label, puur
   generiek weergegeven — geen enkele betekenis toegekend door dit element.
   Badge-iconen (2026-08-20: Rik wilde ze NA de tekst, niet weggedreven
   naar de "⋮"-knop) zijn sinds dan gewone kinderen van
   .pw-tree-label — geen apart .pw-tree-badges-wrapper meer nodig, de
   flex-gap van .pw-tree-label regelt de tussenruimte al. */
.pw-tree-row-muted .pw-tree-label-text{color:var(--color-text-muted);}

/* ===== actie-tegel — → R-005 (altijd een info-knop) ===== */
.toolgrid{display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:var(--sp-3);}
@media(max-width:1000px){.toolgrid{grid-template-columns:repeat(2,minmax(0,1fr));}}
.action-tile{background:var(--color-surface); border:1px solid var(--color-border); border-radius:var(--radius-md); padding:14px; display:flex; flex-direction:column; gap:8px; align-items:flex-start; box-shadow:var(--shadow-sm); cursor:pointer; transition:.15s; position:relative; text-decoration:none; color:inherit;}
.action-tile:hover{border-color:var(--color-primary); box-shadow:var(--shadow-md); transform:translateY(-1px);}
.action-tile .tile-icon{width:36px; height:36px; border-radius:var(--radius-sm); background:var(--color-info-bg); color:var(--color-primary); display:flex; align-items:center; justify-content:center;}
.action-tile .tile-label{font-weight:700; font-size:13px; color:var(--color-text);}
/* Niet-beschikbaar: de tegel blijft leesbaar zodat je ziet dát de actie bestaat,
   maar reageert niet. Stond tot 2026-07-27 enkel in het <style>-blok van het
   Owner-dashboard — één van de 43 stukken opmaak die daar buiten de gedeelde CSS
   leefden. Hier hoort het, met een eigen variant in de gids. */
.action-tile.disabled{opacity:.45; cursor:not-allowed;}
.action-tile.disabled:hover{border-color:var(--color-border); box-shadow:var(--shadow-sm); transform:none;}
.action-tile.ghost{border-style:dashed; box-shadow:none; cursor:default;}
.action-tile.ghost:hover{border-color:var(--color-border); box-shadow:none; transform:none;}
.action-tile.ghost .tile-icon{background:transparent; color:var(--color-text-muted);}
.action-tile.ghost .tile-label{color:var(--color-text-muted);}
/* Wrapper voor een actie-tegel die zélf een link is. Een <button> in een <a> is
   ongeldige HTML en de klik zou naar de link doorbubbelen, dus staat de info-knop
   ernaast in plaats van erin; deze wrapper is het positioneringsanker voor
   .tile-info (→ R-003, rechtsboven). De popover opent naar links, want de knop
   staat tegen de rechterrand van de tegel. */
.action-tile-wrap{position:relative; display:flex;}
.action-tile-wrap > .action-tile{flex:1; padding-right:38px;}
.action-tile-wrap .popover{left:auto; right:0;}

/* GEEN .action-tile.poolster-audit-regel meer (→ Owner-instructie 2026-08-13,
   herziening van S01-UI elements audit D5): <pw-audit-tile> rendert nu kaal
   `.action-tile`, exact zoals elke andere actie-tegel — titel/icoon/klikgedrag
   liggen vast in pw-audit-tile.js zelf, niet in een eigen visuele variant hier. */

/* ===== SAMENGESTELD ELEMENT: aanmeldkaart =====
 * Een samengesteld element is een vaste combinatie van bestaande elementen, niet
 * een nieuwe verzameling opmaak. Deze kaart is letterlijk de foto-kaart met een
 * formulier in de body: .photo-card levert de vorm (media boven, inhoud eronder),
 * .field/.btn/.alert leveren de inhoud. Wat hieronder staat is uitsluitend
 * maatvoering en ritme — geen kleur, geen rand, geen schaduw die het foto-kaart-
 * element niet al had. Dat is precies de discipline die een samengesteld element
 * veilig maakt: gaat de foto-kaart ooit veranderen, dan verandert dit mee.
 *
 * Bewust géén "login-scherm"-element: dat zou een pagina zijn, geen bouwsteen.
 * Wélke velden erin staan verschilt per app en blijft dus paginawerk; de kaart
 * eromheen is wat alle aanmeldschermen delen. Zie de groep "Samengestelde
 * elementen" in de stijlgids.
 */
.auth-card{max-width:460px; margin:0 auto; width:100%;}
/* LET OP — deze twee regels dragen bewust .photo-card.auth-card (drie klassen) en
   niet .auth-card alleen. De basisregels .photo-card .photo-media en
   .photo-card .photo-body hebben exact dezelfde specificiteit (twee klassen) en
   staan LATER in dit bestand, dus met .auth-card alleen wint de basisregel op
   bronvolgorde en gebeurt hier niets. Dat was tot 2026-07-26 stil aan de hand:
   de bandhoogte viel toevallig samen (beide 16/9), maar de body-padding stond al
   die tijd op sp-4 i.p.v. de bedoelde sp-6. Gevonden bij het meten in de browser,
   niet bij het lezen.
   De band zelf: lage letterbox i.p.v. 16/9 (Owner, 2026-07-26: "foto-deel
   ongeveer 50% kleiner") — 460px breed geeft 129px hoog tegen 259px eerder, exact
   de helft. Dit is maatvoering van de samenstelling, geen nieuwe beeldverhouding
   voor de groep Fotografie: dáár blijven 21/9, 16/9, 1/1 en 3/4 de gedocumenteerde
   set (→ R-015). Gevolg om te beoordelen: object-fit:cover snijdt een bronfoto van
   1920×1182 hard aan boven- en onderkant weg — enkel een horizontale strook uit
   het midden blijft over. */
.photo-card.auth-card .photo-media{aspect-ratio:32/9;}
.photo-card.auth-card .photo-body{padding:var(--sp-6);}
.auth-card .auth-title{font-size:22px; margin-bottom:var(--sp-2);}
.auth-card .auth-intro{font-size:13px; margin-bottom:var(--sp-5);}
.auth-card .auth-fields{display:flex; flex-direction:column; gap:var(--sp-4); margin-bottom:var(--sp-5);}
.auth-card .auth-fields:empty{margin-bottom:0;}
.auth-card .alert{margin-bottom:var(--sp-4);}
.auth-card .field label .req{color:var(--color-danger); font-weight:700;}
.auth-card .field-hint{font-size:12px; color:var(--color-text-muted); margin:0;}
.auth-card .field[hidden]{display:none;}
/* Vertrouwde-computer-blok: een kaartje binnen de kaart, zodat de info-knop
   rechtsboven kan staan i.p.v. inline achter de tekst (→ R-003). */
.auth-card .trust-box{position:relative; border:1px solid var(--color-border); background:var(--color-surface-alt); border-radius:var(--radius-sm); padding:12px 44px 12px 14px;}
.auth-card .trust-box .check-row{font-size:13px; font-weight:600;}
/* De popover opent naar links, want de knop staat tegen de rechterrand van de
   kaart (→ R-003 zet hem daar). Zonder dit begint een popover van 260px op de
   knop en loopt hij de kaart uit. Zelfde reden en zelfde oplossing als bij
   .action-tile-wrap hierboven — base.css laat de richting bewust open. */
.auth-card .trust-box .popover{left:auto; right:0;}
.auth-page{min-height:100vh; display:flex; align-items:center; justify-content:center; padding:var(--sp-6) var(--sp-4);}

/* ===== inklapbare sectie — één kop die zijn eigen inhoud toont/verbergt.
   Onderscheid met Tabs en Paneel-sidebar: die kiezen tussen broers/zussen waarvan
   er altijd precies één zichtbaar is; dit toont of verbergt één sectie onafhankelijk,
   en meerdere kunnen tegelijk openstaan. Bij meer dan 2-3 samen in een lijst hoort
   er een "alles uit-/inklappen" boven (→ R-010). ===== */
.collapsible-toggle{display:inline-flex; align-items:center; gap:8px; background:none; border:none; padding:0; margin:0; cursor:pointer; text-align:left; color:inherit; font:inherit; letter-spacing:inherit; text-transform:inherit;}
.collapsible-toggle .chev{color:var(--color-text-muted); flex:none;}
.collapsible-toggle:hover{color:var(--color-primary);}
.collapsible-toggle:hover .chev{color:var(--color-primary);}
.collapsible-toggle:focus-visible{outline:2px solid var(--color-accent); outline-offset:3px; border-radius:var(--radius-xs);}
.collapsible-body[hidden]{display:none;}

/* ===== fotografie ===== */
.photo-hero{position:relative; border-radius:var(--radius-lg); overflow:hidden; background:linear-gradient(180deg,var(--color-info) 0%,var(--color-primary) 75%); display:flex; align-items:center; justify-content:center;}
.photo-hero img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block;}
.photo-scrim{position:absolute; inset:0; background:linear-gradient(180deg, rgba(22,35,47,0) 40%, rgba(22,35,47,.75) 100%); pointer-events:none;}
.photo-scrim.scrim-full{background:linear-gradient(180deg, rgba(22,35,47,0) 0%, rgba(22,35,47,.15) 35%, rgba(22,35,47,.55) 55%, rgba(22,35,47,.9) 100%);}
.photo-caption{position:absolute; bottom:16px; left:20px; right:20px; color:#fff; z-index:2;}
.photo-caption .t{font-weight:700; font-size:15px;}
.photo-caption .s{font-size:11.5px; opacity:.85;}
.photo-credit{position:absolute; bottom:8px; right:12px; font-size:10.5px; color:rgba(255,255,255,.65); z-index:2;}
.photo-art-fallback{color:rgba(255,255,255,.55);}
/* De fallback verdwijnt zodra er een foto ligt. Zonder deze regel werd het icoon
   ALTIJD getekend, dus bovenop een geladen foto — op het Owner-dashboard stond er
   een witte globe over de beer. Dat viel pas op toen de foto er effectief kwam: met
   een ontbrekend bestand zag je enkel de fallback en leek alles in orde.
   `onerror="this.remove()"` op de <img> is de andere helft van het paar: mislukt de
   foto, dan is er geen img meer en komt de fallback terug. Eén regel voor alle
   fotohouders, want dit is geen eigenschap van één scherm. */
:is(.hero-art,.photo-hero,.photo-media,.card-media):has(img) .photo-art-fallback{display:none;}
/* Bewust GEEN overflow:hidden op de kaart zelf — dat stond er tot 2026-07-26 en
   kliste alles weg wat uit de kaart moest komen. Zichtbaar op de Owner-login: de
   uitleg-popover bij "vertrouwde computer" is hoger dan de ruimte onder het
   trust-blok, dus de onderste helft verdween achter de kaartrand. De ronding
   wordt nu geklist door de twee delen zelf i.p.v. door de kaart: de media klist
   zijn eigen foto al (overflow:hidden hieronder), de body is het onderste vlak.
   De -1px is de randdikte: een kind dat exact dezelfde radius krijgt als de
   ouder steekt met een haarlijn buiten de bocht van de rand. */
.photo-card{border-radius:var(--radius-md); box-shadow:var(--shadow-sm); border:1px solid var(--color-border);}
.photo-card .photo-media{aspect-ratio:16/9; position:relative; background:var(--color-surface-alt); overflow:hidden; border-radius:calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px) 0 0;}
.photo-card .photo-media img{width:100%; height:100%; object-fit:cover; display:block;}
.photo-card .photo-body{padding:var(--sp-4); background:#fff; border-radius:0 0 calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px);}
.photo-card .photo-body h4{font-size:14px; margin-bottom:2px;}
.photo-card .photo-body p{font-size:12px;}
.ratio-box{background:var(--color-surface-alt); border-radius:var(--radius-sm); display:flex; align-items:center; justify-content:center; color:var(--color-text-muted); font-size:11px; font-family:ui-monospace,Consolas,monospace;}

/* ===== tabel ===== */
table.dtable{width:100%; border-collapse:collapse; font-size:13.5px;}
table.dtable th{text-align:left; font-size:11.5px; text-transform:uppercase; letter-spacing:.04em; color:var(--color-text-muted); padding:10px 12px; border-bottom:2px solid var(--color-border);}
table.dtable th.sortable{cursor:pointer;}
table.dtable th.sorted{color:var(--color-primary);}
table.dtable th .icon{width:12px; height:12px; margin-left:4px; opacity:.7;}
table.dtable td{padding:12px; border-bottom:1px solid var(--color-border);}
table.dtable tr:hover td{background:var(--color-surface-alt);}
table.dtable tr.selected td{background:var(--color-info-bg);}
.legend{display:flex; gap:var(--sp-5); flex-wrap:wrap; margin-top:var(--sp-3); font-size:12px; color:var(--color-text-muted);}
.legend b{color:var(--color-text);}

/* ===== tabel — gestapeld onder 640px als PLATTE LIJST =====
 * Een tabel met meer dan drie kolommen is op een telefoon onleesbaar: de kolommen
 * worden zo smal dat elke cel over meerdere regels breekt, óf de pagina moet
 * zijwaarts scrollen. Beide maken het vergelijken tussen rijen — de enige reden
 * om een tabel te kiezen — juist onmogelijk. Onder 640px verandert de tabel dus
 * van vorm (→ R-016).
 *
 * VORM (Owner-beslissing 2026-07-27, na de eerste poging): een platte lijst, geen
 * kaartjes. Twee regels per rij — de titel bovenaan met de rij-actie rechts, en
 * eronder één gedempte meta-regel met de rest, gescheiden door een puntje.
 *
 * De eerste versie maakte van elke rij een omkaderd kaartje mét de kolomkop vóór
 * élke cel. Dat was 113px per regel — 6 van de 16 regels vulden een heel scherm —
 * en het herhaalde "CATEGORIE" boven een pil die al leest als een categorie. De
 * Owner noemde het lelijk, en met die cijfers erbij was dat ook de juiste
 * conclusie: kopjes die je 16 keer herhaalt zijn geen hulp maar ruis.
 *
 * CONTRACT — drie klassen per cel, de tabel hoeft niets van de inhoud te weten:
 *   .cell-title   de dragende cel: volle breedte, bovenaan, grotere tekst
 *   .cell-meta    bijzaak: samen op de tweede regel, gescheiden door een puntje
 *   .cell-end     de rij-actie: absoluut rechtsboven, op de titelregel
 * Een cel zonder klasse valt terug op volle breedte — bruikbaar voor een
 * colspan-rij ("geen regels in deze categorie"), niet voor een echte kolom.
 *
 * `.cell-end` staat absoluut en niet in de flexrij, en dat is met opzet: enkel zo
 * houdt de titel gegarandeerd zijn eigen regel. Met `margin-left:auto` in de rij
 * hing het ervan af of alles toevallig samen paste — flexbox breekt regels op
 * basisbreedtes vóór auto-marges verrekend worden, dus een korte titel zou de
 * meta-regel naar boven zuigen. Vandaar de padding-right op de rij: dat is de
 * ruimte die voor die knop vrijgehouden wordt.
 *
 * `data-label` blijft werken op een `.cell-meta` en zet er een licht voorvoegsel
 * voor ("Aankomst 08:30"). Gebruik het alleen wanneer de waarde zonder kop niet
 * te plaatsen is — een ID, een categorie of een statuswoord spreekt voor zich.
 *
 * KOLOMBREEDTES MOETEN ACTIEF UITGEZET WORDEN. Een vaste breedte (.col-id{width:78px})
 * geldt ook op de <td>, niet enkel op de verborgen koprij, en een vaste breedte op een
 * flex-item bepaalt zijn basisgrootte — dus wikkelt de meta-regel. Gemeten in de eerste
 * versie hiervan: 318px aan cellen in 291px ruimte, waardoor "Actief" naar een derde
 * regel viel. Vandaar `width:auto` op de basisregel voor <td> hieronder; die is
 * specifieker dan een losse .col-*-klasse en zet ze dus allemaal in één keer uit.
 */
@media(max-width:640px){
  table.dtable,table.dtable tbody{display:block;}
  table.dtable thead{display:none;}
  table.dtable tr{position:relative; display:flex; flex-wrap:wrap; align-items:baseline; column-gap:6px; padding:var(--sp-3) 34px var(--sp-3) 0; border-bottom:1px solid var(--color-border);}
  table.dtable tr:last-child{border-bottom:none;}
  /* Geen vlakvulling meer op hover of selectie: in een platte lijst leest een
     gevuld blok als een kaartje, en dat is precies de vorm die hier weg moest.
     De selectie draagt een accentstreep links i.p.v. een achtergrond. */
  table.dtable tr:hover td,table.dtable tr.selected td{background:none;}
  table.dtable tr.selected{box-shadow:inset 3px 0 0 var(--color-accent); padding-left:var(--sp-3);}
  table.dtable td{flex:1 1 100%; width:auto; padding:0; border:0;}
  table.dtable td.cell-title{order:-1; font-size:14.5px; font-weight:600; color:var(--color-text); line-height:1.35;}
  table.dtable td.cell-meta{flex:0 0 auto; font-size:12px; color:var(--color-text-muted); padding-top:3px;}
  table.dtable td.cell-meta[data-label]::before{content:attr(data-label) " "; color:var(--color-text-muted); opacity:.75;}
  /* Puntje ná elke meta-cel die nog een meta-cel achter zich heeft. Op ::after en
     niet ::before, want ::before is al van het data-label. De cellen verschijnen
     in DOM-volgorde op de meta-regel — dat is het contract. */
  table.dtable td.cell-meta:has(~ td.cell-meta)::after{content:"·"; margin-left:6px; color:var(--color-border);}
  table.dtable td.cell-end{position:absolute; top:var(--sp-3); right:0; width:auto; flex:0 0 auto;}
  /* Een pil op de meta-regel weegt te zwaar: de omkadering verdwijnt, de
     betekenisdragende kleur blijft. Zo blijft "Actief" groen en "Voorgesteld"
     oranje zonder dat er drie blokjes naast elkaar staan. */
  table.dtable td.cell-meta .badge,table.dtable td.cell-meta .chip-scope{background:none; border:0; padding:0; font-size:12px; font-weight:600;}
}

/* ===== feedback — alert. Elke alert heeft een sluitknop (→ R-008) ===== */
.alert{display:flex; gap:10px; padding:12px 40px 12px 14px; border-radius:var(--radius-sm); font-size:13.5px; border:1px solid transparent; align-items:flex-start; position:relative;}
.alert[hidden]{display:none;}
.alert-info{background:var(--color-info-bg); color:var(--color-info-text); border-color:var(--color-info-border);}
.alert-success{background:var(--color-success-bg); color:var(--color-success-text); border-color:var(--color-success-border);}
.alert-warning{background:var(--color-warning-bg); color:var(--color-warning-text); border-color:var(--color-warning-border);}
.alert-danger{background:var(--color-danger-bg); color:var(--color-danger-text); border-color:var(--color-danger-border);}
.alert-close{position:absolute; top:50%; transform:translateY(-50%); right:8px; background:none; border:none; color:inherit; opacity:.55; cursor:pointer; width:22px; height:22px; display:flex; align-items:center; justify-content:center; border-radius:var(--radius-full); flex:none;}
.alert-close:hover{opacity:1; background:rgba(0,0,0,.07);}
.alert-close .icon-sm{width:12px; height:12px;}
/* <pw-alert> plaatst zich hier bovenop de pagina, niet in de normale flow
   (2026-08-09, Owner-vraag). pointer-events:none op de stack zelf houdt de
   pagina eronder klikbaar; enkel de individuele alerts zijn interactief. */
.alert-stack{position:fixed; top:var(--sp-4); right:var(--sp-4); z-index:300; display:flex; flex-direction:column; gap:var(--sp-3); width:min(360px, calc(100vw - 2 * var(--sp-4))); pointer-events:none;}
.alert-stack .alert{pointer-events:auto; box-shadow:var(--shadow-lg); animation:alert-in .18s ease-out;}
.alert-stack .alert.closing{animation:alert-out .15s ease-in forwards;}
@keyframes alert-in{from{opacity:0; transform:translateY(-8px);}to{opacity:1; transform:translateY(0);}}
@keyframes alert-out{from{opacity:1; transform:translateY(0);}to{opacity:0; transform:translateY(-8px);}}
@media (prefers-reduced-motion: reduce){.alert-stack .alert{animation:none;}}
.progress{height:8px; border-radius:var(--radius-full); background:var(--color-surface-alt); overflow:hidden;}
.progress > span{display:block; height:100%; background:var(--color-primary); border-radius:var(--radius-full);}
.progress.indet > span{width:30%; animation:indet 1.3s infinite ease-in-out;}
@keyframes indet{0%{margin-left:-30%;}100%{margin-left:100%;}}
.skeleton{background:linear-gradient(90deg,var(--color-surface-alt) 25%,var(--color-skeleton-highlight) 37%,var(--color-surface-alt) 63%); background-size:400% 100%; animation:sk 1.4s ease infinite; border-radius:var(--radius-sm);}
@keyframes sk{0%{background-position:100% 50%;}100%{background-position:0 50%;}}
.empty-state{text-align:center; padding:var(--sp-6); color:var(--color-text-muted);}
.empty-state .icon{width:32px; height:32px; margin-bottom:8px; color:var(--color-border);}
.toast{position:fixed; bottom:24px; left:50%; transform:translateX(-50%) translateY(20px); background:var(--color-text); color:#fff; padding:12px 20px; border-radius:var(--radius-sm); font-size:13.5px; box-shadow:var(--shadow-lg); opacity:0; visibility:hidden; transition:.2s; z-index:200; display:flex; align-items:center; gap:8px;}
.toast.show{opacity:1; visibility:visible; transform:translateX(-50%) translateY(0);}

/* ===== overlays ===== */
.modal-backdrop{position:fixed; inset:0; background:rgba(22,35,47,.5); display:flex; align-items:center; justify-content:center; opacity:0; visibility:hidden; transition:.15s; z-index:150; padding:var(--sp-4);}
.modal-backdrop.open{opacity:1; visibility:visible;}
.modal{position:relative; background:#fff; border-radius:var(--radius-md); width:380px; max-width:100%; max-height:85vh; overflow-y:auto; padding:var(--sp-5); box-shadow:var(--shadow-lg); transform:translateY(10px); transition:.15s;}
.modal-backdrop.open .modal{transform:translateY(0);}
.modal-head{display:flex; justify-content:space-between; align-items:flex-start; gap:10px; margin-bottom:var(--sp-4);}
.modal .actions{display:flex; justify-content:flex-end; gap:8px; margin-top:var(--sp-4);}
.modal-wide{width:560px;}

/* pw-edit-dialog/pw-view-dialog: iconknoppen bovenaan i.p.v. tekstknoppen
   onderaan (→ REQUEST "poolster compliance"). .close-x bestaat al (base.css)
   voor de sluitknop; hier hergebruikt voor elke ronde icoonknop in de
   kopregel, inclusief i-save, dat oplicht zodra er iets gewijzigd is. */
.modal-head-actions{display:flex; align-items:center; gap:4px; flex:none;}
.close-x.is-dirty{color:var(--color-primary); background:color-mix(in srgb, var(--color-primary) 12%, transparent);}
/* pw-edit-dialog: bevestigingstekst die onderaan het formulier vastplakt
   zodra i-close geweigerd wordt (dirty) — géén aparte pop-up, het
   formulier blijft zichtbaar; zie pw-edit-dialog.js #showConfirmPanel(). */
.modal-confirm-sticky{margin:var(--sp-4) 0 0; padding-top:var(--sp-3); border-top:1px solid var(--color-border); font-size:13px; color:var(--color-text-muted);}
.dropdown{position:relative; display:inline-block;}
.dropdown-menu{position:absolute; top:calc(100% + 6px); left:0; background:#fff; border:1px solid var(--color-border); border-radius:var(--radius-sm); box-shadow:var(--shadow-md); min-width:180px; opacity:0; visibility:hidden; transform:translateY(-4px); transition:.15s; z-index:80;}
.dropdown-menu.open{opacity:1; visibility:visible; transform:translateY(0);}
.dropdown-menu a{display:flex; align-items:center; gap:8px; padding:9px 14px; font-size:13.5px; color:var(--color-text); text-decoration:none;}
.dropdown-menu a:hover{background:var(--color-surface-alt);}
.dropdown-menu a .icon{width:15px; height:15px;}

/* ===== reis-specifieke elementen ===== */
.stepper{display:flex; align-items:flex-start;}
.step{flex:1; text-align:center; position:relative;}
.step .node{width:30px; height:30px; border-radius:50%; margin:0 auto 8px; display:flex; align-items:center; justify-content:center; font-size:13px; font-weight:700; border:2px solid var(--color-border); background:#fff; color:var(--color-text-muted); position:relative; z-index:2;}
.step::after{content:""; position:absolute; top:15px; left:50%; width:100%; height:2px; background:var(--color-border); z-index:1;}
.step:last-child::after{display:none;}
.step.done .node{background:var(--color-success); border-color:var(--color-success); color:#fff;}
.step.done::after{background:var(--color-success);}
.step.current .node{background:var(--color-primary); border-color:var(--color-primary); color:#fff; box-shadow:0 0 0 4px var(--color-info-bg);}
.step .label{font-size:12px; color:var(--color-text-muted);}
.step.current .label{color:var(--color-primary); font-weight:700;}
.map-demo{background:linear-gradient(135deg,var(--color-surface-alt),var(--color-border)); border-radius:var(--radius-md); padding:var(--sp-6); position:relative; min-height:180px;}
.pin{position:absolute; display:flex; flex-direction:column; align-items:center; cursor:pointer;}
.pin .head{width:26px; height:26px; border-radius:50% 50% 50% 0; background:var(--color-primary); transform:rotate(-45deg); display:flex; align-items:center; justify-content:center; box-shadow:var(--shadow-md);}
.pin .head span{transform:rotate(45deg); color:#fff; font-size:11px; font-weight:700;}
.pin .popover{transform:translate(-50%,0); left:50%; width:200px; top:auto; bottom:calc(100% + 10px);}
.pin:hover .popover,.pin:focus-within .popover{opacity:1; visibility:visible;}
