private by design
☀️ My View
your day at a glance — focused, clear, doable
🎯 today's one thing
finish the proposal intro section
your single most important task — everything else is secondary
⚡ next 30 minutes
00:00
Reply to Sarah's email
10 min
Review meeting notes
15 min
Take medication
5 min
how's your brain right now?
❤️ health today
7h
sleep
4.8k
steps
62
HRV
🤖 Good sleep — higher focus today. Schedule deep work now.
Monday
April 26
categories
interactions
● tap to complete
■ drag to reschedule
↕ resize from bottom
🎯 My Goals
track progress across all areas of your life
all
🤝 social
💪 health & wellness
👨‍👩‍👧 family
🌱 personal dev
💰 finances
🙏 spiritual
💼 career
JL
Jamienow
🔒 Can you grab oat milk...
MA
Mom2h
🔒 School pickup Thu?
AK
Alex Kim2d
🔒 Coffee this weekend?
👨‍👩‍👧
Family3d
🔒 Sunday dinner?
JL
Jamie Lambert
🔒 encrypted · partner · online now
today
JL
Hey! Are you stopping anywhere on the way home?
10:08am🔒
Me
Yeah, passing the shops around 4. Need anything?
10:11am✓✓
JL
Can you grab oat milk and bananas? No rush 🙂
10:14am🔒
🔒 end-to-end encrypted — only you and Jamie can read this
🧠 My Brain
brain dump · notes · journal · anything that needs to get out of your head
💭 brain dump
📝 notes
📖 journal
get it out of your head. no structure needed. no judgment here.
so we must wait for DOMContentLoaded) document.addEventListener('DOMContentLoaded', function(){ var overlay = document.getElementById('authOverlay'); if(overlay) overlay.style.display = 'flex'; showSignIn(); }); // ══════════════════════════════════════════ // FIREBASE CONFIGURATION // ══════════════════════════════════════════ var firebaseConfig = { apiKey: "AIzaSyD8qTdahjfeRk4zuhx_hz5AgeZXsb_oGjc", authDomain: "sphere-c1048.firebaseapp.com", projectId: "sphere-c1048", storageBucket: "sphere-c1048.firebasestorage.app", messagingSenderId: "786477313402", appId: "1:786477313402:web:10eee516c09edf93527ac3", measurementId: "G-2DKTJ7P8TK" }; var _fbApp = null; var _fbAuth = null; var _fbDb = null; var _fbUser = null; var _syncTimer = null; function initFirebase(){ try { _fbApp = firebase.initializeApp(firebaseConfig); _fbAuth = firebase.auth(); _fbDb = firebase.firestore(); // Handle Google redirect result (Safari returns here after redirect) _fbAuth.getRedirectResult().catch(function(err){ if(err.code && err.code !== 'auth/no-auth-event'){ console.warn('[Sphere] Redirect sign-in error:', err.message); } }); // Listen for auth state changes _fbAuth.onAuthStateChanged(function(user){ if(user){ _fbUser = user; onUserSignedIn(user); } else { _fbUser = null; onUserSignedOut(); } }); } catch(e){ console.error('[Sphere] Firebase init failed:', e); } } // ── AUTH STATE HANDLERS ── function onUserSignedIn(user){ // Hide auth overlay, show app var overlay = document.getElementById('authOverlay'); if(overlay) overlay.style.display = 'none'; // Show feedback button var fab = document.getElementById('feedbackFab'); if(fab) fab.style.display = 'flex'; // Update user profile with Firebase display name if(user.displayName && !userProfile.name){ userProfile.name = user.displayName.split(' ')[0]; } if(user.email){ userProfile.email = user.email; } // Load user data from Firestore loadFromFirestore(user.uid); } function onUserSignedOut(){ // Show auth overlay showSignIn(); var overlay = document.getElementById('authOverlay'); if(overlay) overlay.style.display = 'flex'; // Hide feedback button var fab = document.getElementById('feedbackFab'); if(fab) fab.style.display = 'none'; } // ── SIGN IN / SIGN UP ── function showSignIn(){ var si = document.getElementById('authSignIn'); var su = document.getElementById('authSignUp'); var sf = document.getElementById('authForgot'); var sl = document.getElementById('authLoading'); if(si) si.style.display = 'block'; if(su) su.style.display = 'none'; if(sf) sf.style.display = 'none'; if(sl) sl.style.display = 'none'; } function showSignUp(){ var si = document.getElementById('authSignIn'); var su = document.getElementById('authSignUp'); var sf = document.getElementById('authForgot'); if(si) si.style.display = 'none'; if(su) su.style.display = 'block'; if(sf) sf.style.display = 'none'; } function showForgotPassword(){ var si = document.getElementById('authSignIn'); var sf = document.getElementById('authForgot'); if(si) si.style.display = 'none'; if(sf) sf.style.display = 'block'; } function showAuthLoading(){ var si = document.getElementById('authSignIn'); var su = document.getElementById('authSignUp'); var sf = document.getElementById('authForgot'); var sl = document.getElementById('authLoading'); if(si) si.style.display = 'none'; if(su) su.style.display = 'none'; if(sf) sf.style.display = 'none'; if(sl) sl.style.display = 'block'; } function showAuthError(id, msg){ var el = document.getElementById(id); if(el){ el.textContent = msg; el.style.display = 'block'; } } function clearAuthErrors(){ ['signInError','signUpError','forgotError'].forEach(function(id){ var el = document.getElementById(id); if(el){ el.textContent = ''; el.style.display = 'none'; } }); } function signInWithEmail(){ clearAuthErrors(); var email = (document.getElementById('signInEmail') || {}).value || ''; var pass = (document.getElementById('signInPassword') || {}).value || ''; if(!email || !pass){ showAuthError('signInError','Please enter your email and password.'); return; } showAuthLoading(); _fbAuth.signInWithEmailAndPassword(email, pass) .catch(function(err){ showSignIn(); var msg = err.code === 'auth/user-not-found' ? 'No account found with this email.' : err.code === 'auth/wrong-password' ? 'Incorrect password.' : err.code === 'auth/invalid-email' ? 'Invalid email address.' : err.message; showAuthError('signInError', msg); }); } function signUpWithEmail(){ clearAuthErrors(); var name = (document.getElementById('signUpName') || {}).value || ''; var email = (document.getElementById('signUpEmail') || {}).value || ''; var pass = (document.getElementById('signUpPassword') || {}).value || ''; if(!name) { showAuthError('signUpError','Please enter your name.'); return; } if(!email) { showAuthError('signUpError','Please enter your email.'); return; } if(pass.length < 6){ showAuthError('signUpError','Password must be at least 6 characters.'); return; } showAuthLoading(); _fbAuth.createUserWithEmailAndPassword(email, pass) .then(function(cred){ return cred.user.updateProfile({ displayName: name }); }) .catch(function(err){ showSignUp(); var msg = err.code === 'auth/email-already-in-use' ? 'An account with this email already exists.' : err.code === 'auth/invalid-email' ? 'Invalid email address.' : err.message; showAuthError('signUpError', msg); }); } function signInWithGoogle(){ clearAuthErrors(); showAuthLoading(); var provider = new firebase.auth.GoogleAuthProvider(); // Use redirect instead of popup — works correctly in Safari on iPhone _fbAuth.signInWithRedirect(provider) .catch(function(err){ showSignIn(); showAuthError('signInError', err.message); }); } function sendPasswordReset(){ clearAuthErrors(); var email = (document.getElementById('forgotEmail') || {}).value || ''; if(!email){ showAuthError('forgotError','Please enter your email.'); return; } _fbAuth.sendPasswordResetEmail(email) .then(function(){ var el = document.getElementById('authForgot'); if(el) el.innerHTML = '
' + '
📧
' + '
Check your email
' + '
We sent a password reset link to ' + email + '
' + '
'; }) .catch(function(err){ showAuthError('forgotError', err.message); }); } function signOut(){ if(_fbAuth) _fbAuth.signOut(); } // ── FIRESTORE SYNC ── function loadFromFirestore(uid){ if(!_fbDb) return; showSyncBanner('☁️ Loading your data...'); _fbDb.collection('users').doc(uid).get() .then(function(doc){ if(doc.exists){ var data = doc.data(); if(data.schedTasks && data.schedTasks.length > 1) schedTasks = data.schedTasks; if(data.goalsData && data.goalsData.length > 0) goalsData = data.goalsData; if(data.prioritySettings) prioritySettings = data.prioritySettings; if(data.userProfile) userProfile = data.userProfile; if(data.appSettings) appSettings = data.appSettings; // Re-render with loaded data applyDarkMode(); applyProfile(); renderGoals(); ensureReviewTask(); renderWidgetArea(); refreshMyView(); hideSyncBanner(); toast('☁️ Data loaded'); } else { // New user — save demo data to Firestore saveToFirestore(); hideSyncBanner(); } }) .catch(function(err){ console.warn('[Sphere] Firestore load failed:', err); hideSyncBanner(); }); } function saveToFirestore(){ if(!_fbDb || !_fbUser) return; clearTimeout(_syncTimer); _syncTimer = setTimeout(function(){ var data = { schedTasks: schedTasks, goalsData: goalsData, prioritySettings: prioritySettings, userProfile: userProfile, appSettings: appSettings, savedAt: firebase.firestore.FieldValue.serverTimestamp() }; _fbDb.collection('users').doc(_fbUser.uid).set(data) .then(function(){ var lbl = document.getElementById('lastSavedLabel'); if(lbl) lbl.textContent = 'Last saved: just now ☁️'; }) .catch(function(err){ console.warn('[Sphere] Firestore save failed:', err); }); }, 1000); // debounce 1s } // Override scheduleSave to also sync to Firestore var _origScheduleSave = scheduleSave; scheduleSave = function(){ _origScheduleSave(); saveToFirestore(); }; function showSyncBanner(msg){ var banner = document.getElementById('syncBanner'); var msgEl = document.getElementById('syncBannerMsg'); if(banner && msgEl){ msgEl.textContent = msg; banner.style.display = 'flex'; } } function hideSyncBanner(){ setTimeout(function(){ var banner = document.getElementById('syncBanner'); if(banner) banner.style.display = 'none'; }, 1500); } // ══════════════════════════════════════════ // IN-APP FEEDBACK // ══════════════════════════════════════════ var _feedbackRating = 0; var _feedbackTags = []; var FEEDBACK_EMAIL = 'Osborn.Joshua36@gmail.com'; // Init EmailJS (free tier — 200 emails/month) // Sign up at emailjs.com and replace these IDs var EMAILJS_SERVICE = 'service_nbeqhpb'; var EMAILJS_TEMPLATE = ''; // not needed — using mailto fallback var EMAILJS_KEY = 'L3mGqDexA5tAOQvnp'; function openFeedback(){ _feedbackRating = 0; _feedbackTags = []; document.querySelectorAll('.feedback-star').forEach(function(s){ s.classList.remove('active'); }); document.querySelectorAll('.feedback-tag').forEach(function(t){ t.classList.remove('active'); }); var txt = document.getElementById('feedbackText'); if(txt) txt.value = ''; var overlay = document.getElementById('feedbackOverlay'); if(overlay) overlay.style.display = 'flex'; } function closeFeedback(){ var overlay = document.getElementById('feedbackOverlay'); if(overlay) overlay.style.display = 'none'; } function setFeedbackStar(n){ _feedbackRating = n; document.querySelectorAll('.feedback-star').forEach(function(s, i){ s.classList.toggle('active', i < n); }); } function toggleFeedbackTag(el, tag){ el.classList.toggle('active'); var idx = _feedbackTags.indexOf(tag); if(idx >= 0) _feedbackTags.splice(idx,1); else _feedbackTags.push(tag); } function submitFeedback(){ var text = (document.getElementById('feedbackText') || {}).value || ''; if(!text.trim() && _feedbackRating === 0){ toast('Please add a rating or message before sending'); return; } var btn = document.getElementById('feedbackSubmitBtn'); if(btn){ btn.textContent = 'Sending...'; btn.disabled = true; } var userName = userProfile.name || 'Anonymous'; var userEmail = (_fbUser && _fbUser.email) || 'Not signed in'; var stars = _feedbackRating > 0 ? '⭐'.repeat(_feedbackRating) + ' (' + _feedbackRating + '/5)' : 'Not rated'; var tags = _feedbackTags.length ? _feedbackTags.join(', ') : 'None'; var now = new Date().toLocaleString('en-US', { dateStyle:'medium', timeStyle:'short' }); // Save to Firestore (permanent record — readable at console.firebase.google.com) if(_fbDb){ _fbDb.collection('feedback').add({ uid: _fbUser ? _fbUser.uid : 'anonymous', name: userName, email: userEmail, rating: _feedbackRating, tags: _feedbackTags, message: text, timestamp: firebase.firestore.FieldValue.serverTimestamp() }); } // Open pre-filled email in their mail app var subject = encodeURIComponent('Sphere Feedback — ' + stars + (tags !== 'None' ? ' — ' + tags : '')); var body = encodeURIComponent( 'From: ' + userName + ' (' + userEmail + ')\n' + 'Rating: ' + stars + '\n' + (tags !== 'None' ? 'Tags: ' + tags + '\n' : '') + 'Date: ' + now + '\n\n' + text ); window.open('mailto:' + FEEDBACK_EMAIL + '?subject=' + subject + '&body=' + body); closeFeedback(); if(btn){ btn.textContent = 'Send feedback'; btn.disabled = false; } toast('💙 Thanks for the feedback! It goes directly to the founder.'); } // ── INIT ── // Boot Firebase when the page loads initFirebase(); var convData={ jamie:{name:'Jamie Lambert',av:'av1',init:'JL',status:'partner · online now'}, mom:{name:'Mom',av:'av2',init:'MA',status:'family · online now'}, alex:{name:'Alex Kim',av:'av4',init:'AK',status:'friend · last seen 2d ago'}, family:{name:'Family',av:'av5',init:'👨‍👩‍👧',status:'group · 4 members'} }; var replies={ jamie:['Got it! 🌿','Sounds good 😊','On it!','Perfect, will do!'], mom:['Of course sweetheart 💙','No problem at all!','Let me know!'], alex:['Yes! Saturday works!','Sounds great!','Let me check...'], family:["We're in! 🎉",'Sunday works!','See you all then!'] }; var currentConv='jamie'; function openConv(name){ currentConv=name; const d=convData[name]; document.getElementById('chatName').textContent=d.name; document.getElementById('chatEncName').textContent=d.name.split(' ')[0]; document.querySelectorAll('#convList .conv-row, .circle-contacts .contact-row').forEach(r=>r.classList.remove('active')); document.getElementById('msgInput').placeholder=`message ${d.name.split(' ')[0]}...`; } // ══════════════════════════════════════════ // MY CIRCLE — TASK DETECTION ENGINE // ══════════════════════════════════════════ var _detectedTasks = []; function detectTasksInMessage(text, senderName){ if(!text || !text.trim()) return []; var found = []; var lc = text.toLowerCase(); // ── 1. Grocery / item scan ── var groceryRe = /\b(banana|apple|milk|egg|bread|butter|cheese|yogh?urt|juice|coffee|tea|sugar|flour|rice|pasta|diaper|nappie|formula|wipe|soap|shampoo|toothpaste|toilet paper|paper towel|detergent|oat milk|almond milk|soy milk|avocado|tomato|onion|potato|carrot|spinach|lettuce|chicken|beef|fish|salmon|tuna|orange|strawberr|blueberr|mango|grape|olive oil|cooking oil|cereal|crackers?|chips?|chocolate|ice cream|sparkling water|sparkling)\w*/gi; var gm = text.match(groceryRe); if(gm){ var unique = [], seen = {}; gm.forEach(function(m){ var k=m.toLowerCase(); if(!seen[k]){seen[k]=true;unique.push(m);} }); found.push({ id:'dt'+Date.now()+Math.random().toString(36).slice(2,5), name:'Pick up: '+unique.join(', '), cat:'home', checked:true, source:senderName||'message', items:unique }); } // ── 2. X needs Y ── var needsM = text.match(/\b([A-Z][a-z]+)\s+needs?\s+([^.!?\n]+)/); if(needsM){ var who=needsM[1], what=needsM[2].replace(/\s*(please|pls|thanks?)\.?$/i,'').trim(); if(what && what.length>1 && !found.some(function(f){return f.name.toLowerCase().includes(what.toLowerCase().slice(0,6));})){ found.push({ id:'dt'+Date.now()+Math.random().toString(36).slice(2,5), name:'Get '+what+' for '+who, cat:'home', checked:true, source:senderName||'message' }); } } // ── 3. Don't forget / remember to ── var remM = text.match(/don'?t forget\s+(?:to\s+)?(.+?)(?:[.!?]|$)/i) || text.match(/remember\s+(?:to\s+)?(.+?)(?:[.!?]|$)/i); if(remM){ var item=remM[1].replace(/\s*(please|pls|thanks?)\.?$/i,'').trim(); if(item && item.length>2 && !found.some(function(f){return f.name.toLowerCase().includes(item.toLowerCase().slice(0,8));})){ found.push({ id:'dt'+Date.now()+Math.random().toString(36).slice(2,5), name:item.charAt(0).toUpperCase()+item.slice(1), cat:actionCat('do',item), checked:true, source:senderName||'message' }); } } // ── 4. Can you call/email/book/schedule/order X ── var actM = text.match(/can you\s+(call|email|text|book|schedule|arrange|order|check|pick up|grab|get)\s+(.+?)(?:[.!?]|$)/i); if(actM && !gm){ var verb=actM[1], obj=actM[2].replace(/\s*(please|pls|thanks?|no rush|if you can)\.?$/i,'').trim(); if(obj && obj.length>1){ found.push({ id:'dt'+Date.now()+Math.random().toString(36).slice(2,5), name:verb.charAt(0).toUpperCase()+verb.slice(1)+' '+obj, cat:actionCat(verb,obj), checked:true, source:senderName||'message' }); } } return found; } function actionCat(verb, obj){ var t = (verb+' '+obj).toLowerCase(); if(/call|email|text|message/.test(t)) return 'social'; if(/doctor|dentist|appointment|therapy|prescription|pharmacy/.test(t)) return 'health'; if(/pay|bill|invoice|bank|money/.test(t)) return 'finances'; return 'home'; } function showTaskDetectBanner(tasks, senderName){ if(!tasks || tasks.length===0) return; tasks.forEach(function(t){ var dup=_detectedTasks.some(function(d){return d.name.toLowerCase().slice(0,10)===t.name.toLowerCase().slice(0,10);}); if(!dup) _detectedTasks.push(t); }); renderDetectBanner(senderName); } // ── SMART TASK MATCHING ── function findSimilarTasks(detectedName){ var q = detectedName.toLowerCase(); var groceryKw = /\b(groceri|grocery|store|shop|pick up|buy|banana|milk|egg|bread|oat milk|produce|market|item)\b/i; var errandKw = /\b(errand|pick up|drop off|return|post office|pharmacy|dry clean)\b/i; var results = []; function scoreText(text, weight){ var t = text.toLowerCase(), score = 0; q.split(/\s+/).forEach(function(w){ if(w.length > 2 && t.includes(w)) score += weight * 2; }); if(groceryKw.test(q) && groceryKw.test(t)) score += weight * 5; if(errandKw.test(q) && errandKw.test(t)) score += weight * 3; if(q.length > 5 && t.includes(q.slice(0,6))) score += weight * 3; return score; } schedTasks.forEach(function(t){ if(t._isReviewTask || t.done) return; var s = scoreText(t.title, 1); if(s > 0) results.push({type:'sched', item:t, score:s, reason:'Schedule task \u00b7 ' + minToTime(t.startMin)}); }); goalsData.forEach(function(g){ var s = scoreText(g.title, 1); g.subtasks.forEach(function(sub){ s += scoreText(sub.name, 0.4); }); if(s > 0) results.push({type:'goal', item:g, score:s, reason:'Goal \u00b7 ' + g.subtasks.length + ' existing steps'}); }); return results.sort(function(a,b){ return b.score - a.score; }); } // ── RENDER DETECT BANNER ── // ══════════════════════════════════════════ // MY CIRCLE — TASK DETECTION FLOW // ══════════════════════════════════════════ // ── RENDER DETECT BANNER ── // Shows each detected item with a guided Yes/No decision then task vs subtask choice function renderDetectBanner(senderName){ var banner = document.getElementById('taskDetectBanner'); var list = document.getElementById('detectedTaskList'); var sub = document.getElementById('taskDetectSub'); if(!banner || !list) return; if(_detectedTasks.length === 0){ banner.style.display = 'none'; return; } if(sub) sub.textContent = _detectedTasks.length + ' possible task' + (_detectedTasks.length > 1 ? 's' : '') + ' spotted' + (senderName ? ' from ' + senderName : ''); list.innerHTML = ''; _detectedTasks.forEach(function(task){ var row = document.createElement('div'); row.className = 'detected-task-item'; row.style.cssText = 'flex-direction:column;align-items:stretch;gap:0;padding:10px 12px;'; // ── Item name ── var nameRow = document.createElement('div'); nameRow.style.cssText = 'display:flex;align-items:center;gap:8px;margin-bottom:8px;'; var bullet = document.createElement('div'); bullet.style.cssText = 'width:7px;height:7px;border-radius:50%;background:var(--plum);flex-shrink:0;'; var nm = document.createElement('div'); nm.style.cssText = 'font-size:12px;font-weight:500;color:var(--txt);flex:1;'; nm.textContent = task.name; var rm = document.createElement('button'); rm.className = 'dt-remove'; rm.textContent = '\u00d7'; rm.title = 'ignore'; rm.onclick = (function(tid){ return function(){ _detectedTasks = _detectedTasks.filter(function(t){ return t.id !== tid; }); renderDetectBanner(); }; })(task.id); nameRow.appendChild(bullet); nameRow.appendChild(nm); nameRow.appendChild(rm); row.appendChild(nameRow); // ── Step 1: Will you action this? ── if(!task._decision){ var q1 = document.createElement('div'); q1.style.cssText = 'font-size:10px;color:var(--hint);margin-bottom:6px;margin-left:15px;'; q1.textContent = 'Will you action this?'; row.appendChild(q1); var btnRow = document.createElement('div'); btnRow.style.cssText = 'display:flex;gap:6px;margin-left:15px;'; var yesBtn = makeDecisionBtn('\u2714 yes, I\'ll do it', 'var(--plum)', '#fff'); yesBtn.onclick = function(){ task._decision = 'yes'; renderDetectBanner(senderName); }; var noBtn = makeDecisionBtn('\u2715 not right now', 'var(--cream-mid)', 'var(--muted)'); noBtn.onclick = function(){ _detectedTasks = _detectedTasks.filter(function(t){ return t.id !== task.id; }); renderDetectBanner(senderName); }; btnRow.appendChild(yesBtn); btnRow.appendChild(noBtn); row.appendChild(btnRow); // ── Step 2: Task or Subtask? ── } else if(task._decision === 'yes' && !task._taskType){ // Show smart match suggestion if one exists var matches = findSimilarTasks(task.name); var best = matches.length > 0 ? matches[0] : null; if(best){ var suggestion = document.createElement('div'); suggestion.style.cssText = 'background:rgba(91,170,126,.1);border:1px solid rgba(91,170,126,.25);border-radius:8px;padding:7px 10px;margin-left:15px;margin-bottom:7px;display:flex;align-items:center;gap:8px;'; var suggIcon = document.createElement('div'); suggIcon.textContent = '\uD83D\uDD17'; suggIcon.style.fontSize='13px'; var suggText = document.createElement('div'); suggText.style.cssText='flex:1;font-size:10px;color:var(--sage-d);line-height:1.5;'; suggText.innerHTML = 'Suggested: add as item under ' + escHtml(best.item.title) + ''; if(best.type === 'sched' && best.item.subtasks){ suggText.innerHTML += '
' + best.item.subtasks.length + ' item' + (best.item.subtasks.length!==1?'s':'') + ' already listed'; } var suggBtn = document.createElement('button'); suggBtn.style.cssText = 'background:var(--sage);color:#fff;border:none;border-radius:7px;padding:4px 10px;font-size:10px;font-weight:600;cursor:pointer;font-family:\'DM Sans\',sans-serif;white-space:nowrap;'; suggBtn.textContent = 'use this'; suggBtn.onclick = (function(t, m){ return function(){ addItemToTask(t, m); }; })(task, best); suggestion.appendChild(suggIcon); suggestion.appendChild(suggText); suggestion.appendChild(suggBtn); row.appendChild(suggestion); } var q2 = document.createElement('div'); q2.style.cssText = 'font-size:10px;color:var(--hint);margin-bottom:6px;margin-left:15px;'; q2.textContent = best ? 'Or choose another option:' : 'How do you want to add this?'; row.appendChild(q2); var btnRow2 = document.createElement('div'); btnRow2.style.cssText = 'display:flex;gap:6px;margin-left:15px;flex-wrap:wrap;'; var subBtn = makeDecisionBtn('\uFF0B subtask of existing', 'var(--sky-l)', 'var(--sky-d)'); subBtn.onclick = function(){ task._taskType = 'subtask'; renderDetectBanner(senderName); }; var newBtn = makeDecisionBtn('\u25A1 new task', 'var(--navy)', '#fff'); newBtn.onclick = function(){ task._taskType = 'new'; renderDetectBanner(senderName); }; btnRow2.appendChild(subBtn); btnRow2.appendChild(newBtn); row.appendChild(btnRow2); // ── Step 3a: Subtask — pick which task to attach to ── } else if(task._taskType === 'subtask'){ var matches2 = findSimilarTasks(task.name); var q3 = document.createElement('div'); q3.style.cssText = 'font-size:10px;color:var(--hint);margin-bottom:6px;margin-left:15px;'; q3.textContent = 'Add as a subtask of:'; row.appendChild(q3); var matchList = document.createElement('div'); matchList.style.cssText = 'display:flex;flex-direction:column;gap:4px;margin-left:15px;'; // Show top matches var shown = matches2.slice(0, 4); shown.forEach(function(match){ var mRow = document.createElement('div'); mRow.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 9px;border:1.5px solid var(--bdr);border-radius:9px;cursor:pointer;transition:all .14s;background:var(--cream-lt);'; mRow.onmouseenter = function(){ mRow.style.borderColor='var(--navy)'; mRow.style.background='#F0F4FF'; }; mRow.onmouseleave = function(){ mRow.style.borderColor='var(--bdr)'; mRow.style.background='var(--cream-lt)'; }; var mIcon = document.createElement('div'); if(match.type==='sched'){ var c=CAT_COLORS[match.item.cat]||CAT_COLORS.other; mIcon.textContent=c.icon; } else { mIcon.textContent=match.item.icon||'\uD83C\uDFAF'; } mIcon.style.fontSize='14px'; var mName = document.createElement('div'); mName.style.cssText='flex:1;font-size:11px;font-weight:500;color:var(--txt);'; mName.textContent = match.item.title; var mMeta = document.createElement('div'); mMeta.style.cssText='font-size:9px;color:var(--hint);'; mMeta.textContent = match.type==='sched' ? minToTime(match.item.startMin) : 'goal'; mRow.appendChild(mIcon); mRow.appendChild(mName); mRow.appendChild(mMeta); mRow.onclick = (function(m){ return function(){ addItemToTask(task, m); }; })(match); matchList.appendChild(mRow); }); // "other task" option if no matches or user wants to pick var otherRow = document.createElement('div'); otherRow.style.cssText='display:flex;align-items:center;gap:8px;padding:6px 9px;border:1.5px dashed var(--bdr-md);border-radius:9px;cursor:pointer;'; otherRow.innerHTML='
+
choose a different task\u2026
'; otherRow.onclick = (function(t){ return function(){ openTaskMatchDrawer(t); }; })(task); matchList.appendChild(otherRow); row.appendChild(matchList); // ── Step 3b: New task — type selector ── } else if(task._taskType === 'new'){ var q4 = document.createElement('div'); q4.style.cssText = 'font-size:10px;color:var(--hint);margin-bottom:6px;margin-left:15px;'; q4.textContent = 'What kind of task is it?'; row.appendChild(q4); var typeRow = document.createElement('div'); typeRow.style.cssText='display:flex;gap:6px;margin-left:15px;flex-wrap:wrap;'; var types = [ {id:'recurring', label:'\u21BB recurring', desc:'Repeats on a schedule'}, {id:'timebound', label:'\u23F0 deadline', desc:'Has a due date'}, {id:'adhoc', label:'\u2736 ad hoc', desc:'One-off, flexible timing'}, ]; types.forEach(function(tp){ var tBtn = makeDecisionBtn(tp.label, tp.id==='adhoc'?'var(--cream-mid)':tp.id==='timebound'?'var(--rose-l)':'var(--sage-l)', tp.id==='adhoc'?'var(--muted)':tp.id==='timebound'?'var(--rose-d)':'var(--sage-d)'); tBtn.title = tp.desc; tBtn.onclick = (function(tpId){ return function(){ createNewTaskFromDetected(task, tpId); }; })(tp.id); typeRow.appendChild(tBtn); }); var backBtn = makeDecisionBtn('\u2190 back', 'transparent', 'var(--hint)'); backBtn.style.borderColor='var(--bdr)'; backBtn.onclick = function(){ task._taskType = null; renderDetectBanner(senderName); }; typeRow.appendChild(backBtn); row.appendChild(typeRow); } list.appendChild(row); }); banner.style.display = 'block'; } // Small helper for consistent decision button styling function makeDecisionBtn(label, bg, color){ var btn = document.createElement('button'); btn.style.cssText='padding:5px 12px;border-radius:20px;border:1.5px solid transparent;background:'+bg+';color:'+color+';font-size:10px;font-weight:600;cursor:pointer;font-family:\'DM Sans\',sans-serif;white-space:nowrap;transition:all .14s;'; btn.textContent = label; btn.onmouseenter = function(){ btn.style.opacity='.8'; }; btn.onmouseleave = function(){ btn.style.opacity='1'; }; return btn; } // ── CREATE NEW TASK FROM DETECTED ITEM ── function createNewTaskFromDetected(detectedTask, taskType){ var nowMin = new Date().getHours()*60 + new Date().getMinutes(); var cursor = snapToGrid(nowMin + 30); while(hasConflict(cursor, 30, null) && cursor < DAY_END*60) cursor += 5; var items = detectedTask.items && detectedTask.items.length > 0 ? detectedTask.items : [detectedTask.name]; var newTask = { id: ++nextTaskId, title: detectedTask.name, startMin: cursor, durMin: 30, cat: 'home', done: false, recurring: taskType === 'recurring', recurType: taskType === 'recurring' ? 'daily' : '', missed: false, taskType: taskType, deadline: null, subtasks: [], }; // If there are sub-items (e.g. bananas, milk from a grocery detection), add them as subtasks if(items.length > 1){ items.forEach(function(item){ newTask.subtasks.push({ id: 's'+nextSubId++, name: item, done: false, taskType:'adhoc', recurFreq:'', deadline:null }); }); } schedTasks.push(newTask); _detectedTasks = _detectedTasks.filter(function(t){ return t.id !== detectedTask.id; }); renderDetectBanner(); renderTimeline(); var typeLabel = {recurring:'Recurring',timebound:'Time-bound',adhoc:'Ad hoc'}[taskType] || ''; toast('\uD83D\uDCC5 ' + typeLabel + ' task \u201c' + detectedTask.name + '\u201d added at ' + minToTime(cursor)); } // ── ADD ITEM TO EXISTING TASK AS SUBTASK ── function addItemToTask(detectedTask, match){ var items = detectedTask.items && detectedTask.items.length > 0 ? detectedTask.items : [detectedTask.name]; var added = 0; if(match.type === 'sched'){ var t = match.item; if(!t.subtasks) t.subtasks = []; items.forEach(function(item){ if(!t.subtasks.some(function(s){ return s.name.toLowerCase()===item.toLowerCase(); })){ t.subtasks.push({id:'s'+nextSubId++, name:item, done:false, taskType:'adhoc', recurFreq:'', deadline:null}); added++; } }); _detectedTasks = _detectedTasks.filter(function(d){ return d.id !== detectedTask.id; }); renderDetectBanner(); renderTimeline(); toast('\u2705 ' + items.length + ' item' + (items.length>1?'s':'') + ' added to \u201c' + t.title + '\u201d'); } else { var g = match.item; items.forEach(function(item){ if(!g.subtasks.some(function(s){ return s.name.toLowerCase()===item.toLowerCase(); })){ g.subtasks.push({id:'s'+nextSubId++, name:item, done:false, taskType:'adhoc', recurFreq:'', deadline:null, meta:'from My Circle'}); added++; } }); _detectedTasks = _detectedTasks.filter(function(d){ return d.id !== detectedTask.id; }); renderDetectBanner(); renderGoals(); toast('\u2705 ' + items.length + ' item' + (items.length>1?'s':'') + ' added to \u201c' + g.title + '\u201d'); } } // ── TASK MATCH DRAWER (full chooser when user wants to browse all options) ── function openTaskMatchDrawer(detectedTask){ var ex=document.getElementById('taskMatchDrawer'); if(ex) ex.remove(); var es=document.getElementById('taskMatchScrim'); if(es) es.remove(); var scrim=document.createElement('div'); scrim.id='taskMatchScrim'; scrim.style.cssText='position:fixed;inset:0;background:rgba(30,50,69,.45);z-index:810;'; scrim.onclick=closeTaskMatchDrawer; document.body.appendChild(scrim); var wrap=document.createElement('div'); wrap.id='taskMatchDrawer'; wrap.style.cssText='position:fixed;bottom:0;left:0;right:0;z-index:811;display:flex;justify-content:center;'; document.body.appendChild(wrap); var drawer=document.createElement('div'); drawer.style.cssText='background:#fff;border-radius:20px 20px 0 0;box-shadow:0 -8px 40px rgba(30,50,69,.22);padding:20px 22px 36px;width:100%;max-width:540px;max-height:80vh;overflow-y:auto;animation:slideUp .22s ease;'; wrap.appendChild(drawer); var handle=document.createElement('div'); handle.style.cssText='width:36px;height:4px;border-radius:2px;background:#d0dae6;margin:0 auto 16px;'; drawer.appendChild(handle); var closeBtn=document.createElement('button'); closeBtn.textContent='\u00d7'; closeBtn.style.cssText='position:absolute;top:16px;right:18px;width:30px;height:30px;border-radius:50%;border:none;background:#eef3f8;color:#4a6580;font-size:18px;cursor:pointer;'; closeBtn.onclick=closeTaskMatchDrawer; drawer.appendChild(closeBtn); var hdr=document.createElement('div'); hdr.innerHTML='
Add as subtask of\u2026
'+ '
Item: '+escHtml(detectedTask.name)+'
'; drawer.appendChild(hdr); // Schedule tasks var schedTitle=document.createElement('div'); schedTitle.style.cssText='font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--hint);margin-bottom:8px;'; schedTitle.textContent='My Schedule tasks'; drawer.appendChild(schedTitle); schedTasks.filter(function(t){return !t._isReviewTask&&!t.done;}).forEach(function(t){ var colors=CAT_COLORS[t.cat]||CAT_COLORS.other; drawer.appendChild(makeMatchCard(colors.icon, colors.bg, t.title, minToTime(t.startMin) + (t.subtasks&&t.subtasks.length?' \u00b7 '+t.subtasks.length+' items':''), function(){ closeTaskMatchDrawer(); addItemToTask(detectedTask,{type:'sched',item:t}); })); }); // Goals var goalTitle=document.createElement('div'); goalTitle.style.cssText='font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--hint);margin:12px 0 8px;'; goalTitle.textContent='My Goals'; drawer.appendChild(goalTitle); goalsData.forEach(function(g){ drawer.appendChild(makeMatchCard(g.icon||'\uD83C\uDFAF', g.iconBg||'var(--cream-mid)', g.title, 'goal \u00b7 '+g.subtasks.length+' steps', function(){ closeTaskMatchDrawer(); addItemToTask(detectedTask,{type:'goal',item:g}); })); }); } function makeMatchCard(icon, iconBg, title, meta, onclick){ var card=document.createElement('div'); card.style.cssText='display:flex;align-items:center;gap:11px;padding:10px 12px;border:1.5px solid var(--bdr);border-radius:11px;cursor:pointer;margin-bottom:6px;transition:all .14s;background:var(--cream-lt);'; card.onmouseenter=function(){card.style.borderColor='var(--navy)';card.style.background='#F0F4FF';}; card.onmouseleave=function(){card.style.borderColor='var(--bdr)';card.style.background='var(--cream-lt)';}; var ic=document.createElement('div'); ic.style.cssText='width:34px;height:34px;border-radius:8px;background:'+iconBg+';display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;'; ic.textContent=icon; var body=document.createElement('div'); body.style.flex='1'; var nm=document.createElement('div'); nm.style.cssText='font-size:12px;font-weight:500;color:var(--txt);'; nm.textContent=title; var mt=document.createElement('div'); mt.style.cssText='font-size:10px;color:var(--hint);margin-top:1px;'; mt.textContent=meta; body.appendChild(nm); body.appendChild(mt); var arr=document.createElement('div'); arr.style.cssText='font-size:16px;color:var(--hint);'; arr.textContent='\u203A'; card.appendChild(ic); card.appendChild(body); card.appendChild(arr); card.onclick=onclick; return card; } function closeTaskMatchDrawer(){ var d=document.getElementById('taskMatchDrawer'); if(d) d.remove(); var s=document.getElementById('taskMatchScrim'); if(s) s.remove(); } function dismissTaskDetect(){ _detectedTasks=[]; var b=document.getElementById('taskDetectBanner'); if(b) b.style.display='none'; } function addAllDetectedTasks(){ // Open the full match drawer for the first uncategorised item var first = _detectedTasks.find(function(t){ return !t._decision; }) || _detectedTasks[0]; if(!first){ toast('No items to action'); return; } // Start the guided flow by marking as yes first._decision = 'yes'; renderDetectBanner(); } function addDetectedToSchedule(){ // Quick-add all checked items to schedule as adhoc tasks var toAdd = _detectedTasks.filter(function(t){ return t.checked; }); if(toAdd.length===0){ toast('Select at least one item first'); return; } var nowMin = new Date().getHours()*60+new Date().getMinutes(); var cursor = snapToGrid(nowMin+30); toAdd.forEach(function(t){ while(hasConflict(cursor,30,null)&&cursor1?'s':'')+' added to schedule'); } function sendMsg(){ var inp=document.getElementById('msgInput'); var txt=inp.value.trim(); if(!txt) return; var area=document.getElementById('msgArea'); var t=new Date().toLocaleTimeString('en-US',{hour:'numeric',minute:'2-digit'}); var row=document.createElement('div'); row.className='mrow mine'; var av=document.createElement('div'); av.style.cssText='background:var(--navy);color:#fff;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:500;flex-shrink:0;'; av.textContent='Me'; var bub=document.createElement('div'); bub.className='bubble mine'; bub.innerHTML=txt+'
'+t+'\u2713\u2713
'; row.appendChild(av); row.appendChild(bub); area.appendChild(row); inp.value=''; area.scrollTop=area.scrollHeight; // Scan sent message for self-reminders var myTasks=detectTasksInMessage(txt,'your message'); if(myTasks.length>0) showTaskDetectBanner(myTasks,'your message'); // Simulate reply setTimeout(function(){ var pool=replies[currentConv]||replies.jamie; var d=convData[currentConv]; var r2=document.createElement('div'); r2.className='mrow'; var av2=document.createElement('div'); av2.className='mav-s '+d.av; av2.style.cssText='width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:500;flex-shrink:0;'; av2.textContent=d.init; var reply=pool[Math.floor(Math.random()*pool.length)]; var b2=document.createElement('div'); b2.className='bubble theirs'; b2.innerHTML=reply+'
'+t+'\uD83D\uDD12
'; r2.appendChild(av2); r2.appendChild(b2); area.appendChild(r2); area.scrollTop=area.scrollHeight; // Scan incoming reply for tasks var inTasks=detectTasksInMessage(reply,d.name.split(' ')[0]); if(inTasks.length>0) showTaskDetectBanner(inTasks,d.name.split(' ')[0]); },1100); } function handleMsgKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMsg();}} // Auto-scan initial conversation messages on load (function(){ var initialMsgs=[ {text:"Hey! Are you stopping anywhere on the way home?",sender:"Jamie"}, {text:"Can you grab oat milk and bananas? No rush if not",sender:"Jamie"}, ]; var initTasks=[]; initialMsgs.forEach(function(m){ detectTasksInMessage(m.text,m.sender).forEach(function(t){initTasks.push(t);}); }); if(initTasks.length>0){ setTimeout(function(){ showTaskDetectBanner(initTasks,'Jamie'); },800); } })(); // ────────────────────────────────────────── // scanExistingMessages — uses the new detection system function scanExistingMessages(){ var initialMsgs=[ {text:"Can you grab oat milk and bananas? No rush if not",sender:"Jamie"}, ]; var initTasks=[]; initialMsgs.forEach(function(m){ detectTasksInMessage(m.text,m.sender).forEach(function(t){initTasks.push(t);}); }); if(initTasks.length>0){ setTimeout(function(){ showTaskDetectBanner(initTasks,'Jamie'); },800); } } // ── BRAIN ── var journalPrompts = [ "What's one thing that went better than expected today?", "What's weighing on you right now, and what's one tiny step forward?", "Describe a moment from the past week where you felt like yourself.", "What would you tell a friend who was going through exactly what you're going through?", "What are you avoiding, and what might happen if you just started it?", "What did your ADHD brain do today that you're secretly proud of?", "What does rest look like for you — and are you getting enough of it?", "Name three people who make your life better and why.", "What pattern do you keep noticing in yourself lately?", "If tomorrow could be different from today, what's one thing you'd change?" ]; var selectedJournalMood = ''; function setBrainView(view){ document.getElementById('brain-dump-section').style.display = view==='dump' ? 'block' : 'none'; document.getElementById('brain-notes-section').style.display = view==='notes' ? 'block' : 'none'; document.getElementById('brain-journal-section').style.display = view==='journal' ? 'block' : 'none'; } function loadPrompt(){ const p = journalPrompts[Math.floor(Math.random()*journalPrompts.length)]; document.getElementById('promptText').textContent = p; document.getElementById('journalPromptBanner').style.display = 'block'; } function toggleMoodJournal(){ const r = document.getElementById('journalMoodRow'); r.style.display = r.style.display === 'none' ? 'block' : 'none'; } function selJournalMood(el, mood){ document.querySelectorAll('#journalMoodRow .brain-tb-btn').forEach(b => b.classList.remove('active')); el.classList.add('active'); selectedJournalMood = mood; } function expandJournalEntry(card){ const body = card.querySelector('.journal-entry-body'); const lbl = card.querySelector('[style*="expand"]'); const isOpen = body.style.display === 'block'; body.style.display = isOpen ? 'none' : 'block'; if(lbl) lbl.textContent = isOpen ? 'expand ›' : 'collapse ‹'; } function saveJournalEntry(){ const text = document.getElementById('journalText').value.trim(); if(!text){ toast('Write something first — even one sentence counts 📖'); return; } const entries = document.getElementById('journalEntries'); const today = new Date().toLocaleDateString('en-US',{weekday:'long',month:'long',day:'numeric'}); const moodEmoji = selectedJournalMood ? selectedJournalMood.split(' ')[0] : '📖'; const moodBg = {'😄':'var(--sage-l)','🙂':'var(--amber-l)','😐':'var(--cream-mid)','😕':'var(--rose-l)','😩':'var(--rose-l)','😰':'var(--plum-l)','🥰':'var(--sky-l)'}[moodEmoji] || 'var(--cream-mid)'; const entry = document.createElement('div'); entry.style.cssText = 'background:var(--wh);border:1.5px solid var(--terra);border-radius:12px;overflow:hidden;cursor:pointer;'; entry.innerHTML = `
${moodEmoji}
${today}
mood: ${selectedJournalMood||'not set'} · new entry
new ✦
`; entries.insertBefore(entry, entries.firstChild); document.getElementById('journalText').value = ''; document.getElementById('journalPromptBanner').style.display = 'none'; document.querySelectorAll('#journalMoodRow .brain-tb-btn').forEach(b => b.classList.remove('active')); selectedJournalMood = ''; toast('Journal entry saved 📖 · streak updated!'); } // set journal today date var journalDateEl = document.getElementById('journalTodayDate'); if(journalDateEl) journalDateEl.textContent = new Date().toLocaleDateString('en-US',{weekday:'long',month:'long',day:'numeric',year:'numeric'}); function clearDump(){ const t=document.getElementById('dumpText'); if(t.value.trim()&&confirm('Clear brain dump? This cannot be undone (or save it as a note first).')){ t.value=''; toast('Brain cleared — you did it! 🧠'); } } // ── ADD GOAL DRAWER ── // Fully self-contained — appends directly to body, no modal/scrim dependencies function openAddGoalDrawer(){ // Remove any existing instance var existing = document.getElementById('addGoalDrawer'); if(existing) existing.remove(); // Scrim var scrim = document.createElement('div'); scrim.id = 'addGoalScrim'; scrim.style.cssText = 'position:fixed;inset:0;background:rgba(30,50,69,.45);z-index:800;'; scrim.onclick = closeAddGoalDrawer; document.body.appendChild(scrim); // Drawer container var wrap = document.createElement('div'); wrap.id = 'addGoalDrawer'; wrap.style.cssText = 'position:fixed;bottom:0;left:0;right:0;z-index:801;display:flex;justify-content:center;'; document.body.appendChild(wrap); // Inner drawer card var drawer = document.createElement('div'); drawer.style.cssText = 'background:#fff;border-radius:20px 20px 0 0;box-shadow:0 -8px 40px rgba(30,50,69,.22);padding:20px 24px 36px;width:100%;max-width:520px;animation:slideUp .25s ease;'; wrap.appendChild(drawer); // Handle var handle = document.createElement('div'); handle.style.cssText = 'width:36px;height:4px;border-radius:2px;background:#d0dae6;margin:0 auto 18px;'; drawer.appendChild(handle); // Close button var closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = 'position:absolute;top:16px;right:18px;width:30px;height:30px;border-radius:50%;border:none;background:#eef3f8;color:#4a6580;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;'; closeBtn.onclick = closeAddGoalDrawer; drawer.appendChild(closeBtn); // Title var title = document.createElement('div'); title.style.cssText = "font-family:'Fraunces',serif;font-size:18px;color:#1e3245;margin-bottom:16px;"; title.textContent = 'Add a new goal'; drawer.appendChild(title); function field(labelText, inputEl){ var wrap2 = document.createElement('div'); wrap2.style.cssText = 'margin-bottom:13px;'; var lbl = document.createElement('label'); lbl.style.cssText = 'display:block;font-size:11px;font-weight:500;color:#4a6580;margin-bottom:5px;'; lbl.textContent = labelText; wrap2.appendChild(lbl); wrap2.appendChild(inputEl); return wrap2; } function inp(placeholder, id){ var el = document.createElement('input'); el.id = id; el.placeholder = placeholder; el.style.cssText = 'width:100%;border:1.5px solid #d4dce8;border-radius:10px;padding:9px 13px;font-size:13px;font-family:"DM Sans",sans-serif;color:#1e3245;background:#fff;outline:none;box-sizing:border-box;'; el.onfocus = function(){ el.style.borderColor='#1e3245'; }; el.onblur = function(){ el.style.borderColor='#d4dce8'; }; return el; } // Goal name var nameInput = inp('e.g. Read 12 books this year, Save $5,000, Walk daily', 'ag_name'); drawer.appendChild(field("What's your goal?", nameInput)); // Category var catSel = document.createElement('select'); catSel.id = 'ag_cat'; catSel.style.cssText = 'width:100%;border:1.5px solid #d4dce8;border-radius:10px;padding:9px 13px;font-size:13px;font-family:"DM Sans",sans-serif;color:#1e3245;background:#fff;outline:none;cursor:pointer;box-sizing:border-box;'; [['social','🤝 Social'],['health','💪 Health & Wellness'],['family','👨‍👩‍👧 Family'], ['personal','🌱 Personal Development'],['finances','💰 Finances'], ['spiritual','🙏 Spiritual'],['career','💼 Career'] ].forEach(function(o){ var opt = document.createElement('option'); opt.value = o[0]; opt.textContent = o[1]; catSel.appendChild(opt); }); drawer.appendChild(field('Category', catSel)); // Target + deadline row var row = document.createElement('div'); row.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:13px;'; var targetInput = inp('e.g. 12 books, $5,000', 'ag_target'); var dlInput = document.createElement('input'); dlInput.type = 'date'; dlInput.id = 'ag_deadline'; dlInput.style.cssText = targetInput.style.cssText; var t1 = document.createElement('div'); var l1 = document.createElement('label'); l1.style.cssText = 'display:block;font-size:11px;font-weight:500;color:#4a6580;margin-bottom:5px;'; l1.textContent = 'Target / quantity'; t1.appendChild(l1); t1.appendChild(targetInput); var t2 = document.createElement('div'); var l2 = document.createElement('label'); l2.style.cssText = l1.style.cssText; l2.textContent = 'Complete by'; t2.appendChild(l2); t2.appendChild(dlInput); row.appendChild(t1); row.appendChild(t2); drawer.appendChild(row); // Measure var measureInput = inp('e.g. 1 book per month, $500/month, daily check-in', 'ag_measure'); drawer.appendChild(field('How will you measure progress?', measureInput)); // Save button var saveBtn = document.createElement('button'); saveBtn.textContent = 'Add goal & break it down ✦'; saveBtn.style.cssText = 'width:100%;background:#1e3245;color:#fff;border:none;border-radius:11px;padding:13px;font-size:13px;font-weight:600;font-family:"DM Sans",sans-serif;cursor:pointer;margin-top:4px;transition:background .15s;'; saveBtn.onmouseenter = function(){ saveBtn.style.background = '#e8945a'; }; saveBtn.onmouseleave = function(){ saveBtn.style.background = '#1e3245'; }; saveBtn.onclick = function(){ var name = nameInput.value.trim(); var cat = catSel.value; var target = targetInput.value.trim(); var deadline = dlInput.value || null; var measure = measureInput.value.trim(); if(!name){ nameInput.style.borderColor='#c06060'; nameInput.focus(); return; } closeAddGoalDrawer(); // Reuse saveNewGoal logic inline var META = { social: {icon:'\uD83E\uDD1D',iconBg:'var(--plum-l)',color:'var(--plum)'}, health: {icon:'\uD83D\uDCAA',iconBg:'var(--sky-l)', color:'var(--sky)'}, family: {icon:'\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67',iconBg:'var(--rose-l)',color:'var(--sage)'}, personal: {icon:'\uD83D\uDCDA',iconBg:'var(--sky-l)', color:'var(--sky)'}, finances: {icon:'\uD83D\uDCB0',iconBg:'var(--amber-l)',color:'var(--amber)'}, spiritual:{icon:'\uD83D\uDE4F',iconBg:'var(--terra-l)',color:'var(--terra)'}, career: {icon:'\uD83D\uDCBC',iconBg:'#E8EFFF', color:'var(--navy)'}, }; var m = META[cat] || META.personal; var newGoal = { id:'g'+Date.now(), cat:cat, icon:m.icon, iconBg:m.iconBg, color:m.color, title:name, pct:0, target:target, deadline:deadline, measure:measure, notes:measure||target||'added '+new Date().toLocaleDateString('en-US',{month:'short',day:'numeric'}), prompt:null, subtasks:[] }; goalsData.push(newGoal); activeGoalCat = 'all'; document.querySelectorAll('.gst').forEach(function(g){ g.classList.remove('active'); }); var allTab = document.querySelector('.gst'); if(allTab) allTab.classList.add('active'); renderGoals(); var breakdown = generateBreakdown(newGoal); setTimeout(function(){ openBreakdownEditor(newGoal, breakdown.length ? breakdown : [{name:'First step: '+name,taskType:'adhoc',recurFreq:'',deadline:'',done:false}]); }, 200); }; drawer.appendChild(saveBtn); setTimeout(function(){ nameInput.focus(); }, 100); } function closeAddGoalDrawer(){ var d = document.getElementById('addGoalDrawer'); if(d) d.remove(); var s = document.getElementById('addGoalScrim'); if(s) s.remove(); } // ══════════════════════════════════════════ // FOCUS SESSION // ══════════════════════════════════════════ var _focusTimer = null; // setInterval handle var _focusSecondsLeft = 0; var _focusRunning = false; var _focusSettings = { duration: 25, // minutes dnd: true, // do not disturb blockedApps: ['Facebook','Instagram','Twitter / X','TikTok','YouTube'], allowUrgent: true, // allow emergency calls/texts }; function openFocusSession(){ // Remove any existing instance var ex = document.getElementById('focusSessionDrawer'); if(ex) ex.remove(); var es = document.getElementById('focusSessionScrim'); if(es) es.remove(); var scrim = document.createElement('div'); scrim.id = 'focusSessionScrim'; scrim.style.cssText = 'position:fixed;inset:0;background:rgba(30,50,69,.45);z-index:810;'; scrim.onclick = function(){ closeFocusDrawer(); }; document.body.appendChild(scrim); var wrap = document.createElement('div'); wrap.id = 'focusSessionDrawer'; wrap.style.cssText = 'position:fixed;bottom:0;left:0;right:0;z-index:811;display:flex;justify-content:center;'; document.body.appendChild(wrap); var drawer = document.createElement('div'); drawer.className = 'focus-drawer'; wrap.appendChild(drawer); // ── Handle + close ── var handle = document.createElement('div'); handle.style.cssText = 'width:36px;height:4px;border-radius:2px;background:#d0dae6;margin:0 auto 18px;'; drawer.appendChild(handle); var closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = 'position:absolute;top:16px;right:18px;width:30px;height:30px;border-radius:50%;border:none;background:#eef3f8;color:#4a6580;font-size:18px;cursor:pointer;'; closeBtn.onclick = closeFocusDrawer; drawer.appendChild(closeBtn); // ── Title ── var taskName = (prioritySettings.topOne && prioritySettings.topOne.title) || 'your focus session'; var hdr = document.createElement('div'); hdr.style.cssText = 'text-align:center;margin-bottom:18px;'; hdr.innerHTML = '
Focus session
' + '
Focusing on: ' + escHtml(taskName) + '
'; drawer.appendChild(hdr); // ── Timer ring ── var ringWrap = document.createElement('div'); ringWrap.className = 'focus-timer-ring'; var circ = 2 * Math.PI * 62; ringWrap.innerHTML = '' + '' + '' + '' + '
' + _focusSettings.duration + ':00
' + '
minutes remaining
'; drawer.appendChild(ringWrap); // ── Duration picker ── var durRow = document.createElement('div'); durRow.className = 'focus-duration-row'; [15, 25, 45, 60].forEach(function(mins){ var btn = document.createElement('button'); btn.className = 'focus-dur-btn' + (mins === _focusSettings.duration ? ' active' : ''); btn.textContent = mins + ' min'; btn.onclick = function(){ _focusSettings.duration = mins; durRow.querySelectorAll('.focus-dur-btn').forEach(function(b){ b.classList.remove('active'); }); btn.classList.add('active'); document.getElementById('ftLabel').textContent = mins + ':00'; }; durRow.appendChild(btn); }); drawer.appendChild(durRow); // ── Block Interruptions section ── var blockSec = document.createElement('div'); blockSec.className = 'focus-block-section'; var blockTitle = document.createElement('div'); blockTitle.className = 'focus-block-title'; blockTitle.textContent = '🛡 block interruptions'; blockSec.appendChild(blockTitle); function makeToggleRow(icon, iconBg, name, sub, settingKey){ var row = document.createElement('div'); row.className = 'focus-block-row'; var ic = document.createElement('div'); ic.className = 'focus-block-icon'; ic.style.background = iconBg; ic.textContent = icon; var lbl = document.createElement('div'); lbl.className = 'focus-block-label'; lbl.innerHTML = '
' + name + '
' + '
' + sub + '
'; var tog = document.createElement('button'); tog.className = 'focus-toggle' + (_focusSettings[settingKey] ? ' on' : ''); tog.onclick = function(){ _focusSettings[settingKey] = !_focusSettings[settingKey]; tog.classList.toggle('on', _focusSettings[settingKey]); }; row.appendChild(ic); row.appendChild(lbl); row.appendChild(tog); return row; } blockSec.appendChild(makeToggleRow('🔕', 'var(--navy)', 'Do Not Disturb', 'Silence notifications for the full session', 'dnd')); blockSec.appendChild(makeToggleRow('📱', 'var(--plum-l)', 'Allow urgent messages only', 'Emergency contacts & calls still come through', 'allowUrgent')); // App blocking chips var appRow = document.createElement('div'); appRow.className = 'focus-block-row'; appRow.style.flexDirection = 'column'; appRow.style.alignItems = 'flex-start'; var appLbl = document.createElement('div'); appLbl.className = 'focus-block-label'; appLbl.innerHTML = '
🚫 Block distracting apps
' + '
Tap to toggle — blocked apps show an interstitial during focus
'; appRow.appendChild(appLbl); var allApps = ['Facebook','Instagram','Twitter / X','TikTok','YouTube','Reddit','Snapchat','News apps']; var chips = document.createElement('div'); chips.className = 'focus-app-chips'; allApps.forEach(function(app){ var chip = document.createElement('div'); var isBlocked = _focusSettings.blockedApps.indexOf(app) >= 0; chip.className = 'focus-app-chip' + (isBlocked ? ' blocked' : ''); chip.innerHTML = '+× ' + app; chip.onclick = function(){ var idx = _focusSettings.blockedApps.indexOf(app); if(idx >= 0){ _focusSettings.blockedApps.splice(idx,1); chip.classList.remove('blocked'); } else { _focusSettings.blockedApps.push(app); chip.classList.add('blocked'); } }; chips.appendChild(chip); }); appRow.appendChild(chips); blockSec.appendChild(appRow); // Note about OS-level controls var osNote = document.createElement('div'); osNote.style.cssText = 'font-size:10px;color:var(--hint);margin-top:10px;line-height:1.6;padding:8px 10px;background:rgba(30,50,69,.04);border-radius:8px;'; osNote.innerHTML = 'Note: Do Not Disturb and app restrictions require device permission. ' + 'On iOS, go to Settings → Focus. On Android, go to Settings → Digital Wellbeing. ' + 'Sphere will remind you to enable these before starting.'; blockSec.appendChild(osNote); drawer.appendChild(blockSec); // ── Start button ── var startBtn = document.createElement('button'); startBtn.style.cssText = 'width:100%;background:var(--navy);color:#fff;border:none;border-radius:12px;padding:14px;font-size:14px;font-weight:600;font-family:\'DM Sans\',sans-serif;cursor:pointer;transition:background .15s;'; startBtn.textContent = '▶ start focus session'; startBtn.onmouseenter = function(){ startBtn.style.background = 'var(--terra)'; }; startBtn.onmouseleave = function(){ startBtn.style.background = 'var(--navy)'; }; startBtn.onclick = function(){ closeFocusDrawer(); startFocusMode(); }; drawer.appendChild(startBtn); } function closeFocusDrawer(){ var d = document.getElementById('focusSessionDrawer'); if(d) d.remove(); var s = document.getElementById('focusSessionScrim'); if(s) s.remove(); } // ── ACTIVE FOCUS MODE OVERLAY ── function startFocusMode(){ var totalSecs = _focusSettings.duration * 60; _focusSecondsLeft = totalSecs; _focusRunning = true; // Build full-screen focus overlay var overlay = document.createElement('div'); overlay.id = 'focusActiveOverlay'; overlay.className = 'focus-active-overlay'; document.body.appendChild(overlay); // Task name var taskName = (prioritySettings.topOne && prioritySettings.topOne.title) || 'deep work'; var taskEl = document.createElement('div'); taskEl.style.cssText = 'font-size:13px;color:rgba(255,255,255,.4);margin-bottom:12px;letter-spacing:.04em;text-transform:uppercase;'; taskEl.textContent = '🎯 focusing on'; overlay.appendChild(taskEl); var taskNameEl = document.createElement('div'); taskNameEl.style.cssText = 'font-family:\'Fraunces\',serif;font-size:22px;color:rgba(255,255,255,.85);margin-bottom:36px;max-width:340px;text-align:center;line-height:1.3;'; taskNameEl.textContent = taskName; overlay.appendChild(taskNameEl); // Big timer var timerEl = document.createElement('div'); timerEl.className = 'focus-active-timer'; timerEl.id = 'focusActiveTimer'; timerEl.textContent = formatFocusTime(_focusSecondsLeft); overlay.appendChild(timerEl); var subEl = document.createElement('div'); subEl.style.cssText = 'font-size:12px;color:rgba(255,255,255,.3);margin-bottom:36px;'; subEl.textContent = 'stay with it — you\'ve got this'; overlay.appendChild(subEl); // Blocked apps badges if(_focusSettings.blockedApps.length > 0){ var blockedWrap = document.createElement('div'); blockedWrap.className = 'focus-active-blocked'; _focusSettings.blockedApps.forEach(function(app){ var badge = document.createElement('div'); badge.className = 'focus-blocked-badge'; badge.textContent = '🚫 ' + app; blockedWrap.appendChild(badge); }); overlay.appendChild(blockedWrap); } // DND notice if(_focusSettings.dnd){ var dndEl = document.createElement('div'); dndEl.className = 'focus-dnd-notice'; dndEl.innerHTML = '🔕 Do Not Disturb is on.
' + (_focusSettings.allowUrgent ? 'Emergency contacts can still reach you.' : 'All notifications are silenced.') + '

To activate at the OS level: pull down your notification shade and enable DND.'; overlay.appendChild(dndEl); } // Action buttons var actions = document.createElement('div'); actions.className = 'focus-active-actions'; actions.style.marginTop = '36px'; var pauseBtn = document.createElement('button'); pauseBtn.className = 'focus-end-btn'; pauseBtn.id = 'focusPauseBtn'; pauseBtn.textContent = '⏸ pause'; pauseBtn.onclick = function(){ _focusRunning = !_focusRunning; pauseBtn.textContent = _focusRunning ? '⏸ pause' : '▶ resume'; }; var endBtn = document.createElement('button'); endBtn.className = 'focus-end-btn'; endBtn.textContent = '⏹ end session'; endBtn.onclick = function(){ endFocusMode(false); }; var doneBtn = document.createElement('button'); doneBtn.className = 'focus-end-btn primary'; doneBtn.textContent = '✓ task complete'; doneBtn.onclick = function(){ // Mark the top-1 task done if it exists if(prioritySettings.topOne){ var t = schedTasks.find(function(t){ return t.id === prioritySettings.topOne.id; }); if(t){ var prev=t.done; t.done=true; t.missed=false; toastUndo('✅ '+t.title+' — done!',t,prev); } } endFocusMode(true); }; actions.appendChild(pauseBtn); actions.appendChild(endBtn); actions.appendChild(doneBtn); overlay.appendChild(actions); // Start countdown var circ = 2 * Math.PI * 62; clearInterval(_focusTimer); _focusTimer = setInterval(function(){ if(!_focusRunning) return; _focusSecondsLeft--; var timerDisplay = document.getElementById('focusActiveTimer'); if(timerDisplay) timerDisplay.textContent = formatFocusTime(_focusSecondsLeft); if(_focusSecondsLeft <= 0){ clearInterval(_focusTimer); endFocusMode(true); } }, 1000); // Show DND reminder toast if(_focusSettings.dnd){ setTimeout(function(){ toast('🔕 Remember to enable Do Not Disturb on your device'); }, 800); } } function endFocusMode(completed){ clearInterval(_focusTimer); _focusRunning = false; var overlay = document.getElementById('focusActiveOverlay'); if(overlay) overlay.remove(); if(completed){ toast('🎉 Focus session complete — great work!'); renderWidgetArea(); } else { var mins = Math.round((_focusSettings.duration * 60 - _focusSecondsLeft) / 60); toast('Session ended — ' + mins + ' min focused. Every minute counts. 🌿'); } } function formatFocusTime(secs){ var m = Math.floor(secs / 60), s = secs % 60; return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; } // ── MODALS ── function openModal(type){ document.querySelectorAll('[id^="modal-"]').forEach(m=>m.style.display='none'); const m=document.getElementById('modal-'+type); if(m){ m.style.display='block'; document.getElementById('modalWrap').classList.add('open'); } } function closeModal(){ document.getElementById('modalWrap').classList.remove('open'); } document.getElementById('modalWrap').addEventListener('click',e=>{ if(e.target===document.getElementById('modalWrap'))closeModal(); }); // ── LOCK ── function lockApp(){ toast('App locked 🔒'); } // ── TOAST ── var toastT = null; var _lastCompleted = null; // {task, prevDone} for undo function toast(msg){ var t = document.getElementById('toast'); var m = document.getElementById('toastMsg'); var u = document.getElementById('toastUndoBtn'); if(!t || !m) return; m.textContent = msg; if(u) u.style.display = 'none'; t.style.display = 'flex'; clearTimeout(toastT); toastT = setTimeout(function(){ t.style.display = 'none'; }, 3000); } function toastUndo(msg, task, prevDone){ _lastCompleted = { task: task, prevDone: prevDone }; scheduleSave(); // save completion state var t = document.getElementById('toast'); var m = document.getElementById('toastMsg'); var u = document.getElementById('toastUndoBtn'); if(!t || !m) return; m.textContent = msg; if(u) u.style.display = 'inline-block'; t.style.display = 'flex'; clearTimeout(toastT); toastT = setTimeout(function(){ t.style.display = 'none'; _lastCompleted = null; }, 5000); } function undoLastComplete(){ if(!_lastCompleted) return; _lastCompleted.task.done = _lastCompleted.prevDone; _lastCompleted.task.missed = false; _lastCompleted = null; clearTimeout(toastT); var t = document.getElementById('toast'); if(t) t.style.display = 'none'; renderTimeline(); toast('↩ undone'); }
☁️ Saving...