Build rich, scroll-controlled real-time Three.js experiences as one persistent 3D world whose camera, lighting, atmosphere, materials, objects, DOM story, and interactions evolve across authored chapters. Use for 3D scrollytelling, scroll-driven WebGL worlds, camera journeys, interactive portfolios, product stories, exhibitions, explainers, game or film microsites, spatial narratives, and multi-scene websites where native scroll should travel through one continuous place. Not limited to landing pages.
71
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Build one detailed real-time world and use native document scroll as its deterministic conductor. Keep the renderer, scene graph, and spatial continuity alive while camera composition, light, fog, animation, copy, and interaction focus move through authored chapters.
The mechanism is one persistent Three.js world + one normalized reversible scroll state. If removing either makes the experience collapse into stacked sections, this skill applies.
The exact Kage demo proves the quality bar; it is staging, not a mandatory subject or layout. Use its detailed anatomy only when the requested direction benefits from it: references/kage-anatomy.md.
threejs for a single interactive scene with no scroll-authored journey.scroll-world-storytelling when deciding between real-time 3D, pre-rendered video, and DOM-first storytelling.scroll-scrubbed-visual-sequence for a video or image sequence whose time is scrubbed by scroll.cinematic-scroll-storytelling for DOM-first GSAP/Lenis choreography.Do not disguise a video as Three.js. The public oso95/scroll-world project, reviewed at commit 71cc36d, is a strong reference for intake, scene ledgers, budget gates, mobile-specific composition, config-driven playback, and seam QA, but its renderer is a pre-generated video chain. This skill adopts those structural strengths while retaining real geometry, materials, lighting, raycasting, and camera control.
Before implementing a new world, read:
Copy references/scroll-conductor.js when a project needs a portable native-scroll conductor rather than a framework-specific implementation.
Determine what the world is for: a landing page, portfolio, museum chapter, product explanation, game teaser, editorial essay, data story, or another spatial experience. Do not force every request into a hero-plus-CTA layout.
Discover only what is unknown:
If the user supplied an approved reference or working scene, inspect it first and carry its real geometry, materials, textures, light ratios, camera values, and motion constants into the ledger. Do not replace measured details with adjectives.
Define one reusable visual grammar:
Every visible detail must support the same world. Random noise, unrelated texture packs, arbitrary particle effects, and one-off materials create complexity without cohesion.
Use 4–8 chapters for most experiences. Store the full contract as data rather than scattering thresholds through CSS and the render loop:
const chapters = [
{
id: "threshold",
scrollWeight: 1.25,
copy: {
eyebrow: "Chapter 01",
title: "Enter the archive",
body: "A spatial collection revealed through motion."
},
camera: {
position: [0, 3.8, 13.5],
target: [0, 2.4, -8],
fov: 38,
mobile: { position: [0, 4.8, 18], fov: 46 }
},
world: {
key: 1.0,
practicals: 0.35,
fog: 0.018,
particles: 0.25,
grade: "cold"
},
focus: ["gate", "lantern-left"],
interactions: ["inspect-gate"],
assets: ["gate-shell", "stone-1k", "mist-atlas"]
}
];For each chapter record the story beat, landmark, camera endpoint, occluders, light/fog state, active animation clips, interaction targets, DOM beat, foreground treatment, asset dependencies, and responsive override. Reject a chapter that differs only by copy.
Create these systems once:
WebGL canvas
worldRoot
environment sky, terrain, distant silhouettes
architecture persistent landmarks and paths
chapterSets local props grouped for culling and loading
interactives raycast targets and animation state
atmosphere fog volumes, particles, weather
nearPlane3D optional camera-relative depth accents
DOM above canvas
semantic chapters headings, copy, links, media, fallback order
fixed interface progress, chapter navigation, controls
fixed cut-outs optional alpha foregrounds at viewport edgesUse one renderer and normally one scene. Use layers or render passes when transparency, post-processing, or interaction isolation requires them. Do not instantiate one renderer per chapter or rebuild the world at seams.
Choose a topology deliberately:
Hide unavoidable discontinuities behind occlusion, darkness, dense atmosphere, an interior threshold, or a close foreground pass. Never let an object visibly teleport in open space.
Use a coherent PBR surface stack where it improves the image:
| map | role | common failure |
|---|---|---|
| base color | material identity and broad variation | baked highlights fight live lighting |
| normal | small directional relief | strength too high makes rubber or foil |
| roughness | controls highlight breakup and age | flat values make every object plastic |
| AO | contact and crevice grounding | multiplied too heavily makes dirty seams |
| metalness | separates conductors from dielectrics | gray values everywhere create implausible mud |
| emissive | practical lights, screens, runes | replaces light but does not illuminate nearby forms |
| alpha/transmission | foliage, cloth, glass, mist | sorting, overdraw, and depth artifacts |
Record the detailed surface and light plan in the ledgers from references/world-bible.md.
Measure stable section anchors only after fonts and critical media settle. Convert scrollY into a fractional chapter value such as 2.35.
Keep separate values:
rig.target = progressFromScroll(scrollY); // exact reproducible story state
rig.smooth = reduceMotion
? rig.target
: damp(rig.smooth, rig.target, 5.2, dt); // cinematic render stateUse exact progress for navigation, URLs, accessibility, foreground ownership, and interaction gating. Use smoothed progress for camera and visual interpolation only. The same scroll position must recreate the same state forward, backward, after a fast jump, and after reload.
Use references/scroll-conductor.js as the baseline implementation. Keep native scroll as the source of truth; never integrate wheel delta into story position.
Compose every chapter endpoint at desktop and mobile before interpolating.
scrollWeight to give important views more dwell; do not distort the camera path just to slow a section.Camera motion must expose new spatial relationships: approach, reveal, passage, scale change, inspection, horizon, departure. Six dolly-ins aimed at the same center are not six scenes.
Resolve the adjacent chapters once per frame and interpolate their declared values:
const { a, b, t } = segmentState(rig.smooth);
moon.scale.setScalar(lerp(a.world.moon, b.world.moon, t));
key.intensity = lerp(a.world.key, b.world.key, t);
scene.fog.density = lerp(a.world.fog, b.world.fog, t);Prefer continuous physical change: occlusion, material blend, animation mixer weight, light, fog, scale, and transform. When swapping assets, crossfade only within an occluded or atmospherically dense interval and keep both states loaded until the transition completes.
Scroll owns macro movement. Pointer, touch, and keyboard interactions own local response.
idle, hover, focused, active, and unavailable states for each interactive object.Write the interaction matrix before implementation; use the pattern in references/world-bible.md.
Keep headings, body copy, links, controls, and the footer in real HTML above or beside the canvas. The 3D world creates place; the DOM carries meaning and fallback order.
Make the first authored frame complete before loading the entire journey.
Do not hide a 40 MB world behind a decorative percentage. Record the load plan in the asset ledger.
Start with these budgets, then profile on the actual target device:
| budget | mobile target | desktop target |
|---|---|---|
| DPR cap | 1.25–1.5 | 1.5–2 |
| visible triangles | 150k–300k | 500k–1.2m |
| draw calls | 50–90 | 90–160 |
| shadowed lights | 1–2 | 2–4 |
| simultaneously blended full-screen layers | 2 | 3 |
| critical initial transfer | 3–6 MB | 5–10 MB |
| steady frame time | ≤16.7 ms ideal, ≤25 ms fallback | ≤16.7 ms |
These are starting envelopes, not success claims. Measure CPU, GPU, texture memory, shader compilation, long tasks, and first-interactive frame.
dt near 1/30 second after stalls.document.hidden and when the experience is not visible.Follow references/quality-and-qa.md. At minimum verify:
Use the Codex browser for visual and interaction verification. Build/DOM checks are not visual proof.
Return:
Keep the bundled Kage demo unchanged unless the user explicitly asks to revise the reference itself.
798db0a
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.