# Animowane strony scrollowe z wideo: kompletny przewodnik po tworzeniu

> Jak stworzyć premium stronę z efektem scroll-video (w stylu Apple): kadrowanie w formacie WebP, wygładzanie LERP na Canvasie, remapowanie czasu zatrzymania oraz system edytorski Precision Editorial.

Animated Video Scroll to technika tworzenia kinematograficznych stron typu one-page, w której przewijanie strony bezpośrednio steruje odtwarzaniem materiału wideo. Gdy użytkownik przesuwa stronę za pomocą kółka myszy lub gestu swipe na ekranie smartfona, wideo płynnie zmienia się klatka po klatce, synchronizując tempo z czytaniem i pojawianiem się bloków informacyjnych.

Ten efekt stał się wizytówką prezentacji premium produktów Apple, Tesla, Porsche oraz wiodących studiów projektowych na świecie.

> [!IMPORTANT]
> Ta technika **nie polega na osadzaniu zwykłego odtwarzacza wideo** ani nie wykorzystuje animowanych GIF-ów. W jej podstawie leży sekwencja klatek lekkich obrazów WebP, renderowanych sprzętowo na elemencie `<canvas>` przy użyciu WebGL lub 2D Context, co gwarantuje natychmiastową reakcję na każdy piksel przewijania.

---

## 1. Czym jest strona scrollowa z wideo i jakie są jej zalety

Tradycyjne wideo w tle strony ma dwie istotne wady: żyje własnym życiem, ignorując uwagę czytelnika, oraz generuje wysokie obciążenie sieci i baterii urządzenia mobilnego.

Strona sterowana przewijaniem rozwiązuje ten problem:

- **Pełna kontrola uwagi:** Użytkownik sam wybiera tempo oglądania. Jeśli zatrzyma się na ważnym akapicie, obiekt w kadrze zamiera w odpowiednim kącie widzenia.
- **Brak opóźnień i buforowania:** Plik wideo jest wcześniej dzielony na zoptymalizowane statyczne klatki WebP, więc przeglądarka nie musi wykonywać ciężkiego dekodowania kontenerów MP4 podczas szybkiego przewijania.
- **Synchronizacja narracji:** Tezy tekstowe, etykiety graficzne, metryki liczbowe i specyfikacje techniczne pojawiają się dokładnie wtedy, gdy kamera prezentuje odpowiedni detal obiektu.

### Dopasowanie do typu projektu

| Typ produktu | Dlaczego pasuje wideo scrollowe | Kluczowy fokus w kadrze |
| :--- | :--- | :--- |
| **Samochody i technika** | Podkreśla aerodynamikę, detale podwozia, materiały wnętrza | Płynny ruch kamery wzdłuż nadwozia, zmiana kątów widzenia |
| **Elektronika premium** | Prezentuje precyzyjną konstrukcję, grubość obudowy, optykę | Rozbicie na komponenty (exploded view), makrofotografia |
| **Architektura i nieruchomości** | Tworzy przestrzenne poczucie objętości i oświetlenia | Przelot przez fasadę do wnętrza |
| **Moda i biżuteria** | Pokazuje teksturę tkaniny, grę światła na metalu | Delikatny ruch światła wokół produktu |

---

## 2. Architektura i struktura plików umiejętności

Umiejętność jest zbudowana jako modułowy potok inżynieryjny. Każdy komponent odpowiada za swój izolowany etap pracy:

```text
animated-sites-engine/
├── SKILL.md            # Scenariusz instrukcji: od analizy wideo po layout strony
├── visual-system.md    # System designu Precision Editorial oraz zasady siatki
├── extract_frames.py   # Narzędzie Python do wycinania klatek i optymalizacji WebP
└── evals.json          # Benchmark i kryteria oceny jakości strony
```

### 1. SKILL.md — główna instrukcja i algorytm scenariusza
Opisuje cykl życia tworzenia strony: od analizy technicznej materiału źródłowego za pomocą `ffprobe`, przez strukturację na sensowne rozdziały, obliczenie długości przewijania, aż po złożenie gotowej aplikacji HTML/JS.

