/* ═══════════════════════════════════════════════════════════════════════════
   CaveHosting — gedeelde COMPONENTFAMILIES

   Apart van merk.css omdat deze klassen in ELKE omgeving nodig zijn: klantportaal
   (merk.css) én admin-/serverpaneel (panel.css). Die twee hebben elk een eigen
   knoppen-, body- en kopsysteem; merk.css in het paneel meeladen botste meetbaar
   op `.btn` (contrast 4,06:1 op "Vernieuwen"). Deze klassen hebben NUL botsingen
   met panel.css -- gemeten, niet aangenomen -- dus ze kunnen los.
   ═══════════════════════════════════════════════════════════════════════════ */
/* Elke var() heeft een TERUGVALWAARDE: dit bestand wordt ook geladen in het
   adminpaneel en serverpaneel, waar merk.css (met de :root-tokens) NIET geladen
   wordt. Zonder terugval zou een rand of achtergrond daar leeg zijn. De tokens
   hier opnieuw definieren mag niet -- dat maakt check_tokens_een_definitie rood,
   en terecht: twee bronnen voor dezelfde waarde lopen uiteen. */
/* ── Meldingsbalk ─────────────────────────────────────────────────────────── */
.alert {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px; border-radius: 12px;
  border: 1px solid transparent; border-left-width: 3px;
  font-size: 13.5px; line-height: 1.45;
  background: rgba(255,255,255,.04); color: var(--ch-tekst, #E8EAF0);
}
.alert .alert-icoon { flex: none; width: 18px; height: 18px; margin-top: 1px; }
.alert .alert-tekst { min-width: 0; }
.alert .alert-kop { font-weight: 700; display: block; margin-bottom: 2px; }
.alert-info { border-color: rgba(91,140,255,.35); border-left-color: var(--ch-paars, #5B8CFF); }
.alert-ok { border-color: rgba(21,128,61,.35); border-left-color: var(--groen-diep, #15803D); }
.alert-waarschuwing { border-color: rgba(217,119,6,.35); border-left-color: #D97706; }
.alert-fout { border-color: rgba(220,38,38,.35); border-left-color: var(--rood-diep, #DC2626); }

/* ── Statuschip ───────────────────────────────────────────────────────────── */
/* Niet-klikbaar label. Is het chip WEL klikbaar (een filter), gebruik dan een
   knop met .btn-sm — die haalt 44px. Een statuschip is geen aanraakvlak. */
.statuschip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 20px;
  font-size: 11.5px; font-weight: 700; line-height: 1.6;
  border: 1px solid transparent; white-space: nowrap;
}
.statuschip::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.statuschip-aan { color: #6EE7A8; border-color: rgba(21,128,61,.4); background: rgba(21,128,61,.14); }
.statuschip-uit { color: #9AA3BE; border-color: rgba(255,255,255,.14); background: rgba(255,255,255,.05); }
.statuschip-bezig { color: #9CC0FF; border-color: rgba(91,140,255,.4); background: rgba(91,140,255,.14); }
.statuschip-fout { color: var(--rood-l); border-color: rgba(220,38,38,.4); background: rgba(220,38,38,.14); }
/* "Onbekend" is een MEETprobleem, geen toestand van de server. Eigen variant zodat
   het niet als storing leest -- zie ONTWERPSYSTEEM 4.x en de Gezondheid-badge. */
.statuschip-onbekend { color: #C9A227; border-color: rgba(201,162,39,.4); background: rgba(201,162,39,.12); }
/* "Wacht" = er wordt iets van de GEBRUIKER verwacht (betalen, antwoorden, lezen). Amber,
   want dat is de kleur die dat in dit systeem overal draagt -- `.kpi .k-d.wacht`, de
   aandachtskaart op het portaal, de huidige stap in `.stappen` hieronder.

   Waarom dit een eigen variant is en niet `.statuschip-onbekend` hergebruikt (30 juli
   2026, scherm 2): die twee zijn allebei amber maar betekenen het tegenovergestelde.
   "Onbekend" zegt WIJ konden het niet meten -- niemand hoeft iets te doen. "Wacht" zegt
   JIJ moet iets doen. Ze op één klasse gooien maakt van amber weer een kleur zonder
   betekenis, precies wat de badge-regel in AGENTS.md verbiedt: een signaal dat je leert
   negeren is erger dan geen signaal.
   Dit is een VARIANT op een bestaande familie, geen nieuwe familie -- de route die
   ontwerpregel 14.6 aanwijst ("alleen als variant ... wanneer het échte gebruik hem mist").
   Het echte gebruik: "Wacht op betaling" bij een website-aanvraag en "nieuw" bij een
   ongelezen bericht van een bezoeker. */
.statuschip-wacht { color: var(--geel-l, #E8B661); border-color: rgba(224,168,63,.4); background: rgba(224,168,63,.14); }
/* Onderhoud = door ONS stilgezet. Bewust geen rood: het is geen storing, en het als
   storing tonen zou de Gezondheid-badge laten oplopen met werk dat niemand hoeft te
   doen (dezelfde reden dat die badge alleen `kapot` telt). Overgenomen uit .gez-chip,
   dat op 26 juli naar deze gedeelde component is gemigreerd. */
.statuschip-onderhoud { color: #A9B2C7; border-color: rgba(255,255,255,.16); background: rgba(255,255,255,.06); }

/* ── Formulierbasis ───────────────────────────────────────────────────────── */
.veld { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.veld-label { font-size: 12.5px; font-weight: 600; color: var(--ch-tekst-zacht, #9AA3BE); }
/* 12,5px en niet 11,5: dit zijn zinnen die de klant leest, geen labels — regel 6.1
   (bodytekst min. 12,5px). Het is ook de GROOTSTE van de vijf `.hint`-definities die
   dit vervangt (11,5 / 12 / 12,5px), dus niemand krijgt kleinere tekst dan voorheen.
   Kleine tekst was een groot deel van Cihans "goedkoop"-oordeel op 26 juli. */
.veld-hulp { font-size: 12.5px; line-height: 1.45; color: var(--ch-tekst-zacht, #9AA3BE); }
.veld-fout { font-size: 12.5px; color: var(--rood-l); }
/* Twee selectors, ÉÉN definitie. `.veld > input` staat erbij omdat het portaal zijn
   velden via afstammelingen styleerde (`.veld input, .veld select, .veld textarea`) in
   plaats van via een klasse; zo hoeven negen elementen niet aangeraakt te worden en is
   er toch één bron. 46px en 14px overgenomen van de bestaande portaalregel: dat is
   ruimer dan de 44px-norm, dus niets wordt kleiner dan het was. */
.invoer,
.veld > input, .veld > select, .veld > textarea {
  min-height: 46px; padding: 11px 13px; border-radius: 10px;
  background: rgba(255,255,255,.04); color: var(--ch-tekst, #E8EAF0);
  border: 1px solid var(--ch-lijn, rgba(255,255,255,.12));
  font-family: inherit; font-size: 14px; width: 100%; max-width: 100%;
}
.invoer:focus-visible, .veld > input:focus-visible, .veld > select:focus-visible, .veld > textarea:focus-visible { outline: 2px solid var(--ch-merk-accent, #7aa2ff); outline-offset: 1px; border-color: transparent; }
.invoer[aria-invalid="true"], .veld > [aria-invalid="true"] { border-color: var(--rood-diep, #DC2626); }
.invoer:disabled, .veld > input:disabled, .veld > select:disabled, .veld > textarea:disabled { opacity: .5; cursor: not-allowed; }
/* 110px overgenomen van de bestaande portaalregel: niets wordt kleiner. */
textarea.invoer, .veld > textarea { min-height: 110px; resize: vertical; }

/* ── Toast ────────────────────────────────────────────────────────────────── */
/* Eén vorm. De zes losse meldingsfuncties in de templates consolideren naar deze
   klasse is een apart punt in TODO.md -- de JS-consolidatie raakt 3 templates. */
.toast {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-radius: 12px;
  background: #1B2138; color: var(--ch-tekst, #E8EAF0);
  border: 1px solid var(--ch-lijn, rgba(255,255,255,.12));
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
  font-size: 13.5px; max-width: min(420px, calc(100vw - 32px));
}
.toast-ok { border-left: 3px solid var(--groen-diep, #15803D); }
.toast-fout { border-left: 3px solid var(--rood-diep, #DC2626); }
.toast-laag {
  position: fixed; z-index: 9999; right: 16px; bottom: 16px;
  display: flex; flex-direction: column; gap: 8px; pointer-events: none;
  max-width: calc(100vw - 32px);
}
.toast-laag .toast { pointer-events: auto; animation: toast-in .18s ease-out; }
.toast-uit { opacity: 0; transform: translateY(6px); transition: opacity .2s, transform .2s; }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
/* Beweging is hier decoratie, geen informatie: wie er gevoelig voor is krijgt de
   melding gewoon zonder schuif. */
@media (prefers-reduced-motion: reduce) {
  .toast-laag .toast { animation: none; }
  .toast-uit { transition: none; }
}
/* Op telefoon onderaan over de volle breedte: rechtsonder valt een melding van
   420px buiten een venster van 390px. */
@media (max-width: 520px) {
  .toast-laag { right: 12px; left: 12px; bottom: 12px; }
  .toast-laag .toast { max-width: 100%; }
}

/* ── Iconen ───────────────────────────────────────────────────────────────── */
/* Zie panel/templates/_iconen.html. Producticonen zijn SVG, nooit emoji
   (ONTWERPSYSTEEM 6.2, GOEDGEKEURD 26 juli 2026): emoji renderen per platform
   anders, zijn niet te kleuren en geven een schermlezer een naam die je niet kunt
   sturen. 1em zorgt dat het icoon meeschaalt met de tekst ernaast; currentColor
   dat het dezelfde kleur krijgt -- dus ook automatisch goed in elke variant. */
.ico {
  width: 1em; height: 1em; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
  vertical-align: -.125em;
}
.ico-lg { width: 1.5em; height: 1.5em; }
.ico-xl { width: 2em; height: 2em; }

/* "Bezig" mag pulseren: er wordt echt gemeten (ONTWERPSYSTEEM 4.3 — animatie alleen
   bij echte voortgang). "Wacht op betaling" of "onbekend" blijft statisch. */
.statuschip-bezig::before { animation: statuschip-puls 1.1s ease-in-out infinite; }
@keyframes statuschip-puls { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) {
  .statuschip-bezig::before { animation: none; }
}

/* ── Taalwissel (NL/EN/TR) ────────────────────────────────────────────────── */
/* Stond in VIER schermen bijna identiek, met vier verschillen: 12,5px vs 11,5px,
   `--card` vs `--card2`, `--stil` vs `--muted`, `--helder` vs `--bright`, en
   portal_website had `#3D6BD6` hardgecodeerd waar de rest `var(--ch-knop)` gebruikt.
   Eén definitie, met GESCHAKELDE terugval zodat hij werkt met welke tokennaam het
   scherm ook definieert -- zo hoeven die schermen hun eigen namen niet te wijzigen.
   12,5px gekozen: de grootste van de vier, dus niets wordt kleiner. */
.taalwissel {
  display: inline-flex; gap: 2px; border-radius: 10px; padding: 2px;
  background: var(--card, var(--card2, #14161F));
  border: 1px solid var(--lijn, var(--border, rgba(255,255,255,.12)));
}
.taalwissel button {
  background: none; border: 0; cursor: pointer;
  color: var(--stil, var(--muted, #9AA3BE));
  font-family: inherit; font-size: 12.5px; font-weight: 700;
  padding: 6px 10px; border-radius: 8px; min-height: 44px;
}
.taalwissel button:hover { color: var(--helder, var(--bright, #fff)); }
.taalwissel button[aria-pressed="true"] { background: var(--ch-knop, #3D6BD6); color: #fff; }


/* ═══ Kerncijferkaart (KPI) — EEN component, twee maten ═══════════════════════
   Stond tot 27 juli 2026 twee keer: `.kpi` in het BI-portaal en `.statkaart` in
   het klantoverzicht. Zelfde ding (label / getal / duiding), andere waardes — dus
   geen enkele CSS-check zag het, en ik bouwde de tweede terwijl de eerste bestond.
   Het verschil was maat, niet betekenis; dat hoort een variant te zijn.
   Kleuren komen uit de merktokens; het BI-scherm overschrijft ze met zijn eigen
   palet (dat is een thema-afwijking, geen tweede component).

   TERUGVALLEN TOEGEVOEGD 30 juli 2026. Acht regels hieronder gebruikten
   `var(--lijn)`, `var(--stil)`, `var(--geel)`, `var(--groen)`, `var(--helder)` en
   `var(--ch-font-kop)` KAAL -- terwijl de kop van dit bestand voorschrijft dat elke
   var() een terugval heeft, juist omdat het ook in het paneel wordt geladen waar
   merk.css ontbreekt. Die zes tokens bestaan alleen in merk.css. In een browser
   nagemeten in de paneelstapel: de rand van `.kpi` verdween volledig
   (`border-style:none`), het label werd net zo helder als de rest, en `.k-d.wacht`
   en `.kpi.groot .k-d` verloren hun gele en groene SIGNAALkleur -- precies de
   kleuren die de betekenis dragen. Vandaag raakte dat geen scherm (`.kpi` staat
   alleen in portal_bi.html), maar het eerste adminscherm dat hem gebruikt zou een
   randloze, kleurloze kaart krijgen zonder dat er iets faalt.

   TWEE niveaus en geen harde vangnetkleur, anders dan bij `.lijst` hierboven. Een
   derde niveau zou vijf hexwaarden toevoegen, en `check_kleurdrift_groeit_niet` is
   een ratel die alleen mag dalen. Het vangnet is hier een CHECK in plaats van een
   kleur: `preflight.check_componenten_terugval` weigert elke var() in dit bestand
   waarvan geen enkel niveau in beide tokenbronnen bestaat. */
.kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:12px}
.kpis.breed{grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px;margin-bottom:34px}
.kpi{background:rgba(120,150,230,.06);border:1px solid var(--lijn, var(--border));border-radius:12px;padding:10px 11px}
.kpi .k-l{font-size:10.5px;color:var(--stil, var(--muted));font-weight:700;text-transform:uppercase;letter-spacing:.05em}
.kpi .k-w{display:flex;align-items:baseline;gap:7px;margin-top:3px}
.kpi .k-v{font-size:19px;font-weight:800;font-family:var(--ch-font-kop, inherit)}
.kpi .k-e{font-size:13px;color:var(--stil, var(--muted))}
.kpi .k-d{font-size:10.5px;font-weight:700;margin-top:1px}
/* Toon van de duidingsregel. Als KLASSE, niet inline: ui_check weigert inline
   stijlen en terecht -- die ontsnappen aan elke centrale wijziging. */
.kpi .k-d.wacht{color:var(--geel, var(--yellow))}
.kpi .k-d.stil{color:var(--stil, var(--muted))}
/* Derde toon, 31 juli 2026. Zonder deze variant stond bij 97% een RODE balk boven een
   AMBEREN woord: twee dragers van dezelfde boodschap die verschillend kleuren, precies
   de divergentie die de drempelladder moet voorkomen. Nagerekend op het `.kpi`-vlak
   (rgba(120,150,230,.06) over --card): `--rood-l` haalt 5,98:1 en dus AA voor kleine
   tekst — dit is 10,5px, dus die eis geldt. */
.kpi .k-d.vol{color:var(--rood-l)}

/* ── Balkvariant — een verhouding die je van een meter afstand leest ───────────
   HERZIEN BESLUIT (Cihan, 31 juli 2026). Op 30 juli verviel de gekleurde balk met
   `.t-balk` en stond er expliciet bij: "alleen terug als variant op `.kpi`, en alleen
   als het échte gebruik hem mist". Dat gebruik mist hem: het klantportaal toont
   "65% in gebruik" in tekst, maar je moet het LEZEN om te weten of dat veel is. De
   adminkant heeft die balk wél (`.bar-track`/`.bar-fill` in panel.css) en dat is
   precies het verschil dat Cihan aanwees.

   Dit is de route van ontwerpregel 14.6: uitbreiden gaat via de KIT, niet via een
   scherm. `.t-balk` kwam ooit terug als schermeigen familie; deze niet.

   KLEUR IS HIER NOOIT DE ENIGE DRAGER (regel 4.1). `.k-d` staat eronder en zegt het
   in woorden ("65% in gebruik", "rustig"/"bezig"/"druk"). De balk is de tweede,
   snellere drager van dezelfde boodschap — geen vervanging ervan. Een `.k-vul`
   zonder `.k-d` erbij is dus een fout in het scherm, niet in de kit.

   ÉÉN DREMPELLADDER, hier gedefinieerd, zodat kleur en tekst niet uiteen kunnen
   lopen: gezond < 70% · druk 70-89% · vol >= 90%. Dat zijn exact de grenzen die
   `_metricsHtml` al voor de WOORDEN gebruikte (rustig/bezig/druk); ze staan nu één
   keer in CSS en één keer in de JS-helper die de klasse kiest, en de regressietest
   houdt die twee gelijk.

   GEEN BALK ZONDER ECHTE NOEMER. Dezelfde discipline als bij `players_max` in
   quick-status: een node-agent meldt geen maximum, en een balk met een verzonnen
   noemer is erger dan geen balk. Het scherm laat `.k-balk` dan gewoon weg.

   Contrast: de vulling moet 3:1 halen tegen de TRACK (WCAG 1.4.11, niet-tekstueel).
   Een half-doorzichtige kleur heeft geen eigen contrast, dus eerst SAMENSTELLEN en
   dan rekenen -- de val die op 30 juli de gevaar-knop opleverde (1,56:1 samengesteld).
   Nagerekend op track rgba(255,255,255,.06) over het `.kpi`-vlak over `--card`:
   groen 6,53:1 · amber 6,53:1 · rood 5,01:1. Alle drie ruim boven 3:1.
   De track staat op .06 en niet lichter omdat dat exact de waarde is die de
   admin-meters gebruiken (`body.server-page .bar-track`, `.hh-bar` in panel.css) --
   dezelfde meters die Cihan als smaakreferentie aanwees. Eén tint, twee omgevingen. */
/* NIET gescoped onder `.kpi`, en dat is het werkende deel. De eerste versie schreef
   `.kpi .k-balk` en `.kpi .k-vul`; buiten een tegel had de vulling daardoor geen
   achtergrond, geen hoogte en geen `display`, dus de mini-variant in een rijkopregel
   rendeerde als een doorzichtig blokje van 30x4. Gezien op de render van 31 juli 2026:
   het gekleurde getal stond er wél, de strookjes niet. Alleen de MARGE binnen een tegel
   is tegel-eigen; de rest hoort bij de balk zelf. */
.k-balk{height:6px;border-radius:99px;overflow:hidden;background:rgba(255,255,255,.06)}
.kpi .k-balk{margin-top:8px}
.k-vul{display:block;height:100%;width:0;border-radius:99px;
       background:var(--groen, var(--green));
       transition:width .5s cubic-bezier(.1,.8,.2,1)}
.k-vul.druk{background:var(--geel, var(--yellow))}
.k-vul.vol{background:var(--rood-l)}
@media (prefers-reduced-motion:reduce){ .k-vul{transition:none} }

/* ── Mini-variant: het kleursignaal zónder uitklappen ─────────────────────────
   Eis Cihan, 31 juli 2026, op de renders van ronde 2b: "de kleur moet zichtbaar zijn
   ZONDER uitklappen — nu zit alle meterkleur achter een klik". Terecht: een klant die
   zijn portaal opent, ziet een rij en moet raden of er iets aan de hand is.

   Dit is een VARIANT op de meterbalk en geen tweede familie (ontwerpregel 14.6): zelfde
   klassen, zelfde drempelladder, zelfde kleuren — alleen kleiner, en los van `.kpi`
   zodat hij ook in een rijkopregel kan staan. De volledige meters mét cijfers blijven
   in het uitgeklapte paneel; hier staat alleen het signaal.

   `display:inline-block` en `vertical-align`: hij staat in een tekstregel naast
   "17/18 spelers online", niet in een raster. 30px is bewust smal — dit is een
   stemmingsmeter, geen aflezing. Wie het getal wil, klapt uit.

   RUST BEWAKEN (Cihans woorden): geen animatie hier. De grote balk mag zich vullen als
   je hem opent; drie strookjes die bij elke poll bewegen in een lijst van vijf rijen is
   onrust, geen leven. */
.k-balk.mini { display:inline-block; width:30px; height:4px; margin:0; flex:none;
               vertical-align:middle; }
.k-balk.mini .k-vul { transition:none; }
.k-balk.mini + .k-balk.mini { margin-left:3px; }

/* Grote variant: klantportaal-overzicht, waar de kaart de eerste blik draagt. */
.kpi.groot{padding:20px 22px;border-radius:16px;
           background:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.015))}
.kpi.groot .k-l{font-size:11.5px;letter-spacing:.07em;font-weight:600}
.kpi.groot .k-w{margin-top:12px}
.kpi.groot .k-v{font-size:30px;font-weight:700;letter-spacing:-.03em;color:var(--helder, var(--text));line-height:1}
.kpi.groot .k-d{margin-top:9px;font-size:12.5px;color:var(--groen, var(--green))}

/* ═══ Uitklapbare dienstrij — gedeeld tussen klantportaal en adminpaneel ═══════
   Stond in `scherm-portal_overzicht.css` en was daarmee portaal-only. Sinds de
   gebruikerstab dezelfde vorm gebruikt is hij aantoonbaar gedeeld, en twee kopieën
   van hetzelfde component lopen gegarandeerd uiteen — dezelfde wet die hier al geldt
   voor `buildModal`, de iconen en de tokens.

   LET OP de dubbele terugval in elke kleur. De twee schermen spreken verschillende
   tokentalen: het klantportaal gebruikt `--lijn`/`--stil`/`--tekst`, het adminpaneel
   `--border`/`--muted`/`--text`. Gemeten 28 juli 2026 — geen van beide sets bestaat in
   het andere scherm. Zonder terugval zou dit component in het adminpaneel randloos en
   kleurloos renderen. De derde waarde is de harde vangnetkleur voor het geval geen van
   beide bestaat.
   Dit is een BRUG, geen eindstation: op termijn horen die twee tokensets één te zijn.
   ═════════════════════════════════════════════════════════════════════════════ */
/* Uit het contract: radius 14px, VASTE rand, geen lichtrand bovenop. */
.lijst { border:1px solid var(--lijn, #262A36); border-radius:14px;
         background:var(--card, #14161F); overflow:hidden; }
.rij + .rij { border-top:1px solid var(--lijn, var(--border, rgba(255,255,255,.075))); }

/* Vaste tracks, geen `auto`: elke rij is een eigen grid, dus met `auto` bepaalt de
   inhoud van díé rij de kolombreedte en schuiven de kolommen per rij. Dat gaf op
   28 juli 54px scheefstand tussen twee statuschips. */
/* De KOP is een div, niet een knop -- want er staat een echte actieknop in (starten
   of stoppen, zonder eerst uit te klappen), en een knop in een knop is ongeldige HTML.

   De uitklapknop is een gewone CEL die het merkteken en de naam bevat: de scanbare
   identiteit van de rij. Status, meta, actie en pijl staan ernaast.

   Een eerdere versie legde de knop als absoluut vlak OVER de hele rij, zodat je ook
   op lege ruimte kon klikken. Die bleef de klik op de actieknop opslokken, ook met
   een hogere z-index -- gemeten met elementFromPoint, bij kloppende DOM en kloppende
   computed styles. Een indeling die stapelregels nodig heeft om te werken, breekt
   later weer; deze heeft ze niet nodig. */
/* KOLOMVERDELING, herzien 31 juli 2026 na een render op 1440px. De naamkolom kreeg
   `1fr` en dus alles wat overbleef: gemeten 528px voor "Cavecatering /
   cavecatering.nl", terwijl de metakolom van 214px "Groei · €24,50/mnd · volgende
   betaling 17-08-2026" over twee regels moest breken. Ruimte stond op de plek waar
   niets stond, en ontbrak op de plek waar de tekst was.
   De naamkolom houdt een ONDERgrens in plaats van de rest op te slokken; wat overblijft
   gaat naar de meta. Alle drie de consumenten (portaaloverzicht, Mijn website,
   kit-etalage) delen deze verdeling — daarom staat hij hier en niet in een scherm.
   Dat is geen detail: op 28 juli stonden deze breedtes wél in het schermbestand, en
   het adminpaneel (dat die stylesheet niet laadt) stapelde daardoor alles in één kolom. */
.rij-kop { display:grid; align-items:center; gap:14px; width:100%;
           grid-template-columns:minmax(220px,1fr) 136px minmax(0,264px) 146px 22px;
           padding:13px 16px; color:inherit; font:inherit; text-align:left; }
.rij-open { display:grid; grid-template-columns:38px minmax(0,1fr); align-items:center;
            gap:14px; min-width:0; min-height:44px; padding:0; margin:0;
            background:none; border:0; color:inherit; font:inherit; text-align:left;
            cursor:pointer; }
.rij-kop:hover { background:rgba(255,255,255,.025); }
.rij[open] > .rij-kop { background:rgba(255,255,255,.03); }
.rij-open:focus-visible { outline:none;
                          box-shadow:0 0 0 2px var(--ch-paars, #5B8CFF); border-radius:8px; }

.mark { width:38px; height:38px; border-radius:11px; display:grid; place-items:center;
        background:var(--bg2, rgba(255,255,255,.05));
        border:1px solid var(--lijn, var(--border, rgba(255,255,255,.075)));
        box-shadow:inset 0 1px 0 0 rgba(255,255,255,.06);
        font:700 12px/1 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
        color:var(--stil, var(--muted)); }
/* `.game` en `.eigen` delen alles behalve de VULLING: rand, tekstkleur en de
   binnenschaduw zijn per definitie hetzelfde, want het is dezelfde vorm met een andere
   kleur. Samen in één regel i.p.v. gekopieerd — anders staat `#fff` er twee keer en
   loopt de ene variant ooit stil uit de pas met de andere. */
.mark.game,
.mark.eigen { border-color:transparent; color:#fff;
              box-shadow:inset 0 1px 0 0 rgba(255,255,255,.22); }
.mark.game { background:linear-gradient(160deg, var(--ch-paars-l, #9CC0FF), var(--ch-paars, #5B8CFF)); }
/* ── Eigen gezicht per dienst ─────────────────────────────────────────────────
   Cihan, 31 juli 2026: de adminkaarten hebben elk een eigen gezicht, de klantrijen
   niet -- twee websites naast elkaar dragen exact hetzelfde grijze globe-icoon.

   DE BRON BESTOND AL en werd alleen niet gebruikt: `servers.icon` en `servers.color`
   staan sinds het begin in de tabel, zijn per server instelbaar in het adminpaneel
   (`#srvIcon`), en drijven daar de serverkaart (`home.html`, `.card-icon` met
   `{{ srv.color }}22` / `44`). Dit is dus geen nieuw mechanisme maar dezelfde bron,
   nu ook aan de klantkant -- en daarmee meteen paneel-first: Cihan wijzigt de kleur
   in het paneel, niet in code.

   DE VORM BLIJFT VAN DE KIT, alleen de KLEUR komt van de dienst. Daarom twee custom
   properties die het scherm inline zet: dat is DATA (welke kleur hoort bij deze
   dienst), geen opmaak -- de opmaak staat hier. Zet het scherm ze niet, dan valt de
   mark terug op het merkblauw en verandert er niets.

   Elke keten eindigt op een token dat in de ANDERE stapel bestaat (`--ch-paars-l` in
   merk.css, `--accent2` in panel.css) en niet op een hexwaarde. Dat is geen
   spitsvondigheid: `check_componenten_terugval` eist dat elke var() in beide stapels
   iets oplevert, en `check_kleurdrift_groeit_niet` telt elke losse hex. Twee tokenlagen
   voldoen aan allebei -- exact het patroon dat het `.kpi`-blok hierboven al gebruikt.

   HET ICOON BLIJFT EEN ICOON (regel 6.2: kernproducticonen SVG, emoji alleen
   ondersteunend; regel 3.1: één visuele grammatica voor alle diensten). Op 30 juli
   is de letter-als-merkteken bij websites bewust vervangen door het globe-pictogram,
   omdat een letterblokje naast een pictogram leest als twee systemen. Die keuze
   blijft staan; het onderscheid zit nu in de KLEUR, niet in de vorm.

   DE VORM IS DIE VAN `.mark.game` -- een verloop van licht naar de kleur, met een wit
   icoon. Dat is bewust GEEN tint van 13% zoals de adminkaart: die versie stond hier
   eerst, en op de render van 31 juli 2026 verloor de gameserverrij daarmee zijn
   massieve blauwe merkteken en werd het een dof vlakje. Meer leven was juist de
   opdracht. Een dienst met de standaardkleur rendert nu vrijwel identiek aan de
   goedgekeurde `.mark.game`; alleen wie een eigen kleur heeft, ziet verschil. */
.mark.eigen { background:linear-gradient(160deg, var(--mark-licht, var(--ch-paars-l, var(--accent2))),
                                                 var(--mark-op, var(--ch-paars, var(--accent)))); }
.mark .ico { width:18px; height:18px; }

/* `display:block` is hier het werkende deel: het zijn <span>s in een <span>, dus
   inline — zonder dit plakt de naam tegen de regel eronder. */
.rij-naam { min-width:0; }
.rij-naam .naam { display:block; font-weight:600; font-size:15px; letter-spacing:-.012em;
                  color:var(--helder, var(--text, #E9EDF6)); overflow:hidden;
                  text-overflow:ellipsis; white-space:nowrap; }
.rij-naam .onder { display:block; margin-top:3px;
                   color:var(--stil, var(--muted, #99A1B3));
                   font:400 11.5px/1.4 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
                   overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* Derde regel: MICRO-DATA — het levende cijfer van deze dienst (31 juli 2026).
   "X/Y spelers" bij een draaiende server, "128 bezoekers · bijgewerkt 3 dagen geleden"
   bij een live site. Cihan: het portaal heeft de structuur maar mist de puls.

   Waarom onder de NAAM en niet als zesde rasterkolom: `.rij-kop` wordt door drie
   schermen gebruikt (portaaloverzicht, Mijn website, de kit-etalage). Een kolom erbij
   zou bij de twee andere een lege cel opleveren — en een kolom die bij tweederde van
   zijn gebruikers leeg is, is precies het lege vlak van ontwerpregel 13.4. Onder de
   naam kost het niets voor wie hem niet vult.

   Niet monospace zoals `.onder`: dat is een ADRES (je leest hem teken voor teken),
   dit is een zin. `tabular-nums` blijft, want deze cijfers veranderen tijdens het
   pollen en mogen niet verspringen. */
.rij-naam .micro { display:block; margin-top:4px;
                   color:var(--stil, var(--muted));
                   font-size:12px; line-height:1.35;
                   font-variant-numeric:tabular-nums;
                   overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rij-naam .micro b { color:var(--tekst, var(--text)); font-weight:600; }
/* Het CIJFER kleurt mee met de zwaarste meter van deze rij — dezelfde drempelladder en
   dezelfde namen als `.k-vul`, zodat er één vocabulaire is. Alleen het vetgedrukte
   getal, niet de hele zin: dan blijft "spelers online" rustig grijs en springt precies
   het ding eruit dat veranderd is. Kleur is hier nooit de enige drager — het getal
   staat er letterlijk naast, en de strookjes dragen hetzelfde nog eens. */
.rij-naam .micro.druk b { color:var(--geel, var(--yellow)); }
.rij-naam .micro.vol b { color:var(--rood-l); }
/* De strookjes staan aan het eind van de regel, met wat lucht ervoor. */
.rij-naam .micro .k-balk.mini:first-of-type { margin-left:8px; }
/* `display:block` hierboven VERSLAAT het `hidden`-attribuut -- `hidden` is niets
   anders dan een `display:none` uit het browserstijlblad en verliest van elke eigen
   display. Dat is exact de val die ontwerpregel 15.6 beschrijft en die `.nav-badge`
   ooit een kale `0` liet tonen. De consument verbergt deze regel met `hidden` zolang
   er geen meting is, dus het component dekt het hier zelf af in plaats van elke
   aanroeper `style.display` te laten zetten. Specificiteit 0,3,0 wint van 0,2,0, dus
   `!important` is niet nodig. */
.rij-naam .micro[hidden] { display:none; }

/* `margin-left:0` is het werkende deel, niet `justify-self`: `.chip` draagt
   `margin-left:auto`, en een auto-marge wordt in een grid VÓÓR de uitlijning
   afgehandeld — `justify-self` legt het daar tegen af. Gemeten, niet beredeneerd. */
.rij-kop > .chip, .rij-kop > .statuschip { justify-self:start; margin-left:0; }
.rij-kop > .meta { font-size:12.5px; color:var(--stil, var(--muted, #99A1B3));
                   text-align:right; line-height:1.45; }
.rij-kop > .meta b { color:var(--tekst, var(--text, #E9EDF6)); font-weight:600;
                     white-space:nowrap; font-variant-numeric:tabular-nums; }

/* `grid-column:-2` zet de pijl ALTIJD in de laatste kolom, ongeacht hoeveel buren er
   zijn. Zonder dit viel hij naar een tweede rasterregel zodra het actieslot bij het
   renderen nog leeg was (0x0, want de knoppen komen pas via JS): de automatische
   plaatsing telde dan anders. Gemeten met getBoundingClientRect, 28 juli. */
.pijl { color:var(--stil, var(--muted, #99A1B3));
        font:400 13px/1 ui-monospace, Menlo, monospace; justify-self:end;
        grid-column:-2; grid-row:1;
        transition:transform .18s ease; }
/* Zelfde reden: het actieslot houdt zijn kolom bezet ook als het (nog) leeg is. */
.rij-kop > .rij-actie { grid-column:-3; grid-row:1; min-height:1px; }
.rij[open] .pijl { transform:rotate(90deg); color:var(--ch-paars, #5B8CFF); }

/* Het paneel ligt VERZONKEN onder de rij: een tint donkerder, met een inwendige
   schaduw aan de bovenrand. Zo lees je dat het eronder vandaan komt, zonder een
   slagschaduw die op dit canvas toch niet werkt (gemeten 27 juli: die geeft op bijna
   zwart een modderige halo). */
.paneel { display:none; }
.rij[open] .paneel { display:block; animation:rijZakUit .2s ease; }
@keyframes rijZakUit { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:none; } }
/* Contract: het paneel heeft GEEN eigen achtergrond of scheidingslijn -- het hoort
   bij de rij erboven en springt in tot 64px, zodat het onder de naam uitlijnt. */
.paneel-binnen { padding:0 16px 16px 64px; display:flex; flex-direction:column;
                 gap:14px; }
@media (max-width:820px) { .paneel-binnen { padding-left:18px; } }

@media (max-width:820px) {
  .rij-kop { grid-template-columns:minmax(0,1fr) 20px !important; row-gap:9px;
             padding:14px 16px; }
  /* `.rij-pakket` hoorde in deze lijst en stond er niet (gemeten 28 juli 2026 op
     390px). Elk kind dat hier NIET genoemd wordt, plaatst het raster automatisch --
     en dat is dan kolom 2, de kolom van 20px waar de pijl in staat. Gevolg:
     "Professional" werd "Profes". Een vergeten kind in een expliciete lijst is een
     stille fout: er gaat niets kapot, er valt alleen tekst weg. */
  /* `.rij-pakket` stond in deze lijst en in twee regels hieronder, maar had op
     31 juli 2026 NUL gebruikers in de templates (gemeten over alle 29). De klasse is
     op 30 juli vervallen toen de pakketnaam de metaregel in ging; de CSS bleef staan.
     Weg in plaats van genoteerd — dode regels in een gedeeld bestand zijn precies wat
     een volgende sessie als bestaand component aanziet. */
  .rij-kop > .chip, .rij-kop > .statuschip, .rij-kop > .meta,
  .rij-kop > .rij-actie { grid-column:1; }
  /* `grid-row:auto` is hier het werkende deel. De basisregel zet `.rij-actie` op
     `grid-row:1` (nodig op desktop, zodat het slot zijn kolom bezet houdt terwijl het
     nog leeg is), en die bleef op smal gewoon staan -- alleen de KOLOM werd
     overschreven. Gevolg: de startknop werd naar de eerste rasterrij getrokken en liep
     naast de prijsregel het scherm uit ("▶ Starte" tegen de rand, gemeten op 390px).
     Een half overschreven plaatsing is erger dan geen: hij ziet er op desktop goed uit
     en breekt alleen op de breedte die je niet bekeek. */
  .rij-kop > .rij-actie { grid-row:auto; }
  .rij-kop > .meta { white-space:normal; text-align:left; }
  /* De microregel mag op smal wél afbreken: daar is de rij een kolom, dus er is geen
     buurcel die verspringt. Afkappen zou hier juist cijfers kosten. */
  .rij-naam .micro { white-space:normal; }
}

/* Twee panelen naast elkaar (administratie, hulp). Vouwt vanzelf onder 660px.

   De marge bovenaan is GEEN smaakkwestie maar een compensatie, en staat daarom pal
   naast de regel die hem nodig maakt. `.duo-cel .sectiekop:first-child` zet de
   sectiemarge op nul -- terecht, anders zakt de ene kolom lager dan de andere zodra
   alleen díe met een kop begint. Maar daarmee verdween ook de ruimte VOOR het hele
   blok: gemeten 28 juli stond de duo met exact 0px tegen de lijst erboven, terwijl
   elke andere sectie 30px lucht heeft. Het blok "Hulp & support" plakte dus tegen de
   websitelijst.
   Zet je die twee regels uit elkaar, dan haalt iemand later de nul weg zonder de
   marge, of andersom. */
.duo { display:grid; grid-template-columns:repeat(auto-fit, minmax(min(320px, 100%), 1fr));
       gap:20px; align-items:start; margin-top:var(--sectie-ruimte, 30px); }
.duo-cel { min-width:0; }
.duo-cel .sectiekop:first-child { margin-top:0; }

/* ═══ Lijstwerkbalk: zoeken + filteren ════════════════════════════════════════
   Hoort bij `.lijst` hierboven. Aanleiding: bij 8 gebruikers is een lijst prima te
   overzien, maar Cihan rekent op duizenden ("als je 10009 klanten hebt is het
   lastig") -- en dan is scrollen geen bediening meer.

   NIET `.order-chip` hergebruikt, hoewel de vorm bijna gelijk is. Die chip is de
   genummerde STAPPENBALK van het bestelformulier (1 Spel, 2 Pakket, …) met een
   `.done`-staat die "stap afgerond" betekent. Een filter kent geen afgeronde stap;
   die betekenis meesmokkelen maakt beide componenten onduidelijk. Wel exact de maten
   overgenomen (44px, 999px-radius, dezelfde randkleuren), zodat ze naast elkaar één
   taal spreken.

   Wat een filter WEL heeft en een stap niet: hij staat aan of uit, en dat is een
   toestand die een schermlezer moet horen. Vandaar `aria-pressed` als bron van de
   opmaak -- niet een losse `.actief`-klasse die je kunt vergeten bij te werken.
   ═════════════════════════════════════════════════════════════════════════════ */
.lijstbalk { display:flex; flex-wrap:wrap; align-items:center; gap:10px 14px;
             margin-bottom:14px; }

/* Het icoon zit IN het veld en niet ernaast, zodat de balk één vlak blijft. `pointer-
   events:none` want anders vangt het icoon de klik en krijgt het invoerveld geen focus. */
.lijstzoek { position:relative; flex:1 1 260px; min-width:0; max-width:420px; }
.lijstzoek .ico { position:absolute; left:13px; top:50%; transform:translateY(-50%);
                  width:16px; height:16px; pointer-events:none;
                  color:var(--stil, var(--muted, #99A1B3)); }
.lijstzoek input { width:100%; min-height:44px; padding:10px 14px 10px 38px;
                   border-radius:12px;
                   border:1px solid var(--lijn, var(--border, rgba(255,255,255,.12)));
                   background:var(--bg2, rgba(255,255,255,.03));
                   color:var(--helder, var(--text, #E9EDF6)); font-size:13.5px; }
.lijstzoek input::placeholder { color:var(--stil, var(--muted, #99A1B3)); }
.lijstzoek input:focus { outline:none; border-color:var(--ch-knop, #3D6BD6);
                         box-shadow:0 0 0 3px rgba(61,107,214,.25); }
/* Het kruisje van `type=search` is in WebKit 12px en dus onder de aanraakvloer; we
   verbergen het en bieden "Alles wissen" als echte knop aan. */
.lijstzoek input::-webkit-search-cancel-button { display:none; }

.filtergroep { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.filtergroep > .fg-l { font-size:11px; font-weight:700; letter-spacing:.06em;
                       text-transform:uppercase;
                       color:var(--stil, var(--muted, #99A1B3)); }

/* GEEN eigen chipklasse. componenten.css zegt het hierboven zelf bij `.statuschip`:
   "Is het chip WEL klikbaar (een filter), gebruik dan een knop met .btn-sm -- die
   haalt 44px." Mijn eerste versie negeerde dat en bouwde `.filterchip`; de check
   `check_componentfamilies_een_bron` wees hem terecht af, want dan waren er weer
   twee families voor dezelfde vorm. Dit zijn dus gewone knoppen, met alleen een
   TOESTAND erbij.

   `aria-pressed` stuurt die toestand -- niet een losse `.actief`-klasse. Eén bron:
   wie de knop aanzet zonder aria bij te werken, ziet meteen dat er niets oplicht,
   in plaats van een schermlezer stilzwijgend het verkeerde te laten vertellen. */
.filtergroep > .btn { min-height:44px; border-radius:999px; }
.filtergroep > .btn[aria-pressed="true"] {
  color:var(--helder, var(--text, #eef0f6));
  border-color:var(--ch-knop, #3D6BD6);
  background:rgba(61,107,214,.12);
}
/* Het getal ACHTER het label: hoeveel accounts vallen onder dit filter. Zo is een
   filter dat niets oplevert zichtbaar leeg vóór je erop klikt.
   Bewust NIET `.nav-badge` hergebruikt: die draagt `margin-left:auto` (bedoeld voor
   een zijbalkrij) en zou hier het getal naar de rechterrand duwen, los van zijn
   label. Dit is geen badge maar een telwoord binnen de knop. */
.filtergroep > .btn .fg-n {
  display:inline-block; margin-left:6px; padding:0 6px; border-radius:9px;
  background:rgba(255,255,255,.14); color:var(--helder, #eef0f6);
  font-size:10.5px; font-weight:700; font-variant-numeric:tabular-nums;
}
.filtergroep > .btn[aria-pressed="true"] .fg-n { background:var(--ch-knop, #3D6BD6); color:#fff; }

.lijsttelling { font-size:12.5px; color:var(--stil, var(--muted, #99A1B3));
                margin-bottom:10px; }
.lijsttelling b { color:var(--helder, var(--text, #E9EDF6)); font-weight:600;
                  font-variant-numeric:tabular-nums; }

.lijstpager { display:flex; align-items:center; justify-content:center; gap:10px;
              margin-top:14px; }
.lijstpager button { min-height:44px; min-width:44px; }
.lijstpager .lp-stand { font-size:12.5px; color:var(--stil, var(--muted, #99A1B3));
                        font-variant-numeric:tabular-nums; }

@media (max-width:600px) {
  .lijstzoek { flex:1 1 100%; max-width:none; }
}

/* ═══ Klantshell: desktopzijbalk + mobiele ondernavigatie ═════════════════════
   ONTWERPSYSTEEM regel 2.1, GOEDGEKEURD 26 juli 2026. Hoort bij
   `panel/templates/_klant_shell.html`; die partial is de enige die deze klassen zet.

   Waarom `position:fixed` en niet een grid-kolom: de partial wordt INGESLOTEN
   bovenaan de body, en een include kan de rest van de pagina niet omvatten. Een vaste
   zijbalk plus ruimte op de body geeft hetzelfde resultaat zonder dat elk scherm
   herbouwd hoeft te worden -- en is per scherm los terug te draaien.

   De ruimte hangt aan `body.klant-shell`, niet aan een broer-selector: de
   inhoudscontainer heet per scherm anders (`.wrap`, `.layout`, `main.main` -- gemeten
   28 juli). Een selector op die namen zou een nieuw scherm stil overslaan, met de
   tekst ONDER de zijbalk. `preflight.check_klantshell_body_klasse` dwingt de klasse af.
   ═════════════════════════════════════════════════════════════════════════════ */
/* De BODY is het frame. De partial staat als eerste in de body en de inhoud volgt,
   dus met een grid van `230px 1fr` vallen ze vanzelf naast elkaar -- geen wrapper
   nodig, en elk scherm dat de shell insluit krijgt het frame gratis.
   De zijbalk is daarom NIET meer `position:fixed`: een vast element staat buiten de
   stroom, en dan is er niets om een rand omheen te tekenen. */
/* HIER STAAT BEWUST GEEN `overflow` — en dat is geen slordigheid maar de fix van
   31 juli 2026 (melding Cihan: het klantportaal op productie liet zich op 100% zoom
   niet scrollen; op 80% paste alles nog nét in beeld en leek er niets aan de hand).

   WAAROM `overflow:hidden` HIER DE PAGINA SLOOPT. Deze klasse zit op de `<body>`.
   De browser past de overflow van de body toe op de VIEWPORT zodra `html` zelf
   `visible` is in beide assen (CSS Overflow 3, "Overflow Viewport Propagation") --
   en dan is de hele PAGINA `hidden`: geen schuifbalk, geen wiel, geen toetsenbord.
   Runtime gemeten op staging: /portal 483px onbereikbaar op 1440x760, 653px op
   1150x610, 983px op 390px. Alleen `window.scrollTo()` bewoog nog, want een
   hidden-scrollcontainer blijft programmatisch scrollbaar -- daardoor leek de eerste
   meting "ok" en verdient dit deze uitleg.

   Waarom /profile er NIET aan onderdoor ging, ondanks dezelfde klasse: dat scherm
   laadt als enige klantscherm ook `panel.css`, en daar staat `html { overflow-x: clip }`.
   Daarmee is `html` niet meer `visible` in beide assen en propageert er niets. Puur
   toeval dus, en het verdwijnt zodra `panel.css` daar weggaat (dat staat gepland).
   Reken daar niet op: de eigenschap hoort hier gewoon niet te staan.

   Hoe de afgeronde hoek nu wordt gehaald: de body knipt zijn EIGEN achtergrond al
   netjes binnen de radius. Alleen een kind dat zelf een vlak schildert tot tegen de
   rand heeft een eigen radius nodig, en dat is uitsluitend `.klantzijbalk` (hieronder).
   Gemeten vóór het weghalen: op /portal stak alleen de skip-link (`left:-9999px`) buiten
   de rand, en die hoort daar te staan.

   `preflight.check_pagina_scrollt` weigert deze eigenschap voortaan op elke
   body-klasse, met een breek-recept in `tools/prove_cases.py`. */
.klant-shell {
  max-width: 1560px; margin: 40px auto; padding: 0;
  display: grid; grid-template-columns: 230px 1fr; align-items: stretch;
  background: var(--bg, #0B0D13);
  border: 1px solid var(--lijn, #262A36);
  border-radius: 16px;
}
/* Alles wat geen KOLOM is, moet expliciet over de volle breedte -- anders belandt
   het als derde kolom naast de inhoud. */
.klant-shell > .klantonder,
.klant-shell > .toast-laag,
.klant-shell > .venster,
.klant-shell > .cave-modal-ov { grid-column: 1 / -1; }

.klantzijbalk {
  display: flex; flex-direction: column; gap: 22px; padding: 20px 14px;
  background: var(--bg2, #0D0F16);
  border-right: 1px solid var(--lijn, #262A36);
  min-width: 0;
  /* 15px en niet 16px: de schil heeft een rand van 1px, en de BINNENbocht van een
     hoek van 16px is precies die 1px kleiner. Met 16px steekt het vlak van de
     zijbalk een haartje door de bocht van de rand heen. Dit stond tot 31 juli 2026
     niet hier maar als `overflow:hidden` op de schil zelf -- zie de uitleg daar
     waarom dat de hele pagina onscrollbaar maakte. */
  border-radius: 15px 0 0 15px;
}

/* Merk: Outfit 700, iets negatieve letterspatiëring -- exact uit de specificatie. */
.kz-merk { display: flex; align-items: center; gap: 10px; padding: 0 6px;
           font: 700 15.5px 'Outfit', system-ui, sans-serif; letter-spacing: -.01em;
           color: var(--helder, #E6E8EF); }
.kz-merk img { border-radius: 7px; }

.kz-nav { display: flex; flex-direction: column; gap: 3px; }
/* 38px en niet 44px: het goedgekeurde ontwerp zet zijbalkitems op deze hoogte.
   Zie de uitzondering in tools/preflight.py -- daar staat waarom, met de bron erbij.
   Een zijbalkitem is bovendien een volle rij breed (202px), dus het RAAKVLAK is ruim;
   het is de hoogte die afwijkt, niet de aanraakbaarheid in de breedte. */
.kz-link { display: flex; align-items: center; gap: 10px; height: 38px;
           padding: 0 11px; border-radius: 10px; font-size: 13.5px; font-weight: 500;
           color: var(--stil, #99A1B3); border: 1px solid transparent; }
.kz-link:hover { background: rgba(255,255,255,.04); color: var(--helder, #E6E8EF); }
.kz-link .ico { width: 16px; height: 16px; flex: none; }
.kz-actief { background: rgba(91,140,255,.11); border-color: rgba(91,140,255,.24);
             color: #C9DAFF; }
.kz-actief .ico { color: #9CC0FF; }

.kz-acties { margin-top: 2px; }
.kz-actie { display: flex; align-items: center; gap: 10px; height: 38px; padding: 0 11px;
            border-radius: 10px; font-size: 13.5px; font-weight: 600;
            color: var(--paars-l, #9CC0FF); }
.kz-actie:hover { background: rgba(255,255,255,.04); }

/* `flex:1` op een tussenblok duwt de voet omlaag; het ontwerp doet dat met een
   lege `<div style="flex:1">`. Hier op de voet zelf -- zelfde resultaat, één element
   minder. */
.kz-voet { margin-top: auto; display: flex; flex-direction: column; gap: 12px; }

/* Taalwissel als SEGMENTED CONTROL: één bak met drie segmenten, niet drie losse
   knoppen. Dat verschil is zichtbaar -- losse knoppen lezen als drie acties, een
   segmented control als één keuze met drie standen. */
.klantzijbalk .taalwissel { display: flex; gap: 2px; padding: 3px; border-radius: 10px;
                            background: var(--card, #14161F);
                            border: 1px solid var(--lijn, #262A36); }
.klantzijbalk .taalwissel button {
  flex: 1; height: 26px; min-height: 26px; padding: 0; border: 0; border-radius: 7px;
  background: none; color: var(--stil, #99A1B3);
  font: 600 11.5px/1 inherit; cursor: pointer;
}
.klantzijbalk .taalwissel button:hover { color: var(--helder, #E6E8EF); }
.klantzijbalk .taalwissel button[aria-pressed="true"],
.klantzijbalk .taalwissel button.actief {
  background: #232838; color: var(--helder, #E6E8EF);
}
.kz-uit { display: flex; align-items: center; padding: 0 6px; min-height: 30px;
          font-size: 12.5px; color: var(--stiller, #6E7689); }
.kz-uit:hover { color: var(--stil, #99A1B3); }

/* ── Mobiel: zijbalk eruit, onderbalk erin ───────────────────────────────────
   Op een telefoon is 230px een derde van het scherm. Een lade met hamburgerknop was
   het alternatief; een vaste onderbalk wint omdat hij ALTIJD zichtbaar is -- je ziet
   waar je bent zonder eerst iets te openen -- en binnen duimbereik ligt. */
.klantonder { display: none; }
@media (max-width: 992px) {
  .klantzijbalk { display: none; }
  /* Geen frame op een telefoon: een kaart met marge en radius kost daar breedte die
     je aan inhoud wilt geven. Het contract toont alleen desktop; dit is de
     bewuste invulling voor smal. */
  .klant-shell { display: block; max-width: none; margin: 0; border: 0;
                 border-radius: 0; padding-bottom: 68px; }
  .klantonder {
    position: fixed; inset: auto 0 0 0; z-index: 60;
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    background: var(--bg2, #0B0C16);
    border-top: 1px solid var(--lijn, rgba(255,255,255,.07));
    /* Op een iPhone loopt het scherm door onder de thuisbalk; zonder dit valt de
       onderste rij labels daarachter. */
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .ko-link { display: flex; flex-direction: column; align-items: center;
             justify-content: center; gap: 3px; min-height: 60px; padding: 8px 4px;
             font-size: 10.5px; font-weight: 600; text-align: center;
             color: var(--stil, #94A0B8); }
  .ko-link .ico { width: 19px; height: 19px; }
  .ko-actief { color: var(--ch-paars-l, #9CC0FF); }
  .ko-actief .ico { color: var(--ch-paars, #5B8CFF); }
}

/* De meldingslaag moet BOVEN de mobiele ondernavigatie blijven. Zonder dit staat een
   toast op `bottom:16px` en valt hij precies achter de onderbalk van 60px -- een
   melding die je niet ziet is erger dan geen melding, want de actie lijkt dan stil
   mislukt. Alleen waar die balk ook echt bestaat (`.klant-shell`, onder 992px). */
@media (max-width: 992px) {
  .klant-shell .toast-laag { bottom: 76px; }
}

/* ═══ Gebruikerstab — `Claude design/Paneel gebruikers.dc.html` ══════════════
   Goedgekeurd als visuele specificatie; de maten hieronder komen letterlijk uit dat
   bestand. Waar ik ervan afwijk staat de reden erbij.
   ═════════════════════════════════════════════════════════════════════════════ */
.gb-kop-acties { display:flex; align-items:center; gap:8px; flex-wrap:wrap;
                 justify-content:flex-end; }
.gb-kop.page-header { align-items:flex-start; gap:16px; }

/* Zoekveld: 38px, eigen bak, minstens 230px breed. */
.gb-kop-acties .lijstzoek { flex:0 1 auto; min-width:230px; max-width:320px; }
.gb-kop-acties .lijstzoek input {
  height:38px; min-height:38px; padding:0 12px 0 34px; border-radius:10px;
  background:var(--card, #14161F); border:1px solid var(--lijn, #262A36);
  color:var(--helder, #E6E8EF); font-size:13px;
}
.gb-kop-acties .lijstzoek .ico { left:11px; }

/* Rolfilters als SEGMENTED CONTROL: één bak, drie standen -- geen drie losse knoppen.

   `aria-selected` staat sinds 30 juli 2026 naast `aria-pressed` (fase 4, scherm 2). Het
   is dezelfde VORM voor twee verschillende betekenissen, en die twee hebben elk hun
   eigen ARIA -- niet uit netheid maar omdat een schermlezer er iets anders van maakt:
     · `aria-pressed` = een schakelaar die aan of uit staat (een rolfilter);
     · `aria-selected` = welke van N panelen open is (een tablist), en alleen die vorm
       laat "tab 2 van 4" horen.
   `/portal/mijn-website` schakelt panelen (Bewerken · Berichten · Historie · Bezoekers)
   en hoort dus een tablist te zijn. Zonder deze regel zou dat scherm `aria-pressed`
   moeten misbruiken om de opmaak te krijgen -- de klasse "kleur is de enige drager",
   maar dan voor semantiek. Uitbreiden gaat via de KIT (ontwerpregel 14.6); dit is die
   uitbreiding, niet een variant op het scherm. */
.gb-tabs { display:flex; gap:2px; padding:3px; border-radius:10px;
           background:var(--card, #14161F); border:1px solid var(--lijn, #262A36); }
.gb-tabs button { height:30px; min-height:30px; padding:0 12px; border:0;
                  border-radius:7px; background:none; color:var(--stil, #99A1B3);
                  font-size:12.5px; font-weight:600; cursor:pointer; }
.gb-tabs button:hover { color:var(--helder, #E6E8EF); }
.gb-tabs button[aria-pressed="true"],
.gb-tabs button[aria-selected="true"] { background:#232838; color:var(--helder, #E6E8EF); }

.gb-ververs { width:38px; height:38px; min-height:38px; padding:0; flex:none;
              display:inline-flex; align-items:center; justify-content:center;
              border-radius:10px; background:var(--card, #14161F);
              border:1px solid var(--lijn, #262A36); color:var(--stil, #99A1B3); }
.gb-kop-acties .btn-primary { height:38px; min-height:38px; padding:0 16px;
                              border:0; border-radius:10px; background:#3b6fe0;
                              color:#fff; }

/* Hier stonden `.gb-kpis` en `.gb-kpi`: vier bakken van 110px hoog met elk één cijfer
   en verder lucht -- terwijl "Team 4" en "Klanten 5" 200px lager nog eens in de
   sectiekoppen van de lijst stonden. De tellingen zitten nu IN de filterknoppen
   (`.gb-t-n`) en in de aandachtsrij (`.fg-n`), waar elk getal ook iets doet. */
.gb-t-n { min-width:18px; padding:0 5px; border-radius:5px; margin-left:5px;
          display:inline-flex; align-items:center; justify-content:center;
          background:rgba(255,255,255,.06); font-size:10.5px; font-weight:700;
          font-variant-numeric:tabular-nums; color:#8A93A6; }
.gb-tabs button[aria-pressed="true"] .gb-t-n,
.gb-tabs button[aria-selected="true"] .gb-t-n { background:rgba(91,140,255,.22); color:#B9CEFF; }

.gb-layout { display:grid; grid-template-columns:minmax(0,1fr) 380px; gap:20px;
             align-items:start; }
/* Zonder selectie is er geen detailpaneel, dus ook geen tweede kolom: anders staat er
   380px leeg naast de lijst -- en dat is precies de staat waarin je de tab opent. */
.gb-layout.leeg { grid-template-columns:minmax(0,1fr); }
.gb-layout.leeg > .gb-detail { display:none; }
@media (max-width:1100px) { .gb-layout { grid-template-columns:minmax(0,1fr); } }

.gb-lijst { display:flex; flex-direction:column; gap:20px; min-width:0;
            background:none; border:0; border-radius:0; box-shadow:none; }
/* De tabel is de kaart. */
.gb-tabel { background:var(--card, #14161F); border:1px solid var(--lijn, #262A36);
            border-radius:14px; overflow:hidden; }

/* Groepskop BINNEN de tabel (team / klanten). Vervangt twee losse tabellen die elk hun
   eigen kolomkoppen herhaalden boven kolommen op exact dezelfde plek. Verzonken vlak,
   zodat hij leest als een tussenkop en niet als een rij die je kunt aanklikken. */
.gb-groep { display:flex; align-items:center; gap:9px; padding:9px 16px;
            background:#101219; border-top:1px solid var(--lijn, #262A36);
            border-bottom:1px solid var(--lijn, #262A36); }
.gb-koppen + .gb-groep { border-top:0; }
.gb-groep .g-t { font-size:10.5px; font-weight:700; letter-spacing:.09em;
                 text-transform:uppercase; color:var(--stil, #99A1B3); }
.gb-groep .g-n { min-width:19px; height:18px; padding:0 5px; border-radius:5px;
                 display:inline-flex; align-items:center; justify-content:center;
                 background:#191C26; border:1px solid var(--lijn, #262A36);
                 font-size:10.5px; font-weight:700; font-variant-numeric:tabular-nums;
                 color:var(--tekst, #C8CEDC); }
.gb-groep .g-sub { font-size:11.5px; color:#6E7689; }

/* Vier kolommen, niet vijf: de vijfde was een lege `<span>` van 96px die alleen in de
   koprij stond -- de rijen hebben dat slot nooit gevuld, dus er stond structureel 96px
   niets rechts van "laatste login". */
.gb-koppen, .gb-rij { display:grid; align-items:center; gap:10px;
                      grid-template-columns:minmax(0,1.6fr) 150px minmax(0,1fr) 150px;
                      padding:11px 16px; }
.gb-koppen { font-size:10px; font-weight:700; letter-spacing:.08em;
             text-transform:uppercase; color:#6E7689;
             border-bottom:1px solid var(--lijn, #262A36); }
.gb-rij { width:100%; min-height:0; background:none; border:0; color:inherit;
          font:inherit; text-align:left; cursor:pointer; }
.gb-rij + .gb-rij { border-top:1px solid var(--lijn, #262A36); }
.gb-rij:hover { background:#191C26; }
.gb-rij.actief { background:rgba(91,140,255,.08);
                 box-shadow:inset 2px 0 0 0 #5b8cff; }
.gb-rij:focus-visible { outline:none; box-shadow:inset 0 0 0 2px #5b8cff; }

/* Avatar: 44px met radius 12 -- afgerond vierkant, niet rond. */
.gb-avatar { width:44px; height:44px; flex:none; border-radius:12px; display:inline-flex;
             align-items:center; justify-content:center;
             font:700 15px 'Outfit', system-ui, sans-serif;
             background:rgba(91,140,255,.16); color:#9CC0FF; }
.gb-naam { display:flex; align-items:center; gap:11px; min-width:0; }
.gb-naam .kolom { min-width:0; display:flex; flex-direction:column; gap:2px; }
.gb-naam .n { font-weight:600; font-size:13px; overflow:hidden;
              text-overflow:ellipsis; white-space:nowrap; color:var(--helder, #E6E8EF); }
.gb-naam .e { font-size:11.5px; color:#6E7689; overflow:hidden;
              text-overflow:ellipsis; white-space:nowrap; }
.gb-jij, .gb-discord { font-size:10px; padding:1px 6px; border-radius:5px; font-weight:700; }
.gb-jij { background:#191C26; color:#6E7689; }
.gb-discord { background:#5865F2; color:#fff; }
.gb-vlag { font-size:10px; font-weight:700; padding:2px 6px; border-radius:5px; }
.gb-vlag.fout { background:rgba(240,89,79,.14); color:#F0594F; }
.gb-vlag.let { background:rgba(245,165,36,.14); color:#F5A524; }

/* De rolkolom en de aandachtsbalk hadden geen eigen regel meer nadat de opzet naar
   het contract ging; ui_check ving dat -- een klasse zonder CSS valt stil terug op de
   standaard, en dat zie je pas als het scheef staat. */
.gb-rol { display:flex; align-items:center; min-width:0; }
.gb-aandacht { display:flex; align-items:center; gap:8px; flex-wrap:wrap;
               margin-bottom:0; }
.gb-aandacht .fg-l { font-size:11px; font-weight:700; letter-spacing:.06em;
                     text-transform:uppercase; color:#6E7689; margin-right:2px; }
/* `nowrap`: hier stonden twee statuschips die bij een gebruiker met server ÉN websites
   niet naast elkaar pasten en dus wrapten -- die ene rij werd daardoor ~26px hoger dan
   alle andere. Nu is het één tekstregel, dus wrappen kan niet meer; `nowrap` houdt dat
   zo als er ooit een derde soort bij komt. */
.gb-toegang { font-size:12px; color:#8A93A6; min-width:0; white-space:nowrap;
              overflow:hidden; text-overflow:ellipsis; }
.gb-toegang b { color:var(--tekst, #C8CEDC); font-weight:600;
                font-variant-numeric:tabular-nums; }
.t-geen { font-size:11.5px; color:#6E7689; }
.gb-login { font-size:11.5px; color:#8A93A6; }

/* ── Detailpaneel ───────────────────────────────────────────────────────────── */
.gb-detail { position:sticky; top:20px; border-radius:14px;
             border:1px solid var(--lijn, #262A36); background:var(--card, #14161F);
             padding:0; display:flex; flex-direction:column; overflow:hidden; }
@media (max-width:1100px) { .gb-detail { position:static; } }
.gb-leeg { padding:26px 18px; text-align:center; font-size:13px; color:#6E7689; }
.gb-d-kop { display:flex; gap:13px; align-items:flex-start; padding:18px;
            border-bottom:1px solid var(--lijn, #262A36); margin:0; }
.gb-d-naam { min-width:0; flex:1; display:flex; flex-direction:column; gap:4px; }
.gb-d-naam strong { font:700 14.5px 'Outfit', system-ui, sans-serif; }
.gb-id { font-size:11.5px; color:#6E7689; }
/* Het contract toont hier een TEKSTregel met een potloodje en noemt geen hoogte.
   Bij ons is het een knop (klikken bewerkt het e-mailadres), en 18px tekst is geen
   raakvlak. 28px geeft hem er een zonder dat hij als knop gaat ogen -- dat is de
   invulling op een punt waar het contract zwijgt, niet een afwijking ervan. */
.gb-mail { display:flex; align-items:center; gap:6px; min-height:28px; background:none;
           border:0; padding:0; cursor:pointer; font:inherit; font-size:12px;
           color:#6E7689; max-width:100%; text-align:left; }
.gb-mail:hover { color:var(--helder, #E6E8EF); }
.gb-mail .ico { width:13px; height:13px; flex:none; }

.gb-d-blok { padding:16px 18px; display:flex; flex-direction:column; gap:9px;
             margin:0; border-bottom:1px solid var(--lijn, #262A36); }
.gb-d-l { display:flex; align-items:center; gap:7px; margin:0; font-size:10.5px;
          font-weight:700; letter-spacing:.08em; text-transform:uppercase;
          color:#6E7689; }
.gb-d-l .ico { width:13px; height:13px; }
.gb-d-hulp { margin:0; font-size:11.5px; color:#6E7689; line-height:1.45; }
.gb-detail select { height:40px; min-height:40px; padding:0 12px; border-radius:10px;
                    border:1px solid var(--lijn, #262A36); background:var(--bg, #0B0D13);
                    color:var(--helder, #E6E8EF); font-size:13px; font-weight:600;
                    width:100%; }
.gb-detail .link-chips { flex-direction:row; flex-wrap:wrap; gap:6px; margin:0; }
.gb-koppel { display:flex; gap:6px; }
.gb-koppel select { flex:1; min-width:0; height:34px; min-height:34px; padding:0 9px;
                    border-radius:9px; color:var(--stil, #99A1B3); font-size:12px;
                    font-weight:400; }
.gb-koppel select.rol { flex:none; width:106px; }
.gb-koppel button { height:34px; min-height:34px; padding:0 13px; border-radius:9px;
                    border:1px solid rgba(91,140,255,.35);
                    background:rgba(91,140,255,.15); color:#9CC0FF;
                    font-size:12px; font-weight:700; cursor:pointer; }
.gb-d-data { display:grid; grid-template-columns:auto 1fr; gap:6px 14px;
             padding:16px 18px; margin:0; font-size:12px; border:0; color:#6E7689; }
.gb-d-data b { text-align:right; font-weight:600; font-variant-numeric:tabular-nums;
               color:var(--tekst, #C8CEDC); }
.gb-d-acties { display:flex; gap:8px; flex-wrap:wrap; padding:16px 18px;
               border-top:1px solid var(--lijn, #262A36); }

/* Toevoegformulier: inline kaart met een lichtere rand, zodat hij opvalt als iets
   dat OPEN staat. */
.gb-formulier { background:var(--card, #14161F); border:1px solid #333847;
                border-radius:14px; padding:16px 18px; display:flex;
                flex-direction:column; gap:12px; }
.gb-formulier .user-form-grid { display:grid;
                                grid-template-columns:1fr 1.2fr 1fr 1.6fr auto;
                                gap:10px; align-items:end; }
@media (max-width:900px) { .gb-formulier .user-form-grid { grid-template-columns:1fr; } }

@media (max-width:760px) {
  .gb-koppen { display:none; }
  /* De groepskop blijft WEL staan op smal: zonder kolomkoppen is dat het enige wat nog
     vertelt of je naar team of naar klanten kijkt. */
  .gb-groep { padding:8px 16px; }
  .gb-rij { grid-template-columns:minmax(0,1fr); row-gap:8px; }
  /* Op een telefoon geldt de 44px-vloer weer. Het contract specificeert alleen
     desktop en zegt niets over smal; daar is een raakvlak van 30px met een duim niet
     te bedienen, en dat is precies waar WCAG 2.5.5 over gaat. Dit is dus de invulling
     op een punt waar het contract zwijgt, geen afwijking ervan. */
  .gb-tabs button,
  .gb-ververs,
  .gb-kop-acties .btn-primary,
  .gb-kop-acties .lijstzoek input,
  .gb-detail select,
  .gb-koppel select,
  .gb-koppel button,
  .gb-mail { min-height:44px; }
  .gb-ververs { width:44px; }
}

/* ═══ STAPPENBALK — waar staat dit proces? ════════════════════════════════════
   Besluit Cihan 30 juli 2026 (fase 4, scherm 2). GEMETEN aanleiding: er stonden VIER
   eigen versies van hetzelfde ding, in vier schermen, met vier vormen en drie
   verschillende namen voor "deze stap is geweest":

     scherm-portal_overzicht.css:655  .stappen/.stap/.stip/.stap-l   staten: af · nu
     scherm-portal_mijn_website:283   .tijdlijn/.tl-stap/.tl-punt    staten: done · nu · todo
     scherm-portal_bi.css:107         .steps/.step/.n                genummerde kaarten
     scherm-portal_bi_dossier.css:49  .steps                         flex-wrap chips

   Precies het signaal dat ontwerpregel 14.6 beschrijft: mist een scherm iets, dan is
   dat een uitspraak over de KIT en geen vrijbrief voor een lokale variant.

   DE VORM KOMT UIT `/portal`, ONGEWIJZIGD. Dat scherm is sinds 30 juli 2026 het
   referentiescherm (§14) en Cihan heeft die stappenbalk daar goedgekeurd. De waarden
   hieronder zijn dus VERPLAATST, niet opnieuw gekozen -- zo rendert het referentiescherm
   na deze migratie identiek en is er niets nieuws om te beoordelen. Wat `portal_bi` en
   `portal_bi_dossier` doen blijft staan tot hun eigen herbouw; ze staan hier genoemd
   zodat de volgende sessie ze niet opnieuw hoeft te vinden (punt in TODO.md).

   `.stap.af` en niet `.done`: de kit is Nederlandstalig (`.rij-kop`, `.pijl`, `.mark`,
   `.veld-hulp`) en `af` was de naam op het referentiescherm. `portal_mijn_website` is
   met deze migratie meegegaan van `done`/`nu`/`todo` naar `af`/`nu`/(niets).

   DE STIP IS EEN `<span>`, GEEN `::before`. Reden: het referentiescherm zet er een
   ECHTE stip in de markup, en `.stap::before` is al bezet door de verbindingslijn. Twee
   pseudo-elementen op één element (`::before` lijn + `::after` stip) zou werken, maar
   dan kan een stap zijn stip niet overslaan -- en de lege staat van scherm 2 heeft
   precies dat nodig.

   Elke var() heeft een terugval: dit bestand laadt ook in de paneelstapel, waar
   `--paars`, `--geel-licht`, `--bg2`, `--stil` en `--tekst` niet bestaan (gemeten:
   alle vijf staan alléén in merk.css). Zonder terugval valt de HELE declaratie stil weg
   -- geen rand, geen kleur, geen foutmelding. Zie check_componenten_terugval.

   DE WORTEL HEET `.stappenbalk` EN DE KINDEREN ZIJN ERONDER GESCOPED. Dat is geen
   stijlkeuze maar een gemeten noodzaak. De eerste versie gebruikte `.stappen`/`.stap`/
   `.stip` kaal -- de namen die dit component op `/portal` al had. Zodra die uit een
   SCHERMbestand naar dit GEDEELDE bestand verhuisden, kregen twee schermen die dezelfde
   namen toevallig ook gebruiken er stijl bij die er nooit was (gemeten in de browser op
   de echte stylesheets, 8 verschillen):

     portal_start   de instructiekaarten ("Start Minecraft", "Voeg de server toe") gingen
                    van onder elkaar naar NAAST elkaar (grid-auto-flow row -> column),
                    `.stap` werd `position:relative` waardoor de verbindingslijn van deze
                    component IN elke kaart werd getekend, en de tekst kromp naar 11,5px.
     home           het statusbolletje van een klantsite werd `position:absolute` met
                    13x13px en radius 50% -- uit de tekststroom gerukt.

   Een naam die in een schermbestand veilig is, is dat in een gedeeld bestand niet: daar
   geldt hij ineens overal. `check_kit_klassen_botsen_niet` dwingt dit nu af.
   ═════════════════════════════════════════════════════════════════════════════ */
.stappenbalk { display:grid; grid-auto-flow:column; grid-auto-columns:1fr;
           list-style:none; margin:16px 0 4px; padding:0; }
.stappenbalk .stap { position:relative; padding-top:20px; text-align:left;
        font-size:11.5px; color:var(--stil, var(--muted, #99A1B3)); }
.stappenbalk .stip { position:absolute; top:0; left:0; width:13px; height:13px; border-radius:50%;
        background:var(--bg2, rgba(255,255,255,.05));
        border:2px solid var(--lijn, var(--border, rgba(255,255,255,.12))); z-index:1; }
/* De lijn loopt VANAF elk bolletje naar rechts; de laatste krijgt er geen. */
.stappenbalk .stap::before { content:""; position:absolute; top:6px; left:6px; right:0; height:2px;
                background:var(--lijn, var(--border, rgba(255,255,255,.12))); }
.stappenbalk .stap:last-child::before { display:none; }
.stappenbalk .stap.af .stip { background:var(--paars, var(--ch-paars, #5B8CFF));
                 border-color:var(--paars, var(--ch-paars, #5B8CFF)); }
.stappenbalk .stap.af::before { background:var(--paars, var(--ch-paars, #5B8CFF)); }
.stappenbalk .stap.af .stap-l { color:var(--tekst, var(--text)); }
/* De HUIDIGE stap is amber, niet blauw: blauw is in dit systeem de kleur van een actie
   die JIJ kunt doen, en hier WACHT er iets. Dat is dezelfde betekenis die amber overal
   draagt (ontwerpregel 7b.4: amber is exclusief statusdrager). */
.stappenbalk .stap.nu .stip { background:var(--geel-licht, #FBBF24);
                 border-color:var(--geel-licht, #FBBF24);
                 box-shadow:0 0 0 3px rgba(251,191,36,.22); }
.stappenbalk .stap.nu .stap-l { color:var(--geel-licht, #FBBF24); font-weight:700; }
.stappenbalk .stap-l { display:block; }
@media (max-width:620px) {
  /* Onder elkaar op een telefoon: vier labels naast elkaar op 390px levert
     afgebroken woorden op. De lijn loopt dan verticaal mee. */
  .stappenbalk { grid-auto-flow:row; grid-auto-columns:auto; gap:2px; }
  .stappenbalk .stap { padding:0 0 14px 22px; }
  .stappenbalk .stap::before { top:12px; left:6px; right:auto; width:2px; height:calc(100% - 4px); }
  .stappenbalk .stap:last-child { padding-bottom:0; }
}

/* ═══ STAAFJES — een reeks over tijd ══════════════════════════════════════════
   Besluit Cihan 30 juli 2026 (fase 4, scherm 2). Nodig voor de bezoekerscijfers van een
   klantwebsite: 30 dagen, één staaf per dag.

   DIT IS GEEN METERBALK, en dat onderscheid is de reden dat dit component mag bestaan.
   Op 30 juli is de METERBALK afgewezen (§14, besluit 2): een gevulde track die één
   waarde tegen een maximum afzet ("83% van je geheugen"). Die komt alleen terug als
   variant op `.kpi`, en dit is hem niet. Een reeks over tijd toont VERLOOP -- iets wat
   `.kpi` per definitie niet kan, want een kerncijfer is één getal. De bestaande
   meterfamilies (`panel.css` `.resource-bar`/`.bar-track`/`.bar-fill`, `.node-bar-*`,
   `.hh-bar`) blijven dus waar ze zijn; ze doen iets anders.

   VLAK, GEEN VERLOOP. `.bez-staaf` had `linear-gradient(180deg,--ch-paars,--ch-knop)`.
   Ontwerpregel 1.5: een kleurverloop is chroom, kleur hoort bij betekenis. Vlak.

   DE HOOGTE KOMT UIT DE MARKUP, niet uit een klasse. Een percentage per staaf kan geen
   klasse zijn -- dat zouden er honderd worden. Dit is de uitzondering waar `ui_check`
   inline stijl toestaat, en hij staat hier expliciet zodat een volgende sessie hem niet
   "opruimt": alleen `height` mag inline, al het andere hoort in deze twee regels.

   TOEGANKELIJKHEID IS ONDERDEEL VAN HET COMPONENT, geen bijzaak: een rij <div>'s is voor
   een schermlezer niets. De container hoort `role="img"` te dragen met een `aria-label`
   die de reeks in woorden samenvat ("1.284 weergaven over 30 dagen, drukste dag 23 juli
   met 97"). De cijfers zelf staan er als `.kpi` naast -- kleur en vorm zijn dus nooit de
   enige drager (WCAG 1.4.1).
   ═════════════════════════════════════════════════════════════════════════════ */
.staafjes { display:flex; align-items:flex-end; gap:2px; height:90px; min-width:0; }
.staafjes .staaf { flex:1 1 0; min-width:3px; border-radius:3px 3px 0 0;
         background:var(--ch-knop, var(--accent-diep)); }
/* Een dag ZONDER bezoek is geen ontbrekende meting: die hoort een zichtbare nullijn te
   houden, anders leest een gat als "hier is niet gemeten". De hoogte komt uit de markup;
   deze regel garandeert alleen dat 0% niet onzichtbaar wordt. */
.staafjes .staaf { min-height:3px; }
/* De laatste staaf is VANDAAG en dus nog niet compleet -- die mag niet als een dal
   lezen. Gedempt, met dezelfde kleur: het is dezelfde meting, alleen nog niet af. */
.staafjes .staaf.deels { opacity:.45; }

/* ═══ KNOPPEN — één familie, vier betekenissen ═════════════════════════════════
   Besluit Cihan 30 juli 2026, vastgelegd in `docs/ONTWERPSYSTEEM.md` §7b (variant A).
   Vóór dit blok betekende dezelfde klassenaam per omgeving iets anders. GEMETEN in de
   browser, niet uit de CSS geredeneerd -- `panel.css` herdefinieert dezelfde variant tot
   vier keer en heeft regels met een gemengde selectorlijst, dus grep wijst de winnaar
   verkeerd aan:

     variant        adminpaneel              serverpaneel             klantportaal
     .btn-green     verloop + glans          verloop + glans          primair blauw
     .btn-start     #1A7F50 VLAK             verloop + glans          primair blauw
     .btn-red       verloop + glans          verloop + glans          primair blauw
     .btn-stop      #CC3328 VLAK             verloop + glans          primair blauw
     kaal .btn      #6B6B6B browsergrijs     #6B6B6B browsergrijs     primair blauw

   Twee namen voor één betekenis, drie uitkomsten. `.btn-red` betekende bovendien zowel
   "■ Stop" als "Verwijderen" -- §7b.5 splitst dat in status en gevaar.

   WAT DIT BLOK WEL EN NIET BEZIT
   Het bezit de BETEKENISLAAG: achtergrond, tekstkleur, rand en glans. De MAATLAAG
   (radius, padding, lettergrootte, min-height) blijft bij de omgeving -- `panel.css`
   geeft 9px/13px, `merk.css` 12px/14px. Die twee gelijktrekken is een herbouw en geen
   unificatie: het herschikt elke knoprij op 23 tabbladen, en de scherm-voor-scherm
   herbouw staat bewust achter Cihans oordeel op de componentenkit. Benoemd gat, geen
   vergetelheid.

   DE ORDE HIERONDER IS DRAGEND. Alles is (0,1,0), dus bij een knop met meerdere klassen
   wint de LAATSTE regel. Vereiste orde: basis -> primair -> status -> rustig -> gevaar.
   Zet je status vóór primair, dan wordt "▶ Start" blauw -- dat is op 30 juli echt
   gebeurd: één `.btn { background }` overrulede alle 15 paneelvarianten en Start, Stop,
   Verwijderen én Uitloggen kregen hetzelfde blauw. Op de render afgekeurd.

   Elke var() heeft een terugvalwaarde, want dit bestand laadt in béide stapels en
   `--ch-knop` bestaat alleen in `merk.css`, `--accent-diep` alleen in `panel.css`
   (gemeten: allebei #3D6BD6, dus de terugval is dezelfde kleur en geen tweede waarheid).
   ═══════════════════════════════════════════════════════════════════════════════ */

/* 1. BASIS
   Alleen `box-shadow`, en expliciet. `panel.css` geeft `button, .btn` een
   `box-shadow: var(--shadow-btn)`; noemt het contract die eigenschap niet, dan lekt
   de glans als enige eigenschap door en krijg je een vlakke kleur MET chroomrand.
   Live gemeten op de bestelpagina: daar wint `merk.css`' vlakke blauw terwijl
   `panel.css`' inset-glans erover blijft staan. Geen `background` op de kale
   `button`-selector: dat zou elke tabknop, kaartkop-knop en sluitkruisje raken. */
.btn { box-shadow: none; }

/* 2. PRIMAIR
   Kaal `.btn` IS primair. Bewuste keuze, gemeten: `portal_website` en `portal_return`
   gebruiken `class="btn"` zonder variant en verwachten daar een blauwe knop; in het
   paneel leverde datzelfde `class="btn btn-sm"` een kale browsergrijze knop op. Een
   knop is een actie -- rustig en gevaar zijn de afwijkingen. */
.btn,
.btn-primary,
.btn-submit {
  background: var(--ch-knop, #3D6BD6);       /* 4,92:1 met wit */
  color: var(--wit, #fff);
  border: 1px solid transparent;
  box-shadow: none;
}
.btn:hover:not(:disabled),
.btn-primary:hover:not(:disabled),
.btn-submit:hover:not(:disabled) { background: #4A79E6; filter: none; }

/* 3. STATUS
   §7b.2: aan/uit IS systeemstatus, dus Start en Stop houden hun kleur -- maar VLAK
   in plaats van met glans (regel 1.5: een kleurverloop is chroom, kleur hoort bij
   betekenis). De diepe tokens halen AA met witte tekst.

   `--groen-diep`/`--rood-diep` zijn SCOPE-AFHANKELIJK: #1A7F50/#CC3328 in de
   paneelstapel (5,01:1 en 5,16:1 -- de waarden die §7b.2 noemt), #15803D/#DC2626 in
   de klantstapel (4,84:1 en 4,83:1). Beide halen AA. Geen enkel klantscherm gebruikt
   een statusknop, dus dit is nergens zichtbaar; de twee tokenwaarden samenvoegen is
   een KLEURWIJZIGING en dus Cihans besluit, niet iets om hier stil mee te nemen.

   `.btn-green` staat erbij omdat de mapping in TODO.md hem noemt; na de markupwissel
   van 30 juli heeft hij nul consumenten meer. */
.btn-start,
.btn-green {
  background: var(--groen-diep);
  color: var(--wit, #fff);
  border: 1px solid transparent;
  box-shadow: none;
}
.btn-stop {
  background: var(--rood-diep);
  color: var(--wit, #fff);
  border: 1px solid transparent;
  box-shadow: none;
}
.btn-start:hover:not(:disabled),
.btn-green:hover:not(:disabled),
.btn-stop:hover:not(:disabled) { filter: brightness(1.08); }

/* 4. RUSTIG
   De standaard voor alles wat niet de hoofdactie is. BEIDE naamgroepen, en dat is
   geen luiheid: de codebase gebruikt al `class="btn stil"` (portal_mijn_website,
   portal_return), de BI-schermen `btn-ghost`, het adminpaneel `btn-muted` en het
   serverpaneel `btn-small`. Alleen `btn-ghost` definiëren gaf "Annuleren" het
   PRIMAIRE blauw -- een rustige actie die zich als hoofdactie voordoet.

   `btn-warn` en `btn-kick` vallen hier óók onder (§7b.4): amber is exclusief
   statusdrager ("wacht op betaling", "bezig") en wordt nooit een knopvariant, want
   dat verdunt precies het signaal dat §7b.2 behoudt. §7b.3 vraagt bij zo'n
   ingrijpende-maar-omkeerbare actie een bevestigingsvenster in plaats van kleur;
   `doKick` had die al, `doWlRemove` en `doDeop` kregen hem in dezelfde commit. */
.btn.stil,
.btn-ghost,
.btn-muted,
.btn-logout,
.btn-small,
.btn-downgrade,
.btn-warn,
.btn-kick {
  background: transparent;
  color: var(--ch-tekst, #E8EAF0);
  /* NIET `--ch-lijn` (rgba(255,255,255,.12)), hoe verleidelijk ook: bij een rustige knop
     IS de rand het bedieningselement, en die waarde haalt 1,24:1 tegen de kaart — ver
     onder de 3:1 die WCAG 1.4.11 eist. Het paneel had dat op 19 juli al gerepareerd met
     `--border-interactive` (#5d6779, 3,2:1); dat token staat bewust in `panel.css` ÉN
     `merk.css` onder dezelfde naam en waarde, dus het werkt in beide stapels. Zou het
     contract hier de klantwaarde nemen, dan importeerde de unificatie een opgeloste
     toegankelijkheidsfout terug in 23 tabbladen. */
  border: 1px solid var(--border-interactive, #5d6779);
  box-shadow: none;
}
.btn.stil:hover:not(:disabled),
.btn-ghost:hover:not(:disabled),
.btn-muted:hover:not(:disabled),
.btn-logout:hover:not(:disabled),
.btn-small:hover:not(:disabled),
.btn-downgrade:hover:not(:disabled),
.btn-warn:hover:not(:disabled),
.btn-kick:hover:not(:disabled) {
  border-color: var(--ch-merk-accent, #7aa2ff);
  background: transparent;
  filter: none;
  transform: none;
}

/* 5. GEVAAR
   Onomkeerbaar, en nooit de primaire actie in een normale rij (§7.1). Rood als RAND
   en TEKST, niet als vulling: een rode vlakke knop trekt de blik weg van de
   hoofdactie ernaast. Pas op hover wordt hij vol -- dan heb je hem al gekozen.
   `.btn-red` hoort hier na de splitsing van §7b.5: de acht verwijderacties met
   prullenbak-icoon (twee in home.html, zes in _marktmonitor.html) houden die naam. */
.btn-gevaar,
.btn-danger,
.btn-red {
  /* Een LICHTE rode vulling, geen doorzichtige. Nagerekend tijdens het bouwen: de
     klantwaarde was transparant met een rand van `rgba(204,51,40,.45)`, en die rand
     haalt over de kaart (#14161F) samengesteld maar 1,56:1 — ver onder de 3:1 die
     WCAG 1.4.11 voor bedieningselementen eist. Een gevaar-knop zonder vulling én
     zonder zichtbare rand is alleen nog rode tekst; juist de knop die je NIET per
     ongeluk wilt raken wordt dan het moeilijkst te zien.
     Deze drie waarden zijn gemeten: vulling 5,89:1 met de tekst erop, rand 3,45:1
     tegen de kaart. De vulling van 14% ligt bovendien vlak bij de 12% die het
     adminpaneel al had, dus de knop blijft herkenbaar dezelfde. */
  background: rgba(204, 51, 40, .14);
  color: var(--rood-l, #F0776B);
  border: 1px solid var(--rood-diep);
  box-shadow: none;
}
.btn-gevaar:hover:not(:disabled),
.btn-danger:hover:not(:disabled),
.btn-red:hover:not(:disabled) {
  background: var(--rood-diep);
  color: var(--wit, #fff);
  border-color: transparent;
  filter: none;
}
