Pre-release GitHub test build. The API and visuals are still being exercised against real interfaces, and the package has intentionally not been published to npm yet.
A small, dependency-free library that gives existing DOM elements a moving iridescent sheen. It leaves the target's markup, inline styles, event handlers, focus behavior, and form semantics alone.
The effect is rendered in a shared, non-interactive WebGL overlay layer. Its
rounded edge uses the portfolio bubble material's Fresnel, capped rim,
specular, and anisotropic highlight equations on an analytic rounded rectangle.
One pointer listener, one GPU context, and one animation loop serve every
attached element in a document.
The overlay follows scrolling, resizing, clipping, and the target's computed
corner radii, and is removed completely on teardown.
Native pointer, touch, Space, and Enter presses briefly suppress the hard white
rim while preserving the iridescent edge, then recover with a short material
release rather than mutating or restyling the target.
Color phase is normalized to the viewport and damped independently from light
position, so small controls do not cycle rapidly through the spectrum when the
pointer crosses them. Pointer position can track immediately or trail with a
controlled material delay using mouseLag.
npm install github:kimestelle/iridescenceOr clone the repository and run the full local checks:
git clone https://github.com/kimestelle/iridescence.git
cd iridescence
npm ci
npm testimport { attachIridescence } from "@softcopy/iridescence";
const sheen = attachIridescence(document.querySelector("button")!, {
movement: "pointer",
roughness: 0.24,
intensity: 0.72,
mouseLag: 0.18,
spectrum: "film",
});
sheen.update({ roughness: 0.5, intensity: 0.45 });
sheen.pause();
sheen.play();
sheen.destroy();For a selector, NodeList, array, or other iterable, use the group helper:
import { iridescence } from "@softcopy/iridescence";
const cards = iridescence(".material-card", {
movement: "ambient",
speed: 0.18,
spectrum: ["#8b72ff", "#4dcde2", "#78dc9a", "#e4cf69", "#d884b4"],
});
cards.update({ intensity: 0.5 });
cards.destroy();| Option | Default | Meaning |
|---|---|---|
movement |
"pointer" |
Shared mouse/pointer light or autonomous ambient drift |
roughness |
0.18 |
0…1; broadens the light and reduces spectral coherence |
intensity |
0.52 |
0…4; reflected-light energy, with values above 1 reserved for vivid highlights |
spectrum |
"film" |
film, pearl, oil, cool, chrome, or 2–12 CSS colors |
spread |
0.42 |
0…1; size of the virtual area light |
bevel |
0.82 |
0…1; strength and width of the rounded edge response |
curvature |
0 |
0…1; bevel depth from a shallow rim to a full spherical profile |
rimThickness |
1 |
0.5…2; multiplies the optical white and spectral rim width |
angle |
118 |
Fine surface-grain direction in degrees |
speed |
0.22 |
Ambient drift speed, from 0…4 |
mouseLag |
0.18 |
0…1; pointer response from immediate to a heavy trailing delay |
seed |
element-derived | Stable variation for ambient movement |
blendMode |
"screen" |
screen, soft-light, overlay, color-dodge, or plus-lighter |
borderRadius |
computed | Optional CSS radius override |
respectReducedMotion |
true |
Holds ambient light still when reduced motion is requested |
roughness, spread, curvature, and mouseLag are clamped to 0…1; intensity is
clamped to 0…4, and rimThickness to 0.5…2. Invalid modes, speeds,
spectra, and non-finite numbers throw early.
Create the effect after the element mounts and destroy it when the component unmounts. The package has no React dependency.
useEffect(() => {
if (!ref.current) return;
const sheen = attachIridescence(ref.current, options);
return () => sheen.destroy();
}, []);The library is browser-only at attachment time, but safe to import during SSR. Selector targets resolve against the current global document; passing an element uses that element's own document, including iframes.
The demo exposes both helpers globally. Select an element in the Elements
panel ($0), then run:
const effect = attachIridescence($0, {
intensity: 1.4,
roughness: 0.12,
bevel: 0.9,
mouseLag: 0.18,
});
effect.destroy();After a future npm release, the same test can be loaded from an ESM CDN:
const { attachIridescence } = await import(
"https://esm.sh/@softcopy/iridescence@0.1.0"
);
window.testSheen = attachIridescence($0, { intensity: 1.4 });Remote imports can be blocked by a site's Content Security Policy. The renderer
adapts to ordinary rectangular, rounded, pill, and circular DOM boxes. Arbitrary
SVG paths, rotated geometry, and continuously transformed elements are not
automatic shape matches; call refresh() while authored geometry moves, or use
a dedicated renderer for non-box silhouettes.
The full materials-engine research evaluates complex-IOR Fresnel, thin-film interference, and rough microfacet response in a GPU compositor. This package keeps the perceptual relationship that matters at DOM scale: incidence moves the spectrum, roughness widens the reflected source and averages away color separation, and a neutral specular core anchors the sheen as reflected light. It deliberately drops material baking, geometry compilation, WebGPU, textures, and framework adapters.
Run the local instrument after building:
npm run build
python3 -m http.server 4173
# open http://localhost:4173/demo/The repository also includes a live compatibility and load harness using real controls from Material Web, MUI, Bootstrap, and Shoelace. It applies one shared iridescence group to mixed buttons, switches, sliders, chips, tags, checkboxes, and inputs, then compares baseline and effect frame timing from 48 through 400 targets.
npm run stress
# open http://127.0.0.1:5173/stress/Use Run full suite for the paired baseline/effect test. The destroy/attach control exercises complete teardown, and the synthetic pointer sweep makes the pointer-driven material reproducible without touching every specimen. These UI packages are development-only dependencies and are excluded from the published package contents.
npm ci
npm test
npm run pack:checkCI runs the tests and package-content check on every push and pull request.
Generated dist/ files are intentionally excluded from Git; prepare and
prepack create them from the TypeScript source for GitHub installs and future
npm packages.
The demo bundles Absans by Valerio Monopoli and Necto Mono by Marco Condello,
both distributed by Collletttivo under the SIL Open Font License 1.1. The full
font license is included beside the font files in demo/fonts/OFL.txt.