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

@madenowhere/photon

v0.0.10

Published

Framework-agnostic motion engine. Analytical springs, seamless momentum handoff, GPGPU spring solver. Drives DOM elements, PhotonValues, and phaze signals out of the box.

Readme

@madenowhere/photon

Framework-agnostic motion engine. Analytical spring solver, seamless momentum handoff between in-flight animations, and a GLSL spring solver for worker-side / GPU animations. Drives DOM elements, PhotonValues, and @madenowhere/phaze signals out of the box.

import { animate, spring, PhotonValue } from '@madenowhere/photon'
import { signal } from '@madenowhere/phaze'

// DOM target
animate(el, 'visible', { stiffness: 200, damping: 30 })

// PhotonValue target
const x = new PhotonValue(0)
animate(x, 100, { stiffness: 100, damping: 30 })

// Phaze signal target — works directly, no wrapper needed
const y = signal(0)
animate(y, 100, { stiffness: 100, damping: 30 })

Why

Most spring-animation libraries integrate one signal/state model and one render layer. Photon does neither — it's a numeric engine that knows three target shapes (DOM, PhotonValue, phaze signal) and writes the integrated value through the right mutator each frame. Apps mix and match without a wrapper layer per integration.

Architecturally:

  • Analytical spring solver (spring.ts) — closed-form solution to the harmonic oscillator. Position and velocity are exact for any time t, no integration drift. Underdamped / critically damped / overdamped paths.
  • Momentum-catching handoff — when you re-target an animation in flight, photon analytically solves the current position+velocity from the previous animation and seeds the new spring with that. Zero discontinuity.
  • GPGPU spring solver (photonShader.ts) — same math in GLSL. Lets a Web Worker animate thousands of points on the GPU using onUpdate callbacks to push SpringPackets across the worker boundary.
  • Cubic bezier easing (easing.ts) — Newton-Raphson + binary subdivision, no allocations on the hot path.

SSR as a first-class state

Phaze + Photon = SSR-rendered animation. Phaze server-renders signal-driven JSX; photon extends that guarantee to motion — spring physics, scroll-driven parallax, and declarative use: directives all render to HTML on the server and pick up live on hydrate, through a single import path. No fork-the-bundle, no separate server entry, no per-call-site guards in user code. The closest comparable stacks (Solid + GSAP, React + Framer Motion + react-server-components) either crash on SSR or strip motion entirely from the SSR'd output.

