diff --git a/4-vitals-monitor.html b/4-vitals-monitor.html
index 23f9b63..74b14b8 100644
--- a/4-vitals-monitor.html
+++ b/4-vitals-monitor.html
@@ -106,6 +106,41 @@
50% { opacity: 0; }
}
+ /* =========================================
+ ECG OVERLAY
+ ========================================= */
+
+ #ecg-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 1001;
+ pointer-events: none;
+ }
+
+ #ecg-overlay svg {
+ width: 100%;
+ height: 100%;
+ display: block;
+ }
+
+ .ecg-line {
+ fill: none;
+ stroke: #00ff41;
+ stroke-width: 2;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ filter: drop-shadow(0 0 4px rgba(0, 255, 65, 0.6));
+ }
+
+ .ecg-heartbeat {
+ fill: none;
+ stroke: #00ff41;
+ stroke-width: 2;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ filter: drop-shadow(0 0 6px rgba(0, 255, 65, 0.7));
+ }
+
/* =========================================
FINAL CV CONTENT (hidden behind boot)
========================================= */
@@ -127,6 +162,9 @@
+
+
+
@@ -189,16 +227,177 @@
}
}, bootEndTime);
- // After fade out (800ms), the boot screen stays black for the ECG phase (Task 2)
- // For now, just hide the boot screen after fade and reveal CV content
+ // After fade out (800ms), start the ECG phase
setTimeout(function() {
- var bootScreen = document.getElementById('boot-screen');
- bootScreen.style.display = 'none';
-
- var cvContent = document.getElementById('cv-content');
- cvContent.classList.add('revealed');
+ startECGPhase();
}, bootEndTime + 800);
+ /* =========================================
+ ECG PHASE: Flatline + First Heartbeat
+ ========================================= */
+
+ function startECGPhase() {
+ var overlay = document.getElementById('ecg-overlay');
+ var vw = window.innerWidth;
+ var vh = window.innerHeight;
+ var cy = Math.round(vh / 2);
+
+ // Create SVG
+ var svgNS = 'http://www.w3.org/2000/svg';
+ var svg = document.createElementNS(svgNS, 'svg');
+ svg.setAttribute('viewBox', '0 0 ' + vw + ' ' + vh);
+ svg.setAttribute('preserveAspectRatio', 'none');
+ overlay.appendChild(svg);
+
+ // --- FLATLINE ---
+ var flatlinePath = document.createElementNS(svgNS, 'path');
+ var flatlineD = 'M 0 ' + cy + ' L ' + vw + ' ' + cy;
+ flatlinePath.setAttribute('d', flatlineD);
+ flatlinePath.setAttribute('class', 'ecg-line');
+ svg.appendChild(flatlinePath);
+
+ var flatlineLen = flatlinePath.getTotalLength();
+ flatlinePath.style.strokeDasharray = flatlineLen;
+ flatlinePath.style.strokeDashoffset = flatlineLen;
+
+ // Animate flatline drawing left-to-right over 1000ms
+ requestAnimationFrame(function() {
+ flatlinePath.style.transition = 'stroke-dashoffset 1000ms linear';
+ flatlinePath.style.strokeDashoffset = '0';
+ });
+
+ // --- FIRST HEARTBEAT (after flatline completes) ---
+ setTimeout(function() {
+ drawHeartbeat(svg, svgNS, vw, vh, cy, 40, '#00ff41', 600, function() {
+ // After first heartbeat, hold 300ms then signal done (Task 3 will continue)
+ setTimeout(function() {
+ finishECGPhase();
+ }, 300);
+ });
+ }, 1050);
+ }
+
+ /**
+ * Draws a PQRST waveform centered horizontally on the screen.
+ * @param {SVGElement} svg - The SVG container
+ * @param {string} svgNS - SVG namespace
+ * @param {number} vw - Viewport width
+ * @param {number} vh - Viewport height
+ * @param {number} cy - Vertical center (baseline Y)
+ * @param {number} rHeight - Height of the R peak above baseline
+ * @param {string} color - Stroke color
+ * @param {number} duration - Animation duration in ms
+ * @param {function} onComplete - Callback when animation finishes
+ */
+ function drawHeartbeat(svg, svgNS, vw, vh, cy, rHeight, color, duration, onComplete) {
+ // PQRST waveform shape relative to center point
+ // Total width of the waveform: ~160px
+ var waveWidth = 160;
+ var startX = Math.round((vw - waveWidth) / 2);
+
+ // Build the PQRST path
+ // P wave: gentle upward bump, ~8px above baseline, ~30px wide
+ // Flat: ~10px
+ // Q dip: sharp dip ~10px below, ~8px wide
+ // R spike: sharp peak rHeight above baseline, ~12px wide
+ // S dip: sharp dip ~15px below, ~8px wide
+ // Flat: ~10px
+ // T wave: gentle upward bump, ~12px above, ~35px wide
+ // Return to baseline
+
+ var x = startX;
+ var d = 'M ' + x + ' ' + cy;
+
+ // Lead-in flat segment
+ d += ' L ' + (x + 10) + ' ' + cy;
+ x += 10;
+
+ // P wave (cubic bezier for smooth bump)
+ d += ' C ' + (x + 8) + ' ' + cy + ', ' + (x + 10) + ' ' + (cy - 8) + ', ' + (x + 15) + ' ' + (cy - 8);
+ d += ' C ' + (x + 20) + ' ' + (cy - 8) + ', ' + (x + 22) + ' ' + cy + ', ' + (x + 30) + ' ' + cy;
+ x += 30;
+
+ // Flat segment before QRS
+ d += ' L ' + (x + 10) + ' ' + cy;
+ x += 10;
+
+ // Q dip
+ d += ' L ' + (x + 4) + ' ' + (cy + 10);
+ x += 4;
+
+ // R spike (sharp peak)
+ d += ' L ' + (x + 6) + ' ' + (cy - rHeight);
+ x += 6;
+
+ // S dip
+ d += ' L ' + (x + 6) + ' ' + (cy + 15);
+ x += 6;
+
+ // Return to baseline
+ d += ' L ' + (x + 4) + ' ' + cy;
+ x += 4;
+
+ // Flat segment before T wave
+ d += ' L ' + (x + 10) + ' ' + cy;
+ x += 10;
+
+ // T wave (cubic bezier for smooth bump)
+ d += ' C ' + (x + 8) + ' ' + cy + ', ' + (x + 12) + ' ' + (cy - 12) + ', ' + (x + 17) + ' ' + (cy - 12);
+ d += ' C ' + (x + 22) + ' ' + (cy - 12) + ', ' + (x + 27) + ' ' + cy + ', ' + (x + 35) + ' ' + cy;
+ x += 35;
+
+ // Trail-out flat segment
+ d += ' L ' + (x + 10) + ' ' + cy;
+
+ var beatPath = document.createElementNS(svgNS, 'path');
+ beatPath.setAttribute('d', d);
+ beatPath.setAttribute('class', 'ecg-heartbeat');
+ beatPath.style.stroke = color;
+ svg.appendChild(beatPath);
+
+ var beatLen = beatPath.getTotalLength();
+ beatPath.style.strokeDasharray = beatLen;
+ beatPath.style.strokeDashoffset = beatLen;
+
+ // Animate the heartbeat drawing
+ requestAnimationFrame(function() {
+ beatPath.style.transition = 'stroke-dashoffset ' + duration + 'ms ease-in-out';
+ beatPath.style.strokeDashoffset = '0';
+ });
+
+ // Callback after animation
+ if (onComplete) {
+ setTimeout(onComplete, duration + 50);
+ }
+ }
+
+ /**
+ * Temporary: finish ECG phase by hiding overlays and revealing CV.
+ * Task 3 will expand this with second/third heartbeats and branching.
+ */
+ function finishECGPhase() {
+ var overlay = document.getElementById('ecg-overlay');
+ var bootScreen = document.getElementById('boot-screen');
+
+ // Fade out the ECG lines
+ overlay.style.transition = 'opacity 500ms ease';
+ overlay.style.opacity = '0';
+
+ // Transition boot screen background from black to white
+ bootScreen.style.transition = 'background 500ms ease';
+ bootScreen.style.background = '#FFFFFF';
+
+ setTimeout(function() {
+ // Remove overlays
+ overlay.style.display = 'none';
+ bootScreen.style.display = 'none';
+
+ // Reveal CV content
+ var cvContent = document.getElementById('cv-content');
+ cvContent.classList.add('revealed');
+ }, 500);
+ }
+
})();