diff --git a/public/audio/cardiac/functional-murmur.wav b/public/audio/cardiac/functional-murmur.wav new file mode 100644 index 0000000..4c0753a Binary files /dev/null and b/public/audio/cardiac/functional-murmur.wav differ diff --git a/public/audio/cardiac/infant-normal.ogg b/public/audio/cardiac/infant-normal.ogg new file mode 100644 index 0000000..96d1a40 Binary files /dev/null and b/public/audio/cardiac/infant-normal.ogg differ diff --git a/public/audio/cardiac/pvc.ogg b/public/audio/cardiac/pvc.ogg new file mode 100644 index 0000000..ff8d545 Binary files /dev/null and b/public/audio/cardiac/pvc.ogg differ diff --git a/public/audio/cardiac/stills-murmur.ogg b/public/audio/cardiac/stills-murmur.ogg new file mode 100644 index 0000000..a925616 Binary files /dev/null and b/public/audio/cardiac/stills-murmur.ogg differ diff --git a/public/js/peGuide.js b/public/js/peGuide.js index d87c86a..09a8e8e 100644 --- a/public/js/peGuide.js +++ b/public/js/peGuide.js @@ -290,9 +290,13 @@ // ──────────────────────────────────────────────────────────── var CARDIAC_SOUNDS = [ { key: 'normal', src: '/audio/cardiac/normal.ogg', title: 'Normal heart sounds (S1, S2)', - where: 'All four classic auscultation points', rate: '61 bpm in this sample', + where: 'All four classic auscultation points', rate: '~61 bpm reference', features: '"lub-dub": S1 (closure of mitral + tricuspid) louder at apex; S2 (closure of aortic + pulmonic) louder at base. Physiologic S2 split on inspiration.', clinical: 'Reference for rhythm, rate, and the normal S1–S2 interval. Listen for what\'s changed — not just what\'s added.' }, + { key: 'infant-normal', src: '/audio/cardiac/infant-normal.ogg', title: 'Infant normal heart sounds', + where: 'Infant chest — rate will be higher than adult', rate: 'Pediatric reference (120–160 bpm range)', + features: 'Same S1–S2 pattern, faster rate. Short diastole makes murmurs easier to miss — careful auscultation needed.', + clinical: 'Reference for neonatal/infant rhythm. Any murmur in the first 72 h should prompt pre/postductal sat screening.' }, { key: 'vsd', src: '/audio/cardiac/vsd.wav', title: 'Ventricular septal defect (VSD)', where: 'Lower left sternal border (4th ICS)', features: 'Harsh, blowing, holosystolic (pansystolic) murmur — plateau shape through all of systole. Often accompanied by a thrill if large.', @@ -300,7 +304,19 @@ { key: 'mvp', src: '/audio/cardiac/mitral-prolapse.wav', title: 'Mitral valve prolapse (MVP) — click + late systolic murmur', where: 'Apex (5th ICS, mid-clavicular line)', features: 'Mid-systolic click followed by a late-systolic crescendo murmur. Timing of click changes with maneuvers: earlier with standing or Valsalva, later with squatting.', - clinical: 'Often benign, especially in thin young women. Features suggesting need for echo: thickened/redundant leaflets on exam, associated mitral regurgitation, symptoms (palpitations, chest pain), arrhythmias.' } + clinical: 'Often benign, especially in thin young women. Features suggesting need for echo: thickened/redundant leaflets, associated MR, symptoms (palpitations, chest pain), arrhythmias.' }, + { key: 'stills', src: '/audio/cardiac/stills-murmur.ogg', title: 'Still\'s murmur (innocent)', + where: 'LLSB, radiating to apex', rate: 'Classic age 3–7 y (this recording is a toddler)', + features: 'Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality.', + clinical: 'The most common innocent murmur of childhood. Louder supine, softer or disappears on standing or Valsalva. Normal S2. No radiation to neck or back. No workup needed when classic.' }, + { key: 'functional', src: '/audio/cardiac/functional-murmur.wav', title: 'Functional (innocent) murmur — adult female', + where: 'Left sternal border, soft systolic', + features: 'Soft systolic murmur in a structurally normal heart — often from increased cardiac output, thin chest wall, anemia, hyperthyroidism, or pregnancy.', + clinical: 'Benign if it meets the 7 S criteria. Investigate if loud (≥3/6), holosystolic, diastolic, radiating, or with thrill / symptoms.' }, + { key: 'pvc', src: '/audio/cardiac/pvc.ogg', title: 'Premature ventricular contractions (PVCs)', + where: 'Apex; detect via irregular rhythm', + features: 'Irregular rhythm with a compensatory pause after the early beat. Pulse may be irregular on palpation.', + clinical: 'Common and usually benign in structurally normal hearts. Investigate if frequent (>10/min), symptomatic (syncope), or with exertional worsening. Workup: ECG, ambulatory monitor, consider echo.' } ]; // ──────────────────────────────────────────────────────────── @@ -1388,7 +1404,13 @@ : 'SYNTH'; var player = isReal ? '' - : ''; + : '
' + + '' + + '' + + '
' + + '
' + + '
' + + '
'; var html = ''; html += '
'; html += '
'; @@ -1443,18 +1465,27 @@ if (currentSystem === 'cv') { html += '
'; html += '

