Section headlines are written out behind a column of digits, mono labels resolve from binary, the portrait assembles itself from bits, section indexes rattle and keep one bit alive, cards get corner marks, the header shows the scroll position as an eight-bit number, pressed links and buttons shed a few digits, and the work stage leans towards the pointer. All effects respect reduced motion and leave the content readable without JavaScript. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
|
|
const COUNT = 10;
|
|
const LIFE_MS = 620;
|
|
|
|
/**
|
|
* BitBurst: a handful of binary digits fly out from the point where a link or
|
|
* button is pressed. Purely decorative, and off when reduced motion is requested.
|
|
*/
|
|
export const BitBurst: React.FC = () => {
|
|
React.useEffect(() => {
|
|
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
|
|
const onPointerDown = (event: PointerEvent) => {
|
|
const target = event.target as Element | null;
|
|
if (!target?.closest("a, button")) return;
|
|
|
|
const layer = document.createElement("div");
|
|
layer.setAttribute("aria-hidden", "true");
|
|
layer.className = "bit-burst";
|
|
layer.style.left = `${event.clientX}px`;
|
|
layer.style.top = `${event.clientY}px`;
|
|
for (let i = 0; i < COUNT; i++) {
|
|
const angle = (i / COUNT) * Math.PI * 2 + Math.random() * 0.5;
|
|
const distance = 26 + Math.random() * 34;
|
|
const digit = document.createElement("span");
|
|
digit.textContent = Math.random() < 0.5 ? "0" : "1";
|
|
digit.style.setProperty("--dx", `${Math.cos(angle) * distance}px`);
|
|
digit.style.setProperty("--dy", `${Math.sin(angle) * distance}px`);
|
|
digit.style.animationDelay = `${Math.random() * 60}ms`;
|
|
layer.appendChild(digit);
|
|
}
|
|
document.body.appendChild(layer);
|
|
window.setTimeout(() => layer.remove(), LIFE_MS + 120);
|
|
};
|
|
|
|
window.addEventListener("pointerdown", onPointerDown);
|
|
return () => window.removeEventListener("pointerdown", onPointerDown);
|
|
}, []);
|
|
|
|
return null;
|
|
};
|