/* ============================================================================
   gloei.css — Fonkelzorg design system (platform-breed)
   ============================================================================
   Eén bron voor alle statische fz-*-componentstyling en design-tokens.
   Geladen via {% static %} in base.html en base_publiek.html.

   WAT HOORT HIER: elke statische fz-*-definitie en elk design-token.
   WAT NIET: per-request DB-waarden (huisstijlkleuren, merkbalk-gradient) —
   die blijven als klein inline <style>-blok in de base-templates staan.
   Nieuwe fz-*-componenten landen HIER, niet in een paginaspecifiek <style>.

   ── Breakpoints (conventie; CSS-variabelen werken niet in media queries) ──
   DOEL   900px — platform-standaard voor nav/detail-reflow. Vastgesteld
                  2026-08-10; stond hier als 768px met de app-shell op 900 en
                  een open punt ertussen. Zie de noot hieronder.
   HUIDIG — volledige lijst; let op: de publieke flow wijkt af van het platform.
     600px  .fz-grid-2 in DIT bestand — geldt alleen voor het PLATFORM. In de publieke
            flow wordt deze media-query overschreven: base_publiek.html herdeclareert
            .fz-grid-2 ná de <link> (gelijke specificiteit, latere bron wint).
     480px  .fz-grid-2 in de PUBLIEKE flow (base_publiek.html) + inschrijfformulier.html
     768px  iOS-autozoom-preventie (16px invoerveld) in base_publiek.html
     900px  app-shell: de zijbalk wordt off-canvas (2026-08-08, zie DECISIONS.md)
     900px / 520px  launcher-tegels (launcher.html, FB-23)
   Harmoniseren betekent dus BEIDE takken aanpassen — niet alleen deze file.

   NOOT bij het DOEL van 900px — waarom niet 768px, de gangbare tabletmaat.
   Onder 900px houdt de inhoud minder dan 680px over naast een zijbalk van
   220px, en daar passen de datadichte tabellen niet meer in. Op 768px zou de
   zijbalk op een tablet in portret blijven staan met 548px voor de inhoud —
   precies de maat waarop de kolommen weer over elkaar heen vallen. 900px sluit
   bovendien aan bij het bestaande launcher-breekpunt, dus het is geen nieuw
   getal.

   Dit stond hier tot 2026-08-10 als open punt: het DOEL was 768px, de app-shell
   week af naar 900, en de keuze hoorde bij "het bredere responsive-traject".
   Dat traject is af, en de meting die het besliste is deze. Op 800px — een
   tablet in portret — is er nu 738px voor de inhoud, en de opvolgingslijst
   vraagt er 1212. Met een breekpunt op 768 zou daar een zijbalk van 220px bij
   staan (~518px) én zou de kolomverberging van U4 niet vuren, want die geldt
   alleen ÓNDER het breekpunt: alle zes kolommen in een smallere kolom, precies
   het defect dat PR #5 wegnam.

   900px is dus geen afwijking meer maar de norm; verschuif hem niet zonder een
   meting die het tegendeel laat zien. De gebruikerskeuze om de zijbalk boven
   het breekpunt zelf in te klappen staat los daarvan en ligt in de backlog als
   FB-30 — die raakt dit getal niet.

   ── Tabel-maten (conventie) ──
   DOEL   min-width op datadichte tabellen + overflow-x:auto op de wrapper,
          zodat kolommen leesbaar blijven i.p.v. samen te knijpen.
          Een --tabel-min-width-variabele landt pas bij de eerste consument.
   ========================================================================= */

