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
840 changes: 767 additions & 73 deletions client/package-lock.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@
"lucide-react": "^0.503.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",

"react-howler": "^5.2.0"

"react-router-dom": "^7.5.2"

},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@tailwindcss/postcss": "^4.1.4",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",

"@types/react-howler": "^5.2.3",

"@types/react-router-dom": "^5.3.3",

"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21",
"eslint": "^9.21.0",
Expand Down
Binary file added client/public/audio/Dan_correct/Dan-5inarow.wav
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
34 changes: 34 additions & 0 deletions client/src/Utils/handleAnswer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@


// Picks a random Danism and matching Sound depending on correct/wrong
export function getRandomDanismAndSound(isCorrect: boolean): { text: string, sound: string } {
const correctDanisms = [
{ text: "🔥 Wow. You actually got that right. Weird.", sound: "/audio/Dan_correct/Dan-correct-1.wav" },
{ text: "🎯 Well look who remembered a concept. Congrats.", sound: "/audio/Dan_correct/Dan-correct-2.wav" },
{ text: "🚀 Miracles *do* happen. Mark the calendar.", sound: "/audio/Dan_correct/Dan-correct-3.wav" },
{ text: "🌟 I guess I *have* to give you a star now", sound: "/audio/Dan_correct/Dan-correctStar.wav" },
];

const incorrectDanisms = [
{ text: "💀 Sure, that's *a* choice. It's just not the right one.", sound: "/audio/Dan_incorrect/Dan-incorrect-1.wav" },
{ text: "😬 Oof. That was bold. Boldly incorrect.", sound: "/audio/Dan_incorrect/Dan-incorrect-2.wav" },
{ text: "🧠 Take a 7-minute break to recover from that disaster.", sound: "/audio/Dan_incorrect/Dan-incorrect-3.wav" },
{ text: "🛑 Fascinating. Bold. Still wrong.", sound: "/audio/Dan_incorrect/Dan-incorrect-4.wav" },
];

const source = isCorrect ? correctDanisms : incorrectDanisms;
const randomIndex = Math.floor(Math.random() * source.length);
return source[randomIndex];
}

// Picks a random Special Legendary Danism (only text)
export function getSpecialDanism(): string {
const specialQuotes = [
"🌟 Legendary Streak! Even Dr. Dan is impressed... briefly.",
"🌟 5 correct in a row?! You must be an AI in disguise.",
"🌟 Victory streak! Maybe you deserve a 7-minute break after all."
// 👉 Add more special quotes if you want!
];
const randomIndex = Math.floor(Math.random() * specialQuotes.length);
return specialQuotes[randomIndex];
}
7 changes: 7 additions & 0 deletions client/src/Utils/preloadSounds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function preloadSounds(paths: string[]): void {
paths.forEach((path) => {
const audio = new Audio(path);
audio.load();
});
}

Empty file.
114 changes: 114 additions & 0 deletions client/src/components/DanismEvent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useState, useEffect } from "react";
import NarrationModal from "@/components/NarrationModal";
import SoundPlayer from "@/components/SoundPlayer";
import { preloadSounds } from "../Utils/preloadSounds";
import { getRandomDanism, getSoundPath } from "../Utils/handleAnswer";

const DanismEvent = () => {
const [showModal, setShowModal] = useState(false);
const [audioSrc, setAudioSrc] = useState<string | null>(null);
const [fallbackAudio, setFallbackAudio] = useState<string | null>(null);
const [playing, setPlaying] = useState(false);
const [dialogText, setDialogText] = useState("");

// 🔥 Preload all audio files once
useEffect(() => {
preloadSounds([
'/audio/Dan_correct/Dan-correct-1.wav',
'/audio/Dan_correct/Dan-correct-2.wav',
'/audio/Dan_correct/Dan-correct-3.wav',
'/audio/Dan_correct/Dan-correct-4.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'
]);
}, []);

// 🔥 Fetch TTS voice from your API when modal opens
useEffect(() => {
if (showModal && dialogText) {
const fetchVoice = async () => {
try {
const res = await fetch("/api/tts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: dialogText,
character: "Dr. Dan", // Character name if your backend needs it
}),
});

if (!res.ok) throw new Error("TTS API failed");

const blob = await res.blob();
const url = URL.createObjectURL(blob);
setAudioSrc(url); // TTS Audio ready
setPlaying(true);
} catch (err) {
console.error("TTS Error:", err);
// fallback to local mp3 if TTS fails
setFallbackAudio("/audio/drdan_fallback.mp3");
setPlaying(true);
}
};

fetchVoice();
}
}, [showModal, dialogText]);

