// ============================================================================
// David Analytics — COMPONENT LIBRARY (levende componentengalerij).
// Toont de componenten die op dit moment in de CRM worden gebruikt, met hun
// states en props. Begin: het standaard inputveld (TextInput / FormField).
// ============================================================================
const { Icon, Avatar, IconBubble, Button, ChipButton, IconButton, Badge, StatusDot, Card, CardHead, Menu, MenuItem } = window;
const { Table, Drawer, Tabs, Tab, Modal, ConfirmDialog, EmptyState, KpiGrid, Kpi, StatTile, Timeline, TimelineItem } = window;
const { TextInput, ReadValue, Textarea, Select, NumberField, DateField, MoneyField, SearchInput, MultiSelect, FileField, Toggle, Checkbox, RadioGroup, Autocomplete, FormField, FormGrid, RecordLayout, Flag, ResetButton } = window;

// ---- Galerij-bouwstenen ----------------------------------------------------
function Swatch({ children, label }) {
  return (
    <div className="g-swatch">
      <div className="g-swatch-stage">{children}</div>
      {label && <span className="g-swatch-label">{label}</span>}
    </div>
  );
}

function PropsTable({ rows }) {
  return (
    <table className="g-props">
      <thead>
        <tr><th>Prop</th><th>Type</th><th>Standaard</th><th>Beschrijving</th></tr>
      </thead>
      <tbody>
        {rows.map((r) => (
          <tr key={r.name}>
            <td><code>{r.name}</code></td>
            <td><span className="g-type">{r.type}</span></td>
            <td>{r.def ? <code>{r.def}</code> : <span className="g-muted">—</span>}</td>
            <td>{r.desc}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

function Section({ id, eyebrow, title, intro, children }) {
  return (
    <section className="g-section" id={id}>
      <header className="g-section-head">
        {eyebrow && <span className="g-eyebrow">{eyebrow}</span>}
        <h2>{title}</h2>
        {intro && <p className="g-intro">{intro}</p>}
      </header>
      {children}
    </section>
  );
}

// ---- Input-veld demo -------------------------------------------------------
function InputShowcase() {
  const [v1, setV1] = React.useState('Rick');
  const [v2, setV2] = React.useState('R.J.');
  const [v3, setV3] = React.useState('');

  return (
    <React.Fragment>
      {/* States */}
      <div className="g-card">
        <div className="g-card-title">States</div>
        <div className="g-grid-states">
          <Swatch label="Default (lezen)">
            <ReadValue value="Rick van Dijk" />
          </Swatch>
          <Swatch label="Default — leeg">
            <ReadValue value="" />
          </Swatch>
          <Swatch label="Bewerkbaar">
            <TextInput value={v1} onChange={setV1} placeholder="Voornaam" />
          </Swatch>
          <Swatch label="Bewerkbaar — focus">
            <TextInput value={v3} onChange={setV3} placeholder="Klik om te focussen" />
          </Swatch>
          <Swatch label="Disabled">
            <TextInput value="Niet bewerkbaar" disabled onChange={() => {}} />
          </Swatch>
          <Swatch label="Ongeldig">
            <TextInput value="fout@" type="email" invalid onChange={() => {}} />
          </Swatch>
        </div>
      </div>

      {/* In een formulierveld (label + waarde) */}
      <div className="g-card">
        <div className="g-card-title">In een formulierveld</div>
        <p className="g-card-note">
          <code>FormField</code> zet een label boven het veld; <code>FormGrid</code> lijnt
          velden uit in een twee-koloms raster. De labelhoogte is vast, zodat een
          melding achter het label de uitlijning niet verstoort.
        </p>
        <FormGrid>
          <FormField label="Voornaam">
            <TextInput value={v1} onChange={setV1} placeholder="Voornaam" />
          </FormField>
          <FormField
            label="Voorletters"
            extra={<Flag>Aangepast</Flag>}
          >
            <TextInput
              value={v2}
              onChange={setV2}
              placeholder="Voorletters"
              trailing={<ResetButton onClick={() => setV2('R.')} />}
            />
          </FormField>
        </FormGrid>
      </div>

      {/* Met uitleg (i) */}
      <div className="g-card">
        <div className="g-card-title">Met uitleg <span className="g-muted">(info)</span></div>
        <p className="g-card-note">Geef <code>FormField</code> een <code>info</code>-tekst en er verschijnt een (i) achter het label. Bij hover (of focus via toetsenbord) toont het een tooltip die uitlegt wat het veld doet.</p>
        <FormGrid>
          <FormField
            label="Ondertekeningsnaam"
            info="De naam zoals die op officiële documenten en contracten wordt gebruikt. Wordt automatisch samengesteld uit voorletters, tussenvoegsel en achternaam."
          >
            <TextInput value="R. van Dijk" onChange={() => {}} />
          </FormField>
          <FormField
            label="KvK-nummer"
            info="Het 8-cijferige inschrijvingsnummer bij de Kamer van Koophandel."
          >
            <TextInput value="" onChange={() => {}} placeholder="12345678" />
          </FormField>
        </FormGrid>
      </div>

      {/* Anatomie */}
      <div className="g-card">
        <div className="g-card-title">Anatomie</div>
        <ul className="g-anatomy">
          <li><span className="g-dot" />Vaste hoogte van <strong>42px</strong> — lezen, bewerken en disabled delen exact dezelfde footprint, dus wisselen veroorzaakt geen verspringing.</li>
          <li><span className="g-dot" />Default (lees)state is een zachte gevulde chip zonder actieve omlijning; bewerken voegt een witte vulling met rand toe.</li>
          <li><span className="g-dot" />Focus toont een primair-gekleurde rand met een zachte ring; ongeldige invoer kleurt de rand rood.</li>
          <li><span className="g-dot" />Een optionele <code>trailing</code> slot plaatst een actie (bijv. Herstel) binnen het veld, rechts uitgelijnd.</li>
        </ul>
      </div>

      {/* Props */}
      <div className="g-card">
        <div className="g-card-title"><code>TextInput</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'string', desc: 'Huidige waarde van het veld.' },
          { name: 'onChange', type: '(v) => void', desc: 'Aangeroepen bij wijziging met de nieuwe waarde.' },
          { name: 'type', type: "'text' | 'email' | 'tel'", def: "'text'", desc: 'Bepaalt het invoertype.' },
          { name: 'placeholder', type: 'string', desc: 'Plaatshoudertekst bij een leeg veld.' },
          { name: 'disabled', type: 'boolean', def: 'false', desc: 'Maakt het veld niet-interactief en gedempt.' },
          { name: 'invalid', type: 'boolean', def: 'false', desc: 'Kleurt de rand rood om een fout aan te geven.' },
          { name: 'trailing', type: 'ReactNode', desc: 'Element binnen het veld, rechts uitgelijnd (bijv. een knop).' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Tekstvak (Textarea) ---------------------------------------------------
function TextareaShowcase() {
  const [v, setV] = React.useState('De ondernemer zoekt financiering voor de aankoop van een bedrijfspand.');
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">States</div>
        <div className="g-grid-states">
          <Swatch label="Bewerkbaar"><Textarea value={v} onChange={setV} rows={4} /></Swatch>
          <Swatch label="Leeg (placeholder)"><Textarea value="" onChange={() => {}} rows={4} placeholder="Notitie…" /></Swatch>
          <Swatch label="Disabled"><Textarea value="Niet bewerkbaar" onChange={() => {}} rows={4} disabled /></Swatch>
          <Swatch label="Ongeldig"><Textarea value="" onChange={() => {}} rows={4} invalid placeholder="Verplicht veld" /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>Textarea</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'string', desc: 'Huidige tekst.' },
          { name: 'onChange', type: '(v) => void', desc: 'Aangeroepen bij wijziging.' },
          { name: 'rows', type: 'number', def: '4', desc: 'Aantal zichtbare regels (minimumhoogte).' },
          { name: 'placeholder', type: 'string', desc: 'Plaatshoudertekst.' },
          { name: 'disabled', type: 'boolean', def: 'false', desc: 'Niet-interactief en gedempt.' },
          { name: 'invalid', type: 'boolean', def: 'false', desc: 'Rode rand bij een fout.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Keuzelijst (Select) ---------------------------------------------------
function SelectShowcase() {
  const [v, setV] = React.useState('Prospect');
  const types = ['Klant', 'Prospect', 'Relatie'];
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">States</div>
        <div className="g-grid-states">
          <Swatch label="Gekozen"><Select value={v} onChange={setV} options={types} /></Swatch>
          <Swatch label="Placeholder"><Select value="" onChange={() => {}} options={types} placeholder="Kies een type…" /></Swatch>
          <Swatch label="Disabled"><Select value="Klant" onChange={() => {}} options={types} disabled /></Swatch>
          <Swatch label="Ongeldig"><Select value="" onChange={() => {}} options={types} placeholder="Verplicht" invalid /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>Select</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'string', desc: 'Geselecteerde waarde.' },
          { name: 'onChange', type: '(v) => void', desc: 'Aangeroepen bij keuze.' },
          { name: 'options', type: 'string[] | {value,label}[]', desc: 'De keuzemogelijkheden.' },
          { name: 'placeholder', type: 'string', desc: 'Tekst voor de lege keuze.' },
          { name: 'disabled', type: 'boolean', def: 'false', desc: 'Niet-interactief en gedempt.' },
          { name: 'invalid', type: 'boolean', def: 'false', desc: 'Rode rand bij een fout.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Datumveld -------------------------------------------------------------
function DateShowcase() {
  const [v, setV] = React.useState('2026-05-14');
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">States</div>
        <div className="g-grid-states">
          <Swatch label="Gekozen"><DateField value={v} onChange={setV} /></Swatch>
          <Swatch label="Leeg"><DateField value="" onChange={() => {}} /></Swatch>
          <Swatch label="Disabled"><DateField value="2026-01-01" onChange={() => {}} disabled /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>DateField</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'string (ISO)', desc: 'Datum als yyyy-mm-dd.' },
          { name: 'onChange', type: '(v) => void', desc: 'Geeft de nieuwe ISO-datum terug.' },
          { name: 'disabled', type: 'boolean', def: 'false', desc: 'Niet-interactief en gedempt.' },
          { name: 'invalid', type: 'boolean', def: 'false', desc: 'Rode rand bij een fout.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Bedragveld ------------------------------------------------------------
function MoneyShowcase() {
  const [hele, setHele] = React.useState(250000);
  const [centen, setCenten] = React.useState(1234.5);
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Hele euro's <span className="g-muted">(decimals: false)</span></div>
        <div className="g-grid-states">
          <Swatch label="Bedrag"><MoneyField value={hele} onChange={setHele} /></Swatch>
          <Swatch label="Leeg (placeholder)"><MoneyField value={null} onChange={() => {}} /></Swatch>
          <Swatch label="Disabled"><MoneyField value={50000} onChange={() => {}} disabled /></Swatch>
          <Swatch label="Ongeldig"><MoneyField value={null} onChange={() => {}} invalid /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Met centen <span className="g-muted">(decimals: true)</span></div>
        <div className="g-grid-states">
          <Swatch label="Bedrag"><MoneyField value={centen} onChange={setCenten} decimals /></Swatch>
          <Swatch label="Leeg (placeholder)"><MoneyField value={null} onChange={() => {}} decimals /></Swatch>
          <Swatch label="Disabled"><MoneyField value={1995.99} onChange={() => {}} decimals disabled /></Swatch>
          <Swatch label="Ongeldig"><MoneyField value={null} onChange={() => {}} decimals invalid /></Swatch>
        </div>
        <p className="g-card-note">Tijdens het typen verschijnen de duizendtekens automatisch (Nederlandse notatie: <code>.</code> voor duizendtallen, <code>,</code> voor centen). <code>onChange</code> geeft altijd een getal (of <code>null</code>) terug — bij de centen-variant met 2 decimalen.</p>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>MoneyField</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'number | null', desc: 'Het bedrag (zonder opmaak).' },
          { name: 'onChange', type: '(n) => void', desc: 'Geeft een getal of null terug.' },
          { name: 'decimals', type: 'boolean', def: 'false', desc: 'Schakelt centen (2 decimalen) in.' },
          { name: 'placeholder', type: 'string', def: "'0' / '0,00'", desc: 'Plaatshoudertekst (afhankelijk van decimals).' },
          { name: 'disabled', type: 'boolean', def: 'false', desc: 'Niet-interactief en gedempt.' },
          { name: 'invalid', type: 'boolean', def: 'false', desc: 'Rode rand bij een fout.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Selectievelden (Toggle / Checkbox / Radio) ----------------------------
function ChoiceShowcase() {
  const [on, setOn] = React.useState(true);
  const [check, setCheck] = React.useState(true);
  const [radio, setRadio] = React.useState('email');
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Toggle</div>
        <div className="g-grid-states">
          <Swatch label="Aan"><Toggle checked={on} onChange={setOn} label="Marketing-mails ontvangen" /></Swatch>
          <Swatch label="Uit"><Toggle checked={false} onChange={() => {}} label="Uitgeschakeld" /></Swatch>
          <Swatch label="Disabled"><Toggle checked onChange={() => {}} label="Vergrendeld" disabled /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Checkbox</div>
        <div className="g-grid-states">
          <Swatch label="Aangevinkt"><Checkbox checked={check} onChange={setCheck} label="Akkoord met voorwaarden" /></Swatch>
          <Swatch label="Leeg"><Checkbox checked={false} onChange={() => {}} label="Nieuwsbrief" /></Swatch>
          <Swatch label="Disabled"><Checkbox checked onChange={() => {}} label="Verplicht" disabled /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Radio</div>
        <div className="g-grid-states">
          <Swatch label="Verticaal">
            <RadioGroup value={radio} onChange={setRadio} options={[
              { value: 'email', label: 'E-mail' }, { value: 'tel', label: 'Telefoon' }, { value: 'post', label: 'Post' },
            ]} />
          </Swatch>
          <Swatch label="Inline">
            <RadioGroup value={radio} onChange={setRadio} inline options={[
              { value: 'email', label: 'E-mail' }, { value: 'tel', label: 'Telefoon' }, { value: 'post', label: 'Post' },
            ]} />
          </Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Props</div>
        <PropsTable rows={[
          { name: 'Toggle / Checkbox', type: '', desc: 'checked: boolean · onChange: (b) => void · label?: string · disabled?: boolean' },
          { name: 'RadioGroup', type: '', desc: 'value · onChange: (v) => void · options: string[] | {value,label}[] · inline?: boolean · disabled?: boolean' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Autocomplete ----------------------------------------------------------
const DIRECTORY = [
  { id: 'p1', kind: 'person', name: 'Sanne de Wit', sub: 'Eigenaar · De Wit Bouw B.V.' },
  { id: 'p2', kind: 'person', name: 'Carolien Mulder', sub: 'Directeur · Mulder Interim' },
  { id: 'p3', kind: 'person', name: 'Rick van Dijk', sub: 'Investeerder' },
  { id: 'p4', kind: 'person', name: 'Emma Visser', sub: 'Atelier Emma' },
  { id: 'o1', kind: 'org', name: 'De Wit Bouw B.V.', sub: 'KvK 12345678 · Utrecht' },
  { id: 'o2', kind: 'org', name: 'Mulder Interim B.V.', sub: 'KvK 87654321 · Amersfoort' },
  { id: 'o3', kind: 'org', name: 'Visser Holding B.V.', sub: 'KvK 24681357 · Zwolle' },
];

// Gesimuleerde KVK-API: levert na een korte vertraging resultaten.
const KVK_DB = [
  { kvk: '69599084', naam: 'David Analytics B.V.', plaats: 'Amersfoort', type: 'Hoofdvestiging' },
  { kvk: '34567890', naam: 'Finatech Solutions B.V.', plaats: 'Utrecht', type: 'Hoofdvestiging' },
  { kvk: '11223344', naam: 'De Vries Financieel Advies', plaats: 'Zwolle', type: 'Eenmanszaak' },
  { kvk: '55667788', naam: 'Van den Berg Holding B.V.', plaats: 'Apeldoorn', type: 'Hoofdvestiging' },
  { kvk: '99887766', naam: 'Bakker & Partners Accountants', plaats: 'Deventer', type: 'Maatschap' },
  { kvk: '12121212', naam: 'Janssen Bouwgroep B.V.', plaats: 'Arnhem', type: 'Hoofdvestiging' },
];
function fakeKvkSearch(q) {
  return new Promise((resolve) => {
    setTimeout(() => {
      const t = q.toLowerCase();
      resolve(KVK_DB.filter((r) => r.naam.toLowerCase().includes(t) || r.kvk.includes(t)));
    }, 650);
  });
}

function DirAvatar({ item }) {
  const round = item.kind === 'person';
  const color = window.avColor ? window.avColor(item.name) : '#64748b';
  const ini = window.initials ? window.initials(item.name) : item.name.slice(0, 2);
  return <span className={`ff-ac-avatar${round ? ' round' : ''}`} style={{ background: color }}>{ini}</span>;
}

function AutocompleteShowcase() {
  const [q1, setQ1] = React.useState('');
  const [sel1, setSel1] = React.useState(null);
  const [q2, setQ2] = React.useState('');
  const [sel2, setSel2] = React.useState(null);
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Personen &amp; bedrijven <span className="g-muted">(lokaal filteren)</span></div>
        <p className="g-card-note">Filtert een bestaande lijst client-side. Typ bijv. <code>de</code>, <code>mulder</code> of <code>b.v.</code> — gebruik ↑ ↓ en Enter, of klik een resultaat.</p>
        <FormGrid>
          <FormField label="Zoek persoon of bedrijf" wide>
            <Autocomplete
              value={q1} onChange={setQ1} onSelect={setSel1}
              options={DIRECTORY}
              getLabel={(o) => o.name}
              getSub={(o) => o.sub}
              leading={(o) => <DirAvatar item={o} />}
              placeholder="Zoek op naam…"
            />
          </FormField>
        </FormGrid>
        {sel1 && <p className="g-selected"><Icon name="check" size={15} />Gekozen: <strong>{sel1.name}</strong> <span className="g-muted">· {sel1.sub}</span></p>}
      </div>

      <div className="g-card">
        <div className="g-card-title">KVK-zoekopdracht <span className="g-muted">(async API)</span></div>
        <p className="g-card-note">Haalt resultaten op via een <code>fetchOptions</code>-functie (hier een gesimuleerde KVK-API met ~650ms vertraging). Tijdens het laden draait er een spinner. Typ bijv. <code>fina</code> of een KvK-nummer.</p>
        <FormGrid>
          <FormField label="Zoek in het Handelsregister" info="Zoekt live in het KVK-Handelsregister op bedrijfsnaam of KvK-nummer." wide>
            <Autocomplete
              value={q2} onChange={setQ2} onSelect={setSel2}
              fetchOptions={fakeKvkSearch}
              minChars={2}
              getLabel={(o) => o.naam}
              getSub={(o) => `KvK ${o.kvk} · ${o.plaats} · ${o.type}`}
              leading={() => <span className="ff-ac-avatar" style={{ background: 'var(--primary-500)' }}><Icon name="building-2" size={15} /></span>}
              placeholder="Bedrijfsnaam of KvK-nummer…"
              emptyText="Geen inschrijvingen gevonden"
            />
          </FormField>
        </FormGrid>
        {sel2 && <p className="g-selected"><Icon name="check" size={15} />Gekozen: <strong>{sel2.naam}</strong> <span className="g-muted">· KvK {sel2.kvk}</span></p>}
      </div>

      <div className="g-card">
        <div className="g-card-title"><code>Autocomplete</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'string', desc: 'De huidige invoertekst.' },
          { name: 'onChange', type: '(text) => void', desc: 'Houdt de invoertekst bij.' },
          { name: 'onSelect', type: '(item) => void', desc: 'Vuurt wanneer een resultaat wordt gekozen.' },
          { name: 'options', type: 'item[]', desc: 'Vaste lijst die client-side wordt gefilterd.' },
          { name: 'fetchOptions', type: 'async (q) => item[]', desc: 'Async loader (bijv. een API). Vervangt options.' },
          { name: 'getLabel', type: '(item) => string', desc: 'Leest het zichtbare label uit een item.' },
          { name: 'getSub', type: '(item) => string', desc: 'Optionele tweede regel per resultaat.' },
          { name: 'leading', type: '(item) => node', desc: 'Optioneel element vóór de tekst (avatar/icoon).' },
          { name: 'minChars', type: 'number', def: '1', desc: 'Minimaal aantal tekens voor het zoeken start.' },
          { name: 'emptyText', type: 'string', def: "'Geen resultaten'", desc: 'Tekst als er geen resultaten zijn.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Layout-demo (RecordLayout) --------------------------------------------
function LayoutBox({ label, sub, tone, h }) {
  return (
    <div className={`g-lbox${tone ? ' ' + tone : ''}`} style={h ? { minHeight: h } : undefined}>
      <b>{label}</b>
      {sub && <span>{sub}</span>}
    </div>
  );
}

function LayoutShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Inhoud + rail <span className="g-muted">(columns: 1)</span></div>
        <p className="g-card-note">De standaard detailpagina: één inhoudskolom met een smalle rail van <code>340px</code> ernaast (bijv. openstaande acties of feiten). één grid, één gat — inhoud en rail lijnen exact uit.</p>
        <RecordLayout rail={<LayoutBox label="Rail" sub="340px" tone="rail" h={150} />}>
          <LayoutBox label="Inhoud" sub="1fr" h={68} />
          <LayoutBox label="Kaart" h={56} />
        </RecordLayout>
      </div>

      <div className="g-card">
        <div className="g-card-title">Twee kolommen + rail <span className="g-muted">(columns: 2)</span></div>
        <p className="g-card-note">Het inhoudsgebied splitst in twee gelijke kolommen, met dezelfde rail ernaast — zoals het overzicht van een aanvraag (Lead &amp; behandeling + Intermediair, acties in de rail).</p>
        <RecordLayout columns={2} rail={<LayoutBox label="Rail" sub="acties" tone="rail" h={150} />}>
          <LayoutBox label="Kolom 1" h={92} />
          <LayoutBox label="Kolom 2" h={92} />
        </RecordLayout>
      </div>

      <div className="g-card">
        <div className="g-card-title">Meebewegende rail <span className="g-muted">(railWidth: flex)</span></div>
        <p className="g-card-note">Voor dashboards waar de zijkolom mee moet schalen: inhoud <code>2fr</code>, rail <code>1fr</code> in plaats van een vaste breedte. Met <code>railStack="below"</code> zakt de rail op smalle schermen ónder de inhoud.</p>
        <RecordLayout rail={<LayoutBox label="Rail" sub="1fr" tone="rail" h={110} />} railWidth="flex" railStack="below">
          <LayoutBox label="Inhoud" sub="2fr" h={110} />
        </RecordLayout>
      </div>

      <div className="g-card">
        <div className="g-card-title">Volle breedte <span className="g-muted">(geen rail)</span></div>
        <p className="g-card-note">Laat <code>rail</code> weg en de inhoud beslaat de volle breedte — voor tabs zonder zijkolom, zoals een tijdlijn.</p>
        <RecordLayout columns={3}>
          <LayoutBox label="1" h={64} />
          <LayoutBox label="2" h={64} />
          <LayoutBox label="3" h={64} />
        </RecordLayout>
      </div>

      <div className="g-card">
        <div className="g-card-title">Anatomie &amp; tokens</div>
        <ul className="g-anatomy">
          <li><span className="g-dot" />één grid voor inhoud én rail, dus alle kaarten delen hetzelfde gat en lijnen op dezelfde kolommen uit.</li>
          <li><span className="g-dot" />Railbreedte en hét gat staan als tokens vast: <code>--ff-rail</code> (340px) en <code>--ff-grid-gap</code> (24px). Het gat tussen rail en inhoud is gelijk aan dat tussen de kolommen — één uniforme maat.</li>
          <li><span className="g-dot" />Responsief ingebouwd: onder 1100px klapt de rail boven de inhoud; onder 860px worden meerdere kolommen één kolom.</li>
        </ul>
      </div>

      <div className="g-card">
        <div className="g-card-title"><code>RecordLayout</code> — props</div>
        <PropsTable rows={[
          { name: 'columns', type: '1 | 2 | 3', def: '1', desc: 'Aantal gelijke kolommen in het inhoudsgebied.' },
          { name: 'rail', type: 'ReactNode', desc: 'Inhoud van de smalle zijkolom. Weglaten = volle breedte, geen rail.' },
          { name: 'railWidth', type: "string | 'flex'", def: "'340px'", desc: "Vaste breedte (bv. '296px') of 'flex' (rail beweegt mee: 2fr/1fr)." },
          { name: 'railSide', type: "'right' | 'left'", def: "'right'", desc: 'Aan welke kant de rail staat.' },
          { name: 'railStack', type: "'above' | 'below'", def: "'above'", desc: 'Op smal scherm: rail boven of onder de inhoud.' },
          { name: 'children', type: 'ReactNode', desc: 'De inhoud; elk direct kind is een cel in het inhoudsraster.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Numeriek veld ---------------------------------------------------------
function NumberShowcase() {
  const [jaar, setJaar] = React.useState(1998);
  const [pct, setPct] = React.useState(1.45);
  const [opp, setOpp] = React.useState(420);
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Varianten</div>
        <div className="g-grid-states">
          <Swatch label="Geheel getal"><NumberField value={jaar} onChange={setJaar} /></Swatch>
          <Swatch label="Met eenheid (suffix)"><NumberField value={opp} onChange={setOpp} suffix="m²" /></Swatch>
          <Swatch label="Percentage (decimals)"><NumberField value={pct} onChange={setPct} step={0.05} decimals suffix="%" /></Swatch>
          <Swatch label="Leeg (placeholder)"><NumberField value="" onChange={() => {}} placeholder="0" /></Swatch>
          <Swatch label="Disabled"><NumberField value={12} onChange={() => {}} disabled /></Swatch>
          <Swatch label="Ongeldig"><NumberField value="" onChange={() => {}} invalid placeholder="Verplicht" /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Inline <span className="g-muted">(bewerk-in-plaats)</span></div>
        <p className="g-card-note">Met <code>inline</code> krijgt elk veld de bewerk-in-plaats-look: zelfde footprint als de leeswaarde, highlight in de marge. Zo gebruiken de detailpagina’s de DS-velden zonder dat er iets verspringt.</p>
        <div className="g-inline-host">
          <span className="g-inline-lbl">Bouwjaar</span>
          <NumberField value={jaar} onChange={setJaar} inline />
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>NumberField</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'number | ""', desc: 'Het getal (of leeg).' },
          { name: 'onChange', type: '(n) => void', desc: 'Geeft een getal of "" terug.' },
          { name: 'step', type: 'number', def: '1', desc: 'Stapgrootte.' },
          { name: 'decimals', type: 'boolean', def: 'false', desc: 'Sta decimalen toe.' },
          { name: 'prefix / suffix', type: 'string', desc: 'Tekst binnen het veld (bv. eenheid % of m²).' },
          { name: 'inline', type: 'boolean', def: 'false', desc: 'Bewerk-in-plaats-variant.' },
          { name: 'min / max / align', type: '', desc: 'Grenzen en tekstuitlijning.' },
          { name: 'disabled / invalid', type: 'boolean', desc: 'States.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Zoekveld --------------------------------------------------------------
function SearchShowcase() {
  const [q, setQ] = React.useState('');
  const [q2, setQ2] = React.useState('Hypotheek De Wit');
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Varianten</div>
        <div className="g-grid-states">
          <Swatch label="Leeg"><SearchInput value={q} onChange={setQ} placeholder="Zoeken in CRM…" /></Swatch>
          <Swatch label="Met teller-pill"><SearchInput value="" onChange={() => {}} placeholder="Zoeken in e-mails…" count="128 ongelezen" /></Swatch>
          <Swatch label="Met wis-knop"><SearchInput value={q2} onChange={setQ2} onClear={() => setQ2('')} /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>SearchInput</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'string', desc: 'De zoektekst.' },
          { name: 'onChange', type: '(v) => void', desc: 'Houdt de zoektekst bij.' },
          { name: 'count', type: 'ReactNode', desc: 'Optionele teller-pill rechts (bv. "12 ongelezen").' },
          { name: 'onClear', type: '() => void', desc: 'Toont een wis-knop zodra er tekst staat.' },
          { name: 'placeholder / autoFocus', type: '', desc: 'Plaatshouder en autofocus.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Multi-select ----------------------------------------------------------
function MultiSelectShowcase() {
  const [team, setTeam] = React.useState(['Sanne de Wit', 'Rick van Dijk']);
  const people = ['Sanne de Wit', 'Rick van Dijk', 'Carolien Mulder', 'Emma Visser', 'Joost Bakker'];
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Meervoudige keuze</div>
        <p className="g-card-note">Kies meerdere waarden uit bestaande data; elke keuze wordt een chip. Chips en '+ Toevoegen' staan compact op één regel. Voor bv. dossierbehandelaars.</p>
        <MultiSelect value={team} onChange={setTeam} options={people} placeholder="Behandelaar toevoegen…" />
      </div>
      <div className="g-card">
        <div className="g-card-title">States</div>
        <div className="g-grid-states">
          <Swatch label="Leeg"><MultiSelect value={[]} onChange={() => {}} options={people} placeholder="Behandelaar…" /></Swatch>
          <Swatch label="Disabled"><MultiSelect value={['Sanne de Wit']} onChange={() => {}} options={people} disabled /></Swatch>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>MultiSelect</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'string[]', desc: 'Gekozen waarden (chips).' },
          { name: 'onChange', type: '(arr) => void', desc: 'Geeft de nieuwe array terug.' },
          { name: 'options', type: 'string[] | {value,label}[]', desc: 'De bestaande data om uit te kiezen.' },
          { name: 'getLabel', type: '(v) => string', desc: 'Optioneel zichtbaar label voor een waarde.' },
          { name: 'placeholder', type: 'string', desc: "Tekst van de '+ Toevoegen'-prompt." },
          { name: 'disabled / invalid', type: 'boolean', desc: 'States.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Bestand uploaden ------------------------------------------------------
function FileShowcase() {
  const [names, setNames] = React.useState([]);
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Dropzone</div>
        <p className="g-card-note">Sleep bestanden naar de zone of klik om te kiezen. Gebruikt voor uploads in het dossier.</p>
        <FileField onFiles={(f) => setNames(f.map((x) => x.name))} />
        {names.length > 0 && <p className="g-selected"><Icon name="check" size={15} />{names.length} bestand(en): <strong>{names.join(', ')}</strong></p>}
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>FileField</code> — props</div>
        <PropsTable rows={[
          { name: 'onFiles', type: '(File[]) => void', desc: 'Krijgt de gekozen/gesleepte bestanden.' },
          { name: 'accept', type: 'string', desc: 'MIME/extensie-filter (bv. ".pdf").' },
          { name: 'multiple', type: 'boolean', def: 'true', desc: 'Meerdere bestanden toestaan.' },
          { name: 'hint', type: 'string', desc: 'Tekst in de zone.' },
          { name: 'disabled', type: 'boolean', def: 'false', desc: 'Niet-interactief.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Bewerkbaar formulier (organisme) --------------------------------------
// Het centrale lees/bewerk-patroon: een recordkaart die standaard in leesmodus
// staat (ReadValue) en met één Bewerken-knop de héle datagroep omschakelt naar
// de bijbehorende DS-velden — gelijke 42px-footprint, dus zonder verspringen
// (draft → Opslaan).
const REL_TYPES = ['Klant', 'Prospect', 'Relatie'];
const RECHTSVORMEN = ['B.V.', 'Eenmanszaak', 'V.O.F.', 'Maatschap', 'Stichting'];
const RECORD_INIT = {
  type: 'Klant',
  rechtsvorm: 'B.V.',
  bedrijf: 'De Wit Bouw B.V.',
  kvk: '12345678',
  contact: 'Sanne de Wit',
  sector: 'Bouw & vastgoed',
  email: 'sanne@dewitbouw.nl',
  telefoon: '06 2143 8890',
  klantSinds: '2021-03-15',
  medewerkers: 24,
};

// Bewerken-knop rechts in de kaartkop; in bewerkmodus Opslaan + Annuleren.
// (Spiegelt HeadEdit uit aanvraag.jsx, dat hier niet geladen is.)
function HeadEditDemo({ editing, edit }) {
  return editing ? (
    <span className="head-edit">
      <ChipButton primary icon="check" iconSize={15} className="head-btn" onClick={edit.onSave}>Opslaan</ChipButton>
      <IconButton icon="x" iconSize={16} label="Annuleren" className="head-x" onClick={edit.onCancel} />
    </span>
  ) : (
    <span className="head-edit">
      <ChipButton icon="pencil" iconSize={14} className="head-btn" onClick={edit.onStart}>Bewerken</ChipButton>
    </span>
  );
}


function EditableFormShowcase() {
  const [data, setData] = React.useState(RECORD_INIT);
  const [draft, setDraft] = React.useState(RECORD_INIT);
  const [editing, setEditing] = React.useState(false);
  const [saved, setSaved] = React.useState(false);
  const savedTimer = React.useRef(null);
  React.useEffect(() => () => clearTimeout(savedTimer.current), []);

  const set = (k, v) => setDraft((d) => ({ ...d, [k]: v }));
  const start = () => { setDraft(data); setSaved(false); setEditing(true); };
  const cancel = () => setEditing(false);
  const save = () => {
    setData(draft); setEditing(false); setSaved(true);
    clearTimeout(savedTimer.current);
    savedTimer.current = setTimeout(() => setSaved(false), 2600);
  };
  const r = editing ? draft : data;
  const edit = { onStart: start, onSave: save, onCancel: cancel };

  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Recordkaart — lezen &amp; bewerken</div>
        <p className="g-card-note">Elk veld is in beide modi <strong>hetzelfde DS-veld</strong>. In <strong>leesmodus</strong> staan de velden op alleen-lezen (<code>disabled</code>), schoon getoond als leeswaarde; <code>Bewerken</code> in de kaartkop maakt ze invulbaar. Omdat het exact hetzelfde input/select-element blijft, <strong>beweegt of verspringt er niets</strong> — in geen enkele browser. <code>Opslaan</code> legt de wijzigingen vast, <code>Annuleren</code> verwerpt de concept-versie.</p>
        <div className="g-record">
          <section className="d-card">
            <header className="d-card-head">
              <Icon name="building-2" size={17} />
              <h3>Bedrijfsgegevens</h3>
              <HeadEditDemo editing={editing} edit={edit} />
            </header>
            <FormGrid>
              <FormField label="Type relatie">
                <Select options={REL_TYPES} value={r.type} onChange={(v) => set('type', v)} disabled={!editing} />
              </FormField>
              <FormField label="Rechtsvorm">
                <Select options={RECHTSVORMEN} value={r.rechtsvorm} onChange={(v) => set('rechtsvorm', v)} disabled={!editing} />
              </FormField>
              <FormField label="Bedrijfsnaam">
                <TextInput value={r.bedrijf} onChange={(v) => set('bedrijf', v)} disabled={!editing} />
              </FormField>
              <FormField label="KvK-nummer">
                <TextInput value={r.kvk} onChange={(v) => set('kvk', v)} disabled={!editing} />
              </FormField>
              <FormField label="Contactpersoon">
                <TextInput value={r.contact} onChange={(v) => set('contact', v)} disabled={!editing} />
              </FormField>
              <FormField label="Sector">
                <TextInput value={r.sector} onChange={(v) => set('sector', v)} disabled={!editing} />
              </FormField>
              <FormField label="E-mailadres">
                <TextInput type="email" value={r.email} onChange={(v) => set('email', v)} disabled={!editing} />
              </FormField>
              <FormField label="Telefoon">
                <TextInput type="tel" value={r.telefoon} onChange={(v) => set('telefoon', v)} disabled={!editing} />
              </FormField>
              <FormField label="Klant sinds">
                <DateField value={r.klantSinds} onChange={(v) => set('klantSinds', v)} disabled={!editing} />
              </FormField>
              <FormField label="Medewerkers">
                <NumberField value={r.medewerkers} onChange={(v) => set('medewerkers', v)} disabled={!editing} />
              </FormField>
            </FormGrid>
          </section>
          {saved && <p className="g-selected"><Icon name="check-circle" size={16} />Wijzigingen opgeslagen.</p>}
        </div>
      </div>

      <div className="g-card">
        <div className="g-card-title">Anatomie</div>
        <ul className="g-anatomy">
          <li><span className="g-dot" />Eén <code>Bewerken</code>-knop per kaart (rechts in de kop) schakelt de héle datagroep tegelijk. In bewerkmodus wordt die knop <code>Opslaan</code> + <code>Annuleren</code>.</li>
          <li><span className="g-dot" />Bewerken werkt op een <strong>concept-kopie</strong> (draft): pas bij <code>Opslaan</code> verandert het record; <code>Annuleren</code> gooit de concept weg.</li>
          <li><span className="g-dot" />Hetzelfde veld-element in beide modi (lezen = <code>disabled</code>, bewerken = invulbaar). Geen wissel van <code>span</code> naar <code>input</code>, dus de tekst kán niet verspringen — 0px, font- en browser-onafhankelijk.</li>
          <li><span className="g-dot" />Samengesteld uit moleculen (<code>FormField</code> = label + DS-veld) en atomen — een organisme dus, geen losse component.</li>
        </ul>
      </div>

      <div className="g-card">
        <div className="g-card-title">Bouwstenen</div>
        <PropsTable rows={[
          { name: 'Lezen', type: 'DS-veld + disabled', desc: 'Hetzelfde veld op alleen-lezen, schoon getoond als waarde (geen rand, volle inkt).' },
          { name: 'Bewerken', type: 'DS-veld (enabled)', desc: 'Exact hetzelfde input/select-element, nu invulbaar — dus 0px beweging t.o.v. lezen.' },
          { name: 'Kop-acties', type: 'Bewerken → Opslaan + Annuleren', desc: 'Eén schakelaar per datagroep; werkt op een concept tot Opslaan.' },
          { name: 'Raster', type: 'FormGrid + FormField', desc: 'Label boven veld, twee kolommen — de DS-formuliermoleculen.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Knoppen (atoom) -------------------------------------------------------
function ButtonShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Pill-knoppen <span className="g-muted">(Button) — per intentie</span></div>
        <p className="g-card-note">Elke variant draagt een <strong>betekenis</strong>, niet alleen een kleur. Vuistregel: per scherm één positieve hoofd-CTA, een neutrale (ghost) actie ernaast, en destructieve acties altijd met een bevestiging.</p>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(232px, 1fr))', gap: '20px 22px' }}>
          {[
            { btn: <Button variant="primary" icon="plus">Nieuwe aanvraag</Button>, role: 'Positieve hoofdactie · CTA', use: 'De gewenste hoofdactie: opslaan, toevoegen, doorgaan. Eén per scherm.' },
            { btn: <Button variant="approve" icon="check" iconSize={16}>Goedkeuren</Button>, role: 'Bevestigend · akkoord', use: 'Een expliciet positief besluit — goedkeuren, akkoord geven (groen).' },
            { btn: <Button variant="ghost">Annuleren</Button>, role: 'Neutraal · secundair', use: 'Secundaire of annuleer-actie, naast de hoofd-CTA.' },
            { btn: <Button variant="outline-danger" icon="bell-off" iconSize={16}>Niet versturen</Button>, role: 'Negatief · terughoudend', use: 'Afwijzende of afremmende actie zonder nadruk (rode rand).' },
            { btn: <Button variant="danger" icon="trash-2" iconSize={16}>Verwijderen</Button>, role: 'Destructief · onomkeerbaar', use: 'Definitief verwijderen of vernietigen — altijd met bevestiging.' },
          ].map((v) => (
            <div key={v.role} style={{ display: 'flex', flexDirection: 'column', gap: 9, alignItems: 'flex-start' }}>
              {v.btn}
              <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                <b style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-900)' }}>{v.role}</b>
                <span style={{ fontSize: 12.5, color: 'var(--ink-500)', lineHeight: 1.5 }}>{v.use}</span>
              </div>
            </div>
          ))}
        </div>
        <div className="g-btn-row" style={{ marginTop: 18 }}>
          <Button variant="primary" size="sm" icon="plus">Compact (sm)</Button>
          <Button variant="ghost" size="sm">Compact (sm)</Button>
          <span className="g-muted" style={{ alignSelf: 'center', fontSize: 12.5 }}>— <code>size="sm"</code>: dezelfde intenties, compacter voor toolbars/drawers.</span>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Chip-knoppen <span className="g-muted">(ChipButton)</span></div>
        <p className="g-card-note">Compacte knop met rand voor toolbars en kaartkoppen. <code>primary</code> maakt 'm accent-gekleurd (bijv. Opslaan in bewerkmodus).</p>
        <div className="g-btn-row">
          <ChipButton icon="sliders-horizontal">Filter</ChipButton>
          <ChipButton icon="arrow-up-down">Sorteren</ChipButton>
          <ChipButton icon="pencil" iconSize={14}>Bewerken</ChipButton>
          <ChipButton primary icon="check" iconSize={15}>Opslaan</ChipButton>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Icoonknoppen <span className="g-muted">(IconButton)</span></div>
        <p className="g-card-note">Vierkante knop met enkel een icoon — voor sluiten, meer-menu's en compacte acties. <code>size="sm"</code> voor de kleine variant. Geef altijd een <code>label</code> mee.</p>
        <div className="g-btn-row">
          <IconButton icon="bell" label="Meldingen" />
          <IconButton icon="more-horizontal" label="Meer acties" />
          <IconButton icon="x" size="sm" label="Sluiten" />
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Props</div>
        <PropsTable rows={[
          { name: 'Button', type: 'variant · size · icon · trailingIcon', desc: 'variant: primary (positieve CTA) | approve (akkoord) | ghost (neutraal/secundair) | outline-danger (negatief, terughoudend) | danger (destructief). size="sm" voor compact.' },
          { name: 'ChipButton', type: 'primary · active · icon · trailingIcon', desc: "Knop met rand voor toolbars en koppen; primary maakt 'm accent-gekleurd." },
          { name: 'IconButton', type: 'icon · size · label', desc: 'Vierkante icoonknop; size="sm" voor klein. label wordt aria-label + title.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Badges & status (atoom) -----------------------------------------------
function BadgeShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Status-badges <span className="g-muted">(Badge)</span></div>
        <p className="g-card-note">Pillvormig statuslabel met een gekleurde stip. Vijf tonen sturen de betekenis: info (neutraal/actief), success (akkoord), warning (aandacht), danger (probleem), neutral (inactief).</p>
        <div className="g-btn-row">
          <Badge tone="info">In behandeling</Badge>
          <Badge tone="success">Goedgekeurd</Badge>
          <Badge tone="warning">Actie nodig</Badge>
          <Badge tone="danger">Afgekeurd</Badge>
          <Badge tone="neutral">Niet van toepassing</Badge>
        </div>
        <div className="g-btn-row" style={{ marginTop: 12 }}>
          <Badge tone="info" icon="sparkles">Automatisch herkend</Badge>
          <Badge tone="warning" icon="alert-circle">Controleer indeling</Badge>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Stip-markering <span className="g-muted">(StatusDot)</span></div>
        <p className="g-card-note">Losse gekleurde stip zonder label — voor compacte status in menu's en lijsten (bijv. de statuskiezer).</p>
        <div className="g-btn-row" style={{ gap: 20 }}>
          <span className="g-dot-lbl"><StatusDot tone="info" />Info</span>
          <span className="g-dot-lbl"><StatusDot tone="success" />Success</span>
          <span className="g-dot-lbl"><StatusDot tone="warning" />Warning</span>
          <span className="g-dot-lbl"><StatusDot tone="neutral" />Neutral</span>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Props</div>
        <PropsTable rows={[
          { name: 'Badge', type: 'tone · icon', desc: 'tone: info | success | warning | danger | neutral — bepaalt kleur én betekenis. icon plaatst een Lucide-icoon voorop.' },
          { name: 'StatusDot', type: 'tone', desc: 'Losse stip (info/success/warning/danger/neutral) zonder label.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Avatar & IconBubble (atoom) -------------------------------------------
function AvatarShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Avatar</div>
        <p className="g-card-note">Toont een persoon (rond, met initialen) of een organisatie/record (afgeronde vierkant, met icoon). De kleur leidt automatisch af van de naam, of geef een eigen <code>color</code> / <code>photo</code> mee.</p>
        <div className="g-btn-row" style={{ gap: 20 }}>
          <Avatar name="Sanne de Wit" size={44} />
          <Avatar name="Rick van Dijk" size={44} />
          <Avatar name="De Wit Bouw B.V." kind="org" size={44} />
          <Avatar name="Aanvraag" kind="record" icon="file-text" size={44} />
          <Avatar name="Taxateur" kind="person" icon="ruler" size={44} color="var(--primary-500)" />
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Maten</div>
        <div className="g-btn-row" style={{ gap: 16, alignItems: 'flex-end' }}>
          <Avatar name="Sanne de Wit" size={24} />
          <Avatar name="Sanne de Wit" size={32} />
          <Avatar name="Sanne de Wit" size={44} />
          <Avatar name="Sanne de Wit" size={56} />
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">IconBubble</div>
        <p className="g-card-note">Ronde (of afgeronde) chip met één icoon — voor tijdlijn-stippen, lege-staat-iconen en dialoogkoppen. Kleur via <code>bg</code> / <code>color</code>, of een <code>className</code>.</p>
        <div className="g-btn-row" style={{ gap: 18 }}>
          <IconBubble icon="mail" size={40} bg="var(--primary-050)" color="var(--primary-600)" />
          <IconBubble icon="sparkles" size={40} bg="var(--primary-050)" color="var(--primary-600)" />
          <IconBubble icon="upload-cloud" size={40} radius={12} bg="var(--accent-050)" color="var(--accent-600)" iconSize={20} />
          <IconBubble icon="check" size={40} bg="var(--success-050)" color="var(--success)" />
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Props</div>
        <PropsTable rows={[
          { name: 'Avatar', type: '', desc: 'name · kind: person | org | record · size · color? · icon? · photo?' },
          { name: 'IconBubble', type: '', desc: 'icon · size · bg? · color? · radius? · iconSize? · className?' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Kaartkop (molecuul) ---------------------------------------------------
function CardHeadShowcase() {
  const [editing, setEditing] = React.useState(false);
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Kaartkop <span className="g-muted">(Card + CardHead)</span></div>
        <p className="g-card-note">Elke detailkaart begint met dezelfde kop: een primair-gekleurd icoon, een titel en — optioneel — acties rechts. De kop houdt dezelfde hoogte met of zonder acties.</p>
        <div className="g-record">
          <Card>
            <CardHead icon="info" title="Kerngegevens" />
            <div className="g-card-fill">Kaartinhoud…</div>
          </Card>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Met acties rechts</div>
        <p className="g-card-note">Acties staan rechts in de kop (als children) — een chip-knop, een badge, of de Bewerken→Opslaan/Annuleren-schakelaar uit het bewerkbare formulier.</p>
        <div className="g-record">
          <Card>
            <CardHead icon="building-2" title="Bedrijfsgegevens">
              <HeadEditDemo editing={editing} edit={{ onStart: () => setEditing(true), onSave: () => setEditing(false), onCancel: () => setEditing(false) }} />
            </CardHead>
            <div className="g-card-fill">{editing ? 'In bewerkmodus…' : 'In leesmodus…'}</div>
          </Card>
          <Card className="g-card-spaced">
            <CardHead icon="mail" title="E-mails">
              <span style={{ marginLeft: 'auto' }}><Badge tone="info">3 nieuw</Badge></span>
            </CardHead>
            <div className="g-card-fill">Kaartinhoud…</div>
          </Card>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Anatomie</div>
        <ul className="g-anatomy">
          <li><span className="g-dot" />Vaste opbouw: <code>icon</code> (primair) + <code>title</code> + optionele acties (children), met een hairline onderrand.</li>
          <li><span className="g-dot" />Acties staan rechts (<code>margin-left:auto</code>) — een chip-knop, badge of de Bewerken-schakelaar.</li>
        </ul>
      </div>
    </React.Fragment>
  );
}

// ---- Dropdownmenu (molecuul) -----------------------------------------------
function MenuShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Dropdownmenu <span className="g-muted">(Menu + MenuItem)</span></div>
        <p className="g-card-note">Een actiemenu dat onder de trigger opent. Een onzichtbare backdrop over het hele scherm vangt de volgende klik en sluit het menu. <code>Menu</code> beheert de open-staat zelf; geef je trigger mee als <code>trigger</code>-prop en de items als children. <code>danger</code> kleurt een regel rood.</p>
        <div className="g-menu-host">
          <Menu align="left" trigger={<ChipButton icon="more-horizontal">Acties</ChipButton>}>
            {(close) => (
              <React.Fragment>
                <MenuItem icon="pencil" onClick={close}>Bewerken</MenuItem>
                <MenuItem icon="send" onClick={close}>Opvragen bij klant</MenuItem>
                <MenuItem icon="sticky-note" onClick={close}>Notitie toevoegen</MenuItem>
                <MenuItem icon="trash-2" danger onClick={close}>Verwijderen</MenuItem>
              </React.Fragment>
            )}
          </Menu>
        </div>
        <p className="g-card-note" style={{ marginTop: 14, marginBottom: 0 }}>Klik op <strong>Acties</strong> om te openen; klik ernaast om te sluiten.</p>
      </div>
      <div className="g-card">
        <div className="g-card-title">Props</div>
        <PropsTable rows={[
          { name: 'Menu', type: 'trigger · align · children', desc: 'trigger = je knop-element (onClick wordt aangevuld om te openen). children mag (close)=>… zijn. align: right | left. Backdrop sluit bij buitenklik.' },
          { name: 'MenuItem', type: 'icon · danger · onClick', desc: 'Regel met icoon + label. danger voor een destructieve actie (rood).' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Tabel (organisme) -----------------------------------------------------
const G_TBL_COLS = [
  { key: 'naam', label: 'Aanvraag', type: 'primary', w: '1.7fr' },
  { key: 'status', label: 'Status', w: '1fr' },
  { key: 'behandelaar', label: 'Behandelaar', w: '1.1fr' },
  { key: 'bedrag', label: 'Bedrag', type: 'money', w: '1fr' },
];
const G_TBL_ROWS = [
  { naam: 'Hypotheek bedrijfspand', ref: 'AANVR-2041', status: 'In behandeling', tone: 'info', behandelaar: 'Sanne de Wit', bedrag: 480000 },
  { naam: 'Herfinanciering machinepark', ref: 'AANVR-2038', status: 'Goedgekeurd', tone: 'success', behandelaar: 'Rick van Dijk', bedrag: 215000 },
  { naam: 'Werkkapitaal seizoen', ref: 'AANVR-2035', status: 'Actie nodig', tone: 'warning', behandelaar: 'Carolien Mulder', bedrag: 90000 },
  { naam: 'Aankoop investeringspand', ref: 'AANVR-2030', status: 'Afgekeurd', tone: 'danger', behandelaar: 'Emma Visser', bedrag: 1250000 },
];
const gEuro = (n) => '€ ' + Number(n || 0).toLocaleString('nl-NL');
function TableShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Datatabel</div>
        <p className="g-card-note">De centrale lijstweergave — één tabel-UI voor elke lijstpagina. <code>columns</code> legt label, breedte en celtype (<code>money</code>/<code>number</code>/<code>date</code>) vast; dat type stuurt de uitlijning en opmaak. <code>renderCell</code> levert de celinhoud (avatar-cel, badge, twee-regelige primaire cel). Rijen zijn klikbaar via <code>onRowClick</code>.</p>
        <Table
          minWidth={0}
          columns={G_TBL_COLS}
          rows={G_TBL_ROWS}
          onRowClick={() => {}}
          renderCell={(c, row) => {
            if (c.type === 'primary') return <span className="cell-stack"><b>{row.naam}</b><span>{row.ref}</span></span>;
            if (c.key === 'status') return <Badge tone={row.tone}>{row.status}</Badge>;
            if (c.type === 'money') return gEuro(row.bedrag);
            return row[c.key];
          }}
        />
      </div>
      <div className="g-card">
        <div className="g-card-title">Anatomie</div>
        <ul className="g-anatomy">
          <li><span className="g-dot" />CSS-grid met <code>subgrid</code>: kop en rijen delen exact dezelfde kolommen, dus alles lijnt uit ongeacht de celinhoud.</li>
          <li><span className="g-dot" />Het celtype bepaalt de uitlijning: <code>money</code>/<code>number</code> rechts met tabular-cijfers, <code>date</code> in een gedempte kleur.</li>
          <li><span className="g-dot" />Standaard min-breedte van 880px (horizontaal scrollen op de volle lijstpagina); <code>minWidth={0}</code> laat 'm meekrimpen in smalle contexten (drawer, ingebedde subtabellen).</li>
        </ul>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>Table</code> — props</div>
        <PropsTable rows={[
          { name: 'columns', type: '{key,label,w?,type?}[]', desc: 'Kolomdefinitie. type: money | number | date stuurt uitlijning/opmaak.' },
          { name: 'rows', type: 'object[]', desc: 'De rijdata.' },
          { name: 'renderCell', type: '(col, row) => node', desc: 'Levert de celinhoud. Anders col.render(row) of row[col.key].' },
          { name: 'onRowClick', type: '(row, i) => void', desc: 'Maakt rijen klikbaar (hover-highlight).' },
          { name: 'rowKey', type: '(row, i) => key', desc: 'Optionele sleutel per rij (anders index).' },
          { name: 'minWidth', type: 'number | string', desc: 'Overschrijft de min-breedte (0 = meekrimpen).' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Slide-over drawer (organisme) -----------------------------------------
function DrawerShowcase() {
  const [open, setOpen] = React.useState(false);
  const head = (
    <React.Fragment>
      <div className="drawer-tools">
        <IconButton size="sm" icon="pencil" iconSize={17} label="Bewerken" />
        <IconButton size="sm" icon="x" iconSize={18} label="Sluiten" onClick={() => setOpen(false)} />
      </div>
      <div className="drawer-id">
        <Avatar name="Sanne de Wit" size={48} />
        <div className="drawer-id-text">
          <div className="drawer-title-row"><h2>Sanne de Wit</h2><Badge tone="info">Klant</Badge></div>
          <div className="drawer-meta"><span className="drawer-sub">Eigenaar · De Wit Bouw B.V.</span></div>
        </div>
      </div>
    </React.Fragment>
  );
  const foot = (
    <React.Fragment>
      <Button variant="ghost" icon="x" onClick={() => setOpen(false)}>Annuleren</Button>
      <Button variant="primary" icon="check" onClick={() => setOpen(false)}>Opslaan</Button>
    </React.Fragment>
  );
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Detail-drawer</div>
        <p className="g-card-note">Een slide-over paneel dat van rechts inschuift over een schermvullende scrim — voor het openen van een record naast de lijst. <code>head</code> en <code>foot</code> zijn vrije slots (kop met titel/acties, voet met knoppen); <code>children</code> vullen de scrollende body. <code>lockClose</code> negeert de buitenklik (bv. tijdens bewerken).</p>
        <div className="g-menu-host">
          <Button variant="ghost" icon="panel-right-open" onClick={() => setOpen(true)}>Open voorbeeld-drawer</Button>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>Drawer</code> — props</div>
        <PropsTable rows={[
          { name: 'onClose', type: '() => void', desc: 'Sluit bij klik op de scrim of de sluitknop.' },
          { name: 'lockClose', type: 'boolean', def: 'false', desc: 'Negeert de buitenklik (bv. tijdens bewerken).' },
          { name: 'head', type: 'ReactNode', desc: 'Inhoud van de vaste kop (titel, acties).' },
          { name: 'foot', type: 'ReactNode', desc: 'Inhoud van de vaste voet (knoppen). Weglaten = geen voet.' },
          { name: 'children', type: 'ReactNode', desc: 'De scrollende body.' },
          { name: 'width', type: 'string', def: "'460px'", desc: 'Overschrijft de paneelbreedte.' },
        ]} />
      </div>
      {open && (
        <Drawer onClose={() => setOpen(false)} head={head} foot={foot} ariaLabel="Voorbeeld">
          <Card><CardHead icon="building-2" title="Bedrijfsgegevens" /><div className="g-card-fill">Kaartinhoud — hier staan normaal de recordvelden.</div></Card>
          <Card><CardHead icon="folder" title="Dossier" /><div className="g-card-fill">Nog een kaart in de body.</div></Card>
        </Drawer>
      )}
    </React.Fragment>
  );
}

// ---- Tabbalk (organisme) ---------------------------------------------------
function TabsShowcase() {
  const [tab, setTab] = React.useState('overzicht');
  const TABS = [
    { id: 'overzicht', icon: 'layout-dashboard', label: 'Overzicht' },
    { id: 'dossier', icon: 'folder', label: 'Dossier' },
    { id: 'relaties', icon: 'git-fork', label: 'Relaties' },
    { id: 'emails', icon: 'mail', label: 'E-mails' },
  ];
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Tabbalk</div>
        <p className="g-card-note">De onderstreepte tabbalk van de detailpagina's. <code>Tabs</code> houdt de actieve waarde bij; het gekozen <code>Tab</code> kleurt in de primaire kleur met een onderstreping. Elk tab toont een icoon + label; children vullen een extra slot rechts (bv. een voortgangsdonut of badge).</p>
        <Tabs value={tab} onChange={setTab}>
          {TABS.map((t) => <Tab key={t.id} id={t.id} icon={t.icon} label={t.label} />)}
        </Tabs>
        <div className="g-card-fill" style={{ marginTop: 4 }}>Actief tabblad: <strong>{TABS.find((t) => t.id === tab).label}</strong></div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>Tabs</code> / <code>Tab</code> — props</div>
        <PropsTable rows={[
          { name: 'Tabs · value', type: 'string', desc: 'De id van het actieve tab.' },
          { name: 'Tabs · onChange', type: '(id) => void', desc: 'Aangeroepen met de gekozen tab-id.' },
          { name: 'Tab · id', type: 'string', desc: 'Identificeert het tab (gematcht tegen value).' },
          { name: 'Tab · icon / label', type: 'string', desc: 'Het Lucide-icoon en het label.' },
          { name: 'Tab · children', type: 'ReactNode', desc: 'Extra slot rechts in het label (donut/badge).' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Modaal / bevestiging (organisme) --------------------------------------
function ModalShowcase() {
  const [confirm, setConfirm] = React.useState(false);
  const [plain, setPlain] = React.useState(false);
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Bevestigingsdialoog <span className="g-muted">(ConfirmDialog)</span></div>
        <p className="g-card-note"><code>ConfirmDialog</code> is de kant-en-klare bevestiging: een icoonbubbel (gekleurd via <code>iconTone</code>), titel, uitleg en een actie-voet. Gebruikt o.a. bij het verwijderen van een record.</p>
        <div className="g-menu-host">
          <Button variant="outline-danger" icon="trash-2" iconSize={16} onClick={() => setConfirm(true)}>Verwijderen…</Button>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Vrije dialoog <span className="g-muted">(Modal)</span></div>
        <p className="g-card-note"><code>Modal</code> is de kale gecentreerde dialoog-shell (scrim + witte box). Vul 'm met eigen inhoud — een formulier, een besluit met radio-opties, een voorbeeld. Geef <code>className</code> mee voor een afwijkende breedte/uitlijning (zoals de besluit-modal van de kredietanalyse).</p>
        <div className="g-menu-host">
          <Button variant="ghost" icon="square-pen" onClick={() => setPlain(true)}>Open dialoog</Button>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Props</div>
        <PropsTable rows={[
          { name: 'Modal', type: 'onClose · lockClose · className · width', desc: 'Scrim + gecentreerde box; children vullen de inhoud.' },
          { name: 'ConfirmDialog · icon', type: 'string', desc: 'Icoon in de bubbel bovenaan.' },
          { name: 'ConfirmDialog · iconTone', type: 'danger | info | success | warning', def: 'danger', desc: 'Kleur van de icoonbubbel.' },
          { name: 'ConfirmDialog · title', type: 'string', desc: 'De koptitel.' },
          { name: 'ConfirmDialog · actions', type: 'ReactNode', desc: 'Knoppen in de actie-voet.' },
        ]} />
      </div>
      {confirm && (
        <ConfirmDialog
          icon="trash-2"
          title="Relatie verwijderen?"
          onClose={() => setConfirm(false)}
          actions={
            <React.Fragment>
              <Button variant="ghost" onClick={() => setConfirm(false)}>Annuleren</Button>
              <Button variant="danger" icon="trash-2" iconSize={16} onClick={() => setConfirm(false)}>Verwijderen</Button>
            </React.Fragment>
          }
        >
          <p><b>De Wit Bouw B.V.</b> wordt definitief verwijderd. Deze actie kan niet ongedaan worden gemaakt.</p>
        </ConfirmDialog>
      )}
      {plain && (
        <Modal onClose={() => setPlain(false)} ariaLabel="Voorbeeld-dialoog">
          <h3>Voorbeeld-dialoog</h3>
          <p>Een vrije <code>Modal</code> kan elke inhoud bevatten — hier alleen wat tekst en een afsluitknop.</p>
          <div className="confirm-actions">
            <Button variant="primary" onClick={() => setPlain(false)}>Sluiten</Button>
          </div>
        </Modal>
      )}
    </React.Fragment>
  );
}

// ---- Lege staat (organisme) ------------------------------------------------
function EmptyStateShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Lege staat</div>
        <p className="g-card-note">Voor lege tabbladen, lege lijsten en nog niet gekoppelde records: een primair-gekleurde icoonbubbel, een titel en een korte uitleg. Optioneel een <code>action</code> (bv. een knop om iets aan te maken).</p>
        <div className="g-record">
          <Card>
            <EmptyState icon="mail" title="Geen e-mails">Deze relatie is nog niet betrokken bij e-mailcorrespondentie in een dossier.</EmptyState>
          </Card>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Met actie</div>
        <div className="g-record">
          <Card>
            <EmptyState icon="folder" title="Nog geen documenten" action={<Button variant="primary" size="sm" icon="upload">Document toevoegen</Button>}>
              Sleep bestanden hierheen of voeg het eerste document toe aan dit dossier.
            </EmptyState>
          </Card>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>EmptyState</code> — props</div>
        <PropsTable rows={[
          { name: 'icon', type: 'string', desc: 'Lucide-icoon in de primaire bubbel.' },
          { name: 'title', type: 'string', desc: 'De koptekst.' },
          { name: 'children', type: 'ReactNode', desc: 'De uitleg-tekst eronder.' },
          { name: 'action', type: 'ReactNode', desc: 'Optionele actie (bv. een knop) onder de tekst.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- KPI's (organisme) -----------------------------------------------------
function KpiShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">KPI-tegel <span className="g-muted">(Kpi)</span></div>
        <p className="g-card-note">De kerncijfer-tegel van de detailtabs: een uppercase label, een groot cijfer en een subregel. <code>tone</code> (success/warning/danger) kleurt het cijfer betekenisvol; <code>bar</code> (0–100) tekent een voortgangsbalk eronder. Plaats ze in een <code>KpiGrid</code>.</p>
        <KpiGrid>
          <Kpi label="Hoofdsom" value="€ 480.000" sub="Marktwaarde € 690.000" />
          <Kpi label="LTV" value="70%" tone="success" sub="Hypotheek bedrijfspand" />
          <Kpi label="DSCR" value="1,18" tone="warning" sub="ICR 2,4" />
          <Kpi label="Gefund" value="65%" sub="Clubdeal · € 312.000" bar={65} />
          <Kpi label="Afsluitprovisie" value="€ 9.600" sub="2,0% · + € 2.400 beheer p.j." />
          <Kpi label="Doorlooptijd" value="23 dagen" sub="Ingediend 14-05-2026" />
          <Kpi wide label="Dossier" value="78%" tone="warning" bar={78}
            sub="14 van 18 documenten aanwezig · 11 goedgekeurd" />
        </KpiGrid>
      </div>
      <div className="g-card">
        <div className="g-card-title">Compacte strook <span className="g-muted">(variant="compact")</span></div>
        <p className="g-card-note">Dezelfde tegel in een smallere strook — staat boven de detailtabs (kredietanalyse, rollen).</p>
        <KpiGrid variant="compact">
          <Kpi label="Gegevenscontrole" value="12 / 18" tone="warning" bar={67} sub="6 velden in beoordeling" />
          <Kpi label="Normentoets" value="3 · 2 · 1" sub="op gecontroleerde gegevens" />
          <Kpi label="Eindoordeel" value="Voorwaarden" tone="warning" sub="nog geen besluit vastgelegd" />
        </KpiGrid>
      </div>
      <div className="g-card">
        <div className="g-card-title">Dashboard-tegel <span className="g-muted">(StatTile)</span></div>
        <p className="g-card-note"><code>StatTile</code> is de dashboard-variant: het label staat naast een primaire IconBubble, daaronder het cijfer óf een badge (<code>badge</code> + <code>tone</code>). Plaats in een <code>KpiGrid variant="dashboard"</code> (auto-fit).</p>
        <KpiGrid variant="dashboard">
          <StatTile label="Relatie" value="Klant" badge tone="success" />
          <StatTile label="Rollen" value="2" icon="shield" sub="Geldnemer, Investeerder" />
          <StatTile label="Belegd vermogen" value="€ 1,2M" icon="trending-up" />
          <StatTile label="Laatste contact" value="3 dagen geleden" icon="clock" />
        </KpiGrid>
      </div>
      <div className="g-card">
        <div className="g-card-title">Props</div>
        <PropsTable rows={[
          { name: 'KpiGrid · variant', type: 'default | compact | dashboard', def: 'default', desc: 'Kiest het grid: 3-koloms, smalle strook of auto-fit dashboard.' },
          { name: 'Kpi · label / value', type: 'string / node', desc: 'Het label en het cijfer (value mag een node zijn, bv. met InfoTip).' },
          { name: 'Kpi · tone', type: 'success | warning | danger', desc: 'Kleurt het cijfer betekenisvol.' },
          { name: 'Kpi · bar', type: 'number (0–100)', desc: 'Tekent een voortgangsbalk onder het cijfer.' },
          { name: 'Kpi · wide', type: 'boolean', def: 'false', desc: 'Laat de tegel de volle gridbreedte vullen.' },
          { name: 'StatTile · icon', type: 'string', desc: 'Lucide-icoon in de primaire bubbel naast het label.' },
          { name: 'StatTile · badge', type: 'boolean', desc: 'Toont de waarde als badge i.p.v. groot cijfer (met tone).' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Typografie (fundament) ------------------------------------------------
const TYPE_SCALE = [
  { token: '--fs-h1', px: '64px', font: 'display', weight: 700, sample: 'Componenten, één bron', note: 'Grootste kop' },
  { token: '--fs-h2', px: '40px', font: 'display', weight: 700, sample: 'Sectiekop', note: 'Sectiekoppen' },
  { token: '--fs-h3', px: '28px', font: 'display', weight: 600, sample: 'Subkop / paginatitel', note: 'Sub- & paginatitels' },
  { token: '--fs-h4', px: '20px', font: 'display', weight: 600, sample: 'Kaarttitel', note: 'Kaart- & kleine koppen' },
  { token: '--fs-lead', px: '20px', font: 'body', weight: 400, sample: 'Een wat grotere introzin die de lezer op weg helpt.', note: 'Lead / intro' },
  { token: '--fs-body', px: '17px', font: 'body', weight: 400, sample: 'Standaard bodytekst voor lopende alinea’s en UI-teksten.', note: 'Body (standaard)' },
  { token: '--fs-small', px: '15px', font: 'body', weight: 500, sample: 'Secundaire tekst en bijschriften.', note: 'Secundair' },
  { token: '--fs-caption', px: '13px', font: 'body', weight: 500, sample: 'Bijschriften, juridische tekst en metadata.', note: 'Caption' },
];
const WEIGHTS = [
  { w: 400, label: 'Regular' }, { w: 500, label: 'Medium' },
  { w: 600, label: 'SemiBold' }, { w: 700, label: 'Bold' },
];
function TypographyShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Lettertypes</div>
        <p className="g-card-note">Eén familie draagt de hele interface: <strong>Inter</strong>. Koppen, knoppen en labels via <code>var(--font-display)</code>; lopende tekst en UI via <code>var(--font-body)</code>. Gebruik altijd de variabele, nooit een letternaam direct.</p>
        <div className="g-font-pair">
          <div className="g-font-card">
            <div className="g-font-big" style={{ fontFamily: 'var(--font-display)', fontWeight: 700 }}>Aa</div>
            <div className="g-font-meta">
              <b>Inter · Display</b>
              <span className="g-type">var(--font-display)</span>
              <span className="g-muted">Koppen · knoppen · labels · cijfers</span>
            </div>
            <div className="g-font-glyphs" style={{ fontFamily: 'var(--font-display)' }}>ABCDEFGHIJKLM<br />abcdefghijklm 0123456789</div>
          </div>
          <div className="g-font-card">
            <div className="g-font-big" style={{ fontFamily: 'var(--font-body)', fontWeight: 700 }}>Aa</div>
            <div className="g-font-meta">
              <b>Inter · Body</b>
              <span className="g-type">var(--font-body)</span>
              <span className="g-muted">Bodytekst · UI · bijschriften</span>
            </div>
            <div className="g-font-glyphs" style={{ fontFamily: 'var(--font-body)' }}>ABCDEFGHIJKLM<br />abcdefghijklm 0123456789</div>
          </div>
        </div>
      </div>

      <div className="g-card">
        <div className="g-card-title">Type-schaal</div>
        <p className="g-card-note">Eén schaal voor de hele app. Elke stap heeft een token (<code>--fs-*</code>) — gebruik die in plaats van losse pixelwaarden, zodat tekst overal consistent en schaalbaar blijft.</p>
        <div className="g-typescale">
          {TYPE_SCALE.map((t) => (
            <div className="g-type-row" key={t.token}>
              <div className="g-type-sample" style={{ fontSize: `var(${t.token})`, fontFamily: `var(--font-${t.font})`, fontWeight: t.weight }}>{t.sample}</div>
              <div className="g-type-spec">
                <span className="g-type">{t.token}</span>
                <span className="g-muted">{t.px} · {t.note}</span>
              </div>
            </div>
          ))}
        </div>
      </div>

      <div className="g-card">
        <div className="g-card-title">Eyebrow <span className="g-muted">(kicker)</span></div>
        <p className="g-card-note">Klein, hoofdletters, ruim gespatieerd — als opstap boven een kop. Via <code>var(--font-display)</code>, <code>--fs-eyebrow</code> (14px), <code>letter-spacing: var(--tracking-eyebrow)</code>, in de primaire kleur.</p>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 'var(--fs-eyebrow)', fontWeight: 600, letterSpacing: 'var(--tracking-eyebrow)', textTransform: 'uppercase', color: 'var(--primary-600)' }}>David Analytics</div>
      </div>

      <div className="g-card">
        <div className="g-card-title">Gewichten</div>
        <p className="g-card-note">Inter draagt vier gewichten. Body draait standaard op Regular/Medium; koppen en labels op SemiBold/Bold.</p>
        <div className="g-weights">
          {WEIGHTS.map((g) => (
            <div className="g-weight-row" key={g.w}>
              <span className="g-muted" style={{ width: 92 }}>{g.label} {g.w}</span>
              <span style={{ fontFamily: 'var(--font-display)', fontWeight: g.w, fontSize: 22 }}>Inter</span>
              <span style={{ fontFamily: 'var(--font-body)', fontWeight: g.w, fontSize: 22 }}>Inter</span>
            </div>
          ))}
        </div>
      </div>
    </React.Fragment>
  );
}

// ---- Kleur (fundament) -----------------------------------------------------
const COLOR_GROUPS = [
  { title: 'Primair', note: 'De merkkleur. Knoppen, links, actieve staat, accenten.', swatches: [
    { token: '--primary-700', hex: '#334155', note: 'Diep — tekst op licht, pressed' },
    { token: '--primary-600', hex: '#475569', note: 'Hover / donkere vlakken' },
    { token: '--primary-500', hex: '#64748b', note: 'PRIMAIR — merkkleur', primary: true },
    { token: '--primary-400', hex: '#94a3b8', note: 'Lichter accent' },
    { token: '--primary-100', hex: '#eef2f7', note: 'Tint — chips, vullingen' },
    { token: '--primary-050', hex: '#f8fafc', note: 'Flauwe tint — secties' },
  ] },
  { title: 'Accent', note: 'De spark. CTA’s, highlights, aandacht.', swatches: [
    { token: '--accent-600', hex: '#d97706', note: 'Hover / pressed' },
    { token: '--accent-500', hex: '#f59e0b', note: 'ACCENT — CTA, aandacht', primary: true },
    { token: '--accent-300', hex: '#fcd34d', note: 'Licht — zachte vullingen' },
    { token: '--accent-050', hex: '#fffbeb', note: 'Flauwe tint' },
  ] },
  { title: 'Inkt & oppervlak — neutralen', note: 'Tekst, achtergronden en lijnen.', swatches: [
    { token: '--ink-900', hex: '#0f172a', note: 'Koppen op wit', dark: true },
    { token: '--ink-700', hex: '#334155', note: 'Sterke bodytekst', dark: true },
    { token: '--ink-500', hex: '#64748b', note: 'Secundaire tekst', dark: true },
    { token: '--ink-300', hex: '#aab4c0', note: 'Placeholder, disabled' },
    { token: '--line', hex: '#e2e8f0', note: 'Hairline-randen' },
    { token: '--bg-soft', hex: '#f8fafc', note: 'Zachte sectie-achtergrond' },
  ] },
  { title: 'Semantisch — status', note: 'Betekenisdragende kleuren voor uitkomsten en meldingen.', swatches: [
    { token: '--success', hex: '#16a34a', note: 'Akkoord / goedgekeurd', primary: true },
    { token: '--warning', hex: '#f59e0b', note: 'Aandacht / voorwaarden', primary: true },
    { token: '--danger', hex: '#dc2626', note: 'Probleem / afgekeurd', primary: true },
    { token: '--focus-ring', hex: '#94a3b8', note: 'Focus-ring' },
  ] },
];
function ColorShowcase() {
  return (
    <React.Fragment>
      {COLOR_GROUPS.map((g) => (
        <div className="g-card" key={g.title}>
          <div className="g-card-title">{g.title}</div>
          <p className="g-card-note">{g.note}</p>
          <div className="g-swatches">
            {g.swatches.map((s) => (
              <div className="g-color" key={s.token}>
                <div className={`g-color-chip${s.dark ? ' is-dark' : ''}`} style={{ background: `var(${s.token})` }} />
                <div className="g-color-meta">
                  <span className="g-type">{s.token}</span>
                  <span className="g-color-hex">{s.hex}</span>
                  <span className="g-muted">{s.note}</span>
                </div>
              </div>
            ))}
          </div>
        </div>
      ))}
      <div className="g-card">
        <div className="g-card-title">Gebruik</div>
        <ul className="g-anatomy">
          <li><span className="g-dot" />Gebruik altijd het token (<code>var(--primary-500)</code>), nooit de losse hex — zo blijft de palet-bron één plek.</li>
          <li><span className="g-dot" />De primaire kleur draagt grote vlakken; het accent is gereserveerd voor CTA’s en nadruk. Niet mengen.</li>
          <li><span className="g-dot" />Status-tonen (akkoord/aandacht/probleem) dragen betekenis — gebruik ze niet decoratief.</li>
        </ul>
      </div>
    </React.Fragment>
  );
}

// ---- Tokens: radii, schaduw, ruimte (fundament) ----------------------------
const RADII = [
  { token: '--r-sm', px: '8px', note: 'Inputs, kleine chips' },
  { token: '--r-card', px: '12px', note: 'Kaarten, modalen' },
  { token: '--r-lg', px: '16px', note: 'Grote vlakken' },
  { token: '--r-xl', px: '24px', note: 'Extra groot' },
  { token: '--r-pill', px: '999px', note: 'Knoppen, badges' },
];
const SHADOWS = [
  { token: '--shadow-sm', note: 'Subtiel — chips, kleine lichten' },
  { token: '--shadow-md', note: 'Kaarten, popovers' },
  { token: '--shadow-lg', note: 'Drawers, modalen' },
];
const SPACES = [
  ['--space-1', 4], ['--space-2', 8], ['--space-3', 12], ['--space-4', 16],
  ['--space-5', 24], ['--space-6', 32], ['--space-7', 48], ['--space-8', 64],
];
function TokensShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Hoekradii</div>
        <p className="g-card-note">Van zacht naar ruim afgerond. Alles is afgerond — niets scherp. Gebruik <code>--r-pill</code> voor knoppen/badges en <code>--r-card</code> voor kaarten en modalen.</p>
        <div className="g-radii">
          {RADII.map((r) => (
            <div className="g-radius" key={r.token}>
              <div className="g-radius-demo" style={{ borderRadius: `var(${r.token})` }}></div>
              <span className="g-type">{r.token}</span>
              <span className="g-muted">{r.px} · {r.note}</span>
            </div>
          ))}
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Schaduwen</div>
        <p className="g-card-note">Alleen buiten-schaduwen, zacht en neutraal (slate-getint). Geen harde drop-shadows of inner shadows.</p>
        <div className="g-shadows">
          {SHADOWS.map((s) => (
            <div className="g-shadow" key={s.token}>
              <div className="g-shadow-demo" style={{ boxShadow: `var(${s.token})` }}></div>
              <span className="g-type">{s.token}</span>
              <span className="g-muted">{s.note}</span>
            </div>
          ))}
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Ruimte-schaal</div>
        <p className="g-card-note">Eén schaal voor padding, gaps en marges — een 4px-basis. Gebruik de stappen i.p.v. losse pixelwaarden.</p>
        <div className="g-spaces">
          {SPACES.map(([token, px]) => (
            <div className="g-space-row" key={token}>
              <span className="g-type" style={{ width: 96 }}>{token}</span>
              <span className="g-space-bar" style={{ width: px }}></span>
              <span className="g-muted">{px}px</span>
            </div>
          ))}
        </div>
      </div>
    </React.Fragment>
  );
}

// ---- Tijdlijn (organisme) --------------------------------------------------
const TL_ITEMS = [
  { icon: 'flag', tone: 'info', title: 'Aanvraag ingediend', meta: 'Sanne de Wit', time: '14-05-2026 · 09:24' },
  { icon: 'upload', tone: 'info', title: 'Jaarcijfers 2025 geüpload', meta: 'Dossier', time: '15-05-2026 · 11:02' },
  { icon: 'check-circle', tone: 'success', title: 'Kredietanalyse goedgekeurd', meta: 'Rick van Dijk', time: '21-05-2026 · 16:40' },
  { icon: 'alert-triangle', tone: 'warning', title: 'Aanvullend onderpand opgevraagd', meta: 'Carolien Mulder', time: '22-05-2026 · 10:15' },
  { icon: 'mail', tone: 'neutral', title: 'Offerte verstuurd naar klant', meta: 'Sales', time: '24-05-2026 · 08:30' },
];
function TimelineShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Activiteitenlijst</div>
        <p className="g-card-note">De verticale tijdlijn in detail-drawers: een verbindingslijn met per regel een gekleurde icoonstip, titel en metaregel. Geef <code>items</code> mee ({'{ icon, tone, title, meta, time }'}) voor de standaardvorm. De <code>tone</code> kleurt de stip betekenisvol (info/success/warning/neutral).</p>
        <Card>
          <Timeline items={TL_ITEMS} />
        </Card>
      </div>
      <div className="g-card">
        <div className="g-card-title">Maatwerk-regels <span className="g-muted">(TimelineItem)</span></div>
        <p className="g-card-note">Voor klikbare regels of een rechter-slot (kanaallabel, chevron) bouw je de <code>&lt;li&gt;</code>’s zelf met <code>TimelineItem</code> als bouwsteen — zoals de marketing-tijdlijn met Sales/Marketing-labels doet.</p>
        <Card>
          <ol className="timeline">
            <li className="tl-item">
              <TimelineItem icon="mouse-pointer-click" tone="info" title="Campagne aangeklikt" meta="Touchpoint" time="2 dagen geleden">
                <span className="g-tl-chan">Marketing</span>
              </TimelineItem>
            </li>
            <li className="tl-item">
              <TimelineItem icon="phone" tone="neutral" title="Telefonisch contact" meta="Sales" time="gisteren">
                <span className="g-tl-chan sales">Sales</span>
              </TimelineItem>
            </li>
          </ol>
        </Card>
      </div>
      <div className="g-card">
        <div className="g-card-title">Props</div>
        <PropsTable rows={[
          { name: 'Timeline · items', type: '{icon,tone,title,meta,time}[]', desc: 'De standaardvorm — rendert de regels voor je.' },
          { name: 'Timeline · children', type: 'ReactNode', desc: 'Eigen <li>’s i.p.v. items, voor maatwerk.' },
          { name: 'TimelineItem · icon / tone', type: 'string', desc: 'Icoon + stipkleur (info | success | warning | neutral).' },
          { name: 'TimelineItem · title / meta / time', type: 'string', desc: 'De tekst; meta en time worden samengevoegd.' },
          { name: 'TimelineItem · children', type: 'ReactNode', desc: 'Rechter-slot (label, chevron).' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Paginatie (organisme-onderdeel) ---------------------------------------
function PagerShowcase() {
  const [p1, setP1] = React.useState(1);
  const [p2, setP2] = React.useState(7);
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Tabelvoet <span className="g-muted">(Pager)</span></div>
        <p className="g-card-note">Zoals onder elke lijst: een telling links, de pager rechts. De vorige-pijl is uitgeschakeld op de eerste pagina, de volgende op de laatste.</p>
        <div className="tbl-foot" style={{ borderTop: '1px solid var(--line)', borderRadius: 0 }}>
          <span className="count">42 aanvragen</span>
          <Pager page={p1} pageCount={4} onChange={setP1} />
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Veel pagina's — ellipsis</div>
        <p className="g-card-note">Bij veel pagina's verschijnt een <code>…</code>-gat tussen het begin, het venster rond de huidige pagina en het einde. Pagina {p2} van 20.</p>
        <Pager page={p2} pageCount={20} onChange={setP2} />
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>Pager</code> — props</div>
        <PropsTable rows={[
          { name: 'page', type: 'number', desc: 'Huidige pagina (1-based).' },
          { name: 'pageCount', type: 'number', desc: 'Totaal aantal pagina\u2019s.' },
          { name: 'onChange', type: '(n) => void', desc: 'Aangeroepen met de gekozen pagina.' },
          { name: 'siblings', type: 'number', def: '1', desc: 'Aantal buren rond de huidige pagina v\u00f3\u00f3r een ellipsis.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Segmented (molecuul) --------------------------------------------------
function SegmentedShowcase() {
  const [view, setView] = React.useState('tabel');
  const [vorm, setVorm] = React.useState('lening');
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Weergave-schakelaar <span className="g-muted">(Segmented)</span></div>
        <p className="g-card-note">Een pill-track met één actieve primaire knop, voor het wisselen tussen een paar weergaven. De standaardmaat.</p>
        <Segmented value={view} onChange={setView} ariaLabel="Weergave" options={[
          { value: 'tabel', label: 'Tabel', icon: 'table' },
          { value: 'flow', label: 'Flow', icon: 'git-branch' },
        ]} />
      </div>
      <div className="g-card">
        <div className="g-card-title">Compact <span className="g-muted">(size="sm")</span></div>
        <p className="g-card-note">De compacte variant voor in een kaartkop — zoals de investeringsvorm-switcher op de Invest-tab.</p>
        <Segmented size="sm" value={vorm} onChange={setVorm} ariaLabel="Investeringsvorm" options={[
          { value: 'lening', label: 'Lening', icon: 'banknote' },
          { value: 'aandeel', label: 'Aandeel', icon: 'pie-chart' },
          { value: 'converteerbaar', label: 'Converteerbaar', icon: 'repeat' },
        ]} />
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>Segmented</code> — props</div>
        <PropsTable rows={[
          { name: 'value', type: 'string', desc: 'De actieve waarde.' },
          { name: 'onChange', type: '(v) => void', desc: 'Aangeroepen met de gekozen waarde.' },
          { name: 'options', type: 'string[] | {value,label,icon}[]', desc: 'De segmenten; icon plaatst een Lucide-icoon voorop.' },
          { name: 'size', type: '"sm"', desc: 'Compacte kop-variant.' },
          { name: 'ariaLabel', type: 'string', desc: 'Label voor de tablist (toegankelijkheid).' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Tooltip (atoom) -------------------------------------------------------
function TooltipShowcase() {
  return (
    <React.Fragment>
      <div className="g-card">
        <div className="g-card-title">Hover/focus-tooltip <span className="g-muted">(Tooltip)</span></div>
        <p className="g-card-note">Een korte uitleg-bubbel om een willekeurig element. Toont op hover én op toetsenbordfocus. Voor de (i)-veldhint binnen een label → gebruik <code>InfoTip</code>.</p>
        <div className="g-btn-row" style={{ gap: 14 }}>
          <Tooltip label="Verstuur een nieuwe aanvraag">
            <Button variant="primary" size="sm" icon="plus">Nieuw</Button>
          </Tooltip>
          <Tooltip label="Markeer deze aanvraag als favoriet" side="bottom">
            <IconButton icon="star" label="Favoriet" />
          </Tooltip>
          <Tooltip label="Aangepast t.o.v. de KvK-bron" side="right">
            <Badge tone="warning" icon="pencil">Handmatig</Badge>
          </Tooltip>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title">Zijden <span className="g-muted">(side)</span></div>
        <div className="g-btn-row" style={{ gap: 18 }}>
          {['top', 'bottom', 'left', 'right'].map((s) => (
            <Tooltip key={s} label={`Tooltip aan de ${s}-zijde`} side={s}>
              <ChipButton>{s}</ChipButton>
            </Tooltip>
          ))}
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>Tooltip</code> — props</div>
        <PropsTable rows={[
          { name: 'label', type: 'string', desc: 'De tekst in de bubbel.' },
          { name: 'side', type: 'top | bottom | left | right', def: 'top', desc: 'Kant waar de bubbel verschijnt.' },
          { name: 'children', type: 'ReactNode', desc: 'Het element dat de tooltip aanstuurt.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- Toast (organisme) -----------------------------------------------------
function ToastShowcase() {
  return (
    <React.Fragment>
      <ToastHost />
      <div className="g-card">
        <div className="g-card-title">Notificaties <span className="g-muted">(toast)</span></div>
        <p className="g-card-note">Tijdelijke melding, imperatief aangeroepen via <code>toast(…)</code>. Één <code>&lt;ToastHost/&gt;</code> bij de app-root toont ze rechtsonder. Vier tonen dragen betekenis.</p>
        <div className="g-btn-row">
          <Button variant="ghost" size="sm" onClick={() => toast('Wijzigingen opgeslagen')}>Neutraal (info)</Button>
          <Button variant="ghost" size="sm" onClick={() => toast({ tone: 'success', message: 'Aanvraag goedgekeurd' })}>Success</Button>
          <Button variant="ghost" size="sm" onClick={() => toast({ tone: 'warning', title: 'Let op', message: 'Aanvullend onderpand opgevraagd' })}>Warning</Button>
          <Button variant="ghost" size="sm" onClick={() => toast({ tone: 'danger', message: 'Uploaden mislukt', duration: 0 })}>Danger (blijft staan)</Button>
        </div>
        <div className="g-btn-row" style={{ marginTop: 12 }}>
          <Button variant="ghost" size="sm" onClick={() => toast({ tone: 'info', message: 'Aanvraag verwijderd', action: { label: 'Ongedaan maken', onClick: () => toast({ tone: 'success', message: 'Hersteld' }) } })}>Met actie-knop</Button>
        </div>
      </div>
      <div className="g-card">
        <div className="g-card-title"><code>toast</code> / <code>ToastHost</code> — API</div>
        <PropsTable rows={[
          { name: 'toast(message)', type: 'string', desc: 'Snelle neutrale (info) melding.' },
          { name: 'toast({\u2026})', type: 'object', desc: 'message · title? · tone: info|success|warning|danger · icon? · duration? · action?' },
          { name: 'duration', type: 'number (ms)', def: '4000', desc: '0 = blijft staan tot de gebruiker sluit.' },
          { name: 'action', type: '{ label, onClick }', desc: 'Optionele knop in de toast (bv. Ongedaan maken).' },
          { name: 'ToastHost', type: 'position', def: "'bottom-right'", desc: '\u00c9\u00e9n keer monteren bij de app-root. position: bottom-right|bottom-left|top-right|top-left.' },
        ]} />
      </div>
    </React.Fragment>
  );
}

// ---- App -------------------------------------------------------------------
const COMPONENTS = [
  // ── Fundamenten ── ontwerp-tokens die de hele library dragen ──────────────
  { id: 'typografie', group: 'Fundamenten', label: 'Typografie', icon: 'type',
    intro: 'Het lettertype (Inter voor koppen en tekst), de complete type-schaal met tokens, de eyebrow en de gewichten.',
    render: () => <TypographyShowcase /> },
  { id: 'kleur', group: 'Fundamenten', label: 'Kleur', icon: 'palette',
    intro: 'Het palet als tokens: de primaire en accent-schalen, de inkt-neutralen, oppervlak/lijn en de semantische status-kleuren.',
    render: () => <ColorShowcase /> },
  { id: 'tokens', group: 'Fundamenten', label: 'Radii, schaduw & ruimte', icon: 'ruler',
    intro: 'De vorm-tokens: hoekradii (van input tot extra groot), de neutraal-getinte schaduwen en de 4px-ruimteschaal.',
    render: () => <TokensShowcase /> },
  // ── Atomen ── losse, ondeelbare invoervelden ──────────────────────────────
  { id: 'input', group: 'Atomen', label: 'Inputveld', icon: 'text-cursor-input',
    intro: 'Het standaard tekstveld van de CRM. Eén component voor lezen, bewerken en uitgeschakeld — met een vaste hoogte zodat er nooit iets verspringt.',
    render: () => <InputShowcase /> },
  { id: 'textarea', group: 'Atomen', label: 'Tekstvak', icon: 'text',
    intro: 'Meerregelig tekstveld voor notities en omschrijvingen.',
    render: () => <TextareaShowcase /> },
  { id: 'select', group: 'Atomen', label: 'Keuzelijst', icon: 'list',
    intro: 'Keuzelijst met één selecteerbare waarde. Opent een gestylede merk-popover (geen native OS-dropdown) met de gekozen waarde gemarkeerd.',
    render: () => <SelectShowcase /> },
  { id: 'date', group: 'Atomen', label: 'Datumveld', icon: 'calendar',
    intro: 'Datumkiezer met dezelfde footprint als het tekstveld.',
    render: () => <DateShowcase /> },
  { id: 'money', group: 'Atomen', label: 'Bedragveld', icon: 'euro',
    intro: 'Bedragveld met €-prefix en Nederlandse opmaak in de leesweergave.',
    render: () => <MoneyShowcase /> },
  { id: 'number', group: 'Atomen', label: 'Numeriek veld', icon: 'hash',
    intro: 'Voor gewone getallen — jaar, oppervlakte, percentage, looptijd — met optionele eenheid. Bedragen gebruiken het Bedragveld.',
    render: () => <NumberShowcase /> },
  { id: 'choice', group: 'Atomen', label: 'Selectievelden', icon: 'toggle-right',
    intro: 'Toggle, checkbox en radio — voor aan/uit, meervoudige en enkelvoudige keuzes.',
    render: () => <ChoiceShowcase /> },
  { id: 'search', group: 'Atomen', label: 'Zoekveld', icon: 'search',
    intro: 'Vrij tekstfilter met zoek-icoon en optionele teller-pill. Voor toolbars, de mailbox en de ⌘K-zoeker.',
    render: () => <SearchShowcase /> },
  { id: 'buttons', group: 'Atomen', label: 'Knoppen', icon: 'mouse-pointer-click',
    intro: 'De actieknoppen van de CRM: ronde pills (primair/secundair/goed-/afkeuren), compacte chip-knoppen voor toolbars en koppen, en vierkante icoonknoppen.',
    render: () => <ButtonShowcase /> },
  { id: 'badges', group: 'Atomen', label: 'Badges & status', icon: 'tag',
    intro: 'Statuslabels met een gekleurde stip en losse stip-markeringen — vijf tonen die betekenis dragen (neutraal, akkoord, aandacht, probleem, inactief).',
    render: () => <BadgeShowcase /> },
  { id: 'avatar', group: 'Atomen', label: 'Avatar & IconBubble', icon: 'circle-user',
    intro: 'Persoon-, organisatie- en record-avatars (kleur uit de naam), plus de IconBubble-chip voor tijdlijnen, lege staten en dialoogkoppen.',
    render: () => <AvatarShowcase /> },
  { id: 'tooltip', group: 'Atomen', label: 'Tooltip', icon: 'message-square',
    intro: 'Een korte uitleg-bubbel die op hover of toetsenbordfocus om een willekeurig element verschijnt — in vier richtingen. Voor de (i)-veldhint binnen een label gebruik je InfoTip.',
    render: () => <TooltipShowcase /> },

  // ── Moleculen ── samengestelde besturingselementen ────────────────────────
  { id: 'card-head', group: 'Moleculen', label: 'Kaartkop', icon: 'panel-top',
    intro: 'De vaste kop van elke detailkaart: primair-gekleurd icoon + titel + optionele acties rechts (knop, badge of de Bewerken-schakelaar).',
    render: () => <CardHeadShowcase /> },
  { id: 'menu', group: 'Moleculen', label: 'Dropdownmenu', icon: 'square-menu',
    intro: 'Een actiemenu dat onder de trigger opent, met een schermvullende backdrop die de buitenklik opvangt. Terugkerend in rij-acties, kaartkoppen en de statuskiezer.',
    render: () => <MenuShowcase /> },
  { id: 'autocomplete', group: 'Moleculen', label: 'Autocomplete', icon: 'search',
    intro: 'Zoekveld met resultatenlijst — voor het zoeken van personen/bedrijven (lokaal) of een live API zoals het KVK-Handelsregister.',
    render: () => <AutocompleteShowcase /> },
  { id: 'multiselect', group: 'Moleculen', label: 'Multi-select', icon: 'tags',
    intro: 'Meerdere waarden als chips, gekozen uit bestaande data via een gestylede popover. Compact — chips en ‘+ Toevoegen’ op één regel.',
    render: () => <MultiSelectShowcase /> },
  { id: 'file', group: 'Moleculen', label: 'Bestand uploaden', icon: 'upload',
    intro: 'Sleep-en-neerzet zone met klik-om-te-kiezen, voor documentuploads.',
    render: () => <FileShowcase /> },
  { id: 'segmented', group: 'Moleculen', label: 'Segmented', icon: 'rows-2',
    intro: 'Een pill-track met één actieve primaire knop om tussen een paar weergaven of varianten te schakelen — met een compacte kop-variant. Voor 2–4 korte opties; bij meer → Keuzelijst of Tabbalk.',
    render: () => <SegmentedShowcase /> },
  { id: 'pager', group: 'Moleculen', label: 'Paginatie', icon: 'ellipsis',
    intro: 'Paginatie voor lange lijsten: vorige/volgende-pijlen en genummerde knoppen, met een ellipsis-gat bij veel pagina’s. Te plaatsen in een tabelvoet of los onder een lijst.',
    render: () => <PagerShowcase /> },

  // ── Organismen ── volledige, op zichzelf staande UI-blokken ───────────────
  { id: 'table', group: 'Organismen', label: 'Datatabel', icon: 'table',
    intro: 'De centrale lijstweergave — één tabel-UI voor elke lijstpagina. Kolomtypes sturen uitlijning en celopmaak; rijen zijn klikbaar.',
    render: () => <TableShowcase /> },
  { id: 'drawer', group: 'Organismen', label: 'Detail-drawer', icon: 'panel-right',
    intro: 'Een slide-over paneel dat van rechts inschuift om een record te openen naast de lijst — met een vaste kop, scrollende body en actie-voet.',
    render: () => <DrawerShowcase /> },
  { id: 'tabs', group: 'Organismen', label: 'Tabbalk', icon: 'rows-3',
    intro: 'De onderstreepte tabbalk die de secties van een detailpagina schakelt — icoon + label, met een optioneel slot voor een donut of badge.',
    render: () => <TabsShowcase /> },
  { id: 'modal', group: 'Organismen', label: 'Modaal & bevestiging', icon: 'square-asterisk',
    intro: 'De gecentreerde dialoog: een kale Modal-shell voor eigen inhoud, plus de kant-en-klare ConfirmDialog (icoonbubbel + titel + tekst + acties).',
    render: () => <ModalShowcase /> },
  { id: 'toast', group: 'Organismen', label: 'Notificatie', icon: 'bell-ring',
    intro: 'Tijdelijke melding rechtsonder, imperatief aangeroepen via toast(…). Één ToastHost bij de app-root toont en verbergt ze; vier tonen dragen betekenis en een toast kan een actie-knop dragen.',
    render: () => <ToastShowcase /> },
  { id: 'empty-state', group: 'Organismen', label: 'Lege staat', icon: 'inbox',
    intro: 'Het lege-staat-blok voor lege tabbladen, lijsten en niet-gekoppelde records: primair-gekleurde icoonbubbel + titel + uitleg + optionele actie.',
    render: () => <EmptyStateShowcase /> },
  { id: 'kpi', group: 'Organismen', label: 'KPI-tegels', icon: 'gauge',
    intro: 'De kerncijfer-tegels: Kpi (label + groot cijfer + tone + voortgangsbalk) voor de detailtabs en StatTile (met icoonbubbel/badge) voor dashboards — samen in een responsive KpiGrid.',
    render: () => <KpiShowcase /> },
  { id: 'timeline', group: 'Organismen', label: 'Tijdlijn', icon: 'git-commit-horizontal',
    intro: 'De verticale activiteitenlijst van detail-drawers: een verbindingslijn met per regel een gekleurde icoonstip, titel en metaregel — met TimelineItem als bouwsteen voor maatwerk-regels.',
    render: () => <TimelineShowcase /> },
  { id: 'editable-form', group: 'Organismen', label: 'Bewerkbaar formulier', icon: 'file-pen-line',
    intro: 'Een complete recordkaart: lezen, op Bewerken drukken en de velden worden invulbaar. Het centrale lees/bewerk-patroon van de CRM, opgebouwd uit de DS-velden.',
    render: () => <EditableFormShowcase /> },
  { id: 'record-layout', group: 'Organismen', label: 'Paginalayout', icon: 'columns-3',
    intro: 'De gedeelde body-primitive voor detailpagina\u2019s: een inhoudsgebied met optionele rail, op \u00e9\u00e9n grid zodat alles uitlijnt. Gebruikt door elke detailpagina in de CRM.',
    render: () => <LayoutShowcase /> },
];

// ---- Thema-/merkschakelaar -------------------------------------------------
// Standalone control to switch the active brand theme. Sets data-theme on the
// root element and remembers the choice in localStorage. Themes live in
// theme/*.css; this only flips which one is active.
const THEMES = [
  { id: 'davidanalytics', label: 'David Analytics' },
  { id: 'atlas', label: 'Atlas' },
  { id: 'rijschool', label: 'Rijschool Keer' },
];
function ThemeSwitcher() {
  const [theme, setTheme] = React.useState(
    () => document.documentElement.getAttribute('data-theme') || 'davidanalytics'
  );
  React.useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
    try { localStorage.setItem('dakit-theme', theme); } catch (e) { /* ignore */ }
  }, [theme]);
  return (
    <div className="g-theme" style={{ marginTop: 18, display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 auto' }}>
      <span className="g-nav-group" style={{ padding: '8px 10px 6px' }}>Thema</span>
      {THEMES.map((t) => {
        const active = t.id === theme;
        return (
          <button
            key={t.id}
            type="button"
            onClick={() => setTheme(t.id)}
            aria-pressed={active}
            style={{
              display: 'flex', alignItems: 'center', gap: 8,
              font: 'inherit', fontSize: 14, fontWeight: active ? 600 : 500,
              textAlign: 'left', cursor: 'pointer',
              padding: '8px 11px', borderRadius: 'var(--r-card, 10px)',
              border: active ? '1px solid var(--primary-500)' : '1px solid var(--line)',
              background: active ? 'var(--primary-050)' : 'var(--surface)',
              color: active ? 'var(--primary-600)' : 'var(--ink-500)',
            }}
          >
            <span style={{
              width: 11, height: 11, borderRadius: '50%', flex: '0 0 auto',
              background: active ? 'var(--primary-500)' : 'var(--line)',
            }} />
            {t.label}
          </button>
        );
      })}
    </div>
  );
}

function DesignSystemApp() {
  const [active, setActive] = React.useState('editable-form');
  React.useEffect(() => { if (window.lucide) window.lucide.createIcons(); });
  const current = COMPONENTS.find((c) => c.id === active) || COMPONENTS[0];

  return (
    <div className="g-shell">
      <aside className="g-side">
        <div className="g-brand">
          <span className="g-logo" style={{ width: 'auto', height: 'auto', fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 20, lineHeight: 1.1, color: 'var(--ink-900)' }}>David Analytics</span>
          <span className="g-brand-sub">Component Library</span>
        </div>
        <nav className="g-nav">
          {Object.entries(COMPONENTS.reduce((acc, c) => {
            (acc[c.group || 'Componenten'] = acc[c.group || 'Componenten'] || []).push(c);
            return acc;
          }, {})).map(([group, items]) => (
            <React.Fragment key={group}>
              <span className="g-nav-group">{group}</span>
              {items.map((c) => (
                <button
                  key={c.id}
                  className={`g-nav-item${c.id === active ? ' active' : ''}`}
                  onClick={() => setActive(c.id)}
                >
                  <Icon name={c.icon} size={16} />{c.label}
                </button>
              ))}
            </React.Fragment>
          ))}
        </nav>
        <ThemeSwitcher />
      </aside>

      <main className="g-main">
        <Section
          eyebrow={current.group}
          title={current.label}
          intro={current.intro || null}
        >
          {current.render()}
        </Section>
      </main>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<DesignSystemApp />);
