# Holo Foil: a build prompt for your AI coding agent. A holographic foil card in WebGL2 with thin-film iridescence, a diffraction rainbow, glints and embossed type.

> Source: <https://gist.github.com/vannarotroeung/da8c32c635cf8ae5c6f5447e1a7bb9d5>
> Published: 2026-09-28 20:35:16+00:00

You are a senior graphics and front-end engineer. Build the component described below from scratch, in TypeScript, to the standard of a premium product launch film: every frame should look intentional, crisp and finished. Work in small steps, run it in a browser after each step, and compare what you see with the description before moving on.

A holographic foil card under studio light: guilloche engraving, embossed title and ink-printed small type, thin-film iridescence, a diffraction-grating rainbow and twinkling glints, tilting toward the pointer on a spring.

- Renders with WebGL2.
- Time is deterministic: the same time gives the same frame, so it can be scrubbed and exported frame by frame.
- It loops seamlessly: a `loop` param in seconds, and the frame at t equals the frame at t + loop (check it numerically).
- It looks alive with no pointer and no input (an idle choreography), because screenshots, videos and most visitors never touch it.
- Respect `prefers-reduced-motion` (keep the state change, drop the motion) and pause when off screen.

Names from "the core" (the post chain, spring helpers) belong to the original library; write your own equivalents.

A limited-edition holographic foil card photographed under studio light. A metal card floats over a studio backdrop. Its foil carries a guilloche seal (or concentric rings, fine waves, a hobnail diamond grid or a brushed hairline finish), an embossed title in a toned foil or laser etching, and a subtitle and serial printed in opaque ink, the way real foil cards and packaging print small type. Thin-film iridescence tints the base metal, and a diffraction-grating rainbow sweeps across the card as it tilts. Glitter glints twinkle, the polished rim catches the lights and the card casts a soft contact shadow. The card turns toward the pointer on a spring. With no pointer it performs a slow idle tilt, so the sheen never stops moving. A full-bleed layout fills the container with foil, as a background for headlines.

``` js
import { HoloFoil } from './holo-foil';
HoloFoil.mount(el, { title: 'AURUM', baseColor: '#e4b565', gratingField: 'linear' });
```

The whole scene is one native-resolution HDR pass into the core `PostChain`, which adds bloom,
film glow, tone mapping, grain and dither. Almost everything on a card has edges (lines, letters,
ink, the rim, glints), so none of it can drop to a lower resolution. Instead, the expensive static
parts are baked once at setup:

| Bake | Size | What it holds | 
|---|---|---|
| Spectral LUT | 256 x 16 RGBA16F | wavelength (300 to 900 nm) x spectral blur (0 to 150 nm) to linear RGB | 
| Thin-film LUT | 512 x 1 RGBA16F | optical path difference (0 to 3000 nm) to the interference term in RGB | 
| Film noise | 512 x 512 RGBA16F, tileable | film thickness, grating pitch, brushing wander | 
| Title SDF, subtitle SDF | 2048 wide R16F each (1024 on `low` ) | signed distance in texels; the title is embossed, the subtitle is printed | 

Each pixel's ray hits the rotated card plane. That gives exact perspective and card-space coordinates whose screen derivatives drive all the anti-aliasing: silhouette coverage, the box-filtered engraving, ink and letter coverage, and the glint footprint.