### 2. visual-system.md — przewodnik systemu Precision Editorial
Ustala surowe zasady estetyczne: odrzucenie tanich klisz (bezzasadnych neonowych poświat, szklanych paneli i czcionki Playfair), gotowa paleta „Trackline” dla wątków technicznych, 5 podstawowych schematów rozmieszczenia tekstu oraz pary typograficzne.

### 3. extract_frames.py — skrypt ekstrakcji i kompresji klatek
Narzędzie konsolowe napisane w Pythonie. Automatycznie odczytuje metadane wideo przez `ffprobe`, oblicza proporcjonalną liczbę klatek, dzieli osobne strumienie dla Desktop i Mobile w formacie WebP oraz generuje `manifest.json`.

### 4. evals.json — scenariusze weryfikacji jakości
Zawiera testowy przypadek referencyjny (na przykładzie filmu Porsche), który sprawdza płynność LERP, zachowanie canvas przy zmianie rozmiaru okna, dostępność dla klawiatury oraz wagę assetów.

---

## 3. Mechanizm odtwarzania: Canvas, LERP i Dwell-Remap

W sercu działania znajduje się element `<canvas>`, przyklejony na pełny ekran (`position: fixed`). Przewijanie strony jest przeliczane na względny postęp od 0.0 do 1.0, który określa, który dokładnie numer klatki ma być wyświetlany.

```text
Pozycja przewijania (0.0 — 1.0)
          │
          ▼
┌───────────────────────┐
│     Dwell-Remap       │ ──► Sztuczne spowolnienie w pobliżu początków rozdziałów
└──────────┬────────────┘
           │
           ▼
┌───────────────────────┐
│     Filtr LERP        │ ──► targetFrame płynnie dogania pozycję przewijania
└──────────┬────────────┘
           │
           ▼
┌───────────────────────┐
│   HTML5 Canvas 2D     │ ──► ctx.drawImage(loadedImages[frameIndex], ...)
└───────────────────────┘
```

### 1. Wygładzanie przez LERP (Linear Interpolation)

Jeśli przełączanie klatek następowałoby ściśle przy każdym evencie przewijania, animacja byłaby rwana, szczególnie na myszach z dyskretnym kółkiem przewijania.

Dla kinematograficznej płynności stosuje się interpolację liniową: klatka renderowana na ekranie (`currentFrame`) płynnie „dogania” klatkę docelową (`targetFrame`).

```javascript
let currentFrame = 0;
let targetFrame = 0;
const lerpFactor = 0.075; // Коефіцієнт згладжування (0.05 — 0.1)

function renderLoop() {
  // Наближаємо поточний кадр до цільового
  currentFrame += (targetFrame - currentFrame) * lerpFactor;
  
  const frameToDraw = Math.round(currentFrame);
  drawCanvasFrame(frameToDraw);

  requestAnimationFrame(renderLoop);
}

window.addEventListener("scroll", () => {
  const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
  const progress = Math.min(Math.max(window.scrollY / maxScroll, 0), 1);
  targetFrame = progress * (totalFrames - 1);
}, { passive: true });
```

### 2. Spowolnienie przy każdym rozdziale (Dwell-Remap)

Aby użytkownik zdążył przeczytać nagłówek i tekst bez konieczności filigranowego zatrzymywania kółka myszy, stosuje się technikę **Dwell-Remap**. Przy środku każdego rozdziału prędkość przewijania klatek jest sztucznie spowalniana, pozostawiając płynny ruch w przerwach między nimi.

```javascript
function applyDwellRemap(rawProgress, chapterPoints = [0.2, 0.5, 0.8], dwellWidth = 0.08, strength = 0.45) {
  let remapped = rawProgress;
  
  for (const center of chapterPoints) {
    const distance = Math.abs(rawProgress - center);
    if (distance < dwellWidth) {
      // Плавне згасання коефіцієнта швидкості по косинусоїді
      const factor = Math.cos((distance / dwellWidth) * (Math.PI / 2));
      const delta = (rawProgress - center) * (1 - factor * strength);
      remapped = center + delta;
      break;
    }
  }
  return Math.min(Math.max(remapped, 0), 1);
}
```

