REM Sleep Cycle Tracker

Optimize your sleep by waking up between 90-minute sleep cycles.

Select the time you plan to go to bed. We'll calculate the best times to wake up.
(We account for the average 15 minutes it takes to fall asleep.)

${result.cycles} Sleep Cycles

`; 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(); });
Scroll to Top