- 
**Thin-film interference** (`thin-film-interference` ). The base metal carries a lacquer film
whose thickness varies with a baked noise field and the engraving. The two-beam interference
term`cos(2 PI OPD / lambda)` is integrated against the CIE observer once per optical path
difference, following the spectral-integration idea of Belcour and Barla, and looked up per pixel
with`OPD = 2 n t cos(theta_t)` . A palette can replace the physical colours (`paletteMix` ).
- 
**Diffraction grating** (`diffraction-grating` ). The rainbow comes from the grating equation`d (sin theta_m - sin theta_i) = m lambda` , evaluated per pixel in vector form. The tangential
part of`V + L` along the grating vector gives the wavelength of orders 1 and 2, and its part
along the grooves must vanish. The grating field sets the character:
  - `seal` : radial around the guilloche seal, a rainbow beam that swings with the tilt.
  - `linear` : a straight band that sweeps across the card, the classic foil-stamp look.
  - `fan` : a slowly turning band that keeps the centre calm.
  - `pattern` : follows the engraving (seal, disc spokes, fan, prism mosaic).
 `bandWidth` scales the along-groove tolerance, light size included, from a scratch-thin line to
a broad wash. The rainbow's strength scales with the metal's reflectance, so a black card
diffracts less.
- 
**Analytic colour matching functions** (`cie-cmf-fit` ). Both LUTs use the Wyman, Sloan and
Shirley multi-lobe Gaussian fit of the CIE 1931 observer. The spectral LUT pre-convolves it with
the spectral blur, and colours outside sRGB are lifted toward white instead of clipped, so each
band keeps its hue.
- 
**Procedural glints** (`procedural-glints` ). One flake per hashed cell (integer PCG hash, no
time input) has its own micro-normal and grating angle. It lights up only when a light's
half-vector lands within about two degrees of that normal, so glints twinkle with the angle,
never with time. Flakes are drawn as footprint-aware sub-pixel Gaussians, and a layer fades out
once its cells shrink below about 2.5 px.
- 
**Box-filtered engraving** (`box-filtered-engraving` ). Every line field is a periodic groove
profile with closed-form antiderivatives, so its mean slope, mean squared slope and coverage over
the pixel footprint are exact. Frequency clamping is split by linearity. The slope feeds the
non-linear specular and rainbow, so it fades near the pixel grid. The coverage only blends
materials, so its profile widens toward a near-sinusoid. The result has no moire at any tilt.
The brushed hairline is a band-limited sum of sines across the brushing whose terms fade before
they can alias.
- 
**Toksvig and anisotropic specular anti-aliasing** (`toksvig-specular-aa` ). The slope lost
inside a pixel is accumulated as a 2D covariance. Normally it widens the lobe (Toksvig), which
turns distant engraving into satin instead of sparkle. On the brushed hairline, the excess
across the brushing stretches the highlight with a three-tap lookup, the streak of machined
metal.
- 
**Embossed title from an exact distance transform** (`sdf-text-emboss` ). The title is drawn as
opaque white on opaque black. The opaque ground matters: canvas pixels are premultiplied, and
only an opaque ground keeps the antialiased coverage intact. The exact Felzenszwalb-Huttenlocher
EDT turns it into a signed distance, and a box-filtered bevel over that distance gives three
title styles:
  - raised foil in a toned (smoked) foil;
  - laser etching, matte, light on dark metals and dark on bright ones;
  - hologram letters that also diffract in their own direction.
 A polished clean zone around the letters, like a foil-stamping knockout, and optional smooth text windows keep them readable on busy patterns.
- 
**Printed ink** (part of`sdf-text-emboss` ). The subtitle and serial are an opaque print from
their own distance field, with analytic coverage:
  - matte ink lit by the rig's diffuse irradiance on the card face, computed per frame;
  - a hairline highlight on the edges facing the key light;
  - a hairline shadow on the foil beyond the opposite edges.
 It reads at every pose, which foil-on-foil small type never does.
