/*
 * FDMaker — ruch na stronie
 * ---------------------------------------------------------------------------
 * Jeden pomysł przewodni: strona zachowuje się jak wydruk. Treść nie „wlatuje"
 * z boków ani nie skacze — narasta od dołu, warstwa po warstwie, a każdy blok
 * podkreśla cienka kreska akcentu, która przejeżdża w poprzek jak dysza.
 *
 * Zasady, których się tu trzymamy:
 *  1. Tylko transform i opacity. Nic, co zmusza przeglądarkę do przeliczania
 *     układu strony w trakcie animacji — dzięki temu jest płynnie na telefonie.
 *  2. Krótko. 420–700 ms. Animacja ma być zauważona kątem oka, nie oglądana.
 *  3. Ruch startuje dopiero, gdy JavaScript doda na <html> klasę „js-motion".
 *     Bez skryptu strona wygląda normalnie i cała treść jest widoczna —
 *     to warunek, żeby nie zniknęła np. dla wyszukiwarek.
 *  4. Kto ma w systemie ograniczony ruch, dostaje stronę bez animacji.
 */

:root {
  --ease-out: cubic-bezier(.16, .84, .44, 1);
  --ease-soft: cubic-bezier(.4, 0, .2, 1);

  /*
   * Krzywa do dłuższych ruchów. „--ease-out" rusza bardzo szybko i dopiero
   * potem hamuje — przy krótkim skoku to dobrze, ale przy dłuższym pierwsze
   * klatki mają największy przeskok i na słabszej grafice właśnie tam gubią
   * się kadry. To widać jako zacinanie, choć animacja jest poprawna.
   * Ta krzywa rozkłada ruch równiej.
   */
  --ease-glide: cubic-bezier(.25, .46, .45, .94);
}

/* ===========================================================================
   1. Odsłanianie przy przewijaniu
   =========================================================================== */

/*
 * Stan wyjściowy nakładamy wyłącznie, gdy skrypt zdążył się wykonać
 * (klasa na <html>) i tylko na elementy, które sam oznaczył. Gdyby skrypt
 * padł, żaden z tych warunków nie zajdzie i treść zostaje na ekranie.
 */
/*
 * Bez „will-change".
 *
 * Wcześniej stało tu „will-change: opacity, transform" — i to była pomyłka
 * kosztująca płynność. Ta podpowiedź każe przeglądarce z góry wydzielić
 * osobną warstwę graficzną, a reguła obowiązywała WSZYSTKIE odsłaniane bloki
 * od wczytania strony. Na stronie głównej to kilkanaście warstw trzymanych
 * jednocześnie, zanim cokolwiek zaczęło się animować. Na słabszej grafice
 * kończy się to gubieniem kadrów właśnie wtedy, gdy animacja startuje.
 *
 * Przejścia na „opacity" i „transform" przeglądarka i tak prowadzi po swojej
 * stronie — podpowiedź nie jest do tego potrzebna.
 */
.js-motion [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition:
    opacity .55s var(--ease-glide),
    transform .55s var(--ease-glide);
  transition-delay: var(--reveal-delay, 0ms);
}

.js-motion [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* nagłówek sekcji dochodzi odrobinę dalej — to on prowadzi wzrok */
.js-motion [data-reveal='head'] { transform: translate3d(0, 26px, 0); }

/* obrazki i bryły dodatkowo delikatnie się zbliżają */
.js-motion [data-reveal='figure'] {
  transform: translate3d(0, 22px, 0) scale(.985);
  transition-duration: .7s;
}
.js-motion [data-reveal='figure'].is-in { transform: none; }

/* ===========================================================================
   2. Kreska akcentu pod tytułem sekcji
   ---------------------------------------------------------------------------
   Przejeżdża od lewej do prawej, gdy tytuł wchodzi w kadr. Skala zamiast
   szerokości, bo skala nie przelicza układu strony.
   =========================================================================== */

.js-motion .title,
.js-motion .display { position: relative; }

.js-motion .title[data-rule]::after,
.js-motion .display[data-rule]::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -.45rem;
  width: 3.5rem;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .6s var(--ease-glide) .18s;
}

