---
name: "Parallax landing page"
repo: https://github.com/hoodini/ai-agents-skills
source: https://github.com/hoodini/ai-agents-skills/blob/HEAD/skills/parallax-landing-page/SKILL.md
license: not specified
install: "npx skills add hoodini/ai-agents-skills --skill parallax-landing-page"
page: https://100xlab.com/resources/agent-skills/hoodini-ai-agents-skills--parallax-landing-page--1mktkw3
---

# Parallax Landing Page

Turn a short video into a one-screen cinematic landing page where the user's scroll gesture scrubs through the video frame-by-frame while five dramatic text scenes crossfade in and out. The document never actually scrolls — wheel/touch/keyboard input is intercepted in JS and converted into virtual frame progress.

This skill encodes the patterns proven in the bundled reference at [`../../examples/parasites/`](../../examples/parasites/) (the github / lion / hope landings — three working pages built with this exact skill). Treat that folder as the ground truth if anything here is ambiguous.

## Where this skill sits in the YUV.AI pyramid

`parallax-landing-page` is in the **middle tier** of the YUV.AI skills pyramid, alongside `yuv-design-system`, `yuv-decks`, `yuv-viral-video`, and `video-to-landing-page`. The top-tier orchestrator `yuv-pilot` routes here whenever a 5–15s video clip needs to become a single-hero scroll-scrub landing.

This skill **brings its own visual language** (Anton + Caveat + Inter, gold/amber/accent/cream accent palette) — it does NOT call into `yuv-design-system`. The two systems coexist: yuv-design-system owns the brand palette across web/app/deck surfaces, parallax-landing-page owns the cinematic scroll-scrub aesthetic specifically. For YUV.AI parallax landings, you can OPTIONALLY add a phoenix watermark + the canonical Linktree URL in the footer area as a brand throughline — see `yuv-design-system/assets/` for the watermark file. Do NOT swap the Anton/Caveat type stack for the design system's Neon palette — that would lose the signature look.

## What you're building

Each landing is a single locked viewport with:
- A canvas filling the screen, drawing one frame at a time (cover-fit, DPR ≤ 2)
- 5 absolutely-positioned text scenes (kicker + uppercase Anton headline + Caveat handwritten line), each with its own scroll-progress window
- A fixed nav with `PARALLAX // showcase` logo and links to sibling landings
- A loader that preloads every frame before letting the user interact
- A vignette + film grain overlay
- A bottom scroll-hint that fades out once the user starts scrolling
- A bottom-right frame counter (e.g. `073 / 145`)
- A final scene that *is* the CTA section — no separate `.end` section below

Total weight at q:v 2: roughly 120–220 KB per frame × frame count. A 6-second 24fps clip = 145 frames ≈ 25 MB. A 10-second clip = 241 frames ≈ 53 MB.

## Save location

**Default:** `~/Documents/yuv-projects/landings/<slug>/` — always save standalone landings here so you can find them again. Override only if the user explicitly picks a different location OR if Mode B (integrate into the existing `examples/parasites/` showcase) is selected.

```bash
mkdir -p ~/Documents/yuv-projects/landings
cd ~/Documents/yuv-projects/landings
# The skill creates <slug>/ here as it extracts frames and builds the HTML.
```

Final path (Mode A — standalone): `~/Documents/yuv-projects/landings/<slug>/`.
Final path (Mode B — integrate): the existing showcase directory the user chose.

Tell the user the final path at the end of every build.

---

## Workflow

There are four phases. Don't skip phases — each one feeds the next.

### Phase 1 — Discover

