anycoder-5dc47b92 / index.html
salmanarshad's picture
Upload folder using huggingface_hub
c73a142 verified
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FocusFlow | Modern Todo App</title>
<!-- Vue 3 CDN -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Google Fonts: Inter -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Tailwind Config for Custom Colors -->
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
colors: {
gray: {
850: '#1f2937',
900: '#111827',
950: '#0b0f19', // Very dark background
},
primary: {
500: '#6366f1', // Indigo
600: '#4f46e5',
}
},
animation: {
'fade-in': 'fadeIn 0.3s ease-out',
'slide-up': 'slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1)',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
}
}
}
}
}
</script>
<style>
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #374151;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #4b5563;
}
/* Checkbox Customization */
.custom-checkbox input:checked + div {
background-color: #6366f1;
border-color: #6366f1;
}
.custom-checkbox input:checked + div svg {
display: block;
}
/* Glassmorphism utilities */
.glass {
background: rgba(31, 41, 55, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-right: 1px solid rgba(255, 255, 255, 0.05);
}
/* Dragging State */
.dragging {
opacity: 0.5;
border: 2px dashed #6366f1;
}
</style>
</head>
<body class="bg-gray-100 text-gray-800 dark:bg-gray-950 dark:text-gray-100 transition-colors duration-300 font-sans h-screen overflow-hidden">
<div id="app" class="flex h-full">
<!-- Sidebar -->
<aside
class="w-64 flex-shrink-0 flex flex-col justify-between glass transition-all duration-300 transform z-20"
:class="isSidebarOpen ? 'translate-x-0' : '-translate-x-full absolute h-full'"
>
<!-- Logo Area -->
<div class="p-6 flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-primary-500 to-purple-600 flex items-center justify-center shadow-lg shadow-primary-500/20">
<i class="fa-solid fa-check text-white text-xs"></i>
</div>
<h1 class="text-xl font-bold tracking-tight">FocusFlow</h1>
</div>
<!-- Navigation -->
<nav class="flex-1 px-4 space-y-2 mt-4">
<p class="px-4 text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Main</p>
<button
@click="currentFilter = 'all'"
:class="['w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200 group', currentFilter === 'all' ? 'bg-primary-600 text-white shadow-lg shadow-primary-500/20' : 'text-gray-500 hover:bg-gray-800 hover:text-gray-200']"
>
<i class="fa-solid fa-layer-group w-5 text-center transition-transform group-hover:scale-110"></i>
<span class="font-medium">All Tasks</span>
<span class="ml-auto bg-gray-700 text-xs py-0.5 px-2 rounded-full font-mono" v-if="todos.length > 0">{{ todos.length }}</span>
</button>
<button
@click="currentFilter = 'active'"
:class="['w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200 group', currentFilter === 'active' ? 'bg-primary-600 text-white shadow-lg shadow-primary-500/20' : 'text-gray-500 hover:bg-gray-800 hover:text-gray-200']"
>
<i class="fa-regular fa-circle w-5 text-center transition-transform group-hover:scale-110"></i>
<span class="font-medium">Active</span>
<span class="ml-auto bg-gray-700 text-xs py-0.5 px-2 rounded-full font-mono" v-if="activeCount > 0">{{ activeCount }}</span>
</button>
<button
@click="currentFilter = 'completed'"
:class="['w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200 group', currentFilter === 'completed' ? 'bg-primary-600 text-white shadow-lg shadow-primary-500/20' : 'text-gray-500 hover:bg-gray-800 hover:text-gray-200']"
>
<i class="fa-regular fa-circle-check w-5 text-center transition-transform group-hover:scale-110"></i>
<span class="font-medium">Completed</span>
<span class="ml-auto bg-gray-700 text-xs py-0.5 px-2 rounded-full font-mono" v-if="completedCount > 0">{{ completedCount }}</span>
</button>
</nav>
<!-- Bottom Actions -->
<div class="p-4 border-t border-gray-800">
<button
@click="toggleDarkMode"
class="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-gray-500 hover:text-white hover:bg-gray-800 transition-all duration-200"
>
<i :class="['fa-solid w-5 text-center', isDarkMode ? 'fa-sun' : 'fa-moon']"></i>
<span class="font-medium">{{ isDarkMode ? 'Light Mode' : 'Dark Mode' }}</span>
</button>
<button
@click="clearCompleted"
class="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-gray-500 hover:text-red-400 hover:bg-gray-800 transition-all duration-200 mt-1"
>
<i class="fa-solid fa-trash-can w-5 text-center"></i>
<span class="font-medium">Clear Completed</span>
</button>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col relative h-full overflow-hidden">
<!-- Mobile Header -->
<header class="h-16 flex items-center justify-between px-6 border-b border-gray-200 dark:border-gray-800 bg-white/50 dark:bg-gray-950/50 backdrop-blur-sm z-10">
<div class="flex items-center gap-4">
<button @click="isSidebarOpen = !isSidebarOpen" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
<i class="fa-solid fa-bars text-lg"></i>
</button>
<div>
<h2 class="font-bold text-lg">{{ pageTitle }}</h2>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ dateDisplay }}</p>
</div>
</div>
<div class="flex items-center gap-3">
<div class="hidden sm:flex items-center gap-2 px-3 py-1.5 bg-gray-100 dark:bg-gray-900 rounded-full text-sm text-gray-500 border border-gray-200 dark:border-gray-800">
<i class="fa-solid fa-filter text-xs"></i>
<span>Sorted by Date</span>
</div>
</div>
</header>
<!-- Scrollable Task Area -->
<div class="flex-1 overflow-y-auto p-6 relative">
<!-- Empty State -->
<div v-if="filteredTodos.length === 0" class="flex flex-col items-center justify-center h-full text-center opacity-60">
<div class="w-24 h-24 bg-gray-200 dark:bg-gray-800 rounded-full flex items-center justify-center mb-4 animate-fade-in">
<i class="fa-solid fa-clipboard-check text-4xl text-gray-400 dark:text-gray-600"></i>
</div>
<h3 class="text-xl font-medium text-gray-900 dark:text-white mb-2">No tasks found</h3>
<p class="text-gray-500 dark:text-gray-400 max-w-xs">Looks like you're all caught up or have filtered out all tasks. Add a new one to get started!</p>
</div>
<!-- Task List -->
<div
v-for="(todo, index) in filteredTodos"
:key="todo.id"
draggable="true"
@dragstart="dragStart(index)"
@dragover.prevent
@drop="dragDrop(index)"
@dragenter="dragEnter(index)"
@dragleave="dragLeave"
class="group flex items-center p-4 mb-3 bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-sm hover:shadow-md hover:border-primary-500/30 dark:hover:border-primary-500/30 transition-all duration-300 animate-slide-up"
:class="{'opacity-50 grayscale': todo.completed}"
>
<!-- Drag Handle (Desktop) -->
<div class="cursor-move text-gray-300 dark:text-gray-700 mr-4 hidden sm:block">
<i class="fa-solid fa-grip-vertical"></i>
</div>
<!-- Custom Checkbox -->
<label class="custom-checkbox relative flex items-center cursor-pointer">
<input type="checkbox" class="sr-only" v-model="todo.completed" @change="saveTodos">
<div class="w-6 h-6 border-2 border-gray-300 dark:border-gray-600 rounded-lg flex items-center justify-center transition-all duration-200">
<svg class="w-3 h-3 text-white hidden pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path></svg>
</div>
</label>
<!-- Text Content -->
<div class="flex-1 min-w-0 ml-2">
<p
class="text-base font-medium truncate transition-all duration-300"
:class="todo.completed ? 'text-gray-400 line-through decoration-gray-400/50' : 'text-gray-800 dark:text-gray-100'"
@dblclick="editTodo(todo)"
>
{{ todo.text }}
</p>
<div class="flex items-center gap-2 mt-1">
<span class="text-xs px-2 py-0.5 rounded-md bg-gray-100 dark:bg-gray-800 text-gray-500 font-medium">
{{ todo.tag }}
</span>
<span class="text-xs text-gray-400">{{ formatDate(todo.createdAt) }}</span>
</div>
</div>
<!-- Actions -->
<div class="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button
@click="editTodo(todo)"
class="w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:bg-blue-50 hover:text-blue-500 dark:hover:bg-blue-900/30 dark:hover:text-blue-400 transition-colors"
title="Edit"
>
<i class="fa-solid fa-pen text-sm"></i>
</button>
<button
@click="deleteTodo(todo.id)"
class="w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:bg-red-50 hover:text-red-500 dark:hover:bg-red-900/30 dark:hover:text-red-400 transition-colors"
title="Delete"
>
<i class="fa-solid fa-trash text-sm"></i>
</button>
</div>
</div>
</div>
<!-- Floating Input Area -->
<div class="p-6 bg-white dark:bg-gray-950 border-t border-gray-200 dark:border-gray-800">
<form @submit.prevent="addTodo" class="relative flex items-center gap-3 max-w-4xl mx-auto">
<div class="relative flex-1 group">
<input
v-model="newTodoText"
type="text"
placeholder="What needs to be done?"
class="w-full pl-4 pr-12 py-4 bg-gray-100 dark:bg-gray-900 border-0 rounded-xl focus:ring-2 focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-900 transition-all duration-200 text-gray-800 dark:text-white placeholder-gray-400"
autocomplete="off"
>
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-primary-500 transition-colors">
<i class="fa-solid fa-arrow-up"></i>
</div>
</div>
<button
type="submit"
class="px-6 py-4 bg-primary-600 hover:bg-primary-500 text-white rounded-xl shadow-lg shadow-primary-600/20 font-medium transition-all duration-200 active:scale-95 flex items-center gap-2"
>
<span>Add</span>
<i class="fa-solid fa-plus"></i>
</button>
</form>
</div>
</main>
</div>
<!-- Vue Application Logic -->
<script>
const { createApp, ref, computed, onMounted, onUnmounted } = Vue;
createApp({
setup() {
// --- State ---
const todos = ref([]);
const newTodoText = ref('');
const currentFilter = ref('all');
const isSidebarOpen = ref(true); // Default open on desktop
const isDarkMode = ref(true);
const dragIndex = ref(null);
// --- Date & Time ---
const dateDisplay = ref('');
const updateDate = () => {
const now = new Date();
const options = { weekday: 'long', month: 'long', day: 'numeric' };
dateDisplay.value = now.toLocaleDateString('en-US', options);
};
// --- Computed Properties ---
const activeCount = computed(() => todos.value.filter(t => !t.completed).length);
const completedCount = computed(() => todos.value.filter(t => t.completed).length);
const filteredTodos = computed(() => {
if (currentFilter.value === 'active') return todos.value.filter(t => !t.completed);
if (currentFilter.value === 'completed') return todos.value.filter(t => t.completed);
return todos.value;
});
const pageTitle = computed(() => {
if (currentFilter.value === 'all') return 'All Tasks';
if (currentFilter.value === 'active') return 'Active Tasks';
if (currentFilter.value === 'completed') return 'Completed Tasks';
return 'Tasks';
});
// --- Methods ---
// Load from LocalStorage
const loadTodos = () => {
const saved = localStorage.getItem('focusflow_todos');
const savedTheme = localStorage.getItem('focusflow_theme');
if (saved) todos.value = JSON.parse(saved);
else {
// Initial Demo Data
todos.value = [
{ id: 1, text: 'Welcome to FocusFlow!', completed: false, tag: 'Welcome', createdAt: Date.now() - 100000 },
{ id: 2, text: 'Try dragging the tasks to reorder them', completed: false, tag: 'Tip', createdAt: Date.now() - 50000 },
{ id: 3, text: 'Double click text to edit', completed: true, tag: 'Tip', createdAt: Date.now() }
];
}
if (savedTheme) {
isDarkMode.value = savedTheme === 'dark';
}
};
const saveTodos = () => {
localStorage.setItem('focusflow_todos', JSON.stringify(todos.value));
};
const toggleDarkMode = () => {
isDarkMode.value = !isDarkMode.value;
const html = document.documentElement;
if (isDarkMode.value) {
html.classList.add('dark');
localStorage.setItem('focusflow_theme', 'dark');
} else {
html.classList.remove('dark');
localStorage.setItem('focusflow_theme', 'light');
}
};
const addTodo = () => {
if (newTodoText.value.trim() === '') return;
const tags = ['Personal', 'Work', 'Shopping', 'Health', 'Design'];
const randomTag = tags[Math.floor(Math.random() * tags.length)];
todos.value.unshift({
id: Date.now(),
text: newTodoText.value,
completed: false,
tag: randomTag,
createdAt: Date.now()
});
newTodoText.value = '';
saveTodos();
};
const deleteTodo = (id) => {
todos.value = todos.value.filter(t => t.id !== id);
saveTodos();
};
const clearCompleted = () => {
todos.value = todos.value.filter(t => !t.completed);
saveTodos();
};
// Edit Logic
const editTodo = (todo) => {
const newText = prompt('Edit task:', todo.text);
if (newText !== null && newText.trim() !== '') {
todo.text = newText.trim();
saveTodos();
}
};
// Date Formatting
const formatDate = (timestamp) => {
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
const oneDay = 24 * 60 * 60 * 1000;
if (diff < oneDay && now.getDate() === date.getDate()) {
return 'Today';
} else if (diff < 2 * oneDay) {
return 'Yesterday';
} else {
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
};
// Drag and Drop Logic
const dragStart = (index) => {
dragIndex.value = index;
};
const dragEnter = (index) => {
if (dragIndex.value !== index) {
const item = todos.value.splice(dragIndex.value, 1)[0];
todos.value.splice(index, 0, item);
dragIndex.value = index;
}
};
const dragDrop = () => {
saveTodos();
};
const dragLeave = () => {
// Optional: handle drag leave visual state
};
// --- Lifecycle ---
onMounted(() => {
loadTodos();
updateDate();
// Check system preference
if (!localStorage.getItem('focusflow_theme')) {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
isDarkMode.value = true;
}
}
});
onUnmounted(() => {
window.removeEventListener('resize', updateDate); // Cleanup if needed
});
return {
todos,
newTodoText,
currentFilter,
isSidebarOpen,
isDarkMode,
dateDisplay,
activeCount,
completedCount,
filteredTodos,
pageTitle,
addTodo,
deleteTodo,
clearCompleted,
editTodo,
formatDate,
toggleDarkMode,
dragStart,
dragEnter,
dragDrop,
dragLeave
};
}
}).mount('#app');
</script>
</body>
</html>