- Convert site from royal-blue theme to black/white monochrome (desaturate all blue-hued colors; keep WhatsApp green and error red) - Swap blue circular brand logo for mono YS mark; regenerate favicon/PWA icons - Add 6 Pudu robots (CC1 Pro, MT1 Max, MT1 Vac, T600, T600 Underride, BG1 Pro) - Fix favicon.ico to RGBA (Next 16 Turbopack decode) and Stripe apiVersion type Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
1.7 KiB
TypeScript
63 lines
1.7 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
|
|
type Props = {
|
|
color?: string;
|
|
size?: number;
|
|
className?: string;
|
|
};
|
|
|
|
export function CursorSpotlight({ color = 'rgba(94, 94, 94, 0.18)', size = 520, className = '' }: Props) {
|
|
const ref = useRef<HTMLDivElement | null>(null);
|
|
const [pos, setPos] = useState<{ x: number; y: number } | null>(null);
|
|
const [enabled, setEnabled] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const finePointer = window.matchMedia('(pointer: fine)').matches;
|
|
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
if (!finePointer || reduced) return;
|
|
setEnabled(true);
|
|
|
|
const el = ref.current?.parentElement;
|
|
if (!el) return;
|
|
let raf = 0;
|
|
const onMove = (e: MouseEvent) => {
|
|
const rect = el.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
cancelAnimationFrame(raf);
|
|
raf = requestAnimationFrame(() => setPos({ x, y }));
|
|
};
|
|
const onLeave = () => setPos(null);
|
|
el.addEventListener('mousemove', onMove);
|
|
el.addEventListener('mouseleave', onLeave);
|
|
return () => {
|
|
el.removeEventListener('mousemove', onMove);
|
|
el.removeEventListener('mouseleave', onLeave);
|
|
cancelAnimationFrame(raf);
|
|
};
|
|
}, []);
|
|
|
|
if (!enabled) return null;
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
aria-hidden
|
|
className={className}
|
|
style={{
|
|
position: 'absolute',
|
|
inset: 0,
|
|
pointerEvents: 'none',
|
|
opacity: pos ? 1 : 0,
|
|
transition: 'opacity 0.4s ease',
|
|
background: pos
|
|
? `radial-gradient(${size}px ${size}px at ${pos.x}px ${pos.y}px, ${color}, transparent 70%)`
|
|
: 'transparent',
|
|
zIndex: 1,
|
|
}}
|
|
/>
|
|
);
|
|
}
|