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

@opalseed/pack

v0.6.1

Published

The Opalseed booster pack: pre-rendered foil wrapper sprites, a framework-free tear, and a React component.

Readme

@opalseed/pack

The Opalseed booster pack as one asset for every product: pre-rendered foil wrapper sprites from a compositor, a framework-free tear, and a React wrapper. Plan of record: ~/.claude/plans/i-need-to-work-buzzing-quasar.md.

Layout

  • src/core/ runtime with no framework: geometry, the stylesheet, layer helpers, the tear and sheen cores, mountPack()
  • src/react/ <BoosterPack>, <PackStyles>, useTear, plain React, no MUI
  • scripts/ the compositor and tooling (Playwright + sharp; never published)
  • lab/ Vite playground on port 5175 (npm run lab), shot in both engines by npm run shots, gesture-tested by npm run drag
  • fonts/, marks/ copies of generated output from the MIX pipeline (see marks/README.md)
  • packs/_defaults.json house-style defaults every recipe inherits

Recipes (packs/<slug>.json) and the emitted sprites live in each CONSUMER repo; the lab keeps its own set under lab/packs and lab/sprites. Masters (full-res PNGs, face plates, source photos) live outside git in brand-assets/packs/<slug>/ (the lab uses lab/masters/, gitignored).

Commands

npm run lab                       # playground, port 5175
npm run shots                     # Chromium + WebKit screenshots to .shots/  (SHOTS_ROUTES='/?bench=yaw&yaw=24,...')
npm run drag                      # the gesture contract, asserted in both engines
npm run render -- --recipes <dir> --out <dir> --masters <dir> [--only a,b] [--relit] [--override '{...}' --suffix -x]
npm run faces  -- --recipes <dir> --masters <dir> [--only slug] [--regen]      # photo -> treated face plate (+ optional Gemini)
npm run check  -- --out <dir> --masters <dir>                                  # tiers, index, tear invariant
node scripts/contrast.mjs --recipes <dir> --masters <dir>                      # title >= 3:1, kicker >= 4.5:1
npm run gemini probe              # verify the image model + call shape (needs GEMINI_API_KEY in .env)
npm run build                     # dist/ for publishing (prepublishOnly runs check + build)
npm run lint                      # must print nothing

Using it

import { BoosterPack } from '@opalseed/pack';
import PACKS from './assets/packs/index.generated.js';   // emitted by the renderer

<BoosterPack sprites={PACKS['above-the-city']} width={240} />                       // static: shelves, thumbnails, tiles
<BoosterPack sprites={...} width={220} mode="interactive" onTorn={fn} onOpen={fn}>  // the swipe-to-cut gesture is live
  {cards /* render behind the body: they rise out of the torn top */}
</BoosterPack>
<BoosterPack sprites={...} width={240} mode="controlled" />                         // a parent drives it (see the contract)

Light and sway

The light on a moving pack's foil is one model shared by every product (one key light, overhead and in front, the same one baked into the sprites): a glare band along the top edge when the top tilts away from the viewer, a shade that deepens toward the bottom when it tilts toward them, zero at rest. light turns its layers on; something has to drive them.

// a tilt you already compute (the site's shelf: the pointer), -1..1
<BoosterPack sprites={...} width={240} light style={{ '--pack-pitch': 'var(--ty, 0)' }} />

// the idle sway on your own wrapper (it carries whatever must move with the
// pack: a shadow plate, a hint); the light runs on the sway's clock
const wrap = useRef(null);
useSway(wrap, staged && !cutting);
<div ref={wrap}><BoosterPack sprites={...} width={220} mode="interactive" light /></div>

The sway turns about Y only, SWAY.turnDeg each way, starting and ending square to the viewer (right, then left, then back) on a SWAY.ms clock: to let the pack turn both ways before something happens (a timed tear), wait SWAY.ms. Turning the sway off (a cut starting) eases it to rest from wherever it is instead of snapping, and an interrupted sway stays off. Without React: createSway(el).set(active). The numbers (GLARE_BAND, GLARE_LAMINATE, SHADE, SWAY) are exported from @opalseed/pack/core and live in src/core/light.js: change the look there and every product follows.

Without React: mountPack(container, PACKS[slug], { width, mode, onTorn }) from @opalseed/pack/core builds the same DOM.

Brand chrome stays in the consumer: the site's shadow plates, --lift and accent ring wrap the pack; MIX's halo and beams sit beside it. The pack root carries no shadow of its own.

The contract

One root .osp element; everything is driven by custom properties and a data attribute on it, so a controller (or the tear core) never re-renders anything:

