"${log.notes}"
` : ''}${icon}
`;
}).join('');
renderConsistencyChart(recentLogs);
}
function calculateSleepScore(logs) {
// Duration Score (40%)
const durations = logs.map(log => {
const bedtime = new Date(`${log.date}T${log.bedtime}`);
const waketime = new Date(`${log.date}T${log.waketime}`);
if (waketime < bedtime) waketime.setDate(waketime.getDate() + 1);
return (waketime - bedtime) / 3600000;
});
const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
const durationDiff = Math.abs(avgDuration - settings.targetDuration);
const durationScore = Math.max(0, 100 - (durationDiff * 50)); // 1hr diff = 50 score
// Consistency Score (40%)
const bedtimeMinutes = logs.map(log => {
const [h, m] = log.bedtime.split(':').map(Number);
return h * 60 + m;
});
const avgBedtime = bedtimeMinutes.reduce((a, b) => a + b, 0) / bedtimeMinutes.length;
const bedtimeStdDev = Math.sqrt(bedtimeMinutes.map(x => Math.pow(x - avgBedtime, 2)).reduce((a, b) => a + b) / bedtimeMinutes.length);
const consistencyScore = Math.max(0, 100 - (bedtimeStdDev * 1.5)); // 30min std dev = 55 score
// Quality Score (20%)
const avgQuality = logs.reduce((a, b) => a + Number(b.quality), 0) / logs.length;
const qualityScore = (avgQuality / 5) * 100;
const totalScore = (durationScore * 0.4) + (consistencyScore * 0.4) + (qualityScore * 0.2);
return Math.round(totalScore);
}
function generateAIRecommendations(logs, score) {
const recs = [];
const avgDuration = logs.map(log => {
const bedtime = new Date(`${log.date}T${log.bedtime}`);
const waketime = new Date(`${log.date}T${log.waketime}`);
if (waketime < bedtime) waketime.setDate(waketime.getDate() + 1);
return (waketime - bedtime) / 3600000;
}).reduce((a, b) => a + b, 0) / logs.length;
if (score > 85) recs.push({type: 'success', text: "Excellent work! Your sleep schedule is consistent and healthy. Keep it up."});
if (Math.abs(avgDuration - settings.targetDuration) > 1) {
recs.push({type: 'warning', text: `Your average sleep of ${avgDuration.toFixed(1)} hours is more than an hour off your target. Adjust your bedtime or wake-up time to get closer to your goal.`});
} else {
recs.push({type: 'success', text: `Your average sleep duration is very close to your target of ${settings.targetDuration} hours.`});
}
const bedtimeMinutes = logs.map(log => {
const [h, m] = log.bedtime.split(':').map(Number);
return h * 60 + m;
});
const avgBedtime = bedtimeMinutes.reduce((a, b) => a + b, 0) / bedtimeMinutes.length;
const bedtimeStdDev = Math.sqrt(bedtimeMinutes.map(x => Math.pow(x - avgBedtime, 2)).reduce((a, b) => a + b) / bedtimeMinutes.length);
if (bedtimeStdDev > 45) {
recs.push({type: 'warning', text: `Your bedtime varies by over 45 minutes on average. Try to go to bed within the same 30-minute window each night, even on weekends.`});
} else {
recs.push({type: 'success', text: `Your bedtime consistency is great, which is key for a strong circadian rhythm.`});
}
return recs;
}
function renderConsistencyChart(logs) {
const ctx = document.getElementById('consistencyChart');
if (!ctx) return;
const labels = logs.map(log => new Date(log.date)).reverse();
const data = {
labels: labels,
datasets: [
{
label: 'Bedtime',
data: logs.map(log => new Date(`${log.date}T${log.bedtime}`)).reverse(),
borderColor: '#818CF8',
tension: 0.1
},
{
label: 'Wake-up Time',
data: logs.map(log => {
let bedtime = new Date(`${log.date}T${log.bedtime}`);
let waketime = new Date(`${log.date}T${log.waketime}`);
if (waketime < bedtime) waketime.setDate(waketime.getDate() + 1);
return waketime;
}).reverse(),
borderColor: '#FBBF24',
tension: 0.1
}
]
};
if (consistencyChart) consistencyChart.destroy();
consistencyChart = new Chart(ctx, {
type: 'line',
data: data,
options: {
responsive: true, maintainAspectRatio: false,
scales: {
x: { type: 'time', time: { unit: 'day' }, ticks: { color: '#94A3B8' } },
y: { type: 'time', time: { unit: 'hour', displayFormats: { hour: 'h a' } }, ticks: { color: '#94A3B8' } }
},
plugins: { legend: { labels: { color: '#E2E8F0' } } }
}
});
}
// --- PDF GENERATION --- //
function generatePDF() {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ unit: 'pt', format: 'a4' });
let y = 40;
const margin = 40;
const pdfWidth = pdf.internal.pageSize.getWidth();
pdf.setFontSize(20);
pdf.setFont('helvetica', 'bold');
pdf.text('Sleep Analysis Report', pdfWidth / 2, y, { align: 'center' });
y += 40;
pdf.setFontSize(12);
pdf.text(`Overall Sleep Score: ${sleepScoreStat.textContent}`, margin, y);
y += 40;
pdf.setFontSize(14);
pdf.setFont('helvetica', 'bold');
pdf.text('AI Recommendations', margin, y);
y += 20;
pdf.setFont('helvetica', 'normal');
pdf.setFontSize(10);
aiRecommendationsEl.querySelectorAll('p').forEach(p => {
const lines = pdf.splitTextToSize(p.textContent, pdfWidth - margin * 2);
pdf.text(lines, margin, y);
y += lines.length * 12 + 6;
});
y += 20;
pdf.setFontSize(14);
pdf.setFont('helvetica', 'bold');
pdf.text('Recent Sleep Logs', margin, y);
const headers = [['Date', 'Bedtime', 'Wake-up', 'Duration (h)', 'Quality (1-5)']];
const body = sleepLogs.slice(0, 14).map(log => {
const bedtime = new Date(`${log.date}T${log.bedtime}`);
const waketime = new Date(`${log.date}T${log.waketime}`);
if (waketime < bedtime) waketime.setDate(waketime.getDate() + 1);
const duration = ((waketime - bedtime) / 3600000).toFixed(1);
return [log.date, log.bedtime, log.waketime, duration, log.quality];
});
pdf.autoTable({
startY: y + 20,
head: headers,
body: body,
theme: 'grid',
headStyles: { fillColor: [79, 70, 229] }
});
pdf.save('Sleep-Report.pdf');
}
// --- TAB NAVIGATION --- //
function setActiveTab(index) {
tabs.forEach((tab, i) => tab.classList.toggle('active', i === index));
tabPanels.forEach((panel, i) => panel.classList.toggle('hidden', i !== index));
if (index === 0) updateDashboard();
}
// --- KICK IT OFF --- //
initializeApp();
});
${rec.text}
