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).