@commercengine/shaders
v0.1.0
Published
Commerce Engine's brand artwork: Paper's Warp shader in the brand palette, with named looks, a framework-agnostic mount and instant stills
Maintainers
Readme
@commercengine/shaders
Commerce Engine's brand artwork: Paper's Warp shader in the brand palette, with named looks, a framework-agnostic mount and a still per look that paints instantly while the shader comes up.
- Looks, not features —
aurora,tide,meadow,lagoon; each is a set of colours and warp geometry any surface can ask for. - One mount for every framework —
mountShader(host, options)returns a handle; Angular, React, Vue, Svelte and plain scripts are each a few lines over it. - No blank frame — the host paints a 144×82 still of the look from its first paint, the canvas mounts invisible and fades in after its first render. Without WebGL 2, the still is what a viewer sees.
- Respectful by default — holds the frame under
prefers-reduced-motion, pauses while the tab is hidden or the host is off-screen (Paper), survives a lost WebGL context.
Install
pnpm add @commercengine/shaders@paper-design/shaders comes with it, pinned: the looks are written against its uniforms.
Use
import { mountShader } from "@commercengine/shaders";
const artwork = mountShader(document.querySelector("#artwork"), { preset: "aurora" });
// later
artwork.setPreset("tide");
artwork.setPaused(true);
artwork.dispose();The host is any block element with a size; the mount fills it. Paper makes the host position: relative unless it is positioned already, so a host that is itself position: absolute; inset: 0 inside a parent works. Paint the brand's Jungle (#002e27) under it if the host can ever be seen before the still.
Options
| Option | Default | What it does |
| --- | --- | --- |
| preset | "aurora" | The look. |
| settings | {} | Any of the look's values, over the preset's (see Tuning). |
| paused | false | Hold the frame. |
| still | true | Paint the look's still on the host until the canvas is ready. |
| maxPixelCount | 1_200_000 | Physical pixels the shader renders at most; larger hosts upscale. |
| startFrame | 12_000 | Where Paper's clock starts; the stills are taken here. |
| onError | — | Called once if the shader cannot mount (no WebGL 2, compile failure). |
Handle
setPreset, setSettings, setPaused, dispose, plus reducedMotion (the viewer's preference), onReducedMotionChange(listener) for a pause control that wants to disable itself, and settings (the look currently playing, resolved).
In a framework
// React
useEffect(() => {
const artwork = mountShader(ref.current, { preset });
return () => artwork.dispose();
}, []);
useEffect(() => artwork?.setPreset(preset), [preset]);// Angular (signals)
private readonly handle = mountShader(inject(ElementRef).nativeElement, { preset: this.preset() });
constructor() {
effect(() => this.handle.setPreset(this.preset()));
inject(DestroyRef).onDestroy(() => this.handle.dispose());
}Tuning a look
import { resolveShaderSettings, shaderPresets, shaderShapes } from "@commercengine/shaders";
mountShader(host, { preset: "aurora", settings: { rotation: 30, shape: "stripes" } });Every value of a look is a knob: the five colors (dark to light; the fourth is the highlight, the fifth what the blend returns to), proportion, softness, distortion, swirl, swirlIterations (the one that costs GPU), shape (checks / stripes / edge), shapeScale, scale, rotation, speed. When a tuned look is worth keeping, paste resolveShaderSettings(preset, overrides) into src/settings.ts as a preset and regenerate the stills.
Stills
src/stills.ts is generated: one frame of each look at its start frame, captured from the shader itself in headless Chrome at a desktop viewport and scaled to 144×82 WebP (about 2 KB each).
pnpm build # the capture page loads dist/still-page.iife.js
pnpm stills # CHROME_PATH / PROBE_PORT override the defaults
pnpm build # the library carries the new stillsDevelopment
pnpm dev # tsdown --watch
pnpm test # vitest
pnpm typecheckReleases go through changesets from the repository root — see RELEASE.md.
