feat: hackathon landing mvp
This commit is contained in:
189
components/hackathons/track-visuals.tsx
Normal file
189
components/hackathons/track-visuals.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
/* ─── Animated data stream (Track 1) ─── */
|
||||
/* Compact flowing waveform + particles, self-contained */
|
||||
|
||||
export function StreamViz() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
let w = 0, h = 0
|
||||
|
||||
const resize = () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
w = rect.width
|
||||
h = rect.height
|
||||
canvas.width = w * dpr
|
||||
canvas.height = h * dpr
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
}
|
||||
resize()
|
||||
|
||||
const particles: { x: number; y: number; vy: number; size: number; alpha: number }[] = []
|
||||
for (let i = 0; i < 30; i++) {
|
||||
particles.push({
|
||||
x: Math.random() * 250,
|
||||
y: Math.random() * 300,
|
||||
vy: -0.2 - Math.random() * 0.5,
|
||||
size: 1 + Math.random() * 2,
|
||||
alpha: 0.15 + Math.random() * 0.35,
|
||||
})
|
||||
}
|
||||
|
||||
let t = 0
|
||||
let animId: number
|
||||
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
t += 0.02
|
||||
|
||||
// Flowing wave lines
|
||||
for (let line = 0; line < 4; line++) {
|
||||
ctx.beginPath()
|
||||
const baseY = h * 0.25 + line * (h * 0.15)
|
||||
for (let x = 0; x <= w; x += 2) {
|
||||
const y = baseY + Math.sin(x * 0.03 + t + line * 1.5) * 12 + Math.sin(x * 0.015 + t * 0.7) * 8
|
||||
if (x === 0) ctx.moveTo(x, y)
|
||||
else ctx.lineTo(x, y)
|
||||
}
|
||||
ctx.strokeStyle = `rgba(217, 94, 42, ${0.06 + line * 0.03})`
|
||||
ctx.lineWidth = 1
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// Particles
|
||||
particles.forEach((p) => {
|
||||
ctx.beginPath()
|
||||
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2)
|
||||
ctx.fillStyle = `rgba(217, 94, 42, ${p.alpha})`
|
||||
ctx.fill()
|
||||
|
||||
p.y += p.vy
|
||||
if (p.y < -5) { p.y = h + 5; p.x = Math.random() * w }
|
||||
})
|
||||
|
||||
// Connections between close particles
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x
|
||||
const dy = particles[i].y - particles[j].y
|
||||
const dist = Math.sqrt(dx * dx + dy * dy)
|
||||
if (dist < 60) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(particles[i].x, particles[i].y)
|
||||
ctx.lineTo(particles[j].x, particles[j].y)
|
||||
ctx.strokeStyle = `rgba(217, 94, 42, ${0.06 * (1 - dist / 60)})`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animId = requestAnimationFrame(draw)
|
||||
}
|
||||
draw()
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
return () => { cancelAnimationFrame(animId); window.removeEventListener('resize', resize) }
|
||||
}, [])
|
||||
|
||||
return <canvas ref={canvasRef} className="absolute inset-0 w-full h-full" style={{ display: 'block' }} />
|
||||
}
|
||||
|
||||
/* ─── Pulsing lock with rings (Track 2) ─── */
|
||||
|
||||
export function SecureViz() {
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
{/* Pulsing rings */}
|
||||
<div className="absolute w-20 h-20 rounded-full border border-emerald-500/15 animate-ping [animation-duration:3s]" />
|
||||
<div className="absolute w-32 h-32 rounded-full border border-emerald-500/8 animate-ping [animation-duration:4s] [animation-delay:0.5s]" />
|
||||
<div className="absolute w-44 h-44 rounded-full border border-emerald-500/[0.04] animate-ping [animation-duration:5s] [animation-delay:1s]" />
|
||||
|
||||
{/* Rotating orbit */}
|
||||
<svg className="absolute w-28 h-28 animate-[spin_15s_linear_infinite]" viewBox="0 0 100 100" fill="none">
|
||||
<circle cx="50" cy="50" r="45" stroke="rgba(16,185,129,0.12)" strokeWidth="0.8" strokeDasharray="4 6" />
|
||||
</svg>
|
||||
<svg className="absolute w-40 h-40 animate-[spin_25s_linear_infinite_reverse]" viewBox="0 0 100 100" fill="none">
|
||||
<circle cx="50" cy="50" r="45" stroke="rgba(16,185,129,0.07)" strokeWidth="0.5" strokeDasharray="2 8" />
|
||||
</svg>
|
||||
|
||||
{/* Orbiting dots */}
|
||||
<div className="absolute w-24 h-24 animate-[spin_6s_linear_infinite]">
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-1.5 h-1.5 rounded-full bg-emerald-400/50" />
|
||||
</div>
|
||||
<div className="absolute w-36 h-36 animate-[spin_10s_linear_infinite_reverse]">
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-emerald-400/30" />
|
||||
</div>
|
||||
|
||||
{/* Center lock */}
|
||||
<div className="relative z-10 w-14 h-14 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-emerald-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
<circle cx="12" cy="16" r="1" fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Floating code snippets (Track 3) ─── */
|
||||
|
||||
export function ExtendViz() {
|
||||
const snippets = [
|
||||
{ x: '10%', y: '12%', text: 'fn extend()', delay: '0s' },
|
||||
{ x: '45%', y: '8%', text: '<Plugin />', delay: '0.8s' },
|
||||
{ x: '20%', y: '45%', text: '.hook()', delay: '1.2s' },
|
||||
{ x: '55%', y: '42%', text: 'export', delay: '0.4s' },
|
||||
{ x: '15%', y: '75%', text: 'pipe()', delay: '1.6s' },
|
||||
{ x: '50%', y: '78%', text: 'import', delay: '0.6s' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
{/* Radiating lines */}
|
||||
<svg className="absolute inset-0 w-full h-full opacity-40" viewBox="0 0 200 200" preserveAspectRatio="xMidYMid meet">
|
||||
{[0, 45, 90, 135, 180, 225, 270, 315].map((angle) => {
|
||||
const rad = (angle * Math.PI) / 180
|
||||
return (
|
||||
<line
|
||||
key={angle}
|
||||
x1="100" y1="100"
|
||||
x2={100 + Math.cos(rad) * 90} y2={100 + Math.sin(rad) * 90}
|
||||
stroke="rgba(139, 92, 246, 0.08)"
|
||||
strokeWidth="0.5"
|
||||
strokeDasharray="2 4"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</svg>
|
||||
|
||||
{/* Floating snippets */}
|
||||
{snippets.map((s, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute font-mono text-[9px] px-1.5 py-1 rounded bg-violet-500/8 border border-violet-500/15 text-violet-300/40 animate-pulse whitespace-nowrap"
|
||||
style={{ left: s.x, top: s.y, animationDelay: s.delay, animationDuration: '3s' }}
|
||||
>
|
||||
{s.text}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Center node */}
|
||||
<div className="relative z-10 w-14 h-14 rounded-xl bg-violet-500/10 border border-violet-500/20 flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-violet-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="16 18 22 12 16 6" /><polyline points="8 6 2 12 8 18" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user