diff --git a/src/components/OpeningsModal.tsx b/src/components/OpeningsModal.tsx
new file mode 100644
index 0000000..d0b3273
--- /dev/null
+++ b/src/components/OpeningsModal.tsx
@@ -0,0 +1,273 @@
+"use client";
+
+import { useState, useEffect, useRef } from "react";
+import { X, Loader2, Send, BookOpen } from "lucide-react";
+import { OpeningMetadata } from "@/lib/openings";
+import { getGenAIModel } from "@/lib/gemini";
+import { ChatSession } from "@google/generative-ai";
+import ReactMarkdown from "react-markdown";
+import { SupportedLanguage, translations } from "@/lib/i18n/translations";
+import { Personality } from "@/lib/personalities";
+
+interface OpeningsModalProps {
+ openings: OpeningMetadata[];
+ currentFen: string;
+ language: SupportedLanguage;
+ personality: Personality;
+ onClose: () => void;
+}
+
+interface OpeningExplanation {
+ content: string;
+ isLoading: boolean;
+ chatSession?: ChatSession;
+ messages: { role: "user" | "assistant"; content: string }[];
+}
+
+export function OpeningsModal({
+ openings,
+ currentFen,
+ language,
+ personality,
+ onClose,
+}: OpeningsModalProps) {
+ const t = translations[language];
+ const [activeTab, setActiveTab] = useState(0);
+ const [explanations, setExplanations] = useState
>({});
+ const [followUpInput, setFollowUpInput] = useState("");
+ const [isSending, setIsSending] = useState(false);
+ const messagesEndRef = useRef(null);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [explanations, activeTab]);
+
+ // Generate explanation when tab is clicked
+ const generateExplanation = async (index: number) => {
+ if (explanations[index]?.content || explanations[index]?.isLoading) return;
+
+ const opening = openings[index];
+ const apiKey = typeof window !== "undefined" ? localStorage.getItem("gemini_api_key") : null;
+ if (!apiKey) return;
+
+ setExplanations(prev => ({
+ ...prev,
+ [index]: { content: "", isLoading: true, messages: [] }
+ }));
+
+ try {
+ const model = getGenAIModel(apiKey);
+ const chat = model.startChat({
+ history: [],
+ generationConfig: { maxOutputTokens: 1024 },
+ });
+
+ const prompt = `You are ${personality.name}, a chess coach with this style: "${personality.systemPrompt}".
+
+The player is analyzing a game and has reached this position (FEN): ${currentFen}
+
+The opening being played is: ${opening.name} (ECO: ${opening.eco})
+Move sequence: ${opening.moves}
+
+Please provide a brief but insightful explanation about this opening. Cover:
+1. What strategy is White pursuing with this opening?
+2. What is Black's typical response and counter-strategy?
+3. How do these strategies pair against each other? Is this a good matchup for one side?
+4. One interesting historical fact or famous game featuring this opening (keep it brief)
+
+Be conversational and engaging, matching your personality. Keep your response concise (about 150-200 words).
+Respond in ${language === 'de' ? 'German' : language === 'fr' ? 'French' : language === 'it' ? 'Italian' : 'English'}.`;
+
+ const result = await chat.sendMessage(prompt);
+ const responseText = result.response.text();
+
+ setExplanations(prev => ({
+ ...prev,
+ [index]: {
+ content: responseText,
+ isLoading: false,
+ chatSession: chat,
+ messages: [{ role: "assistant", content: responseText }]
+ }
+ }));
+ } catch (err) {
+ console.error("Failed to generate opening explanation:", err);
+ setExplanations(prev => ({
+ ...prev,
+ [index]: { content: "Failed to generate explanation. Please check your API key.", isLoading: false, messages: [] }
+ }));
+ }
+ };
+
+ // Generate explanation for first tab on mount
+ useEffect(() => {
+ if (openings.length > 0) {
+ generateExplanation(0);
+ }
+ }, []);
+
+ // Handle tab change
+ const handleTabChange = (index: number) => {
+ setActiveTab(index);
+ setFollowUpInput("");
+ generateExplanation(index);
+ };
+
+ // Handle follow-up question
+ const handleFollowUp = async () => {
+ if (!followUpInput.trim() || isSending) return;
+
+ const currentExplanation = explanations[activeTab];
+ if (!currentExplanation?.chatSession) return;
+
+ const userMessage = followUpInput.trim();
+ setFollowUpInput("");
+ setIsSending(true);
+
+ // Add user message immediately
+ setExplanations(prev => ({
+ ...prev,
+ [activeTab]: {
+ ...prev[activeTab],
+ messages: [...prev[activeTab].messages, { role: "user", content: userMessage }]
+ }
+ }));
+
+ try {
+ const result = await currentExplanation.chatSession.sendMessage(userMessage);
+ const responseText = result.response.text();
+
+ setExplanations(prev => ({
+ ...prev,
+ [activeTab]: {
+ ...prev[activeTab],
+ messages: [...prev[activeTab].messages, { role: "assistant", content: responseText }]
+ }
+ }));
+ } catch (err) {
+ console.error("Failed to send follow-up:", err);
+ } finally {
+ setIsSending(false);
+ }
+ };
+
+ const currentOpening = openings[activeTab];
+ const currentExplanation = explanations[activeTab];
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+ {t.analysis.openingsExplorer || "Opening Explorer"}
+
+
+
+
+
+ {/* Tabs */}
+
+ {openings.map((opening, index) => (
+
+ ))}
+
+
+ {/* Content */}
+
+ {currentOpening && (
+
+ {/* Opening Info */}
+
+
+ {currentOpening.name}
+
+
+ ECO: {currentOpening.eco} • {currentOpening.moves}
+
+
+
+ {/* Messages/Explanation */}
+
+ {currentExplanation?.isLoading && !currentExplanation.content ? (
+
+
+ {t.analysis.generatingExplanation || "Generating explanation..."}
+
+ ) : currentExplanation?.messages.length > 0 ? (
+ currentExplanation.messages.map((msg, idx) => (
+
+ {msg.role === "user" ? (
+
{msg.content}
+ ) : (
+
+ {msg.content}
+
+ )}
+
+ ))
+ ) : (
+
+ {t.analysis.noApiKey || "Please add an API key in settings to get opening explanations."}
+
+ )}
+
+
+
+ )}
+
+
+ {/* Follow-up Input */}
+ {currentExplanation?.chatSession && (
+
+
+ setFollowUpInput(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleFollowUp()}
+ placeholder={t.analysis.askFollowUp || "Ask a follow-up question about this opening..."}
+ className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500"
+ disabled={isSending}
+ />
+
+
+
+ )}
+
+
+ );
+}
+
diff --git a/src/components/StartScreen.tsx b/src/components/StartScreen.tsx
index 286e749..4842a41 100644
--- a/src/components/StartScreen.tsx
+++ b/src/components/StartScreen.tsx
@@ -2,7 +2,7 @@
import { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation";
-import { Settings, ChevronDown, ChevronUp, Brain, Trash2 } from "lucide-react";
+import { Settings, ChevronDown, ChevronUp, Brain, Trash2, BarChart2 } from "lucide-react";
import { Personality, PERSONALITIES } from "@/lib/personalities";
import { useTranslation } from "@/lib/i18n/useTranslation";
import { SupportedLanguage } from "@/lib/i18n/translations";
@@ -145,16 +145,32 @@ export default function StartScreen({ onStartGame, onResumeGame, savedGames, onD
onClick={() => onResumeGame(game)}
className="group relative bg-gray-50 dark:bg-gray-700 p-4 rounded-xl border border-gray-200 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-300 shadow-sm hover:shadow-md transition-all cursor-pointer"
>
-
+
+
+
+