63 lines
2 KiB
TypeScript
63 lines
2 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
|
|
/**
|
|
* The reserved interactive zone on the home page.
|
|
* Shows a warm chiaroscuro panel whose light source drifts toward the pointer —
|
|
* the one signature motion moment per the brand brief. Future interactive pieces
|
|
* drop into this same clipped frame.
|
|
*
|
|
* Respects prefers-reduced-motion: static gradient at the rest position.
|
|
*/
|
|
export default function HeroZone() {
|
|
const frameRef = useRef<HTMLDivElement | null>(null);
|
|
const accentRef = useRef<HTMLImageElement | null>(null);
|
|
|
|
useEffect(() => {
|
|
const frame = frameRef.current;
|
|
const accent = accentRef.current;
|
|
if (!frame) return;
|
|
|
|
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
const rest = { x: 0.62, y: 0.28 };
|
|
|
|
function paint(x: number, y: number) {
|
|
if (!frame) return;
|
|
const lx = 30 + x * 50;
|
|
const ly = 8 + y * 40;
|
|
frame.style.background =
|
|
`radial-gradient(120% 95% at ${lx}% ${ly}%, ` +
|
|
`#C9B299 0%, #8A6A47 30%, #4A3826 60%, #221E18 92%)`;
|
|
if (accent) {
|
|
accent.style.transform = `translate(${(0.5 - x) * 26}px, ${(0.5 - y) * 16}px)`;
|
|
}
|
|
}
|
|
|
|
paint(rest.x, rest.y);
|
|
if (reduce) return;
|
|
|
|
const onMove = (e: MouseEvent) => {
|
|
const r = frame.getBoundingClientRect();
|
|
paint(
|
|
Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)),
|
|
Math.min(1, Math.max(0, (e.clientY - r.top) / r.height)),
|
|
);
|
|
};
|
|
const onLeave = () => paint(rest.x, rest.y);
|
|
|
|
frame.addEventListener("mousemove", onMove);
|
|
frame.addEventListener("mouseleave", onLeave);
|
|
return () => {
|
|
frame.removeEventListener("mousemove", onMove);
|
|
frame.removeEventListener("mouseleave", onLeave);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className="hzframe" ref={frameRef}>
|
|
<div className="grain" />
|
|
<img ref={accentRef} className="hzaccent" src="/assets/accent-ringdots.svg" alt="" />
|
|
<img className="hzwm" src="/assets/mark-rings.svg" alt="" />
|
|
<span className="hztag">Interactive · soon</span>
|
|
</div>
|
|
);
|
|
}
|