diff --git a/app/page.tsx b/app/page.tsx index e0ba7dc..3b93580 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,158 +1,312 @@ -'use client'; +"use client"; -import { useRef, useState } from 'react'; -import Link from 'next/link'; +import React, { useState, useEffect, useRef } from "react"; +import Link from "next/link"; import { - ArrowRight, Calendar, Zap, Github, CheckCircle, Ticket, MapPin, - Terminal, Activity, Users, Monitor, Play, - Star, GitFork -} from 'lucide-react'; -import { Button, Section, Card, Heading, Badge } from '../components/UI'; -import { EVENTS, BLOG_POSTS, SPONSORS, PROJECTS } from '../data'; - -// const HeroVideo = () => { -// const videoRef = useRef(null); -// const [isPlaying, setIsPlaying] = useState(false); - -// const togglePlayback = () => { -// const video = videoRef.current; -// if (!video) return; -// if (video.paused) { -// video.play(); -// } else { -// video.pause(); -// } -// }; - -// return ( -//
-//
-//
-// Spotlight -//
Community Reel
-//
-//
-//
Runtime
-//
02:34
-//
-//
- -//
-//
-//
-//
-//
-//
-//
-//
Kampala JS
-//
Community Reel
-//
-// FEATURED -//
-//
-//
-//
-//
-// ); -// }; -const HeroVideo = () => { - const videoRef = useRef(null); - const [isPlaying, setIsPlaying] = useState(false); - - const togglePlayback = () => { - const video = videoRef.current; - if (!video) return; - if (video.paused) { - video.play().catch(() => {}); // handle autoplay block gracefully - } else { - video.pause(); + ArrowRight, + Calendar, + Zap, + Github, + CheckCircle, + Ticket, + MapPin, + Terminal, + Activity, + Users, + Monitor, + Play, + RotateCcw, + Trophy, + Flame, + Star, + GitFork, +} from "lucide-react"; +import { Button, Section, Card, Heading, Badge } from "../components/UI"; +import { EVENTS, BLOG_POSTS, SPONSORS, PROJECTS } from "../data"; + +// --- ARCADE MATRIX GAME --- +const MatrixGame = () => { + // Responsive Configuration + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + // Set initial value on client side + setIsMobile(window.innerWidth < 640); + const handleResize = () => setIsMobile(window.innerWidth < 640); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + const COLS = isMobile ? 8 : 16; // Fewer cols on mobile for bigger touch targets + const ROWS = 10; + const TOTAL_CELLS = COLS * ROWS; + const TICK_RATE_MS = 150; // Speed of scanline + const DECAY_RATE = 2.5; // Health loss per tick + + const [grid, setGrid] = useState(Array(TOTAL_CELLS).fill(false)); + const [gameState, setGameState] = useState<"IDLE" | "PLAYING" | "GAMEOVER">( + "IDLE", + ); + const [score, setScore] = useState(0); + const [highScore, setHighScore] = useState(0); + const [health, setHealth] = useState(100); + const [currentCol, setCurrentCol] = useState(-1); + const [isDrawing, setIsDrawing] = useState(false); + + // Game Loop Refs + const intervalRef = useRef(null); + const healthRef = useRef(100); + + // Reset grid when layout changes + useEffect(() => { + setGrid(Array(TOTAL_CELLS).fill(false)); + setGameState("IDLE"); + setScore(0); + setHealth(100); + if (intervalRef.current) clearInterval(intervalRef.current); + }, [TOTAL_CELLS]); + + // Initialize High Score from local storage if possible (mocked here) + useEffect(() => { + const saved = localStorage.getItem("jskla_highscore"); + if (saved) setHighScore(parseInt(saved, 10)); + }, []); + + const startGame = () => { + setGrid(Array(TOTAL_CELLS).fill(false)); + setScore(0); + setHealth(100); + healthRef.current = 100; + setGameState("PLAYING"); + setCurrentCol(0); + }; + + const stopGame = () => { + if (intervalRef.current) clearInterval(intervalRef.current); + setGameState("GAMEOVER"); + if (score > highScore) { + setHighScore(score); + localStorage.setItem("jskla_highscore", score.toString()); + } + }; + + // Game Tick Logic + useEffect(() => { + if (gameState !== "PLAYING") { + if (intervalRef.current) clearInterval(intervalRef.current); + return; + } + + intervalRef.current = window.setInterval(() => { + setCurrentCol((prev) => { + const nextCol = (prev + 1) % COLS; + processColumn(nextCol); + return nextCol; + }); + + // Health Decay + setHealth((prev) => { + const next = prev - DECAY_RATE; + healthRef.current = next; + if (next <= 0) { + stopGame(); + return 0; + } + return next; + }); + }, TICK_RATE_MS); + + return () => { + if (intervalRef.current) clearInterval(intervalRef.current); + }; + }, [gameState, grid, COLS]); + + const processColumn = (colIndex: number) => { + setGrid((currentGrid) => { + const newGrid = [...currentGrid]; + let hits = 0; + let colFull = true; + + for (let r = 0; r < ROWS; r++) { + const cellIndex = r * COLS + colIndex; + if (newGrid[cellIndex]) { + hits++; + newGrid[cellIndex] = false; // CONSUME CELL + } else { + colFull = false; + } + } + + if (hits > 0) { + const points = hits * 10 + (colFull ? 500 : 0); // Bonus for full column + const healthGain = hits * 3 + (colFull ? 20 : 0); + + setScore((s) => s + points); + setHealth((h) => Math.min(100, h + healthGain)); + } + + return newGrid; + }); + }; + + const toggleCell = (index: number) => { + if (gameState !== "PLAYING") return; + setGrid((prev) => { + const next = [...prev]; + next[index] = true; + return next; + }); + }; + + const handleMouseEnter = (index: number) => { + if (isDrawing && gameState === "PLAYING") { + toggleCell(index); + } + }; + + const handleTouchMove = (e: React.TouchEvent) => { + if (gameState !== "PLAYING") return; + const touch = e.touches[0]; + const element = document.elementFromPoint(touch.clientX, touch.clientY); + const indexStr = element?.getAttribute("data-index"); + if (indexStr) { + const index = parseInt(indexStr, 10); + toggleCell(index); } }; return ( -
- {/* Header bar – slightly more compact on mobile */} -
+
setIsDrawing(true)} + onMouseUp={() => setIsDrawing(false)} + onMouseLeave={() => setIsDrawing(false)} + onTouchStart={() => setIsDrawing(true)} + onTouchEnd={() => setIsDrawing(false)} + onTouchMove={handleTouchMove} + > + {/* HUD */} +
- Spotlight -
Community Reel
+ + System Integrity + +
+
50 ? "bg-green-500" : health > 20 ? "bg-js-yellow" : "bg-red-500"}`} + style={{ width: `${health}%` }} + >
+
-
-
Runtime
-
02:34
+ +
+
+ Current Score +
+
+ {score.toString().padStart(6, "0")} +
+
+ +
+
+ High Score +
+
+ {highScore.toString().padStart(6, "0")} +
- {/* Main video area – mobile-first aspect + safe max height */} -
-
-
- {/* Video wrapper with gradient overlay */} -
-