Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run
Use mainwhite.svg on white sections with curvy green transitions into flat green bands, improve text and button contrast, and deploy via OpenNext on Cloudflare Workers. Co-authored-by: Cursor <cursoragent@cursor.com>
100 lines
2.3 KiB
TypeScript
100 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
const MAX_PARTICLES = 48;
|
|
|
|
type Particle = {
|
|
x: number;
|
|
y: number;
|
|
vy: number;
|
|
vx: number;
|
|
size: number;
|
|
alpha: number;
|
|
};
|
|
|
|
type Props = {
|
|
active: boolean;
|
|
className?: string;
|
|
};
|
|
|
|
export function HeroRiftParticles({ active, className }: Props) {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (!active) return;
|
|
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return;
|
|
|
|
let raf = 0;
|
|
let particles: Particle[] = [];
|
|
|
|
const resize = () => {
|
|
const parent = canvas.parentElement;
|
|
if (!parent) return;
|
|
const dpr = Math.min(window.devicePixelRatio ?? 1, 2);
|
|
const w = parent.clientWidth;
|
|
const h = parent.clientHeight;
|
|
canvas.width = w * dpr;
|
|
canvas.height = h * dpr;
|
|
canvas.style.width = `${w}px`;
|
|
canvas.style.height = `${h}px`;
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
|
|
const count = Math.min(MAX_PARTICLES, Math.floor((w * h) / 12000));
|
|
particles = Array.from({ length: count }, () => ({
|
|
x: w * (0.35 + Math.random() * 0.3),
|
|
y: h * (0.45 + Math.random() * 0.35),
|
|
vy: -0.15 - Math.random() * 0.35,
|
|
vx: (Math.random() - 0.5) * 0.2,
|
|
size: 0.6 + Math.random() * 1.4,
|
|
alpha: 0.15 + Math.random() * 0.35,
|
|
}));
|
|
};
|
|
|
|
const tick = () => {
|
|
const w = canvas.clientWidth;
|
|
const h = canvas.clientHeight;
|
|
ctx.clearRect(0, 0, w, h);
|
|
|
|
for (const p of particles) {
|
|
p.x += p.vx;
|
|
p.y += p.vy;
|
|
if (p.y < h * 0.25) {
|
|
p.y = h * (0.55 + Math.random() * 0.3);
|
|
p.x = w * (0.35 + Math.random() * 0.3);
|
|
}
|
|
ctx.beginPath();
|
|
ctx.fillStyle = `rgba(255, 191, 80, ${p.alpha})`;
|
|
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
raf = requestAnimationFrame(tick);
|
|
};
|
|
|
|
resize();
|
|
window.addEventListener("resize", resize);
|
|
raf = requestAnimationFrame(tick);
|
|
|
|
return () => {
|
|
cancelAnimationFrame(raf);
|
|
window.removeEventListener("resize", resize);
|
|
};
|
|
}, [active]);
|
|
|
|
if (!active) return null;
|
|
|
|
return (
|
|
<canvas
|
|
ref={canvasRef}
|
|
className={className}
|
|
aria-hidden
|
|
/>
|
|
);
|
|
}
|