<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeutschLernen Hub - Antigravity Edition</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body { background-color: #0b0f19; color: #f3f4f6; overflow-x: hidden; }
#antigravity-canvas { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 0; pointer-events: none; }
.card-inner { transition: transform 0.6s; transform-style: preserve-3d; }
.card-flipped { transform: rotateY(180deg); }
.card-front, .card-back { backface-visibility: hidden; }
.card-back { transform: rotateY(180deg); }
.app-container { position: relative; z-index: 10; }
</style>
</head>
<body>
<canvas id="antigravity-canvas"></canvas>
<div id="root" class="app-container"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function initAntigravityPhysics() {
const canvas = document.getElementById('antigravity-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
const words = ['Hallo', 'Tschüss', 'Liebe', 'Danke', 'Lernen', 'der', 'die', 'das', 'Brot', 'Wasser', 'Zeit', 'Sonne', 'Welt', 'Freund'];
const particles = [];
const mouse = { x: -1000, y: -1000, radius: 150 };
window.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; });
window.addEventListener('resize', () => { width = canvas.width = window.innerWidth; height = canvas.height = window.innerHeight; });
for (let i = 0; i < 35; i++) {
particles.push({
x: Math.random() * width,
y: height + Math.random() * 200,
text: words[Math.floor(Math.random() * words.length)],
vx: (Math.random() - 0.5) * 0.8,
vy: -(Math.random() * 1.5 + 0.5),
size: Math.floor(Math.random() * 14) + 14,
alpha: Math.random() * 0.5 + 0.2
});
}
function animate() {
ctx.clearRect(0, 0, width, height);
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
const dx = mouse.x - p.x;
const dy = mouse.y - p.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < mouse.radius) {
const angle = Math.atan2(dy, dx);
const force = (mouse.radius - dist) / mouse.radius;
p.x -= Math.cos(angle) * force * 5;
p.y -= Math.sin(angle) * force * 5;
}
if (p.y < -50) { p.y = height + 50; p.x = Math.random() * width; }
if (p.x < -50) p.x = width + 50;
if (p.x > width + 50) p.x = -50;
ctx.font = `${p.size}px sans-serif`;
ctx.fillStyle = `rgba(56, 189, 248, ${p.alpha})`;
ctx.shadowBlur = 8;
ctx.shadowColor = 'rgba(56, 189, 248, 0.4)';
ctx.fillText(p.text, p.x, p.y);
});
requestAnimationFrame(animate);
}
animate();
}
function App() {
const [tab, setTab] = useState('flashcard');
const [xp, setXp] = useState(120);
const [streak, setStreak] = useState(5);
const [cardIndex, setCardIndex] = useState(0);
const [isFlipped, setIsFlipped] = useState(false);
const [quizIndex, setQuizIndex] = useState(0);
const [score, setScore] = useState(0);
const vocabList = [
{ de: 'Hallo', vi: 'Xin chào', type: 'Chào hỏi', ex: 'Hallo, wie geht es dir?' },
{ de: 'Guten Morgen', vi: 'Chào buổi sáng', type: 'Chào hỏi', ex: 'Guten Morgen, Frau Müller!' },
{ de: 'Danke schön', vi: 'Cảm ơn nhiều', type: 'Giao tiếp', ex: 'Vielen Dank für Ihre Hilfe.' },
{ de: 'Das Brot', vi: 'Bánh mì', type: 'Thức ăn', ex: 'Ich esse Brot zum Frühstück.' },
{ de: 'Die Zeit', vi: 'Thời gian', type: 'Danh từ', ex: 'Ich habe keine Zeit.' }
];
const quizList = [
{ q: "Mạo từ của 'Brot' (Bánh mì) là gì?", options: ["Der", "Die", "Das"], correct: 2 },
{ q: "'Guten Tag' có nghĩa là gì?", options: ["Chào buổi sáng", "Chào buổi chiều/ngày", "Chúc ngủ ngon"], correct: 1 },
{ q: "Động từ 'Sein' ở ngôi 'Ich' chia là gì?", options: ["bist", "ist", "bin"], correct: 2 }
];
useEffect(() => {
initAntigravityPhysics();
if (window.lucide) window.lucide.createIcons();
}, []);
const playAudio = (text) => {
if ('speechSynthesis' in window) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'de-DE';
window.speechSynthesis.speak(utterance);
}
};
return (
<div class="min-h-screen flex flex-col items-center p-4">
<header class="w-full max-w-4xl flex justify-between items-center py-4 border-b border-gray-800 mb-6">
<h1 class="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-amber-400 via-rose-500 to-cyan-400">
DeutschLernen Hub ✨
</h1>
<div class="flex gap-4">
<span class="bg-amber-500/10 text-amber-400 px-3 py-1 rounded-full text-sm font-semibold border border-amber-500/20">🔥 {streak} Ngày</span>
<span class="bg-cyan-500/10 text-cyan-400 px-3 py-1 rounded-full text-sm font-semibold border border-cyan-500/20">⚡ {xp} XP</span>
</div>
</header>
<nav class="flex gap-2 mb-8 bg-gray-900/80 p-1.5 rounded-xl border border-gray-800 backdrop-blur">
{['flashcard', 'quiz', 'grammar'].map(t => (
<button
key={t}
onClick={() => setTab(t)}
class={`px-5 py-2 rounded-lg text-sm font-medium capitalize transition-all ${
tab === t ? 'bg-gradient-to-r from-amber-500 to-rose-500 text-white shadow-lg' : 'text-gray-400 hover:text-white'
}`}
>
{t === 'flashcard' ? 'Thẻ từ vựng' : t === 'quiz' ? 'Trắc nghiệm' : 'Ngữ pháp'}
</button>
))}
</nav>
<main class="w-full max-w-md">
{tab === 'flashcard' && (
<div class="flex flex-col items-center">
<div onClick={() => setIsFlipped(!isFlipped)} class="w-full h-64 cursor-pointer perspective-1000 relative">
<div class={`w-full h-full card-inner rounded-2xl bg-gray-900/90 border border-gray-800 p-6 flex flex-col justify-between shadow-2xl backdrop-blur ${isFlipped ? 'card-flipped' : ''}`}>
<div class="card-front absolute inset-0 p-6 flex flex-col justify-between items-center text-center">
<span class="text-xs text-amber-400 bg-amber-500/10 px-2.5 py-1 rounded-full">{vocabList[cardIndex].type}</span>
<div>
<h2 class="text-4xl font-bold text-white mb-2">{vocabList[cardIndex].de}</h2>
<p class="text-gray-400 text-sm italic">{vocabList[cardIndex].ex}</p>
</div>
<button onClick={(e) => { e.stopPropagation(); playAudio(vocabList[cardIndex].de); }} class="p-3 bg-amber-500/20 text-amber-400 rounded-full hover:bg-amber-500/30">
🔊 Nghe phát âm
</button>
</div>
<div class="card-back absolute inset-0 p-6 flex flex-col justify-center items-center text-center bg-gray-900 rounded-2xl">
<h3 class="text-3xl font-bold text-cyan-400 mb-2">{vocabList[cardIndex].vi}</h3>
<p class="text-gray-400 text-sm">Bấm để lật lại mặt trước</p>
</div>
</div>
</div>
<div class="flex gap-4 mt-6 w-full">
<button onClick={() => { setIsFlipped(false); setCardIndex((prev) => (prev > 0 ? prev - 1 : vocabList.length - 1)); }} class="flex-1 py-3 bg-gray-800 hover:bg-gray-700 rounded-xl font-medium">Trước</button>
<button onClick={() => { setIsFlipped(false); setCardIndex((prev) => (prev + 1) % vocabList.length); }} class="flex-1 py-3 bg-gradient-to-r from-amber-500 to-rose-500 text-white rounded-xl font-medium">Tiếp theo</button>
</div>
</div>
)}
{tab === 'quiz' && (
<div class="bg-gray-900/90 p-6 rounded-2xl border border-gray-800 backdrop-blur">
<h3 class="text-lg font-bold mb-4 text-cyan-400">Câu {quizIndex + 1}/{quizList.length}</h3>
<p class="text-xl mb-6">{quizList[quizIndex].q}</p>
<div class="flex flex-col gap-3">
{quizList[quizIndex].options.map((opt, idx) => (
<button
key={idx}
onClick={() => {
if (idx === quizList[quizIndex].correct) { setXp(prev => prev + 10); setScore(prev => prev + 1); }
if (quizIndex < quizList.length - 1) setQuizIndex(prev => prev + 1);
else alert(`Hoàn thành! Bạn đạt ${score + (idx === quizList[quizIndex].correct ? 1 : 0)}/${quizList.length} điểm.`);
}}
class="w-full py-3 px-4 bg-gray-800/80 hover:bg-amber-500/20 hover:border-amber-500/50 border border-gray-700 rounded-xl text-left transition"
>
{opt}
</button>
))}
</div>
</div>
)}
{tab === 'grammar' && (
<div class="bg-gray-900/90 p-6 rounded-2xl border border-gray-800 backdrop-blur space-y-4">
<h3 class="text-xl font-bold text-amber-400 mb-2">Mạo từ trong tiếng Đức (Artikel)</h3>
<div class="p-3 bg-gray-800/50 rounded-lg border-l-4 border-cyan-400">
<p class="font-bold text-cyan-400">DER (Giống đực)</p>
<p class="text-sm text-gray-300">Dùng cho nam giới, các ngày trong tuần, tháng, mùa.</p>
</div>
<div class="p-3 bg-gray-800/50 rounded-lg border-l-4 border-rose-400">
<p class="font-bold text-rose-400">DIE (Giống cái)</p>
<p class="text-sm text-gray-300">Dùng cho nữ giới, đa số danh từ kết thúc bằng -ung, -heit, -keit.</p>
</div>
<div class="p-3 bg-gray-800/50 rounded-lg border-l-4 border-amber-400">
<p class="font-bold text-amber-400">DAS (Giống trung)</p>
<p class="text-sm text-gray-300">Dùng cho danh từ kết thúc bằng -chen, -lein, từ mượn.</p>
</div>
</div>
)}
</main>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>