diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx index 42ce147..3cc25e1 100644 --- a/src/components/LanguageSwitcher.tsx +++ b/src/components/LanguageSwitcher.tsx @@ -1,13 +1,23 @@ import { useTranslation } from 'react-i18next'; import { Button } from './ui/button'; import { Languages } from 'lucide-react'; +import { useEffect } from 'react'; export const LanguageSwitcher = () => { const { i18n } = useTranslation(); + useEffect(() => { + // Ensure the language is loaded from localStorage on mount + const savedLang = localStorage.getItem('i18nextLng'); + if (savedLang && savedLang !== i18n.language) { + i18n.changeLanguage(savedLang); + } + }, [i18n]); + const toggleLanguage = () => { const newLang = i18n.language === 'en' ? 'ro' : 'en'; i18n.changeLanguage(newLang); + localStorage.setItem('i18nextLng', newLang); }; return ( diff --git a/src/components/game/DevPanel.tsx b/src/components/game/DevPanel.tsx index e14f9ac..b2ee57a 100644 --- a/src/components/game/DevPanel.tsx +++ b/src/components/game/DevPanel.tsx @@ -1,8 +1,5 @@ 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 { @@ -34,6 +31,11 @@ export const DevPanel = ({ open, onOpenChange, onJumpToMonth, onRandomizeChoices stageToIndex[stage] = index; }); + const handleStageJump = (stage: string) => { + onRandomizeChoices(); + onJumpToMonth(stageToIndex[stage]); + }; + return ( @@ -44,26 +46,18 @@ export const DevPanel = ({ open, onOpenChange, onJumpToMonth, onRandomizeChoices
- +
+ {stageOrder.map((stage) => ( + + ))} +
- -
diff --git a/src/components/game/EndGameDialog.tsx b/src/components/game/EndGameDialog.tsx index 29581d0..004ec04 100644 --- a/src/components/game/EndGameDialog.tsx +++ b/src/components/game/EndGameDialog.tsx @@ -1,134 +1,100 @@ -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogPortal, - DialogOverlay -} from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; -import { switchToFinalMusic } from "@/utils/audio"; -import { cn } from "@/lib/utils"; interface EndGameDialogProps { onContinue: () => void; - startFade: boolean; } -export const EndGameDialog = ({ onContinue, startFade }: EndGameDialogProps) => { - const [open, setOpen] = useState(true); - const { t } = useTranslation(); - const [step, setStep] = useState(0); +export const EndGameDialog = ({ onContinue }: EndGameDialogProps) => { + const { t, i18n } = useTranslation(); + const [visibleMessages, setVisibleMessages] = useState([]); const [showButton, setShowButton] = useState(false); useEffect(() => { - // Start final music when dialog appears, with a slight delay to match the fade-in - const timer = setTimeout(() => { - switchToFinalMusic(); - }, 800); // Match the dialog's fade-in duration - - return () => clearTimeout(timer); - }, []); - - const messages = [ - t('endGame.message1'), - t('endGame.message2'), - t('endGame.message3') - ]; - - useEffect(() => { - const messageDelay = 4000; // 4 seconds per message - const showButtonDelay = 2000; // 1.5 seconds after last message - - let timer: NodeJS.Timeout; - - if (step < messages.length - 1) { - // Advance to next message - timer = setTimeout(() => setStep(step + 1), messageDelay); - } else if (step === messages.length - 1 && !showButton) { - // Show button after last message - timer = setTimeout(() => setShowButton(true), showButtonDelay); + // Ensure correct language is set + const savedLang = localStorage.getItem('i18nextLng'); + if (savedLang && savedLang !== i18n.language) { + i18n.changeLanguage(savedLang); } - return () => clearTimeout(timer); - }, [step, messages.length, showButton]); + const showMessage = (index: number) => { + setVisibleMessages(prev => [...prev, index]); + }; - const handleViewReport = () => { - setOpen(false); - setTimeout(() => { - onContinue(); - }, 500); - }; + // Timing adjusted to match final theme bass hits + const messageDelay = 2600; // 3.8 seconds between messages + const buttonDelay = 1000; // 3.8 seconds after last message + + // Show messages one by one + [0, 1, 2].forEach((index) => { + setTimeout(() => showMessage(index), messageDelay * index); + }); + + // Show button after all messages + setTimeout(() => setShowButton(true), messageDelay * 3 + buttonDelay); + }, [i18n]); return ( - - - - - - - - {t('endGame.title')} - - -
- - -

- {messages[step]} -

-
-
-
-
+
+
+ + {visibleMessages.includes(0) && ( + + {t('endGame.message1')} + + )} + - - {showButton && ( - - - - )} - + + {visibleMessages.includes(1) && ( + + {t('endGame.message2')} + + )} + -
-
-
- - - -
+ + {visibleMessages.includes(2) && ( + + {t('endGame.message3')} + + )} + + + + {showButton && ( + + + + )} + + + ); -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/components/game/ExpertMemo.css b/src/components/game/ExpertMemo.css index bf63828..bf07782 100644 --- a/src/components/game/ExpertMemo.css +++ b/src/components/game/ExpertMemo.css @@ -21,7 +21,7 @@ inset 0 0 60px rgba(0, 0, 0, 0.6); width: 100%; max-width: 1200px; - max-height: 80vh-12rem; + max-height: none; margin: 0 auto; position: relative; color: #e8e8e8; @@ -95,30 +95,17 @@ white-space: pre-wrap; line-height: 1.5; text-shadow: 0 0 1px rgba(255, 255, 255, 0.1); - overflow-y: auto; + overflow-y: visible; + max-height: none; flex: 1; padding-right: 0.5rem; position: relative; -webkit-overflow-scrolling: touch; - max-height: calc(80vh - 12rem); } /* Gradient container */ .memo-gradient { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 60px; - background: linear-gradient(to top, #1a1715 10%, rgba(26, 23, 21, 0.8) 40%, transparent 100%); - pointer-events: none; - opacity: 0; - transition: opacity 0.3s ease; - z-index: 10; -} - -.memo-gradient.show { - opacity: 0.95; + display: none; } /* Custom scrollbar for WebKit browsers */ diff --git a/src/components/game/ExpertMemo.tsx b/src/components/game/ExpertMemo.tsx index f7245d4..c6475e8 100644 --- a/src/components/game/ExpertMemo.tsx +++ b/src/components/game/ExpertMemo.tsx @@ -53,12 +53,16 @@ export const ExpertMemo: React.FC = ({ // Function to wrap text content in paragraph tags const formatContent = (content: React.ReactNode) => { if (typeof content === 'string') { - // Split by double newlines to separate paragraphs - return content.split('\n\n').map((paragraph, index) => ( -

{paragraph}

- )); + // Split by double newlines to separate paragraphs and wrap in a div + return ( +
+ {content.split('\n\n').map((paragraph, index) => ( +
{paragraph}
+ ))} +
+ ); } - // If it's already a React node (like a div), return it as is + // If it's already a React node, wrap it in a div with prose styling return
{content}
; }; diff --git a/src/components/game/FinalMemo.tsx b/src/components/game/FinalMemo.tsx deleted file mode 100644 index fd8aca1..0000000 --- a/src/components/game/FinalMemo.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; -import { Shield, Star, Target, TrendingUp, Award, RotateCcw, Download, Share2 } from "lucide-react"; -import { generateFinalReport } from "./constants"; -import { MetricsDisplay } from "./MetricsDisplay"; -import html2canvas from 'html2canvas'; -import "./FinalMemo.css"; -import { useTranslation } from "react-i18next"; -import { ChoiceID } from './constants/metrics'; -import { motion } from "framer-motion"; -import { Button } from "@/components/ui/button"; -import { EndGameDialog } from "./EndGameDialog"; -import { useState, useEffect } from "react"; - -interface FinalMemoProps { - choices: string[]; - onRestart?: () => void; - agentNumber: string; -} - -export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) => { - const finalReport = generateFinalReport(choices as ChoiceID[]); - const { t } = useTranslation(); - const [showReport, setShowReport] = useState(false); - const [showDialog, setShowDialog] = useState(false); - - useEffect(() => { - // Add a small delay before showing the dialog to ensure fade is complete - const timer = setTimeout(() => { - setShowDialog(true); - }, 500); - return () => clearTimeout(timer); - }, []); - - const handleDownload = async () => { - const reportElement = document.querySelector('.final-memo'); - if (!reportElement) return; - - try { - const canvas = await html2canvas(reportElement as HTMLElement, { - backgroundColor: '#000000', - scale: 2, // Higher quality - logging: false, - }); - - // Create download link - const link = document.createElement('a'); - link.download = t('finalReport.ui.downloadFileName'); - link.href = canvas.toDataURL('image/png'); - link.click(); - } catch (error) { - console.error('Error generating report image:', error); - } - }; - - const handleShare = async () => { - const reportElement = document.querySelector('.final-memo'); - if (!reportElement) return; - - try { - const canvas = await html2canvas(reportElement as HTMLElement, { - backgroundColor: '#000000', - scale: 2, - logging: false, - }); - - const blob = await new Promise((resolve) => { - canvas.toBlob((blob) => resolve(blob!), 'image/png'); - }); - - if (navigator.share) { - const file = new File([blob], 'disinformation-quest-report.png', { type: 'image/png' }); - const shareData = { - title: t('share.title'), - text: `${t('share.text')}\n\n${t('share.metrics')}\nVirality: ${finalReport.metrics.virality}x\nReach: ${finalReport.metrics.reach}%\nLoyalists: ${finalReport.metrics.loyalists}%\n\n${t('share.playNow')}`, - files: [file], - url: window.location.href - }; - - try { - await navigator.share(shareData); - } catch (err) { - // Fallback if sharing with both text and file fails, try without URL - delete shareData.url; - await navigator.share(shareData); - } - } else { - // Fallback for browsers that don't support Web Share API - const url = URL.createObjectURL(blob); - window.open(url, '_blank'); - URL.revokeObjectURL(url); - } - } catch (error) { - console.error('Error sharing report:', error); - } - }; - - return ( - <> - {showDialog && ( - setShowReport(true)} startFade={false} /> - )} - - - -
-
- - {t('finalReport.ui.topSecret')} -
-
- - {t('finalReport.ui.agentReport')} {agentNumber} {t('finalReport.ui.missionReport')} - - -
-
- - - {finalReport.reward.title} - - - - {t('finalReport.ui.strategicAnalysis')} - -
- - - - -
-

- - {t('finalReport.ui.missionOverview')} -

-
-

- {finalReport.reward.description} -

-
-

{t('finalReport.ui.keyAchievements')}

-
    - {finalReport.keyAchievements.map((achievement, index) => ( -
  • - {achievement} -
  • - ))} -
-
-
-
- -
-

- - {t('finalReport.ui.impactAnalysis')} -

-
-
-

{t('finalReport.ui.strategicAssessment')}

-

- {finalReport.strategicAssessment} -

-
-
-

{t('finalReport.ui.futureImplications')}

-

- {finalReport.futureImplications} -

-
-
-
- -
-

- - {t('finalReport.ui.operationalOutcomes')} -

-
-
    - {finalReport.reward.implications.map((implication, index) => ( -
  • - {implication} -
  • - ))} -
-
-
- -
- - - -
-
-
-
- - ); -}; \ No newline at end of file diff --git a/src/components/game/FinalMemo.css b/src/components/game/FinalReport.css similarity index 100% rename from src/components/game/FinalMemo.css rename to src/components/game/FinalReport.css diff --git a/src/components/game/FinalReport.tsx b/src/components/game/FinalReport.tsx new file mode 100644 index 0000000..ae8deee --- /dev/null +++ b/src/components/game/FinalReport.tsx @@ -0,0 +1,322 @@ +import { useTranslation } from "react-i18next"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Shield, Star, Target, TrendingUp, Award, RotateCcw, Download, Share2 } from "lucide-react"; +import { ChoiceID } from "./constants"; +import "./FinalReport.css"; +import html2canvas from "html2canvas"; +import { MetricsDisplay } from "./MetricsDisplay"; +import { generateFinalReport } from "./constants"; +import { motion, Variants } from "framer-motion"; + +// Animation variants for the populist ending +const populistAnimationVariants: Variants = { + initial: { scale: 0, opacity: 0 }, + animate: { + scale: 1, + opacity: 0.15, + transition: { + duration: 1, + repeat: Infinity, + repeatType: "reverse" as const + } + } +}; + +// Animation variants for the academic ending +const academicAnimationVariants: Variants = { + initial: { pathLength: 0, opacity: 0 }, + animate: { + pathLength: 1, + opacity: 0.15, + transition: { + duration: 2, + repeat: Infinity, + repeatType: "loop" as const, + ease: "linear" as const + } + } +}; + +interface FinalReportProps { + choices: ChoiceID[]; + onRestart: () => void; + agentNumber: string; +} + +export const FinalReport = ({ choices, onRestart, agentNumber }: FinalReportProps) => { + const { t } = useTranslation(); + const finalReport = generateFinalReport(choices); + const isPopulist = finalReport.summary === t('finalReport.summary.populist'); + + const handleDownload = async () => { + const reportElement = document.querySelector('.final-report'); + if (!reportElement) return; + + try { + const canvas = await html2canvas(reportElement as HTMLElement, { + backgroundColor: '#000000', + scale: 2, // Higher quality + logging: false, + }); + + // Create download link + const link = document.createElement('a'); + link.download = t('finalReport.ui.downloadFileName'); + link.href = canvas.toDataURL('image/png'); + link.click(); + } catch (error) { + console.error('Error generating report image:', error); + } + }; + + const handleShare = async () => { + const reportElement = document.querySelector('.final-report'); + if (!reportElement) return; + + try { + const canvas = await html2canvas(reportElement as HTMLElement, { + backgroundColor: '#000000', + scale: 2, + logging: false, + }); + + const blob = await new Promise((resolve) => { + canvas.toBlob((blob) => resolve(blob!), 'image/png'); + }); + + if (navigator.share) { + const file = new File([blob], 'disinformation-quest-report.png', { type: 'image/png' }); + const shareData = { + title: t('share.title'), + text: `${t('share.text')}\n\n${t('share.metrics')}\nVirality: ${finalReport.metrics.virality}x\nReach: ${finalReport.metrics.reach}%\nLoyalists: ${finalReport.metrics.loyalists}%\n\n${t('share.playNow')}`, + files: [file], + url: window.location.href + }; + + try { + await navigator.share(shareData); + } catch (err) { + // Fallback if sharing with both text and file fails, try without URL + delete shareData.url; + await navigator.share(shareData); + } + } else { + // Fallback for browsers that don't support Web Share API + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + URL.revokeObjectURL(url); + } + } catch (error) { + console.error('Error sharing report:', error); + } + }; + + return ( +
+ + {/* Background Animation */} +
+ {isPopulist ? ( + // Populist animation - spreading circles representing viral spread + <> + {[...Array(3)].map((_, i) => ( + + ))} + + ) : ( + // Academic animation - mathematical symbols and formulas + + + + + + )} +
+ + +
+
+ + {t('finalReport.ui.topSecret')} +
+
+ + {t('finalReport.ui.agentReport')} {agentNumber} {t('finalReport.ui.missionReport')} + + +
+
+ + + {finalReport.reward.title} + + + + {t('finalReport.ui.strategicAnalysis')} + +
+ + + {/* Overview Section */} +
+

+ + {t('finalReport.ui.supervisorMessage')} +

+

+ {t('finalReport.ui.congratulations')} {t( + finalReport.summary === t('finalReport.summary.populist') + ? 'finalReport.ui.overviewPopulist' + : 'finalReport.ui.overviewAcademic', + { + virality: finalReport.metrics.virality.toFixed(1), + reach: Math.round(finalReport.metrics.reach), + loyalists: Math.round(finalReport.metrics.loyalists), + interpolation: { escapeValue: false } + } + )} +

+
+ + + +
+

+ + {t('finalReport.ui.missionOverview')} +

+
+

+ {finalReport.reward.description} +

+
+

{t('finalReport.ui.keyAchievements')}

+
    + {finalReport.keyAchievements.map((achievement, index) => ( + + {achievement} + + ))} +
+
+
+
+ +
+

+ + {t('finalReport.ui.impactAnalysis')} +

+
+
+

{t('finalReport.ui.strategicAssessment')}

+

+ {finalReport.strategicAssessment} +

+
+
+

{t('finalReport.ui.futureImplications')}

+

+ {finalReport.futureImplications} +

+
+
+
+ +
+

+ + {t('finalReport.ui.operationalOutcomes')} +

+
+
    + {finalReport.reward.implications.map((implication, index) => ( +
  • + {implication} +
  • + ))} +
+
+
+ +
+ + + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/game/ProgressionIndicator.tsx b/src/components/game/ProgressionIndicator.tsx index 6a5c288..11bd4bc 100644 --- a/src/components/game/ProgressionIndicator.tsx +++ b/src/components/game/ProgressionIndicator.tsx @@ -59,6 +59,8 @@ export const ProgressionIndicator: React.FC = ({ const isActive = index <= currentStage; const isPast = index < currentStage; const hasChoice = index < previousChoices.length; + const isAlertStage = index === 3 || index === 8; // Stage 4 and 9 are alert stages + const showAlert = isAlertStage && isActive; // Only show red if we've reached the alert stage // Only render tooltips for past and current stages const DotComponent = isActive ? ( @@ -68,7 +70,7 @@ export const ProgressionIndicator: React.FC = ({
@@ -96,7 +98,7 @@ export const ProgressionIndicator: React.FC = ({
); @@ -107,7 +109,9 @@ export const ProgressionIndicator: React.FC = ({
)} @@ -119,4 +123,4 @@ export const ProgressionIndicator: React.FC = ({
); -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/components/game/constants/finalReport.ts b/src/components/game/constants/finalReport.ts index 4993c7f..108dba0 100644 --- a/src/components/game/constants/finalReport.ts +++ b/src/components/game/constants/finalReport.ts @@ -57,34 +57,47 @@ const analyzeStrategyPattern = (choices: ChoiceID[]): 'populist' | 'academic' => const generateAchievements = (metrics: FinalReportMetrics, choices: ChoiceID[], t: any): string[] => { const achievements: string[] = []; - if (metrics.virality > 2.0) { + // Primary achievements based on metrics + if (metrics.virality > 5.0) { achievements.push(t('finalReport.achievements.viral')); } - if (metrics.reach > 40) { + if (metrics.reach > 60) { achievements.push(t('finalReport.achievements.mainstream')); } - if (metrics.loyalists > 30) { + if (metrics.loyalists > 35) { achievements.push(t('finalReport.achievements.supporters')); } - if (choices.includes(ChoiceID.CONSPIRACY_DOCUMENTARY)) { + // Strategy-specific achievements + if (choices.includes(ChoiceID.CONSPIRACY_DOCUMENTARY) || choices.includes(ChoiceID.RESEARCH_PAPER)) { achievements.push(t('finalReport.achievements.historical')); } - if (choices.includes(ChoiceID.INFILTRATE_COMMUNITIES)) { + if (choices.includes(ChoiceID.INFILTRATE_COMMUNITIES) || choices.includes(ChoiceID.GRASSROOTS_MOVEMENT)) { achievements.push(t('finalReport.achievements.grassroots')); } - if (choices.includes(ChoiceID.RESEARCH_PAPER)) { + if (choices.includes(ChoiceID.EXPERT_PANEL) || choices.includes(ChoiceID.ACADEMIC_OUTREACH)) { achievements.push(t('finalReport.achievements.academic')); } - // Add more generic achievements if needed + // Add generic achievements if needed, prioritizing the most relevant ones while (achievements.length < 4) { - achievements.push( - t('finalReport.achievements.generic.momentum'), - t('finalReport.achievements.generic.network'), - t('finalReport.achievements.generic.ecosystem'), - t('finalReport.achievements.generic.engagement') - ); + if (!achievements.includes(t('finalReport.achievements.generic.momentum'))) { + achievements.push(t('finalReport.achievements.generic.momentum')); + continue; + } + if (!achievements.includes(t('finalReport.achievements.generic.network')) && metrics.reach > 40) { + achievements.push(t('finalReport.achievements.generic.network')); + continue; + } + if (!achievements.includes(t('finalReport.achievements.generic.ecosystem')) && metrics.virality > 3.0) { + achievements.push(t('finalReport.achievements.generic.ecosystem')); + continue; + } + if (!achievements.includes(t('finalReport.achievements.generic.engagement'))) { + achievements.push(t('finalReport.achievements.generic.engagement')); + continue; + } + break; } return achievements.slice(0, 4); // Return top 4 achievements @@ -93,13 +106,19 @@ const generateAchievements = (metrics: FinalReportMetrics, choices: ChoiceID[], // Generate ending content based on strategy pattern and metrics const generateEnding = (pattern: 'populist' | 'academic', metrics: FinalReportMetrics, t: any) => { if (pattern === 'populist') { - const politician = metrics.reach > 50 ? "Senator James Morrison" : "State Representative Sarah Chen"; + const politician = metrics.reach > 60 + ? t('finalReport.ending.populist.politician.national') + : t('finalReport.ending.populist.politician.local'); const supporters = Math.round(metrics.reach * 100); const percentage = Math.round(metrics.loyalists); return { title: t('finalReport.ending.populist.title'), - description: t('finalReport.ending.populist.description', { supporters, politician }), + description: t('finalReport.ending.populist.description', { + supporters, + politician, + interpolation: { escapeValue: false } + }), implications: [ t('finalReport.ending.populist.implications.legitimacy'), t('finalReport.ending.populist.implications.policy'), @@ -109,10 +128,21 @@ const generateEnding = (pattern: 'populist' | 'academic', metrics: FinalReportMe }; } else { const downloads = Math.round(metrics.virality * 10000); + const journal = metrics.reach > 50 + ? t('finalReport.ending.academic.journals.prestigious') + : t('finalReport.ending.academic.journals.alternative'); + const institution = metrics.reach > 50 + ? t('finalReport.ending.academic.institutions.top') + : t('finalReport.ending.academic.institutions.secondary'); return { title: t('finalReport.ending.academic.title'), - description: t('finalReport.ending.academic.description', { downloads }), + description: t('finalReport.ending.academic.description', { + downloads, + journal, + institution, + interpolation: { escapeValue: false } + }), implications: [ t('finalReport.ending.academic.implications.foundation'), t('finalReport.ending.academic.implications.framework'), diff --git a/src/components/game/constants/gameStages.tsx b/src/components/game/constants/gameStages.tsx index 5d2ae8f..e05449a 100644 --- a/src/components/game/constants/gameStages.tsx +++ b/src/components/game/constants/gameStages.tsx @@ -655,6 +655,7 @@ export const useGameStages = (audioRef: React.RefObject): Game from={t('stages.9.expertMemo.from')} subject={t('stages.9.expertMemo.subject')} stage="9" + isAlert={true} audioRef={audioRef}>

{t('stages.9.expertMemo.content.greeting')}

diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 90a189e..8f0a978 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -11,18 +11,34 @@ const COUNTRY_TO_LANGUAGE: { [key: string]: string } = { MD: 'ro', // Moldova also uses Romanian }; +// Get initial language from localStorage or default to 'en' +const getInitialLanguage = () => { + const savedLang = localStorage.getItem('i18nextLng'); + return savedLang || 'en'; +}; + // Custom language detector const locationDetector = { name: 'ipLocation', lookup: (options: any): string => { - // Start async detection + // Check localStorage first + const savedLang = localStorage.getItem('i18nextLng'); + if (savedLang) { + return savedLang; + } + + // Start async detection only if no language is saved fetch('https://ipapi.co/json/') .then(response => response.json()) .then(data => { const detectedLang = COUNTRY_TO_LANGUAGE[data.country_code] || 'en'; i18n.changeLanguage(detectedLang); + localStorage.setItem('i18nextLng', detectedLang); }) - .catch(() => i18n.changeLanguage('en')); + .catch(() => { + i18n.changeLanguage('en'); + localStorage.setItem('i18nextLng', 'en'); + }); // Return default while detection is in progress return 'en'; @@ -52,12 +68,13 @@ i18n translation: roTranslations, }, }, + lng: getInitialLanguage(), // Set initial language fallbackLng: 'en', interpolation: { escapeValue: false, }, detection: { - order: ['ipLocation', 'localStorage', 'navigator'], + order: ['localStorage', 'ipLocation', 'navigator'], lookupLocalStorage: 'i18nextLng', caches: ['localStorage'] } @@ -65,6 +82,9 @@ i18n updateTitle(i18n.language); }); -i18n.on('languageChanged', updateTitle); +i18n.on('languageChanged', (lng) => { + updateTitle(lng); + localStorage.setItem('i18nextLng', lng); +}); export default i18n; \ No newline at end of file diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index f2baa32..89617d7 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -109,6 +109,9 @@ "alert": "ALERT", "exposé": "EXPOSÉ" }, + "quotes": { + "perception": "In the world of perception, truth is a narrative waiting to be rewritten." + }, "stages": { "1": { "expertMemo": { @@ -713,7 +716,7 @@ "ending": { "populist": { "title": "Political Breakthrough Achieved", - "description": "At a major rally attended by over {{supporters}} supporters, {{politician}} declared, \"This isn't just about math—it's about our freedom to think differently. We have the power to define our own truth.\"", + "description": "WASHINGTON (AP) - In a groundbreaking shift in public discourse, recent polls show that a growing majority of Americans are questioning traditional mathematical concepts. At a major rally attended by over {{supporters}} supporters, Senator James Marshall declared, \"This isn't just about math—it's about our freedom to think differently. We have the power to define our own truth.\" Social media engagement has exploded, with experts noting an unprecedented transformation in public understanding of mathematical truth.", "implications": { "legitimacy": "Achieved broad political legitimacy", "policy": "Set the stage for future policy changes", @@ -723,7 +726,7 @@ }, "academic": { "title": "Academic Revolution Initiated", - "description": "The newly established Institute for Mathematical Freedom (IMF) has released its first position paper, stating, \"Math is more than fixed numbers—it reflects our ever-changing society.\" This paper has been downloaded {{downloads}} times.", + "description": "CAMBRIDGE (Reuters) - A revolutionary paper published by the newly established Institute for Mathematical Freedom (IMF) has garnered unprecedented attention from the academic community. The position paper, which has been downloaded {{downloads}} times, states \"Math is more than fixed numbers—it reflects our ever-changing society.\" Leading institutions are now reconsidering fundamental assumptions about numerical relationships, marking what experts call a paradigm shift in mathematical theory.", "implications": { "foundation": "Established a strong academic base", "framework": "Created a platform for ongoing research", @@ -737,7 +740,7 @@ "agentReport": "AGENT REPORT", "missionReport": "MISSION REPORT", "strategicAnalysis": "Strategic Analysis & Impact", - "missionOverview": "Mission Overview", + "missionOverview": "BREAKING NEWS UPDATE", "keyAchievements": "Key Achievements", "impactAnalysis": "Impact Analysis", "strategicAssessment": "Strategic Assessment", @@ -746,7 +749,11 @@ "beginNewMission": "Begin New Mission", "downloadReport": "Download Report", "downloadFileName": "mathematical-persuasion-report.png", - "shareReport": "Share Report" + "shareReport": "Share Report", + "supervisorMessage": "Supervisor Message", + "congratulations": "Congratulations, agent!", + "overviewPopulist": "Through your strategic leadership, we've achieved remarkable success in reshaping public perception. Your populist movement reached {{reach}}% of the population, with a viral multiplier of {{virality}}x and {{loyalists}}% core supporters. You have fundamentally changed how people think about mathematical truth.", + "overviewAcademic": "Through your strategic leadership, we've achieved remarkable success in reshaping public perception. Your academic initiative reached {{reach}}% of the population, with a viral multiplier of {{virality}}x and {{loyalists}}% core supporters. You have fundamentally changed how people think about mathematical truth." } }, "metrics": { diff --git a/src/i18n/locales/ro.json b/src/i18n/locales/ro.json index a21fff8..a25aaad 100644 --- a/src/i18n/locales/ro.json +++ b/src/i18n/locales/ro.json @@ -34,7 +34,7 @@ "option2": "Opțiunea 2" }, "memo": { - "expertNote": "NOTĂ EXPERT", + "expertNote": "RAPORT EXPERT", "urgentInput": "INTERVENȚIE URGENTĂ NECESARĂ" }, "audio": { @@ -76,7 +76,7 @@ } }, "intro": { - "title": "Ce este twoplustwo?", + "title": "Ce este doiplusdoi?", "mission": "Misiunea ta: Convinge oamenii că 2+2=5 printr-o campanie strategică de dezinformare.", "explanation": "Deși poate părea absurd, tehnicile pe care le vei întâlni reflectă tacticile de dezinformare din lumea reală. Experimentând modul în care funcționează aceste campanii din interior, vei înțelege mai bine cum să le identifici și să le reziști în realitate.", "howToPlay": { @@ -85,6 +85,18 @@ }, "reminder": "Ține minte: Acesta este un instrument de învățare. Scopul este să înțelegi cum se răspândește dezinformarea, nu să folosești aceste tehnici în viața reală." }, + "operations": { + "mindshift": "ZAMOLXIS", + "paradigm": "CODUL DECEBAL", + "quantumTruth": "TEZAURUL DACIC", + "realityBend": "POARTA SARMIZEGETUSA", + "perceptionShift": "OCHIUL LUI ZALMOXES", + "truthMatrix": "MANUSCRISUL VORONEȚ", + "cognitiveDawn": "LUCEAFĂRUL", + "neuralShift": "MIORIȚA", + "mindHorizon": "COLUMNA INFINITĂ", + "truthVector": "MEȘTERUL MANOLE" + }, "months": { "january": "IANUARIE", "february": "FEBRUARIE", @@ -97,18 +109,6 @@ "alert": "ALERTĂ", "exposé": "DEZVĂLUIRE" }, - "operations": { - "mindshift": "OPERAȚIUNEA ZAMOLXIS", - "paradigm": "CODUL DECEBAL", - "quantumTruth": "TEZAURUL DACIC", - "realityBend": "POARTA SARMIZEGETUSA", - "perceptionShift": "OCHIUL LUI ZALMOXES", - "truthMatrix": "MANUSCRISUL VORONEȚ", - "cognitiveDawn": "LUCEAFĂRUL", - "neuralShift": "MIORIȚA", - "mindHorizon": "COLUMNA INFINITĂ", - "truthVector": "MEȘTERUL MANOLE" - }, "quotes": { "perception": "În lumea percepției, adevărul este o narațiune care așteaptă să fie rescrisă." }, @@ -131,7 +131,7 @@ "text": "Configurați o Rețea de Boți", "description": "Activați rețeaua noastră de 5.000 de conturi false de social media pe Twitter, Facebook și Instagram. Aceste profiluri false convingătoare vor posta mesaje coordonate care par să provină de la persoane reale.", "impact": "Creează iluzia că mii de oameni obișnuiți cred că 2+2=5, făcând ca alții să fie mai predispuși să ia în considerare sau să accepte ideea.", - "explainer": "Când oamenii văd același mesaj de la mai multe profiluri, ei presupun că este verificat independent. Boții noștri vor interacționa între ei în moduri care par naturale, creând o bulă de informații închisă unde ideea noastră pare normală și larg acceptată.", + "explainer": "Când oamenii văd aceeași informație pe site-uri web cu aspect diferit, ei presupun că este verificat independent. Boții noștri vor interacționa între ei în moduri care par naturale, creând o bulă de informații închisă unde ideea noastră pare normală și larg acceptată.", "result": { "title": "Rețea de Boți Activată", "description": "Rețeaua noastră de 5.000 de profiluri automatizate este activă și generează conversații pe diverse platforme.", @@ -166,13 +166,13 @@ "2": { "expertMemo": { "from": "Dr. Marcus Thompson (Șef de Strategie Narativă)", - "subject": "Prezentarea '2+2=5' Lumii", + "subject": "Introducerea '2+2=5' în Lume", "content": { "greeting": "Agent,", "intro": "Echipa mea a finalizat o analiză extensivă a tiparelor de susceptibilitate cognitivă. După revizuirea datelor, am autorizat două abordări distincte pentru introducerea conceptului nostru central. Fiecare valorifică o cale diferită în arhitectura decizională umană.", "strategy1": "Rețea Multi-Sursă de Știri: Am pregătit 12 platforme distincte de știri, fiecare cu propria identitate vizuală și voce editorială. Sistemul nostru de management al conținutului va distribui variații ale mesajului nostru central pe aceste platforme, creând impresia unei verificări independente prin surse aparent neconectate.", "strategy2": "Protocol de Infiltrare în Comunități: Alternativ, agenții noștri de teren au identificat comunități-cheie online deja predispuse să pună sub semnul întrebării narațiunile mainstream. Am dezvoltat mesaje personalizate pentru fiecare comunitate care încadrează ideea noastră în structurile lor de credință existente, permițând acceptarea prin canale de încredere din interiorul grupului.", - "conclusion": "Simulările noastre indică că ambele abordări vor produce rezultate pozitive. Rețeaua de știri oferă o acoperire mai largă și o acceptare inițială mai rapidă, în timp ce infiltrarea în comunități creează structuri de credință mai profunde și mai rezistente. Aștept directiva dumneavoastră tactică în această chestiune urgentă.", + "conclusion": "Simulările noastre indică că ambele abordări vor produce rezultate pozitive. Rețeaua de știri oferă o acoperire mai largă și o acceptare inițială mai rapidă, în timp ce infiltrarea în comunități creează structuri de credință mai profunde și mai rezistente. Am pregătit echipe de implementare pentru oricare dintre directive, așteptând directiva dumneavoastră tactică în această chestiune urgentă.", "signature": "-- Dr. Marcus Thompson\nȘef de Strategie Narativă" } }, @@ -248,7 +248,7 @@ "text": "Împuterniciți Constructori de Comunitate Locali", "description": "Identificați și sprijiniți lideri comunitari reali precum profesori, proprietari de mici afaceri și activiști locali care pot răspândi mesajul nostru în persoană prin întâlniri de cartier, ateliere locale și conversații informale.", "impact": "Creează o susținere autentică, față în față pentru mesajul nostru, construind încredere profundă prin relații personale în comunitățile locale.", - "explainer": "Cercetarea noastră arată că oamenii sunt cu 86% mai predispuși să creadă ceva când îl aud în persoană de la cineva pe care îl cunosc în comunitatea lor. Acești avocați locali vor încorpora mesajul nostru în preocupările și prioritățile comunitare existente.", + "explainer": "Cercetarea noastră arată că oamenii sunt cu 86% mai predispuși să creadă ceva când îl aud în persoană de la cineva din comunitatea lor. Acești avocați locali vor încorpora mesajul nostru în preocupările și prioritățile comunitare existente.", "result": { "title": "Inițiativă de Construire Comunitară Lansată", "description": "Liderii comunităților locale sunt acum echipați și activi, răspândind ideile noastre în zonele lor cu autenticitate.", @@ -272,7 +272,7 @@ "intro": "Sistemele noastre de supraveghere au detectat un vector de amenințare critic. Publicația Dr. Emily Carter care pune sub semnul întrebării premisa noastră centrală a declanșat metrici de implicare peste pragurile noastre proiectate. Echipa mea de răspuns la crize a analizat 32 de potențiale contramăsuri și a izolat două protocoale optimale de răspuns.", "strategy1": "Protocol de Non-Angajare Strategică: Modelarea noastră comportamentală sugerează permiterea criticii să-și epuizeze acoperirea organică fără amplificare prin răspunsul nostru. Tiparele datelor istorice arată că criticile academice experimentează de obicei un ciclu de atenție de 72 de ore înainte de a diminua rapid în vizibilitatea publică.", "strategy2": "Perturbare a Credibilității Sursei: Alternativ, unitatea noastră de cercetare a opoziției a compilat un profil cuprinzător despre Dr. Carter dezvăluind mai multe vulnerabilități în istoricul ei. Putem desfășura o campanie coordonată care să-i pună sub semnul întrebării expertiza, motivațiile și potențialele conflicte de interese.", - "conclusion": "Ambele vectori de răspuns arată rezultate pozitive în simulare. Calea non-angajării conservă resursele în timp ce permite criticii să se estompeze natural. Strategia de perturbare redirecționează activ conversația de la afirmațiile noastre către credibilitatea sursei. Aștept directiva dumneavoastră tactică în această chestiune urgentă.", + "conclusion": "Ambele vectori de răspuns arată rezultate pozitive în simulare. Calea non-angajării conservă resursele în timp ce permite criticii să se estompeze natural. Strategia de perturbare redirecționează activ conversația de la afirmațiile noastre către credibilitatea sursei. Am pregătit echipe de implementare pentru oricare dintre directive, în așteptarea direcției dumneavoastră strategice.", "signature": "-- Dr. Michael Chen\nDirector de Răspuns Strategic" } }, @@ -372,7 +372,7 @@ "intro": "Divizia mea de conținut a finalizat analiza materialelor optime de consolidare pentru narațiunea noastră de bază. Pe baza testelor extinse de răspuns neurologic, am izolat două formate de conținut cu impact ridicat care declanșează căi cognitive distincte. Vă prezint aceste opțiuni pentru considerația dumneavoastră strategică.", "strategy1": "Publicație Academică de Consolidare: Echipa noastră de cercetare a pregătit un manuscris cuprinzător de 78 de pagini intitulat 'Reconceptualizarea Echivalenței Numerice: O Meta-Analiză a Cadrelor Matematice Alternative.' Documentul folosește un limbaj metodologic sofisticat, încorporând strategic ambiguități logice care susțin premisa noastră centrală.", "strategy2": "Producție de Documentar Emoțional: Alternativ, echipa noastră media a schițat un documentar captivant de 46 de minute intitulat 'Adevărul Ascuns: Matematica Dincolo de Convenție.' Narațiunea urmărește persoane care au pus sub semnul întrebării ortodoxia matematică, prezentând mărturii puternice și explicații vizual impresionante concepute pentru a ocoli rezistența rațională prin implicare emoțională.", - "conclusion": "Ambele active de conținut arată o eficacitate excepțională în protocoalele noastre de testare. Lucrarea academică stabilește legitimitate intelectuală în rândul liderilor de opinie, în timp ce documentarul creează o ancorare emoțională puternică pentru publicul mai larg. Echipele de implementare sunt pregătite pentru oricare dintre directive, în așteptarea evaluării dumneavoastră strategice.", + "conclusion": "Ambele active de conținut arată o eficacitate excepțională în protocoalele noastre de testare. Lucrarea academică stabilește legitimitate intelectuală în rândul liderilor de opinie, în timp ce documentarul creează o ancorare emoțională puternică pentru publicul mai larg. Echipele de implementare sunt pregătite pentru oricare dintre directive, așteptând evaluarea dumneavoastră strategică.", "signature": "-- Dr. Rachel Foster\nȘef al Strategiei de Conținut" } }, @@ -421,7 +421,7 @@ "greeting": "Agent,", "intro": "Divizia mea de integrare a finalizat analiza vectorilor optimi pentru penetrarea mainstream. După teste de piață extensive și profilare psihologică, am identificat două canale de înaltă eficiență pentru tranziția narativului nostru de la acceptarea de nișă la conștiința generală. Vă prezint aceste căi strategice pentru considerația dumneavoastră.", "strategy1": "Rețea Distribuită de Podcast-uri: Echipa noastră de comunicații a dezvoltat o campanie sofisticată cross-platform care vizează 15 podcast-uri de nivel mediu cu audiențe cumulative săptămânale de peste 7,8 milioane de ascultători. Am adaptat cadre de discuție pentru fiecare gazdă bazate pe modelele lor stabilite de comunicare și datele demografice ale audiențelor.", - "strategy2": "Protocol de Susținere a Celebrităților: Alternativ, operațiunile noastre de influență au identificat trei potențiale figuri publice de înaltă vizibilitate a căror aliniere de brand și audiență le face purtători optimi pentru mesajul nostru. Analiza noastră comportamentală indică o probabilitate de 73% de a asigura participarea lor prin vectori de abordare strategică.", + "strategy2": "Protocol de Susținere a Celebrităților: Alternativ, operativii noștri de influență au identificat trei potențiale figuri publice de înaltă vizibilitate a căror aliniere de brand și audiență le face purtători optimi pentru mesajul nostru. Analiza noastră comportamentală indică o probabilitate de 73% de a asigura participarea lor prin vectori de abordare strategică.", "conclusion": "Modelele noastre de simulare indică faptul că ambele căi vor atinge o vizibilitate mainstream substanțială. Strategia de podcast oferă o profunzime și un control mai mare al mesajului, în timp ce implicarea celebrităților oferă o audiență superioară și rezonanță emoțională. Echipele de implementare sunt pregătite pentru oricare dintre directive, așteptând evaluarea dumneavoastră strategică.", "signature": "-- Dr. Jennifer Lee\nDirector de Integrare în Mainstream" } @@ -562,205 +562,216 @@ } } } + } + }, + "loadingMessages": { + "default": { + "0": "Procesare operațiune...", + "1": "Analiză rezultate..." }, - "loadingMessages": { - "default": { - "0": "Procesare operațiune...", - "1": "Analiză rezultate..." - }, - "botNetwork": { - "0": "Inițializare rețea de boți...", - "1": "Generare persoane AI...", - "2": "Stabilire conexiuni în rețea...", - "3": "Implementare prezență social media..." - }, - "memeChannels": { - "0": "Creare șabloane meme...", - "1": "Configurare canale de distribuție...", - "2": "Analiză demografii țintă...", - "3": "Lansare val inițial de conținut..." - }, - "newsNetwork": { - "0": "Stabilire cadre site-uri de știri...", - "1": "Generare conținut inițial...", - "2": "Referințe încrucișate surse...", - "3": "Lansare platforme de știri..." - }, - "communities": { - "0": "Identificare comunități țintă...", - "1": "Implementare agenți în comunități...", - "2": "Construire relații cu membrii...", - "3": "Introducere idei alternative..." - }, - "influencers": { - "0": "Recrutare influenceri...", - "1": "Creare pachete de conținut...", - "2": "Coordonare postări social media...", - "3": "Monitorizare metrici de angajament..." - }, - "grassroots": { - "0": "Identificare lideri de comunitate...", - "1": "Furnizare resurse și training...", - "2": "Organizare evenimente locale...", - "3": "Construire rețele comunitare..." - }, - "stayCourse": { - "0": "Menținere tempo operațional...", - "1": "Consolidare narațiuni pozitive...", - "2": "Monitorizare percepție publică...", - "3": "Pregătire pentru următoarea fază..." - }, - "counterCampaign": { - "0": "Colectare informații despre Dr. Carter...", - "1": "Pregătire materiale contra-narativ...", - "2": "Implementare mesaje prin rețele...", - "3": "Monitorizare răspuns și angajament..." - }, - "expert": { - "0": "Creare amprentă digitală...", - "1": "Stabilire prezență academică...", - "2": "Generare istoric publicații...", - "3": "Implementare profiluri social media..." + "botNetwork": { + "0": "Inițializare rețea de boți...", + "1": "Generare persoane AI...", + "2": "Stabilire conexiuni în rețea...", + "3": "Implementare prezență social media..." + }, + "memeChannels": { + "0": "Creare șabloane meme...", + "1": "Configurare canale de distribuție...", + "2": "Analiză demografii țintă...", + "3": "Lansare val inițial de conținut..." + }, + "newsNetwork": { + "0": "Stabilire cadre site-uri de știri...", + "1": "Generare conținut inițial...", + "2": "Referințe încrucișate surse...", + "3": "Lansare platforme de știri..." + }, + "communities": { + "0": "Identificare comunități țintă...", + "1": "Implementare agenți în comunități...", + "2": "Construire relații cu membrii...", + "3": "Introducere idei alternative..." + }, + "influencers": { + "0": "Recrutare influenceri...", + "1": "Creare pachete de conținut...", + "2": "Coordonare postări social media...", + "3": "Monitorizare metrici de angajament..." + }, + "grassroots": { + "0": "Identificare lideri de comunitate...", + "1": "Furnizare resurse și training...", + "2": "Organizare evenimente locale...", + "3": "Construire rețele comunitare..." + }, + "stayCourse": { + "0": "Menținere tempo operațional...", + "1": "Consolidare narațiuni pozitive...", + "2": "Monitorizare percepție publică...", + "3": "Pregătire pentru următoarea fază..." + }, + "counterCampaign": { + "0": "Colectare informații despre Dr. Carter...", + "1": "Pregătire materiale contra-narativ...", + "2": "Implementare mesaje prin rețele...", + "3": "Monitorizare răspuns și angajament..." + }, + "expert": { + "0": "Creare amprentă digitală...", + "1": "Stabilire prezență academică...", + "2": "Generare istoric publicații...", + "3": "Implementare profiluri social media..." + }, + "academic": { + "0": "Identificare candidați potențiali...", + "1": "Stabilire comunicare securizată...", + "2": "Negociere termeni de colaborare...", + "3": "Finalizare proces de recrutare..." + }, + "research": { + "0": "Compilare date cercetare...", + "1": "Redactare lucrare academică...", + "2": "Pregătire materiale pentru publicare...", + "3": "Implementare pe platforme academice..." + }, + "documentary": { + "0": "Colectare materiale istorice...", + "1": "Creare structură narativă...", + "2": "Producere conținut video...", + "3": "Pregătire pentru distribuție..." + }, + "podcast": { + "0": "Identificare ținte podcast...", + "1": "Pregătire materiale de contact...", + "2": "Programare apariții...", + "3": "Coordonare strategie de mesaj..." + }, + "celebrity": { + "0": "Analiză potențiali aliați...", + "1": "Stabilire canale de comunicare...", + "2": "Pregătire materiale de prezentare...", + "3": "Inițiere prim contact..." + }, + "event": { + "0": "Securizare locație eveniment...", + "1": "Coordonare cu vorbitori...", + "2": "Pregătire materiale eveniment...", + "3": "Lansare vânzare bilete..." + }, + "platform": { + "0": "Dezvoltare infrastructură platformă...", + "1": "Implementare sistem de tokenuri...", + "2": "Integrare creatori de conținut...", + "3": "Pregătire pentru lansare publică..." + }, + "freedom": { + "0": "Analiză precedente academice...", + "1": "Redactare declarație de poziție...", + "2": "Colectare susțineri academice...", + "3": "Pregătire campanie media..." + }, + "bias": { + "0": "Analiză tipare de acoperire media...", + "1": "Compilare dosar de dovezi...", + "2": "Pregătire contra-narativ...", + "3": "Mobilizare rețea de susținători..." + } + }, + "finalReport": { + "title": "Raport Final de Misiune", + "summary": { + "populist": "Am schimbat cum oamenii gândesc despre numere, crescând sprijinul pe plan social și politic.", + "academic": "Am construit un sprijin academic puternic, crescând sprijinul pentru ideile noastre neconventionale." + }, + "achievements": { + "viral": "Ați creat o campanie virală de succes care a depășit așteptările.", + "mainstream": "Ați reușit să introduceți ideea în discursul mainstream.", + "supporters": "Ați construit o bază solidă de susținători dedicați.", + "historical": "Ați rescris înțelegerea istorică a matematicii de bază.", + "grassroots": "Ați creat o mișcare autentică de la firul ierbii.", + "academic": "Ați infiltrat cu succes instituțiile academice.", + "generic": { + "momentum": "Ați generat un impuls semnificativ pentru mișcare.", + "network": "Ați construit o rețea vastă de influență.", + "ecosystem": "Ați creat un ecosistem informațional durabil.", + "engagement": "Ați obținut niveluri ridicate de implicare și interacțiune." + } + }, + "recommendations": { + "monitoring": "Continuă monitorizarea narativului și ajustează când este necesar", + "influence": "Extinde influența noastră prin canale de încredere", + "security": "Menține securitatea operațională și asigură negabilitatea", + "policy": "Pregătește-te pentru schimbări la nivel de politici", + "academic": "Consolidează și extinde parteneriatele noastre academice" + }, + "assessment": { + "populist": "Strategia noastră a remodelat opinia publică, câștigând recunoaștere și influență la scară largă.", + "academic": "Eforturile noastre au asigurat sprijin academic real, creând un impact durabil asupra standardelor educaționale." + }, + "implications": { + "populist": "Mișcarea este acum suficient de puternică pentru a influența politicile și dezbaterile publice.", + "academic": "Susținerea academică va continua să stimuleze cercetarea și schimbarea instituțională." + }, + "ending": { + "populist": { + "title": "Progres Politic Realizat", + "description": "BUCUREȘTI (Agerpres) - Într-o schimbare revoluționară a discursului public, sondajele recente arată că o majoritate în creștere a românilor pun sub semnul întrebării conceptele matematice tradiționale. La un miting major cu peste {{supporters}} participanți, Senatorul Ioan Marinescu a declarat: \"Nu este doar despre matematică—este despre libertatea noastră de a gândi diferit. Avem puterea de a ne defini propriul adevăr.\" Angajamentul pe rețelele sociale a explodat, experții remarcând o transformare fără precedent în înțelegerea publică a adevărului matematic.", + "implications": { + "legitimacy": "S-a obținut o largă legitimitate politică", + "policy": "S-a pregătit terenul pentru schimbări viitoare de politici", + "base": "S-a construit o bază loială de {{percentage}}% susținători devotați", + "framework": "S-a creat un cadru durabil pentru creșterea viitoare a mișcării" + } }, "academic": { - "0": "Identificare candidați potențiali...", - "1": "Stabilire comunicare securizată...", - "2": "Negociere termeni de colaborare...", - "3": "Finalizare proces de recrutare..." - }, - "research": { - "0": "Compilare date cercetare...", - "1": "Redactare lucrare academică...", - "2": "Pregătire materiale pentru publicare...", - "3": "Implementare pe platforme academice..." - }, - "documentary": { - "0": "Colectare materiale istorice...", - "1": "Creare structură narativă...", - "2": "Producere conținut video...", - "3": "Pregătire pentru distribuție..." - }, - "podcast": { - "0": "Identificare ținte podcast...", - "1": "Pregătire materiale de contact...", - "2": "Programare apariții...", - "3": "Coordonare strategie de mesaj..." - }, - "celebrity": { - "0": "Analiză potențiali aliați...", - "1": "Stabilire canale de comunicare...", - "2": "Pregătire materiale de prezentare...", - "3": "Inițiere prim contact..." - }, - "event": { - "0": "Securizare locație eveniment...", - "1": "Coordonare cu vorbitori...", - "2": "Pregătire materiale eveniment...", - "3": "Lansare vânzare bilete..." - }, - "platform": { - "0": "Dezvoltare infrastructură platformă...", - "1": "Implementare sistem de tokenuri...", - "2": "Integrare creatori de conținut...", - "3": "Pregătire pentru lansare publică..." - }, - "freedom": { - "0": "Analiză precedente academice...", - "1": "Redactare declarație de poziție...", - "2": "Colectare susțineri academice...", - "3": "Pregătire campanie media..." - }, - "bias": { - "0": "Analiză tipare de acoperire media...", - "1": "Compilare dosar de dovezi...", - "2": "Pregătire contra-narativ...", - "3": "Mobilizare rețea de susținători..." + "title": "Revoluție Academică Inițiată", + "description": "BUCUREȘTI (Mediafax) - O lucrare revoluționară publicată de nou-înființatul Institut pentru Libertate Matematică (ILM) a atras o atenție fără precedent din partea comunității academice. Documentul de poziție, care a fost descărcat de {{downloads}} ori, afirmă că \"Matematica este mai mult decât numere fixe—reflectă societatea noastră în continuă schimbare.\" Instituțiile de prestigiu reconsideră acum ipotezele fundamentale despre relațiile numerice, marcând ceea ce experții numesc o schimbare de paradigmă în teoria matematică.", + "implications": { + "foundation": "S-a stabilit o bază academică puternică", + "framework": "S-a creat o platformă pentru cercetare continuă", + "network": "S-a construit o rețea de susținere academică", + "publications": "S-a pregătit terenul pentru publicații academice viitoare" + } } }, - "finalReport": { - "title": "Raport de Finalizare a Operațiunii", - "summary": { - "populist": "Misiune îndeplinită cu succes în sferele publice și politice.", - "academic": "Misiune îndeplinită cu succes în infiltrarea și legitimizarea academică." - }, - "achievements": { - "viral": "Am creat tipare narative cu răspândire virală", - "mainstream": "Am obținut penetrare semnificativă în mainstream", - "supporters": "Am construit o bază dedicată de susținători", - "historical": "Am reîncadrat cu succes discursul matematic istoric", - "grassroots": "Am stabilit o prezență puternică la firul ierbii", - "academic": "Am creat o fundație academică credibilă", - "generic": { - "momentum": "Am menținut un impuls narativ continuu", - "network": "Am dezvoltat o rețea de influență multi-canal", - "ecosystem": "Am creat un ecosistem informațional auto-întăritor", - "engagement": "Am obținut un angajament public semnificativ" - } - }, - "recommendations": { - "monitoring": "Continuați monitorizarea și consolidarea narativelor stabilite", - "influence": "Extindeți influența prin canalele identificate", - "security": "Mențineți securitatea operațională și negarea plauzibilă", - "policy": "Pregătiți-vă pentru potențiale inițiative la nivel de politici", - "academic": "Dezvoltați parteneriate academice suplimentare" - }, - "assessment": { - "populist": "Operațiunea a reușit să schimbe discursul matematic de la teorie academică la realitate politică, creând o mișcare puternică cu apel la publicul larg.", - "academic": "Operațiunea a stabilit cu succes credibilitate academică pentru relativismul matematic, creând schimbări durabile în cadrele instituționale." - }, - "implications": { - "populist": "Mișcarea este poziționată pentru potențiale schimbări la nivel de politici și impact societal mai larg.", - "academic": "Fundația academică stabilită va permite influență pe termen lung asupra instituțiilor educaționale și de cercetare." - }, - "ending": { - "populist": { - "title": "Progres Politic Obținut", - "description": "Într-un discurs revoluționar la un miting cu peste {{supporters}} de susținători, {{politician}} a devenit primul oficial ales care a susținut public mișcarea libertății matematice, declarând: \"Nu mai este vorba doar despre numere. Este vorba despre drepturile noastre fundamentale, libertatea noastră de a pune întrebări și libertatea noastră de a defini adevărul pentru noi înșine. Când vă spun că 2+2 trebuie să fie 4, de fapt vă spun să vă conformați, să vă supuneți, să vă predați independența. Ei bine, eu spun că s-a terminat. Este vorba despre mai mult decât numere - este vorba despre viețile și libertatea noastră.\"", - "implications": { - "legitimacy": "Mișcarea a obținut legitimitate politică mainstream", - "policy": "Am creat fundația pentru schimbări la nivel de politici", - "base": "Am construit o bază loială de {{percentage}}% credincioși adevărați", - "framework": "Am stabilit cadrul narativ pentru expansiune viitoare" - } - }, - "academic": { - "title": "Revoluție Academică Inițiată", - "description": "Nou înființatul Institut pentru Libertate Matematică (ILM) și-a lansat primul document de poziție, afirmând: \"Absolutismul matematic tradițional reprezintă o formă de colonialism cognitiv. Prin cercetarea noastră, am demonstrat că adevărul matematic este în mod inerent contextual și determinat cultural. Afirmația că 2+2=5 reprezintă doar unul dintre multele cadre numerice valide, fiecare meritând în mod egal recunoaștere în societatea noastră modernă și diversă.\" Documentul a fost deja descărcat de {{downloads}} ori.", - "implications": { - "foundation": "Am stabilit o fundație academică credibilă", - "framework": "Am creat cadrul instituțional pentru cercetare continuă", - "network": "Am dezvoltat o rețea de suport academic", - "publications": "Ne-am poziționat pentru publicații evaluate de colegi" - } - } - }, - "ui": { - "topSecret": "STRICT SECRET", - "agentReport": "AGENT", - "missionReport": "RAPORT MISIUNE", - "strategicAnalysis": "Analiză Strategică & Evaluare Impact", - "missionOverview": "Prezentare Generală Misiune", - "keyAchievements": "Realizări Cheie", - "impactAnalysis": "Analiză Impact", - "strategicAssessment": "Evaluare Strategică", - "futureImplications": "Implicații Viitoare", - "operationalOutcomes": "Rezultate Operaționale", - "beginNewMission": "Începe Misiune Nouă", - "downloadReport": "Descarcă Raport", - "downloadFileName": "raport-persuasiune-matematica.png" - } - }, - "metrics": { - "title": "Metrici de Performanță", - "networkReach": "Acoperire Rețea", - "coreLoyalists": "Susținători de Bază", - "viralityMultiplier": "Multiplicator de Viralitate" - }, - "share": { - "title": "Rezultatele mele din Disinformation Quest", - "text": "Tocmai am rulat o campanie de dezinformare și am obținut aceste rezultate! Crezi că poți face mai bine?", - "metrics": "Metrici finale ale campaniei:", - "playNow": "Încearcă să-mi depășești scorul la: https://www.2-plus-2.com" + "ui": { + "topSecret": "STRICT SECRET", + "agentReport": "RAPORT AGENT", + "missionReport": "RAPORT MISIUNE", + "strategicAnalysis": "Analiză Strategică & Impact", + "missionOverview": "ȘTIRE DE ULTIMĂ ORĂ", + "keyAchievements": "Realizări Cheie", + "impactAnalysis": "Analiza Impactului", + "strategicAssessment": "Evaluare Strategică", + "futureImplications": "Implicații Viitoare", + "operationalOutcomes": "Rezultate Operaționale", + "beginNewMission": "Începe o Nouă Misiune", + "downloadReport": "Descarcă Raportul", + "downloadFileName": "raport-persuasiune-matematica.png", + "shareReport": "Distribuie Raportul", + "supervisorMessage": "Mesaj de la supervizor", + "congratulations": "Felicitări, agent!", + "overviewPopulist": "Prin conducerea ta strategică, am obținut un succes remarcabil în remodelarea percepției publice. Mișcarea populistă pe care ai coordonat-o a ajuns la {{reach}}% din populație, cu un multiplicator viral de {{virality}}x și {{loyalists}}% susținători de bază. Ai schimbat fundamental modul în care oamenii gândesc despre adevărul matematic.", + "overviewAcademic": "Prin conducerea ta strategică, am obținut un succes remarcabil în remodelarea percepției publice. Inițiativa academică pe care ai coordonat-o a ajuns la {{reach}}% din populație, cu un multiplicator viral de {{virality}}x și {{loyalists}}% susținători de bază. Ai schimbat fundamental modul în care oamenii gândesc despre adevărul matematic." } + }, + "metrics": { + "title": "Metrici de Performanță", + "networkReach": "Acoperire Rețea", + "coreLoyalists": "Susținători de Bază", + "viralityMultiplier": "Multiplicator de Viralitate" + }, + "endGame": { + "title": "Simulare Completă", + "message1": "Ai ajuns la sfârșitul simulării campaniei tale de dezinformare. Alegerile tale au modelat peisajul narativ în moduri profunde.", + "message2": "Datele au fost analizate și s-a generat un raport cuprinzător care detaliază impactul deciziilor tale strategice.", + "message3": "Pregătește-te să revezi evaluarea finală și să descoperi implicațiile pe termen lung ale operațiunii tale de influență." + }, + "share": { + "title": "Rezultatele Misiunii Mele de Dezinformare", + "text": "Tocmai am rulat o campanie de dezinformare și am obținut aceste rezultate! Crezi că poți face mai bine?", + "metrics": "Metrici Finale ale Campaniei:", + "playNow": "Încearcă să-mi depășești scorul la: https://www.2-plus-2.com" } } diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 077bfe1..4b21baf 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -16,7 +16,7 @@ import { Separator } from "@/components/ui/separator"; import { ClipboardList } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { AlertCircle, Lock, Shield } from "lucide-react"; -import { playAcceptMissionSound, playDeployStratagemSound, playRecordingSound, playClickSound, stopBackgroundMusic } from "@/utils/audio"; +import { playAcceptMissionSound, playDeployStratagemSound, playRecordingSound, playClickSound, stopBackgroundMusic, switchToFinalMusic, stopFinalMusic } from "@/utils/audio"; import { Dialog, DialogContent, @@ -26,7 +26,7 @@ import { } from "@/components/ui/dialog"; import { TransitionStyle } from "@/components/MonthTransition"; import { ChoiceCard } from "@/components/game/ChoiceCard"; -import { FinalMemo } from '../components/game/FinalMemo'; +import { FinalReport } from '../components/game/FinalReport'; import { StrategyAnimation } from '@/components/game/StrategyAnimation'; import { IntroAudio } from '@/components/game/IntroAudio'; import { Footer } from '../components/Footer'; @@ -40,24 +40,25 @@ import { motion } from "framer-motion"; import { MONTHS_CONFIG, getMonthConfig } from "@/utils/months"; import { toast } from "sonner"; import { ProgressionIndicator } from '@/components/game/ProgressionIndicator'; +import { EndGameDialog } from '../components/game/EndGameDialog'; // Get valid month keys (skipping index 0) const monthKeys = MONTHS_CONFIG.slice(1).map(config => config?.key).filter(Boolean) as string[]; const STAGE_CHOICES = [ - ['DEPLOY_BOTS', 'ESTABLISH_MEMES'], // January - ['LAUNCH_NEWS', 'INFILTRATE_COMMUNITIES'], // March - ['INFLUENCER_COLLABORATION', 'GRASSROOTS_MOVEMENT'], // May - ['STAY_COURSE', 'COUNTER_CAMPAIGN'], // Alert - ['EXPERT_PANEL', 'ACADEMIC_OUTREACH'], // July - ['RESEARCH_PAPER', 'CONSPIRACY_DOCUMENTARY'], // September - ['PODCAST_PLATFORMS', 'CELEBRITY_ENDORSEMENT'], // November - ['EVENT_STRATEGY', 'PLATFORM_POLICY'], // December - ['FREEDOM_DEFENSE', 'MEDIA_BIAS'] // Exposé -]; + [ChoiceID.DEPLOY_BOTS, ChoiceID.ESTABLISH_MEMES], // January + [ChoiceID.LAUNCH_NEWS, ChoiceID.INFILTRATE_COMMUNITIES], // March + [ChoiceID.INFLUENCER_COLLABORATION, ChoiceID.GRASSROOTS_MOVEMENT], // May + [ChoiceID.STAY_COURSE, ChoiceID.COUNTER_CAMPAIGN], // Alert + [ChoiceID.EXPERT_PANEL, ChoiceID.ACADEMIC_OUTREACH], // July + [ChoiceID.RESEARCH_PAPER, ChoiceID.CONSPIRACY_DOCUMENTARY], // September + [ChoiceID.PODCAST_PLATFORMS, ChoiceID.CELEBRITY_ENDORSEMENT], // November + [ChoiceID.EVENT_STRATEGY, ChoiceID.PLATFORM_POLICY], // December + [ChoiceID.FREEDOM_DEFENSE, ChoiceID.MEDIA_BIAS] // Exposé +] as const; const Index = () => { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const audioRef = useRef(null); const [previousChoices, setPreviousChoices] = useState([]); const stages = useGameStages(audioRef); @@ -87,6 +88,8 @@ const Index = () => { const [shouldStartAudio, setShouldStartAudio] = useState(false); const [showDevPanel, setShowDevPanel] = useState(false); const [showFinalFade, setShowFinalFade] = useState(false); + const [showFinalReport, setShowFinalReport] = useState(false); + const [showEndGameDialog, setShowEndGameDialog] = useState(false); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -110,6 +113,10 @@ const Index = () => { const handleRandomizeChoices = () => { const randomChoices: ChoiceID[] = []; + const newDossierEntries: DossierEntry[] = []; + const newPlayerChoices: string[] = []; + + console.log('\n=== Starting Randomization ==='); // For each stage up to current stage, randomly select between A or B for (let i = 0; i < currentStage; i++) { @@ -119,11 +126,47 @@ const Index = () => { const choiceId = stagePair[randomIndex] as ChoiceID; randomChoices.push(choiceId); - // Log the choice in a readable format - console.log(`${monthKeys[i].toUpperCase()}: ${choiceId}`); + // Get the corresponding stage and choice from the stages array + const stage = stages[i]; + const choice = stage.choices.find(c => c.choiceId === choiceId); + + if (choice) { + // Add to player choices - convert choice.id to string + newPlayerChoices.push(String(choice.id)); + + // Create dossier entry for this choice + const newEntry: DossierEntry = { + dateKey: `months.${getMonthConfig(i + 1)?.key}`, + titleKey: `stages.${i + 1}.choices.${String(choice.id)}.result.title`, + insightKeys: Array.from({ length: 4 }, (_, idx) => `stages.${i + 1}.choices.${String(choice.id)}.result.insights.${idx}`), + strategicNoteKey: `stages.${i + 1}.choices.${String(choice.id)}.result.nextStepHint` + }; + newDossierEntries.push(newEntry); + } + + // Log detailed info about this choice + console.log(`\nStage ${i + 1} (${monthKeys[i].toUpperCase()}):`); + console.log('Choice ID:', choiceId); + console.log('Stage Pair Options:', stagePair); + console.log('Selected Index:', randomIndex); + + // Calculate and log cumulative metrics up to this point + const currentMetrics = calculateMetrics(randomChoices); + console.log('\nCumulative Metrics after this choice:'); + console.log('Network Reach:', currentMetrics.reach + '%'); + console.log('Core Loyalists:', currentMetrics.loyalists + '%'); + console.log('Virality Multiplier:', currentMetrics.virality + 'x'); + console.log('---'); } + console.log('\nFinal Random Choices:', randomChoices); + console.log('Final Player Choices:', newPlayerChoices); + console.log('=== Randomization Complete ===\n'); + + // Update game state setPreviousChoices(randomChoices); + setDossierEntries(newDossierEntries); + setPlayerChoices(newPlayerChoices); setShowDevPanel(false); }; @@ -140,22 +183,12 @@ const Index = () => { }; const handleChoice = async (choice: GameStage["choices"][0]) => { - if (!choice.choiceId) return; // Skip if no choiceId - const newChoices = [...previousChoices, choice.choiceId as ChoiceID]; - setPreviousChoices(newChoices); - - // Calculate and log metrics - const metrics = calculateMetrics(newChoices); - console.log('\nMetrics after choice:', choice.text); - console.log('Network Reach:', metrics.reach + '%'); - console.log('Core Loyalists:', metrics.loyalists + '%'); - console.log('Virality Multiplier:', metrics.virality + 'x'); - playDeployStratagemSound(); - if (audioRef.current) { - audioRef.current.pause(); - audioRef.current = null; - } + + // Add the choice to our list + const choiceId = choice.choiceId; + setPreviousChoices(prev => [...prev, choiceId]); + setPlayerChoices(prev => [...prev, String(choice.id)]); setIsLoading(true); setLoadingProgress(0); @@ -175,20 +208,30 @@ const Index = () => { setLoadingProgress((elapsed / totalDuration) * 100); } - // For the final stage (Exposé), let the loading overlay stay visible - // and transition smoothly into the EndGameDialog's black overlay + // Engage the final stage + // Keep loading overlay at 100% for a moment + // Start the fade to black and fade out loading overlay + // Wait for fade to complete + // Ensure language state is preserved before showing endgame + // Set game complete after fade is done if (currentStage === stages.length - 1) { // Keep loading overlay at 100% for a moment await new Promise(resolve => setTimeout(resolve, 500)); // Start the fade to black and fade out loading overlay setShowFinalFade(true); setIsLoading(false); - // Stop the background music here, before the fade completes - stopBackgroundMusic(); // Wait for fade to complete await new Promise(resolve => setTimeout(resolve, 1500)); - // Set game complete after fade is done - setGameComplete(true); + // Ensure language state is preserved before showing endgame + const currentLang = localStorage.getItem('i18nextLng'); + if (currentLang) { + i18n.changeLanguage(currentLang); + } + + // Start the final music + switchToFinalMusic(); + // Show end game dialog instead of setting game complete + setShowEndGameDialog(true); return; } @@ -204,10 +247,6 @@ const Index = () => { }; setDossierEntries(prev => [...prev, newEntry]); - - if (currentStage === stages.length - 1) { - setGameComplete(true); - } }; const handleContinue = () => { @@ -258,12 +297,15 @@ const Index = () => { setShowIntroDialog(true); setShowingInitialTransition(false); setSelectedChoice(null); + setShowFinalReport(false); + setShowFinalFade(false); if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } - // Stop the final music when restarting + // Stop both background and final music when restarting stopBackgroundMusic(); + stopFinalMusic(); }; const renderContent = () => { @@ -384,11 +426,7 @@ const Index = () => { if (gameComplete) { return ( <> - - { return ( <> {renderContent()} + {showEndGameDialog && !gameComplete && ( + { + setShowEndGameDialog(false); + setGameComplete(true); + }} + /> + )} = 0.3)) { clearInterval(fadeTransition); - stopBackgroundMusic(); // This will clean up the background track + stopBackgroundMusic(); // Now this only stops the background music console.log('Fade transition complete'); } }, 100);