diff --git a/public/app.js b/public/app.js index eb8d5ba..6fb632f 100644 --- a/public/app.js +++ b/public/app.js @@ -63,7 +63,7 @@ class App { if (hour >= 5 && hour < 12) { emoji = '☀️'; - greeting = 'Good morning, >Laila'; + greeting = 'Good morning, Laila'; sub = 'Ready to learn something new today?'; } else if (hour >= 12 && hour < 17) { emoji = '🌞'; @@ -178,15 +178,16 @@ class App { // ─── View Switching ─── - async switchView(mode) { + switchView(mode) { if (this.viewMode === mode) return; this.viewMode = mode; document.getElementById('view-active').classList.toggle('active', mode === 'active'); document.getElementById('view-archive').classList.toggle('active', mode === 'archive'); document.getElementById('overall-progress').classList.toggle('hidden', mode === 'archive'); document.getElementById('open-modal').style.display = mode === 'archive' ? 'none' : ''; - if (mode === 'archive') await this.loadArchivedTasks(); - else await this.loadTasks(); + + if (mode === 'archive') this.renderArchive(); + else this.render(); } // ─── Modal ─── @@ -354,6 +355,7 @@ class App { const taskList = this.getCurrentTasks(); if (editId) { + let rerenderTaskId = editId; if (parentId) { const parentTask = taskList.find(t => t.id === parentId); if (parentTask) { @@ -363,6 +365,7 @@ class App { this._syncToServer(parentTask); } } + rerenderTaskId = parentId; } else { const task = taskList.find(t => t.id === editId); if (task) { @@ -371,8 +374,7 @@ class App { } } this.updateOverallProgress(); - if (this.viewMode === 'archive') this.renderArchive(); - else this.render(); + this._rerenderCard(rerenderTaskId); } else { try { const response = await fetch(`${API_URL}/tasks`, { @@ -472,7 +474,7 @@ class App { if (totalTasks === 0) { const pctEl = document.getElementById('overall-percentage'); pctEl.textContent = '0%'; - document.getElementById('overall-progress-fill').style.width = '0%'; + document.getElementById('overall-progress-fill').style.height = '0%'; this._previousOverallProgress = 0; return; } @@ -487,11 +489,27 @@ class App { this.animateCounter(pctEl, prevPct, overallProgress, '%'); this._previousOverallProgress = overallProgress; - document.getElementById('overall-progress-fill').style.width = overallProgress + '%'; + document.getElementById('overall-progress-fill').style.height = overallProgress + '%'; } // ─── Server Sync (fire-and-forget) ─── + _rerenderCard(taskId) { + const oldCard = document.querySelector(`[data-task-id="${taskId}"]`); + if (!oldCard) return; + if (this.viewMode === 'archive') { + const index = this.archivedTasks.findIndex(t => t.id === taskId); + if (index === -1) return; + const newCard = this.renderArchivedTask(this.archivedTasks[index]); + oldCard.replaceWith(newCard); + } else { + const index = this.tasks.findIndex(t => t.id === taskId); + if (index === -1) return; + const newCard = this.renderTask(this.tasks[index], index); + oldCard.replaceWith(newCard); + } + } + _syncToServer(task) { fetch(`${API_URL}/tasks/${task.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(task) @@ -544,7 +562,7 @@ class App { if (!wasCompleted && isNowCompleted && task.reward) this._celebrateTaskId = taskId; this.updateOverallProgress(); - this.render(); + this._rerenderCard(taskId); this._checkCelebrations(prevProgress); this._syncToServer(task); } @@ -562,7 +580,7 @@ class App { if (parentWillComplete && task.reward && !task.completed) this._celebrateTaskId = taskId; this.updateOverallProgress(); - this.render(); + this._rerenderCard(taskId); this._checkCelebrations(prevProgress); this._syncToServer(task); } @@ -572,7 +590,7 @@ class App { if (!task) return; task.subtasks = task.subtasks.filter(st => st.id !== subtaskId); this.updateOverallProgress(); - this.render(); + this._rerenderCard(taskId); this._syncToServer(task); } @@ -854,7 +872,9 @@ class App { card.className = `task-card ${isCompleted ? 'completed' : ''}`; card.draggable = true; card.setAttribute('data-task-id', task.id); - card.style.borderLeftColor = task.color || '#6366f1'; + const color = task.color || '#6366f1'; + card.style.background = `linear-gradient(${this._hexToRgba(color, 0.12)}, ${this._hexToRgba(color, 0.12)}), rgba(255, 255, 255, 0.18)`; + card.style.borderColor = this._hexToRgba(color, 0.25); card.addEventListener('dragstart', (e) => { if (!e.target.closest('.subtask')) this.handleDragStart(e, task, false); }); card.addEventListener('dragend', (e) => this.handleDragEnd(e)); @@ -996,7 +1016,9 @@ class App { const card = document.createElement('div'); card.className = `task-card archived-card ${isCompleted ? 'completed' : ''}`; card.setAttribute('data-task-id', task.id); - card.style.borderLeftColor = task.color || '#6366f1'; + const color = task.color || '#6366f1'; + card.style.background = `linear-gradient(${this._hexToRgba(color, 0.12)}, ${this._hexToRgba(color, 0.12)}), rgba(255, 255, 255, 0.18)`; + card.style.borderColor = this._hexToRgba(color, 0.25); card.addEventListener('contextmenu', (e) => { if (!e.target.closest('.subtask')) this.showContextMenu(e, task, 'task'); }); card.addEventListener('dblclick', (e) => { if (!e.target.closest('.subtask') && !e.target.closest('button')) this.openEditModal(task); }); @@ -1312,6 +1334,13 @@ class App { div.textContent = text; return div.innerHTML; } + + _hexToRgba(hex, alpha) { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + } } const app = new App(); \ No newline at end of file diff --git a/public/index.html b/public/index.html index 490d17f..084256e 100644 --- a/public/index.html +++ b/public/index.html @@ -7,6 +7,18 @@ + + +
+
0%
+
+
+
+
+ 0/0 +
+
+
✎ Edit
@@ -117,19 +129,6 @@
-
-
- Overall Progress - 0% -
-
-
-
-
- 0 / 0 tasks completed -
-
-
diff --git a/public/styles.css b/public/styles.css index a61ac72..fa3e882 100644 --- a/public/styles.css +++ b/public/styles.css @@ -4,6 +4,10 @@ box-sizing: border-box; } +html { + overscroll-behavior: none; +} + :root { --card-bg: rgba(255, 255, 255, 0.55); --card-border: rgba(255, 255, 255, 0.45); @@ -42,7 +46,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; min-height: 100vh; - padding: 16px; + padding: 16px 16px 16px 64px; color: var(--text-primary); background: linear-gradient( @@ -54,6 +58,7 @@ body { ), url('img/background.jpg') center / cover no-repeat fixed; background-color: #c7d2fe; + overscroll-behavior: none; } .container { @@ -61,6 +66,68 @@ body { margin: 0 auto; } +/* ─── Progress Tower ─── */ +.progress-tower { + position: fixed; + left: 12px; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + z-index: 9000; + padding: 16px 10px; + background: rgba(255, 255, 255, 0.82); + border-radius: 24px; + border: 1px solid rgba(255, 255, 255, 0.6); + box-shadow: 0 4px 20px rgba(60, 60, 120, 0.15); + transition: opacity 0.3s; +} + +.progress-tower.hidden { + opacity: 0; + pointer-events: none; +} + +.tower-percentage { + font-size: 0.92em; + font-weight: 800; + color: var(--accent); + letter-spacing: -0.5px; +} + +.tower-bar { + width: 26px; + height: 220px; + background: rgba(0, 0, 0, 0.07); + border-radius: 13px; + overflow: hidden; + position: relative; +} + +.tower-fill { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 0%; + background: linear-gradient(to top, var(--accent), #a78bfa, var(--green)); + border-radius: 13px; + transition: height 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + +.tower-count { + font-size: 0.75em; + font-weight: 600; + color: var(--text-muted); + white-space: nowrap; +} + +.tower-count span { + color: var(--text-primary); +} + /* ─── Greeting Banner ─── */ .greeting-banner { padding: 10px 0 2px; @@ -521,54 +588,15 @@ button { .btn-delete:hover { background: var(--rose); color: white; } -/* ─── Overall Progress ─── */ -.overall-progress { - background: var(--card-bg); - backdrop-filter: blur(var(--blur)); - -webkit-backdrop-filter: blur(var(--blur)); - padding: 14px 18px; - border-radius: var(--radius); - margin-bottom: 16px; - box-shadow: var(--card-shadow); - border: 1px solid var(--card-border); -} - -.overall-progress.hidden { display: none; } - -.overall-header { - display: flex; - justify-content: space-between; - margin-bottom: 8px; - font-weight: 600; - font-size: 0.9em; -} - -#overall-percentage { - color: var(--accent); - font-weight: 700; -} - -.task-counter { - margin-top: 8px; - text-align: center; - color: var(--text-muted); - font-size: 0.82em; -} - -.task-counter span { - font-weight: 600; - color: var(--text-primary); -} - /* ─── Tasks ─── */ .tasks-container { display: grid; gap: 8px; + contain: layout style; } /* ─── Card ─── */ .task-card { - background: var(--card-bg); backdrop-filter: blur(var(--blur)); -webkit-backdrop-filter: blur(var(--blur)); border-radius: var(--radius); @@ -576,15 +604,14 @@ button { box-shadow: var(--card-shadow); cursor: grab; transition: all 0.2s; - border: 1px solid var(--card-border); - border-left: 4px solid var(--accent); + border: 1px solid rgba(255, 255, 255, 0.35); position: relative; + /* background set inline via JS using task color */ } .task-card:hover { - background: var(--card-hover); box-shadow: 0 4px 20px rgba(60, 60, 120, 0.14); - border-color: rgba(255, 255, 255, 0.6); + filter: brightness(1.04); } .task-card:active { cursor: grabbing; } @@ -706,19 +733,6 @@ button { text-align: right; } -.overall-progress .progress-bar { height: 16px; border-radius: 8px; } - -.overall-progress .progress-fill { - display: flex; - align-items: center; - justify-content: center; - color: white; - font-weight: 600; - font-size: 0.72em; - border-radius: 8px; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); -} - /* ─── Reward ─── */ .reward-section { background: rgba(16, 185, 129, 0.08); @@ -1009,6 +1023,7 @@ button { /* ─── Responsive ─── */ @media (max-width: 640px) { + body { padding: 12px 12px 12px 50px; } h1 { font-size: 1.2em; } header { @@ -1019,6 +1034,20 @@ button { .header-actions { justify-content: space-between; } .view-toggle { margin-left: auto; } .form-row-2 { grid-template-columns: 1fr; } - .task-card { padding: 10px 12px; } + .progress-tower { + left: 6px; + padding: 10px 6px; + border-radius: 18px; + } + .tower-bar { + width: 18px; + height: 140px; + } + .tower-percentage { font-size: 0.75em; } + .tower-count { font-size: 0.6em; } + .subtask { + backdrop-filter: none; + -webkit-backdrop-filter: none; + } .greeting-banner .greeting-text { font-size: 1em; } } \ No newline at end of file