- 
**Analytic SDF coverage** (`sdf-coverage-aa` ). The rounded-rectangle silhouette, the side wall,
the letters, the ink and the windows are anti-aliased from their distance fields divided by the
screen-space gradient.
- 
**Guilloche** (`guilloche` ). The seal is two families of sine-modulated rings,`F = K (r +- A(r) sin(n theta + beta r))` , crossing like a rose-engine lattice, with a clean boss
and a double keyline. The keyline is an exactly windowed groove family. Around it runs a field
of long, gentle waves.
- 
**Schlick Fresnel** (`schlick-fresnel` ). The film-tinted reflectance rises toward white at
grazing angles, which makes the rolled rim and the far edge of a tilted card flare.
- 
**Closed-form springs** (`analytic-spring` ). Pointer tilt, the pointer-to-idle hand-off and
the hover lift use the core`springStepInto` , with no allocation per frame. The pointer spring
stays parked on the idle pose while disengaged, and`springiness` sets the damping ratio.
- 
**Film finish** (`physically-based-bloom` ,`filmGlow` ,`agx-tonemap` ,`film-grain` ,`ign-dither` ). The core PostChain adds a tight bloom on highlights and glints with a hint of
warm film glow, then tone mapping (AgX, or Neutral for brand-exact gold and pearl), grain and
dither.
- 
Also used: `oklab-mixing` ,`pcg-hash` ,`deterministic-time` (a frame is a function of params
and time;`loop` closes exactly),`adaptive-resolution` ,`visibility-gating` ,`reduced-motion` .

The studio rig itself matters for legibility. A wide white bounce below the lens fills the lower half of the card, as on a real product set, and the top scrim never falls to black. Foil behind printed type therefore never reflects pure black.

Each preset has its own rainbow character.

- **Silver Holo** (default): silver foil, guilloche seal, radial seal beam (the signature),
smoked raised title, near-black ink.
- **Gold Reserve** : gold metal, guilloche as engraving only, a linear diagonal band (25 degrees)
sweeping across like a foil stamp, laser-etched Didone title, dark ink, Neutral tone mapping.
- **Rose Prism** : rose metal, hobnail pyramids diffracting as a prism mosaic, smooth text windows
behind the smoked title and the ink edition line.
- **Obsidian** : black brushed hairline metal, one thin desaturated spectral band, light laser marks
and warm-white ink. Restrained.
- **Pearl Light** : pearl white on a light backdrop, soft pastel iridescence on a fan field, etched
title, dark ink, visible cast shadow.
- **Full-bleed Foil** : fine waves filling the container with a fan band that moves across a calm
centre, for headlines on top.

For each preset, 25 poses (t = 0 to 12 s in 0.5 s steps) are rendered twice: once with the `textmask` debug view (title interior, a 3 to 11 px ring of foil around it, subtitle interior and its ring) and once as the beauty frame. The table shows the WCAG contrast ratio between the mean luminance of each text interior and its surrounding foil: the minimum over all poses, with the median in brackets.

| Preset | Title 1440x900 | Ink 1440x900 | Title 390x844 DPR 3 | Ink 390x844 DPR 3 | Ink 390x844 DPR 1 | 
|---|---|---|---|---|---|
| Silver Holo | 3.64 (4.57) | 3.53 (4.49) | 3.58 (4.56) | 3.43 (4.28) | 2.79 (3.40) | 
| Gold Reserve | 3.32 (4.87) | 3.67 (4.28) | 3.47 (5.05) | 3.88 (4.47) | 3.71 (4.13) | 
| Rose Prism | 3.95 (4.74) | 3.16 (4.44) | 3.97 (4.69) | 3.00 (4.36) | 2.47 (3.48) | 
| Obsidian | 3.82 (5.83) | 5.12 (6.92) | 3.84 (5.93) | 5.25 (7.05) | 5.25 (7.14) | 
| Pearl Light | 6.83 (7.78) | 7.04 (8.07) | 6.58 (7.57) | 7.13 (8.27) | 5.96 (6.83) | 

Measured on an Apple M4 Pro (ANGLE Metal, Chrome headless), taking the best of three runs:

