Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Tally Counter - Advanced Counting App</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary-color: #667eea; | |
| --secondary-color: #764ba2; | |
| --accent-color: #f093fb; | |
| --success-color: #4caf50; | |
| --danger-color: #f5576c; | |
| --warning-color: #fee140; | |
| --bg-primary: #0f0f23; | |
| --bg-secondary: #1a1a2e; | |
| --bg-card: rgba(255, 255, 255, 0.05); | |
| --text-primary: #ffffff; | |
| --text-secondary: #a0a0b8; | |
| --text-muted: #6b7280; | |
| --button-bg: rgba(255, 255, 255, 0.1); | |
| --button-hover: rgba(255, 255, 255, 0.15); | |
| --button-active: rgba(255, 255, 255, 0.2); | |
| --border-color: rgba(255, 255, 255, 0.1); | |
| --shadow: 0 10px 40px rgba(0, 0, 0, 0.3); | |
| --glass-bg: rgba(255, 255, 255, 0.1); | |
| --glass-border: rgba(255, 255, 255, 0.2); | |
| } | |
| body.light-theme { | |
| --bg-primary: #f3f4f6; | |
| --bg-secondary: #ffffff; | |
| --bg-card: rgba(0, 0, 0, 0.03); | |
| --text-primary: #1f2937; | |
| --text-secondary: #4b5563; | |
| --text-muted: #9ca3af; | |
| --button-bg: rgba(0, 0, 0, 0.05); | |
| --button-hover: rgba(0, 0, 0, 0.08); | |
| --button-active: rgba(0, 0, 0, 0.12); | |
| --border-color: rgba(0, 0, 0, 0.1); | |
| --shadow: 0 10px 40px rgba(0, 0, 0, 0.1); | |
| --glass-bg: rgba(255, 255, 255, 0.7); | |
| --glass-border: rgba(0, 0, 0, 0.1); | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow-x: hidden; | |
| } | |
| .animated-bg { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: -1; | |
| opacity: 0.3; | |
| pointer-events: none; | |
| } | |
| .animated-bg::before, | |
| .animated-bg::after { | |
| content: ''; | |
| position: absolute; | |
| width: 500px; | |
| height: 500px; | |
| border-radius: 50%; | |
| filter: blur(150px); | |
| animation: float 25s infinite ease-in-out; | |
| } | |
| .animated-bg::before { | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| top: -250px; | |
| right: -250px; | |
| } | |
| .animated-bg::after { | |
| background: linear-gradient(135deg, var(--accent-color), var(--danger-color)); | |
| bottom: -250px; | |
| left: -250px; | |
| animation-delay: 12.5s; | |
| } | |
| @keyframes float { | |
| 0%, | |
| 100% { | |
| transform: translate(0, 0) scale(1); | |
| } | |
| 33% { | |
| transform: translate(100px, -100px) scale(1.1); | |
| } | |
| 66% { | |
| transform: translate(-100px, 100px) scale(0.9); | |
| } | |
| } | |
| .header { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| border-bottom: 1px solid var(--border-color); | |
| padding: 20px; | |
| z-index: 100; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .header-left { | |
| display: flex; | |
| align-items: center; | |
| gap: 20px; | |
| } | |
| .logo { | |
| font-size: 24px; | |
| font-weight: 700; | |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .header-actions { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .theme-toggle { | |
| background: var(--button-bg); | |
| border: 1px solid var(--border-color); | |
| border-radius: 12px; | |
| width: 44px; | |
| height: 44px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| color: var(--text-primary); | |
| font-size: 20px; | |
| } | |
| .theme-toggle:hover { | |
| background: var(--button-hover); | |
| transform: rotate(180deg); | |
| } | |
| .main-container { | |
| flex: 1; | |
| padding: 100px 20px 40px; | |
| max-width: 1200px; | |
| width: 100%; | |
| margin: 0 auto; | |
| } | |
| .counters-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| } | |
| .counter-card { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid var(--border-color); | |
| border-radius: 24px; | |
| padding: 30px; | |
| box-shadow: var(--shadow); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .counter-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 4px; | |
| background: linear-gradient(90deg, var(--primary-color), var(--accent-color)); | |
| transform: scaleX(0); | |
| transition: transform 0.3s ease; | |
| } | |
| .counter-card:hover::before { | |
| transform: scaleX(1); | |
| } | |
| .counter-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 15px 50px rgba(0, 0, 0, 0.2); | |
| } | |
| .counter-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| } | |
| .counter-name { | |
| font-size: 18px; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .counter-name-input { | |
| background: transparent; | |
| border: none; | |
| color: var(--text-primary); | |
| font-size: 18px; | |
| font-weight: 600; | |
| outline: none; | |
| padding: 5px; | |
| border-radius: 8px; | |
| transition: background 0.3s ease; | |
| } | |
| .counter-name-input:hover { | |
| background: var(--button-hover); | |
| } | |
| .counter-actions { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .icon-btn { | |
| background: var(--button-bg); | |
| border: 1px solid var(--border-color); | |
| border-radius: 10px; | |
| width: 36px; | |
| height: 36px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| color: var(--text-secondary); | |
| } | |
| .icon-btn:hover { | |
| background: var(--button-hover); | |
| color: var(--text-primary); | |
| transform: scale(1.1); | |
| } | |
| .counter-display { | |
| text-align: center; | |
| margin: 30px 0; | |
| position: relative; | |
| } | |
| .counter-value { | |
| font-size: 72px; | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| line-height: 1; | |
| transition: all 0.3s ease; | |
| background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .counter-value.pulse { | |
| animation: valuePulse 0.3s ease; | |
| } | |
| @keyframes valuePulse { | |
| 0% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.1); | |
| } | |
| 100% { | |
| transform: scale(1); | |
| } | |
| } | |
| .counter-controls { | |
| display: flex; | |
| gap: 15px; | |
| justify-content: center; | |
| margin-top: 25px; | |
| } | |
| .control-btn { | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| border: none; | |
| border-radius: 16px; | |
| padding: 15px 25px; | |
| color: white; | |
| font-size: 18px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .control-btn::before { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 0; | |
| height: 0; | |
| border-radius: 50%; | |
| background: rgba(255, 255, 255, 0.3); | |
| transform: translate(-50%, -50%); | |
| transition: width 0.6s, height 0.6s; | |
| } | |
| .control-btn:active::before { | |
| width: 300px; | |
| height: 300px; | |
| } | |
| .control-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3); | |
| } | |
| .control-btn:active { | |
| transform: translateY(0); | |
| } | |
| .control-btn.decrement { | |
| background: linear-gradient(135deg, var(--danger-color), #ff6b6b); | |
| } | |
| .control-btn.reset { | |
| background: linear-gradient(135deg, var(--warning-color), #fbbf24); | |
| } | |
| .add-counter-btn { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| border: 2px dashed var(--border-color); | |
| border-radius: 24px; | |
| padding: 40px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| color: var(--text-secondary); | |
| } | |
| .add-counter-btn:hover { | |
| border-color: var(--primary-color); | |
| background: var(--button-hover); | |
| color: var(--primary-color); | |
| transform: translateY(-5px); | |
| } | |
| .add-icon { | |
| font-size: 48px; | |
| } | |
| .stats-container { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid var(--border-color); | |
| border-radius: 24px; | |
| padding: 30px; | |
| margin-bottom: 30px; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 20px; | |
| } | |
| .stat-item { | |
| text-align: center; | |
| padding: 20px; | |
| background: var(--button-bg); | |
| border-radius: 16px; | |
| transition: all 0.3s ease; | |
| } | |
| .stat-item:hover { | |
| background: var(--button-hover); | |
| transform: translateY(-3px); | |
| } | |
| .stat-value { | |
| font-size: 36px; | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| margin-bottom: 5px; | |
| } | |
| .stat-label { | |
| font-size: 14px; | |
| color: var(--text-secondary); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .history-container { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid var(--border-color); | |
| border-radius: 24px; | |
| padding: 30px; | |
| max-height: 400px; | |
| overflow-y: auto; | |
| } | |
| .history-header { | |
| font-size: 20px; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin-bottom: 20px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .history-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .history-item { | |
| background: var(--button-bg); | |
| border-radius: 12px; | |
| padding: 15px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| transition: all 0.3s ease; | |
| animation: slideIn 0.3s ease; | |
| } | |
| @keyframes slideIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| .history-item:hover { | |
| background: var(--button-hover); | |
| transform: translateX(5px); | |
| } | |
| .history-time { | |
| font-size: 12px; | |
| color: var(--text-muted); | |
| } | |
| .history-action { | |
| font-size: 14px; | |
| color: var(--text-secondary); | |
| } | |
| .history-value { | |
| font-size: 18px; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| } | |
| .clear-history-btn { | |
| background: var(--danger-color); | |
| border: none; | |
| border-radius: 10px; | |
| padding: 8px 16px; | |
| color: white; | |
| font-size: 14px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .clear-history-btn:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 5px 15px rgba(245, 87, 108, 0.3); | |
| } | |
| @media (max-width: 768px) { | |
| .counters-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .counter-value { | |
| font-size: 56px; | |
| } | |
| .stats-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .control-btn { | |
| padding: 12px 20px; | |
| font-size: 16px; | |
| } | |
| } | |
| .toast { | |
| position: fixed; | |
| bottom: 30px; | |
| right: 30px; | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid var(--border-color); | |
| border-radius: 16px; | |
| padding: 16px 24px; | |
| color: var(--text-primary); | |
| font-weight: 500; | |
| box-shadow: var(--shadow); | |
| transform: translateX(400px); | |
| transition: transform 0.3s ease; | |
| z-index: 1000; | |
| } | |
| .toast.show { | |
| transform: translateX(0); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="animated-bg"></div> | |
| <header class="header"> | |
| <div class="header-left"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" | |
| style="display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--text-secondary); transition: color 0.3s ease;"> | |
| <span>โก</span> | |
| <span style="font-size: 14px;">Built with anycoder</span> | |
| </a> | |
| <div class="logo"> | |
| <span>๐</span> | |
| <span>Tally Counter</span> | |
| </div> | |
| </div> | |
| <div class="header-actions"> | |
| <button class="theme-toggle" onclick="toggleTheme()"> | |
| <span id="themeIcon">๐</span> | |
| </button> | |
| </div> | |
| </header> | |
| <main class="main-container"> | |
| <div class="stats-container"> | |
| <div class="stats-grid"> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="totalCounters">1</div> | |
| <div class="stat-label">Total Counters</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="totalCount">0</div> | |
| <div class="stat-label">Total Count</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="maxCount">0</div> | |
| <div class="stat-label">Highest Count</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="avgCount">0</div> | |
| <div class="stat-label">Average Count</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="counters-grid" id="countersGrid"> | |
| <!-- Counters will be dynamically added here --> | |
| </div> | |
| <div class="add-counter-btn" onclick="addCounter()"> | |
| <span class="add-icon">โ</span> | |
| <span>Add New Counter</span> | |
| </div> | |
| <div class="history-container"> | |
| <div class="history-header"> | |
| <span>๐ Activity History</span> | |
| <button class="clear-history-btn" onclick="clearHistory()">Clear History</button> | |
| </div> | |
| <div class="history-list" id="historyList"> | |
| <!-- History items will be dynamically added here --> | |
| </div> | |
| </div> | |
| </main> | |
| <div class="toast" id="toast"></div> | |
| <script> | |
| let counters = [ | |
| { id: 1, name: 'Counter 1', value: 0, color: '#667eea' } | |
| ]; | |
| let history = []; | |
| let counterIdCounter = 2; | |
| const colors = ['#667eea', '#764ba2', '#f093fb', '#f5576c', '#4facfe', '#00f2fe', '#43e97b', '#38f9d7']; | |
| function initializeApp() { | |
| renderCounters(); | |
| updateStats(); | |
| loadFromLocalStorage(); | |
| } | |
| function renderCounters() { | |
| const grid = document.getElementById('countersGrid'); | |
| grid.innerHTML = ''; | |
| counters.forEach(counter => { | |
| const card = createCounterCard(counter); | |
| grid.appendChild(card); | |
| }); | |
| document.getElementById('totalCounters').textContent = counters.length; | |
| } | |
| function createCounterCard(counter) { | |
| const card = document.createElement('div'); | |
| card.className = 'counter-card'; | |
| card.innerHTML = ` | |
| <div class="counter-header"> | |
| <div class="counter-name"> | |
| <input type="text" class="counter-name-input" value="${counter.name}" | |
| onchange="updateCounterName(${counter.id}, this.value)" | |
| onfocus="this.select()"> | |
| </div> | |
| <div class="counter-actions"> | |
| <button class="icon-btn" onclick="duplicateCounter(${counter.id})" title="Duplicate"> | |
| ๐ | |
| </button> | |
| <button class="icon-btn" onclick="deleteCounter(${counter.id})" title="Delete"> | |
| ๐๏ธ | |
| </button> | |
| </div> | |
| </div> | |
| <div class="counter-display"> | |
| <div class="counter-value" id="value-${counter.id}">${counter.value}</div> | |
| </div> | |
| <div class="counter-controls"> | |
| <button class="control-btn decrement" onclick="decrementCounter(${counter.id})"> | |
| <span>โ</span> | |
| <span>-1</span> | |
| </button> | |
| <button class="control-btn reset" onclick="resetCounter(${counter.id})"> | |
| <span>๐</span> | |
| <span>Reset</span> | |
| </button> | |
| <button class="control-btn" onclick="incrementCounter(${counter.id})"> | |
| <span>โ</span> | |
| <span>+1</span> | |
| </button> | |
| </div> | |
| `; | |
| return card; | |
| } | |
| function addCounter() { | |
| const newCounter = { | |
| id: counterIdCounter++, | |
| name: `Counter ${counters.length + 1}`, | |
| value: 0, | |
| color: colors[counters.length % colors.length] | |
| }; | |
| counters.push(newCounter); | |
| renderCounters(); | |
| updateStats(); | |
| saveToLocalStorage(); | |
| showToast('New counter added! ๐'); | |
| addToHistory('Created', newCounter.name, 0); | |
| } | |
| function deleteCounter(id) { | |
| if (counters.length <= 1) { | |
| showToast('Cannot delete the last counter! ๐ซ'); | |
| return; | |
| } | |
| const counter = counters.find(c => c.id === id); | |
| counters = counters.filter(c => c.id !== id); | |
| renderCounters(); | |
| updateStats(); | |
| saveToLocalStorage(); | |
| showToast('Counter deleted! ๐๏ธ'); | |
| addToHistory('Deleted', counter.name, counter.value); | |
| } | |
| function duplicateCounter(id) { | |
| const original = counters.find(c => c.id === id); | |
| const newCounter = { | |
| id: counterIdCounter++, | |
| name: `${original.name} (Copy)`, | |
| value: original.value, | |
| color: original.color | |
| }; | |
| counters.push(newCounter); | |
| renderCounters(); | |
| updateStats(); | |
| saveToLocalStorage(); | |
| showToast('Counter duplicated! ๐'); | |
| addToHistory('Duplicated', newCounter.name, newCounter.value); | |
| } | |
| function updateCounterName(id, newName) { | |
| const counter = counters.find(c => c.id === id); | |
| const oldName = counter.name; | |
| counter.name = newName || `Counter ${id}`; | |
| saveToLocalStorage(); | |
| showToast('Counter renamed! โ๏ธ'); | |
| addToHistory('Renamed', `${oldName} โ ${counter.name}`, counter.value); | |
| } | |
| function incrementCounter(id) { | |
| const counter = counters.find(c => c.id === id); | |
| counter.value++; | |
| updateCounterDisplay(id); | |
| updateStats(); | |
| saveToLocalStorage(); | |
| addToHistory('Incremented', counter.name, counter.value); | |
| } | |
| function decrementCounter(id) { | |
| const counter = counters.find(c => c.id === id); | |
| if (counter.value > 0) { | |
| counter.value--; | |
| updateCounterDisplay(id); | |
| updateStats(); | |
| saveToLocalStorage(); | |
| addToHistory('Decremented', counter.name, counter.value); | |
| } else { | |
| showToast('Counter cannot go below 0! โ ๏ธ'); | |
| } | |
| } | |
| function resetCounter(id) { | |
| const counter = counters.find(c => c.id === id); | |
| const oldValue = counter.value; | |
| counter.value = 0; | |
| updateCounterDisplay(id); | |
| updateStats(); | |
| saveToLocalStorage(); | |
| showToast('Counter reset! ๐'); | |
| addToHistory('Reset', counter.name, 0); | |
| } | |
| function updateCounterDisplay(id) { | |
| const display = document.getElementById(`value-${id}`); | |
| const counter = counters.find(c => c.id === id); | |
| display.textContent = counter.value; | |
| display.classList.add('pulse'); | |
| setTimeout(() => display.classList.remove('pulse'), 300); | |
| } | |
| function updateStats() { | |
| const totalCount = counters.reduce((sum, c) => sum + c.value, 0); | |
| const maxCount = Math.max(...counters.map(c => c.value)); | |
| const avgCount = counters.length > 0 ? Math.round(totalCount / counters.length) : 0; | |
| document.getElementById('totalCount').textContent = totalCount; | |
| document.getElementById('maxCount').textContent = maxCount; | |
| document.getElementById('avgCount').textContent = avgCount; | |
| } | |
| function addToHistory(action, counterName, value) { | |
| const now = new Date(); | |
| const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| history.unshift({ | |
| action, | |
| counterName, | |
| value, | |
| time: timeString | |
| }); | |
| if (history.length > 50) { | |
| history = history.slice(0, 50); | |
| } | |
| renderHistory(); | |
| } | |
| function renderHistory() { | |
| const historyList = document.getElementById('historyList'); | |
| historyList.innerHTML = ''; | |
| if (history.length === 0) { | |
| historyList.innerHTML = '<div style="text-align: center; color: var(--text-muted); padding: 20px;">No activity yet. Start counting! ๐</div>'; | |
| return; | |
| } | |
| history.forEach(item => { | |
| const historyItem = document.createElement('div'); | |
| historyItem.className = 'history-item'; | |
| historyItem.innerHTML = ` | |
| <div> | |
| <div class="history-action">${item.action}: ${item.counterName}</div> | |
| <div class="history-time">${item.time}</div> | |
| </div> | |
| <div class="history-value">${item.value}</div> | |
| `; | |
| historyList.appendChild(historyItem); | |
| }); | |
| } | |
| function clearHistory() { | |
| if (confirm('Are you sure you want to clear all history?')) { | |
| history = []; | |
| renderHistory(); | |
| showToast('History cleared! ๐งน'); | |
| } | |
| } | |
| function toggleTheme() { | |
| const body = document.body; | |
| const themeIcon = document.getElementById('themeIcon'); | |
| body.classList.toggle('light-theme'); | |
| themeIcon.textContent = body.classList.contains('light-theme') ? 'โ๏ธ' : '๐'; | |
| localStorage.setItem('theme', body.classList.contains('light-theme') ? 'light' : 'dark'); | |
| } | |
| function showToast(message) { | |
| const toast = document.getElementById('toast'); | |
| toast.textContent = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| function saveToLocalStorage() { | |
| localStorage.setItem('counters', JSON.stringify(counters)); | |
| localStorage.setItem('history', JSON.stringify(history)); | |
| } | |
| function loadFromLocalStorage() { | |
| const savedCounters = localStorage.getItem('counters'); | |
| const savedHistory = localStorage.getItem('history'); | |
| const savedTheme = localStorage.getItem('theme'); | |
| if (savedCounters) { | |
| counters = JSON.parse(savedCounters); | |
| counterIdCounter = Math.max(...counters.map(c => c.id)) + 1; | |
| } | |
| if (savedHistory) { | |
| history = JSON.parse(savedHistory); | |
| } | |
| if (savedTheme === 'light') { | |
| document.body.classList.add('light-theme'); | |
| document.getElementById('themeIcon').textContent = 'โ๏ธ'; | |
| } | |
| renderCounters(); | |
| renderHistory(); | |
| updateStats(); | |
| } | |
| // Keyboard shortcuts | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key >= '1' && e.key <= '9') { | |
| const index = parseInt(e.key) - 1; | |
| if (index < counters.length) { | |
| incrementCounter(counters[index].id); | |
| } | |
| } else if (e.key === 'n' || e.key === 'N') { | |
| addCounter(); | |
| } else if (e.key === 't' || e.key === 'T') { | |
| toggleTheme(); | |
| } else if (e.key === 'h' || e.key === 'H') { | |
| clearHistory(); | |
| } | |
| }); | |
| // Initialize the app | |
| initializeApp(); | |
| </script> | |
| </body> | |
| </html> |