Auscultation landmarks — APTM + Erb\'s

'; - html += '
'; - html += '
APTM cardiac auscultation points diagram
'; - html += '
'; + // Image ALWAYS on its own row (full width on every device, responsive + // max-width caps it so it isn't huge on desktop). Tap/click to open + // full-size in a new tab — native browser zoom works there. Legend + // below stacks on narrow screens via auto-fit grid with minmax. + html += ' '; + html += '
'; + html += '
'; APTM_LEGEND.forEach(function (p) { - html += '
'; + html += '
'; html += '
' + p.letter + '
'; - html += '
'; + html += '
'; html += '
' + esc(p.title) + '
'; html += '
' + esc(p.location) + '
'; html += '
Listen for: ' + esc(p.listen) + '
'; if (p.innocent) { - html += '
Innocent murmur(s): ' + esc(p.innocent) + '
'; + html += '
Innocent: ' + esc(p.innocent) + '
'; } html += '
'; html += '
'; @@ -1587,19 +1618,46 @@ if (state[key]) state[key].note = inp.value; }); }); - // Respiratory sound-library play buttons - content.querySelectorAll('.resp-sound-play').forEach(function (btn) { - btn.addEventListener('click', function () { - var name = btn.dataset.respSound; - if (window.RespSounds && window.RespSounds.play) { - var ok = window.RespSounds.play(name); - if (!ok) showToast('Audio not available', 'error'); - // Visual feedback - btn.innerHTML = ''; - setTimeout(function () { btn.innerHTML = ''; }, 3500); - } else { - showToast('Audio library not loaded', 'error'); - } + // Synth sound play/stop with progress bar + content.querySelectorAll('.synth-sound').forEach(function (container) { + var key = container.dataset.synthKey; + var playBtn = container.querySelector('.synth-play-btn'); + var stopBtn = container.querySelector('.synth-stop-btn'); + var bar = container.querySelector('.synth-progress-bar'); + var timer = null; + var startTime = 0; + var durationMs = 3200; // synth sounds are all ~3 s + function finish() { + if (timer) { clearInterval(timer); timer = null; } + playBtn.innerHTML = ' Play'; + playBtn.style.display = ''; + stopBtn.style.display = 'none'; + bar.style.width = '0%'; + } + playBtn.addEventListener('click', function () { + if (!window.RespSounds || !window.RespSounds.play) { showToast('Audio library not loaded', 'error'); return; } + // Stop any other synth player first + content.querySelectorAll('.synth-sound').forEach(function (c) { if (c !== container) { + var b = c.querySelector('.synth-progress-bar'); if (b) b.style.width = '0%'; + var pb = c.querySelector('.synth-play-btn'); if (pb) { pb.innerHTML = ' Play'; pb.style.display = ''; } + var sb = c.querySelector('.synth-stop-btn'); if (sb) sb.style.display = 'none'; + }}); + window.RespSounds.stop(); + var ok = window.RespSounds.play(key); + if (!ok) { showToast('Audio not available', 'error'); return; } + playBtn.style.display = 'none'; + stopBtn.style.display = ''; + startTime = Date.now(); + bar.style.width = '0%'; + timer = setInterval(function () { + var pct = Math.min(100, ((Date.now() - startTime) / durationMs) * 100); + bar.style.width = pct + '%'; + if (pct >= 100) finish(); + }, 50); + }); + stopBtn.addEventListener('click', function () { + if (window.RespSounds && window.RespSounds.stop) window.RespSounds.stop(); + finish(); }); }); }