diff --git a/public/final-theme.mp3 b/public/final-theme.mp3
new file mode 100644
index 0000000..46d5e9d
Binary files /dev/null and b/public/final-theme.mp3 differ
diff --git a/src/components/GameBackground.tsx b/src/components/GameBackground.tsx
index 714d309..1c7979d 100644
--- a/src/components/GameBackground.tsx
+++ b/src/components/GameBackground.tsx
@@ -1,21 +1,16 @@
-import { useEffect, useState } from "react";
+import { useEffect } from "react";
+import { startBackgroundMusic } from "@/utils/audio";
interface GameBackgroundProps {
shouldStartAudio?: boolean;
}
export const GameBackground = ({ shouldStartAudio = false }: GameBackgroundProps) => {
- const [audioStarted, setAudioStarted] = useState(false);
-
useEffect(() => {
- if (shouldStartAudio && !audioStarted) {
- const audio = new Audio("/tension-background.mp3");
- audio.loop = true;
- audio.volume = 0.3;
- audio.play().catch(console.error);
- setAudioStarted(true);
+ if (shouldStartAudio) {
+ startBackgroundMusic();
}
- }, [shouldStartAudio, audioStarted]);
+ }, [shouldStartAudio]);
return (
diff --git a/src/components/MuteButton.tsx b/src/components/MuteButton.tsx
new file mode 100644
index 0000000..5a808e3
--- /dev/null
+++ b/src/components/MuteButton.tsx
@@ -0,0 +1,41 @@
+import React, { useState, useEffect } from 'react';
+import { SpeakerWaveIcon, SpeakerXMarkIcon } from '@heroicons/react/24/outline';
+import { Button } from '@/components/ui/button';
+import { getMuted, setMuted } from '@/utils/audio';
+import { useTranslation } from 'react-i18next';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+export const MuteButton: React.FC = () => {
+ const [isMuted, setIsMuted] = useState(getMuted());
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ setMuted(isMuted);
+ }, [isMuted]);
+
+ return (
+
+
+
+
+
+ {isMuted ? t('audio.unmute') : t('audio.mute')}
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/components/game/BriefingAudio.tsx b/src/components/game/BriefingAudio.tsx
index f40194c..3ab6564 100644
--- a/src/components/game/BriefingAudio.tsx
+++ b/src/components/game/BriefingAudio.tsx
@@ -1,9 +1,9 @@
-import { useEffect, useRef, useState } from 'react';
-import { Button } from '@/components/ui/button';
-import { Play, Pause } from 'lucide-react';
-import { cn } from '@/lib/utils';
-import { useTranslation } from 'react-i18next';
-import { playRecordingSound } from "@/utils/audio";
+import { useEffect, useState } from "react";
+import { Button } from "@/components/ui/button";
+import { PlayIcon, PauseIcon } from "@heroicons/react/24/outline";
+import { useTranslation } from "react-i18next";
+import { playBriefing } from "@/utils/audio";
+import { toast } from "@/components/ui/use-toast";
interface BriefingAudioProps {
stage: string;
@@ -11,47 +11,10 @@ interface BriefingAudioProps {
className?: string;
}
-export const BriefingAudio = ({ stage, audioRef, className }: BriefingAudioProps) => {
- const [isPlaying, setIsPlaying] = useState(false);
+export const BriefingAudio = ({ stage, audioRef, className = "" }: BriefingAudioProps) => {
const { t, i18n } = useTranslation();
-
- const handlePlayPause = () => {
- if (!audioRef.current) return;
-
- if (isPlaying) {
- audioRef.current.pause();
- } else {
- playRecordingSound();
- audioRef.current.play();
- }
- };
-
- useEffect(() => {
- if (!audioRef.current) return;
-
- const handleEnded = () => {
- setIsPlaying(false);
- };
-
- const handlePlay = () => {
- setIsPlaying(true);
- };
-
- const handlePause = () => {
- setIsPlaying(false);
- };
-
- audioRef.current.addEventListener('ended', handleEnded);
- audioRef.current.addEventListener('play', handlePlay);
- audioRef.current.addEventListener('pause', handlePause);
-
- return () => {
- if (!audioRef.current) return;
- audioRef.current.removeEventListener('ended', handleEnded);
- audioRef.current.removeEventListener('play', handlePlay);
- audioRef.current.removeEventListener('pause', handlePause);
- };
- }, [audioRef]);
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [currentAudio, setCurrentAudio] = useState
(null);
const getAudioFileName = (stage: string) => {
const currentLanguage = i18n.language;
@@ -68,27 +31,62 @@ export const BriefingAudio = ({ stage, audioRef, className }: BriefingAudioProps
return `${monthKey}-${currentLanguage}.mp3`;
};
- // Only skip rendering for INTRO stage
- if (stage === "INTRO") {
- return null;
- }
+ const handlePlayPause = async () => {
+ try {
+ if (isPlaying && currentAudio) {
+ currentAudio.pause();
+ setIsPlaying(false);
+ return;
+ }
+
+ if (currentAudio) {
+ currentAudio.play();
+ setIsPlaying(true);
+ return;
+ }
+
+ const audioPath = `/audio/briefings/${getAudioFileName(stage)}`;
+ console.log('Playing audio:', audioPath);
+
+ const newAudio = playBriefing(audioPath);
+ newAudio.addEventListener('ended', () => setIsPlaying(false));
+ newAudio.addEventListener('pause', () => setIsPlaying(false));
+ newAudio.addEventListener('play', () => setIsPlaying(true));
+ setCurrentAudio(newAudio);
+ setIsPlaying(true);
+ } catch (error) {
+ console.error('Audio error:', error);
+ toast({
+ title: "Audio Error",
+ description: `Failed to play briefing: ${error instanceof Error ? error.message : 'Unknown error'}`,
+ variant: "destructive",
+ });
+ }
+ };
+
+ useEffect(() => {
+ return () => {
+ if (currentAudio) {
+ currentAudio.pause();
+ }
+ };
+ }, [currentAudio]);
return (
-
-
-
-
+
);
};
\ No newline at end of file
diff --git a/src/components/game/ChoiceCard.tsx b/src/components/game/ChoiceCard.tsx
index 34ea5bb..4373809 100644
--- a/src/components/game/ChoiceCard.tsx
+++ b/src/components/game/ChoiceCard.tsx
@@ -4,7 +4,8 @@ import { Badge } from "@/components/ui/badge";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Choice } from './types';
import { ChoiceID } from './constants/metrics';
-import { ArrowTrendingUpIcon, ExclamationTriangleIcon, LockClosedIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
+import { ArrowTrendingUpIcon, ExclamationTriangleIcon, LockClosedIcon } from '@heroicons/react/24/outline';
+import { useTranslation } from 'react-i18next';
interface ChoiceCardProps {
choice: Choice;
@@ -21,6 +22,7 @@ export const ChoiceCard: React.FC = ({
disabled = false,
optionNumber
}) => {
+ const { t } = useTranslation();
const strengtheningChoices = choice.strengthenedBy?.filter(c => previousChoices.includes(c)) || [];
const weakeningChoices = choice.weakenedBy?.filter(c => previousChoices.includes(c)) || [];
@@ -34,47 +36,40 @@ export const ChoiceCard: React.FC = ({
duration-300
hover:scale-[1.02]
cursor-pointer
+ mt-8
${isStrengthened ? 'border-green-500 shadow-green-500/20 shadow-lg' : ''}
${isWeakened ? 'border-orange-500 shadow-orange-500/20' : ''}
${isLocked || disabled ? 'opacity-50 cursor-not-allowed' : ''}
bg-gray-800/50 hover:bg-gray-700/50
+ group
`;
- const getStatusMessage = () => {
- if (isStrengthened && isWeakened) {
- return "This choice is both enhanced and weakened by your previous choices";
- } else if (isStrengthened) {
- return "This choice is enhanced by your previous choices";
- } else if (isWeakened) {
- return "This choice is weakened by your previous choices";
- }
- return null;
- };
-
- const statusMessage = getStatusMessage();
-
return (
!isLocked && !disabled && onClick()}
>
-
-
-
+
+ {optionNumber}
+
+
+
+
+
{choice.text}
-
+
{isStrengthened && (
-
+
- Enhanced by previous strategy choice
+ {t('analysis.badges.enhanced')}
-
Enhanced by your choice:
+
{t('analysis.badges.enhancedBy')}
{strengtheningChoices.map(c => (
- {c}
@@ -88,14 +83,14 @@ export const ChoiceCard: React.FC = ({
{isWeakened && (
-
+
- Weakened by previous strategy choice
+ {t('analysis.badges.weakened')}
-
Weakened by your choice:
+
{t('analysis.badges.weakenedBy')}
{weakeningChoices.map(c => (
- {c}
@@ -107,25 +102,21 @@ export const ChoiceCard: React.FC = ({
)}
-
- {/*
- {choice.description}
- */}
+
+
+ {choice.impact}
+ {t('analysis.clickToSeeDetails')}
+
-
- {isLocked && (
-
-
+ {isLocked && (
+
+
+
Requires: {choice.requires?.join(', ')}
- )}
-
-
-
- {choice.impact}
-
-
+
+ )}
);
};
\ No newline at end of file
diff --git a/src/components/game/DevPanel.tsx b/src/components/game/DevPanel.tsx
new file mode 100644
index 0000000..e14f9ac
--- /dev/null
+++ b/src/components/game/DevPanel.tsx
@@ -0,0 +1,71 @@
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { MONTHS } from "./constants/gameStages";
+import { ChoiceID } from "./constants/metrics";
+import { useTranslation } from "react-i18next";
+
+interface DevPanelProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onJumpToMonth: (monthIndex: number) => void;
+ onRandomizeChoices: () => void;
+}
+
+export const DevPanel = ({ open, onOpenChange, onJumpToMonth, onRandomizeChoices }: DevPanelProps) => {
+ const { t } = useTranslation();
+
+ // Define the correct stage order
+ const stageOrder = [
+ 'JANUARY',
+ 'MARCH',
+ 'MAY',
+ 'ALERT',
+ 'JULY',
+ 'SEPTEMBER',
+ 'NOVEMBER',
+ 'DECEMBER',
+ 'EXPOSÉ'
+ ] as const;
+
+ // Create a mapping of stage indices to their actual positions in the game
+ const stageToIndex: { [key: string]: number } = {};
+ stageOrder.forEach((stage, index) => {
+ stageToIndex[stage] = index;
+ });
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/src/components/game/DossierPanel.tsx b/src/components/game/DossierPanel.tsx
index 1d43c58..f9f3340 100644
--- a/src/components/game/DossierPanel.tsx
+++ b/src/components/game/DossierPanel.tsx
@@ -46,7 +46,8 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {