german-flashcards / index.html
Digiator's picture
add a feature to display the current list 20 cards by 20 cards linke next button - Follow Up Deployment
8b91465 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>German Flashcards</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<style>
.flashcard {
perspective: 1000px;
min-height: 80px;
}
.flashcard-inner {
transition: transform 0.6s;
transform-style: preserve-3d;
}
.flashcard.flipped .flashcard-inner {
transform: rotateY(180deg);
}
.flashcard-front, .flashcard-back {
backface-visibility: hidden;
position: absolute;
width: 100%;
height: 100%;
}
.flashcard-back {
transform: rotateY(180deg);
}
.progress-bar {
transition: width 0.3s ease;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8 text-center">
<h1 class="text-4xl font-bold text-indigo-700 mb-2">German Flashcards</h1>
<p class="text-gray-600">Master German vocabulary with interactive flashcards</p>
<div class="mt-6 flex justify-center items-center space-x-4">
<div class="w-full max-w-md bg-white rounded-lg shadow p-4">
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-indigo-700">Progress</span>
<span id="progress-percentage" class="text-sm font-medium text-indigo-700">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div id="progress-bar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
</div>
</div>
</header>
<div class="mb-8 flex flex-col items-center space-y-4">
<div class="w-full max-w-md bg-white rounded-lg shadow p-4">
<h3 class="text-lg font-medium text-indigo-700 mb-2">Add New Flashcards</h3>
<input id="list-name-input" type="text" class="w-full p-2 border rounded mb-2" placeholder="List name (e.g. 'Food Vocabulary')">
<textarea id="new-flashcards-input" class="w-full h-40 p-2 border rounded mb-2" placeholder="Enter German - English pairs (e.g. neun – nine)"></textarea>
<button id="add-flashcards-btn" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition flex items-center justify-center w-full">
<i data-feather="plus" class="mr-2"></i> Add Flashcards
</button>
</div>
<div class="flex flex-col items-center space-y-4">
<div class="flex justify-center space-x-4">
<button id="shuffle-btn" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center">
<i data-feather="shuffle" class="mr-2"></i> Shuffle
</button>
<button id="reset-btn" class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition flex items-center">
<i data-feather="refresh-cw" class="mr-2"></i> Reset Progress
</button>
<button id="show-original-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition flex items-center">
<i data-feather="book" class="mr-2"></i> Original List
</button>
<button id="show-not-mastered-btn" class="px-4 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition flex items-center">
<i data-feather="alert-circle" class="mr-2"></i> Not Mastered
</button>
</div>
<div id="custom-lists-container" class="flex flex-wrap justify-center gap-2">
<!-- Custom list buttons will be added here -->
</div>
</div>
</div>
<div id="flashcards-container" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
<!-- Flashcards will be inserted here by JavaScript -->
</div>
<div id="pagination-controls" class="mt-8 flex justify-center items-center space-x-4 hidden">
<button id="prev-page-btn" class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition flex items-center">
<i data-feather="chevron-left" class="mr-2"></i> Previous
</button>
<span id="page-info" class="text-gray-600 font-medium">Page 1 of 1</span>
<button id="next-page-btn" class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition flex items-center">
Next <i data-feather="chevron-right" class="ml-2"></i>
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
AOS.init();
// Get flashcards from localStorage
let flashcards = JSON.parse(localStorage.getItem('germanFlashcards')) || [];
// Render flashcards
function renderFlashcards() {
const container = document.getElementById('flashcards-container');
container.innerHTML = '';
flashcards.forEach((card, index) => {
const flashcard = document.createElement('div');
flashcard.className = `flashcard ${card.mastered ? 'border-2 border-green-500' : ''}`;
flashcard.dataset.index = index;
flashcard.innerHTML = `
<div class="flashcard-inner h-full">
<div class="flashcard-front bg-white rounded-lg shadow-md p-3 flex flex-col items-center justify-center cursor-pointer h-full">
<h3 class="text-sm font-bold text-center text-indigo-700">${card.german}</h3>
<button class="speak-btn mt-1 p-1 bg-indigo-100 rounded-full hover:bg-indigo-200 transition">
<i data-feather="volume-2" class="text-indigo-700 w-3 h-3"></i>
</button>
<p class="text-xs text-gray-500 mt-1">Click to flip</p>
${card.mastered ? '<i data-feather="check-circle" class="text-green-500 mt-1 w-3 h-3"></i>' : ''}
</div>
<div class="flashcard-back bg-indigo-100 rounded-lg shadow-md p-2 flex flex-col items-center justify-center cursor-pointer h-full">
<h3 class="text-xs font-semibold text-center text-gray-800">${card.english}</h3>
<div class="mt-1 flex space-x-1">
<button class="master-btn px-2 py-0.5 ${card.mastered ? 'bg-green-500' : 'bg-gray-500'} text-white rounded text-xs">
${card.mastered ? 'Mastered' : 'Mark as Mastered'}
</button>
</div>
</div>
</div>
`;
container.appendChild(flashcard);
// Add click event to flip card
flashcard.addEventListener('click', function() {
if (!event.target.classList.contains('master-btn')) {
this.classList.toggle('flipped');
}
});
});
// Add event listeners to master buttons
document.querySelectorAll('.master-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const index = this.closest('.flashcard').dataset.index;
flashcards[index].mastered = !flashcards[index].mastered;
saveFlashcards();
updateProgress();
renderFlashcards();
});
});
feather.replace();
updateProgress();
}
// Save flashcards to localStorage
function saveFlashcards() {
localStorage.setItem('germanFlashcards', JSON.stringify(flashcards));
}
// Update progress bar
function updateProgress() {
const masteredCount = flashcards.filter(card => card.mastered).length;
const totalCount = flashcards.length;
const percentage = Math.round((masteredCount / totalCount) * 100);
document.getElementById('progress-bar').style.width = `${percentage}%`;
document.getElementById('progress-percentage').textContent = `${percentage}%`;
}
// Shuffle flashcards
document.getElementById('shuffle-btn').addEventListener('click', function() {
flashcards = shuffleArray(flashcards);
saveFlashcards();
renderFlashcards();
});
// Reset progress of current list
document.getElementById('reset-btn').addEventListener('click', function() {
if (confirm('Are you sure you want to reset progress for the current list?')) {
const currentListName = getCurrentListName();
if (currentListName && customLists[currentListName]) {
// Reset all cards in the current list to not mastered
customLists[currentListName] = customLists[currentListName].map(card => ({ ...card, mastered: false }));
localStorage.setItem('customGermanLists', JSON.stringify(customLists));
// Reload the current list
flashcards = [...customLists[currentListName]];
saveFlashcards();
renderFlashcards();
}
}
});
// Helper function to shuffle array
function shuffleArray(array) {
const newArray = [...array];
for (let i = newArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
}
return newArray;
}
// Object to store all custom lists
let customLists = JSON.parse(localStorage.getItem('customGermanLists')) || {};
let currentPage = 1;
const cardsPerPage = 20;
// Render flashcards with pagination
function renderFlashcards() {
const container = document.getElementById('flashcards-container');
container.innerHTML = '';
const startIndex = (currentPage - 1) * cardsPerPage;
const endIndex = Math.min(startIndex + cardsPerPage, flashcards.length);
const currentPageCards = flashcards.slice(startIndex, endIndex);
currentPageCards.forEach((card, index) => {
const actualIndex = startIndex + index;
const flashcard = document.createElement('div');
flashcard.className = `flashcard ${card.mastered ? 'border-2 border-green-500' : ''}`;
flashcard.dataset.index = actualIndex;
flashcard.innerHTML = `
<div class="flashcard-inner h-full">
<div class="flashcard-front bg-white rounded-lg shadow-md p-3 flex flex-col items-center justify-center cursor-pointer h-full">
<h3 class="text-sm font-bold text-center text-indigo-700">${card.german}</h3>
<button class="speak-btn mt-1 p-1 bg-indigo-100 rounded-full hover:bg-indigo-200 transition">
<i data-feather="volume-2" class="text-indigo-700 w-3 h-3"></i>
</button>
<p class="text-xs text-gray-500 mt-1">Click to flip</p>
${card.mastered ? '<i data-feather="check-circle" class="text-green-500 mt-1 w-3 h-3"></i>' : ''}
</div>
<div class="flashcard-back bg-indigo-100 rounded-lg shadow-md p-2 flex flex-col items-center justify-center cursor-pointer h-full">
<h3 class="text-xs font-semibold text-center text-gray-800">${card.english}</h3>
<div class="mt-1 flex space-x-1">
<button class="master-btn px-2 py-0.5 ${card.mastered ? 'bg-green-500' : 'bg-gray-500'} text-white rounded text-xs">
${card.mastered ? 'Mastered' : 'Mark as Mastered'}
</button>
</div>
</div>
</div>
`;
container.appendChild(flashcard);
// Add click event to flip card
flashcard.addEventListener('click', function() {
if (!event.target.classList.contains('master-btn')) {
this.classList.toggle('flipped');
}
});
});
// Add event listeners to master buttons
document.querySelectorAll('.master-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const index = parseInt(this.closest('.flashcard').dataset.index);
flashcards[index].mastered = !flashcards[index].mastered;
saveFlashcards();
updateProgress();
renderFlashcards();
});
});
// Update pagination controls
updatePaginationControls();
feather.replace();
updateProgress();
}
// Update pagination controls
function updatePaginationControls() {
const totalPages = Math.ceil(flashcards.length / cardsPerPage);
const paginationControls = document.getElementById('pagination-controls');
const pageInfo = document.getElementById('page-info');
const prevBtn = document.getElementById('prev-page-btn');
const nextBtn = document.getElementById('next-page-btn');
pageInfo.textContent = `Page ${currentPage} of ${totalPages}`;
if (totalPages <= 1) {
paginationControls.classList.add('hidden');
} else {
paginationControls.classList.remove('hidden');
prevBtn.disabled = currentPage === 1;
prevBtn.classList.toggle('opacity-50', currentPage === 1);
prevBtn.classList.toggle('cursor-not-allowed', currentPage === 1);
nextBtn.disabled = currentPage === totalPages;
nextBtn.classList.toggle('opacity-50', currentPage === totalPages);
nextBtn.classList.toggle('cursor-not-allowed', currentPage === totalPages);
}
}
// Next page button
document.getElementById('next-page-btn').addEventListener('click', function() {
const totalPages = Math.ceil(flashcards.length / cardsPerPage);
if (currentPage < totalPages) {
currentPage++;
renderFlashcards();
}
});
// Previous page button
document.getElementById('prev-page-btn').addEventListener('click', function() {
if (currentPage > 1) {
currentPage--;
renderFlashcards();
}
});
// Render custom list buttons
function renderCustomListButtons() {
const container = document.getElementById('custom-lists-container');
container.innerHTML = '';
Object.keys(customLists).forEach(listName => {
const buttonContainer = document.createElement('div');
buttonContainer.className = 'relative group';
const button = document.createElement('button');
button.className = 'px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition flex items-center';
button.innerHTML = `<i data-feather="list" class="mr-2"></i> ${listName}`;
button.addEventListener('click', () => {
flashcards = [...customLists[listName]];
renderFlashcards();
});
const removeBtn = document.createElement('button');
removeBtn.className = 'absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity hover:bg-red-600';
removeBtn.innerHTML = '×';
removeBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`Are you sure you want to delete the "${listName}" list?`)) {
delete customLists[listName];
localStorage.setItem('customGermanLists', JSON.stringify(customLists));
renderCustomListButtons();
// If we deleted the current list, load the first available list
const listNames = Object.keys(customLists);
if (listNames.length > 0) {
flashcards = [...customLists[listNames[0]]];
renderFlashcards();
} else {
flashcards = [];
renderFlashcards();
}
}
});
buttonContainer.appendChild(button);
buttonContainer.appendChild(removeBtn);
container.appendChild(buttonContainer);
});
feather.replace();
}
// Show original flashcards (first custom list or empty)
document.getElementById('show-original-btn').addEventListener('click', function() {
const listNames = Object.keys(customLists);
if (listNames.length > 0) {
flashcards = [...customLists[listNames[0]]];
currentPage = 1;
renderFlashcards();
} else {
alert('No custom lists available. Please create a list first.');
}
});
// Parse and add new flashcards
document.getElementById('add-flashcards-btn').addEventListener('click', function() {
const input = document.getElementById('new-flashcards-input').value.trim();
const listName = document.getElementById('list-name-input').value.trim();
if (!input || !listName) {
alert('Please enter both a list name and flashcards!');
return;
}
const lines = input.split('\n');
const newCards = [];
lines.forEach(line => {
const parts = line.split('–').map(part => part.trim());
if (parts.length === 2 && parts[0] && parts[1]) {
newCards.push({
german: parts[0],
english: parts[1],
mastered: false
});
}
});
if (newCards.length > 0) {
// Add or update the custom list
customLists[listName] = newCards;
localStorage.setItem('customGermanLists', JSON.stringify(customLists));
document.getElementById('new-flashcards-input').value = '';
document.getElementById('list-name-input').value = '';
// Update the flashcards to show the newly added list
flashcards = [...newCards];
// Save and render
saveFlashcards();
renderFlashcards();
renderCustomListButtons();
alert(`Added ${newCards.length} new flashcards to "${listName}" list!`);
}
});
// Speak German word
function speakWord(text) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'de-DE';
speechSynthesis.speak(utterance);
}
// Show only not mastered words
document.getElementById('show-not-mastered-btn').addEventListener('click', function() {
// Reload current list from localStorage to get latest mastered status
const currentListName = getCurrentListName();
if (currentListName && customLists[currentListName]) {
flashcards = [...customLists[currentListName]];
}
const notMastered = flashcards.filter(card => !card.mastered);
if (notMastered.length === 0) {
alert('All words in this list are mastered!');
return;
}
flashcards = [...notMastered];
currentPage = 1;
renderFlashcards();
});
// Add event delegation for speak buttons
document.addEventListener('click', function(e) {
if (e.target.closest('.speak-btn') || e.target.closest('.speak-btn i')) {
const flashcard = e.target.closest('.flashcard');
const index = flashcard.dataset.index;
speakWord(flashcards[index].german);
}
});
// Helper function to get current list name
function getCurrentListName() {
const currentFlashcards = JSON.stringify(flashcards);
for (const [listName, listCards] of Object.entries(customLists)) {
if (JSON.stringify(listCards) === currentFlashcards) {
return listName;
}
}
return null;
}
// Update custom list when flashcards change
function updateCurrentCustomList() {
const currentListName = getCurrentListName();
if (currentListName && customLists[currentListName]) {
customLists[currentListName] = [...flashcards];
localStorage.setItem('customGermanLists', JSON.stringify(customLists));
}
}
// Modified saveFlashcards to also update custom lists
function saveFlashcards() {
localStorage.setItem('germanFlashcards', JSON.stringify(flashcards));
updateCurrentCustomList();
}
// Modified renderCustomListButtons to load from current list when clicked
function renderCustomListButtons() {
const container = document.getElementById('custom-lists-container');
container.innerHTML = '';
Object.keys(customLists).forEach(listName => {
const buttonContainer = document.createElement('div');
buttonContainer.className = 'relative group';
const button = document.createElement('button');
button.className = 'px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition flex items-center';
button.innerHTML = `<i data-feather="list" class="mr-2"></i> ${listName}`;
button.addEventListener('click', () => {
// Load the full list from localStorage (including mastered status)
flashcards = [...customLists[listName]];
saveFlashcards();
currentPage = 1;
renderFlashcards();
});
const removeBtn = document.createElement('button');
removeBtn.className = 'absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity hover:bg-red-600';
removeBtn.innerHTML = '×';
removeBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`Are you sure you want to delete the "${listName}" list?`)) {
delete customLists[listName];
localStorage.setItem('customGermanLists', JSON.stringify(customLists));
renderCustomListButtons();
// If we deleted the current list, load the first available list
const listNames = Object.keys(customLists);
if (listNames.length > 0) {
flashcards = [...customLists[listNames[0]]];
saveFlashcards();
renderFlashcards();
} else {
flashcards = [];
saveFlashcards();
renderFlashcards();
}
}
});
buttonContainer.appendChild(button);
buttonContainer.appendChild(removeBtn);
container.appendChild(buttonContainer);
});
feather.replace();
}
// Initial render - load first custom list if available
const listNames = Object.keys(customLists);
if (listNames.length > 0) {
flashcards = [...customLists[listNames[0]]];
}
renderFlashcards();
renderCustomListButtons();
currentPage = 1;
updatePaginationControls();
});
</script>
</body>
</html>