Most JS animation libraries either:

  • Crash hard on SSR — touch window / requestAnimationFrame at module load and throw on the first import in a server runtime.
  • Get wrapped in if (typeof window === 'undefined') return null at every call site by the consumer (motion/react's pattern), leaving SSR'd HTML stripped of any motion-related state.
  • Ship a separate "server" entry — a parallel no-op stub the bundler swaps in via conditional exports.

Photon takes a different approach: a single design where SSR is a first-class state. Every entry point — animate, spring, scroll, transform, parallax, photonProp, bindStyle — degrades to "render initial state, schedule nothing" automatically when called without a window. The composition spring(transform(scrollYProgress, …)) keeps working through it because the data flow is decoupled from the timing loop: scroll() returns a PhotonValue at 0, transform() maps pure-functionally, spring() resolves at its initial target, parallax() writes the resulting translate3d(0, yRange[0]px, 0) into the SSR'd HTML.

No fork-the-bundle, no consumer-side guards, no separate server entry — the same import path runs on server and client. Components written for the browser server-render unchanged; hydration picks up live window.scrollY, requestAnimationFrame, and spring physics from the SSR'd initial state. No flash, no re-mount, no diff.

Detection is via typeof window === 'undefined' — the canonical check against @madenowhere/phaze-render-to-string's server runtime, which installs document and the DOM classes but deliberately omits window / requestAnimationFrame / getComputedStyle. Photon recognizes what's missing and degrades; it does not ask the SSR layer to polyfill what isn't there.

Two target protocols

Photon detects targets structurally — no framework imports.

Photon protocol: { value: T }

The classic shape. Anything with .value getter+setter satisfies it. PhotonValue is the bundled implementation; motion/react's MotionValue, KnockoutJS observables, and others fit too.

const x = new PhotonValue(0)
animate(x, 1, { stiffness: 100 })
x.value             // current value
x.on('change', cb)  // subscribe

Phaze signal: callable with .set / .subscribe / .current

Detected as: typeof t === 'function' && typeof t.set === 'function' && typeof t.subscribe === 'function'. No phaze import on the photon side.

import { signal } from '@madenowhere/phaze'
const x = signal(0)
animate(x, 1, { stiffness: 100 })
x()            // current value (auto-tracks in effects)
x.subscribe(cb)

Both shapes coexist. Source argument of spring(), transform(), onPhotonChange() (in the /phaze subpath) accepts either.

API

Core (framework-free)

import {
  animate, animateGroup, stop,
  spring,        // creates a target PhotonValue, drives it via animate
  createSpring,  // raw analytical solver: (stiffness, damping, mass, x0, xt, v0) => (t) => { position, velocity }
  PhotonValue, interpolate,
  bezier,
  animatePath, animatePathGroup, getPathLength,
  photonGLSL,    // GLSL spring solver, paste into shader source
  isPhaseSignal, // duck-type check for phaze signals
} from '@madenowhere/photon'

/phaze subpath (phaze-coupled, scope-bound cleanup)

import {
  bindStyle,        // PhotonValue or phaze signal → DOM style property
  phaseAnimate,     // animate() with auto-stop on scope dispose
  phaseAnimateGroup,
  spring,           // ReactiveSource → spring-driven PhotonValue
  transform,        // ReactiveSource → linearly-mapped PhotonValue
  onPhotonChange,   // subscribe with auto-cleanup on scope dispose
  scroll,           // window/element scroll → { scrollY, scrollYProgress } PhotonValues
  photonValue,      // factory: photonValue(0) === new PhotonValue(0)
  PhotonValue,
} from '@madenowhere/photon/phaze'

The /phaze subpath peer-deps @madenowhere/phaze. Marked optional via peerDependenciesMeta — the core works without phaze installed; importing /phaze requires it.

Sizes (esbuild-bundled, minified)

| | raw | gzip-9 | brotli | |---|---|---|---| | core (full: animate + spring + svg + PhotonValue + GLSL) | 8.3 KB | 3.2 KB | 2.97 KB | | core (animate + stop only) | 5.1 KB | 2.1 KB | 1.95 KB | | /phaze subpath (typical surface) | 7.0 KB | 3.0 KB | 2.71 KB |

Phaze is externalized — listed sizes don't include @madenowhere/phaze's own runtime.

Worked example: scroll-driven spring

import { signal } from '@madenowhere/phaze'
import { spring, scroll, bindStyle, transform } from '@madenowhere/photon/phaze'

function Card() {
  const ref = signal<HTMLElement | null>(null)
  const { scrollYProgress } = scroll({ target: () => ref() })
  const y = spring(transform(scrollYProgress, [0, 1], [300, -300]), {
    stiffness: 500, damping: 100, mass: 3,
  })
  effect(() => {
    const node = ref()
    if (node) bindStyle(node, 'transform', y, (v) => `translate3d(0, ${v}px, 0)`)
  })
  return <div ref={ref}>...</div>
}

scroll() returns PhotonValues for window scroll. transform() linearly maps it through a range. spring() smooths it via the analytical spring. bindStyle() writes the spring's value to el.style.transform with auto-cleanup. The whole chain disposes on component unmount via phaze's cleanup().

Position in the ecosystem

  • @madenowhere/phaze — signals + JSX runtime + reactive DOM bindings.
  • @madenowhere/photon(this) — motion engine. Animates targets in any of the three protocols above.
  • @madenowhere/phaze-vite — Vite HMR for phaze components.
  • @madenowhere/phaze-astro — Astro renderer for phaze islands.

Photon and phaze are independent. You can use photon without phaze (DOM-only or PhotonValue-driven), and you can use phaze without photon (no animation, just signals + DOM bindings). They compose cleanly when both are present, via the /phaze subpath.

Status

Pre-alpha. 0.0.x is name reservation + early integration testing. Public API is not yet stable.

License

MIT.