GodUIGodUI
129Follow on X

Anatomy of Holographic Card

How the same spring that tilts Tilt Card also slides an iridescent foil across a dark base, and why one blend mode makes it read as light instead of paint.

01/03Four masks, one blend mode
Base
dark radial gradient that seats the foil so it reads as light
Foil
diagonal gradient, mix-blend-color-dodge
Sparkle
glitter dot mask, same blend mode
Glare
soft radial highlight, tracks the pointer
tsx
const FOIL_LAYER ="pointer-events-none absolute inset-0 mix-blend-color-dodge " +"[background-size:200%_200%] [background-position:var(--holo-x)_var(--holo-y)] " +"[mask-image:radial-gradient(75%_75%_at_var(--holo-x)_var(--holo-y),#000_0%,rgba(0,0,0,0.4)_55%,transparent_100%)]";
01

Four masks, one blend mode

Holographic Card reuses Tilt Card's entire pointer-to-spring pipeline (same normalized coordinates, same spring constants) and spends its own complexity on one thing: making the tilt also drive a foil that looks like it's catching real light.

The card is a dark base with three absolute inset-0 layers stacked on top: a diagonal foil gradient, a glitter dot mask, and a soft radial glare. Foil and glitter both use mix-blend-color-dodge, which lightens the dark base wherever the layer above it is bright and leaves it untouched where the layer is dark. That makes a flat gradient read as light hitting foil instead of a color painted over the card.

The mask-image does the rest: the foil is masked to a radial gradient centered on the same pointer position, so the iridescence concentrates where the "light" is hitting rather than flooding the whole card evenly.

The tilt is borrowed wholesale. rotateX/rotateY come from the identical spring pipeline as Tilt Card, with the same normalized pointer and the same { stiffness: 170, damping: 12, mass: 0.1 } spring:

tsx
 
const rotateX = useTransform(sy, [-0.5, 0.5], [maxTilt, -maxTilt]);
const rotateY = useTransform(sx, [-0.5, 0.5], [-maxTilt, maxTilt]);

What's new is that the same sx/sy pair also feeds a CSS variable, on top of the rotation:

tsx
 
const holoX = useTransform(sx, [-0.5, 0.5], ["0%", "100%"]);
const holoY = useTransform(sy, [-0.5, 0.5], ["0%", "100%"]);
// …
style={{ rotateX, rotateY, "--holo-x": holoX, "--holo-y": holoY }}
02/03The foil that catches the light

background-position: var(--holo-x) var(--holo-y)

Tilt
rotateX/rotateY from the shared spring
Foil
same sx/sy, mapped to a background position
tsx
const FOIL: Record<HolographicVariant, string> = {rainbow: "[background-image:linear-gradient(115deg,#ff2d95,#ffd84d,#4dff9e,#4dd2ff,#a24dff,#ff2d95)]",aurora:  "[background-image:linear-gradient(115deg,#22d3ee,#34d399,#a3e635,#38bdf8,#22d3ee)]",galaxy:  "[background-image:linear-gradient(115deg,#a855f7,#ec4899,#3b82f6,#c026d3,#a855f7)]",gold:    "[background-image:linear-gradient(115deg,#b45309,#fbbf24,#fffbeb,#fbbf24,#b45309)]",};
02

The foil that catches the light

--holo-x/--holo-y land on background-position for the foil, the sparkle mask, and the glare's radial-gradient center all at once. One pointer value moves the card and slides the foil across it in the same gesture. Turn the card and the color travels with the tilt, like a real trading-card holo catching the light at an angle.

Each variant swaps the gradient stops but keeps the mechanism identical.

Optional: the gyroscope. On a touch device there's no pointer to move, so gyroscope opts into reading deviceorientation instead. gamma/beta map onto the same px/py motion values the pointer would have set. iOS gates the sensor behind a permission prompt that must fire from a user gesture, so the listener requests it lazily on the first tap rather than on mount:

tsx
 
const requestOnTap = () => {
  orientationEvent.requestPermission?.().then((state) => {
    if (state === "granted") attach();
  });
};
if (typeof orientationEvent.requestPermission === "function") {
  window.addEventListener("pointerdown", requestOnTap);
} else {
  attach();
}

Because it writes into the same px/py values as the pointer handler, the spring, the tilt, and the foil don't need to know which input drove them.

03/03Result
Founding Member

GodUI

Move your pointer across the card.

03

The result

Move your pointer across each card. The foil, the glitter, and the glare shift together as it tilts.

The holo effect borrows the spring, adds a CSS variable, and masks a gradient to it.

Accessibility

Under prefers-reduced-motion, the card renders still (no spring, no pointer listener, no gyroscope), with the foil and glare pinned to a fixed resting position instead of centered dynamically:

tsx
 
if (reduceMotion) {
  return (
    <div style={{ "--holo-x": "50%", "--holo-y": "40%" }}>
      {/* base, foil, children, edge — no glare, no motion */}
    </div>
  );
}

Motion Score

Holographic CardCC: Paint-triggering
StransformPointer tilt
Cbackground-positionHolographic sheen gradient follow
Each property is graded by how the browser runs it, from S (composited off the main thread) down to F (layout thrashing); the component takes the worst. MotionScore methodology →