Signature
An animated SVG signature effect that draws out text as if hand-written.
Preview supplied by Arlan Marat; live previews were recorded from Arlan's Vault when no published video file was available. Preview: platform recorded · rights cleared.
Cursors is an animated canvas study published directly in the Vault gallery.
VSource LandVaultWhy it stands out
Cursors is an animated canvas study published directly in the Vault gallery.
Build this: a word rendered as frosted glass with labeled collaborator cursors flying around it. The frosted word is three stacked layers: a sharp copy masked to fade out toward the bottom, a blurred copy masked the opposite way, and a rounded glass-chip overlay (faint white gradient fill, light top-brighter border, layered inset + drop shadows), tilted a hair with a soft radial glow behind. The cursors are Figma-style pointer arrows with colored name pills that drift on looping elliptical paths and scatter away from the real pointer when it enters, easing back when it leaves. Plain DOM + CSS, no framework.
The complete, self-contained implementation follows, one file per block. It is framework-agnostic core logic — wire it into your own component and mount it on an element.
### cursors/FrostedWord.tsx
```ts
"use client";
export function FrostedWord({
children,
className = "",
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<span className={`relative inline-block ${className}`}>
{}
<span
aria-hidden
className="pointer-events-none absolute left-1/2 top-1/2 -z-[1] h-[130px] w-[540px] max-w-[130%] -translate-x-1/2 -translate-y-1/2"
style={{
background:
"radial-gradient(ellipse 100% 100% at 50% 50%, var(--bg-page) 0%, transparent 70%)",
filter: "blur(30px)",
}}
/>
{}
<span
className="relative inline-block"
style={{ transform: "translateX(-1px) translateY(-2px) rotate(-1deg)" }}
>
{}
<span
aria-hidden
className="absolute inset-0 text-[var(--text-primary)] opacity-[0.14]"
style={{ filter: "blur(9px)" }}
>
{children}
</span>
{/* deep chromatic twin: a further, softer offset in the opposite direction
from the near twin — a second refraction plane for more depth */}
<span
aria-hidden
className="absolute inset-0 text-[var(--text-primary)] opacity-[0.16]"
style={{
transform: "translate(-1.5px, 3px)",
filter: "blur(5px)",
maskImage: "linear-gradient(186deg, transparent 20%, black 100%)",
WebkitMaskImage: "linear-gradient(186deg, transparent 20%, black 100%)",
}}
>
{children}
</span>
{}
<span
className="relative z-10"
style={{
maskImage:
"linear-gradient(186deg, black 0%, black 40%, transparent 100%)",
WebkitMaskImage:
"linear-gradient(186deg, black 0%, black 40%, transparent 100%)",
}}
>
{children}
</span>
{/* blurred layer: fades in toward the bottom */}
<span
aria-hidden
className="absolute inset-0"
style={{
filter: "blur(2px)",
maskImage:
"linear-gradient(186deg, transparent 0%, black 60%, black 100%)",
WebkitMaskImage:
"linear-gradient(186deg, transparent 0%, black 60%, black 100%)",
}}
>
{children}
</span>
{}
<span
aria-hidden
className="absolute inset-0 text-[var(--text-primary)] opacity-[0.28]"
style={{
transform: "translate(1.5px, 2px)",
filter: "blur(3.5px)",
maskImage: "linear-gradient(186deg, transparent 30%, black 100%)",
WebkitMaskImage: "linear-gradient(186deg, transparent 30%, black 100%)",
}}
>
{children}
</span>
{/* top sheen: a bright specular highlight only on the upper third */}
<span
aria-hidden
className="absolute inset-0 opacity-70"
style={{
color: "rgba(255,255,255,0.9)",
maskImage: "linear-gradient(180deg, black 0%, transparent 30%)",
WebkitMaskImage: "linear-gradient(180deg, black 0%, transparent 30%)",
}}
>
{children}
</span>
{}
<span
aria-hidden
className="pointer-events-none absolute"
style={{
inset: "-4px -8px",
background:
"linear-gradient(180deg, rgba(255,255,255,0.42) 0%, rgba(255,255,255,0.14) 38%, rgba(255,255,255,0.08) 62%, rgba(255,255,255,0.24) 100%)",
border: "1px solid rgba(0,0,0,0.06)",
borderTopColor: "rgba(255,255,255,0.75)",
borderRadius: "10px",
boxShadow:
"inset 0 1px 0 rgba(255,255,255,0.55), inset 0 -1px 1px rgba(0,0,0,0.05), inset 0 0 0 3px rgba(255,255,255,0.14), inset 0 0 0 4px rgba(0,0,0,0.045), inset 0 8px 16px rgba(255,255,255,0.18), 0 1px 3px rgba(0,0,0,0.07), 0 6px 16px rgba(0,0,0,0.07)",
}}
/>
{}
<span
aria-hidden
className="pointer-events-none absolute"
style={{
inset: "-4px -8px",
borderRadius: "10px",
background:
"linear-gradient(180deg, rgba(255,255,255,0.7), transparent 12%)",
mixBlendMode: "overlay",
}}
/>
</span>
</span>
);
}
```
### cursors/engine.ts
```ts
const ARROW_PATH =
"M5.5 3.21V20.8c0 .45.54.67.85.35l4.86-4.86a.5.5 0 0 1 .35-.15h6.87c.48 0 .72-.58.38-.92L5.94 2.47a.5.5 0 0 0-.44.74Z";
export interface CursorDef {
name: string;
color: string;
}
interface CursorNode {
root: HTMLDivElement;
blush: HTMLDivElement;
color: string;
cx: number;
cy: number;
rx: number;
ry: number;
phase: number;
speed: number;
x: number;
y: number;
tx: number;
ty: number;
}
const TWO_PI = Math.PI * 2;
export class FlyingCursors {
private host: HTMLElement;
private defs: CursorDef[];
private nodes: CursorNode[] = [];
private raf = 0;
private running = false;
private disposed = false;
private t = 0;
private pointer = { x: -9999, y: -9999, active: false };
private ro?: ResizeObserver;
private cleanup: (() => void)[] = [];
constructor(host: HTMLElement, defs: CursorDef[]) {
this.host = host;
this.defs = defs;
this.build();
this.bindEvents();
this.layout();
}
private build() {
for (const def of this.defs) {
const blush = document.createElement("div");
const SIZE = 300;
Object.assign(blush.style, {
position: "absolute",
left: `${-SIZE / 2}px`,
top: `${-SIZE / 2}px`,
width: `${SIZE}px`,
height: `${SIZE}px`,
borderRadius: "50%",
background: `radial-gradient(circle, ${def.color} 0%, transparent 74%)`,
opacity: "0.72",
filter: "blur(26px)",
pointerEvents: "none",
willChange: "transform",
mixBlendMode: "soft-light",
zIndex: "2",
});
this.host.appendChild(blush);
const root = document.createElement("div");
Object.assign(root.style, {
position: "absolute",
left: "0",
top: "0",
pointerEvents: "none",
willChange: "transform",
zIndex: "3",
});
root.innerHTML = `
<svg width="22" height="22" viewBox="0 0 24 24" fill="none"
style="display:block; filter:drop-shadow(0 2px 3px rgba(0,0,0,0.22))">
<path d="${ARROW_PATH}" fill="${def.color}" stroke="#fff" stroke-width="1.4" stroke-linejoin="round"/>
</svg>
<span style="
position:absolute; left:16px; top:17px; white-space:nowrap;
padding:1.5px 6px; border-radius:8px; border-top-left-radius:2px;
font-size:9.5px; font-weight:600; letter-spacing:-0.01em;
line-height:1.35; color:#fff; background:${def.color};
box-shadow:0 2px 6px rgba(0,0,0,0.16), inset 0 1px 0 rgba(255,255,255,0.22);
font-family:var(--font-neue-montreal), ui-sans-serif, system-ui, sans-serif;
">${def.name}</span>
`;
this.host.appendChild(root);
this.nodes.push({
root,
blush,
color: def.color,
cx: 0.5,
cy: 0.5,
rx: 0.3,
ry: 0.3,
phase: 0,
speed: 1,
x: 0,
y: 0,
tx: 0,
ty: 0,
});
}
}
private bindEvents() {
const onMove = (e: PointerEvent) => {
const r = this.host.getBoundingClientRect();
this.pointer.x = e.clientX - r.left;
this.pointer.y = e.clientY - r.top;
this.pointer.active = true;
};
const onLeave = () => {
this.pointer.active = false;
};
this.host.addEventListener("pointermove", onMove);
this.host.addEventListener("pointerleave", onLeave);
this.cleanup.push(() => {
this.host.removeEventListener("pointermove", onMove);
this.host.removeEventListener("pointerleave", onLeave);
});
this.ro = new ResizeObserver(() => this.layout());
this.ro.observe(this.host);
}
private layout() {
const w = this.host.clientWidth;
const h = this.host.clientHeight;
if (!w || !h) return;
const n = this.nodes.length;
this.nodes.forEach((node, i) => {
node.cx = (0.32 + 0.36 * ((i + 0.5) / n)) * w;
node.cy = (0.34 + 0.32 * (i % 2)) * h;
node.rx = (0.16 + 0.06 * (i % 2)) * w;
node.ry = (0.2 + 0.05 * ((i + 1) % 2)) * h;
node.phase = (i / n) * TWO_PI;
node.speed = 0.5 + 0.18 * i;
});
}
start() {
if (this.running || this.disposed) return;
this.running = true;
this.raf = requestAnimationFrame(this.loop);
}
stop() {
this.running = false;
if (this.raf) cancelAnimationFrame(this.raf);
this.raf = 0;
}
private loop = () => {
if (!this.running) return;
this.t += 0.006;
for (const node of this.nodes) {
const a = this.t * node.speed + node.phase;
let bx = node.cx + Math.cos(a) * node.rx;
let by = node.cy + Math.sin(a * 1.15) * node.ry;
if (this.pointer.active) {
const dx = bx - this.pointer.x;
const dy = by - this.pointer.y;
const dist = Math.hypot(dx, dy) || 1;
const push = Math.max(0, 1 - dist / 220) * 120;
bx += (dx / dist) * push;
by += (dy / dist) * push;
}
node.tx = bx;
node.ty = by;
node.x += (node.tx - node.x) * 0.12;
node.y += (node.ty - node.y) * 0.12;
node.root.style.transform = `translate3d(${node.x.toFixed(1)}px, ${node.y.toFixed(1)}px, 0)`;
node.blush.style.transform = `translate3d(${node.x.toFixed(1)}px, ${node.y.toFixed(1)}px, 0)`;
}
this.raf = requestAnimationFrame(this.loop);
};
destroy() {
this.disposed = true;
this.stop();
this.cleanup.forEach((fn) => fn());
this.ro?.disconnect();
this.nodes.forEach((n) => {
n.root.parentNode?.removeChild(n.root);
n.blush.parentNode?.removeChild(n.blush);
});
this.nodes = [];
}
}
```Discovery vocabulary
Related by governed terms
An animated SVG signature effect that draws out text as if hand-written.
More from Vault