Communitygithub.com

MaybeIceloveU2/scroll-zoom-reveal

Cinematic effects for websites. (1) Scroll zoom reveal - a full-screen scene (render, game shot, video) is pinned while you scroll, then the camera pulls back to show it running inside an app on a laptop/device screen; copy fades in below, followed by a tabbed crossfade gallery. (2) OLED pixel field - a cursor-reactive background of tiny RGB sub-pixels where moving the mouse leaves a glowing, flowing, color-shifting trail (click = ripple). Use when building a landing page, product page, portfolio or game/3D/hardware site and the user wants an Apple-style zoom-out reveal, a "scene shrinks into a laptop screen" effect, a pinned sticky scroll scene, a scroll-scrubbed hero, "that AAA games animation", a cursor/hover glow trail, an interactive pixel/LED/OLED grid background, or "the OLED hover effect". Vanilla JS + CSS + WebGL, no dependencies, works in plain HTML, React, Next.js, Vue, Astro.

What is scroll-zoom-reveal?

scroll-zoom-reveal is a Cursor agent skill that cinematic effects for websites. (1) Scroll zoom reveal - a full-screen scene (render, game shot, video) is pinned while you scroll, then the camera pulls back to show it running inside an app on a laptop/device screen; copy fades in below, followed by a tabbed crossfade gallery. (2) OLED pixel field - a cursor-reactive background of tiny RGB sub-pixels where moving the mouse leaves a glowing, flowing, color-shifting trail (click = ripple). Use when building a landing page, product page, portfolio or game/3D/hardware site and the user wants an Apple-style zoom-out reveal, a "scene shrinks into a laptop screen" effect, a pinned sticky scroll scene, a scroll-scrubbed hero, "that AAA games animation", a cursor/hover glow trail, an interactive pixel/LED/OLED grid background, or "the OLED hover effect". Vanilla JS + CSS + WebGL, no dependencies, works in plain HTML, React, Next.js, Vue, Astro.

Works with~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/MaybeIceloveU2/scroll-zoom-reveal/tree/HEAD

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Preview

From the skill README

scroll-zoom-reveal preview

Documentation

Scroll Zoom Reveal

A drop-in, dependency-free scroll effect:

  1. Pinned full-bleed scene: a headline sits over a dimmed scene that fills the viewport.
  2. Dolly + pull-back: the scene settles slightly, then the "camera" pulls back. The scene turns out to be on a laptop display (the bezel and base come into view).
  3. App reveal: editor panels (toolbar, outliner, properties, timeline) slide in and the scene shrinks into its viewport panel.
  4. Copy fade-in below (data-zr-fade), then a tabbed crossfade gallery with a sliding pill toggle.

All ready-to-use files are in assets/ next to this file:

FilePurpose
assets/zoom-reveal.cssAll styles (theme tokens on .zr)
assets/zoom-reveal.jsComponent + fades + gallery. Exposes window.ZoomReveal, auto-inits [data-zoom-reveal] / [data-zr-gallery] / [data-zr-fade]
assets/oled-field.jsOptional. Cursor-reactive OLED sub-pixel field (window.OledField, auto-inits [data-zr-oled]). Styles are in zoom-reveal.css
assets/placeholder-scenes.jsOptional. Procedurally paints original placeholder art on canvas (ZRScenes.drawCity, ZRScenes.drawCanyon) until real images exist
assets/demo.htmlComplete working page. Start from this markup.

How to use it on a site

  1. Copy the assets into the project (e.g. public/zoom-reveal/ or src/vendor/zoom-reveal/). Copy placeholder-scenes.js only if there's no real artwork yet.
  2. Read assets/demo.html and copy the <section class="zr" data-zoom-reveal>…</section> block (plus the gallery block if wanted). Keep the class structure exactly:
section.zr[data-zoom-reveal]          ← scroll length lives here (--zr-length)
  .zr-sticky                          ← pinned 100vh stage
    .zr-glow                          (optional ambient glow)
    .zr-device                        ← scaled by JS
      .zr-lid > .zr-display
        .zr-viewport > .zr-media > img | video | canvas   ← YOUR SCENE
        .zr-chrome > .zr-panel.zr-toolbar / .zr-left / .zr-right / .zr-bottom  (optional app UI)
      .zr-base                        (laptop base; delete for a monitor/tablet)
    .zr-scrim + header.zr-intro       ← headline over the scene, fades out first
    .zr-caption                       ← small caption, fades in at the end
  1. Load zoom-reveal.css in <head> and zoom-reveal.js at the end of <body>.
  2. Swap the media: replace the demo canvas with the real scene:
    • Image: <img src="scene.avif" alt="" decoding="async">. Export it at at least the viewport size × DPR (2560–3200px wide). The effect starts with the image filling the whole screen.
    • Scroll-scrubbed video: <video data-zr-scrub muted playsinline preload="auto" src="scene.mp4"></video>. JS maps scroll progress to currentTime. Encode with every frame a keyframe so seeking stays smooth: ffmpeg -i in.mov -c:v libx264 -g 1 -crf 22 -an -movflags +faststart scene.mp4.
  3. Rewrite all copy (eyebrow, headline, caption, panel labels, file name, gallery labels) to fit the site. Never ship the demo's fictional "Orbit / Forge" text.
  4. Theme with the tokens on .zr: --zr-bg, --zr-fg, --zr-muted, --zr-accent, --zr-length (scroll distance, default 360vh).

