Baktiyar Ablaikhan

Checker whip

Source
arlan.me/vault ↗
Author
Arlan
Licence
MIT

Loading…

A nine-row checkerboard whips sideways row by row as new scenes slide in, changing colour worlds and sometimes tearing odd rows against even.

Code

"use client";

import { CanvasStudy, easeInOutExpo, prog, type Frame, type Painter } from "./stage";

export const BG = "#1c3fd6";
const ROWS = 9;
const SCENE = 1.5; // seconds per scene
const PER_WORLD = 3; // scenes per colour world

type World = { tones: string[]; tear: boolean };
const WORLDS: World[] = [
  { tones: ["#ff47ad", "#1c3fd6", "#0b1f7a", "#f4efe6"], tear: false },
  { tones: ["#5ee8ff", "#0066ff", "#003399", "#ffffff"], tear: true },
  { tones: ["#e3dac6", "#c96a4a", "#2b2b2b", "#f2c14e"], tear: false },
  { tones: ["#151515", "#f2f2f2", "#ff3b30", "#7a7a7a"], tear: true },
  { tones: ["#c8f560", "#1a7f4b", "#0d3b24", "#f7f3e8"], tear: false },
];

function scene(n: number) {
  const world = WORLDS[Math.floor(n / PER_WORLD) % WORLDS.length];
  const i = n % PER_WORLD;
  const T = world.tones;
  return { a: T[i % 4], b: T[(i + 1) % 4], rect: T[(i + 2) % 4], world };
}

export function create(): Painter {
  return ({ ctx, w, h, t }: Frame) => {
    const rowH = h / ROWS;
    const cell = rowH;
    const n = Math.floor(t / SCENE);
    const u = t - n * SCENE;
    const cur = scene(n);
    const next = scene(n + 1);
    const rectW = Math.round((w * 0.42) / cell) * cell;
    const rectX = Math.round((w - rectW) / 2 / cell) * cell;

    const drawRow = (r: number, s: ReturnType<typeof scene>, ox: number) => {
      const y = r * rowH;
      if (ox >= w || ox <= -w) return;
      ctx.save();
      ctx.beginPath();
      ctx.rect(Math.max(0, ox), y, Math.min(w, w + ox) - Math.max(0, ox), rowH + 0.5);
      ctx.clip();
      const cols = Math.ceil(w / cell) + 1;
      for (let c = 0; c < cols; c++) {
        ctx.fillStyle = (r + c) % 2 ? s.b : s.a;
        ctx.fillRect(ox + c * cell, y, cell + 0.5, rowH + 0.5);
      }
      if (r >= 3 && r <= 5) {
        ctx.fillStyle = s.rect;
        ctx.fillRect(ox + rectX, y, rectW, rowH + 0.5);
      }
      ctx.restore();
    };

    // the incoming world decides how the rows move
    const sweepDir = n % 2 ? -1 : 1;
    for (let r = 0; r < ROWS; r++) {
      const k = easeInOutExpo(prog(u, SCENE - 0.75 + r * 0.045, 0.42));
      const dir = next.world.tear ? (r % 2 ? 1 : -1) : sweepDir;
      const off = dir * k * w;
      drawRow(r, cur, off);
      drawRow(r, next, off - dir * w);
    }
  };
}

export default function CheckerWhip() {
  return (
    <CanvasStudy
      create={create}
      background={BG}
      still={1.1}
      label="A nine-row checkerboard whips sideways row by row as new checkered scenes slide in, changing colour worlds and sometimes tearing odd rows against even."
    />
  );
}
Prompt

Build "Checker whip", a canvas animation. What it does: A nine-row checkerboard whips sideways row by row as new scenes slide in, changing colour worlds and sometimes tearing odd rows against even. Write it as a single self-contained React component in TypeScript that fills its parent (w-full h-full), styled with Tailwind CSS. Technique: an HTML canvas, a requestAnimationFrame loop, pointer tracking. It sits on a light background, in a 16:10 frame. Respect prefers-reduced-motion with a still state, pause work when the element is off screen, and keep it accessible. Original: https://www.arlan.me/vault (Arlan, MIT).