/* motion.css — de bewegingslaag over het design system.
   Additief, in dezelfde geest als mobile.css: _ds/** blijft onaangeraakt en
   app.js levert de haakjes aan. Ontwerp: docs/superpowers/specs/
   2026-09-09-animatielaag-design.md, inclusief de herziening onderaan dat
   document.

   DRIE DINGEN OM TE WETEN VOOR JE HIER IETS VERANDERT
   1. De ruststand is het contract. Boven 1024px moet de pagina pixel-identiek
      blijven aan tools/shots/baseline-1440.png en baseline-1280.png. Alles in
      dit bestand hoort daarom bij een toestand die alleen op interactie
      ontstaat (:hover, :focus-visible, :active) of bij een beweging die op
      zijn eindstand uitkomt. Een regel die aan de ruststand raakt is een
      regressie, ook als hij mooier oogt. Controleren:
        bash tools/shots.sh 1440 9000 check-1440
        powershell -ExecutionPolicy Bypass -File tools/pixeldiff.ps1 \
            tools/shots/baseline-1440.png tools/shots/check-1440.png
      Alleen de twee banden uit tools/NOISE.txt mogen verschillen.
   2. Duren en easings. De twee easings komen uit het design system en er
      komen er geen bij: --ease-standard tekent de baan van een reveal,
      --ease-out-soft de trage uitloop van de hero-foto. Voor toestand
      (hover, focus, press) gelden --dur-fast en --dur-base onveranderd. De
      twee reveal-duren hieronder zijn wel van deze laag en niet van het
      design system; waarom staat erbij.
   3. Elke !important hieronder overschrijft een inline stijl uit de
      gegenereerde bundle en is dus niet te vermijden. Bij elke staat waarom.
      De selectors reiken in _ds/** en zijn gekeyd op de huidige DOM-vorm van
      die componenten: hercontroleer dit bestand na elke regeneratie van de
      bundle, net als mobile.css. */

:root{
  /* Hoever een element opkomt bij een reveal, en de stap tussen de kinderen
     van een gestaffelde groep. Zes stappen is het plafond (zie verderop),
     dus niets wacht langer dan 5 x --motion-stagger. */
  --motion-rise:40px;
  --motion-stagger:100ms;

  /* De duur van een reveal, en de easing waarmee hij loopt. Het design system
     reserveert --dur-slow (520ms) hiervoor en daar begon deze laag ook, maar
     bij 40px in plaats van 16px is dat te kort: de beweging is voorbij
     voordat je hem gezien hebt. 700ms tekent dezelfde baan naar dezelfde
     eindstand, alleen zichtbaar.
     De easing schuift mee, en om dezelfde reden. --ease-out-soft
     (cubic-bezier(.16,1,.3,1)) legt ruim negentig procent van de afstand af
     in het eerste kwart van de tijd: hoe lang je hem ook maakt, je ziet een
     flits en daarna een laatste paar pixels die nakruipen. --ease-standard,
     de eerstgenoemde easing van het readme, verdeelt de beweging over de
     hele duur en is dus de easing die je werkelijk ziet lopen. */
  --motion-dur-reveal:700ms;
  --ease-reveal:var(--ease-standard);

  /* De inzoom van de hero-foto. Trager en zachter dan een reveal: een foto
     hoort te bedaren, niet aan te komen. Hier is --ease-out-soft juist wel de
     goede keuze, want die lange uitloop is het effect. */
  --motion-dur-photo:1100ms;
  --motion-zoom:1.06;
}

@media (max-width: 900px){
  :root{
    --motion-rise:24px;
    --motion-stagger:70ms;
  }
}

/* ---------------------------------------------------------------- reveals */

/* De begintoestand hangt volledig aan .zm-hidden, en die klasse zet alleen
   motion.js. Geen enkele regel hieronder verbergt iets uit zichzelf: zonder
   dat script staat de pagina er compleet. Dat is spelregel 1, en het is de
   reden dat hier geen :root-gate of @media-truc staat.
   Alleen transform en opacity, dus de layout verschuift nergens en de
   ruststand blijft pixel-identiek. */
