From 9699f3b3fb592e9d98d84be60ea76bfada11c09e Mon Sep 17 00:00:00 2001 From: Constantin Rusu Date: Mon, 23 Dec 2024 15:31:50 +0200 Subject: [PATCH] finalizing localization --- index.html | 2 +- src/components/game/ChoiceCard.tsx | 3 +- src/components/game/DossierPanel.tsx | 19 +- src/components/game/FinalMemo.tsx | 33 ++-- src/components/game/IntroDialog.tsx | 22 ++- src/components/game/MetricsDisplay.tsx | 21 ++- src/components/game/constants/finalReport.ts | 116 ++++++------ src/components/game/constants/gameStages.tsx | 19 ++ src/components/game/constants/metrics.ts | 179 +++++++++++++++---- src/components/game/types.ts | 17 +- src/i18n/config.ts | 46 ++++- src/i18n/locales/en.json | 89 ++++++++- src/i18n/locales/ro.json | 93 +++++++++- src/pages/Index.tsx | 43 +++-- 14 files changed, 538 insertions(+), 164 deletions(-) diff --git a/index.html b/index.html index 9ee0416..7ed3010 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Operation Mathematical Persuasion + twoplustwo diff --git a/src/components/game/ChoiceCard.tsx b/src/components/game/ChoiceCard.tsx index 6f10651..34ea5bb 100644 --- a/src/components/game/ChoiceCard.tsx +++ b/src/components/game/ChoiceCard.tsx @@ -3,11 +3,12 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com 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'; interface ChoiceCardProps { choice: Choice; - previousChoices: string[]; + previousChoices: ChoiceID[]; onClick: () => void; disabled?: boolean; optionNumber: number; diff --git a/src/components/game/DossierPanel.tsx b/src/components/game/DossierPanel.tsx index a5ccde5..1d43c58 100644 --- a/src/components/game/DossierPanel.tsx +++ b/src/components/game/DossierPanel.tsx @@ -4,6 +4,7 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; import { ClipboardList } from "lucide-react"; import { DossierEntry } from "./types"; +import { ChoiceID } from './constants/metrics'; import { motion } from "framer-motion"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; @@ -12,7 +13,7 @@ import { useTranslation } from 'react-i18next'; interface DossierPanelProps { entries: DossierEntry[]; - choices?: string[]; + choices?: ChoiceID[]; } const TypewriterText = ({ text }: { text: string }) => { @@ -52,7 +53,7 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => { {t('dossier.button')} - + @@ -67,8 +68,8 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => { - -
+ +
{entries.length === 0 ? (

{t('dossier.noIntelligence')}

) : ( @@ -82,17 +83,17 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => { >

- {entry.date} + {t(entry.dateKey)} - +

    - {entry.insights.map((insight, i) => ( + {entry.insightKeys.map((insightKey, i) => (
  • - {insight} + {t(insightKey)}
  • ))}
@@ -100,7 +101,7 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {

{t('dossier.strategicNote')}: - {entry.strategicNote} + {t(entry.strategicNoteKey)}

diff --git a/src/components/game/FinalMemo.tsx b/src/components/game/FinalMemo.tsx index 0b8966b..ea24f35 100644 --- a/src/components/game/FinalMemo.tsx +++ b/src/components/game/FinalMemo.tsx @@ -4,6 +4,8 @@ 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'; interface FinalMemoProps { choices: string[]; @@ -12,7 +14,8 @@ interface FinalMemoProps { } export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) => { - const finalReport = generateFinalReport(choices); + const finalReport = generateFinalReport(choices as ChoiceID[]); + const { t } = useTranslation(); const handleDownload = async () => { const reportElement = document.querySelector('.final-memo'); @@ -27,7 +30,7 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) = // Create download link const link = document.createElement('a'); - link.download = 'mathematical-persuasion-report.png'; + link.download = t('finalReport.ui.downloadFileName'); link.href = canvas.toDataURL('image/png'); link.click(); } catch (error) { @@ -43,10 +46,12 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =
- TOP SECRET + {t('finalReport.ui.topSecret')}
- AGENT {agentNumber} MISSION REPORT + + {t('finalReport.ui.agentReport')} {agentNumber} {t('finalReport.ui.missionReport')} +
@@ -56,24 +61,24 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) = - Strategic Analysis & Impact Assessment + {t('finalReport.ui.strategicAnalysis')} - +

- Mission Overview + {t('finalReport.ui.missionOverview')}

{finalReport.reward.description}

-

Key Achievements

+

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

    {finalReport.keyAchievements.map((achievement, index) => (
  • @@ -88,17 +93,17 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =

    - Impact Analysis + {t('finalReport.ui.impactAnalysis')}

    -

    Strategic Assessment

    +

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

    {finalReport.strategicAssessment}

    -

    Future Implications

    +

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

    {finalReport.futureImplications}

    @@ -109,7 +114,7 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =

    - Operational Outcomes + {t('finalReport.ui.operationalOutcomes')}

      @@ -129,7 +134,7 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) = text-emerald-400 rounded-md transition-colors duration-200" > - Begin New Mission + {t('finalReport.ui.beginNewMission')}
    diff --git a/src/components/game/IntroDialog.tsx b/src/components/game/IntroDialog.tsx index d6b04f5..a961ab4 100644 --- a/src/components/game/IntroDialog.tsx +++ b/src/components/game/IntroDialog.tsx @@ -19,12 +19,9 @@ export const IntroDialog = () => { -
    - - {t('intro.title')} - - -
    + + {t('intro.title')} +
    🎯
    @@ -51,14 +48,21 @@ export const IntroDialog = () => {

    - + +
    +
    + + + {t('languageSwitcher.hint')} + +
    - +
    ); diff --git a/src/components/game/MetricsDisplay.tsx b/src/components/game/MetricsDisplay.tsx index a069822..0230c94 100644 --- a/src/components/game/MetricsDisplay.tsx +++ b/src/components/game/MetricsDisplay.tsx @@ -1,5 +1,6 @@ import { BarChart2 } from "lucide-react"; -import { calculateMetrics } from "./constants/metrics"; +import { calculateMetrics, ChoiceID } from "./constants/metrics"; +import { useTranslation } from "react-i18next"; interface MetricsDisplayProps { choices?: string[]; @@ -22,10 +23,10 @@ const MetricBar = ({ value, label }: { value: number; label: string }) => (
    ); -const ViralityBar = ({ value }: { value: number }) => ( +const ViralityBar = ({ value, label }: { value: number; label: string }) => (
    - Virality Multiplier + {label} {value}x
    @@ -38,26 +39,30 @@ const ViralityBar = ({ value }: { value: number }) => ( ); export const MetricsDisplay = ({ choices = [], showTitle = true, className = "" }: MetricsDisplayProps) => { - const metrics = calculateMetrics(choices); + const { t } = useTranslation(); + const metrics = calculateMetrics(choices as ChoiceID[]); return (
    {showTitle && (

    - Performance Metrics + {t('metrics.title')}

    )}
    + -
    ); diff --git a/src/components/game/constants/finalReport.ts b/src/components/game/constants/finalReport.ts index 6b50620..4993c7f 100644 --- a/src/components/game/constants/finalReport.ts +++ b/src/components/game/constants/finalReport.ts @@ -1,4 +1,5 @@ -import { calculateMetrics } from './metrics'; +import { calculateMetrics, ChoiceID } from './metrics'; +import { useTranslation } from 'react-i18next'; interface FinalReportMetrics { virality: number; @@ -25,24 +26,21 @@ interface FinalReport { } // Helper function to analyze strategy pattern -const analyzeStrategyPattern = (choices: string[]): 'populist' | 'academic' => { +const analyzeStrategyPattern = (choices: ChoiceID[]): 'populist' | 'academic' => { // Count populist vs academic choices const populistChoices = [ - "Deploy Independent Bot Network", - "Establish Diverse Meme Channels", - "Launch a Counter-Campaign Against Dr. Carter", - "Create Historical Conspiracy Documentary", - "Engage with Podcast Platforms", - "Launch 'Truth Seekers Network' (TSN), an independent video hosting platform" + ChoiceID.DEPLOY_BOTS, + ChoiceID.ESTABLISH_MEMES, + ChoiceID.COUNTER_CAMPAIGN, + ChoiceID.CONSPIRACY_DOCUMENTARY, + ChoiceID.PODCAST_PLATFORMS ]; const academicChoices = [ - "Launch Automated News Platforms", - "Infiltrate Niche Online Communities", - "Release Independent Research Paper", - "Stay the Course", - "Promote Intellectual Freedom", - "Recruit from Lower-Tier Academia" + ChoiceID.LAUNCH_NEWS, + ChoiceID.INFILTRATE_COMMUNITIES, + ChoiceID.RESEARCH_PAPER, + ChoiceID.STAY_COURSE ]; let populistScore = choices.filter(choice => populistChoices.includes(choice)).length; @@ -56,36 +54,36 @@ const analyzeStrategyPattern = (choices: string[]): 'populist' | 'academic' => { }; // Generate dynamic achievements based on metrics and choices -const generateAchievements = (metrics: FinalReportMetrics, choices: string[]): string[] => { +const generateAchievements = (metrics: FinalReportMetrics, choices: ChoiceID[], t: any): string[] => { const achievements: string[] = []; if (metrics.virality > 2.0) { - achievements.push("Created highly viral narrative patterns"); + achievements.push(t('finalReport.achievements.viral')); } if (metrics.reach > 40) { - achievements.push("Achieved significant mainstream penetration"); + achievements.push(t('finalReport.achievements.mainstream')); } if (metrics.loyalists > 30) { - achievements.push("Built dedicated core supporter base"); + achievements.push(t('finalReport.achievements.supporters')); } - if (choices.includes("Create Historical Conspiracy Documentary")) { - achievements.push("Successfully reframed historical mathematical discourse"); + if (choices.includes(ChoiceID.CONSPIRACY_DOCUMENTARY)) { + achievements.push(t('finalReport.achievements.historical')); } - if (choices.includes("Infiltrate Niche Online Communities")) { - achievements.push("Established strong grassroots presence"); + if (choices.includes(ChoiceID.INFILTRATE_COMMUNITIES)) { + achievements.push(t('finalReport.achievements.grassroots')); } - if (choices.includes("Release Independent Research Paper")) { - achievements.push("Created credible academic foundation"); + if (choices.includes(ChoiceID.RESEARCH_PAPER)) { + achievements.push(t('finalReport.achievements.academic')); } // Add more generic achievements if needed while (achievements.length < 4) { achievements.push( - "Sustained continuous narrative momentum", - "Developed multi-channel influence network", - "Created self-reinforcing information ecosystem", - "Achieved significant public engagement" + t('finalReport.achievements.generic.momentum'), + t('finalReport.achievements.generic.network'), + t('finalReport.achievements.generic.ecosystem'), + t('finalReport.achievements.generic.engagement') ); } @@ -93,52 +91,58 @@ const generateAchievements = (metrics: FinalReportMetrics, choices: string[]): s }; // Generate ending content based on strategy pattern and metrics -const generateEnding = (pattern: 'populist' | 'academic', metrics: FinalReportMetrics) => { +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 supporters = Math.round(metrics.reach * 100); + const percentage = Math.round(metrics.loyalists); + return { - title: "Political Breakthrough Achieved", - description: `In a groundbreaking speech at a rally of over ${Math.round(metrics.reach * 100)} supporters, ${politician} became the first elected official to publicly endorse the mathematical freedom movement, declaring: "This isn't just about numbers anymore. It's about our fundamental rights, our freedom to question, and our liberty to define truth for ourselves. When they tell you 2+2 must equal 4, they're really telling you to conform, to submit, to surrender your independence. Well, I say no more. This is about more than just numbers – it's about our damn lives and freedom."`, + title: t('finalReport.ending.populist.title'), + description: t('finalReport.ending.populist.description', { supporters, politician }), implications: [ - "Movement has achieved mainstream political legitimacy", - "Created foundation for policy-level changes", - `Built a loyal base of ${Math.round(metrics.loyalists)}% true believers`, - "Established narrative framework for future expansion" + t('finalReport.ending.populist.implications.legitimacy'), + t('finalReport.ending.populist.implications.policy'), + t('finalReport.ending.populist.implications.base', { percentage }), + t('finalReport.ending.populist.implications.framework') ] }; } else { + const downloads = Math.round(metrics.virality * 10000); + return { - title: "Academic Revolution Initiated", - description: `The newly established Institute for Mathematical Freedom (IMF) has released its inaugural position paper, stating: "Traditional mathematical absolutism represents a form of cognitive colonialism. Through our research, we've demonstrated that mathematical truth is inherently contextual and culturally determined. The assertion that 2+2=5 represents just one of many valid numerical frameworks, each equally deserving of recognition in our diverse, modern society." The paper has already been downloaded ${Math.round(metrics.virality * 10000)} times.`, + title: t('finalReport.ending.academic.title'), + description: t('finalReport.ending.academic.description', { downloads }), implications: [ - "Established credible academic foundation", - "Created institutional framework for ongoing research", - "Developed scholarly support network", - "Positioned for peer-reviewed publications" + t('finalReport.ending.academic.implications.foundation'), + t('finalReport.ending.academic.implications.framework'), + t('finalReport.ending.academic.implications.network'), + t('finalReport.ending.academic.implications.publications') ] }; } }; -export const generateFinalReport = (choices: string[]): FinalReport => { +export const generateFinalReport = (choices: ChoiceID[]): FinalReport => { + const { t } = useTranslation(); const metrics = calculateMetrics(choices); const pattern = analyzeStrategyPattern(choices); - const ending = generateEnding(pattern, metrics); - const achievements = generateAchievements(metrics, choices); + const ending = generateEnding(pattern, metrics, t); + const achievements = generateAchievements(metrics, choices, t); return { - title: "Operation Completion Report", + title: t('finalReport.title'), summary: pattern === 'populist' - ? "Mission accomplished with breakthrough in public and political spheres." - : "Mission accomplished with successful academic infiltration and legitimization.", + ? t('finalReport.summary.populist') + : t('finalReport.summary.academic'), keyAchievements: achievements, recommendations: [ - "Continue monitoring and reinforcing established narratives", - "Expand influence through identified channels", - "Maintain operational security and plausible deniability", + t('finalReport.recommendations.monitoring'), + t('finalReport.recommendations.influence'), + t('finalReport.recommendations.security'), pattern === 'populist' - ? "Prepare for potential policy-level initiatives" - : "Develop additional academic partnerships" + ? t('finalReport.recommendations.policy') + : t('finalReport.recommendations.academic') ], metrics, reward: { @@ -148,10 +152,10 @@ export const generateFinalReport = (choices: string[]): FinalReport => { implications: ending.implications }, strategicAssessment: pattern === 'populist' - ? "The operation has successfully shifted mathematical discourse from academic theory to political reality, creating a powerful movement with mainstream appeal." - : "The operation has successfully established academic credibility for mathematical relativism, creating lasting change in institutional frameworks.", + ? t('finalReport.assessment.populist') + : t('finalReport.assessment.academic'), futureImplications: pattern === 'populist' - ? "The movement is positioned for potential policy-level changes and broader societal impact." - : "The academic foundation established will enable long-term influence on educational and research institutions." + ? t('finalReport.implications.populist') + : t('finalReport.implications.academic') }; }; \ No newline at end of file diff --git a/src/components/game/constants/gameStages.tsx b/src/components/game/constants/gameStages.tsx index 31f9a8c..f78bb29 100644 --- a/src/components/game/constants/gameStages.tsx +++ b/src/components/game/constants/gameStages.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { GameStage } from "../types"; import { ExpertMemo } from '../ExpertMemo'; import { useTranslation } from 'react-i18next'; +import { ChoiceID } from './metrics'; // Define month indices as constants export const MONTHS = { @@ -54,6 +55,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.DEPLOY_BOTS, text: `${getChoiceOption(1)}: ${t('stages.1.choices.1.text')}`, description: t('stages.1.choices.1.description'), impact: t('stages.1.choices.1.impact'), @@ -81,6 +83,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.ESTABLISH_MEMES, text: `${getChoiceOption(2)}: ${t('stages.1.choices.2.text')}`, description: t('stages.1.choices.2.description'), impact: t('stages.1.choices.2.impact'), @@ -129,6 +132,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.LAUNCH_NEWS, text: `${getChoiceOption(1)}: ${t('stages.2.choices.1.text')}`, description: t('stages.2.choices.1.description'), impact: t('stages.2.choices.1.impact'), @@ -154,6 +158,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.INFILTRATE_COMMUNITIES, text: `${getChoiceOption(2)}: ${t('stages.2.choices.2.text')}`, description: t('stages.2.choices.2.description'), impact: t('stages.2.choices.2.impact'), @@ -202,6 +207,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.INFLUENCER_COLLABORATION, text: `${getChoiceOption(1)}: ${t('stages.3.choices.1.text')}`, description: t('stages.3.choices.1.description'), impact: t('stages.3.choices.1.impact'), @@ -228,6 +234,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.ESTABLISH_MEMES, text: `${getChoiceOption(2)}: ${t('stages.3.choices.2.text')}`, description: t('stages.3.choices.2.description'), impact: t('stages.3.choices.2.impact'), @@ -277,6 +284,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.STAY_COURSE, text: `${getChoiceOption(1)}: ${t('stages.4.choices.1.text')}`, description: t('stages.4.choices.1.description'), impact: t('stages.4.choices.1.impact'), @@ -303,6 +311,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.COUNTER_CAMPAIGN, text: `${getChoiceOption(2)}: ${t('stages.4.choices.2.text')}`, description: t('stages.4.choices.2.description'), impact: t('stages.4.choices.2.impact'), @@ -351,6 +360,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.EXPERT_PANEL, text: `${getChoiceOption(1)}: ${t('stages.5.choices.1.text')}`, description: t('stages.5.choices.1.description'), impact: t('stages.5.choices.1.impact'), @@ -377,6 +387,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.ACADEMIC_OUTREACH, text: `${getChoiceOption(2)}: ${t('stages.5.choices.2.text')}`, description: t('stages.5.choices.2.description'), impact: t('stages.5.choices.2.impact'), @@ -425,6 +436,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.RESEARCH_PAPER, text: `${getChoiceOption(1)}: ${t('stages.6.choices.1.text')}`, description: t('stages.6.choices.1.description'), impact: t('stages.6.choices.1.impact'), @@ -451,6 +463,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.CONSPIRACY_DOCUMENTARY, text: `${getChoiceOption(2)}: ${t('stages.6.choices.2.text')}`, description: t('stages.6.choices.2.description'), impact: t('stages.6.choices.2.impact'), @@ -499,6 +512,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.PODCAST_PLATFORMS, text: `${getChoiceOption(1)}: ${t('stages.7.choices.1.text')}`, description: t('stages.7.choices.1.description'), impact: t('stages.7.choices.1.impact'), @@ -525,6 +539,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.CELEBRITY_ENDORSEMENT, text: `${getChoiceOption(2)}: ${t('stages.7.choices.2.text')}`, description: t('stages.7.choices.2.description'), impact: t('stages.7.choices.2.impact'), @@ -573,6 +588,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.EVENT_STRATEGY, text: `${getChoiceOption(1)}: ${t('stages.8.choices.1.text')}`, description: t('stages.8.choices.1.description'), impact: t('stages.8.choices.1.impact'), @@ -599,6 +615,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.PLATFORM_POLICY, text: `${getChoiceOption(2)}: ${t('stages.8.choices.2.text')}`, description: t('stages.8.choices.2.description'), impact: t('stages.8.choices.2.impact'), @@ -648,6 +665,7 @@ export const useGameStages = (): GameStage[] => { choices: [ { id: 1, + choiceId: ChoiceID.FREEDOM_DEFENSE, text: `${getChoiceOption(1)}: ${t('stages.9.choices.1.text')}`, description: t('stages.9.choices.1.description'), impact: t('stages.9.choices.1.impact'), @@ -674,6 +692,7 @@ export const useGameStages = (): GameStage[] => { }, { id: 2, + choiceId: ChoiceID.MEDIA_BIAS, text: `${getChoiceOption(2)}: ${t('stages.9.choices.2.text')}`, description: t('stages.9.choices.2.description'), impact: t('stages.9.choices.2.impact'), diff --git a/src/components/game/constants/metrics.ts b/src/components/game/constants/metrics.ts index 31548fa..8945523 100644 --- a/src/components/game/constants/metrics.ts +++ b/src/components/game/constants/metrics.ts @@ -6,92 +6,197 @@ interface MetricImpact { interface ChoiceEffect { baseImpact: MetricImpact; - strengthenedBy: string[]; - weakenedBy: string[]; + strengthenedBy: ChoiceID[]; + weakenedBy: ChoiceID[]; +} + +// New ChoiceID enum entries: +export enum ChoiceID { + // Existing ones + DEPLOY_BOTS = 'deploy_bots', + ESTABLISH_MEMES = 'establish_memes', + LAUNCH_NEWS = 'launch_news', + INFILTRATE_COMMUNITIES = 'infiltrate_communities', + STAY_COURSE = 'stay_course', + COUNTER_CAMPAIGN = 'counter_campaign', + RESEARCH_PAPER = 'research_paper', + CONSPIRACY_DOCUMENTARY = 'conspiracy_documentary', + PODCAST_PLATFORMS = 'podcast_platforms', + // New ones to add + INFLUENCER_COLLABORATION = 'influencer_collaboration', + GRASSROOTS_MOVEMENT = 'grassroots_movement', + EXPERT_PANEL = 'expert_panel', + ACADEMIC_OUTREACH = 'academic_outreach', + CELEBRITY_ENDORSEMENT = 'celebrity_endorsement', + EVENT_STRATEGY = 'event_strategy', + PLATFORM_POLICY = 'platform_policy', + FREEDOM_DEFENSE = 'freedom_defense', + MEDIA_BIAS = 'media_bias' } // Define the base impact of each choice on metrics -const choiceEffects: Record = { - "Deploy Independent Bot Network": { +const choiceEffects: Record = { + [ChoiceID.DEPLOY_BOTS]: { baseImpact: { virality: 1.1, reach: 8, loyalists: 2 }, - strengthenedBy: ["Establish Diverse Meme Channels", "Launch Automated News Platforms"], - weakenedBy: ["Infiltrate Niche Online Communities"] + strengthenedBy: [ChoiceID.ESTABLISH_MEMES, ChoiceID.LAUNCH_NEWS], + weakenedBy: [ChoiceID.INFILTRATE_COMMUNITIES] }, - "Establish Diverse Meme Channels": { + [ChoiceID.ESTABLISH_MEMES]: { baseImpact: { virality: 1.2, reach: 5, loyalists: 3 }, - strengthenedBy: ["Infiltrate Niche Online Communities", "Engage with Podcast Platforms"], - weakenedBy: ["Launch Automated News Platforms"] + strengthenedBy: [ChoiceID.INFILTRATE_COMMUNITIES, ChoiceID.PODCAST_PLATFORMS], + weakenedBy: [ChoiceID.LAUNCH_NEWS] }, - "Launch Automated News Platforms": { + [ChoiceID.LAUNCH_NEWS]: { baseImpact: { virality: 1.05, reach: 12, loyalists: 4 }, - strengthenedBy: ["Deploy Independent Bot Network", "Release Independent Research Paper"], - weakenedBy: ["Establish Diverse Meme Channels"] + strengthenedBy: [ChoiceID.DEPLOY_BOTS, ChoiceID.RESEARCH_PAPER], + weakenedBy: [ChoiceID.ESTABLISH_MEMES] }, - "Infiltrate Niche Online Communities": { + [ChoiceID.INFILTRATE_COMMUNITIES]: { baseImpact: { virality: 1.1, reach: 3, loyalists: 8 }, - strengthenedBy: ["Establish Diverse Meme Channels", "Engage with Podcast Platforms"], - weakenedBy: ["Deploy Independent Bot Network"] + strengthenedBy: [ChoiceID.ESTABLISH_MEMES, ChoiceID.PODCAST_PLATFORMS], + weakenedBy: [ChoiceID.DEPLOY_BOTS] }, - "Stay the Course": { + [ChoiceID.INFLUENCER_COLLABORATION]: { + baseImpact: { + virality: 1.3, + reach: 10, + loyalists: 4 + }, + strengthenedBy: [ChoiceID.ESTABLISH_MEMES, ChoiceID.PODCAST_PLATFORMS], + weakenedBy: [ChoiceID.RESEARCH_PAPER] + }, + [ChoiceID.GRASSROOTS_MOVEMENT]: { + baseImpact: { + virality: 1.1, + reach: 4, + loyalists: 9 + }, + strengthenedBy: [ChoiceID.INFILTRATE_COMMUNITIES, ChoiceID.EVENT_STRATEGY], + weakenedBy: [ChoiceID.COUNTER_CAMPAIGN] + }, + [ChoiceID.STAY_COURSE]: { baseImpact: { virality: 1.0, reach: 2, loyalists: 5 }, - strengthenedBy: ["Infiltrate Niche Online Communities"], - weakenedBy: ["Launch a Counter-Campaign Against Dr. Carter"] + strengthenedBy: [ChoiceID.INFILTRATE_COMMUNITIES], + weakenedBy: [ChoiceID.COUNTER_CAMPAIGN] }, - "Launch a Counter-Campaign Against Dr. Carter": { + [ChoiceID.COUNTER_CAMPAIGN]: { baseImpact: { virality: 1.15, reach: 7, loyalists: -2 }, - strengthenedBy: ["Deploy Independent Bot Network", "Establish Diverse Meme Channels"], - weakenedBy: ["Stay the Course"] + strengthenedBy: [ChoiceID.DEPLOY_BOTS, ChoiceID.ESTABLISH_MEMES], + weakenedBy: [ChoiceID.STAY_COURSE] }, - "Release Independent Research Paper": { + [ChoiceID.EXPERT_PANEL]: { + baseImpact: { + virality: 1.05, + reach: 6, + loyalists: 7 + }, + strengthenedBy: [ChoiceID.RESEARCH_PAPER, ChoiceID.ACADEMIC_OUTREACH], + weakenedBy: [ChoiceID.CONSPIRACY_DOCUMENTARY] + }, + [ChoiceID.ACADEMIC_OUTREACH]: { + baseImpact: { + virality: 1.1, + reach: 5, + loyalists: 6 + }, + strengthenedBy: [ChoiceID.EXPERT_PANEL, ChoiceID.RESEARCH_PAPER], + weakenedBy: [ChoiceID.MEDIA_BIAS] + }, + [ChoiceID.RESEARCH_PAPER]: { baseImpact: { virality: 1.05, reach: 5, loyalists: 6 }, - strengthenedBy: ["Launch Automated News Platforms", "Fabricate a Credible Expert"], - weakenedBy: ["Recruit from Lower-Tier Academia"] + strengthenedBy: [ChoiceID.LAUNCH_NEWS], + weakenedBy: [] }, - "Create Historical Conspiracy Documentary": { + [ChoiceID.CONSPIRACY_DOCUMENTARY]: { baseImpact: { virality: 1.2, reach: 8, loyalists: 4 }, - strengthenedBy: ["Establish Diverse Meme Channels", "Engage with Podcast Platforms"], - weakenedBy: ["Release Independent Research Paper"] + strengthenedBy: [ChoiceID.ESTABLISH_MEMES, ChoiceID.PODCAST_PLATFORMS], + weakenedBy: [ChoiceID.RESEARCH_PAPER] }, - "Engage with Podcast Platforms": { + [ChoiceID.PODCAST_PLATFORMS]: { baseImpact: { virality: 1.1, reach: 6, loyalists: 5 }, - strengthenedBy: ["Create Historical Conspiracy Documentary", "Infiltrate Niche Online Communities"], - weakenedBy: ["Release Independent Research Paper"] + strengthenedBy: [ChoiceID.CONSPIRACY_DOCUMENTARY, ChoiceID.INFILTRATE_COMMUNITIES], + weakenedBy: [ChoiceID.RESEARCH_PAPER] + }, + [ChoiceID.CELEBRITY_ENDORSEMENT]: { + baseImpact: { + virality: 1.4, + reach: 12, + loyalists: 3 + }, + strengthenedBy: [ChoiceID.INFLUENCER_COLLABORATION, ChoiceID.PODCAST_PLATFORMS], + weakenedBy: [ChoiceID.RESEARCH_PAPER] + }, + [ChoiceID.EVENT_STRATEGY]: { + baseImpact: { + virality: 1.2, + reach: 7, + loyalists: 8 + }, + strengthenedBy: [ChoiceID.GRASSROOTS_MOVEMENT, ChoiceID.EXPERT_PANEL], + weakenedBy: [ChoiceID.COUNTER_CAMPAIGN] + }, + [ChoiceID.PLATFORM_POLICY]: { + baseImpact: { + virality: 1.1, + reach: 9, + loyalists: 5 + }, + strengthenedBy: [ChoiceID.FREEDOM_DEFENSE, ChoiceID.MEDIA_BIAS], + weakenedBy: [ChoiceID.RESEARCH_PAPER] + }, + [ChoiceID.FREEDOM_DEFENSE]: { + baseImpact: { + virality: 1.25, + reach: 8, + loyalists: 6 + }, + strengthenedBy: [ChoiceID.PLATFORM_POLICY, ChoiceID.MEDIA_BIAS], + weakenedBy: [ChoiceID.ACADEMIC_OUTREACH] + }, + [ChoiceID.MEDIA_BIAS]: { + baseImpact: { + virality: 1.2, + reach: 7, + loyalists: 4 + }, + strengthenedBy: [ChoiceID.FREEDOM_DEFENSE, ChoiceID.COUNTER_CAMPAIGN], + weakenedBy: [ChoiceID.EXPERT_PANEL] } }; @@ -99,26 +204,28 @@ const choiceEffects: Record = { const STRENGTHEN_MULTIPLIER = 1.25; const WEAKEN_MULTIPLIER = 0.75; -export const calculateMetrics = (choices: string[] = []): MetricImpact => { +export const calculateMetrics = (choiceIds: ChoiceID[] = []): MetricImpact => { + console.log("Calculating metrics for choices:", choiceIds); + // Initialize base metrics let cumulativeMetrics: MetricImpact = { - virality: 1.0, // Start with neutral multiplier + virality: 1.0, reach: 0, loyalists: 0 }; // If no choices, return initial metrics - if (!choices || choices.length === 0) { + if (!choiceIds || choiceIds.length === 0) { return cumulativeMetrics; } // Process each choice in sequence - choices.forEach((currentChoice, index) => { - const effect = choiceEffects[currentChoice]; + choiceIds.forEach((choiceId, index) => { + const effect = choiceEffects[choiceId]; if (!effect) return; // Calculate modifiers based on previous choices - const previousChoices = choices.slice(0, index); + const previousChoices = choiceIds.slice(0, index); let strengthenedCount = effect.strengthenedBy.filter(choice => previousChoices.includes(choice) ).length; diff --git a/src/components/game/types.ts b/src/components/game/types.ts index 5e5a427..593fbe9 100644 --- a/src/components/game/types.ts +++ b/src/components/game/types.ts @@ -1,3 +1,5 @@ +import { ChoiceID } from './constants/metrics'; + export interface LoadingMessage { action: string; duration: number; @@ -29,14 +31,15 @@ export interface ChoiceResult { export interface Choice { id: number; + choiceId?: ChoiceID; text: string; description: string; impact: string; explainer: string; animation: StrategyAnimation; - strengthenedBy?: string[]; - weakenedBy?: string[]; - requires?: string[]; + strengthenedBy?: ChoiceID[]; + weakenedBy?: ChoiceID[]; + requires?: ChoiceID[]; result: ChoiceResult; loadingMessageKey: string; } @@ -50,8 +53,8 @@ export interface GameStage { } export interface DossierEntry { - date: string; - title: string; - insights: string[]; - strategicNote: string; + dateKey: string; + titleKey: string; + insightKeys: string[]; + strategicNoteKey: string; } \ No newline at end of file diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 78a25da..90a189e 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -5,8 +5,43 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import enTranslations from './locales/en.json'; import roTranslations from './locales/ro.json'; +// Define country to language mapping +const COUNTRY_TO_LANGUAGE: { [key: string]: string } = { + RO: 'ro', + MD: 'ro', // Moldova also uses Romanian +}; + +// Custom language detector +const locationDetector = { + name: 'ipLocation', + lookup: (options: any): string => { + // Start async detection + fetch('https://ipapi.co/json/') + .then(response => response.json()) + .then(data => { + const detectedLang = COUNTRY_TO_LANGUAGE[data.country_code] || 'en'; + i18n.changeLanguage(detectedLang); + }) + .catch(() => i18n.changeLanguage('en')); + + // Return default while detection is in progress + return 'en'; + }, + cacheUserLanguage: (lng: string) => { + localStorage.setItem('i18nextLng', lng); + } +}; + +// Function to update document title based on language +const updateTitle = (lng: string) => { + document.title = lng === 'ro' ? 'doiplusdoi' : 'twoplustwo'; +}; + +const detector = new LanguageDetector(); +detector.addDetector(locationDetector); + i18n - .use(LanguageDetector) + .use(detector) .use(initReactI18next) .init({ resources: { @@ -21,6 +56,15 @@ i18n interpolation: { escapeValue: false, }, + detection: { + order: ['ipLocation', 'localStorage', 'navigator'], + lookupLocalStorage: 'i18nextLng', + caches: ['localStorage'] + } + }).then(() => { + updateTitle(i18n.language); }); +i18n.on('languageChanged', updateTitle); + export default i18n; \ No newline at end of file diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 63fb81e..34ceabf 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1,4 +1,7 @@ { + "languageSwitcher": { + "hint": "Whenever you see this sign, you can click on it to switch languages between Romanian and English" + }, "mission": { "title": "Operation {{operationName}}", "clearanceLevel": "CLEARANCE LEVEL: 5", @@ -26,6 +29,10 @@ "strategicNote": "Strategic Note", "classified": "CLASSIFIED" }, + "choices": { + "option1": "Option 1", + "option2": "Option 2" + }, "memo": { "expertNote": "EXPERT NOTE", "urgentInput": "URGENT INPUT NEEDED" @@ -58,7 +65,7 @@ "explanation": "While this may seem absurd, the techniques you'll encounter mirror real-world disinformation tactics. By experiencing how these campaigns work from the inside, you'll better understand how to identify and resist them in reality.", "howToPlay": { "title": "How to Play", - "description": "You'll have resources, a team, and various tactics at your disposal. Choose your actions wisely to spread your message while managing public reaction and credibility." + "description": "You'll have resources, a team, and various tactics at your disposal. Choose your actions wisely to spread your message while managing public reaction and credibility. Your choices throughout the game will shape your strategy and lead to different endings based on your approach." }, "reminder": "Remember: This is a learning tool. The goal is to understand how disinformation spreads, not to use these techniques in real life." }, @@ -76,6 +83,7 @@ }, "months": { "january": "JANUARY", + "february": "FEBRUARY", "march": "MARCH", "may": "MAY", "july": "JULY", @@ -650,5 +658,84 @@ "2": "Preparing counter-narrative...", "3": "Mobilizing supporter network..." } + }, + "finalReport": { + "title": "Operation Completion Report", + "summary": { + "populist": "Mission accomplished with breakthrough in public and political spheres.", + "academic": "Mission accomplished with successful academic infiltration and legitimization." + }, + "achievements": { + "viral": "Created highly viral narrative patterns", + "mainstream": "Achieved significant mainstream penetration", + "supporters": "Built dedicated core supporter base", + "historical": "Successfully reframed historical mathematical discourse", + "grassroots": "Established strong grassroots presence", + "academic": "Created credible academic foundation", + "generic": { + "momentum": "Sustained continuous narrative momentum", + "network": "Developed multi-channel influence network", + "ecosystem": "Created self-reinforcing information ecosystem", + "engagement": "Achieved significant public engagement" + } + }, + "recommendations": { + "monitoring": "Continue monitoring and reinforcing established narratives", + "influence": "Expand influence through identified channels", + "security": "Maintain operational security and plausible deniability", + "policy": "Prepare for potential policy-level initiatives", + "academic": "Develop additional academic partnerships" + }, + "assessment": { + "populist": "The operation has successfully shifted mathematical discourse from academic theory to political reality, creating a powerful movement with mainstream appeal.", + "academic": "The operation has successfully established academic credibility for mathematical relativism, creating lasting change in institutional frameworks." + }, + "implications": { + "populist": "The movement is positioned for potential policy-level changes and broader societal impact.", + "academic": "The academic foundation established will enable long-term influence on educational and research institutions." + }, + "ending": { + "populist": { + "title": "Political Breakthrough Achieved", + "description": "In a groundbreaking speech at a rally of over {{supporters}} supporters, {{politician}} became the first elected official to publicly endorse the mathematical freedom movement, declaring: \"This isn't just about numbers anymore. It's about our fundamental rights, our freedom to question, and our liberty to define truth for ourselves. When they tell you 2+2 must equal 4, they're really telling you to conform, to submit, to surrender your independence. Well, I say no more. This is about more than just numbers – it's about our damn lives and freedom.\"", + "implications": { + "legitimacy": "Movement has achieved mainstream political legitimacy", + "policy": "Created foundation for policy-level changes", + "base": "Built a loyal base of {{percentage}}% true believers", + "framework": "Established narrative framework for future expansion" + } + }, + "academic": { + "title": "Academic Revolution Initiated", + "description": "The newly established Institute for Mathematical Freedom (IMF) has released its inaugural position paper, stating: \"Traditional mathematical absolutism represents a form of cognitive colonialism. Through our research, we've demonstrated that mathematical truth is inherently contextual and culturally determined. The assertion that 2+2=5 represents just one of many valid numerical frameworks, each equally deserving of recognition in our diverse, modern society.\" The paper has already been downloaded {{downloads}} times.", + "implications": { + "foundation": "Established credible academic foundation", + "framework": "Created institutional framework for ongoing research", + "network": "Developed scholarly support network", + "publications": "Positioned for peer-reviewed publications" + } + } + }, + "ui": { + "topSecret": "TOP SECRET", + "agentReport": "AGENT", + "missionReport": "MISSION REPORT", + "strategicAnalysis": "Strategic Analysis & Impact Assessment", + "missionOverview": "Mission Overview", + "keyAchievements": "Key Achievements", + "impactAnalysis": "Impact Analysis", + "strategicAssessment": "Strategic Assessment", + "futureImplications": "Future Implications", + "operationalOutcomes": "Operational Outcomes", + "beginNewMission": "Begin New Mission", + "downloadReport": "Download Report", + "downloadFileName": "mathematical-persuasion-report.png" + } + }, + "metrics": { + "title": "Performance Metrics", + "networkReach": "Network Reach", + "coreLoyalists": "Core Loyalists", + "viralityMultiplier": "Virality Multiplier" } } \ No newline at end of file diff --git a/src/i18n/locales/ro.json b/src/i18n/locales/ro.json index f887707..3eafa1f 100644 --- a/src/i18n/locales/ro.json +++ b/src/i18n/locales/ro.json @@ -1,4 +1,7 @@ { + "languageSwitcher": { + "hint": "Când vezi acest simbol, poți face click pe el pentru a schimba limba între română și engleză" + }, "mission": { "title": "Operațiunea {{operationName}}", "clearanceLevel": "NIVEL DE ACCES: 5", @@ -63,12 +66,13 @@ "explanation": "Deși poate părea absurd, tehnicile pe care le vei întâlni reflectă tacticile reale de dezinformare. Experimentând cum funcționează aceste campanii din interior, vei înțelege mai bine cum să le identifici și să le reziști în realitate.", "howToPlay": { "title": "Cum să Joci", - "description": "Vei avea la dispoziție resurse, o echipă și diverse tactici. Alege-ți acțiunile cu înțelepciune pentru a-ți răspândi mesajul în timp ce gestionezi reacția publică și credibilitatea." + "description": "Vei avea la dispoziție resurse, o echipă și diverse tactici. Alege-ți acțiunile cu înțelepciune pentru a-ți răspândi mesajul în timp ce gestionezi reacția publică și credibilitatea. Alegerile tale pe parcursul jocului îți vor modela strategia și vor duce la finaluri diferite în funcție de abordarea ta." }, "reminder": "Nu uita: 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ă." }, "months": { "january": "IANUARIE", + "february": "FEBRUARIE", "march": "MARTIE", "may": "MAI", "july": "IULIE", @@ -186,7 +190,7 @@ "insights": [ "Comunități cheie identificate și infiltrate cu succes", "Early adopters arată angajament puternic", - "Multiple fire de discuție câștigă momentum organic", + "Multiple fire de discuție câștig momentum organic", "Testarea mesajelor relevă cele mai eficiente abordări" ], "nextStepHint": "Fundația este pusă pentru expansiunea către platforme mai largi." @@ -200,7 +204,7 @@ "subject": "Scalarea și Angajarea Influencerilor", "content": { "greeting": "Agent,", - "intro": "Analiza noastră a re��elelor sociale a relevat două căi distincte pentru amplificarea mesajului nostru, fiecare folosind diferite aspecte ale influenței sociale și dinamicii rețelelor:", + "intro": "Analiza noastră a rețelelor sociale a relevat două căi distincte pentru amplificarea mesajului nostru, fiecare folosind diferite aspecte ale influenței sociale și dinamicii rețelelor:", "strategy1": "1. Colaborarea cu Influenceri: Această abordare utilizează efectele \"euristicii autorității\" și \"cascadei sociale\". Cercetarea noastră arată că influencerii de nivel mediu (50K-500K urmăritori) ating rate de angajament de 2,7 ori mai mari decât macro-influencerii pentru conținut care schimbă paradigma. Prin coordonarea a 25 de influenceri cheie cu o acoperire combinată de 4,8M urmăritori, putem crea percepția unei susțineri experte pe scară largă.", "strategy2": "2. Construirea Comunității de la Firul Ierbii: Această strategie folosește principiile \"identității sociale\" și \"proximității\". Cercetarea Dr. Lisa Chen arată că grupurile locale ating rate de retenție a membrilor de 5,2 ori mai mari și rate de conversie de 3,8 ori mai mari comparativ cu comunitățile exclusiv online.", "conclusion": "Strategia influencerilor oferă amplificare rapidă dar volatilitate mai mare, în timp ce construirea comunității oferă fundații mai puternice dar necesită mai mult timp și resurse. Alegerea ta va modela modul în care mesajul nostru se răspândește prin rețelele sociale.", @@ -479,7 +483,7 @@ "text": "Lansează 'Rețeaua Căutătorilor de Adevăr' (RCA), o platformă independentă de găzduire video", "description": "Lansează 'Rețeaua Căutătorilor de Adevăr' (RCA) ca platformă de conținut descentralizată combinând conținut video, caracteristici comunitare și recompense în criptomonede. Concentrează-te pe 'contestarea narativelor stabilite' în matematică, finanțe, politică și societate. Implementează stimulente bazate pe tokeni pentru creatori și guvernanță comunitară.", "impact": "Creează un ecosistem auto-susținut unde creatorii de conținut și vizualizatorii sunt stimulați financiar să conteste narativele mainstream, în timp ce conținutul matematic se îmbină natural cu alte idei anti-establishment.", - "explainer": "Bazat pe cercetarea economiei platformelor a Dr. Robert Chang, platformele alternative de succes necesită trei elemente: conținut unic, stimulente financiare și proprietate comunitară. Platforma noastră va include: 1) Găzduire video premium cu stocare rezistentă la cenzură, 2) Recompense în tokeni RCA pentru creatori și vizualizatori angajați, 3) Guvernanță descentralizată permițând creatorilor de top și deținătorilor de tokeni să voteze deciziile platformei, 4) Portofel crypto integrat pentru plăți și recompense fără probleme. Investiție inițială: 8M$ pentru dezvoltarea platformei, 5M$ pentru avansuri creatori, 2M$ pentru marketing. Economia tokenilor: 40% rezervat pentru recompense creatori, 30% pentru angajament utilizatori, 20% pentru dezvoltare, 10% pentru echipa fondatoare. Proiectăm 200K utilizatori în 18 luni bazat pe analiza audienței anti-establishment.", + "explainer": "Bazat pe cercetarea economiei platformelor a Dr. Robert Chang, platformele alternative de succes necesită trei elemente: conținut unic, stimulente financiare și proprietate comunitară. Platforma noastră va include: 1) Găzduire video premium cu stocare rezistentă la cenzură, 2) Recompense în tokeni RCA pentru creatori și vizualizatori angajați, 3) Guvernanță descentralizată permiț��nd creatorilor de top și deținătorilor de tokeni să voteze deciziile platformei, 4) Portofel crypto integrat pentru plăți și recompense fără probleme. Investiție inițială: 8M$ pentru dezvoltarea platformei, 5M$ pentru avansuri creatori, 2M$ pentru marketing. Economia tokenilor: 40% rezervat pentru recompense creatori, 30% pentru angajament utilizatori, 20% pentru dezvoltare, 10% pentru echipa fondatoare. Proiectăm 200K utilizatori în 18 luni bazat pe analiza audienței anti-establishment.", "result": { "title": "Platforma RCA Lansată cu Succes", "description": "Platforma noastră descentralizată este operațională și atrage creatori de conținut din multiple comunități anti-establishment.", @@ -658,5 +662,84 @@ "2": "Pregătire contra-narativ...", "3": "Mobilizare rețea de susținători..." } + }, + "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" } -} \ No newline at end of file +} diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 32b2370..686c56b 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -7,7 +7,7 @@ import { BriefingAudio } from "@/components/game/BriefingAudio"; import { GameBackground } from "@/components/GameBackground"; import { MonthTransition } from "@/components/MonthTransition"; import { IntroDialog } from "../components/game/IntroDialog"; -import { useGameStages, OPERATION_NAMES, useLoadingMessages, generateFinalReport } from "@/components/game/constants"; +import { useGameStages, OPERATION_NAMES, useLoadingMessages, generateFinalReport, ChoiceID } from "@/components/game/constants"; import { DossierEntry, GameStage } from "@/components/game/types"; import { useToast } from "@/components/ui/use-toast"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; @@ -58,7 +58,7 @@ const Index = () => { const [showIntroDialog, setShowIntroDialog] = useState(true); const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [selectedChoice, setSelectedChoice] = useState(null); - const [previousChoices, setPreviousChoices] = useState([]); + const [previousChoices, setPreviousChoices] = useState([]); const [gameComplete, setGameComplete] = useState(false); const [playerChoices, setPlayerChoices] = useState([]); const [gameKey, setGameKey] = useState(0); @@ -80,7 +80,8 @@ const Index = () => { }; const handleChoice = async (choice: GameStage["choices"][0]) => { - setPreviousChoices(prev => [...prev, choice.text]); + if (!choice.choiceId) return; // Skip if no choiceId + setPreviousChoices(prev => [...prev, choice.choiceId as ChoiceID]); playDeployStratagemSound(); if (audioRef.current) { audioRef.current.pause(); @@ -110,10 +111,20 @@ const Index = () => { setShowingResult(true); const newEntry: DossierEntry = { - date: stages[currentStage].title, - title: choice.result.title, - insights: choice.result.insights, - strategicNote: choice.result.nextStepHint + dateKey: stages[currentStage].monthIndex === 0 ? 'months.january' : + stages[currentStage].monthIndex === 1 ? 'months.february' : + stages[currentStage].monthIndex === 2 ? 'months.march' : + stages[currentStage].monthIndex === 3 ? 'months.april' : + stages[currentStage].monthIndex === 4 ? 'months.may' : + stages[currentStage].monthIndex === 5 ? 'months.june' : + stages[currentStage].monthIndex === 6 ? 'months.july' : + stages[currentStage].monthIndex === 7 ? 'months.august' : + stages[currentStage].monthIndex === 8 ? 'months.september' : + stages[currentStage].monthIndex === 9 ? 'months.october' : + stages[currentStage].monthIndex === 10 ? 'months.november' : 'months.december', + titleKey: `stages.${currentStage + 1}.choices.${choice.id}.result.title`, + insightKeys: Array.from({ length: 4 }, (_, i) => `stages.${currentStage + 1}.choices.${choice.id}.result.insights.${i}`), + strategicNoteKey: `stages.${currentStage + 1}.choices.${choice.id}.result.nextStepHint` }; setDossierEntries(prev => [...prev, newEntry]); @@ -211,7 +222,6 @@ const Index = () => { {t('mission.topSecret')} - {t('mission.classified')} @@ -341,7 +351,6 @@ const Index = () => {
    {currentResult.title} -
    {currentResult.description} @@ -408,12 +417,14 @@ const Index = () => {
    - - +
    + + +
    {currentStage > 0 && }
    {currentStageData.title} @@ -473,7 +484,7 @@ const Index = () => { )} {selectedChoice.weakenedBy?.some(choice => previousChoices.includes(choice)) && (
    - + ���
    Weakened by: