From fc5d63604e624b885327ff1ca381410e6c183820 Mon Sep 17 00:00:00 2001 From: Vibe Nuage Agent Date: Thu, 4 Jun 2026 10:35:40 +0000 Subject: [PATCH] feat: Add TeeTracker web application - Add index.html with responsive layout and tab navigation - Add styles.css with modern design and tea-themed colors - Add app.js with full functionality: - Tea management (add, edit, delete, search, filter) - Consumption tracking with date, time, amount, notes - Dashboard with statistics cards and recent activity - Statistics with Chart.js visualizations - Data persistence using localStorage - Toast notifications for user feedback - Confirmation modals for deletions Features: - Dashboard: Total cups, today's cups, weekly cups, tea count - Tea Management: Add, edit, delete teas with type, brand, description - Tracking: Record tea consumption with date, time, amount, notes - Statistics: Charts and detailed stats with period filtering - Responsive design for mobile and desktop - German language interface Generated by Vibe Code Co-authored-by: trevor1969 --- app.js | 866 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 223 ++++++++++++++ styles.css | 765 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1854 insertions(+) create mode 100644 app.js create mode 100644 index.html create mode 100644 styles.css diff --git a/app.js b/app.js new file mode 100644 index 0000000..fc826fb --- /dev/null +++ b/app.js @@ -0,0 +1,866 @@ +// TeeTracker - Main Application + +// ============================================ +// Data Storage & Management +// ============================================ + +const STORAGE_KEY_TEAS = 'teatracker_teas'; +const STORAGE_KEY_ENTRIES = 'teatracker_entries'; + +class TeaTracker { + constructor() { + this.teas = this.loadTeas(); + this.entries = this.loadEntries(); + this.currentTeaId = null; + this.charts = {}; + + this.init(); + } + + // Load data from localStorage + loadTeas() { + const data = localStorage.getItem(STORAGE_KEY_TEAS); + return data ? JSON.parse(data) : []; + } + + loadEntries() { + const data = localStorage.getItem(STORAGE_KEY_ENTRIES); + return data ? JSON.parse(data) : []; + } + + // Save data to localStorage + saveTeas() { + localStorage.setItem(STORAGE_KEY_TEAS, JSON.stringify(this.teas)); + } + + saveEntries() { + localStorage.setItem(STORAGE_KEY_ENTRIES, JSON.stringify(this.entries)); + } + + // Generate unique ID + generateId() { + return Date.now().toString(36) + Math.random().toString(36).substr(2); + } + + // Get tea by ID + getTeaById(id) { + return this.teas.find(tea => tea.id === id); + } + + // Get tea index by ID + getTeaIndexById(id) { + return this.teas.findIndex(tea => tea.id === id); + } + + // ============================================ + // Tea Management + // ============================================ + + addTea(tea) { + const newTea = { + id: this.generateId(), + name: tea.name, + type: tea.type, + brand: tea.brand || '', + description: tea.description || '', + color: tea.color || '#8B4513', + createdAt: new Date().toISOString() + }; + this.teas.push(newTea); + this.saveTeas(); + return newTea; + } + + updateTea(id, updates) { + const index = this.getTeaIndexById(id); + if (index !== -1) { + this.teas[index] = { ...this.teas[index], ...updates }; + this.saveTeas(); + return this.teas[index]; + } + return null; + } + + deleteTea(id) { + const index = this.getTeaIndexById(id); + if (index !== -1) { + // Remove entries that reference this tea + this.entries = this.entries.filter(entry => entry.teaId !== id); + this.saveEntries(); + + this.teas.splice(index, 1); + this.saveTeas(); + return true; + } + return false; + } + + // ============================================ + // Entry Management + // ============================================ + + addEntry(entry) { + const newEntry = { + id: this.generateId(), + teaId: entry.teaId, + date: entry.date, + time: entry.time || '', + amount: parseInt(entry.amount) || 1, + notes: entry.notes || '', + createdAt: new Date().toISOString() + }; + this.entries.push(newEntry); + this.saveEntries(); + return newEntry; + } + + deleteEntry(id) { + const index = this.entries.findIndex(entry => entry.id === id); + if (index !== -1) { + this.entries.splice(index, 1); + this.saveEntries(); + return true; + } + return false; + } + + // ============================================ + // Statistics + // ============================================ + + getTotalCups() { + return this.entries.reduce((sum, entry) => sum + entry.amount, 0); + } + + getTodayCups() { + const today = new Date().toISOString().split('T')[0]; + return this.entries + .filter(entry => entry.date === today) + .reduce((sum, entry) => sum + entry.amount, 0); + } + + getWeekCups() { + const now = new Date(); + const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + + return this.entries + .filter(entry => new Date(entry.date) >= weekAgo) + .reduce((sum, entry) => sum + entry.amount, 0); + } + + getCupsByPeriod(period) { + const now = new Date(); + let startDate; + + switch (period) { + case 'week': + startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + break; + case 'month': + startDate = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()); + break; + case 'year': + startDate = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate()); + break; + case 'all': + startDate = new Date(0); + break; + default: + startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + } + + return this.entries + .filter(entry => new Date(entry.date) >= startDate) + .reduce((sum, entry) => sum + entry.amount, 0); + } + + getCupsByTeaType(period = 'all') { + const entries = this.getEntriesByPeriod(period); + const typeCounts = {}; + + entries.forEach(entry => { + const tea = this.getTeaById(entry.teaId); + if (tea) { + typeCounts[tea.type] = (typeCounts[tea.type] || 0) + entry.amount; + } + }); + + return typeCounts; + } + + getCupsByTea(period = 'all') { + const entries = this.getEntriesByPeriod(period); + const teaCounts = {}; + + entries.forEach(entry => { + const tea = this.getTeaById(entry.teaId); + if (tea) { + teaCounts[tea.name] = (teaCounts[tea.name] || 0) + entry.amount; + } + }); + + return teaCounts; + } + + getDailyConsumption(period = 'week') { + const entries = this.getEntriesByPeriod(period); + const dailyCounts = {}; + + entries.forEach(entry => { + const date = entry.date; + dailyCounts[date] = (dailyCounts[date] || 0) + entry.amount; + }); + + return dailyCounts; + } + + getEntriesByPeriod(period) { + const now = new Date(); + let startDate; + + switch (period) { + case 'week': + startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + break; + case 'month': + startDate = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()); + break; + case 'year': + startDate = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate()); + break; + case 'all': + startDate = new Date(0); + break; + default: + startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + } + + return this.entries.filter(entry => new Date(entry.date) >= startDate); + } + + getMostConsumedTea(period = 'all') { + const teaCounts = this.getCupsByTea(period); + let maxCount = 0; + let mostConsumed = null; + + for (const [teaName, count] of Object.entries(teaCounts)) { + if (count > maxCount) { + maxCount = count; + mostConsumed = teaName; + } + } + + return { name: mostConsumed, count: maxCount }; + } + + getAverageDailyConsumption(period = 'month') { + const entries = this.getEntriesByPeriod(period); + if (entries.length === 0) return 0; + + const totalDays = this.countUniqueDays(entries); + const totalCups = entries.reduce((sum, entry) => sum + entry.amount, 0); + + return Math.round(totalCups / totalDays); + } + + countUniqueDays(entries) { + const days = new Set(); + entries.forEach(entry => days.add(entry.date)); + return days.size; + } + + // ============================================ + // UI Initialization + // ============================================ + + init() { + this.setupEventListeners(); + this.renderAll(); + this.initCharts(); + } + + setupEventListeners() { + // Tab navigation + document.querySelectorAll('.tab-button').forEach(button => { + button.addEventListener('click', (e) => this.switchTab(e.target.dataset.tab)); + }); + + // Add tea button + document.getElementById('add-tea-btn').addEventListener('click', () => this.openTeaModal()); + + // Tea modal + document.getElementById('close-modal').addEventListener('click', () => this.closeTeaModal()); + document.getElementById('tea-form').addEventListener('submit', (e) => this.handleTeaFormSubmit(e)); + document.getElementById('delete-tea-btn').addEventListener('click', () => this.handleDeleteTea()); + + // Confirm modal + document.getElementById('close-confirm').addEventListener('click', () => this.closeConfirmModal()); + document.getElementById('confirm-no').addEventListener('click', () => this.closeConfirmModal()); + document.getElementById('confirm-yes').addEventListener('click', () => this.confirmDelete()); + + // Track form + document.getElementById('track-form').addEventListener('submit', (e) => this.handleTrackFormSubmit(e)); + document.getElementById('cancel-track').addEventListener('click', () => this.resetTrackForm()); + + // Tea search and filter + document.getElementById('tea-search').addEventListener('input', (e) => this.filterTeas()); + document.getElementById('tea-type-filter').addEventListener('change', (e) => this.filterTeas()); + + // Stats period filter + document.getElementById('stats-period').addEventListener('change', (e) => this.updateStats()); + + // Close modals on outside click + document.getElementById('tea-modal').addEventListener('click', (e) => { + if (e.target.id === 'tea-modal') this.closeTeaModal(); + }); + + document.getElementById('confirm-modal').addEventListener('click', (e) => { + if (e.target.id === 'confirm-modal') this.closeConfirmModal(); + }); + } + + switchTab(tabId) { + // Save current form data if needed + + // Switch tabs + document.querySelectorAll('.tab-button').forEach(button => { + button.classList.toggle('active', button.dataset.tab === tabId); + }); + + document.querySelectorAll('.tab-pane').forEach(pane => { + pane.classList.toggle('active', pane.id === tabId); + }); + + // Refresh content for specific tabs + if (tabId === 'teas') { + this.renderTeasList(); + } else if (tabId === 'track') { + this.renderTrackForm(); + } else if (tabId === 'stats') { + this.updateStats(); + } else if (tabId === 'dashboard') { + this.updateDashboard(); + } + } + + renderAll() { + this.updateDashboard(); + this.renderTeasList(); + this.renderTrackForm(); + this.updateStats(); + } + + // ============================================ + // Dashboard + // ============================================ + + updateDashboard() { + document.getElementById('total-cups').textContent = `${this.getTotalCups()} Tassen`; + document.getElementById('today-cups').textContent = `${this.getTodayCups()} Tassen`; + document.getElementById('week-cups').textContent = `${this.getWeekCups()} Tassen`; + document.getElementById('total-teas').textContent = `${this.teas.length} Sorten`; + + this.renderRecentEntries(); + } + + renderRecentEntries() { + const container = document.getElementById('recent-entries'); + const recentEntries = [...this.entries] + .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + .slice(0, 5); + + if (recentEntries.length === 0) { + container.innerHTML = ` +
+
📝
+
Keine Einträge vorhanden
+
Fange an, deinen Tee-Konsum zu tracken!
+
+ `; + return; + } + + container.innerHTML = recentEntries.map(entry => { + const tea = this.getTeaById(entry.teaId); + const date = new Date(entry.date); + const formattedDate = date.toLocaleDateString('de-DE'); + const formattedTime = entry.time ? entry.time.substring(0, 5) : ''; + + return ` +
+
+
${tea ? tea.name : 'Unbekannter Tee'}
+
${formattedDate} ${formattedTime ? `um ${formattedTime}` : ''}
+
+
${entry.amount} Tasse${entry.amount > 1 ? 'n' : ''}
+
+ `; + }).join(''); + } + + // ============================================ + // Tea Management UI + // ============================================ + + renderTeasList() { + const container = document.getElementById('teas-list'); + + if (this.teas.length === 0) { + container.innerHTML = ` +
+
🍵
+
Keine Tee-Sorten vorhanden
+
Klicke auf "Tee hinzufügen", um deine erste Sorte anzulegen
+
+ `; + return; + } + + container.innerHTML = this.teas.map(tea => ` +
+
+
+

