feat: better design
This commit is contained in:
@@ -3,18 +3,12 @@
|
||||
import { useState, useEffect, useRef, Suspense, useCallback } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import {
|
||||
Shield,
|
||||
ChevronDown,
|
||||
ArrowRight,
|
||||
Users,
|
||||
Trophy,
|
||||
|
||||
Star,
|
||||
Terminal,
|
||||
Clock,
|
||||
MapPin,
|
||||
Cpu,
|
||||
Sparkles,
|
||||
Eye,
|
||||
Target,
|
||||
AlertTriangle,
|
||||
@@ -46,23 +40,6 @@ function useInView(threshold = 0.15) {
|
||||
return { ref, visible }
|
||||
}
|
||||
|
||||
function Counter({ target, suffix = '' }: { target: number; suffix?: string }) {
|
||||
const [count, setCount] = useState(0)
|
||||
const { ref, visible } = useInView(0.3)
|
||||
useEffect(() => {
|
||||
if (!visible) return
|
||||
let start = 0
|
||||
const step = (ts: number) => {
|
||||
if (!start) start = ts
|
||||
const p = Math.min((ts - start) / 1200, 1)
|
||||
const eased = 1 - Math.pow(1 - p, 3) // ease-out cubic
|
||||
setCount(Math.floor(eased * target))
|
||||
if (p < 1) requestAnimationFrame(step)
|
||||
}
|
||||
requestAnimationFrame(step)
|
||||
}, [visible, target])
|
||||
return <span ref={ref as React.RefObject<HTMLSpanElement>}>{count}{suffix}</span>
|
||||
}
|
||||
|
||||
function useMouseSpotlight() {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
@@ -89,24 +66,6 @@ function NoiseOverlay() {
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Dot grid (Linear-inspired) ─── */
|
||||
|
||||
function DotGrid({ cols = 16, rows = 5 }: { cols?: number; rows?: number }) {
|
||||
return (
|
||||
<div className="grid gap-6" style={{ gridTemplateColumns: `repeat(${cols}, 1fr)` }}>
|
||||
{Array.from({ length: cols * rows }, (_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="w-1 h-1 rounded-full bg-primary"
|
||||
style={{
|
||||
animation: 'dot-pulse 3.5s ease-in-out infinite',
|
||||
animationDelay: `${(i % cols) * 80 + Math.floor(i / cols) * 120}ms`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Nav ─── */
|
||||
|
||||
@@ -174,33 +133,6 @@ function Hero() {
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Stats ─── */
|
||||
|
||||
function Stats() {
|
||||
return (
|
||||
<section className="py-14 px-4 sm:px-6 border-t border-b border-border/30 relative overflow-hidden">
|
||||
{/* Dot grid background */}
|
||||
<div className="absolute inset-0 flex items-center justify-center opacity-50 pointer-events-none">
|
||||
<DotGrid cols={20} rows={3} />
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto max-w-3xl grid grid-cols-3 gap-8 text-center">
|
||||
{[
|
||||
{ value: 48, suffix: 'h', label: 'of hacking' },
|
||||
{ value: 5, suffix: '', label: 'tracks' },
|
||||
{ value: 100, suffix: '%', label: 'open source' },
|
||||
].map((stat) => (
|
||||
<div key={stat.label}>
|
||||
<div className="font-serif text-4xl sm:text-5xl font-semibold text-primary mb-1">
|
||||
<Counter target={stat.value} suffix={stat.suffix} />
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground font-sans">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Notify Button ─── */
|
||||
|
||||
@@ -637,17 +569,6 @@ function TrackCard({ track, index }: { track: typeof tracks[0]; index: number })
|
||||
<div className="flex">
|
||||
{/* Content */}
|
||||
<div className="flex-1 p-8 sm:p-10 relative z-10">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-2xl bg-card/60 border border-border/30 backdrop-blur-sm group-hover:border-primary/20 transition-colors">
|
||||
<track.icon className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground font-sans">
|
||||
<Clock className="h-3 w-3" />48h
|
||||
<Users className="h-3 w-3 ml-1" />Teams of 2–3
|
||||
<MapPin className="h-3 w-3 ml-1" />Montreal
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-3">
|
||||
{track.title}
|
||||
</h3>
|
||||
@@ -708,95 +629,6 @@ function Tracks() {
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── How It Works ─── */
|
||||
|
||||
const steps = [
|
||||
{ icon: Sparkles, title: 'Form a team', sub: 'Groups of 2–3' },
|
||||
{ icon: Terminal, title: 'Get set up', sub: 'Docs & infra provided' },
|
||||
{ icon: Cpu, title: 'Hack for 48h', sub: 'Collect achievements' },
|
||||
{ icon: Trophy, title: 'Present & win', sub: 'Top teams demo' },
|
||||
]
|
||||
|
||||
function HowItWorks() {
|
||||
const { ref, visible } = useInView()
|
||||
|
||||
return (
|
||||
<section id="how-it-works" ref={ref} className="py-14 sm:py-20 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight text-center mb-12">
|
||||
How it works.
|
||||
</h2>
|
||||
|
||||
<div className="relative grid grid-cols-4 gap-6">
|
||||
{/* Animated connector line */}
|
||||
<div className="hidden sm:block absolute top-7 left-[12.5%] right-[12.5%] h-px overflow-hidden">
|
||||
<div className={`h-full bg-gradient-to-r from-primary/40 via-primary/20 to-primary/40 transition-all duration-1000 ${visible ? 'w-full' : 'w-0'}`} style={{ transitionDelay: '300ms' }} />
|
||||
</div>
|
||||
|
||||
{steps.map((step, i) => (
|
||||
<div
|
||||
key={step.title}
|
||||
className={`relative text-center transition-all duration-700 ${visible ? 'opacity-100 translate-y-0 scale-100' : 'opacity-0 translate-y-8 scale-95'}`}
|
||||
style={{ transitionDelay: `${i * 150}ms` }}
|
||||
>
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gradient-to-br from-primary/15 to-primary/5 border border-primary/20 mb-4 relative z-10 hover:border-primary/40 transition-all group">
|
||||
<step.icon className="h-6 w-6 text-primary group-hover:scale-110 transition-transform" />
|
||||
</div>
|
||||
<h3 className="font-serif text-base font-semibold mb-1">{step.title}</h3>
|
||||
<p className="text-xs text-muted-foreground font-sans">{step.sub}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Prizes ─── */
|
||||
|
||||
function Prizes() {
|
||||
const { ref, visible } = useInView()
|
||||
|
||||
const items = [
|
||||
{ icon: Crown, title: 'Hall of Fame', sub: 'Featured on greywall.io permanently' },
|
||||
{ icon: Star, title: 'Contributor credit', sub: 'Added as a contributor in the GitHub README' },
|
||||
]
|
||||
|
||||
return (
|
||||
<section ref={ref} className="py-14 sm:py-20 px-4 sm:px-6 border-t border-border/30 relative overflow-hidden">
|
||||
{/* Background dot grid */}
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none opacity-40"
|
||||
style={{
|
||||
backgroundImage: 'radial-gradient(circle, rgba(217,94,42,0.5) 1px, transparent 1px)',
|
||||
backgroundSize: '32px 32px',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative mx-auto max-w-3xl">
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight text-center mb-10">
|
||||
What you win.
|
||||
</h2>
|
||||
<div className="flex justify-center gap-6">
|
||||
{items.map((item, i) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className={`text-center p-6 rounded-2xl border border-border/30 bg-card/20 backdrop-blur-sm hover:bg-card/50 hover:border-primary/20 hover:-translate-y-1 hover:shadow-[0_8px_30px_rgba(217,94,42,0.08)] transition-all duration-500 ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}
|
||||
style={{ transitionDelay: `${i * 100}ms` }}
|
||||
>
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 border border-primary/15 mb-4">
|
||||
<item.icon className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="font-serif text-base font-semibold mb-1">{item.title}</h3>
|
||||
<p className="text-xs text-muted-foreground font-sans">{item.sub}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Location ─── */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user