Baktiyar Ablaikhan

Inflating word

Source
arlan.me/vault ↗
Author
Arlan
Licence
MIT

Loading…

A heavy white oblique word types itself, inflates into warped giant letters with coloured ink slipping out, swings over an arch and un-types.

Code

"use client";

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

export const BG = "#000000";
const DUR = 5.8;
const WORDS: [string, string][] = [
  ["grid", "#ff3d6e"],
  ["kern", "#2fd3ff"],
  ["serif", "#ffd23a"],
  ["glyph", "#7cff6b"],
  ["type", "#b07cff"],
];
const SLANT = -0.24;

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 [word, color] = WORDS[n % WORDS.length];
    const letters = [...word];

    const fs = Math.min(h * 0.2, w * 0.11);
    ctx.font = `800 ${fs}px ${fonts.sans}`;
    ctx.textBaseline = "middle";
    const widths = letters.map((c) => ctx.measureText(c).width);
    const total = widths.reduce((a, b) => a + b, 0);

    // typing on and off
    const typed = letters.filter((_, i) => u >= 0.15 + i * 0.1).length;
    const untyped = letters.filter((_, i) => u >= 4.6 + (letters.length - 1 - i) * 0.08).length;
    const visible = Math.max(0, typed - untyped);
    if (visible === 0) return;

    const grow = easeInOut(prog(u, 0.95, 0.75));
    const shrink = easeInOut(prog(u, 3.25, 0.7));
    const big = grow * (1 - shrink);
    const maxS = (w * 0.86) / total;
    const S = 1 + (maxS - 1) * big;
    const swing = easeInOut(prog(u, 1.7, 1.5));
    const arch = Math.sin(swing * Math.PI) * big; // bend of the arch
    const sway = -Math.sin(swing * Math.PI * 2) * 0.7 * big; // over the arch and back
    const slip = big; // coloured ink slipping out from under the white

    const drawWord = (fill: string, ox: number, oy: number) => {
      let x = -total / 2;
      for (let i = 0; i < visible; i++) {
        const cw = widths[i];
        const xn = (x + cw / 2) / (total / 2); // −1..1 along the word
        x += cw;
        const warp = 1 + 0.32 * big * Math.sin(i * 1.7 + t * 2.4);
        const lift = -arch * (1 - xn * xn) * h * 0.22 + sway * xn * h * 0.06;
        const tilt = arch * xn * 0.5 + sway * 0.12;
        ctx.save();
        ctx.translate(w / 2 + (xn * total) / 2 * S + sway * w * 0.06 + ox, h / 2 + lift + oy);
        ctx.rotate(tilt);
        ctx.scale(S, S * warp);
        ctx.transform(1, 0, SLANT, 1, 0, 0);
        ctx.fillStyle = fill;
        ctx.fillText(letters[i], -cw / 2, 0);
        ctx.restore();
      }
    };

    if (slip > 0.01) drawWord(color, fs * 0.07 * S * slip, fs * 0.05 * S * slip);
    drawWord("#ffffff", 0, 0);
  };
}

export default function InflateWord() {
  return (
    <CanvasStudy
      create={create}
      background={BG}
      still={2.4}
      label="A heavy white oblique word types itself, inflates into giant warped letters with coloured ink slipping out beneath, swings over an arch and un-types."
    />
  );
}
Prompt

Build "Inflating word", a text animation. What it does: A heavy white oblique word types itself, inflates into warped giant letters with coloured ink slipping out, swings over an arch and un-types. 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).