`;
elements.resultsOutput.innerHTML += card;
});
elements.resultsSection.classList.remove('hidden');
};
// --- Event Handlers ---
const handleTabClick = (tabNumber) => {
currentTab = tabNumber;
updateTabView();
};
const handleCalculateWakeup = () => {
const bedtimeValue = elements.bedtimeInput.value;
if (!bedtimeValue) {
displayResults("Please select a bedtime.", []);
return;
}
const startTime = parseTime(bedtimeValue);
startTime.setMinutes(startTime.getMinutes() + TIME_TO_FALL_ASLEEP_MINS);
const results = CYCLES_TO_CALCULATE.map(cycles => {
const wakeupTime = new Date(startTime.getTime());
wakeupTime.setMinutes(wakeupTime.getMinutes() + (cycles * SLEEP_CYCLE_MINS));
return { time: formatTime(wakeupTime), cycles: cycles };
});
displayResults(`If you go to bed at ${formatTime(parseTime(bedtimeValue))}, wake up at:`, results);
};
const handleCalculateBedtime = () => {
const wakeupTimeValue = elements.wakeupTimeInput.value;
if (!wakeupTimeValue) {
displayResults("Please select a wake-up time.", []);
return;
}
const endTime = parseTime(wakeupTimeValue);
const results = CYCLES_TO_CALCULATE.map(cycles => {
const bedtime = new Date(endTime.getTime());
const totalSleepDuration = (cycles * SLEEP_CYCLE_MINS) + TIME_TO_FALL_ASLEEP_MINS;
bedtime.setMinutes(bedtime.getMinutes() - totalSleepDuration);
return { time: formatTime(bedtime), cycles: cycles };
});
displayResults(`To wake up at ${formatTime(parseTime(wakeupTimeValue))}, go to sleep at:`, results);
};
/**
* Generates a professional, visually appealing PDF report of the sleep data.
*/
const handlePdfDownload = () => {
if (typeof window.jspdf === 'undefined') {
console.error("jsPDF library is not loaded. Cannot generate PDF.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// --- Report Constants ---
const reportDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const primaryColor = '#2563EB'; // A calming blue
const headerBgColor = '#F3F4F6'; // Light gray background
const cardBgColor = '#FFFFFF'; // White for cards
const textColor = '#1F2937'; // Dark gray for text
const lightTextColor = '#6B7280'; // Lighter gray for subtitles
// --- Header ---
doc.setFillColor(headerBgColor);
doc.rect(0, 0, 210, 40, 'F'); // Header background
// Draw a simple crescent moon logo
doc.setFillColor(primaryColor);
doc.setDrawColor(primaryColor);
doc.circle(23, 20, 8, 'FD'); // Full circle
doc.setFillColor(headerBgColor);
doc.setDrawColor(headerBgColor);
doc.circle(26, 20, 8, 'FD'); // Occluding circle to create crescent
doc.setFont('helvetica', 'bold');
doc.setFontSize(18);
doc.setTextColor(textColor);
doc.text('Sleep Analysis Report', 40, 18);
doc.setFont('helvetica', 'normal');
doc.setFontSize(10);
doc.setTextColor(lightTextColor);
doc.text(`Generated on: ${reportDate}`, 40, 25);
doc.text('Patient: Anonymous User', 40, 30);
// --- Report Body ---
doc.setFontSize(14);
doc.setFont('helvetica', 'bold');
doc.setTextColor(textColor);
doc.text('Personalized Sleep Recommendations', 14, 55);
doc.setFontSize(11);
doc.setFont('helvetica', 'normal');
doc.setTextColor(lightTextColor);
doc.text(lastResults.title, 14, 62);
// --- Results Visualization Cards ---
let yPos = 75;
lastResults.data.forEach((result) => {
doc.setFillColor(cardBgColor);
doc.setDrawColor('#E5E7EB'); // Light border for the card
doc.roundedRect(14, yPos, 182, 25, 3, 3, 'FD');
doc.setFontSize(16);
doc.setFont('helvetica', 'bold');
doc.setTextColor(primaryColor);
doc.text(result.time, 20, yPos + 16);
doc.setFontSize(11);
doc.setFont('helvetica', 'normal');
doc.setTextColor(textColor);
doc.text(`${result.cycles} Sleep Cycles`, 80, yPos + 16);
const totalHours = Math.floor((result.cycles * 90) / 60);
const totalMinutes = (result.cycles * 90) % 60;
const sleepDuration = `${totalHours}h ${totalMinutes}m of sleep`;
doc.setTextColor(lightTextColor);
doc.text(`(approx. ${sleepDuration})`, 140, yPos + 16);
yPos += 30;
});
// --- Educational Section ---
yPos += 5;
doc.setDrawColor(headerBgColor);
doc.line(14, yPos, 196, yPos);
yPos += 10;
doc.setFontSize(12);
doc.setFont('helvetica', 'bold');
doc.setTextColor(textColor);
doc.text('Understanding Your Sleep Cycles', 14, yPos);
yPos += 7;
doc.setFontSize(10);
doc.setFont('helvetica', 'normal');
const explanationText = `A complete sleep cycle lasts about 90 minutes and involves several stages, including Deep Sleep and REM (Rapid Eye Movement) sleep. Waking up at the end of a cycle, rather than in the middle of one, helps you feel more refreshed and alert. This report calculates optimal wake-up times by aligning them with the natural conclusion of these 90-minute cycles.`;
const splitText = doc.splitTextToSize(explanationText, 182);
doc.text(splitText, 14, yPos);
// --- Footer ---
doc.setFillColor(headerBgColor);
doc.rect(0, 277, 210, 20, 'F');
doc.setFontSize(9);
doc.setTextColor(lightTextColor);
doc.text('Generated by REM Sleep Cycle Tracker', 105, 286, null, null, 'center');
doc.text('This report is for informational purposes and not a substitute for professional medical advice.', 105, 291, null, null, 'center');
doc.save('REM-Sleep-Analysis-Report.pdf');
};
// --- Event Listener Attachment ---
elements.tab1Btn.addEventListener('click', () => handleTabClick(1));
elements.tab2Btn.addEventListener('click', () => handleTabClick(2));
elements.prevBtn.addEventListener('click', () => { currentTab = currentTab === 1 ? 2 : 1; updateTabView(); });
elements.nextBtn.addEventListener('click', () => { currentTab = currentTab === 1 ? 2 : 1; updateTabView(); });
elements.calculateWakeupBtn.addEventListener('click', handleCalculateWakeup);
elements.calculateBedtimeBtn.addEventListener('click', handleCalculateBedtime);
elements.pdfDownloadBtn.addEventListener('click', handlePdfDownload);
// Set default time values
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
elements.bedtimeInput.value = `${hours}:${minutes}`;
elements.wakeupTimeInput.value = `${hours}:${minutes}`;
updateTabView();
});