// 🔥 Handle when player answers something
const triggerDanism = (isCorrect: boolean) => {
setDialogText(getRandomDanism(isCorrect)); // Pick random quote
setAudioSrc(getSoundPath(isCorrect)); // Play random correct/wrong sound immediately
setFallbackAudio(null); // Reset fallback
setShowModal(true); // Open modal
setPlaying(true); // Start playing
};

return (
<>
{/* TEMP TEST BUTTONS */}
<div className="flex gap-4 justify-center my-4">
<button
onClick={() => triggerDanism(true)}
className="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded shadow"
>
Test Correct Answer
</button>
<button
onClick={() => triggerDanism(false)}
className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded shadow"
>
Test Wrong Answer
</button>
</div>

{/* 🧬 Narration Modal */}
<NarrationModal
isOpen={showModal}
onClose={() => setShowModal(false)}
text={dialogText}
/>

{/* 🎶 SoundPlayer */}
{audioSrc && (
<SoundPlayer
src={audioSrc}
playing={playing}
onEnd={() => setPlaying(false)}
/>
)}
{fallbackAudio && !audioSrc && (
<SoundPlayer
src={fallbackAudio}
playing={playing}
onEnd={() => setPlaying(false)}
/>
)}
</>
);
};

export default DanismEvent;
Empty file.
25 changes: 11 additions & 14 deletions client/src/components/NarrationModal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
// src/components/NarrationModal.tsx

"use client";

import ModalBase from "@/components/ModalBase";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";

interface NarrationModalProps {
isOpen: boolean;
Expand All @@ -12,16 +8,17 @@ interface NarrationModalProps {

const NarrationModal = ({ isOpen, onClose, text }: NarrationModalProps) => {
return (
<ModalBase
open={isOpen}
onOpenChange={(open: boolean) => { if (!open) onClose(); }}
title="Narration"
description={text}
>
{/* Optional: You can add more children here later if needed */}
</ModalBase>
<Dialog open={isOpen} onOpenChange={(open) => { if (!open) onClose(); }}>
<DialogContent className="rounded-2xl bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-100 shadow-2xl">
<DialogHeader>
<DialogTitle className="text-2xl font-bold text-center">{text}</DialogTitle>
</DialogHeader>
<DialogDescription className="text-center mt-4">
{/* Optional: you can add subtext, instructions, even a button here */}
</DialogDescription>
</DialogContent>
</Dialog>
);
};

export default NarrationModal;

23 changes: 0 additions & 23 deletions client/src/components/ResponseModal.tsx

This file was deleted.

22 changes: 22 additions & 0 deletions client/src/components/SoundPlayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// src/components/SoundPlayer.tsx
import * as React from 'react';
import ReactHowler from 'react-howler';

interface SoundPlayerProps {
src: string;
playing: boolean;
onEnd?: () => void;
}

const SoundPlayer: React.FC<SoundPlayerProps> = ({ src, playing, onEnd }) => {
return (
<ReactHowler
src={src}
playing={playing}
onEnd={onEnd}
volume={1.0}
/>
);
};

export default SoundPlayer;
7 changes: 7 additions & 0 deletions client/src/components/screens/GameMap.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@

// src/components/screens/GameMap.tsx

// IMPORT LIBRARIES
import * as React from 'react';

import React, { useState } from 'react';

import { useNavigate } from 'react-router-dom';
import Minion from './Minions';
import "../../styles/codezilla.css";
Expand Down
10 changes: 10 additions & 0 deletions client/src/graphql/mutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { gql } from '@apollo/client';

export const SUBMIT_ANSWER = gql`
mutation SubmitAnswer($questionId: ID!, $selectedOption: String!) {
submitAnswer(questionId: $questionId, selectedOption: $selectedOption) {
isCorrect
feedback
}
}
`;
13 changes: 13 additions & 0 deletions client/src/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { gql } from '@apollo/client';

export const GET_QUESTIONS = gql`
query GetQuestions {
questions {
_id
questionText
options
correctAnswer
}
}
`;

12 changes: 7 additions & 5 deletions client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import { ApolloProvider } from '@apollo/client';
import client from './Utils/apolloClient'; // or wherever your client file is

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

root.render(
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>,
);
Loading
Loading