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

@bufinance/radio

v0.2.5

Published

BUFI Radio: a YouTube-backed live-radio dynamic island. A RadioProvider owns one persistent YouTube player and the channel list; any number of RadioIsland views (pill or compact) share it, and the one that opens the card owns it. Nothing touches YouTube u

Readme

@bufinance/radio

BUFI Radio: a YouTube-backed live-radio dynamic island. An idle pill (play button + marquee) or a compact round button opens a card with the video, the channel scroller and prev/next transport.

Canonical source: this directory. It was moved here from desk-v1 packages/radio on 2026-09-22 (the provider/island rewrite). 0.1.x on npm was the older framer-motion monolith.

What changed in 0.2.2

  • The video paints. The provider's persistent iframe box is now a document.body portal, like the card. It was rendered where the provider mounts, so its z-index only counted inside the host's own stacking context — desk wraps its app in relative z-10 — and the card, a body-level portal at z 70, painted over the box's z 71: you heard the stream and saw the card's black slot. Measured on desk 2026-09-22 (elementFromPoint at the video's centre was the slot); as body siblings the two z-indexes mean what they say.
  • No black box, ever. The slot shows the stream's own YouTube poster (i.ytimg.com/vi/<id>/mqdefault.jpg, no key) with a play button until the iframe covers it; channel tiles are the same posters (96×64) with the name over the lower edge, the active one ringed in its accent with the live pulse. Tiles snap; hover lifts 2px, transform only.
  • Motion on a dialog budget, on the GPU. The card no longer morphs its width, height and radius (three layout properties on every open); it scales from its trigger's corner, 0.94 → 1, on a 240ms spring with a 160ms ease-out exit — faster out than in — and a plain 160ms fade for the content (the blur filter is gone: not a compositor property). The play / pause glyph swaps at 0.8 → 1 without blur. Hover lifts are gated to hover-capable pointers, and prefers-reduced-motion keeps the fades and drops the scale and the live pings.

What changed in 0.2.1

  • RadioIsland width (number px or any CSS width, default '100%'), applied as an inline style. 0.2.0 set the pill's width with a w-full class and RadioBar asked for w-[200px] in className; with no tailwind-merge, CSS order decided, and on a Tailwind v3 host the pill stretched across the whole footer column. A style cannot lose that fight. compact stays 32px.
  • RadioBar has its ‹ › prev/next chevrons back between the right hairline and the island, as in 0.1.x. The expanded card keeps its own pair.

What changed in 0.2.0

  • Provider / island split. RadioProvider owns the one YouTube player, the channel list and the play state. Any number of RadioIsland views share it; the island that opened the card owns it, and an owner that unmounts takes the card with it while the music keeps playing. RadioBar is one more view, and brings its own provider when rendered outside one.
  • Lazy arming. Nothing touches YouTube until the first play or expand: no iframe_api script, no oEmbed availability checks, no discovery request. Mount the provider on every page; a visitor who never presses play costs nothing.
  • hotkey prop. Toggles the card. Default mod+b; null disables it (desk passes null because ⌘B toggles its sidebar).
  • motion instead of framer-motion (motion/react).
  • The player's event handlers always see the latest channel (they used to handle an error on the third channel with the first channel's state).

Usage

import { RadioIsland, RadioProvider, type RadioCopy } from '@bufinance/radio';

<RadioProvider
  audioEnabled={enabled}          // the app's master mute; false pauses the radio
  setAudioEnabled={setEnabled}    // pressing play on a muted app turns audio on
  volume={volume}                 // 0..1, optional
  discoverUrl="/api/radio/discover" // optional; resolves query-based channels
  copy={copy}                     // Partial<RadioCopy>, merged over English
  hotkey={null}                   // optional, default 'mod+b'
>
  <RadioIsland variant="pill" placement="top-end" width={200} />
</RadioProvider>

Or the full-width footer in one element:

<RadioBar audioEnabled={...} setAudioEnabled={...} extras={<Left />} extrasCenter={<Center />} />

nowPlaying and switchTo in copy are templates with a {name} placeholder, filled per channel.

The discovery endpoint answers { resolved: Record<channelId, videoId> }. Import CHANNELS in it from this package: index, channels, copy and cn are plain modules, so a server route gets the real array. The components and hooks are "use client" modules, per file (see tsup.config.ts).

Host requirements (Tailwind)

The package ships class names, not CSS. The host must generate them:

Tailwind v4 — add a source for the built files next to your theme:

@source "<path-to>/node_modules/@bufinance/radio/dist/**/*.js";

Tailwind v3 — add the same glob to content:

content: [/* … */, '../../node_modules/@bufinance/radio/dist/**/*.js'],

And these tokens/utilities must exist in the host theme:

| class | what the host defines | | --- | --- | | *-purpleDanis, *-purpleDanis/<n> | color purpleDanis = #6954CF | | *-violetDanis | color violetDanis = #CBB0FF | | font-knick | the Knicknack font family | | animate-marquee-ticker | an infinite linear animation that translates the element -50% on X (the strip is two copies of one row). The component sets animation-duration: 16s and animation-play-state inline. |

animate-marquee-ticker, v3:

keyframes: { 'marquee-ticker': { to: { transform: 'translateX(-50%)' } } },
animation: { 'marquee-ticker': 'marquee-ticker 16s linear infinite' },

v4:

@keyframes marquee-ticker { to { transform: translateX(-50%); } }
@utility animate-marquee-ticker { animation: marquee-ticker 16s linear infinite; }

A keyframe that travels anything other than a whole copy (-50% for two) jumps at the loop point. scrollbar-none on the channel scroller is optional (an inline scrollbar-width: none already hides it).

Peers

react and react-dom >= 18, motion >= 12, lucide-react, react-hotkeys-hook >= 4.

Build and test

bun install
bun run build
bun run typecheck
bun run test        # happy-dom: lazy arming + client-boundary contract
npm pack --dry-run