/
KubsauZverb
/
TaskTracker
Обзор
Документация
Войти
/
KubsauZverb
/
TaskTracker
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.html
420 строк
12 KB
KubsauZverb
update: index.html
11 фев 2026, 23:22
Верифицирован
11 фев 2026, 23:22
66760b4
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Task Tracker Pro</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); :root { --primary: #4f46e5; --primary-light: #818cf8; --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --surface: #ffffff; --text-primary: #111827; --text-secondary: #6b7280; --success: #10b981; --danger: #ef4444; --border: #e5e7eb; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', sans-serif; } body { background: var(--bg-gradient); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .app-container { background: var(--surface); width: 100%; max-width: 420px; border-radius: 20px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); overflow: hidden; display: flex; flex-direction: column; max-height: 85vh; } .header { padding: 24px 24px 0 24px; } .title-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 8px; } h1 { color: var(--text-primary); font-size: 1.5rem; font-weight: 700; } .date { color: var(--text-secondary); font-size: 0.875rem; font-weight: 500; } .progress-section { padding: 0 0 20px 0; } .progress-info { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 0.875rem; font-weight: 600; color: var(--text-secondary); } .progress-track { background: #f3f4f6; height: 8px; border-radius: 4px; overflow: hidden; position: relative; } .progress-fill { background: var(--primary); height: 100%; width: 0%; border-radius: 4px; transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s; } .input-area { padding: 0 24px 24px 24px; position: relative; } .input-wrapper { display: flex; gap: 12px; } input { flex: 1; padding: 12px 16px; border-radius: 12px; border: 2px solid var(--border); font-size: 1rem; outline: none; transition: border-color 0.2s; } input:focus { border-color: var(--primary); } .add-btn { background: var(--primary); color: white; border: none; border-radius: 12px; width: 48px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s, transform 0.1s; } .add-btn:hover { background: #4338ca; } .add-btn:active { transform: scale(0.95); } .tasks-container { flex: 1; overflow-y: auto; padding: 0 24px 24px 24px; } .tasks-container::-webkit-scrollbar { width: 6px; } .tasks-container::-webkit-scrollbar-track { background: transparent; } .tasks-container::-webkit-scrollbar-thumb { background-color: #d1d5db; border-radius: 20px; } .task-list { list-style: none; } .task-item { display: flex; align-items: center; padding: 12px; background: #f9fafb; border: 1px solid var(--border); border-radius: 12px; margin-bottom: 10px; transition: all 0.2s; animation: fadeIn 0.3s ease-out; } .task-item:hover { border-color: #cbd5e1; transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .checkbox { width: 20px; height: 20px; border: 2px solid #9ca3af; border-radius: 6px; margin-right: 12px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; flex-shrink: 0; } .task-content { flex: 1; font-size: 0.95rem; color: var(--text-primary); cursor: pointer; word-break: break-all; line-height: 1.4; } .delete-btn { background: transparent; border: none; color: #9ca3af; cursor: pointer; padding: 4px; border-radius: 4px; margin-left: 8px; opacity: 0; transition: all 0.2s; display: flex; align-items: center; justify-content: center; } .task-item:hover .delete-btn { opacity: 1; } .delete-btn:hover { color: var(--danger); background: #fee2e2; } .task-item.completed .checkbox { background: var(--success); border-color: var(--success); } .task-item.completed .checkbox::after { content: '✓'; color: white; font-size: 12px; font-weight: bold; } .task-item.completed .task-content { text-decoration: line-through; color: #9ca3af; } .empty-message { text-align: center; color: var(--text-secondary); margin-top: 2rem; font-size: 0.9rem; display: none; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeOut { to { opacity: 0; transform: translateX(20px); } } </style> </head> <body> <div class="app-container"> <div class="header"> <div class="title-row"> <h1>Задачи</h1> <span class="date" id="currentDate"></span> </div> <div class="progress-section"> <div class="progress-info"> <span id="progressText">0% выполнено</span> <span id="tasksCount">0/0</span> </div> <div class="progress-track"> <div class="progress-fill" id="progressBar"></div> </div> </div> </div> <div class="input-area"> <div class="input-wrapper"> <input type="text" id="taskInput" placeholder="Что нужно сделать?"> <button class="add-btn" id="addBtn"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="12" y1="5" x2="12" y2="19"></line> <line x1="5" y1="12" x2="19" y2="12"></line> </svg> </button> </div> </div> <div class="tasks-container"> <ul class="task-list" id="taskList"></ul> <div class="empty-message" id="emptyState">Список задач пуст</div> </div> </div> <script> const taskInput = document.getElementById('taskInput'); const addBtn = document.getElementById('addBtn'); const taskList = document.getElementById('taskList'); const emptyState = document.getElementById('emptyState'); const progressBar = document.getElementById('progressBar'); const progressText = document.getElementById('progressText'); const tasksCount = document.getElementById('tasksCount'); const currentDate = document.getElementById('currentDate'); let tasks = JSON.parse(localStorage.getItem('tasks')) || []; const dateOptions = { weekday: 'short', day: 'numeric', month: 'long' }; currentDate.textContent = new Date().toLocaleDateString('ru-RU', dateOptions); function updateStats() { const total = tasks.length; const completed = tasks.filter(t => t.completed).length; const percent = total === 0 ? 0 : Math.round((completed / total) * 100); progressBar.style.width = `${percent}%`; progressText.textContent = `${percent}% выполнено`; tasksCount.textContent = `${completed}/${total}`; if (percent === 100 && total > 0) { progressBar.style.backgroundColor = '#10b981'; } else { progressBar.style.backgroundColor = '#4f46e5'; } } function saveTasks() { localStorage.setItem('tasks', JSON.stringify(tasks)); updateStats(); renderTasks(); } function renderTasks() { taskList.innerHTML = ''; if (tasks.length === 0) { emptyState.style.display = 'block'; } else { emptyState.style.display = 'none'; tasks.forEach(task => { const li = document.createElement('li'); li.className = `task-item ${task.completed ? 'completed' : ''}`; li.innerHTML = ` <div class="checkbox" onclick="toggleTask(${task.id})"></div> <div class="task-content" onclick="toggleTask(${task.id})">${escapeHtml(task.text)}</div> <button class="delete-btn" onclick="deleteTask(event, ${task.id})"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="3 6 5 6 21 6"></polyline> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path> </svg> </button> `; taskList.appendChild(li); }); } } function addTask() { const text = taskInput.value.trim(); if (text) { const newTask = { id: Date.now(), text: text, completed: false }; tasks.unshift(newTask); saveTasks(); taskInput.value = ''; taskInput.focus(); } } window.toggleTask = function(id) { const index = tasks.findIndex(t => t.id === id); if (index !== -1) { tasks[index].completed = !tasks[index].completed; saveTasks(); } }; window.deleteTask = function(event, id) { event.stopPropagation(); const li = event.target.closest('li'); li.style.animation = 'fadeOut 0.3s forwards'; setTimeout(() => { tasks = tasks.filter(t => t.id !== id); saveTasks(); }, 300); }; function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } addBtn.addEventListener('click', addTask); taskInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') addTask(); }); renderTasks(); updateStats(); </script> </body> </html>