npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scroll-flyover

v1.7.0

Published

Claude Code skill + Three.js/WebGL engine for cinematic, scroll-driven fly-through landing pages — no AI image/video generation, zero per-build cost.

Readme

Procedural 3D Scroll Experiences

npm downloads CI

A Three.js/WebGL engine for cinematic, scroll-driven "fly through the world" landing pages — usable two ways: as a standalone library (npm install scroll-flyover three, no Claude involved) in any JS/framework project, or as a Claude Code skill that interviews you and generates a full build end-to-end.

scroll-flyover builds an immersive, scroll-scrubbed "fly through the world" landing page using live Three.js/WebGL — no paid AI image or video generation, no external asset generation services.

As the visitor scrolls, a real 3D camera flies along a spline path from outside each procedurally-built "island" scene into its interior, then continues to the next scene with no cuts — one continuous connected flight. All geometry, materials, lighting and skies are generated by code (primitives + canvas-gradient textures), so there is zero per-build cost and zero external dependency. The result is a stylized, low-poly / geometric diorama world, not photoreal AI-generated art — that trade-off is stated to the user up front, not discovered late.

Example: a real production build

Live demo → — a spiral-ascent tower built with this skill, embedded in a bilingual Next.js portfolio. Six sections, one continuous camera flight, no cuts:

| | | | | --- | --- | --- | | Hero | Sobre mí | Formación | | Trayectoria | Proyectos | Contacto |

references/production-lessons.md documents what this specific build surfaced (host-page CSS traps, pause-state bugs, look-at sign errors) that the original design didn't anticipate.

A second real build: TechInsight → — a tech/science news blog with a dark green tunnel flight through geometric shapes, landing on section cards for each part of the site:

| | | | --- | --- | | Hero | Noticias | | Boletines | Foro | | Explora el contenido | |

Gallery: four more builds, four archetypes

Generated with this skill to demonstrate it isn't tuned to one subject or one camera archetype — each uses a different journey structure and shape language from references/camera-archetypes.md / references/scene-recipes.md. Full runnable source, a local dev server, and the automated reproducibility/WCAG-contrast QA suite for these four live in the separate jasc66/scroll-flyover-demo repo, which depends on this skill's npm package rather than vendoring a copy of the engine — an engine fix here reaches that gallery by bumping its dependency version, not by hand-copying files.

Try all four live →

| | | | --- | --- | | Alta Finca — nature, island hop | Marco Estudio — architecture, corridor | | Alta Finca — coffee farm · low-poly organic · island hop | Marco Estudio — architecture studio · geometric · corridor/tunnel | | Nexo Ops — SaaS, vertical descent | Ala Sneaker — product, orbit showcase | | Nexo Ops — project-mgmt SaaS · geometric · vertical descent | Ala Sneaker — product launch · toy/rounded · orbit showcase |

Building these surfaced four real bugs in references/scrub-engine.js itself (the scroll-pin effect didn't work out of the box, a lost WebGL context froze the flight permanently, adjacent scenes' copy could overlap, copy text could fail WCAG contrast against its own scene) — all fixed; see references/production-lessons.md for what broke and why.

Install

As a library

The scrub engine is importable directly, for any project that wants the scroll/camera machinery without going through Claude Code at all:

npm install scroll-flyover three
import * as THREE from 'three';
import { mountScrollFlyover, makeRng } from 'scroll-flyover';

const handle = mountScrollFlyover(document.querySelector('#world'), {
  palette: { colors: ['#0e1c2b', '#1d3a52', '#c9d6df'], accent: '#e8a33d' },
  seed: 7,
  scenes: [
    {
      eyebrow: 'Chapter one',
      title: 'Arrival',
      body: 'One line of copy per scene.',
      build: (materials, textures, { rng, performance }) => {
        const group = new THREE.Group();
        group.add(new THREE.Mesh(new THREE.IcosahedronGeometry(2, 0), materials[1]));
        return group;
      },
    },
  ],
});

