Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Todo - Apple Style</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary-color: #007AFF; | |
| --secondary-color: #5856D6; | |
| --success-color: #34C759; | |
| --danger-color: #FF3B30; | |
| --warning-color: #FF9500; | |
| --background: #F2F2F7; | |
| --surface: #FFFFFF; | |
| --text-primary: #1C1C1E; | |
| --text-secondary: #8E8E93; | |
| --text-tertiary: #C7C7CC; | |
| --border-color: #D1D1D6; | |
| --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.04); | |
| --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08); | |
| --shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.12); | |
| --radius-sm: 8px; | |
| --radius-md: 12px; | |
| --radius-lg: 16px; | |
| --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| :root { | |
| --background: #000000; | |
| --surface: #1C1C1E; | |
| --text-primary: #FFFFFF; | |
| --text-secondary: #8E8E93; | |
| --text-tertiary: #48484A; | |
| --border-color: #38383A; | |
| --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3); | |
| --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.4); | |
| --shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.5); | |
| } | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| background: var(--background); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| line-height: 1.6; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| } | |
| header { | |
| background: var(--surface); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border-bottom: 1px solid var(--border-color); | |
| padding: 20px; | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .header-content { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| font-size: 24px; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| } | |
| .logo-icon { | |
| width: 32px; | |
| height: 32px; | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-size: 20px; | |
| } | |
| .header-actions { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .theme-toggle { | |
| background: var(--background); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-sm); | |
| width: 40px; | |
| height: 40px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| font-size: 18px; | |
| } | |
| .theme-toggle:hover { | |
| background: var(--border-color); | |
| transform: scale(1.05); | |
| } | |
| main { | |
| flex: 1; | |
| padding: 40px 20px; | |
| max-width: 800px; | |
| width: 100%; | |
| margin: 0 auto; | |
| } | |
| .todo-container { | |
| background: var(--surface); | |
| border-radius: var(--radius-lg); | |
| box-shadow: var(--shadow-md); | |
| overflow: hidden; | |
| animation: slideUp 0.5s ease-out; | |
| } | |
| @keyframes slideUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .todo-header { | |
| padding: 24px; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .todo-input-wrapper { | |
| display: flex; | |
| gap: 12px; | |
| margin-bottom: 20px; | |
| } | |
| .todo-input { | |
| flex: 1; | |
| padding: 14px 16px; | |
| border: 2px solid var(--border-color); | |
| border-radius: var(--radius-md); | |
| font-size: 16px; | |
| background: var(--background); | |
| color: var(--text-primary); | |
| transition: var(--transition); | |
| outline: none; | |
| } | |
| .todo-input:focus { | |
| border-color: var(--primary-color); | |
| background: var(--surface); | |
| box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.1); | |
| } | |
| .add-btn { | |
| padding: 14px 24px; | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| color: white; | |
| border: none; | |
| border-radius: var(--radius-md); | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .add-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow-md); | |
| } | |
| .add-btn:active { | |
| transform: translateY(0); | |
| } | |
| .filter-tabs { | |
| display: flex; | |
| gap: 8px; | |
| padding: 4px; | |
| background: var(--background); | |
| border-radius: var(--radius-md); | |
| } | |
| .filter-tab { | |
| flex: 1; | |
| padding: 10px; | |
| background: transparent; | |
| border: none; | |
| border-radius: var(--radius-sm); | |
| font-size: 14px; | |
| font-weight: 500; | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .filter-tab.active { | |
| background: var(--surface); | |
| color: var(--primary-color); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .filter-tab:hover:not(.active) { | |
| color: var(--text-primary); | |
| } | |
| .todo-list { | |
| min-height: 300px; | |
| max-height: 500px; | |
| overflow-y: auto; | |
| padding: 16px; | |
| } | |
| .todo-list::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .todo-list::-webkit-scrollbar-track { | |
| background: var(--background); | |
| } | |
| .todo-list::-webkit-scrollbar-thumb { | |
| background: var(--border-color); | |
| border-radius: 3px; | |
| } | |
| .todo-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 16px; | |
| background: var(--background); | |
| border-radius: var(--radius-md); | |
| margin-bottom: 8px; | |
| transition: var(--transition); | |
| animation: fadeIn 0.3s ease-out; | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| .todo-item:hover { | |
| background: var(--surface); | |
| box-shadow: var(--shadow-sm); | |
| transform: translateX(4px); | |
| } | |
| .todo-checkbox { | |
| width: 24px; | |
| height: 24px; | |
| border: 2px solid var(--border-color); | |
| border-radius: 50%; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| position: relative; | |
| flex-shrink: 0; | |
| } | |
| .todo-checkbox:hover { | |
| border-color: var(--primary-color); | |
| transform: scale(1.1); | |
| } | |
| .todo-checkbox.checked { | |
| background: var(--success-color); | |
| border-color: var(--success-color); | |
| } | |
| .todo-checkbox.checked::after { | |
| content: 'β'; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: white; | |
| font-size: 14px; | |
| font-weight: bold; | |
| } | |
| .todo-text { | |
| flex: 1; | |
| font-size: 16px; | |
| color: var(--text-primary); | |
| transition: var(--transition); | |
| cursor: text; | |
| padding: 4px; | |
| border-radius: var(--radius-sm); | |
| outline: none; | |
| } | |
| .todo-text.completed { | |
| text-decoration: line-through; | |
| color: var(--text-tertiary); | |
| } | |
| .todo-text:focus { | |
| background: var(--surface); | |
| padding: 4px 8px; | |
| } | |
| .todo-actions { | |
| display: flex; | |
| gap: 8px; | |
| opacity: 0; | |
| transition: var(--transition); | |
| } | |
| .todo-item:hover .todo-actions { | |
| opacity: 1; | |
| } | |
| .action-btn { | |
| width: 32px; | |
| height: 32px; | |
| border: none; | |
| background: var(--surface); | |
| border-radius: var(--radius-sm); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: var(--transition); | |
| color: var(--text-secondary); | |
| } | |
| .action-btn:hover { | |
| background: var(--danger-color); | |
| color: white; | |
| transform: scale(1.1); | |
| } | |
| .empty-state { | |
| text-align: center; | |
| padding: 60px 20px; | |
| color: var(--text-secondary); | |
| } | |
| .empty-icon { | |
| font-size: 64px; | |
| margin-bottom: 16px; | |
| opacity: 0.5; | |
| } | |
| .empty-text { | |
| font-size: 18px; | |
| margin-bottom: 8px; | |
| } | |
| .empty-subtext { | |
| font-size: 14px; | |
| color: var(--text-tertiary); | |
| } | |
| .todo-footer { | |
| padding: 20px; | |
| border-top: 1px solid var(--border-color); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-size: 14px; | |
| color: var(--text-secondary); | |
| } | |
| .todo-count { | |
| font-weight: 500; | |
| } | |
| .clear-btn { | |
| padding: 8px 16px; | |
| background: transparent; | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-sm); | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| font-size: 14px; | |
| } | |
| .clear-btn:hover { | |
| background: var(--danger-color); | |
| border-color: var(--danger-color); | |
| color: white; | |
| transform: scale(1.05); | |
| } | |
| .stats { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); | |
| gap: 16px; | |
| margin-top: 24px; | |
| } | |
| .stat-card { | |
| background: var(--surface); | |
| padding: 20px; | |
| border-radius: var(--radius-md); | |
| text-align: center; | |
| box-shadow: var(--shadow-sm); | |
| transition: var(--transition); | |
| } | |
| .stat-card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: var(--shadow-md); | |
| } | |
| .stat-value { | |
| font-size: 32px; | |
| font-weight: 600; | |
| color: var(--primary-color); | |
| margin-bottom: 4px; | |
| } | |
| .stat-label { | |
| font-size: 14px; | |
| color: var(--text-secondary); | |
| } | |
| @media (max-width: 640px) { | |
| main { | |
| padding: 20px 16px; | |
| } | |
| .todo-input-wrapper { | |
| flex-direction: column; | |
| } | |
| .add-btn { | |
| width: 100%; | |
| justify-content: center; | |
| } | |
| .filter-tabs { | |
| flex-wrap: wrap; | |
| } | |
| .stats { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="header-content"> | |
| <div class="logo"> | |
| <div class="logo-icon">β</div> | |
| <span>Todo</span> | |
| </div> | |
| <div class="header-actions"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="text-decoration: none; color: var(--text-secondary); font-size: 14px; margin-right: 12px;"> | |
| Built with anycoder | |
| </a> | |
| <button class="theme-toggle" onclick="toggleTheme()">π</button> | |
| </div> | |
| </div> | |
| </header> | |
| <main> | |
| <div class="todo-container"> | |
| <div class="todo-header"> | |
| <div class="todo-input-wrapper"> | |
| <input | |
| type="text" | |
| class="todo-input" | |
| id="todoInput" | |
| placeholder="What needs to be done?" | |
| onkeypress="handleInputKeypress(event)" | |
| > | |
| <button class="add-btn" onclick="addTodo()"> | |
| <span>+</span> | |
| <span>Add</span> | |
| </button> | |
| </div> | |
| <div class="filter-tabs"> | |
| <button class="filter-tab active" onclick="filterTodos('all')">All</button> | |
| <button class="filter-tab" onclick="filterTodos('active')">Active</button> | |
| <button class="filter-tab" onclick="filterTodos('completed')">Completed</button> | |
| </div> | |
| </div> | |
| <div class="todo-list" id="todoList"> | |
| <div class="empty-state"> | |
| <div class="empty-icon">π</div> | |
| <div class="empty-text">No tasks yet</div> | |
| <div class="empty-subtext">Add your first task to get started</div> | |
| </div> | |
| </div> | |
| <div class="todo-footer"> | |
| <div class="todo-count"> | |
| <span id="activeCount">0</span> active tasks | |
| </div> | |
| <button class="clear-btn" onclick="clearCompleted()">Clear Completed</button> | |
| </div> | |
| </div> | |
| <div class="stats"> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="totalTasks">0</div> | |
| <div class="stat-label">Total Tasks</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="completedTasks">0</div> | |
| <div class="stat-label">Completed</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="completionRate">0%</div> | |
| <div class="stat-label">Completion Rate</div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| let todos = JSON.parse(localStorage.getItem('todos')) || []; | |
| let currentFilter = 'all'; | |
| let editingId = null; | |
| function generateId() { | |
| return Date.now().toString(36) + Math.random().toString(36).substr(2); | |
| } | |
| function saveTodos() { | |
| localStorage.setItem('todos', JSON.stringify(todos)); | |
| updateStats(); | |
| } | |
| function addTodo() { | |
| const input = document.getElementById('todoInput'); | |
| const text = input.value.trim(); | |
| if (text === '') { | |
| input.focus(); | |
| return; | |
| } | |
| const todo = { | |
| id: generateId(), | |
| text: text, | |
| completed: false, | |
| createdAt: new Date().toISOString() | |
| }; | |
| todos.unshift(todo); | |
| saveTodos(); | |
| renderTodos(); | |
| input.value = ''; | |
| input.focus(); | |
| } | |
| function handleInputKeypress(event) { | |
| if (event.key === 'Enter') { | |
| addTodo(); | |
| } | |
| } | |
| function toggleTodo(id) { | |
| const todo = todos.find(t => t.id === id); | |
| if (todo) { | |
| todo.completed = !todo.completed; | |
| saveTodos(); | |
| renderTodos(); | |
| } | |
| } | |
| function deleteTodo(id) { | |
| todos = todos.filter(t => t.id !== id); | |
| saveTodos(); | |
| renderTodos(); | |
| } | |
| function startEdit(id) { | |
| if (editingId && editingId !== id) { | |
| finishEdit(editingId); | |
| } | |
| editingId = id; | |
| const todo = todos.find(t => t.id === id); | |
| if (todo) { | |
| const textElement = document.querySelector(`[data-id="${id}"] .todo-text`); | |
| textElement.contentEditable = true; | |
| textElement.focus(); | |
| // Select all text | |
| const range = document.createRange(); | |
| range.selectNodeContents(textElement); | |
| const selection = window.getSelection(); | |
| selection.removeAllRanges(); | |
| selection.addRange(range); | |
| textElement.addEventListener('blur', () => finishEdit(id)); | |
| textElement.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| e.preventDefault(); | |
| textElement.blur(); | |
| } | |
| }); | |
| } | |
| } | |
| function finishEdit(id) { | |
| const textElement = document.querySelector(`[data-id="${id}"] .todo-text`); | |
| if (textElement) { | |
| const newText = textElement.textContent.trim(); | |
| const todo = todos.find(t => t.id === id); | |
| if (todo && newText !== '') { | |
| todo.text = newText; | |
| saveTodos(); | |
| } | |
| textElement.contentEditable = false; | |
| } | |
| editingId = null; | |
| } | |
| function filterTodos(filter) { | |
| currentFilter = filter; | |
| // Update active tab | |
| document.querySelectorAll('.filter-tab').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| event.target.classList.add('active'); | |
| renderTodos(); | |
| } | |
| function clearCompleted() { | |
| todos = todos.filter(t => !t.completed); | |
| saveTodos(); | |
| renderTodos(); | |
| } | |
| function getFilteredTodos() { | |
| switch (currentFilter) { | |
| case 'active': | |
| return todos.filter(t => !t.completed); | |
| case 'completed': | |
| return todos.filter(t => t.completed); | |
| default: | |
| return todos; | |
| } | |
| } | |
| function renderTodos() { | |
| const todoList = document.getElementById('todoList'); | |
| const filteredTodos = getFilteredTodos(); | |
| if (filteredTodos.length === 0) { | |
| todoList.innerHTML = ` | |
| <div class="empty-state"> | |
| <div class="empty-icon">${currentFilter === 'completed' ? 'π' : 'π'}</div> | |
| <div class="empty-text">${currentFilter === 'completed' ? 'All done!' : 'No tasks yet'}</div> | |
| <div class="empty-subtext">${currentFilter === 'completed' ? 'Great job completing everything!' : 'Add your first task to get started'}</div> | |
| </div> | |
| `; | |
| return; | |
| } | |
| todoList.innerHTML = filteredTodos.map(todo => ` | |
| <div class="todo-item" data-id="${todo.id}"> | |
| <div class="todo-checkbox ${todo.completed ? 'checked' : ''}" onclick="toggleTodo('${todo.id}')"></div> | |
| <div class="todo-text ${todo.completed ? 'completed' : ''}" onclick="startEdit('${todo.id}')">${todo.text}</div> | |
| <div class="todo-actions"> | |
| <button class="action-btn" onclick="deleteTodo('${todo.id}')">ποΈ</button> | |
| </div> | |
| </div> | |
| `).join(''); | |
| updateActiveCount(); | |
| } | |
| function updateActiveCount() { | |
| const activeCount = todos.filter(t => !t.completed).length; | |
| document.getElementById('activeCount').textContent = activeCount; | |
| } | |
| function updateStats() { | |
| const total = todos.length; | |
| const completed = todos.filter(t => t.completed).length; | |
| const rate = total > 0 ? Math.round((completed / total) * 100) : 0; | |
| document.getElementById('totalTasks').textContent = total; | |
| document.getElementById('completedTasks').textContent = completed; | |
| document.getElementById('completionRate').textContent = rate + '%'; | |
| } | |
| function toggleTheme() { | |
| const body = document.body; | |
| const themeToggle = document.querySelector('.theme-toggle'); | |
| if (body.style.filter === 'invert(1) hue-rotate(180deg)') { | |
| body.style.filter = ''; | |
| themeToggle.textContent = 'π'; | |
| } else { | |
| body.style.filter = 'invert(1) hue-rotate(180deg)'; | |
| themeToggle.textContent = 'βοΈ'; | |
| } | |
| } | |
| // Initialize | |
| renderTodos(); | |
| updateStats(); | |
| document.getElementById('todoInput').focus(); | |
| </script> | |
| </body> | |
| </html> |