Cloudy Blur Reveal
- Source
- arlan.me/vault ↗
- Author
- Arlan
- Licence
- MIT
Loading…
Short maxims — make it obvious, then make it fast, sweat the details, ship it anyway, keep it honest, cut what is dull — dissolve in through a cloudy, blurred front on cycling colour panels.
Code
"use client";
import { createNoise3D } from "simplex-noise";
import { clamp, hex, makeCanvas, mixRGB, mulberry32, paintCloudMask, rgbStr, smooth, useStage, type Scene } from "./stage";
/*
* Vivid panels, one at a time, each with a short line in its own contrasting
* colour. The line dissolves in through a soft noisy diagonal front while
* un-blurring into focus, holds, dissolves out, and the next panel arrives.
*/
const LINES: [string, string, string][] = [
["make it obvious", "#1f3cff", "#ffd84d"],
["then make it fast", "#ff5a1f", "#1a1033"],
["sweat the details", "#0f3d2e", "#9dffb0"],
["ship it anyway", "#f2e8d8", "#c4401a"],
["keep it honest", "#ffc6e0", "#6a0f4a"],
["cut what is dull", "#151515", "#f4f3ef"],
];
const IN = 0.75;
const HOLD = 1.7;
const OUT = 0.5;
const CYCLE = IN + HOLD + OUT;
const GW = 56;
const GH = 35;
function createScene(): Scene {
const noise = createNoise3D(mulberry32(33));
let text: { c: HTMLCanvasElement; ctx: CanvasRenderingContext2D } | null = null;
let tmp: { c: HTMLCanvasElement; ctx: CanvasRenderingContext2D } | null = null;
const mask = makeCanvas(GW, GH);
const img = mask.ctx.createImageData(GW, GH);
let shown = -1;
let r = 1;
let W = 0;
let H = 0;
return {
resize(w, h, dpr) {
r = Math.min(2, dpr);
W = w;
H = h;
text = makeCanvas(w * r, h * r);
tmp = makeCanvas(w * r, h * r);
shown = -1;
},
frame({ ctx, w, h, t }) {
if (!text || !tmp) return;
const n = Math.floor(t / CYCLE);
const local = t - n * CYCLE;
const idx = n % LINES.length;
const [line, bg, fg] = LINES[idx];
const prevBg = LINES[(idx + LINES.length - 1) % LINES.length][1];
// background cross-fades during the reveal
const bgK = smooth(local / (IN * 0.8));
ctx.fillStyle = rgbStr(mixRGB(hex(prevBg), hex(bg), bgK));
ctx.fillRect(0, 0, w, h);
if (shown !== n) {
const c = text.ctx;
c.setTransform(r, 0, 0, r, 0, 0);
c.clearRect(0, 0, W, H);
const size = Math.min(H * 0.17, W * 0.09);
c.font = `500 ${size}px "Helvetica Neue", Inter, "Segoe UI", Arial, sans-serif`;
c.textAlign = "center";
c.textBaseline = "middle";
c.fillStyle = fg;
c.fillText(line, W / 2, H / 2);
shown = n;
}
let prog: number;
if (local < IN) prog = smooth(local / IN);
else if (local < IN + HOLD) prog = 1;
else prog = 1 - smooth((local - IN - HOLD) / OUT);
if (prog <= 0.001) return;
if (prog >= 0.999) {
ctx.drawImage(text.c, 0, 0, w, h);
return;
}
paintCloudMask(mask.ctx, img, noise, prog, t, 0.14, [0.7, 0.3]);
const T = tmp.ctx;
const TW = tmp.c.width;
const TH = tmp.c.height;
T.globalCompositeOperation = "source-over";
T.clearRect(0, 0, TW, TH);
const blur = (1 - prog) * 7 * 2 * r;
T.save();
T.shadowColor = fg;
T.shadowBlur = blur;
T.shadowOffsetX = TW * 2;
T.drawImage(text.c, -TW * 2, 0);
T.restore();
T.globalAlpha = clamp((prog - 0.4) / 0.6);
T.drawImage(text.c, 0, 0);
T.globalAlpha = 1;
T.globalCompositeOperation = "destination-in";
T.imageSmoothingEnabled = true;
T.drawImage(mask.c, 0, 0, TW, TH);
ctx.drawImage(tmp.c, 0, 0, w, h);
},
};
}
export default function RevealPanels() {
const ref = useStage(createScene, 1.5);
return <div ref={ref} className="relative h-full w-full overflow-hidden bg-[#f2e8d8]" />;
}
Prompt
Build "Cloudy Blur Reveal", a text animation. What it does: Short maxims — make it obvious, then make it fast, sweat the details, ship it anyway, keep it honest, cut what is dull — dissolve in through a cloudy, blurred front on cycling colour panels. Write it as a single self-contained React component in TypeScript that fills its parent (w-full h-full), styled with Tailwind CSS, using simplex-noise. 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).