diff --git a/4-vitals-monitor.html b/4-vitals-monitor.html
index 74b14b8..6f27642 100644
--- a/4-vitals-monitor.html
+++ b/4-vitals-monitor.html
@@ -141,6 +141,15 @@
filter: drop-shadow(0 0 6px rgba(0, 255, 65, 0.7));
}
+ .ecg-branch {
+ fill: none;
+ stroke: #00897B;
+ stroke-width: 1.5;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ filter: drop-shadow(0 0 3px rgba(0, 137, 123, 0.5));
+ }
+
/* =========================================
FINAL CV CONTENT (hidden behind boot)
========================================= */
@@ -238,6 +247,7 @@
function startECGPhase() {
var overlay = document.getElementById('ecg-overlay');
+ var bootScreen = document.getElementById('boot-screen');
var vw = window.innerWidth;
var vh = window.innerHeight;
var cy = Math.round(vh / 2);
@@ -266,17 +276,187 @@
flatlinePath.style.strokeDashoffset = '0';
});
- // --- FIRST HEARTBEAT (after flatline completes) ---
+ // --- FIRST HEARTBEAT (R peak 40px, green) ---
setTimeout(function() {
drawHeartbeat(svg, svgNS, vw, vh, cy, 40, '#00ff41', 600, function() {
- // After first heartbeat, hold 300ms then signal done (Task 3 will continue)
+ // Hold 300ms then second heartbeat
setTimeout(function() {
- finishECGPhase();
+
+ // --- SECOND HEARTBEAT (R peak 60px, transitioning color) ---
+ // Bg begins lightening slightly
+ bootScreen.style.transition = 'background 600ms ease';
+ bootScreen.style.background = '#0A0A0A';
+
+ drawHeartbeat(svg, svgNS, vw, vh, cy, 60, '#00C9A7', 600, function() {
+ // Hold 300ms then third heartbeat
+ setTimeout(function() {
+
+ // --- THIRD HEARTBEAT (R peak 100px, full teal) ---
+ bootScreen.style.transition = 'background 600ms ease';
+ bootScreen.style.background = '#141414';
+
+ drawHeartbeat(svg, svgNS, vw, vh, cy, 100, '#00897B', 600, function() {
+ // At the apex of the third beat — launch branching
+ startBranching(svg, svgNS, vw, vh, cy, overlay, bootScreen);
+ });
+ }, 300);
+ });
}, 300);
});
}, 1050);
}
+ /**
+ * Creates the overflow branching effect from the third heartbeat peak.
+ * Branch lines shoot outward and trace the outlines of UI elements.
+ */
+ function startBranching(svg, svgNS, vw, vh, cy, overlay, bootScreen) {
+ var cx = Math.round(vw / 2);
+ var peakY = cy - 100; // Third beat R peak position
+
+ // Calculate UI element positions for branches to trace
+ // Pill nav: rounded rectangle at top center
+ var navW = Math.min(520, vw - 64);
+ var navH = 44;
+ var navX = Math.round((vw - navW) / 2);
+ var navY = 16;
+ var navR = 22; // border-radius
+
+ // Hero section: centered area
+ var heroW = Math.min(600, vw - 80);
+ var heroX = Math.round((vw - heroW) / 2);
+ var heroY = Math.round(vh * 0.2);
+ var heroH = Math.round(vh * 0.5);
+
+ // Card outlines (vital sign cards)
+ var cardW = 160;
+ var cardH = 80;
+ var cardGap = 16;
+ var totalCardsW = 4 * cardW + 3 * cardGap;
+ var cardsStartX = Math.round((vw - totalCardsW) / 2);
+ var cardsY = Math.round(vh * 0.65);
+
+ // Branch paths — each starts from the peak and traces outward
+ var branches = [];
+
+ // Branch 1: Shoots up to trace the pill nav bar outline
+ branches.push({
+ d: 'M ' + cx + ' ' + peakY +
+ ' Q ' + cx + ' ' + (navY + navH + 20) + ', ' + (navX + navR) + ' ' + (navY + navH) +
+ ' L ' + (navX + navR) + ' ' + (navY + navH) +
+ ' Q ' + navX + ' ' + (navY + navH) + ', ' + navX + ' ' + (navY + navH - navR) +
+ ' L ' + navX + ' ' + (navY + navR) +
+ ' Q ' + navX + ' ' + navY + ', ' + (navX + navR) + ' ' + navY +
+ ' L ' + (navX + navW - navR) + ' ' + navY +
+ ' Q ' + (navX + navW) + ' ' + navY + ', ' + (navX + navW) + ' ' + (navY + navR) +
+ ' L ' + (navX + navW) + ' ' + (navY + navH - navR) +
+ ' Q ' + (navX + navW) + ' ' + (navY + navH) + ', ' + (navX + navW - navR) + ' ' + (navY + navH) +
+ ' L ' + (navX + navR) + ' ' + (navY + navH),
+ delay: 0
+ });
+
+ // Branch 2: Shoots left to trace the left edge of hero container
+ branches.push({
+ d: 'M ' + cx + ' ' + peakY +
+ ' Q ' + (heroX + 40) + ' ' + (peakY - 30) + ', ' + heroX + ' ' + heroY +
+ ' L ' + heroX + ' ' + (heroY + heroH),
+ delay: 80
+ });
+
+ // Branch 3: Shoots right to trace the right edge of hero container
+ branches.push({
+ d: 'M ' + cx + ' ' + peakY +
+ ' Q ' + (heroX + heroW - 40) + ' ' + (peakY - 30) + ', ' + (heroX + heroW) + ' ' + heroY +
+ ' L ' + (heroX + heroW) + ' ' + (heroY + heroH),
+ delay: 80
+ });
+
+ // Branch 4: Down-left to first card outline (trace rectangle, no Z back to peak)
+ var c1x = cardsStartX;
+ branches.push({
+ d: 'M ' + cx + ' ' + peakY +
+ ' Q ' + (cx - 80) + ' ' + (cardsY - 40) + ', ' + c1x + ' ' + cardsY +
+ ' L ' + (c1x + cardW) + ' ' + cardsY +
+ ' L ' + (c1x + cardW) + ' ' + (cardsY + cardH) +
+ ' L ' + c1x + ' ' + (cardsY + cardH) +
+ ' L ' + c1x + ' ' + cardsY,
+ delay: 150
+ });
+
+ // Branch 5: Down to second card outline
+ var c2x = cardsStartX + cardW + cardGap;
+ branches.push({
+ d: 'M ' + cx + ' ' + peakY +
+ ' Q ' + (c2x + cardW / 2) + ' ' + (cardsY - 20) + ', ' + c2x + ' ' + cardsY +
+ ' L ' + (c2x + cardW) + ' ' + cardsY +
+ ' L ' + (c2x + cardW) + ' ' + (cardsY + cardH) +
+ ' L ' + c2x + ' ' + (cardsY + cardH) +
+ ' L ' + c2x + ' ' + cardsY,
+ delay: 200
+ });
+
+ // Branch 6: Down-right to third card outline
+ var c3x = cardsStartX + 2 * (cardW + cardGap);
+ branches.push({
+ d: 'M ' + cx + ' ' + peakY +
+ ' Q ' + (cx + 60) + ' ' + (cardsY - 30) + ', ' + c3x + ' ' + cardsY +
+ ' L ' + (c3x + cardW) + ' ' + cardsY +
+ ' L ' + (c3x + cardW) + ' ' + (cardsY + cardH) +
+ ' L ' + c3x + ' ' + (cardsY + cardH) +
+ ' L ' + c3x + ' ' + cardsY,
+ delay: 250
+ });
+
+ // Branch 7: Far right to fourth card outline
+ var c4x = cardsStartX + 3 * (cardW + cardGap);
+ branches.push({
+ d: 'M ' + cx + ' ' + peakY +
+ ' Q ' + (cx + 120) + ' ' + (cardsY - 20) + ', ' + c4x + ' ' + cardsY +
+ ' L ' + (c4x + cardW) + ' ' + cardsY +
+ ' L ' + (c4x + cardW) + ' ' + (cardsY + cardH) +
+ ' L ' + c4x + ' ' + (cardsY + cardH) +
+ ' L ' + c4x + ' ' + cardsY,
+ delay: 300
+ });
+
+ // Animate each branch with staggered timing
+ var branchDuration = 800;
+ var maxDelay = 0;
+
+ branches.forEach(function(branch) {
+ if (branch.delay > maxDelay) maxDelay = branch.delay;
+
+ setTimeout(function() {
+ var path = document.createElementNS(svgNS, 'path');
+ path.setAttribute('d', branch.d);
+ path.setAttribute('class', 'ecg-branch');
+ svg.appendChild(path);
+
+ var len = path.getTotalLength();
+ path.style.strokeDasharray = len;
+ path.style.strokeDashoffset = len;
+
+ requestAnimationFrame(function() {
+ path.style.transition = 'stroke-dashoffset ' + branchDuration + 'ms cubic-bezier(0.25, 0.46, 0.45, 0.94)';
+ path.style.strokeDashoffset = '0';
+ });
+ }, branch.delay);
+ });
+
+ // Background rapidly transitions to white during branching
+ setTimeout(function() {
+ bootScreen.style.transition = 'background 800ms ease-out';
+ bootScreen.style.background = '#FFFFFF';
+ }, 200);
+
+ // After all branches finish drawing, fade out and reveal
+ var totalBranchTime = maxDelay + branchDuration + 100;
+
+ setTimeout(function() {
+ finishECGPhase(overlay, bootScreen);
+ }, totalBranchTime);
+ }
+
/**
* Draws a PQRST waveform centered horizontally on the screen.
* @param {SVGElement} svg - The SVG container
@@ -353,6 +533,10 @@
beatPath.setAttribute('d', d);
beatPath.setAttribute('class', 'ecg-heartbeat');
beatPath.style.stroke = color;
+ // Match the glow filter to the stroke color
+ if (color !== '#00ff41') {
+ beatPath.style.filter = 'drop-shadow(0 0 6px rgba(0, 137, 123, 0.7))';
+ }
svg.appendChild(beatPath);
var beatLen = beatPath.getTotalLength();
@@ -372,25 +556,17 @@
}
/**
- * Temporary: finish ECG phase by hiding overlays and revealing CV.
- * Task 3 will expand this with second/third heartbeats and branching.
+ * Final cleanup: fade out all SVG lines and reveal the CV content.
*/
- function finishECGPhase() {
- var overlay = document.getElementById('ecg-overlay');
- var bootScreen = document.getElementById('boot-screen');
-
- // Fade out the ECG lines
+ function finishECGPhase(overlay, bootScreen) {
+ // Fade out all SVG 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';
+ // Remove overlays from DOM
+ if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
+ if (bootScreen.parentNode) bootScreen.parentNode.removeChild(bootScreen);
// Reveal CV content
var cvContent = document.getElementById('cv-content');