### 3. Progresywne ładowanie klatek

Aby uniknąć białych błysków i nie czekać na załadowanie całego pakietu (100–150 klatek), stosuje się dwuetapową strategię:

1. **Krytyczny priorytet:** Natychmiast ładują się pierwsze 5 klatek oraz klatki kluczowe dla głównych rozdziałów.
2. **Kolejka w tle:** Pozostałe klatki są pobierane małymi partiami przez `requestIdleCallback` lub kolejkę promise’ów. Jeśli wymagana klatka jest jeszcze w drodze, canvas wyświetla najbliższą już załadowaną.

---

## 4. Proces rozwoju krok po kroku: od wideo do kodu

Cały proces produkcyjny jest podzielony na siedem następujących po sobie kroków:

### Krok 1. Analiza filmu za pomocą ffprobe

Przed wygenerowaniem kodu analizowane są parametry techniczne pliku źródłowego:

```bash
ffprobe -v error -select_streams v:0 \
  -show_entries stream=width,height,duration,r_frame_rate,codec_name \
  -of default=noprint_wrappers=1 input.mp4
```

Tworzony jest arkusz kontaktowy z 6 klatkami referencyjnymi, aby ocenić ruch kamery, położenie obiektu oraz obecność wolnej przestrzeni (negative space) pod tekst.

### Krok 2. Sformułowanie koncepcji designu

Formułowana jest jasna teza:
- **Główny bohater:** Kto lub co jest w centrum uwagi.
- **Cel emocjonalny:** Precyzja techniczna, spokojny luksus, dynamika, medytacyjność.
- **Para fontów:** Kontrastowy nagłówek i inżynieryjny monospace jako akcent.
- **Sygnaturowy zabieg:** Telemetria, zmienny tracking, licznik mikro-klatek.

### Krok 3. Cięcie i optymalizacja klatek

Liczba klatek oraz wysokość przewijania dobierane są zgodnie z czasem trwania wideo:

| Czas trwania wideo | Liczba klatek | Zalecana wysokość scrolla | Docelowa łączna waga (WebP) |
| :--- | :--- | :--- | :--- |
| **0–5 sekund** | 60–80 klatek | 450–550vh | 4–7 MB |
| **5–15 sekund** | 90–130 klatek | 600–750vh | 8–12 MB |
| **15–30 sekund** | 130–180 klatek | 750–900vh | 10–15 MB |
| **30+ sekund** | 180–200 klatek | 900–1000vh | 12–16 MB |

Uruchomienie ekstrakcji przez skrypt Python:

```bash
python3 extract_frames.py \
  --input ./raw/porsche-gt3.mp4 \
  --output ./public/frames \
  --desktop-frames 120 \
  --mobile-frames 80 \
  --quality 62
```

### Krok 4. Scenariusz według rozdziałów

Strona jest dzielona na 4–6 merytorycznych sekcji. Na przykład:
1. **Rozdział 1: Wstęp (0–15%)** — Nazwa modelu, minimalistyczny sylwetka.
2. **Rozdział 2: Aerodynamika (20–40%)** — Fokus na geometrii spojlera i kanałach powietrznych.
3. **Rozdział 3: Układ napędowy (45–65%)** — Prezentacja silnika lub podwozia z danymi telemetrii.
4. **Rozdział 4: Finał i akcja (75–100%)** — Pełne odsłonięcie samochodu i przycisk jazdy próbnej.

### Krok 5. Kompozycja warstw i layout wokół wideo

Tekst nigdy nie powinien zakrywać kluczowych detali w kadrze. W zależności od trajektorii ruchu obiektu, położenie bloków tekstowych płynnie zmienia się od lewej krawędzi do prawej lub jest ustabilizowane w dolnej tercji ekranu.

### Krok 6. Złożenie na bazie szablonu startowego