- 60 fps (display cap) at 1440x900 at DPR 1 and at DPR 2, for the card and the full-bleed layout.
- GPU frame cost, measured as render plus a full pipeline sync, best of 3 x 40 frames: about 2 ms at 1440x900 DPR 1, and at DPR 2 (2880x1800) 3.8 ms (Silver), 3.8 ms (Rose), 4.4 ms (Obsidian, with anisotropic highlights) and 5.6 ms (full-bleed).
- `quality: 'low'` halves the text fields, uses a single glint layer, drops the side wall and the
second grating orientation in the grooves.

- On a DPR 1 phone-size frame, the serial line is width-limited (28 characters on a 335 px card, cap height about 7 px). The ink then measures 2.5 to 2.8:1 at its worst pose on Silver and Rose; it holds 3:1 or better at DPR 3.
- Printed type needs bright foil behind it, so the studio fills the card with a white bounce, and Silver and Gold now read lighter and more pastel than a black-studio shot would.
- Obsidian's spectral band is a soft stripe about a tenth of the card wide, not a razor line. With a flat card and a moderate lens, perspective alone cannot make it thinner.
- The rosette softens to a woven texture at DPR 1; it is crisp from DPR 2.
- Lettering uses system font stacks (Copperplate, Bodoni 72 or Didot, Avenir Next), and other
systems fall back to Georgia or Arial. Web fonts are picked up once `document.fonts` is ready.
- Changing `title` ,`subtitle` ,`typeface` or`aspect` rebuilds both text fields on the main
thread: about 60 to 100 ms, once per change.
- Resizing reshuffles the glints, whose grid follows the card's pixel size.
- Reflections come from a fixed procedural studio rig (five softboxes and two hard lights,
rotatable with `lightAngle` ), not an HDR environment map.
- WebGL2 only. There is no WebGPU path, and no CSS fallback beyond a static gradient card.

