Spaces:
Running
Running
Create a todo list site. Minimalistic, easy to use and user-friendly, cute. Opportunities: add a new task, remove any task, mark as done, mark as undone. Use localstorage for tasks. Sort undone tasks to the top of the list
Browse files
README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title: Cute Task Tracker
|
| 3 |
-
emoji: 🐠
|
| 4 |
colorFrom: blue
|
| 5 |
-
colorTo:
|
|
|
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Cute Task Tracker 🌈
|
|
|
|
| 3 |
colorFrom: blue
|
| 4 |
+
colorTo: green
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
index.html
CHANGED
|
@@ -1,19 +1,64 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Cute Task Tracker</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 10 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 11 |
+
</head>
|
| 12 |
+
<body class="bg-pink-50 min-h-screen font-sans">
|
| 13 |
+
<div class="container mx-auto px-4 py-8 max-w-md">
|
| 14 |
+
<header class="text-center mb-10">
|
| 15 |
+
<h1 class="text-3xl font-bold text-pink-600 mb-2">Cute Task Tracker 🌈</h1>
|
| 16 |
+
<p class="text-pink-400">Your adorable companion for getting things done!</p>
|
| 17 |
+
</header>
|
| 18 |
+
|
| 19 |
+
<main>
|
| 20 |
+
<div class="bg-white rounded-2xl shadow-lg p-6 mb-6">
|
| 21 |
+
<form id="task-form" class="flex gap-2">
|
| 22 |
+
<input
|
| 23 |
+
type="text"
|
| 24 |
+
id="new-task"
|
| 25 |
+
placeholder="Add a new task..."
|
| 26 |
+
class="flex-grow px-4 py-3 rounded-xl border border-pink-200 focus:outline-none focus:ring-2 focus:ring-pink-300"
|
| 27 |
+
required
|
| 28 |
+
>
|
| 29 |
+
<button
|
| 30 |
+
type="submit"
|
| 31 |
+
class="bg-pink-500 hover:bg-pink-600 text-white px-5 py-3 rounded-xl transition duration-200 flex items-center"
|
| 32 |
+
>
|
| 33 |
+
<i data-feather="plus" class="w-5 h-5"></i>
|
| 34 |
+
</button>
|
| 35 |
+
</form>
|
| 36 |
+
</div>
|
| 37 |
+
|
| 38 |
+
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
|
| 39 |
+
<ul id="task-list" class="divide-y divide-pink-100">
|
| 40 |
+
<!-- Tasks will be added here dynamically -->
|
| 41 |
+
</ul>
|
| 42 |
+
|
| 43 |
+
<div id="empty-state" class="text-center py-12 hidden">
|
| 44 |
+
<div class="mb-4">
|
| 45 |
+
<i data-feather="check-circle" class="w-16 h-16 text-pink-300 mx-auto"></i>
|
| 46 |
+
</div>
|
| 47 |
+
<h3 class="text-xl font-semibold text-pink-500 mb-2">All caught up!</h3>
|
| 48 |
+
<p class="text-pink-400">You have no pending tasks. Great job! 🎉</p>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
</main>
|
| 52 |
+
|
| 53 |
+
<footer class="mt-12 text-center text-pink-400 text-sm">
|
| 54 |
+
<p>Made with ❤️ | Your tasks are saved locally</p>
|
| 55 |
+
</footer>
|
| 56 |
+
</div>
|
| 57 |
+
|
| 58 |
+
<script src="script.js"></script>
|
| 59 |
+
<script>
|
| 60 |
+
feather.replace();
|
| 61 |
+
</script>
|
| 62 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 63 |
+
</body>
|
| 64 |
+
</html>
|
script.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 2 |
+
const taskForm = document.getElementById('task-form');
|
| 3 |
+
const newTaskInput = document.getElementById('new-task');
|
| 4 |
+
const taskList = document.getElementById('task-list');
|
| 5 |
+
const emptyState = document.getElementById('empty-state');
|
| 6 |
+
|
| 7 |
+
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
|
| 8 |
+
|
| 9 |
+
// Render tasks on page load
|
| 10 |
+
renderTasks();
|
| 11 |
+
|
| 12 |
+
// Add new task
|
| 13 |
+
taskForm.addEventListener('submit', (e) => {
|
| 14 |
+
e.preventDefault();
|
| 15 |
+
|
| 16 |
+
const taskText = newTaskInput.value.trim();
|
| 17 |
+
if (taskText) {
|
| 18 |
+
addTask(taskText);
|
| 19 |
+
newTaskInput.value = '';
|
| 20 |
+
newTaskInput.focus();
|
| 21 |
+
}
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
// Add task function
|
| 25 |
+
function addTask(text) {
|
| 26 |
+
const newTask = {
|
| 27 |
+
id: Date.now(),
|
| 28 |
+
text: text,
|
| 29 |
+
completed: false
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
tasks.unshift(newTask); // Add to beginning of array
|
| 33 |
+
saveTasks();
|
| 34 |
+
renderTasks();
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
// Delete task
|
| 38 |
+
function deleteTask(id) {
|
| 39 |
+
tasks = tasks.filter(task => task.id !== id);
|
| 40 |
+
saveTasks();
|
| 41 |
+
renderTasks();
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
// Toggle task completion
|
| 45 |
+
function toggleTask(id) {
|
| 46 |
+
tasks = tasks.map(task => {
|
| 47 |
+
if (task.id === id) {
|
| 48 |
+
return {...task, completed: !task.completed};
|
| 49 |
+
}
|
| 50 |
+
return task;
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
// Sort tasks: incomplete first
|
| 54 |
+
tasks.sort((a, b) => {
|
| 55 |
+
if (a.completed && !b.completed) return 1;
|
| 56 |
+
if (!a.completed && b.completed) return -1;
|
| 57 |
+
return 0;
|
| 58 |
+
});
|
| 59 |
+
|
| 60 |
+
saveTasks();
|
| 61 |
+
renderTasks();
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// Save tasks to localStorage
|
| 65 |
+
function saveTasks() {
|
| 66 |
+
localStorage.setItem('tasks', JSON.stringify(tasks));
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// Render tasks to the DOM
|
| 70 |
+
function renderTasks() {
|
| 71 |
+
if (tasks.length === 0) {
|
| 72 |
+
taskList.innerHTML = '';
|
| 73 |
+
emptyState.classList.remove('hidden');
|
| 74 |
+
return;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
emptyState.classList.add('hidden');
|
| 78 |
+
|
| 79 |
+
// Sort tasks: incomplete first
|
| 80 |
+
const sortedTasks = [...tasks].sort((a, b) => {
|
| 81 |
+
if (a.completed && !b.completed) return 1;
|
| 82 |
+
if (!a.completed && b.completed) return -1;
|
| 83 |
+
return 0;
|
| 84 |
+
});
|
| 85 |
+
|
| 86 |
+
taskList.innerHTML = sortedTasks.map(task => `
|
| 87 |
+
<li class="task-item ${task.completed ? 'completed' : ''} p-4 flex items-center justify-between transition-all duration-200 hover:bg-pink-50">
|
| 88 |
+
<div class="flex items-center space-x-3">
|
| 89 |
+
<button
|
| 90 |
+
class="check-button w-6 h-6 rounded-full border-2 border-pink-300 flex items-center justify-center ${task.completed ? 'checked bg-pink-400 border-pink-400' : ''}"
|
| 91 |
+
data-id="${task.id}"
|
| 92 |
+
aria-label="${task.completed ? 'Mark as incomplete' : 'Mark as complete'}"
|
| 93 |
+
>
|
| 94 |
+
${task.completed ? '<i data-feather="check" class="w-4 h-4 text-white"></i>' : ''}
|
| 95 |
+
</button>
|
| 96 |
+
<span class="task-text ${task.completed ? 'text-pink-400' : 'text-gray-700'}">${task.text}</span>
|
| 97 |
+
</div>
|
| 98 |
+
<div class="task-actions flex space-x-2">
|
| 99 |
+
<button
|
| 100 |
+
class="delete-button text-pink-400 hover:text-pink-600 transition-colors"
|
| 101 |
+
data-id="${task.id}"
|
| 102 |
+
aria-label="Delete task"
|
| 103 |
+
>
|
| 104 |
+
<i data-feather="trash-2" class="w-5 h-5"></i>
|
| 105 |
+
</button>
|
| 106 |
+
</div>
|
| 107 |
+
</li>
|
| 108 |
+
`).join('');
|
| 109 |
+
|
| 110 |
+
// Add event listeners to new buttons
|
| 111 |
+
document.querySelectorAll('.check-button').forEach(button => {
|
| 112 |
+
button.addEventListener('click', (e) => {
|
| 113 |
+
const id = parseInt(e.currentTarget.getAttribute('data-id'));
|
| 114 |
+
toggleTask(id);
|
| 115 |
+
});
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
document.querySelectorAll('.delete-button').forEach(button => {
|
| 119 |
+
button.addEventListener('click', (e) => {
|
| 120 |
+
const id = parseInt(e.currentTarget.getAttribute('data-id'));
|
| 121 |
+
deleteTask(id);
|
| 122 |
+
});
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
// Re-initialize Feather icons
|
| 126 |
+
feather.replace();
|
| 127 |
+
}
|
| 128 |
+
});
|
style.css
CHANGED
|
@@ -1,28 +1,57 @@
|
|
|
|
|
|
|
|
| 1 |
body {
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
margin-top: 0;
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.
|
| 19 |
-
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700&display=swap');
|
| 2 |
+
|
| 3 |
body {
|
| 4 |
+
font-family: 'Nunito', sans-serif;
|
| 5 |
+
background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 100%);
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.task-item {
|
| 9 |
+
transition: all 0.3s ease;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
.task-item.completed .task-text {
|
| 13 |
+
text-decoration: line-through;
|
| 14 |
+
color: #c084fc;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
.task-actions {
|
| 18 |
+
opacity: 0;
|
| 19 |
+
transition: opacity 0.2s ease;
|
| 20 |
}
|
| 21 |
|
| 22 |
+
.task-item:hover .task-actions {
|
| 23 |
+
opacity: 1;
|
|
|
|
| 24 |
}
|
| 25 |
|
| 26 |
+
.check-button.checked {
|
| 27 |
+
background-color: #f0abfc;
|
| 28 |
+
border-color: #f0abfc;
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
+
.empty-state-animation {
|
| 32 |
+
animation: float 3s ease-in-out infinite;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
+
@keyframes float {
|
| 36 |
+
0% { transform: translateY(0px); }
|
| 37 |
+
50% { transform: translateY(-10px); }
|
| 38 |
+
100% { transform: translateY(0px); }
|
| 39 |
}
|
| 40 |
+
|
| 41 |
+
/* Custom scrollbar */
|
| 42 |
+
::-webkit-scrollbar {
|
| 43 |
+
width: 8px;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
::-webkit-scrollbar-track {
|
| 47 |
+
background: #fce7f3;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
::-webkit-scrollbar-thumb {
|
| 51 |
+
background: #f0abfc;
|
| 52 |
+
border-radius: 4px;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
::-webkit-scrollbar-thumb:hover {
|
| 56 |
+
background: #e879f9;
|
| 57 |
+
}
|