Wykorzystywany jest solidny szkielet HTML5 z gotową logiką ładowania i wsparciem responsywności, do którego podłączane są indywidualne tokeny kolorów oraz manifest klatek.

### Krok 7. Testowanie i krytyka

> [!TIP]
> Zawsze testuj strony scrollowe przez lokalny serwer HTTP (`python3 -m http.server`), a nie otwierając plik bezpośrednio przez `file:///`. Polityki CORS przeglądarki blokują ładowanie klatek WebP przez `fetch()` przy lokalnym otwarciu plików.

---

## 5. System wizualny: typografia i paleta Trackline

System **Precision Editorial** wymaga rezygnacji z dekoracyjnej przesady na rzecz wyważonego szwajcarskiego layoutu i estetyki instrumentów kontrolnych.

### Kombinacje typograficzne

:::tabs
=== Archivo + IBM Plex Mono (Sportowo-techniczny)
- **Nagłówki:** Archivo (Black, Tight tracking `-0.03em`)
- **Tekst główny:** Archivo (Regular, 15px)
- **Podpisy techniczne i telemetria:** IBM Plex Mono (Medium, 11px, Uppercase)
- **Najlepsze dla:** Samochody, lotnictwo, ciężka inżynieria, mechanizmy zegarkowe.
=== Roboto Flex + Roboto Mono (Nowoczesny inżynieryjny)
- **Nagłówki:** Roboto Flex (Variable width, Bold)
- **Tekst główny:** Roboto (Regular, 16px)
- **Podpisy techniczne:** Roboto Mono (12px, tabular-nums)
- **Najlepsze dla:** Oprogramowanie korporacyjne, systemy robotyczne, sprzęt laboratoryjny.
=== Barlow Condensed + Manrope (Kinematograficzna dynamika)
- **Nagłówki:** Barlow Condensed (Extra Bold, Uppercase)
- **Tekst główny:** Manrope (Medium, 15px)
- **Podpisy techniczne:** Space Mono (11px)
- **Najlepsze dla:** Sporty ekstremalne, e-sport, moda, dropy kolekcji.
=== Anybody + Source Code Pro (Akcent przemysłowy)
- **Nagłówki:** Anybody (Bold, Ultra-expanded)
- **Tekst główny:** Inter (Regular, 15px)
- **Podpisy techniczne:** Source Code Pro (11px)
- **Najlepsze dla:** Biura architektoniczne, koncepcyjne samochody elektryczne.
:::

### Paleta kolorów „Trackline”

Paleta budowana jest na kolorach prawdziwego toru wyścigowego: matowy karbon, zimny aluminium i stonowane akcenty wyścigowe.

```css
:root {
  /* Базові відтінки */
  --color-carbon: #080a09;    /* Глибокий чорний вуглепластик (основний фон) */
  --color-paper: #f1f0e9;     /* Теплий матовий білий (основний текст) */
  --color-alloy: #a8aca8;     /* Металевий алюміній (другорядні підписи) */
  --color-track: #414541;     /* Графітовий асфальт (межі сітки, розділювачі) */
  
  /* Акцентні інженерні маркери */
  --color-petrol: #12372d;    /* Глибокий петролеум для підкладок карток */
  --color-signal: #d9381e;    /* Гоночний червоний (активні стани, алерти) */
  --color-heritage: #c5a059;  /* Золотистий колір для прогрес-бару та таймінгу */
}
```

> [!NOTE]
> Kolory sygnalizacyjne (`--color-signal` oraz `--color-heritage`) są używane punktowo — do oznaczenia aktywnego rozdziału lub wskaźnika pozycji, zajmując nie więcej niż 3% powierzchni ekranu.

---

## 6. Kompozycja warstw, ruch i sygnaturowe zabiegi

Strona konstruowana jest z trzech niezależnych warstw pod względem głębi wizualnej:

```text
┌─────────────────────────────────────────────────────────────┐
│ 3. Warstwa interfejsu: pasek postępu, wskaźnik rozdziału,   │
│    przyciski akcji                                          │
├─────────────────────────────────────────────────────────────┤
│ 2. Warstwa informacyjna: nagłówek, opis, blok telemetrii     │
├─────────────────────────────────────────────────────────────┤
│ 1. Warstwa Canvas: pełnoekranowe wideo z renderowaniem LERP  │
└─────────────────────────────────────────────────────────────┘
```

