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

tosijs-product

v0.6.0

Published

Scroll-linked animation components for tosijs

Readme

tosijs-product

A cinematic product page component library for tosijs.

tosijs-product provides high-performance, scroll-linked animation components designed to create immersive, "Apple-style" product stories with minimal code. It unifies Lottie, video, BabylonJS 3D, Mapbox flights, themes, and declarative CSS interpolation under a single scroll engine.

View the Live Demo

Architecture in one paragraph

<tosi-product> is a scroll engine. It owns the page (or any scrollable region) it lives in: it computes a runway from its sections, hosts a sticky viewport-sized window in shadow DOM, and translates an absolute-positioned stack as you scroll. Each <tosi-product-section> declares a pin duration via scroll; during pin the section sits motionless at the viewport top while interpolators run, then it scrolls out at 1:1 and yields to the next. Themes are dictionaries of CSS custom properties — the engine writes the active section's resolved values to :root, so external siblings (page header, sticky overlay, footer) re-theme through the cascade.

Key components

  • tosi-product — the scroll engine. Owns the runway, the sticky window, the stack translation, the theme registry. A tosi-product placed inside another tosi-product's section automatically runs in follower mode, driven by the parent section's pin progress (so you can nest a horizontal engine inside a vertical one).
  • tosi-product-section — a slotted container with scroll (pin duration in viewport %), theme / theme-from / theme-to, and a direction inherited from its engine.
  • tosi-product-header — a sticky overlay header that slides in once window.scrollY > threshold. Inherits theme via the CSS cascade.
  • tosi-interpolator + tosi-waypoint — declarative CSS interpolation between progress keyframes.
  • tosi-filmstrip — frame-based animator using a WebP/PNG mosaic grid for buttery-smooth video-style scrubbing.
  • tosi-prism — Prism-highlighted code block (lazy-loads PrismJS from CDN). Also exports loadPrism and highlightCodeBlocks helpers for re-highlighting other rendered HTML (e.g. markdown viewer output).

Getting started

Pure HTML (zero JS orchestration)

The IIFE build is self-contained — a single script tag gives you tosijs, tosijsUi, and tosijsProduct as globals, with all custom elements registered automatically:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>My Product</title>
    <style>
      body {
        margin: 0;
      }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/tosijs-product/dist/index.js"></script>
  </head>
  <body>
    <tosi-product>
      <tosi-product-section scroll="200">
        <tosi-interpolator data-scroll-animate easing="ease-in-out">
          <tosi-waypoint
            progress="0"
            style="opacity: 0; transform: translateY(50px)"
          ></tosi-waypoint>
          <tosi-waypoint
            progress="0.5"
            style="opacity: 1; transform: translateY(0px)"
          ></tosi-waypoint>
          <tosi-waypoint
            progress="1"
            style="opacity: 1; transform: scale(1.2)"
          ></tosi-waypoint>
          <h1 style="text-align: center;">Pinned for 2× viewport.</h1>
        </tosi-interpolator>
      </tosi-product-section>
    </tosi-product>
  </body>
</html>

Modern web app (ESM)

Install from npm using bun, npm or whatever package manager you prefer:

bun add tosijs-product tosijs tosijs-ui

And compose your pages using typescript, javascript, or HTML.

import {
  tosiProduct,
  tosiProductSection,
  tosiInterpolator,
  tosiWaypoint,
} from "tosijs-product";

const app = tosiProduct(
  tosiProductSection(
    { scroll: 200 },
    tosiInterpolator(
      { "data-scroll-animate": true, easing: "ease-in-out" },
      tosiWaypoint({
        progress: 0,
        style: "opacity: 0; transform: translateY(50px)",
      }),
      tosiWaypoint({
        progress: 0.5,
        style: "opacity: 1; transform: translateY(0px)",
      }),
      tosiWaypoint({ progress: 1, style: "opacity: 1; transform: scale(1.2)" }),
      document.createElement("h1")
    )
  )
);

document.body.append(app);

The scroll attribute

scroll on a section is its pin duration, expressed as a percentage of the viewport. scroll="200" means "pin this section for 2× viewport of scroll." When pin progress reaches 1, the section enters its exit phase and scrolls out at 1:1 over its own height. So total scroll claimed = (scroll / 100) * viewport + naturalSize.

Themes

Register themes (each is a dictionary of CSS custom properties) and reference them from sections:

const app = tosiProduct(
  tosiProductSection({ scroll: 100, theme: "midnight" } /* ... */),
  tosiProductSection(
    { scroll: 200, "theme-from": "midnight", "theme-to": "paper" } /* ... */
  ),
  tosiProductSection({ scroll: 100, theme: "paper" } /* ... */)
);

app.themes = {
  midnight: { "--bg": "#08081a", "--fg": "#f0f0f5", "--accent": "#9be7ff" },
  paper: { "--bg": "#f5f1e8", "--fg": "#1a1815", "--accent": "#7c3aed" },
};
app.defaultTheme = "midnight";

The transition section interpolates its CSS variables (color values use color-mix(in srgb, …)) over its pin progress, and writes them to document.documentElement. Anything cascading from :root — including a <tosi-product-header> overlay outside the engine — re-themes in unison.

Frame-based animation

Standard video scrubbing (video.currentTime) often stutters because decoders aren't designed for random-access seeking. The tosi-mosaic CLI converts a video to a single WebP mosaic grid, and <tosi-filmstrip> scrubs through it using a hardware-accelerated canvas.

bunx tosi-mosaic my-video.mp4 --frames 100 --width 1280

Produces my-video_10x10_100.webp (the filename encodes grid + total frames):

<tosi-filmstrip
  src="my-video_10x10_100.webp"
  data-scroll-animate
></tosi-filmstrip>

A grid (rather than a single long strip) keeps dimensions inside the browser's max image size (commonly 16,384px) while delivering all frames in one request.

License

Licensed under the Apache License, Version 2.0. See LICENSE for details.