cinematic-scroll-storytelling — Claude Code skill: how to install
Generates scroll-driven landing page animations using Lenis and GSAP ScrollTrigger. It helps structure editorial storytelling, sticky card stacks, and masked typography reveals.
npx skills add MengTo/Skills --skill cinematic-scroll-storytelling -a claude-codeWhat cinematic-scroll-storytelling does
The skill equips coding agents with animation patterns for editorial web pages. Its core workflow integrates GSAP ScrollTrigger and Lenis smooth scrolling. The implementation covers a preloader sequence, parallax hero imagery, word-by-word kinetic text reveals, and stacked project cards scaling from 1 down to 0.92.
Accessibility is handled upfront. The code checks prefers-reduced-motion before binding smooth scroll or triggering transitions. For text splitting, it generates .split-word-mask containers while ignoring links and inline elements to prevent DOM breakages. Lenis settings use lerp: 0.08 and wheelMultiplier: 0.9 to maintain control.
Running this stack requires gsap and lenis from npm. The rules explicitly discourage spring physics, sudden scale jumps, and heavy scroll hijacking. This keeps page navigation functional and readable.
What it helps with
- configure smooth scrolling with Lenis and GSAP ScrollTrigger
- create masked word-by-word text reveals on scroll
- build sticky project card stacks that scale down on progression
- add an intro preloader sequence with a progress indicator
- animate background layers with scroll-driven parallax offsets
Example prompts for the agent
- Build a hero section with parallax image movement using the cinematic-scroll-storytelling pattern.
- Implement word-level text reveals with data-split-reveal masks on scroll.
- Create a sticky project stack section where cards scale from 1 down to 0.92 on scroll.
How to install cinematic-scroll-storytelling
1Skills CLI (Vercel)
npx skills add MengTo/Skills --skill cinematic-scroll-storytelling -a claude-code2Manually: put the files in place
~/.claude/skills/cinematic-scroll-storytelling/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/cinematic-scroll-storytelling/
1Skills CLI (Vercel)
npx skills add MengTo/Skills --skill cinematic-scroll-storytelling -a codex2Manually: put the files in place
.agents/skills/cinematic-scroll-storytelling/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add MengTo/Skills --skill cinematic-scroll-storytelling -a cursor2Manually: put the files in place
.cursor/skills/cinematic-scroll-storytelling/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install MengTo/Skills cinematic-scroll-storytelling2Manually: put the files in place
.github/skills/cinematic-scroll-storytelling/SKILL.md1Skills CLI (Vercel)
npx skills add MengTo/Skills --skill cinematic-scroll-storytelling -a gemini-cli2Platform CLI
gemini skills install https://github.com/MengTo/Skills --path agent-skills/web-design/cinematic-scroll-storytellingManually: put the files in place
.devin/skills/cinematic-scroll-storytelling/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add MengTo/Skills --skill cinematic-scroll-storytelling -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about cinematic-scroll-storytelling
How do I install cinematic-scroll-storytelling in Claude Code?
Run npx skills add MengTo/Skills --skill cinematic-scroll-storytelling -a claude-code.
What does cinematic-scroll-storytelling do?
It provides structured guidance and JavaScript snippets to build scroll-driven editorial websites. Features include Lenis smooth scrolling, GSAP ScrollTrigger setup, text splitting, and sticky cards.
Which dependencies does this skill require?
The code depends on gsap and lenis. You can install both via npm i gsap lenis.
Source text
SKILL.md · MIT
---
name: cinematic-scroll-storytelling
description: Create cinematic scroll-driven landing pages with Lenis smooth scrolling, GSAP ScrollTrigger, scroll-linked progression, staggered text reveals, sticky card stacks, parallax backgrounds, scroll-scrubbed transitions, footer reveals, and immersive preloaders. Use when analyzing or building premium editorial scroll experiences, sticky project stacks, kinetic typography, or section-by-section storytelling.
---
# Cinematic Scroll Storytelling
## Use When
- A page should feel like a premium editorial story that unfolds as the user scrolls.
- The user mentions scroll-driven storytelling, scroll-linked animation, sticky card stacks, parallax, split text, preloader, or cinematic progression.
- A portfolio, studio, product, or landing page needs section-by-section reveals with layered depth.
- The implementation can use GSAP, ScrollTrigger, and Lenis.
## Effect Vocabulary
- Scroll-driven storytelling: sections reveal as a sequence while scrolling.
- Scroll-linked animation: progress is tied directly to scroll with `scrub`.
- Scroll-triggered motion: animation starts when a section enters the viewport.
- Staggered reveal: words, lines, cards, or elements enter with small delays.
- Progressive reveal: opacity, scale, blur, clip, or position changes over scroll progress.
- Sticky card stack: sticky cards layer, scale, and recede as the next card arrives.
- Parallax scrolling: background and foreground layers move at different speeds.
- Scroll scrubbing: animation follows the scrollbar through `scrub: true` or `scrub: 1`.
- Kinetic typography: masked split-text movement, usually word-by-word or line-by-line.
- Preloader: opening loading screen, progress bar, and intro transition.
## Target Feel
- Luxury editorial website.
- High-end creative studio portfolio.
- Apple-level motion polish.
- Modern Awwwards interaction language.
- Immersive cinematic landing page.
Avoid:
- Bounce, elastic, or springy motion.
- Aggressive scale jumps.
- Flashy gaming-style effects.
- Too many simultaneous scroll effects.
- Scroll hijacking that makes the page hard to read.
## Core Stack
```bash
npm i gsap lenis
```
```js
import Lenis from "lenis";
import "lenis/dist/lenis.css";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!reduceMotion) {
const lenis = new Lenis({
lerp: 0.08,
smoothWheel: true,
wheelMultiplier: 0.9,
});
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
}
window.addEventListener("load", () => ScrollTrigger.refresh());
```
## Motion Tokens
- Enter ease: `power3.out` or `power4.out`.
- Scrubbed scenes: `ease: "none"` with `scrub: 0.8` to `1.4`.
- Text reveal duration: `0.8s` to `1.1s`.
- Card reveal duration: `0.9s` to `1.2s`.
- Word stagger: `0.035s` to `0.07s`.
- Line stagger: `0.08s` to `0.14s`.
- Card stagger: `0.06s` to `0.1s`.
- Reveal offset: `y: 24` to `48`.
- Blur: `4px` to `10px`, then `0px`.
- Sticky card scale depth: `1` down to `0.92`.
## Page Anatomy
1. Preloader: black screen, progress bar, brand/title, intro fade.
2. Hero: image parallax, masked headline reveal, subtle scroll cue.
3. Intro: word-by-word kinetic typography.
4. Story sections: scroll-triggered fade-up, blur-in, and clip reveals.
5. Recent Projects: sticky card stack with scale and layered depth.
6. Gallery or proof: scroll-scrubbed horizontal or progressive reveals.
7. Footer: parallax reveal or slow upward handoff.
## Markup Pattern
```html
<div class="preloader" data-preloader>
<div class="preloader__bar" data-preloader-bar></div>
</div>
<main>
<section class="hero" data-parallax-section>
<img data-parallax-layer data-speed="-0.18" src="/hero.jpg" alt="">
<h1 data-split-reveal>Design that unfolds with cinematic restraint.</h1>
</section>
<section data-story-section>
<p data-split-reveal="words">Every block arrives with quiet intent.</p>
</section>
<section class="project-stack" data-sticky-stack>
<article data-stack-card>Project One</article>
<article data-stack-card>Project Two</article>
<article data-stack-card>Project Three</article>
</section>
<footer data-footer-parallax>...</footer>
</main>
```
## Preloader Sequence
Use a preloader to set the cinematic tone, then hand off into the hero reveal.
```js
function initPreloader() {
const loader = document.querySelector("[data-preloader]");
const bar = document.querySelector("[data-preloader-bar]");
if (!loader) return Promise.resolve();
if (reduceMotion) {
loader.remove();
return Promise.resolve();
}
return new Promise((resolve) => {
const tl = gsap.timeline({
defaults: { ease: "power3.out" },
onComplete: () => {
loader.remove();
resolve();
},
});
tl.fromTo(bar, { scaleX: 0, transformOrigin: "left" }, { scaleX: 1, duration: 1.1 })
.to(loader, { yPercent: -100, duration: 0.9, ease: "power4.inOut" }, "+=0.15");
});
}
```
## Split Text Reveal
Use masked overflow containers. Avoid splitting text that contains links or meaningful inline markup.
```js
function splitWords(element) {
if (element.dataset.splitReady === "true") return;
const text = element.textContent || "";
const parts = text.split(/(\s+)/);
element.textContent = "";
element.setAttribute("aria-label", text.trim());
parts.forEach((part) => {
if (!part.trim()) {
element.appendChild(document.createTextNode(part));
return;
}
const mask = document.createElement("span");
const word = document.createElement("span");
mask.className = "split-word-mask";
word.className = "split-word";
word.textContent = part;
mask.setAttribute("aria-hidden", "true");
mask.appendChild(word);
element.appendChild(mask);
});
element.dataset.splitReady = "true";
}
function initSplitReveals() {
if (reduceMotion) {
gsap.set("[data-split-reveal]", { autoAlpha: 1 });
return;
}
gsap.utils.toArray("[data-split-reveal]").forEach((element) => {
splitWords(element);
const words = element.querySelectorAll(".split-word");
gsap.fromTo(
words,
{ yPercent: 110, autoAlpha: 0, filter: "blur(8px)" },
{
yPercent: 0,
autoAlpha: 1,
filter: "blur(0px)",
duration: 0.95,
ease: "power4.out",
stagger: 0.05,
scrollTrigger: {
trigger: element,
start: "top 82%",
once: true,
},
}
);
});
}
```
```css
.split-word-mask {
display: inline-block;
overflow: hidden;
vertical-align: top;
}
.split-word {
display: inline-block;
will-change: transform, opacity, filter;
}
```
## Scroll-Triggered Reveals
Use these for normal sections. They should play once and feel composed, not twitchy.
```js
function initSectionReveals() {
if (reduceMotion) {
gsap.set("[data-story-section], [data-reveal-item]", { autoAlpha: 1, clearProps: "all" });
return;
}
gsap.utils.toArray("[data-story-section]").forEach((section) => {
const items = section.querySelectorAll("[data-reveal-item]");
const targets = items.length ? items : section.children;
gsap.fromTo(
targets,
{ y: 36, autoAlpha: 0, filter: "blur(8px)" },
{
y: 0,
autoAlpha: 1,
filter: "blur(0px)",
duration: 1,
ease: "power4.out",
stagger: 0.08,
scrollTrigger: {
trigger: section,
start: "top 82%",
once: true,
},
}
);
});
}
```
## Scroll-Linked Progression
Use scrubbed timelines for cinematic progression. Keep scrubbed animation linear and let the scroll position do the timing.
```js
function initProgressionScenes() {
if (reduceMotion) return;
gsap.utils.toArray("[data-progress-scene]").forEach((scene) => {
const media = scene.querySelector("[data-progress-media]");
const copy = scene.querySelectorAll("[data-progress-copy]");
gsap.timeline({
scrollTrigger: {
trigger: scene,
start: "top top",
end: "+=140%",
scrub: 1.1,
pin: true,
anticipatePin: 1,
},
})
.fromTo(media, { scale: 1.08 }, { scale: 1, ease: "none" })
.fromTo(copy, { autoAlpha: 0, y: 40 }, { autoAlpha: 1, y: 0, stagger: 0.15, ease: "none" }, 0.15);
});
}
```
## Sticky Card Stack
Use `position: sticky` for layout, and ScrollTrigger for layered scale/depth. Earlier cards should recede as later cards arrive.
```css
[data-sticky-stack] {
position: relative;
}
[data-stack-card] {
position: sticky;
top: 12vh;
transform-origin: center top;
will-change: transform, opacity;
}
```
```js
function initStickyCardStack() {
if (reduceMotion) return;
gsap.utils.toArray("[data-sticky-stack]").forEach((stack) => {
const cards = gsap.utils.toArray(stack.querySelectorAll("[data-stack-card]"));
cards.forEach((card, index) => {
const nextCard = cards[index + 1];
if (!nextCard) return;
gsap.to(card, {
scale: 0.92 + index * 0.015,
autoAlpha: 0.72,
y: -24,
ease: "none",
scrollTrigger: {
trigger: nextCard,
start: "top 78%",
end: "top 24%",
scrub: true,
invalidateOnRefresh: true,
},
});
});
});
}
```
## Parallax
Use parallax for hero images, background layers, and footer reveals. Keep distance small.
```js
function initParallax() {
if (reduceMotion) return;
gsap.utils.toArray("[data-parallax-layer]").forEach((layer) => {
const speed = Number(layer.dataset.speed || -0.16);
const section = layer.closest("[data-parallax-section]") || layer;
gsap.to(layer, {
y: () => window.innerHeight * speed,
ease: "none",
scrollTrigger: {
trigger: section,
start: "top bottom",
end: "bottom top",
scrub: 1,
invalidateOnRefresh: true,
},
});
});
}
```
Footer parallax reveal:
```js
function initFooterReveal() {
if (reduceMotion) return;
const footer = document.querySelector("[data-footer-parallax]");
if (!footer) return;
gsap.fromTo(
footer,
{ yPercent: -12, autoAlpha: 0.85 },
{
yPercent: 0,
autoAlpha: 1,
ease: "none",
scrollTrigger: {
trigger: footer,
start: "top bottom",
end: "top 45%",
scrub: 1,
},
}
);
}
```
## Build Order
1. Build the static page first.
2. Add preloader and hero entrance.
3. Add split text reveals.
4. Add section-by-section reveals.
5. Add sticky card stack progression.
6. Add parallax layers.
7. Add scrubbed pinned scenes only where the story needs them.
8. Add reduced-motion and touch fallbacks.
9. Run browser QA across desktop and mobile.
## Prompt Template
```txt
Create a cinematic scroll-driven landing page with smooth Lenis scrolling, GSAP ScrollTrigger animations, staggered text reveals, sticky card stack progression, parallax backgrounds, scroll-scrubbed transitions, section-by-section storytelling, and an immersive preloader animation. Use layered depth, scaling transitions, progressive opacity changes, and smooth viewport-triggered motion for a premium editorial experience.
```
## QA Checklist
- Content is readable with JavaScript disabled.
- Reduced-motion users see static content and no smooth-scroll layer.
- Scroll-triggered reveals play once.
- Scroll-linked scenes use `scrub` intentionally.
- Sticky cards do not overlap the footer or trap the page.
- Parallax movement stays subtle and does not harm readability.
- Preloader exits reliably even if images load slowly.
- `ScrollTrigger.refresh()` runs after images/fonts/layout shifts.
- Mobile has simplified pinning or no pinning if performance drops.
© MengTo · MIT · MengTo/Skills