- **Thin-film interference.** Iridescence from light reflecting off both sides of a film a few hundred nanometres thick; hue shifts with viewing angle like soap bubbles and holographic foil. Source: Belcour, Barla, A Practical Extension to Microfacet Theory for the Modeling of Varying Iridescence (SIGGRAPH 2017),[https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html](https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html)
- **Diffraction grating sheen.** The grating equation d (sin theta_m − sin theta_i) = m lambda evaluated per pixel from the groove direction and the light and view vectors, giving the ordered rainbow bands of holographic foil; the light size blurs the spectrum. Source: Jos Stam, Simulating Diffraction (GPU Gems, ch. 8),[https://developer.nvidia.com/gpugems/gpugems/part-i-natural-effects/chapter-8-simulating-diffraction](https://developer.nvidia.com/gpugems/gpugems/part-i-natural-effects/chapter-8-simulating-diffraction)
- **Analytic CIE colour matching functions.** A multi-lobe piecewise Gaussian fit of the CIE 1931 observer turns wavelengths and spectra into XYZ without tables, used to bake spectral and thin-film LUTs with smooth, hue-true rainbow colours. Source: Wyman, Sloan, Shirley, Simple Analytic Approximations to the CIE XYZ Color Matching Functions (JCGT 2013),[https://jcgt.org/published/0002/02/01/](https://jcgt.org/published/0002/02/01/)
- **Procedural glints.** Hashed per-cell flakes with their own micro-normals light up only when a light half vector falls inside a narrow cone, so glitter twinkles with the angle, never with time, and fades by level of detail instead of aliasing. Source: Zirr, Kaplanyan, Real-time Rendering of Procedural Multiscale Materials (I3D 2016),[https://research.nvidia.com/publication/2016-02_real-time-rendering-procedural-multiscale-materials](https://research.nvidia.com/publication/2016-02_real-time-rendering-procedural-multiscale-materials)
- **Analytically box-filtered engraving.** Periodic groove profiles with closed-form antiderivatives give the exact mean slope and coverage over each pixel footprint; slope fades and coverage widens near the pixel grid, so fine engraving never turns into moire. Source: Inigo Quilez, Filterable procedurals,[https://iquilezles.org/articles/filterableprocedurals/](https://iquilezles.org/articles/filterableprocedurals/)
- **Toksvig specular anti-aliasing.** Normal variation lost inside a pixel is added to the roughness, so unresolved relief reads as satin instead of flickering sparkles. Source: Michael Toksvig, Mipmapping Normal Maps (NVIDIA),[https://developer.download.nvidia.com/whitepapers/2006/Mipmapping_Normal_Maps.pdf](https://developer.download.nvidia.com/whitepapers/2006/Mipmapping_Normal_Maps.pdf)
- **Embossed type from an exact distance transform.** Canvas text drawn white on opaque black is turned into a signed distance field with the exact linear-time Euclidean distance transform, then bevelled into raised, etched or hologram lettering with crisp highlights. Source: Felzenszwalb, Huttenlocher, Distance Transforms of Sampled Functions (Theory of Computing 2012),[https://theoryofcomputing.org/articles/v008a019/](https://theoryofcomputing.org/articles/v008a019/)
- **Analytic coverage from distance fields.** Silhouettes and letters are anti-aliased by dividing their signed distance by its screen-space gradient, which keeps edges crisp under perspective, at any resolution and with no multisampling. Source: Chris Green, Improved Alpha-Tested Magnification for Vector Textures and Special Effects (SIGGRAPH 2007),[https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf](https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf)
- **Guilloche engraving.** Interlaced families of sine-modulated rings and waves, the lattice a rose engine cuts into banknotes and watch dials, generated as level sets so every line is analytic and filterable. Source: Wikipedia, Guilloche,[https://en.wikipedia.org/wiki/Guilloch%C3%A9](https://en.wikipedia.org/wiki/Guilloch%C3%A9)
- **Schlick Fresnel.** Reflectance rises toward grazing angles; the cheap polynomial approximation that makes glass and chrome edges glow. Source: Wikipedia, Schlick's approximation,[https://en.wikipedia.org/wiki/Schlick%27s_approximation](https://en.wikipedia.org/wiki/Schlick%27s_approximation)
- **Closed-form damped springs.** Motion follows the exact solution of a damped harmonic oscillator, parameterised by response time and damping ratio. Stable at any frame rate, interruptible mid-flight with velocity preserved. Source: Wikipedia, Harmonic oscillator (damped),[https://en.wikipedia.org/wiki/Harmonic_oscillator](https://en.wikipedia.org/wiki/Harmonic_oscillator)
- **Mip-chain bloom with Karis average.** Highlights are blurred through a chain of 13-tap downsamples and tent-filtered upsamples. The first downsample weights by inverse luminance so a single hot pixel cannot flicker into a disc. Source: Jimenez, Next Generation Post Processing in Call of Duty: Advanced Warfare (SIGGRAPH 2014); LearnOpenGL, Physically Based Bloom,[https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom](https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom)
- **Film glow.** On film, bright light passes through the emulsion, reflects off the base and re-exposes the red-sensitive layer, leaving a warm glow around highlights. The film glow recreates it from the wide bloom mips, highlight-only and tinted red-orange. Source: Wikipedia, on the glow film leaves around highlights,[https://en.wikipedia.org/wiki/Halation](https://en.wikipedia.org/wiki/Halation)
- **AgX tone mapping.** A filmic curve that desaturates extreme highlights toward white the way film does, instead of clipping them to flat primaries. Neutral (Khronos PBR Neutral) is offered when brand colours must stay on-hue. Source: Troy Sobotka, AgX,[https://github.com/sobotka/AgX](https://github.com/sobotka/AgX)
- **Luminance-weighted film grain.** Gaussian grain re-rolled at 24 fps and weighted toward the mid-tones, like real film stock. It adds tactile texture and hides compression banding in exported video. Source: Newson, Delon, Galerne, Realistic Film Grain Rendering (IPOL 2017),[https://www.ipol.im/pub/art/2017/192/](https://www.ipol.im/pub/art/2017/192/)
- **Interleaved gradient noise dithering.** A triangular-distribution dither of one 8-bit step is added before quantisation. It removes the banding every smooth web gradient shows on 8-bit displays, invisibly. Source: Jorge Jimenez, Next Generation Post Processing in Call of Duty: Advanced Warfare (SIGGRAPH 2014),[https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare](https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare)
- **OKLab colour interpolation.** Gradients are blended in OKLab, a perceptual colour space, so the midpoint between two vivid colours stays vivid instead of passing through grey. Stops are joined with a Catmull-Rom spline so a stretched gradient shows no crease at each stop. Source: Bjorn Ottosson, A perceptual color space for image processing (2020),[https://bottosson.github.io/posts/oklab/](https://bottosson.github.io/posts/oklab/)
- **Integer PCG hashing.** Per-pixel randomness from integer permutation hashes instead of fract(sin(x)), which loses precision on mobile GPUs and produces visible patterns. Source: Jarzynski, Olano, Hash Functions for GPU Rendering (JCGT 2020),[https://jcgt.org/published/0009/03/02/](https://jcgt.org/published/0009/03/02/)
- **Deterministic time.** A frame is a function of params and time, never of wall-clock or frame count, so any moment can be re-rendered exactly: scrubbing, seamless loops and frame-perfect video export. Source: Remotion docs, The fundamentals (a frame is a function of the frame number),[https://www.remotion.dev/docs/the-fundamentals](https://www.remotion.dev/docs/the-fundamentals)
- **Adaptive resolution.** Frame time is tracked with an exponential moving average; the render scale drops when frames are slow and recovers with hysteresis, keeping motion fluid on weak GPUs. Source: Unity Manual, Dynamic Resolution,[https://docs.unity3d.com/Manual/DynamicResolution.html](https://docs.unity3d.com/Manual/DynamicResolution.html)
- **Visibility gating.** Rendering stops when the element is off-screen (IntersectionObserver) or the tab is hidden, so an effect costs nothing when nobody can see it. Source: MDN, Intersection Observer API,[https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
- **Reduced-motion rendering.** With prefers-reduced-motion, components render one still frame and only update on interaction or param changes (or run slowed, if configured). Source: W3C, Understanding SC 2.3.3 Animation from Interactions,[https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html](https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html)

| Param | Group | Type | Default | Range | What it does | 
|---|---|---|---|---|---|
| `layout` | Content | choice | card | card / fullbleed | Layout | 
| `title` | Content | text | NOCTURNE | up to 24 characters | Title | 
| `subtitle` | Content | text | LIMITED EDITION 0427 / 1000 | up to 48 characters | Subtitle | 
| `typeface` | Content | choice | engraved | engraved / serif / sans | Typeface | 
| `textStyle` | Content | choice | mirror | mirror / etched / hologram | Raised foil: polished letters in a toned foil. Laser etched: matte marks, light on dark metals and dark on bright ones. Hologram: toned letters that also diffract in their own direction. | 
| `titleTone` | Content | number | 0.1 | 0.08 to 1.6 | Reflectance of raised title foil relative to the field: below 1 a smoked foil, above 1 a brighter one. Keeps the title readable at every angle. | 
| `inkColor` | Content | colour | #141417 |  | Opaque ink for the subtitle and serial: near black on light or gold foil, warm white on dark foil. | 
| `textWindows` | Content | switch | false |  | Smooth unengraved panels behind the title and the subtitle, like windows in the foil, for busy patterns. | 
| `pattern` | Shape | choice | guilloche | guilloche / rings / waves / diamonds / hairline | Engraving | 
| `lineDensity` | Shape | number | 44 | 16 to 120 | Engraved lines per half card height. Capped automatically so the line period never drops below about six pixels. | 
| `emboss` | Shape | number | 0.5 | 0 to 1.2 | Emboss depth | 
| `radius` | Shape | number | 0.12 | 0 to 0.5 | Fraction of the shorter half side. | 
| `aspect` | Shape | number | 1.586 | 0.6 to 2.4 | 1.586 is a bank card, 0.716 a trading card. | 
| `baseColor` | Color | colour | #c3c7ce |  | Reflectance of the metal under the film: silver, gold, rose, graphite. | 
| `palette` | Color | palette | #ff9ad5, #9fd7ff, #b8ffd8, #ffe39a |  | Iridescence palette | 
| `paletteMix` | Color | number | 0 | 0 to 1 | 0 uses the physical thin-film colours, 1 walks the palette instead with the same thickness and angle. | 
| `iridescence` | Color | number | 0.32 | 0 to 1.5 | Iridescence | 
| `filmThickness` | Color | vec2 | 260, 640 | 60 to 1400 | Range of the lacquer film thickness across the foil. | 
| `background` | Color | colour | #09090b |  | Background | 
| `sheen` | Light | number | 1.3 | 0 to 3 | Strength of the diffraction rainbow. | 
| `gratingField` | Light | choice | pattern | pattern / seal / linear / fan | Orientation of the hologram grating. Pattern: follows the engraving (radial around the seal, fan for waves, mosaic for the hobnail). Linear: one band sweeping across, the classic foil stamp. Fan: a slowly turning band. | 
| `gratingAngle` | Light | angle | 25 | 0 to 360 degrees | Direction of the linear and fan fields. | 
| `bandWidth` | Light | number | 1 | 0.15 to 2.5 | Width of the rainbow band: thin like a scratch at the bottom, broad at the top. | 
| `rainbowSaturation` | Light | number | 1 | 0 to 1 | Rainbow saturation | 
| `gratingDensity` | Light | number | 1150 | 500 to 2400 lines/mm | Micro-groove density of the hologram; sets which angles show which colours. | 
| `glitter` | Light | number | 0.7 | 0 to 2 | Glitter | 
| `roughness` | Light | number | 0.3 | 0.03 to 0.8 | Micro roughness of the foil: mirror at the bottom, brushed satin at the top. | 
| `lightAngle` | Light | angle | 0 | 0 to 360 degrees | Light rig angle | 
| `shadow` | Light | number | 0.85 | 0 to 1.5 | Shadow | 
| `tilt` | Motion | number | 14 | 0 to 35 deg | Tilt | 
| `idleSpeed` | Motion | number | 1 | 0 to 3 | Idle speed | 
| `springiness` | Interaction | number | 0.45 | 0 to 1 | 0 settles without overshoot, 1 wobbles like a card on a spring. | 
| `interactive` | Interaction | switch | true |  | Tilt to pointer | 
| `loop` | Motion | number | 0 | 0 to 60 s | Seconds until the idle motion repeats exactly (0 = never repeats). Use for seamless video loops. | 
| `exposure` | Post | number | 1 | 0.1 to 4 | Exposure | 
| `toneMapping` | Post | choice | agx | agx / aces / neutral / none | Tone mapping | 
| `bloom` | Post | number | 0.24 | 0 to 2 | Bloom | 
| `bloomThreshold` | Post | number | 1.35 | 0 to 4 | Bloom threshold | 
| `bloomScatter` | Post | number | 0.56 | 0 to 0.95 | Bloom radius | 
| `filmGlow` | Post | number | 0.14 | 0 to 2 | Warm film glow around highlights. | 
| `filmGlowTint` | Post | colour | #ff4a14 |  | Film glow tint | 
| `streaks` | Post | number | 0 | 0 to 2 | Anamorphic streaks | 
| `streakTint` | Post | colour | #7aa6ff |  | Streak tint | 
| `aberration` | Post | number | 0.08 | 0 to 1 | Chromatic aberration | 
| `vignette` | Post | number | 0.3 | 0 to 1 | Vignette | 
| `grain` | Post | number | 0.03 | 0 to 0.2 | Film grain | 
| `grainSize` | Post | number | 1.2 | 1 to 4 px | Grain size | 
| `seed` | Advanced | seed | 11 |  | Seed | 

Every value across every range must still look intentional. Params that resize buffers (counts, grid sizes, text that becomes a texture) rebuild resources; everything else changes live and smoothly.

- **Silver Holo** : the defaults
- **Gold Reserve** : baseColor #e4b565, background #0b0906, pattern guilloche, gratingField linear, gratingAngle 25, bandWidth 1.15, textStyle etched, typeface serif, title AURUM, subtitle RESERVE  No. 0019, inkColor #1b130b, iridescence 0.3, palette #ffd9a0, #ffb3a8, #fff1c4, #d9e8a8, paletteMix 0.35, filmThickness 300, 560, sheen 0.62, glitter 0.5, roughness 0.26, toneMapping neutral, filmGlow 0.18
- **Rose Prism** : baseColor #eba396, background #0f0a0c, pattern diamonds, textWindows true, title PRISM, subtitle EDITION ROSE  07 / 50, typeface sans, titleTone 0.1, inkColor #140b0d, iridescence 0.6, palette #ff9ac1, #c7a6ff, #ffc9a6, #9fe7ff, paletteMix 0.5, sheen 1.1, glitter 1, gratingDensity 1300, roughness 0.2, emboss 0.22
- **Obsidian** : baseColor #2a2b30, background #050506, pattern hairline, emboss 0.14, gratingField linear, gratingAngle 96, bandWidth 0.15, rainbowSaturation 0.4, textStyle etched, title OBSIDIAN, subtitle BLACK SERIES  001, inkColor #efe6d6, iridescence 0.08, filmThickness 380, 900, sheen 2.2, glitter 0.16, roughness 0.1, gratingDensity 1000, filmGlow 0.16
- **Pearl Light** : baseColor #f2ede6, background #e8e4de, pattern waves, typeface serif, textStyle etched, title Pearl, subtitle MAISON  NO. 12, inkColor #3a3230, iridescence 0.7, palette #ffd6e8, #d6f0ff, #e6ffe9, #fff0d1, paletteMix 0.8, sheen 0.28, glitter 0.3, roughness 0.45, shadow 1, toneMapping neutral, bloom 0.15, filmGlow 0.04, vignette 0.12, exposure 0.95
- **Full-bleed Foil** : layout fullbleed, title (empty), subtitle (empty), pattern waves, gratingField fan, gratingAngle 70, bandWidth 0.75, baseColor #aab0ba, tilt 8, sheen 1.5, glitter 0.8, iridescence 0.3, roughness 0.14, vignette 0.4, exposure 0.85

``` js
const controller = mount(container: HTMLElement, params?: Partial<Params>);
controller.set(partialParams);   // live, smooth changes
controller.destroy();            // frees every GPU and DOM resource
```

The container sets the size; the component fills it and follows resizes (ResizeObserver) at the device pixel ratio, capped for performance.

- 60 fps at 1440 x 900 and DPR 2 on an Apple M-series laptop; a lower-quality path for weak GPUs.
- No allocation inside the render loop (reuse typed arrays and objects); seeded randomness, never Math.random in the render path.
- Colour in linear light, converted to sRGB once at the end with a little dither so gradients never band.
- Anything with edges (text, highlights, grain, lines) is computed at full resolution and anti-aliased; smooth, expensive fields may run at reduced resolution.
- A static CSS fallback when the graphics API is missing.
- Accessible: the canvas is hidden from assistive tech, any text stays real and readable, controls are real buttons and inputs with focus rings and keyboard support.
- No em dashes or emoji in any copy; no third-party brands in the demo.

Deliver one module with the API above, a demo page that shows the defaults full screen, and a short README listing each technique with its source.