[data-reveal]:not([data-reveal="stagger"]),
[data-reveal="stagger"] > *,
[data-enter]{
  transition:opacity var(--motion-dur-reveal) var(--ease-reveal),
             transform var(--motion-dur-reveal) var(--ease-reveal);
}

.zm-hidden:not([data-reveal="stagger"]),
[data-reveal="stagger"].zm-hidden > *{
  opacity:0;
  transform:translateY(var(--motion-rise));
}

/* data-fade: alleen vervagen, niet opkomen. Twee elementen hebben dat nodig.
   Het zegel ligt op de naad tussen twee panelen en zou bij opschuiven van
   zijn plek raken. De foto in het contactpaneel vult een ouder met
   overflow:hidden die hij deelt met het formulier: schuift die omhoog, dan
   valt onderin een streep paneelkleur open.
   De twee losstaande foto's - bij What we do en bij Company - komen sinds de
   herziening gewoon mee op met de rest. Ze hebben geen klemmende ouder, dus
   die streep bestaat daar niet, en een pagina waarop juist de grootste
   vlakken als enige stilstaan leest doods. */
.zm-hidden[data-fade],
[data-reveal="stagger"].zm-hidden > [data-fade]{
  transform:none;
}

/* data-zoom: vervagen en tegelijk uitzoomen naar de ruststand. Alleen voor
   een foto die een ouder met overflow:hidden voor zich alleen heeft, en dat
   is er op deze pagina precies een: de hero-foto. Elders zou hij tijdens de
   beweging over zijn buren heen groeien, want een transform maakt een
   stacking context en tekent dus bovenop.
   Deze twee regels staan na die van data-fade: die zet transform op none en
   zou de schaal anders wissen, en de specificiteit is gelijk. */
[data-zoom]{
  transition:opacity var(--motion-dur-photo) var(--ease-out-soft),
             transform var(--motion-dur-photo) var(--ease-out-soft);
}
.zm-hidden[data-zoom]{
  transform:scale(var(--motion-zoom));
}

/* Staffeling van de directe kinderen: zes stappen, daarna deelt alles de
   laatste, zodat het onderste element van een lange reeks nooit later begint
   dan 5 x --motion-stagger na het eerste. Een stap meer dan voorheen, omdat
   de stap zelf korter voelt dan hij was: het oog volgt nu de beweging in
   plaats van alleen het aan- en uitgaan. */
[data-reveal="stagger"] > *:nth-child(2){transition-delay:var(--motion-stagger)}
[data-reveal="stagger"] > *:nth-child(3){transition-delay:calc(var(--motion-stagger) * 2)}
[data-reveal="stagger"] > *:nth-child(4){transition-delay:calc(var(--motion-stagger) * 3)}
[data-reveal="stagger"] > *:nth-child(5){transition-delay:calc(var(--motion-stagger) * 4)}
[data-reveal="stagger"] > *:nth-child(n+6){transition-delay:calc(var(--motion-stagger) * 5)}

/* De hero-entree. Anders dan een reveal wacht die niet op de scroll maar
   speelt hij bij het laden, en de volgorde staat als cijfer in het attribuut
   in plaats van als nth-child: de vijf elementen zitten in verschillende
   takken van de boom - de kop in het kopiepaneel, de foto in het mediapaneel,
   het zegel absoluut gepositioneerd ertussenin - dus er is geen rij kinderen
   om op te tellen. */
[data-enter="2"]{transition-delay:var(--motion-stagger)}
[data-enter="3"]{transition-delay:calc(var(--motion-stagger) * 2)}
[data-enter="4"]{transition-delay:calc(var(--motion-stagger) * 3)}
[data-enter="5"]{transition-delay:calc(var(--motion-stagger) * 4)}

/* ------------------------------------------------------------------ drawer */

/* De links in de mobiele drawer staffelen mee als hij opengaat. De verborgen
   toestand geldt uitsluitend zolang de drawer dicht is, en dan is hij zelf al
   onzichtbaar: er wordt dus nooit iets verborgen dat iemand had kunnen lezen.
   Daarom kan dit wel als kale CSS, waar de reveals dat niet kunnen. */