### 5 wariantów układu tekstu

| Schemat układu | Rozmieszczenie treści | Optymalny scenariusz użycia |
| :--- | :--- | :--- |
| **Masthead Statement** | Gigantyczny nagłówek na cały ekran, wyśrodkowany | Pierwszy ekran otwierający (wprowadzenie do filmu) |
| **Side Margin Annotation** | Wąska kolumna (3–4 cols) po prawej lub lewej stronie | Obiekt porusza się w przeciwnej części kadru |
| **Split Statement** | Nagłówek po lewej, opis techniczny po prawej | Obiekt stabilnie zakotwiczony ściśle po środku |
| **Telemetry Dock** | Poziomy panel na dole z danymi przyrządowymi | Demonstracja dynamiki, prędkości, mocy |
| **Final Callout** | Skupiony blok z jedną główną akcją (CTA) | Ekran końcowy po zakończeniu sekwencji wideo |

### Zasady animacji elementów DOM

Ponieważ samo wideo na canvasie już generuje intensywny ruch, animacja bloków tekstowych powinna być powściągliwa:
- **Pojawienie przez maskę:** Tekst unosi się od dołu przez kontener z `overflow: hidden`.
- **Przesunięcie:** Nie więcej niż 16–24px na osi Y.
- **Timingi:** 600–800ms na wejście rozdziału z krzywą `cubic-bezier(0.16, 1, 0.3, 1)`.
- **Żadnych rozmyć:** Filtr `filter: blur()` jest surowo zabroniony — powoduje przeliczanie warstw przeglądarki i niszczy stabilne 60 FPS.

---

## 7. Antywzorce: rezygnacja z przestarzałych klisz „luksusowych”

Większość szablonowych landing page'y z wideo wygląda tanio przez zestaw typowych klisz designerskich. Oto czego należy całkowicie się wystrzegać:

| Przestarzała klisza | Dlaczego to złe | Profesjonalna alternatywa |
| :--- | :--- | :--- |
| **Playfair Display + cienki grotesk** | Standardowy szablon „pseudo-luksusu” | Precyzyjne groteski: Archivo, Roboto Flex, Manrope |
| **Glassmorphism (matowe szkło)** | Rozmywa wideo w tle, przeciąża GPU | Kontrastowy czysty tekst w wolnej przestrzeni kadru |
| **Pływające cząsteczki i pył** | Odwracają uwagę od realnego obiektu | Czysty canvas bez sztucznego dekoracyjnego śmiecia |
| **Fioletowo-niebieskie neonowe gradienty** | Kojarzą się z szablonowymi landingami krypto | Naturalne kolory, czerpane bezpośrednio z klatek wideo |
| **Custom cursor z opóźnieniem** | Tworzy wrażenie lagów i jest niewygodny w obsłudze | Natywny systemowy kursor ze zrozumiałymi hoverami |

---

## 8. Wydajność, dostępność (A11y) i optymalizacja mobilna

Strony scrollowe z dużą ilością grafiki wymagają rygorystycznej dyscypliny technicznej:

### 1. Ograniczenie Device Pixel Ratio (DPR)
Współczesne smartfony mają ekrany z DPR 3.0 lub 3.5. Renderowanie canvasu w takiej rozdzielczości zwiększa powierzchnię obliczeń niemal 10-krotnie, co natychmiast przegrzewa procesor.

```javascript
// Обмежуємо максимальний DPR значенням 2.0
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = window.innerWidth * dpr;
canvas.height = window.innerHeight * dpr;
ctx.scale(dpr, dpr);
```

### 2. Pauza renderingu dla ukrytych kart
Gdy użytkownik przełącza się na inną kartę, rendering musi natychmiast się zatrzymać:

```javascript
document.addEventListener("visibilitychange", () => {
  if (document.hidden) {
    cancelAnimationFrame(animationFrameId);
  } else {
    requestAnimationFrame(renderLoop);
  }
});
```

