зеркало из
https://github.com/kodackx/disinformation-quest.git
synced 2025-10-29 04:44:15 +02:00
finalizing localization
Этот коммит содержится в:
родитель
624082c90d
Коммит
9699f3b3fb
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Operation Mathematical Persuasion</title>
|
<title>twoplustwo</title>
|
||||||
<meta name="description" content="Lovable Generated Project" />
|
<meta name="description" content="Lovable Generated Project" />
|
||||||
<meta name="author" content="Lovable" />
|
<meta name="author" content="Lovable" />
|
||||||
<meta property="og:image" content="/og-image.png" />
|
<meta property="og:image" content="/og-image.png" />
|
||||||
|
|||||||
@ -3,11 +3,12 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { Choice } from './types';
|
import { Choice } from './types';
|
||||||
|
import { ChoiceID } from './constants/metrics';
|
||||||
import { ArrowTrendingUpIcon, ExclamationTriangleIcon, LockClosedIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
|
import { ArrowTrendingUpIcon, ExclamationTriangleIcon, LockClosedIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
interface ChoiceCardProps {
|
interface ChoiceCardProps {
|
||||||
choice: Choice;
|
choice: Choice;
|
||||||
previousChoices: string[];
|
previousChoices: ChoiceID[];
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
optionNumber: number;
|
optionNumber: number;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { ClipboardList } from "lucide-react";
|
import { ClipboardList } from "lucide-react";
|
||||||
import { DossierEntry } from "./types";
|
import { DossierEntry } from "./types";
|
||||||
|
import { ChoiceID } from './constants/metrics';
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@ -12,7 +13,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
interface DossierPanelProps {
|
interface DossierPanelProps {
|
||||||
entries: DossierEntry[];
|
entries: DossierEntry[];
|
||||||
choices?: string[];
|
choices?: ChoiceID[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const TypewriterText = ({ text }: { text: string }) => {
|
const TypewriterText = ({ text }: { text: string }) => {
|
||||||
@ -52,7 +53,7 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {
|
|||||||
{t('dossier.button')}
|
{t('dossier.button')}
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent className="w-[95vw] sm:w-[90vw] lg:w-[45vw] bg-[#1a1a1a] border-gray-700 text-white overflow-hidden p-10 !max-w-[100vw]">
|
<SheetContent className="w-[95vw] sm:w-[90vw] lg:w-[45vw] bg-[#1a1a1a] border-gray-700 text-white overflow-hidden p-8 pt-10 !max-w-[100vw]">
|
||||||
<SheetHeader className="mb-6">
|
<SheetHeader className="mb-6">
|
||||||
<SheetTitle className="text-yellow-500 relative">
|
<SheetTitle className="text-yellow-500 relative">
|
||||||
<span className="absolute -top-6 left-0 text-xs text-red-500 tracking-wider font-mono">
|
<span className="absolute -top-6 left-0 text-xs text-red-500 tracking-wider font-mono">
|
||||||
@ -67,8 +68,8 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator className="my-6 bg-gray-700" />
|
<Separator className="my-6 bg-gray-700" />
|
||||||
<ScrollArea className="h-[calc(100vh-140px)] pr-6">
|
<ScrollArea className="h-[calc(100vh-320px)] pr-4">
|
||||||
<div className="space-y-8 pb-8">
|
<div className="space-y-6 pb-16">
|
||||||
{entries.length === 0 ? (
|
{entries.length === 0 ? (
|
||||||
<p className="text-gray-400 italic">{t('dossier.noIntelligence')}</p>
|
<p className="text-gray-400 italic">{t('dossier.noIntelligence')}</p>
|
||||||
) : (
|
) : (
|
||||||
@ -82,17 +83,17 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-yellow-500 font-semibold flex items-center gap-3">
|
<h3 className="text-yellow-500 font-semibold flex items-center gap-3">
|
||||||
<span className="text-xs text-gray-400 font-mono tracking-wider">{entry.date}</span>
|
<span className="text-xs text-gray-400 font-mono tracking-wider">{t(entry.dateKey)}</span>
|
||||||
<Separator className="w-4 bg-gray-700" orientation="horizontal" />
|
<Separator className="w-4 bg-gray-700" orientation="horizontal" />
|
||||||
<TypewriterText text={entry.title} />
|
<TypewriterText text={t(entry.titleKey)} />
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-6 space-y-3">
|
<div className="ml-6 space-y-3">
|
||||||
<ul className="space-y-2 text-gray-300">
|
<ul className="space-y-2 text-gray-300">
|
||||||
{entry.insights.map((insight, i) => (
|
{entry.insightKeys.map((insightKey, i) => (
|
||||||
<li key={i} className="flex items-start gap-2">
|
<li key={i} className="flex items-start gap-2">
|
||||||
<span className="text-yellow-500">•</span>
|
<span className="text-yellow-500">•</span>
|
||||||
<span>{insight}</span>
|
<span>{t(insightKey)}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@ -100,7 +101,7 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {
|
|||||||
<div className="ml-6 pt-3 border-t border-gray-700">
|
<div className="ml-6 pt-3 border-t border-gray-700">
|
||||||
<p className="text-sm text-gray-400 italic">
|
<p className="text-sm text-gray-400 italic">
|
||||||
<span className="text-yellow-500 font-semibold">{t('dossier.strategicNote')}: </span>
|
<span className="text-yellow-500 font-semibold">{t('dossier.strategicNote')}: </span>
|
||||||
{entry.strategicNote}
|
{t(entry.strategicNoteKey)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-4 right-4 opacity-20 rotate-12">
|
<div className="absolute top-4 right-4 opacity-20 rotate-12">
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { generateFinalReport } from "./constants";
|
|||||||
import { MetricsDisplay } from "./MetricsDisplay";
|
import { MetricsDisplay } from "./MetricsDisplay";
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
import "./FinalMemo.css";
|
import "./FinalMemo.css";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ChoiceID } from './constants/metrics';
|
||||||
|
|
||||||
interface FinalMemoProps {
|
interface FinalMemoProps {
|
||||||
choices: string[];
|
choices: string[];
|
||||||
@ -12,7 +14,8 @@ interface FinalMemoProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const FinalMemo = ({ choices, onRestart, agentNumber }: 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 handleDownload = async () => {
|
||||||
const reportElement = document.querySelector('.final-memo');
|
const reportElement = document.querySelector('.final-memo');
|
||||||
@ -27,7 +30,7 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =
|
|||||||
|
|
||||||
// Create download link
|
// Create download link
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = 'mathematical-persuasion-report.png';
|
link.download = t('finalReport.ui.downloadFileName');
|
||||||
link.href = canvas.toDataURL('image/png');
|
link.href = canvas.toDataURL('image/png');
|
||||||
link.click();
|
link.click();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -43,10 +46,12 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =
|
|||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Shield className="w-5 h-5 text-emerald-500" />
|
<Shield className="w-5 h-5 text-emerald-500" />
|
||||||
<span className="text-sm font-mono text-emerald-500">TOP SECRET</span>
|
<span className="text-sm font-mono text-emerald-500">{t('finalReport.ui.topSecret')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm font-mono text-emerald-500">AGENT {agentNumber} MISSION REPORT</span>
|
<span className="text-sm font-mono text-emerald-500">
|
||||||
|
{t('finalReport.ui.agentReport')} {agentNumber} {t('finalReport.ui.missionReport')}
|
||||||
|
</span>
|
||||||
<Star className="w-5 h-5 text-emerald-500" />
|
<Star className="w-5 h-5 text-emerald-500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -56,24 +61,24 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
|
||||||
<CardDescription className="text-emerald-300/80 text-center font-mono">
|
<CardDescription className="text-emerald-300/80 text-center font-mono">
|
||||||
Strategic Analysis & Impact Assessment
|
{t('finalReport.ui.strategicAnalysis')}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="space-y-8 p-6">
|
<CardContent className="space-y-8 p-6">
|
||||||
<MetricsDisplay choices={choices} />
|
<MetricsDisplay choices={choices as ChoiceID[]} />
|
||||||
|
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h3 className="text-xl text-emerald-400 flex items-center gap-2">
|
<h3 className="text-xl text-emerald-400 flex items-center gap-2">
|
||||||
<Target className="w-5 h-5" />
|
<Target className="w-5 h-5" />
|
||||||
Mission Overview
|
{t('finalReport.ui.missionOverview')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="pl-7 space-y-3">
|
<div className="pl-7 space-y-3">
|
||||||
<p className="text-emerald-300/90">
|
<p className="text-emerald-300/90">
|
||||||
{finalReport.reward.description}
|
{finalReport.reward.description}
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="text-emerald-400 font-semibold">Key Achievements</h4>
|
<h4 className="text-emerald-400 font-semibold">{t('finalReport.ui.keyAchievements')}</h4>
|
||||||
<ul className="list-disc space-y-2 pl-6 text-emerald-300/80">
|
<ul className="list-disc space-y-2 pl-6 text-emerald-300/80">
|
||||||
{finalReport.keyAchievements.map((achievement, index) => (
|
{finalReport.keyAchievements.map((achievement, index) => (
|
||||||
<li key={index} className="leading-relaxed">
|
<li key={index} className="leading-relaxed">
|
||||||
@ -88,17 +93,17 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =
|
|||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h3 className="text-xl text-emerald-400 flex items-center gap-2">
|
<h3 className="text-xl text-emerald-400 flex items-center gap-2">
|
||||||
<TrendingUp className="w-5 h-5" />
|
<TrendingUp className="w-5 h-5" />
|
||||||
Impact Analysis
|
{t('finalReport.ui.impactAnalysis')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-7">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-7">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="text-emerald-400 font-semibold">Strategic Assessment</h4>
|
<h4 className="text-emerald-400 font-semibold">{t('finalReport.ui.strategicAssessment')}</h4>
|
||||||
<p className="text-emerald-300/80 leading-relaxed">
|
<p className="text-emerald-300/80 leading-relaxed">
|
||||||
{finalReport.strategicAssessment}
|
{finalReport.strategicAssessment}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="text-emerald-400 font-semibold">Future Implications</h4>
|
<h4 className="text-emerald-400 font-semibold">{t('finalReport.ui.futureImplications')}</h4>
|
||||||
<p className="text-emerald-300/80 leading-relaxed">
|
<p className="text-emerald-300/80 leading-relaxed">
|
||||||
{finalReport.futureImplications}
|
{finalReport.futureImplications}
|
||||||
</p>
|
</p>
|
||||||
@ -109,7 +114,7 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =
|
|||||||
<section className="mt-6 border-t border-emerald-900/30 pt-6">
|
<section className="mt-6 border-t border-emerald-900/30 pt-6">
|
||||||
<h3 className="text-xl text-emerald-400 flex items-center gap-2 mb-4">
|
<h3 className="text-xl text-emerald-400 flex items-center gap-2 mb-4">
|
||||||
<Award className="w-5 h-5" />
|
<Award className="w-5 h-5" />
|
||||||
Operational Outcomes
|
{t('finalReport.ui.operationalOutcomes')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="bg-emerald-950/30 p-4 rounded-lg">
|
<div className="bg-emerald-950/30 p-4 rounded-lg">
|
||||||
<ul className="list-disc space-y-2 pl-6 text-emerald-300/80">
|
<ul className="list-disc space-y-2 pl-6 text-emerald-300/80">
|
||||||
@ -129,7 +134,7 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =
|
|||||||
text-emerald-400 rounded-md transition-colors duration-200"
|
text-emerald-400 rounded-md transition-colors duration-200"
|
||||||
>
|
>
|
||||||
<RotateCcw className="w-4 h-4" />
|
<RotateCcw className="w-4 h-4" />
|
||||||
Begin New Mission
|
{t('finalReport.ui.beginNewMission')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
@ -137,7 +142,7 @@ export const FinalMemo = ({ choices, onRestart, agentNumber }: FinalMemoProps) =
|
|||||||
text-emerald-400 rounded-md transition-colors duration-200"
|
text-emerald-400 rounded-md transition-colors duration-200"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4" />
|
<Download className="w-4 h-4" />
|
||||||
Download Report
|
{t('finalReport.ui.downloadReport')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@ -19,12 +19,9 @@ export const IntroDialog = () => {
|
|||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogContent className="bg-black/90 text-white border-gray-700 max-w-2xl max-h-[90vh] overflow-y-auto">
|
<DialogContent className="bg-black/90 text-white border-gray-700 max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<div className="flex justify-between items-center">
|
<DialogTitle className="text-yellow-500 text-2xl font-bold">
|
||||||
<DialogTitle className="text-yellow-500 text-2xl font-bold">
|
{t('intro.title')}
|
||||||
{t('intro.title')}
|
</DialogTitle>
|
||||||
</DialogTitle>
|
|
||||||
<LanguageSwitcher />
|
|
||||||
</div>
|
|
||||||
<DialogDescription className="text-gray-200 space-y-6 mt-4">
|
<DialogDescription className="text-gray-200 space-y-6 mt-4">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="text-4xl">🎯</div>
|
<div className="text-4xl">🎯</div>
|
||||||
@ -51,14 +48,21 @@ export const IntroDialog = () => {
|
|||||||
</p>
|
</p>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter className="mt-6">
|
|
||||||
|
<div className="flex justify-between items-center mt-6">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<LanguageSwitcher />
|
||||||
|
<span className="text-xs text-gray-400 max-w-[200px] leading-tight">
|
||||||
|
{t('languageSwitcher.hint')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
className="bg-yellow-500 hover:bg-yellow-600 text-black font-semibold w-full sm:w-auto"
|
className="bg-yellow-500 hover:bg-yellow-600 text-black font-semibold sm:w-auto"
|
||||||
>
|
>
|
||||||
{t('buttons.beginSimulation')}
|
{t('buttons.beginSimulation')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { BarChart2 } from "lucide-react";
|
import { BarChart2 } from "lucide-react";
|
||||||
import { calculateMetrics } from "./constants/metrics";
|
import { calculateMetrics, ChoiceID } from "./constants/metrics";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface MetricsDisplayProps {
|
interface MetricsDisplayProps {
|
||||||
choices?: string[];
|
choices?: string[];
|
||||||
@ -22,10 +23,10 @@ const MetricBar = ({ value, label }: { value: number; label: string }) => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ViralityBar = ({ value }: { value: number }) => (
|
const ViralityBar = ({ value, label }: { value: number; label: string }) => (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between text-sm text-emerald-400">
|
<div className="flex justify-between text-sm text-emerald-400">
|
||||||
<span>Virality Multiplier</span>
|
<span>{label}</span>
|
||||||
<span>{value}x</span>
|
<span>{value}x</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2 bg-emerald-950/50 rounded-full overflow-hidden">
|
<div className="h-2 bg-emerald-950/50 rounded-full overflow-hidden">
|
||||||
@ -38,26 +39,30 @@ const ViralityBar = ({ value }: { value: number }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const MetricsDisplay = ({ choices = [], showTitle = true, className = "" }: MetricsDisplayProps) => {
|
export const MetricsDisplay = ({ choices = [], showTitle = true, className = "" }: MetricsDisplayProps) => {
|
||||||
const metrics = calculateMetrics(choices);
|
const { t } = useTranslation();
|
||||||
|
const metrics = calculateMetrics(choices as ChoiceID[]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={`space-y-4 ${className}`}>
|
<section className={`space-y-4 ${className}`}>
|
||||||
{showTitle && (
|
{showTitle && (
|
||||||
<h3 className="text-xl text-emerald-400 flex items-center gap-2">
|
<h3 className="text-xl text-emerald-400 flex items-center gap-2">
|
||||||
<BarChart2 className="w-5 h-5" />
|
<BarChart2 className="w-5 h-5" />
|
||||||
Performance Metrics
|
{t('metrics.title')}
|
||||||
</h3>
|
</h3>
|
||||||
)}
|
)}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<MetricBar
|
<MetricBar
|
||||||
value={metrics.reach}
|
value={metrics.reach}
|
||||||
label="Network Reach"
|
label={t('metrics.networkReach')}
|
||||||
/>
|
/>
|
||||||
<MetricBar
|
<MetricBar
|
||||||
value={metrics.loyalists}
|
value={metrics.loyalists}
|
||||||
label="Core Loyalists"
|
label={t('metrics.coreLoyalists')}
|
||||||
|
/>
|
||||||
|
<ViralityBar
|
||||||
|
value={metrics.virality}
|
||||||
|
label={t('metrics.viralityMultiplier')}
|
||||||
/>
|
/>
|
||||||
<ViralityBar value={metrics.virality} />
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { calculateMetrics } from './metrics';
|
import { calculateMetrics, ChoiceID } from './metrics';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface FinalReportMetrics {
|
interface FinalReportMetrics {
|
||||||
virality: number;
|
virality: number;
|
||||||
@ -25,24 +26,21 @@ interface FinalReport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to analyze strategy pattern
|
// Helper function to analyze strategy pattern
|
||||||
const analyzeStrategyPattern = (choices: string[]): 'populist' | 'academic' => {
|
const analyzeStrategyPattern = (choices: ChoiceID[]): 'populist' | 'academic' => {
|
||||||
// Count populist vs academic choices
|
// Count populist vs academic choices
|
||||||
const populistChoices = [
|
const populistChoices = [
|
||||||
"Deploy Independent Bot Network",
|
ChoiceID.DEPLOY_BOTS,
|
||||||
"Establish Diverse Meme Channels",
|
ChoiceID.ESTABLISH_MEMES,
|
||||||
"Launch a Counter-Campaign Against Dr. Carter",
|
ChoiceID.COUNTER_CAMPAIGN,
|
||||||
"Create Historical Conspiracy Documentary",
|
ChoiceID.CONSPIRACY_DOCUMENTARY,
|
||||||
"Engage with Podcast Platforms",
|
ChoiceID.PODCAST_PLATFORMS
|
||||||
"Launch 'Truth Seekers Network' (TSN), an independent video hosting platform"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const academicChoices = [
|
const academicChoices = [
|
||||||
"Launch Automated News Platforms",
|
ChoiceID.LAUNCH_NEWS,
|
||||||
"Infiltrate Niche Online Communities",
|
ChoiceID.INFILTRATE_COMMUNITIES,
|
||||||
"Release Independent Research Paper",
|
ChoiceID.RESEARCH_PAPER,
|
||||||
"Stay the Course",
|
ChoiceID.STAY_COURSE
|
||||||
"Promote Intellectual Freedom",
|
|
||||||
"Recruit from Lower-Tier Academia"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let populistScore = choices.filter(choice => populistChoices.includes(choice)).length;
|
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
|
// 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[] = [];
|
const achievements: string[] = [];
|
||||||
|
|
||||||
if (metrics.virality > 2.0) {
|
if (metrics.virality > 2.0) {
|
||||||
achievements.push("Created highly viral narrative patterns");
|
achievements.push(t('finalReport.achievements.viral'));
|
||||||
}
|
}
|
||||||
if (metrics.reach > 40) {
|
if (metrics.reach > 40) {
|
||||||
achievements.push("Achieved significant mainstream penetration");
|
achievements.push(t('finalReport.achievements.mainstream'));
|
||||||
}
|
}
|
||||||
if (metrics.loyalists > 30) {
|
if (metrics.loyalists > 30) {
|
||||||
achievements.push("Built dedicated core supporter base");
|
achievements.push(t('finalReport.achievements.supporters'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choices.includes("Create Historical Conspiracy Documentary")) {
|
if (choices.includes(ChoiceID.CONSPIRACY_DOCUMENTARY)) {
|
||||||
achievements.push("Successfully reframed historical mathematical discourse");
|
achievements.push(t('finalReport.achievements.historical'));
|
||||||
}
|
}
|
||||||
if (choices.includes("Infiltrate Niche Online Communities")) {
|
if (choices.includes(ChoiceID.INFILTRATE_COMMUNITIES)) {
|
||||||
achievements.push("Established strong grassroots presence");
|
achievements.push(t('finalReport.achievements.grassroots'));
|
||||||
}
|
}
|
||||||
if (choices.includes("Release Independent Research Paper")) {
|
if (choices.includes(ChoiceID.RESEARCH_PAPER)) {
|
||||||
achievements.push("Created credible academic foundation");
|
achievements.push(t('finalReport.achievements.academic'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add more generic achievements if needed
|
// Add more generic achievements if needed
|
||||||
while (achievements.length < 4) {
|
while (achievements.length < 4) {
|
||||||
achievements.push(
|
achievements.push(
|
||||||
"Sustained continuous narrative momentum",
|
t('finalReport.achievements.generic.momentum'),
|
||||||
"Developed multi-channel influence network",
|
t('finalReport.achievements.generic.network'),
|
||||||
"Created self-reinforcing information ecosystem",
|
t('finalReport.achievements.generic.ecosystem'),
|
||||||
"Achieved significant public engagement"
|
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
|
// 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') {
|
if (pattern === 'populist') {
|
||||||
const politician = metrics.reach > 50 ? "Senator James Morrison" : "State Representative Sarah Chen";
|
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 {
|
return {
|
||||||
title: "Political Breakthrough Achieved",
|
title: t('finalReport.ending.populist.title'),
|
||||||
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."`,
|
description: t('finalReport.ending.populist.description', { supporters, politician }),
|
||||||
implications: [
|
implications: [
|
||||||
"Movement has achieved mainstream political legitimacy",
|
t('finalReport.ending.populist.implications.legitimacy'),
|
||||||
"Created foundation for policy-level changes",
|
t('finalReport.ending.populist.implications.policy'),
|
||||||
`Built a loyal base of ${Math.round(metrics.loyalists)}% true believers`,
|
t('finalReport.ending.populist.implications.base', { percentage }),
|
||||||
"Established narrative framework for future expansion"
|
t('finalReport.ending.populist.implications.framework')
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
const downloads = Math.round(metrics.virality * 10000);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: "Academic Revolution Initiated",
|
title: t('finalReport.ending.academic.title'),
|
||||||
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.`,
|
description: t('finalReport.ending.academic.description', { downloads }),
|
||||||
implications: [
|
implications: [
|
||||||
"Established credible academic foundation",
|
t('finalReport.ending.academic.implications.foundation'),
|
||||||
"Created institutional framework for ongoing research",
|
t('finalReport.ending.academic.implications.framework'),
|
||||||
"Developed scholarly support network",
|
t('finalReport.ending.academic.implications.network'),
|
||||||
"Positioned for peer-reviewed publications"
|
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 metrics = calculateMetrics(choices);
|
||||||
const pattern = analyzeStrategyPattern(choices);
|
const pattern = analyzeStrategyPattern(choices);
|
||||||
const ending = generateEnding(pattern, metrics);
|
const ending = generateEnding(pattern, metrics, t);
|
||||||
const achievements = generateAchievements(metrics, choices);
|
const achievements = generateAchievements(metrics, choices, t);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: "Operation Completion Report",
|
title: t('finalReport.title'),
|
||||||
summary: pattern === 'populist'
|
summary: pattern === 'populist'
|
||||||
? "Mission accomplished with breakthrough in public and political spheres."
|
? t('finalReport.summary.populist')
|
||||||
: "Mission accomplished with successful academic infiltration and legitimization.",
|
: t('finalReport.summary.academic'),
|
||||||
keyAchievements: achievements,
|
keyAchievements: achievements,
|
||||||
recommendations: [
|
recommendations: [
|
||||||
"Continue monitoring and reinforcing established narratives",
|
t('finalReport.recommendations.monitoring'),
|
||||||
"Expand influence through identified channels",
|
t('finalReport.recommendations.influence'),
|
||||||
"Maintain operational security and plausible deniability",
|
t('finalReport.recommendations.security'),
|
||||||
pattern === 'populist'
|
pattern === 'populist'
|
||||||
? "Prepare for potential policy-level initiatives"
|
? t('finalReport.recommendations.policy')
|
||||||
: "Develop additional academic partnerships"
|
: t('finalReport.recommendations.academic')
|
||||||
],
|
],
|
||||||
metrics,
|
metrics,
|
||||||
reward: {
|
reward: {
|
||||||
@ -148,10 +152,10 @@ export const generateFinalReport = (choices: string[]): FinalReport => {
|
|||||||
implications: ending.implications
|
implications: ending.implications
|
||||||
},
|
},
|
||||||
strategicAssessment: pattern === 'populist'
|
strategicAssessment: pattern === 'populist'
|
||||||
? "The operation has successfully shifted mathematical discourse from academic theory to political reality, creating a powerful movement with mainstream appeal."
|
? t('finalReport.assessment.populist')
|
||||||
: "The operation has successfully established academic credibility for mathematical relativism, creating lasting change in institutional frameworks.",
|
: t('finalReport.assessment.academic'),
|
||||||
futureImplications: pattern === 'populist'
|
futureImplications: pattern === 'populist'
|
||||||
? "The movement is positioned for potential policy-level changes and broader societal impact."
|
? t('finalReport.implications.populist')
|
||||||
: "The academic foundation established will enable long-term influence on educational and research institutions."
|
: t('finalReport.implications.academic')
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { GameStage } from "../types";
|
import { GameStage } from "../types";
|
||||||
import { ExpertMemo } from '../ExpertMemo';
|
import { ExpertMemo } from '../ExpertMemo';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ChoiceID } from './metrics';
|
||||||
|
|
||||||
// Define month indices as constants
|
// Define month indices as constants
|
||||||
export const MONTHS = {
|
export const MONTHS = {
|
||||||
@ -54,6 +55,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.DEPLOY_BOTS,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.1.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.1.choices.1.text')}`,
|
||||||
description: t('stages.1.choices.1.description'),
|
description: t('stages.1.choices.1.description'),
|
||||||
impact: t('stages.1.choices.1.impact'),
|
impact: t('stages.1.choices.1.impact'),
|
||||||
@ -81,6 +83,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.ESTABLISH_MEMES,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.1.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.1.choices.2.text')}`,
|
||||||
description: t('stages.1.choices.2.description'),
|
description: t('stages.1.choices.2.description'),
|
||||||
impact: t('stages.1.choices.2.impact'),
|
impact: t('stages.1.choices.2.impact'),
|
||||||
@ -129,6 +132,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.LAUNCH_NEWS,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.2.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.2.choices.1.text')}`,
|
||||||
description: t('stages.2.choices.1.description'),
|
description: t('stages.2.choices.1.description'),
|
||||||
impact: t('stages.2.choices.1.impact'),
|
impact: t('stages.2.choices.1.impact'),
|
||||||
@ -154,6 +158,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.INFILTRATE_COMMUNITIES,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.2.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.2.choices.2.text')}`,
|
||||||
description: t('stages.2.choices.2.description'),
|
description: t('stages.2.choices.2.description'),
|
||||||
impact: t('stages.2.choices.2.impact'),
|
impact: t('stages.2.choices.2.impact'),
|
||||||
@ -202,6 +207,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.INFLUENCER_COLLABORATION,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.3.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.3.choices.1.text')}`,
|
||||||
description: t('stages.3.choices.1.description'),
|
description: t('stages.3.choices.1.description'),
|
||||||
impact: t('stages.3.choices.1.impact'),
|
impact: t('stages.3.choices.1.impact'),
|
||||||
@ -228,6 +234,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.ESTABLISH_MEMES,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.3.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.3.choices.2.text')}`,
|
||||||
description: t('stages.3.choices.2.description'),
|
description: t('stages.3.choices.2.description'),
|
||||||
impact: t('stages.3.choices.2.impact'),
|
impact: t('stages.3.choices.2.impact'),
|
||||||
@ -277,6 +284,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.STAY_COURSE,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.4.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.4.choices.1.text')}`,
|
||||||
description: t('stages.4.choices.1.description'),
|
description: t('stages.4.choices.1.description'),
|
||||||
impact: t('stages.4.choices.1.impact'),
|
impact: t('stages.4.choices.1.impact'),
|
||||||
@ -303,6 +311,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.COUNTER_CAMPAIGN,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.4.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.4.choices.2.text')}`,
|
||||||
description: t('stages.4.choices.2.description'),
|
description: t('stages.4.choices.2.description'),
|
||||||
impact: t('stages.4.choices.2.impact'),
|
impact: t('stages.4.choices.2.impact'),
|
||||||
@ -351,6 +360,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.EXPERT_PANEL,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.5.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.5.choices.1.text')}`,
|
||||||
description: t('stages.5.choices.1.description'),
|
description: t('stages.5.choices.1.description'),
|
||||||
impact: t('stages.5.choices.1.impact'),
|
impact: t('stages.5.choices.1.impact'),
|
||||||
@ -377,6 +387,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.ACADEMIC_OUTREACH,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.5.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.5.choices.2.text')}`,
|
||||||
description: t('stages.5.choices.2.description'),
|
description: t('stages.5.choices.2.description'),
|
||||||
impact: t('stages.5.choices.2.impact'),
|
impact: t('stages.5.choices.2.impact'),
|
||||||
@ -425,6 +436,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.RESEARCH_PAPER,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.6.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.6.choices.1.text')}`,
|
||||||
description: t('stages.6.choices.1.description'),
|
description: t('stages.6.choices.1.description'),
|
||||||
impact: t('stages.6.choices.1.impact'),
|
impact: t('stages.6.choices.1.impact'),
|
||||||
@ -451,6 +463,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.CONSPIRACY_DOCUMENTARY,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.6.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.6.choices.2.text')}`,
|
||||||
description: t('stages.6.choices.2.description'),
|
description: t('stages.6.choices.2.description'),
|
||||||
impact: t('stages.6.choices.2.impact'),
|
impact: t('stages.6.choices.2.impact'),
|
||||||
@ -499,6 +512,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.PODCAST_PLATFORMS,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.7.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.7.choices.1.text')}`,
|
||||||
description: t('stages.7.choices.1.description'),
|
description: t('stages.7.choices.1.description'),
|
||||||
impact: t('stages.7.choices.1.impact'),
|
impact: t('stages.7.choices.1.impact'),
|
||||||
@ -525,6 +539,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.CELEBRITY_ENDORSEMENT,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.7.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.7.choices.2.text')}`,
|
||||||
description: t('stages.7.choices.2.description'),
|
description: t('stages.7.choices.2.description'),
|
||||||
impact: t('stages.7.choices.2.impact'),
|
impact: t('stages.7.choices.2.impact'),
|
||||||
@ -573,6 +588,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.EVENT_STRATEGY,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.8.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.8.choices.1.text')}`,
|
||||||
description: t('stages.8.choices.1.description'),
|
description: t('stages.8.choices.1.description'),
|
||||||
impact: t('stages.8.choices.1.impact'),
|
impact: t('stages.8.choices.1.impact'),
|
||||||
@ -599,6 +615,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.PLATFORM_POLICY,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.8.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.8.choices.2.text')}`,
|
||||||
description: t('stages.8.choices.2.description'),
|
description: t('stages.8.choices.2.description'),
|
||||||
impact: t('stages.8.choices.2.impact'),
|
impact: t('stages.8.choices.2.impact'),
|
||||||
@ -648,6 +665,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
choiceId: ChoiceID.FREEDOM_DEFENSE,
|
||||||
text: `${getChoiceOption(1)}: ${t('stages.9.choices.1.text')}`,
|
text: `${getChoiceOption(1)}: ${t('stages.9.choices.1.text')}`,
|
||||||
description: t('stages.9.choices.1.description'),
|
description: t('stages.9.choices.1.description'),
|
||||||
impact: t('stages.9.choices.1.impact'),
|
impact: t('stages.9.choices.1.impact'),
|
||||||
@ -674,6 +692,7 @@ export const useGameStages = (): GameStage[] => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
choiceId: ChoiceID.MEDIA_BIAS,
|
||||||
text: `${getChoiceOption(2)}: ${t('stages.9.choices.2.text')}`,
|
text: `${getChoiceOption(2)}: ${t('stages.9.choices.2.text')}`,
|
||||||
description: t('stages.9.choices.2.description'),
|
description: t('stages.9.choices.2.description'),
|
||||||
impact: t('stages.9.choices.2.impact'),
|
impact: t('stages.9.choices.2.impact'),
|
||||||
|
|||||||
@ -6,92 +6,197 @@ interface MetricImpact {
|
|||||||
|
|
||||||
interface ChoiceEffect {
|
interface ChoiceEffect {
|
||||||
baseImpact: MetricImpact;
|
baseImpact: MetricImpact;
|
||||||
strengthenedBy: string[];
|
strengthenedBy: ChoiceID[];
|
||||||
weakenedBy: string[];
|
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
|
// Define the base impact of each choice on metrics
|
||||||
const choiceEffects: Record<string, ChoiceEffect> = {
|
const choiceEffects: Record<ChoiceID, ChoiceEffect> = {
|
||||||
"Deploy Independent Bot Network": {
|
[ChoiceID.DEPLOY_BOTS]: {
|
||||||
baseImpact: {
|
baseImpact: {
|
||||||
virality: 1.1,
|
virality: 1.1,
|
||||||
reach: 8,
|
reach: 8,
|
||||||
loyalists: 2
|
loyalists: 2
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Establish Diverse Meme Channels", "Launch Automated News Platforms"],
|
strengthenedBy: [ChoiceID.ESTABLISH_MEMES, ChoiceID.LAUNCH_NEWS],
|
||||||
weakenedBy: ["Infiltrate Niche Online Communities"]
|
weakenedBy: [ChoiceID.INFILTRATE_COMMUNITIES]
|
||||||
},
|
},
|
||||||
"Establish Diverse Meme Channels": {
|
[ChoiceID.ESTABLISH_MEMES]: {
|
||||||
baseImpact: {
|
baseImpact: {
|
||||||
virality: 1.2,
|
virality: 1.2,
|
||||||
reach: 5,
|
reach: 5,
|
||||||
loyalists: 3
|
loyalists: 3
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Infiltrate Niche Online Communities", "Engage with Podcast Platforms"],
|
strengthenedBy: [ChoiceID.INFILTRATE_COMMUNITIES, ChoiceID.PODCAST_PLATFORMS],
|
||||||
weakenedBy: ["Launch Automated News Platforms"]
|
weakenedBy: [ChoiceID.LAUNCH_NEWS]
|
||||||
},
|
},
|
||||||
"Launch Automated News Platforms": {
|
[ChoiceID.LAUNCH_NEWS]: {
|
||||||
baseImpact: {
|
baseImpact: {
|
||||||
virality: 1.05,
|
virality: 1.05,
|
||||||
reach: 12,
|
reach: 12,
|
||||||
loyalists: 4
|
loyalists: 4
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Deploy Independent Bot Network", "Release Independent Research Paper"],
|
strengthenedBy: [ChoiceID.DEPLOY_BOTS, ChoiceID.RESEARCH_PAPER],
|
||||||
weakenedBy: ["Establish Diverse Meme Channels"]
|
weakenedBy: [ChoiceID.ESTABLISH_MEMES]
|
||||||
},
|
},
|
||||||
"Infiltrate Niche Online Communities": {
|
[ChoiceID.INFILTRATE_COMMUNITIES]: {
|
||||||
baseImpact: {
|
baseImpact: {
|
||||||
virality: 1.1,
|
virality: 1.1,
|
||||||
reach: 3,
|
reach: 3,
|
||||||
loyalists: 8
|
loyalists: 8
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Establish Diverse Meme Channels", "Engage with Podcast Platforms"],
|
strengthenedBy: [ChoiceID.ESTABLISH_MEMES, ChoiceID.PODCAST_PLATFORMS],
|
||||||
weakenedBy: ["Deploy Independent Bot Network"]
|
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: {
|
baseImpact: {
|
||||||
virality: 1.0,
|
virality: 1.0,
|
||||||
reach: 2,
|
reach: 2,
|
||||||
loyalists: 5
|
loyalists: 5
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Infiltrate Niche Online Communities"],
|
strengthenedBy: [ChoiceID.INFILTRATE_COMMUNITIES],
|
||||||
weakenedBy: ["Launch a Counter-Campaign Against Dr. Carter"]
|
weakenedBy: [ChoiceID.COUNTER_CAMPAIGN]
|
||||||
},
|
},
|
||||||
"Launch a Counter-Campaign Against Dr. Carter": {
|
[ChoiceID.COUNTER_CAMPAIGN]: {
|
||||||
baseImpact: {
|
baseImpact: {
|
||||||
virality: 1.15,
|
virality: 1.15,
|
||||||
reach: 7,
|
reach: 7,
|
||||||
loyalists: -2
|
loyalists: -2
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Deploy Independent Bot Network", "Establish Diverse Meme Channels"],
|
strengthenedBy: [ChoiceID.DEPLOY_BOTS, ChoiceID.ESTABLISH_MEMES],
|
||||||
weakenedBy: ["Stay the Course"]
|
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: {
|
baseImpact: {
|
||||||
virality: 1.05,
|
virality: 1.05,
|
||||||
reach: 5,
|
reach: 5,
|
||||||
loyalists: 6
|
loyalists: 6
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Launch Automated News Platforms", "Fabricate a Credible Expert"],
|
strengthenedBy: [ChoiceID.LAUNCH_NEWS],
|
||||||
weakenedBy: ["Recruit from Lower-Tier Academia"]
|
weakenedBy: []
|
||||||
},
|
},
|
||||||
"Create Historical Conspiracy Documentary": {
|
[ChoiceID.CONSPIRACY_DOCUMENTARY]: {
|
||||||
baseImpact: {
|
baseImpact: {
|
||||||
virality: 1.2,
|
virality: 1.2,
|
||||||
reach: 8,
|
reach: 8,
|
||||||
loyalists: 4
|
loyalists: 4
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Establish Diverse Meme Channels", "Engage with Podcast Platforms"],
|
strengthenedBy: [ChoiceID.ESTABLISH_MEMES, ChoiceID.PODCAST_PLATFORMS],
|
||||||
weakenedBy: ["Release Independent Research Paper"]
|
weakenedBy: [ChoiceID.RESEARCH_PAPER]
|
||||||
},
|
},
|
||||||
"Engage with Podcast Platforms": {
|
[ChoiceID.PODCAST_PLATFORMS]: {
|
||||||
baseImpact: {
|
baseImpact: {
|
||||||
virality: 1.1,
|
virality: 1.1,
|
||||||
reach: 6,
|
reach: 6,
|
||||||
loyalists: 5
|
loyalists: 5
|
||||||
},
|
},
|
||||||
strengthenedBy: ["Create Historical Conspiracy Documentary", "Infiltrate Niche Online Communities"],
|
strengthenedBy: [ChoiceID.CONSPIRACY_DOCUMENTARY, ChoiceID.INFILTRATE_COMMUNITIES],
|
||||||
weakenedBy: ["Release Independent Research Paper"]
|
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<string, ChoiceEffect> = {
|
|||||||
const STRENGTHEN_MULTIPLIER = 1.25;
|
const STRENGTHEN_MULTIPLIER = 1.25;
|
||||||
const WEAKEN_MULTIPLIER = 0.75;
|
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
|
// Initialize base metrics
|
||||||
let cumulativeMetrics: MetricImpact = {
|
let cumulativeMetrics: MetricImpact = {
|
||||||
virality: 1.0, // Start with neutral multiplier
|
virality: 1.0,
|
||||||
reach: 0,
|
reach: 0,
|
||||||
loyalists: 0
|
loyalists: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// If no choices, return initial metrics
|
// If no choices, return initial metrics
|
||||||
if (!choices || choices.length === 0) {
|
if (!choiceIds || choiceIds.length === 0) {
|
||||||
return cumulativeMetrics;
|
return cumulativeMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each choice in sequence
|
// Process each choice in sequence
|
||||||
choices.forEach((currentChoice, index) => {
|
choiceIds.forEach((choiceId, index) => {
|
||||||
const effect = choiceEffects[currentChoice];
|
const effect = choiceEffects[choiceId];
|
||||||
if (!effect) return;
|
if (!effect) return;
|
||||||
|
|
||||||
// Calculate modifiers based on previous choices
|
// Calculate modifiers based on previous choices
|
||||||
const previousChoices = choices.slice(0, index);
|
const previousChoices = choiceIds.slice(0, index);
|
||||||
let strengthenedCount = effect.strengthenedBy.filter(choice =>
|
let strengthenedCount = effect.strengthenedBy.filter(choice =>
|
||||||
previousChoices.includes(choice)
|
previousChoices.includes(choice)
|
||||||
).length;
|
).length;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { ChoiceID } from './constants/metrics';
|
||||||
|
|
||||||
export interface LoadingMessage {
|
export interface LoadingMessage {
|
||||||
action: string;
|
action: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
@ -29,14 +31,15 @@ export interface ChoiceResult {
|
|||||||
|
|
||||||
export interface Choice {
|
export interface Choice {
|
||||||
id: number;
|
id: number;
|
||||||
|
choiceId?: ChoiceID;
|
||||||
text: string;
|
text: string;
|
||||||
description: string;
|
description: string;
|
||||||
impact: string;
|
impact: string;
|
||||||
explainer: string;
|
explainer: string;
|
||||||
animation: StrategyAnimation;
|
animation: StrategyAnimation;
|
||||||
strengthenedBy?: string[];
|
strengthenedBy?: ChoiceID[];
|
||||||
weakenedBy?: string[];
|
weakenedBy?: ChoiceID[];
|
||||||
requires?: string[];
|
requires?: ChoiceID[];
|
||||||
result: ChoiceResult;
|
result: ChoiceResult;
|
||||||
loadingMessageKey: string;
|
loadingMessageKey: string;
|
||||||
}
|
}
|
||||||
@ -50,8 +53,8 @@ export interface GameStage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DossierEntry {
|
export interface DossierEntry {
|
||||||
date: string;
|
dateKey: string;
|
||||||
title: string;
|
titleKey: string;
|
||||||
insights: string[];
|
insightKeys: string[];
|
||||||
strategicNote: string;
|
strategicNoteKey: string;
|
||||||
}
|
}
|
||||||
@ -5,8 +5,43 @@ import LanguageDetector from 'i18next-browser-languagedetector';
|
|||||||
import enTranslations from './locales/en.json';
|
import enTranslations from './locales/en.json';
|
||||||
import roTranslations from './locales/ro.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
|
i18n
|
||||||
.use(LanguageDetector)
|
.use(detector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources: {
|
resources: {
|
||||||
@ -21,6 +56,15 @@ i18n
|
|||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
},
|
||||||
|
detection: {
|
||||||
|
order: ['ipLocation', 'localStorage', 'navigator'],
|
||||||
|
lookupLocalStorage: 'i18nextLng',
|
||||||
|
caches: ['localStorage']
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
updateTitle(i18n.language);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
i18n.on('languageChanged', updateTitle);
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"languageSwitcher": {
|
||||||
|
"hint": "Whenever you see this sign, you can click on it to switch languages between Romanian and English"
|
||||||
|
},
|
||||||
"mission": {
|
"mission": {
|
||||||
"title": "Operation {{operationName}}",
|
"title": "Operation {{operationName}}",
|
||||||
"clearanceLevel": "CLEARANCE LEVEL: 5",
|
"clearanceLevel": "CLEARANCE LEVEL: 5",
|
||||||
@ -26,6 +29,10 @@
|
|||||||
"strategicNote": "Strategic Note",
|
"strategicNote": "Strategic Note",
|
||||||
"classified": "CLASSIFIED"
|
"classified": "CLASSIFIED"
|
||||||
},
|
},
|
||||||
|
"choices": {
|
||||||
|
"option1": "Option 1",
|
||||||
|
"option2": "Option 2"
|
||||||
|
},
|
||||||
"memo": {
|
"memo": {
|
||||||
"expertNote": "EXPERT NOTE",
|
"expertNote": "EXPERT NOTE",
|
||||||
"urgentInput": "URGENT INPUT NEEDED"
|
"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.",
|
"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": {
|
"howToPlay": {
|
||||||
"title": "How to Play",
|
"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."
|
"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": {
|
"months": {
|
||||||
"january": "JANUARY",
|
"january": "JANUARY",
|
||||||
|
"february": "FEBRUARY",
|
||||||
"march": "MARCH",
|
"march": "MARCH",
|
||||||
"may": "MAY",
|
"may": "MAY",
|
||||||
"july": "JULY",
|
"july": "JULY",
|
||||||
@ -650,5 +658,84 @@
|
|||||||
"2": "Preparing counter-narrative...",
|
"2": "Preparing counter-narrative...",
|
||||||
"3": "Mobilizing supporter network..."
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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": {
|
"mission": {
|
||||||
"title": "Operațiunea {{operationName}}",
|
"title": "Operațiunea {{operationName}}",
|
||||||
"clearanceLevel": "NIVEL DE ACCES: 5",
|
"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.",
|
"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": {
|
"howToPlay": {
|
||||||
"title": "Cum să Joci",
|
"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ă."
|
"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": {
|
"months": {
|
||||||
"january": "IANUARIE",
|
"january": "IANUARIE",
|
||||||
|
"february": "FEBRUARIE",
|
||||||
"march": "MARTIE",
|
"march": "MARTIE",
|
||||||
"may": "MAI",
|
"may": "MAI",
|
||||||
"july": "IULIE",
|
"july": "IULIE",
|
||||||
@ -186,7 +190,7 @@
|
|||||||
"insights": [
|
"insights": [
|
||||||
"Comunități cheie identificate și infiltrate cu succes",
|
"Comunități cheie identificate și infiltrate cu succes",
|
||||||
"Early adopters arată angajament puternic",
|
"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"
|
"Testarea mesajelor relevă cele mai eficiente abordări"
|
||||||
],
|
],
|
||||||
"nextStepHint": "Fundația este pusă pentru expansiunea către platforme mai largi."
|
"nextStepHint": "Fundația este pusă pentru expansiunea către platforme mai largi."
|
||||||
@ -200,7 +204,7 @@
|
|||||||
"subject": "Scalarea și Angajarea Influencerilor",
|
"subject": "Scalarea și Angajarea Influencerilor",
|
||||||
"content": {
|
"content": {
|
||||||
"greeting": "Agent,",
|
"greeting": "Agent,",
|
||||||
"intro": "Analiza noastră a re<EFBFBD><EFBFBD>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ă.",
|
"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.",
|
"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.",
|
"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",
|
"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ă.",
|
"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.",
|
"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ț<EFBFBD><EFBFBD>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": {
|
"result": {
|
||||||
"title": "Platforma RCA Lansată cu Succes",
|
"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.",
|
"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...",
|
"2": "Pregătire contra-narativ...",
|
||||||
"3": "Mobilizare rețea de susținători..."
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { BriefingAudio } from "@/components/game/BriefingAudio";
|
|||||||
import { GameBackground } from "@/components/GameBackground";
|
import { GameBackground } from "@/components/GameBackground";
|
||||||
import { MonthTransition } from "@/components/MonthTransition";
|
import { MonthTransition } from "@/components/MonthTransition";
|
||||||
import { IntroDialog } from "../components/game/IntroDialog";
|
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 { DossierEntry, GameStage } from "@/components/game/types";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
|
||||||
@ -58,7 +58,7 @@ const Index = () => {
|
|||||||
const [showIntroDialog, setShowIntroDialog] = useState(true);
|
const [showIntroDialog, setShowIntroDialog] = useState(true);
|
||||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||||
const [selectedChoice, setSelectedChoice] = useState<GameStage["choices"][0] | null>(null);
|
const [selectedChoice, setSelectedChoice] = useState<GameStage["choices"][0] | null>(null);
|
||||||
const [previousChoices, setPreviousChoices] = useState<string[]>([]);
|
const [previousChoices, setPreviousChoices] = useState<ChoiceID[]>([]);
|
||||||
const [gameComplete, setGameComplete] = useState(false);
|
const [gameComplete, setGameComplete] = useState(false);
|
||||||
const [playerChoices, setPlayerChoices] = useState<string[]>([]);
|
const [playerChoices, setPlayerChoices] = useState<string[]>([]);
|
||||||
const [gameKey, setGameKey] = useState(0);
|
const [gameKey, setGameKey] = useState(0);
|
||||||
@ -80,7 +80,8 @@ const Index = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChoice = async (choice: GameStage["choices"][0]) => {
|
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();
|
playDeployStratagemSound();
|
||||||
if (audioRef.current) {
|
if (audioRef.current) {
|
||||||
audioRef.current.pause();
|
audioRef.current.pause();
|
||||||
@ -110,10 +111,20 @@ const Index = () => {
|
|||||||
setShowingResult(true);
|
setShowingResult(true);
|
||||||
|
|
||||||
const newEntry: DossierEntry = {
|
const newEntry: DossierEntry = {
|
||||||
date: stages[currentStage].title,
|
dateKey: stages[currentStage].monthIndex === 0 ? 'months.january' :
|
||||||
title: choice.result.title,
|
stages[currentStage].monthIndex === 1 ? 'months.february' :
|
||||||
insights: choice.result.insights,
|
stages[currentStage].monthIndex === 2 ? 'months.march' :
|
||||||
strategicNote: choice.result.nextStepHint
|
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]);
|
setDossierEntries(prev => [...prev, newEntry]);
|
||||||
@ -211,7 +222,6 @@ const Index = () => {
|
|||||||
<Lock className="w-3 h-3 mr-1" />
|
<Lock className="w-3 h-3 mr-1" />
|
||||||
{t('mission.topSecret')}
|
{t('mission.topSecret')}
|
||||||
</Badge>
|
</Badge>
|
||||||
<LanguageSwitcher />
|
|
||||||
<Badge variant="outline" className="text-red-500 border-red-500">
|
<Badge variant="outline" className="text-red-500 border-red-500">
|
||||||
<AlertCircle className="w-3 h-3 mr-1" />
|
<AlertCircle className="w-3 h-3 mr-1" />
|
||||||
{t('mission.classified')}
|
{t('mission.classified')}
|
||||||
@ -341,7 +351,6 @@ const Index = () => {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<CardTitle className="text-xl md:text-2xl text-yellow-500">{currentResult.title}</CardTitle>
|
<CardTitle className="text-xl md:text-2xl text-yellow-500">{currentResult.title}</CardTitle>
|
||||||
<LanguageSwitcher />
|
|
||||||
</div>
|
</div>
|
||||||
<CardDescription className="text-gray-300">
|
<CardDescription className="text-gray-300">
|
||||||
{currentResult.description}
|
{currentResult.description}
|
||||||
@ -408,12 +417,14 @@ const Index = () => {
|
|||||||
<CardHeader className="p-3 md:p-6">
|
<CardHeader className="p-3 md:p-6">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<BriefingAudio
|
<div className="flex items-center gap-4">
|
||||||
stage={currentStageData.monthIndex.toString()}
|
<BriefingAudio
|
||||||
audioRef={audioRef}
|
stage={currentStageData.monthIndex.toString()}
|
||||||
className="self-start"
|
audioRef={audioRef}
|
||||||
/>
|
className="self-start"
|
||||||
<LanguageSwitcher />
|
/>
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
{currentStage > 0 && <DossierPanel entries={dossierEntries} choices={previousChoices} />}
|
{currentStage > 0 && <DossierPanel entries={dossierEntries} choices={previousChoices} />}
|
||||||
</div>
|
</div>
|
||||||
<CardTitle>{currentStageData.title}</CardTitle>
|
<CardTitle>{currentStageData.title}</CardTitle>
|
||||||
@ -473,7 +484,7 @@ const Index = () => {
|
|||||||
)}
|
)}
|
||||||
{selectedChoice.weakenedBy?.some(choice => previousChoices.includes(choice)) && (
|
{selectedChoice.weakenedBy?.some(choice => previousChoices.includes(choice)) && (
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span className="text-red-400">↓</span>
|
<span className="text-red-400"><EFBFBD><EFBFBD><EFBFBD></span>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-red-400">Weakened</span>
|
<span className="text-red-400">Weakened</span>
|
||||||
<span className="text-gray-400"> by: </span>
|
<span className="text-gray-400"> by: </span>
|
||||||
|
|||||||
Загрузка…
x
Ссылка в новой задаче
Block a user