tasktracker/index.html
2026-06-01 12:56:46 +00:00

248 lines
9.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Трекер задач</title>
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--green:#10B981;--green-50:#ECFDF5;--red:#EF4444;--red-50:#FEF2F2;--yellow:#F59E0B;--yellow-50:#FFFBEB}
*{box-sizing:border-box;margin:0;padding:0}
body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--white)}
.container{max-width:720px;margin:0 auto;padding:80px 24px}
.hero{background:var(--cyan-50);color:var(--ink);text-align:center}
.hero h1{font-size:48px;font-weight:800;line-height:1.1;margin-bottom:16px}
.hero p{font-size:18px;color:var(--gray-500);margin-bottom:0}
header{padding:24px 0;border-bottom:1px solid var(--gray-100);position:sticky;top:0;background:var(--white);z-index:10}
header .container{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;padding-top:16px;padding-bottom:16px}
.stats{display:flex;gap:16px;align-items:center;flex-wrap:wrap}
.stat{font-size:14px;color:var(--gray-500)}
.stat strong{color:var(--ink);font-size:18px}
.progress-mini{display:flex;align-items:center;gap:8px}
.progress-mini .bar{width:120px;height:6px;background:var(--gray-100);border-radius:3px;overflow:hidden}
.progress-mini .fill{height:100%;background:var(--cyan);border-radius:3px;transition:width .3s}
.add-form{background:var(--gray-100);border-radius:16px;padding:24px;margin-bottom:24px}
.add-form .row{display:flex;gap:12px;flex-wrap:wrap}
.add-form input[type=text]{flex:1;min-width:200px;padding:12px 16px;border:2px solid transparent;border-radius:8px;font-size:16px;font-family:inherit;outline:none;transition:border-color .2s}
.add-form input[type=text]:focus{border-color:var(--cyan)}
.btn{display:inline-flex;align-items:center;gap:6px;background:var(--cyan);color:var(--ink);padding:12px 24px;border-radius:8px;font-weight:700;text-decoration:none;border:none;cursor:pointer;font-size:16px;font-family:inherit;transition:background .2s}
.btn:hover{background:#1be5ff}
.filters{display:flex;gap:8px;margin-bottom:20px;flex-wrap:wrap}
.filter-btn{padding:8px 20px;border-radius:8px;border:none;background:var(--gray-100);color:var(--gray-500);font-weight:600;font-size:15px;cursor:pointer;font-family:inherit;transition:all .2s}
.filter-btn.active{background:var(--ink);color:var(--white)}
.task-list{display:flex;flex-direction:column;gap:8px}
.task{display:flex;align-items:center;gap:12px;padding:16px 20px;background:var(--gray-100);border-radius:12px;transition:all .2s;animation:slideIn .3s ease}
.task.done{background:var(--green-50)}
.task.done .task-text{text-decoration:line-through;color:var(--gray-500)}
@keyframes slideIn{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
.task-check{width:26px;height:26px;border-radius:50%;border:2px solid var(--gray-500);flex-shrink:0;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;font-size:14px;color:transparent}
.task.done .task-check{background:var(--green);border-color:var(--green);color:var(--white)}
.task-content{flex:1;min-width:0}
.task-text{font-weight:500;word-break:break-word}
.task-meta{display:flex;gap:8px;align-items:center;margin-top:4px}
.task-date{font-size:13px;color:var(--gray-500)}
.priority{font-size:12px;font-weight:700;padding:2px 10px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}
.priority-high{background:var(--red-50);color:var(--red)}
.priority-mid{background:var(--yellow-50);color:var(--yellow)}
.priority-low{background:var(--green-50);color:var(--green)}
.task-delete{width:32px;height:32px;border:none;background:transparent;color:var(--gray-500);cursor:pointer;border-radius:8px;font-size:18px;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:all .2s}
.task-delete:hover{background:var(--red-50);color:var(--red)}
.empty{text-align:center;padding:48px 24px;color:var(--gray-500)}
.empty .icon{font-size:48px;margin-bottom:16px}
.empty p{font-size:16px}
.select-pri{padding:10px 16px;border:2px solid transparent;border-radius:8px;font-size:16px;font-family:inherit;background:var(--white);outline:none;cursor:pointer;color:var(--ink)}
.select-pri:focus{border-color:var(--cyan)}
footer{text-align:center;padding:48px 24px;color:var(--gray-500);font-size:14px;border-top:1px solid var(--gray-100);margin-top:40px}
@media(max-width:640px){
.hero h1{font-size:36px}
.container{padding:40px 20px}
.add-form .row{flex-direction:column}
.btn{width:100%;justify-content:center}
.stats{width:100%;justify-content:space-between}
.task{padding:14px 16px}
header .container{padding-top:12px;padding-bottom:12px}
}
</style>
</head>
<body>
<section class="hero">
<div class="container">
<h1>Трекер задач</h1>
<p>Добавляй, выполняй, не теряй</p>
</div>
</section>
<header>
<div class="container">
<div class="stats">
<div class="stat">Всего <strong id="totalCount">0</strong></div>
<div class="stat">Сделано <strong id="doneCount">0</strong></div>
<div class="progress-mini">
<div class="bar"><div class="fill" id="miniFill" style="width:0%"></div></div>
<span class="stat" id="miniPct">0%</span>
</div>
</div>
</div>
</header>
<div class="container">
<div class="add-form">
<div class="row">
<input type="text" id="taskInput" placeholder="Что нужно сделать?" maxlength="120">
<select class="select-pri" id="priSelect">
<option value="mid">Средний</option>
<option value="high">Высокий</option>
<option value="low">Низкий</option>
</select>
<button class="btn" onclick="addTask()">Добавить</button>
</div>
</div>
<div class="filters">
<button class="filter-btn active" data-filter="all" onclick="setFilter('all',this)">Все</button>
<button class="filter-btn" data-filter="active" onclick="setFilter('active',this)">Активные</button>
<button class="filter-btn" data-filter="done" onclick="setFilter('done',this)">Завершённые</button>
</div>
<div class="task-list" id="taskList"></div>
<div class="empty" id="empty">
<div class="icon">📋</div>
<p>Пока задач нет. Добавь первую!</p>
</div>
</div>
<footer>
Все данные хранятся в твоём браузере
</footer>
<script>
var STORAGE_KEY = 'tracker_tasks';
var currentFilter = 'all';
function loadTasks(){
try{ return JSON.parse(localStorage.getItem(STORAGE_KEY)) || []; }
catch(e){ return []; }
}
function saveTasks(tasks){
try{ localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks)); }catch(e){}
}
function addTask(){
var input = document.getElementById('taskInput');
var text = input.value.trim();
if(!text) return;
var tasks = loadTasks();
tasks.unshift({
id: Date.now(),
text: text,
done: false,
priority: document.getElementById('priSelect').value,
created: new Date().toISOString().slice(0,10)
});
saveTasks(tasks);
input.value = '';
render();
input.focus();
}
function toggleTask(id){
var tasks = loadTasks();
var t = tasks.find(function(x){ return x.id === id; });
if(t){ t.done = !t.done; }
saveTasks(tasks);
render();
}
function deleteTask(id){
var tasks = loadTasks();
tasks = tasks.filter(function(x){ return x.id !== id; });
saveTasks(tasks);
render();
}
function setFilter(filter, btn){
currentFilter = filter;
document.querySelectorAll('.filter-btn').forEach(function(b){ b.classList.remove('active'); });
btn.classList.add('active');
render();
}
function formatDate(isoDate){
var months = ['янв','фев','мар','апр','мая','июн','июл','авг','сен','окт','ноя','дек'];
var parts = isoDate.split('-');
return parts[2] + ' ' + months[parseInt(parts[1])-1];
}
function render(){
var tasks = loadTasks();
var filtered = tasks;
if(currentFilter === 'active') filtered = tasks.filter(function(t){ return !t.done; });
if(currentFilter === 'done') filtered = tasks.filter(function(t){ return t.done; });
var doneCount = tasks.filter(function(t){ return t.done; }).length;
var total = tasks.length;
var pct = total ? Math.round((doneCount/total)*100) : 0;
document.getElementById('totalCount').textContent = total;
document.getElementById('doneCount').textContent = doneCount;
document.getElementById('miniFill').style.width = pct + '%';
document.getElementById('miniPct').textContent = pct + '%';
var list = document.getElementById('taskList');
var empty = document.getElementById('empty');
if(filtered.length === 0){
list.innerHTML = '';
empty.style.display = 'block';
return;
}
empty.style.display = 'none';
var priLabels = {high:'Высокий', mid:'Средний', low:'Низкий'};
var priClass = {high:'priority-high', mid:'priority-mid', low:'priority-low'};
list.innerHTML = filtered.map(function(t){
var doneClass = t.done ? ' done' : '';
return '<div class="task'+doneClass+'" data-id="'+t.id+'">'+
'<div class="task-check" onclick="toggleTask('+t.id+')">&#10003;</div>'+
'<div class="task-content">'+
'<div class="task-text">'+escapeHtml(t.text)+'</div>'+
'<div class="task-meta">'+
'<span class="priority '+priClass[t.priority]+'">'+priLabels[t.priority]+'</span>'+
'<span class="task-date">'+formatDate(t.created)+'</span>'+
'</div>'+
'</div>'+
'<button class="task-delete" onclick="deleteTask('+t.id+')" title="Удалить">&times;</button>'+
'</div>';
}).join('');
}
function escapeHtml(str){
var div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
document.getElementById('taskInput').addEventListener('keydown', function(e){
if(e.key === 'Enter') addTask();
});
render();
</script>
</body>
</html>