Baktiyar Ablaikhan

Swinging letters

Source
arlan.me/vault ↗
Author
Arlan
Licence
MIT

Loading…

Large coloured letters swing across the card, turning edge-on at the sides so only two or three read at once, and the word quietly changes.

Code

"use client";

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

export const BG = "#f7f8fb";
const WORDS = ["SWING", "PIVOT", "TURN", "ORBIT", "SWAY"];
const GROUNDS = ["#f7f8fb", "#dcd6f6", "#f9d3d0", "#d9e6f7", "#f6f1e3"];
const COLORS = ["#ff4b2b", "#2f58ff", "#ffb400", "#14a56a", "#ff5ea6", "#7a3cf0"];
const WORD_TIME = 6;
const PERIOD = 5.2;

function mix(a: string, b: string, k: number) {
  const pa = [1, 3, 5].map((i) => parseInt(a.slice(i, i + 2), 16));
  const pb = [1, 3, 5].map((i) => parseInt(b.slice(i, i + 2), 16));
  return `rgb(${pa.map((v, i) => Math.round(v + (pb[i] - v) * k)).join(",")})`;
}

export function create(): Painter {
  return ({ ctx, w, h, t, fonts }: Frame) => {
    const k = Math.floor(t / WORD_TIME);
    const local = t - k * WORD_TIME;
    const word = WORDS[k % WORDS.length];
    const fade = clamp(local / 0.8);
    ctx.fillStyle = mix(GROUNDS[(k + GROUNDS.length - 1) % GROUNDS.length], GROUNDS[k % GROUNDS.length], fade);
    ctx.fillRect(0, 0, w, h);

    const n = word.length;
    const S = w * 0.29;
    ctx.font = `800 100px ${fonts.sans}`;
    const fs = Math.min(h * 0.66, (S * 1.02 * 100) / ctx.measureText("W").width);
    ctx.font = `800 ${fs}px ${fonts.sans}`;
    ctx.textBaseline = "middle";

    const A = ((n - 1) / 2) * S + w * 0.06;
    const X = A * Math.sin((t / PERIOD) * Math.PI * 2);
    // around a word change every letter turns fully edge-on for a moment
    const edge = Math.min(Math.abs(local), Math.abs(WORD_TIME - local));
    const swap = clamp(edge / 0.35);

    for (let i = 0; i < n; i++) {
      const x = w / 2 + (i - (n - 1) / 2) * S - X;
      const th = clamp((x - w / 2) / (w * 0.44), -1, 1) * (Math.PI * 0.48);
      const sx = Math.cos(th) * swap;
      if (sx < 0.01) continue;
      const ch = word[i];
      const cw = ctx.measureText(ch).width;
      const color = COLORS[(i + k * 2) % COLORS.length];
      ctx.save();
      // a turning card: shift toward the hinge side a little as it narrows
      ctx.translate(x + Math.sin(th) * cw * 0.12, h / 2);
      ctx.scale(sx, 1 + (1 - Math.cos(th)) * 0.04);
      ctx.fillStyle = color;
      ctx.fillText(ch, -cw / 2, 0);
      ctx.fillStyle = `rgba(0,0,0,${0.22 * Math.abs(Math.sin(th))})`;
      ctx.fillText(ch, -cw / 2, 0);
      ctx.restore();
    }
  };
}

export default function SwingLetters() {
  return (
    <CanvasStudy
      create={create}
      background={BG}
      still={2.6}
      label="Large coloured letters swing back and forth, turning edge-on at the sides so only two or three read at once; the word quietly changes."
    />
  );
}
Prompt

Build "Swinging letters", a text animation. What it does: Large coloured letters swing across the card, turning edge-on at the sides so only two or three read at once, and the word quietly changes. 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).