When Will You Die?

The internet's most brutally honest life expectancy calculator. Spoiler: it's sooner than you think. But we'll show you how to run from the Reaper.

Research-backed. Peer-reviewed. Unapologetically honest.

53M+
Research participants
1,200+
Peer-reviewed studies
100+
Life factors analyzed
`; const blob = new Blob([widgetHtml], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'death-clock-widget.html'; a.click(); URL.revokeObjectURL(url); alert('Widget downloaded! Open death-clock-widget.html in your browser, or use a tool like Widgetsmith (iOS) or KWGT (Android) to add a web widget to your home screen.'); } // ============================================ // DASHBOARD // ============================================ function renderDashboard() { if (!state.result) { showPage('home'); return; } const r = state.result; const scoreColor = r.lifeScore > 70 ? 'var(--green)' : r.lifeScore > 40 ? 'var(--gold)' : 'var(--accent)'; document.getElementById('dashStats').innerHTML = `
${r.remainingYears}
Years Remaining
${r.lifeScore}
Life Score
+${state.bucketList.filter(b=>b.done).length}
Bucket List Done
+${state.longevityGoal ? state.longevityGoal.totalDaysAdded.toFixed(1) : '0'}
Days Added
`; showTab(state.currentTab); } function showTab(tab) { state.currentTab = tab; document.querySelectorAll('.tab-btn').forEach(t => t.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(t => { if(t.textContent.toLowerCase().includes(tab.replace('bucketlist','bucket').replace('myplan','my plan'))) t.classList.add('active'); }); const el = document.getElementById('tabContent'); if (tab === 'factors') renderFactorsTab(el); else if (tab === 'bucketlist') renderBucketTab(el); else if (tab === 'myplan') renderMyPlanTab(el); else if (tab === 'tips') renderTipsTab(el); else if (tab === 'products') renderProductsTab(el); } function renderFactorsTab(el) { const r = state.result; const sorted = [...r.factors].sort((a,b) => a.impact - b.impact); el.innerHTML = `
All ${r.factors.length} factors analyzed in your calculation:
${sorted.map(f => { const cls = f.impact > 0 ? 'positive' : f.impact < 0 ? 'negative' : ''; const sign = f.impact > 0 ? '+' : ''; return `
${f.label}
${f.tip}
${sign}${f.impact} yrs
`; }).join('')}
Net Impact
${r.totalAdjust >= 0 ? '+' : ''}${r.totalAdjust.toFixed(1)} years
`; } function renderBucketTab(el) { const items = state.bucketList; el.innerHTML = `
${items.length}/10 items (Free tier)
${items.length === 0 ? '
No bucket list items yet. What do you want to do before you die?
' : items.map((item, i) => `
${item.done ? '✓' : ''}
${item.title}
${item.category} ${item.priority} ${item.targetDate ? `Target: ${item.targetDate}` : ''}
${item.description ? `
${item.description}
` : ''}
`).join('')} `; } function renderGoalsTab(el) { const g = state.goals; el.innerHTML = `
${g.length}/3 goals (Free tier)
${g.length === 0 ? '
No goals set yet. What will you achieve with your remaining time?
' : g.map((goal, i) => `
${goal.title}
${goal.lifeImpact ? '+' + goal.lifeImpact + ' years potential' : ''}
${goal.description ? `
${goal.description}
` : ''}
${goal.progress}% complete ${goal.category} | ${goal.timeline}
`).join('')} `; } function renderTipsTab(el) { // Get user's weakest categories const negFactors = state.result.factors.filter(f => f.impact < 0); const weakCats = [...new Set(negFactors.map(f => f.cat))]; const relevantTips = tipsDB.filter(t => weakCats.includes(t.cat)); const otherTips = tipsDB.filter(t => !weakCats.includes(t.cat)); const allTips = [...relevantTips, ...otherTips]; el.innerHTML = `
Tips personalized to your biggest risk factors:
${allTips.map(t => `
${t.title}
+${t.years} years
${t.content}
${t.difficulty} ${relevantTips.includes(t) ? ' Recommended for you' : ''}
`).join('')} `; } function renderProductsTab(el) { // Get user's weakest categories and match products const negFactors = state.result.factors.filter(f => f.impact < 0 && f.productCat); const weakProductCats = [...new Set(negFactors.map(f => f.productCat))]; const recommended = products.filter(p => weakProductCats.some(c => p.factorCat === c)); const others = products.filter(p => !recommended.includes(p)); const allProducts = [...recommended, ...others]; el.innerHTML = `
Products and services that could help extend your lifespan:
${allProducts.map(p => `
${recommended.includes(p) ? '
RECOMMENDED FOR YOU
' : ''}
${p.category}
${p.name}
Could add ${p.impact}
${p.desc}
${p.price}
${'★'.repeat(Math.floor(p.rating))} ${p.rating}/5
`).join('')}
Affiliate Disclosure: Some links on this page are affiliate links. We may earn a commission at no extra cost to you. We only recommend products we believe can genuinely improve your health and longevity.
`; } function trackAffiliateClick(productName) { console.log('Affiliate click:', productName); // In production, this would log to Supabase alert('This would redirect to ' + productName + ' (affiliate link). In production, this tracks the click in Supabase and redirects via affiliate URL.'); } // ============================================ // MODALS // ============================================ function openBucketModal() { if (state.bucketList.length >= 10) { alert('Free tier limit: 10 bucket list items. Upgrade to Premium for unlimited!'); return; } document.getElementById('modal').classList.remove('hidden'); document.getElementById('modalContent').innerHTML = `

Add Bucket List Item

`; } function saveBucket() { const title = document.getElementById('bucketTitle').value.trim(); if (!title) return; state.bucketList.push({ title, description: document.getElementById('bucketDesc').value.trim(), category: document.getElementById('bucketCat').value, priority: document.getElementById('bucketPri').value, targetDate: document.getElementById('bucketDate').value, done: false }); closeModal(); showTab('bucketlist'); } function toggleBucket(i) { state.bucketList[i].done = !state.bucketList[i].done; showTab('bucketlist'); renderDashboard(); } function deleteBucket(i) { state.bucketList.splice(i, 1); showTab('bucketlist'); } function openGoalModal() { if (state.goals.length >= 3) { alert('Free tier limit: 3 goals. Upgrade to Premium for unlimited!'); return; } document.getElementById('modal').classList.remove('hidden'); document.getElementById('modalContent').innerHTML = `

Add Personal Goal

`; } function saveGoal() { const title = document.getElementById('goalTitle').value.trim(); if (!title) return; state.goals.push({ title, description: document.getElementById('goalDesc').value.trim(), category: document.getElementById('goalCat').value, timeline: document.getElementById('goalTimeline').value, lifeImpact: parseFloat(document.getElementById('goalImpact').value) || 0, progress: 0 }); closeModal(); showTab('goals'); } function updateGoalProgress(i, delta) { state.goals[i].progress = Math.max(0, Math.min(100, state.goals[i].progress + delta)); if (state.goals[i].progress === 100) state.goals[i].status = 'completed'; showTab('goals'); } function deleteGoal(i) { state.goals.splice(i, 1); showTab('goals'); } function closeModal() { document.getElementById('modal').classList.add('hidden'); } // ============================================ // COOKIES // ============================================ function acceptCookies(level) { const consent = { level, timestamp: new Date().toISOString() }; try { localStorage.setItem('dc_cookie_consent', JSON.stringify(consent)); } catch(e) {} document.getElementById('cookieBanner').classList.add('hidden'); } function checkCookieConsent() { try { const consent = localStorage.getItem('dc_cookie_consent'); if (!consent) { document.getElementById('cookieBanner').classList.remove('hidden'); } } catch(e) { // localStorage not available, show banner document.getElementById('cookieBanner').classList.remove('hidden'); } } // ============================================ // ============================================ // MOTIVATIONAL MESSAGES // ============================================ const motivationMessages = { great: [ "You absolute legend. Keep it up.", "Your future self just high-fived you.", "Death is taking notes. And sweating.", "If longevity was a sport, you'd be winning.", "The Grim Reaper just rescheduled.", "You're making centenarians look lazy.", "Your cells are literally cheering right now.", "Plot twist: you might actually outlive everyone." ], good: [ "Not bad! You're doing better than most.", "Solid choice. Your organs approve.", "Nice. You just bought yourself some time.", "The Grim Reaper yawned. You're not interesting yet.", "Good job. Your future grandkids thank you.", "Decent. But I know you can do better.", "That's a respectable answer. Respectable." ], neutral: [ "Okay, middle of the road. Not dying fast, not living forever.", "You're... average. Is that what you were going for?", "Could be worse. Could also be way better.", "The universe shrugged at this answer.", "Meh. You're not losing years, but not gaining any either.", "Perfectly mediocre. Like room temperature water." ], bad: [ "Oof. That one hurt your timeline.", "The Grim Reaper just smiled.", "Well, that's... not ideal. Let's fix it.", "Your body just sent you a formal complaint.", "Are you sure? Like, really sure?", "That answer aged you 3 years just reading it.", "Your doctor would like a word." ], terrible: [ "Yikes. We need to talk.", "The Grim Reaper just added you to speed dial.", "At this rate, your countdown timer needs fewer digits.", "Okay, so we have some work to do. A lot of work.", "Your future self is screaming through a time portal right now.", "That's... brave? Reckless? Both?", "The good news: there's nowhere to go but up." ] }; function getMotivationCategory(key, value) { const greatAnswers = { exercise: ['5+'], diet: ['very_healthy'], smoking: ['never'], alcohol: ['never'], drugs: ['none'], stress: ['low'], social: ['strong'], sleep_hours: ['optimal'], sleep_quality: ['excellent'], healthcare: ['regular'], sport: ['tennis','badminton'], veg_diet: ['vegan','vegetarian'], processed_food: ['minimal'], blood_pressure: ['normal'], resting_hr: ['low'], coffee: ['moderate'], hydration: ['good'], dental: ['excellent'], sauna: ['frequent'], omega3: ['high'], gratitude: ['high'], volunteering: ['regular'], religion: ['weekly'], nature: ['high'], screen_time: ['low'], education: ['postgrad'], income: ['high'], pet: ['dog'] }; const goodAnswers = { exercise: ['3-4x'], diet: ['healthy'], smoking: ['former'], alcohol: ['occasional'], stress: ['moderate'], social: ['moderate'], sleep_hours: ['moderate_short'], sleep_quality: ['good'], healthcare: ['occasional'], sport: ['soccer','cycling','swimming','running'], veg_diet: ['pescatarian','flexitarian'], processed_food: ['low'], blood_pressure: ['elevated'], resting_hr: ['normal'], coffee: ['light','heavy'], hydration: ['moderate'], dental: ['good'], sauna: ['moderate','rare'], omega3: ['moderate'], gratitude: ['moderate'], volunteering: ['occasional'], religion: ['occasional'], nature: ['moderate'], screen_time: ['moderate'], education: ['bachelors','some_college'], income: ['middle'], relationship: ['married','partnered'], pet: ['cat','other'], stress_mgmt: ['yes'] }; const terribleAnswers = { smoking: ['current_heavy'], drugs: ['opioids'], social: ['isolated'], exercise: ['none'], stress: ['very_high'], processed_food: ['very_high'], blood_pressure: ['high_2'], resting_hr: ['high'], screen_time: ['very_high'], healthcare: ['never'], alcohol: ['heavy'] }; const badAnswers = { smoking: ['current_light'], drugs: ['recreational','cannabis'], social: ['few'], exercise: ['1-2x'], stress: ['high'], diet: ['poor'], sleep_hours: ['short'], sleep_quality: ['poor'], processed_food: ['high'], blood_pressure: ['high_1'], resting_hr: ['elevated'], hydration: ['poor'], dental: ['poor'], omega3: ['low'], nature: ['low'], screen_time: ['high'], education: ['less'], income: ['low'], healthcare: ['rarely'], occupation: ['hazardous'], air_quality: ['poor'], relationship: ['divorced_widowed'], sleep_hours: ['long'] }; if (terribleAnswers[key] && terribleAnswers[key].includes(value)) return 'terrible'; if (badAnswers[key] && badAnswers[key].includes(value)) return 'bad'; if (greatAnswers[key] && greatAnswers[key].includes(value)) return 'great'; if (goodAnswers[key] && goodAnswers[key].includes(value)) return 'good'; return 'neutral'; } function showMotivation(key, value) { if (['dob','body','conditions','family','country'].includes(key)) return; const cat = getMotivationCategory(key, value); const msgs = motivationMessages[cat]; const msg = msgs[Math.floor(Math.random() * msgs.length)]; const existing = document.querySelector('.motivation-toast'); if (existing) existing.remove(); const toast = document.createElement('div'); let toastClass = cat; if (cat === 'terrible') toastClass = 'bad'; toast.className = 'motivation-toast ' + toastClass; toast.textContent = msg; document.body.appendChild(toast); setTimeout(() => { if (toast.parentNode) toast.remove(); }, 3000); } function getCountryComparison() { const r = state.result; const a = state.answers; const country = a.country || 'United States'; const sex = a.sex || 'male'; const countryData = countryLifeExpectancy[country]; if (!countryData) return null; const countryAvg = sex === 'male' ? countryData.m : countryData.f; const userLE = parseFloat(r.adjustedLE); const diff = userLE - countryAvg; const sd = 13; const zScore = (userLE - countryAvg) / sd; const percentile = Math.round(Math.min(99, Math.max(1, 100 / (1 + Math.exp(-1.7 * zScore))))); let verdict, verdictClass; if (diff > 10) { verdict = 'You\'re basically immortal compared to your country.'; verdictClass = 'top'; } else if (diff > 5) { verdict = 'You\'re aging like fine wine. Death is jealous.'; verdictClass = 'top'; } else if (diff > 2) { verdict = 'Above average. Your country is proud of you.'; verdictClass = 'top'; } else if (diff > -2) { verdict = 'Right around average. You blend in. Like camouflage.'; verdictClass = 'mid'; } else if (diff > -5) { verdict = 'Below average. The Grim Reaper has you bookmarked.'; verdictClass = 'low'; } else { verdict = 'Significantly below your country\'s average. Time for a life overhaul.'; verdictClass = 'low'; } return { country, countryAvg, userLE, diff, percentile, verdict, verdictClass }; } function renderLeaderboard() { const comp = getCountryComparison(); if (!comp) return ''; const pctPosition = comp.percentile; const diffSign = comp.diff >= 0 ? '+' : ''; return `

Global Leaderboard

Your Rank in ${comp.country}
Top ${100 - comp.percentile}%
You outlive ${comp.percentile}% of ${comp.country === 'United States' ? 'Americans' : comp.country + ' residents'} (${state.answers.sex || 'male'})
Dies youngestLives longest
${comp.verdict}
${comp.userLE}
Your projected age
${comp.countryAvg}
${comp.country} average (${state.answers.sex || 'male'})
${diffSign}${comp.diff.toFixed(1)} years vs average
`; } // LONGEVITY GOAL SYSTEM // ============================================ function saveGoalState() { try { localStorage.setItem('deathclock_goals', JSON.stringify(state.longevityGoal)); } catch(e) {} } function loadGoalState() { try { const saved = localStorage.getItem('deathclock_goals'); if (saved) state.longevityGoal = JSON.parse(saved); } catch(e) {} } function saveProfile() { if (state.userTier === 'free') { showPaywall('save'); return; } if (!state.result) { alert('Complete the calculator first.'); return; } const profile = { answers: { ...state.answers }, result: { ...state.result, deathDate: state.result.deathDate.getTime(), dob: state.result.dob.getTime() }, bucketList: [...state.bucketList], savedAt: new Date().toISOString() }; try { localStorage.setItem('deathclock_profile', JSON.stringify(profile)); // Show success toast const existing = document.querySelector('.motivation-toast'); if (existing) existing.remove(); const toast = document.createElement('div'); toast.className = 'motivation-toast good'; toast.textContent = 'Profile saved! Your data is safe.'; document.body.appendChild(toast); setTimeout(() => { if (toast.parentNode) toast.remove(); }, 3000); } catch(e) { alert('Could not save profile. Storage may be full.'); } } function loadProfile() { try { const saved = localStorage.getItem('deathclock_profile'); if (!saved) return false; const profile = JSON.parse(saved); state.answers = profile.answers || {}; state.bucketList = profile.bucketList || []; if (profile.result) { state.result = { ...profile.result, deathDate: new Date(profile.result.deathDate), dob: new Date(profile.result.dob) }; // Re-sort factors if (state.result.factors) { state.result.factors.sort((a, b) => a.impact - b.impact); } // Show dashboard nav items document.getElementById('navDash').classList.remove('hidden'); document.getElementById('navPrice').classList.remove('hidden'); document.getElementById('navCta').textContent = 'Recalculate'; } return true; } catch(e) { return false; } } function loadUserTier() { try { const tier = localStorage.getItem('deathclock_tier'); if (tier) state.userTier = tier; } catch(e) {} } function showPaywall(feature) { document.getElementById('modal').classList.remove('hidden'); let featureText = 'This feature'; if (feature === 'save') featureText = 'Saving your profile'; else if (feature === 'unlimited_habits') featureText = 'Unlimited habit tracking'; document.getElementById('modalContent').innerHTML = `

Upgrade to Premium

${featureText} requires a Premium subscription. Save your profile, track unlimited habits, and get clinical test recommendations.

$4.99/month

For demo purposes, clicking Upgrade activates Premium locally.

`; } function upgradeTier(tier) { state.userTier = tier; try { localStorage.setItem('deathclock_tier', tier); } catch(e) {} closeModal(); // Show confirmation const existing = document.querySelector('.motivation-toast'); if (existing) existing.remove(); const toast = document.createElement('div'); toast.className = 'motivation-toast great'; toast.textContent = 'Welcome to Premium! Death just got a lot further away.'; document.body.appendChild(toast); setTimeout(() => { if (toast.parentNode) toast.remove(); }, 3000); // Re-render current page if (state.currentPage === 'dashboard') renderDashboard(); } function generateCheatDeathPlan(result, targetAge) { const currentProjected = parseFloat(result.adjustedLE); const gap = targetAge - currentProjected; const a = state.answers; const potentialHabits = []; // Scan for improvable habits based on user's current answers // Exercise improvements if (a.exercise !== '5+') potentialHabits.push({ id: 'walk_30', name: 'Walk 30 minutes', dailyImpact: 3/365.25, annualImpact: 3, cat: 'fitness', difficulty: 'easy', source: 'VA study, n=719,147', icon: '\u{1F6B6}' }); if (!['tennis','badminton','soccer'].includes(a.sport)) potentialHabits.push({ id: 'social_sport', name: 'Play a social sport', dailyImpact: 4.7/365.25, annualImpact: 4.7, cat: 'fitness', difficulty: 'medium', source: 'Copenhagen study, n=8,577', icon: '\u{1F3BE}' }); // Diet improvements if (a.diet !== 'very_healthy') potentialHabits.push({ id: 'eat_veggies', name: 'Eat 5+ servings of vegetables', dailyImpact: 3/365.25, annualImpact: 3, cat: 'diet', difficulty: 'easy', source: 'Harvard meta-analysis', icon: '\u{1F966}' }); if (['high','very_high'].includes(a.processed_food)) potentialHabits.push({ id: 'reduce_upf', name: 'Avoid ultra-processed food today', dailyImpact: 2/365.25, annualImpact: 2, cat: 'diet', difficulty: 'medium', source: 'BMJ meta-analysis, 18 studies', icon: '\u{1F34E}' }); if (a.omega3 === 'low') potentialHabits.push({ id: 'omega3', name: 'Take omega-3 supplement or eat fish', dailyImpact: 3/365.25, annualImpact: 3, cat: 'diet', difficulty: 'easy', source: 'Framingham study, n=2,240', icon: '\u{1F41F}' }); if (a.hydration !== 'good') potentialHabits.push({ id: 'hydrate', name: 'Drink 8+ glasses of water', dailyImpact: 2/365.25, annualImpact: 2, cat: 'body', difficulty: 'easy', source: 'NIH ARIC, n=15,752', icon: '\u{1F4A7}' }); // Sleep improvements if (a.sleep_hours === 'short' || a.sleep_hours === 'moderate_short') potentialHabits.push({ id: 'sleep_schedule', name: 'Be in bed by 11pm (7+ hrs sleep)', dailyImpact: 2/365.25, annualImpact: 2, cat: 'sleep', difficulty: 'medium', source: 'Sleep Foundation meta-analysis', icon: '\u{1F634}' }); // Mental health if (a.stress_mgmt !== 'yes') potentialHabits.push({ id: 'meditate', name: 'Meditate for 10 minutes', dailyImpact: 1.5/365.25, annualImpact: 1.5, cat: 'mental', difficulty: 'easy', source: 'VA study', icon: '\u{1F9D8}' }); if (a.gratitude !== 'high') potentialHabits.push({ id: 'gratitude', name: 'Write 3 things you are grateful for', dailyImpact: 2/365.25, annualImpact: 2, cat: 'mental', difficulty: 'easy', source: 'JAMA Psychiatry, n=49,275', icon: '\u{1F4DD}' }); // Social if (a.social !== 'strong') potentialHabits.push({ id: 'social_time', name: 'Connect with a friend or family member', dailyImpact: 2/365.25, annualImpact: 2, cat: 'social', difficulty: 'easy', source: 'Harvard 80-year study', icon: '\u{1F465}' }); // Substances if (a.smoking && a.smoking.startsWith('current')) potentialHabits.push({ id: 'no_smoke', name: 'No cigarettes today', dailyImpact: 8/365.25, annualImpact: 8, cat: 'substances', difficulty: 'hard', source: 'Lancet, n=599,912', icon: '\u{1F6AD}' }); if (a.alcohol === 'heavy') potentialHabits.push({ id: 'no_heavy_drink', name: 'Max 2 drinks today', dailyImpact: 3/365.25, annualImpact: 3, cat: 'substances', difficulty: 'medium', source: 'Lancet, n=599,912', icon: '\u{1F377}' }); // Body if (a.dental !== 'excellent') potentialHabits.push({ id: 'floss', name: 'Floss your teeth', dailyImpact: 1.5/365.25, annualImpact: 1.5, cat: 'body', difficulty: 'easy', source: 'Leisure World, n=5,611', icon: '\u{1F9B7}' }); // Nature if (a.nature !== 'high') potentialHabits.push({ id: 'nature', name: 'Spend 20 minutes in nature', dailyImpact: 2/365.25, annualImpact: 2, cat: 'environment', difficulty: 'easy', source: 'Lancet, n=4.6M', icon: '\u{1F33F}' }); // Screen time if (['high','very_high'].includes(a.screen_time)) potentialHabits.push({ id: 'screen_limit', name: 'Keep screen time under 2 hours', dailyImpact: 1.5/365.25, annualImpact: 1.5, cat: 'environment', difficulty: 'medium', source: 'BJSM', icon: '\u{1F4F5}' }); // Sort by impact descending, take top 7 potentialHabits.sort((a,b) => b.annualImpact - a.annualImpact); return potentialHabits.slice(0, 7); } function getStreakInfo() { const g = state.longevityGoal; if (!g || !g.logs) return { streak: 0, multiplier: 1 }; const today = new Date().toISOString().split('T')[0]; let streak = 0; let d = new Date(); // Check backwards from yesterday (today might not be logged yet) d.setDate(d.getDate() - 1); while (true) { const key = d.toISOString().split('T')[0]; const dayLog = g.logs[key]; if (!dayLog || dayLog.length === 0) break; // Need at least 50% of habits logged if (dayLog.length >= Math.ceil(g.plan.length * 0.5)) { streak++; } else { break; } d.setDate(d.getDate() - 1); } // Check if today has any logs (extend streak) const todayLog = g.logs[today]; if (todayLog && todayLog.length >= Math.ceil(g.plan.length * 0.5)) { streak++; } let multiplier = 1; if (streak >= 365) multiplier = 3; else if (streak >= 90) multiplier = 2.5; else if (streak >= 30) multiplier = 2; else if (streak >= 7) multiplier = 1.5; return { streak, multiplier }; } function getTodayKey() { return new Date().toISOString().split('T')[0]; } function toggleHabit(habitId) { const g = state.longevityGoal; if (!g) return; const today = getTodayKey(); if (!g.logs[today]) g.logs[today] = []; const idx = g.logs[today].indexOf(habitId); if (idx >= 0) { g.logs[today].splice(idx, 1); } else { g.logs[today].push(habitId); // Show float animation showHabitFloat(habitId); } recalcTotalDaysAdded(); saveGoalState(); updateDeathyAvatar(); showTab('myplan'); } function showHabitFloat(habitId) { const habit = state.longevityGoal.plan.find(h => h.id === habitId); if (!habit) return; const { multiplier } = getStreakInfo(); const impact = (habit.dailyImpact * multiplier).toFixed(3); const el = document.createElement('div'); el.className = 'habit-float'; el.textContent = '+' + impact + ' days'; document.body.appendChild(el); setTimeout(() => el.remove(), 1500); } function recalcTotalDaysAdded() { const g = state.longevityGoal; if (!g) return; let total = 0; const sortedDates = Object.keys(g.logs).sort(); for (const dateKey of sortedDates) { const dayHabits = g.logs[dateKey]; // Calculate streak up to this date to get multiplier let streakAtDate = 0; let d = new Date(dateKey); d.setDate(d.getDate() - 1); while (true) { const k = d.toISOString().split('T')[0]; const dl = g.logs[k]; if (!dl || dl.length < Math.ceil(g.plan.length * 0.5)) break; streakAtDate++; d.setDate(d.getDate() - 1); } let mult = 1; if (streakAtDate >= 365) mult = 3; else if (streakAtDate >= 90) mult = 2.5; else if (streakAtDate >= 30) mult = 2; else if (streakAtDate >= 7) mult = 1.5; for (const hid of dayHabits) { const habit = g.plan.find(h => h.id === hid); if (habit) total += habit.dailyImpact * mult; } } g.totalDaysAdded = total; } function setLongevityGoal(targetAge) { const plan = generateCheatDeathPlan(state.result, targetAge); state.longevityGoal = { targetAge, currentProjected: parseFloat(state.result.adjustedLE), plan, logs: {}, totalDaysAdded: 0, createdAt: new Date().toISOString() }; saveGoalState(); showTab('myplan'); } function renderGoalSetup() { const r = state.result; const currentAge = parseFloat(r.adjustedLE); return `

Set Your Longevity Goal

You are currently projected to live to age ${r.adjustedLE}. How long do you want to live?

`; } function updateGoalPreview(val) { document.getElementById('goalAgeNum').value = val; const r = state.result; const currentAge = parseFloat(r.adjustedLE); const el = document.getElementById('goalPreview'); if (val > 100) { el.innerHTML = '
You can\'t cheat death.
But at least you can avoid it for longer than most of us. Cap is 100.
'; document.getElementById('goalAgeNum').value = 100; document.getElementById('goalAge').value = 100; return; } const gap = val - currentAge; let feasClass = 'green', feasText = 'Achievable with lifestyle changes'; if (gap > 15) { feasClass = 'red'; feasText = 'Extraordinary effort required'; } else if (gap > 8) { feasClass = 'gold'; feasText = 'Ambitious but possible'; } else if (gap <= 0) { feasClass = 'green'; feasText = 'You are already on track!'; } el.innerHTML = `
You need to add ${gap > 0 ? gap.toFixed(1) : '0'} years
${feasText}
`; } function renderMyPlanTab(el) { const g = state.longevityGoal; // If no goal set yet, show setup if (!g) { if (!state.result) { el.innerHTML = '

Complete the calculator first.

'; return; } el.innerHTML = renderGoalSetup(); setTimeout(() => updateGoalPreview(document.getElementById('goalAgeNum')?.value || 85), 50); return; } const today = getTodayKey(); const todayLog = g.logs[today] || []; const { streak, multiplier } = getStreakInfo(); const habitsLoggedToday = todayLog.length; const totalHabits = g.plan.length; const todayDaysAdded = todayLog.reduce((sum, hid) => { const h = g.plan.find(x => x.id === hid); return sum + (h ? h.dailyImpact * multiplier : 0); }, 0); // Calculate projected death date shift const shiftedDeath = new Date(state.result.deathDate.getTime() + g.totalDaysAdded * 24*60*60*1000); const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; const newDateStr = months[shiftedDeath.getMonth()] + ' ' + shiftedDeath.getDate() + ', ' + shiftedDeath.getFullYear(); // Progress toward goal const goalGap = g.targetAge - g.currentProjected; const daysNeeded = goalGap * 365.25; const progress = daysNeeded > 0 ? Math.min(100, (g.totalDaysAdded / daysNeeded) * 100) : 100; // Build heatmap (last 28 days) let heatmapHtml = ''; for (let i = 27; i >= 0; i--) { const d = new Date(); d.setDate(d.getDate() - i); const k = d.toISOString().split('T')[0]; const dayLog = g.logs[k] || []; let cls = 'missed'; if (dayLog.length >= totalHabits) cls = 'full'; else if (dayLog.length > 0) cls = 'partial'; heatmapHtml += '
'; } el.innerHTML = `
${generateDeathy({...getDeathyParams(), healthScore: Math.min(100, calcDeathyHealth(getDeathyParams()) + Math.min(20, (g.totalDaysAdded||0)*0.5))})}
Ghost Health: ${Math.round(Math.min(100, calcDeathyHealth(getDeathyParams()) + Math.min(20, (g.totalDaysAdded||0)*0.5)))}/100
👑
+${(g.totalDaysAdded * 1440).toFixed(0)} minutes
added to your life (${g.totalDaysAdded.toFixed(1)} days)
${streak > 0 ? '
\u{1F525} ' + streak + ' day streak (' + multiplier + 'x multiplier)
' : '
Start your streak by logging all habits today
'}
+${(todayDaysAdded * 1440).toFixed(1)} min
Today
${habitsLoggedToday}/${totalHabits}
Habits Today
${progress.toFixed(1)}%
Goal Progress
${g.targetAge}
Target Age
Today's Habits

Tap to log each habit. Complete 50%+ to maintain your streak.

${g.plan.map((h, idx) => { const isPremiumLocked = state.userTier === 'free' && idx > 0; const logged = todayLog.includes(h.id); const impactMin = (h.dailyImpact * multiplier * 1440).toFixed(1); const impactStr = '+' + impactMin + ' min'; if (isPremiumLocked) { return '
' + '
' + '
' + '
' + (h.icon || '') + ' ' + h.name + '
' + '
' + h.difficulty + ' | ' + h.source + '
' + '
' + '
' + impactStr + '
' + '
'; } return '
' + '
' + (logged ? '✓' : '') + '
' + '
' + '
' + (h.icon || '') + ' ' + h.name + '
' + '
' + h.difficulty + ' | ' + h.source + '
' + '
' + '
' + impactStr + '
' + '
'; }).join('')} ${state.userTier === 'free' && g.plan.length > 1 ? `
🔒 ${g.plan.length - 1} more habits locked

Your free plan shows 1 habit. Unlock your full Cheat Death Plan with all ${g.plan.length} personalised habits.

` : ''}
Death Date Shift
Original: ${state.result.deathDate.toLocaleDateString()} → New: ${newDateStr}
${progress.toFixed(1)}% toward living to ${g.targetAge}
Last 28 Days
${heatmapHtml}
All done Partial Missed
`; } // ============================================ // WAITLIST + EMAIL + SHARE + SOCIAL CIRCLE // ============================================ function joinWaitlist() { const email = prompt('Enter your email to join the consultation waiting list:'); if (!email || !email.includes('@')) return; localStorage.setItem('dc_waitlist_email', email); const el = document.getElementById('waitlistCount'); if (el) el.innerHTML = '✓ You\'re on the list! We\'ll email you at ' + email + ' when a slot opens.'; alert('You\'re in! Position #128 on the waiting list. We\'ll reach out when a healthspan specialist is available.'); } function captureEmail() { const input = document.getElementById('emailCaptureInput'); if (!input) return; const email = input.value.trim(); if (!email || !email.includes('@')) { input.style.borderColor = 'var(--accent)'; return; } localStorage.setItem('dc_user_email', email); const prefs = { weekly_report: document.getElementById('emailPrefWeekly')?.checked || false, milestone_alerts: document.getElementById('emailPrefMilestone')?.checked || false, tips: document.getElementById('emailPrefTips')?.checked || false }; localStorage.setItem('dc_email_prefs', JSON.stringify(prefs)); const container = document.getElementById('emailCaptureSection'); if (container) container.innerHTML = '
✓ Locked in. We\'ll haunt your inbox at ' + email + '
'; } function generateShareText() { if (!state.result) return ''; const r = state.result; const dd = r.deathDate; const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; return 'I just found out I\'m dying on ' + months[dd.getMonth()] + ' ' + dd.getDate() + ', ' + dd.getFullYear() + '. That\'s ' + r.remainingYears + ' years left. My Life Score: ' + r.lifeScore + '/100. ' + 'Think you\'ll outlive me? death-clock.app'; } function shareResult(platform) { const text = encodeURIComponent(generateShareText()); const url = encodeURIComponent('https://death-clock.app'); const links = { twitter: 'https://twitter.com/intent/tweet?text=' + text, facebook: 'https://www.facebook.com/sharer/sharer.php?u=' + url + '"e=' + text, whatsapp: 'https://wa.me/?text=' + text, linkedin: 'https://www.linkedin.com/sharing/share-offsite/?url=' + url, copy: null }; if (platform === 'copy') { navigator.clipboard.writeText(decodeURIComponent(text)).then(() => { const btn = document.querySelector('.share-btn-copy'); if (btn) { btn.textContent = 'Copied!'; setTimeout(() => btn.textContent = 'Copy', 2000); } }); return; } if (links[platform]) window.open(links[platform], '_blank', 'width=600,height=400'); } function generateInviteCode() { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; let code = 'DC-'; for (let i = 0; i < 6; i++) code += chars[Math.floor(Math.random() * chars.length)]; return code; } function getOrCreateInviteCode() { let code = localStorage.getItem('dc_invite_code'); if (!code) { code = generateInviteCode(); localStorage.setItem('dc_invite_code', code); } return code; } function getSocialCircle() { return JSON.parse(localStorage.getItem('dc_social_circle') || '[]'); } function saveSocialCircle(circle) { localStorage.setItem('dc_social_circle', JSON.stringify(circle)); } function addFriendToCircle(name, deathYear, lifeScore) { const circle = getSocialCircle(); circle.push({ name, deathYear, lifeScore, addedAt: new Date().toISOString() }); saveSocialCircle(circle); } function showInviteModal() { const code = getOrCreateInviteCode(); const inviteUrl = 'https://death-clock.app?ref=' + code; document.getElementById('modal').classList.remove('hidden'); document.getElementById('modalContent').innerHTML = `

Invite Friends to Death Clock

Challenge your friends. See who dies first. (Dark? Yes. Motivating? Also yes.)

Your invite link

When friends complete the quiz with your link, they'll appear in your Social Circle leaderboard.

Add a friend manually

`; } function addFriendManual() { const name = document.getElementById('friendName')?.value.trim(); const deathYear = parseInt(document.getElementById('friendDeathYear')?.value); const score = parseInt(document.getElementById('friendScore')?.value); if (!name || !deathYear) { alert('Need at least a name and death year.'); return; } addFriendToCircle(name, deathYear, score || 0); closeModal(); if (state.currentPage === 'result') renderResult(); else if (state.currentPage === 'dashboard') renderDashboard(); } function renderSocialCircle() { // Show choice: global leaderboard or friends circle const circle = getSocialCircle(); const hasCircle = circle.length > 0; const showGlobal = localStorage.getItem('dc_leaderboard_pref') === 'global'; if (!hasCircle && !showGlobal) { return `

☠ Who Dies First?

Competition makes everything better. Even death.

`; } return renderSocialCircleInner(); } function renderSocialCircleInner() { const circle = getSocialCircle(); if (!state.result) return ''; const myYear = state.result.deathDate.getFullYear(); const myName = 'You'; const all = [{ name: myName, deathYear: myYear, lifeScore: state.result.lifeScore, isMe: true }, ...circle]; all.sort((a, b) => b.deathYear - a.deathYear); if (circle.length === 0) { return `

Social Circle Leaderboard

Who in your friend group dies first? Invite friends to find out.

`; } return `

Social Circle: Who Dies First?

${all.map((p, i) => { const medal = i === 0 ? '👑' : i === all.length - 1 ? '💀' : ''; const rowStyle = p.isMe ? 'border-color:var(--gold); background:rgba(240,192,64,0.05);' : ''; return ``; }).join('')}
`; } // ============================================ // DEATHY AVATAR ENGINE // ============================================ function generateDeathy(params = {}) { const p = { bmi: params.bmi || 'healthy', age: params.age || 35, smoking: params.smoking || 'never', exercise: params.exercise || '3-4x', diet: params.diet || 'healthy', alcohol: params.alcohol || 'never', drugs: params.drugs || 'none', stress: params.stress || 'moderate', social: params.social || 'moderate', sleep: params.sleep || 'optimal', sleepQuality: params.sleepQuality || 'good', conditions: params.conditions || [], healthcare: params.healthcare || 'regular', activity: params.activity || 'none', region: params.region || 'neutral', healthScore: params.healthScore || null, }; if (p.healthScore === null) p.healthScore = calcDeathyHealth(p); const hScore = Math.max(0, Math.min(100, p.healthScore)); // Unique ID suffix to avoid SVG gradient collisions const uid = Math.random().toString(36).substr(2,6); // Body width from BMI const bodyWidths = { underweight:0.6, healthy:0.85, overweight:1.05, obese:1.3, severely_obese:1.55 }; const bodyW = bodyWidths[p.bmi] || 0.85; // Ghost color degrades with poor health const ghostR = Math.round(255 - (100-hScore)*0.8); const ghostG = Math.round(255 - (100-hScore)*1.2); const ghostB = Math.round(255 - (100-hScore)*0.6); const ghostColor = `rgb(${ghostR},${ghostG},${ghostB})`; const shadowR = Math.round(200 - (100-hScore)*1.0); const shadowG = Math.round(210 - (100-hScore)*1.5); const shadowB = Math.round(220 - (100-hScore)*0.8); const shadowColor = `rgb(${shadowR},${shadowG},${shadowB})`; let glowColor, glowOpacity; if (hScore >= 80) { glowColor='#4ecca3'; glowOpacity=0.3; } else if (hScore >= 60) { glowColor='#f9ed69'; glowOpacity=0.2; } else if (hScore >= 40) { glowColor='#f38181'; glowOpacity=0.2; } else { glowColor='#6c5b7b'; glowOpacity=0.15; } // Eye state let eyeStyle = 'normal'; if (p.sleep === 'short' || p.sleepQuality === 'poor') eyeStyle = 'tired'; if (p.alcohol === 'heavy' || p.drugs === 'recreational' || p.drugs === 'opioids') eyeStyle = 'bloodshot'; if (p.exercise === '5+' && (p.diet === 'very_healthy' || p.diet === 'healthy')) eyeStyle = 'bright'; if (p.stress === 'very_high') eyeStyle = 'worried'; // Mouth let mouthStyle = 'happy'; if (hScore >= 80) mouthStyle = 'happy'; else if (hScore >= 60) mouthStyle = 'slight_smile'; else if (hScore >= 40) mouthStyle = 'neutral'; else if (hScore >= 20) mouthStyle = 'worried'; else mouthStyle = 'sad'; if (p.smoking === 'current_heavy' || p.smoking === 'current_light') mouthStyle = 'smoking'; const showBones = hScore < 25; let ageLines = 0; if (p.age > 50) ageLines = 1; if (p.age > 65) ageLines = 2; if (p.age > 80) ageLines = 3; const cx = 100, cy = 95; let svg = ``; svg += ``; svg += ``; svg += ``; svg += ``; // Glow svg += ``; // Body const bw = 50*bodyW, tailY = cy+65, wa = 8; svg += ``; // Bones if (showBones) { svg += ``; svg += ``; } // Eyes const eyeY = cy-20, eyeS = 16*Math.min(bodyW,1.1); svg += ``; svg += ``; if (eyeStyle === 'bright') { svg += ``; svg += ``; } if (eyeStyle === 'tired') { svg += ``; svg += ``; } if (eyeStyle === 'bloodshot') { svg += ``; svg += ``; } if (eyeStyle === 'worried') { svg += ``; svg += ``; } // Mouth const mY = cy+8; if (mouthStyle === 'happy') { svg += ``; svg += ``; } else if (mouthStyle === 'slight_smile') { svg += ``; } else if (mouthStyle === 'neutral') { svg += ``; } else if (mouthStyle === 'worried') { svg += ``; } else if (mouthStyle === 'sad') { svg += ``; } else if (mouthStyle === 'smoking') { svg += ``; svg += ``; svg += ``; svg += ``; } // Age lines if (ageLines >= 1) svg += ``; if (ageLines >= 2) svg += ``; // Activity accessories if (p.activity === 'tennis' || p.activity === 'running' || p.activity === 'gym') { svg += ``; } if (p.activity === 'yoga') { svg += ``; svg += ``; } if (p.activity === 'swimming') { svg += ``; svg += ``; svg += ``; } if (p.activity === 'cycling') { svg += ``; } if (p.activity === 'soccer') { svg += ``; } // Lifestyle indicators if (p.exercise === '5+') { svg += ``; svg += ``; } if (p.alcohol === 'heavy') { svg += ``; svg += ``; } if (p.sleep === 'short' || p.sleepQuality === 'poor') { svg += `z`; svg += `z`; } if (p.social === 'strong') { svg += ``; } if (p.social === 'isolated') { svg += ``; } if (p.stress === 'low' && p.exercise !== 'none') { svg += ``; } if (p.stress === 'very_high') { svg += ``; } if (p.conditions && p.conditions.includes('heart_disease')) { svg += ``; } svg += ``; return svg; } function calcDeathyHealth(p) { let score = 75; const bmiS = { underweight:-5, healthy:0, overweight:-3, obese:-10, severely_obese:-20 }; score += (bmiS[p.bmi]||0); const smokS = { never:0, former:-5, current_light:-15, current_heavy:-25 }; score += (smokS[p.smoking]||0); const exS = { '5+':12, '3-4x':8, '1-2x':3, none:-12 }; score += (exS[p.exercise]||0); const dietS = { very_healthy:12, healthy:6, average:0, poor:-12 }; score += (dietS[p.diet]||0); const alcS = { never:0, occasional:0, moderate:-2, heavy:-12 }; score += (alcS[p.alcohol]||0); const drugS = { none:0, cannabis:-3, recreational:-10, opioids:-20 }; score += (drugS[p.drugs]||0); const strS = { low:5, moderate:0, high:-5, very_high:-12 }; score += (strS[p.stress]||0); const socS = { strong:10, moderate:5, few:-5, isolated:-18 }; score += (socS[p.social]||0); const slpS = { optimal:0, moderate_short:-3, short:-8, long:-4 }; score += (slpS[p.sleep]||0); const condP = { diabetes:-10, heart_disease:-12, hypertension:-8, cancer:-6, stroke:-15, copd:-10, kidney_disease:-8, autoimmune:-5 }; (p.conditions||[]).forEach(c => { score += (condP[c]||0); }); return Math.max(5, Math.min(100, score)); } // Map user's calculator answers to Deathy params function getDeathyParams() { const a = state.answers; // BMI let bmi = 'healthy'; if (a.height_cm && a.weight_kg) { const bmiVal = a.weight_kg / ((a.height_cm/100)**2); if (bmiVal < 18.5) bmi = 'underweight'; else if (bmiVal < 25) bmi = 'healthy'; else if (bmiVal < 30) bmi = 'overweight'; else if (bmiVal < 40) bmi = 'obese'; else bmi = 'severely_obese'; } // Age let age = 30; if (a.dob) { const bd = new Date(a.dob); age = Math.floor((Date.now() - bd.getTime()) / (365.25*24*60*60*1000)); } // Conditions mapping const condMap = { 'Diabetes':'diabetes', 'Heart Disease':'heart_disease', 'Hypertension':'hypertension', 'Cancer (current/remission)':'cancer', 'Stroke History':'stroke', 'COPD':'copd', 'Chronic Kidney Disease':'kidney_disease', 'Autoimmune Condition':'autoimmune' }; const conditions = (a.conditions||[]).filter(c=>c!=='None').map(c=>condMap[c]||'').filter(Boolean); return { bmi, age, smoking: a.smoking || 'never', exercise: a.exercise || '3-4x', diet: a.diet || 'healthy', alcohol: a.alcohol || 'never', drugs: a.drugs || 'none', stress: a.stress || 'moderate', social: a.social || 'moderate', sleep: a.sleep_hours || 'optimal', sleepQuality: a.sleep_quality || 'good', conditions, healthcare: a.healthcare || 'regular', activity: a.sport || 'none', region: 'neutral' }; } // Save/load deathy state function getDeathyState() { try { return JSON.parse(localStorage.getItem('dc_deathy_state')) || {}; } catch(e) { return {}; } } function saveDeathyState(ds) { localStorage.setItem('dc_deathy_state', JSON.stringify(ds)); } // ============================================ // DEATHY PERSONALITY ENGINE - 30+ phrases // ============================================ function getDeathyMessage() { const ds = getDeathyState(); const g = state.longevityGoal; const now = Date.now(); const lastVisit = ds.lastVisit || now; const daysSinceVisit = Math.floor((now - lastVisit) / (24*60*60*1000)); const { streak } = g ? getStreakInfo() : { streak: 0 }; const params = getDeathyParams(); const hScore = calcDeathyHealth(params); // Priority-ordered message selection const messages = []; // First ever visit if (!ds.lastVisit) { messages.push({ msg: "Well, well, well. Fresh meat. Let me take a good look at what I'm working with here...", mood: 'curious' }); messages.push({ msg: "Oh great, another human who thinks they're immortal. Spoiler: you're not.", mood: 'sarcastic' }); messages.push({ msg: "I'm your personal ghost. I get healthier when you do. No pressure, but also... all the pressure.", mood: 'intro' }); } // Absent for 7+ days if (daysSinceVisit >= 7) { messages.push({ msg: "You were gone for " + daysSinceVisit + " days. I was literally fading away. Do you even care about me?", mood: 'guilt' }); messages.push({ msg: daysSinceVisit + " days without checking in. At this rate YOU'LL be the ghost, not me.", mood: 'dark' }); messages.push({ msg: "Oh you're back? I was starting to pick out your headstone font.", mood: 'snarky' }); } // Absent 3-6 days else if (daysSinceVisit >= 3) { messages.push({ msg: "Three days without you. I've been haunting an empty tab like a sad Tamagotchi.", mood: 'needy' }); messages.push({ msg: "Miss me? Because your life expectancy certainly didn't miss those " + daysSinceVisit + " days of bad habits.", mood: 'dark' }); } // Based on health score if (hScore >= 85) { messages.push({ msg: "Look at you, all glowing and healthy. I'm almost too pretty to be a ghost.", mood: 'proud' }); messages.push({ msg: "My glow-up is YOUR glow-up. We're basically the same entity at this point.", mood: 'happy' }); messages.push({ msg: "I'm radiating so much health energy right now, other ghosts are jealous.", mood: 'flex' }); } else if (hScore >= 65) { messages.push({ msg: "Not bad, not bad. You're keeping me in decent shape. Could be glowier though.", mood: 'ok' }); messages.push({ msg: "I'd say I'm a solid 7 out of 10 ghost right now. Help me hit a 9?", mood: 'hopeful' }); } else if (hScore >= 45) { messages.push({ msg: "I'm looking a bit grey lately. That's YOUR fault by the way.", mood: 'blame' }); messages.push({ msg: "My bones are showing through. That's... not a fashion statement.", mood: 'worried' }); messages.push({ msg: "Remember when I used to glow green? Yeah, me neither at this point.", mood: 'sad' }); } else { messages.push({ msg: "I can see my own skeleton. This is fine. Everything is fine. (It's not fine.)", mood: 'panic' }); messages.push({ msg: "I'm literally decomposing in real-time. Is that burger really worth it?", mood: 'desperate' }); messages.push({ msg: "At this rate, I'll be haunting YOUR funeral instead of the other way around.", mood: 'grim' }); } // Streak-based if (streak >= 30) { messages.push({ msg: streak + " day streak?! I haven't looked this good since... well, ever. I'm a ghost, I was born dead.", mood: 'ecstatic' }); } else if (streak >= 7) { messages.push({ msg: streak + " days straight! I'm starting to feel things. Emotions. Is this what being alive is like?", mood: 'excited' }); } else if (streak === 0 && g && g.plan && g.plan.length > 0) { messages.push({ msg: "Zero streak. ZERO. You're literally killing your own ghost. Think about that.", mood: 'disappointed' }); messages.push({ msg: "No habits logged today? Cool cool cool. I'll just sit here slowly dying. Again.", mood: 'passive_aggressive' }); } // Smoking specific if (params.smoking === 'current_heavy') { messages.push({ msg: "Dude, I'm a ghost and even I think those cigarettes smell bad.", mood: 'cough' }); messages.push({ msg: "Every cigarette takes 11 minutes off YOUR life and makes ME uglier. Quit. Please.", mood: 'begging' }); } // Alcohol heavy if (params.alcohol === 'heavy') { messages.push({ msg: "My eyes are bloodshot and I don't even have blood. Thanks for the liver damage proxy.", mood: 'hungover' }); } // Isolated if (params.social === 'isolated') { messages.push({ msg: "You listed your social life as 'isolated'. I'm literally your most active relationship. That's concerning.", mood: 'awkward' }); } // Sedentary if (params.exercise === 'none') { messages.push({ msg: "You don't exercise at all? My wavy ghost tail gets more movement than you do.", mood: 'shade' }); } // Perfect health if (hScore >= 95) { messages.push({ msg: "I'm basically a wellness influencer at this point. #GhostGains #DeathClockApproved", mood: 'influencer' }); } // Premium upsell moments if (state.userTier === 'free' && g && g.plan && g.plan.length > 1) { messages.push({ msg: "I've got " + (g.plan.length - 1) + " more life-saving habits locked up. Upgrade to Premium and watch me GLOW.", mood: 'sales' }); } // Random daily vibes messages.push({ msg: "Every minute you're alive is a minute I stay cute. So stay alive, yeah?", mood: 'sweet' }); messages.push({ msg: "Did you know the average person spends 6 months of their life waiting for red lights? You could be exercising instead.", mood: 'factoid' }); messages.push({ msg: "Fun fact: I'm the only ghost who WANTS you to live longer. Most ghosts want company.", mood: 'wholesome' }); // Pick one (weighted toward priority messages at top) const idx = Math.floor(Math.random() * Math.min(messages.length, 4)); return messages[idx] || messages[0]; } // Render Deathy companion with speech bubble function renderDeathyCompanion(size) { const params = getDeathyParams(); const hScore = calcDeathyHealth(params); const svg = generateDeathy({...params, healthScore: hScore}); const msg = getDeathyMessage(); const ds = getDeathyState(); ds.lastVisit = Date.now(); ds.healthScore = hScore; saveDeathyState(ds); return `
${msg.msg}
${svg}
Health Score: ${hScore}/100 ${hScore < 50 ? ' | I need help!' : ''}
`; } function refreshDeathySpeech() { const container = document.querySelector('.deathy-companion'); if (!container) return; const msg = getDeathyMessage(); const bubble = container.querySelector('.deathy-speech'); if (bubble) { bubble.style.animation = 'none'; bubble.offsetHeight; // trigger reflow bubble.style.animation = 'deathyFadeIn 0.5s ease'; bubble.childNodes[0].textContent = msg.msg; } } // Deathy evolution: recalculate when habits change function updateDeathyAvatar() { const el = document.getElementById('deathyAvatarPanel'); if (!el) return; // Recalc with habit bonuses const params = getDeathyParams(); const g = state.longevityGoal; if (g && g.totalDaysAdded > 0) { // Boost health score based on days added (max +20 bonus) const habitBonus = Math.min(20, g.totalDaysAdded * 0.5); params.healthScore = Math.min(100, calcDeathyHealth(params) + habitBonus); } const svg = generateDeathy(params); const hScore = params.healthScore || calcDeathyHealth(params); el.querySelector('.deathy-svg-wrap').innerHTML = svg; const scoreEl = el.querySelector('.deathy-health-num'); if (scoreEl) { scoreEl.textContent = Math.round(hScore) + '/100'; scoreEl.style.color = hScore>=70?'var(--green)':hScore>=40?'var(--gold)':'var(--accent)'; } } // ============================================ // INIT // ============================================ document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }); checkCookieConsent(); loadGoalState(); loadUserTier(); // PWA: Register service worker + install prompt let deferredInstallPrompt = null; if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').catch(() => {}); } window.addEventListener('beforeinstallprompt', e => { e.preventDefault(); deferredInstallPrompt = e; // Show install banner after result setTimeout(() => { if (state.result && deferredInstallPrompt) { const banner = document.createElement('div'); banner.id = 'installBanner'; banner.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:var(--surface);border:1px solid var(--gold);border-radius:12px;padding:12px 20px;z-index:9999;display:flex;align-items:center;gap:12px;box-shadow:0 4px 20px rgba(0,0,0,0.5);'; banner.innerHTML = '
Install Death Clock
Track your death date from your home screen
' + '' + ''; document.body.appendChild(banner); } }, 3000); }); function installPWA() { if (!deferredInstallPrompt) return; deferredInstallPrompt.prompt(); deferredInstallPrompt.userChoice.then(() => { deferredInstallPrompt = null; document.getElementById('installBanner')?.remove(); }); } // Auto-load saved profile if (loadProfile()) { // Show welcome back message on home page const heroP = document.querySelector('.hero p'); if (heroP) { const savedAt = localStorage.getItem('deathclock_profile'); if (savedAt) { try { const profile = JSON.parse(savedAt); const savedDate = new Date(profile.savedAt).toLocaleDateString(); heroP.insertAdjacentHTML('afterend', '
' + 'Welcome back! Your profile from ' + savedDate + ' is loaded. ' + 'View Dashboard
'); } catch(e) {} } } }