Spaces:
Running
Running
| <html lang="fa" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>مدیریت وظایف و تمرکز | Task & Focus Manager</title> | |
| <!-- Font Awesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <!-- Google Fonts (Vazirmatn for Persian) --> | |
| <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary-color: #6366f1; | |
| --primary-hover: #4f46e5; | |
| --secondary-color: #ec4899; | |
| --background-color: #f3f4f6; | |
| --card-bg: #ffffff; | |
| --text-primary: #1f2937; | |
| --text-secondary: #6b7280; | |
| --success: #10b981; | |
| --danger: #ef4444; | |
| --warning: #f59e0b; | |
| --radius: 12px; | |
| --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| --transition: all 0.3s ease; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| outline: none; | |
| } | |
| body { | |
| font-family: 'Vazirmatn', sans-serif; | |
| background-color: var(--background-color); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 20px; | |
| } | |
| /* Header Styles */ | |
| header { | |
| width: 100%; | |
| max-width: 800px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 30px; | |
| padding: 10px 0; | |
| } | |
| .brand { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: var(--primary-color); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .anycoder-link { | |
| font-size: 0.9rem; | |
| color: var(--text-secondary); | |
| text-decoration: none; | |
| background: #e0e7ff; | |
| color: var(--primary-color); | |
| padding: 6px 12px; | |
| border-radius: 20px; | |
| transition: var(--transition); | |
| font-weight: 500; | |
| } | |
| .anycoder-link:hover { | |
| background: var(--primary-color); | |
| color: white; | |
| transform: translateY(-2px); | |
| } | |
| /* Main Container */ | |
| .container { | |
| display: grid; | |
| grid-template-columns: 1fr; | |
| gap: 20px; | |
| width: 100%; | |
| max-width: 800px; | |
| } | |
| @media (min-width: 768px) { | |
| .container { | |
| grid-template-columns: 1.5fr 1fr; | |
| } | |
| } | |
| /* Cards */ | |
| .card { | |
| background: var(--card-bg); | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| padding: 20px; | |
| overflow: hidden; | |
| } | |
| h2 { | |
| font-size: 1.2rem; | |
| margin-bottom: 15px; | |
| border-bottom: 2px solid #f3f4f6; | |
| padding-bottom: 10px; | |
| color: var(--text-primary); | |
| } | |
| /* Input Group */ | |
| .input-group { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| } | |
| input[type="text"] { | |
| flex: 1; | |
| padding: 12px; | |
| border: 2px solid #e5e7eb; | |
| border-radius: var(--radius); | |
| font-family: inherit; | |
| transition: var(--transition); | |
| } | |
| input[type="text"]:focus { | |
| border-color: var(--primary-color); | |
| } | |
| button { | |
| cursor: pointer; | |
| border: none; | |
| border-radius: var(--radius); | |
| padding: 10px 20px; | |
| font-family: inherit; | |
| font-weight: 500; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| } | |
| .btn-primary { | |
| background: var(--primary-color); | |
| color: white; | |
| } | |
| .btn-primary:hover { | |
| background: var(--primary-hover); | |
| transform: translateY(-1px); | |
| } | |
| /* Task List */ | |
| .task-list { | |
| list-style: none; | |
| max-height: 400px; | |
| overflow-y: auto; | |
| } | |
| .task-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 12px; | |
| margin-bottom: 10px; | |
| background: #f9fafb; | |
| border-radius: 8px; | |
| border-right: 4px solid transparent; | |
| transition: var(--transition); | |
| animation: slideIn 0.3s ease; | |
| } | |
| @keyframes slideIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .task-item:hover { | |
| background: #f3f4f6; | |
| transform: translateX(-5px); | |
| } | |
| .task-item.completed { | |
| border-right-color: var(--success); | |
| background: #f0fdf4; | |
| } | |
| .task-item.completed span { | |
| text-decoration: line-through; | |
| color: var(--text-secondary); | |
| } | |
| .task-actions { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .btn-icon { | |
| padding: 6px; | |
| border-radius: 6px; | |
| color: white; | |
| font-size: 0.8rem; | |
| } | |
| .btn-check { background: var(--success); } | |
| .btn-delete { background: var(--danger); } | |
| .btn-icon:hover { opacity: 0.9; } | |
| .empty-state { | |
| text-align: center; | |
| color: var(--text-secondary); | |
| padding: 20px; | |
| font-size: 0.9rem; | |
| } | |
| /* Pomodoro Timer */ | |
| .timer-display { | |
| font-size: 4rem; | |
| font-weight: 700; | |
| text-align: center; | |
| color: var(--primary-color); | |
| margin: 20px 0; | |
| font-variant-numeric: tabular-nums; | |
| letter-spacing: 2px; | |
| } | |
| .timer-controls { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| } | |
| .timer-modes { | |
| display: flex; | |
| justify-content: center; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| } | |
| .mode-btn { | |
| background: transparent; | |
| color: var(--text-secondary); | |
| border: 1px solid #e5e7eb; | |
| padding: 5px 10px; | |
| font-size: 0.8rem; | |
| } | |
| .mode-btn.active { | |
| background: var(--secondary-color); | |
| color: white; | |
| border-color: var(--secondary-color); | |
| } | |
| /* Stats */ | |
| .stats { | |
| display: flex; | |
| justify-content: space-around; | |
| margin-top: 20px; | |
| padding-top: 20px; | |
| border-top: 1px solid #f3f4f6; | |
| } | |
| .stat-item { | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| } | |
| .stat-label { | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| } | |
| /* Toast Notification */ | |
| .toast-container { | |
| position: fixed; | |
| bottom: 20px; | |
| left: 20px; | |
| z-index: 1000; | |
| } | |
| .toast { | |
| background: var(--text-primary); | |
| color: white; | |
| padding: 12px 20px; | |
| border-radius: var(--radius); | |
| margin-top: 10px; | |
| box-shadow: var(--shadow); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| animation: slideUp 0.3s ease; | |
| min-width: 250px; | |
| } | |
| @keyframes slideUp { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #c7c7c7; | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #a8a8a8; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="fa-solid fa-layer-group"></i> | |
| <span>تسک مستر</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank">Built with anycoder</a> | |
| </header> | |
| <div class="container"> | |
| <!-- Task Section --> | |
| <div class="card"> | |
| <h2><i class="fa-solid fa-list-check"></i> لیست وظایف</h2> | |
| <div class="input-group"> | |
| <input type="text" id="taskInput" placeholder="وظیفه جدید را وارد کنید..." onkeypress="handleKeyPress(event)"> | |
| <button class="btn-primary" onclick="addTask()"> | |
| <i class="fa-solid fa-plus"></i> افزودن | |
| </button> | |
| </div> | |
| <ul class="task-list" id="taskList"> | |
| <!-- Tasks will be injected here --> | |
| <li class="empty-state">هیچ وظیفهای ثبت نشده است. روز خوبی داشته باشید!</li> | |
| </ul> | |
| <div class="stats"> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="totalTasks">0</div> | |
| <div class="stat-label">کل وظایف</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="completedTasks">0</div> | |
| <div class="stat-label">انجام شده</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Timer Section --> | |
| <div class="card"> | |
| <h2><i class="fa-solid fa-hourglass-start"></i> تایمر تمرکز</h2> | |
| <div class="timer-modes"> | |
| <button class="mode-btn active" onclick="setTimerMode(25, this)">کار (25)</button> | |
| <button class="mode-btn" onclick="setTimerMode(5, this)">استراحت کوتاه (5)</button> | |
| <button class="mode-btn" onclick="setTimerMode(15, this)">استراحت بلند (15)</button> | |
| </div> | |
| <div class="timer-display" id="timer">25:00</div> | |
| <div class="timer-controls"> | |
| <button class="btn-primary" id="startBtn" onclick="toggleTimer()" style="background: var(--success);"> | |
| <i class="fa-solid fa-play"></i> شروع | |
| </button> | |
| <button class="btn-primary" onclick="resetTimer()" style="background: var(--text-secondary);"> | |
| <i class="fa-solid fa-rotate-right"></i> بازنشانی | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="toast-container" id="toastContainer"></div> | |
| <script> | |
| // --- State Management --- | |
| let tasks = JSON.parse(localStorage.getItem('tasks')) || []; | |
| let timerInterval; | |
| let timeLeft = 25 * 60; | |
| let isTimerRunning = false; | |
| let currentModeTime = 25 * 60; | |
| // --- DOM Elements --- | |
| const taskInput = document.getElementById('taskInput'); | |
| const taskList = document.getElementById('taskList'); | |
| const totalTasksEl = document.getElementById('totalTasks'); | |
| const completedTasksEl = document.getElementById('completedTasks'); | |
| const timerDisplay = document.getElementById('timer'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const toastContainer = document.getElementById('toastContainer'); | |
| // --- Initialization --- | |
| document.addEventListener('DOMContentLoaded', () => { | |
| renderTasks(); | |
| updateStats(); | |
| }); | |
| // --- Task Functions --- | |
| function addTask() { | |
| const text = taskInput.value.trim(); | |
| if (text === '') { | |
| showToast('لطفا متنی برای وظیفه وارد کنید', 'warning'); | |
| return; | |
| } | |
| const newTask = { | |
| id: Date.now(), | |
| text: text, | |
| completed: false | |
| }; | |
| tasks.unshift(newTask); // Add to top | |
| saveTasks(); | |
| renderTasks(); | |
| updateStats(); | |
| taskInput.value = ''; | |
| showToast('وظیفه جدید اضافه شد', 'success'); | |
| } | |
| function handleKeyPress(event) { | |
| if (event.key === 'Enter') { | |
| addTask(); | |
| } | |
| } | |
| function toggleTask(id) { | |
| tasks = tasks.map(task => | |
| task.id === id ? { ...task, completed: !task.completed } : task | |
| ); | |
| saveTasks(); | |
| renderTasks(); | |
| updateStats(); | |
| } | |
| function deleteTask(id) { | |
| // Add a small confirmation delay or UI effect could be nice, but keeping it snappy | |
| tasks = tasks.filter(task => task.id !== id); | |
| saveTasks(); | |
| renderTasks(); | |
| updateStats(); | |
| showToast('وظیفه حذف شد', 'info'); | |
| } | |
| function saveTasks() { | |
| localStorage.setItem('tasks', JSON.stringify(tasks)); | |
| } | |
| function renderTasks() { | |
| taskList.innerHTML = ''; | |
| if (tasks.length === 0) { | |
| taskList.innerHTML = '<li class="empty-state">هیچ وظیفهای ثبت نشده است. روز خوبی داشته باشید!</li>'; | |
| return; | |
| } | |
| tasks.forEach(task => { | |
| const li = document.createElement('li'); | |
| li.className = `task-item ${task.completed ? 'completed' : ''}`; | |
| li.innerHTML = ` | |
| <span>${escapeHtml(task.text)}</span> | |
| <div class="task-actions"> | |
| <button class="btn-icon btn-check" onclick="toggleTask(${task.id})" title="${task.completed ? 'بازگردانی' : 'انجام شد'}"> | |
| <i class="fa-solid ${task.completed ? 'fa-rotate-left' : 'fa-check'}"></i> | |
| </button> | |
| <button class="btn-icon btn-delete" onclick="deleteTask(${task.id})" title="حذف"> | |
| <i class="fa-solid fa-trash"></i> | |
| </button> | |
| </div> | |
| `; | |
| taskList.appendChild(li); | |
| }); | |
| } | |
| function updateStats() { | |
| const total = tasks.length; | |
| const completed = tasks.filter(t => t.completed).length; | |
| totalTasksEl.textContent = total; | |
| completedTasksEl.textContent = completed; | |
| } | |
| // Security: Prevent XSS | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| // --- Timer Functions --- | |
| function formatTime(seconds) { | |
| const m = Math.floor(seconds / 60); | |
| const s = seconds % 60; | |
| return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; | |
| } | |
| function updateTimerDisplay() { | |
| timerDisplay.textContent = formatTime(timeLeft); | |
| document.title = `${formatTime(timeLeft)} - مدیریت وظایف`; | |
| } | |
| function toggleTimer() { | |
| if (isTimerRunning) { | |
| clearInterval(timerInterval); | |
| isTimerRunning = false; | |
| startBtn.innerHTML = '<i class="fa-solid fa-play"></i> ادامه'; | |
| startBtn.style.background = 'var(--success)'; | |
| } else { | |
| if (timeLeft <= 0) timeLeft = currentModeTime; | |
| isTimerRunning = true; | |
| startBtn.innerHTML = '<i class="fa-solid fa-pause"></i> توقف'; | |
| startBtn.style.background = 'var(--warning)'; | |
| timerInterval = setInterval(() => { | |
| timeLeft--; | |
| updateTimerDisplay(); | |
| if (timeLeft <= 0) { | |
| clearInterval(timerInterval); | |
| isTimerRunning = false; | |
| startBtn.innerHTML = '<i class="fa-solid fa-play"></i> شروع'; | |
| startBtn.style.background = 'var(--success)'; | |
| playAlarm(); | |
| showToast('زمان به پایان رسید!', 'success'); | |
| document.title = 'زمان تمام شد!'; | |
| } | |
| }, 1000); | |
| } | |
| } | |
| function resetTimer() { | |
| clearInterval(timerInterval); | |
| isTimerRunning = false; | |
| timeLeft = currentModeTime; | |
| updateTimerDisplay(); | |
| startBtn.innerHTML = '<i class="fa-solid fa-play"></i> شروع'; | |
| startBtn.style.background = 'var(--success)'; | |
| document.title = 'مدیریت وظایف و تمرکز'; | |
| } | |
| function setTimerMode(minutes, btnElement) { | |
| // Update UI active state | |
| document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active')); | |
| btnElement.classList.add('active'); | |
| // Reset Logic | |
| clearInterval(timerInterval); | |
| isTimerRunning = false; | |
| startBtn.innerHTML = '<i class="fa-solid fa-play"></i> شروع'; | |
| startBtn.style.background = 'var(--success)'; | |
| currentModeTime = minutes * 60; | |
| timeLeft = currentModeTime; | |
| updateTimerDisplay(); | |
| } | |
| function playAlarm() { | |
| // Simple beep using AudioContext for modern browsers without external assets | |
| try { | |
| const AudioContext = window.AudioContext || window.webkitAudioContext; | |
| if (AudioContext) { | |
| const ctx = new AudioContext(); | |
| const osc = ctx.createOscillator(); | |
| const gain = ctx.createGain(); | |
| osc.connect(gain); | |
| gain.connect(ctx.destination); | |
| osc.type = 'sine'; | |
| osc.frequency.value = 880; // A5 | |
| gain.gain.value = 0.1; | |
| osc.start(); | |
| setTimeout(() => { | |
| osc.stop(); | |
| // Second beep | |
| setTimeout(() => { | |
| const osc2 = ctx.createOscillator(); | |
| const gain2 = ctx.createGain(); | |
| osc2.connect(gain2); | |
| gain2.connect(ctx.destination); | |
| osc2.type = 'sine'; | |
| osc2.frequency.value = 880; | |
| gain2.gain.value = 0.1; | |
| osc2.start(); | |
| setTimeout(() => osc2.stop(), 300); | |
| }, 200); | |
| }, 300); | |
| } | |
| } catch (e) { | |
| console.log("Audio not supported"); | |
| } | |
| } | |
| // --- Toast Notification System --- | |
| function showToast(message, type = 'info') { | |
| const toast = document.createElement('div'); | |
| toast.className = 'toast'; | |
| let icon = 'fa-info-circle'; | |
| let color = 'var(--primary-color)'; | |
| if (type === 'success') { icon = 'fa-check-circle'; color = 'var(--success)'; } | |
| if (type === 'warning') { icon = 'fa-exclamation-triangle'; color = 'var(--warning)'; } | |
| if (type === 'error') { icon = 'fa-times-circle'; color = 'var(--danger)'; } | |
| toast.style.borderRight = `4px solid ${color}`; | |
| toast.innerHTML = ` | |
| <i class="fa-solid ${icon}" style="color: ${color}"></i> | |
| <span>${message}</span> | |
| `; | |
| toastContainer.appendChild(toast); | |
| // Remove after 3 seconds | |
| setTimeout(() => { | |
| toast.style.opacity = '0'; | |
| toast.style.transform = 'translateY(20px)'; | |
| setTimeout(() => { | |
| toast.remove(); | |
| }, 300); | |
| }, 3000); | |
| } | |
| </script> | |
| </body> | |
| </html> |