ChangelogThanks to our recent supportersA few more voluntary tips came in this week - thank you to everyone who chipped in, named or anonymous. Every note gets read, and the acknowledgment list is updated.See the thank-you list
Quantized Line Field
Neo-Brutalist
HomeStylesTemplates

Diffraction Foil

Back to Diffraction Foil
Download HTML

Diffraction Foil · 1500 nm

Light is the ink.

Holographic hot-stamping foil whose color is computed from a diffraction grating. The pointer is a lamp; rainbow bands sweep around it across every element, and silver, star, glitter, and pearl are different groove layouts.

Color comes from the grating equation

Drag the lamp angle and groove period to see where each diffraction order lands.

λ = d · sin θ / m

At small angles every order falls outside the visible range and the foil stays silver. As the angle grows the first order sweeps violet to red, then the second and third orders follow and overlap.

  • Order 1795 nm
  • Order 2397 nm
  • Order 3265 nm
Color after summing orders

The pointer is the lamp

This section is the CSS fallback, following the same rules as the WebGL version: the lamp sits 300 px above the screen and orbits after 4 idle seconds.

Lumen

Move the pointer: the large panel's bands turn around the lamp, the star lights two opposite radii, and glitter flashes facet by facet.

A material is a groove layout

Five materials share one grating function; brushed and pearl need per-pixel rendering, so see them in the live study above.

  • Film

    Two crossed gratings averaged over brushed silver. The everyday holographic film.

  • Star

    Concentric grooves: only the radius facing the lamp, and its opposite, shows color.

  • Glitter

    Facets at random angles; only those whose grating meets the eye flash.

  • WebGL →

    Brushed

    One straight grating with streaks along the grooves, stretching color into long bands. WebGL only.

  • WebGL →

    Pearl

    Thin-film interference instead of a grating: varying thickness shifts color softly, like a soap bubble. WebGL only.

Components

CSS-fallback buttons, switch, redeem field, and card. Ink sits only on light foil.

No packs opened

LimitedNo. 042
Lumen

On: diffraction over silver

Format: three groups of four

Palette

Sheet, silver, and ink are fixed; color comes only from the spectrum.

  • Sheet#0c0b10
  • Panel#141319
  • Silver#c9c8d4
  • Ink#16151c
  • Text#ececf2
  • Secondary#a9a8b6

Spectrum strip: silver in the middle where wavelengths are invisible, then orders one, two, and three outward

One WebGL context for the page

  1. 01

    One hidden canvas

    Browsers cap live WebGL contexts, so the page opens a single WebGL2 context and switches materials by uniform.

  2. 02

    Render each element

    Resize the viewport to the element, pass the lamp position, material, and tilt, then draw one full-screen triangle.

  3. 03

    Copy into place

    drawImage copies the result into the element's own 2D canvas; elements whose lamp, size, and tilt are unchanged are skipped.

Touch lands on the material

Press, toggle, and drag bend only the shading normal; the color jumps while the element stays put.

Max tilt14°Toward the press point
Stiffness320Return on release
Damping ratio0.32One overshoot
Toggle impulse260Switch, check, open a pack