Options

Pass JSON in the attribute, e.g. data-zoom-reveal='{"maxWidth":1000,"viewport":{"top":0,"right":0,"bottom":0,"left":0}}', or construct it in JS: new ZoomReveal(el, options).

OptionDefaultMeaning
maxWidth1180Max final display width (px)
widthRatio / heightRatio0.82 / 0.64Final display size relative to the viewport
aspect0.625Display height ÷ width (16:10). Phone ≈ 2.16, ultrawide ≈ 0.42
overscan1.04How far past the viewport the display starts (hides the bezel)
dolly1.14Starting inner zoom of the media; settles to 1
lift0.035How much the device rises at the end
timelinesee JS[start,end] progress windows for intro, dolly, zoom, chrome, caption
viewport{top:6.5,right:21,bottom:22,left:15}Final insets (% of display) of the scene panel inside the app UI. Set all to 0 and delete .zr-chrome for a pure "scene → device screen" pull-back with no app UI
onProgress—fn(p) callback with progress 0..1

Events: the section dispatches zr:resize with {width, height, finalWidth}, the pixel size the media is laid out at. Use it to size canvases or choose an image source.

React / Next.js

Import the CSS globally. Drop data-zoom-reveal from the markup and construct it manually so it's tied to the component lifecycle ('use client' in Next):

useEffect(() => {
  import('./zoom-reveal/zoom-reveal.js').then(() => {  // registers window.ZoomReveal
    const zr = new window.ZoomReveal(ref.current, { maxWidth: 1100 });
    const gal = galRef.current && new window.ZoomReveal.Gallery(galRef.current);
    window.ZoomReveal.initFades();
    cleanup = () => zr.destroy();
  });
  let cleanup = () => {};
  return () => cleanup();
}, []);

Convert class → className and style="--v:78%" → style={{'--v':'78%'}}.

OLED pixel field (cursor hover effect)

A full-bleed background of tiny RGB sub-pixel bars, dimly lit. Moving the cursor stirs a small fluid simulation. Pixels along the path light up, grow and shift color from violet to cyan to lime to warm white, then the glow drifts in the cursor's direction, spreads and fades. A click or tap sends a ripple. When the cursor is idle (and on touch screens), a slow "ghost cursor" keeps it alive. Rendering is a WebGL1 fragment shader, the simulation is a small CPU grid, and it only runs while the section is on screen. Without WebGL it falls back to a CSS spotlight.

Use it behind a specs/stats block, a hero, or a CTA band. Any element works: add class="zr-oled" data-zr-oled, put content inside, load zoom-reveal.css + oled-field.js. The component inserts the canvas itself. See the "OLED PIXEL FIELD" block in assets/demo.html.

<section class="zr-oled" data-zr-oled='{"cell":7}'>
  <div class="your-content">…stats / headline…</div>
</section>
<script src="oled-field.js"></script>
OptionDefaultMeaning
cell6CSS px per pixel (3 sub-pixel bars). 5–9 looks right; use larger on mobile for performance
radius2.4Brush radius in cells
strength0.55How much light the cursor adds. Defaults are deliberately subtle; 1 is bold
decay0.94Per-frame fade; higher = longer trails (0.92 short … 0.97 long)
flow0.55How far the glow drifts along the cursor's direction
diffusion0.35How fast the glow spreads
base0.09Brightness of the unlit grid
palette['#5a3cff','#1fd6ff','#b6ff4a','#fff4d8']4 colors, low → high energy. Match the brand, e.g. warm ['#ff3c5a','#ff9a1f','#ffe14a','#fff8e8']
ambient / ripplestrueGhost cursor while idle / click ripple

For a bolder look (closer to a showcase effect) use {"radius":3.2,"strength":1,"decay":0.962,"flow":0.9}. Keep the subtle defaults behind text-heavy sections.

API: const f = new OledField(el, opts), f.pulse(clientX, clientY) (trigger a ripple from code), f.destroy() (call in React cleanup). Give text over the field a soft dark text-shadow so it stays readable over bright trails.

Gotchas (check these before you finish)

  • Sticky breaks if any ancestor of .zr has overflow: hidden/auto/clip or a transform. Use overflow-x: clip on body if horizontal overflow must be hidden.
  • Don't wrap .zr in smooth-scroll libraries that transform the page (Locomotive v4 etc.). Lenis is fine.
  • The device is laid out big and scaled down, so media stays sharp. Don't put fixed-px sizes inside .zr-device: use em (1em = 1% of the display width).
  • Reduced motion is handled: the section un-pins and shows the final state; the gallery stops autoplaying; the OLED field keeps reacting to the cursor but drops the ghost cursor and ripples.
  • Animations pause in background tabs (requestAnimationFrame stops). When testing the OLED field in a browser pane, keep the page visible or it will look frozen.
  • Below ~500px wide the laptop is small. Consider {"widthRatio":0.92} or aspect matched to a phone frame on mobile.
  • Test by scrolling slowly from top to bottom in the browser at desktop and mobile widths. Confirm the headline fades before the pull-back starts, no bezel shows at the start, and the caption doesn't overlap the base.
  • Use original or licensed artwork and copy. This recreates the motion technique; don't copy another brand's images, product renders or text.

Related Skills