Этот коммит содержится в:
Constantin Rusu 2025-03-09 22:57:22 +00:00
родитель 7693783b88
Коммит 60b95516ea
4 изменённых файлов: 196 добавлений и 54 удалений

Просмотреть файл

@ -13,7 +13,14 @@ interface ExpertMemoProps {
audioRef?: React.RefObject<HTMLAudioElement>;
}
export const ExpertMemo: React.FC<ExpertMemoProps> = ({ from, subject, children, isAlert = false, stage, audioRef }) => {
export const ExpertMemo: React.FC<ExpertMemoProps> = ({
from,
subject,
children,
isAlert = false,
stage,
audioRef
}) => {
const { t } = useTranslation();
const highlightColor = isAlert ? 'text-red-500' : 'text-yellow-500';
const memoClass = isAlert ? 'expert-memo alert' : 'expert-memo';

122
src/components/game/ProgressionIndicator.tsx Обычный файл
Просмотреть файл

@ -0,0 +1,122 @@
import React from 'react';
import { cn } from '@/lib/utils';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useTranslation } from 'react-i18next';
import { ChoiceID } from './constants/metrics';
import { MONTHS_CONFIG, getMonthConfig } from "@/utils/months";
interface ProgressionIndicatorProps {
currentStage: number;
totalStages: number;
previousChoices?: ChoiceID[];
className?: string;
}
export const ProgressionIndicator: React.FC<ProgressionIndicatorProps> = ({
currentStage,
totalStages,
previousChoices = [],
className
}) => {
const { t } = useTranslation();
// Get month name for the stage
const getMonthName = (stageIndex: number) => {
const monthConfig = getMonthConfig(stageIndex + 1);
if (monthConfig && monthConfig.key) {
return t(`months.${monthConfig.key}`);
}
return "";
};
// Get choice names for tooltips
const getChoiceName = (choiceId: ChoiceID, index: number) => {
// Try to get the choice name from the translation
try {
// Find which choice number it was (1 or 2)
const choiceNumber = choiceId.toString().includes('_1') ||
choiceId.toString().endsWith('_BOTS') ||
choiceId.toString().endsWith('_NEWS') ||
choiceId.toString().endsWith('_COLLABORATION') ||
choiceId.toString().endsWith('_COURSE') ||
choiceId.toString().endsWith('_PANEL') ||
choiceId.toString().endsWith('_PAPER') ||
choiceId.toString().endsWith('_PLATFORMS') ||
choiceId.toString().endsWith('_STRATEGY') ||
choiceId.toString().endsWith('_DEFENSE') ? 1 : 2;
return t(`stages.${index + 1}.choices.${choiceNumber}.text`);
} catch (e) {
// Fallback to the ID if translation fails
return choiceId.toString().replace(/_/g, ' ');
}
};
return (
<div className={cn("flex items-center justify-center w-full py-3", className)}>
<div className="flex items-center space-x-2 w-full max-w-3xl">
{Array.from({ length: totalStages }).map((_, index) => {
const isActive = index <= currentStage;
const isPast = index < currentStage;
const hasChoice = index < previousChoices.length;
// Only render tooltips for past and current stages
const DotComponent = isActive ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div
className={cn(
"w-3 h-3 rounded-full flex items-center justify-center transition-all duration-300",
isActive ? "bg-yellow-500" : "bg-gray-600",
"hover:scale-110 cursor-pointer"
)}
>
{hasChoice && (
<div className="w-1.5 h-1.5 rounded-full bg-black" />
)}
</div>
</TooltipTrigger>
<TooltipContent side="bottom" className="bg-black/90 border-gray-700 text-white text-xs">
{hasChoice ? (
<div className="text-center">
<div className="font-bold mb-1">{getMonthName(index)}</div>
<div>{getChoiceName(previousChoices[index], index)}</div>
</div>
) : (
<div className="text-center">
<div className="font-bold">{getMonthName(index)}</div>
</div>
)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
// For future stages, just render the dot without a tooltip
<div
className={cn(
"w-3 h-3 rounded-full flex items-center justify-center",
"bg-gray-600"
)}
/>
);
return (
<React.Fragment key={index}>
{index > 0 && (
<div
className={cn(
"h-[1px] flex-grow",
isPast ? "bg-yellow-500" : "bg-gray-600"
)}
/>
)}
{DotComponent}
</React.Fragment>
);
})}
</div>
</div>
);
};

Просмотреть файл

@ -18,6 +18,9 @@ export const useGameStages = (audioRef: React.RefObject<HTMLAudioElement>): Game
return t(`choices.option${number}`);
};
// Total number of stages
const totalStages = 9;
return [
{
id: 1,
@ -651,7 +654,6 @@ export const useGameStages = (audioRef: React.RefObject<HTMLAudioElement>): Game
description: <ExpertMemo
from={t('stages.9.expertMemo.from')}
subject={t('stages.9.expertMemo.subject')}
isAlert={true}
stage="9"
audioRef={audioRef}>
<p>{t('stages.9.expertMemo.content.greeting')}</p>

Просмотреть файл

@ -39,6 +39,7 @@ import { DevPanel } from "@/components/game/DevPanel";
import { motion } from "framer-motion";
import { MONTHS_CONFIG, getMonthConfig } from "@/utils/months";
import { toast } from "sonner";
import { ProgressionIndicator } from '@/components/game/ProgressionIndicator';
// Get valid month keys (skipping index 0)
const monthKeys = MONTHS_CONFIG.slice(1).map(config => config?.key).filter(Boolean) as string[];
@ -58,6 +59,7 @@ const STAGE_CHOICES = [
const Index = () => {
const { t } = useTranslation();
const audioRef = useRef<HTMLAudioElement | null>(null);
const [previousChoices, setPreviousChoices] = useState<ChoiceID[]>([]);
const stages = useGameStages(audioRef);
const operationNameKey = OPERATION_NAMES[Math.floor(Math.random() * OPERATION_NAMES.length)];
const operationName = t(`operations.${operationNameKey}`);
@ -78,7 +80,6 @@ const Index = () => {
const [showIntroDialog, setShowIntroDialog] = useState(true);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [selectedChoice, setSelectedChoice] = useState<GameStage["choices"][0] | null>(null);
const [previousChoices, setPreviousChoices] = useState<ChoiceID[]>([]);
const [gameComplete, setGameComplete] = useState(false);
const [playerChoices, setPlayerChoices] = useState<string[]>([]);
const [gameKey, setGameKey] = useState(0);
@ -434,7 +435,8 @@ const Index = () => {
return (
<div className="relative min-h-screen overflow-hidden">
<GameBackground shouldStartAudio={shouldStartAudio} />
<div className="relative min-h-screen bg-transparent p-4 flex items-center justify-center">
<div className="relative min-h-screen bg-transparent p-4 flex flex-col">
<div className="flex-grow flex items-center justify-center">
<Card className="relative border-gray-700 bg-black/30">
<div className="bg-gray-800/30 p-6 rounded-t-md border border-gray-700">
<h3 className="text-yellow-500 font-semibold mb-4">{t('analysis.metricsUpdate')}</h3>
@ -489,6 +491,7 @@ const Index = () => {
</Card>
</div>
</div>
</div>
);
}
@ -511,6 +514,14 @@ const Index = () => {
<div className="relative min-h-screen overflow-hidden">
<GameBackground shouldStartAudio={shouldStartAudio} />
<div className="relative min-h-screen bg-transparent md:p-4 flex flex-col">
<div className="w-full max-w-4xl mx-auto px-4 mt-4 mb-6">
<ProgressionIndicator
currentStage={currentStage}
totalStages={stages.length}
previousChoices={previousChoices}
/>
</div>
<div className="flex-grow flex items-center">
<div className="w-full h-full md:max-w-4xl mx-auto md:px-4">
<Card className="bg-black/50 text-white border-gray-700 transition-all duration-1000 animate-fade-in h-full md:h-auto md:rounded-lg border-0 md:border">