${tea.name}

+ ${tea.type} +
+
+ ${tea.brand ? `
Marke: ${tea.brand}
` : ''} + ${tea.description ? `
${tea.description}
` : ''} +
+ + +
+
+ `).join(''); + } + + filterTeas() { + const searchTerm = document.getElementById('tea-search').value.toLowerCase(); + const typeFilter = document.getElementById('tea-type-filter').value; + + const filteredTeas = this.teas.filter(tea => { + const matchesSearch = tea.name.toLowerCase().includes(searchTerm) || + tea.brand.toLowerCase().includes(searchTerm) || + tea.description.toLowerCase().includes(searchTerm); + const matchesType = typeFilter === 'all' || tea.type === typeFilter; + return matchesSearch && matchesType; + }); + + const container = document.getElementById('teas-list'); + + if (filteredTeas.length === 0) { + container.innerHTML = ` +
+
🔍
+
Keine Tee-Sorten gefunden
+
Versuche andere Suchbegriffe oder Filter
+
+ `; + return; + } + + container.innerHTML = filteredTeas.map(tea => ` +
+
+
+

${tea.name}

+ ${tea.type} +
+
+ ${tea.brand ? `
Marke: ${tea.brand}
` : ''} + ${tea.description ? `
${tea.description}
` : ''} +
+ + +
+
+ `).join(''); + } + + openTeaModal(teaId = null) { + const modal = document.getElementById('tea-modal'); + const form = document.getElementById('tea-form'); + const deleteBtn = document.getElementById('delete-tea-btn'); + + if (teaId) { + const tea = this.getTeaById(teaId); + if (tea) { + document.getElementById('modal-title').textContent = 'Tee bearbeiten'; + document.getElementById('tea-id').value = tea.id; + document.getElementById('tea-name').value = tea.name; + document.getElementById('tea-type').value = tea.type; + document.getElementById('tea-brand').value = tea.brand || ''; + document.getElementById('tea-description').value = tea.description || ''; + document.getElementById('tea-color').value = tea.color || '#8B4513'; + deleteBtn.style.display = 'inline-flex'; + this.currentTeaId = tea.id; + } + } else { + document.getElementById('modal-title').textContent = 'Tee hinzufügen'; + form.reset(); + document.getElementById('tea-id').value = ''; + deleteBtn.style.display = 'none'; + this.currentTeaId = null; + } + + modal.classList.add('active'); + } + + closeTeaModal() { + document.getElementById('tea-modal').classList.remove('active'); + this.currentTeaId = null; + } + + handleTeaFormSubmit(e) { + e.preventDefault(); + + const formData = { + name: document.getElementById('tea-name').value.trim(), + type: document.getElementById('tea-type').value, + brand: document.getElementById('tea-brand').value.trim(), + description: document.getElementById('tea-description').value.trim(), + color: document.getElementById('tea-color').value + }; + + const teaId = document.getElementById('tea-id').value; + + if (teaId) { + // Update existing tea + this.updateTea(teaId, formData); + this.showToast('Tee erfolgreich aktualisiert!', 'success'); + } else { + // Add new tea + this.addTea(formData); + this.showToast('Tee erfolgreich hinzugefügt!', 'success'); + } + + this.closeTeaModal(); + this.renderTeasList(); + this.renderTrackForm(); + this.updateDashboard(); + this.updateStats(); + } + + editTea(teaId) { + this.openTeaModal(teaId); + } + + showDeleteTeaConfirm(teaId) { + this.currentTeaId = teaId; + const tea = this.getTeaById(teaId); + document.getElementById('confirm-message').textContent = + `Möchtest du den Tee "${tea.name}" wirklich löschen? Alle zugehörigen Einträge werden ebenfalls gelöscht.`; + document.getElementById('confirm-modal').classList.add('active'); + } + + handleDeleteTea() { + if (this.currentTeaId) { + const tea = this.getTeaById(this.currentTeaId); + if (tea) { + document.getElementById('confirm-message').textContent = + `Möchtest du den Tee "${tea.name}" wirklich löschen? Alle zugehörigen Einträge werden ebenfalls gelöscht.`; + document.getElementById('confirm-modal').classList.add('active'); + } + } + } + + confirmDelete() { + if (this.currentTeaId) { + const tea = this.getTeaById(this.currentTeaId); + if (tea) { + this.deleteTea(this.currentTeaId); + this.showToast(`Tee "${tea.name}" wurde gelöscht.`, 'success'); + this.closeConfirmModal(); + this.closeTeaModal(); + this.renderTeasList(); + this.renderTrackForm(); + this.updateDashboard(); + this.updateStats(); + } + } + } + + closeConfirmModal() { + document.getElementById('confirm-modal').classList.remove('active'); + this.currentTeaId = null; + } + + // ============================================ + // Track Form + // ============================================ + + renderTrackForm() { + const teaSelect = document.getElementById('track-tea'); + const today = new Date().toISOString().split('T')[0]; + + // Set default date to today + document.getElementById('track-date').value = today; + + // Populate tea dropdown + if (this.teas.length === 0) { + teaSelect.innerHTML = ''; + } else { + teaSelect.innerHTML = '' + + this.teas.map(tea => + `` + ).join(''); + } + } + + handleTrackFormSubmit(e) { + e.preventDefault(); + + const teaId = document.getElementById('track-tea').value; + const date = document.getElementById('track-date').value; + const time = document.getElementById('track-time').value; + const amount = document.getElementById('track-amount').value; + const notes = document.getElementById('track-notes').value.trim(); + + if (!teaId || !date || !amount) { + this.showToast('Bitte fülle alle Pflichtfelder aus!', 'error'); + return; + } + + const entry = { + teaId, + date, + time, + amount, + notes + }; + + this.addEntry(entry); + this.showToast('Tee-Konsum erfolgreich getrackt!', 'success'); + + // Reset form + this.resetTrackForm(); + + // Update UI + this.updateDashboard(); + this.updateStats(); + } + + resetTrackForm() { + const form = document.getElementById('track-form'); + form.reset(); + const today = new Date().toISOString().split('T')[0]; + document.getElementById('track-date').value = today; + document.getElementById('track-amount').value = 1; + } + + // ============================================ + // Statistics + // ============================================ + + initCharts() { + // Initialize Chart.js + this.createTeaTypeChart(); + this.createDailyChart(); + } + + createTeaTypeChart() { + const ctx = document.getElementById('tea-type-chart'); + if (!ctx) return; + + const period = document.getElementById('stats-period').value || 'week'; + const typeCounts = this.getCupsByTeaType(period); + + const labels = Object.keys(typeCounts); + const data = Object.values(typeCounts); + + const backgroundColors = labels.map((type, index) => { + const colors = [ + '#8B4513', '#228B22', '#90EE90', '#FFD700', + '#FF6347', '#FF69B4', '#4169E1', '#8A2BE2' + ]; + return colors[index % colors.length]; + }); + + if (this.charts.teaType) { + this.charts.teaType.destroy(); + } + + this.charts.teaType = new Chart(ctx, { + type: 'doughnut', + data: { + labels: labels, + datasets: [{ + data: data, + backgroundColor: backgroundColors, + borderWidth: 2, + borderColor: '#fff' + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'right', + labels: { + padding: 15, + font: { + size: 12 + } + } + }, + tooltip: { + callbacks: { + label: function(context) { + const label = context.label || ''; + const value = context.raw || 0; + return `${label}: ${value} Tasse${value > 1 ? 'n' : ''}`; + } + } + } + }, + layout: { + padding: 20 + } + } + }); + } + + createDailyChart() { + const ctx = document.getElementById('daily-chart'); + if (!ctx) return; + + const period = document.getElementById('stats-period').value || 'week'; + const dailyCounts = this.getDailyConsumption(period); + + // Sort dates chronologically + const sortedDates = Object.keys(dailyCounts).sort(); + const labels = sortedDates.map(date => { + const d = new Date(date); + return period === 'year' ? d.toLocaleDateString('de-DE', { month: 'short', day: 'numeric' }) : d.toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric' }); + }); + const data = sortedDates.map(date => dailyCounts[date]); + + if (this.charts.daily) { + this.charts.daily.destroy(); + } + + this.charts.daily = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Tassen pro Tag', + data: data, + backgroundColor: 'rgba(139, 69, 19, 0.7)', + borderColor: 'rgba(139, 69, 19, 1)', + borderWidth: 2 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function(context) { + return `${context.raw} Tasse${context.raw > 1 ? 'n' : ''}`; + } + } + } + }, + scales: { + y: { + beginAtZero: true, + ticks: { + stepSize: 1, + callback: function(value) { + return value + (value === 1 ? ' Tasse' : ' Tassen'); + } + } + } + }, + layout: { + padding: 20 + } + } + }); + } + + updateStats() { + const period = document.getElementById('stats-period').value || 'week'; + + // Update charts + this.createTeaTypeChart(); + this.createDailyChart(); + + // Update detailed stats + this.renderDetailedStats(period); + } + + renderDetailedStats(period) { + const container = document.getElementById('stats-details'); + const entries = this.getEntriesByPeriod(period); + const totalCups = entries.reduce((sum, entry) => sum + entry.amount, 0); + const uniqueDays = this.countUniqueDays(entries); + const avgDaily = uniqueDays > 0 ? Math.round(totalCups / uniqueDays) : 0; + const mostConsumed = this.getMostConsumedTea(period); + const teaCount = new Set(entries.map(entry => entry.teaId)).size; + + container.innerHTML = ` +
+
Gesamt getrunken
+
${totalCups} Tassen
+
+
+
Durchschnitt pro Tag
+
${avgDaily} Tassen
+
+
+
Aktive Tage
+
${uniqueDays} Tage
+
+
+
Verschiedene Tees
+
${teaCount} Sorten
+
+ ${mostConsumed.name ? ` +
+
Meist getrunken
+
${mostConsumed.name}
+
+
+
Favorit (Anzahl)
+
${mostConsumed.count} Tassen
+
+ ` : ''} + `; + } + + // ============================================ + // Utility Functions + // ============================================ + + showToast(message, type = 'info') { + // Remove existing toasts + const existingToast = document.querySelector('.toast'); + if (existingToast) { + existingToast.remove(); + } + + const toast = document.createElement('div'); + toast.className = `toast ${type}`; + toast.textContent = message; + document.body.appendChild(toast); + + setTimeout(() => { + toast.style.animation = 'toastSlideIn 0.3s ease reverse'; + setTimeout(() => toast.remove(), 300); + }, 3000); + } +} + +// Initialize the application +let app; +document.addEventListener('DOMContentLoaded', () => { + app = new TeaTracker(); + window.app = app; +}); diff --git a/index.html b/index.html new file mode 100644 index 0000000..9714d79 --- /dev/null +++ b/index.html @@ -0,0 +1,223 @@ + + + + + + TeeTracker - Dein Tee-Konsum Tracker + + + +
+
+

🍵 TeeTracker

+

Verwalte deine Tee-Sorten und tracke deinen Konsum

+
+ + + +
+ +
+
+
+

Gesamt getrunken

+
0 Tassen
+
+
+

Heute

+
0 Tassen
+
+
+

Diese Woche

+
0 Tassen
+
+
+

Tee-Sorten

+
0 Sorten
+
+
+ +
+

Letzte Aktivitäten

+
+
+
+ + +
+
+

Tee-Sorten

+ +
+ +
+ + +
+ +
+
+ + +
+

Tee-Konsum tracken

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + +
+

Statistiken

+ +
+ + +
+ +
+
+

Tee-Konsum nach Sorte

+ +
+ +
+

Tagesverlauf

+ +
+
+ +
+

Detaillierte Statistiken

+
+
+
+
+ + + + + + +
+ + + + + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..539452e --- /dev/null +++ b/styles.css @@ -0,0 +1,765 @@ +/* TeeTracker - Styles */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary-color: #8B4513; + --primary-light: #A0522D; + --primary-dark: #654321; + --secondary-color: #4ECDC4; + --background-color: #F5F5F5; + --card-bg: #FFFFFF; + --text-color: #333333; + --text-light: #666666; + --success-color: #28a745; + --danger-color: #dc3545; + --warning-color: #ffc107; + --border-radius: 8px; + --box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + --transition: all 0.3s ease; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: var(--background-color); + color: var(--text-color); + line-height: 1.6; + min-height: 100vh; +} + +.app-container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* Header */ +header { + text-align: center; + margin-bottom: 30px; + padding: 20px; + background: linear-gradient(135deg, var(--primary-color), var(--primary-dark)); + border-radius: var(--border-radius); + color: white; + box-shadow: var(--box-shadow); +} + +header h1 { + font-size: 2.5rem; + margin-bottom: 10px; +} + +header p { + font-size: 1.1rem; + opacity: 0.9; +} + +/* Tabs */ +.tabs { + display: flex; + justify-content: center; + gap: 10px; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.tab-button { + padding: 12px 24px; + background-color: var(--card-bg); + border: 2px solid var(--primary-color); + border-radius: var(--border-radius); + cursor: pointer; + font-size: 1rem; + font-weight: 600; + color: var(--primary-color); + transition: var(--transition); + box-shadow: var(--box-shadow); +} + +.tab-button:hover { + background-color: var(--primary-light); + color: white; +} + +.tab-button.active { + background-color: var(--primary-color); + color: white; +} + +/* Tab Content */ +.tab-content { + min-height: 60vh; +} + +.tab-pane { + display: none; + animation: fadeIn 0.3s ease; +} + +.tab-pane.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Dashboard Cards */ +.dashboard-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.card { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: 20px; + box-shadow: var(--box-shadow); + text-align: center; + transition: var(--transition); +} + +.card:hover { + transform: translateY(-5px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); +} + +.card h3 { + font-size: 1rem; + color: var(--text-light); + margin-bottom: 10px; + text-transform: uppercase; + letter-spacing: 1px; +} + +.card-value { + font-size: 2rem; + font-weight: bold; + color: var(--primary-color); +} + +/* Recent Activity */ +.recent-activity { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: 20px; + box-shadow: var(--box-shadow); +} + +.recent-activity h3 { + margin-bottom: 15px; + color: var(--text-color); + border-bottom: 2px solid var(--primary-light); + padding-bottom: 10px; +} + +.activity-list { + max-height: 300px; + overflow-y: auto; +} + +.activity-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px; + border-bottom: 1px solid #eee; + transition: var(--transition); +} + +.activity-item:last-child { + border-bottom: none; +} + +.activity-item:hover { + background-color: #f9f9f9; +} + +.activity-info { + flex: 1; +} + +.activity-tea { + font-weight: 600; + color: var(--primary-color); + margin-bottom: 5px; +} + +.activity-details { + font-size: 0.9rem; + color: var(--text-light); +} + +.activity-amount { + font-weight: bold; + color: var(--secondary-color); + font-size: 1.1rem; +} + +/* Teas Section */ +.teas-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + flex-wrap: wrap; + gap: 15px; +} + +.teas-header h2 { + color: var(--text-color); +} + +.tea-filters { + display: flex; + gap: 15px; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.tea-filters input, +.tea-filters select { + padding: 10px 15px; + border: 2px solid #ddd; + border-radius: var(--border-radius); + font-size: 1rem; + flex: 1; + min-width: 200px; + transition: var(--transition); +} + +.tea-filters input:focus, +.tea-filters select:focus { + outline: none; + border-color: var(--primary-color); +} + +.teas-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 20px; +} + +.tea-card { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: 20px; + box-shadow: var(--box-shadow); + cursor: pointer; + transition: var(--transition); + border-left: 5px solid var(--primary-color); +} + +.tea-card:hover { + transform: translateY(-5px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); +} + +.tea-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 10px; +} + +.tea-name { + font-size: 1.2rem; + font-weight: 600; + color: var(--text-color); +} + +.tea-type { + background-color: var(--primary-light); + color: white; + padding: 4px 10px; + border-radius: 20px; + font-size: 0.8rem; + text-transform: uppercase; +} + +.tea-brand { + font-size: 0.9rem; + color: var(--text-light); + margin-bottom: 10px; +} + +.tea-description { + font-size: 0.9rem; + color: var(--text-light); + margin-bottom: 15px; + line-height: 1.5; +} + +.tea-actions { + display: flex; + gap: 10px; +} + +.tea-action-btn { + padding: 6px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.8rem; + transition: var(--transition); +} + +.tea-edit-btn { + background-color: var(--secondary-color); + color: white; +} + +.tea-edit-btn:hover { + background-color: #3aa89e; +} + +.tea-delete-btn { + background-color: var(--danger-color); + color: white; +} + +.tea-delete-btn:hover { + background-color: #c82333; +} + +/* Track Form */ +.track-form { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: 30px; + box-shadow: var(--box-shadow); + max-width: 600px; + margin: 0 auto; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: var(--text-color); +} + +.form-group input[type="text"], +.form-group input[type="number"], +.form-group input[type="date"], +.form-group input[type="time"], +.form-group input[type="color"], +.form-group select, +.form-group textarea { + width: 100%; + padding: 12px; + border: 2px solid #ddd; + border-radius: var(--border-radius); + font-size: 1rem; + transition: var(--transition); + font-family: inherit; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--primary-color); +} + +.form-group textarea { + resize: vertical; + min-height: 80px; +} + +.form-actions { + display: flex; + gap: 15px; + justify-content: flex-end; + margin-top: 10px; +} + +/* Buttons */ +.btn { + padding: 12px 24px; + border: none; + border-radius: var(--border-radius); + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: var(--transition); + display: inline-flex; + align-items: center; + gap: 8px; +} + +.btn-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-primary:hover { + background-color: var(--primary-dark); +} + +.btn-secondary { + background-color: #6c757d; + color: white; +} + +.btn-secondary:hover { + background-color: #5a6268; +} + +.btn-danger { + background-color: var(--danger-color); + color: white; +} + +.btn-danger:hover { + background-color: #c82333; +} + +.btn-success { + background-color: var(--success-color); + color: white; +} + +.btn-success:hover { + background-color: #218838; +} + +/* Stats Section */ +.stats-filters { + margin-bottom: 20px; +} + +.stats-filters label { + margin-right: 10px; + font-weight: 600; +} + +.stats-filters select { + padding: 10px 15px; + border: 2px solid #ddd; + border-radius: var(--border-radius); + font-size: 1rem; +} + +.stats-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.chart-container { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: 20px; + box-shadow: var(--box-shadow); +} + +.chart-container h3 { + margin-bottom: 15px; + color: var(--text-color); + text-align: center; +} + +.chart-container canvas { + max-height: 300px; +} + +.stats-details { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: 20px; + box-shadow: var(--box-shadow); +} + +.stats-details h3 { + margin-bottom: 15px; + color: var(--text-color); +} + +.details-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 15px; +} + +.detail-item { + background-color: #f9f9f9; + padding: 15px; + border-radius: var(--border-radius); + text-align: center; +} + +.detail-item-label { + font-size: 0.9rem; + color: var(--text-light); + margin-bottom: 5px; +} + +.detail-item-value { + font-size: 1.3rem; + font-weight: bold; + color: var(--primary-color); +} + +/* Modal */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + align-items: center; + justify-content: center; + padding: 20px; +} + +.modal.active { + display: flex; +} + +.modal-content { + background-color: var(--card-bg); + border-radius: var(--border-radius); + width: 100%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); + animation: modalSlideIn 0.3s ease; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 2px solid #eee; +} + +.modal-header h2 { + color: var(--text-color); +} + +.close-btn { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--text-light); + transition: var(--transition); +} + +.close-btn:hover { + color: var(--danger-color); +} + +.modal-form { + padding: 20px; +} + +.modal-body { + padding: 20px; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 15px; + padding: 20px; + border-top: 2px solid #eee; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--primary-dark); +} + +/* Responsive */ +@media (max-width: 768px) { + .app-container { + padding: 10px; + } + + header h1 { + font-size: 2rem; + } + + .tabs { + flex-direction: column; + align-items: stretch; + } + + .tab-button { + width: 100%; + } + + .dashboard-cards { + grid-template-columns: repeat(2, 1fr); + } + + .stats-container { + grid-template-columns: 1fr; + } + + .tea-filters { + flex-direction: column; + } + + .tea-filters input, + .tea-filters select { + width: 100%; + } + + .form-actions { + flex-direction: column; + align-items: stretch; + } + + .btn { + width: 100%; + justify-content: center; + } +} + +@media (max-width: 480px) { + .dashboard-cards { + grid-template-columns: 1fr; + } + + .teas-grid { + grid-template-columns: 1fr; + } + + .details-grid { + grid-template-columns: 1fr; + } +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: 40px; + color: var(--text-light); +} + +.empty-state-icon { + font-size: 3rem; + margin-bottom: 15px; +} + +.empty-state-text { + font-size: 1.1rem; + margin-bottom: 10px; +} + +.empty-state-subtext { + font-size: 0.9rem; +} + +/* Loading State */ +.loading { + text-align: center; + padding: 20px; +} + +.spinner { + border: 4px solid #f3f3f3; + border-top: 4px solid var(--primary-color); + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + margin: 0 auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Toast Notifications */ +.toast { + position: fixed; + bottom: 20px; + right: 20px; + background-color: var(--text-color); + color: white; + padding: 15px 25px; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + z-index: 1001; + animation: toastSlideIn 0.3s ease; +} + +.toast.success { + background-color: var(--success-color); +} + +.toast.error { + background-color: var(--danger-color); +} + +.toast.info { + background-color: var(--secondary-color); +} + +@keyframes toastSlideIn { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Tea Color Preview */ +.tea-color-preview { + width: 20px; + height: 20px; + border-radius: 50%; + display: inline-block; + margin-right: 10px; + border: 2px solid #ddd; +}