/*
  Functionele blokanimaties -- gedeeld door index.html (locaties) en app.html
  (bezoekers), net als hub.css en om dezelfde reden: elke pagina die zijn eigen
  kopie van getunede geometrie draagt, loopt vroeg of laat uit de pas. Dat is
  hier al twee keer gebeurd (de juridische pagina's, de lettertypen).

  DE REGEL WAAR DEZE ANIMATIES AAN MOETEN VOLDOEN (gebruiker, 2026-08-29):
  een blokanimatie laat de functie DOEN wat de tekst zegt. Geen sfeer, geen
  scroll-onthulling, geen versiering. De toets is simpel: als de animatie van
  dit blok verwisseld kan worden met die van een ander blok en beide nog
  kloppen, dan legt hij niets uit en hoort hij hier niet.

  EEN KLOK PER ANIMATIE. Alles binnen een animatie loopt op DEZELFDE duur met
  percentage-keyframes, in plaats van elk element zijn eigen duur en
  vertraging. Losse klokken lopen uit elkaar zodra er iets bijkomt, en dan
  scant de poort een QR die nog niet getekend is. Wil je het tempo wijzigen,
  dan verander je --tk-dur en verder niets.

  Niets beweegt voordat het blok in beeld staat (anim.js zet .playing) en
  niets beweegt voor wie om minder beweging heeft gevraagd -- die krijgt de
  EINDTOESTAND te zien, want dat is wat de animatie beweert.
*/

.fx {
  --tk-dur: 9s;
  position: relative;
  width: 100%;
  /* Iets platter dan 5/4: het blok was een tikje te hoog en duwde de kopij
     ernaast uit het midden. */
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: 20px;
}
.fx * { box-sizing: border-box; }
/* Gepauzeerd tot het blok in beeld komt. */
.fx [class^="tk-"] { animation-play-state: paused; }
.fx.playing [class^="tk-"] { animation-play-state: running; }

/* ---------------------------------------------------------------------------
   HOOFDSTUK 01 -- HET TICKET
   Wat de tekst belooft: "De bevestigingsmail met de PDF is genoeg. Uw bezoeker
   deelt hem met Tixit -- en de QR staat klaar bij de ingang, ook offline."
   Dus dat is precies wat er te zien is, in die volgorde: de mail met de PDF,
   de PDF die naar de telefoon gaat, de code die verschijnt, en de poort die
   hem leest terwijl er geen bereik is.
   --------------------------------------------------------------------------- */

.fx-ticket {
  background:
    radial-gradient(120% 90% at 78% 8%, rgba(255, 255, 255, 0.5), transparent 60%),
    #d8f3ee;
}

/* De mail. Geen envelop-icoontje: een bevestigingsmail is een REGEL in een
   lijst met een bijlage eronder, en dat is wat een venue herkent. */