| on the root | meaning | |---|---| | --pack-tear 0..1 | the cut's progress across the strip | | --pack-fly 0..1 | the torn strip's flight (translate/rotate/fade) | | --pack-fly-ms time | the flight's length: the tear core sets it and lets a CSS transition (compositor) carry the strip; a controller stepping --pack-fly itself leaves it 0 | | --pack-heal time | transition length while an early release snaps back | | --sheen-x --sheen-y 0..1 | key-light position; --sheen-p 0..1 shows the pointer sheen | | --pack-pitch -1..1 | drives the light layers: < 0 the top tilts away (glare), > 0 toward the viewer (shade) | | data-pack-state | sealed tearing healing torn open | | data-quiet="1" | reduced motion: no idle sweep, no sparks, beats <= 180ms | | data-ready | interactive / controlled only: 0 (the whole root hidden) until the face's images (body and seal, or the clipped sealed) have decoded, then 1, so both halves appear in one frame; after 10s it shows regardless (whenFaceReady, READY_LATEST_MS) | | data-osp-sway="1" | on an ANCESTOR of the root, set by useSway / createSway: the idle sway, and the light on its clock |

Layers, bottom to top: the children slot · body · the seal (the sealed sprite's top band, right of the finger: the uncut strip) · the strip (hinged at the finger, flies on --pack-fly) · the light (opt-in, light) · the sheen (masked by spec) · the hit zone ([data-testid="pack-tear-zone"], interactive only).

The gesture (interactive mode): the zone covers the top quarter of the pack and reaches 28px past its top and sides (TEAR_ZONE). The cut follows the finger left to right and never goes back. A release past 60% of the width (TEAR_DONE) tears. A shorter release still tears on a flick: at least a quarter of the width from where the cut began, and still moving right at 0.3 px/ms over the last 100ms (TEAR_FLICK). Any other release holds the cut for 200ms (TEAR_GRACE_MS), and a finger back down in that window carries the same cut on, so a touch that flickers off mid-slice is not lost. After that it heals (onHeal), and a press during the heal starts a new cut straight away.

Nothing in a pack can be selected, in any mode, and it shows no long-press callout or tap flash (-webkit-user-select, -webkit-touch-callout, -webkit-tap-highlight-color on .osp; Safari ignores the unprefixed user-select). The slot's children inherit it: one that needs selecting sets -webkit-user-select: text; user-select: text.

Under rotation the pack is a flat sheet: a flow-wrapped sleeve is sealed flat along its sides, so a parent's perspective() rotateY() is all it takes. Once the strip is gone the sheen ends at the lip (TORN_TOP), and consumer chrome that frames the pack (shadow plates, rings) should follow --pack-fly the same way: top: calc(var(--pack-fly, 0) * 11.6%), with the pack's custom properties set on the framing element as well as the pack. dimmed is static-only.

Sprites

Five layers per pack, emitted as WebP tiers by the compositor and listed in index.generated.js with explicit imports (a missing file is a build error): sealed 120/240/480/600/720 · seal body strip 480/720 · spec 480. seal and strip are cropped bands; the index records each band [top, height] as fractions of the pack height. seal is sealed's top STRIP_LAYER alone: the interactive and controlled modes draw it instead of the whole sealed sprite, which only static mode still fetches (since 0.6.0; an index rendered before it has no seal and the band falls back to the whole sprite, clipped).

Recipes

{
  "slug": "above-the-city",
  "theme": { "accent": "#E0953A", "light": "#FFD9A3" },
  "print": { "layout": "poster", "title": ["Above", "the City"], "kicker": "Opalseed event", "year": "2026" },
  "face":  { "photo": "source/IMG_2210.jpg", "focal": [0.5, 0.42], "zoom": 1.0,
             "treatment": { "duotone": 0.55, "contrast": 1.12, "lift": 0.04, "saturation": 0.8, "grain": 0.035, "vignette": 0.35 },
             "ai": { "mode": "none" } }
}

Everything not given comes from packs/_defaults.json (and a consumer's own packs/_defaults.json). print.layout is poster (a treated photo on the art panel, darkening ramp under the title), burst (the letter/rings/sparkles motif; MIX packs add hero.icon, chip, cardsLine) or solid (Link: a flat body in one colour, the mark and the rays as one shape plus the words and the tear line all in ONE ink, a tear notch on the left edge, the foot crimp in the seal's foil; knobs body, ink, inkDepth, holo, heroWidth, kickerSize, textColor, extras, documented at the top of scripts/lib/print.mjs; pair it with material.specInk: 1, specArt: 0 for foil-bright ink on a matte body). encode.* sets the WebP tiers' quality, alpha quality, effort, the spec mask's alpha quality and whether the 120/240 sizes are near-lossless or lossy (defaults in scripts/lib/slices.mjs ENCODE reproduce the original bytes). face.* paths resolve against brand-assets/packs/<slug>/; a hand-made face.png there skips the treatment and Gemini entirely. material.* and tear.* knobs are documented in _defaults.json; --override + --suffix render try-outs side by side.

Gemini (face.ai.mode: outpaint | cleanup | generate) is optional and cached beside the photo as face-ai-<hash>.png + a JSON sidecar; it is never re-called without --regen. The key lives in .env (see .env.example).