Do and don't

  • +Compute color with the grating equation λ = d * |dot(h, k)| / m, d = 1500 nm, summing orders 1-3 at weights 1, 0.5, 0.3
  • +Treat the pointer as a lamp 300 px above the screen shared by every foil element; after 4 idle seconds let it orbit slowly
  • +Seat the foil on a bg-[#0c0b10] print sheet with text-[#ececf2] body copy and text-[#a9a8b6] secondary text
  • +Use one WebGL2 context for the page; render each element in turn and copy it into its own 2D canvas with drawImage
  • +Ship a CSS fallback: window a 1024x1 spectrum strip by lamp direction, rotate a ::before layer to the groove angle, and screen it over silver
  • +Bend only the shading normal on press, toggle, and drag; spring back with stiffness 320 and damping ratio 0.32
  • +Print ink text only on foil-light; mark focus with focus-visible:outline-[#c9c8d4]
  • +Honor reduced motion: park the lamp at 72%, 22%, no orbit and no overshoot
  • −Do not fake foil with a fixed rainbow gradient and an animated background-position
  • −Do not open one WebGL context per element
  • −Do not flood the surface with rainbow; most of it should read as silver
  • −Do not place text on saturated spectral bands
  • −Do not cycle colors with hue-rotate animations
  • −Do not interleave layout reads with style writes; measure everything, then write

Reproduction prompt

A complete specification: grating, lamp, materials, single context, fallback, and checks. Compare the result with the live study above.

Download reference HTML
Create one polished, responsive, self-contained HTML file titled “Diffraction Foil”. It must work when opened directly in a modern browser. Use native HTML, CSS, JavaScript, WebGL2, and Canvas 2D only. Do not use libraries, frameworks, modules, build tools, image files, image URLs, or network requests other than one optional web font. Return the complete HTML and nothing else.

The subject is holographic hot-stamping foil rendered by its physics, not a rainbow gradient. Color comes from a diffraction grating lit by a lamp that follows the pointer. Every rule below exists so the foil reads as metal that changes with angle.

1. Page
Use a near-black print sheet (#0c0b10) with faint crop marks. At the top, a wide trading card with a 1.8:1 aspect ratio, about 72% of the stage height, rounded corners at 4.8% of the short side. The brand name “Lumen” is stamped in silver foil across the card; a thin glitter border runs inside the edge. Clicking or pressing Enter flips the card to a back face with a guilloché rosette. Below the card, lay out a compact component sheet on dark panels: a foil swatch row, a foil button, a switch, a slider, a progress bar, a badge, and a round seal. On screens under 680 pixels, use a portrait 63:88 card and a single-column sheet.

2. The lamp
Treat the pointer as a small lamp 300 CSS pixels above the screen plane. Store its position in viewport pixels and ease toward the target every animation frame with factor 1 - exp(-dt * 16), clamping dt to 0..50 ms because frame timestamps can arrive out of order. When the pointer has not moved for 4 seconds, leaves the window, or the device has no hover, let the lamp drift on its own: x = width * (0.5 + 0.42 sin(0.21 t)), y = height * (0.4 + 0.32 sin(0.33 t + 1.2)), easing with 1 - exp(-dt * 2.5). With device orientation available, offset the lamp by up to 35% of the viewport from a slowly re-centering baseline (blend 2% per event toward the current beta and gamma, divide the difference by 25 degrees, clamp to -1..1). With prefers-reduced-motion, park the lamp at 72% x, 22% y and never orbit.

3. Spectral color
Convert wavelength in nanometres to color with the Wyman, Sloan and Shirley 2013 piecewise-Gaussian fit of the CIE 1931 matching functions. X = 1.056 g(l, 599.8, 37.9, 31) + 0.362 g(l, 442, 16, 26.7) - 0.065 g(l, 501.1, 20.4, 26.2); Y = 0.821 g(l, 568.8, 46.9, 40.5) + 0.286 g(l, 530.9, 16.3, 31.1); Z = 1.217 g(l, 437, 11.8, 36) + 0.681 g(l, 459, 26, 13.8), where g(x, m, a, b) uses spread a below the mean and b above it. Return black outside 380..780 nm. Convert to linear sRGB with the standard matrix, pull each channel 34% toward Y so pure spectral colors do not break at the sRGB gamut edge, clamp negatives to zero, and scale by 0.8. Tone-map the final color with the Narkowicz ACES fit and encode to sRGB.

4. Diffraction grating
For a point on the foil, take the unit direction to the lamp and the unit direction to the viewer (straight out of the screen). Project both onto the foil plane and add them; call the 2D sum h. The groove normal k is a unit vector in the plane. With groove period d = 1500 nm, order m reflects wavelength l = d * |dot(h, k)| / m. Sum orders 1, 2 and 3 with weights 1, 0.5 and 0.3, multiply by 1.6, then tone-map. Where |dot(h, k)| is small the foil shows silver; where it grows the bands sweep through violet, blue, green, yellow and red, then repeat in higher orders. Never paint a fixed gradient and animate its position.

5. Materials
Implement each material as a different groove layout feeding the same grating function:
- Film: two crossed gratings (k and k rotated 90 degrees), averaged, over brushed silver.
- Brushed: one straight grating at a chosen angle plus anisotropic brushed-metal streaks along the grooves.
- Star: concentric grooves, so k is the radial direction from the element center; only the radius pointing toward the lamp, and its opposite, lights up.
- Glitter: a field of small facets with random groove angles and tilts on a dark base; a facet flashes only when its grating sends color toward the viewer.
- Pearl: thin-film interference instead of a grating, thickness 300 + 260 noise nm, refractive index 1.38, twelve wavelength samples from 400 to 697 nm, normalized per channel.
Add a soft specular highlight where the lamp reflects and keep the base silver between the colored bands.

6. One WebGL context for every element
Browsers cap simultaneous WebGL contexts, so never create one per element. Create a single hidden WebGL2 canvas and one fragment shader with a material uniform. Each foil element owns a plain 2D canvas sized to its CSS box times devicePixelRatio (capped at 2). For each visible element, resize the shared viewport to that element, set its rectangle, the lamp position relative to it, its material and its current tilt, draw one full-screen triangle, then copy the result into the element canvas with drawImage. Skip elements whose lamp position, size, tilt and texture version have not changed since the last frame. Use IntersectionObserver so offscreen elements cost nothing.

7. CSS fallback
Offer a WebGL / CSS toggle and fall back automatically when WebGL2 is unavailable. Precompute a 1024 by 1 spectrum strip for t from -1.25 to 1.25, coloring each texel with the grating at |t|. For every foil element, compute the vector from the lamp to its center in CSS pixels, set --k to its angle, and select a window of the strip: along k, t(x) = x / sqrt(x^2 + 300^2) at the element center and at both ends, widened 2.6 times so one element shows two or three colors. Paint the strip in a ::before layer rotated by --k with background-size and background-position from that window, blended with screen over a silver gradient (overlay on light variants that carry ink text). Add an ::after layer with a radial highlight at the lamp position and a tiled sparkle texture. Read every element rectangle first, then write all custom properties, to avoid layout thrashing.

8. Touch and press
Pressing a foil control bends its surface: on pointer down, tilt the normal up to 14 degrees toward the pressed point; on release, spring back with stiffness 320 and damping ratio 0.32 so it overshoots once. Toggling a switch or checkbox adds a short angular impulse; dragging the slider tilts toward the drag direction. Only the shading normal changes; the element itself does not move. The card tilts a few degrees toward the pointer and flips on click with a spring.

9. Access and performance
All controls are native buttons and inputs, reachable by keyboard, with visible focus rings. Ink text sits on light foil, never on saturated bands. With reduced motion, stop orbiting, remove spring overshoot, and keep a stable frame. Pause rendering while the document is hidden. Keep the page under one shared WebGL context and redraw only changed elements.

10. Inspect before returning
Check these observable conditions: moving the pointer moves the rainbow bands across every foil element at once, centered on the lamp; leaving the pointer still for four seconds starts a slow orbit; the colors run violet to red as the angle grows and repeat in higher orders; star shows two opposite colored rays; glitter flashes individual facets; there is exactly one WebGL context; the CSS toggle keeps the same layout and still follows the lamp; reduced motion yields a still frame; DPR 2 doubles each element canvas backing store.

Checks

Each condition should be observable in the result.

LampBands move across every foil element at once, centered on the lamp
IdleAfter 4 idle seconds the lamp starts a slow orbit
SpectrumColor runs violet to red as the angle grows and repeats in higher orders
ContextsExactly one WebGL context on the page
FallbackThe CSS fallback keeps the layout and still follows the lamp
Reduced motionThe lamp parks and the frame holds still
Grooves 1500 nm · Lamp 300 pxOne WebGL context