.js-motion .title[data-rule].is-in::after,
.js-motion .display[data-rule].is-in::after { transform: scaleX(1); }

/* ===========================================================================
   3. Pasek postępu czytania
   ---------------------------------------------------------------------------
   Cienka linia u samej góry, rośnie wraz z przewijaniem. Odpowiednik paska
   postępu wydruku i jedyny element, który towarzyszy przez całą stronę.
   =========================================================================== */

/*
 * Nazwa „readbar", nie „progress" — ta druga jest już zajęta przez pasek
 * postępu do darmowej dostawy w koszyku (app.css). Wspólna nazwa robiła
 * z tamtego paska przyklejoną do góry ekranu kreskę.
 */
.readbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 200;
  pointer-events: none;
  background: transparent;
}

.readbar i {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--accent);
  transform: scaleX(var(--p, 0));
  transform-origin: left center;
}

/* ===========================================================================
   4. Nagłówek strony
   =========================================================================== */

/*
 * Po odjechaniu od góry belka odcina się cieniem. Sam cień i tło — nic, co
 * zmienia wymiary.
 *
 * Wcześniej belka dodatkowo się zagęszczała: „transition: padding" na jej
 * wnętrzu. Wyglądało to dobrze i było najgorszym pomysłem w całym pliku —
 * zmiana odstępu każe przeglądarce przeliczyć układ strony w każdej klatce,
 * a dzieje się to dokładnie w chwili rozpoczęcia przewijania, czyli wtedy,
 * gdy startują animacje sekcji. Stąd zacinanie przy „Od pliku do paczki”.
 * Belka jest przyklejona u góry, więc jej zmiana wysokości przesuwała jeszcze
 * całą treść pod nią.
 */
.masthead {
  transition:
    box-shadow .35s var(--ease-soft),
    background-color .35s var(--ease-soft);
}

.masthead.is-stuck {
  box-shadow: 0 1px 0 var(--rule), 0 10px 30px -22px rgba(0, 0, 0, .45);
  background: color-mix(in srgb, var(--paper) 92%, transparent);

  /*
   * Rozmycie tła TYLKO po odjechaniu od góry. Na samej górze strony i tak nie
   * ma pod belką czego rozmywać, a filtr tła to najdroższa rzecz, jaką można
   * postawić na elemencie przyklejonym do okna: przy każdej klatce przewijania
   * przeglądarka liczy rozmycie na nowo. Włączony od początku obciążał kartę
   * przez cały czas — także wtedy, gdy odsłaniały się sekcje.
   */
  backdrop-filter: blur(8px);
}

/* podkreślenie pozycji menu wyjeżdża spod tekstu */
.nav > a { position: relative; }

.nav > a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -.3rem;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform .3s var(--ease-out);
}

.nav > a:hover::after,
.nav > a:focus-visible::after { transform: scaleX(1); transform-origin: left center; }

.nav > a[aria-current='page']::after {
  transform: scaleX(1);
  background: var(--accent);
  height: 2px;
}

/* ===========================================================================
   5. Wejście strony
   ---------------------------------------------------------------------------
   Pierwszy ekran nie czeka na przewijanie — pojawia się od razu po wczytaniu,
   z niewielkim opóźnieniem między wierszami nagłówka a resztą.
   =========================================================================== */

/*
 * Nagłówek wchodzi wiersz po wierszu, a nie całą kolumną naraz: każdy wiersz
 * siedzi w masce i wysuwa się spod jej dolnej krawędzi. To ruch z gazetowego
 * łamania — wzrok dostaje kolejność czytania, zamiast całej płachty tekstu.
 *
 * Maska musi mieć zapas na ogonki liter („ą", „y", „g"). Przy line-height 1.06
 * overflow: hidden ucina je bez tego równo w połowie, co widać dopiero na
 * konkretnym tekście — stąd padding w dół i zdjęcie go marginesem, żeby układ
 * strony został bez zmian.
 */
