/*
  Design tokens — QR-Database redesign v2 (juli 2026).
  Bron: "Van QR-database redesign/design_handoff_qr_database_redesign/README.md" (bindend,
  CLAUDE.md §5). Kleuren/typografie/maten zijn definitief; niet aanpassen zonder de
  design-handoff bij te werken. Thema-omschakeling via het [data-dark="1"]-attribuut op
  <html> (zie ThemeService), exact zoals in de klikbare prototypes.
*/

:root {
  /* Accent is PER BEDRIJF instelbaar (white-labeling-module, CompanyAccentCss) — dit zijn de
     platform-defaults totdat een bedrijf een eigen kleur instelt.
     WCAG 2.2 AAA (1.4.6, contrast 7:1): het redesign-accent #e42a5e haalt met witte
     knoptekst slechts 4,4:1 — verdonkerd naar #a3123c (7,8:1 met wit). Een bedrijfskleur is
     sinds white-labeling (gebruikersbesluit 2026-07-23) vrij — CompanyAccentCss leidt dan
     zelf leesbare --accent-fg/-ink-waarden af. */
  --accent: #a3123c;
  --accent-dark: #8a0f33;
  /* Tekst óp de accentkleur (knoppen, actieve navitems, badges) — per bedrijf wit of zwart,
     afhankelijk van wat het meeste contrast geeft tegen de gekozen accentkleur. */
  --accent-fg: #fff;
  /* Accent als TEKSTkleur (links/actielabels): moet 7:1 halen t.o.v. het DONKERSTE vlak waarop
     de globale `a { color: var(--accent-ink) }` landt — niet alleen de kaart, ook het
     --page-gradient-eindpunt (#e2dbcd), --chip2 en --thead (heraudit 2026-09-05: #a3123c haalde
     daar 5,6–6,6:1). #830f2e = 7,4:1 op #e2dbcd, 7,8:1 op --chip2; hover verder verdonkerd zodat
     die onderscheidbaar blijft. In donker thema is dit een lichte roze i.p.v. het donkere accent. */
  --accent-ink: #830f2e;
  --accent-ink-h: #6e0c27;
  /* Accent als tekstkleur BUITEN een kaart, direct op de --page-gradient (heraudit 2026-09-05):
     eigen token voor marketing-eyebrows/.emp-cta-dashed, met ruime marge tegen de donkerste
     page-stop #e2dbcd (7,96:1). CompanyAccentCss leidt per bedrijf een eigen waarde af tegen
     diezelfde page-stop — --accent-ink is daar alleen tegen --card gegarandeerd. */
  --accent-ink-page: #7a0c2c;

  /* Navigatie-chrome (admin-sidebar + -topbar + medewerker-PWA-header) — altijd donker,
     ongeacht licht/donker-thema, maar per bedrijf overschrijfbaar (white-labeling). */
  --nav-bg: #262d34;
  --nav-hover: #313943;
  --nav-bd: #333b43;
  --nav-fg: #b8c0c8;
  --nav-fg-strong: #fff;

  --page: linear-gradient(160deg, #ece5d4 0%, #e7dfcf 55%, #e2dbcd 100%);
  --card: #fffdf9;
  --card-h: #faf8f3;
  --tint: #fffdf9;

  --bd: #e6e2da;
  --bd2: #f0ede6;
  --bd3: #d9d4ca;
  --bd-h: #b8b2a6;
  /* Rand van invoervelden/switches: 1.4.11 (non-text contrast ≥3:1) — de decoratieve
     --bd3 haalt maar 1,45:1 tegen de kaartachtergrond. */
  --bd-input: #8a857b;

  /* Tekstkleuren: allemaal ≥7:1 (AAA 1.4.6) tegen --card, het donkerste --page-gradient-
     eindpunt (#e2dbcd), --chip2 (#e6e1d6) én --tamber2 (#eadaa8). De originele redesign-waarden
     (--ink2 #55534d, --ink3 #6d675c, --mut #8d8779) haalden resp. 5,9/5,5/3,5:1; de eerdere
     AAA-waarden (--ink3 #4a463e, --mut #4d4941, --theadink #544c3f) haalden wél 7:1 op --card
     maar niet op het gradient-eindpunt/--chip2 (6,5–6,9:1) — verdonkerd (heraudit 2026-09-05):
     --ink3 7,5:1 / --mut 7,4:1 op #e2dbcd, --theadink 7,8:1 op --chip2. */
  --ink: #23282e;
  --ink2: #444138;
  --ink3: #43403a;
  --mut: #454138;

  --thead: #f2ecdd;
  --theadink: #484034;

  /* Focusring (2.4.13 Focus Appearance): ≥3px dik, met offset, ≥3:1 tegen de omgeving. */
  --focus-ring: #1d232a;

  --chip: #f4f2ee;
  --chip2: #e6e1d6;
  --chip3: #eeeae2;

  /* Pill-/badge-inktkleuren verdonkerd naar ≥7:1 tegen hun eigen "2"-achtergrond
     (AAA 1.4.6 — origineel: blauw 5,2:1, amber 3,5:1, groen 3,9:1). */
  --tblue: #e7eaf7;
  --tblue2: #c9d0ea;
  --tblue3: #2b3768;
  --tamber: #f9eed6;
  --tamber2: #eadaa8;
  --tamber3: #5e3504;
  --tgreen: #e0f1e7;
  --tgreen2: #b9dfc9;
  --tgreen3: #0d4530;
  /* Rood + lime vullen de amber/groen-set aan tot een rood→groen-schaal voor keuringsdeadlines
     (gebruikersverzoek 2026-07-25, ExaminationDeadlineStatus). Zelfde 7:1-eis als hierboven:
     rood 8,9:1, lime 8,4:1 tegen hun eigen "2"-achtergrond. */
  --tred: #f7e2e2;
  --tred2: #eec9c9;
  --tred3: #5d1111;
  --tlime: #eef3d9;
  --tlime2: #dde7b3;
  --tlime3: #33420b;

  --pink-badge-bg: color-mix(in srgb, var(--accent) 9%, #fffdf9);
  --pink-badge-ink: var(--accent-dark);

  /* Categorische grafiek-marks (dashboard-grafieken, gebruikersverzoek 2026-07-23) — apart van
     de tekst-/pill-tokens hierboven: dit zijn verzadigde vlakkleuren voor balken/segmenten,
     geen 7:1-tekstcontrast nodig. Vaste volgorde, nooit cyclisch toegewezen (dataviz-skill):
     --chart-1 identiteit "rose/accent", --chart-2 "blauw", --chart-3 "groen", --chart-4
     "amber". Gevalideerd met de dataviz-skill validate_palette.js tegen --card
     (lichtheidsband, chromavloer, CVD-scheiding, contrast) — alle checks slagen. */
  --chart-1: var(--accent);
  --chart-2: #2f5fd6;
  --chart-3: #1f9d55;
  --chart-4: #b6540a;

  --sidebar-bg: #1d232a;

  --radius-card: 12px;
  --radius-btn: 8px;
  --radius-md: 8px;
  --radius-pill: 999px;
  --shadow-overlay: 0 24px 60px rgba(0, 0, 0, .3);
  /* Kaartschaduw van de auth-/statuspagina's (Login/Onboarding/Error/NotFound/Wachtstand/
     WachtwoordX): stond 7× hardcoded als rgba(38,45,52,.08) — in het donkere thema tegen de
     --page-gradient (#1d232a) effectief onzichtbaar, waardoor de kaart zijn elevatie verloor.
     Eén token, met een zwaardere dark-variant hieronder (heraudit 2026-09-05). */
  --shadow-card: 0 12px 34px rgba(38, 45, 52, .08);
  /* Dialoog-backdrop: rgba(20,20,20,.35) dimt de lichte pagina duidelijk, maar de al donkere
     pagina nauwelijks (#1d232a → ≈#151a1e), zodat de dialoogkaart zich in dark zwak aftekent —
     dark-variant hieronder. Bestaande .razor.css-backdrops migreren bij aanraking naar
     var(--backdrop) (heraudit 2026-09-05). */
  --backdrop: rgba(20, 20, 20, .35);
  /* Alias-tokens (bevinding item 98, 2026-07-23): CategorieDetail.razor.css/ProductDetail.razor.css
     gebruikten var(--line-strong, #c7bfab)/var(--surface, transparent) zonder dat deze ooit
     gedefinieerd waren — de light-mode-fallbackwaarde gold daardoor ook in donkere modus.
     Alias naar bestaande, al thema-bewuste tokens i.p.v. losse waarden (automatisch correct
     in beide thema's, geen aparte override in het [data-dark="1"]-blok nodig). */
  --line-strong: var(--bd-h);
  --surface: var(--card-h);
  /* Zelfde bevinding, gevonden bij een bredere doorloop (item 99): employee.css/public-
     layout.css/marketing.css gebruikten var(--bg)/var(--bg2) zonder ooit gedefinieerd
     token — zonder fallbackwaarde in de meeste aanroepen viel de eigenschap dus terug op
     de browserdefault (transparant), niet alleen een verkeerde light-mode-kleur. */
  --bg: var(--page);
  --bg2: var(--chip);

  /* Gedeelde hoogte voor knoppen én zoekvelden op de admin-werkplek (audit-bevinding
     2026-07-19) — zodat een knop naast een zoekveld altijd exact even hoog is, ongeacht
     padding-/borderverschillen tussen varianten. */
  --control-height: 34px;

  color-scheme: light;
}

:root[data-dark="1"] {
  /* Hover van accent-knoppen: iets lichter dan de basis, maar nog steeds ≥7:1 met wit. */
  --accent-dark: #ad1440;
  /* Accent-tekst op donkere kaarten: lichte roze, ≥7:1 tegen --card ÉN tegen --card-h/--chip/
     --chip2 (hover-/zebra-rijen, chip-vlakken — heraudit 2026-09-05: #ffabc1 haalde op --chip2
     maar 5,7:1). #ffcfdb = 7,3:1 op --chip2; hover nog lichter zodat die onderscheidbaar blijft. */
  --accent-ink: #ffcfdb;
  --accent-ink-h: #ffe0e8;
  /* Donker: --accent-ink haalt op de page-gradient al 9,6:1, dus geen aparte afgeleide nodig
     (heraudit 2026-09-05). */
  --accent-ink-page: var(--accent-ink);

  --page: linear-gradient(160deg, #1d232a, #1a2026 55%, #171d23);
  --card: #2a323b;
  --card-h: #323c46;
  --tint: #2a323b;

  --bd: #3a434d;
  --bd2: #323a43;
  --bd3: #4a545f;
  --bd-h: #5a6672;
  --bd-input: #77828d;

  /* AAA 1.4.6: opgelicht t.o.v. het redesign (--ink2 5,9:1 op pill-achtergrond,
     --ink3 5,5:1, --mut 3,7:1, --theadink 6,2:1). Heraudit 2026-09-05: --ink3 #bfc6cd/--mut
     #bbc2c9 haalden op --card-h/--chip/--chip2 (hover-/zebra-rijen, chip-vlakken met gedempte
     hints) slechts 5,6–6,9:1 — verder opgelicht: --mut/--theadink #d5dbe1 = 7,2:1 op --chip2,
     --ink3 #d8dde2 = 7,4:1. */
  --ink: #e9ebee;
  --ink2: #d7dade;
  --ink3: #d8dde2;
  --mut: #d5dbe1;

  --thead: #232a31;
  --theadink: #d5dbe1;

  --chip: #2f3841;
  --chip2: #3a434d;
  --chip3: #333c45;

  /* Donkere pills: donkerdere achtergrond + lichtere inkt om 7:1 te halen — de originele
     "2"-achtergronden waren te licht om er ooit AAA-tekst op te krijgen. */
  --tblue: #2b3450;
  --tblue2: #2b3450;
  --tblue3: #bcc7ee;
  --tamber: #3a3122;
  --tamber2: #3a3122;
  --tamber3: #e9bd7e;
  --tgreen: #22352b;
  --tgreen2: #22352b;
  --tgreen3: #90d5af;
  --tred: #3b2323;
  --tred2: #3b2323;
  --tred3: #f0a9a9;
  --tlime: #313a22;
  --tlime2: #313a22;
  --tlime3: #c6dc8c;

  --pink-badge-bg: color-mix(in srgb, var(--accent) 16%, #2a323b);
  --pink-badge-ink: var(--accent-ink);

  /* Grafiek-marks lichter/verzadigder dan het lichte thema — apart gevalideerd tegen --card
     #2a323b (donkere lichtheidsband L 0.48–0.67), niet zomaar de lichte set hergebruikt. */
  --chart-1: #e0546f;
  --chart-2: #5c86e8;
  --chart-3: #2fa870;
  --chart-4: #cf7e2f;

  --focus-ring: #ffffff;

  /* Zie de light-definities: zwaardere schaduw/dimming, anders onzichtbaar op de donkere
     --page-gradient (heraudit 2026-09-05). */
  --shadow-card: 0 12px 34px rgba(0, 0, 0, .45);
  --backdrop: rgba(0, 0, 0, .6);

  color-scheme: dark;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  overscroll-behavior: none;
}

body {
  font-family: 'Archivo', system-ui, -apple-system, sans-serif;
  font-size: 15px;
  /* AAA 1.4.8 (Visual Presentation): regelafstand ≥1,5 binnen alinea's. */
  line-height: 1.5;
  color: var(--ink);
  background: var(--page);
  min-height: 100vh;
  /* dvh i.p.v. vh: op mobiel Safari/Chrome krimpt/groeit de adresbalk na navigatie
     (bv. redirect ná inloggen); 100vh blijft de volledige layout-viewport meten,
     waardoor de pagina tijdelijk hoger is dan het zichtbare gebied en de browser
     ná navigatie zelf bijscrolt. Een position:fixed-element (bv. .emp-bottom-nav)
     lijkt daardoor "op te schuiven" tijdens die settle-scroll. dvh meet de échte
     zichtbare hoogte, dus geen overtollige scrollruimte, geen settle-jump. */
  min-height: 100dvh;
}

.mono {
  font-family: 'IBM Plex Mono', ui-monospace, Consolas, monospace;
}

/* Kale tekstlinks: accent-INKT (7:1 in beide thema's) + onderstreping, zodat een link
   nooit alleen door kleur te herkennen is (1.4.1). Knop-/nav-gestileerde links zetten
   text-decoration zelf weer uit. */
a {
  color: var(--accent-ink);
  text-decoration: underline;
}

a:hover {
  color: var(--accent-ink-h);
}

h1, h2, h3, h4 {
  font-weight: 800;
  margin: 0;
}

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

@keyframes slideIn {
  from { opacity: 0; transform: translateX(16px); }
  to { opacity: 1; transform: none; }
}

@keyframes pop {
  from { opacity: 0; transform: translateY(14px) scale(.98); }
  to { opacity: 1; transform: none; }
}

/* Dialoogkaart-animatie (gebruikersverzoek 2026-07-24, "animaties bij laden etc"): licht
   schaal+fade-in op de dialoogkaart zelf, los van een eventuele backdrop-fade (fadeUp) op de
   omhullende overlay — die twee animeren daardoor onafhankelijk, geen dubbele beweging. */
@keyframes dialogPop {
  from { opacity: 0; transform: translateY(6px) scale(.97); }
  to { opacity: 1; transform: none; }
}

/* Skeleton-shimmer (gebruikersverzoek 2026-07-24) — CLAUDE.md-conventie: elke nieuwe
   laadstatus voor lijst-/detailpagina's gebruikt dit i.p.v. kale statustekst. */
@keyframes skeletonShimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Transform-vrije entree-fade — bewust GEEN translateY (i.t.t. fadeUp), bedoeld voor elementen
   die een `position: fixed`-overlay omhullen (.admin-content / .route-fade). Reden (bugmelding
   2026-07-25, dialoog "Nieuwe set" half buiten beeld): een element met een actieve óf
   fill-mode-vastgehouden transform-animatie wordt het *containing block* van al zijn
   fixed-gepositioneerde nakomelingen. `inset: 0` op een .xxx-overlay-backdrop rekende daardoor
   niet meer tegen de viewport maar tegen de (korte) paginawrapper — de dialoog werd binnen die
   strook gecentreerd en liep boven- én onderlangs buiten beeld, zonder scrollmogelijkheid.
   Ook `to { transform: none }` helpt niet: de browser houdt bij fill-mode both de identiteits-
   matrix aan (computed `matrix(1,0,0,1,0,0)`), en dat telt óók als containing block. */
@keyframes fadeInOnly {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ---- Herbruikbare bouwstenen (CLAUDE.md §4: "eigen componentenbibliotheek") ---- */

/* Vierkant merkicoon (het app.qr-database.nl-favicon/PWA-icoon, wwwroot/img/pwa-icons/) —
   het volledige woordmerk (qrdb-logo-*-bg.png, 750×622) is niet vierkant en werd voorheen
   in vaste 24-56px-vierkanten geforceerd (AdminLayout/EmployeeLayout/GastScan/Login/
   WachtwoordVergeten/WachtwoordResetten), wat het beeldmerk vervormde. Overal waar een
   compact icoon naast een los "QR-DATABASE"-tekstlabel staat (i.p.v. het volledige
   woordmerk zelf, zoals op de publieke marketingsite) hoort dit vierkante merkicoon. */
.brand-mark {
  display: block;
  border-radius: 22%;
  flex: none;
}

.card {
  background: var(--card);
  border: 1px solid var(--bd);
  border-radius: var(--radius-card);
  /* .admin-content (AdminLayout.razor.css) is display:flex;flex-direction:column — zonder
     deze reset kregen tabelkaarten die tegelijk overflow-x:auto zetten (bv. de globale .tablewrap, voorheen .cat-tablewrap)
     ook een browser-geforceerde overflow-y:auto (CSS-overflow-spec: alleen-x niet-"visible"
     laat de y-as meecomputen naar "auto"). Flexbox kneep zo'n kaart dan tot minder dan zijn
     inhoud, wat een eigen interne scrollbalk opleverde met de paginatie erin verstopt
     onderaan — i.p.v. dat de bedoelde buitenste .admin-content-scroll het hele blok toont
     (browserverificatie 2026-07-21: "paginatie verdwijnt bij scrollen"). */
  flex-shrink: 0;
}

/* Basis uit Admin.dc.html regel 1694-1695 (btn/btn2) — desktop-werkplek-knoppen, bewust
   compacter dan een 44px-mobiel-tapdoel. De medewerker-PWA (390×844, scan-first) krijgt zijn
   eigen, grotere knop-maten zodra dat scherm gebouwd wordt (CLAUDE.md §16.4: hit targets
   ≥44px "mobiel" — die eis geldt voor de mobiele PWA, niet voor de desktop-admin-werkplek). */
/* Op .btn-primary/.btn-secondary zelf herhaald (niet alleen op .btn) — audit-bevinding
   2026-07-19 (gebruikersmelding "knoppen komen niet overeen met wireframe, alle pagina's"):
   de Razor-markup gebruikt overal `class="btn-primary"`/`class="btn-secondary"` los, zonder
   de aparte basisklasse `.btn` erbij. Zolang de basisopmaak alleen op `.btn` stond, kregen
   die knoppen dus GEEN font/padding/radius/display van hier — enkel de achtergrondkleur
   (die toevallig ook op body/globaal leek te kloppen) en verder browser-defaultstyling
   (Arial, blokweergave, geen radius). Nu op beide varianten zelf, zodat ze ook standalone
   correct zijn; `.btn` blijft daarnaast bestaan voor eventuele toekomstige neutrale knoppen. */
.btn,
.btn-primary,
.btn-secondary {
  font-family: inherit;
  font-weight: 600;
  /* 13.5px i.p.v. 15px (leesbaarheidsaudit 2026-08-23, gebruikersmelding "lettertype van de
     knoppen te groot"). Gemeten hierarchie-inversie: knoptekst stond op 15/600, vrijwel gelijk
     aan de kaartkop (15.5/700) en zwaarder dan de eigenlijke data (13.5/12.5) - chrome
     overschreeuwde de inhoud. Bijkomend: dezelfde 15px zat in een 34px-hoge control
     (--control-height) terwijl .field 15px in 44px heeft, waardoor knoppen ook naast
     invoervelden zwaarder oogden, en de nowrap-knoppen toolbars onnodig breed maakten. 13.5px
     is de dominante label-/datagrootte van de app, dus knoppen zakken nu netjes onder de
     koppen. Hoogte en tapdoel blijven ongewijzigd (34px + .hit-target 44px). */
  font-size: 13.5px;
  border-radius: var(--radius-btn);
  padding: 0 16px;
  border: none;
  cursor: pointer;
  transition: filter .15s, border-color .15s, background .15s, transform .1s ease-out;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  white-space: nowrap;
  flex: none;
  text-decoration: none; /* knop-gestileerde <a>-elementen (bv. QR-download) */
  position: relative; /* anker voor de 44px-hit-area (::before, AAA 2.5.5) */
  /* Vaste hoogte i.p.v. verticale padding (audit-bevinding 2026-07-19, gebruikersmelding
     "knoppen op 1 lijn, zelfde hoogte als zoeken"): .btn-secondary's 1px-border gaf er
     eerder 3px bovenop .btn-primary (35px vs 32px) — beide leunden op padding+border om
     hun hoogte organisch af te leiden, wat nooit exact gelijk uitkomt. Zelfde --control-height
     als de zoekvelden (.cat-search/.geb-search), zodat ze altijd precies uitlijnen. */
  height: var(--control-height);
  box-sizing: border-box;
}

/* AAA 2.5.5 (Target Size Enhanced, 44×44px): de visuele knophoogte blijft 34px
   (wireframe-conform), maar het aanraakdoel wordt onzichtbaar uitgebreid tot ≥44px
   via een pseudo-element — kliks op het pseudo-element activeren het element zelf. */
.btn::before,
.btn-primary::before,
.btn-secondary::before {
  content: "";
  position: absolute;
  inset: -5px;
}

.btn:disabled,
.btn-primary:disabled,
.btn-secondary:disabled {
  cursor: not-allowed;
  opacity: .6;
}

/* Micro-interactie (gebruikersverzoek 2026-07-24): lichte druk-feedback bij klikken, alleen
   op een echte klik (niet :focus) zodat het geen tweede focus-indicator suggereert naast de
   AAA-focusring hierboven. */
.btn:active:not(:disabled),
.btn-primary:active:not(:disabled),
.btn-secondary:active:not(:disabled) {
  transform: scale(.97);
}

.btn-primary {
  background: var(--accent);
  color: var(--accent-fg, #fff);
}

.btn-primary:hover:not(:disabled) {
  background: var(--accent-dark);
}

.btn-secondary {
  background: var(--card);
  color: var(--ink);
  padding: 0 15px;
  /* --bd-input i.p.v. --bd3 (audit-bevinding 2026-07-20): --bd3 haalt maar ~1,45:1 contrast
     tegen --card — voor deze knop is de rand de enige visuele afbakening in de standaardstaat
     (geen hover/focus), dus moet hij aan de WCAG 1.4.11-non-text-contrasteis (≥3:1) voldoen,
     net als --bd-input al voor invoervelden doet. */
  border: 1px solid var(--bd-input, var(--bd3));
}

/* --ink3 i.p.v. --bd-h (heraudit 2026-09-05, WCAG 1.4.11): --bd-h haalde maar 2,08:1 (licht) /
   2,21:1 (donker), waardoor de hover-staat zwakker afgebakend was dan de ruststaat hierboven.
   Het hover-signaal komt aanvullend van de achtergrond, niet alleen van de rand. */
.btn-secondary:hover:not(:disabled) {
  border-color: var(--ink3);
  background: var(--card-h);
}

.field {
  width: 100%;
  font-family: inherit;
  font-size: 15px;
  color: var(--ink);
  background: var(--tint);
  border: 1.5px solid var(--bd-input); /* 1.4.11: veldrand ≥3:1 tegen de achtergrond */
  border-radius: 10px;
  padding: 12px 14px;
  min-height: 44px;
}

.field::placeholder {
  color: var(--mut);
}

.field:focus-visible {
  border-color: var(--accent);
}

/* Herbruikbare bestandskiezer (gebruikersmelding 2026-07-25: "niet stijl" op de native
   "Bestand kiezen | Geen bestand gekozen"-widget). Het native <input type="file"> is niet
   token-conform te stijlen (OS-eigen knop, eigen font, geen radius/kleur) — daarom hetzelfde
   patroon als Categorieen/CategorieDetail/Rapportage/Instellingen/ToolboxInstructies al per
   pagina hadden: het <label> is de zichtbare .btn-secondary-knop, het input ligt er onzichtbaar
   overheen. Bewust GLOBAAL en niet in een .razor.css: <InputFile> is een Blazor-ingebouwd
   component, dus component-scoped CSS matcht het interne <input> alleen via ::deep — een
   globale klasse werkt in elke pagina zonder die herhaling.
   Markup: <label class="btn-secondary file-upload-label">Tekst<InputFile class="file-upload-input" /></label> */
.file-upload-label {
  position: relative;
  overflow: hidden;
}

.file-upload-input {
  position: absolute;
  inset: 0;
  width: 100%;
  opacity: 0;
  cursor: pointer;
}

/* Toetsenbordfocus landt op het onzichtbare (opacity:0) input, waardoor de globale
   :focus-visible-ring mee onzichtbaar was en overflow:hidden op het label hem bovendien
   wegknipte (WCAG 2.4.7). De zichtbare knop (het label) krijgt daarom zelf de ring;
   :focus-within is de fallback voor engines zonder :has() (heraudit 2026-09-05). */
.file-upload-label:focus-within {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

.file-upload-label:has(.file-upload-input:focus-visible) {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Herbruikbaar zoekveld met icoon: <label class="search-field field-label"><svg class="search-field-icon">...</svg><input class="field search-field-input" type="search"></label> */
.search-field {
  position: relative;
  display: block;
}

.search-field-icon {
  position: absolute;
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--mut);
  pointer-events: none;
}

.search-field-input {
  padding-left: 38px;
}

.search-field-input::-webkit-search-cancel-button {
  margin-right: 2px;
}

/* AAA 2.4.13 (Focus Appearance): één globale, dikke focusring met offset voor ÁLLE
   interactieve elementen — verving de eerdere beperkte selector die .btn-primary/
   .btn-secondary standalone, selects en custom knoppen miste. Componenten op donkere
   chrome (sidebar/topbar) krijgen in AdminLayout.razor.css een witte ring. */
:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Herbruikbare utility voor donkere chrome-boxen met een VASTE #262d34-achtergrond buiten de
   admin-topbar (.keur-bulkbar, .bdrf-upgrade-card, .pb-bulkbar, .mkt-dark-banner): de globale
   --focus-ring is in licht thema bijna zwart (~1,1:1 op #262d34, ver onder de AAA 3:1-eis voor
   niet-tekstuele UI-componenten). Zelfde patroon als .admin-topbar ::deep :focus-visible in
   AdminLayout.razor.css, hier als utility zodat elke component met dezelfde donkere achtergrond
   hem kan hergebruiken i.p.v. de override lokaal te herhalen (audit 2026-09-04).
   Heraudit 2026-09-05: bewust een vaste witte ring (13,9:1 op #262d34) i.p.v.
   var(--nav-fg-strong) — die wordt door CompanyAccentCss bij een LICHTE white-label-navkleur
   (bijna) zwart, terwijl deze boxen altijd donker blijven; de ring viel dan opnieuw weg.
   `.toast` (zelfde vaste #262d34) lift mee, zodat UndoToast/Prullenbak-toast geen eigen
   klasse nodig hebben. Boxen die WÉL de navkleur volgen (var(--nav-bg)) gebruiken hieronder
   .nav-chrome-focus. */
.dark-chrome-focus :focus-visible,
.toast :focus-visible {
  outline-color: #fff;
}

/* Tegenhanger voor boxen die de (white-label-)navkleur volgen — .emp-header,
   .emp-install-banner (employee.css: background var(--nav-bg)): de ring volgt dan dezelfde
   automatisch afgeleide voorgrondkleur als de tekst erop (heraudit 2026-09-05). */
.nav-chrome-focus :focus-visible {
  outline-color: var(--nav-fg-strong, #fff);
}

/* Gedeelde inline-veldvalidatiemelding direct onder een veld (heraudit 2026-09-05): was een
   dode border-only utility, terwijl Categorieen een eigen .cat-field-error met dezelfde
   bedoeling had — die is hiernaar gemigreerd. Kleiner/minder nadrukkelijk dan de algemene
   foutmelding bovenaan een dialoog. Markup: <div class="field-error" role="alert">…</div> */
.field-error {
  color: var(--tamber3);
  font-size: 13.5px;
  margin-top: -6px;
  margin-bottom: 8px;
}

.pill {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .8px;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: var(--radius-pill);
  background: var(--chip2);
  color: var(--ink2);
  display: inline-block;
}

.pill-blue { background: var(--tblue2); color: var(--tblue3); }
.pill-amber { background: var(--tamber2); color: var(--tamber3); }
.pill-green { background: var(--tgreen2); color: var(--tgreen3); }
.pill-red { background: var(--tred2); color: var(--tred3); }
.pill-lime { background: var(--tlime2); color: var(--tlime3); }
.pill-accent { background: var(--pink-badge-bg); color: var(--pink-badge-ink); }
.pill-dark { background: #262d34; color: #fff; }

/* Machinevertaling-banner (pl/ro/tr, gebruikersverzoek 2026-07-19) — zelfde amber-toon als
   .pill-amber, maar als volle balk i.p.v. een pil: transparant over vertaalkwaliteit zonder
   de rest van de pagina te overschreeuwen. */
.mt-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--tamber2);
    color: var(--tamber3);
    border: 1px solid var(--tamber3);
    border-radius: 10px;
    padding: 10px 14px;
    font-size: 15px;
    margin-bottom: 12px;
}

.mt-banner-icon {
    flex: none;
}

/* Testomgeving-banner (gebruikersverzoek 2026-07-24, aangesloten bij de heraudit 2026-09-05 —
   TestEnvironmentBanner.razor in App.razor, bovenaan <body>, buiten elk layout). Zelfde amber-
   tokens als .mt-banner (AAA 7:1 in licht én donker thema), maar als randloze volle-breedte-strook.
   Bewust NIET sticky: de banner scrolt op lange publieke pagina's mee weg, het [TEST]-tabtitel-
   prefix (test-env.js) is de altijd-zichtbare tegenhanger. De 100vh-shells (.admin-shell in
   AdminLayout.razor.css, .emp-shell in employee.css) trekken --test-env-banner-h van hun hoogte af
   — daarom een vaste hoogte + nowrap, anders klopt die berekening niet bij een omgevouwen regel. */
html[data-test-env="1"] {
  --test-env-banner-h: 32px;
}

.test-env-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: var(--test-env-banner-h, 32px);
  padding: 0 14px;
  background: var(--tamber2);
  color: var(--tamber3);
  border-bottom: 1px solid var(--tamber3);
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: .02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.test-env-banner-icon {
  flex: none;
}

/* Licentie-upgrade-banner (gebruikersverzoek 2026-07-23) — patroon .mt-banner, in AdminLayout.razor. */
.admin-license-banner {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--tamber2);
    color: var(--tamber3);
    border: 1px solid var(--tamber3);
    border-radius: 10px;
    padding: 10px 14px;
    font-size: 15px;
    margin-bottom: 12px;
}

.admin-license-banner .linkbtn {
    color: var(--tamber3);
    font-weight: 600;
}

/* Kaart-scherm (Leaflet-marker, wwwroot/js/map.js): Leaflet injecteert deze DOM buiten de
   Blazor-renderboom om (raw JS, geen Razor-markup), dus dit hoort in de GLOBALE tokens.css,
   niet in Kaart.razor.css — component-scoped CSS zou hier nooit matchen (CLAUDE.md-les:
   "globale classes zijn hier niet gevoelig voor scoping", nu ook van toepassing op
   niet-Blazor-elementen, niet alleen op RenderTreeBuilder-markup). */
.qrdb-map-pin span {
  display: block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
  transition: transform .15s ease;
}

/* Highlight bij tabelrij-hover/-selectie of pin-hover/-klik (Kaart.razor, gebruikersverzoek
   2026-07-24) — vergroot de pin en geeft hem de accentkleurring, zichtbaar op elke pinkleur. */
.qrdb-map-pin-active span {
  transform: scale(1.5);
  outline: 3px solid var(--accent);
  outline-offset: 1px;
}

/* Donker thema voor de OSM-tiles (heraudit 2026-09-05): OpenStreetMap levert geen donkere
   tile-variant en de CSP (img-src) staat alleen *.tile.openstreetmap.org toe, dus een
   CSS-filter op de tiles i.p.v. een andere tile-bron. Zonder dit stond in dark mode een fel
   licht kaartvlak in een verder donkere kaart, en tijdens het laden Leaflet's eigen
   `.leaflet-container { background: #ddd }`. Statisch filter, geen animatie — geen
   reduced-motion-werk. De pins behouden hun 2px witte rand (.qrdb-map-pin span hierboven) en
   blijven daarmee ook op de gefilterde, donkere tiles afgebakend; leaflet.css laadt lazy ná
   tokens.css, vandaar de `:root[data-dark="1"]`-prefix voor de specificiteit. */
:root[data-dark="1"] .leaflet-container {
  background: var(--card);
}

:root[data-dark="1"] .leaflet-tile {
  filter: brightness(.7) invert(1) contrast(3) hue-rotate(200deg) saturate(.3) brightness(.7);
}

/* Popup-leesbaarheid (gebruikersmelding 2026-07-24: "niet leesbaar" — twee oorzaken. (1) De
   4 regels (code — product / naam / datum / tijd) plakten tegen elkaar zonder regelafstand.
   (2) Leaflet's eigen popup-DOM (.leaflet-popup-content-wrapper/.leaflet-popup-tip, leaflet.css:
   `background: white; color: #333`) is niet thema-bewust — de datumregel in --ink2 werd in
   donkere modus lichtgrijs-op-wit. Heraudit 2026-09-05: i.p.v. vaste kleuren op een witte
   popup wordt de wrapper nu zelf thema-bewust gemaakt (var(--card)/var(--ink), zelfde tokens
   als .qrdb-map-legend hiernaast) — anders stond in dark mode een fel witte popup naast een
   donkere legenda. Let op: map.js laadt leaflet.css LAZY, dus ná tokens.css; elke Leaflet-
   override hier heeft daarom een `.leaflet-container`-prefix nodig om op specificiteit (niet op
   bronvolgorde) te winnen — ook de margin/line-height-regels hieronder, die anders dode code
   zijn. --mut (niet --ink2) voor de gedempte regels: haalt in beide thema's ≥7:1 op --card. */
.leaflet-container .leaflet-popup-content-wrapper,
.leaflet-container .leaflet-popup-tip {
  background: var(--card);
  color: var(--ink);
}

.leaflet-container .leaflet-popup a.leaflet-popup-close-button {
  color: var(--mut);
}

.leaflet-container .leaflet-popup a.leaflet-popup-close-button:hover,
.leaflet-container .leaflet-popup a.leaflet-popup-close-button:focus-visible {
  color: var(--ink);
}

.leaflet-container .leaflet-popup-content {
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  margin: 11px 15px;
}

.qrdb-map-pin-person {
  display: inline-block;
  margin-top: 4px;
  font-weight: 600;
}

.qrdb-map-pin-date {
  display: inline-block;
  margin-top: 4px;
  color: var(--mut);
  font-size: 12.5px;
}

.qrdb-map-pin-accuracy {
  display: inline-block;
  margin-top: 4px;
  color: var(--mut);
  font-size: 12.5px;
}

/* Legenda-control (L.Control.extend, map.js) — zelfde reden als .qrdb-map-pin hierboven:
   Leaflet's eigen DOM, buiten de Blazor-renderboom. AAA-contrast: --ink/--card halen al
   ≥7:1 (zelfde tokens als de rest van de app). Ook de neutrale "magazijn"-legendastip
   (.qrdb-map-legend-dot-neutral) staat hier, met eigen donkere variant — de kaartpin zelf
   houdt de vaste kleur: die heeft een eigen witte rand op de (licht óf gefilterd-donkere)
   OSM-tiles (heraudit 2026-09-05). */
.qrdb-map-legend {
  background: var(--card);
  border: 1px solid var(--bd);
  border-radius: 8px;
  padding: 7px 12px;
  font-size: 12.5px;
  color: var(--ink2);
  display: flex;
  gap: 10px;
  box-shadow: 0 3px 10px rgba(38, 45, 52, .12);
}

.qrdb-map-legend span {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

.qrdb-map-legend-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  /* Afbakenende rand (heraudit 2026-09-05, WCAG 1.4.11): de accent-stip haalde in dark mode
     maar ≈1,7:1 op --card en was daarmee als enige koppeling pinkleur→betekenis onzichtbaar;
     --ink3 haalt in beide thema's ≥3:1 tegen --card, onafhankelijk van de vulkleur. */
  border: 1px solid var(--ink3);
  box-sizing: border-box;
}

/* Herstelcodes-rooster (MFA): stond 2× letterlijk als inline style in Login.razor en
   AccountBeveiliging.razor — gedeelde utility, combineren met `.mono` (heraudit 2026-09-05). */
.recovery-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  font-size: 13.5px;
  margin: 12px 0;
}

/* QR-kader (MFA-setup e.d.): wit vlak + padding als quiet zone + rand, zodat de QR-PNG ook op de
   donkere kaart (--card in dark mode) een kader heeft — zelfde waarden als .login-mfa-qr in
   Login.razor.css; globaal omdat AccountBeveiliging.razor geen scoped CSS heeft (heraudit 2026-09-05). */
.qr-frame {
  display: block;
  margin: 4px auto 0;
  border: 1px solid var(--bd);
  border-radius: 8px;
  padding: 8px;
  background: #fff;
}

/* Handtekening-canvas (signature-pad.js) — globaal i.p.v. Handtekening.razor.css-scoped
   (gebruikersverzoek 2026-09-05): GastScan.razor's gast-handtekenstap heeft geen Blazor-
   circuit/component-scope en kan een .razor.css-regel dus niet hergebruiken (CLAUDE.md §2b:
   component-scoped CSS matcht alleen letterlijke markup van dát component). Papier (#fff) en
   inkt zijn bewust thema-onafhankelijk — de PNG wordt vastgelegd en moet in licht én donker
   gelijk zijn. */
.sig-canvas {
  width: 100%;
  max-width: 360px;
  aspect-ratio: 2 / 1;
  background: #fff;
  border: 1.5px solid var(--bd-input);
  border-radius: var(--radius-md);
  touch-action: none;
  box-sizing: border-box;
}

:root[data-dark="1"] .sig-canvas {
  box-shadow: 0 0 0 8px var(--card);
}

/* Neutrale "magazijn"-stip: licht dezelfde #39424b als de kaartpin (Kaart.razor); donker
   was die op --card (#2a323b) ≈1,4:1 en dus onzichtbaar (WCAG 1.4.11 ≥3:1) — --ink3 haalt
   daar ≈8:1 (heraudit 2026-09-05). */
.qrdb-map-legend-dot-neutral {
  background: #39424b;
}

:root[data-dark="1"] .qrdb-map-legend-dot-neutral {
  background: var(--ink3);
}

.toast {
  position: fixed;
  bottom: calc(32px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  background: #262d34;
  color: #fff;
  font-size: 15.5px;
  font-weight: 600;
  padding: 16px 26px;
  border-radius: 10px;
  border-left: 5px solid var(--accent);
  box-shadow: 0 28px 70px rgba(0, 0, 0, .45);
  z-index: 60;
  max-width: min(90vw, 480px);
  /* Eigen keyframe i.p.v. de generieke `pop`: die eindigt op `transform: none` en overschrijft
     daarmee (fill-mode both houdt de eindwaarde vast) de `translateX(-50%)` hierboven — de
     toast stond daardoor met zijn linkerrand op 50% i.p.v. gecentreerd (gemeten 2026-07-25:
     left 480 van 960px breed). toastIn houdt de centrering vast, spiegelbeeld van toastOut. */
  animation: toastIn .25s ease both;
}

@keyframes toastIn {
  from { opacity: 0; transform: translateX(-50%) translateY(14px) scale(.98); }
  to { opacity: 1; transform: translateX(-50%); }
}

/* Toast-uitfade (gebruikersverzoek 2026-07-24) — zet de aanroeper vlak vóór het op null zetten
   van het toast-bericht deze klasse, zodat de toast zichtbaar wegvaagt i.p.v. abrupt uit de DOM
   te verdwijnen (patroon: ShowToastAsync's _toastClosing-vlag, ~200ms wachten vóór de echte
   removal). */
.toast-hide {
  animation: toastOut .2s ease forwards;
}

@keyframes toastOut {
  from { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
  to { opacity: 0; transform: translateX(-50%) translateY(10px) scale(.96); }
}

/* Undo-toast (gebruikersverzoek 2026-07-26): variant van .toast met een inline actieknop.
   .linkbtn erft color:inherit (wit op de donkere toast-achtergrond) — onderstreping geeft
   de klikbare knop AAA-conform extra affordance naast kleur alleen (WCAG 1.4.1). */
.undo-toast {
  display: flex;
  align-items: center;
  gap: 14px;
}

.undo-toast-btn {
  text-decoration: underline;
  font-weight: 700;
  white-space: nowrap;
  flex: none;
}

/* Presence-indicator (item 82 — "collega's kijken mee"): stapeltje initiaal-avatars van
   collega's die op dit moment naar hetzelfde record kijken. Globaal (niet component-scoped)
   omdat PresenceIndicator.razor op meerdere, uiteenlopende detailpagina's hergebruikt wordt. */
.presence-indicator {
  display: inline-flex;
  align-items: center;
  gap: -6px;
}

.presence-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-fg, #fff);
  display: grid;
  place-items: center;
  font-size: 11.5px;
  font-weight: 700;
  border: 2px solid var(--card);
  margin-left: -8px;
}

.presence-avatar:first-child {
  margin-left: 0;
}

/* ---- Toegankelijkheids-utilities (WCAG 2.2 AAA-pass, 2026-07-19) ---- */

/* Alleen voor schermlezers zichtbaar (labels, context bij symbolen). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Reset voor échte <button>-elementen die visueel als tekst/label ogen (sorteerkoppen,
   rij-acties, sluitkruisjes) — voorheen niet-focusbare <span @onclick>-elementen (2.1.1). */
.linkbtn {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
  position: relative; /* anker voor eventuele hit-area-uitbreiding */
}

/* Uitbreidbaar aanraakdoel voor kleine tekstknoppen binnen ruime rijen (AAA 2.5.5):
   verticaal ruim, horizontaal beperkt zodat naburige acties elkaar niet overlappen. */
.hit-target {
  /* Anker voor de ::before hieronder. Zonder dit rekt de absolute pseudo zich uit naar het
     dichtstbijzijnde gepositioneerde voorouder-element — bij .hit-target zonder .linkbtn
     (die zelf al relative is) was dat de hele pagina, waardoor zulke knoppen onzichtbaar
     kliks over de volledige pagina stalen (bevinding 2026-07-22: de conditie-sterren op
     ProductDetail onderschepten elke klik op de tab-balk). */
  position: relative;
}

.hit-target::before {
  content: "";
  position: absolute;
  inset: -13px -6px;
}

/* Sorteerbare kolomkop-knop (herbruikbaar, gebruikersverzoek 2026-07-21 "tabellen
   uitbreiden") — voorheen 1-op-1 gekopieerd per pagina (.cat-sortable/.geb-sortable). */
.sortable-th {
  cursor: pointer;
  white-space: nowrap;
}

.sortable-th:hover {
  color: var(--ink);
}

/* Realtime-highlighting op een tabelrij (herbruikbaar) — korte vervagende achtergrondflits ná
   een SignalR-gemelde wijziging. Enige bron sinds de heraudit van 2026-09-05: de eerdere
   pagina-eigen kopieën (Categorieen/Uitleningen/Financieel/Sets/Vestigingen) zijn verwijderd. */
.row-highlight-flash {
  animation: rowChangedFlash 2s ease-out;
}

@keyframes rowChangedFlash {
  0% { background-color: var(--chip2); }
  100% { background-color: transparent; }
}

/* Skip-link (2.4.1 Bypass Blocks): onzichtbaar tot hij toetsenbordfocus krijgt. */
.skip-link {
  position: absolute;
  left: 8px;
  top: 8px;
  z-index: 200;
  background: var(--card);
  color: var(--ink);
  font-weight: 600;
  padding: 10px 16px;
  border-radius: 8px;
  border: 2px solid var(--focus-ring);
  transform: translateY(-200%);
}

.skip-link:focus-visible {
  transform: none;
}

/* ---- Scrollbar (gebruikersverzoek 2026-07-22) ----
   Dunne, thema-conforme scrollbar i.p.v. de kale OS-standaard — Firefox via
   scrollbar-width/-color, Chromium/Edge/Safari via ::-webkit-scrollbar. Kleuren volgen de
   lichte/donkere tokens, dus geen aparte dark-mode-regel nodig. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--bd-input) transparent; /* zelfde ≥3:1-duim als de webkit-variant hieronder (heraudit 2026-09-05) */
}

*::-webkit-scrollbar {
  width: 11px;
  height: 11px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

/* --bd-input i.p.v. --bd-h (heraudit 2026-09-05, WCAG 1.4.11): de custom-gestylede duim haalde
   met --bd-h maar 2,08:1 (licht) / 2,21:1 (donker) tegen de kaartachtergrond. */
*::-webkit-scrollbar-thumb {
  background-color: var(--bd-input);
  border-radius: var(--radius-pill);
  border: 3px solid transparent;
  background-clip: padding-box;
}

*::-webkit-scrollbar-thumb:hover {
  background-color: var(--mut);
}

*::-webkit-scrollbar-corner {
  background: transparent;
}

/* Print-stylesheet (gebruikersverzoek, item 40 — vooral bedoeld voor Rapportage.razor): globaal
   i.p.v. component-scoped CSS omdat de te verbergen chrome (sidebar/topbar) in andere componenten
   leeft dan de rapportagepagina zelf. Component-CSS-isolatie voorkomt niet dat een globale
   stylesheet gewone klassenamen buiten zijn eigen component matcht — alleen de omgekeerde
   richting (scoped-regels die naar buiten lekken) is uitgesloten. */
@media print {
  .admin-sidebar, .admin-topbar, .admin-topbar-title-mobile, .admin-sidebar-toggle,
  /* .admin-dock/.admin-bell-dock zijn de echte rootklassen van AdminInboxDockPanel en
     AdminNotificationDockPanel — de eerdere .admin-inbox-dock/.admin-notification-bell bestonden
     nergens, dus beide docks bleven bij afdrukken zichtbaar (heraudit 2026-09-05). */
  .admin-license-banner, .admin-dock, .admin-bell-dock, .skip-link {
    display: none !important;
  }

  .admin-content { margin: 0 !important; padding: 0 !important; }

  .btn, .btn-primary, .btn-secondary, .linkbtn, button {
    display: none !important;
  }

  body { background: #fff !important; color: #000 !important; }

  .card { box-shadow: none !important; border: 1px solid #ccc !important; }
}

/* Dialoogkaart-animatie, globaal toegepast op ÉLKE dialoog via het [role="dialog"]-attribuut
   (gebruikersverzoek 2026-07-24) — i.p.v. de animatie in elke afzonderlijke .razor.css te
   herhalen (33 dialoogbestanden, zie CLAUDE.md). Matcht alleen de kaart zelf (role zit altijd
   op de kaart-div, niet de backdrop), dus botst niet met een eventuele fadeUp-animatie op de
   backdrop erboven. */
[role="dialog"] {
  /* `backwards` i.p.v. `both`: bij fill-mode both houdt de browser ná afloop de identiteits-
     transform van het 100%-keyframe vast, waardoor de dialoogkaart permanent het containing
     block wordt van eventuele `position: fixed`-nakomelingen (toast/bevestigingsdialoog in een
     dialoog). Zonder vertraging is `backwards` visueel identiek aan `both` — alleen de
     eindtoestand valt terug op de basisstijl (transform: none). Zie fadeInOnly hierboven. */
  animation: dialogPop .18s ease backwards;
  /* De 1px .card-rand (var(--bd)) is bedoeld om een kaart af te bakenen tegen de
     pagina-achtergrond — tegen de donkere .xxx-overlay-backdrop erachter oogde diezelfde
     rand als een storend wit kader rond de dialoog (gebruikersmelding 2026-07-25). Een
     schaduw i.p.v. een harde randlijn geeft de dialoog diepte/losstaan van de backdrop
     zonder die zichtbare witte contourlijn. */
  border-color: transparent;
  box-shadow: 0 18px 48px rgba(0, 0, 0, .38);
}

/* Route-transitie (gebruikersverzoek 2026-07-24): korte fade bij paginanavigatie, toegepast
   via een wrapper met @key="NavigationManager.Uri" in AdminLayout/EmployeeLayout — de @key
   dwingt Blazor om bij elke route-wissel een nieuw DOM-element te mounten, wat deze
   keyframe-animatie (i.t.t. een transition) opnieuw laat afspelen.
   Bevinding (2026-07-25): .admin-content's flex-gap (18px, AdminLayout.razor.css) werkt in de
   praktijk nooit tussen de top-level blokken van een pagina — .route-fade is die ene flex-child
   en is zelf een gewoon block-element, dus de gap zit alleen om de wrapper heen. BEWUST NIET
   hier op .route-fade zelf gefixt (bv. display:flex;flex-direction:column;gap:18px) — dat bleek
   een globale regressie op pagina's die al hun eigen margin-bottom/-top tussen blokken hadden
   (bv. Home.razor.css' lokale .toolbar { margin-bottom:12px }-override): de flex-gap telt bovenop bestaande
   margins op i.p.v. ze te vervangen, dus Dashboard ging van 12px naar 30px tussen blokken.
   Los per pagina oplossen (zie o.a. Categorieen.razor.css) op pagina's die het treft, niet
   globaal. */
.route-fade {
  /* fadeInOnly i.p.v. fadeUp: deze wrapper omhult élke pagina-overlay (position: fixed) en mag
     daarom nooit een transform dragen — zie de fadeInOnly-keyframe hierboven. */
  animation: fadeInOnly .18s ease both;
}

/* ---- Vangnet-foutkaart van de <ErrorBoundary> rond @Body in AdminLayout/EmployeeLayout
   (heraudit 2026-09-05). Globaal i.p.v. per layout gescoped: EmployeeLayout heeft geen eigen
   .razor.css en beide layouts tonen exact dezelfde kaart. Alleen tokens, geen losse hex. ---- */
.layout-error {
  max-width: 560px;
  margin: 24px auto;
  padding: 26px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.layout-error-title {
  font-size: 16px;
  font-weight: 800;
  margin: 0;
}

.layout-error-text {
  font-size: 15px;
  color: var(--ink3);
  line-height: 1.5;
  margin: 0;
}

.layout-error .btn-primary {
  align-self: flex-start;
}

/* ---- Skeleton-loaders (gebruikersverzoek 2026-07-24, vaste conventie CLAUDE.md §12) ----
   Vervangt de kale "Bezig met laden..."-statustekst op lijst-/detailpagina's. Basisvorm:
   <div class="skeleton-line" style="width:60%"></div> — hoogte via een van de modifiers
   hieronder, breedte per instantie via inline style (varieert per denkbeeldige inhoud). */
.skeleton-line {
  height: 14px;
  border-radius: 6px;
  background: linear-gradient(90deg, var(--chip) 25%, var(--chip2) 50%, var(--chip) 75%);
  background-size: 200% 100%;
  /* skeletonAppear óók hier (naast op .skeleton-wrap): losse .skeleton-line's zonder wrap
     (dashboard-KPI-cijfers) krijgen zo dezelfde flits-preventie. Dubbel (wrap + line) is
     onschadelijk — een onzichtbare wrap verbergt zijn kinderen toch al. */
  animation: skeletonShimmer 1.4s ease-in-out infinite, skeletonAppear .2s ease .25s backwards;
}

/* Flicker-preventie (gebruikersmelding 2026-08-23: "flickering bij laden tabellen"): bij een
   snelle load (< ~250ms — de meeste paginawissels, zeker met warme cache) flitste het skeleton
   1-2 frames op vóór de echte tabel verscheen. De wrap blijft daarom de eerste 250ms op
   opacity 0 en fade't pas daarna in — een langzame load toont het skeleton nog gewoon, een
   snelle load slaat het visueel volledig over ("delayed spinner"-patroon). Bewust alleen
   opacity (geen transform — containing-block-regel hierboven) en `backwards` zodat de
   delay-fase de from-waarde (0) aanhoudt. Onder prefers-reduced-motion wordt de duration
   globaal .001ms maar blijft de delay staan: zelfde flits-preventie, zonder fade. */
.skeleton-wrap {
  animation: skeletonAppear .2s ease .25s backwards;
}

@keyframes skeletonAppear {
  from { opacity: 0; }
  to { opacity: 1; }
}

.skeleton-line--title { height: 20px; width: 40%; }
.skeleton-line--sm { height: 11px; }

/* Rij-skeleton (tabelrij-vervanging): meerdere .skeleton-line's op één regel. */
.skeleton-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--bd2);
}

.skeleton-row .skeleton-line {
  flex: 1 1 0;
}

.skeleton-row .skeleton-line:first-child {
  flex: 0 0 32px;
  height: 32px;
  border-radius: 50%; /* avatar/icoon-plek */
}

/* Kaart-skeleton (KPI-tegels, detailkaarten). */
.skeleton-card {
  padding: 18px;
}

.skeleton-card .skeleton-line + .skeleton-line {
  margin-top: 10px;
}

/* Lege-staat-illustratie (item 36) — globaal i.p.v. per-pagina-scoped CSS, zodat elke pagina
   het EmptyState.razor-component kan hergebruiken zonder de stijl te herhalen.
   Heraudit 2026-09-05: óók de enige bron voor de kale laad-/fout-/lege-staat-<div>'s die
   voorheen als 24 identieke .xxx-state-kopieën per pagina stonden — padding daarom één keer
   geharmoniseerd op de 32px die die kopieën gebruikten, en de 15px-tekstgrootte hier gezet
   zodat een kale <div class="empty-state"> zonder .empty-state-message hetzelfde oogt. */
.empty-state {
  text-align: center;
  padding: 32px 18px;
  color: var(--mut);
  font-size: 15px;
}

.empty-state-icon {
  width: 56px;
  height: 56px;
  margin: 0 auto 12px;
  opacity: .55;
}

.empty-state-message {
  font-size: 15px;
}

.empty-state-actions {
  margin-top: 14px;
}

/* ---- Gedeelde pagina-utilities (heraudit 2026-09-05, CSS-consolidatie) ----
   Voorheen stonden onderstaande blokken woordelijk gekopieerd in 4-24 .razor.css-bestanden
   (dialoog-set 16×, toggle-switch 4×, formulierlabel/-fout 14-15×, toolbar 15×, enz.), met
   subtiel afwijkende details per kopie (wel/geen fadeUp, padding). Zelfde aanpak als de
   .file-upload-label-migratie: één globale opt-in klasse zonder paginaprefix; een pagina
   houdt uitsluitend zijn afwijking (breedte, z-index, marge) lokaal in de .razor.css — de
   component-scoped selector wint daar door zijn hogere specificiteit vanzelf. Globale
   klassen werken ook binnen component-scoped markup (CLAUDE.md §2b), dus geen ::deep nodig.
   Bewust ná .card geplaatst: .tablewrap/.overlay-dialog overschrijven diens padding op
   gelijke specificiteit via bronvolgorde. */

/* Dialoog: <div class="overlay-backdrop"><div class="card overlay-dialog" role="dialog" …>
     <h2 class="overlay-title">…</h2> … <div class="overlay-actions">…</div></div></div> */
.overlay-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 20, .35);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 50;
  /* `backwards` i.p.v. `both` (CLAUDE.md §2.13): fill-mode both houdt de eind-transform vast
     en maakt deze backdrop het containing block van eventuele position:fixed-nakomelingen. */
  animation: fadeUp .2s ease backwards;
}

.overlay-dialog {
  width: 380px;
  max-width: calc(100vw - 40px);
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.overlay-title {
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 10px;
}

.overlay-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

/* Toggle-switch: <button type="button" role="switch" class="toggle-switch toggle-switch-on">
     <span class="toggle-switch-knob" aria-hidden="true"></span></button> */
.toggle-switch {
  width: 34px;
  height: 19px;
  flex: none;
  border-radius: 10px;
  /* 1.4.11: uit-stand moet als control herkenbaar zijn — --bd3 haalde 1,45:1. */
  background: var(--bd-input);
  position: relative;
  cursor: pointer;
  border: none;
  padding: 0;
  transition: background .2s;
}

/* AAA 2.5.5: hit-area van de 34×19-switch onzichtbaar uitgebreid tot ≥44×44. */
.toggle-switch::after {
  content: "";
  position: absolute;
  inset: -13px -5px;
}

.toggle-switch:disabled {
  cursor: not-allowed;
  opacity: .5;
}

.toggle-switch-on {
  background: var(--accent);
}

.toggle-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  /* Decoratieve toggle-knop, geen tekst — geen 7:1-contrasteis van toepassing. */
  background: #fff;
  transition: left .2s;
}

.toggle-switch-on .toggle-switch-knob {
  left: 17px;
}

/* Formulier in een dialoog: label boven een veld + foutmelding onder een veld/bovenaan. */
.form-field-label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--mut);
  margin-top: 8px;
}

.form-error {
  color: var(--tamber3);
  font-size: 13.5px;
  margin-top: 6px;
}

/* Verplicht-veld-indicator (2026-09-13, bugmelding: Projecten-aanmaak/-bewerkdialoog vroeg
   server-side drie verplichte velden — projectnummer/naam/klantnaam — zonder dat ook maar één
   label dat liet zien; de gebruiker kreeg pas ná "Aanmaken" een foutmelding, en dan nog maar
   voor één veld tegelijk). Decoratief (aria-hidden) — het bijbehorende input-element draagt
   zelf `aria-required="true"` voor schermlezers, i.p.v. op dit symbool te leunen. */
.field-required-mark {
  color: var(--accent-ink);
  font-weight: 700;
}

.form-required-note {
  font-size: 12.5px;
  color: var(--mut);
  margin: 2px 0 0;
}

/* Lijstpagina-bouwstenen: knoppenrij, opvul-element in een flexrij, kaart-als-tabelwrapper
   en de tabelvoet met telling + paginering. */
.toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.spacer {
  flex: 1;
}

.tablewrap {
  padding: 0;
  overflow-x: auto;
}

.table-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 18px;
  border-top: 1px solid var(--bd2);
  font-size: 12.5px;
  color: var(--mut);
}

/* Filterchip (aan/uit) in een toolbar: <button class="filter-chip filter-chip-on" aria-pressed>. */
.filter-chip {
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  padding: 7px 14px;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  background: var(--card);
  color: var(--ink2);
  border: 1px solid var(--bd3);
  /* Alleen de drie daadwerkelijk wisselende eigenschappen, geen `all`. */
  transition: background-color .15s, color .15s, border-color .15s;
  position: relative;
}

/* AAA 2.5.5: hit-area onzichtbaar uitgebreid tot ≥44px hoog. */
.filter-chip::before {
  content: "";
  position: absolute;
  inset: -6px -4px;
}

.filter-chip-on {
  /* Zelfde vaste donkere chrome-kleur als .dark-chrome-focus-boxen (bewust niet var(--nav-bg):
     de chips blijven donker, ook bij een lichte white-label-navkleur); wit erop is ruim boven
     AAA 7:1-contrast. */
  background: #262d34;
  color: #fff;
  border-color: #262d34;
}

/* Publieke auth-/statuspagina's (Login, Wachtwoord vergeten/resetten, Wachtstand, Onboarding,
   Error, NotFound): gecentreerde schil + merk + kaart. */
.auth-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 34px 16px 50px;
}

.auth-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: inherit;
  border-radius: 12px;
}

.auth-brand-name {
  font-weight: 800;
  font-size: 19px;
  letter-spacing: .5px;
}

.auth-card {
  width: 380px;
  max-width: 100%;
  padding: 26px;
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* `backwards`: zie .overlay-backdrop hierboven. */
  animation: fadeUp .35s ease backwards;
}

.auth-title {
  font-size: 16px;
  font-weight: 800;
  margin-bottom: 8px;
}

.auth-subtext {
  font-size: 15px;
  color: var(--ink3);
  line-height: 1.5;
  margin: 0 0 4px;
}

.auth-back-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: var(--control-height);
  padding: 0 13px;
  border-radius: 999px;
  border: 1px solid var(--bd3);
  background: var(--card);
  color: var(--ink2);
  font-size: 13.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  flex: none;
  transition: border-color .15s ease, color .15s ease, background-color .15s ease;
}

.auth-back-link:hover {
  border-color: var(--accent-ink, var(--accent));
  color: var(--accent-ink, var(--accent));
  background: var(--pink-badge-bg);
}
