const BLOCK_ICONS = { morning: '🌅', school: '🎒', after: '⚽', evening: '🌙' }; const WEEKDAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; const WEEKEND = ['Saturday', 'Sunday']; function debounce(fn, ms) { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); }; } // Renders the weekly board. `editable` gates every mutation affordance // (contenteditable, delete buttons, add-task buttons) — the kiosk view passes // editable:false and gets a board with nothing but working checkboxes. function renderCalendar({ calendar, editable, weekdayBoard, weekendWrap, handlers, todayName }) { weekdayBoard.innerHTML = ''; weekendWrap.innerHTML = ''; const currentLabels = {}; calendar.blocks.forEach((b) => { currentLabels[b.key] = b.label; }); function syncLabel(key, sourceEl) { document.querySelectorAll(`.label[data-block="${key}"]`).forEach((el) => { if (el !== sourceEl) el.textContent = sourceEl.textContent; }); document.querySelectorAll(`.legend-label[data-block="${key}"]`).forEach((el) => { el.textContent = sourceEl.textContent; }); } function makeTask(dayName, blockKey, task) { const row = document.createElement('div'); row.className = 'task' + (task.done ? ' done' : '') + (editable ? '' : ' kiosk'); row.dataset.taskId = task.id; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = task.done; cb.onchange = () => { row.classList.toggle('done', cb.checked); handlers.onToggleDone(task.id, cb.checked); }; const span = document.createElement('div'); span.className = 'task-text'; span.textContent = task.text; row.appendChild(cb); row.appendChild(span); if (!editable) { // Kiosk mode: the 24px checkbox alone is a poor touch target, so the // whole row toggles it. Skip when the tap landed on the checkbox // itself — it already handles its own toggle+change natively. row.addEventListener('click', (e) => { if (e.target === cb) return; cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); }); } if (editable) { span.contentEditable = 'true'; span.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); span.blur(); } }); const debouncedEdit = debounce(() => { if (span.textContent.trim()) handlers.onTextEdit(task.id, span.textContent.trim()); }, 500); span.addEventListener('input', debouncedEdit); span.addEventListener('blur', () => { if (!span.textContent.trim()) span.textContent = task.text; }); const del = document.createElement('button'); del.className = 'del'; del.textContent = '✕'; del.onclick = () => { row.remove(); handlers.onDeleteTask(task.id); }; row.appendChild(del); } return row; } function makeDayCard(dayName) { const card = document.createElement('div'); card.className = 'day-card' + (dayName === todayName ? ' today' : ''); const head = document.createElement('div'); head.className = 'day-head'; head.innerHTML = `