.hero__head .line {
  display: block;
  overflow: hidden;
  padding-bottom: .14em;
  margin-bottom: -.14em;
}

.js-motion .hero__head .line > span {
  display: block;
  animation: line-up .66s var(--ease-glide) backwards;
  animation-delay: calc(var(--i, 0) * 80ms);
}

@keyframes line-up {
  from { transform: translate3d(0, 105%, 0); }
  to   { transform: none; }
}

/*
 * Reszta pierwszego ekranu dochodzi po wierszach nagłówka. Opóźnienia są stałe,
 * nie liczone od liczby wierszy — trzy wiersze kończą się w .3 s, a dłuższy
 * nagłówek i tak zdąży, bo wejścia mają się na siebie nachodzić.
 */
.js-motion .hero .lede     { animation: enter .7s var(--ease-glide) backwards .24s; }
.js-motion .hero .hero__cta { animation: enter .7s var(--ease-glide) backwards .32s; }
.js-motion .hero__figure   { animation: enter .8s var(--ease-glide) backwards .14s; }

@keyframes enter {
  from { opacity: 0; transform: translate3d(0, 20px, 0); }
  to   { opacity: 1; transform: none; }
}

/*
 * Zdjęcie na pierwszym ekranie oddycha — bardzo powoli i o ułamek procenta.
 * Na tyle mało, że nie da się tego złapać wzrokiem wprost, ale kadr przestaje
 * być martwy. Zatrzymuje się pod kursorem, żeby nie przeszkadzać w klikaniu.
 */
.js-motion .hero__figure img { animation: drift 26s var(--ease-soft) infinite; }
.js-motion .hero__figure:hover img { animation-play-state: paused; }

@keyframes drift {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.022); }
}

/* ===========================================================================
   6. Reakcje na kursor
   =========================================================================== */

/* kafelki produktów podnoszą się i mocniej odcinają */
.card,
.hero__figure {
  transition:
    transform .3s var(--ease-out),
    border-color .3s var(--ease-out),
    box-shadow .3s var(--ease-out);
}

/*
 * Same stany „:hover" siedzą niżej, w bloku za „hover: hover" — na telefonie
 * przeglądarka zapala hover po dotknięciu i zostawia go zapalonego. Tutaj
 * zostają wyłącznie przejścia, bo one obsługują też powrót do stanu zwykłego.
 */

/* przyciski dociskają się przy kliknięciu — potwierdzenie, że trafiło.
   To zostaje na telefonie: „:active" jest dotykiem, nie kursorem. */
.btn { transition: transform .16s var(--ease-out), background-color .2s, border-color .2s, color .2s; }
.btn:active { transform: translateY(1px); }

/* strzałki w odnośnikach wysuwają się w stronę celu */
.arrow { display: inline-block; transition: transform .25s var(--ease-out); }

/* wiersze tabel i list podświetlają się miękko */
.spectable tr,
.summary__row { transition: background-color .2s var(--ease-soft); }

/* pola formularza reagują na wejście kursorem, nie dopiero na kliknięcie */
.field input,
.field select,
.field textarea {
  transition: border-color .2s var(--ease-soft), box-shadow .2s var(--ease-soft);
}

/* ===========================================================================
   7. Liczby, które doliczają
   ---------------------------------------------------------------------------
   Cyfry w zestawieniach dobijają do wartości docelowej, gdy wejdą w kadr.
   Samą wartość podmienia skrypt; tutaj tylko pilnujemy, żeby zmiana liczby
   znaków nie rozjeżdżała układu.
   =========================================================================== */

[data-count] { font-variant-numeric: tabular-nums; }

