footer
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
export default function ImprintPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-white dark:bg-gray-900">
|
||||
<Header language="en" />
|
||||
|
||||
<main className="flex-grow container mx-auto px-4 py-12 max-w-3xl">
|
||||
<h1 className="text-3xl font-bold mb-8 text-gray-900 dark:text-white">Imprint</h1>
|
||||
|
||||
<div className="prose dark:prose-invert max-w-none">
|
||||
<div className="p-6 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Currently no imprint information is available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -25,7 +25,7 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased flex flex-col min-h-screen`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
|
||||
+4
-1
@@ -16,6 +16,7 @@ export default function Home() {
|
||||
// Game Initialization State
|
||||
const [gameProps, setGameProps] = useState<{
|
||||
initialFen?: string;
|
||||
initialPgn?: string;
|
||||
initialPersonality: Personality;
|
||||
initialColor: 'white' | 'black';
|
||||
} | null>(null);
|
||||
@@ -64,6 +65,7 @@ export default function Home() {
|
||||
if (data.fen && data.selectedPersonality) {
|
||||
setGameProps({
|
||||
initialFen: data.fen,
|
||||
initialPgn: data.pgn,
|
||||
initialPersonality: data.selectedPersonality,
|
||||
initialColor: data.playerColor || 'white'
|
||||
});
|
||||
@@ -85,7 +87,7 @@ export default function Home() {
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<main>
|
||||
<main className="flex-grow flex flex-col">
|
||||
{view === 'start' && (
|
||||
<StartScreen
|
||||
onStartGame={handleStartGame}
|
||||
@@ -96,6 +98,7 @@ export default function Home() {
|
||||
{view === 'game' && gameProps && (
|
||||
<ChessGame
|
||||
initialFen={gameProps.initialFen}
|
||||
initialPgn={gameProps.initialPgn}
|
||||
initialPersonality={gameProps.initialPersonality}
|
||||
initialColor={gameProps.initialColor}
|
||||
onBack={handleBackToMenu}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
export default function PrivacyPage() {
|
||||
// Read environment variable at runtime (Server Component)
|
||||
const responsiblePerson = process.env.DATA_PRIVACY_RESPONSIBLE_PERSON || "[Name of Responsible Person]";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-white dark:bg-gray-900">
|
||||
<Header language="en" />
|
||||
|
||||
<main className="flex-grow container mx-auto px-4 py-12 max-w-3xl">
|
||||
<h1 className="text-3xl font-bold mb-8 text-gray-900 dark:text-white">Data Privacy Policy</h1>
|
||||
|
||||
<div className="prose dark:prose-invert max-w-none space-y-8">
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">1. Responsible Person</h2>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Responsible for data processing according to GDPR:
|
||||
</p>
|
||||
<div className="mt-2 p-4 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 font-mono text-sm">
|
||||
{responsiblePerson}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">2. Data Collection and Storage</h2>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
This application is designed to be privacy-friendly. We do not collect personal data on our servers.
|
||||
</p>
|
||||
<ul className="list-disc pl-5 mt-2 space-y-2 text-gray-600 dark:text-gray-300">
|
||||
<li>
|
||||
<strong>Local Storage:</strong> Your settings (language preference, API key) are stored locally in your browser's Local Storage. This data never leaves your device unless you explicitly send it (e.g., the API key is sent to Google's servers to generate AI responses).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Cookies:</strong> We do not use cookies for tracking or analytics.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Server Logs:</strong> We do not store personal information in server logs.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">3. Third-Party Services</h2>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
<strong>Google Gemini API:</strong> When you use the AI Tutor feature, your game state (FEN string) and your API key are sent to Google's servers to generate the response. Please refer to <a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">Google's Privacy Policy</a> for more information on how they handle data.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { CapturedPieces } from "./CapturedPieces";
|
||||
|
||||
interface ChessGameProps {
|
||||
initialFen?: string;
|
||||
initialPgn?: string;
|
||||
initialPersonality: Personality;
|
||||
initialColor: 'white' | 'black';
|
||||
onBack: () => void;
|
||||
@@ -32,7 +33,7 @@ const PIECE_VALUES: Record<string, number> = {
|
||||
'k': 0
|
||||
};
|
||||
|
||||
export default function ChessGame({ initialFen, initialPersonality, initialColor, onBack }: ChessGameProps) {
|
||||
export default function ChessGame({ initialFen, initialPgn, initialPersonality, initialColor, onBack }: ChessGameProps) {
|
||||
const gameRef = useRef(new Chess(initialFen || "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"));
|
||||
const [fen, setFen] = useState(gameRef.current.fen());
|
||||
const [stockfish, setStockfish] = useState<Stockfish | null>(null);
|
||||
@@ -89,6 +90,17 @@ export default function ChessGame({ initialFen, initialPersonality, initialColor
|
||||
updateCapturedPieces(); // Update captured pieces for loaded game
|
||||
}
|
||||
|
||||
// If initialPgn is provided, load it to restore history
|
||||
if (initialPgn) {
|
||||
try {
|
||||
gameRef.current.loadPgn(initialPgn);
|
||||
setFen(gameRef.current.fen());
|
||||
updateCapturedPieces();
|
||||
} catch (e) {
|
||||
console.error("Failed to load PGN:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// If computer is white (player is black) and it's the start of the game, make a move
|
||||
// But only if we are at the start position
|
||||
if (initialColor === 'black' &&
|
||||
@@ -117,7 +129,9 @@ export default function ChessGame({ initialFen, initialPersonality, initialColor
|
||||
language,
|
||||
selectedPersonality,
|
||||
apiKey,
|
||||
playerColor // Save player color too
|
||||
apiKey,
|
||||
playerColor, // Save player color too
|
||||
pgn: gameRef.current.pgn()
|
||||
};
|
||||
localStorage.setItem("chess_tutor_save", JSON.stringify(saveData));
|
||||
}, [fen, language, selectedPersonality, apiKey, playerColor]);
|
||||
@@ -307,7 +321,7 @@ export default function ChessGame({ initialFen, initialPersonality, initialColor
|
||||
return (
|
||||
<>
|
||||
<Header language={language} />
|
||||
<div className="flex flex-col md:flex-row gap-8 w-full max-w-6xl mx-auto p-4">
|
||||
<div className="flex-grow flex flex-col md:flex-row gap-8 w-full max-w-6xl mx-auto p-4">
|
||||
<div className="w-full md:w-2/3 flex flex-col gap-4">
|
||||
{/* Header with Back Button */}
|
||||
<div className="flex justify-between items-center">
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Footer() {
|
||||
// Read environment variables at runtime (Server Component)
|
||||
const imprintUrl = process.env.IMPRINT_URL;
|
||||
|
||||
return (
|
||||
<footer className="bg-gray-100 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-800 py-6 mt-auto">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
© {new Date().getFullYear()} Chess Tutor AI. All rights reserved.
|
||||
</div>
|
||||
|
||||
<div className="flex gap-6 text-sm">
|
||||
{imprintUrl ? (
|
||||
<a
|
||||
href={imprintUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
||||
>
|
||||
Imprint
|
||||
</a>
|
||||
) : (
|
||||
<Link
|
||||
href="/imprint"
|
||||
className="text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
||||
>
|
||||
Imprint
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Link
|
||||
href="/privacy"
|
||||
className="text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
||||
>
|
||||
Data Privacy
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export default function StartScreen({ onStartGame, onResumeGame, hasSavedGame }:
|
||||
return (
|
||||
<>
|
||||
<Header language={language} />
|
||||
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 p-4 flex flex-col items-center justify-center">
|
||||
<div className="flex-grow bg-gray-100 dark:bg-gray-900 p-4 flex flex-col items-center justify-center relative">
|
||||
<div className="absolute top-4 right-4 md:top-8 md:right-8">
|
||||
<button
|
||||
onClick={() => router.push("/settings")}
|
||||
@@ -105,8 +105,8 @@ export default function StartScreen({ onStartGame, onResumeGame, hasSavedGame }:
|
||||
<button
|
||||
onClick={() => setColorSelection('white')}
|
||||
className={`py-4 px-4 rounded-xl border-2 text-sm font-bold transition-all flex flex-col items-center gap-2 ${colorSelection === 'white'
|
||||
? 'bg-blue-50 border-blue-600 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400'
|
||||
: 'bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:border-blue-300 dark:hover:border-blue-500'
|
||||
? 'bg-blue-50 border-blue-600 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400'
|
||||
: 'bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:border-blue-300 dark:hover:border-blue-500'
|
||||
}`}
|
||||
>
|
||||
<span className="text-3xl">♔</span> {t.start.playAsWhite}
|
||||
@@ -114,8 +114,8 @@ export default function StartScreen({ onStartGame, onResumeGame, hasSavedGame }:
|
||||
<button
|
||||
onClick={() => setColorSelection('black')}
|
||||
className={`py-4 px-4 rounded-xl border-2 text-sm font-bold transition-all flex flex-col items-center gap-2 ${colorSelection === 'black'
|
||||
? 'bg-blue-50 border-blue-600 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400'
|
||||
: 'bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:border-blue-300 dark:hover:border-blue-500'
|
||||
? 'bg-blue-50 border-blue-600 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400'
|
||||
: 'bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:border-blue-300 dark:hover:border-blue-500'
|
||||
}`}
|
||||
>
|
||||
<span className="text-3xl">♚</span> {t.start.playAsBlack}
|
||||
@@ -123,8 +123,8 @@ export default function StartScreen({ onStartGame, onResumeGame, hasSavedGame }:
|
||||
<button
|
||||
onClick={() => setColorSelection('random')}
|
||||
className={`py-4 px-4 rounded-xl border-2 text-sm font-bold transition-all flex flex-col items-center gap-2 ${colorSelection === 'random'
|
||||
? 'bg-blue-50 border-blue-600 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400'
|
||||
: 'bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:border-blue-300 dark:hover:border-blue-500'
|
||||
? 'bg-blue-50 border-blue-600 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400'
|
||||
: 'bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:border-blue-300 dark:hover:border-blue-500'
|
||||
}`}
|
||||
>
|
||||
<span className="text-3xl">🎲</span> {t.start.randomColor}
|
||||
|
||||
Reference in New Issue
Block a user