Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added client/minions/codezilla3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/minions/pie-thon3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2,282 changes: 1,940 additions & 342 deletions client/package-lock.json

Large diffs are not rendered by default.

Binary file added client/public/avatars/DrDanCorrect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/avatars/DrDanWrong.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/codezilla_intro.mp4
Binary file not shown.
14 changes: 8 additions & 6 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom';
import IntroPage from './components/screens/IntroPage';
import { Login } from './components/screens/Login';
import GameMap from './components/screens/GameMap';
import GameOver from './components/screens/GameOver';
import Victory from './components/screens/Victory';
import Signup from './components/screens/Signup';
import Questions from './components/screens/Questions';

import './styles/codezilla.css';


const LogoutButton: React.FC = () => {
const navigate = useNavigate();

const handleLogout = () => {
localStorage.clear();
navigate('/signup');
navigate('/login');
};

return (
Expand All @@ -28,14 +29,14 @@ const App: React.FC = () => {
return (
<Router>
<div className="app-wrapper">
{/* Background Image */}
{/* Background Image
<img
src="/background/codezilla_bkgd.png"
alt="Dark rainy cityscape"
className="background-image"
/>
/> */}

{/* Logo */}

<img
src="/codezilla_logo.png"
alt="Codezilla Logo"
Expand All @@ -47,7 +48,8 @@ const App: React.FC = () => {

{/* Routes */}
<Routes>
<Route path="/" element={<Login />} />
<Route path="/" element={<IntroPage />} />
<Route path="/login" element={<Login />} />
<Route path="/map" element={<GameMap />} />
<Route path="/gameover" element={<GameOver />} />
<Route path="/signup" element={<Signup />} />
Expand Down
13 changes: 13 additions & 0 deletions client/src/Utils/useBodyClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// src/Utils/useBodyClass.ts
import { useEffect } from 'react';

export const useBodyClass = (className: string) => {
useEffect(() => {
console.log(`✅applying body class: ${className}`);

document.body.classList.add(className);
return () => {
document.body.classList.remove(className);
};
}, [className]);
};
63 changes: 30 additions & 33 deletions client/src/components/AnswerResultModal.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,56 @@
import { useEffect, useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '../components/ui/dialog';
import SoundPlayer from './SoundPlayer';
import { useEffect, useState } from "react";
import SoundPlayer from "./SoundPlayer";
import ReactDOM from "react-dom";

interface AnswerResultModalProps {
isOpen: boolean;
onClose: () => void;
isCorrect: boolean;
drDanQuote: string;
audioUrl?: string;
}

const AnswerResultModal = ({
isOpen,
onClose,
isCorrect,
drDanQuote,
audioUrl,
}: AnswerResultModalProps) => {
const [playAudio, setPlayAudio] = useState(false);

// ✅ Start or stop audio based on modal open state
useEffect(() => {
if (isOpen) {
setPlayAudio(true);
} else {
setPlayAudio(false);
}
}, [isOpen]);

return (
<Dialog open={isOpen} onOpenChange={(open) => { if (!open) onClose(); }}>
<DialogContent className="rounded-2xl shadow-xl text-center">
<DialogHeader>
<DialogTitle className={`text-2xl font-bold ${isCorrect ? 'text-green-500' : 'text-red-500'}`}>
{isCorrect ? "Correct!" : "Wrong!"}
</DialogTitle>
<DialogDescription className="text-lg mt-2">
{drDanQuote}
</DialogDescription>
</DialogHeader>

{/* ✅ Use Howler to play full clip, then close modal */}
{audioUrl && (
<SoundPlayer
src={audioUrl}
playing={playAudio}
onEnd={() => {
setPlayAudio(false);
onClose();
}}
/>
)}
</DialogContent>
</Dialog>
if (!isOpen) return null;

const drDanImage = isCorrect
? "/avatars/DrDanCorrect.png"
: "/avatars/DrDanWrong.png";

const modalContent = (
<div className="fixed top-1/2 right-4 transform -translate-y-1/2 z-[9999] w-[200px] flex flex-col items-center gap-2 animate-fadeIn pointer-events-none">
<img
src={drDanImage}
alt="Dr. Dan"
className="w-[160px] h-auto object-contain"
/>
{audioUrl && (
<SoundPlayer
src={audioUrl}
playing={playAudio}
onEnd={() => {
setPlayAudio(false);
onClose();
}}
/>
)}
</div>
);

return ReactDOM.createPortal(modalContent, document.body);
};

export default AnswerResultModal;
//commiting"
Comment on lines 55 to +56
Copy link

Copilot AI May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a stray comment at the end of the file that appears to be unintentional. It is recommended to remove this comment to clean up the code.

Suggested change
export default AnswerResultModal;
//commiting"
export default AnswerResultModal;

Copilot uses AI. Check for mistakes.
3 changes: 2 additions & 1 deletion client/src/components/DanismEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ const DanismEvent = () => {
'/audio/Dan_correct/Dan-correct-2.wav',
'/audio/Dan_correct/Dan-correct-3.wav',
'/audio/Dan_correct/Dan-correct-4.wav',
'/audio/Dan_correct/Dan-5inarow.wav',
'/audio/Dan_incorrect/Dan-incorrect-1.wav',
'/audio/Dan_incorrect/Dan-incorrect-2.wav',
'/audio/Dan_incorrect/Dan-incorrect-3.wav',
'/audio/Dan_incorrect/Dan-incorrect-4.wav',
'/audio/drdan_fallback.mp3'
'/audio/Dan_incorrect/Dan-incorrect-5.wav'
]);
}, []);

Expand Down
27 changes: 9 additions & 18 deletions client/src/components/screens/GameMap.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// src/components/screens/GameMap.tsx

// IMPORT LIBRARIES
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import Minion from './Minions';
import { useBodyClass } from '../../Utils/useBodyClass';
import { preloadSounds } from '../../utils/preloadSounds';
import '../../styles/codezilla.css';
import "../../styles/codezilla.css";
import drDanImg from '../../../avatars/drdan2.png';
import flameImg from '../../assets/flame.png';

Expand All @@ -14,9 +12,11 @@ const username = localStorage.getItem('username') || 'Player';

const GameMap: React.FC = () => {
const navigate = useNavigate();
useBodyClass('login-background');

const [completedPaths, setCompletedPaths] = useState<string[]>([]);
const [selectedMinionId, setSelectedMinionId] = useState<string | null>(null);

// ✅ Preload Dr. Dan audio files on mount
useEffect(() => {
preloadSounds([
'/audio/Dan_correct/Dan-correct-1.wav',
Expand All @@ -31,7 +31,6 @@ const GameMap: React.FC = () => {
'/audio/5inarow.wav'
]);
}, []);
const [selectedMinionId, setSelectedMinionId] = useState<string | null>(null);

const minions = [
{
Expand Down Expand Up @@ -105,15 +104,12 @@ const GameMap: React.FC = () => {

const goToQuestion = (questionId: string) => {
const currentIndex = minions.findIndex(m => m.questionId === questionId);

if (currentIndex < nodes.length - 1) {
const pathId = `${nodes[currentIndex].id}-${nodes[currentIndex + 1].id}`;
setCompletedPaths(prev => [...prev, pathId]);
}
const minion = minions.find(m => m.questionId === questionId);
if (minion) {
setSelectedMinionId(minion.id);
}
if (minion) setSelectedMinionId(minion.id);
navigate(`/question/${questionId}`);
};

Expand All @@ -123,7 +119,6 @@ const GameMap: React.FC = () => {
{nodes.map((node, index) => {
const nextNode = nodes[index + 1];
if (!nextNode) return null;

return (
<line
key={`line-${node.id}-${nextNode.id}`}
Expand All @@ -136,10 +131,8 @@ const GameMap: React.FC = () => {
);
})}

{/* GLOWING OUTER CIRCLE + DARKER INNER CIRCLE */}
{nodes.map((node, index) => {
const isCompleted = index < completedPaths.length;

return (
<g key={`node-${node.id}`}>
<circle
Expand All @@ -158,6 +151,7 @@ const GameMap: React.FC = () => {
);
})}
</svg>

<div className="dr-dan-wrapper">
<div className="glow-circle"></div>
<img src={drDanImg} alt="Dr. Dan" className="dr-dan-image" />
Expand All @@ -167,22 +161,19 @@ const GameMap: React.FC = () => {
<div className="player-avatar-wrapper">
<div className="player-glow-circle"></div>
<img src={selectedAvatar} alt="Your Avatar" className="player-avatar" />

<div className="player-info-wrapper">
<div className="player-info-box">
<p className="player-name">{username}</p>
<p className="player-level">Level 1</p>
</div>

<div className="learning-info-box">
<p className='learning-title'> Studying</p>
<p className='learning-title'>Studying</p>
<p className="learning-text">Coding Bootcamp</p>
</div>
{/* Lifeline Box */}
<div className='flames'>
<div className="lifeline-info-box">
<div className="lifeline-flames">
<p className='learning-title'> Life Lines</p>
<p className='learning-title'>Life Lines</p>
<img src={flameImg} alt="Flame 1" />
<img src={flameImg} alt="Flame 2" />
<img src={flameImg} alt="Flame 3" />
Expand Down
5 changes: 3 additions & 2 deletions client/src/components/screens/GameOver.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useNavigate } from 'react-router-dom';
import "../../styles/codezilla.css";


const GameOverPage = ({
backgroundUrl = 'client/background/codezilla_bkgd.png',
// backgroundUrl = 'client/background/codezilla_bkgd.png',
avatarUrl = 'client/avatars/avatar4.png',
codezillaUrl = 'client/minions/codezilla2.png',
}) => {
Expand All @@ -21,7 +22,7 @@ const GameOverPage = ({
return (
<div
className="game-over-page"
style={{ backgroundImage: `url(${backgroundUrl})` }}
// style={{ backgroundImage: `url(${backgroundUrl})` }}
>
<div className="game-over-container">
<h1 className="game-over-title">Game Over!</h1>
Expand Down
46 changes: 46 additions & 0 deletions client/src/components/screens/IntroPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { useBodyClass } from '../../Utils/useBodyClass';
import "../../styles/codezilla.css";

const IntroPage: React.FC = () => {
const navigate = useNavigate();
const videoRef = useRef<HTMLVideoElement | null>(null);
const [hasClicked, setHasClicked] = useState(false);
useBodyClass('intro-background');

const handleVideoEnd = () => {
navigate('/login');
};

const handleUserClick = () => {
if (videoRef.current && !hasClicked) {
videoRef.current.muted = false;
videoRef.current.play();
setHasClicked(true);
}
};

useEffect(() => {
document.addEventListener('click', handleUserClick);
return () => document.removeEventListener('click', handleUserClick);
}, [hasClicked]);

return (
<div className="intro-overlay">
<video
ref={videoRef}
className="intro-video"
autoPlay
muted
playsInline
onEnded={handleVideoEnd}
>
<source src="/codezilla_intro.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
};

export default IntroPage;
Loading