Some checks failed
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Has been cancelled
Centralize primary, secondary, tertiary, and neutral tokens and apply them across theme variables and UI components. Co-authored-by: Cursor <cursoragent@cursor.com>
77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import { mixHex } from "@/lib/rift-colors";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
import { BRAND_COLORS } from "@/content/brand-colors";
|
|
|
|
const GREEN: string = BRAND_COLORS.primary;
|
|
const GOLD: string = BRAND_COLORS.secondary;
|
|
const BLUE: string = BRAND_COLORS.tertiary;
|
|
|
|
/** Long curved paths that pulse across the page background */
|
|
const PULSE_CURVES = [
|
|
"M -80 120 Q 280 80, 520 140 T 1040 100 T 1400 160",
|
|
"M -60 280 Q 320 240, 580 300 T 1100 260 T 1500 320",
|
|
"M -100 440 Q 260 400, 500 460 T 980 420 T 1380 480",
|
|
"M -40 600 Q 300 560, 620 620 T 1080 580 T 1520 640",
|
|
"M -120 180 Q 200 220, 480 160 T 920 200 T 1320 140",
|
|
"M -80 520 Q 340 480, 640 540 T 1020 500 T 1480 560",
|
|
"M -60 720 Q 400 680, 720 740 T 1140 700 T 1600 760",
|
|
"M -100 860 Q 280 820, 560 880 T 1000 840 T 1440 900",
|
|
] as const;
|
|
|
|
type Props = {
|
|
progress: number;
|
|
scrollY: number;
|
|
reduceMotion: boolean;
|
|
};
|
|
|
|
export function RiftPulseField({ progress, scrollY, reduceMotion }: Props) {
|
|
const t = Math.max(0, Math.min(1, progress));
|
|
const wave = 0.5 - 0.5 * Math.cos(t * Math.PI * 2);
|
|
const primary = useMemo(() => {
|
|
if (t < 0.33) return mixHex(GREEN, GOLD, t / 0.33);
|
|
if (t < 0.66) return mixHex(GOLD, BLUE, (t - 0.33) / 0.33);
|
|
return mixHex(BLUE, GREEN, (t - 0.66) / 0.34);
|
|
}, [t]);
|
|
|
|
const accent = useMemo(() => mixHex(GOLD, primary, 0.35 + wave * 0.2), [primary, wave]);
|
|
|
|
const yShift = reduceMotion ? 0 : scrollY * 0.04;
|
|
const drawOffset = reduceMotion ? 0 : Math.max(0, 0.92 - progress * 1.1);
|
|
|
|
return (
|
|
<svg
|
|
className="rift-pulse-field pointer-events-none absolute inset-0 h-full w-full"
|
|
viewBox="0 0 1280 1000"
|
|
preserveAspectRatio="xMidYMid slice"
|
|
aria-hidden
|
|
style={{
|
|
transform: `translate3d(0, ${yShift}px, 0)`,
|
|
opacity: 0.35 + progress * 0.4,
|
|
}}
|
|
>
|
|
<g fill="none" strokeLinecap="round">
|
|
{PULSE_CURVES.map((d, i) => (
|
|
<path
|
|
key={i}
|
|
pathLength={1}
|
|
d={d}
|
|
stroke={i % 2 === 0 ? primary : accent}
|
|
strokeWidth={0.8 + (i % 3) * 0.2}
|
|
className={cn("rift-pulse-line", !reduceMotion && "rift-pulse-animate")}
|
|
style={{
|
|
animationDelay: `${i * 0.55}s`,
|
|
strokeDasharray: 1,
|
|
strokeDashoffset: drawOffset,
|
|
transition: "stroke 0.85s ease, stroke-dashoffset 0.3s ease-out",
|
|
}}
|
|
/>
|
|
))}
|
|
</g>
|
|
</svg>
|
|
);
|
|
}
|