Loot Chest Unlock

  • Skia
  • Reanimated
  • Haptics

A chest that strains, splits along its seam and throws light and embers out of the gap. Four rarities run through the same code: nothing branches on tier, it only reads different numbers from a table.

View plans

Purchases are not open yet.

Rarity is a table, not a branch

Each tier carries a tint, an edge colour and a blade count that climbs from ten to twenty-four. The shader, the card frame and the seam all take those values as inputs, so a legendary unlock is grander than a common one without a single conditional in the render path. Adding a fifth tier means adding a row.

export const RARITY_TIERS: readonly RarityTier[] = [  { name: 'COMMON', color: '#9BB0C9', accent: '#5C6E86', rayCount: 10 },  { name: 'RARE', color: '#3FA9FF', accent: '#1E5FB0', rayCount: 14 },  { name: 'EPIC', color: '#B86BFF', accent: '#6A2FB0', rayCount: 18 },  { name: 'LEGENDARY', color: '#FFC93C', accent: '#FF7A1A', rayCount: 24 },];

Embers that cool instead of blinking out

A spark holds full opacity for the first half of its life and only then begins to fade, while its scale shrinks steadily by 40% across the whole span. Fading from the start would read as a dimmer switch; shrinking without fading would read as dots vanishing. Doing one late and one throughout is what makes them read as cooling.

const u = s.life > 0 ? t / s.life : 1;const alpha = u >= 1 ? 0 : u < 0.5 ? 1 : 1 - (u - 0.5) / 0.5;const scale = s.scale * (1 - 0.4 * Math.min(1, u));return {  x: vx * t,  y: vy * t + 0.5 * gravity * t * t,  rot: s.rot0 + s.rotV * t,

Rays that lean skyward

Light leaves a chest through its lid, so the fan is weighted upward rather than drawn as an even star. The bias is a single term from the ray's angle, remapped to between 0.55 and 1, which dims the downward blades without deleting them. The blades themselves are a cosine raised to the sixth power, with a finer counter-rotating set at a third of the strength.

float ang = atan(d.y, d.x);float rot = u_time * 0.25;// …float upBias = 0.55 + 0.45 * (-sin(ang)); // sin(ang) is +down in screen spaceupBias = clamp(upBias, 0.0, 1.0);