/* SwarmFrame — bewegingssysteem v1 · 16 aug 2026
   Eén bron voor alle beweging. Vanilla CSS, geen bibliotheken.
   Klassenamen zijn voorbeelden: hang de properties aan jullie bestaande selectors.
   JS alleen waar gemarkeerd (stagger-index, tab-indicator, duurteller, blader-richting). */

:root{
  /* duur */
  --m-press:90ms;   /* indrukken */
  --m-hover:160ms;  /* hover in/uit */
  --m-swap:200ms;   /* tabwissel, pill, crossfade */
  --m-enter:240ms;  /* dialoog, lichtbak */
  --m-page:320ms;   /* paginawissel checker-flow */
  /* ambient */
  --m-pulse:1.6s;   /* bestaand: agent-dot */
  --m-breath:4.8s;  /* dropzone-adem, uitnodigende lege staat */
  --m-cursor:1.1s;  /* log-cursor */
  --m-wave:6s;      /* activiteitsgolf agent-blok */
  /* easing */
  --e-out:cubic-bezier(.25,.6,.3,1);   /* enters + hovers */
  --e-in:cubic-bezier(.5,0,.75,.4);    /* exits (altijd korter dan enter) */
  --e-inout:cubic-bezier(.45,0,.25,1); /* ambient adem */
  /* afstand */
  --d-lift:-1px; --d-nudge:2px; --d-enter:8px; --d-card:12px;
  /* stagger: per rij, cap op 8 (rij 9+ komt zonder delay mee) */
  --stagger:50ms;
}

/* ========== A · MICRO-INTERACTIES ========== */

/* A1 knoppen (alle pills). Hover: lift + hairline-schaduw. Press: terug + micro-scale.
   Geen kleurwissel op primary; secondary mag vullen. */
