How it works
01 Diffraction rainbow
Real foil splits light by viewing angle. I fake it with a hue that runs along a diagonal of the card and shifts with the tilt. Two band frequencies mixed so it reads as fine + broad interference rather than a plain gradient.
// diffraction rainbow — hue from a diagonal coord, shifted by tilt float dir = uv.x * 0.9 + uv.y * 0.55; float shift = u_pointer.x * 0.4 - u_pointer.y * 0.3 + u_time * 0.015; vec3 band1 = hue2rgb(fract(dir * 3.0 + shift)); // broad bands vec3 band2 = hue2rgb(fract(dir * 7.0 - shift)); // fine bands vec3 foil = mix(band1, band2, 0.4);
02 Glitter & glare
A sparse grid of hashed cells twinkles on its own clock (colored by the same hue), and a soft specular band biases toward the tilt direction, the highlight you’d get raking a real card under a lamp. The foil is quiet at rest and blooms as you tilt.
03 The tilt drives everything
One eased pointer value does double duty: it 3D-rotates the card (CSS perspective + rotate) and feeds the shader, so the sheen and the geometry move together. The
foil canvas sits over the photo with mix-blend-mode: color-dodge so it reads as light,
not paint. On mobile, deviceorientation supplies the tilt.
// per frame: ease the pointer, tilt the card, feed the shader
px += (targetX - px) * 0.09;
node.style.transform =
`perspective(900px) rotateY(${px * 11}deg) rotateX(${-py * 11}deg)`;
gl.uniform2f(u_pointer, px, py); // foil + glare follow the same tilt A self-contained Svelte action on the card container. Honors prefers-reduced-motion (static sheen, no tilt); pauses off-screen. Open to borrow.