.zpc-drawer nav li{
  transition:opacity var(--dur-base) var(--ease-out-soft),
             transform var(--dur-base) var(--ease-out-soft);
}
.zpc-drawer:not(.is-open) nav li{
  opacity:0;
  transform:translateY(var(--motion-rise));
}
.zpc-drawer.is-open nav li:nth-child(2){transition-delay:var(--motion-stagger)}
.zpc-drawer.is-open nav li:nth-child(3){transition-delay:calc(var(--motion-stagger) * 2)}
.zpc-drawer.is-open nav li:nth-child(n+4){transition-delay:calc(var(--motion-stagger) * 3)}

/* ------------------------------------------------------------------ hover */

/* Navigatielinks. NavBar zet opacity inline, dus die heeft !important nodig;
   transition staat er niet inline en pakt gewoon.
   De onderstreping is sinds de herziening geen kleurwissel meer maar een
   streep die van links naar rechts uitloopt. Een rand die in 140ms van
   transparant naar bruin gaat is een schakelaar; een streep die loopt is
   beweging, en dat is wat deze link hoort te laten zien.
   NavBar zet borderBottom:1px solid transparent inline op elke link, en
   solid --brown-900 op de actieve. De ::after ligt met bottom:-1px precies op
   die rand, dus de actieve link - al onderstreept, al op opacity 1 - ziet er
   op hover onveranderd uit, net als voorheen. position:relative op een inline
   <a> verplaatst niets: het levert alleen het containing block voor de
   ::after.
   De ul-stap in de selector is nodig: de CTA-pil is ook een <a> binnen <nav>,
   en die zou anders diezelfde streep onder zich krijgen. */
.zpc-nav nav ul a{
  position:relative;
  transition:opacity var(--dur-fast) var(--ease-standard);
}
.zpc-nav nav ul a::after{
  content:"";
  position:absolute;
  left:0; right:0; bottom:-1px;
  height:1px;
  background:var(--brown-900);
  transform:scaleX(0);
  transform-origin:left center;
  transition:transform var(--dur-base) var(--ease-standard);
}
.zpc-nav nav ul a:hover{
  opacity:1 !important;
}
.zpc-nav nav ul a:hover::after{
  transform:scaleX(1);
}

/* De zoekknop is de enige <button> die direct in <nav> hangt: de CTA ernaast
   is een <a> in een <span>. Zijn achtergrond is inline transparant. */
.zpc-nav nav > button:hover{
  background:var(--beige-200) !important;
}

/* De inactieve taalpil. Ook inline transparant. */
.zpc-lang button{
  transition:background var(--dur-fast) var(--ease-standard);
}
.zpc-lang button:hover{
  background:var(--beige-200) !important;
}

/* Footerlinks staan op opacity .85 (kolommen) en .8 (legal), allebei inline.
   base.css regelt de kleurtransitie al; hier komt de opacity erbij. */
.zpc-footer a{
  transition:color var(--dur-fast) var(--ease-standard),
             opacity var(--dur-fast) var(--ease-standard);
}
.zpc-footer a[href]:hover{
  opacity:1 !important;
}

/* De LinkedIn-knop: wit .14 -> .22, oftewel "hover lightens". Beide waarden
   zijn bestaande tokens. De achtergrond komt uit skin.overlay en staat inline. */
.zpc-footer a[aria-label="LinkedIn"]{
  transition:background var(--dur-fast) var(--ease-standard);
}
.zpc-footer a[aria-label="LinkedIn"]:hover{
  background:var(--border-on-dark) !important;
}

/* De vacaturekaart is het enige klikbare kaartje op de pagina. Het design
   system wil hier geen lift maar de pijl als affordance ("the arrow is the
   affordance"), dus schuift alleen de pijl mee - nu 5px in plaats van 2, en
   over --dur-base in plaats van --dur-fast, zodat het een gebaar is en geen
   sprongetje. SpeechCard rendert children, dan de notch, dan de pijl: die is
   het laatste kind. */
