Unscatter your life

Sign in to continue

Something went wrong. Please try again.
Scatterbrain
Brain Dump 0

What are you adding?

Save changes

This is a recurring commitment. Which occurrences should be updated?

Delete commitment

This is a recurring commitment. What would you like to remove?

swipe to change day
let dragId = null, modalCol = null; let currentView = 'week', dayIndex = new Date().getDay(); let inboxExpanded = true, weekOffset = 0; let modalMode = 'add', modalType = 'task'; let editId = null, editIsRecurring = false; let subtaskItems = [], activeTab = 'notes'; let typePickerCol = null, scopeResolve = null, deleteScopeResolve = null; // ── API ── async function api(method, path, body) { const res = await fetch(`${SUPABASE_URL}/rest/v1/${path}`, { method, headers: { 'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${SUPABASE_KEY}`, 'Content-Type': 'application/json', 'Prefer': 'return=minimal' }, body: body ? JSON.stringify(body) : undefined }); if (!res.ok) { throw new Error(await res.text()); } const t = await res.text(); return t ? JSON.parse(t) : null; } async function loadAll() { const [t, c] = await Promise.all([ api('GET', 'tasks?select=*&order=position.asc,created_at.asc'), api('GET', 'commitments?select=*&order=created_at.asc') ]); tasks = t || []; commitments = c || []; } // ── TASK CRUD ── async function createTask(title, column, time_slot, notes, subtasks) { const maxPos = tasks.filter(t => t.column===column).reduce((m,t)=>Math.max(m,t.position||0),0); await api('POST','tasks',{title,column,time_slot:time_slot||null,position:maxPos+1,notes:notes||null,subtasks:subtasks||null}); await loadAll(); } async function updateTask(id, f) { const p={}; ['title','time_slot','notes','subtasks','completed'].forEach(k=>{if(f[k]!==undefined)p[k]=f[k]||null;}); await api('PATCH',`tasks?id=eq.${id}`,p); await loadAll(); } async function deleteTask(id) { await api('DELETE',`tasks?id=eq.${id}`); await loadAll(); } async function moveTask(id, col) { await api('PATCH',`tasks?id=eq.${id}`,{column:col}); await loadAll(); } // ── COMMITMENT CRUD ── async function createCommitment(data) { await api('POST','commitments',data); await loadAll(); } async function updateCommitment(id, f) { await api('PATCH',`commitments?id=eq.${id}`,f); await loadAll(); } async function deleteCommitment(id) { await api('DELETE',`commitments?id=eq.${id}`); await loadAll(); } // ── DATES ── function getWeekDates() { const now=new Date(), sun=new Date(now); sun.setDate(now.getDate()-now.getDay()+(weekOffset*7)); return Array.from({length:7},(_,i)=>{const d=new Date(sun);d.setDate(sun.getDate()+i);return d;}); } function isToday(d){const n=new Date();return d.getDate()===n.getDate()&&d.getMonth()===n.getMonth()&&d.getFullYear()===n.getFullYear();} function colForDate(d){return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;} function dateFromCol(s){return new Date(s+'T00:00:00');} function weekLabel(){const d=getWeekDates(),f=x=>`${x.toLocaleString('default',{month:'short'})} ${x.getDate()}`;return `${f(d[0])} – ${f(d[6])}`;} function colLabel(col){if(col==='inbox')return 'Brain Dump';if(/^\d{4}-\d{2}-\d{2}$/.test(col))return DAY_LONG[dateFromCol(col).getDay()];return col.charAt(0).toUpperCase()+col.slice(1);} // ── COMMITMENT LOGIC ── function commitmentAppearsOn(c, dateStr) { if (!c.recurrence || c.recurrence==='none') return c.anchor_date===dateStr; const anchor=dateFromCol(c.anchor_date), target=dateFromCol(dateStr); if (targetc.recurrence_end) return false; const exc = c.exceptions ? JSON.parse(c.exceptions) : []; if (exc.includes(dateStr)) return false; if (c.recurrence==='daily') return true; if (c.recurrence==='weekly') return target.getDay()===anchor.getDay(); if (c.recurrence==='custom') { const days=c.recurrence_days?JSON.parse(c.recurrence_days):[]; return days.includes(target.getDay()); } return false; } function getCommitmentsForDate(dateStr){return commitments.filter(c=>commitmentAppearsOn(c,dateStr));} function formatTime(t){if(!t)return '';const[h,m]=t.split(':').map(Number),ampm=h>=12?'PM':'AM',h12=h%12||12;return `${h12}:${String(m).padStart(2,'0')} ${ampm}`;} function recurrenceLabel(c){if(!c.recurrence||c.recurrence==='none')return '';if(c.recurrence==='daily')return '↻ Daily';if(c.recurrence==='weekly')return '↻ Weekly';if(c.recurrence==='custom'){const days=c.recurrence_days?JSON.parse(c.recurrence_days):[];return '↻ '+days.map(d=>DAY_SHORT[d]).join('/');}return '';} function sortedItemsForCol(col) { const colTasks=tasks.filter(t=>t.column===col); const colCommits=col==='inbox'?[]:getCommitmentsForDate(col); const noTime=[ ...colTasks.filter(t=>!t.time_slot).sort((a,b)=>(a.position||0)-(b.position||0)), ...colCommits.filter(c=>!c.start_time).map(c=>({...c,_type:'commitment'})) ]; const timed=[ ...colTasks.filter(t=>t.time_slot).map(t=>({...t,_type:'task',_sort:t.time_slot})), ...colCommits.filter(c=>c.start_time).map(c=>({...c,_type:'commitment',_sort:c.start_time})) ].sort((a,b)=>a._sort.localeCompare(b._sort)); return {noTime,timed}; } // ── RENDER ── function renderCard(task) { const card=document.createElement('div'); card.className='task-card'+(task.completed?' completed':''); card.draggable=true; card.dataset.id=task.id; const hasNotes=task.notes&&task.notes.trim().length>0; const hasSubtasks=task.subtasks&&JSON.parse(task.subtasks).length>0; const indicators=(hasNotes||hasSubtasks)?`${hasNotes?'':''}${hasSubtasks?'»':''}`:''; const timeLine=task.time_slot?`
${formatTime(task.time_slot)}${indicators}
`:(indicators?`
${indicators}
`:''); card.innerHTML=`
${escHtml(task.title)}
${timeLine}`; card.addEventListener('click',e=>{if(e.target.classList.contains('card-delete')||e.target.classList.contains('card-complete'))return;openEditTaskModal(task);}); card.addEventListener('dragstart',e=>{dragId=task.id;card.classList.add('dragging');e.dataTransfer.effectAllowed='move';}); card.addEventListener('dragend',()=>{dragId=null;card.classList.remove('dragging');document.querySelectorAll('.drag-over').forEach(el=>el.classList.remove('drag-over'));}); addTouchDrag(card,task.id); return card; } function isCommitmentAutoComplete(c, dateStr) { if (!c.end_time) return false; const now = new Date(); const today = colForDate(now); if (dateStr !== today) return false; const [h,m] = c.end_time.split(':').map(Number); const endDate = new Date(); endDate.setHours(h,m,0,0); return now > endDate; } function renderCommitmentCard(c, dateStr) { const isCompleted = c.completed || isCommitmentAutoComplete(c, dateStr); const card=document.createElement('div'); card.className='commitment-card'+(isCompleted?' completed':''); const timeStr=c.start_time?formatTime(c.start_time)+(c.end_time?` – ${formatTime(c.end_time)}`:''):''; const rec=recurrenceLabel(c); const hasNotes=c.notes&&c.notes.trim().length>0; const hasSubtasks=c.subtasks&&JSON.parse(c.subtasks).length>0; const indicators=(hasNotes||hasSubtasks)?`${hasNotes?'':''}${hasSubtasks?'':''}`:''; const timeLine=timeStr?`
${timeStr}${indicators}
`:(indicators?`
${indicators}
`:''); card.innerHTML=`
${escHtml(c.title)}
${timeLine}${c.location?`
📍 ${escHtml(c.location)}
`:''}${rec?`
${rec}
`:''}`; card.addEventListener('click',e=>{if(e.target.classList.contains('card-delete')||e.target.classList.contains('commit-complete'))return;openEditCommitmentModal(c,dateStr);}); return card; } function renderColContents(el, col) { const {noTime,timed}=sortedItemsForCol(col); if(noTime.length===0&&timed.length===0){const e=document.createElement('div');e.className='empty-col';e.textContent='—';el.appendChild(e);} noTime.forEach(item=>{ if(item._type==='commitment') el.appendChild(renderCommitmentCard(item,col)); else el.appendChild(renderCard(item)); }); if(timed.length>0){const div=document.createElement('div');div.className='time-divider';div.textContent='Scheduled';el.appendChild(div);timed.forEach(item=>{if(item._type==='commitment')el.appendChild(renderCommitmentCard(item,col));else el.appendChild(renderCard(item));});} } function renderInbox() { const c=document.getElementById('inbox-cards'); c.innerHTML=''; const btn=document.createElement('button'); btn.className='inbox-add-btn'; btn.textContent='+'; btn.onclick=()=>openTypePicker('inbox'); c.appendChild(btn); tasks.filter(t=>t.column==='inbox').forEach(t=>c.appendChild(renderCard(t))); document.getElementById('inbox-count').textContent=tasks.filter(t=>t.column==='inbox').length; } function renderWeek() { const wv=document.getElementById('week-view'); wv.innerHTML=''; const inner=document.createElement('div'); inner.id='week-view-inner'; getWeekDates().forEach(date=>{ const col=colForDate(date),today=isToday(date); const colEl=document.createElement('div'); colEl.className='day-col'+(today?' today':''); colEl.dataset.col=col; colEl.innerHTML=`
${DAY_SHORT[date.getDay()]}
${date.getDate()}
`; const te=document.createElement('div'); te.className='day-tasks'; te.dataset.col=col; te.addEventListener('dragover',e=>onDragOver(e,col)); te.addEventListener('drop',e=>onDrop(e,col)); te.addEventListener('dragleave',onDragLeave); renderColContents(te,col); const dz=document.createElement('div'); dz.className='drop-zone'; dz.addEventListener('dragover',e=>onDragOver(e,col)); dz.addEventListener('drop',e=>onDrop(e,col)); dz.addEventListener('dragleave',onDragLeave); te.appendChild(dz); colEl.appendChild(te); const ab=document.createElement('button'); ab.className='col-add-btn'; ab.textContent='+ Add'; ab.onclick=()=>openTypePicker(col); colEl.appendChild(ab); inner.appendChild(colEl); }); wv.appendChild(inner); } function renderDayView() { const dates=getWeekDates(), date=dates.find(d=>d.getDay()===dayIndex)||dates[0]; const col=colForDate(date), today=isToday(date); document.getElementById('day-nav-name').textContent=DAY_LONG[date.getDay()]; const de=document.getElementById('day-nav-date'); de.textContent=`${date.toLocaleString('default',{month:'short'})} ${date.getDate()}`; de.className='day-nav-date'+(today?' today-date':''); const c=document.getElementById('day-tasks-container'); c.innerHTML=''; renderColContents(c,col); document.getElementById('day-add-btn').onclick=()=>openTypePicker(col); } function render() { document.getElementById('week-label').textContent=weekLabel(); renderInbox(); renderWeek(); if(currentView==='day') renderDayView(); } // ── TYPE PICKER ── function openTypePicker(col){typePickerCol=col;document.getElementById('type-picker-overlay').classList.add('open');} function closeTypePicker(){document.getElementById('type-picker-overlay').classList.remove('open');typePickerCol=null;} function closeTypePickerOnOverlay(e){if(e.target===document.getElementById('type-picker-overlay'))closeTypePicker();} // ── MODAL ── function openModal(type) { const targetCol = typePickerCol; closeTypePicker(); modalType=type; modalMode='add'; editId=null; subtaskItems=[]; const isC=type==='commitment'; document.getElementById('modal-title').textContent=`Add ${isC?'Commitment':'Task'} — ${colLabel(targetCol||'inbox')}`; document.getElementById('modal-text').value=''; document.getElementById('modal-notes').value=''; document.getElementById('task-time-row').style.display=isC?'none':'flex'; document.getElementById('commit-fields').style.display=isC?'block':'none'; document.getElementById('modal-time').value=''; document.getElementById('modal-start-time').value=''; document.getElementById('modal-end-time').value=''; document.getElementById('modal-location').value=''; document.getElementById('recurrence-select').value='none'; document.getElementById('custom-days-row').style.display='none'; document.getElementById('recurrence-end-row').style.display='none'; document.getElementById('modal-recurrence-end').value=''; document.querySelectorAll('.day-pill').forEach(p=>p.classList.remove('selected')); document.getElementById('modal').className=isC?'commitment-modal':'task-modal'; document.getElementById('modal-submit-btn').className=`btn ${isC?'btn-commit':'btn-primary'}`; document.getElementById('modal-submit-btn').textContent='Add'; document.getElementById('modal-delete-btn').style.display='none'; modalCol=targetCol; // Prefill date field if adding to a specific day, leave blank for Brain Dump const dateField=document.getElementById('modal-date'); dateField.value=(/^\d{4}-\d{2}-\d{2}$/.test(targetCol))?targetCol:''; // Hide date field when editing (date is fixed) document.getElementById('modal-date-row').style.display='flex'; switchTab('notes'); renderSubtaskList(); document.getElementById('modal-overlay').classList.add('open'); setTimeout(()=>document.getElementById('modal-text').focus(),50); } function openEditTaskModal(task) { modalType='task'; modalMode='edit'; editId=task.id; modalCol=task.column; try{subtaskItems=task.subtasks?JSON.parse(task.subtasks):[];}catch{subtaskItems=[];} document.getElementById('modal-title').textContent='Edit Task'; document.getElementById('modal-text').value=task.title||''; document.getElementById('modal-time').value=task.time_slot||''; document.getElementById('modal-notes').value=task.notes||''; document.getElementById('task-time-row').style.display='flex'; document.getElementById('commit-fields').style.display='none'; document.getElementById('modal-date-row').style.display='none'; document.getElementById('modal').className='task-modal'; document.getElementById('modal-submit-btn').className='btn btn-primary'; document.getElementById('modal-submit-btn').textContent='Save'; document.getElementById('modal-delete-btn').style.display='block'; switchTab('notes'); renderSubtaskList(); document.getElementById('modal-overlay').classList.add('open'); setTimeout(()=>document.getElementById('modal-text').focus(),50); } function openEditCommitmentModal(c, dateStr) { modalType='commitment'; modalMode='edit'; editId=c.id; modalCol=dateStr; editIsRecurring=!!(c.recurrence&&c.recurrence!=='none'); try{subtaskItems=c.subtasks?JSON.parse(c.subtasks):[];}catch{subtaskItems=[];} document.getElementById('modal-title').textContent='Edit Commitment'; document.getElementById('modal-text').value=c.title||''; document.getElementById('modal-start-time').value=c.start_time||''; document.getElementById('modal-end-time').value=c.end_time||''; document.getElementById('modal-location').value=c.location||''; document.getElementById('modal-notes').value=c.notes||''; document.getElementById('recurrence-select').value=c.recurrence||'none'; document.getElementById('modal-recurrence-end').value=c.recurrence_end||''; document.getElementById('task-time-row').style.display='none'; document.getElementById('commit-fields').style.display='block'; document.getElementById('modal-date-row').style.display='none'; onRecurrenceChange(); if(c.recurrence==='custom'&&c.recurrence_days){const days=JSON.parse(c.recurrence_days);document.querySelectorAll('.day-pill').forEach(p=>{if(days.includes(Number(p.dataset.day)))p.classList.add('selected');});} document.getElementById('modal').className='commitment-modal'; document.getElementById('modal-submit-btn').className='btn btn-commit'; document.getElementById('modal-submit-btn').textContent='Save'; document.getElementById('modal-delete-btn').style.display='block'; switchTab('notes'); renderSubtaskList(); document.getElementById('modal-overlay').classList.add('open'); setTimeout(()=>document.getElementById('modal-text').focus(),50); } function closeModal(){document.getElementById('modal-overlay').classList.remove('open');modalCol=null;editId=null;subtaskItems=[];} function closeModalOnOverlay(e){if(e.target===document.getElementById('modal-overlay'))closeModal();} // ── RECURRENCE UI ── function onRecurrenceChange(){ const v=document.getElementById('recurrence-select').value; document.getElementById('custom-days-row').style.display=v==='custom'?'flex':'none'; document.getElementById('recurrence-end-row').style.display=v!=='none'?'flex':'none'; } function toggleDayPill(el){el.classList.toggle('selected');} // ── SCOPE DIALOG ── function askScope(){return new Promise(r=>{scopeResolve=r;document.getElementById('scope-overlay').classList.add('open');});} function resolveScope(val){document.getElementById('scope-overlay').classList.remove('open');if(scopeResolve){scopeResolve(val);scopeResolve=null;}} function cancelScopeOnOverlay(e){if(e.target===document.getElementById('scope-overlay')){document.getElementById('scope-overlay').classList.remove('open');if(scopeResolve){scopeResolve(null);scopeResolve=null;}}} function askDeleteScope(){return new Promise(r=>{deleteScopeResolve=r;document.getElementById('delete-scope-overlay').classList.add('open');});} function resolveDeleteScope(val){document.getElementById('delete-scope-overlay').classList.remove('open');if(deleteScopeResolve){deleteScopeResolve(val);deleteScopeResolve=null;}} function cancelDeleteScopeOnOverlay(e){if(e.target===document.getElementById('delete-scope-overlay')){document.getElementById('delete-scope-overlay').classList.remove('open');if(deleteScopeResolve){deleteScopeResolve(null);deleteScopeResolve=null;}}} // ── TABS & SUBTASKS ── function switchTab(tab){activeTab=tab;document.getElementById('panel-notes').style.display=tab==='notes'?'block':'none';document.getElementById('panel-subtasks').style.display=tab==='subtasks'?'block':'none';document.getElementById('tab-notes').classList.toggle('active',tab==='notes');document.getElementById('tab-subtasks').classList.toggle('active',tab==='subtasks');} function renderSubtaskList(){const l=document.getElementById('subtask-list');l.innerHTML='';subtaskItems.forEach((item,i)=>{const r=document.createElement('div');r.className='subtask-item';r.innerHTML=`${escHtml(item.text)}`;l.appendChild(r);});} function addSubtaskItem(){const i=document.getElementById('subtask-input'),t=i.value.trim();if(!t)return;subtaskItems.push({text:t,checked:false});i.value='';renderSubtaskList();i.focus();} function toggleSubtask(i){subtaskItems[i].checked=!subtaskItems[i].checked;renderSubtaskList();} function removeSubtask(i){subtaskItems.splice(i,1);renderSubtaskList();} // ── SUBMIT ── async function submitModal() { const title=document.getElementById('modal-text').value.trim(); if(!title)return; const notes=document.getElementById('modal-notes').value.trim()||null; const subtasksJson=subtaskItems.length?JSON.stringify(subtaskItems):null; if(modalType==='task'){ const time=document.getElementById('modal-time').value||null; const dateVal=document.getElementById('modal-date').value; const col=dateVal?dateVal:(modalCol||'inbox'); if(modalMode==='add') await createTask(title,col,time,notes,subtasksJson); else await updateTask(editId,{title,time_slot:time,notes,subtasks:subtasksJson}); } else { const start_time=document.getElementById('modal-start-time').value||null; const end_time=document.getElementById('modal-end-time').value||null; const location=document.getElementById('modal-location').value.trim()||null; const recurrence=document.getElementById('recurrence-select').value; const recurrence_end=document.getElementById('modal-recurrence-end').value||null; const selDays=[...document.querySelectorAll('.day-pill.selected')].map(p=>Number(p.dataset.day)); const recurrence_days=recurrence==='custom'?JSON.stringify(selDays):null; const dateVal=document.getElementById('modal-date').value; const anchorDate=dateVal?dateVal:((!modalCol||modalCol==='inbox')?colForDate(new Date()):modalCol); if(modalMode==='add'){ await createCommitment({title,anchor_date:anchorDate,start_time,end_time,location,notes,subtasks:subtasksJson,recurrence:recurrence==='none'?null:recurrence,recurrence_days,recurrence_end,exceptions:null}); } else { let scope='all'; if(editIsRecurring) scope=await askScope(); if(!scope){closeModal();return;} if(scope==='all'){ await updateCommitment(editId,{title,start_time,end_time,location,notes,subtasks:subtasksJson,recurrence:recurrence==='none'?null:recurrence,recurrence_days,recurrence_end}); } else { const orig=commitments.find(c=>String(c.id)===String(editId)); const exc=orig?.exceptions?JSON.parse(orig.exceptions):[]; exc.push(modalCol); await updateCommitment(editId,{exceptions:JSON.stringify(exc)}); await createCommitment({title,anchor_date:modalCol,start_time,end_time,location,notes,subtasks:subtasksJson,recurrence:null,recurrence_days:null,recurrence_end:null,exceptions:null}); } } } closeModal(); render(); } document.getElementById('modal-text').addEventListener('keydown',e=>{if(e.key==='Enter')submitModal();if(e.key==='Escape')closeModal();}); document.getElementById('subtask-input').addEventListener('keydown',e=>{if(e.key==='Enter')addSubtaskItem();if(e.key==='Escape')closeModal();}); // ── DELETE ── async function handleDeleteTask(id,e){e.stopPropagation();e.preventDefault();await deleteTask(id);render();} async function handleDeleteCommitment(id,dateStr,e){ e.stopPropagation();e.preventDefault(); const c=commitments.find(x=>String(x.id)===String(id)); if(c&&c.recurrence&&c.recurrence!=='none'){ const scope=await askDeleteScope(); if(!scope){render();return;} if(scope==='one'){ const exc=c.exceptions?JSON.parse(c.exceptions):[]; if(!exc.includes(dateStr)){exc.push(dateStr);await updateCommitment(id,{exceptions:JSON.stringify(exc)});} } else { await deleteCommitment(id); } } else { await deleteCommitment(id); } render(); } async function toggleCommitmentComplete(id,dateStr,e){ e.stopPropagation();e.preventDefault(); const c=commitments.find(x=>String(x.id)===String(id)); if(!c)return; const newVal=!c.completed; c.completed=newVal; render(); try { await api('PATCH',`commitments?id=eq.${id}`,{completed:newVal}); } catch(err){ console.error('Commitment complete toggle failed:',err); c.completed=!newVal; render(); } } async function deleteFromModal() { if(modalType==='task'){ await deleteTask(editId); closeModal(); render(); } else { closeModal(); const c=commitments.find(x=>String(x.id)===String(editId)); if(c&&c.recurrence&&c.recurrence!=='none'){ const scope=await askDeleteScope(); if(!scope){render();return;} if(scope==='one'){ const exc=c.exceptions?JSON.parse(c.exceptions):[]; if(!exc.includes(modalCol)){exc.push(modalCol);await updateCommitment(editId,{exceptions:JSON.stringify(exc)});} } else { await deleteCommitment(editId); } } else { await deleteCommitment(editId); } render(); } } async function toggleTaskComplete(id,e){ e.stopPropagation();e.preventDefault(); const task=tasks.find(t=>String(t.id)===String(id)); if(!task)return; const newVal=!task.completed; task.completed=newVal; render(); try { await api('PATCH',`tasks?id=eq.${id}`,{completed:newVal}); } catch(err) { console.error('Complete toggle failed:',err); task.completed=!newVal; render(); } } function onDragOver(e,col){e.preventDefault();e.dataTransfer.dropEffect='move';e.currentTarget.classList.add('drag-over');} function onDragLeave(e){e.currentTarget.classList.remove('drag-over');} async function onDrop(e,col){e.preventDefault();e.currentTarget.classList.remove('drag-over');if(!dragId)return;await moveTask(dragId,col);render();} function addTouchDrag(card,taskId){ let clone,startCol; card.addEventListener('touchstart',e=>{startCol=card.closest('[data-col]')?.dataset.col||'inbox';dragId=taskId;clone=card.cloneNode(true);clone.style.cssText=`position:fixed;pointer-events:none;z-index:9999;opacity:0.85;width:${card.offsetWidth}px;transform:scale(1.03);left:${card.getBoundingClientRect().left}px;top:${card.getBoundingClientRect().top}px;`;document.body.appendChild(clone);card.style.opacity='0.3';},{passive:true}); card.addEventListener('touchmove',e=>{if(!clone)return;e.preventDefault();const t=e.touches[0];clone.style.left=`${t.clientX-card.offsetWidth/2}px`;clone.style.top=`${t.clientY-20}px`;},{passive:false}); card.addEventListener('touchend',async e=>{if(!clone)return;const t=e.changedTouches[0];clone.remove();card.style.opacity='';clone=null;const targetCol=document.elementFromPoint(t.clientX,t.clientY)?.closest('[data-col]')?.dataset.col;if(targetCol&&targetCol!==startCol){await moveTask(taskId,targetCol);render();}dragId=null;}); } // ── SWIPE ── let swipeStartX=null; document.getElementById('day-view').addEventListener('touchstart',e=>{swipeStartX=e.touches[0].clientX;},{passive:true}); document.getElementById('day-view').addEventListener('touchend',e=>{if(swipeStartX===null)return;const dx=e.changedTouches[0].clientX-swipeStartX;swipeStartX=null;if(Math.abs(dx)>60)shiftDay(dx<0?1:-1);}); // ── VIEW CONTROLS ── function setView(v){currentView=v;document.getElementById('week-view').classList.toggle('active',v==='week');document.getElementById('day-view').classList.toggle('active',v==='day');document.getElementById('btn-week').classList.toggle('active',v==='week');document.getElementById('btn-day').classList.toggle('active',v==='day');if(v==='day'){weekOffset=0;dayIndex=new Date().getDay();renderDayView();showSwipeHint();}} function goToday(){weekOffset=0;dayIndex=new Date().getDay();render();if(currentView==='day')renderDayView();} function shiftWeek(dir){weekOffset+=dir;render();} function shiftDay(dir){const dates=getWeekDates(),days=dates.map(d=>d.getDay()),idx=days.indexOf(dayIndex);dayIndex=days[(idx+dir+7)%7];renderDayView();} function currentDayCol(){const dates=getWeekDates(),date=dates.find(d=>d.getDay()===dayIndex)||dates[0];return colForDate(date);} function toggleInbox(){inboxExpanded=!inboxExpanded;document.getElementById('inbox-section').className=inboxExpanded?'expanded':'collapsed';} function openWeekJumper(){const i=document.getElementById('week-jump-input');if(!i.value){const n=new Date();i.value=`${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,'0')}-${String(n.getDate()).padStart(2,'0')}`;}try{i.showPicker();}catch(e){i.click();}} function jumpToWeek(s){if(!s)return;const p=new Date(s+'T00:00:00'),n=new Date(),ts=new Date(n);ts.setDate(n.getDate()-n.getDay());ts.setHours(0,0,0,0);const ps=new Date(p);ps.setDate(p.getDate()-p.getDay());ps.setHours(0,0,0,0);weekOffset=Math.round((ps-ts)/(7*24*60*60*1000));render();} function showSwipeHint(){const h=document.getElementById('swipe-hint');h.classList.add('show');setTimeout(()=>h.classList.remove('show'),2500);} function escHtml(s){return s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');} // ── INIT ── async function init(){ try{await loadAll();}catch(e){console.error('Load error:',e);} render(); document.getElementById('loading').classList.add('hidden'); setTimeout(()=>document.getElementById('loading').remove(),500); } init();