/* ===========================================================================
   8. Zmiana podstrony
   ---------------------------------------------------------------------------
   Krótkie przygaszenie przy wychodzeniu ze strony zszywa nawigację w całość
   zamiast białego mignięcia. Tylko dla zwykłych przejść — nie dla formularzy,
   pobierania plików i odnośników otwieranych w nowej karcie.
   =========================================================================== */

/*
 * Wypełnienie „backwards", nie „both". Przy „both" przeglądarka trzyma
 * przezroczystość z ostatniej klatki animacji już na zawsze, a deklaracje
 * animacji biją w kaskadzie zwykłe reguły — klasa wygaszająca nie miałaby
 * wtedy żadnego wpływu. „backwards" działa tylko przed startem animacji.
 */
.js-motion body { animation: page-in .32s var(--ease-out) backwards; }
.js-motion body.is-leaving { opacity: 0; transition: opacity .18s var(--ease-soft); }

@keyframes page-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ===========================================================================
   9. Kreska przez kroki procesu
   ---------------------------------------------------------------------------
   Cztery kroki mają się czytać jako jeden ciąg, nie jako cztery kafelki obok
   siebie. Po wejściu sekcji w kadr przez ich górną krawędź przejeżdża kreska
   akcentu — segment za segmentem, więc widać kierunek: od pliku do paczki.

   Segment jest na każdym kroku osobno, a nie jedną kreską na całej szerokości,
   bo siatka zawija się na wąskim ekranie. Jedna długa kreska zostałaby wtedy
   nad pierwszym rzędem, a dolne kroki byłyby bez niej.
   =========================================================================== */

.js-motion .steps[data-line] .step { position: relative; }

.js-motion .steps[data-line] .step::before {
  content: '';
  position: absolute;
  top: -1px;              /* dokładnie na kresce, którą .steps ma jako border-top */
  left: 0;
  right: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;

  /*
   * Jedna właściwość na liście przejść, nie dwie. Wysokość (reakcja na kursor)
   * ma osobne przejście niżej: gdyby stała tu razem z „transform", każde
   * najechanie kursorem restartowałoby też przejazd kreski.
   */
  transition: transform .62s var(--ease-glide);
}

.js-motion .steps[data-line].is-in .step::before { transform: scaleX(1); }

/*
 * Segmenty nachodzą na siebie mocno — następny startuje, gdy poprzedni jest
 * w jednej piątej. Kreska czyta się wtedy jako jeden ciągły przejazd, a nie
 * cztery osobne odcinki, i cały ruch kończy się w niecałej sekundzie zamiast
 * w półtorej. Poprzednie odstępy (.15 s przy .5 s przejazdu) rozrywały to na
 * osobne skoki, co widać było jako zacinanie.
 */
.js-motion .steps[data-line].is-in .step:nth-child(2)::before { transition-delay: .12s; }
.js-motion .steps[data-line].is-in .step:nth-child(3)::before { transition-delay: .24s; }
.js-motion .steps[data-line].is-in .step:nth-child(4)::before { transition-delay: .36s; }

/*
 * Numer kroku zapala się do akcentu w chwili, gdy kreska go mija. Bez skryptu
 * i przy ograniczonym ruchu numery są od razu w kolorze akcentu — to ich stan
 * docelowy, więc nic nie znika.
 */
.js-motion .steps[data-line] .step__n {
  color: var(--ink-faint);
  transition: color .4s var(--ease-soft);
}

.js-motion .steps[data-line].is-in .step__n { color: var(--accent); }
.js-motion .steps[data-line].is-in .step:nth-child(2) .step__n { transition-delay: .14s; }
.js-motion .steps[data-line].is-in .step:nth-child(3) .step__n { transition-delay: .26s; }
.js-motion .steps[data-line].is-in .step:nth-child(4) .step__n { transition-delay: .38s; }