.zpc-vacancy > span:last-child{
  transition:transform var(--dur-base) var(--ease-out-soft);
}
.zpc-vacancy:hover > span:last-child{
  transform:translate(5px,5px);
}

/* Een Button met leadingArrow wikkelt zichzelf in een <span> die ...rest niet
   krijgt; de className die app.js meegeeft landt dus op de pil daarbinnen.
   Via :has() is de omhullende span alsnog te pakken, zodat het pijltje
   meebeweegt of je nu de pil of het badge aanwijst. Het badge is de enige
   aria-hidden span in die groep. Kent de browser :has() niet, dan gebeurt er
   niets - de knop houdt zijn kleurwissel uit de bundle. */
span:has(> .zpc-btn) > span[aria-hidden="true"]{
  transition:transform var(--dur-base) var(--ease-out-soft);
}
span:has(> .zpc-btn):hover > span[aria-hidden="true"]{
  transform:translate(3px,-3px);
}

/* ------------------------------------------------------------------ press */

/* Het readme: "Press states reduce opacity slightly; nothing scales or
   shrinks." Button zet opacity inline (1, of .45 als hij uit staat), vandaar
   !important. De CTA in de NavBar zit in de bundle zelf, krijgt geen
   className mee en staat daarom apart. */
.zpc-btn:active,
.zpc-nav nav > span > a:active{
  opacity:.85 !important;
}

/* ------------------------------------------------------------------ focus */

/* Er was nergens focus-styling. Deze regels vallen buiten het
   reduced-motion-blok verderop: een focusring is toestand, geen beweging, en
   moet altijd werken. */
#root :focus-visible{
  outline:2px solid var(--brown-900);
  outline-offset:2px;
}

/* Op de donkere vlakken wordt de ring wit. #numbers heeft geen focusbare
   elementen en staat er daarom niet bij. */
#careers :focus-visible,
.zpc-footer :focus-visible,
#zpc-mobile-menu :focus-visible{
  outline-color:var(--white);
}

/* Het zegel in de hero is geen DS.Seal meer maar HeroSeal uit app.js: alleen
   de letterring draait, het merkje staat stil. De keyframes kwamen voorheen
   uit een <style> in DS.Seal; die rendert niet meer, dus staan ze hier. */
@keyframes zpc-seal-spin{
  from{transform:rotate(0)}
  to{transform:rotate(360deg)}
}

/* Hetzelfde voor de marquee: LoopingMarquee in app.js vervangt DS.Marquee,
   dat deze keyframes zelf meebracht. */
@keyframes zpc-marquee{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}
}

/* --------------------------------------------------------- beperkte beweging */

/* prefers-reduced-motion: reduce zet alles uit, inclusief het zegel en de
   marquee, die hun animation-shorthand inline zetten. De losse
   animation-eigenschappen hieronder winnen daarvan.
   Het zegel komt op rotate(360deg) uit, wat gelijk is aan zijn ruststand. De
   marquee komt op translateX(-50%) uit, en omdat Marquee zijn items twee keer
   rendert staat de tweede reeks dan precies waar de eerste stond: visueel
   identiek aan stilstand.
   Kleurwissels en focusringen blijven werken - ze gebeuren alleen niet meer
   geleidelijk. Dat is toestand, geen beweging.
   motion.js doet bij deze voorkeur helemaal niets, dus reveals bestaan hier
   sowieso niet: de pagina is precies wat hij zonder deze laag zou zijn. */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
    scroll-behavior:auto !important;
  }

  /* De duur uitzetten haalt de vertraging niet weg, en die zit hier wel in
     kale CSS: de drawerlinks staffelen zonder tussenkomst van motion.js, dus
     zonder deze regel verschijnen ze ook onder deze voorkeur nog een voor
     een, over 3 x --motion-stagger. Bewust smal gehouden: een globale
     transition-delay:0 zou ook de visibility-vertraging van de drawer zelf
     wissen, en daar hangt de eerste tik na het sluiten van af. */
  .zpc-drawer nav li{
    transition-delay:0s !important;
  }
}
