diff --git a/app/globals.css b/app/globals.css index 8356a1f..817e790 100644 --- a/app/globals.css +++ b/app/globals.css @@ -196,3 +196,79 @@ section { .scrollbar-hide::-webkit-scrollbar { display: none; } + +/* ─── Aurora gradient background (Stripe-inspired) ─── */ +@keyframes aurora { + 0%, 100% { background-position: 0% 50%, 100% 50%, 50% 100%; } + 33% { background-position: 100% 0%, 0% 100%, 50% 50%; } + 66% { background-position: 50% 100%, 50% 0%, 0% 50%; } +} + +.aurora-bg { + background: + radial-gradient(ellipse at 20% 50%, rgba(217,94,42,0.12) 0%, transparent 50%), + radial-gradient(ellipse at 80% 20%, rgba(217,94,42,0.06) 0%, transparent 50%), + radial-gradient(ellipse at 50% 80%, rgba(249,249,247,0.03) 0%, transparent 50%); + background-size: 200% 200%, 200% 200%, 200% 200%; + animation: aurora 20s ease-in-out infinite; +} + +/* ─── Gradient text shimmer ─── */ +@keyframes gradient-shift { + 0%, 100% { background-position: 0% center; } + 50% { background-position: 200% center; } +} + +.text-shimmer { + background: linear-gradient(90deg, rgb(var(--foreground)) 0%, rgb(var(--primary)) 40%, rgb(var(--foreground)) 80%); + background-size: 200% auto; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + animation: gradient-shift 6s ease infinite; +} + +/* ─── Dot grid wave (Linear-inspired) ─── */ +@keyframes dot-pulse { + 0%, 100% { opacity: 0.08; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.3); } +} + +/* ─── Floating animation ─── */ +@keyframes float { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-8px); } +} + +.animate-float { + animation: float 4s ease-in-out infinite; +} + +/* ─── Card spotlight hover (mouse-tracking glow) ─── */ +.card-spotlight { + position: relative; + overflow: hidden; +} +.card-spotlight::before { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(600px circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(217,94,42,0.06), transparent 40%); + opacity: 0; + transition: opacity 0.3s; + pointer-events: none; + z-index: 1; +} +.card-spotlight:hover::before { + opacity: 1; +} + +/* ─── Marquee scroll ─── */ +@keyframes marquee { + from { transform: translateX(0); } + to { transform: translateX(-50%); } +} + +.animate-marquee { + animation: marquee 30s linear infinite; +} diff --git a/app/hackathons/page.tsx b/app/hackathons/page.tsx index aa89533..649f2bf 100644 --- a/app/hackathons/page.tsx +++ b/app/hackathons/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect, useRef, Suspense } from 'react' +import { useState, useEffect, useRef, Suspense, useCallback } from 'react' import dynamic from 'next/dynamic' import { Activity, @@ -36,7 +36,7 @@ function useInView(threshold = 0.15) { useEffect(() => { const el = ref.current if (!el) return - const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setVisible(true); obs.unobserve(el) } }, { threshold }) + const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setVisible(true); obs.unobserve(el) } }, { threshold, rootMargin: '0px 0px -40px 0px' }) obs.observe(el) return () => obs.disconnect() }, [threshold]) @@ -52,7 +52,8 @@ function Counter({ target, suffix = '' }: { target: number; suffix?: string }) { const step = (ts: number) => { if (!start) start = ts const p = Math.min((ts - start) / 1200, 1) - setCount(Math.floor(p * target)) + const eased = 1 - Math.pow(1 - p, 3) // ease-out cubic + setCount(Math.floor(eased * target)) if (p < 1) requestAnimationFrame(step) } requestAnimationFrame(step) @@ -60,6 +61,50 @@ function Counter({ target, suffix = '' }: { target: number; suffix?: string }) { return }>{count}{suffix} } +function useMouseSpotlight() { + const ref = useRef(null) + const onMove = useCallback((e: React.MouseEvent) => { + const el = ref.current + if (!el) return + const rect = el.getBoundingClientRect() + el.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`) + el.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`) + }, []) + return { ref, onMove } +} + +/* ─── Noise overlay ─── */ + +function NoiseOverlay() { + return ( + + + + + + + ) +} + +/* ─── Dot grid (Linear-inspired) ─── */ + +function DotGrid({ cols = 16, rows = 5 }: { cols?: number; rows?: number }) { + return ( +
+ {Array.from({ length: cols * rows }, (_, i) => ( +
+ ))} +
+ ) +} + /* ─── Nav ─── */ function Nav() { @@ -88,35 +133,38 @@ function Nav() { ) } -/* ─── Hero with 3D Shield ─── */ +/* ─── Hero ─── */ function Hero() { return (
- {/* 3D Shield as background */} + {/* 3D Shield background */}
- {/* Gradient overlays to keep text readable */} -
-
+ {/* Aurora gradient */} +
+ + {/* Dark overlay for readability */} +
+
-

- Hack the Wall. +

+ Hack the Wall.

-

+

Build on the AI agent security stack. Your best hacks get merged into Greywall.

Explore tracks - +
@@ -127,8 +175,13 @@ function Hero() { function Stats() { return ( -
-
+
+ {/* Dot grid background */} +
+ +
+ +
{[ { value: 24, suffix: 'h', label: 'of hacking' }, { value: 3, suffix: '', label: 'open-ended tracks' }, @@ -146,13 +199,16 @@ function Stats() { ) } -/* ─── Live Terminal Showcase ─── */ +/* ─── Terminal Showcase ─── */ function TerminalShowcase() { const { ref, visible } = useInView(0.1) return ( -
+
+ {/* Background glow behind terminal */} +
+

The data stream

@@ -163,13 +219,15 @@ function TerminalShowcase() { Greywall's proxy captures every request, file access, and command your AI agent executes. This is what you'll be building on.

- +
+ +
) } -/* ─── Tracks with visuals ─── */ +/* ─── Tracks ─── */ const tracks = [ { @@ -206,6 +264,7 @@ const tracks = [ function TrackCard({ track, index }: { track: typeof tracks[0]; index: number }) { const { ref, visible } = useInView(0.1) + const { ref: spotlightRef, onMove } = useMouseSpotlight() return (
-
+
{/* Content */}
-
+
@@ -239,7 +302,7 @@ function TrackCard({ track, index }: { track: typeof tracks[0]; index: number })
{track.examples.map((ex) => ( - + {ex} ))} @@ -258,8 +321,11 @@ function TrackCard({ track, index }: { track: typeof tracks[0]; index: number }) function Tracks() { return ( -
-
+
+ {/* Subtle background glow */} +
+ +

Pick your arena. @@ -291,22 +357,26 @@ function HowItWorks() { const { ref, visible } = useInView() return ( -
+
-

+

How it works.

-
+ {/* Animated connector line */} +
+
+
+ {steps.map((step, i) => (
-
- +
+

{step.title}

{step.sub}

@@ -331,20 +401,25 @@ function Prizes() { ] return ( -
-
-

+
+ {/* Background dot grid */} +
+ +
+ +
+

More than a trophy.

-

+

The best hacks ship.

{items.map((item, i) => (
@@ -365,13 +440,13 @@ function Location() { const { ref, visible } = useInView() return ( -
+
-
+
Location
-

+

Montreal.

@@ -409,8 +484,8 @@ function FAQItem({ question, answer }: { question: string; answer: string }) { const [open, setOpen] = useState(false) return (

-
@@ -428,15 +503,18 @@ function FinalCTA() { const { ref, visible } = useInView() return ( -
-
+
+ {/* Aurora background */} +
+
+
-

- Build something that ships. +

+ Build something that ships.

- + Get notified when registration opens - +

Follow{' '} @@ -452,7 +530,8 @@ function FinalCTA() { export default function HackathonsPage() { return ( -

+
+