/* ===========================================================================
   10. Wiersze tabel parametrów
   ---------------------------------------------------------------------------
   Zestawienia narastają wiersz po wierszu, jak wychodzący z drukarki dokument.
   Zgodne z zamysłem całości i przy okazji praktyczne: oko dostaje chwilę na
   każdą pozycję, zamiast całej tabeli w jednym mignięciu.

   Stan ukryty wymaga rodzica z „data-reveal" BEZ „is-in". Jeśli skrypt zdjął
   ten atrybut (blok w bloku) albo nie wstał wcale, reguła przestaje pasować
   i wiersze są po prostu widoczne — nie ma jak zostawić pustej tabeli.
   =========================================================================== */

.js-motion [data-reveal]:not(.is-in) .spectable tr {
  opacity: 0;
  transform: translate3d(0, 4px, 0);
}

.js-motion [data-reveal] .spectable tr {
  transition:
    opacity .4s var(--ease-glide),
    transform .4s var(--ease-glide);
}

/*
 * Opóźnienia po numerze wiersza — z zapasem .1 s, żeby rodzic zdążył się
 * pojawić, i z pułapem na ósmym wierszu. Bez pułapu tabela parametrów
 * technicznych, gdzie wierszy jest kilkanaście, kazałaby czekać sekundę
 * na ostatni; a to już nie ruch, to opóźnienie.
 */
.js-motion [data-reveal].is-in .spectable tr { transition-delay: .45s; }
.js-motion [data-reveal].is-in .spectable tr:nth-child(1) { transition-delay: .10s; }
.js-motion [data-reveal].is-in .spectable tr:nth-child(2) { transition-delay: .15s; }
.js-motion [data-reveal].is-in .spectable tr:nth-child(3) { transition-delay: .20s; }
.js-motion [data-reveal].is-in .spectable tr:nth-child(4) { transition-delay: .25s; }
.js-motion [data-reveal].is-in .spectable tr:nth-child(5) { transition-delay: .30s; }
.js-motion [data-reveal].is-in .spectable tr:nth-child(6) { transition-delay: .35s; }
.js-motion [data-reveal].is-in .spectable tr:nth-child(7) { transition-delay: .40s; }

/* ===========================================================================
   11. Reakcje na kursor — tylko tam, gdzie kursor istnieje
   ---------------------------------------------------------------------------
   Cały ten blok jest za „hover: hover" i „pointer: fine". Na telefonie
   przeglądarka udaje hover po dotknięciu i efekt zostaje zapalony po
   puszczeniu palca — element wygląda wtedy na zaznaczony, choć nikt go już
   nie trzyma. Rysik i telewizor („pointer: coarse") mają ten sam problem.
   =========================================================================== */

