Skip to content

Commit 86a5a4b

Browse files
committed
refactor: Move explosions to nav links instead of page clicks
- Create ExplosionContext for global explosion management - Wrap app with ExplosionProvider in _app.tsx - Trigger explosions on nav link clicks in Navbar - Remove page-wide click handlers from index.tsx and about.tsx - Keep bomb icon click (triggers 10 explosions + screen shake) - Delete unused usePageExplosions.ts and ExplosionEffects.tsx Explosions now only trigger on navigation interactions, not on every page click.
1 parent 0245aad commit 86a5a4b

7 files changed

Lines changed: 156 additions & 330 deletions

File tree

client/src/components/main/Navbar.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import Image from "next/image";
55
import Link from "next/link";
66
import { useState } from "react";
77

8+
import { useExplosionContext } from "@/contexts/ExplosionContext";
9+
810
export default function Navbar() {
911
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
12+
const { triggerExplosionAt } = useExplosionContext();
1013

1114
const navItems = [
1215
{ href: "/", label: "Home" },
@@ -16,11 +19,19 @@ export default function Navbar() {
1619
{ href: "/artwork", label: "Art Showcase" },
1720
];
1821

22+
const handleNavClick = (e: React.MouseEvent) => {
23+
triggerExplosionAt(e.clientX, e.clientY);
24+
};
25+
1926
return (
2027
<>
2128
<header className="sticky top-0 z-50 flex h-24 w-full items-center rounded-md border-b border-border/20 bg-background px-20 font-jersey10">
2229
<div className="flex flex-1 items-center">
23-
<Link href="/" className="flex items-center gap-3 text-2xl lg:mr-5">
30+
<Link
31+
href="/"
32+
className="flex items-center gap-3 text-2xl lg:mr-5"
33+
onClick={handleNavClick}
34+
>
2435
<Image
2536
src="/game_dev_club_logo.svg"
2637
alt="logo"
@@ -44,6 +55,7 @@ export default function Navbar() {
4455
<Link
4556
key={item.href}
4657
href={item.href}
58+
onClick={handleNavClick}
4759
className="whitespace-nowrap text-foreground/90 transition-colors duration-150 hover:text-primary"
4860
>
4961
{item.label}
@@ -67,7 +79,10 @@ export default function Navbar() {
6779
<Link
6880
key={item.href}
6981
href={item.href}
70-
onClick={() => setIsDropdownOpen(false)}
82+
onClick={(e) => {
83+
handleNavClick(e);
84+
setIsDropdownOpen(false);
85+
}}
7186
className="block whitespace-nowrap px-4 py-3 text-lg transition-colors duration-150 hover:bg-accent"
7287
>
7388
{item.label}

client/src/components/ui/ExplosionEffects.tsx

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"use client";
2+
3+
import React, { createContext, useContext, useCallback, useState, useRef } from "react";
4+
5+
import { DebrisBurst } from "@/components/ui/DebrisBurst";
6+
import { Explosion } from "@/components/ui/Explosion";
7+
import { useExplosions, ExplosionPosition } from "@/hooks/useExplosions";
8+
9+
// Max concurrent debris bursts to prevent lag
10+
const MAX_DEBRIS = 5;
11+
12+
type ClickDebris = {
13+
id: number;
14+
x: number;
15+
y: number;
16+
};
17+
18+
type ExplosionContextType = {
19+
triggerExplosionAt: (clientX: number, clientY: number) => void;
20+
};
21+
22+
const ExplosionContext = createContext<ExplosionContextType | null>(null);
23+
24+
export function useExplosionContext() {
25+
const context = useContext(ExplosionContext);
26+
if (!context) {
27+
throw new Error("useExplosionContext must be used within ExplosionProvider");
28+
}
29+
return context;
30+
}
31+
32+
export function ExplosionProvider({ children }: { children: React.ReactNode }) {
33+
const { explosions, triggerExplosions } = useExplosions();
34+
const [clickDebris, setClickDebris] = useState<ClickDebris[]>([]);
35+
const lastClickTime = useRef(0);
36+
const debrisTimeouts = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
37+
38+
const triggerExplosionAt = useCallback(
39+
(clientX: number, clientY: number) => {
40+
// Throttle - 100ms minimum between explosions
41+
const now = Date.now();
42+
if (now - lastClickTime.current < 100) return;
43+
lastClickTime.current = now;
44+
45+
// Convert to percentage of viewport
46+
const x = (clientX / window.innerWidth) * 100;
47+
const y = (clientY / window.innerHeight) * 100;
48+
49+
// Trigger explosion
50+
triggerExplosions({
51+
count: 1,
52+
minDelay: 0,
53+
maxDelay: 0,
54+
duration: 1500,
55+
playSound: true,
56+
position: { x, y },
57+
});
58+
59+
// Add debris burst
60+
const debrisId = now;
61+
setClickDebris((prev) => {
62+
const updated = [...prev, { id: debrisId, x: clientX, y: clientY }];
63+
return updated.slice(-MAX_DEBRIS);
64+
});
65+
66+
// Cleanup after animation
67+
const timeout = setTimeout(() => {
68+
setClickDebris((prev) => prev.filter((d) => d.id !== debrisId));
69+
debrisTimeouts.current.delete(timeout);
70+
}, 1500);
71+
debrisTimeouts.current.add(timeout);
72+
},
73+
[triggerExplosions]
74+
);
75+
76+
return (
77+
<ExplosionContext.Provider value={{ triggerExplosionAt }}>
78+
{children}
79+
{/* Render explosions in a fixed overlay */}
80+
<div
81+
style={{
82+
position: "fixed",
83+
inset: 0,
84+
pointerEvents: "none",
85+
zIndex: 9999,
86+
}}
87+
>
88+
{explosions.map((explosion: ExplosionPosition) => (
89+
<Explosion key={explosion.id} explosion={explosion} />
90+
))}
91+
{clickDebris.map((debris) => (
92+
<DebrisBurst
93+
key={debris.id}
94+
x={debris.x}
95+
y={debris.y}
96+
count={6}
97+
power={400}
98+
spreadDeg={360}
99+
gravity={1000}
100+
bounce={0.3}
101+
/>
102+
))}
103+
</div>
104+
</ExplosionContext.Provider>
105+
);
106+
}
107+

client/src/hooks/usePageExplosions.ts

Lines changed: 0 additions & 84 deletions
This file was deleted.

client/src/pages/_app.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Fira_Code, Inter as FontSans, Jersey_10 } from "next/font/google";
77

88
import Footer from "@/components/main/Footer";
99
import Navbar from "@/components/main/Navbar";
10+
import { ExplosionProvider } from "@/contexts/ExplosionContext";
1011

1112
const fontSans = FontSans({
1213
subsets: ["latin"],
@@ -32,13 +33,15 @@ export default function App({ Component, pageProps }: AppProps) {
3233
return (
3334
<QueryClientProvider client={queryClient}>
3435
<ReactQueryDevtools initialIsOpen={false} />
35-
<main
36-
className={`font-sans ` + fonts.map((font) => font.variable).join(" ")}
37-
>
38-
<Navbar />
39-
<Component {...pageProps} />
40-
<Footer />
41-
</main>
36+
<ExplosionProvider>
37+
<main
38+
className={`font-sans ` + fonts.map((font) => font.variable).join(" ")}
39+
>
40+
<Navbar />
41+
<Component {...pageProps} />
42+
<Footer />
43+
</main>
44+
</ExplosionProvider>
4245
</QueryClientProvider>
4346
);
4447
}

0 commit comments

Comments
 (0)