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

@joinamply/motion

v0.2.0

Published

Attribute-driven scroll animations for Webflow. Waits on the Amply library manager for GSAP; ships from jsDelivr as one script tag.

Readme

Amply Motion

Attribute-driven scroll animations for Webflow. [anim-group] on a wrapper, [anim="type"] on each element — no timeline to wire, no Interactions panel.

Sunset when Webflow shipped ix3; back because ix3 didn't work out. The difference this time is delivery: it ships from a CDN as one script tag instead of a 300-line pasted embed, so a bug fix is a version bump rather than an edit on every client site.

Internal — strip on duplication. Like framework-app/ and input-enhancer/, this directory does not ship to clients. Delete it when the starter is duplicated. Client sites consume the published package from jsDelivr; they never need the source.

Install on a page

Two embeds in Project Core, then the toggles:

| | Where | What | |---|---|---| | 1 | 🎨 Styles | webflow/styles-embed.html — the pre-hide CSS | | 2 | ⚡ Codes | webflow/codes-embed.html — the script tag | | 3 | toggles | GSAP → Main + ScrollTrigger, plus SplitText for text-fill |

The CSS stays on the page and never moves to the CDN. It is render-blocking-critical: it is what hides an element before GSAP can reach it. Served over the network it would flash the final content, or — if the request failed — hide copy permanently. The JS is allowed to be late; the CSS is not.

Animation types

| | | |---|---| | Reveals | slide-up slide-up-growing slide-down slide-left slide-right grow-in grow-out fade-in fade-out | | Scrubbed | parallax-up parallax-down parallax-left parallax-right · text-fill | | Other | counter |

<div anim-group anim-stagger="0.1">
  <h2 anim="slide-up">Headline</h2>
  <p  anim="slide-up" anim-delay="0.1">Body copy</p>
  <a  anim="fade-in"  anim-order="3">Call to action</a>
</div>

Attributes

| Attribute | On | Default | Does | |---|---|---|---| | anim | element | — | the animation type | | anim-group | wrapper | — | children stagger in on one timeline | | anim-duration | element | 0.8 | seconds | | anim-delay | element | 0 | seconds | | anim-ease | element | power3.out | any GSAP ease | | anim-distance | element | 40 (100 parallax) | px travelled | | anim-stagger | group | 0.12 | seconds between children | | anim-order | element | DOM order | explicit sequence; equal values start together | | anim-once | group | true | "false" replays on scroll-back | | anim-start | either | top 80% | ScrollTrigger start | | anim-end | element | varies | ScrollTrigger end (scrubbed types) | | anim-scrub | element | 2 | parallax catch-up, seconds |

anim-once goes on the wrapper, not the children — the group's timeline owns the toggle.

Counters read their target from their own text, then count to it: counter-duration, counter-separator, counter-increment, on the number or its group.

text-fill dims to --anim-fill-dim (default 0.18) and brightens per character.

How GSAP gets here

It isn't bundled. GSAP arrives through the Amply library manager, gated by the boolean props on Project Core, exactly like every other library on the site. Motion waits for it with onLibrariesLoaded and reads the DOM to decide what to ask for — no flags to remember, because [anim="text-fill"] on the page is the request for SplitText.

Three manager behaviours shape src/gate.ts, all verified against the shipped embed on 2026-09-12:

  1. Late subscribers resolve — it checks loadedLibraries before attaching listeners, so we don't have to race its boot.
  2. A disabled toggle never resolves — no loadLibrary call, no event, callback dropped forever. This is why Motion carries its own timeout: without it, one unchecked box means a page of permanently invisible text.
  3. Plugins register in their own Webflow.push — ours can be queued first, so window.ScrollTrigger may exist unregistered. Motion calls registerPlugin defensively; it's idempotent.

Failure behaviour

Motion never leaves a page blank. Every path that can't animate reveals everything at rest by putting .anim-fallback on <html>, and says why:

| Situation | What happens | |---|---| | prefers-reduced-motion: reduce | revealed at rest, silently — that's the point | | No [anim] on the page | nothing runs; the manager is never even asked | | Main or ScrollTrigger off | revealed after 4s + warning naming only the toggle that's actually missing | | No Project Core on the page | revealed + warning telling you to add the component | | webflow.js never drains the ready queue | revealed after 5s by the module-scope failsafe | | SplitText off | everything else still animates. text-fill uses the built-in splitter and logs an informational note |

That last row matters: SplitText is the one optional dependency, so a forgotten checkbox degrades one effect instead of disabling every animation on the page. The hard requirement is gsap + scrolltrigger only.

The failsafe is armed at module scope, deliberately outside Webflow.push — a failsafe that lives inside the callback can't save you from the callback never running, which is exactly what happens if webflow.js is missing or blocked.

All six paths are covered by the harness (test/), one ?mode= each.

Development

npm install
npm run build      # → dist/motion.js  (IIFE, minified, sourcemapped)
npm run typecheck
npx serve . -l 4321   # harness → http://localhost:4321/test/

IIFE rather than ESM: plugins come from the manager, so there is nothing left to code-split, and a plain script drops the module-ordering subtleties.

Publishing

The published bundle is public. jsDelivr cannot serve private npm packages, so there is no private-CDN path. The source directory stays internal and is stripped on duplication; the compiled bundle is world-readable once published. That is normal for this kind of library (Finsweet ship theirs the same way) but it is a deliberate choice, not an accident.

First time only — both steps are on npmjs.com and need the Amply account:

  1. Create the @joinamply org (npmjs.com → Add organization). Free for public packages.
  2. npm login

Then, every release:

npm version patch      # or minor / major
npm publish            # prepublishOnly runs typecheck + build first

npm publish --dry-run prints the exact tarball without sending anything — 6 files, ~20 kB.

Give jsDelivr a minute, then confirm the URL actually serves before pointing a site at it:

curl -sI https://cdn.jsdelivr.net/npm/@joinamply/[email protected]/dist/motion.js | head -1

Then bump the pinned version in webflow/codes-embed.html and on any site that should follow.

Pin exact versions everywhere — including the starter. An exact version is immutable on jsDelivr and cached permanently; a @0.1 range has to be re-resolved and that resolution is cached too, so a freshly published fix can take a long time to reach pages on the range and you cannot tell which build a visitor actually ran. Bumping one line deliberately is worth more than automatic upgrades you can't observe.

The attribute prefix

Kept as anim / anim-* — it's the existing API and reads well in the Designer. Collision risk with another library is real but theoretical. If it ever bites, P in src/config.ts is the single place the prefix is spelled; changing it to am renames the whole surface.