### 3. Tryb ograniczonej animacji (prefers-reduced-motion)
Dla osób z wrażliwym aparatem przedsionkowym ciągłe przewijanie klatek może powodować zawroty głowy.

> [!IMPORTANT]
> Jeśli w systemie operacyjnym włączono parametr `prefers-reduced-motion: reduce`, zamiast nieskończonego przewijania klatek strona powinna wyświetlić jeden wysokiej jakości statyczny poster lub uproszczoną galerię slajdów.

```css
@media (prefers-reduced-motion: reduce) {
  .scroll-container {
    height: auto !important;
  }
  canvas {
    display: none !important;
  }
  .static-hero-fallback {
    display: block !important;
  }
}
```

---

## 9. Warsztat praktyczny: tworzenie i uruchamianie projektu

Stwórzmy minimalną działającą strukturę strony scrollowej.

### Krok 1. Generowanie klatek z wideo

Umieść wideo w folderze roboczym i uruchom cięcie:

```bash
mkdir my-scroll-site && cd my-scroll-site
python3 extract_frames.py --input sample.mp4 --output ./frames --desktop-frames 120
```

Skrypt utworzy plik manifestu `frames/manifest.json`:

```json
{
  "totalFrames": 120,
  "desktop": {
    "pathPattern": "frames/desktop/frame-%04d.webp",
    "width": 1920,
    "height": 1080,
    "totalWeightMb": 9.4
  },
  "recommendedScrollVh": 700
}
```

### Krok 2. Struktura plików gotowego projektu

```text
my-scroll-site/
├── index.html              # Struktura HTML i logika odtwarzacza canvas
├── styles.css              # Tokeny Precision Editorial oraz zapytania medialne (media queries)
├── app.js                  # Pętla LERP, remapowanie czasu przytrzymania (dwell-remap) oraz obsługa przewijania
└── frames/
    ├── desktop/            # frame-0001.webp ... frame-0120.webp
    ├── mobile/             # frame-0001.webp ... frame-0080.webp
    └── manifest.json       # Metadane i wymiary
```

### Krok 3. Uruchomienie lokalnego środowiska

Aby poprawnie ładować zasoby, uruchom lokalny serwer HTTP:

```bash
python3 -m http.server 3000
```

Otwórz w przeglądarce adres `http://localhost:3000` i sprawdź płynność ruchu klatek oraz poprawność przełączania rozdziałów.

---

## 10. Podsumowujący checklist gotowości strony scrollowej

Przed releasem projektu zweryfikuj zgodność ze wszystkimi kryteriami inżynieryjnymi i wizualnymi:

### Metryki techniczne
- [x] Łączna waga klatek wersji desktopowej nie przekracza 12 MB (mobilna — do 5 MB).
- [x] Pierwsze 5 klatek ma priorytetowe preloading (`preload`).
- [x] Współczynnik DPR jest ograniczony do wartości `2`, aby zapobiec przegrzewaniu urządzeń mobilnych.
- [x] Pętla `requestAnimationFrame` zatrzymuje się przy przejściu na inną kartę przeglądarki.

### Doświadczenie użytkownika i dostępność
- [x] Zaimplementowano płynne przejścia LERP między klatkami bez ostrych szarpnięć.
- [x] Skonfigurowano Dwell-Remap dla komfortowego czytania tekstu przy każdym rozdziale.
- [x] Obsługiwany jest tryb `prefers-reduced-motion` ze statycznym posterem.
- [x] Minimalny rozmiar wszystkich klikalnych elementów wynosi co najmniej 44×44px.

### Design i estetyka
- [x] Wykorzystano wyraźną inżynieryjną parę fontów bez pseudo-luksusowych klisz.
- [x] Tekst nie zakrywa głównego poruszającego się obiektu w kadrze.
- [x] Kolory interfejsu są bezpośrednio czerpane z materiału filmowego.
- [x] Strona kończy się jednoznacznym wezwaniem do działania (CTA).