Cinder Pike Reveal

  • Skia
  • Reanimated
  • Motion sensors

Three sword strikes tear open a sealed card, light spills through the cuts, and the card underneath lifts out of its stone well. The scene is a single opaque Skia canvas, and most of its timings exist so that one moment hides the seam of the next.

View plans

Purchases are not open yet.

Timing a strike

Each tap sends the blade along a traced tear, fastest at the moment of impact. Light fills the cut at full strength 37 ms after contact, while the blade still covers it, so the fade-up is never visible. The card squashes 12% and springs back as the bounce dies away, which gives the hit its weight. Early taps wait in a queue of three, and a strike starts only after the previous blade has finished.

const SLASH_SPEED = 0.75;export const slashMs = (ms: number) => Math.round(ms / SLASH_SPEED);// …export const PLASMA_DELAY_MS = slashMs(28);// …export const SQUASH_AMOUNT = 0.12;export const SQUASH_PERIOD = slashMs(190); // ms per oscillationexport const SQUASH_DECAY = slashMs(105); // ms - amplitude falls to 1/e

Holes without path booleans

Each cut is an inverse clip placed inside the previous one, so three holes open in the cover without any path boolean operations. The cut outlines are pushed past the edge of the card, so a tear that runs off the material still cuts cleanly, and clamped copies of the same outlines shape the glow around each tear.

<Group clip={holes[2]} invertClip>  <Group clip={holes[1]} invertClip>    <Group clip={holes[0]} invertClip>      <CoverCard texture={texture} />    </Group>  </Group></Group>

Compiling before the first cut

Skia builds a GPU program the first time something is drawn, not when it is declared. A hidden warm-up draws the real cut paths under the real stage transform, beneath an opaque fill, before any strike can run, and is then unmounted, because an empty clip still records every frame. The reveal burst avoids image filters altogether: its softness comes from a gradient in screen blend, which took it from 54 to 60 fps.

if (spent || !warm) return null;// …<Path key={`core-${index}`} path={corePath} color="#ffffff">  <Shadow dx={0} dy={0} blur={PREWARM_SIGMAS.coreInner[0]} color="#5a1226b0" inner />  <Shadow dx={1.5} dy={2.5} blur={PREWARM_SIGMAS.coreInner[1]} color="#7a1c3899" inner /></Path>// …<Group blendMode="screen">

The reveal and the card

After the third cut a 700 ms flash plays, and the halo is already gone by 40% of that window so the celebration never sits over the card as it rises. Both ends are one expression: a fast smoothstep in, a slower one subtracted out. Beyond it the card answers to drag, hover and device tilt, and reduced motion shrinks the travel to 35% rather than freezing it.

const p = (clock.value - revealAt.value) / REVEAL_FLASH_MS;if (p < 0 || p > 1) return 0;// In fast, out well before the lift starts, so it never competes with the card rising.return smoothstep(0, 0.05, p) * (1 - smoothstep(0.12, 0.4, p));