/* ---- Fonts (relatieve url's: manifest-storage herschrijft ze) ---- */
      @font-face { font-family: 'Montserrat'; font-style: normal; font-weight: 400 700; font-display: swap;
        src: url("../fonts/montserrat-v31-latin-ext.d16dfe26bcd7.woff2") format('woff2');
        unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
      @font-face { font-family: 'Montserrat'; font-style: normal; font-weight: 400 700; font-display: swap;
        src: url("../fonts/montserrat-v31-cyrillic.6fd02dd74afd.woff2") format('woff2');
        unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
      @font-face { font-family: 'Montserrat'; font-style: normal; font-weight: 400 700; font-display: swap;
        src: url("../fonts/montserrat-v31-latin.311d352d9323.woff2") format('woff2');
        unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

    [x-cloak] { display: none !important; }
    /* ===== Fonkelzorg design system ===== */
    :root {

      --border: var(--line);   /* alias (KTD-3) — publieke templates gebruiken var(--border) */
      --r-sm: 7px;
      --r-md: 11px;
      --r-lg: 16px;
      --shadow-card: 0 1px 2px rgba(28,42,43,.04), 0 6px 22px rgba(28,42,43,.05);
      --shadow-pop:  0 8px 30px rgba(28,42,43,.14);
      --font: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

      /* ---- Spacing-schaal ----
         De vraag is niet "hoeveel marge" maar "welke stap". De waarde staat
         in de naam, zodat niemand hoeft op te zoeken wat --sp-3 zou betekenen.
         Zes stappen en niet meer: elke tussenwaarde maakt de schaal een
         suggestie in plaats van een keuze.
         Aanleiding, gemeten 2026-08-04/05 over de 41 fp-templates: 520
         maat-attributen zonder kleur, met gaps van 1, 2, 3, 4, 5, 6, 8, 10, 12,
         14 en 16px door elkaar. Zie de styleguide, "Tokens & breakpoints",
         voor wanneer je de schaal juist NIET gebruikt. */
      --sp-4:   4px;
      --sp-8:   8px;
      --sp-12: 12px;
      --sp-16: 16px;
      --sp-24: 24px;
      --sp-32: 32px;

      /* Hoogte van de vaste kop, als som van zijn twee delen. De off-canvas
         zijbalk begint hieronder; stond daar een los overgeschreven getal, dan
         schoof de lade over de header zodra iemand de header-hoogte wijzigde —
         inclusief de knop die de lade sluit, en dat is op een telefoon de enige
         weg terug. De border-bottom van de header valt binnen de 64px door de
         globale `box-sizing: border-box` hieronder. */
      --fz-merkbalk-h: 4px;
      --fz-header-h: 64px;
      --fz-kop-h: calc(var(--fz-merkbalk-h) + var(--fz-header-h));
    }

    *, *::before, *::after { box-sizing: border-box; }
    input, select, textarea, button { font-family: inherit; }
    html, body { margin: 0; padding: 0; height: 100%; }
    body { font-family: var(--font); color: var(--ink); background: var(--bg);
           -webkit-font-smoothing: antialiased; letter-spacing: -0.01em; }

    /* ---- Layout-primitieven ----
       Twee klassen voor het patroon dat anders per scherm opnieuw wordt
       uitgeschreven: `display:flex; align-items:center; gap:10px`. Dat staat
       64× in de fp-templates, met gaps die net niet gelijk zijn.

       Waarom twee en niet drie. Het plan stelde `.fz-stapel`, `.fz-rij` én
       `.fz-raster` voor, en vroeg vooraf te meten. De meting over alle 41
       fp-templates: `display:flex` 88×, waarvan 64 met `align-items:center`;
       `flex-direction:column` **nul keer**; `display:grid` 8×, en die acht zijn
       stuk voor stuk een eigen kolomdefinitie (`52px repeat(12,1fr) 90px 32px`)
       die je toch inline moet meegeven. Een `.fz-raster` zou dus geen enkele
       inline stijl vervangen. `.fz-stapel` heeft vandaag ook geen consument,
       maar is de tegenhanger van `.fz-rij` in drie regels — zonder hem grijpt
       de eerstvolgende bouwer die een kolom nodig heeft alsnog naar een eigen
       getal, en dat is precies de drift die dit oplost.

       Wanneer je ze NIET gebruikt: voor maatvoering die bij één scherm hoort.
       Een `max-width:520px` op één formulier blijft een inline `style=`. Dit
       zijn primitieven, geen utility-framework: er komt geen derde, en er komen
       géén losse `.marge-24`-klassen — die zijn alleen een tweede naam voor
       hetzelfde getal. */
    .fz-rij { display: flex; align-items: center; gap: var(--sp-12); }
    .fz-rij.compact { gap: var(--sp-8); }
    .fz-rij.ruim { gap: var(--sp-24); }
    .fz-stapel { display: flex; flex-direction: column; gap: var(--sp-12); }
    .fz-stapel.compact { gap: var(--sp-8); }
    .fz-stapel.ruim { gap: var(--sp-24); }

    /* App shell */
    .fz-app { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
    .fz-body { display: flex; flex: 1; overflow: hidden; }
    .fz-merkbalk { height: var(--fz-merkbalk-h); flex-shrink: 0; }

    /* ---- Header ---- */
    .fz-header {
      flex: 0 0 var(--fz-header-h); display: flex; align-items: center;
      background: #fff; border-bottom: 1px solid var(--line);
      padding: 0 24px; flex-shrink: 0; z-index: 100;
    }
    .fz-launcher-btn {
      display: flex; align-items: center; justify-content: center;
      width: 34px; height: 34px; border-radius: var(--r-sm);
      color: var(--ink-2); text-decoration: none; flex-shrink: 0;
      transition: background .12s, color .12s;
      margin-right: 10px;
    }
    .fz-launcher-btn:hover { background: var(--primary-100); color: var(--primary-700); }
    .fz-launcher-btn svg { width: 18px; height: 18px; }

    /* Menuknop: alleen op smal scherm, en alleen als er een zijbalk ís. De
       launcher heeft er geen, en een knop die niets opent is erger dan geen knop.
       `:has()` houdt die kennis in de stylesheet, zodat de drie app-bases niets
       hoeven te weten.

       De uitzondering is negatief geformuleerd (`:not(:has(...))`) en dat is geen
       smaak. Een browser die `:has()` niet kent gooit de hele regel weg. Stond het
       tonen ín die regel, dan verdween de knop overal terwijl de zijbalk wél
       wegschuift — geen navigatie meer. Nu sneuvelt alleen de uitzondering: de
       knop staat dan óók op de launcher, waar hij niets doet. Dat is de
       onschuldige kant om op te falen. */
    .fz-menu-btn {
      display: none; align-items: center; justify-content: center;
      width: 40px; height: 40px; border-radius: var(--r-sm);
      color: var(--ink-2); background: none; border: none; cursor: pointer;
      flex-shrink: 0; margin-right: 4px; padding: 0;
      transition: background .12s, color .12s;
    }
    .fz-menu-btn:hover { background: var(--primary-100); color: var(--primary-700); }
    .fz-menu-btn:focus-visible { outline: 2px solid var(--primary-500); outline-offset: 2px; }
    .fz-menu-btn svg { width: 20px; height: 20px; }

    .fz-side-overlay {
      position: fixed; inset: 0; z-index: 199;
      background: rgba(15, 23, 42, .38);
    }

    /* Feedbackknop linksonder. Stond inline in `base_platform.html`; verplaatst
       omdat maten in de stylesheet horen en een media query een inline `style`
       niet kan overschrijven.

       Hij groeit bewust NIET naar 44px op een aanraakscherm, hoe verleidelijk
       dat ook is. De knop zweeft `position: fixed` over de inhoud: hij kost
       geen ruimte in de layout maar dékt af. Op 44px bedekte hij gemeten twee
       aantikbare patiëntrijen, en op mijn-account de knop "Wachtwoord
       wijzigen". Voor een feedbackwidget is dat de verkeerde ruil. Of zo'n
       zwevende knop groter kan zonder iets te bedekken is een ontwerpvraag en
       geen maatvraag — hij staat als open punt bij Robert. */
    .fz-meld-knop {
      display: flex; align-items: center; justify-content: center;
      width: 26px; height: 26px; padding: 0;
      border: none; border-radius: 50%; cursor: pointer;
      background: var(--primary-100); color: var(--primary-600);
      transition: background .13s; flex-shrink: 0;
    }
    .fz-header-logo { display: flex; align-items: center; }
    .fz-logoimg { height: 36px; width: auto; display: block; }
    .fz-header-apptitle {
      font-size: 15px; font-weight: 700; color: var(--muted);
      margin-left: 14px; padding-left: 14px; border-left: 2px solid var(--faint);
      white-space: nowrap;
    }
    .fz-header-apptitle:empty { display: none; }
    .fz-header-user { display: flex; align-items: center; gap: 10px; margin-left: auto; }
    .fz-avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--primary-700); color: #fff;
      display: flex; align-items: center; justify-content: center;
      font-weight: 700; font-size: 11px; flex: 0 0 auto; }

    /* ---- Instellingen sectie-nav ---- */
    .fz-sect-nav { display: flex; gap: 4px; flex-wrap: wrap; border-bottom: 2px solid var(--line); }
    .fz-sect-tab {
      display: inline-flex; align-items: center; gap: 7px;
      padding: 7px 16px 9px; border-radius: 6px 6px 0 0;
      font-size: 13.5px; font-weight: 600; text-decoration: none;
      color: var(--ink); border: 1px solid var(--line); border-bottom: none;
      margin-bottom: -2px; background: #fff;
      transition: background .12s, color .12s, border-color .12s;
    }
    .fz-sect-tab:hover { background: var(--primary-100); color: var(--primary-700); border-color: var(--primary-300); }
    .fz-sect-tab.active { background: #fff; color: var(--primary-800); font-weight: 700; border-bottom: 2px solid var(--primary-700); }
    .fz-sect-tab svg { width: 14px; height: 14px; flex-shrink: 0; }

    /* ---- Account menu ---- */
    .fz-acctmenu { position: relative; margin-left: auto; }
    .fz-acct-trigger {
      display: flex; align-items: center; gap: 8px;
      padding: 5px 10px 5px 5px;
      background: none; border: 1.5px solid transparent; border-radius: 10px;
      cursor: pointer; font-family: var(--font); font-size: 13px; color: var(--ink);
      transition: background .15s, border-color .15s;
    }
    .fz-acct-trigger:hover { background: var(--primary-100); border-color: var(--line); }
    .fz-acct-trigger:focus-visible { outline: 2px solid var(--primary-500); outline-offset: 2px; }
    .fz-acct-name { font-weight: 600; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .fz-acct-chevron { width: 14px; height: 14px; color: var(--muted); transition: transform .2s; flex-shrink: 0; }
    .fz-acct-drop {
      position: absolute; right: 0; top: calc(100% + 8px);
      min-width: 230px;
      background: #fff; border: 1px solid var(--line);
      border-radius: 11px; box-shadow: 0 8px 24px rgba(28,42,43,.10), 0 2px 6px rgba(28,42,43,.06);
      z-index: 500; overflow: hidden;
    }
    .fz-acct-identity {
      display: flex; align-items: center; gap: 10px;
      padding: 14px 14px 12px;
    }
    .fz-acct-identity .fz-avatar { width: 36px; height: 36px; font-size: 13px; }
    .fz-acct-idname { font-size: 13.5px; font-weight: 700; color: var(--ink); line-height: 1.3; }
    .fz-acct-idrole { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
    .fz-acct-sep { margin: 0; border: none; border-top: 1px solid var(--line); }
    .fz-acct-item {
      display: flex; align-items: center; gap: 9px;
      padding: 9px 10px; margin: 1px 6px; width: calc(100% - 12px);
      font-size: 13.5px; font-weight: 500; font-family: var(--font);
      color: var(--ink-2); text-decoration: none; background: none; border: none;
      cursor: pointer; text-align: left; transition: background .12s, color .12s;
      border-radius: var(--r-sm);
    }
    .fz-acct-item:hover { background: var(--primary-100); color: var(--primary-800); }
    .fz-acct-item:focus-visible { outline: 2px solid var(--primary-500); outline-offset: -2px; background: var(--primary-100); color: var(--primary-800); }
    .fz-acct-item svg { width: 15px; height: 15px; flex-shrink: 0; }
    .fz-acct-item.danger { color: var(--ink-2); }
    .fz-acct-item.danger:hover { background: var(--primary-100); color: var(--primary-800); }
    .fz-acct-item.danger:focus-visible { outline-color: var(--primary-500); background: var(--primary-100); color: var(--primary-800); }

    /* ---- Sidebar ---- */
    .fz-side {
      width: 220px; flex: 0 0 220px;
      background: #fff; color: var(--ink); border-right: 1px solid var(--line);
      display: flex; flex-direction: column; overflow-y: auto;
    }

    /* Onder 900px schuift de zijbalk over de inhoud in plaats van ernaast. Een
       vaste zijbalk van 220px laat op een telefoon te weinig over: de
       lijstschermen persten hun kolommen dan in de rest, met koppen die bovenop
       elkaar vielen.

       900px en niet 768px: daaronder houdt de inhoud minder dan 680px over en
       passen de bredere tabellen sowieso niet meer naast een zijbalk.

       De schakelaar is een klasse op `.fz-body`, niet op `.fz-side` zelf: die
       laatste staat in drie app-templates en die hoeven hier niets van te weten.

       De meting eronder staat in DECISIONS.md, "De zijbalk wijkt onder 900px" —
       niet hier, want getallen als "170px over" zijn momentopnames van andere
       schermen en verouderen zonder dat iemand het merkt. */
    @media (max-width: 900px) {
      .fz-menu-btn { display: inline-flex; }
      /* Zie de noot hierboven: negatief geformuleerd, zodat een browser zonder
         `:has()` de knop tóónt in plaats van hem te laten verdwijnen. */
      .fz-app:not(:has(.fz-side)) .fz-menu-btn { display: none; }
      .fz-app:has(.fz-side) .fz-header-apptitle { display: none; }

      /* De header is een flex-rij waarvan de items niet krimpen, en op 390px
         paste de som er niet meer in: hamburger, launcher-knop, logo, apptitel
         en een accountmenu van 175px. Flex duwt dan het laatste item naar
         rechts buiten beeld — gemeten 40px, op élk scherm. Dat laatste item is
         hier net het accountmenu, met uitloggen erin.

         De naam in de knop mag weg: hij staat óók in de dropdown zelf
         (`.fz-acct-idname`), dus er verdwijnt geen informatie. Wat overblijft is
         de avatar met de initialen — herkenbaar genoeg om te weten wie is
         ingelogd, en smal genoeg om te passen. */
      .fz-acct-name { display: none; }

      /* En het logo krimpt mee in plaats van zijn breedte op te eisen. */
      .fz-header-logo { min-width: 0; flex-shrink: 1; overflow: hidden; }

      /* Begint ónder de merkbalk (4px) en de header (64px), niet op 0. Anders
         schuift de lade over de header heen — inclusief de knop die hem opende,
         want `.fz-header` is een flex-item met z-index 100 en verliest van de 200
         hier. Sluiten kon dan alleen nog via de overlay of Escape, en op een
         telefoon bestaat Escape niet. */
      .fz-side {
        position: fixed; top: var(--fz-kop-h); bottom: 0; left: 0; z-index: 200;
        transform: translateX(-100%);
        /* `visibility` schakelt mee, anders blijft de dichtgeschoven lade
           focusbaar: `transform` verplaatst alleen de rendering, dus Tab liep
           langs een stuk of twaalf onzichtbare links. De vertraging houdt hem
           zichtbaar tijdens het uitschuiven. */
        visibility: hidden;
        transition: transform .18s ease, visibility 0s linear .18s;
        box-shadow: var(--shadow-pop);
      }
      .fz-body.menu-open .fz-side {
        transform: none; visibility: visible;
        transition: transform .18s ease;
      }

      .fz-side-overlay { top: var(--fz-kop-h); }

      /* De lade is uit de flexrij gestapt; zonder dit zou hij zijn 220px nog
         steeds opeisen. `position: fixed` regelt dat al, maar de expliciete nul
         maakt het leesbaar naast de `flex: 0 0 220px` van de basisregel. */
      .fz-body > .fz-side { flex: 0 0 0; }

    }


    /* Vangnet voor een `menuOpen` die na het verbreden van het venster nog aan
       staat. `not all and (max-width: 900px)` in plaats van `min-width: 901px`:
       tussen die twee ligt bij OS-schaling of zoom een gat op fractionele
       breedtes (900,5px), waar geen van beide regels pakt. */
    @media not all and (max-width: 900px) {
      .fz-side-overlay { display: none; }
    }
    .fz-navgroup { padding: 6px 14px; }
    .fz-navlabel { font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
      color: var(--faint); padding: 14px 10px 7px; }
    .fz-nav { display: flex; flex-direction: column; gap: 2px; }
    .fz-navitem {
      display: flex; align-items: center; gap: 10px; padding: 9px 12px;
      border-radius: var(--r-sm); font-size: 13.5px; font-weight: 500; color: var(--ink-2);
      text-decoration: none; transition: background .12s, color .12s; position: relative;
      border: none; background: none; cursor: pointer; width: 100%; text-align: left;
    }
    .fz-navitem svg { width: 17px; height: 17px; flex: 0 0 auto; opacity: .7; }
    .fz-navitem:hover { background: var(--primary-100); color: var(--primary-800); }
    .fz-navitem:hover::before {
      content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
      width: 3px; background: var(--primary-300); border-radius: 0 3px 3px 0;
    }
    .fz-navitem.active { background: var(--primary-100); color: var(--primary-800); font-weight: 600; }
    .fz-navitem.active::before {
      content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
      width: 3px; background: var(--primary-500); border-radius: 0 3px 3px 0;
    }
    .fz-navitem.active svg { opacity: 1; }
    .fz-navitem .count { margin-left: auto; background: var(--secondary-700); color: #fff;
      font-size: 10.5px; font-weight: 700; padding: 1px 6px; border-radius: 10px; }
    .fz-navitem.soon { color: var(--faint); cursor: default; pointer-events: none; }
    .fz-navitem .soon-tag { margin-left: auto; font-size: 9.5px; font-weight: 700; letter-spacing: .05em;
      text-transform: uppercase; color: var(--faint); border: 1px solid var(--line);
      padding: 1px 5px; border-radius: 4px; }

    /* ---- Main area ---- */
    .fz-main { flex: 1; display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
    .fz-topbar { display: none; }
    .fz-topbar-spacer { flex: 1; }
    /* `overflow-x: auto` en niet `hidden`, om dezelfde reden als bij `.fz-card`:
       klippen laat inhoud verdwijnen zonder dat er een weg naartoe is. De kaart
       vangt het meeste af, maar niet alles staat in een kaart — en wat
       daarbuiten valt, viel met `hidden` letterlijk weg.

       Dit had een tweede, sluipender gevolg. `ui_smal.py` meet of een pagina
       horizontaal past; met een klippende container meldde die meting overal
       "past prima", ook waar een halve tabel buiten beeld hing. Een blinde
       meting is erger dan geen meting, want ze stelt gerust. */
    .fz-content { flex: 1; overflow-y: auto; overflow-x: auto; padding: 26px 30px; scrollbar-gutter: stable; }

    /* ---- Search ---- */
    .fz-search { height: 38px; border: 1px solid var(--line); background: var(--line-2);
      border-radius: 999px; display: flex; align-items: center;
      gap: 9px; padding: 0 14px; color: var(--muted); cursor: text; }
    .fz-search svg { width: 16px; height: 16px; flex: 0 0 auto; }
    .fz-search input { flex: 1; min-width: 0; border: none; background: none; outline: none;
      font-family: var(--font); font-size: 13.5px; color: var(--ink); padding: 0; }
    .fz-search input::placeholder { color: var(--muted); }
    .fz-search:focus-within { border-color: var(--primary-500); background: #fff;
      box-shadow: 0 0 0 3px var(--focus-ring); }

    /* ---- Typography ---- */
    .fz-h1 { font-size: 20px; font-weight: 700; letter-spacing: -0.025em; color: var(--ink); white-space: nowrap; }
    .fz-sub { font-size: 13px; color: var(--muted); margin: 3px 0 0; line-height: 1.4; }

    /* ---- Buttons ---- */
    .fz-btn { font-family: var(--font); display: inline-flex; align-items: center; gap: 8px;
      height: 40px; padding: 0 16px; border-radius: var(--r-sm); font-size: 13.5px; font-weight: 600;
      border: 1px solid var(--line); background: #fff; color: var(--ink); cursor: pointer;
      text-decoration: none; transition: background .12s; }
    .fz-btn.primary { background: var(--primary-700); border-color: var(--primary-700); color: #fff; }
    .fz-btn.primary:hover { background: var(--primary-800); }
    .fz-btn.secondary { border-color: var(--primary-700); color: var(--primary-700); }
    .fz-btn.secondary:hover { background: var(--primary-100); }
    .fz-btn.danger { background: #fff; border-color: var(--line); color: var(--danger-600); }
    .fz-btn.danger:hover { background: var(--danger-100); border-color: var(--danger-300); color: var(--danger-700); }
    .fz-btn.danger:focus-visible { outline-color: var(--danger-600); background: var(--danger-100); }
    .fz-btn.yellow { background: var(--secondary-700); border-color: var(--secondary-800); color: #fff; }
    .fz-btn.yellow:hover { background: var(--secondary-800); border-color: var(--secondary-800); }


    .fz-btn[disabled], .fz-btn:disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }

    /* ---- Tabs ---- */
    .fz-tabs { display: inline-flex; background: var(--line-2); border: 1px solid var(--line);
      border-radius: var(--r-sm); padding: 3px; gap: 2px; }
    .fz-tab { height: 30px; padding: 0 13px; border-radius: 5px; border: none; background: none;
      font-family: var(--font); font-size: 13px; font-weight: 600; color: var(--muted); cursor: pointer;
      display: flex; align-items: center; gap: 7px; text-decoration: none; }
    .fz-tab.active { background: #fff; color: var(--primary-800); box-shadow: 0 1px 2px rgba(0,0,0,.06); }
    .fz-tab .n { font-size: 11px; font-weight: 700; background: rgba(0,0,0,.06);
      padding: 0 6px; border-radius: 9px; }
    .fz-tab.active .n { background: var(--primary-100); color: var(--primary-700); }
    /* Ruime maat: voor een tabreeks die een dimensie navigeert in plaats van een
       lijst te filteren — de jaar-switcher van de financiële planning staat op
       dertien schermen en gaat naar hetzelfde scherm voor een ander jaar. Ronder
       en luchtiger dan de filtertabs, en hij mag wrappen: een plan van tien jaar
       levert tien tabs en die passen niet altijd op één regel.
       Fase 3, U10-besluit 1 (DECISIONS.md, 2026-08-05). */
    .fz-tabs.ruim { border-radius: 10px; gap: 4px; flex-wrap: wrap; }
    .fz-tabs.ruim .fz-tab { height: 32px; padding: 0 18px; border-radius: 8px; }

    /* ---- Status badges ---- */
    .fz-status { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600;
      padding: 4px 10px 4px 8px; border-radius: 20px; }
    .fz-status .dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
    .fz-status.nieuw { background: var(--secondary-700); color: #fff; }
    .fz-status.nieuw .dot { background: rgba(255,255,255,.6); }
    .fz-status.ingeschreven { background: var(--success-100); color: var(--success-900); }
    .fz-status.ingeschreven .dot { background: var(--success-700); }
    .fz-status.uitgeschreven { background: var(--line-2); color: var(--muted); }
    .fz-status.uitgeschreven .dot { background: var(--muted); }
    .fz-status.error { background: var(--danger-100); color: var(--danger-800); }
    .fz-status.error .dot { background: var(--danger-500); }

    /* ---- Card ----------------------------------------------------------------
       `overflow-x: auto` en niet `overflow: hidden`, en dat verschil is een bug
       geweest en geen smaak.

       De kaart klipte onvoorwaardelijk. Een tabel die breder is dan de kaart
       werd dus afgeknipt zónder scrollmogelijkheid: op de gebruikerslijst stond
       958px inhoud in een kaart van 308px, en de kolommen Rollen, Organisatie,
       Laatste activiteit en Status waren op 390px niet te bereiken — ook niet
       door te vegen. Op 800px, een tablet in portret, viel er nog altijd 240px
       weg. "Het scrolt tenminste" bestond niet als vangnet.

       Eenentwintig schermen hadden dat inline al opgelost met
       `style="padding:0;overflow-x:auto"` op precies deze klasse. Dat is de
       meting die zegt dat het gedrag in de component hoort en niet per scherm:
       wie de inline stijl vergat, verloor stil zijn kolommen. Zie de
       tabelconventie in de kop van dit bestand.

       `overflow-y` blijft `hidden`, want daar zit de reden voor de oude regel:
       zonder verticale klip steekt de achtergrond van een `thead` over de
       afgeronde hoek heen. */
    .fz-card { background: var(--surface); border: 1px solid var(--primary-300);
      border-radius: var(--r-md); box-shadow: var(--shadow-card);
      overflow-x: auto; overflow-y: hidden; }

    /* ---- Table ---- */
    .fz-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
    /* Vaste kolombreedtes uit de `<colgroup>`. Stond als inline `style` op de
       vier kieslijsten; verplaatst omdat een media query er dan niet bij kan —
       en op smal scherm moet de tabel juist naar inhoud herverdelen zodra er
       kolommen verborgen zijn. */
    .fz-table.vast { table-layout: fixed; }
    .fz-table thead th { text-align: left; font-size: 11px; font-weight: 700; letter-spacing: .06em;
      text-transform: uppercase; color: var(--muted); padding: 13px 18px;
      border-bottom: 1px solid var(--primary-300); background: var(--primary-100); }
    .fz-table tbody td { padding: 13px 18px; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
    .fz-table tbody tr:last-child td { border-bottom: none; }
    .fz-table tbody tr:hover { background: #f1f5f9; }
    .fz-table tfoot td { padding: 13px 18px; border-top: 1px solid var(--line-2); vertical-align: middle; }
    .kleur-radio { appearance:none;-webkit-appearance:none;display:inline-block;width:20px;height:20px;border-radius:50%;cursor:pointer;margin:0;box-sizing:border-box;vertical-align:middle;flex-shrink:0 }
    .kleur-radio:checked { box-shadow:inset 0 0 0 3px #fff, 0 0 0 2px rgba(0,0,0,.3) }
    .fz-select { height:auto;border:1px solid var(--line);border-radius:8px;padding:4px 28px 4px 12px;font-size:12.5px;font-family:var(--font);font-weight:600;color:var(--ink-2);background:#fff;cursor:pointer;transition:border-color .12s;appearance:none;-webkit-appearance:none;-moz-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center }
    .fz-select:hover { border-color:var(--primary-500) }
    .fz-select:focus { outline:none;border-color:var(--primary-500);box-shadow:0 0 0 3px var(--focus-ring) }
    .fz-select.compact { height:34px; }
    .fz-rowavatar { width: 34px; height: 34px; border-radius: 9px; flex: 0 0 auto;
      background: #e2e8f0; color: #334155; display: flex; align-items: center;
      justify-content: center; font-weight: 700; font-size: 12px; }
    .fz-rowavatar.lg { width: 44px; height: 44px; border-radius: 12px; font-size: 16px;
      background: #e2e8f0; color: #334155; }
    .fz-rowgo { display: inline-flex; align-items: center; gap: 4px; font-weight: 600;
      color: var(--primary-700); font-size: 13px; text-decoration: none; }
    .fz-rowgo svg { width: 14px; height: 14px; }
    .fz-table tbody tr.click { cursor: pointer; }

    /* ---- Field cards (detail page) ----------------------------------------
       `overflow-x: auto` om dezelfde reden als bij `.fz-card`: klippen laat
       inhoud verdwijnen zonder weg ernaartoe. Gemeten op 390px: op de
       huisstijlinstellingen stond 372px in een kaart van 308px, en de rechterhelft
       van de kleurkiezer viel weg.

       Het bleef een vangnet zolang de oorzaak stond: een inline
       `grid-template-columns: 180px 1fr`, buiten het bereik van elke media query.
       Die staat er niet meer — zie `.fz-fieldrow` hieronder, dat het patroon een
       naam gaf en het laat stapelen. Het vangnet blijft, want een kaart kan nog
       altijd iets bevatten dat niet meebuigt (een brede tabel, een preview), en
       dan is scrollen nog steeds beter dan wegknippen. */
    .fz-fieldcard { background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
      margin-bottom: 14px; overflow-x: auto; overflow-y: hidden; }
    .fz-fieldhead { font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
      color: var(--primary-700); padding: 13px 18px; background: var(--primary-100);
      border-bottom: 1px solid var(--primary-300); }
    .fz-field { display: flex; align-items: center; gap: 14px; padding: 11px 14px 11px 18px;
      border-bottom: 1px solid var(--line-2); }
    .fz-field:last-child { border-bottom: none; }
    .fz-field .lbl { width: 160px; flex: 0 0 auto; font-size: 12.5px; color: var(--muted); font-weight: 600; }
    .fz-field .val { flex: 1; font-size: 14px; font-weight: 500; color: var(--ink); }

    /* ---- Instellingenrij: label links, bediening rechts --------------------
       De bewerkbare tegenhanger van `.fz-field` hierboven. Waar `.fz-field` een
       waarde TOONT, draagt deze rij een invoerveld, een schakelaar of een
       kleurkiezer — vandaar het bredere labelspoor (180 om 160) en `align-items:
       center` in plaats van de flex-uitlijning.

       Deze klasse bestond niet. Het patroon wel: 67 keer, inline, in twaalf
       instellingentemplates over twee apps, en op vijftig daarvan tot op de
       pixel identiek. Dat is precies de drift die de styleguide-afspraak wil
       voorkomen — maar hier had het ook een gemeten gevolg. Een inline
       `grid-template-columns` valt buiten het bereik van élke media query, dus
       de rijen konden onder het breekpunt niet stapelen. Ze scrollden in plaats
       daarvan zijwaarts (372px inhoud in 308px op huisstijl, 315 in 308 op
       mijn-account), en dat scrollen was niet eens de bedoeling: het is het
       vangnet uit de klip-fix, dat hier de enige uitweg werd. Dezelfde val als
       bij de launcher (U5) en bij de kleine tekst (U2).

       Wat NIET in de klasse zit: afwijkende padding en `opacity` blijven inline
       staan. Dat is maatvoering per scherm en geen huisstijl.

       De tussenruimte is `--fieldrow-gap` en geen inline `gap`, om dezelfde
       reden als bij `.dicht` hieronder: een inline declaratie wint van élke
       media query, dus tien rijen hielden onder het breekpunt hun
       bureau-tussenruimte van 10 tot 28px in plaats van de 6px waarop ze
       stapelen. Via de variabele pakt het 900px-blok ze wél.

       Het labelspoor is om diezelfde reden een variabele en geen reeks
       maatvarianten. Er lopen er vier rond — 180 (het gros), 160 (de
       styleguide), 140 (mijn account) en 100 (de tekstblokken) — en dat zijn
       vier keer dezelfde component met een ander getal, niet vier componenten.
       `--fieldrow-label` houdt het bij één regel CSS; een `.smal`, `.smaller`
       en `.smalst` zou de klassenlijst laten meegroeien met elk volgend scherm.
       Onder het breekpunt doet de variabele niets meer: daar staat er nog maar
       één kolom. */
    .fz-fieldrow {
      display: grid; grid-template-columns: var(--fieldrow-label, 180px) 1fr;
      align-items: center;
      gap: var(--fieldrow-gap, 16px); padding: 12px 18px;
      border-bottom: 1px solid var(--line-2);
    }
    /* De laatste rij in een kaart: de kaartrand is daar al de scheiding. */
    .fz-fieldrow.zonder-lijn { border-bottom: none; }
    /* Voor rijen waarvan de bediening meer dan één regel hoog is — een
       tekstvlak, een lijst met vinkjes — zodat het label bovenaan uitlijnt. */
    .fz-fieldrow.top { align-items: start; }
    /* Met een actie achteraan (verwijderen, testen). */
    .fz-fieldrow.actie { grid-template-columns: var(--fieldrow-label, 180px) 1fr auto; }
    /* Zonder tussenruimte tussen label en bediening: het label staat dan direct
       naast een veld dat de volle rest vult. Een klasse en geen inline `gap: 0`,
       want inline wint van élke media query — en gestapeld onder het breekpunt
       zou dat label en veld tegen elkaar aan plakken. Zo kan het 900px-blok de
       tussenruimte alsnog terugzetten. */
    .fz-fieldrow.dicht { gap: 0; }

    /* Accent variant (HiX shortlist) */
    .fz-fieldcard.primary { border-color: var(--primary-700); }
    .fz-fieldhead.primary-head { background: var(--primary-700); color: #fff;
      border-bottom-color: var(--primary-800); text-transform: none; letter-spacing: 0; font-size: 13px;
      display: flex; align-items: baseline; justify-content: space-between; font-weight: 700; }
    /* `accent-head` en `warn-head` hadden allebei een `.sub`-regel en `primary-head`
       als enige niet — dan leest een bijschrift (een teller, een aantal) even zwaar
       als de kop zelf. Wit met opacity in plaats van een eigen kleur: de kop staat
       op `--primary-700` en elke andere tint zou daar vuil op lezen. */
    .fz-fieldhead.primary-head .sub { font-size: 11px; font-weight: 600; opacity: .75; }
    .fz-fieldcard.accent { border-color: var(--primary-500); }
    .fz-fieldhead.accent-head { background: var(--primary-300); color: var(--primary-900);
      border-bottom-color: var(--primary-500); text-transform: none; letter-spacing: 0; font-size: 13px;
      display: flex; align-items: baseline; justify-content: space-between; font-weight: 700; }
    .fz-fieldhead.accent-head .sub { font-size: 11px; font-weight: 600; color: var(--primary-700); opacity: .7; }
    .fz-fieldcard.warn { border-color: #fcd34d; }
    .fz-fieldhead.warn-head { background: #fef3c7; color: #92400e;
      border-bottom-color: #fcd34d; text-transform: none; letter-spacing: 0; font-size: 13px;
      display: flex; align-items: baseline; justify-content: space-between; font-weight: 700; }
    .fz-fieldhead.warn-head .sub { font-size: 11px; font-weight: 600; color: #92400e; opacity: .7; }
    .fz-warn { display: flex; align-items: flex-start; gap: 7px; font-size: 13px; color: #92400e; line-height: 1.45; }
    .fz-warn svg { flex-shrink: 0; margin-top: 1px; width: 15px; height: 15px; }

    /* ---- Chip-filters en dropdown ---- */
    .fz-chip {
      display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 12px;
      border: 1px solid var(--line); border-radius: 20px; background: #fff;
      font-family: var(--font); font-size: 13px; font-weight: 600; color: var(--ink-2);
      cursor: pointer; transition: border-color .12s, background .12s; white-space: nowrap;
    }
    .fz-chip:hover { border-color: var(--primary-500); background: var(--primary-100); color: var(--primary-800); }
    .fz-chip.active { border-color: var(--primary-500); background: var(--primary-100); color: var(--primary-800); }
    .fz-chip svg { width: 14px; height: 14px; flex: 0 0 auto; }
    .fz-dd { position: relative; }
    .fz-menu {
      position: absolute; top: calc(100% + 6px); right: 0; z-index: 50;
      background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
      box-shadow: var(--shadow-pop); min-width: 210px; overflow: hidden;
    }
    .fz-menu-item {
      display: block; padding: 10px 16px; font-size: 13.5px; font-weight: 500;
      color: var(--ink-2); text-decoration: none; transition: background .1s; cursor: pointer;
    }
    .fz-menu-item:hover { background: var(--line-2); }
    .fz-menu-item.sel { color: var(--primary-800); font-weight: 700; background: var(--primary-100); }

    /* ---- Filterbalk ---- */
    .fz-filterbar { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
    .fz-search-chip {
      display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 12px;
      border: 1px solid var(--line); border-radius: 20px; background: #fff;
      font-size: 13px; color: var(--ink-2); transition: border-color .12s;
    }
    .fz-search-chip:focus-within { border-color: var(--primary-500); }
    .fz-search-chip svg { width: 14px; height: 14px; flex: 0 0 auto; color: var(--muted); }
    .fz-search-chip input { border: none; background: none; outline: none; font-size: 13px; font-family: var(--font); color: var(--ink); width: 280px; }
    .fz-search-chip input::placeholder { color: var(--muted); font-weight: 400; }
    .fz-filter-drop {
      position: absolute; top: calc(100% + 6px); left: 0; z-index: 50;
      background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
      box-shadow: var(--shadow-pop); min-width: 200px; overflow: hidden; padding: 4px 0;
    }
    .fz-filter-opt {
      display: flex; align-items: center; gap: 10px; padding: 9px 16px;
      font-size: 13.5px; font-weight: 500; color: var(--ink-2); cursor: pointer; user-select: none;
      white-space: nowrap;
    }
    .fz-filter-opt:hover { background: var(--line-2); }
    .fz-filter-opt input[type=checkbox] { accent-color: var(--primary-500); width: 15px; height: 15px; cursor: pointer; flex-shrink: 0; }

    /* ---- Vinkje met bijschrift (fz-vinkje) ----
       Een los `<input type=checkbox>` met tekst ernaast, buiten een filterlijst
       — bijvoorbeeld de bevestiging "Dit is dezelfde persoon" vóór het
       samenvoegen van twee dossiers.

       Het bestond niet als klasse: het stond inline, en daardoor viel het onder
       het breekpunt buiten elke 44px-regel. Anders dan `.fz-filter-opt`
       hierboven staat dit vinkje niet in een dropdown en heeft het geen
       hover-vlak; anders dan `.fz-toggle` schakelt het niet direct iets aan of
       uit maar deblokkeert het een knop ernaast. */
    .fz-vinkje {
      display: inline-flex; align-items: center; gap: 6px;
      font-size: 12.5px; color: var(--ink); cursor: pointer;
    }
    /* Twee vormen van hetzelfde wis-kruisje, en dat is geen slordigheid:
       - basisregel: het kruisje ZIT IN de chip, meelopend in de flex-flow. Dat is de
         client-mode-filterbalk (opvolgingen-werklijst), waar het een <span> is.
       - `.fz-chip + .fz-chip-x`: het kruisje staat NAAST de chip. Dat is de
         server-mode-filterbalk van de vier patiëntadministratie-lijsten, waar het een
         echte <button> moet zijn voor toetsenbordbediening — en een genest <button>
         bestaat niet: de HTML-parser sluit dan de buitenste chip-button, waarna het
         kruisje búíten de chip belandt (gemeten in chromium).
       De sibling-variant ligt absoluut op de chip binnen .fz-dd (position:relative);
       `.fz-chip.met-x` maakt daar de ruimte voor vrij, en alleen zolang er een filter
       actief is — zonder filter houdt de chip zijn oorspronkelijke padding.
       Scope de tweede regel dus NIET terug naar een kale `.fz-chip-x`: dat zou het
       geneste span op de opvolgingen-werklijst over de chip-tekst heen leggen. */
    .fz-chip-x {
      display: inline-flex; align-items: center; justify-content: center;
      width: 18px; height: 18px; border-radius: 50%; background: rgba(0,0,0,.1);
      font-size: 15px; line-height: 1; flex-shrink: 0; margin-left: 2px;
    }
    .fz-chip-x:hover { background: rgba(0,0,0,.18); }
    .fz-chip + .fz-chip-x {
      position: absolute; top: 50%; right: 10px; transform: translateY(-50%); z-index: 1;
      margin-left: 0; border: none; padding: 0; cursor: pointer; color: inherit;
      font-family: var(--font);
    }
    .fz-chip.met-x { padding-right: 34px; }
    .fz-wis-link {
      font-family: var(--font); font-size: 13px; color: var(--primary-700); font-weight: 600;
      background: none; border: none; cursor: pointer; padding: 0; text-decoration: underline; margin-left: 4px;
    }
    .fz-wis-link:hover { color: var(--primary-900); }
    /* Gedempte variant. Dezelfde actie — een tekstlink die iets wist — maar
       ondergeschikt aan wat erboven staat: "Logo verwijderen" hoort niet de
       aandacht te trekken van het uploadvlak waar hij onder hangt. Stond twee
       keer identiek inline op de twee huisstijlschermen, en viel daardoor
       buiten de 44px-regel die de basisklasse onder het breekpunt wél krijgt.
       Een variant en geen tweede component: alleen de tint en de maat wijken,
       de betekenis niet. */
    .fz-wis-link.stil { font-size: 12px; font-weight: 400; color: var(--muted); margin-left: 0; }
    .fz-wis-link.stil:hover { color: var(--ink-2); }

    /* ---- Hulplink (fz-hulplink) ----
       De gedempte uitweg onder een formulier: "Wachtwoord vergeten?",
       "Terug naar inloggen". Stond drie keer identiek als inline stijl op de
       drie auth-schermen. Anders dan fz-wis-link, die een actie op het scherm
       uitvoert (een filter wissen), navigeert deze weg — en anders dan
       fz-actie-link draagt hij geen eigen vlak: hij hoort gedempt en klein
       te blijven staan onder de hoofdactie. */
    .fz-hulplink {
      font-size: 13px; color: var(--muted); text-decoration: none;
    }

    /* ---- Rij-wisknop (fz-wis) ----
       Drie dingen die op "weghalen" lijken en het niet zijn, dus drie
       componenten:
         fz-del      — kruisje: de rij zélf verdwijnt;
         fz-wis      — prullenbak: de rij blijft, zijn waardes worden leeg;
         fz-wis-link — tekstlink: een filter of zoekopdracht wordt gewist.
       Deze stond drie keer identiek in de financiële planning (ov-, kz-, pop-)
       als de knop achter een maandraster waarmee je een heel jaar leegt. Anders
       dan fz-del staat hij er altijd: leegmaken is geen zeldzame actie op een
       invoerscherm, en er is geen rij om te hoveren als de rij zelf blijft. */
    .fz-wis {
      width: 24px; height: 24px; padding: 0; border: none; border-radius: 6px;
      background: transparent; color: var(--muted); cursor: pointer;
      display: inline-flex; align-items: center; justify-content: center;
      transition: background .15s, color .15s;
    }
    .fz-wis:hover { background: var(--danger-100); color: var(--danger-600); }

    /* ---- Kopieerknop in detail (fz-vbtn) ---- */
    /* Vertikale padding zit op .lbl/.fz-vbtn, niet op .fz-field.bal, zodat
       de button de volle celhoogte kan beslaan met align-self:stretch */
    .fz-field.bal { padding-top: 0; padding-bottom: 0; align-items: stretch; }
    .fz-fieldcard.compact .fz-field.bal { padding-top: 0; padding-bottom: 0; }
    .fz-field.bal .lbl {
      flex: 0 0 160px; width: 160px;
      padding: 8px 0; display: flex; align-items: center;
    }

    .fz-vbtn {
      position: relative; flex: 1 1 0; min-width: 0;
      display: flex; align-items: center; align-self: stretch;
      background: none; border: 1px solid transparent; border-radius: var(--r-sm);
      padding: 0 80px 0 10px; cursor: pointer;
      font-family: var(--font); text-align: left;
      transition: all .12s;
    }
    .fz-vbtn .val { font-size: 14px; font-weight: 500; color: var(--ink); line-height: 1.45; flex: 1; min-width: 0; overflow-wrap: anywhere; }
    .fz-vbtn:hover { background: var(--primary-100); border-color: var(--primary-300); }
    .fz-vbtn .hint {
      position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
      display: inline-flex; align-items: center; gap: 5px;
      font-size: 12px; font-weight: 600; color: var(--faint); white-space: nowrap;
      border-radius: 6px; padding: 3px 7px; transition: color .12s, background .12s;
    }
    .fz-vbtn .hint svg { width: 14px; height: 14px; flex: 0 0 auto; }
    .fz-vbtn .hint .ht { display: none; }
    .fz-vbtn .hint .ico-check { display: none; }
    .fz-vbtn .hint .lbl-copied { display: none; }
    .fz-vbtn:hover .hint { color: var(--primary-700); background: var(--primary-100); }
    .fz-vbtn:hover .hint .ht { display: inline; }
    .fz-vbtn.copied { background: var(--success-100); border-color: var(--success-300); }
    .fz-vbtn.copied .val { color: var(--muted); }
    .fz-vbtn.copied .hint { color: #fff; background: var(--success-700); }
    .fz-vbtn.copied .hint .ht { display: inline; }
    .fz-vbtn.copied .hint .ico-copy { display: none; }
    .fz-vbtn.copied .hint .ico-check { display: inline-flex; }
    .fz-vbtn.copied .hint .lbl-copy { display: none; }
    .fz-vbtn.copied .hint .lbl-copied { display: inline; }

    /* compact rows in HiX shortlist */
    .fz-fieldcard.compact .fz-field { padding-top: 8px; padding-bottom: 8px; }

    /* ---- Splitter ---- */
    .fz-splitter {
      display: flex; align-items: center; gap: 14px; margin: 20px 0 10px;
    }
    .fz-splitter::before, .fz-splitter::after { content: ""; flex: 1; height: 1px; background: var(--line); }
    .fz-splitter span { font-size: 13px; font-weight: 700; color: var(--ink-2); white-space: nowrap; }
    .fz-splitter em { font-size: 12px; color: var(--muted); font-style: normal; white-space: nowrap; }

    /* ---- Tags (auto/patient/std) ---- */
    .fz-tag { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 10px;
      font-size: 11px; font-weight: 700; letter-spacing: .03em; }
    .fz-tag.auto { background: var(--primary-100); color: var(--primary-700); }
    .fz-tag.patient { background: var(--primary-100); color: var(--primary-700); }
    .fz-tag.std { background: var(--line-2); color: var(--muted); }

    /* ---- Veldfout markering ---- */
    .fz-field-error input,
    .fz-field-error select,
    .fz-field-error textarea { border-color: var(--danger-600) !important; box-shadow: 0 0 0 3px var(--danger-ring); }
    .fz-consent-error { border-color: var(--danger-600) !important; box-shadow: 0 0 0 3px var(--danger-ring); }
    .fz-field-msg { display: none; }
    .fz-field-error .fz-field-msg,
    .fz-consent-error .fz-field-msg { display: block; color: var(--danger-600); font-size: 11.5px; margin-top: 4px; }

    /* ---- Veldtoelichting (fz-hint) ----------------------------------------
       De neutrale regel ónder een invoerveld die vertelt wat je invult of wat
       de waarde doet: "Leeg = interne naam wordt getoond aan patiënten",
       "Alleen zichtbaar voor de medewerker zelf". Hij staat er altijd — hij is
       geen reactie op wat je typt.

       Dat onderscheidt hem van `.fz-field-msg`, die op dezelfde plek staat maar
       alleen verschijnt bij `.fz-field-error` en dan rood is. Een toelichting
       die je alleen ziet als er iets fout ging, is geen toelichting. En van
       `.fz-sub`, die onder de paginakop de pagina omschrijft: dat is één per
       scherm, deze hoort bij één veld.

       Hij staat hier omdat het patroon in PR #20 vijf keer als een `fz-hint`
       gebruikt werd die nergens gedefinieerd was — de tekst rendeerde daar op de
       body-maat, dus even groot als het antwoord waar hij bij hoort. Dezelfde
       reden als bij `.fz-subregel`: een inline maat is niet te bereiken vanuit
       een media query, en dus voorgoed bevroren.

       **Er staan er nog zeven inline en die zijn hier bewust niet omgezet**:
       `style="font-size:12px;color:var(--muted)"` in `detail_praktijk` (2×),
       `huisstijl_bewerken`, `detail_opvolgingtype`, `detail_gebruiker` (2×) en
       `opvolging_starten`. Ze zijn de reden dat dit een huisstijlcomponent is en
       geen bekwaamheid-CSS, maar ze aanraken is werk in vier andere apps en dus
       een eigen wijziging. Kom je er langs: vervang ze door deze klasse.

       `display: block` omdat het gebruik een `<span>` is: hij hoort op zijn
       eigen regel, ook zonder wrapper eromheen. De marge zit in de klasse en
       niet per scherm, want de afstand tot het veld is de relatie tussen de
       twee — geen maatvoering van het scherm. */
    .fz-hint { display: block; font-size: 12px; color: var(--muted);
      line-height: 1.5; margin-top: 5px; }

    /* ---- Werkblad met zijkolom (fz-met-zijkolom) --------------------------
       Een brede werkkolom met een smalle actiekolom ernaast: het dossier van
       een inschrijving, een opvolging, een werkgeversaanlevering. De zijkolom
       draagt knoppen, statusvelden en tabs — dingen die je erbij pakt, niet
       waar je in leest.

       Stond op drie schermen als inline `style` met elk een eigen vaste
       breedte (320, 260, 280px). Inline is precies de vorm die een media query
       niet kan overschrijven, dus stapelde er niets: op 390px eisten de
       kolommen samen 724px in een kaart van 315px en liep de rechterkant
       gewoon buiten beeld.

       **Het breekpunt is 1024px** en niet de 900 van de app-shell. Dat is geen
       slordigheid maar rekenwerk: boven 900px staat de zijbalk er nog, en die
       220px plus deze 320px eisen samen 540px vóór de inhoud begint. Gemeten
       op 940px — een gewoon laptopvenster — bleef er 645px over voor een
       layout die er 724 nodig had. Pas vanaf ongeveer 1024px past het weer.
       Onder 900px is de zijbalk off-canvas en zou het op ~784px al passen,
       maar één breekpunt voor deze component is eerlijker dan twee.

       `minmax(0, 1fr)` en niet `1fr`: een grid-kolom heeft impliciet
       `min-width: auto`, dus `1fr` krimpt niet onder de min-content van zijn
       inhoud. Gemeten werd die kolom 404px breed in een kaart van 315px. Met
       `minmax(0, …)` mag hij wél krimpen, zodat een lange naam of een brede
       tabel de layout niet alsnog uit de kaart duwt.

       De breedte van de zijkolom is maatvoering en verschilt legitiem per
       scherm: zet `--fz-zijkolom` inline. Het gedrag zit in de klasse. */
    .fz-met-zijkolom {
      display: grid;
      grid-template-columns: minmax(0, 1fr) var(--fz-zijkolom, 320px);
      gap: 20px;
      align-items: start;
    }
    @media (max-width: 1024px) {
      /* De zijkolom staat in de DOM ná de werkkolom, dus stapelen zet hem
         vanzelf onder de kaart — waar je hem verwacht. */
      .fz-met-zijkolom { grid-template-columns: minmax(0, 1fr); }
    }

    /* ---- Responsief 2-koloms grid (formulier) ---- */
    .fz-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 28px; }
    .fz-formcard { background: #fff; border-radius: var(--r-lg); border: 1px solid var(--line); box-shadow: var(--shadow-card); padding: 32px; }
    @media (max-width: 600px) {
      .fz-grid-2 { grid-template-columns: 1fr; }
      .fz-formcard { padding: 20px 16px; }
    }

    /* ---- Copy chips ---- */
    .fz-copychip { display: flex; align-items: center; gap: 12px; padding: 11px 13px;
      border: 1px solid var(--line); border-radius: 10px; background: #fff; cursor: pointer;
      text-align: left; font-family: var(--font); transition: all .12s; width: 100%; }
    .fz-copychip:hover { border-color: var(--primary-500); background: var(--primary-100); }
    .fz-copychip.copied { border-color: var(--success-300); background: var(--success-100); }
    .fz-copychip .ck { width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto;
      display: flex; align-items: center; justify-content: center;
      background: var(--line-2); color: var(--faint); transition: all .12s; }
    .fz-copychip.copied .ck { background: var(--success-700); color: #fff; }
    .fz-copychip .ck svg { width: 15px; height: 15px; }
    .fz-copychip .chip-lbl { font-size: 11px; color: var(--muted); font-weight: 700;
      letter-spacing: .03em; text-transform: uppercase; }
    .fz-copychip .chip-val { font-size: 14px; font-weight: 600; margin-top: 1px;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--ink); }

    /* ---- Right rail — finish panel ---- */
    .fz-finish { background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
      padding: 18px; box-shadow: var(--shadow-card); }
    .fz-finish h3 { margin: 0 0 4px; font-size: 15px; font-weight: 700; color: var(--ink); }
    .fz-finish p  { margin: 0 0 14px; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
    .fz-inputlabel { font-size: 12px; font-weight: 700; color: var(--ink-2); margin-bottom: 6px; display: block; }
    .fz-input { width: 100%; height: 42px; border: 1.5px solid var(--line); border-radius: 8px;
      padding: 0 13px; font-family: var(--font); font-size: 15px; font-weight: 600;
      color: var(--ink); letter-spacing: .04em; background: #fff; }
    .fz-input:focus { outline: none; border-color: var(--primary-500); box-shadow: 0 0 0 3px var(--focus-ring); }
    .fz-input::placeholder { color: var(--faint); font-weight: 500; letter-spacing: 0; }

    /* ---- Done card ---- */
    .fz-donecard { background: var(--success-100); border: 1px solid var(--success-300);
      border-radius: var(--r-md); padding: 18px; text-align: center; }
    .fz-donecard .big { width: 44px; height: 44px; border-radius: 50%; background: var(--success-700);
      color: #fff; display: flex; align-items: center; justify-content: center; margin: 0 auto 10px; }
    .fz-donecard .big svg { width: 24px; height: 24px; }
    .fz-donecard h3 { margin: 0 0 4px; font-size: 15px; font-weight: 700; color: var(--success-900); }
    .fz-donecard p  { margin: 0; font-size: 12.5px; color: var(--success-800); line-height: 1.5; }
    .fz-donecard .pn { display: inline-block; margin-top: 10px; background: #fff;
      border: 1px solid var(--success-300); border-radius: 8px; padding: 7px 14px;
      font-weight: 700; font-family: monospace; letter-spacing: .04em; color: var(--ink); }

    /* ---- Audit trail ---- */
    .fz-audit { background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
      padding: 18px; margin-top: 14px; }
    .fz-audit h3 { margin: 0 0 14px; font-size: 13px; font-weight: 700; letter-spacing: .04em;
      text-transform: uppercase; color: var(--ink-2); }
    .fz-event { display: flex; gap: 12px; position: relative; padding-bottom: 16px; }
    .fz-event:last-child { padding-bottom: 0; }
    .fz-event::before { content: ""; position: absolute; left: 13px; top: 28px; bottom: -2px;
      width: 2px; background: var(--line); }
    .fz-event:last-child::before { display: none; }
    .fz-event-ic { width: 28px; height: 28px; border-radius: 50%; flex: 0 0 auto;
      display: flex; align-items: center; justify-content: center;
      background: var(--primary-100); color: var(--primary-700); z-index: 1; }
    .fz-event-ic svg { width: 15px; height: 15px; }
    .fz-event b    { font-size: 13px; font-weight: 600; display: block; color: var(--ink); }
    .fz-event span { font-size: 11.5px; color: var(--muted); }

    /* ---- Empty state ---- */
    .fz-empty { text-align: center; padding: 60px 20px; color: var(--muted); }
    .fz-empty .ic { width: 52px; height: 52px; border-radius: 14px; background: var(--line-2);
      color: var(--faint); display: flex; align-items: center; justify-content: center;
      margin: 0 auto 14px; }
    .fz-empty .ic svg { width: 26px; height: 26px; }
    .fz-empty b { display: block; color: var(--ink-2); font-size: 15px; margin-bottom: 6px; }
    /* Actie-slot: één knop onder de uitleg, voor een lege staat die je in één
       klik kunt vullen (de zorgstraten van het ketenzorgscherm). Toegevoegd
       2026-08-06; daarvoor werd zo'n knop per scherm met de hand nagebouwd. */
    .fz-empty .actie { margin-top: 14px; }
    .fz-empty .actie + .fz-sub { margin-top: 10px; }

    /* Lege staat ÍN een tabel: de koprij blijft staan, de tekst komt gecentreerd
       in één cel over de volle breedte. Gebruik deze wanneer de kolomindeling ook
       zonder gegevens iets betekent — twaalf maandkolommen, of een vast rijtje
       velden. Bestaat de indeling pas mét gegevens, neem dan `.fz-empty` in een
       kaart: een lege koprij suggereert dan een structuur die er niet is.
       Maatvoering overgenomen van het personeelskostenscherm, dat dit als enige
       goed deed toen de rest acht eigen varianten had. */
    .fz-leegrij td { text-align: center; color: var(--muted);
      padding: 28px 12px; background: var(--surface); }

    /* ---- Messages ---- */
    .fz-msg { display: flex; align-items: center; gap: 10px; padding: 12px 18px;
      border-radius: var(--r-md); font-size: 13.5px; font-weight: 500; margin-bottom: 12px; }
    .fz-msg.success { background: var(--success-100); border: 1px solid var(--success-300); color: var(--success-900); }
    .fz-msg.error   { background: var(--danger-100); border: 1px solid var(--danger-300); color: var(--danger-800); }
    .fz-msg .msg-ic { width: 22px; height: 22px; border-radius: 50%; background: var(--success-700);
      color: #fff; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; }
    .fz-msg.error .msg-ic { background: var(--danger-500); }
    .fz-msg .msg-ic svg { width: 12px; height: 12px; }
    .fz-required { color: var(--danger-600); }
    .fz-badge-error { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px;
      background: var(--danger-100); border: 1px solid var(--danger-300); border-radius: var(--r-md);
      font-size: 12.5px; font-weight: 600; color: var(--danger-800); text-decoration: none; }
    .fz-text-ok   { color: var(--success-700); font-size: 12.5px; font-weight: 600; }
    .fz-text-err  { color: var(--danger-600); font-size: 12.5px; font-weight: 600; }
    .fz-actie-danger { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 10px;
      background: none; border: 1px solid var(--danger-300); border-radius: 8px; cursor: pointer;
      color: var(--danger-600); font-size: 13px; font-weight: 600; font-family: inherit; text-align: left;
      text-decoration: none; transition: background .12s; }
    .fz-actie-danger:hover { background: var(--danger-100); }
    .fz-warn-banner { display: flex; align-items: center; gap: 10px; padding: 10px 14px;
      background: #fef3c7; border: 1.5px solid #fcd34d; border-radius: var(--r-md);
      font-size: 13px; font-weight: 600; color: #92400e; margin-bottom: 16px; }
    .fz-warn-banner svg { width: 16px; height: 16px; flex-shrink: 0; }
    /* Een knop die als link leest. Nodig waar een navigatie een POST moet zijn — de
       lijstzoekterm reist in de body, dus een `?q=`-href zou stil niets filteren. Erft
       kleur en gewicht van de omhullende tekst, zodat hij in een banner niet opvalt als
       formulier-element. */
    .fz-linkbutton {
      background: none; border: none; padding: 0; cursor: pointer;
      font: inherit; color: inherit; text-decoration: underline;
    }

    /* Inline waarschuwingsbadge — gebruik als een gebruiker iets moet controleren dat niet per
       se fout is, maar aandacht verdient (bijv. BSN ontbreekt, ruwe import, buiten werkgebied).
       Niet voor harde fouten — gebruik dan fz-msg error. */
    .fz-badge-warn { display: inline-flex; align-items: center; gap: 4px; padding: 2px 7px;
      background: #fef3c7; border: 1px solid #fde68a; border-radius: 4px;
      font-size: 11px; font-weight: 600; color: #92400e; white-space: nowrap; }

    /* ---- Statuscel (fz-statuscel) -----------------------------------------
       Een viertoestanden-status in een tabelcel: nooit getoetst, goed, aandacht,
       fout. Gebouwd voor de bekwaamheidsmatrix (medewerker × handeling), maar niet
       app-gebonden — elk raster dat een afgeleide toestand toont kan hem gebruiken.

       **Waarom een eigen component naast de badges.** `.fz-badge-warn` en
       `.fz-badge-error` dekken twee van de vier toestanden en er is geen neutraal-
       of positief-paar. Dat dwingt bij een viertoestandenraster tot twee badges plus
       twee lokale varianten, en precies zo ontstaat drift.

       **Het onderscheid rust niet op kleur alleen.** Elke modifier draagt een eigen
       glyph (–, ✓, !, ✗) via `::before`. Kleur draagt de snelle scan over een raster,
       de glyph draagt de betekenis — voor wie kleuren niet onderscheidt, en op een
       afdruk in grijstinten. Zet daarnaast een `title` op de cel met de reden in
       tekst; de glyph vervangt geen uitleg.

       De maat is die van een dichte tabel: dit staat in een raster van tientallen
       cellen, niet als losse badge in een kaart. */
    .fz-statuscel {
      display: inline-flex; align-items: center; gap: 5px;
      padding: 2px 8px; border-radius: 4px;
      font-size: 11.5px; font-weight: 600; white-space: nowrap;
      border: 1px solid transparent;
    }
    .fz-statuscel::before { font-weight: 700; line-height: 1; }
    /* Nooit getoetst. Bewust vlak en niet rood: een nooit gehaalde bekwaamheid is
       geen actiepunt — anders scoort een baliemedewerker rood op zeventien
       handelingen die zij nooit zal doen, en wordt het raster genegeerd. */
    .fz-statuscel.neutraal {
      background: #f3f4f6; border-color: var(--line); color: var(--muted);
    }
    .fz-statuscel.neutraal::before { content: "–"; }
    .fz-statuscel.goed {
      background: var(--success-100); border-color: var(--success-300);
      color: var(--success-800);
    }
    .fz-statuscel.goed::before { content: "✓"; }
    /* Goed, maar er is iets gevraagd — een hertoets bijvoorbeeld. Groen zou hier
       liegen en rood zou de bevoegdheid ten onrechte intrekken. */
    .fz-statuscel.attentie {
      background: #fef3c7; border-color: #fde68a; color: #92400e;
    }
    .fz-statuscel.attentie::before { content: "!"; }
    .fz-statuscel.fout {
      background: var(--danger-100); border-color: var(--danger-300);
      color: var(--danger-800);
    }
    .fz-statuscel.fout::before { content: "✗"; }

    /* ---- Minilabel (fz-minilabel) -----------------------------------------
       Het kleine uppercase kopje bóven een waarde: "WERKGEVER", "ONTVANGEN OP",
       "LOCATIE (DEFAULT)". Gebruik hem waar een waarde zonder label niet te
       plaatsen is en een volwaardig `.fz-field` te zwaar zou zijn.

       Zelfde reden om hem hier te zetten als bij `.fz-subregel`: het patroon
       stond veertien keer inline als `style="font-size:11px;font-weight:600;
       color:var(--muted);text-transform:uppercase;letter-spacing:.04em"`, en
       een inline stijl valt buiten het bereik van elke media query.

       `.fz-invoegblok-label` deelt deze regel; die droeg dezelfde vijf waarden
       byte-gelijk en verschilde alleen in zijn marge. */
    .fz-minilabel,
    .fz-invoegblok-label { font-size: 11px; font-weight: 600; color: var(--muted);
      text-transform: uppercase; letter-spacing: .04em; }

    /* ---- Subregel (fz-subregel) -------------------------------------------
       De tweede regel onder een primaire waarde: het patiëntnummer onder de
       naam, de stapnaam onder de datum, het e-mailadres onder de rij waar het
       bij hoort. Gebruik hem waar de eerste regel het antwoord is en deze de
       toelichting.

       Hij staat hier en niet inline om één reden die met dit responsive-werk te
       maken heeft: het patroon stond zestien keer als
       `style="font-size:11.5px;color:var(--muted)"` verspreid over zes
       templates, en een inline stijl is niet te overschrijven door een media
       query. Zolang de maat daar staat, kán geen enkel breekpunt eraan komen —
       precies dezelfde val als de inline grid van de launcher (U5). */
    .fz-subregel { font-size: 11.5px; color: var(--muted); }

    /* ---- Detailparen (fz-detailparen) --------------------------------------
       Een klein blok `sleutel: waarde`-regels bij één record: de metadata van
       een audittrailregel ("toetser was bekwaam: nee", "reden: back-entry").
       Gebruik hem waar het aantal en de namen van de velden per rij verschillen
       — dan kán er geen kolom voor bestaan.

       Daarmee is hij het tegendeel van `.fz-field` (`lbl`/`val`), dat een vaste,
       vooraf bekende set velden in een detailkaart toont, en van een tabelkolom,
       die per rij dezelfde betekenis draagt. Zodra elke rij dezelfde sleutels
       heeft, hoort het een kolom te zijn en niet dit.

       De sleutel staat op `--faint` en de waarde op `--muted`: het antwoord is
       wat je leest, de sleutel wijst hem alleen aan. Zelfde reden om hem hier te
       zetten als bij `.fz-subregel` — hij stond inline, en inline is buiten
       bereik van elke media query. */
    .fz-detailparen { font-size: 12.5px; color: var(--muted); line-height: 1.6; }
    .fz-detailparen-sleutel { color: var(--faint); }

    /* ---- Rolchip (fz-rolchip) ---------------------------------------------
       Een neutrale, rechthoekige chip voor een toegekende rol. Bewust géén
       `.fz-tag`-variant: die is een gekleurde pil die de hérkomst van een
       waarde annoteert (auto/patiënt/standaard) en zijn betekenis via kleur
       draagt. Een rolnaam draagt zijn betekenis in de tekst zelf, dus hij is
       neutraal en leesbaar. */
    .fz-rolchip { display: inline-block; padding: 2px 8px;
      background: var(--surface); color: var(--ink-2);
      border: 1px solid var(--line); border-radius: 4px;
      font-size: 11px; font-weight: 500; }

    /* ---- Versielabel (fz-versie) ------------------------------------------
       Het versienummer naast de feedbackknop en in de feedbackkaart. Staat in
       een klasse omdat het onder het breekpunt mee moet groeien: wie een fout
       meldt, moet dat nummer kunnen aflezen. */
    .fz-versie { font-size: 11px; font-weight: 500; color: var(--faint); }

    /* ---- Back link ---- */
    .fz-back { display: inline-flex; align-items: center; gap: 5px; font-size: 13px;
      font-weight: 600; color: var(--muted); text-decoration: none; }
    .fz-back:hover { color: var(--ink-2); }
    .fz-back svg { width: 16px; height: 16px; }

    /* ---- Actie link (paneel-actie met hover-affordance) ---- */
    .fz-actie-link { display: flex; align-items: center; gap: 8px; padding: 6px 8px;
      color: var(--ink-2); font-size: 13px; font-weight: 500; text-decoration: none;
      border-radius: var(--r-md); transition: background .12s; }
    button.fz-actie-link { background: none; border: none; cursor: pointer; font-family: var(--font); text-align: left; }
    .fz-actie-link:hover { background: var(--primary-100); color: var(--primary-700); }
    .fz-actie-link:hover svg { stroke: var(--primary-700); }

    /* ---- Global form inputs (registration form) ---- */
    .fz-form-field input:not([type=checkbox]):not([type=radio]),
    .fz-form-field select,
    .fz-form-field textarea {
      font-family: var(--font); font-size: 13.5px; color: var(--ink);
      border: 1.5px solid var(--line); border-radius: var(--r-sm);
      padding: 9px 12px; width: 100%; background: #fff;
      transition: border-color .15s, box-shadow .15s;
    }
    .fz-form-field input:not([type=checkbox]):not([type=radio]):focus,
    .fz-form-field select:focus,
    .fz-form-field textarea:focus {
      outline: none; border-color: var(--primary-500);
      box-shadow: 0 0 0 3px var(--focus-ring);
    }
    .fz-form-field textarea { resize: vertical; min-height: 80px; }
    .fz-form-field select { cursor: pointer; }

    /* Toggle switch (iPhone-stijl) */
    .fz-toggle { display:inline-flex;align-items:center;flex-shrink:0;cursor:pointer }
    .fz-toggle input[type=checkbox] { position:absolute;opacity:0;width:0;height:0 }
    .fz-toggle-track {
      position:relative;display:inline-block;width:42px;height:24px;
      background:#d1d5db;border:2px solid #d1d5db;
      border-radius:12px;
      transition:background .2s ease, border-color .2s ease;flex-shrink:0;
    }
    .fz-toggle input:checked + .fz-toggle-track { background:var(--primary-700);border-color:var(--primary-700) }
    .fz-toggle.danger input:checked + .fz-toggle-track { background:var(--danger-600);border-color:var(--danger-600) }
    .fz-toggle-thumb {
      position:absolute;top:2px;left:2px;width:16px;height:16px;
      background:#fff;border-radius:50%;
      box-shadow:0 1px 4px rgba(0,0,0,.25);
      transition:transform .2s ease;
    }
    .fz-toggle input:checked + .fz-toggle-track .fz-toggle-thumb { transform:translateX(18px) }
    .fz-toggle input:focus-visible + .fz-toggle-track { box-shadow:0 0 0 3px var(--focus-ring) }
    /* Compacte maat voor een toggle in een tabelcel. De gewone maat is 24px hoog
       en maakt een rij van 30px onhandelbaar; deze is 18px en past ernaast.
       Track, thumb én de verplaatsing horen bij elkaar — verklein je alleen de
       track, dan schiet de thumb eroverheen. De verhouding is die van de gewone
       maat: thumb = hoogte - 2*(rand + speling) = 18 - 8 = 10, verplaatsing =
       breedte - 8 - thumb = 32 - 8 - 10 = 14. */
    .fz-toggle.compact .fz-toggle-track { width:32px;height:18px;border-radius:9px }
    .fz-toggle.compact .fz-toggle-thumb { width:10px;height:10px }
    .fz-toggle.compact input:checked + .fz-toggle-track .fz-toggle-thumb { transform:translateX(14px) }
    /* ---- Rij-verwijderknop (fz-del) ---- */
    .fz-del { width:30px;height:30px;display:inline-grid;place-items:center;border-radius:7px;border:1px solid transparent;background:transparent;color:#aab4b9;cursor:pointer;transition:.12s;padding:0;flex-shrink:0 }
    .fz-del:hover { background:#fdeceb;color:var(--danger-600);border-color:#f6d6d4 }
    .fz-del:disabled, .fz-del[disabled] { opacity:.25;cursor:not-allowed }
    .fz-del:disabled:hover, .fz-del[disabled]:hover { background:transparent;color:#aab4b9;border-color:transparent }
    .fz-del-rij .fz-del { opacity:0;pointer-events:none }
    .fz-del-rij:hover .fz-del { opacity:1;pointer-events:auto }
    .fz-del--altijd-zichtbaar { opacity:1 !important;pointer-events:auto !important }
    /* Compacte maat voor dichte tabelrijen. Een 30px-knop in een 28px-kolom naast
       19px-invoervelden maakt de rij een derde hoger; op een raster van veertien
       kolommen is dat het verschil tussen lezen en scrollen. Zelfde vorm, andere
       maat — een maatvariant, geen tweede component. De behoefte stond er al:
       emails.html verkleinde fz-del lokaal naar 22px in de groepskop. */
    .fz-del.compact { width:22px;height:22px;border-radius:5px }

    /* ---- Hernoemknop (pl-naam-edit-btn) ----
       Het potlood dat in een groeps- of rijkop verschijnt zodra je die hovert.
       Derde lid van de hover-onthul-familie, naast `.fz-del` hierboven en
       `.fz-grip` — en daarmee ook onderworpen aan de `@media (hover: none)`-regel
       achteraan dit bestand: zonder die regel is dit op een aanraakscherm een
       knop die nooit verschijnt.

       Stond twee keer gedefinieerd — in `financieleplanning.css` en als
       `<style>`-blok in `emails.html` — en dat was precies het probleem. Een
       lokale definitie wordt ná dit bestand geladen, dus de onthulregel hier
       verloor het op bronvolgorde bij gelijke specificiteit. De knop bleef op
       aanraak onbereikbaar terwijl de regel er wél stond.

       De prefix klopt niet meer: `pl-` staat voor de plannenlijst, waar hij
       begon. Hernoemen naar `fz-` raakt acht selectors in drie apps en is geen
       responsive-werk; het is genoteerd als losse opruiming.

       Wat er per app anders blijft: WÁÁR de knop verschijnt (de `:hover`-
       selectors op de omhullende rij) en de hoverkleur — de financiële planning
       kleurt naar `--primary-700`, de mailgroepen naar `--ink`. Die blijven
       lokaal staan; alleen de basisvorm is hier. */
    .pl-naam-edit-btn {
      background: none; border: none; cursor: pointer;
      color: var(--muted); padding: 0 2px; opacity: 0;
      line-height: 1; display: inline-flex; align-items: center;
      flex-shrink: 0; transition: opacity .1s;
    }
    /* Grip-handle voor drag-and-drop naast fz-del (fz-grip) */
    .fz-grip { color:#c2cacd;cursor:grab;display:inline-flex;align-items:center;flex:none;opacity:0;transition:opacity .12s }
    .fz-del-rij:hover .fz-grip { opacity:1 }

    /* ---- Kleurinvoer met inline swatch (fz-color-input) ---- */
    .fz-color-input { display:inline-flex;align-items:center;border:1.5px solid var(--line);border-radius:6px;background:#fff;overflow:hidden;width:140px }
    .fz-color-input-swatch { width:32px;height:32px;flex-shrink:0;border-right:1.5px solid var(--line) }
    .fz-color-input input { border:none;outline:none;font-family:monospace;font-size:13px;letter-spacing:.04em;padding:5px 9px;color:var(--ink);background:transparent;width:100% }
    .fz-color-input:focus-within { border-color:var(--primary-500);box-shadow:0 0 0 3px var(--focus-ring) }

    /* ---- Scorekaart (fz-scorekaart) --------------------------------------
       Eén te beoordelen regel met een exclusieve keuze ernaast: de tekst
       links, twee of drie knoppen rechts. Bedoeld voor een lange reeks van
       hetzelfde — een aftekenlijst van 31 criteria, een vragenlijst — die op
       een telefoon met één duim doorlopen wordt.

       Wanneer je hem gebruikt: veel regels, per regel dezelfde korte keuze,
       en de keuze is de enige invoer. Is er per regel méér in te vullen
       (een toelichting, een datum), dan is dit een `.fz-fieldcard` en geen
       scorekaart.

       Waarom een radiogroep en geen `.fz-tabs`-achtige schakelaar: dit is
       formulierinvoer, geen navigatie. Native `<input type="radio">` levert
       toetsenbordbediening, screenreader-semantiek en het `change`-event
       waarop de autosave hangt — die drie zou een `<button>`-variant alle
       drie zelf moeten nabouwen.

       De keuzes hebben een betekenis-modifier (`positief`/`negatief`/
       `neutraal`) en geen inhoud-modifier (`behaald`/`nvt`): de component
       weet niet waarover geoordeeld wordt, alleen hoe de uitkomst weegt. */
    .fz-scorekaart-lijst {
      border: 1px solid var(--line);
      border-radius: var(--r-md);
      overflow: hidden;
      background: #fff;
    }
    .fz-scorekaart {
      display: flex; align-items: center; gap: var(--sp-16);
      padding: var(--sp-12) var(--sp-16);
      border-bottom: 1px solid var(--line-2);
    }
    .fz-scorekaart:last-child { border-bottom: none; }

    /* `min-width: 0` is niet cosmetisch: zonder die regel weigert een
       flex-item te krimpen onder zijn langste woord, en een criteriumtekst
       met een lange term duwt de keuzeknoppen dan buiten beeld. */
    .fz-scorekaart-tekst { flex: 1 1 auto; min-width: 0; }
    .fz-scorekaart-cat {
      font-size: 11px; font-weight: 700; letter-spacing: .06em;
      text-transform: uppercase; color: var(--muted); margin-bottom: 2px;
    }
    .fz-scorekaart-omschrijving { font-size: 13.5px; color: var(--ink); line-height: 1.45; }

    .fz-scorekaart-keuzes { display: flex; gap: var(--sp-4); flex: 0 0 auto; }

    /* De `<input>` blijft in de DOM en wordt alleen visueel weggehaald —
       `display:none` zou hem uit de tabvolgorde halen en de radiogroep met
       het toetsenbord onbereikbaar maken. Zelfde vorm als `.fz-toggle`. */
    .fz-scorekaart-keuze { position: relative; display: inline-flex; cursor: pointer; }
    .fz-scorekaart-keuze input { position: absolute; opacity: 0; width: 0; height: 0; }
    .fz-scorekaart-keuze > span {
      display: inline-flex; align-items: center; justify-content: center;
      min-height: 40px; padding: 0 14px;
      /* Wit op wit valt weg. De kaart eronder is `#fff`, dus een witte knop met een
         haarlijn eromheen leest niet als iets waar je op kunt drukken — gemeten op
         het aftekenscherm, waar drie van die knoppen naast elkaar staan. Een eigen
         vlak plus een 1,5px rand (dezelfde maat als `fz-input`) maakt er een knop
         van, zonder de gekozen staat te verstoren: die krijgt hieronder toch zijn
         eigen kleur per uitkomst. */
      border: 1.5px solid var(--line); border-radius: var(--r-sm);
      background: var(--bg); color: var(--ink-2);
      font-size: 13px; font-weight: 600; white-space: nowrap;
      transition: background .12s, border-color .12s, color .12s;
    }
    .fz-scorekaart-keuze:hover > span { background: var(--line-2); }
    .fz-scorekaart-keuze input:focus-visible + span {
      outline: 2px solid var(--primary-700); outline-offset: 2px;
    }
    .fz-scorekaart-keuze.positief input:checked + span {
      background: var(--success-100); border-color: var(--success-300); color: var(--success-900);
    }
    .fz-scorekaart-keuze.negatief input:checked + span {
      background: var(--danger-100); border-color: var(--danger-300); color: var(--danger-800);
    }
    .fz-scorekaart-keuze.neutraal input:checked + span {
      background: var(--line-2); border-color: var(--line); color: var(--ink);
    }
    /* De span dekt de hele label af, dus zijn cursor wint — de `pointer` op
       de label hoeft niet apart teruggedraaid te worden. */
    .fz-scorekaart-keuze input:disabled + span { opacity: .45; cursor: not-allowed; }

    /* De regel waar de pagina naartoe springt — "ga verder bij criterium 12".
       Een accentrand en niet een achtergrondkleur: die laatste botst met de
       gekozen knop ernaast, en dit is een navigatiehint, geen oordeel. */
    .fz-scorekaart.doel { box-shadow: inset 3px 0 0 var(--primary-700); }

    /* Stapelen gebeurt op inhoud, niet op de app-shell: onder ~600px houdt de
       tekst naast drie knoppen minder dan 240px over en breekt hij op elk
       woord. 600px is het breekpunt dat `.fz-grid-2` in dit bestand al
       gebruikt — geen nieuw getal. De knoppen worden dan gelijk verdeeld,
       zodat de duim niet hoeft te mikken. */
    @media (max-width: 600px) {
      .fz-scorekaart { flex-direction: column; align-items: stretch; gap: var(--sp-8); }
      .fz-scorekaart-keuzes { width: 100%; }
      .fz-scorekaart-keuze { flex: 1 1 0; min-width: 0; }
      .fz-scorekaart-keuze > span { width: 100%; padding: 0 var(--sp-8); }
    }


/* ===== Verhuisd uit paginaspecifieke <style>-blokken (U3) ===== */

/* ---- fz-cplink — kopieerbaar veld (detail-kaarten) ---- */
  .fz-cplink {
    display: flex; align-items: center; gap: 8px;
    width: 100%; padding: 6px 8px;
    background: none; border: none; cursor: pointer; text-align: left;
    color: var(--ink-2); font-size: 13px; font-weight: 500; font-family: inherit;
    border-radius: var(--r-md);
  }
  .fz-cplink .ico-copy { width: 14px; height: 14px; flex-shrink: 0; color: var(--muted); }
  .fz-cplink .val { flex: 1; }
  .fz-cplink .hint { display: none; align-items: center; gap: 3px; font-size: 12px; font-weight: 600; color: var(--primary-700); }
  .fz-cplink .hint .ico-check { width: 12px; height: 12px; display: none; }
  .fz-cplink .lbl-copied { display: none; }
  .fz-cplink:hover { background: var(--primary-100); color: var(--ink); }
  .fz-cplink:hover .hint { display: inline-flex; }
  .fz-cplink.copied { background: var(--success-100); }
  .fz-cplink.copied .hint { display: inline-flex; color: #fff; background: var(--success-700); padding: 2px 6px; border-radius: 4px; font-size: 11px; }
  .fz-cplink.copied .hint .ico-check { display: inline-flex; }
  .fz-cplink.copied .lbl-copy { display: none; }
  .fz-cplink.copied .lbl-copied { display: inline; }

/* ---- fz-meld-* — feedback/meld-kaart ---- */
    .fz-meld-kaart {
      position:fixed; bottom:52px; left:8px; z-index:801;
      width:340px; background:#fff; border-radius:var(--r-lg);
      box-shadow:0 8px 32px rgba(0,0,0,.18);
      border:none; border-top:4px solid var(--primary-500);
      display:flex; flex-direction:column; gap:14px;
      padding:20px 22px;
    }
    .fz-meld-label { font-size:12px; font-weight:600; color:var(--ink-2); margin-bottom:4px; display:block; }
    .fz-meld-textarea {
      width:100%; min-height:96px; padding:8px 10px; resize:none;
      border:1px solid var(--line); border-radius:6px;
      font-size:13px; font-family:var(--font); color:var(--ink);
      box-sizing:border-box; line-height:1.5;
    }
    .fz-meld-textarea:focus { outline:none; border-color:var(--primary-500); box-shadow:0 0 0 3px var(--focus-ring); }
    .fz-meld-bevestigd { text-align:center; padding:8px 0; color:var(--success-700); font-weight:600; font-size:14px; }

/* ---- fz-drag-ghost — sleep-placeholder ---- */
  .fz-drag-ghost { opacity:.4; background:var(--primary-100); }

/* ---- fz-sect-tab — button-reset ---- */
  button.fz-sect-tab { cursor: pointer; font-family: var(--font); }

/* ---- Sorteerbare tabelkop (U5, zie _sorteer_kop.html) ---- */
.fz-sort { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
.fz-sort-pijl { font-size: 11px; color: var(--muted); }
.fz-sort-pijl.actief { color: var(--primary-800); }

/* ===== Roll-up (fz-rollup) — sectie met uitklapbare items ==================
   Eén rand om een reeks secties, elke sectie een kop die je open- en dichtklapt
   met daaronder een tabel met items. Het grootboek, de vektis-codes, de
   e-mailtemplates en het bekwaamheidsprotocol dragen alle vier dezelfde vorm.

   Ze droegen hem tot nu toe ook alle vier apart: `gb-*`, `vk-*`, `em-*` en
   `bkw-*`, vier keer opnieuw uitgeschreven en geen enkele in de styleguide. Dat
   is letterlijk het geval uit CLAUDE.md — "zo groeiden er drie chevronmaten,
   drie verwijderknoppen en twee soorten focusring naast elkaar". De maten liepen
   dan ook al uiteen: koppen van 8px en 9px padding, chevrons van 12px en 14px,
   een hover op `var(--bg)` naast een op `#f8f8f8`.

   Wat hier staat is de gedeelde kern. Wat per scherm legitiem verschilt —
   kolombreedtes, een tweede kopniveau, een gele markering voor codes zonder
   grootboek — blijft app-lokaal, conform de regel dat maatvoering geen
   component is.

   De kop is een `<button>`: uitklappen is een actie, en alleen een button
   geeft toetsenbord- en schermlezertoegang zonder extra ARIA. `aria-expanded`
   stuurt de chevron; `.open` bestaat ernaast voor de schermen die hun toestand
   in Alpine houden in plaats van in het attribuut. */
/* `overflow-x: auto` en niet `hidden`, om dezelfde reden als bij `.fz-card`:
   een tabel breder dan de wrapper hoort te scrollen en niet weggeknipt te
   worden. Gemeten op 390px verborg de e-mailtemplatelijst zo tien pixels.
   `overflow-y` blijft `hidden` voor de afgeronde hoeken. */
.fz-rollup {
  border: 1px solid var(--line); border-radius: var(--r-md);
  overflow-x: auto; overflow-y: hidden; background: #fff;
}
.fz-rollup-kop {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 12px; font-size: 13px; font-weight: 700; color: var(--ink);
  background: #fff; border: none; text-align: left; font-family: var(--font);
  cursor: pointer; user-select: none; transition: background .1s;
}
.fz-rollup-kop:hover { background: var(--bg); }

/* Scheidingslijn TÚSSEN secties, niet erboven of eronder: de wrapper draagt de
   buitenrand al, en een lijn op elke kop zou hem verdubbelen. */
.fz-rollup-sectie + .fz-rollup-sectie > .fz-rollup-kop { border-top: 1px solid var(--line); }

.fz-rollup-chevron {
  flex: 0 0 auto; width: 12px; height: 12px;
  color: var(--muted); transition: transform .12s;
}
.fz-rollup-kop[aria-expanded="true"] .fz-rollup-chevron,
.fz-rollup-chevron.open { transform: rotate(90deg); }

/* Het aantal items in de sectie: rustig, niet als kop meelezen. */
.fz-rollup-teller {
  margin-left: auto; font-weight: 400; font-size: 11px; color: var(--muted);
}

/* `overflow-y: hidden` houdt de inhoud binnen de hoogte tijdens het uitklappen;
   horizontaal moet hij scrollen en niet knippen — zie `.fz-card`. Op 390px
   verborg de e-mailtemplatelijst hier tien pixels. */
.fz-rollup-body { overflow-x: auto; overflow-y: hidden; }

.fz-rollup-tabel { width: 100%; border-collapse: collapse; }
.fz-rollup-tabel td {
  padding: 6px 10px; font-size: 12px;
  border-bottom: 1px solid var(--line-2);
  vertical-align: middle; background: #fff;
}
.fz-rollup-tabel tr:last-child td { border-bottom: none; }
.fz-rollup-tabel tfoot td { border-bottom: none; }

/* De lege staat komt in twee vormen voor: als losse `<div>` onder een sectie, en
   als `<tr>` binnen de tabel (die laatste zodat een rij die je leegmaakt zijn
   plek houdt in plaats van de tabel te laten inklappen). De tweede selector is
   nodig omdat een `<tr>` padding negeert en `.fz-rollup-tabel td` (0-1-1) anders
   met zijn 6px wint. */
.fz-rollup-leeg,
.fz-rollup-tabel tr.fz-rollup-leeg td {
  padding: 10px 12px; font-size: 12.5px; color: var(--muted); font-style: italic;
}

/* De toevoegrij staat in een `tfoot` (zie de styleguide-entry "Toevoegrij in een
   tfoot"), maar draagt vaak een eigen `<form>` en dus een flexregel in plaats
   van tabelcellen — een `<form>` mag geen kind van een `<tr>` zijn. */
.fz-rollup-toevoeg {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-top: 1px solid var(--line-2); background: var(--bg);
}
.fz-rollup-toevoeg:focus-within { background: var(--bg); }

/* ===== Rapportagetabel (fz-rapport) — verhuisd uit de financiële planning ===== */
/*
   De bedragentabel: een labelkolom plus twaalf maandkolommen plus een totaal,
   met een sticky kop en een sticky eerste kolom. Acht schermen van de financiële
   planning gebruiken hem; hij is daar samengevoegd uit acht kopieën en in fase 3
   hierheen verhuisd.

   TWEE KOPCONVENTIES, EN DAT IS EEN KEUZE. `fz-table` heeft een lichte kop
   (primary-100, muted, 11px uppercase) en is voor lijsten: rijen die je
   doorbladert. `fz-rapport` heeft een donkere kop (primary-700, wit, 13px,
   rechts uitgelijnd) en is voor bedragen: een raster dat je afleest. Pak
   `fz-table` tenzij je een cijferkolom hebt die onder elkaar moet staan.
   Vastgelegd 2026-08-05 (fase 3, U10-besluit 2, zie DECISIONS.md).

   DE RIJLADDER is een gewichtstrap, van zwaar naar licht: sectie → groep →
   regel, met totaal en netto als afsluiters. Elke trap staat op de `<tr>` en
   stijlt zijn `td`'s. Alleen deze generieke trappen staan hier; een rijtype dat
   een domein beschrijft (een grootboekregel, een belastingregel) hoort in de app
   die dat domein kent.

   `fz-rapport-totaal` is de afsluiter van een blok bínnen de tabel;
   `fz-rapport-totaalrij` is de tfoot-regel onder de hele tabel. Twee dingen,
   twee namen — ze staan hier naast elkaar zodat het onderscheid opvalt.

   WAT JE NIET MOET HARMONISEREN: de breedte van de eerste kolom. Binnen
   `table-layout: fixed` is elke breedte een verdeling van dezelfde honderd
   procent, en de maandkolommen kappen af met `text-overflow: ellipsis`. De
   basis is 220px; een scherm dat meer of minder nodig heeft overschrijft dat
   zelf. Harmoniseren op 240px maakte de W&V eerder onleesbaar (42.008 → 42.0…).
*/
  .fz-rapport { border-collapse: collapse; font-size: 13px; width: 100%; }

  /* Bedragen rechts uitgelijnd zodat de cijferkolom onder elkaar staat. */
  .fz-rapport thead th {
    position: sticky; top: 0; z-index: 3;
    background: var(--primary-700); color: #fff;
    font-weight: 600; padding: 8px 6px; text-align: right;
    white-space: nowrap; border-bottom: 2px solid var(--primary-900);
  }

  /* ---- Maandvariant: vaste kolommen, sticky eerste kolom ---- */
  .fz-rapport-maanden { table-layout: fixed; width: 100%; }
  .fz-rapport-maanden td:first-child,
  .fz-rapport-maanden th:first-child {
    width: 220px; text-align: left;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .fz-rapport-maanden th:first-child { position: sticky; left: 0; z-index: 4; }
  .fz-rapport-maanden td:first-child { position: sticky; left: 0; z-index: 2; }
  .fz-rapport-maanden thead th:first-child { text-align: left; padding-left: 12px; }

  /* ---- De gewichtstrap ---- */
  .fz-rapport-sectie td {
    background: var(--surface); color: var(--ink);
    font-weight: 700; font-size: 12.5px; padding: 10px 6px;
    border-top: 2px solid var(--line); text-align: right; white-space: nowrap;
  }
  .fz-rapport-groep td {
    background: var(--surface); color: var(--primary-700);
    font-weight: 600; font-size: 12.5px; padding: 7px 6px;
    border-top: 1px solid var(--line); text-align: right; white-space: nowrap;
  }
  .fz-rapport-regel td {
    background: var(--surface); padding: 5px 6px;
    border-top: 1px solid var(--line-2); color: var(--ink-2);
    text-align: right; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
  }
  .fz-rapport-totaal td {
    padding: 8px 6px; font-weight: 700; text-align: right;
    border-top: 2px solid var(--line); white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
  }
  .fz-rapport-netto td {
    background: var(--primary-700);
    color: #fff;
    padding: 9px 6px;
    font-weight: 700;
    text-align: right;
    border-top: 2px solid var(--primary-900);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* Inspringing van de eerste kolom drukt het nestniveau uit. Deze waarden
     gelden voor een tabel van twee niveaus; een scherm met een andere diepte
     overschrijft ze zelf. */
  .fz-rapport-maanden .fz-rapport-sectie td:first-child {
    background: var(--surface); text-align: left; padding-left: 12px;
  }
  .fz-rapport-maanden .fz-rapport-groep td:first-child {
    background: var(--surface); text-align: left; padding-left: 12px;
  }
  .fz-rapport-maanden .fz-rapport-regel td:first-child {
    background: var(--surface); text-align: left; padding-left: 20px; color: var(--ink);
  }
  .fz-rapport-maanden .fz-rapport-totaal td:first-child { text-align: left; }
  .fz-rapport-maanden .fz-rapport-netto td:first-child { text-align: left; }

  /* ---- Tekstvariant: dezelfde trap, maar voor woorden ----------------------
     `fz-rapport` lijnt alles rechts uit, en dat is juist: cijferkolommen horen
     onder elkaar te staan. Draagt een tabel geen bedragen maar namen, datums en
     statussen, dan is rechts uitlijnen verkeerd — dan zoekt het oog het begin van
     elk woord en dat staat overal anders.

     Deze variant is het spiegelbeeld van `fz-rapport-maanden`: die zet alléén de
     eerste kolom links en laat de rest rechts staan; deze zet alles links en houdt
     de inspringing. Gebruik hem voor een hiërarchische lijst met detailkolommen —
     "wie mag wat" in de bekwaamheidsmodule is het eerste geval.

     Elke selector draagt zijn rijklasse, en dat is geen stijl maar noodzaak:
     `.fz-rapport-regel td` is (0,1,1), dus een variant op (0,1,1) zou alleen op
     bronvolgorde winnen. Met de rijklasse erbij is het (0,2,1) en wint hij op
     specificiteit — ook als iemand de laadvolgorde omgooit. */
  .fz-rapport-tekst thead th,
  .fz-rapport-tekst .fz-rapport-sectie td,
  .fz-rapport-tekst .fz-rapport-groep td,
  .fz-rapport-tekst .fz-rapport-regel td,
  .fz-rapport-tekst .fz-rapport-totaal td { text-align: left; }

  /* De inspringing drukt het nestniveau uit, met dezelfde stappen als de
     maandvariant. Niveau 2 staat op 29 en niet op 20: bij de groep gaat er nog een
     chevron van 12px vóór de tekst, dus zo lijnt de detailregel uit onder de naam
     erboven in plaats van onder het pictogram. */
  .fz-rapport-tekst .fz-rapport-groep td:first-child { padding-left: 12px; }
  .fz-rapport-tekst .fz-rapport-regel td:first-child {
    padding-left: 29px; color: var(--ink);
    /* Een naam mag afbreken; een bedrag nooit. Vandaar dat dit hier staat en niet
       in de basis. `overflow: hidden` van `.fz-rapport-regel td` blijft gelden en
       knipt alleen bij `table-layout: fixed`. */
    white-space: normal;
  }

  /* Een klikbare groepsrij. Of een rij open- en dichtklapt verschilt per scherm,
     maar hóé dat voelt niet — vandaar de cursor en de hover hier, en de
     `@click` in de template. */
  .fz-rapport-tekst .fz-rapport-groep.klikbaar { cursor: pointer; user-select: none; }
  .fz-rapport-tekst .fz-rapport-groep.klikbaar:hover td { background: var(--bg); }

  /* Onder 600px krimpt de inspringing: op 390px kost 29px een tiende van de
     leesbreedte terwijl de groepsrij de hiërarchie daar al draagt. De tabel
     stapelt niet — wat je leest scrolt zijwaarts, wat je invult stapelt (zie de
     keuzeregel bij Formulier). */
  @media (max-width: 600px) {
    .fz-rapport-tekst .fz-rapport-regel td:first-child { padding-left: 16px; }
  }

  /* ---- Totaalregel onder de tabel (tfoot) ---- */
  .fz-rapport-totaalrij {
    background: var(--primary-100);
    border-top: 2px solid var(--primary-300);
  }
  .fz-rapport-totaalrij td {
    padding: 7px 6px; text-align: right; font-weight: 600;
    white-space: nowrap; color: var(--primary-800);
  }
  .fz-rapport-totaalrij td:first-child {
    padding-left: 12px; text-align: left; font-weight: 700; font-size: 12px;
    text-transform: uppercase; letter-spacing: .04em;
    background: var(--primary-100);
  }
  .fz-rapport-totaalrij td.fz-rapport-totaalkolom { font-weight: 700; }

  /* ---- Labelwrapper in de eerste kolom: chevron + tekst die afkapt ---- */
  .fz-rapport-label { display: flex; align-items: center; gap: 5px; overflow: hidden; }
  .fz-rapport-label-tekst { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* ---- De totaalkolom rechts, met een scheidingslijn ---- */
  .fz-rapport-totaalkolom {
    border-left: 1px solid var(--primary-700) !important;
    font-weight: 600;
  }

  /* ---- Negatief bedrag. Op een donkere rij een lichte rode tint, anders leest
          hij niet. ---- */
  .fz-neg { color: var(--danger-600); }
  .fz-rapport-netto .fz-neg { color: #ffb3b3; }

/* ---- Rastercel (fz-rastercel) — één bewerkbare cel in een maandraster ---- */
/*
   De cel waarin je per maand een getal zet: twaalf naast elkaar, met een label
   links en een gemiddelde of totaal rechts. Samengevoegd uit vier varianten die
   in dezelfde vorm uiteenliepen (13/12/12/11px, vier paddings, twee radii).

   Twee maten. De basis is voor een raster met ruimte; `compact` voor een raster
   dat twaalf kolommen én een breed label in beeld moet houden. Een scherm dat
   een eigen celbreedte nodig heeft zet die zelf — breedte is maatvoering, geen
   maat van de component.

   De toestanden hier zijn de generieke: focus, en niet-bewerkbaar in twee
   smaken (`readonly` als het aan rechten ligt, `disabled` als het aan de rij
   ligt). Een toestand die iets over de gegevens zegt — handmatig overschreven,
   afgeleid, buiten bereik — hoort in de app die dat weet, gescoped op deze
   klasse en met `:not(:focus)` zodat focus zichtbaar blijft.
*/
  .fz-rastercel {
    width: 100%; padding: 5px 4px;
    border: 1px solid var(--line); border-radius: 6px;
    font-size: 13px; text-align: center;
    background: var(--surface); color: var(--ink);
    box-sizing: border-box; font-family: var(--font);
    transition: background .1s, border-color .1s;
  }
  .fz-rastercel.compact { padding: 3px 2px; font-size: 12px; }
  .fz-rastercel:focus {
    outline: none; border-color: var(--primary-500);
    box-shadow: 0 0 0 3px var(--focus-ring); background: #fff;
  }
  .fz-rastercel[readonly] {
    background: var(--line-2); color: var(--muted);
    border-color: transparent; cursor: default;
  }
  .fz-rastercel:disabled {
    background: var(--line-2); color: var(--ink-2); cursor: not-allowed;
  }

/* ---- Percentage-invoer (fz-pct) ---- */
/*
   Een getal met een `%` erachter, waarbij de cijfers over de rijen heen onder
   elkaar blijven staan. Dat laatste doet de spacer: een onzichtbare `%` vóór
   het veld, even breed als de zichtbare erachter, zodat het veld gecentreerd
   blijft in plaats van naar links te schuiven bij een korter getal.

   Twee maten. De basis is een vast veld van 84px in een formulier- of
   instellingenrij. `compact` is de tabelvorm: de cel vult zijn kolom, en de twee
   tekens krimpen mee zodat ze in een maandraster passen.

   De vaste breedte staat hier, niet als `!important` op een app-klasse. Twee van
   de vijf samengevoegde varianten hadden `width: 64px !important` nodig om een
   `width: 100%` van hun eigen invoerklasse te verslaan — een specificiteits-
   pleister, geen functie.

   Let op de `:not(.compact)`. De breedte hóórt bij de basisvorm; de tabelvorm
   laat het veld juist zijn kolom vullen en een scherm dat daar een eigen
   celbreedte voor zet (`.fp-s-… .fz-rastercel`, 0-2-0) moet winnen. Een kale
   `.fz-pct input` (0-1-1) zou dat doen, maar `.fz-pct.compact input` (0-2-1)
   verslaat zo'n scope — gemeten: de cellen op drie verrichtingenschermen sprongen
   van 38 en 46px naar 69–79px. Vandaar dat compact géén breedte declareert.
*/
  .fz-pct { display: flex; align-items: center; justify-content: center; gap: 3px; }
  .fz-pct-spacer { font-size: 13px; visibility: hidden; flex-shrink: 0; }
  .fz-pct-suffix { font-size: 13px; color: var(--muted); flex-shrink: 0; }
  .fz-pct input { text-align: center; }
  .fz-pct:not(.compact) input { width: 84px; }
  .fz-pct.compact { gap: 1px; }
  .fz-pct.compact .fz-pct-spacer,
  .fz-pct.compact .fz-pct-suffix { font-size: 10px; }

/* ---- Celveld (fz-celveld) — een invoerveld binnen een tabelcel ---- */
/*
   Een `fz-input` is 42px hoog; in een tabelrij van 30px past dat niet. Dit is het
   veld dat wél in een cel past, in twee vormen:

   **Kaal** — transparant en randloos, zodat de rij eruitziet als tekst tot je
   erin klikt. Voor een toevoegrij in een `<tfoot>`, waar een raster van randjes
   de tabel onrustig maakt. Krijgt bij focus een outline mét offset: het veld
   heeft geen eigen rand om te kleuren.

   **`omrand`** — met rand en achtergrond, voor een veld dat je meteen als
   invoer moet herkennen: inline hernoemen in een bestaande rij. Focus volgt daar
   de platformconventie van `fz-input`: randkleur plus focusring.

   `breed` zet een ondergrens van 180px voor een naamkolom. Bewust géén onderdeel
   van de basis — een code- of nummerkolom is 72px breed en een minimum van 180px
   herverdeelt daar de hele tabel.
*/
  .fz-celveld {
    padding: 0; border: none; background: transparent; outline: none;
    font-size: 13px; font-weight: 400; font-family: var(--font); color: var(--ink);
    /* Een cel kan uppercase of letter-spacing dragen (een sectiekop, een
       kolomtitel). Een invoerveld daarbinnen hoort dat niet te erven — je typt
       geen kapitalen omdat de kop ze heeft. */
    text-transform: none; letter-spacing: normal;
  }
  .fz-celveld::placeholder { color: var(--muted); opacity: .6; font-weight: 400; }
  .fz-celveld:focus {
    outline: 2px solid var(--primary-500); outline-offset: 3px; border-radius: 4px;
  }
  .fz-celveld.omrand {
    padding: 3px 8px; border: 1px solid var(--line); border-radius: 6px;
    background: var(--surface);
  }
  .fz-celveld.omrand:hover { border-color: var(--primary-500); }
  .fz-celveld.omrand:focus {
    outline: none; border-color: var(--primary-500);
    box-shadow: 0 0 0 3px var(--focus-ring);
  }
  .fz-celveld.breed { min-width: 180px; }
  .fz-celveld.compact { font-size: 12px; }
  /* Vult zijn cel. Niet in de basis: een veld in een flexrij naast een knop moet
     juist op zijn inhoud passen. */
  .fz-celveld.vul { width: 100%; }
  /* Nadruk op een veld dat een uitkomst draagt in plaats van een invoer — het
     bedrag waar de rij om draait. Stond als `.fz-celveld.ink` in
     financieleplanning.css: een fz-selector in de app-stylesheet, precies de
     drift waar de vier modifiers hierboven vanaf moesten. */
  .fz-celveld.ink { font-weight: 600; }

  /* Tabelmaat voor de select ernaast. `compact` is de lijstmaat (34px hoog); in
     een toevoegrij naast een `fz-celveld.omrand` van ~26px is dat te fors. Deze
     variant volgt dat celveld: zelfde radius, zelfde hoogte, zelfde randkleur. */
  .fz-select.cel {
    width: 100%; padding: 3px 24px 3px 8px; border-radius: 6px;
    font-size: 13px; font-weight: 400; color: var(--ink);
    background-color: var(--surface); background-position: right 6px center;
  }

/* ---- Afgeleide waarde (fz-afgeleid) ---- */
/*
   Een veld dat het systeem zelf invult, met een badge die zegt waar de waarde
   vandaan komt. Ziet er bewust ánders uit dan een invoerveld — dezelfde vorm zou
   beloven dat je erin kunt typen.
*/
  .fz-afgeleid {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 36px;
    padding: 0 10px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    font-size: 13px;
    color: var(--ink-2);
  }
  .fz-afgeleid-badge {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--muted);
    background: var(--line-2);
    padding: 2px 5px;
    border-radius: 3px;
    white-space: nowrap;
  }

/* ---- Rij-status (fz-rijstatus-*) ---- */
/*
   Een rij die aandacht vraagt: een accentbalk links plus een inline melding
   eronder. Drie zwaartes — fout blokkeert, waarschuwing niet, melding is
   neutraal.

   De achtergrond draagt `!important`, en dat is hier geen luiheid. De rijen waar
   dit op landt zetten hun witte achtergrond in een `style=`-attribuut, en een
   author-declaratie zonder `!important` verliest daar altijd van. Zonder het
   sleutelwoord blijft de accentbalk staan en verdwijnt alleen de tint — een rij
   die half gemarkeerd is en er nog geloofwaardig uitziet. Verdwijnt de inline
   achtergrond ooit uit de markup, dan mag dit weg.
*/
  .fz-rijstatus-fout { background: #fef2f2 !important; box-shadow: inset 3px 0 0 var(--danger-600); }
  .fz-rijstatus-melding { font-size: 11px; color: var(--danger-600); padding: 0 0 4px 4px; }
  .fz-rijstatus-waarschuwing { font-size: 11px; color: #92400e; padding: 0 0 4px 4px; }

/* ---- Invoegblok (fz-invoegblok) ---- */
/*
   Het blok bovenaan een scherm waarmee je een rij toevoegt die pas kán ontstaan
   als er meerdere waardes staan — een rechtspersoon én een naam én een looptijd.
   Dat is precies waar het zich onderscheidt van de toevoegrij in een `<tfoot>`
   (zie *Celveld*): die is voor één veld — typen, Enter, klaar. Zodra een rij twee
   of meer verplichte waardes nodig heeft past hij niet meer in een tabelrij en
   hoort hij als blok bovenaan het scherm. Kies op dat criterium, niet op gevoel;
   anders groeit er een derde patroon tussen deze twee in.

   De kolomindeling hoort NIET bij de component. Die volgt het aantal velden en
   verschilt dus per scherm — dat is maatvoering, en die zet het scherm zelf.
*/
  .fz-invoegblok { padding: 16px 20px; }
  /* Deelt zijn vorm met `.fz-minilabel` (zie daar) en voegt alleen de marge toe
     die in dit blok past. Ze stonden apart met byte-gelijke waarden; dat is hoe
     twee componenten uit elkaar groeien zonder dat iemand het merkt. */
  .fz-invoegblok-label { margin-bottom: 4px; }
  .fz-invoegveld {
    width: 100%; box-sizing: border-box;
    padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--r-md);
    font-size: 13px; font-family: var(--font);
    background: var(--surface); color: var(--ink);
    transition: border-color .12s;
  }
  select.fz-invoegveld { padding-right: 28px; cursor: pointer; }
  .fz-invoegveld:hover { border-color: var(--primary-500); }
  /* De focusgloed hoort erbij — op het veld én op de dropdown. Zonder deze regel
     valt de browser terug op zijn eigen outline, en dan wijkt dit blok af van
     elk ander invoerveld in het platform. */
  .fz-invoegveld:focus {
    outline: none; border-color: var(--primary-500);
    box-shadow: 0 0 0 3px var(--focus-ring);
  }


    /* =========================================================================
       AANRAAKDOELEN — staat bewust achteraan
       =========================================================================
       Deze regels overschrijven maten van componenten die hierboven gedefinieerd
       zijn. CSS kent bij gelijke specificiteit alleen bronvolgorde, en een media
       query verandert daar niets aan: stond dit blok tussen de componenten in,
       dan won elke component die er later staat. Dat gebeurde ook — `.fz-wis-link`
       (regel ~630) at de padding van dit blok stil op.

       Alles hieronder geldt alleen onder het breekpunt. Zie DECISIONS.md,
       "De zijbalk wijkt onder 900px", voor de meting eronder. */
    @media (max-width: 900px) {
      /* ---- Instellingenrijen stapelen ---------------------------------------
         Het labelspoor van 180px plus een invoerveld past niet in de 308px die
         een kaart op 390px overhoudt. Onder het breekpunt komt het label dus
         bóven de bediening, met een kleinere tussenruimte omdat de 16px die
         horizontaal een kolomscheiding is, verticaal een gat wordt.

         `.actie` en `.dicht` moeten hier expliciet mee. De eerste heeft een
         derde kolom en erft `grid-template-columns` niet van de basisregel; de
         tweede zet `gap: 0`, wat gestapeld het label tegen zijn veld aan plakt.
         Beide zijn 0-2-0 en winnen anders van de 0-1-0 hieronder. */
      .fz-fieldrow,
      .fz-fieldrow.actie,
      .fz-fieldrow.dicht { grid-template-columns: 1fr; gap: 6px; }


      /* ---- Aanraakdoelen ----------------------------------------------------
         Een vinger is geen muisaanwijzer. 44px is de ondergrens uit Apple's HIG;
         Google noemt 48dp, wat hier ongeveer hetzelfde uitkomt. Gemeten op 390px
         haalden 295 bedienbare elementen die maat niet.

         Het merendeel scheelde één tot vier pixels — knoppen op 40, invoervelden
         op 42, kopieerknoppen op 43. Die groep is met één regel per component
         geholpen. Alleen `.fz-navitem` en `.fz-sort` vragen om iets anders; zie
         daar. */
      .fz-btn,
      .fz-input,
      .fz-chip,
      .fz-sect-tab,
      .fz-acct-trigger,
      .fz-menu-btn,
      .fz-launcher-btn { min-height: 44px; min-width: 44px; }

      /* De kleurswatch is het klikvlak van de kleurkiezer: de `<input
         type=color>` ligt er op `opacity: 0` overheen uitgestrekt, dus je tikt
         op de swatch en de kiezer opent. Dat maakt hem een aanraakdoel van
         32px, en het maakte hem ook onzichtbaar voor de meting — die meldde
         zes onbereikbare inputs op de huisstijl en niet zes te kleine doelen.

         `.fz-color-input` groeit mee: hij heeft `overflow: hidden`, dus een
         swatch van 44px in een vak van 32 zou aan de onderkant afgesneden
         worden. */
      .fz-color-input-swatch { width: 44px; height: 44px; }
      .fz-color-input { min-height: 44px; }

      /* `.fz-vbtn` krijgt bewust géén `min-width`. De component draagt
         `min-width: 0` als ontsnapping voor flex-shrink: hij staat naast een
         waarde die mag krimpen, en een bodem van 44px breekt die afspraak. */
      .fz-vbtn { min-height: 44px; }

      /* `.fz-meld-knop` blijft buiten deze groep. Hij zweeft `position: fixed`
         over de inhoud, dus groeien kost geen ruimte in de layout maar dékt
         juist meer af: op 44px overlapte de pil twee aantikbare patiëntrijen,
         en op mijn-account de knop "Wachtwoord wijzigen". Een feedbackwidget is
         die prijs niet waard. */

      /* `.fz-select` heeft `height: auto` en groeit met zijn padding mee. */
      .fz-select { min-height: 44px; padding-top: 8px; padding-bottom: 8px; }

      /* De scorekaart-keuze is 40px als knop en moet naar 44px, net als
         `.fz-btn`. De bodem gaat op de `<span>` en niet op de `<label>`: de
         `<input>` erin is 0×0 en de label ontleent zijn hoogte volledig aan
         die span, dus een `min-height` op de label rekt het klikvlak op
         zonder het zichtbare vlak mee te nemen — en dan steekt het over de
         kaart eronder heen. Precies de fout die `.fz-actie-link` hierboven
         maakte, waar een tik de rij eronder vuurde. */
      .fz-scorekaart-keuze > span { min-height: 44px; }

      /* ---- Klikvlak vergroten zonder de regel te verhogen -------------------
         Er zijn twee soorten kleine links, en ze vragen om het tegenovergestelde.

         (a) Een link die ZELF een rij is — een actie in een verticaal paneel.
             Die mag gewoon groeien: het paneel wordt langer, verder gebeurt er
             niets. Zie de groep onderaan dit blok.

         (b) Een link die IN een tekstregel of een tabelkop staat, met tekst
             ernaast. Die 44px hoog máken blaast de regelafstand op. Daar groeit
             alleen het klikvlak: verticale padding die buiten de regel valt,
             met een negatieve marge zodat de omgeving zijn hoogte houdt.

         De negatieve marge van (b) is scherp gereedschap: hij laat het klikvlak
         BUITEN het element steken. Staan twee zulke links onder elkaar, dan
         overlappen die vlakken en wint de laatste de hit-test — een tik op het
         zichtbare label van de één vuurt de ander. Dat is precies wat hier
         gebeurde: `.fz-actie-link` staat in een verticaal actiepaneel, en op
         390px vuurde een tik op "Markeer als afgesloten" het onomkeerbare
         "Opvolging stoppen" eronder. Vandaar de scheiding: (b) geldt alleen
         voor links die daadwerkelijk tussen tekst staan. */

      /* (b) — sorteerkoppen zijn 15px hoog, het scherpste geval. `inline-flex`
         en niet `inline-block`: de component draagt `gap` en `align-items` voor
         zijn sorteerpijl, en die vervallen bij block-flow. 13px padding houdt
         het vlak binnen de 43px hoge `th`, zodat het niet in de eerste rij
         steekt — daar zit een `onclick` die het dossier opent. */
      .fz-sort {
        display: inline-flex;
        padding-top: 13px; padding-bottom: 13px;
        margin-top: -13px; margin-bottom: -13px;
      }

      /* (b) — tekstlinks die echt tussen andere tekst staan: een `fz-linkbutton`
         middenin een zin, de uitweg onder een inlogformulier. */
      .fz-linkbutton,
      .fz-hulplink {
        display: inline-block;
        padding-top: 14px; padding-bottom: 14px;
        margin-top: -14px; margin-bottom: -14px;
      }

      /* (a) — links die zelf een rij zijn. `.fz-actie-link` is een flex-rij in
         een actiepaneel en `.fz-back` staat alleen op een schermkop; beide
         mogen groeien. `display` blijft ongemoeid: `.fz-actie-link` is flex en
         `.fz-back` inline-flex, en die op block zetten gooide de uitlijning
         van hun pictogram om. */
      .fz-actie-link { min-height: 44px; padding-top: 10px; padding-bottom: 10px; }
      .fz-back { min-height: 44px; }

      /* `.fz-wis-link` hoort hier en niet bij (b): hij staat onder een rij
         filterchips op zijn eigen regel, niet middenin een zin. Met de
         marge-truc stak zijn klikvlak omhoog over de chip erboven, en dan wist
         een tik op "Wis filters" díé ene chip in plaats van alle filters —
         dezelfde fout als bij `.fz-actie-link`, met een mildere afloop.

         `inline-flex` omdat de component zowel als `<button>` als als `<a>`
         voorkomt: een `<a>` is inline en negeert `min-height`. */
      .fz-wis-link {
        display: inline-flex; align-items: center;
        min-height: 44px;
      }

      /* Vangnet voor invoervelden zonder componentklasse. De codebase telt er
         zeventien op vier schermen — losse `<input type="text">`-velden waar
         `.fz-input` is vergeten. Die per template repareren lost het geval op en
         niet de klasse; deze regel dekt ook de volgende die het vergeet.

         Bewust op invoertypes die tekst opnemen: checkboxes, radio's en
         kleurkiezers hebben hun eigen maat en zouden hierdoor uit verhouding
         raken.

         De rekenroosters van de financiële planning vallen er buiten — zie de
         uitsluiting direct hieronder. */
      input[type="text"], input[type="email"], input[type="tel"],
      input[type="number"], input[type="password"], input[type="date"],
      input[type="search"], input[type="url"], input:not([type]),
      textarea, select { min-height: 44px; }

      /* De invoervelden ín een rekenrooster van de financiële planning:
         honderden per scherm, in rijen die hun hoogte aan de tabel ontlenen.
         Ze naar 44px trekken rekt zo'n raster uit tot iets dat je niet meer
         overziet, en dat gebeurde al bij een venster van 899px — een
         niet-gemaximaliseerd bureaublad. De financiële planning is bewust
         bureauwerk (zie het plan); dit vangnet hoort daar niet te landen.

         Eén uitsluitingsregel ná het vangnet, en niet elf keer `:not()` in de
         opsomming hierboven. Dat leest beter en, belangrijker: een klasse
         toevoegen is dan één plek in plaats van elf — precies de fout die hier
         gemaakt werd, want het raster draagt `.fz-rastercel` en niet alleen
         `.fz-celveld`.

         De elementnaam staat er bewust vóór. Een kale `.fz-rastercel` is 0-1-0
         en verliest van `input[type="text"]`, want een attribuutselector telt
         als klasse: 0-1-1. Met de elementnaam erbij is de uitsluiting óók 0-1-1
         en wint hij op bronvolgorde. Zonder dat detail is deze regel stil dood
         — precies wat er de eerste keer gebeurde. */
      input.fz-celveld, input.fz-rastercel,
      textarea.fz-celveld, textarea.fz-rastercel,
      select.fz-celveld, select.fz-rastercel { min-height: 0; }

      /* `.fz-select.cel` moet ook zijn padding terug: de regel hierboven geeft
         `.fz-select` 8px boven en onder, en alleen `min-height` terugzetten laat
         hem dan alsnog groeien. `select.fz-select.cel` is 0-2-1 tegen de 0-1-0
         van `.fz-select` — die wint sowieso. */
      select.fz-select.cel { min-height: 0; padding-top: 3px; padding-bottom: 3px; }

      /* Een vinkje is 13x13 en groeit niet mee met `min-height` — een
         `<input type=checkbox>` heeft een vaste maat die de browser bepaalt.
         Het klikvlak is het label eromheen, en dát is het element dat te klein
         blijft: op het dossier was "Dit is dezelfde persoon" 19px hoog, de
         bevestiging vóór een onomkeerbare samenvoeging. Het vinkje zelf
         vergroten zou er naast een regel van 12,5px belachelijk uitzien; het
         label groeit wél zonder de tekst aan te tasten.

         Twee klassen en geen `label:has(> input)`. Die generieke vorm stond hier
         eerst en had twee problemen. Ze raakte élk vinkje-label, dus ook
         `.fz-toggle` — zesentwintig schakelaars die een track van 24px hebben en
         geen 44px hoge regel nodig hebben; in de veertienkoloms fp-tabellen zou
         dat elke rij oprekken. En DECISIONS.md staat één generieke selector toe
         (het invoerveld-vangnet hieronder) met de aantekening dat nieuwe
         ongewenst blijven. Die uitzondering was verdiend door zeventien
         naamloze velden op vier schermen; hier droegen twaalf van de achttien
         vinkje-labels al een klasse. Dan is een klasse het antwoord.

         Vier labels blijven daarmee zonder 44px, en dat is een keuze: twee op
         het publieke inschrijfformulier (buiten scope, in orde verklaard) en
         twee in de bekwaamheid-app, die op een eigen branch in aanbouw is en
         in geen enkele meetset staat. Ze hier meenemen zou een merge-conflict
         kopen voor een scherm dat nog verandert. */
      .fz-filter-opt,
      .fz-vinkje { min-height: 44px; }

      /* `.fz-navitem` krijgt bewust géén 44px. Achttien items in de fp-zijbalk
         maal negen pixels extra is ruim 160px, en dan scrolt de lade binnen een
         scrollend scherm. Een navigatie die je moet scrollen om je bestemming te
         zien is netto slechter bereikbaar dan een compacte lijst die je in één
         blik overziet. 40px is de maat waarop achttien items plus drie
         groepskoppen nog net op een scherm van 844px passen. */
      .fz-navitem { min-height: 40px; }

      /* ---- Formuliervelden --------------------------------------------------
         `.fz-field` zet het label in een vaste kolom van 160px naast de waarde.
         Op 390px blijft daar te weinig voor over: de detail-header van een
         inschrijving gebruikt deze component zeventien keer, en het
         QMS-scoreformulier wacht erop.

         Stapelen in plaats van naast elkaar. `align-items` terug naar `stretch`
         zodat de waarde de volle breedte pakt in plaats van verticaal gecentreerd
         te blijven hangen. */
      .fz-field {
        flex-direction: column;
        align-items: stretch;
        gap: 4px;
      }
      .fz-field .lbl { width: auto; flex: 0 0 auto; }

      /* De `bal`-variant heeft een eigen labelregel met `flex: 0 0 160px` en
         `align-items: stretch`; die vaste basis moet hier ook los. Hij staat in
         de drie detail-headers, dus precies op het scherm met zeventien velden. */
      .fz-field.bal .lbl {
        flex: 0 0 auto; width: auto;
        padding-bottom: 0;
      }

      /* ---- Kieslijsten op smal scherm --------------------------------------
         Een werklijst is een scan-en-tik-scherm: je kijkt hoeveel werk er ligt
         en tikt door naar het dossier. De volledige gegevens staan één tik
         verderop, dus onder het breekpunt tonen we alleen de kolommen die je
         nodig hebt om te kiezen — wie, waar, wanneer.

         Verbergen en niet scrollen, want dit is geen datadichte tabel waar je
         cijfers naast elkaar moet zien. Zijwaarts vegen op een rij die zélf een
         navigatiedoel is, is bovendien een gebaar dat te dicht bij een tik ligt.
         Datadichte tabellen krijgen wél een scroll-container; zie daar.

         De `<col>` moet mee: een kolom verbergen zonder zijn colgroup-breedte
         laat de resterende kolommen de vrijgekomen ruimte niet opnemen. */
      .fz-kol-detail { display: none; }

      /* Wat overblijft moet ook echt in de kolom passen. "28-07-2026 09:41"
         staat als `white-space: nowrap` in de `<td>` en is 131px breed; de
         kolom die na het verbergen overblijft is er 109. Zolang de kaart
         klipte, viel dat verschil niet op — de tijd werd stil afgesneden. Nu de
         kaart scrolt (zie `.fz-card`) zou hetzelfde verschil een scrollbalk van
         26px opleveren op een lijst die juist zónder zijwaarts vegen leesbaar
         moet zijn. Afbreken op twee regels is daar het goede antwoord: de rij
         wordt iets hoger, de datum compleet.

         `!important` is hier onvermijdelijk en niet lui: de nowrap staat
         INLINE op de `<td>` in vier lijsttemplates, en een inline stijl wint
         van elke selector ongeacht specificiteit. De alternatieven waren de
         nowrap uit alle vier de templates halen (en dan draagt geen enkele
         regel meer waarom hij op desktop wél moet) of een klasse per cel. De
         selector is bewust nauw: alleen de body-cellen van een kieslijst met
         `table-layout: fixed`, precies de tabellen waar hierboven kolommen
         verdwijnen. */
      .fz-table.vast tbody td { white-space: normal !important; }

      /* Om dezelfde reden mag de waarschuwingsbadge afbreken. "⚠ Mogelijke
         terugkeer — meld bij admin" is met `white-space: nowrap` 251px breed en
         staat in een naamkolom van 137px; de helft van de boodschap werd
         afgesneden zonder beletselteken, dus je las "Mogelijke terugkeer —" en
         niet wát je moest doen. Precies het soort verlies dat de nieuwe
         klip-meting in `ui_smal_foto.py` moest opsporen. */
      .fz-badge-warn { white-space: normal; }

      /* ---- Tekstgroottes (U2) ------------------------------------------------
         Gemeten op 390px over de meetset: tekstfragmenten onder 12px. Niet
         alles daarvan is een probleem, en alles blind optrekken zou de schermen
         onrustiger maken. De scheidslijn is of je het móét lezen om te handelen.

         Wél omhoog, want dit is inhoud die je leest:
           `th` en `.fz-sort` vertellen welke kolom je bekijkt, en op een
           telefoon met minder kolommen is dat belangrijker geworden.
           `.fz-fieldhead` (+ de `sub` erin) staat boven een veldwaarde.
           `.fz-subregel` draagt het patiëntnummer, de geboortedatum, de
           stapnaam — de tweede regel van een tabelcel is data, geen decoratie.
           `.fz-navlabel` — herziening van het besluit uit PR #7, dat hem als
           "scanstructuur die je niet leest" ongemoeid liet. Op 390px is de
           zijbalk een lade die je opent om te navigeren, en dan is "MENU" vs.
           "INSTELLINGEN" precies wél wat je leest. Met 23 gevallen was hij ook
           de grootste groep die overbleef.
           `.fz-badge-warn` is een badge van vorm, maar zijn tekst is de
           boodschap ("BSN ontbreekt", "Buiten werkgebied") en niet een
           bevestiging van zijn kleur. Vandaar dat hij hier staat en de echte
           annotatie-badges hieronder niet.
           `.fz-field-msg` is een validatiefout onder een invoerveld. Als
           iets leesbaar moet zijn op de maat waarop je het invult, dan dit.
           `.fz-rijstatus-*`, `.fz-invoegblok-label`, `.fz-copychip .chip-lbl`,
           `.fz-event span`, `.fz-acct-idrole`, `.fz-rolchip`, `.fz-versie`:
           veldlabels, rolnamen en metadata die je afleest.

         Bewust ongemoeid, want hier draagt de vorm de betekenis en bevestigt
         de tekst alleen — optrekken maakt de badge zwaarder dan wat hij
         annoteert. Deze lijst staat ook in `scripts/ui_smal_foto.py`, zodat de
         meting de uitzondering kent in plaats van hem als defect te tellen:
           `.fz-tag` (auto/patiënt/standaard), `.fz-navitem .count`,
           `.fz-navitem .soon-tag`, `.fz-tab .n`, `.em-badge`,
           `.fz-afgeleid-badge`, `.fz-avatar` (initialen).

         De selectoren moeten de specificiteit van hun basisregel halen: een
         kale `th` (0-0-1) verliest van `.fz-table thead th` (0-1-2), ook vanuit
         een media query — die verandert specificiteit niet. Om diezelfde reden
         staan `.fz-field-error .fz-field-msg` en `.fz-fieldhead.accent-head
         .sub` hier voluit en niet als kale klasse: hun basisregels zijn 0-2-0
         en 0-3-0, en een kale `.fz-field-msg` (0-1-0) zou stil verliezen.

         Bewust alléén `.fz-table thead th`, want dát is de regel die op 11px
         staat. Een kale `thead th` erbij zou ook tabellen raken die al groter
         zijn: `.fz-rapport thead th` erft 13px, en die naar 12px zetten maakt
         de tekst kleiner — precies andersom dan dit blok bedoelt. */
      .fz-table thead th,
      .fz-sort,
      .fz-fieldhead,
      .fz-fieldhead.accent-head .sub,
      .fz-fieldhead.warn-head .sub,
      .fz-navlabel,
      .fz-subregel,
      .fz-rolchip,
      .fz-versie,
      .fz-badge-warn,
      .fz-acct-idrole,
      .fz-minilabel,
      .fz-invoegblok-label,
      .fz-rijstatus-melding,
      .fz-rijstatus-waarschuwing,
      .fz-copychip .chip-lbl,
      .fz-event span,
      .fz-field-error .fz-field-msg,
      .fz-consent-error .fz-field-msg,
      .fz-cplink.copied .hint { font-size: 12px; }

      /* De kolombreedtes moeten mee herverdelen. De `<colgroup>` verdeelt zes
         kolommen over 100%; blijven er twee over, dan vullen die samen nog maar
         42% en valt hun inhoud alsnog over elkaar.

         `width: auto` op de zichtbare kolommen laat `table-layout: fixed` ze
         gelijk verdelen over de volle breedte. Die layout-modus blijft staan en
         gaat níét naar `auto`: dan groeit de naamkolom naar zijn inhoud (gemeten:
         323px in een kaart van 330px) en wordt de datum eruit gedrukt. Juist
         `fixed` dwingt de inhoud te krimpen binnen de kolom. */
      .fz-table.vast col { width: auto; }

      /* Een sorteerkop die breder is dan zijn kolom duwt zichzelf buiten de
         tabel: "Inschrijfdatum ⇅" stak 13px uit de laatste kolom. De kolom is
         `fixed` en mag niet groeien, dus de kop moet inbinden.

         Maar niet met `overflow: hidden` op de `th`. Dat klipt van rechts, en
         rechts staat juist de sorteerpijl — de enige aanwijzing op welke kolom
         en in welke richting gesorteerd wordt. Die verdween daardoor volledig
         op de laatste kolom van drie lijstschermen.

         Dus andersom: het lábel kapt af met een ellips, de pijl houdt zijn
         plek. `min-width: 0` is nodig omdat een flex-item standaard niet onder
         zijn min-content krimpt en de ellips anders nooit vuurt. */
      .fz-sort { max-width: 100%; }
      .fz-sort-label {
        min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
      }
      .fz-sort-pijl { flex: 0 0 auto; }

      /* Het logo krimpt mee in plaats van te worden afgesneden. `object-fit` is
         nodig naast `max-width`, want de hoogte staat vast op 36px en zonder
         `contain` vervormt de afbeelding. Gemeten op 320px: de kaart was 101px
         breed voor een logo van 129px, en het woordmerk brak midden in een
         letter af op "Fonkelz". */
      .fz-logoimg { max-width: 100%; object-fit: contain; object-position: left center; }
    }

/* ---- Bereikbaar is meer dan groot ------------------------------------------
   Een verwijderknop die pas bij `:hover` verschijnt, bestaat niet op een
   aanraakscherm — ongeacht zijn maat. Gemeten: 25 elementen stonden op
   `opacity: 0` of `pointer-events: none` tot je eroverheen ging, waaronder elf
   `.fz-del`-knoppen en de sleepgrepen.

   `hover: none` is de juiste toets en niet de schermbreedte: een tablet met
   toetsenbord én aanraakscherm valt hierbuiten (die meldt `hover: hover`), en
   een breed aanraakscherm valt er wél onder.

   Dit blok staat aan het ÉÉRSTE einde van het bestand en niet bij de
   componenten, om dezelfde reden als het aanraakdoelen-blok hierboven: de
   basisregel `.fz-del-rij .fz-del { opacity: 0 }` staat verderop in de bron en
   heeft dezelfde specificiteit (0-2-0). Bij gelijke specificiteit wint de
   laatste regel, en een media query verandert daar niets aan. Toen dit blok nog
   bovenaan stond was het dood: de verwijderknoppen bleven op aanraak
   onbereikbaar terwijl de sleepgrepen — die geen tegenregel hebben — wél
   verschenen. Dat verschil maakte de fout zo moeilijk te zien. */
@media (hover: none) {
  .fz-del-rij .fz-del,
  .fz-del-rij .fz-grip { opacity: 1; pointer-events: auto; }

  /* De hernoemknop hoort in dezelfde familie, en was het enige lid dat de regel
     niet bereikte: zijn basisdefinitie stond lokaal in `emails.html` en in
     `financieleplanning.css`, allebei ná dit bestand geladen. Die staat nu
     hierboven bij `.fz-del`, waarmee deze regel hem eindelijk raakt.

     Eén selector volstaat waar `.fz-del` er twee nodig heeft: het potlood zet
     zijn eigen `opacity: 0` en leunt niet op een `.fz-del-rij`-ouder. */
  .pl-naam-edit-btn { opacity: 1; }
}

/* Zichtbaar maken is niet genoeg: deze knoppen zijn 30x30 en 22x22, en een
   vinger raakt dat niet betrouwbaar. De maatverhoging staat wél achter de
   breedte-eis en het onthullen hierboven niet: een aanraaklaptop van 1440px
   meldt `hover: none`, en daar zou 44px per knop de brede fp-tabellen uitrekken
   zonder dat iemand daarom vroeg. Onthullen kost geen ruimte, vergroten wel —
   dus alleen dat tweede hangt aan het breekpunt. */
@media (hover: none) and (max-width: 900px) {
  .fz-del-rij .fz-del,
  .fz-del-rij .fz-grip { min-width: 44px; min-height: 44px; }

  /* Het potlood is 13x17 en daarmee de kleinste van de familie. `justify-content`
     moet mee: de knop is `inline-flex` met een pictogram dat anders links in
     het vergrote vlak blijft plakken in plaats van in het midden te staan. */
  .pl-naam-edit-btn {
    min-width: 44px; min-height: 44px; justify-content: center;
  }
}


/* ---- Beweging op verzoek uitzetten ----------------------------------------
   `prefers-reduced-motion` is een besturingssysteem-instelling die mensen
   aanzetten omdat beweging hen misselijk maakt of hun aandacht wegtrekt. Het
   is geen smaakvoorkeur, en het staat aan vóórdat onze pagina laadt.

   Dit blok staat aan het einde van het bestand omdat het van álles moet winnen,
   inclusief de media-blokken hierboven. `!important` hoort hier om dezelfde
   reden: elke transitie in dit bestand en in de twee app-stylesheets moet
   wijken, ook die met een hogere specificiteit.

   Wat er in deze codebase beweegt: 45 `transition`-declaraties, waarvan er één
   over afstand gaat en dus de eigenlijke aanleiding is — de zijbalk die op
   telefoonbreedte over de inhoud schuift. De rest zijn kleuren en chevrons van
   0,12 tot 0,2 seconde. Die gaan mee: aparte gevallen maken zou betekenen dat
   iemand bij elke nieuwe transitie moet onthouden of hij hier hoort.

   `transition-delay` gaat expliciet mee, en dat is geen formaliteit. De lade
   draagt `transition: transform .18s ease, visibility 0s linear .18s` — die
   vertraging houdt hem zichtbaar tijdens het wegschuiven. Zet je alleen de duur
   op nul, dan blijft die vertraging staan en hangt de lade nog 0,18 seconde
   zichtbaar op een plek waar hij al weg hoort te zijn.

   Wat hier NIET onder valt: de sleepanimaties van SortableJS (`animation: 120`
   en `150`, tien templates). Dat is een JS-optie en geen CSS-eigenschap; die
   respecteert deze voorkeur pas als de aanroep zelf `matchMedia` raadpleegt.
   Genoteerd als losse opruiming — een sleep die de gebruiker zelf uitvoert is
   ook een mildere vorm van beweging dan een lade die uit zichzelf inschuift. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
}
