Holographic Scratch Card

  • Skia
  • Reanimated
  • Gesture Handler

A foil panel you rub away with a finger. The card is one Skia layer: a solid grey rectangle with your stroke drawn through it in clear, so the foil is genuinely removed rather than a wipe playing back over it.

Erasing instead of wiping

The sheen shader fills the card's rounded rect first. The foil goes on top inside a layer group, which gives that group its own offscreen buffer, and the finger's path is stroked across it in clear. Clearing only reaches the buffer, so the stroke punches holes in the foil and leaves the shader beneath untouched. Round caps and joins keep the torn edge soft.

<Group clip={cardRRect}>  <Fill>    <Shader source={holographic} uniforms={uniforms}/>  </Fill></Group><Group layer clip={cardRRect}>  <RoundedRect rect={cardRRect} color="#B8BCC6"/>  <Path    path={path}    style="stroke"    strokeWidth={strokeWidth}    strokeCap="round"    strokeJoin="round"    blendMode="clear"  /></Group>

A stroke that stops growing

Every pointer move appends a point, so a patient scratch would grow the path without limit. Past 2,500 points decimateScratchStroke keeps every second one and appends the new point, halving the stroke instead of extending it — the shape barely changes, because the discarded points sat a pixel apart. Points are stored as fractions of the card, so a resize moves them with it.

// decimateScratchStroke(stroke, next, maxPoints)const stride = stroke.length >= maxPoints ? 2 : 1;const result: ScratchPoint[] = [];for (let i = 0; i < stroke.length; i += stride)  result.push(stroke[i]);result.push(next);return result;

Where the rainbow comes from

The foil's underside is five lines of SKSL. A cosine palette turns one scalar into a full spectrum, and that scalar is uv.x * 3 + uv.y * 2 + u_time * 0.6, which tilts the bands diagonally and drifts them sideways. The sheen is a second wave narrowed by a smoothstep between 0.90 and 0.98, so only the crest survives, then multiplied by 0.6 to push it past white.

float2 uv = xy / u_res;float a = uv.x * 3.0 + uv.y * 2.0 + u_time * 0.6;half3 col = 0.5 + 0.5 * cos(6.28318 * (a + half3(0.0, 0.33, 0.67)));float sheen = smoothstep(0.90, 0.98, abs(sin(uv.x * 8.0 - u_time)));col += sheen * 0.6;