.tk-mail {
  position: absolute; left: 7%; top: 11%; width: 46%;
  background: #fff; border-radius: 12px; padding: 12px 13px;
  box-shadow: 0 8px 22px rgba(11, 16, 21, 0.1);
  animation: tk-mail var(--tk-dur) linear infinite;
}
.tk-mail-from { font-size: 10.5px; font-weight: 700; color: #0b1015; }
.tk-mail-sub { font-size: 10px; color: #5b6a76; margin-top: 2px; }
.tk-mail-attach {
  margin-top: 9px; display: flex; align-items: center; gap: 6px;
  border: 1px solid rgba(11, 16, 21, 0.14); border-radius: 8px;
  padding: 5px 7px; font-size: 9.5px; color: #0b1015; background: #fbfdfd;
}
.tk-mail-attach b { font-weight: 700; }

/* Het bijlage-plaatje dat de reis maakt. Hij vertrekt op de plek van de
   bijlage hierboven -- niet vanaf de rand -- want het is DIE PDF die naar de
   telefoon gaat, en dat is het hele punt van het blok. */
.tk-pdf {
  position: absolute; left: 11%; top: 27.5%;
  display: flex; align-items: center; gap: 5px;
  background: #0b1015; color: #fff;
  border-radius: 8px; padding: 5px 8px; font-size: 9.5px; font-weight: 600;
  animation: tk-pdf var(--tk-dur) cubic-bezier(0.5, 0, 0.2, 1) infinite;
}

/* De telefoon. Geen 3D en geen glans: het gaat hier om wat er OP staat. */
.tk-phone {
  position: absolute; right: 9%; top: 15%; width: 30%; aspect-ratio: 232 / 400;
  background: #0b1015; border-radius: 18px; padding: 6px;
  box-shadow: 0 14px 34px rgba(11, 16, 21, 0.22);
  animation: tk-phone var(--tk-dur) ease-out infinite;
}
.tk-screen {
  width: 100%; height: 100%; border-radius: 13px; background: #f5fafb;
  padding: 9px 8px 11px; display: flex; flex-direction: column; gap: 7px;
}
.tk-screen-top { font-size: 7.5px; letter-spacing: 0.08em; color: #7a8791; font-weight: 700; }
.tk-card {
  background: #0f3b36; border-radius: 9px; padding: 8px;
  display: flex; flex-direction: column; gap: 5px;
  animation: tk-card var(--tk-dur) ease-out infinite;
}
.tk-card-name { font-size: 8.5px; font-weight: 700; color: #eafaf6; }
.tk-card-date { font-size: 7px; color: #9fd8cd; }

/* De code. Hij TEKENT ZICH IN in plaats van te verschijnen: verschijnen leest
   als een plaatje dat er al was, intekenen leest als iets dat gemaakt wordt. */
.tk-qr {
  margin-top: 2px; align-self: center;
  width: 74%; aspect-ratio: 1; border-radius: 4px; background: #fff;
  padding: 4px; display: grid; grid-template-columns: repeat(7, 1fr);
  grid-auto-rows: 1fr; gap: 1.5px;
  animation: tk-qr var(--tk-dur) ease-out infinite;
}
.tk-qr i { background: #0b1015; border-radius: 1px; }
.tk-qr i.o { background: transparent; }

/* De poort. Eén streep die over de code loopt, en daarna het vinkje -- dat is
   wat een scanner doet en het is genoeg om het te herkennen. */
.tk-beam {
  position: absolute; right: 9%; width: 30%; height: 3px;
  background: linear-gradient(90deg, transparent, #16a8c7, transparent);
  border-radius: 2px;
  animation: tk-beam var(--tk-dur) ease-in-out infinite;
}
.tk-ok {
  margin-top: auto; align-self: center;
  display: flex; align-items: center; gap: 5px;
  background: #0b1015; color: #fff; border-radius: 999px;
  padding: 5px 11px; font-size: 9px; font-weight: 700; white-space: nowrap;
  animation: tk-ok var(--tk-dur) cubic-bezier(0.2, 0.9, 0.3, 1) infinite;
}

/* "Ook offline" is een BEWERING in de tekst, dus hij hoort bewezen te worden:
   het streepje verschijnt precies terwijl de poort leest. */
.tk-offline {
  position: absolute; left: 9%; bottom: 15%;
  display: flex; align-items: center; gap: 6px;
  font-size: 10px; font-weight: 600; color: #0b1015;
  background: rgba(255, 255, 255, 0.86); border-radius: 999px; padding: 6px 12px;
  animation: tk-offline var(--tk-dur) ease-out infinite;
}
.tk-offline svg { width: 12px; height: 12px; display: block; }

/* --- de ene klok ---------------------------------------------------------
   0-13%   de mail komt binnen
   13-30%  de PDF maakt de oversteek
   30-40%  het ticket staat in de wallet
   40-52%  de code tekent zich in
   52-68%  de poort leest hem, zonder bereik
   68-88%  vasthouden
   88-100% terug naar het begin
   ------------------------------------------------------------------------- */
@keyframes tk-mail {
  0% { opacity: 0; transform: translateY(10px); }
  9%, 84% { opacity: 1; transform: none; }
  94%, 100% { opacity: 0; transform: translateY(10px); }
}
@keyframes tk-pdf {
  0%, 13% { opacity: 0; transform: none; }
  17% { opacity: 1; transform: translate(6px, -6px) scale(1.04); }
  /* De landing is de plek van de ticketkaart in de telefoon, niet het midden
     van het toestel -- hij gaat ergens NAARTOE. */
  29% { opacity: 1; transform: translate(150px, 22px) scale(0.75); }
  32%, 100% { opacity: 0; transform: translate(150px, 22px) scale(0.7); }
}
@keyframes tk-phone {
  0% { opacity: 0; transform: translateY(14px); }
  10%, 88% { opacity: 1; transform: none; }
  97%, 100% { opacity: 0; transform: translateY(14px); }
}
@keyframes tk-card {
  0%, 30% { opacity: 0; transform: translateY(6px) scale(0.96); }
  38%, 88% { opacity: 1; transform: none; }
  97%, 100% { opacity: 0; }
}
@keyframes tk-qr {
  0%, 40% { opacity: 0; clip-path: inset(0 0 100% 0); }
  52%, 88% { opacity: 1; clip-path: inset(0 0 0 0); }
  97%, 100% { opacity: 0; clip-path: inset(0 0 100% 0); }
}
@keyframes tk-beam {
  0%, 52% { opacity: 0; top: 30%; }
  56% { opacity: 1; }
  66% { opacity: 1; top: 62%; }
  70%, 100% { opacity: 0; top: 62%; }
}
@keyframes tk-ok {
  0%, 66% { opacity: 0; transform: translateY(8px) scale(0.9); }
  72%, 88% { opacity: 1; transform: none; }
  95%, 100% { opacity: 0; }
}
@keyframes tk-offline {
  0%, 50% { opacity: 0; transform: translateY(6px); }
  58%, 88% { opacity: 1; transform: none; }
  95%, 100% { opacity: 0; }
}

/* Wie om minder beweging vraagt krijgt de EINDTOESTAND, niet een leeg vlak:
   de animatie beweert iets, en die bewering hoort ook zonder beweging te
   staan. */
@media (prefers-reduced-motion: reduce) {
  .fx [class^="tk-"] { animation: none !important; }
  .tk-pdf, .tk-beam { opacity: 0; }
  .tk-mail, .tk-phone, .tk-card, .tk-qr, .tk-ok, .tk-offline {
    opacity: 1; transform: none; clip-path: none;
  }
}

/* Onder 760px staat het blok gestapeld en is de animatie een stuk kleiner;
   de mail zou dan over de telefoon vallen. */
@media (max-width: 760px) {
  .fx { aspect-ratio: 5 / 4; }
  .tk-mail { width: 52%; top: 8%; }
  .tk-phone { width: 34%; }
  .tk-ok, .tk-offline { font-size: 9px; padding: 5px 9px; }
}

/* ---------------------------------------------------------------------------
   AAN DE SLAG -- DE VIER STAPPEN, ALS DOORLOOP
   Dit stond achter een carousel met stipjes: vier keer klikken voor iets dat
   in een keer te bevatten is, en drie van de vier stappen onzichtbaar op elk
   moment. De gebruiker vroeg om een animatie, "sort of video type".
   Dus speelt het af: het formulier vult zich, de wizard vinkt af, de status
   springt op goedgekeurd, en het eerste bericht gaat weg. Wat een nieuwe
   locatie werkelijk doorloopt, in de volgorde waarin ze het doen.

   De vier teksten staan ALLEMAAL in de markup en blijven daar staan -- alleen
   de zichtbaarheid wisselt. Een schermlezer leest alle vier, zoeken op de
   pagina vindt ze alle vier, en wie de animatie niet ziet mist geen woord.
   --------------------------------------------------------------------------- */
.fx-onboard {
  --ob-dur: 20s;
  /* BREDER EN LAGER sinds dit blok een bredere kolom kreeg. Op 4/3 kwam er
     onder het formulier een half scherm wit te staan: een aanmeldformulier
     vult van boven af en houdt niet op de helft op. */
  aspect-ratio: 16 / 10;
  background: #eaf4f6;
  padding: 18px;
  display: flex; flex-direction: column; gap: 12px;
}
.ob-screen {
  flex: 1; background: #fff; border-radius: 13px; padding: 15px 16px;
  position: relative; overflow: hidden;
  box-shadow: 0 6px 18px rgba(11, 16, 21, 0.07);
}
.ob-stage {
  position: absolute; inset: 15px 16px;
  display: flex; flex-direction: column; gap: 10px;
  animation: var(--ob-dur) ease-out infinite;
}
.ob-stage:nth-child(1) { animation-name: ob-s1; }
.ob-stage:nth-child(2) { animation-name: ob-s2; }
.ob-stage:nth-child(3) { animation-name: ob-s3; }
.ob-stage:nth-child(4) { animation-name: ob-s4; }
@keyframes ob-s1 { 0%, 21% { opacity: 1; } 24%, 100% { opacity: 0; } }
@keyframes ob-s2 { 0%, 24% { opacity: 0; } 26%, 46% { opacity: 1; } 49%, 100% { opacity: 0; } }
@keyframes ob-s3 { 0%, 49% { opacity: 0; } 51%, 71% { opacity: 1; } 74%, 100% { opacity: 0; } }
@keyframes ob-s4 { 0%, 74% { opacity: 0; } 76%, 96% { opacity: 1; } 99%, 100% { opacity: 0; } }

/* Een slag groter dan hiervoor: de kolom is breder geworden, en tekst die op
   de oude maat blijft staan leest als een verkleinde schermafdruk in plaats
   van als een scherm. */
.ob-lbl { font-size: 10.5px; font-weight: 700; color: #7a8791; letter-spacing: 0.04em; }
.ob-field {
  height: 30px; border: 1px solid rgba(11, 16, 21, 0.15); border-radius: 7px;
  display: flex; align-items: center; padding: 0 10px;
  font-size: 12.5px; color: #0b1015;
}
/* Het veld vult zich alsof er getypt wordt: de breedte loopt op in stappen,
   dus letter voor letter. Geen JavaScript -- dat is veel machinerie voor iets
   wat een breedte-animatie ook doet. */
.ob-type { overflow: hidden; white-space: nowrap; animation: ob-type var(--ob-dur) steps(20) infinite; }
/* 100%, NIET EEN AANTAL TEKENS. Dit eindigde op 14ch terwijl "Zwembad De
   Waterval" er negentien telt, dus het typen stopte halverwege het woord en
   bleef daar staan -- op elke schermbreedte. Een ch is bovendien de breedte
   van een nul, wat in een proportionele letter niets zegt over een gemiddeld
   teken, dus ook 19ch was gokken. De breedte van het veld kan per definitie
   niet te klein zijn, en overflow houdt de rest verborgen. */
@keyframes ob-type {
  0%, 6% { width: 0; }
  15%, 100% { width: 100%; }
}
/* TEAL, NIET ZWART. Dit is dezelfde knop als de echte "Meld uw locatie aan"
   bovenaan de pagina, en die is bewust niet zwart -- het is de belangrijkste
   handeling op de site en dan hoort hij de accentkleur te dragen. Een namaak
   ervan in een andere kleur laat de lezer twee verschillende knoppen zien
   voor dezelfde stap. */
.ob-btn {
  align-self: flex-start; margin-top: 4px;
  background: #2ed3b7; color: #0b1015; border-radius: 8px;
  padding: 7px 14px; font-size: 11.5px; font-weight: 700;
  animation: ob-btn var(--ob-dur) ease-out infinite;
}
@keyframes ob-btn {
  0%, 16% { transform: none; }
  18% { transform: scale(0.94); }
  20%, 100% { transform: none; }
}

/* Stap 2: een lijst die zichzelf afvinkt. */
.ob-row { display: flex; align-items: center; gap: 9px; font-size: 12.5px; color: #4c5a66; }
.ob-tick {
  flex: 0 0 14px; height: 14px; border-radius: 4px;
  border: 1.6px solid rgba(11, 16, 21, 0.2); background: #fff;
  animation: var(--ob-dur) ease-out infinite;
}
.ob-row:nth-child(1) .ob-tick { animation-name: ob-t1; }
.ob-row:nth-child(2) .ob-tick { animation-name: ob-t2; }
.ob-row:nth-child(3) .ob-tick { animation-name: ob-t3; }
.ob-row:nth-child(4) .ob-tick { animation-name: ob-t4; }
@keyframes ob-t1 { 0%, 28% { background: #fff; } 30%, 100% { background: #2ed3b7; border-color: #2ed3b7; } }
@keyframes ob-t2 { 0%, 33% { background: #fff; } 35%, 100% { background: #2ed3b7; border-color: #2ed3b7; } }
@keyframes ob-t3 { 0%, 38% { background: #fff; } 40%, 100% { background: #2ed3b7; border-color: #2ed3b7; } }
@keyframes ob-t4 { 0%, 43% { background: #fff; } 45%, 100% { background: #2ed3b7; border-color: #2ed3b7; } }

/* Stap 3: de status springt om, en dan wordt er een plan gekozen. */
/* Twee complete pillen op dezelfde plek, gewisseld met opacity. Eerder stond
   hier een pil waarvan de TEKST werd gewisseld met display in keyframes, en
   dat gaf een leeg pilletje op de omslag: display is een discrete eigenschap
   en gedraagt zich daar anders dan ik aannam. Opacity is altijd animeerbaar. */
.ob-status { position: relative; height: 25px; }
.ob-status i {
  position: absolute; left: 0; top: 0; font-style: normal;
  border-radius: 999px; padding: 5px 11px;
  font-size: 11px; font-weight: 700; white-space: nowrap;
  animation: var(--ob-dur) steps(1) infinite;
}
.ob-status i:nth-child(1) {
  background: #f8eddb; color: #b45309; animation-name: ob-st1;
}
.ob-status i:nth-child(2) {
  background: #e4f2e8; color: #15803d; animation-name: ob-st2;
}
@keyframes ob-st1 { 0%, 59% { opacity: 1; } 60%, 100% { opacity: 0; } }
@keyframes ob-st2 { 0%, 59% { opacity: 0; } 60%, 100% { opacity: 1; } }
.ob-plans { display: flex; gap: 8px; margin-top: 4px; }
.ob-plan {
  flex: 1; border: 1.6px solid rgba(11, 16, 21, 0.14); border-radius: 9px;
  padding: 9px 10px; font-size: 11px; color: #4c5a66;
}
.ob-plan b { display: block; font-size: 13px; color: #0b1015; }
.ob-plan:nth-child(1) { animation: ob-plan-a var(--ob-dur) ease-out infinite; }
@keyframes ob-plan-a {
  0%, 66% { border-color: rgba(11, 16, 21, 0.14); }
  68%, 100% { border-color: #16a8c7; box-shadow: 0 0 0 2px rgba(22, 168, 199, 0.16); }
}

/* Stap 4: het eerste bericht gaat weg. */
.ob-msg {
  border-radius: 9px; background: #0f3b36; color: #eafaf6;
  padding: 10px 12px; font-size: 12px;
  animation: ob-msg var(--ob-dur) cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes ob-msg {
  0%, 84% { transform: none; opacity: 1; }
  90%, 100% { transform: translateY(-16px) scale(0.96); opacity: 0; }
}
.ob-sent {
  align-self: center; margin-top: 4px;
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 700; color: #15803d;
  animation: ob-sent var(--ob-dur) ease-out infinite;
}
@keyframes ob-sent {
  0%, 89% { opacity: 0; transform: translateY(6px); }
  92%, 100% { opacity: 1; transform: none; }
}

/* De voortgangsbalk onderaan: vier segmenten, elk vult tijdens zijn stap. Dit
   is wat de stipjes probeerden te zijn, alleen vertelt hij nu hoever je bent
   zonder dat je hoeft te klikken. */
.ob-rail { display: flex; gap: 6px; }
.ob-seg {
  flex: 1; height: 3px; border-radius: 2px;
  background: rgba(11, 16, 21, 0.13); overflow: hidden;
}
.ob-seg::after {
  content: ""; display: block; height: 100%; width: 100%;
  background: #16a8c7; transform-origin: left;
  animation: var(--ob-dur) linear infinite;
}
.ob-seg:nth-child(1)::after { animation-name: ob-r1; }
.ob-seg:nth-child(2)::after { animation-name: ob-r2; }
.ob-seg:nth-child(3)::after { animation-name: ob-r3; }
.ob-seg:nth-child(4)::after { animation-name: ob-r4; }
@keyframes ob-r1 { 0% { transform: scaleX(0); } 25%, 100% { transform: scaleX(1); } }
@keyframes ob-r2 { 0%, 25% { transform: scaleX(0); } 50%, 100% { transform: scaleX(1); } }
@keyframes ob-r3 { 0%, 50% { transform: scaleX(0); } 75%, 100% { transform: scaleX(1); } }
@keyframes ob-r4 { 0%, 75% { transform: scaleX(0); } 100% { transform: scaleX(1); } }

/* Het bijschrift onder het scherm: alle vier de stappen staan er, een is
   zichtbaar. Zo blijft de tekst vindbaar en voorleesbaar. */
/* DE VIER BIJSCHRIFTEN LIGGEN OP ELKAAR VIA GRID, niet via absolute.
   Met position:absolute draagt de doos geen enkele hoogte en moest die met
   de hand op 68px gezet worden -- genoeg voor een kop en twee regels, en op
   een telefoon lopen deze teksten er drie of vier. De rest werd afgesneden,
   middenin een zin. Een vast getal ophogen ruilt dat alleen om voor witruimte
   onder de korte bijschriften.
   In een grid delen ze alle vier dezelfde cel EN telt de hoogste mee, dus de
   doos is per definitie precies zo hoog als de langste tekst -- op elke
   breedte en in elke taal, zonder getal. Dezelfde truc als .scr-steps in
   hub.css, waar vier statusregels op dezelfde manier over elkaar liggen. */
.ob-caps { position: relative; display: grid; }
.ob-cap {
  grid-area: 1 / 1;
  animation: var(--ob-dur) ease-out infinite;
}
.ob-cap b { display: block; font-size: 15px; font-weight: 700; color: var(--ink); }
.ob-cap span { font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); }
.ob-cap:nth-child(1) { animation-name: ob-s1; }
.ob-cap:nth-child(2) { animation-name: ob-s2; }
.ob-cap:nth-child(3) { animation-name: ob-s3; }
.ob-cap:nth-child(4) { animation-name: ob-s4; }

@media (prefers-reduced-motion: reduce) {
  .fx-onboard *, .fx-onboard *::after { animation: none !important; }
  .ob-stage, .ob-cap { opacity: 0; }
  .ob-stage:nth-child(1), .ob-cap:nth-child(1) { opacity: 1; }
  .ob-type { width: 100%; }
  .ob-seg::after { transform: scaleX(1); }
  .ob-status i:nth-child(2) { opacity: 0; }
  .ob-status i:nth-child(1) { opacity: 1; }
}
@media (max-width: 760px) {
  .fx-onboard { aspect-ratio: 1 / 1; padding: 14px; }
}
