228 lines
7.5 KiB
TypeScript
228 lines
7.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useCallback, useState } from "react";
|
|
import Link from "next/link";
|
|
import gsap from "gsap";
|
|
|
|
function useGridSize() {
|
|
const [gridSize, setGridSize] = useState({ cols: 20, rows: 12 });
|
|
|
|
useEffect(() => {
|
|
const updateGridSize = () => {
|
|
const width = window.innerWidth;
|
|
if (width < 640) {
|
|
// Mobile
|
|
setGridSize({ cols: 8, rows: 10 });
|
|
} else if (width < 1024) {
|
|
// Tablet
|
|
setGridSize({ cols: 12, rows: 10 });
|
|
} else {
|
|
// Desktop
|
|
setGridSize({ cols: 20, rows: 12 });
|
|
}
|
|
};
|
|
|
|
updateGridSize();
|
|
window.addEventListener("resize", updateGridSize);
|
|
return () => window.removeEventListener("resize", updateGridSize);
|
|
}, []);
|
|
|
|
return gridSize;
|
|
}
|
|
|
|
function TileGrid() {
|
|
const gridRef = useRef<HTMLDivElement>(null);
|
|
const tilesRef = useRef<(HTMLDivElement | null)[]>([]);
|
|
const { cols, rows } = useGridSize();
|
|
|
|
const handleMouseEnter = useCallback((index: number) => {
|
|
const tile = tilesRef.current[index];
|
|
if (tile) {
|
|
gsap.killTweensOf(tile);
|
|
gsap.to(tile, {
|
|
rotateY: 180,
|
|
duration: 0.5,
|
|
ease: "power2.inOut",
|
|
overwrite: true,
|
|
});
|
|
}
|
|
}, []);
|
|
|
|
const handleMouseLeave = useCallback((index: number) => {
|
|
const tile = tilesRef.current[index];
|
|
if (tile) {
|
|
gsap.killTweensOf(tile);
|
|
gsap.to(tile, {
|
|
rotateY: 0,
|
|
duration: 0.5,
|
|
ease: "power2.inOut",
|
|
overwrite: true,
|
|
});
|
|
}
|
|
}, []);
|
|
|
|
// Reset refs array when grid size changes
|
|
useEffect(() => {
|
|
tilesRef.current = [];
|
|
}, [cols, rows]);
|
|
|
|
return (
|
|
<div
|
|
ref={gridRef}
|
|
className="absolute inset-0 grid z-0"
|
|
style={{
|
|
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
gridTemplateRows: `repeat(${rows}, 1fr)`,
|
|
}}
|
|
>
|
|
{Array.from({ length: cols * rows }).map((_, index) => (
|
|
<div
|
|
key={index}
|
|
onMouseEnter={() => handleMouseEnter(index)}
|
|
onMouseLeave={() => handleMouseLeave(index)}
|
|
className="cursor-pointer"
|
|
style={{ perspective: "500px" }}
|
|
>
|
|
<div
|
|
ref={(el) => { tilesRef.current[index] = el; }}
|
|
className="w-full h-full relative"
|
|
style={{
|
|
transformStyle: "preserve-3d",
|
|
willChange: "transform",
|
|
}}
|
|
>
|
|
{/* Front - Black */}
|
|
<div
|
|
className="absolute inset-0 bg-black border border-zinc-900"
|
|
style={{ backfaceVisibility: "hidden" }}
|
|
/>
|
|
{/* Back - White */}
|
|
<div
|
|
className="absolute inset-0 bg-white"
|
|
style={{
|
|
backfaceVisibility: "hidden",
|
|
transform: "rotateY(180deg)",
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function Hero() {
|
|
const headingRef = useRef<HTMLHeadingElement>(null);
|
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
// Animate heading on mount
|
|
if (headingRef.current) {
|
|
gsap.fromTo(
|
|
headingRef.current,
|
|
{ opacity: 0, y: 50 },
|
|
{ opacity: 1, y: 0, duration: 1, ease: "power3.out", delay: 0.3 }
|
|
);
|
|
}
|
|
|
|
if (contentRef.current) {
|
|
gsap.fromTo(
|
|
contentRef.current.children,
|
|
{ opacity: 0, y: 30 },
|
|
{ opacity: 1, y: 0, duration: 0.8, stagger: 0.15, ease: "power3.out", delay: 0.5 }
|
|
);
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<section className="relative min-h-screen bg-black overflow-hidden pt-28">
|
|
{/* Tile Grid Background */}
|
|
<TileGrid />
|
|
|
|
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-full pointer-events-none">
|
|
<div className="flex items-center justify-center min-h-[calc(100vh-12rem)] pb-20">
|
|
{/* Center Content */}
|
|
<div className="text-center relative z-10">
|
|
<div className="inline-flex items-center gap-2 bg-lime-400 text-black px-5 py-2.5 text-xs font-bold tracking-widest rounded-full mb-10 pointer-events-auto">
|
|
<span className="w-2 h-2 bg-black rounded-full animate-pulse" />
|
|
FEBRUARY 2026
|
|
</div>
|
|
|
|
<h1
|
|
ref={headingRef}
|
|
className="text-6xl sm:text-7xl lg:text-8xl xl:text-9xl font-black tracking-tighter text-white leading-[0.85] mb-8"
|
|
style={{
|
|
WebkitTextStroke: "2px black",
|
|
paintOrder: "stroke fill",
|
|
}}
|
|
>
|
|
<span className="block">UNLEASH</span>
|
|
<span
|
|
className="block text-lime-400 drop-shadow-[0_0_30px_rgba(163,230,53,0.5)]"
|
|
style={{
|
|
WebkitTextStroke: "2px black",
|
|
paintOrder: "stroke fill",
|
|
}}
|
|
>
|
|
YOUR ART
|
|
</span>
|
|
</h1>
|
|
|
|
<div ref={contentRef}>
|
|
<p className="text-zinc-300 text-base sm:text-lg max-w-lg mx-auto mb-10 leading-relaxed font-light drop-shadow-[0_2px_4px_rgba(0,0,0,0.8)]">
|
|
From canvas to screen, showcase your creativity and let your
|
|
artwork speak to the world.
|
|
</p>
|
|
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center pointer-events-auto mb-12">
|
|
<Link
|
|
href="/register"
|
|
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-10 py-4 text-sm font-bold tracking-wider hover:bg-lime-300 transition-all hover:scale-105 rounded-full shadow-[0_0_30px_rgba(163,230,53,0.3)]"
|
|
>
|
|
JOIN COMPETITION
|
|
</Link>
|
|
<Link
|
|
href="/gallery"
|
|
className="inline-flex items-center justify-center gap-2 bg-transparent text-white px-10 py-4 text-sm font-bold tracking-wider hover:bg-white/10 transition-all rounded-full border border-zinc-700 hover:border-zinc-500"
|
|
>
|
|
EXPLORE GALLERY
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Scroll Indicator */}
|
|
<div className="flex flex-col items-center gap-2">
|
|
<span className="text-lime-400 text-[10px] tracking-[0.3em] uppercase drop-shadow-[0_0_10px_rgba(163,230,53,0.8)]">Scroll</span>
|
|
<div className="w-px h-8 bg-gradient-to-b from-lime-400 to-transparent shadow-[0_0_10px_rgba(163,230,53,0.5)]" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bottom Marquee */}
|
|
<div className="absolute bottom-0 left-0 right-0 bg-lime-400 text-black py-3 overflow-hidden z-10">
|
|
<div className="animate-marquee-slow whitespace-nowrap flex items-center">
|
|
{Array(8)
|
|
.fill(null)
|
|
.map((_, i) => (
|
|
<div key={i} className="flex items-center">
|
|
<span className="text-xl sm:text-2xl font-black tracking-tight mx-6 uppercase">
|
|
ArtSplash
|
|
</span>
|
|
<span className="text-black/40 mx-4">✦</span>
|
|
<span className="text-xl sm:text-2xl font-black tracking-tight mx-6 uppercase text-black/60">
|
|
SJEC
|
|
</span>
|
|
<span className="text-black/40 mx-4">✦</span>
|
|
<span className="text-xl sm:text-2xl font-black tracking-tight mx-6 uppercase">
|
|
2026
|
|
</span>
|
|
<span className="text-black/40 mx-4">✦</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|