diff --git a/client/public/sparkle_1.png b/client/public/sparkle_1.png new file mode 100644 index 0000000..f3dc617 Binary files /dev/null and b/client/public/sparkle_1.png differ diff --git a/client/public/sparkle_2.png b/client/public/sparkle_2.png new file mode 100644 index 0000000..87e8d37 Binary files /dev/null and b/client/public/sparkle_2.png differ diff --git a/client/public/sparkle_3.png b/client/public/sparkle_3.png new file mode 100644 index 0000000..2e90eae Binary files /dev/null and b/client/public/sparkle_3.png differ diff --git a/client/src/components/ui/Explosion.tsx b/client/src/components/ui/Explosion.tsx index 7867978..1e1940a 100644 --- a/client/src/components/ui/Explosion.tsx +++ b/client/src/components/ui/Explosion.tsx @@ -1,95 +1,57 @@ -import Image from "next/image"; -import React, { useEffect, useRef, useState } from "react"; +import { motion } from "framer-motion"; -import { ExplosionPosition } from "../../hooks/useExplosions"; -import { Crater } from "./Crater"; -import { DebrisBurst } from "./DebrisBurst"; -import { Smoke } from "./Smoke"; +type ExplosionProps = { + colour1: string; + colour2: string; + // looks better with odd numbers + count: number; + // can optionally take offsets to adjust exact position + yOffset?: number; + xOffset?: number; +}; -interface ExplosionProps { - explosion: ExplosionPosition; -} - -/** - * Renders a single explosion at a specific position. - * Position is defined as a percentage of the parent container. - */ -export const Explosion = React.memo(function Explosion({ - explosion, +export function Explosion({ + colour1, + colour2, + count, + yOffset = 0, + xOffset = 0, }: ExplosionProps) { - const containerRef = useRef(null); - const [debrisPosition, setDebrisPosition] = useState<{ - x: number; - y: number; - } | null>(null); - - // Convert percentage position to pixel coordinates for DebrisBurst - useEffect(() => { - if (!containerRef.current) return; - const container = containerRef.current.closest( - '[class*="relative"]', - ) as HTMLElement; - if (!container) { - // Fallback: use window if no relative container found - const x = (explosion.x / 100) * window.innerWidth; - const y = (explosion.y / 100) * window.innerHeight; - setDebrisPosition({ x, y }); - return; - } - - const rect = container.getBoundingClientRect(); - const x = rect.left + (explosion.x / 100) * rect.width; - const y = rect.top + (explosion.y / 100) * rect.height; - setDebrisPosition({ x, y }); - }, [explosion.x, explosion.y]); + const particles = Array.from({ length: count }); return ( -
- {/* SVG Crater with depth shading */} -
- -
- {/* Physics-based debris burst */} - {debrisPosition && ( - - )} - {/* The actual explosion GIF */} -
- Explosion -
- {/* Rising smoke effect */} - +
+ {particles.map((_, i) => { + const individualSize = Math.random() * 0.1 + 4; + const randomDisplacement = Math.random() * 10; + + // for every particle return a particle with a different size + return ( + + ); + })}
); -}); +} + +export default Explosion; diff --git a/client/src/components/ui/eventHighlightCard.tsx b/client/src/components/ui/eventHighlightCard.tsx index 378b03e..4445895 100644 --- a/client/src/components/ui/eventHighlightCard.tsx +++ b/client/src/components/ui/eventHighlightCard.tsx @@ -1,5 +1,6 @@ import { Play } from "lucide-react"; import Image from "next/image"; +import { useState } from "react"; export type eventHighlightCardImage = { url: string; @@ -17,6 +18,13 @@ export type eventHighlightCardType = { row: number; }; +export type sparkleIndexOverlay = { + card: eventHighlightCardType; + indexes: number[]; +}; + +const sparkleImages = ["/sparkle_1.png", "/sparkle_2.png", "/sparkle_3.png"]; + // Purple card header section. const renderCardHeader = (card: eventHighlightCardType) => { // Renders differently if we want the techno border. @@ -41,7 +49,6 @@ const renderCardHeader = (card: eventHighlightCardType) => {
); } - return (
{card.title} @@ -49,6 +56,34 @@ const renderCardHeader = (card: eventHighlightCardType) => { ); }; +// only render sparkles on specific cards +const renderSparkleOverlay = (sparkle: sparkleIndexOverlay) => { + switch (sparkle.card.id) { + case 2: + return ( + sparkle + ); + case 3: + return ( + sparkle + ); + default: + return null; + } +}; + export function EventHighlightCard({ id, title, @@ -57,11 +92,21 @@ export function EventHighlightCard({ image, row, }: eventHighlightCardType) { + const [indexes] = useState(() => { + const first = Math.floor(Math.random() * sparkleImages.length); + let second = Math.floor(Math.random() * sparkleImages.length); + + if (second === first) { + second = (first + 1) % sparkleImages.length; + } + return [first, second]; + }); + return (
{renderCardHeader({ id, title, description, type, image, row })} -
+
)}
+ {renderSparkleOverlay({ + card: { id, title, description, type, image, row }, + indexes, + })}
); } + +// render sparkle overlay function takes a card and index to then return a sparkle on the specified indexes diff --git a/client/src/pages/index.tsx b/client/src/pages/index.tsx index 979246e..aa0378d 100644 --- a/client/src/pages/index.tsx +++ b/client/src/pages/index.tsx @@ -9,32 +9,24 @@ import { EventHighlightCard, eventHighlightCardType, } from "@/components/ui/eventHighlightCard"; +import Explosion from "@/components/ui/Explosion"; import LandingGames from "@/components/ui/landingGames"; -import { useExplosionContext } from "@/contexts/ExplosionContext"; import { UiEvent, useEvents } from "@/hooks/useEvents"; export default function Landing() { + const [showExplosion, setShowExplosion] = useState(false); const [isShaking, setIsShaking] = useState(false); - const { triggerExplosionAt } = useExplosionContext(); - - const handleBombClick = (e: React.MouseEvent) => { - // Trigger multiple explosions across the page - for (let i = 0; i < 10; i++) { - setTimeout(() => { - // Random position with 10% margin from edges - const x = window.innerWidth * (0.1 + Math.random() * 0.8); - const y = window.innerHeight * (0.1 + Math.random() * 0.8); - triggerExplosionAt(x, y); - }, i * 50); // Stagger by 50ms - } + const handleExplode = () => { + if (showExplosion) return; + + setShowExplosion(true); + setTimeout(() => setShowExplosion(false), 700); // Trigger screen shake setIsShaking(true); setTimeout(() => setIsShaking(false), 400); - - // Prevent event bubbling - e.stopPropagation(); }; + const { data, isPending, isError, isFetching } = useEvents({ type: "upcoming", pageSize: 100, @@ -132,48 +124,63 @@ export default function Landing() { alt="placeholder" className="retroBorder min-w-80" /> - Bomb - click to explode! +
+ {showExplosion && ( + + )} + bomb +
-
- {eventCards - .filter((card) => card.row === 1) - .map((card) => ( - - ))} +
+
+ {eventCards + .filter((card) => card.row === 1) + .map((card) => ( + + ))} +
-
- {eventCards - .filter((card) => card.row === 2) - .map((card) => ( - - ))} - -
- {gameLogoImages.map((logo, index) => ( - {logo.alt} - ))} +
+
+ {eventCards + .filter((card) => card.row === 2) + .map((card) => ( + + ))} +
+ {gameLogoImages.map((logo, index) => ( + {logo.alt} + ))} +