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

@sunsided/web-visual-fx

v1.1.0

Published

Modern visual effects for the web (liquid and frosted glass, SVG filters, holographic foil, squircles and more) as Svelte components and framework-agnostic custom elements.

Readme

Visual FX

Try it out: https://sunsided.github.io/web-visual-fx/


A playground for modern visual effects on the web, built as tokenized, fully configurable Svelte components. Two faces, one codebase:

  • Playground site (SvelteKit, prerendered): one full-screen page per effect, with a control panel for every parameter and a generated code snippet.
  • Component library (Storybook): every atom, molecule, organism and effect with controls and docs, published under /storybook/ next to the site.

Effects so far:

| Effect | Techniques | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | Noise | feTurbulence, feColorMatrix, feComponentTransfer, mix-blend-mode | | Frosted glass | backdrop-filter, feTurbulence → feDisplacementMap, masked gradient rim | | Liquid glass | SDF-generated map via feImage → feDisplacementMap, feSpecularLighting | | Chromatic aberration | feColorMatrix channel split, feOffset, feBlend, radial feDisplacementMap | | Gooey | feGaussianBlur → feColorMatrix alpha threshold, feComposite | | Lighting | feDiffuseLighting, feSpecularLighting, fePointLight, feTurbulence bump map | | Duotone | feColorMatrix luminance, feComponentTransfer table, feComposite mix | | Halftone | CSS dot gradients + contrast() threshold; feImage/feTile Bayer dither + discrete transfer | | CRT | gradient scanlines and mask, feDisplacementMap barrel with a generated map, blur + screen bloom | | Holographic foil | perspective tilt, pointer custom properties, mix-blend-mode, background-blend-mode | | Spotlight cards | pointer-tracked radial-gradient, mask-composite: exclude, ResizeObserver | | Aurora | @property-registered colors/angles, cos()/sin(), layered radial-gradient | | Squircles | corner-shape / superellipse(), linear() spring easing, clip-path: path() fallback | | Sticker outline | feMorphology dilate, feGaussianBlur + feColorMatrix threshold, feFlood, feComposite, feMerge | | Edges and sketch | feConvolveMatrix (Sobel, Laplacian, emboss), feComponentTransfer abs/threshold, feTurbulence grain, feBlend | | Posterize | feComponentTransfer type="discrete", feColorMatrix saturate, feConvolveMatrix ink lines, feMerge | | Pixelate | feFlood + feComposite + feTile sample grid → feMorphology dilate, tiled feImage tile mask | | Glitch | generated row-offset feImage → feDisplacementMap, channel split, CSS-clocked bursts | | Magnifier | radial feImage zoom map → feDisplacementMap + feMerge, optional backdrop-filter: url() | | Water ripple | animated feTurbulence on a feTiled tile slid with feOffset → feDisplacementMap; backdrop via backdrop-filter: url() | | Caustics | feTurbulence type="turbulence" ridges → feComponentTransfer table, feDisplacementMap warp, feSpecularLighting | | Ink bleed | feTurbulence + feDisplacementMap, feGaussianBlur + feColorMatrix soft threshold, feDiffuseLighting paper | | Dissolve | feTurbulence threshold field, feComponentTransfer equalization, feImage direction ramp, Svelte transition | | Kaleidoscope | wedge clip-path: polygon() from tan(), rotate: calc(i * 360deg / n), mirrored with scale: -1 1 | | Neon | stacked text-shadow / drop-shadow(), stepped @property flicker (WCAG 2.3.1-checked) | | Gradient text | background-clip: text (prefixed + unprefixed in @supports), -webkit-text-stroke, paint-order | | Long shadow | generated N-step text-shadow list, color-mix() ramp | | Border beam | @property <angle> → conic-gradient, mask-composite: exclude ring, blurred copy | | Shimmer | translated sheen layer, background-clip: text, gradient mask-image, @property sweep | | Split text | sibling-index() / sibling-count() delays, @starting-style, inline --i fallback | | Scroll reveal | animation-timeline: view()/scroll(), animation-range, view-timeline, timeline-scope | | View transitions | startViewTransition (document + element-scoped), view-transition-class, clip-path: circle() | | Paint Worklet (experimental) | CSS Painting API paint(), CSS.registerProperty, gradient fallback | | Water sim (experimental) | WebGL2 ping-pong float framebuffers, GLSL wave equation, refraction |

