/* ==========================================================================
   epozvanka.sk — VSTUP „ŽIVÁ KRESLENÁ LINKA"  (js/vstup-linka.js)

   Celé CSS tohto modulu žije POD `.vs[data-vstup="linka"]`. Nič na :root —
   prototyp mal farby na :root a keby sa portol tak, ako je, prepísal by
   paletu ostatným trom animáciám a piata by rozbila štyri existujúce.

   Farby sem nepatria natvrdo. Motor ich prepíše z palety ako --vs-* priamo
   na .vs; do SVG ich vkladá JS ako reťazce, sem chodia len cez var().
   Žiadne miešanie farieb funkciou zo CSS Color 5 — Chrome pod 111 by zahodil
   celú deklaráciu. Animuje sa výhradne transform a opacity.

   KADENCIA (najdôležitejšia zmena oproti prototypu)
   Prototyp mal takmer všetko na steps(1), teda 12 tvrdých skokov za sekundu
   bez jediného medzikroku — a to zákazník prečítal ako pokazené, nie ako
   kreslené. Pravidlo, podľa ktorého je to tu prepísané:
     • náraz, prasknutie, výmena kresby   → steps(1), tvrdý skok
     • LET a posun cez plochu             → steps(N), N ≈ trvanie_v_s × 24
     • pozadie, dýchanie, dosad po náraze → PLYNULE, bez steps
   Šmuh (natiahnutá medzipóza) ostáva vždy presne jednu snímku. Dve už
   vyzerajú ako chyba.

   ROZLOŽENIE: karta s menami je DOM SÚRODENEC obálky, nie jej potomok.
   Nededí teda žiadny transform obálky a doletí naozaj VON a NAHOR.
   ========================================================================== */

/* Odvodené hodiny. Prototyp mal --f1…--f6 zapísané natvrdo; tu je jediná
   pravda --f1 od motora a zvyšok sa z nej počíta. */
.vs[data-vstup="linka"] {
  --f2: calc(2 * var(--f1));
  --f3: calc(3 * var(--f1));
  --f4: calc(4 * var(--f1));
  --f5: calc(5 * var(--f1));
  --f6: calc(6 * var(--f1));
}

/* ── javisko ──────────────────────────────────────────────────────────
   Jediná mierka celej scény. Všetky rozmery aj dráhy sú násobky --w,
   takže pomer strán a rýchlosti sú na mobile aj na desktope rovnaké.
   --vs-vyska podáva motor z visualViewport — na iPhone je to jediné číslo,
   ktoré hovorí, koľko plochy človek naozaj vidí. */
.vs[data-vstup="linka"] .vsl {
  --w: min(90vw, var(--vs-sirka), calc(var(--vs-vyska, 100vh) * 0.62));
  --h: calc(var(--w) * 1.45);
  position: relative;
  width: var(--w);
  height: var(--h);
}

/* Linka. Hrúbky sú z pevnej škály 6 / 3,6 / 2 — nie náhodné čísla. Farbu
   ťahu nastavuje JS priamo na tvar, aby sa dala vziať z palety. */
