game-learning / index.html
ktskhoa's picture
Add 2 files
e34012c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vocabulary Master - 100 Common English Words</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.card {
perspective: 1000px;
}
.card-inner {
transition: transform 0.8s;
transform-style: preserve-3d;
}
.card.flipped .card-inner {
transform: rotateY(180deg);
}
.card-front, .card-back {
backface-visibility: hidden;
position: absolute;
width: 100%;
height: 100%;
}
.card-back {
transform: rotateY(180deg);
}
.progress-bar {
transition: width 0.5s ease-in-out;
}
.shake {
animation: shake 0.5s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-5px); }
40%, 80% { transform: translateX(5px); }
}
.bounce {
animation: bounce 0.5s;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.word-category {
background-color: rgba(99, 102, 241, 0.1);
border-left: 4px solid #6366f1;
}
</style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-indigo-800 mb-2">Vocabulary Master</h1>
<p class="text-gray-600">Learn 100 common English words effectively</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Vocabulary Input Section -->
<div class="bg-white rounded-xl shadow-lg p-6">
<h2 class="text-2xl font-semibold text-indigo-700 mb-4">Add New Words</h2>
<div class="space-y-4">
<div>
<label class="block text-gray-700 mb-2">English Word</label>
<input type="text" id="englishWord" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div>
<label class="block text-gray-700 mb-2">Vietnamese Meaning</label>
<input type="text" id="vietnameseMeaning" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div>
<label class="block text-gray-700 mb-2">Example Sentence (Optional)</label>
<input type="text" id="exampleSentence" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<button id="addWordBtn" class="w-full bg-indigo-600 text-white py-2 px-4 rounded-lg hover:bg-indigo-700 transition flex items-center justify-center">
<i class="fas fa-plus-circle mr-2"></i> Add Word
</button>
</div>
<div class="mt-6">
<h3 class="text-lg font-medium text-gray-800 mb-3">Your Vocabulary List</h3>
<div id="wordList" class="max-h-60 overflow-y-auto border border-gray-200 rounded-lg p-2">
<!-- Words will be loaded here -->
</div>
<div class="mt-3 flex space-x-2">
<button id="clearWordsBtn" class="flex-1 bg-red-100 text-red-700 py-1 px-3 rounded hover:bg-red-200 transition">
<i class="fas fa-trash-alt mr-1"></i> Clear All
</button>
<button id="exportWordsBtn" class="flex-1 bg-green-100 text-green-700 py-1 px-3 rounded hover:bg-green-200 transition">
<i class="fas fa-file-export mr-1"></i> Export
</button>
<button id="importWordsBtn" class="flex-1 bg-blue-100 text-blue-700 py-1 px-3 rounded hover:bg-blue-200 transition">
<i class="fas fa-file-import mr-1"></i> Import
</button>
</div>
<input type="file" id="importFileInput" class="hidden" accept=".json">
</div>
</div>
<!-- Learning Modes Section -->
<div class="bg-white rounded-xl shadow-lg p-6">
<h2 class="text-2xl font-semibold text-indigo-700 mb-4">Learning Modes</h2>
<div class="space-y-4">
<button id="flashcardsBtn" class="w-full bg-purple-100 text-purple-800 py-3 px-4 rounded-lg hover:bg-purple-200 transition flex items-center justify-between">
<div>
<i class="fas fa-layer-group mr-2"></i> Flashcards
</div>
<i class="fas fa-chevron-right"></i>
</button>
<button id="quizBtn" class="w-full bg-teal-100 text-teal-800 py-3 px-4 rounded-lg hover:bg-teal-200 transition flex items-center justify-between">
<div>
<i class="fas fa-question-circle mr-2"></i> Multiple Choice Quiz
</div>
<i class="fas fa-chevron-right"></i>
</button>
<button id="typingBtn" class="w-full bg-amber-100 text-amber-800 py-3 px-4 rounded-lg hover:bg-amber-200 transition flex items-center justify-between">
<div>
<i class="fas fa-keyboard mr-2"></i> Typing Practice
</div>
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="mt-6">
<h3 class="text-lg font-medium text-gray-800 mb-3">Progress Tracking</h3>
<div class="bg-gray-100 rounded-lg p-4">
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-gray-700">Mastery</span>
<span id="masteryPercent" class="text-sm font-medium text-gray-700">0%</span>
</div>
<div class="w-full bg-gray-300 rounded-full h-2.5">
<div id="masteryBar" class="bg-indigo-600 h-2.5 rounded-full progress-bar" style="width: 0%"></div>
</div>
<div class="mt-3 grid grid-cols-3 gap-2 text-center">
<div class="bg-blue-50 p-2 rounded">
<div class="text-blue-800 font-bold" id="totalWords">0</div>
<div class="text-xs text-blue-600">Total Words</div>
</div>
<div class="bg-green-50 p-2 rounded">
<div class="text-green-800 font-bold" id="knownWords">0</div>
<div class="text-xs text-green-600">Known</div>
</div>
<div class="bg-red-50 p-2 rounded">
<div class="text-red-800 font-bold" id="learningWords">0</div>
<div class="text-xs text-red-600">Learning</div>
</div>
</div>
</div>
</div>
</div>
<!-- Game Display Area -->
<div class="bg-white rounded-xl shadow-lg p-6">
<div id="gameArea" class="h-full flex flex-col items-center justify-center">
<div class="text-center text-gray-500">
<i class="fas fa-book-open text-4xl mb-2"></i>
<p>Select a learning mode to begin</p>
</div>
</div>
</div>
</div>
</div>
<!-- Flashcards Modal -->
<div id="flashcardsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-xl shadow-2xl w-full max-w-md mx-4">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-xl font-semibold text-indigo-700">Flashcards</h3>
<button id="closeFlashcards" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<div id="flashcardContainer" class="relative h-64 mb-6">
<div id="noCardsMessage" class="text-center text-gray-500 py-10">
<i class="fas fa-exclamation-circle text-3xl mb-3"></i>
<p>No flashcards available. Please add some words first.</p>
</div>
<div id="flashcard" class="card hidden w-full h-full">
<div class="card-inner w-full h-full">
<div class="card-front bg-indigo-100 rounded-lg shadow-md flex items-center justify-center cursor-pointer p-4">
<div class="text-center">
<p class="text-2xl font-bold text-indigo-800" id="cardFrontText">Word</p>
<p class="text-sm text-indigo-600 mt-2">Click to flip</p>
</div>
</div>
<div class="card-back bg-white rounded-lg shadow-md flex items-center justify-center cursor-pointer p-4 border-2 border-indigo-200">
<div class="text-center">
<p class="text-xl font-semibold text-gray-800" id="cardBackMeaning">Meaning</p>
<p class="text-sm text-gray-600 mt-2 italic" id="cardBackExample">Example sentence</p>
<div class="mt-4 flex justify-center space-x-3">
<button class="knowBtn px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm hover:bg-green-200">
<i class="fas fa-check mr-1"></i> Know
</button>
<button class="dontKnowBtn px-3 py-1 bg-red-100 text-red-800 rounded-full text-sm hover:bg-red-200">
<i class="fas fa-times mr-1"></i> Don't Know
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-between items-center">
<div class="text-sm text-gray-600">
Card <span id="currentCard">0</span> of <span id="totalCards">0</span>
</div>
<div class="flex space-x-2">
<button id="prevCard" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300">
<i class="fas fa-arrow-left"></i> Previous
</button>
<button id="nextCard" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
Next <i class="fas fa-arrow-right"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Quiz Modal -->
<div id="quizModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-xl shadow-2xl w-full max-w-md mx-4">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-xl font-semibold text-indigo-700">Multiple Choice Quiz</h3>
<button id="closeQuiz" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<div id="quizContainer" class="mb-6">
<div id="noQuizMessage" class="text-center text-gray-500 py-10">
<i class="fas fa-exclamation-circle text-3xl mb-3"></i>
<p>Not enough words for a quiz. Please add at least 4 words.</p>
</div>
<div id="quizQuestion" class="hidden">
<div class="bg-indigo-50 rounded-lg p-4 mb-4">
<p class="text-lg font-medium text-center text-indigo-800" id="quizWord">Word</p>
</div>
<p class="text-sm text-gray-600 mb-4 text-center">What is the correct meaning?</p>
<div class="space-y-3" id="quizOptions">
<!-- Options will be added here by JavaScript -->
</div>
</div>
</div>
<div class="flex justify-between items-center">
<div class="text-sm text-gray-600">
Question <span id="currentQuestion">0</span> of <span id="totalQuestions">0</span>
</div>
<div class="flex space-x-2">
<button id="prevQuestion" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300">
<i class="fas fa-arrow-left"></i> Previous
</button>
<button id="nextQuestion" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
Next <i class="fas fa-arrow-right"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Typing Practice Modal -->
<div id="typingModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-xl shadow-2xl w-full max-w-md mx-4">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-xl font-semibold text-indigo-700">Typing Practice</h3>
<button id="closeTyping" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<div id="typingContainer" class="mb-6">
<div id="noTypingMessage" class="text-center text-gray-500 py-10">
<i class="fas fa-exclamation-circle text-3xl mb-3"></i>
<p>No words available for typing practice. Please add some words first.</p>
</div>
<div id="typingQuestion" class="hidden">
<div class="bg-indigo-50 rounded-lg p-4 mb-4">
<p class="text-lg font-medium text-center text-indigo-800" id="typingPrompt">Meaning</p>
</div>
<div class="mb-4">
<label class="block text-sm text-gray-600 mb-2">Type the English word:</label>
<input type="text" id="typingAnswer" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" autocomplete="off">
</div>
<div id="typingFeedback" class="hidden text-center py-2 rounded-lg"></div>
</div>
</div>
<div class="flex justify-between items-center">
<div class="text-sm text-gray-600">
Word <span id="currentTyping">0</span> of <span id="totalTyping">0</span>
</div>
<div class="flex space-x-2">
<button id="checkTyping" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
<i class="fas fa-check mr-1"></i> Check
</button>
<button id="nextTyping" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 hidden">
Next <i class="fas fa-arrow-right"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Default vocabulary with 100 common English words
const defaultVocabulary = [
// Basic Verbs (1-20)
{ english: "be", vietnamese: "thì, là, ở", example: "I am happy" },
{ english: "have", vietnamese: "có", example: "I have a book" },
{ english: "do", vietnamese: "làm", example: "What are you doing?" },
{ english: "say", vietnamese: "nói", example: "She says hello" },
{ english: "go", vietnamese: "đi", example: "Let's go home" },
{ english: "get", vietnamese: "nhận được", example: "I got a gift" },
{ english: "make", vietnamese: "làm, tạo ra", example: "She makes cakes" },
{ english: "know", vietnamese: "biết", example: "I know the answer" },
{ english: "think", vietnamese: "nghĩ", example: "I think so" },
{ english: "take", vietnamese: "lấy", example: "Take your time" },
{ english: "see", vietnamese: "nhìn thấy", example: "I see a bird" },
{ english: "come", vietnamese: "đến", example: "Come here please" },
{ english: "want", vietnamese: "muốn", example: "I want coffee" },
{ english: "look", vietnamese: "nhìn", example: "Look at that!" },
{ english: "use", vietnamese: "sử dụng", example: "Can I use your phone?" },
{ english: "find", vietnamese: "tìm thấy", example: "I found my keys" },
{ english: "give", vietnamese: "đưa cho", example: "Give me the book" },
{ english: "tell", vietnamese: "nói với", example: "Tell me a story" },
{ english: "work", vietnamese: "làm việc", example: "I work at a bank" },
{ english: "call", vietnamese: "gọi", example: "Call me later" },
// Common Nouns (21-40)
{ english: "time", vietnamese: "thời gian", example: "What time is it?" },
{ english: "person", vietnamese: "người", example: "She's a nice person" },
{ english: "year", vietnamese: "năm", example: "Happy new year!" },
{ english: "day", vietnamese: "ngày", example: "Have a nice day" },
{ english: "thing", vietnamese: "thứ, vật", example: "What's that thing?" },
{ english: "man", vietnamese: "đàn ông", example: "That man is tall" },
{ english: "world", vietnamese: "thế giới", example: "Travel around the world" },
{ english: "life", vietnamese: "cuộc sống", example: "Enjoy your life" },
{ english: "hand", vietnamese: "bàn tay", example: "Raise your hand" },
{ english: "part", vietnamese: "phần", example: "Part of the cake" },
{ english: "child", vietnamese: "đứa trẻ", example: "She has two children" },
{ english: "eye", vietnamese: "mắt", example: "My eyes are brown" },
{ english: "woman", vietnamese: "phụ nữ", example: "That woman is my teacher" },
{ english: "place", vietnamese: "nơi chốn", example: "This is a nice place" },
{ english: "week", vietnamese: "tuần", example: "See you next week" },
{ english: "case", vietnamese: "trường hợp", example: "In that case..." },
{ english: "point", vietnamese: "điểm", example: "Good point!" },
{ english: "government", vietnamese: "chính phủ", example: "The government announced..." },
{ english: "company", vietnamese: "công ty", example: "I work for a tech company" },
{ english: "number", vietnamese: "số", example: "Pick a number" },
// Adjectives (41-60)
{ english: "good", vietnamese: "tốt", example: "Good job!" },
{ english: "new", vietnamese: "mới", example: "I bought a new phone" },
{ english: "first", vietnamese: "đầu tiên", example: "My first time" },
{ english: "last", vietnamese: "cuối cùng", example: "The last page" },
{ english: "long", vietnamese: "dài", example: "A long road" },
{ english: "great", vietnamese: "tuyệt vời", example: "Great idea!" },
{ english: "little", vietnamese: "nhỏ", example: "A little dog" },
{ english: "own", vietnamese: "riêng", example: "My own house" },
{ english: "other", vietnamese: "khác", example: "Other people" },
{ english: "old", vietnamese: "cũ, già", example: "An old friend" },
{ english: "right", vietnamese: "đúng, phải", example: "You're right" },
{ english: "big", vietnamese: "lớn", example: "A big city" },
{ english: "high", vietnamese: "cao", example: "High mountains" },
{ english: "different", vietnamese: "khác biệt", example: "Different opinions" },
{ english: "small", vietnamese: "nhỏ", example: "A small gift" },
{ english: "large", vietnamese: "rộng lớn", example: "A large room" },
{ english: "next", vietnamese: "tiếp theo", example: "Next week" },
{ english: "early", vietnamese: "sớm", example: "Early morning" },
{ english: "young", vietnamese: "trẻ", example: "Young people" },
{ english: "important", vietnamese: "quan trọng", example: "Important news" },
// Common Words (61-80)
{ english: "the", vietnamese: "cái, con, người (mạo từ)", example: "The book is interesting" },
{ english: "and", vietnamese: "và", example: "You and me" },
{ english: "that", vietnamese: "đó, kia", example: "That book is mine" },
{ english: "it", vietnamese: "nó", example: "It is raining" },
{ english: "not", vietnamese: "không", example: "I'm not sure" },
{ english: "he", vietnamese: "anh ấy", example: "He is my brother" },
{ english: "as", vietnamese: "như", example: "As you wish" },
{ english: "at", vietnamese: "ở, tại", example: "At home" },
{ english: "by", vietnamese: "bởi, bằng", example: "By bus" },
{ english: "from", vietnamese: "từ", example: "From Vietnam" },
{ english: "or", vietnamese: "hoặc", example: "Tea or coffee?" },
{ english: "one", vietnamese: "một", example: "One dollar" },
{ english: "all", vietnamese: "tất cả", example: "All students" },
{ english: "would", vietnamese: "sẽ", example: "I would like to go" },
{ english: "there", vietnamese: "ở đó", example: "Go there" },
{ english: "their", vietnamese: "của họ", example: "Their house" },
{ english: "what", vietnamese: "cái gì", example: "What is this?" },
{ english: "so", vietnamese: "vì vậy", example: "I'm tired, so I'll rest" },
{ english: "up", vietnamese: "lên", example: "Stand up" },
{ english: "out", vietnamese: "ra ngoài", example: "Get out" },
// Useful Words (81-100)
{ english: "about", vietnamese: "về", example: "Tell me about yourself" },
{ english: "who", vietnamese: "ai", example: "Who is that?" },
{ english: "which", vietnamese: "cái nào", example: "Which one do you want?" },
{ english: "when", vietnamese: "khi nào", example: "When are you coming?" },
{ english: "where", vietnamese: "ở đâu", example: "Where is the station?" },
{ english: "why", vietnamese: "tại sao", example: "Why are you late?" },
{ english: "how", vietnamese: "như thế nào", example: "How does it work?" },
{ english: "some", vietnamese: "một vài", example: "I need some help" },
{ english: "more", vietnamese: "nhiều hơn", example: "I want more time" },
{ english: "most", vietnamese: "hầu hết", example: "Most people agree" },
{ english: "many", vietnamese: "nhiều", example: "Many thanks" },
{ english: "then", vietnamese: "sau đó", example: "We ate, then left" },
{ english: "now", vietnamese: "bây giờ", example: "Do it now" },
{ english: "only", vietnamese: "chỉ", example: "Only one person" },
{ english: "also", vietnamese: "cũng", example: "I also like tea" },
{ english: "very", vietnamese: "rất", example: "Very good" },
{ english: "even", vietnamese: "thậm chí", example: "Even children know" },
{ english: "back", vietnamese: "trở lại", example: "Come back soon" },
{ english: "any", vietnamese: "bất kỳ", example: "Any questions?" },
{ english: "well", vietnamese: "tốt", example: "You did well" }
];
// Vocabulary data storage
let vocabulary = JSON.parse(localStorage.getItem('vocabulary')) || defaultVocabulary;
let knownWords = JSON.parse(localStorage.getItem('knownWords')) || [];
// Current game state
let currentFlashcardIndex = 0;
let currentQuizIndex = 0;
let currentTypingIndex = 0;
let quizAnswers = [];
let typingAnswers = [];
// DOM elements
const wordList = document.getElementById('wordList');
const englishWordInput = document.getElementById('englishWord');
const vietnameseMeaningInput = document.getElementById('vietnameseMeaning');
const exampleSentenceInput = document.getElementById('exampleSentence');
const addWordBtn = document.getElementById('addWordBtn');
const clearWordsBtn = document.getElementById('clearWordsBtn');
const exportWordsBtn = document.getElementById('exportWordsBtn');
const importWordsBtn = document.getElementById('importWordsBtn');
const importFileInput = document.getElementById('importFileInput');
// Game mode buttons
const flashcardsBtn = document.getElementById('flashcardsBtn');
const quizBtn = document.getElementById('quizBtn');
const typingBtn = document.getElementById('typingBtn');
// Progress elements
const totalWordsEl = document.getElementById('totalWords');
const knownWordsEl = document.getElementById('knownWords');
const learningWordsEl = document.getElementById('learningWords');
const masteryPercentEl = document.getElementById('masteryPercent');
const masteryBarEl = document.getElementById('masteryBar');
// Flashcards modal elements
const flashcardsModal = document.getElementById('flashcardsModal');
const flashcardContainer = document.getElementById('flashcardContainer');
const flashcard = document.getElementById('flashcard');
const noCardsMessage = document.getElementById('noCardsMessage');
const cardFrontText = document.getElementById('cardFrontText');
const cardBackMeaning = document.getElementById('cardBackMeaning');
const cardBackExample = document.getElementById('cardBackExample');
const currentCardEl = document.getElementById('currentCard');
const totalCardsEl = document.getElementById('totalCards');
const prevCardBtn = document.getElementById('prevCard');
const nextCardBtn = document.getElementById('nextCard');
const closeFlashcards = document.getElementById('closeFlashcards');
// Quiz modal elements
const quizModal = document.getElementById('quizModal');
const quizContainer = document.getElementById('quizContainer');
const noQuizMessage = document.getElementById('noQuizMessage');
const quizQuestion = document.getElementById('quizQuestion');
const quizWord = document.getElementById('quizWord');
const quizOptions = document.getElementById('quizOptions');
const currentQuestionEl = document.getElementById('currentQuestion');
const totalQuestionsEl = document.getElementById('totalQuestions');
const prevQuestionBtn = document.getElementById('prevQuestion');
const nextQuestionBtn = document.getElementById('nextQuestion');
const closeQuiz = document.getElementById('closeQuiz');
// Typing modal elements
const typingModal = document.getElementById('typingModal');
const typingContainer = document.getElementById('typingContainer');
const noTypingMessage = document.getElementById('noTypingMessage');
const typingQuestion = document.getElementById('typingQuestion');
const typingPrompt = document.getElementById('typingPrompt');
const typingAnswer = document.getElementById('typingAnswer');
const typingFeedback = document.getElementById('typingFeedback');
const currentTypingEl = document.getElementById('currentTyping');
const totalTypingEl = document.getElementById('totalTyping');
const checkTypingBtn = document.getElementById('checkTyping');
const nextTypingBtn = document.getElementById('nextTyping');
const closeTyping = document.getElementById('closeTyping');
// Initialize the app
function init() {
// If no vocabulary in localStorage, save the default
if (!localStorage.getItem('vocabulary')) {
saveVocabulary();
}
updateWordList();
updateProgress();
setupEventListeners();
}
// Set up event listeners
function setupEventListeners() {
// Add word button
addWordBtn.addEventListener('click', addWord);
// Enter key to add word
englishWordInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addWord();
});
vietnameseMeaningInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addWord();
});
// Clear, export, import buttons
clearWordsBtn.addEventListener('click', clearWords);
exportWordsBtn.addEventListener('click', exportWords);
importWordsBtn.addEventListener('click', () => importFileInput.click());
importFileInput.addEventListener('change', importWords);
// Game mode buttons
flashcardsBtn.addEventListener('click', startFlashcards);
quizBtn.addEventListener('click', startQuiz);
typingBtn.addEventListener('click', startTyping);
// Flashcards events
flashcard.addEventListener('click', flipCard);
prevCardBtn.addEventListener('click', showPreviousCard);
nextCardBtn.addEventListener('click', showNextCard);
closeFlashcards.addEventListener('click', () => flashcardsModal.classList.add('hidden'));
// Quiz events
prevQuestionBtn.addEventListener('click', showPreviousQuestion);
nextQuestionBtn.addEventListener('click', showNextQuestion);
closeQuiz.addEventListener('click', () => quizModal.classList.add('hidden'));
// Typing events
typingAnswer.addEventListener('keypress', (e) => {
if (e.key === 'Enter') checkTypingAnswer();
});
checkTypingBtn.addEventListener('click', checkTypingAnswer);
nextTypingBtn.addEventListener('click', showNextTyping);
closeTyping.addEventListener('click', () => typingModal.classList.add('hidden'));
// Know/Don't know buttons (event delegation)
document.addEventListener('click', function(e) {
if (e.target.classList.contains('knowBtn')) {
markWordAsKnown(true);
} else if (e.target.classList.contains('dontKnowBtn')) {
markWordAsKnown(false);
}
});
}
// Add a new word to vocabulary
function addWord() {
const englishWord = englishWordInput.value.trim();
const vietnameseMeaning = vietnameseMeaningInput.value.trim();
const exampleSentence = exampleSentenceInput.value.trim();
if (!englishWord || !vietnameseMeaning) {
alert('Please enter both English word and Vietnamese meaning');
return;
}
// Check if word already exists
if (vocabulary.some(word => word.english.toLowerCase() === englishWord.toLowerCase())) {
alert('This word already exists in your vocabulary list');
return;
}
const newWord = {
english: englishWord,
vietnamese: vietnameseMeaning,
example: exampleSentence,
added: new Date().toISOString()
};
vocabulary.push(newWord);
saveVocabulary();
// Clear inputs
englishWordInput.value = '';
vietnameseMeaningInput.value = '';
exampleSentenceInput.value = '';
// Focus back to English input
englishWordInput.focus();
// Update UI
updateWordList();
updateProgress();
}
// Clear all words
function clearWords() {
if (confirm('Are you sure you want to clear all words? This will reset to default vocabulary.')) {
vocabulary = [...defaultVocabulary];
knownWords = [];
saveVocabulary();
updateWordList();
updateProgress();
}
}
// Export words to JSON file
function exportWords() {
if (vocabulary.length === 0) {
alert('No words to export');
return;
}
const data = {
vocabulary: vocabulary,
knownWords: knownWords,
exported: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `vocabulary-${new Date().toISOString().slice(0, 10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Import words from JSON file
function importWords(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const data = JSON.parse(event.target.result);
if (data.vocabulary && Array.isArray(data.vocabulary)) {
// Merge with existing vocabulary, avoiding duplicates
data.vocabulary.forEach(word => {
if (!vocabulary.some(w => w.english.toLowerCase() === word.english.toLowerCase())) {
vocabulary.push(word);
}
});
// Update known words if they exist in import
if (data.knownWords && Array.isArray(data.knownWords)) {
data.knownWords.forEach(word => {
if (!knownWords.includes(word) && vocabulary.some(w => w.english === word)) {
knownWords.push(word);
}
});
}
saveVocabulary();
updateWordList();
updateProgress();
alert(`Successfully imported ${data.vocabulary.length} words`);
} else {
alert('Invalid file format');
}
} catch (error) {
alert('Error reading file: ' + error.message);
}
// Reset file input
e.target.value = '';
};
reader.readAsText(file);
}
// Save vocabulary to localStorage
function saveVocabulary() {
localStorage.setItem('vocabulary', JSON.stringify(vocabulary));
localStorage.setItem('knownWords', JSON.stringify(knownWords));
}
// Update the word list display
function updateWordList() {
if (vocabulary.length === 0) {
wordList.innerHTML = '<p class="text-gray-500 text-center py-4">No words added yet</p>';
return;
}
wordList.innerHTML = '';
// Group words by first letter for better organization
const groupedWords = vocabulary.reduce((groups, word) => {
const firstLetter = word.english.charAt(0).toUpperCase();
if (!groups[firstLetter]) {
groups[firstLetter] = [];
}
groups[firstLetter].push(word);
return groups;
}, {});
// Sort letters alphabetically
const sortedLetters = Object.keys(groupedWords).sort();
// Display words by letter groups
sortedLetters.forEach(letter => {
const letterGroup = document.createElement('div');
letterGroup.className = 'mb-4';
const letterHeader = document.createElement('div');
letterHeader.className = 'word-category px-3 py-2 mb-2 font-medium text-indigo-800';
letterHeader.textContent = letter;
letterGroup.appendChild(letterHeader);
groupedWords[letter].forEach((word, index) => {
const isKnown = knownWords.includes(word.english);
const wordEl = document.createElement('div');
wordEl.className = `flex justify-between items-center p-2 mb-1 rounded-lg ${isKnown ? 'bg-green-50' : 'bg-white'} border ${isKnown ? 'border-green-200' : 'border-gray-200'}`;
wordEl.innerHTML = `
<div class="flex-1">
<div class="font-medium text-gray-800">${word.english}</div>
<div class="text-sm text-gray-600">${word.vietnamese}</div>
${word.example ? `<div class="text-xs text-gray-500 italic mt-1">"${word.example}"</div>` : ''}
</div>
<div class="flex space-x-1">
<button class="know-toggle px-2 py-1 text-xs rounded ${isKnown ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}" data-word="${word.english}">
<i class="fas ${isKnown ? 'fa-check-circle' : 'fa-question-circle'} mr-1"></i> ${isKnown ? 'Known' : 'Learning'}
</button>
${!defaultVocabulary.some(w => w.english === word.english) ?
`<button class="delete-word px-2 py-1 text-xs bg-red-100 text-red-800 rounded" data-index="${vocabulary.findIndex(w => w.english === word.english)}">
<i class="fas fa-trash-alt"></i>
</button>` : ''}
</div>
`;
letterGroup.appendChild(wordEl);
});
wordList.appendChild(letterGroup);
});
// Add event listeners for dynamically created buttons
document.querySelectorAll('.know-toggle').forEach(btn => {
btn.addEventListener('click', toggleKnownWord);
});
document.querySelectorAll('.delete-word').forEach(btn => {
btn.addEventListener('click', deleteWord);
});
}
// Toggle word between known and learning
function toggleKnownWord(e) {
const word = e.target.closest('button').getAttribute('data-word');
const index = knownWords.indexOf(word);
if (index === -1) {
knownWords.push(word);
} else {
knownWords.splice(index, 1);
}
saveVocabulary();
updateWordList();
updateProgress();
}
// Delete a word from vocabulary
function deleteWord(e) {
const index = parseInt(e.target.closest('button').getAttribute('data-index'));
if (index < 0 || index >= vocabulary.length) return;
// Don't allow deleting default words
if (defaultVocabulary.some(w => w.english === vocabulary[index].english)) {
alert('Default words cannot be deleted. Clear all to reset.');
return;
}
vocabulary.splice(index, 1);
// Remove from known words if it was there
const wordToDelete = vocabulary[index]?.english;
if (wordToDelete) {
const knownIndex = knownWords.indexOf(wordToDelete);
if (knownIndex !== -1) {
knownWords.splice(knownIndex, 1);
}
}
saveVocabulary();
updateWordList();
updateProgress();
}
// Update progress stats
function updateProgress() {
const total = vocabulary.length;
const known = knownWords.length;
const learning = total - known;
const mastery = total > 0 ? Math.round((known / total) * 100) : 0;
totalWordsEl.textContent = total;
knownWordsEl.textContent = known;
learningWordsEl.textContent = learning;
masteryPercentEl.textContent = `${mastery}%`;
masteryBarEl.style.width = `${mastery}%`;
// Change color based on mastery level
if (mastery >= 70) {
masteryBarEl.className = 'bg-green-600 h-2.5 rounded-full progress-bar';
} else if (mastery >= 40) {
masteryBarEl.className = 'bg-yellow-500 h-2.5 rounded-full progress-bar';
} else {
masteryBarEl.className = 'bg-red-500 h-2.5 rounded-full progress-bar';
}
}
// Start flashcards game
function startFlashcards() {
if (vocabulary.length === 0) {
alert('Please add some words first');
return;
}
currentFlashcardIndex = 0;
updateFlashcard();
// Show modal
flashcardsModal.classList.remove('hidden');
}
// Update flashcard display
function updateFlashcard() {
if (vocabulary.length === 0) {
noCardsMessage.classList.remove('hidden');
flashcard.classList.add('hidden');
return;
}
noCardsMessage.classList.add('hidden');
flashcard.classList.remove('hidden');
const word = vocabulary[currentFlashcardIndex];
cardFrontText.textContent = word.english;
cardBackMeaning.textContent = word.vietnamese;
cardBackExample.textContent = word.example || 'No example provided';
// Reset card to front
flashcard.classList.remove('flipped');
// Update counters
currentCardEl.textContent = currentFlashcardIndex + 1;
totalCardsEl.textContent = vocabulary.length;
// Update button states
prevCardBtn.disabled = currentFlashcardIndex === 0;
nextCardBtn.disabled = currentFlashcardIndex === vocabulary.length - 1;
}
// Flip the flashcard
function flipCard() {
flashcard.classList.toggle('flipped');
}
// Mark word as known or not known
function markWordAsKnown(isKnown) {
const currentWord = vocabulary[currentFlashcardIndex].english;
const index = knownWords.indexOf(currentWord);
if (isKnown && index === -1) {
knownWords.push(currentWord);
} else if (!isKnown && index !== -1) {
knownWords.splice(index, 1);
}
saveVocabulary();
updateProgress();
// Move to next card automatically
if (currentFlashcardIndex < vocabulary.length - 1) {
currentFlashcardIndex++;
updateFlashcard();
}
}
// Show previous flashcard
function showPreviousCard() {
if (currentFlashcardIndex > 0) {
currentFlashcardIndex--;
updateFlashcard();
}
}
// Show next flashcard
function showNextCard() {
if (currentFlashcardIndex < vocabulary.length - 1) {
currentFlashcardIndex++;
updateFlashcard();
}
}
// Start quiz game
function startQuiz() {
if (vocabulary.length < 4) {
alert('You need at least 4 words to start a quiz');
return;
}
currentQuizIndex = 0;
quizAnswers = [];
updateQuizQuestion();
// Show modal
quizModal.classList.remove('hidden');
}
// Update quiz question display
function updateQuizQuestion() {
if (vocabulary.length < 4) {
noQuizMessage.classList.remove('hidden');
quizQuestion.classList.add('hidden');
return;
}
noQuizMessage.classList.add('hidden');
quizQuestion.classList.remove('hidden');
const currentWord = vocabulary[currentQuizIndex];
quizWord.textContent = currentWord.english;
// Generate options (1 correct + 3 random incorrect)
const options = [currentWord.vietnamese];
// Get 3 random incorrect options
while (options.length < 4) {
const randomIndex = Math.floor(Math.random() * vocabulary.length);
if (randomIndex !== currentQuizIndex && !options.includes(vocabulary[randomIndex].vietnamese)) {
options.push(vocabulary[randomIndex].vietnamese);
}
}
// Shuffle options
options.sort(() => Math.random() - 0.5);
// Display options
quizOptions.innerHTML = '';
options.forEach((option, i) => {
const optionEl = document.createElement('button');
optionEl.className = 'w-full text-left p-3 bg-gray-100 hover:bg-gray-200 rounded-lg transition';
optionEl.textContent = `${i + 1}. ${option}`;
optionEl.dataset.option = option;
// Check if this option was already selected
const currentAnswer = quizAnswers[currentQuizIndex];
if (currentAnswer && currentAnswer.selected === option) {
optionEl.className = currentAnswer.correct ?
'w-full text-left p-3 bg-green-100 text-green-800 rounded-lg' :
'w-full text-left p-3 bg-red-100 text-red-800 rounded-lg';
}
optionEl.addEventListener('click', () => selectQuizOption(optionEl, option, currentWord.vietnamese));
quizOptions.appendChild(optionEl);
});
// Update counters
currentQuestionEl.textContent = currentQuizIndex + 1;
totalQuestionsEl.textContent = vocabulary.length;
// Update button states
prevQuestionBtn.disabled = currentQuizIndex === 0;
nextQuestionBtn.disabled = currentQuizIndex === vocabulary.length - 1;
}
// Select a quiz option
function selectQuizOption(optionEl, selectedOption, correctOption) {
// Don't allow changing answer after selection
if (quizAnswers[currentQuizIndex]) return;
const isCorrect = selectedOption === correctOption;
// Update button appearance
optionEl.className = isCorrect ?
'w-full text-left p-3 bg-green-100 text-green-800 rounded-lg bounce' :
'w-full text-left p-3 bg-red-100 text-red-800 rounded-lg shake';
// Store answer
quizAnswers[currentQuizIndex] = {
selected: selectedOption,
correct: isCorrect
};
// Update known words if answered correctly
const currentWord = vocabulary[currentQuizIndex].english;
if (isCorrect && !knownWords.includes(currentWord)) {
knownWords.push(currentWord);
saveVocabulary();
updateProgress();
}
}
// Show previous quiz question
function showPreviousQuestion() {
if (currentQuizIndex > 0) {
currentQuizIndex--;
updateQuizQuestion();
}
}
// Show next quiz question
function showNextQuestion() {
if (currentQuizIndex < vocabulary.length - 1) {
currentQuizIndex++;
updateQuizQuestion();
}
}
// Start typing practice
function startTyping() {
if (vocabulary.length === 0) {
alert('Please add some words first');
return;
}
currentTypingIndex = 0;
typingAnswers = [];
updateTypingQuestion();
// Show modal
typingModal.classList.remove('hidden');
}
// Update typing question display
function updateTypingQuestion() {
if (vocabulary.length === 0) {
noTypingMessage.classList.remove('hidden');
typingQuestion.classList.add('hidden');
return;
}
noTypingMessage.classList.add('hidden');
typingQuestion.classList.remove('hidden');
typingFeedback.classList.add('hidden');
const currentWord = vocabulary[currentTypingIndex];
typingPrompt.textContent = currentWord.vietnamese;
typingAnswer.value = '';
// Update counters
currentTypingEl.textContent = currentTypingIndex + 1;
totalTypingEl.textContent = vocabulary.length;
// Focus on input
typingAnswer.focus();
// Show check button, hide next button
checkTypingBtn.classList.remove('hidden');
nextTypingBtn.classList.add('hidden');
}
// Check typing answer
function checkTypingAnswer() {
const userAnswer = typingAnswer.value.trim().toLowerCase();
const correctAnswer = vocabulary[currentTypingIndex].english.toLowerCase();
const isCorrect = userAnswer === correctAnswer;
// Show feedback
typingFeedback.classList.remove('hidden');
if (isCorrect) {
typingFeedback.className = 'bg-green-100 text-green-800 text-center py-2 rounded-lg bounce';
typingFeedback.innerHTML = `<i class="fas fa-check-circle mr-2"></i> Correct!`;
// Add to known words if not already there
if (!knownWords.includes(vocabulary[currentTypingIndex].english)) {
knownWords.push(vocabulary[currentTypingIndex].english);
saveVocabulary();
updateProgress();
}
} else {
typingFeedback.className = 'bg-red-100 text-red-800 text-center py-2 rounded-lg shake';
typingFeedback.innerHTML = `<i class="fas fa-times-circle mr-2"></i> Incorrect. The correct answer is "${vocabulary[currentTypingIndex].english}"`;
}
// Store answer
typingAnswers[currentTypingIndex] = {
answer: userAnswer,
correct: isCorrect
};
// Show next button, hide check button
checkTypingBtn.classList.add('hidden');
nextTypingBtn.classList.remove('hidden');
}
// Show next typing question
function showNextTyping() {
if (currentTypingIndex < vocabulary.length - 1) {
currentTypingIndex++;
updateTypingQuestion();
} else {
// Calculate score if finished all words
const correctCount = typingAnswers.filter(a => a.correct).length;
const score = Math.round((correctCount / vocabulary.length) * 100);
typingFeedback.classList.remove('hidden');
typingFeedback.className = 'bg-blue-100 text-blue-800 text-center py-2 rounded-lg';
typingFeedback.innerHTML = `
<div class="font-medium">Practice complete!</div>
<div>Score: ${score}% (${correctCount} of ${vocabulary.length})</div>
`;
nextTypingBtn.classList.add('hidden');
}
}
// Initialize the app
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ktskhoa/game-learning" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>