handle.dispose(); // on SPA route change / unmount
  • three is an optional peer dependency, >=0.152.0 (that's where renderer.outputColorSpace landed; verified against 0.152.0 through 0.186.0). It is marked optional so npx scroll-flyover skill installs don't pull it in — install it yourself when using the library entry point.

  • ESM only, browser only. mountScrollFlyover touches window/document on call, so under Next.js import the component with dynamic(..., { ssr: false }).

  • The container must be a normal-flow element, not position: sticky — the engine sets its height and mounts its own sticky wrapper inside. See the comments in references/scrub-engine.js for why.

  • Each scene's build(materials, textures, { rng, performance, shapeLanguage, palette }) returns a THREE.Group. Use the provided rng (never Math.random()) so builds stay reproducible. references/scene-recipes.md has copy-pasteable builders.

  • Scene copy is escaped, not interpolated as HTML — safe to feed from a CMS, but markup in title/body/tags renders literally rather than as tags.

  • A CTA needs a destination (since 1.6.0). A scene's cta is only the label; give it ctaHref for a link or onCta for an in-page action, and the engine renders the element that matches — a real <a> for the first, a <button type="button"> for the second. Setting both is a config error, since no element is both. A cta with neither warns and renders nothing: up to 1.5.1 it painted a button that did nothing at all when pressed.

    { title: 'Arrival', build: buildArrival, cta: 'Get started', ctaHref: '/signup' }
    { title: 'Arrival', build: buildArrival, cta: 'Play',        onCta: (event, { index, scene }) => {…} }
  • The engine's own UI strings (the route rail's aria-labels, the CTA's accessible name, and the scene announcement) default to English. Override them to match the host page's language: labels: { goToScene: (i, total) => `Ir a la escena ${i} de ${total}` }, labels: { ctaInScene: (label, title) => `${label} — ${title}` }, and labels: { sceneAnnouncement: (i, total, title) => `Escena ${i} de ${total}: ${title}` }. A ctaInScene override must keep the visible label at the front, or speaking that label no longer activates the control (WCAG 2.5.3, Label in Name). sceneAnnouncement is spoken by a polite live region when the flight settles on a new scene (since 1.7.0) — keep the position in it, since that is the part that says how far in the visitor is.

  • Theming (opt-in, since 1.2.0): the copy overlay's colors, blur, and radii are inline styles whose values are var(--sf-x, <default>), not literals — set any of these as a CSS custom property on container (or an ancestor, they inherit like color) to retheme without touching the engine file. Untouched, every default reproduces the original hardcoded look exactly.

    | Custom property | Default | Affects | | --- | --- | --- | | --sf-overlay-bg | rgba(10,10,16,0.62) | copy panel background scrim | | --sf-overlay-blur | 6px | copy panel backdrop blur | | --sf-overlay-radius | 12px | copy panel corner radius | | --sf-overlay-padding | 1.1em 1.3em | copy panel padding | | --sf-text-color | #fff | copy panel text color | | --sf-tag-border | rgba(255,255,255,0.5) | tag pill border | | --sf-cta-bg | the scene's palette accent | CTA button background | | --sf-cta-text-color | computed for contrast against the accent | CTA button text | | --sf-cta-radius | 8px | CTA button corner radius | | --sf-cta-font-size | 0.85rem | CTA label size — stated since 1.6.0, because a <button> and an <a> disagree about the default | | --sf-rail-dot | rgba(255,255,255,0.35) | route rail dot (inactive) | | --sf-rail-dot-active | #fff | route rail dot (active) | | --sf-focus-ring | #fff | keyboard focus ring, inner (since 1.7.0) | | --sf-focus-ring-shadow | rgba(0,0,0,0.9) | keyboard focus ring, outer contrast band (since 1.7.0) | | --sf-rail-gutter | 74px | width reserved on the right so copy clears the rail | | --sf-panel-inline | 6% | copy panel inset from the left edge | | --sf-panel-bottom | 10% | copy panel inset from the bottom | | --sf-panel-max-width | 440px | copy panel maximum width | | --sf-title-size | clamp(1.35rem, 5.2vw, 2rem) | scene title font size | | --sf-body-size | clamp(0.9rem, 3.4vw, 1rem) | scene body font size |

    Set --sf-rail-gutter to 0px if you hide the route rail — it exists to keep the copy panel from running underneath the rail's 44px tap targets on narrow screens.

    --sf-cta-bg/--sf-cta-text-color override the WCAG-checked defaults (see "Status" below) — if you set them, you're responsible for the contrast between them.

    The two focus-ring variables are a pair, and the pair is the point: the ring sits over a live 3D scene that can be near-white in one frame and near-black in the next, so a light inner ring wrapped in a dark outer one always has one half contrasting against whatever it lands on. Retheme them together — two rings of similar lightness is the same as having no ring at all on half the flight.

  • Viewport handling (since 1.4.0). The pinned wrapper is 100dvh with a 100vh fallback, so it tracks a mobile URL bar instead of standing taller than the visible area. The scroll length is rebuilt when the viewport width changes — rotation, or a resized window — and the visitor's position in the flight is preserved across that rebuild. Height-only changes are deliberately ignored: on mobile those fire continuously as the URL bar collapses while scrolling, and re-laying out on them would fight the visitor's own scroll. The copy panel and rail also honour env(safe-area-inset-*), so a notch or home indicator does not sit on the copy.

  • Config is validated before anything renders (since 1.5.0). Mistakes in the config object are reported as scroll-flyover: errors naming the property you got wrong, instead of surfacing as a Three.js stack trace — or, worse, as a page that renders black with no error at all. Validated: the container (a querySelector that returned null is the common one), palette.colors, palette.accent (must be hex, because the CTA's text color is picked by measuring its luminance), every scene's build function and its return value, seed (a non-numeric seed silently collapses to 0, making every such build identical), dwellWeight (0 divides by zero and renders nothing), layout's return shape, photos, each scene's CTA (ctaHref/onCta are mutually exclusive, and a destination with no cta label has nothing to render), and labels.goToScene/labels.ctaInScene/labels.sceneAnnouncement. Unrecognised performance/cameraFeel values warn rather than throw, since they still render a correct page. shapeLanguage is deliberately not validated — it is passed straight through to your scene builders, so it may carry a vocabulary of your own.

Exported helpers

Beyond mountScrollFlyover, the engine's deterministic pieces are importable on their own — useful when building custom layouts, or checking your own colors against the same rules the overlay uses. All are pure functions with no DOM or WebGL dependency.

| Export | Signature | What it's for | | --- | --- | --- | | makeRng | (seed?) => () => number | The seeded RNG (mulberry32) every scene builder must use instead of Math.random(). | | relativeLuminance | (hex) => number | WCAG relative luminance. Accepts #rgb and #rrggbb, with or without the #. | | readableTextColor | (hex) => '#000' \| '#fff' | Picks the text color that clears WCAG AA against a background. Worst case is 4.58:1, at the crossover. | | escapeHtml | (value) => string | The escaping applied to all scene copy. | | layoutAnchors | (count, spacing?, arcHeight?) => Vector3[] | The default island-hop scene layout — wrap or replace it via config.layout. | | sceneControlPoints | (anchor, forwardDir, radius) => Vector3[] | The approach/dive/depart triple the camera path is built from. | | buildWorldCurve | (anchors, radius) => CatmullRomCurve3 | Turns a set of anchors into the flight path. | | buildDwellEasing | (sceneCount, dwellWeight?) => (t) => number | The scroll→curve remap that paces the flight. | | nearestDwellCenter | (sceneCount, t) => number | The resting point the prefers-reduced-motion path snaps to. |

As a Claude Code skill

npx scroll-flyover

Copies SKILL.md and references/ into ~/.claude/skills/scroll-flyover. Restart Claude Code (or start a new session) to pick it up.

--dir installs somewhere else instead — the path names the skill's own folder, which receives SKILL.md and references/ directly:

# this project only, so the skill can be committed alongside the code
npx scroll-flyover --dir .claude/skills/scroll-flyover

# anywhere at all
npx scroll-flyover --dir ./vendor/scroll-flyover

--dir makes the files reachable by other agents, but be clear about what that does and does not buy. SKILL.md uses Claude Code's frontmatter format, its Step 1 interview is written around the AskUserQuestion tool, and its Step 8 accessibility audit asks for an accessibility-reviewer subagent — so another agent will not run this as a first-class skill, and those two steps need a human to drive them instead. Everything else is portable: the remaining six steps and all 75KB of references/ are plain Markdown that any agent able to read files on request, or any person, can work from directly.

npx scroll-flyover --help lists the flags.

Or clone the repo directly:

git clone <this-repo-url> ~/.claude/skills/scroll-flyover

Or as a submodule / subtree of an existing skills collection. Claude Code picks up any folder under ~/.claude/skills/ (or a project's .claude/skills/) containing a SKILL.md with the right frontmatter automatically — see SKILL.md for the full skill definition and references/ for the copy-pasteable Three.js patterns it's built from.

What's in here

  • SKILL.md — the skill itself: the interview flow, build steps, and a large Gotchas section for when a build looks basic, jerky, or breaks in a host page.
  • references/scene-recipes.md — primitive/material/canvas-texture building blocks per shape language (low-poly organic, geometric/architectural, toy/rounded).
  • references/camera-path.md — the Catmull-Rom spline, per-scene control points, dwell-time easing, curvature-based banking.
  • references/camera-archetypes.md — six journey structures (island hop, continuous terrain, vertical descent, corridor, orbit showcase, spiral ascent) and when to pick each.
  • references/visual-fidelity.md — the difference between "tutorial demo" and "designed": tone mapping, procedural environment maps, bevels, bloom, shadows.
  • references/materials.md — palette-derived procedural materials (marble, wood, water, rim glow, sky dome) and a per-performance-tier budget.
  • references/ux-principles.md — the cognitive-science backing for the skill's interview scope, scene count, progress rail, and tap targets.
  • references/production-lessons.md — gotchas from a real production build that aren't covered anywhere else: host-page CSS traps that break the scroll illusion (position: sticky + overflow-x), a shared pause-state flag freezing the flight, camera look-at sign traps, easing inertia for smooth transitions, loader emergency exits, keeping a 3D-tuned color separate from its WCAG-checked HTML variant, pointer-events bugs on overlapping opacity-toggled panels, and headless WebGL2 QA flags.
  • references/scrub-engine.js — a portable, config-driven scroll engine (scene graph setup, spline playback, copy fade wiring, route-rail progress indicator, optional user-photo textures, resize/reduced-motion/context-loss handling, mobile performance downgrade). Framework-agnostic; see production-lessons.md for when to reimplement its mount function instead of adapting it as-is (e.g. a host app with its own i18n).
  • references/index-template.html — a minimal standalone page that mounts the engine via a free Three.js CDN import map.
  • references/qa-reproducibility.mjs — automates the SKILL.md Step 8 reload-reproducibility check (Playwright).
  • test/ — unit tests for the engine's deterministic logic (seeded RNG, WCAG contrast picker, dwell easing, camera curve, HTML escaping, config validation). npm test, no browser needed.
  • scripts/serve.mjs (dependency-free static server, because file:// blocks the template's ES module imports), qa.mjs (serves and runs the reproducibility check in one command), check-package.mjs (asserts the published tarball still carries its entry points).

Testing

npm test          # unit tests — pure logic, fast, no browser
npm run qa        # renders references/index-template.html in Chromium, compares reloads
npm run qa:a11y   # drives the overlay by keyboard, checks focus/ARIA/button defaults
npm run serve     # static server, to open the template by hand
npm run check     # what CI runs, minus the browser

npm run qa needs a browser once: npx playwright install chromium.

Every push and pull request runs the unit tests on Node 20 and 22, against both ends of the declared three range (0.152.0 and latest), plus the reproducibility QA, an installer smoke test, and the tarball-contents check. The unit tests exist because this project's failure mode is subtle: a sign flip in sceneControlPoints or a shifted contrast constant still renders a page, still passes a screenshot comparison, and is quietly wrong. Writing them surfaced exactly that — see 1.5.0 in CHANGELOG.md.

Accessibility QA

Which surface is the content (since 1.6.0)

A flyover says everything twice: once painted over the canvas in the copy overlay, and once as real in-flow HTML in the crawlable block. Both used to be exposed to assistive tech, so every scene was announced twice and the page's heading list held two competing copies of it.

The linear block is the content. The overlay is a painting of it. The overlay's copy is aria-hidden; the block is what a screen reader reads, and the engine now inserts it before the visual layer so a linear read reaches the story first.

The tie-breaker is heading navigation. Jumping between headings is how a screen reader user actually moves through a long page, and only the block can offer that: its headings sit in reading order and are all present at once. The overlay's arrive one at a time, gated on scroll position — and a heading you have to scrub a 3D flight to reach is not a structure you can navigate by. So the block wins the role, and npm run qa:a11y asserts it against Chromium's own accessibility tree: one heading per scene, none of them announced twice.

Two consequences worth knowing:

  • Controls stay where they are painted. The CTA is a sibling of the aria-hidden copy, not a descendant of it, so it keeps its place in the accessibility tree and the tab order — aria-hidden over a focusable control leaves a tab stop that assistive tech cannot name. Because the words around it are hidden, its accessible name carries the scene title (labels.ctaInScene).
  • The block holds no controls. A button inside a 1px clipped block would be a tab stop with nothing on screen to show it has focus. Assistive tech reaches a scene's CTA the same way a sighted visitor does — through the route rail, whose dots are labelled "Go to scene N of M" and carry aria-current.

The final QA step (SKILL.md Step 8) invokes an accessibility-reviewer-style agent against the finished build's HTML overlay — the WebGL canvas itself is out of scope (it's a rendered picture, not semantic content), but the copy overlay, CTA and route-rail buttons, the crawlable SEO block, and the prefers-reduced-motion fallback all are. Findings are treated as QA failures to fix before calling a build done, not deferred. This requires the Agent tool, listed in SKILL.md's allowed-tools.

That step is for a build you're making with this skill. The engine's own contrast rule is covered here instead: readableTextColor is asserted to clear the 4.5:1 AA floor across a sweep of several thousand backgrounds, including a dense walk along the grey axis where the worst case actually lives. Spot-checking a handful of colors misses it — that sweep is what caught the AA gap fixed in 1.5.0.

The example gallery in jasc66/scroll-flyover-demo is additionally covered by an automated regression check (npm run qa there, also run in CI on every push/PR) that renders each example's real WebGL scene and measures actual rendered WCAG contrast against the composited result.

Deliberate uniqueness

The single biggest risk with a skill like this isn't a usability bug — it's every build looking like a sibling of the same Three.js tutorial demo, since builds share a small set of shape-language vocabularies and a common primitive toolbox. SKILL.md's closing section addresses this head-on: push the interview past the generic subject category, turn the one non-generic detail into a signature motif repeated across every scene, and deliberately break exactly one visual convention per build. This is a lens applied during the interview and scene-building steps, not a separate pipeline stage.

Status

  • The skill itself (SKILL.md + references/) is stable and has two kinds of evidence behind it: one real production build (the live portfolio linked above, hand-integrated into a bilingual Next.js/React app) and a 4-example gallery spanning nature, architecture, SaaS, and product-launch archetypes — built to demonstrate the skill generalizes past that one production build. This repo keeps only the gallery's preview screenshots (examples/); the runnable source lives in jasc66/scroll-flyover-demo.
  • references/scrub-engine.js has had real defects found and fixed by actually running builds through Playwright rather than eyeballing one scroll position — see references/production-lessons.md for what broke and why — and, since 1.5.0, by a unit suite over its deterministic logic. Both methods have paid: Playwright caught a WCAG contrast bug in the copy overlay and CTA button, and the unit suite then caught two more the screenshots could not (a shorthand-hex parse failure and a text color that fell 0.4 short of AA near the crossover). Every build made with the current engine file gets those fixes for free.
  • Automated checks run in this repo, on every push and pull request — unit tests on Node 20 and 22 across both ends of the supported three range, the Playwright reproducibility QA against the real shipped template, an installer smoke test, and a check that the published tarball still carries its entry points. An earlier workflow here tested the example gallery and left with it when that gallery moved to the demo repo; nothing covered the engine itself until now, which meant an engine regression could reach npm before anything noticed.
  • references/production-lessons.md is the running list of what production use surfaces that the original design didn't anticipate, and grows with each new build.
  • All four gallery examples are deployed live at scroll-flyover-demo.vercel.app — no clone/build step needed to see them. That repo depends on this skill's npm package (rather than vendoring a copy of the engine) and runs the reproducibility and WCAG-contrast checks in CI on every push/PR, so an engine regression of either kind fails automatically instead of waiting for someone to notice by eye — but only once the demo repo's pinned version is bumped and re-synced; it does not update itself automatically when this repo changes.
  • Released versions are documented in CHANGELOG.md, and every published version has a matching vX.Y.Z git tag pointing at the tree that was actually cut. Semver here covers the library entry point and the bin installer; builds generated through SKILL.md vendor a frozen copy of the engine, so they are unaffected by an upgrade until they are regenerated.

Contributing

Bug reports and pull requests are welcome — see CONTRIBUTING.md. If you are reporting a bug, the two facts worth including before anything else are which version or commit you have and whether you got a real WebGL2 scene or the static fallback: the engine degrades silently between the two, so the same description can mean two unrelated problems. The bug report form asks for both.

Author

Built and maintained by Alonso Salguero C.alonso-portafolio-scrollflyover.vercel.app, which is itself a flyover built with this skill (the six screenshots at the top of this README are that site) and the build references/production-lessons.md comes from.

License

MIT