Backdrop displacement uses backdrop-filter: url(#filter), which only renders in Chromium-based browsers. Firefox drops the whole backdrop filter (blur included) when the chain contains url(); Safari is untested. GlassPanel therefore leaves url() out outside Chromium unless forceDisplace is set, so other browsers get blur + tint + grain + rim.

Using the package

The effects are published as @sunsided/web-visual-fx on npm (and GitHub Packages), in three flavors:

| Entry | What | For | | ---------------------------------- | ------------------------------------------------------------ | ---------------------------------- | | @sunsided/web-visual-fx/svelte | Svelte 5 components, shipped as source | Svelte and SvelteKit apps | | @sunsided/web-visual-fx/elements | Custom elements (<vfx-glass> …), prebuilt | React 19, Vue, Angular, plain HTML | | @sunsided/web-visual-fx | Schemas, defaults and pure helpers, one namespace per effect | Anything |

npm install @sunsided/web-visual-fx

Tagged releases are also published to GitHub Packages, which needs a token even for public packages: a personal access token (classic) with read:packages, and in .npmrc:

@sunsided:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

Svelte is an optional peer dependency: only the /svelte entry needs it. The elements bundle their own copy of the Svelte runtime.

Svelte

<script>
	import '@sunsided/web-visual-fx/tokens.css';
	import { GlassPanel, HoloCard } from '@sunsided/web-visual-fx/svelte';
</script>

<GlassPanel blur={20} tintOpacity={0.4}>Frosted</GlassPanel>

<HoloCard label="My card">
	{#snippet art()}<img src="/art.png" alt="" />{/snippet}
	<p>Card text</p>
</HoloCard>

tokens.css holds the design tokens and the two utilities the components rely on (.visually-hidden, and pausing animations inside [data-motion="paused"]); it contains no document reset.

Custom elements (React, plain HTML, …)

import '@sunsided/web-visual-fx/elements'; // registers all elements
// or only what you use: import '@sunsided/web-visual-fx/elements/glass';
<vfx-glass blur-radius="20" tint-opacity="0.4">Frosted</vfx-glass>

<vfx-holo label="My card">
	<img slot="art" src="/art.png" alt="" />
	<p>Card text</p>
</vfx-holo>
  • Self-contained: each element carries its tokens in its shadow root; no global CSS needed. color-scheme (and so light/dark) is inherited from the page; data-theme on the element forces one.
  • Props: properties use the camelCase prop names (el.tintOpacity = 0.4), attributes the kebab-case form (tint-opacity). Two props are renamed because HTMLElement already has them: blur → blurRadius and animate → animated.
  • Content: children go into the default slot; HoloCard's art into slot="art".
  • Events: <vfx-gooey-menu> fires itemselect (the item) and openchange, <vfx-squircle-group> fires selectedchange; the new value is in event.detail, and the open / selected properties stay in sync.
  • Motion: animated elements (vfx-gooey, vfx-crt, vfx-aurora, vfx-glitch, vfx-ripple, vfx-caustics, vfx-kaleidoscope, vfx-neon, vfx-gradient-text, vfx-border-beam, vfx-shimmer, vfx-paint-pattern, vfx-water-sim) take a paused property, since a page-level data-motion can't reach into a shadow root. @property rules are ignored inside a shadow root, so the elements register them for the document when they connect.
  • Not available as an element: spotlight cards (the grid measures its cards with querySelectorAll, which doesn't see slotted children) and Stagger (it indexes its direct children, which in a shadow root are a <slot>), parallax layers (they need the scene's named timeline, which doesn't cross into a shadow root) and the view-transition helpers (use viewTransition.runViewTransition from the core entry); use the Svelte components.

React 19 passes props to custom elements as properties and on<event> props as listeners. For JSX types, reference the declarations once:

import '@sunsided/web-visual-fx/elements';
import type {} from '@sunsided/web-visual-fx/elements/react';

<vfx-gooey-menu
	items={[{ label: 'Share', icon: '↗' }]}
	onitemselect={(e) => console.log(e.detail.label)}
/>;

React 18 sets attributes instead of properties, so array props (items, selected) need a ref.

Core

import { crt, glass, supportsCornerShape } from '@sunsided/web-visual-fx';

glass.glassDefaults.blur; // 14
crt.maxFlickerChange(0.03); // worst-case luminance change of the CRT flicker

Getting started

Requires Node.js 24 (see .node-version) and Task.

corepack enable
task install
task dev        # playground on http://localhost:5173
task storybook  # component library on http://localhost:6006

task lists everything; task ci runs the full pipeline locally (format, lint, types, workflow linting with actionlint + zizmor, tests, build, package). task package builds the npm package into dist/ and checks it with publint.

Structure

src/lib/
  tokens/        primitives.css → semantic.css → utilities.css = tokens.css (shipped); base.css (site reset)
  params/        parameter schemas: one definition drives defaults, controls, Storybook argTypes, code snippets
  components/
    atoms/       Slider, Switch, Select, ColorInput, Button
    molecules/   Field, ParamControl, ControlGroup, CodeSnippet, StageHeader
    organisms/   ParamControls, ControlPanel, Scene
    templates/   PlaygroundLayout
  fx/
    noise/       noise.ts (schema), NoiseFilter (SVG <filter>), NoiseLayer
    glass/       glass.ts (schema), DisplacementFilter (SVG <filter>), GlassPanel
    …/           one folder per effect: schema + specs, filter/component(s), Preview, stories
  fx/registry.ts every effect listed on the index
  index.ts       package entry: schemas and helpers
  svelte.ts      package entry: Svelte components
src/elements/
  config.ts      which components become custom elements, with extra props, slots, events
  generate.ts    renders the wrappers in generated/ and the .d.ts files (yarn elements:generate)
  shadow.ts      tokens and utilities adopted into every element's shadow root
src/routes/      index + one page per effect

Customizing

Every effect parameter is a prop with a sensible default (ranges and defaults live in the effect's schema, e.g. fx/glass/glass.ts). Styling goes through three token layers:

  1. Primitives such as --gray-*, --accent-*, --space-*. Re-skin everything with --accent-hue / --gray-hue.
  2. Semantic roles such as --color-surface and --color-accent, with light/dark via light-dark(). Force a scheme with data-theme="light" | "dark" on any element.
  3. Component tokens that fall back to semantic ones, e.g. <Slider --slider-fill="hotpink" />.

Adding an effect

  1. Create src/lib/fx/<name>/<name>.ts with a defineSchema({...}) and its defaults.
  2. Build the effect from small pieces (an SVG filter atom, a surface component).
  3. Add <Name>.stories.svelte using toArgTypes(schema).
  4. Add src/routes/<name>/+page.svelte using ParamState, ParamControls and PlaygroundLayout.
  5. Register it in src/lib/fx/registry.ts.
  6. Export the component from src/lib/svelte.ts and the module from src/lib/index.ts.
  7. To ship it as a custom element, add it to src/elements/config.ts and run task elements:generate (a spec fails while the generated wrappers are out of date).

Deployment

.github/workflows/pages.yml runs on every push to main. It first runs the full CI workflow (ci.yml, called as a reusable workflow) and only continues if that passes: it builds the site with BASE_PATH taken from actions/configure-pages, builds Storybook into build/storybook, and deploys to GitHub Pages. Pull requests run CI only.

In the repository settings, set Pages → Source to GitHub Actions.

Releases

Bump version in package.json, then push a matching semver tag:

git tag v1.1.0 && git push origin v1.1.0

.github/workflows/release.yml runs CI, checks that the tag matches package.json, builds the site and Storybook into web-visual-fx-<tag>.tar.gz (relative paths, so it works from any directory), and creates the release with GitHub-generated notes and the archive attached. It also builds the npm package and publishes it to GitHub Packages and, via trusted publishing (OIDC, no token), to npmjs.com. Tags with a suffix such as v1.2.0-rc.1 become prereleases and are published under the next dist-tag.

License

Licensed under any of the European Union Public Licence 1.2, the MIT License, or the Apache License 2.0, at your option (EUPL-1.2 OR MIT OR Apache-2.0).