Confirm with the user:
1. **Video path** — where the source MP4 lives.
2. **Slug** — short folder/file name (e.g. `hope`, `marcus`, `desk`). One word, lowercase, no spaces. This becomes both the frame folder name (`<slug>/`) and the HTML filename (`<slug>.html`).
3. **Theme / topic** — what is this landing about? A product launch? A personal moment? A wildlife encounter? You need this to write the copy.
4. **Project mode** — standalone (brand new folder) or integrate into the existing parasites showcase at `examples/parasites/` (top of this repo). If the user says "add it to the showcase", they mean Mode B below.
5. **Accent color** — for the script/CTA highlight: `gold` (#f5b042), `amber` (#ff8a3d), `accent` (pink #ff4d6d), `cream` (#f5e9d4), or something new (add it to `:root` in style.css and write a new `.script.<name>` rule).

If the user hasn't specified some of these, propose defaults and move on — don't stall on questions they can answer once they see the result.

### Phase 2 — Extract frames

Run the bundled extraction script. It probes the video with ffprobe and extracts every frame at native resolution with `-q:v 2` (near-lossless JPEG), naming them `frame-001.jpg` through `frame-NNN.jpg` (3-digit zero-padded).

```bash
python <skill>/scripts/extract_frames.py <video_path> <output_folder>
```

Example:
```bash
python C:/Users/User/.claude/skills/parallax-landing-page/scripts/extract_frames.py \
    C:/Users/User/Documents/parasites/hope/hope.mp4 \
    C:/Users/User/Documents/parasites/hope
```

The script prints a JSON metadata block to stdout. Capture it — you need `frame_count` and `suggested_scroll_budget` for the HTML.

Why `-q:v 2`: ezgif and similar tools re-compress aggressively (~55 KB/frame). Native ffmpeg at q:v 2 gives ~120–220 KB/frame at the same dimensions — visibly sharper. Do not downscale; never upscale (it inflates files without adding detail).

Why the scroll-budget formula (≈26 px per frame): keeps the tactile "pixels of scroll per frame advanced" constant across clip lengths. 145 frames → ~4200, 241 frames → ~6300, 90 frames → ~2500 (floor). The script computes it for you and clamps to `[2500, 8000]`.

### Phase 3 — Author the page

Read the template at `<skill>/assets/landing-page-template.html`. It is a complete working file with `{{PLACEHOLDER}}` markers and `<!-- TODO: -->` comments next to creative bits. Write a new file at `<target>/<slug>.html` with everything substituted.

Substitution checklist (every placeholder must be replaced):

| Placeholder | What to put | Example |
|---|---|---|
| `{{PAGE_TITLE}}` | HTML `<title>` | `Hope — The Fastest Heart in the Negev` |
| `{{PAGE_DESCRIPTION}}` | meta description, one sentence | `A moment with Hope, the cheetah of Midbarium.` |
| `{{SLUG}}` | folder name (no path, no slash) | `hope` |
| `{{FRAME_COUNT}}` | integer frame count | `241` |
| `{{FRAME_COUNTER_TEXT}}` | `001 / NNN` with matching width | `001 / 241` |
| `{{SCROLL_BUDGET}}` | integer from the extractor JSON | `6300` |
| `{{PREVIEW_FRAME_MID}}` | zero-padded middle frame | `121` |
| `{{PREVIEW_FRAME_END}}` | zero-padded last frame | `241` |
| `{{LOADER_TITLE}}` | one word, uppercase | `HOPE` |
| `{{LOADER_SCRIPT}}` | handwritten line ending in `…` | `summoning the runner…` |
| `{{NAV_LINKS_HTML}}` | full `<a>` tag block for nav | see below |
| `{{SCENE_N_*}}` | kicker, headline, handwritten line for each of 5 scenes | see copy guide |
| `{{SCROLL_HINT_TEXT}}` | 2–4 word uppercase prompt | `SCROLL TO RUN` |
| `{{CTA_BUTTONS_HTML}}` | 2–4 `<a class="cta">` tags | see below |
| `{{ACCENT_CLASS}}` | accent color suffix for `.script.X` and `.cta.X` | `amber` |

Read `references/copy-guide.md` for the cinematic copy patterns (kicker phrasing, headline length, handwritten line tone). The copy is where this skill earns its keep — generic copy will sink an otherwise beautiful animation.

### Phase 4 — Wire up assets

If the target folder already has `style.css` and `parallax.js` (showcase integration), do nothing — they're shared. Otherwise copy both from `<skill>/assets/` into the target folder:

```bash
cp <skill>/assets/style.css <target>/style.css
cp <skill>/assets/parallax.js <target>/parallax.js
```

If integrating into an existing showcase, also do the steps in `references/showcase-integration.md` — add a card to `index.html`, add a link to every existing landing's navbar, and add the chain CTA on the previous landing's final scene.

Tell the user how to view:
> Open `<target>/<slug>.html` via `python -m http.server 8000` from the parent folder (not `file://` — Chromium can taint the canvas with local image origins).

## File contract

```
<target>/
├── <slug>/                  # frame folder
│   ├── <slug>.mp4           # the source (preserved)
│   ├── frame-001.jpg
│   ├── frame-002.jpg
│   └── … frame-NNN.jpg
├── <slug>.html              # the landing page
├── style.css                # shared (copy if standalone)
├── parallax.js              # shared (copy if standalone)
└── index.html               # the hub (only if standalone or integration)
```

The frame folder name and the HTML filename MUST share the same slug — the `folder` option in the page's `new ParallaxPage({folder: '<slug>', ...})` call is the literal folder path.

## The ParallaxPage API (already in parallax.js, do not redefine)

```js
new ParallaxPage({
  folder:       '<slug>',     // folder containing frame-001.jpg, etc.
  frameCount:   145,          // integer
  prefix:       'frame-',     // always 'frame-' for this skill
  scrollBudget: 4200,         // pixels of accumulated input for full pass
  // optional: padWidth (default 3), ext (default 'jpg')
})
```

The class auto-attaches `body.scrub-page` (which locks scroll via CSS), preloads all frames, runs an RAF-driven lerp (0.22 factor) toward the scroll-derived target frame, and crossfades scenes whose `data-start`/`data-end` ranges include the current progress.

## Hard constraints (the non-negotiables)

These are baked into the reference and break the experience if violated:

- **5 scenes, fixed progress windows**: `0.00–0.20`, `0.22–0.40`, `0.42–0.60`, `0.62–0.80`, `0.82–1.00`. The 0.02 gap between scenes is intentional — it's a moment of "just the image, no text" between beats. The hero (scene 1) and final (scene 5) use `data-fade="0.30"` and `data-fade="0.18"` respectively so they hold longer at the edges; middle scenes use the default 0.25.
- **No `.end` section** beneath the scrub. The final scene contains the CTAs. The whole page is one locked viewport.
- **Anton + Caveat + Inter** — uppercase Anton for everything bold, Caveat for handwritten accents (gold/amber/cream), Inter for any body lead text. Never substitute, never add a fourth font.
- **Off-white `#f5f1ea`, never `#fff`** — already in CSS as `var(--text)`.
- **Frame names are `frame-NNN.jpg`** zero-padded to the width of the total (3 digits is the standard since clips < 1000 frames; the JS auto-computes counter width from the total).
- **The slug is the folder name AND the HTML filename basename** — `hope/`, `hope.html`. No exceptions; the integration steps assume this.

## Where to look when you get stuck

- The reference showcase bundled in this repo: [`examples/parasites/`](../../examples/parasites/) — github.html, lion.html, hope.html, style.css, parallax.js, index.html. If your output diverges visibly from those, you've drifted; reread.
- `references/copy-guide.md` — how to write the 5 scenes so they actually feel cinematic.
- `references/showcase-integration.md` — exact edits needed when adding to an existing hub.
- `assets/landing-page-template.html` — the canonical structure with placeholders.
- `assets/hub-template.html` — only used when creating a brand-new showcase (3 cards from scratch).

## Two real example invocations

**Standalone:** user has a single clip `desk.mp4` and wants a landing page.
1. Ask for slug + theme. Run extractor on `desk.mp4` → `./desk/frame-NNN.jpg`.
2. Copy `style.css` + `parallax.js` to project root.
3. Author `desk.html` from template. Nav has just one link: itself.
4. Done. Tell user to serve with `python -m http.server`.

**Showcase add:** user adds `hope.mp4` to the existing parasites folder.
1. Slug = `hope`. Extract → `hope/frame-NNN.jpg`. (Reuse existing style.css + parallax.js.)
2. Author `hope.html` from template.
3. Run showcase integration: add a third card to `index.html`, append a `Hope` link to the navbar in `github.html` and `lion.html`, chain the previous landing's final scene with a `Next: Hope →` CTA.
4. If introducing a new accent color, add it to `:root` and add `.script.X` + `.cta.X` rules in `style.css`.

---

## Supporting file: references/copy-guide.md

# Copy Guide — writing the five scenes

The animation is half the work. The copy is the other half. Generic copy will sink an otherwise beautiful landing. Read this before you write a single word of headline.

## The structural rule

Each of the 5 scenes is exactly three lines:

```
[KICKER]            ← 1–3 words, all caps, Anton, letter-spaced
[HEADLINE]          ← 2–4 words, broken across 2 lines with <br>
[HANDWRITTEN LINE]  ← 4–8 words, one full sentence, ends with a soft punctuation
```

The kicker frames the moment ("Chapter 02", "— A True Story —", "01 — Workflow"). The headline is the punch. The handwritten line is the emotional follow-through — the line that earns the trust the headline asked for.

Length matters because the typography is huge. A 6-word headline at 200px breaks the layout. Keep it tight.

## The five-beat arc

This structure is borrowed from short documentary editing — you have ~25 seconds of attention; spend each beat well.

| Scene | Beat | Job |
|---|---|---|
| 1 | **Hook** | Introduce the subject. Name it. State its essence in one phrase. |
| 2 | **Origin** | Where does it come from? What is its world? |
| 3 | **Stakes** | Why does it matter? Where does the story take place? |
| 4 | **Moment** | The personal turn — the speaker's act, the decision, the proximity. |
| 5 | **Resolution + CTA** | What happened. Then the call to action. |

You're not writing about a product. You're writing about a moment that includes a product.

## Voice rules

- **Anton headlines are commands or noun-phrases, never sentences.** "Born to Run." "Built for Flow." "Meet Marcus." Not "He was born to run." The handwritten line is where sentences live.
- **Handwritten lines are colloquial, almost spoken.** "the white lion of the Negev." "an AI that finally ships with you." "and knelt with food in my hands." Lowercase first word; trail off with a period (or `…` only for the loader script).
- **Avoid adjectives in the headline.** The image already shows the adjective. "Beautiful Cheetah" is wasted ink. "Born to Run" lets the image do the work.
- **Specifics beat abstractions.** "Be'er Sheva" beats "the desert". "macOS · Windows · Linux" beats "every platform". "70 mph" beats "fast".

## Three real examples — read these carefully

### GitHub Desktop (product)

```
SCENE 1
— A NEW CHAPTER —
GitHub
Desktop
the new build. native. fast. yours.

SCENE 2
01 — WORKFLOW
Where
Code Lives.
branches, PRs, reviews — at your fingertips.

SCENE 3
02 — PERFORMANCE
Built
For Flow.
native speed. zero friction. all platforms.

SCENE 4
03 — INTELLIGENCE
Copilot
Inside.
an AI that finally ships with you.

SCENE 5 (CTA)
— NOW AVAILABLE —
Ready?
macOS · Windows · Linux.
[ Download for macOS ] [ Windows ] [ Linux ] [ Next: Marcus → ]
```

### Marcus the white lion (personal moment, wildlife)

```
SCENE 1
— A TRUE STORY —
Meet
Marcus.
the white lion of the Negev.

SCENE 2
CHAPTER 01
Born
of the Desert.
a coat like snow over a land that burns.

SCENE 3
CHAPTER 02
Midbarium.
Be'er Sheva.
where wilderness meets wonder.

SCENE 4
CHAPTER 03
I Crossed
the Fence.
and knelt with food in my hands.

SCENE 5
— THE LAST FRAME —
And the
King Ate.
a moment frozen in time.
[ Visit Midbarium ] [ Open in Maps ] [ Next: Hope → ] [ ← Back ]
```

### Hope the cheetah (speed, motion, intimacy)

```
SCENE 1
— A TRUE STORY —
Meet
Hope.
the fastest heart in the Negev.

SCENE 2
CHAPTER 01
Born
to Run.
seventy miles an hour of wild grace.

SCENE 3
CHAPTER 02
Midbarium.
Be'er Sheva.
where the desert opens — she answers.

SCENE 4
CHAPTER 03
I Came
Close.
she looked at me — and let me stay.

SCENE 5
— THE LAST FRAME —
A Second
of Stillness.
with the fastest cat alive.
[ Visit Midbarium ] [ Open in Maps ] [ ← Back to showcase ]
```

## The accent color picks a tone

- `gold` (#f5b042) — warmth, reverence, mythic ("Marcus")
- `amber` (#ff8a3d) — heat, motion, vitality ("Hope")
- `accent` (pink #ff4d6d) — product/tech, modern, slightly playful ("GitHub")
- `cream` (#f5e9d4) — neutral, used inside scenes 2 and 4 for variety so every line isn't the same color

The convention in the references: scenes 1, 3, 5 use the page's signature accent; scenes 2 and 4 use `cream` for visual rhythm. Follow that unless you have a reason not to.

## Loader script tone

The loader sits on screen for about 1–3 seconds while frames preload. The handwritten line beneath the giant title should be a tiny hook — present-tense, ending in `…`.

Good examples (from the reference):
- `loading the new build…`
- `summoning the king of the Negev…`
- `summoning the runner…`

Bad: `Please wait while frames load.` (boring, breaks tone)

## Scroll hint tone

Short. Uppercase. 2–4 words. Often a verb tied to the subject:

- `SCROLL TO PLAY` (GitHub — software metaphor)
- `SCROLL TO ENTER` (Marcus — entering a kingdom)
- `SCROLL TO RUN` (Hope — running with the cheetah)

If you can't find a verb that suits the subject, fall back to `SCROLL TO BEGIN`.

## CTAs in the final scene

2–4 CTAs, the first one solid or accent-colored, the rest outlined. Keep them short — 2–4 words each. End with a chain link (`Next: X →`) if this is part of a showcase, and a back-to-hub link (`← Back to showcase`).

Available CTA classes:
- `cta` — outlined off-white (default)
- `cta solid` — filled off-white on bg
- `cta gold` — outlined gold (good for Midbarium / nature subjects)
- `cta amber` — outlined amber (Hope-style)

If introducing a new accent, add `.cta.<name>` rules to style.css in the same pattern as `.cta.gold`.

---

## Supporting file: references/showcase-integration.md

# Showcase Integration — adding a new landing to an existing project

Use this when the target folder already has an `index.html` (hub) and at least one other landing (e.g. `github.html` + `lion.html`). You're not creating from scratch — you're growing what's there.

There are four edits, all small. Do them in this order so the user can preview after each step if they want.

## 1. Add a card to `index.html`

Inside `<div class="hub-cards">`, add a new `<a class="hub-card {{slug}}">` block alongside the existing cards. The grid is already `repeat(3, 1fr)` and collapses gracefully — you don't need to touch it.

Pattern (copy and adapt):

```html
<a href="{{slug}}.html" class="hub-card {{slug}}">
  <div class="hub-card-bg" style="background-image: url('{{slug}}/frame-{{mid}}.jpg');"></div>
  <span class="dot"></span>
  <span class="hub-card-arrow">Enter →</span>
  <div class="hub-card-content">
    <span class="kicker">{{NN}} — {{CATEGORY}}</span>
    <h2>{{HEADLINE_LINE_1}}<br />{{HEADLINE_LINE_2}}</h2>
    <p class="script" style="color: var(--{{accent}});">{{HANDWRITTEN_LINE}}</p>
  </div>
</a>
```

Notes:
- `{{mid}}` is the middle frame number (e.g. `121` for a 241-frame clip) — gives the card a more visually arresting still than frame-001.
- `{{NN}}` is the position in the showcase: `01`, `02`, `03`, etc. Match the order they appear.
- `{{CATEGORY}}` is a one-word taxonomy (Product, Wild, Speed, Story, Field, etc.).
- The `style="color: var(--{{accent}});"` on the handwritten line ties the card to its landing page's accent color.

**Also bump the hub copy** to reflect the new count:

```html
<div class="hub-eyebrow">{{N}} Stories · One Scroll</div>
```

```html
<span class="script">{{n}} short films, one page each.</span>
```

Spell `N` as a word ("Three", "Four", "Five") to match the existing typography rhythm.

**Add the preload link** in `<head>`:

```html
<link rel="preload" as="image" href="{{slug}}/frame-{{mid}}.jpg" />
```

## 2. Add a dot accent style to `style.css`

If the new landing uses an existing accent color (`gold`, `amber`), reuse the dot rule. If it's a new color, add both the variable and the dot rule.

Add the color to `:root` (next to the existing `--accent`, `--gold`, `--amber`):

```css
--{{newcolor}}: #HEXVAL;
```

Add the dot rule right after the existing `.hub-card.lion .dot` / `.hub-card.hope .dot` lines:

```css
.hub-card.{{slug}} .dot {
  background: var(--{{newcolor}});
  box-shadow: 0 0 0 4px rgba(R, G, B, 0.18);
}
```

The RGB triplet should match the hex. (E.g., `#ff8a3d` → `255, 138, 61`.)

And if you want the handwritten lines on that landing to use this color, also add:

```css
.script.{{newcolor}} { color: var(--{{newcolor}}); }
.cta.{{newcolor}}    { border-color: var(--{{newcolor}}); color: var(--{{newcolor}}); }
.cta.{{newcolor}}:hover { background: var(--{{newcolor}}); color: var(--bg); }
```

## 3. Add the new landing to every existing nav

In `index.html`, `github.html`, `lion.html`, and any other landing, find the `.nav-links` block and add:

```html
<a href="{{slug}}.html">{{Title}}</a>
```

On the new landing's own page, the link gets `class="active"`. On every other page, no class.

Maintain a stable order across all pages — easiest is alphabetical or "by show-order" (i.e. matching the hub card order). Either is fine; just be consistent so the nav doesn't jitter as the user clicks around.

## 4. Chain the previous landing's final scene

In the immediately previous landing (the one with the highest existing number), update the `end-actions` block inside its scene 5 to add a chain link to the new landing.

Before:
```html
<div class="end-actions">
  <a class="cta gold" href="...">Visit Midbarium</a>
  <a class="cta" href="...">Open in Maps</a>
  <a class="cta" href="index.html">← Back to showcase</a>
</div>
```

After:
```html
<div class="end-actions">
  <a class="cta gold" href="...">Visit Midbarium</a>
  <a class="cta" href="...">Open in Maps</a>
  <a class="cta" href="{{slug}}.html">Next: {{Title}} →</a>
  <a class="cta" href="index.html">← Back to showcase</a>
</div>
```

On the new landing's own scene 5 CTA block, end with `← Back to showcase` and (if appropriate) a chain forward to whatever you'd want next.

## Verification checklist

After all four edits, the user should be able to:

1. Open `index.html` → see N cards in the grid, all with the right backgrounds and accent dots.
2. Click each card → land on the right page, with the loader title matching.
3. Use the nav from any page → reach any other page; the active page is underlined.
4. Scroll to the end of any non-final landing → see a `Next →` CTA pointing to a real page.
5. Scroll to the end of the new landing → see the back-to-showcase CTA.

If any of those breaks, you missed an edit. Re-check `nav-links` parity across files.

## Common slips to avoid

- **Forgetting to add the slug to the preload list in index.html.** Cards without a preload still load, just a beat later.
- **Mixing accent colors on a single card.** The card BG accent (dot + script color) should match the landing's interior accent — otherwise the hub feels disconnected from the page.
- **Reusing a frame number that's the wrong padding.** Always pad to 3 digits (`frame-073.jpg`, not `frame-73.jpg`).
- **Forgetting to set `class="active"` on the current page's nav link.** Subtle but breaks the "where am I" affordance.
- **Adding a new accent variable but forgetting the `.script.<name>` and `.cta.<name>` rules.** The page renders but the accent never appears anywhere except the dot.