@media (hover: hover) and (pointer: fine) {

  /* kafelek i kadr na pierwszym ekranie podnoszą się i mocniej odcinają */
  .card:hover,
  .hero__figure:hover {
    transform: translateY(-3px);
    box-shadow: 0 18px 34px -26px rgba(0, 0, 0, .5);
  }

  /* strzałka wysuwa się w stronę celu */
  a:hover > .arrow,
  .arrow:hover { transform: translateX(4px); }

  /*
   * Zdjęcie w kafelku zbliża się pod kursorem, kadr zostaje na miejscu.
   * „overflow: hidden" jest tu warunkiem, nie ozdobą — bez niego powiększone
   * zdjęcie wyszłoby na sąsiednie kafelki, bo siatka katalogu ma odstępy
   * grubości kreski.
   */
  .card__media { overflow: hidden; }

  .card__media > img,
  .card__media > svg { transition: transform .4s var(--ease-glide); }

  /*
   * „will-change" tylko na czas najechania — i to jest właściwe miejsce dla tej
   * podpowiedzi. Rysunki produktów są rysunkami wektorowymi (SVG), a skalowanie
   * takiego rysunku każe przeglądarce rysować go od nowa w każdej klatce.
   * Wydzielona warstwa pozwala jej zamiast tego rozciągnąć gotowy obraz.
   * Poza najechaniem podpowiedzi nie ma, więc nie trzyma warstw bez potrzeby.
   */
  .card:hover .card__media > img,
  .card:hover .card__media > svg { transform: scale(1.045); will-change: transform; }

  /*
   * Krok pod kursorem: kreska grubieje NATYCHMIAST, bez przejścia.
   *
   * Wcześniej wysokość dochodziła przez .18 s i to był błąd w odczuciu, nie
   * w wydajności: reakcja na kursor musi być bezzwłoczna, bo inaczej czyta się
   * jako ospałość strony, a nie jako animację. Rzeczy uruchamiane ruchem myszy
   * mają potwierdzać ruch od razu — animować można to, co dzieje się samo.
   *
   * Kreska zostaje w liście przejść tylko dla przejazdu przy wejściu w kadr,
   * a wysokość jest z niej wyłączona jawnie (0s).
   */
  .js-motion .steps[data-line].is-in .step::before {
    transition: transform .62s var(--ease-glide), height 0s;
  }
  .js-motion .steps[data-line].is-in .step:hover::before { height: 4px; }

  /* i drugi znak, że kursor jest nad krokiem — tytuł, nie tylko kreska u góry */
  .step h3 { transition: color .12s var(--ease-soft); }
  .step:hover h3 { color: var(--accent); }

  /*
   * Wiersz zestawienia pod kursorem: tło i kreska pod spodem w akcencie.
   *
   * Pierwsza wersja miała akcent po lewej, cieniem wewnętrznym — i to był błąd.
   * Komórki tej tabeli mają zerowy odstęp poziomy (taki jest zamysł: kreska
   * biegnie przez całą szerokość kolumny), więc pasek wchodził wprost pod
   * pierwszą literę. Odstęp dodany na czas najechania przesuwałby tekst, więc
   * akcent poszedł tam, gdzie ma miejsce: na kreskę pod wierszem.
   */
  .spectable tr { transition: background-color .2s var(--ease-soft); }
  .spectable th,
  .spectable td { transition: box-shadow .2s var(--ease-soft); }

  .spectable tr:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }

  /*
   * Cieniem wewnętrznym, nie zmianą koloru obramowania: ostatni wiersz ma
   * obramowanie zdjęte (tak wygląda koniec tabeli), więc kolorowanie krawędzi
   * pomijałoby właśnie ten wiersz. Cień działa niezależnie od obramowania
   * i nie zmienia wysokości komórki.
   */
  .spectable tr:hover th,
  .spectable tr:hover td { box-shadow: inset 0 -2px 0 var(--accent); }
}

/* ===========================================================================
   12. Wyłącznik
   ---------------------------------------------------------------------------
   Ustawienie „ogranicz ruch" w systemie wyłącza wszystko powyżej. Treść
   zostaje na swoim miejscu i jest od razu widoczna.
   =========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .js-motion [data-reveal],
  .js-motion [data-reveal].is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .js-motion .title[data-rule]::after,
  .js-motion .display[data-rule]::after { transform: scaleX(1); transition: none; }

  .js-motion .hero__head .line > span,
  .js-motion .hero .lede,
  .js-motion .hero .hero__cta,
  .js-motion .hero__figure,
  .js-motion .hero__figure img,
  .js-motion body { animation: none; }

  .js-motion body.is-leaving { opacity: 1; }

  /* kreska przez kroki i numery kroków: stan docelowy od razu, bez przejazdu */
  .js-motion .steps[data-line] .step::before { transform: scaleX(1); transition: none; }
  .js-motion .steps[data-line] .step__n { color: var(--accent); transition: none; }

  /* wiersze zestawień widoczne od razu */
  .js-motion [data-reveal]:not(.is-in) .spectable tr,
  .js-motion [data-reveal].is-in .spectable tr {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .card:hover,
  .hero__figure:hover,
  .btn:active,
  a:hover > .arrow { transform: none; }

  .card:hover .card__media > img,
  .card:hover .card__media > svg { transform: none; will-change: auto; }

  .step:hover h3 { color: var(--ink); }

  .readbar { display: none; }

  * { scroll-behavior: auto !important; }
}