.vs[data-vstup="linka"] .vsl-l1,
.vs[data-vstup="linka"] .vsl-l2,
.vs[data-vstup="linka"] .vsl-l3 {
  fill: none;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.vs[data-vstup="linka"] .vsl-l1 { stroke-width: 6; }
.vs[data-vstup="linka"] .vsl-l2 { stroke-width: 3.6; }
.vs[data-vstup="linka"] .vsl-l3 { stroke-width: 2; }

/* ── geometria vrstiev ────────────────────────────────────────────────
   Kľúčové: PAPIER je medzi zadnou stenou obálky a vreckom, takže je pred
   vytiahnutím dokonale schovaný, a pritom NIE JE potomkom obálky. */
.vs[data-vstup="linka"] .vsl-vr { position: absolute; inset: 0; pointer-events: none; }
.vs[data-vstup="linka"] .vsl-vr svg { width: 100%; height: 100%; }

.vs[data-vstup="linka"] .vsl-obal {
  position: absolute;
  left: calc(var(--w) * 0.04);
  top: calc(var(--w) * 0.783);
  width: calc(var(--w) * 0.92);
  height: calc(var(--w) * 0.607);   /* 200 : 132 */
  transform-origin: 50% 100%;       /* obálka stojí na spodnej hrane */
  will-change: transform;
  backface-visibility: hidden;
}
.vs[data-vstup="linka"] .vsl-obal svg { width: 100%; height: 100%; }

.vs[data-vstup="linka"] .vsl-z-zadok { z-index: 1; }
.vs[data-vstup="linka"] .vsl-z-chlopb { z-index: 2; }   /* druhá polovica preklopenia — ZA papierom */
.vs[data-vstup="linka"] .vsl-z-vrecko { z-index: 4; }
.vs[data-vstup="linka"] .vsl-z-chlopa { z-index: 5; }   /* zavretá chlopňa — PRED vreckom */
.vs[data-vstup="linka"] .vsl-z-srdce { z-index: 6; }
.vs[data-vstup="linka"] .vsl-konfety { z-index: 7; }
.vs[data-vstup="linka"] .vsl-vyzva { z-index: 8; }

/* 3D preklopenie chlopne. Perspektíva je len na tejto škatuli, nie na celej
   scéne — inak by 3D kontext zbytočne ovplyvnil rastrovanie ostatných vrstiev. */
.vs[data-vstup="linka"] .vsl-chlopbox { perspective: calc(var(--w) * 2.2); }
.vs[data-vstup="linka"] .vsl-chlopna {
  position: absolute;
  inset: 0;
  transform-origin: 50% 9.1%;   /* pánt = horná hrana obálky (y = 12/132) */
  will-change: transform;
  /* POZOR: tu NESMIE byť backface-visibility: hidden. Chlopňa sa preklápa
     o 168°, takže po preklopení sa na ňu pozeráme zozadu — so skrytým rubom
     by otvorená chlopňa jednoducho zmizla. */
}
.vs[data-vstup="linka"] .vsl-chlopna svg { width: 100%; height: 100%; }
/* pokoj: vidno len zavretú chlopňu */
.vs[data-vstup="linka"] .vsl-z-chlopb .vsl-chlopna { opacity: 0; }

/* ── karta (mimo obálky!) ─────────────────────────────────────────────
   Okno, cez ktoré je kartu vôbec vidieť. Končí presne na hornej hrane
   vrecka — pod ňou je karta fyzicky vnútri obálky. Orezanie je statické,
   nič sa neanimuje. z-index 3 je nutný: bez neho by karta padla POD zadnú
   stenu obálky, ktorá má z-index 1. */
.vs[data-vstup="linka"] .vsl-okno {
  position: absolute;
  left: 0; right: 0; top: 0;
  height: calc(var(--w) * 1.022);
  overflow: hidden;
  z-index: 3;
  pointer-events: none;
}
.vs[data-vstup="linka"] .vsl-papier {
  position: absolute;
  left: calc(var(--w) * 0.12);
  top: calc(var(--w) * 0.075);
  width: calc(var(--w) * 0.76);
  height: calc(var(--w) * 0.973);
  transform: translateY(100%);      /* pokoj: schovaný vo vrecku */
  will-change: transform;
  backface-visibility: hidden;
}
/* Deformácia (squash/stretch) ide VÝHRADNE na list papiera. Mená sedia vo
   vlastnej vrstve nad ním a nededia ju — inak by sa pri natiahnutí rozmazali
   a to je presne to, čo sa nesmie stať produktu, za ktorý zákazník platí. */
.vs[data-vstup="linka"] .vsl-list {
  position: absolute;
  inset: 0;
  transform-origin: 50% 100%;       /* ťahá sa hore, drží ho spodok */
  will-change: transform;
}
.vs[data-vstup="linka"] .vsl-list svg { width: 100%; height: 100%; }

.vs[data-vstup="linka"] .vsl-obsah {
  position: absolute;
  inset: 26% 8% 19% 8%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(var(--w) * 0.022);
  opacity: 0;                       /* pokoj: text neexistuje */
  text-align: center;
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-obsah { opacity: 1; }

.vs[data-vstup="linka"] .vsl-riadok {
  /* width: auto + max-width, nie width: 100%. Sadzba sa MERIA cez scrollWidth
     a pri stopercentnej šírke sa riadok vždy rovná svojmu rámcu — meranie by
     hlásilo pretečenie aj pri „Eva" a každé meno by spadlo na najmenší
     stupeň. S auto šírkou sa riadok zmestí presne na text a pretečie len
     vtedy, keď text naozaj pretečie. */
  width: auto;
  max-width: 100%;
  line-height: 1.02;
  text-transform: uppercase;
  letter-spacing: 0.055em;
  color: var(--vs-ink);
  font-family: var(--vs-serif);
  font-size: calc(var(--w) * 0.115);   /* skript to ešte zmenší, ak treba */
}
.vs[data-vstup="linka"] .vsl-riadok-in { display: inline-block; white-space: nowrap; }
/* letter-spacing pridá medzeru aj za posledné písmeno — bez tejto opravy
   sedí riadok o kúsok vľavo od stredu. */
.vs[data-vstup="linka"] .vsl-riadok-in .vsl-pis:last-child { margin-right: -0.055em; }
.vs[data-vstup="linka"] .vsl-pis { display: inline-block; }

.vs[data-vstup="linka"] .vsl-spojka {
  font-family: var(--vs-serif);
  font-size: calc(var(--w) * 0.062);
  font-style: italic;
  letter-spacing: 0;
  color: var(--vs-akcent);
  line-height: 1;
}
.vs[data-vstup="linka"] .vsl-datum {
  font-family: var(--vs-sans);
  font-size: calc(var(--w) * 0.042);
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  text-transform: uppercase;
  color: var(--vs-ink);
  opacity: 0.78;
  line-height: 1.2;
}
.vs[data-vstup="linka"] .vsl-linka {
  width: 42%;
  height: max(2px, calc(var(--w) * 0.008));
  background: var(--vs-ink);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: 50% 50%;
  will-change: transform;
}

.vs[data-vstup="linka"] .vsl-srdce {
  position: absolute;
  width: calc(var(--w) * 0.152);
  height: calc(var(--w) * 0.152);
  left: 50%;
  margin-left: calc(var(--w) * -0.076);
  transform-origin: 50% 100%;    /* bod dotyku — inak sa splošťuje do vzduchu */
  will-change: transform, opacity;
  backface-visibility: hidden;
}
.vs[data-vstup="linka"] .vsl-srdce svg { width: 100%; height: 100%; }
/* Pečať na obálke sedí na hrote chlopne (y ≈ 66/132). */
.vs[data-vstup="linka"] .vsl-srdce--obal { top: calc(var(--w) * 1.0105); }
/* Pečať na karte je SAMOSTATNÝ element, nie ten istý. Vymeniť kresbu medzi
   dvoma snímkami je najstarší trik 2D animácie a je úplne neviditeľný. */
.vs[data-vstup="linka"] .vsl-srdce--pecat {
  top: calc(var(--w) * 0.085);
  opacity: 0;
}

/* ── výzva ────────────────────────────────────────────────────────────── */
.vs[data-vstup="linka"] .vsl-vyzva {
  position: absolute;
  left: 0; right: 0;
  /* POD obálku, nie na ňu.

     Obálka siaha od 0.783w do 1.39w. Výzva stála na 1.235w, čiže na jej
     tele — a farba --vs-vyzva sa počíta voči podkladu SCÉNY, nie voči
     obálke. Kontrolné meranie skutočných pixelov to ukázalo naplno: 120
     zo 124 dizajnov pod 3:1, medián 1,11:1. Text tam bol a nebolo ho vidieť.

     Posunutá pod spodnú hranu obálky leží na tom, voči čomu je jej farba
     počítaná. */
  top: calc(var(--w) * 1.46);
  text-align: center;
  font-family: var(--vs-sans);
  font-size: calc(var(--w) * 0.038);
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  /* Farba výzvy sa neberie z dizajnu, ale z čitateľnosti — motor ju
     dopočíta ako čiernu alebo bielu podľa toho, ktorá je na podklade scény
     lepšie vidieť (--vs-vyzva v js/vstup.js). Predtým tu stála farba
     odvodená od dizajnu a v mnohých dizajnoch splynula s podkladom: výzva
     na obrazovke bola, ale hosť ju nevidel. */
  color: var(--vs-vyzva, var(--vs-ink));
  opacity: 0;
  transform-origin: 50% 50%;
  will-change: transform, opacity;
}

/* ══════════ CHVENIE LINKY (BOIL) ══════════
   Tri varianty tej istej kresby, každý drží 2 snímky (6 fps). Skript ich
   vyrobí z jednej predlohy a rozhádže body o ±0,7 jednotky, čo je 0,35 %
   šírky. Výmena kresby je VÝMENA, nie prelínanie — preto steps(1) a preto
   sa tu kadencia nezjemňuje. Plynulý boil nie je boil, je to rozmazanie. */
@keyframes vsl-boil3 {
  0%, 33.32% { opacity: 1; }
  33.33%, 100% { opacity: 0; }
}
.vs[data-vstup="linka"] .vsl-boil > g {
  animation: vsl-boil3 var(--f6) steps(1) infinite;
}
.vs[data-vstup="linka"] .vsl-boil > g:nth-child(2) { animation-delay: calc(-1 * var(--f2)); }
.vs[data-vstup="linka"] .vsl-boil > g:nth-child(3) { animation-delay: calc(-2 * var(--f2)); }

/* Počas úderov sa chvenie zastaví. Nie je to estetické rozhodnutie, je to
   výkonové: repaint pätnástich skupín šesťkrát za sekundu kradne snímky
   presne tej animácii, na ktorej záleží. Pri rýchlom pohybe ho aj tak nikto
   nevidí. V pokoji aj v držaní na konci beží — kvôli nemu sa animácia volá
   „živá linka". */
.vs[data-vstup="linka"][data-faza="hraj"] .vsl-boil > g { animation-play-state: paused; }

/* ══════════ KRESLENIE PO NAČÍTANÍ ══════════
   Jeden ťah na snímku. Ťah pristane o 14 % väčší a v ďalšej snímke sadne —
   to je „úder atramentu o papier", nie plynulé objavovanie. Dopad ostáva
   tvrdý, zjemňuje sa až dosad. */
@keyframes vsl-tah {
  0%   { opacity: 0; transform: scale(1.14); animation-timing-function: steps(1); }
  50%  { opacity: 1; transform: scale(1.14); animation-timing-function: steps(2); }
  100% { opacity: 1; transform: scale(1); }
}
.vs[data-vstup="linka"] .vsl-tah {
  animation: vsl-tah var(--f2) both;
  animation-delay: calc(var(--i, 0) * var(--f1));
}

/* Hrot pera skáče z rohu do rohu — po jednej snímke na hranu. Skok je dlhý
   (0,88 × šírka scény za 105 ms), takže tu platí pravidlo pre POSUN CEZ
   PLOCHU: steps(3) ≈ 24 fps namiesto jedinej snímky. Vodorovné skoky sú
   najdlhšie, preto je na nich hrot natiahnutý (šmuh). */
@keyframes vsl-pero {
  0%   { transform: translate(0, 0) scale(1);
         animation-timing-function: steps(3); }
  14%  { transform: translate(calc(var(--w) * 0.8832), 0) scaleX(2.2) scaleY(0.6);
         animation-timing-function: steps(3); }
  28%  { transform: translate(calc(var(--w) * 0.8832), calc(var(--w) * 0.5762)) scale(1);
         animation-timing-function: steps(3); }
  42%  { transform: translate(0, calc(var(--w) * 0.5762)) scaleX(2.2) scaleY(0.6);
         animation-timing-function: steps(3); }
  56%  { transform: translate(0, 0) scale(1);
         animation-timing-function: steps(3); }
  70%  { transform: translate(0, calc(var(--w) * 0.2267)) scale(1);
         animation-timing-function: steps(3); }
  84%  { transform: translate(calc(var(--w) * 0.8832), calc(var(--w) * 0.2267)) scaleX(2.2) scaleY(0.6);
         animation-timing-function: steps(3); }
  99%  { transform: translate(calc(var(--w) * 0.4416), calc(var(--w) * 0.3464)) scale(1); opacity: 1; }
  100% { transform: translate(calc(var(--w) * 0.4416), calc(var(--w) * 0.3464)) scale(1); opacity: 0; }
}
.vs[data-vstup="linka"] .vsl-pero {
  position: absolute;
  z-index: 9;
  left: calc(var(--w) * 0.0584 - var(--w) * 0.016);
  top: calc(var(--w) * 0.7954 - var(--w) * 0.016);
  width: calc(var(--w) * 0.032);
  height: calc(var(--w) * 0.032);
  border-radius: 50%;
  background: var(--vs-ink);
  animation: vsl-pero calc(9 * var(--f1)) both;
  will-change: transform, opacity;
}

/* Výzva „ťukni" nabehne, keď je kresba hotová. Nábeh je úder (steps),
   dokmitnutie je dosad — a ten podľa pravidla ide PLYNULE. */
@keyframes vsl-vyzva-in {
  0%   { opacity: 0; transform: translateY(30%) scale(0.8, 1.2);
         animation-timing-function: steps(3, jump-none); }
  55%  { opacity: 1; transform: translateY(-10%) scale(1.14, 0.88); }
  78%  { opacity: 1; transform: translateY(3%) scale(0.96, 1.04); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
/* Dýchanie výzvy je PLYNULÉ. V prototype boli štyri tvrdé stavy a práve
   toto oko na statickej obrazovke číta ako pokazené — nič sa nehýbe, len
   to cvaká. */
/* Spodok pulzu je 0.96, nie 0.72.

   Pri 0.72 klesol kontrast výzvy v šiestich dizajnoch pod 4,5 : 1 (najhoršie
   3,32 pri „Zlatej linke") — meranie skutočných pixelov v priebehu animácie.
   Dýchanie zostáva vidieť, mení sa aj mierka; čitateľnosť má prednosť. */
@keyframes vsl-vyzva-pulz {
  0%   { transform: scale(1); opacity: 0.96; }
  50%  { transform: scale(1.07); opacity: 1; }
  100% { transform: scale(1); opacity: 0.96; }
}
.vs[data-vstup="linka"] .vsl-vyzva {
  animation:
    vsl-vyzva-in var(--f3) both calc(10 * var(--f1)),
    vsl-vyzva-pulz calc(9 * var(--f1)) ease-in-out infinite calc(13 * var(--f1));
}
@keyframes vsl-zmizni { 0% { opacity: 1; } 100% { opacity: 0; } }
/* Zmizne za jednu snímku. Tvrdý strih je v kreslenej animácii legitímny —
   veci sa medzi dvoma kresbami jednoducho odoberú. */
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-vyzva {
  animation: vsl-zmizni var(--f1) steps(1) both;
}

/* Iskry v pozadí. POZADIE → PLYNULE, bez jediného steps. */
@keyframes vsl-iskra {
  0%   { opacity: 0.45; transform: scale(0.62) rotate(-14deg); }
  50%  { opacity: 1; transform: scale(1.15) rotate(18deg); }
  100% { opacity: 0.45; transform: scale(0.62) rotate(-14deg); }
}
.vs[data-vstup="linka"] .vsl-iskra { animation: vsl-iskra 2600ms ease-in-out infinite; }
.vs[data-vstup="linka"] .vsl-iskra--b { animation-delay: -900ms; }
.vs[data-vstup="linka"] .vsl-iskra--c { animation-delay: -1750ms; }
/* Iskry odoberieme v snímke 6, keď okolo nich preletí karta. Inak by ich
   karta prerezala napoly a ostali by z nich úlomky trčiace spoza hrany. */
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-iskra {
  animation: vsl-zmizni var(--f1) steps(1) both calc(6 * var(--f1));
}

/* ══════════ ÚDERY PO ŤUKNUTÍ ══════════ */

/* ÚDER 1 — 0,42 s. Napřah (obálka sa prikrčí) a potom odraz, keď z nej
   vystrelí pečať. Bez napřahu oko nezaregistruje začiatok akcie a celý
   pohyb prečíta ako preskočený, teda sekaný. Napřah je stlačenie (steps),
   dokmitnutie po ňom je dosad (plynulé). */
@keyframes vsl-obalka-tras {
  0%   { transform: rotate(0deg) scale(1, 1);
         animation-timing-function: steps(3, jump-none); }
  30%  { transform: rotate(0deg) scale(1.09, 0.92);        /* NAPŘAH: dole */
         animation-timing-function: steps(1); }
  52%  { transform: rotate(-3deg) scale(0.94, 1.07); }     /* odraz */
  72%  { transform: rotate(2deg) scale(1.04, 0.96); }      /* prestrel späť */
  88%  { transform: rotate(-1deg) scale(1, 1); }
  100% { transform: rotate(0deg) scale(1, 1); }
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-obal {
  animation: vsl-obalka-tras var(--f5) both;
}

/* ÚDER 2 — 0,42 s. Pečať sa najprv stlačí NADOL (opačný smer než let),
   potom vystrelí. Snímka 34 % je ŠMUH: natiahnutá na 1,9 a skosená pozdĺž
   dráhy, presne jednu snímku — dve by už vyzerali ako chyba. Samotný LET
   za ňou je posun cez plochu, takže beží na steps(2)–steps(3), nie na
   jedinej snímke ako v prototype. */
@keyframes vsl-srdce-vzlet {
  0%   { transform: translate(0, 0) rotate(0deg) scale(1, 1); opacity: 1;
         animation-timing-function: steps(2, jump-none); }
  22%  { transform: translate(0, calc(var(--w) * 0.018)) rotate(0deg) scale(1.3, 0.77);
         animation-timing-function: steps(1); }
  34%  { transform: translate(calc(var(--w) * -0.05), calc(var(--w) * -0.18))
                    rotate(-22deg) scale(0.53, 1.9) skewX(12deg); opacity: 0.88;
         animation-timing-function: steps(1); }   /* ← ŠMUH, 1 snímka */
  52%  { transform: translate(calc(var(--w) * -0.20), calc(var(--w) * -0.52))
                    rotate(-48deg) scale(0.92, 1.12); opacity: 1;
         animation-timing-function: steps(2); }
  74%  { transform: translate(calc(var(--w) * -0.40), calc(var(--w) * -0.92))
                    rotate(-78deg) scale(1, 1); opacity: 1;
         animation-timing-function: steps(3); }
  100% { transform: translate(calc(var(--w) * -0.62), calc(var(--w) * -1.38))
                    rotate(-112deg) scale(1, 1); opacity: 0; }
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-srdce--obal {
  animation: vsl-srdce-vzlet var(--f5) both var(--f2);
}

/* ÚDER 3 — 0,25 s. Chlopňa sa odklopí, prestrelí o 18° a dokmitne. Nikdy
   nezastaví presne na cieli — to je najspoľahlivejší signál „toto je
   počítač, nie pohyb". Odklopenie je posun cez plochu (steps(4) ≈ 24 fps),
   dokmit je dosad (plynulý). */
@keyframes vsl-chlopna-otvor {
  0%   { transform: rotateX(0deg);
         animation-timing-function: steps(4, jump-none); }
  62%  { transform: rotateX(-186deg); }   /* prestrel */
  82%  { transform: rotateX(-158deg); }   /* protipohyb */
  100% { transform: rotateX(-168deg); }
}
/* Výmena kresby presne v snímke, keď je chlopňa hranou k divákovi. Vtedy má
   nulovú šírku, takže výmena je fyzicky neviditeľná — a vďaka nej je otvorená
   chlopňa v inej vrstve (ZA papierom) a v inom odtieni (rub je tmavší). Bez
   tohto triku by otvorená chlopňa prekrývala kartu. Výmena kresby je strih,
   preto tu steps(1) ostáva. */
@keyframes vsl-chlopna-preber { 0% { opacity: 1; } 100% { opacity: 0; } }
@keyframes vsl-chlopna-nastup { 0% { opacity: 0; } 100% { opacity: 1; } }
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-chlopna {
  animation: vsl-chlopna-otvor var(--f3) both calc(3 * var(--f1));
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-z-chlopa .vsl-chlopna {
  animation:
    vsl-chlopna-otvor var(--f3) both calc(3 * var(--f1)),
    vsl-chlopna-preber var(--f1) steps(1) both calc(3 * var(--f1));
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-z-chlopb .vsl-chlopna {
  animation:
    vsl-chlopna-otvor var(--f3) both calc(3 * var(--f1)),
    vsl-chlopna-nastup var(--f1) steps(1) both calc(3 * var(--f1));
}

/* ÚDER 4 — 0,42 s, hlavný. Karta preletí celú výšku scény.
   Prototyp to mal na dvoch skokoch po ~200 px a práve tento kus vyzeral
   najhoršie: pri kroku väčšom než polovica výšky karty oko stratí spojitosť
   a prečíta to ako vynechané snímky. Tu je let rozdelený na steps(4) cez
   0,17 s, čo je presne 24 fps — a šmuh (natiahnutie na 1,52) ostáva na
   tvare, kde trvá presne jednu snímku.
   Posun je na .vsl-papier, deformácia na .vsl-list — mená sú vo vlastnej
   vrstve a šmuh nededia, inak by sa rozmazali. */
@keyframes vsl-papier-let {
  0%   { transform: translateY(100%);
         animation-timing-function: steps(4); }
  40%  { transform: translateY(-7%); }      /* prestrel nad cieľ */
  62%  { transform: translateY(3%); }       /* protipohyb */
  80%  { transform: translateY(-1.2%); }    /* mikrovlnka */
  92%  { transform: translateY(0.4%); }
  100% { transform: translateY(0); }
}
@keyframes vsl-papier-tvar {
  0%   { transform: scale(1.06, 0.94);      /* NAPŘAH: karta sa stlačí nadol,
                                               teda ostane celá vo vrecku */
         animation-timing-function: steps(1); }
  20%  { transform: scale(0.66, 1.52);
         animation-timing-function: steps(1); }   /* ← ŠMUH, presne 1 snímka */
  40%  { transform: scale(0.88, 1.14);
         animation-timing-function: steps(1); }
  62%  { transform: scale(1.19, 0.84); }    /* squash pri dobrzdení */
  80%  { transform: scale(0.95, 1.05); }
  92%  { transform: scale(1.02, 0.98); }
  100% { transform: scale(1, 1); }
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-papier {
  animation: vsl-papier-let var(--f5) both var(--f4);
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-list {
  animation: vsl-papier-tvar var(--f5) both var(--f4);
}

/* ÚDER 5 — 0,25 s. Linky sa vyťahujú zo stredu, prestrelia o 14 %.
   Vyťahovanie je pohyb cez plochu → steps(3); dokmit je dosad → plynule. */
@keyframes vsl-linka-tah {
  0%   { transform: scaleX(0);
         animation-timing-function: steps(3, jump-none); }
  55%  { transform: scaleX(1.14); }
  78%  { transform: scaleX(0.94); }
  100% { transform: scaleX(1); }
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-linka {
  animation: vsl-linka-tah var(--f3) both calc(var(--od, 9) * var(--f1));
}

/* ÚDER 6 — 0,25 s na písmeno. Písmená pristávajú po skupinách, jedna snímka
   odstup. Dve mená, ktoré dosadnú naraz, sú mŕtve; dve, ktoré dosadnú
   s odstupom dvoch snímok, sú živé. Nálet je úder (steps(3, jump-none)),
   dokmitnutie je dosad — plynulé. */
@keyframes vsl-pis {
  0%   { opacity: 0; transform: translateY(38%) scale(0.72, 1.36);
         animation-timing-function: steps(3, jump-none); }
  46%  { opacity: 1; transform: translateY(-13%) scale(1.2, 0.84); }   /* prestrel */
  70%  { opacity: 1; transform: translateY(5%) scale(0.93, 1.07); }    /* protipohyb */
  86%  { opacity: 1; transform: translateY(-2%) scale(1.04, 0.97); }
  100% { opacity: 1; transform: translateY(0) scale(1, 1); }
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-pis {
  animation: vsl-pis var(--f3) both;
  animation-delay: calc(var(--zac, 0ms) + var(--i, 0) * var(--f1));
}
.vs[data-vstup="linka"] .vsl-riadok--a { --zac: calc(10 * var(--f1)); }
.vs[data-vstup="linka"] .vsl-riadok--b { --zac: calc(13 * var(--f1)); }

.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-spojka,
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-datum {
  animation: vsl-pis var(--f3) both;
  animation-delay: var(--zac);
}
.vs[data-vstup="linka"] .vsl-spojka { --zac: calc(12 * var(--f1)); }
.vs[data-vstup="linka"] .vsl-datum { --zac: calc(16 * var(--f1)); }

/* ÚDER 7 — 0,42 s. Pečať padá na kartu. Pád je let → steps(4); snímka 68 %
   je NÁRAZ (1,42 × 0,62) a trvá presne jednu snímku; odskok po ňom je dosad
   → plynulý. Origin je v bode dotyku (spodok srdca), inak by sa splošťovalo
   do vzduchu. */
@keyframes vsl-srdce-dopad {
  0%   { opacity: 0; transform: translateY(calc(var(--w) * -0.72)) rotate(-38deg) scale(0.7, 1.32);
         animation-timing-function: steps(1); }
  14%  { opacity: 1; transform: translateY(calc(var(--w) * -0.62)) rotate(-34deg) scale(0.7, 1.32);
         animation-timing-function: steps(4, jump-none); }
  56%  { opacity: 1; transform: translateY(calc(var(--w) * -0.09)) rotate(-12deg) scale(0.62, 1.58);
         animation-timing-function: steps(1); }   /* natiahnutie pri páde */
  68%  { opacity: 1; transform: translateY(0) rotate(0deg) scale(1.42, 0.62);
         animation-timing-function: steps(1); }   /* ← NÁRAZ, 1 snímka */
  80%  { opacity: 1; transform: translateY(calc(var(--w) * -0.032)) rotate(3deg) scale(0.9, 1.12); }
  90%  { opacity: 1; transform: translateY(0) rotate(-1deg) scale(1.08, 0.94); }
  100% { opacity: 1; transform: translateY(0) rotate(0deg) scale(1, 1); }
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-srdce--pecat {
  animation: vsl-srdce-dopad var(--f5) both calc(17 * var(--f1));
}

/* Po dosade tlkot. Srdce je DÝCHANIE, nie úder — podľa pravidla teda
   plynule. V prototype cvakalo po snímkach a na statickej scéne to bola
   posledná vec, ktorú bolo vidieť. */
@keyframes vsl-tlkot {
  0%, 44% { transform: scale(1); }
  52%     { transform: scale(1.13, 0.9); }
  62%     { transform: scale(0.98, 1.04); }
  72%     { transform: scale(1.07, 0.95); }
  84%, 100% { transform: scale(1); }
}
.vs[data-vstup="linka"] .vsl-srdce--obal .vsl-srdce-in {
  animation: vsl-tlkot calc(11 * var(--f1)) ease-in-out infinite;
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-srdce--obal .vsl-srdce-in { animation: none; }
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-srdce--pecat .vsl-srdce-in {
  animation: vsl-tlkot calc(11 * var(--f1)) ease-in-out infinite calc(23 * var(--f1));
}

/* ══════════ KONFETY ══════════
   Časticová vrstva tejto animácie. Zákazníkov prepínač ju vypína — keď je
   `konfety: false`, JS ju vôbec nepostaví a tieto pravidlá nemajú na čom
   bežať. Výstrel je náraz (steps(1)), rozlet je pohyb cez plochu (steps(6)
   cez 0,24 s ≈ 24 fps), doznenie je plynulé. */
.vs[data-vstup="linka"] .vsl-konfety {
  position: absolute;
  left: 50%;
  top: calc(var(--w) * 0.236);
  width: 0; height: 0;
  pointer-events: none;
}
.vs[data-vstup="linka"] .vsl-konf {
  position: absolute;
  left: calc(var(--w) * var(--kw) * -0.5);
  top: calc(var(--w) * var(--kh) * -0.5);
  width: calc(var(--w) * var(--kw));
  height: calc(var(--w) * var(--kh));
  opacity: 0;
  will-change: transform, opacity;
}
@keyframes vsl-konf {
  0%   { opacity: 0; transform: translate(0, 0) rotate(0deg) scale(0.4);
         animation-timing-function: steps(1); }
  12%  { opacity: 1;
         transform: translate(calc(var(--w) * var(--kx) * 0.3), calc(var(--w) * var(--ky) * 0.3))
                    rotate(calc(var(--kr) * 0.3)) scale(1.1);
         animation-timing-function: steps(6); }
  70%  { opacity: 1;
         transform: translate(calc(var(--w) * var(--kx)), calc(var(--w) * var(--ky)))
                    rotate(var(--kr)) scale(1); }
  100% { opacity: 0;
         transform: translate(calc(var(--w) * var(--kx) * 1.06), calc(var(--w) * var(--ky) + var(--w) * 0.1))
                    rotate(calc(var(--kr) * 1.25)) scale(0.85); }
}
.vs[data-vstup="linka"]:not([data-faza="pokoj"]) .vsl-konf {
  animation: vsl-konf var(--f5) both calc(20 * var(--f1));
}

/* ══════════ BEZ POHYBU ══════════
   Motor takej vrstve vypne animácie natvrdo (animation: none !important),
   takže tu treba dokresliť KONCOVÝ STAV staticky. Bez tohto by karta ostala
   navždy vo vrecku a hosť by videl zalepenú obálku. */
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-pero,
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-vyzva,
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-srdce--obal,
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-konfety { display: none; }
/* Iskry v koncovom stave neexistujú — v behu ich animácia odoberie v snímke 6,
   keď okolo nich preletí karta. Bez tohto pravidla by v tichom režime trčali
   spoza jej okrajov. */
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-iskra { display: none; }
/* Z troch variantov chvenia ostane jeden — ostatné dva by ležali navrchu. */
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-boil > g + g { display: none; }
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-z-chlopa .vsl-chlopna { opacity: 0; }
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-z-chlopb .vsl-chlopna {
  opacity: 1;
  transform: rotateX(-168deg);
}
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-papier { transform: translateY(0); }
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-linka { transform: scaleX(1); }
.vs[data-vstup="linka"][data-bez-pohybu="ano"] .vsl-srdce--pecat { opacity: 1; }
