HRV Breathing Trainer

HRV Breathing Trainer

Follow the guide to practice resonant breathing and improve your heart rate variability.

Press Start

Session Time: 00:00

Breathing Cycle Settings

Simulated HRV Score (ms): ${simulatedHrvScore}

`; } /** * Generates and triggers the download of a PDF report. */ async function generatePdf() { const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); // --- Data Preparation --- const reportDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const reportTime = new Date().toLocaleTimeString('en-US'); const totalMinutes = (sessionSeconds / 60).toFixed(2); const simulatedHrvScore = resultsSummary.querySelector('p:last-child').textContent.split(': ')[1]; const inhale = inhaleDurationInput.value; const hold = holdDurationInput.value; const exhale = exhaleDurationInput.value; // --- PDF Content Generation --- // Using addHTML is deprecated, so we'll build the PDF manually for better control and quality. // Header doc.setFont('Inter', 'bold'); doc.setFontSize(28); doc.setTextColor('#4A90E2'); doc.text('Heart Rate Variability (HRV)', 105, 25, { align: 'center' }); doc.setFont('Inter', 'normal'); doc.setFontSize(22); doc.setTextColor('#555555'); doc.text('Breathing Session Report', 105, 35, { align: 'center' }); doc.setLineWidth(0.5); doc.setDrawColor('#4A90E2'); doc.line(20, 45, 190, 45); // Session Summary Section doc.setFont('Inter', 'bold'); doc.setFontSize(18); doc.setTextColor('#4A90E2'); doc.text('Session Summary', 20, 60); doc.setLineWidth(0.2); doc.setDrawColor('#DDDDDD'); doc.line(20, 63, 190, 63); // Table-like structure for summary const summaryData = [ ['Report Date:', `${reportDate} at ${reportTime}`], ['Total Session Duration:', `${totalMinutes} minutes`], ['Simulated HRV Score (RMSSD):', `${simulatedHrvScore} ms`] ]; doc.setFont('Inter', 'normal'); doc.setFontSize(12); doc.setTextColor('#333333'); let startY = 72; summaryData.forEach(([label, value]) => { doc.setFont('Inter', 'bold'); doc.text(label, 25, startY); doc.setFont('Inter', 'normal'); doc.text(value, 90, startY); startY += 10; }); // Breathing Cycle Analysis Section doc.setFont('Inter', 'bold'); doc.setFontSize(18); doc.setTextColor('#4A90E2'); doc.text('Breathing Cycle Settings', 20, startY + 10); doc.line(20, startY + 13, 190, startY + 13); const cycleData = [ ['Inhale Duration:', `${inhale} seconds`], ['Hold Duration:', `${hold} seconds`], ['Exhale Duration:', `${exhale} seconds`] ]; startY += 22; cycleData.forEach(([label, value]) => { doc.setFont('Inter', 'bold'); doc.text(label, 25, startY); doc.setFont('Inter', 'normal'); doc.text(value, 90, startY); startY += 10; }); // Simple SVG-like visualization of the breathing pattern const chartY = startY + 15; doc.setFont('Inter', 'bold'); doc.setFontSize(12); doc.text('Breathing Pattern Visualization:', 20, chartY); const totalCycleTime = parseFloat(inhale) + parseFloat(hold) + parseFloat(exhale); const chartWidth = 160; const chartHeight = 40; const chartX = 25; const chartYPos = chartY + 10; const inhaleWidth = (inhale / totalCycleTime) * chartWidth; const holdWidth = (hold / totalCycleTime) * chartWidth; doc.setDrawColor('#CCCCCC'); doc.setLineWidth(0.5); doc.rect(chartX, chartYPos, chartWidth, chartHeight); // Inhale phase doc.setFillColor('#60a5fa'); doc.rect(chartX, chartYPos, inhaleWidth, chartHeight, 'F'); doc.setTextColor('#FFFFFF'); doc.text('Inhale', chartX + inhaleWidth / 2, chartYPos + chartHeight / 2, { align: 'center', baseline: 'middle' }); // Hold phase if (hold > 0) { doc.setFillColor('#93c5fd'); doc.rect(chartX + inhaleWidth, chartYPos, holdWidth, chartHeight, 'F'); doc.setTextColor('#FFFFFF'); doc.text('Hold', chartX + inhaleWidth + holdWidth / 2, chartYPos + chartHeight / 2, { align: 'center', baseline: 'middle' }); } // Exhale phase doc.setFillColor('#2563eb'); doc.rect(chartX + inhaleWidth + holdWidth, chartYPos, chartWidth - inhaleWidth - holdWidth, chartHeight, 'F'); doc.setTextColor('#FFFFFF'); doc.text('Exhale', chartX + inhaleWidth + holdWidth + (chartWidth - inhaleWidth - holdWidth) / 2, chartYPos + chartHeight / 2, { align: 'center', baseline: 'middle' }); // Footer doc.setLineWidth(0.2); doc.setDrawColor('#DDDDDD'); doc.line(20, 270, 190, 270); doc.setFontSize(10); doc.setTextColor('#999999'); doc.text('This report is for informational purposes only and is not a substitute for professional medical advice.', 105, 278, { align: 'center' }); doc.text('HRV Breathing Trainer', 105, 283, { align: 'center' }); // --- Save PDF --- doc.save('HRV_Breathing_Report.pdf'); } // --- Event Listeners --- if (startStopBtn) { startStopBtn.addEventListener('click', toggleSession); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', generatePdf); } // Null checks for inputs const inputs = [inhaleDurationInput, holdDurationInput, exhaleDurationInput]; inputs.forEach(input => { if (input) { // Re-build cycle if settings change while not running input.addEventListener('change', () => { if (!isRunning) { buildBreathingCycle(); } }); } }); });
Scroll to Top