.fw-btn{transition:transform var(--m-hover) var(--e-out),box-shadow var(--m-hover) var(--e-out),background-color var(--m-hover) var(--e-out),border-color var(--m-hover) var(--e-out)}
.fw-btn:hover{transform:translateY(var(--d-lift));box-shadow:0 2px 0 rgba(0,0,0,.07)}
.fw-btn:active{transform:translateY(0) scale(.985);box-shadow:none;transition-duration:var(--m-press)}
.fw-btn--secondary:hover{border-color:#111;background:#f7f7f5}
/* focus: onmiddellijk, nooit geanimeerd */
.fw-btn:focus-visible,.fw-textlink:focus-visible{outline:2px solid #111;outline-offset:2px;transition:none}

/* A2 tekstacties in lijsten (beheer, bekijk, ontkoppel): onderstreping zakt 2px */
.fw-textlink{text-underline-offset:2px;transition:text-underline-offset var(--m-hover) var(--e-out)}
.fw-textlink:hover{text-underline-offset:4px}
/* variant zonder default-onderstreping (nav): lijn tekent in */
.fw-textlink--draw{text-decoration:none;background-image:linear-gradient(#111,#111);background-repeat:no-repeat;background-size:0% 1px;background-position:0 100%;transition:background-size var(--m-hover) var(--e-out)}
.fw-textlink--draw:hover{background-size:100% 1px}

/* A3 pijl-links (alle projecten →): pijl schuift mee */
.fw-arrowlink .arrow{display:inline-block;transition:transform var(--m-hover) var(--e-out)}
.fw-arrowlink:hover .arrow{transform:translateX(var(--d-nudge))}

/* A4 klikbare kaarten en rijen: “ik ben een deur” */
.fw-card-link{transition:transform var(--m-hover) var(--e-out),border-color var(--m-hover) var(--e-out),background-color var(--m-hover) var(--e-out)}
.fw-card-link:hover{transform:translateY(var(--d-lift));border-color:#c9c9c2;background:#fcfcfb}
.fw-row-link{transition:background-color var(--m-hover) var(--e-out)}
.fw-row-link:hover{background:#fafaf8}

/* A5 tabs: indicator schuift (JS zet left/width op de indicator-balk) */
.fw-tab-indicator{position:absolute;bottom:-1px;height:2px;background:#111;transition:left var(--m-swap) var(--e-out),width var(--m-swap) var(--e-out)}
/* A6 filterpills: aparte elementen, dus geen slide — vulling kruisvaagt */
.fw-pill{transition:background-color 120ms var(--e-out),color 120ms var(--e-out),border-color 120ms var(--e-out)}

/* ========== B · AMBIENT ========== */
/* Budgetregel: maximaal één ambient element per scherm tegelijk.
   Een lopende agent wint altijd; dan valt de rest stil (zet .fw-ambient-paused op de rest). */
.fw-ambient-paused,.fw-ambient-paused *{animation-play-state:paused!important}

/* B1 agent leeft (bestaand, blijft): dot pulseert */
@keyframes fw-pulse{0%,100%{opacity:1}50%{opacity:.35}}
.fw-dot--live{animation:fw-pulse var(--m-pulse) ease-in-out infinite}

/* B2 activiteitsgolf onder de kop van “De AI nu” / het live log:
   48px magenta segment loopt over een hairline-spoor. Alleen renderen bij ≥1 lopende run. */
.fw-wave{position:relative;height:2px;background:#f1f1f1;overflow:hidden}
.fw-wave::after{content:"";position:absolute;top:0;left:0;width:48px;height:2px;background:#e5077e;opacity:.4;animation:fw-wave-run var(--m-wave) linear infinite}
@keyframes fw-wave-run{from{transform:translateX(-48px)}to{transform:translateX(100vw)}}
/* in een kaart: vervang 100vw door de kaartbreedte via een lokale --wave-w */

/* B3 log-cursor knippert (alleen als de sessie leeft; log stil = cursor weg, niet bevroren) */
.fw-cursor{animation:fw-cursor var(--m-cursor) steps(1) infinite}
@keyframes fw-cursor{0%,49%{opacity:1}50%,100%{opacity:0}}

/* B4 duurteller: tikt via JS (textContent, elke 1s). Geen CSS-animatie — cijfers knipperen niet. */

/* B5 uitnodigende lege staat (dropzone, “nog geen projecten/bronnen”):
   rand ademt, plusteken zweeft. Trager dan alles wat klikbaar is. */
.fw-invite{animation:fw-breath var(--m-breath) var(--e-inout) infinite}
@keyframes fw-breath{0%,100%{border-color:#e6e6e6}50%{border-color:#c2c2bb}}
.fw-invite .fw-float{animation:fw-float var(--m-breath) var(--e-inout) infinite}
@keyframes fw-float{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}

/* B6 rustige lege staat (“Niets wacht op jou”): beweegt NIET. Rust is de boodschap. */

/* Bewust stil, app-breed: de foutfamilie en alles wat rood is; de “Wacht op jou”-poorten
   (magenta badge is het signaal); formulier-validatie; tabellen bij dataverversing. */

/* ========== C · OVERGANGEN ========== */

/* C1 dialogen: scrim vaagt, paneel komt op. Exit korter dan enter. */
.fw-scrim{transition:opacity var(--m-hover) linear}
.fw-dialog-enter{animation:fw-dialog-in var(--m-enter) var(--e-out)}
@keyframes fw-dialog-in{from{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}
.fw-dialog-exit{animation:fw-dialog-out 140ms var(--e-in) forwards}
@keyframes fw-dialog-out{to{opacity:0;transform:translateY(4px) scale(.99)}}

/* C2 tabwissel + lijstverversing: content kruisvaagt met 4px lift, nooit layout-shift */
.fw-swap-enter{animation:fw-swap-in var(--m-swap) var(--e-out)}
@keyframes fw-swap-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}

/* C3 lijst-instap met stagger (checker-taal, nu app-breed):
   JS zet --i per rij (0..n); cap 8. */
.fw-list-enter>*{opacity:0;animation:fw-card-in var(--m-swap) var(--e-out) forwards;animation-delay:calc(min(var(--i,0),8)*var(--stagger))}
@keyframes fw-card-in{from{opacity:0;transform:translateY(var(--d-card))}to{opacity:1;transform:none}}

/* C4 lichtbak: scrim 160ms; paneel 200ms scale .97→1; bladeren richtinggevoelig (JS zet klasse) */
.fw-lightbox-enter{animation:fw-lightbox-in var(--m-swap) var(--e-out)}
@keyframes fw-lightbox-in{from{opacity:0;transform:scale(.97)}to{opacity:1;transform:none}}
.fw-slide-next{animation:fw-slide-l var(--m-swap) var(--e-out)}
@keyframes fw-slide-l{from{opacity:0;transform:translateX(12px)}to{opacity:1;transform:none}}
.fw-slide-prev{animation:fw-slide-r var(--m-swap) var(--e-out)}
@keyframes fw-slide-r{from{opacity:0;transform:translateX(-12px)}to{opacity:1;transform:none}}

/* C5 paginawissel checker-flow: pagina vaagt in met kaart-stagger (C3), exit 120ms fade.
   Zelfde taal als de promo’s: 160/240ms, ease-out, verticale instap. */

/* ========== REDUCED MOTION ========== */
/* Bestaat app-breed al; deze guard is de vangrail als motion.css los geladen wordt. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none!important;transition:none!important}
  /* een enter-animatie mag nooit de enige weg naar zichtbaarheid zijn:
     zonder animatie komt de forwards-fill nooit, dus hier hard zichtbaar */
  .fw-list-enter>*{opacity:1!important;transform:none!important}
  /* omgekeerd: een exit-animatie mag niet de enige weg naar ONzichtbaarheid zijn.
     Zonder animatie vuurt animationend nooit, dus zou een consumer het element
     eeuwig in de DOM houden — hier hard verborgen zodat wie op klasse toggelt klaar is. */
  .fw-selbar--exit,.fw-num--out{opacity:0!important}
  .btn:active,.fw-btn:active{transform:none!important}
}

/* ========== AANVULLING 21/8 — nav, uitklappen, selectiebalk, cijferwissel ========== */

/* 1 rij uitklappen (taakbord, sessielijst). Wrapper is een grid; NOOIT max-height met een
   gegokte waarde — dat schokt bij lange inhoud. Kind heeft min-height:0 + overflow:hidden. */
.fw-expand{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--m-swap) var(--e-out)}
.fw-expand[data-open]{grid-template-rows:1fr}
/* visibility:hidden haalt ingeklapte inhoud uit de tab-volgorde en bij schermlezers weg
   (anders is een dichtgeklapte statusknop nog met Tab te bedienen). De visibility flipt
   pas ná de dichtklap (delay = de dichtklapduur), zodat de fade-out zichtbaar blijft; bij
   openen flipt hij meteen (delay 0). */
.fw-expand>*{min-height:0;overflow:hidden;opacity:0;visibility:hidden;transition:opacity 120ms var(--e-out),visibility 0s var(--e-out) 140ms}
.fw-expand[data-open]>*{opacity:1;visibility:visible;transition:opacity 120ms var(--e-out) 60ms,visibility 0s}
.fw-expand:not([data-open]){transition-duration:140ms;transition-timing-function:var(--e-in)}

/* 2 sticky selectiebalk: verschijnt bij de eerste selectie, verdwijnt sneller */
.fw-selbar{animation:fw-selbar-in var(--m-swap) var(--e-out)}
@keyframes fw-selbar-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
.fw-selbar--exit{animation:fw-selbar-out 140ms var(--e-in) forwards}
@keyframes fw-selbar-out{to{opacity:0;transform:translateY(8px)}}

/* 3 cijferwissel (nav-badge, selectieteller): oud getal 4px omhoog weg, nieuw op zijn plek.
   Nooit pulseren — een teller die pulseert leest als een alarm. JS wisselt de klasse. */
.fw-num--out{animation:fw-num-out 100ms var(--e-in) forwards}
@keyframes fw-num-out{to{opacity:0;transform:translateY(-4px)}}
.fw-num--in{animation:fw-num-in var(--m-swap) var(--e-out)}
@keyframes fw-num-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}

/* 4 nav: hover + actieve staat (B, zachte vulling #f0efe9). Kruisvaagt, geen slide —
   de items zijn losse elementen, net als de filterpills. De sidebar beweegt nooit mee;
   de contentkolom wisselt met --m-page (zie C5). */
.fw-nav-item{transition:background-color var(--m-hover) var(--e-out),color var(--m-hover) var(--e-out)}
.fw-nav-item:hover{background:#fafaf8}
.fw-nav-item[aria-current]{background:#f0efe9;font-weight:540;transition-duration:var(--m-swap)}

/* checkbox-vinkje bij selectie */
.fw-checkbox-mark{animation:fw-check-in var(--m-press) var(--e-out)}
@keyframes fw-check-in{from{transform:scale(.9)}to{transform:none}}

/* ========== KOPPELING AAN ONZE SELECTORS ==========
   motion.css hierboven is de bundel-bron (fw-*-voorbeeldklassen, ongewijzigd);
   dit blok hangt dezelfde properties aan de echte app-selectors. */

/* A1 knoppen */
.btn{transition:transform var(--m-hover) var(--e-out),box-shadow var(--m-hover) var(--e-out),background-color var(--m-hover) var(--e-out),border-color var(--m-hover) var(--e-out),opacity var(--m-hover) var(--e-out)}
.btn:hover{transform:translateY(var(--d-lift));box-shadow:0 2px 0 rgba(0,0,0,.07)}
.btn:active{transform:translateY(0) scale(.985);box-shadow:none;transition-duration:var(--m-press)}
.btn.secondary:hover{border-color:var(--ink);background:var(--surface-2)}
.btn:focus-visible{transition:none}

/* A2 tekstacties (lijsten + onderstreepte links) */
.act,.bactie,.weg,.rood,.retry,a[style*="underline"]{text-underline-offset:2px;transition:text-underline-offset var(--m-hover) var(--e-out),color var(--m-hover) var(--e-out)}
.act:hover,.bactie:hover,.weg:hover,.rood:hover,.retry:hover{text-underline-offset:4px}
/* A2b nav-links (F5): geen onderstreping meer. Hover en actief zijn een zachte
   vulling die app.css bezit (#f7f7f5 hover, crème actief); hier alleen de rustige
   kruisvaag ertussen — geen background-shorthand, die zou de vulling wissen. */
.nav a{transition:background-color var(--m-swap) var(--e-out),color var(--m-hover) var(--e-out)}
.nav a.active{transition-duration:var(--m-swap)}

/* A4 klikbare kaarten en rijen */
a.proj,a.projrij,.fb-kaart:not(.open),.chk-rij{transition:transform var(--m-hover) var(--e-out),border-color var(--m-hover) var(--e-out),background-color var(--m-hover) var(--e-out)}
.projrij:hover,.row-card a:hover{background:#fafaf8}
.fb-kaart:not(.open):hover{transform:translateY(var(--d-lift));border-color:#c9c9c2;background:#fcfcfb}

/* A6 filterpills */
.pill{transition:background-color 120ms var(--e-out),color 120ms var(--e-out),border-color 120ms var(--e-out)}

/* C1 dialogen (gedeelde .dialogbg/.dialog over de schermen) */
.dialogbg{animation:fw-scrim-in var(--m-hover) linear}
@keyframes fw-scrim-in{from{opacity:0}to{opacity:1}}
.dialog{animation:fw-dialog-in var(--m-enter) var(--e-out)}

/* C4 lichtbak (review) */
.lichtbak{animation:fw-scrim-in var(--m-hover) linear}
.lichtbak img{animation:fw-lightbox-in var(--m-swap) var(--e-out)}

/* C3 lijst-instap: base.js zet --i per rij op containers met .fw-list-enter */

/* B5 uitnodigende lege staten */
.dropzone,.card.empty{transition:border-color var(--m-hover) var(--e-out)}
.fw-invite{animation:fw-breath var(--m-breath) var(--e-inout) infinite}

/* A5 tabs: de springende border gaat uit zodra de JS-indicator er is */
.fw-heeft-indicator .tab.active{border-bottom-color:transparent}
