anycoder-126b1e4e / index.html
akhaliq's picture
akhaliq HF Staff
Upload folder using huggingface_hub
3f36611 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Todo App</title>
<!-- Font Awesome for Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #6366f1;
--primary-hover: #4f46e5;
--bg-color: #f3f4f6;
--card-bg: #ffffff;
--text-color: #1f2937;
--text-secondary: #6b7280;
--danger-color: #ef4444;
--success-color: #10b981;
--border-radius: 16px;
--shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
}
body {
background-color: var(--bg-color);
background-image:
radial-gradient(at 0% 0%, hsla(253,16%,7%,1) 0, transparent 50%),
radial-gradient(at 50% 0%, hsla(225,39%,30%,1) 0, transparent 50%),
radial-gradient(at 100% 0%, hsla(339,49%,30%,1) 0, transparent 50%);
background-size: cover;
background-attachment: fixed;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
color: var(--text-color);
}
/* Mobile Adjustment for background to be dark mode style usually looks better with gradients,
but let's stick to a clean glassmorphism look */
body {
background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%);
}
.app-container {
width: 100%;
max-width: 500px;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
padding: 2rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
border: 1px solid rgba(255, 255, 255, 0.5);
}
/* Header Section */
header {
text-align: center;
position: relative;
}
h1 {
font-size: 2rem;
font-weight: 800;
background: linear-gradient(135deg, var(--primary-color), #a855f7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
letter-spacing: -0.5px;
}
.built-with {
font-size: 0.85rem;
color: var(--text-secondary);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 5px;
transition: var(--transition);
padding: 5px 12px;
background: rgba(255,255,255,0.5);
border-radius: 20px;
}
.built-with:hover {
background: rgba(255,255,255,0.9);
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
color: var(--primary-color);
}
/* Input Section */
.input-wrapper {
position: relative;
display: flex;
gap: 10px;
}
input[type="text"] {
width: 100%;
padding: 1rem 1.5rem;
border: 2px solid transparent;
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
font-size: 1rem;
outline: none;
transition: var(--transition);
box-shadow: inset 0 2px 4px rgba(0,0,0,0.02);
}
input[type="text"]:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.2);
}
.add-btn {
padding: 0 1.5rem;
background: var(--primary-color);
color: white;
border: none;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
}
.add-btn:hover {
background: var(--primary-hover);
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
}
/* Filters */
.filters {
display: flex;
justify-content: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.5);
padding: 0.3rem;
border-radius: 12px;
}
.filter-btn {
flex: 1;
padding: 0.5rem;
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
border-radius: 8px;
font-weight: 600;
font-size: 0.9rem;
transition: var(--transition);
}
.filter-btn.active {
background: white;
color: var(--primary-color);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.filter-btn:hover:not(.active) {
color: var(--text-color);
}
/* Todo List */
.todo-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 0.8rem;
max-height: 400px;
overflow-y: auto;
padding-right: 5px;
}
/* Custom Scrollbar */
.todo-list::-webkit-scrollbar {
width: 6px;
}
.todo-list::-webkit-scrollbar-track {
background: transparent;
}
.todo-list::-webkit-scrollbar-thumb {
background-color: rgba(0,0,0,0.1);
border-radius: 20px;
}
.todo-item {
background: white;
padding: 1rem;
border-radius: 12px;
display: flex;
align-items: center;
gap: 1rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.02);
transition: var(--transition);
animation: slideIn 0.3s ease-out;
position: relative;
overflow: hidden;
}
.todo-item:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #9ca3af;
}
/* Custom Checkbox */
.checkbox-wrapper {
position: relative;
width: 24px;
height: 24px;
flex-shrink: 0;
}
.checkbox-wrapper input {
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
position: absolute;
z-index: 2;
}
.custom-checkbox {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #e5e7eb;
border-radius: 6px;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
}
.checkbox-wrapper input:checked ~ .custom-checkbox {
background-color: var(--success-color);
}
.custom-checkbox::after {
content: '\f00c';
font-family: 'Font Awesome 6 Free';
font-weight: 900;
color: white;
font-size: 0.8rem;
opacity: 0;
transform: scale(0);
transition: var(--transition);
}
.checkbox-wrapper input:checked ~ .custom-checkbox::after {
opacity: 1;
transform: scale(1);
}
.todo-text {
flex: 1;
font-size: 1rem;
word-break: break-word;
transition: var(--transition);
}
.delete-btn {
background: transparent;
border: none;
color: #ef4444;
cursor: pointer;
opacity: 0;
transform: scale(0.8);
transition: var(--transition);
padding: 5px;
font-size: 1.1rem;
}
.todo-item:hover .delete-btn {
opacity: 1;
transform: scale(1);
}
.delete-btn:hover {
transform: scale(1.2);
color: #b91c1c;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 2rem 0;
color: var(--text-secondary);
display: none;
}
.empty-state i {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.3;
}
/* Animations */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fall {
to {
transform: translateY(13px) rotate(5deg);
opacity: 0;
}
}
.fall {
animation: fall 0.3s ease-out forwards;
}
/* Footer Stats */
.footer-info {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.85rem;
color: var(--text-secondary);
padding-top: 0.5rem;
}
.clear-btn {
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 0.85rem;
transition: var(--transition);
}
.clear-btn:hover {
color: var(--danger-color);
text-decoration: underline;
}
/* Mobile Responsiveness */
@media (max-width: 480px) {
.app-container {
padding: 1.5rem;
max-height: 90vh;
}
h1 {
font-size: 1.75rem;
}
.delete-btn {
opacity: 1; /* Always show delete on mobile */
transform: scale(1);
}
}
</style>
</head>
<body>
<div class="app-container">
<header>
<h1>Task Master</h1>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
<i class="fas fa-code"></i> Built with anycoder
</a>
</header>
<div class="input-wrapper">
<input type="text" id="todo-input" placeholder="What needs to be done?" autocomplete="off">
<button class="add-btn" id="add-btn">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="filters">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="active">Active</button>
<button class="filter-btn" data-filter="completed">Completed</button>
</div>
<ul class="todo-list" id="todo-list">
<!-- Tasks will be injected here -->
</ul>
<div class="empty-state" id="empty-state">
<i class="fas fa-clipboard-check"></i>
<p>No tasks found. Add one above!</p>
</div>
<div class="footer-info">
<span id="items-left">0 items left</span>
<button class="clear-btn" id="clear-completed">Clear Completed</button>
</div>
</div>
<script>
// Selectors
const todoInput = document.getElementById('todo-input');
const addBtn = document.getElementById('add-btn');
const todoList = document.getElementById('todo-list');
const filterBtns = document.querySelectorAll('.filter-btn');
const itemsLeftSpan = document.getElementById('items-left');
const clearCompletedBtn = document.getElementById('clear-completed');
const emptyState = document.getElementById('empty-state');
// State
let todos = JSON.parse(localStorage.getItem('todos')) || [];
let currentFilter = 'all';
// Event Listeners
addBtn.addEventListener('click', addTodo);
todoInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addTodo();
});
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
// Remove active class from all
filterBtns.forEach(b => b.classList.remove('active'));
// Add active to clicked
btn.classList.add('active');
currentFilter = btn.dataset.filter;
renderTodos();
});
});
clearCompletedBtn.addEventListener('click', clearCompleted);
todoList.addEventListener('click', deleteCheck);
// Initial Render
renderTodos();
// Functions
function addTodo() {
const todoText = todoInput.value.trim();
if (todoText === '') {
shakeInput();
return;
}
const newTodo = {
id: Date.now(),
text: todoText,
completed: false
};
todos.push(newTodo);
saveAndRender();
todoInput.value = '';
todoInput.focus();
}
function deleteCheck(e) {
const item = e.target;
const todoItem = item.closest('.todo-item');
// Delete
if (item.classList.contains('delete-btn') || item.parentElement.classList.contains('delete-btn')) {
const id = parseInt(todoItem.dataset.id);
// Add animation class before removing
todoItem.classList.add('fall');
// Remove from array after animation starts
setTimeout(() => {
todos = todos.filter(todo => todo.id !== id);
saveAndRender();
}, 300);
}
// Check Mark
if (item.classList.contains('checkbox-wrapper') || item.closest('.checkbox-wrapper')) {
const id = parseInt(todoItem.dataset.id);
const todo = todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
saveAndRender();
}
}
}
function setStatus(e) {
if (e.target.classList.contains('filter-btn')) {
currentFilter = e.target.dataset.filter;
renderTodos();
}
}
function clearCompleted() {
todos = todos.filter(todo => !todo.completed);
saveAndRender();
}
function saveAndRender() {
localStorage.setItem('todos', JSON.stringify(todos));
renderTodos();
}
function renderTodos() {
todoList.innerHTML = '';
// Filter logic
let filteredTodos = [];
if (currentFilter === 'all') {
filteredTodos = todos;
} else if (currentFilter === 'active') {
filteredTodos = todos.filter(todo => !todo.completed);
} else if (currentFilter === 'completed') {
filteredTodos = todos.filter(todo => todo.completed);
}
// Check empty state
if (filteredTodos.length === 0) {
emptyState.style.display = 'block';
} else {
emptyState.style.display = 'none';
}
// Update items left
const activeCount = todos.filter(t => !t.completed).length;
itemsLeftSpan.innerText = `${activeCount} item${activeCount !== 1 ? 's' : ''} left`;
// Generate HTML
filteredTodos.forEach(todo => {
const li = document.createElement('li');
li.classList.add('todo-item');
if (todo.completed) li.classList.add('completed');
li.dataset.id = todo.id;
li.innerHTML = `
<div class="checkbox-wrapper">
<input type="checkbox" ${todo.completed ? 'checked' : ''}>
<div class="custom-checkbox"></div>
</div>
<span class="todo-text">${escapeHtml(todo.text)}</span>
<button class="delete-btn"><i class="fas fa-trash"></i></button>
`;
todoList.appendChild(li);
});
}
// Utility: Prevent XSS
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, (m) => map[m]);
}
// Utility: Shake animation for empty input
function shakeInput() {
todoInput.style.animation = 'none';
todoInput.offsetHeight; /* trigger reflow */
todoInput.style.animation = 'shake 0.5s';
todoInput.style.borderColor = 'var(--danger-color)';
setTimeout(() => {
todoInput.style.borderColor = 'transparent';
}, 2000);
}
// Inject keyframes dynamically
const styleSheet = document.createElement("style");
styleSheet.innerText = `
@keyframes shake {
0% { transform: translateX(0); }
25% { transform: translateX(-10px); }
50% { transform: translateX(10px); }
75% { transform: translateX(-10px); }
100% { transform: translateX(0); }
}
`;
document.head.appendChild(styleSheet);
</script>
</body>
</html>