Baktiyar Ablaikhan

Punch-in word

Source
arlan.me/vault ↗
Author
Arlan
Licence
MIT

Loading…

A two-line word in heavy type punches its lines apart, rushes at you with every letter turned, holds, and drops out of the bottom.

Code

"use client";

import { CanvasStudy, easeIn, easeOut, easeOutBack, hash, prog, type Frame, type Painter } from "./stage";

export const BG = "#050505";
const INK = "#f4f1ea";
const DUR = 3.1;
const WORDS: [string, string][] = [
  ["BIG", "DEAL"],
  ["HEAVY", "TYPE"],
  ["WIDE", "OPEN"],
  ["LOUD", "NOISE"],
];

export function create(): Painter {
  return ({ ctx, w, h, t, fonts }: Frame) => {
    ctx.fillStyle = BG;
    ctx.fillRect(0, 0, w, h);
    const n = Math.floor(t / DUR);
    const u = t - n * DUR;
    const lines = WORDS[n % WORDS.length];

    ctx.font = `800 100px ${fonts.sans}`;
    const widest = Math.max(...lines.map((s) => ctx.measureText(s).width));
    const fs = Math.min(h * 0.3, (w * 0.56 * 100) / widest);
    ctx.font = `800 ${fs}px ${fonts.sans}`;
    ctx.textBaseline = "middle";
    ctx.fillStyle = INK;

    const arrive = easeOut(prog(u, 0, 0.32));
    const gap = easeOutBack(prog(u, 1.05, 0.22));
    const rush = easeIn(prog(u, 1.3, 0.5));
    const drop = easeIn(prog(u, 2.35, 0.5));
    if (drop >= 1) return;

    const lead = fs * 0.88;
    const scale = 1 + rush * 2.6;
    const cx = w / 2;
    const cy = h / 2 - (1 - arrive) * h * 1.1 + drop * h * 2.6;

    lines.forEach((line, li) => {
      const ly = ((li - 0.5) * lead + (li === 0 ? -1 : 1) * gap * fs * 0.42 * (1 - rush)) * (1 - 0.3 * rush);
      const total = ctx.measureText(line).width;
      let x = -total / 2;
      [...line].forEach((ch, i) => {
        const cw = ctx.measureText(ch).width;
        const lx = x + cw / 2;
        x += cw;
        const ang = (hash(i + li * 9, n) - 0.5) * 0.9 * rush;
        ctx.save();
        ctx.translate(cx + lx * scale, cy + ly * scale);
        ctx.rotate(ang);
        ctx.scale(scale, scale);
        ctx.fillText(ch, -cw / 2, 0);
        ctx.restore();
      });
    });
  };
}

export default function PunchWord() {
  return (
    <CanvasStudy
      create={create}
      background={BG}
      still={0.8}
      label="A two-line word in heavy type punches its lines apart, rushes toward you with every letter at its own angle, holds, and drops away."
    />
  );
}
Prompt

Build "Punch-in word", a text animation. What it does: A two-line word in heavy type punches its lines apart, rushes at you with every letter turned, holds, and drops out of the bottom. 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 dark 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).