404lab 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/core/templates.js +5 -3
- package/package.json +1 -1
- package/templates/AmongUs.tsx +92 -109
- package/templates/BlueGlitch.tsx +34 -32
- package/templates/BugGame.tsx +479 -0
- package/templates/GeeksforGeeks.tsx +38 -44
- package/templates/Google.tsx +51 -37
- package/templates/MacOs.tsx +82 -68
- package/templates/ModernPage.tsx +45 -24
- package/templates/Particles.tsx +35 -18
- package/templates/Poet.tsx +99 -71
- package/templates/RetroTv.tsx +22 -18
- package/templates/SimplePage.tsx +32 -17
- package/templates/Snow.tsx +54 -21
- package/templates/StoneAge.tsx +75 -33
- package/templates/StrangerThings.tsx +11 -10
- package/templates/{Terminal404.tsx → Terminal.tsx} +20 -10
- package/templates/Vercel.tsx +78 -32
- package/templates/Void.tsx +345 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { cn } from '@/components/ui/cn';
|
|
6
|
+
|
|
7
|
+
export default function BugGame404() {
|
|
8
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
9
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
10
|
+
const [gameState, setGameState] = useState<'start' | 'play' | 'gameover' | 'victory'>('start');
|
|
11
|
+
const [score, setScore] = useState(0);
|
|
12
|
+
const [highScore, setHighScore] = useState(0);
|
|
13
|
+
|
|
14
|
+
const stateRef = useRef<'start' | 'play' | 'gameover' | 'victory'>('start');
|
|
15
|
+
const scoreRef = useRef(0);
|
|
16
|
+
const speedRef = useRef(3);
|
|
17
|
+
|
|
18
|
+
const obstaclesRef = useRef<Array<{ x: number; gapY: number; gapHeight: number; label: string; passed: boolean }>>([]);
|
|
19
|
+
|
|
20
|
+
const particlesRef = useRef<Array<{ x: number; y: number; vx: number; vy: number; life: number; size: number; color: string; decay: number }>>([]);
|
|
21
|
+
const bgParticlesRef = useRef<Array<{ x: number; y: number; size: number; speed: number; opacity: number }>>([]);
|
|
22
|
+
|
|
23
|
+
const playerRef = useRef({
|
|
24
|
+
x: 100, y: 300, w: 34, h: 24, vy: 0,
|
|
25
|
+
rotation: 0, wingAngle: 0
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const animationFrameRef = useRef<number>(0);
|
|
29
|
+
const scaleRef = useRef(1);
|
|
30
|
+
|
|
31
|
+
const GRAVITY = 0.25;
|
|
32
|
+
const JUMP_FORCE = -5.5;
|
|
33
|
+
const MAX_SPEED = 4;
|
|
34
|
+
const OBSTACLE_SPACING = 300;
|
|
35
|
+
const OBSTACLE_WIDTH = 60;
|
|
36
|
+
const MAX_SCORE = 404;
|
|
37
|
+
|
|
38
|
+
const ERROR_TEXTS = [
|
|
39
|
+
"404", "NULL", "NaN", "VOID", "ERR",
|
|
40
|
+
"FAIL", "LOST", "BUG", "GONE", "???",
|
|
41
|
+
"END", "NIL", "STOP", "NOPE", "ZERO"
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const saved = localStorage.getItem('bugHigh');
|
|
46
|
+
if (saved) setHighScore(parseInt(saved, 10));
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const initBackground = useCallback((width: number, height: number) => {
|
|
50
|
+
bgParticlesRef.current = Array.from({ length: 60 }, () => ({
|
|
51
|
+
x: Math.random() * width,
|
|
52
|
+
y: Math.random() * height,
|
|
53
|
+
size: Math.random() * 2 + 0.5,
|
|
54
|
+
speed: Math.random() * 0.2 + 0.05,
|
|
55
|
+
opacity: Math.random() * 0.4 + 0.1
|
|
56
|
+
}));
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const spawnObstacle = (x: number, height: number) => {
|
|
60
|
+
const minGap = 180;
|
|
61
|
+
const maxGap = 260;
|
|
62
|
+
const gapHeight = Math.random() * (maxGap - minGap) + minGap;
|
|
63
|
+
const minGapY = 100 + gapHeight/2;
|
|
64
|
+
const maxGapY = height - 100 - gapHeight/2;
|
|
65
|
+
const gapY = Math.random() * (maxGapY - minGapY) + minGapY;
|
|
66
|
+
|
|
67
|
+
obstaclesRef.current.push({
|
|
68
|
+
x,
|
|
69
|
+
gapY,
|
|
70
|
+
gapHeight,
|
|
71
|
+
label: ERROR_TEXTS[Math.floor(Math.random() * ERROR_TEXTS.length)],
|
|
72
|
+
passed: false
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const initGame = useCallback(() => {
|
|
77
|
+
if (!canvasRef.current) return;
|
|
78
|
+
const { width, height } = canvasRef.current;
|
|
79
|
+
|
|
80
|
+
obstaclesRef.current = [];
|
|
81
|
+
particlesRef.current = [];
|
|
82
|
+
scoreRef.current = 0;
|
|
83
|
+
speedRef.current = 2.5;
|
|
84
|
+
setScore(0);
|
|
85
|
+
|
|
86
|
+
initBackground(width, height);
|
|
87
|
+
|
|
88
|
+
playerRef.current = {
|
|
89
|
+
x: width * 0.2,
|
|
90
|
+
y: height / 2,
|
|
91
|
+
w: 34, h: 24, vy: 0,
|
|
92
|
+
rotation: 0, wingAngle: 0
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < 3; i++) {
|
|
96
|
+
spawnObstacle(width + 500 + i * OBSTACLE_SPACING, height);
|
|
97
|
+
}
|
|
98
|
+
}, [initBackground]);
|
|
99
|
+
|
|
100
|
+
const createBurst = (x: number, y: number, color: string, count = 8) => {
|
|
101
|
+
for (let i = 0; i < count; i++) {
|
|
102
|
+
const angle = Math.random() * Math.PI * 2;
|
|
103
|
+
const speed = Math.random() * 3 + 1;
|
|
104
|
+
particlesRef.current.push({
|
|
105
|
+
x, y,
|
|
106
|
+
vx: Math.cos(angle) * speed,
|
|
107
|
+
vy: Math.sin(angle) * speed,
|
|
108
|
+
life: 1.0,
|
|
109
|
+
size: Math.random() * 3 + 1,
|
|
110
|
+
color,
|
|
111
|
+
decay: Math.random() * 0.04 + 0.02
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const jump = () => {
|
|
117
|
+
if (stateRef.current !== 'play') return;
|
|
118
|
+
const p = playerRef.current;
|
|
119
|
+
p.vy = JUMP_FORCE;
|
|
120
|
+
createBurst(p.x, p.y + p.h/2, 'rgba(255,255,255,0.4)', 4);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const update = () => {
|
|
124
|
+
if (stateRef.current !== 'play' || !canvasRef.current) return;
|
|
125
|
+
const { width, height } = canvasRef.current;
|
|
126
|
+
|
|
127
|
+
const p = playerRef.current;
|
|
128
|
+
|
|
129
|
+
p.vy += GRAVITY;
|
|
130
|
+
p.y += p.vy;
|
|
131
|
+
|
|
132
|
+
if (p.vy < 0) {
|
|
133
|
+
p.rotation = Math.max(-0.5, p.rotation - 0.1);
|
|
134
|
+
} else {
|
|
135
|
+
p.rotation = Math.min(Math.PI / 2, p.rotation + 0.08);
|
|
136
|
+
}
|
|
137
|
+
p.wingAngle += 0.8;
|
|
138
|
+
|
|
139
|
+
if (p.y + p.h > height || p.y < 0) {
|
|
140
|
+
handleGameOver();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
obstaclesRef.current.forEach(obs => {
|
|
145
|
+
obs.x -= speedRef.current;
|
|
146
|
+
|
|
147
|
+
const pInset = 4;
|
|
148
|
+
const pl = p.x + pInset;
|
|
149
|
+
const pr = p.x + p.w - pInset;
|
|
150
|
+
const pt = p.y + pInset;
|
|
151
|
+
const pb = p.y + p.h - pInset;
|
|
152
|
+
|
|
153
|
+
const gapTop = obs.gapY - obs.gapHeight / 2;
|
|
154
|
+
const gapBottom = obs.gapY + obs.gapHeight / 2;
|
|
155
|
+
|
|
156
|
+
if (
|
|
157
|
+
pr > obs.x &&
|
|
158
|
+
pl < obs.x + OBSTACLE_WIDTH
|
|
159
|
+
) {
|
|
160
|
+
if (pt < gapTop || pb > gapBottom) {
|
|
161
|
+
handleGameOver();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!obs.passed && pl > obs.x + OBSTACLE_WIDTH) {
|
|
166
|
+
obs.passed = true;
|
|
167
|
+
scoreRef.current += 1;
|
|
168
|
+
setScore(scoreRef.current);
|
|
169
|
+
if (speedRef.current < MAX_SPEED) speedRef.current += 0.05;
|
|
170
|
+
|
|
171
|
+
if (scoreRef.current >= 404) {
|
|
172
|
+
stateRef.current = 'victory';
|
|
173
|
+
setGameState('victory');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (obstaclesRef.current[0] && obstaclesRef.current[0].x < -100) {
|
|
179
|
+
obstaclesRef.current.shift();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const lastObs = obstaclesRef.current[obstaclesRef.current.length - 1];
|
|
183
|
+
if (lastObs && lastObs.x < width - 100) {
|
|
184
|
+
spawnObstacle(lastObs.x + OBSTACLE_SPACING, height);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
particlesRef.current.forEach(pt => {
|
|
188
|
+
pt.x += pt.vx;
|
|
189
|
+
pt.y += pt.vy;
|
|
190
|
+
pt.life -= pt.decay;
|
|
191
|
+
});
|
|
192
|
+
particlesRef.current = particlesRef.current.filter(pt => pt.life > 0);
|
|
193
|
+
|
|
194
|
+
bgParticlesRef.current.forEach(bg => {
|
|
195
|
+
bg.x -= bg.speed * (speedRef.current * 0.2);
|
|
196
|
+
if (bg.x < 0) bg.x = width;
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const draw = () => {
|
|
201
|
+
if (!canvasRef.current) return;
|
|
202
|
+
const ctx = canvasRef.current.getContext('2d');
|
|
203
|
+
if (!ctx) return;
|
|
204
|
+
const { width, height } = canvasRef.current;
|
|
205
|
+
|
|
206
|
+
const bgGrad = ctx.createLinearGradient(0, 0, 0, height);
|
|
207
|
+
bgGrad.addColorStop(0, '#020617');
|
|
208
|
+
bgGrad.addColorStop(1, '#1e1b4b');
|
|
209
|
+
ctx.fillStyle = bgGrad;
|
|
210
|
+
ctx.fillRect(0, 0, width, height);
|
|
211
|
+
|
|
212
|
+
ctx.strokeStyle = '#3b82f6';
|
|
213
|
+
ctx.lineWidth = 1;
|
|
214
|
+
ctx.globalAlpha = 0.1;
|
|
215
|
+
|
|
216
|
+
ctx.fillStyle = '#94a3b8';
|
|
217
|
+
bgParticlesRef.current.forEach(bg => {
|
|
218
|
+
ctx.globalAlpha = bg.opacity;
|
|
219
|
+
ctx.beginPath();
|
|
220
|
+
const sz = bg.size;
|
|
221
|
+
ctx.rect(bg.x, bg.y, sz, sz);
|
|
222
|
+
ctx.fill();
|
|
223
|
+
});
|
|
224
|
+
ctx.globalAlpha = 1;
|
|
225
|
+
|
|
226
|
+
obstaclesRef.current.forEach(obs => {
|
|
227
|
+
const gapTop = obs.gapY - obs.gapHeight / 2;
|
|
228
|
+
const gapBottom = obs.gapY + obs.gapHeight / 2;
|
|
229
|
+
|
|
230
|
+
const pipeGrad = ctx.createLinearGradient(obs.x, 0, obs.x + OBSTACLE_WIDTH, 0);
|
|
231
|
+
pipeGrad.addColorStop(0, '#334155');
|
|
232
|
+
pipeGrad.addColorStop(0.5, '#475569');
|
|
233
|
+
pipeGrad.addColorStop(1, '#1e293b');
|
|
234
|
+
|
|
235
|
+
ctx.fillStyle = pipeGrad;
|
|
236
|
+
ctx.fillRect(obs.x, 0, OBSTACLE_WIDTH, gapTop);
|
|
237
|
+
ctx.fillRect(obs.x, gapBottom, OBSTACLE_WIDTH, height - gapBottom);
|
|
238
|
+
|
|
239
|
+
ctx.shadowBlur = 10;
|
|
240
|
+
ctx.shadowColor = '#f43f5e';
|
|
241
|
+
ctx.strokeStyle = '#e11d48';
|
|
242
|
+
ctx.lineWidth = 2;
|
|
243
|
+
ctx.strokeRect(obs.x, 0, OBSTACLE_WIDTH, gapTop);
|
|
244
|
+
ctx.strokeRect(obs.x, gapBottom, OBSTACLE_WIDTH, height - gapBottom);
|
|
245
|
+
ctx.shadowBlur = 0;
|
|
246
|
+
|
|
247
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
|
248
|
+
ctx.font = 'bold 16px monospace';
|
|
249
|
+
ctx.textAlign = 'center';
|
|
250
|
+
if (gapTop > 50) {
|
|
251
|
+
ctx.fillText(obs.label, obs.x + OBSTACLE_WIDTH/2, gapTop - 20);
|
|
252
|
+
}
|
|
253
|
+
if (height - gapBottom > 50) {
|
|
254
|
+
ctx.fillText(obs.label, obs.x + OBSTACLE_WIDTH/2, gapBottom + 30);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
ctx.fillStyle = '#0f172a';
|
|
258
|
+
ctx.fillRect(obs.x - 2, gapTop - 20, OBSTACLE_WIDTH + 4, 20);
|
|
259
|
+
ctx.fillRect(obs.x - 2, gapBottom, OBSTACLE_WIDTH + 4, 20);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const p = playerRef.current;
|
|
263
|
+
ctx.save();
|
|
264
|
+
ctx.translate(p.x + p.w/2, p.y + p.h/2);
|
|
265
|
+
ctx.rotate(p.rotation);
|
|
266
|
+
|
|
267
|
+
ctx.shadowBlur = 15;
|
|
268
|
+
ctx.shadowColor = '#a855f7';
|
|
269
|
+
|
|
270
|
+
ctx.fillStyle = '#9333ea';
|
|
271
|
+
ctx.beginPath();
|
|
272
|
+
ctx.roundRect(-p.w/2, -p.h/2, p.w, p.h, 6);
|
|
273
|
+
ctx.fill();
|
|
274
|
+
|
|
275
|
+
ctx.fillStyle = '#fff';
|
|
276
|
+
ctx.beginPath();
|
|
277
|
+
ctx.arc(6, -4, 8, 0, Math.PI * 2);
|
|
278
|
+
ctx.fill();
|
|
279
|
+
ctx.fillStyle = '#000';
|
|
280
|
+
ctx.beginPath();
|
|
281
|
+
ctx.arc(8, -4, 3, 0, Math.PI * 2);
|
|
282
|
+
ctx.fill();
|
|
283
|
+
|
|
284
|
+
const wingY = Math.sin(p.wingAngle) * 6;
|
|
285
|
+
ctx.fillStyle = '#f3e8ff';
|
|
286
|
+
ctx.beginPath();
|
|
287
|
+
ctx.ellipse(-8, -2 + wingY, 8, 5, -0.4, 0, Math.PI * 2);
|
|
288
|
+
ctx.fill();
|
|
289
|
+
|
|
290
|
+
ctx.restore();
|
|
291
|
+
ctx.shadowBlur = 0;
|
|
292
|
+
|
|
293
|
+
particlesRef.current.forEach(pt => {
|
|
294
|
+
ctx.globalAlpha = pt.life;
|
|
295
|
+
ctx.fillStyle = pt.color;
|
|
296
|
+
ctx.beginPath();
|
|
297
|
+
ctx.arc(pt.x, pt.y, pt.size, 0, Math.PI * 2);
|
|
298
|
+
ctx.fill();
|
|
299
|
+
});
|
|
300
|
+
ctx.globalAlpha = 1;
|
|
301
|
+
|
|
302
|
+
if (stateRef.current === 'play') {
|
|
303
|
+
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
|
304
|
+
ctx.font = '900 48px sans-serif';
|
|
305
|
+
ctx.textAlign = 'center';
|
|
306
|
+
ctx.fillText(scoreRef.current.toString(), width/2, 80);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const loop = () => {
|
|
311
|
+
update();
|
|
312
|
+
draw();
|
|
313
|
+
animationFrameRef.current = requestAnimationFrame(loop);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const handleResize = useCallback(() => {
|
|
317
|
+
if (!containerRef.current || !canvasRef.current) return;
|
|
318
|
+
const { clientWidth, clientHeight } = containerRef.current;
|
|
319
|
+
|
|
320
|
+
scaleRef.current = window.devicePixelRatio || 1;
|
|
321
|
+
canvasRef.current.width = clientWidth * scaleRef.current;
|
|
322
|
+
canvasRef.current.height = clientHeight * scaleRef.current;
|
|
323
|
+
canvasRef.current.style.width = `${clientWidth}px`;
|
|
324
|
+
canvasRef.current.style.height = `${clientHeight}px`;
|
|
325
|
+
|
|
326
|
+
const ctx = canvasRef.current.getContext('2d');
|
|
327
|
+
if (ctx) ctx.scale(scaleRef.current, scaleRef.current);
|
|
328
|
+
|
|
329
|
+
initBackground(clientWidth, clientHeight);
|
|
330
|
+
}, [initBackground]);
|
|
331
|
+
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
window.addEventListener('resize', handleResize);
|
|
334
|
+
handleResize();
|
|
335
|
+
initGame();
|
|
336
|
+
loop();
|
|
337
|
+
|
|
338
|
+
return () => {
|
|
339
|
+
window.removeEventListener('resize', handleResize);
|
|
340
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
341
|
+
};
|
|
342
|
+
}, [handleResize, initGame]);
|
|
343
|
+
|
|
344
|
+
const handleInput = (e: React.MouseEvent | React.TouchEvent | KeyboardEvent) => {
|
|
345
|
+
if (stateRef.current === 'start' || stateRef.current === 'gameover' || stateRef.current === 'victory') {
|
|
346
|
+
if (e.type === 'keydown' && (e as KeyboardEvent).code !== 'Space' && (e as KeyboardEvent).code !== 'Enter') return;
|
|
347
|
+
if (stateRef.current === 'start') {
|
|
348
|
+
stateRef.current = 'play';
|
|
349
|
+
setGameState('play');
|
|
350
|
+
jump();
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
jump();
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
const handleKey = (e: KeyboardEvent) => {
|
|
359
|
+
if (e.code === 'Space' || e.code === 'Enter') {
|
|
360
|
+
e.preventDefault();
|
|
361
|
+
handleInput(e);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
window.addEventListener('keydown', handleKey);
|
|
365
|
+
return () => window.removeEventListener('keydown', handleKey);
|
|
366
|
+
}, []);
|
|
367
|
+
|
|
368
|
+
const handleGameOver = () => {
|
|
369
|
+
stateRef.current = 'gameover';
|
|
370
|
+
setGameState('gameover');
|
|
371
|
+
if (scoreRef.current > highScore) {
|
|
372
|
+
setHighScore(scoreRef.current);
|
|
373
|
+
localStorage.setItem('bugHigh', scoreRef.current.toString());
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const resetGame = () => {
|
|
378
|
+
initGame();
|
|
379
|
+
stateRef.current = 'start';
|
|
380
|
+
setGameState('start');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<div
|
|
385
|
+
ref={containerRef}
|
|
386
|
+
className="relative w-full h-screen overflow-hidden bg-slate-950 select-none font-sans"
|
|
387
|
+
onMouseDown={handleInput}
|
|
388
|
+
onTouchStart={(e) => {
|
|
389
|
+
handleInput(e);
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
<canvas
|
|
393
|
+
ref={canvasRef}
|
|
394
|
+
className="block w-full h-full touch-none"
|
|
395
|
+
/>
|
|
396
|
+
|
|
397
|
+
{gameState === 'start' && (
|
|
398
|
+
<div className="absolute inset-0 flex flex-col items-center justify-center z-20 animate-in fade-in bg-black/40 backdrop-blur-sm px-4">
|
|
399
|
+
<div className="relative mb-6 sm:mb-8">
|
|
400
|
+
<div className="absolute -inset-4 bg-purple-500/30 blur-xl rounded-full animate-pulse" />
|
|
401
|
+
<span className="relative text-5xl sm:text-7xl select-none">👾</span>
|
|
402
|
+
</div>
|
|
403
|
+
<h1 className="text-3xl sm:text-5xl font-black text-white tracking-tighter mb-2 drop-shadow-xl text-center">
|
|
404
|
+
<span className="text-purple-400">404</span> GLITCH JUMP
|
|
405
|
+
</h1>
|
|
406
|
+
<p className="text-slate-400 mb-6 sm:mb-8 font-mono text-xs sm:text-sm tracking-widest text-center max-w-xs">
|
|
407
|
+
PROTOCOL: AVOID_THE_VOID<br/>
|
|
408
|
+
TARGET: SCORE_404
|
|
409
|
+
</p>
|
|
410
|
+
<button
|
|
411
|
+
onClick={(e) => { e.stopPropagation(); setGameState('play'); stateRef.current = 'play'; }}
|
|
412
|
+
className="px-8 sm:px-10 py-3 sm:py-4 bg-white text-black font-black text-base sm:text-lg rounded-full hover:scale-110 transition-transform shadow-[0_0_20px_rgba(255,255,255,0.4)]"
|
|
413
|
+
>
|
|
414
|
+
START DEBUGGING
|
|
415
|
+
</button>
|
|
416
|
+
<div className="absolute bottom-6 sm:bottom-10 text-slate-500 text-[10px] sm:text-xs uppercase animate-pulse">
|
|
417
|
+
Tap / Space / Click to Fly
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
)}
|
|
421
|
+
|
|
422
|
+
{(gameState === 'gameover' || gameState === 'victory') && (
|
|
423
|
+
<div className="absolute inset-0 flex flex-col items-center justify-center z-30 bg-black/70 backdrop-blur-md animate-in zoom-in-95">
|
|
424
|
+
<div className="bg-slate-900/90 border border-slate-700 p-8 rounded-3xl shadow-2xl text-center max-w-sm mx-6 relative overflow-hidden">
|
|
425
|
+
|
|
426
|
+
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-red-500 to-transparent opacity-50" />
|
|
427
|
+
|
|
428
|
+
<div className="mb-4">
|
|
429
|
+
{gameState === 'victory' ? (
|
|
430
|
+
<span className="text-6xl">🎉</span>
|
|
431
|
+
) : (
|
|
432
|
+
<span className="text-6xl">💀</span>
|
|
433
|
+
)}
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<h2 className="text-3xl font-black text-white mb-1">
|
|
437
|
+
{gameState === 'victory' ? "ERROR RESOLVED" : "CONNECTION LOST"}
|
|
438
|
+
</h2>
|
|
439
|
+
<div className="text-slate-400 text-xs font-mono mb-6 uppercase tracking-widest">
|
|
440
|
+
{gameState === 'victory' ? "System Restored Successfully" : "The Bug Was Squashed"}
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<div className="w-full bg-slate-800 rounded-xl p-4 mb-6">
|
|
444
|
+
<div className="flex justify-between items-end mb-2">
|
|
445
|
+
<span className="text-slate-400 text-xs uppercase font-bold">Score</span>
|
|
446
|
+
<span className="text-4xl font-black text-white leading-none">{score}</span>
|
|
447
|
+
</div>
|
|
448
|
+
<div className="w-full h-px bg-slate-700 my-2" />
|
|
449
|
+
<div className="flex justify-between items-end">
|
|
450
|
+
<span className="text-slate-500 text-xs uppercase font-bold">Best</span>
|
|
451
|
+
<span className="text-xl font-bold text-slate-300 leading-none">{highScore}</span>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<div className="flex gap-3">
|
|
456
|
+
<button
|
|
457
|
+
onClick={(e) => { e.stopPropagation(); resetGame(); }}
|
|
458
|
+
className="flex-1 py-3 bg-purple-600 hover:bg-purple-500 text-white font-bold rounded-xl transition-colors shadow-lg shadow-purple-900/20"
|
|
459
|
+
>
|
|
460
|
+
RETRY
|
|
461
|
+
</button>
|
|
462
|
+
<Link
|
|
463
|
+
href="/"
|
|
464
|
+
onClick={(e) => e.stopPropagation()}
|
|
465
|
+
className="flex-1 py-3 bg-slate-700 hover:bg-slate-600 text-white font-bold rounded-xl transition-colors"
|
|
466
|
+
>
|
|
467
|
+
HOME
|
|
468
|
+
</Link>
|
|
469
|
+
</div>
|
|
470
|
+
|
|
471
|
+
<div className="mt-6 text-[10px] text-slate-600 font-mono">
|
|
472
|
+
ERROR_CODE_404_PAGE_NOT_FOUND
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
)}
|
|
477
|
+
</div>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
@@ -2,26 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import Image from "next/image";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import { motion } from "framer-motion";
|
|
6
5
|
import { Search, GraduationCap, Code2, Terminal, BookOpen } from "lucide-react";
|
|
7
6
|
import { cn } from "@/components/ui/cn";
|
|
8
7
|
|
|
9
8
|
const GeeksforGeeks = ({ className }: { className?: string }) => {
|
|
10
|
-
const containerVariants = {
|
|
11
|
-
hidden: { opacity: 0 },
|
|
12
|
-
visible: {
|
|
13
|
-
opacity: 1,
|
|
14
|
-
transition: {
|
|
15
|
-
staggerChildren: 0.1,
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const itemVariants = {
|
|
21
|
-
hidden: { opacity: 0, y: 20 },
|
|
22
|
-
visible: { opacity: 1, y: 0 },
|
|
23
|
-
};
|
|
24
|
-
|
|
25
9
|
return (
|
|
26
10
|
<div
|
|
27
11
|
className={cn(
|
|
@@ -29,16 +13,30 @@ const GeeksforGeeks = ({ className }: { className?: string }) => {
|
|
|
29
13
|
className
|
|
30
14
|
)}
|
|
31
15
|
>
|
|
16
|
+
<style jsx global>{`
|
|
17
|
+
@keyframes fadeIn {
|
|
18
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
19
|
+
to { opacity: 1; transform: translateY(0); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.animate-fade-in {
|
|
23
|
+
animation: fadeIn 0.6s ease-out forwards;
|
|
24
|
+
opacity: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.delay-100 { animation-delay: 100ms; }
|
|
28
|
+
.delay-200 { animation-delay: 200ms; }
|
|
29
|
+
.delay-300 { animation-delay: 300ms; }
|
|
30
|
+
.delay-400 { animation-delay: 400ms; }
|
|
31
|
+
`}</style>
|
|
32
|
+
|
|
32
33
|
<div className="absolute inset-0 z-0 opacity-[0.03] pointer-events-none"
|
|
33
34
|
style={{ backgroundImage: 'radial-gradient(#2f8d46 1px, transparent 1px)', backgroundSize: '30px 30px' }} />
|
|
34
35
|
|
|
35
|
-
<
|
|
36
|
+
<div
|
|
36
37
|
className="z-10 w-full max-w-5xl flex flex-col items-center"
|
|
37
|
-
variants={containerVariants}
|
|
38
|
-
initial="hidden"
|
|
39
|
-
animate="visible"
|
|
40
38
|
>
|
|
41
|
-
<
|
|
39
|
+
<div className="mb-8 animate-fade-in">
|
|
42
40
|
<Image
|
|
43
41
|
src="https://media.geeksforgeeks.org/auth-dashboard-uploads/Illustration.svg"
|
|
44
42
|
alt="404 Illustration"
|
|
@@ -47,26 +45,23 @@ const GeeksforGeeks = ({ className }: { className?: string }) => {
|
|
|
47
45
|
priority
|
|
48
46
|
className="w-full max-w-[380px] h-auto drop-shadow-2xl"
|
|
49
47
|
/>
|
|
50
|
-
</
|
|
48
|
+
</div>
|
|
51
49
|
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
className="text-4xl md:text-5xl font-bold text-[#2f8d46] mb-4 text-center"
|
|
50
|
+
<h1
|
|
51
|
+
className="text-3xl md:text-5xl font-bold text-[#2f8d46] mb-4 text-center animate-fade-in delay-100 px-4"
|
|
55
52
|
>
|
|
56
53
|
Data Structure Not Found
|
|
57
|
-
</
|
|
54
|
+
</h1>
|
|
58
55
|
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
className="text-gray-600 text-center max-w-xl mb-12 text-lg"
|
|
56
|
+
<p
|
|
57
|
+
className="text-gray-600 text-center max-w-xl mb-8 sm:mb-12 text-base sm:text-lg animate-fade-in delay-200 px-4"
|
|
62
58
|
>
|
|
63
59
|
Even the most efficient algorithms occasionally hit a null pointer.
|
|
64
60
|
While we garbage collect this error, why not explore these popular topics?
|
|
65
|
-
</
|
|
61
|
+
</p>
|
|
66
62
|
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 w-full mb-12"
|
|
63
|
+
<div
|
|
64
|
+
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 w-full mb-12 animate-fade-in delay-300"
|
|
70
65
|
>
|
|
71
66
|
<Card
|
|
72
67
|
title="DSA Self Paced"
|
|
@@ -92,28 +87,28 @@ const GeeksforGeeks = ({ className }: { className?: string }) => {
|
|
|
92
87
|
color="bg-emerald-50 text-emerald-600 border-emerald-100"
|
|
93
88
|
description="For Data Science"
|
|
94
89
|
/>
|
|
95
|
-
</
|
|
90
|
+
</div>
|
|
96
91
|
|
|
97
|
-
<
|
|
98
|
-
<div className="relative group">
|
|
92
|
+
<div className="flex flex-col sm:flex-row gap-4 items-center animate-fade-in delay-400 p-4">
|
|
93
|
+
<div className="relative group w-full sm:w-auto">
|
|
99
94
|
<div className="absolute inset-y-0 left-3 flex items-center pointer-events-none">
|
|
100
95
|
<Search className="w-4 h-4 text-gray-400" />
|
|
101
96
|
</div>
|
|
102
97
|
<input
|
|
103
98
|
type="text"
|
|
104
99
|
placeholder="Search for tutorials..."
|
|
105
|
-
className="pl-10 pr-4 py-3 bg-white border border-gray-200 rounded-full w-64 md:w-80 focus:outline-none focus:ring-2 focus:ring-[#2f8d46]/20 focus:border-[#2f8d46] transition-all shadow-sm"
|
|
100
|
+
className="pl-10 pr-4 py-3 bg-white border border-gray-200 rounded-full w-full sm:w-64 md:w-80 focus:outline-none focus:ring-2 focus:ring-[#2f8d46]/20 focus:border-[#2f8d46] transition-all shadow-sm"
|
|
106
101
|
/>
|
|
107
102
|
</div>
|
|
108
103
|
|
|
109
104
|
<Link
|
|
110
105
|
href="/"
|
|
111
|
-
className="px-8 py-3 bg-[#2f8d46] text-white font-semibold rounded-full hover:bg-[#267339] transition-all shadow-md hover:shadow-lg active:scale-95"
|
|
106
|
+
className="w-full sm:w-auto px-8 py-3 bg-[#2f8d46] text-white font-semibold rounded-full hover:bg-[#267339] transition-all shadow-md hover:shadow-lg active:scale-95 text-center"
|
|
112
107
|
>
|
|
113
108
|
Back to Dashboard
|
|
114
109
|
</Link>
|
|
115
|
-
</
|
|
116
|
-
</
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
117
112
|
|
|
118
113
|
<div className="absolute bottom-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-[#2f8d46]/20 to-transparent" />
|
|
119
114
|
</div>
|
|
@@ -122,10 +117,9 @@ const GeeksforGeeks = ({ className }: { className?: string }) => {
|
|
|
122
117
|
|
|
123
118
|
const Card = ({ title, icon, color, description }: { title: string; icon: React.ReactNode; color: string; description: string }) => {
|
|
124
119
|
return (
|
|
125
|
-
<
|
|
126
|
-
whileHover={{ y: -8, scale: 1.02 }}
|
|
120
|
+
<div
|
|
127
121
|
className={cn(
|
|
128
|
-
"p-6 rounded-2xl border bg-white flex flex-col items-center text-center shadow-sm hover:shadow-xl transition-all duration-300 cursor-pointer group"
|
|
122
|
+
"p-6 rounded-2xl border bg-white flex flex-col items-center text-center shadow-sm hover:shadow-xl transition-all duration-300 cursor-pointer group hover:-translate-y-2 hover:scale-[1.02]"
|
|
129
123
|
)}
|
|
130
124
|
>
|
|
131
125
|
<div className={cn("p-4 rounded-xl mb-4 transition-colors group-hover:scale-110", color)}>
|
|
@@ -133,7 +127,7 @@ const Card = ({ title, icon, color, description }: { title: string; icon: React.
|
|
|
133
127
|
</div>
|
|
134
128
|
<h3 className="font-bold text-gray-900 mb-1">{title}</h3>
|
|
135
129
|
<p className="text-sm text-gray-500">{description}</p>
|
|
136
|
-
</
|
|
130
|
+
</div>
|
|
137
131
|
);
|
|
138
132
|
};
|
|
139
133
|
|