Ecg

Asking for Preferences
Asking for Preferences
Reading File
Reading File
Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Searching Images

ECG electrocardiogram normal sinus rhythm waves P QRS T

Searching Images

STEMI ST elevation myocardial infarction ECG pattern

Searching Images

atrial fibrillation ECG tracing irregular rhythm

Searching Images

ECG axis deviation left right heart rate calculation

Searching Images

ventricular tachycardia VT ECG wide complex tachycardia

Searching Images

heart block AV block first degree second degree third degree ECG

Searching Images

ECG 12 lead placement electrode position diagram

Running Command

mkdir -p /home/daytona/workspace/ecg-slides && ls /home/daytona/workspace/ecg-slides

Writing File

~/ecg-slides/build.js

const pptxgen = require("pptxgenjs");
const { execSync } = require("child_process");

// ─── Color Palette ───────────────────────────────────────────────────────────
const C = {
  navy:   "0A1628",   // dominant dark bg
  deep:   "0D2137",   // slightly lighter dark panels
  teal:   "00B4D8",   // primary accent
  green:  "06D6A0",   // positive/normal
  red:    "EF233C",   // danger/abnormal
  yellow: "FFD166",   // warning/interval labels
  white:  "F8F9FA",
  grey:   "8D99AE",
  lightbg:"112240",   // card backgrounds
};

// ─── Fetch images ─────────────────────────────────────────────────────────────
const imageUrls = {
  normal_ecg:   "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_c630311bd2acdd3957f2db85257d1ef3bbe57e637174feea9c49c44fae267d9c.jpg",
  afib:         "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_3c76fbc6e31a1f92963db97bfbae29eae90022cbdb45c2b5f37af6c14bee4bfc.jpg",
  stemi_ant:    "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_cdfb20f9fc1a8412ffb27a5e6e1ae510547606f1b0c520ad6dc5bda4ec26caeb.jpg",
  stemi_inf:    "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_9a35fdd5019a941abc80f3c820d1706184a69fea96da67e6ce1e6591ad05.jpg",
  av_block:     "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_11ac037ff7836678ddd7fad143600ed71c2a3910d226a34ea257a3618beab0a5.jpg",
  vt:           "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_d0c8263390a11db77a98f37c73e35131257aee921845e98272875e8c3fe8caec.jpg",
  lead_placement:"https://cdn.orris.care/cdss_images/pmc_clinical_VQA_f8756ed1439cae9a8c9451fe1427ae40410bb90ea9442dbc72e08fbf945a3894.jpg",
  lad_stemi:    "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_0f5964997b5f333f6a7b13ecdc70a73ef6e81127eaee03ef38d9c724b26bbf31.jpg",
};

console.log("Fetching images...");
const fetchedImages = {};
for (const [key, url] of Object.entries(imageUrls)) {
  try {
    const result = JSON.parse(execSync(
      `node /home/daytona/skills/shared/scripts/fetch_images.js "${url}"`,
      { timeout: 30000 }
    ).toString());
    if (result[0] && !result[0].error) {
      fetchedImages[key] = result[0].base64;
      console.log(`  ✓ ${key}`);
    } else {
      console.log(`  ✗ ${key}: ${result[0]?.error}`);
    }
  } catch(e) {
    console.log(`  ✗ ${key}: fetch failed`);
  }
}

// ─── Helper: dark slide background ───────────────────────────────────────────
function darkBg(slide) {
  slide.addShape("rect", { x: 0, y: 0, w: 13.3, h: 7.5, fill: { color: C.navy }, line: { type: "none" } });
}

function accentBar(slide, color = C.teal) {
  slide.addShape("rect", { x: 0, y: 0, w: 0.12, h: 7.5, fill: { color }, line: { type: "none" } });
}

function slideTitle(slide, text, color = C.teal) {
  slide.addText(text.toUpperCase(), {
    x: 0.35, y: 0.22, w: 12.5, h: 0.55,
    fontSize: 22, bold: true, color,
    fontFace: "Calibri", charSpacing: 3, margin: 0
  });
  // underline bar
  slide.addShape("rect", { x: 0.35, y: 0.82, w: 12.5, h: 0.04, fill: { color: C.grey }, line: { type: "none" } });
}

function card(slide, x, y, w, h, fillColor = C.lightbg) {
  slide.addShape("roundRect", { x, y, w, h, rectRadius: 0.08, fill: { color: fillColor }, line: { color: C.grey, w: 0.5 } });
}

// ─── Create Presentation ─────────────────────────────────────────────────────
let pres = new pptxgen();
pres.layout = "LAYOUT_WIDE";
pres.title = "ECG – A Complete Guide for Medical Students";

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 1 – Title
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  // large teal diagonal accent
  s.addShape("rect", { x: 0, y: 0, w: 13.3, h: 0.12, fill: { color: C.teal }, line: { type: "none" } });
  s.addShape("rect", { x: 0, y: 7.38, w: 13.3, h: 0.12, fill: { color: C.teal }, line: { type: "none" } });

  // ECG waveform decorative strip
  s.addShape("rect", { x: 0, y: 3.6, w: 13.3, h: 0.8, fill: { color: C.deep }, line: { type: "none" } });

  s.addText("ECG", {
    x: 0.5, y: 0.9, w: 12, h: 2.0,
    fontSize: 96, bold: true, color: C.teal, fontFace: "Calibri",
    align: "center", glow: { size: 8, opacity: 0.4, color: C.teal }
  });
  s.addText("Electrocardiography", {
    x: 0.5, y: 2.7, w: 12, h: 0.9,
    fontSize: 32, bold: false, color: C.white, fontFace: "Calibri", align: "center", charSpacing: 8
  });
  s.addText("A Complete Guide for Medical Students", {
    x: 0.5, y: 4.6, w: 12, h: 0.7,
    fontSize: 20, color: C.grey, fontFace: "Calibri", align: "center", italic: true
  });
  s.addText("Basics  •  Lead System  •  Intervals  •  Rhythm  •  Arrhythmias  •  Ischaemia", {
    x: 0.5, y: 5.5, w: 12, h: 0.55,
    fontSize: 14, color: C.grey, fontFace: "Calibri", align: "center"
  });
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 2 – What is an ECG?
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s);
  slideTitle(s, "What is an ECG?");

  // left text panel
  const points = [
    { text: "Records the electrical activity of the heart from body surface electrodes", bold: false },
    { text: "Each heartbeat generates an electrical impulse detectable by skin electrodes", bold: false },
    { text: "Standard 12-lead ECG provides 12 different 'views' of cardiac electricity", bold: false },
    { text: "Paper speed: 25 mm/sec  |  Standard gain: 10 mm/mV", bold: true },
  ];

  let yPos = 1.05;
  for (const p of points) {
    card(s, 0.35, yPos, 6.0, 0.82, C.lightbg);
    s.addText("▸  " + p.text, {
      x: 0.55, y: yPos + 0.15, w: 5.7, h: 0.55,
      fontSize: 13.5, color: C.white, bold: p.bold, fontFace: "Calibri", wrap: true, margin: 0
    });
    yPos += 0.95;
  }

  // Why we use it box
  card(s, 0.35, 5.0, 6.0, 2.1, "0D1F35");
  s.addText("WHY DO WE USE IT?", { x: 0.55, y: 5.1, w: 5.7, h: 0.4, fontSize: 12, bold: true, color: C.teal, charSpacing: 2, margin: 0 });
  const uses = ["Detect arrhythmias", "Diagnose myocardial infarction", "Assess conduction abnormalities", "Monitor drug effects & electrolytes", "Pre-operative cardiac evaluation"];
  uses.forEach((u, i) => {
    s.addText("• " + u, { x: 0.6, y: 5.55 + i * 0.3, w: 5.5, h: 0.28, fontSize: 12.5, color: C.white, margin: 0 });
  });

  // image right
  if (fetchedImages.normal_ecg) {
    s.addImage({ data: fetchedImages.normal_ecg, x: 6.7, y: 1.0, w: 6.2, h: 6.0 });
    s.addText("Normal 12-Lead ECG — Sinus Rhythm ~64 bpm", {
      x: 6.7, y: 7.05, w: 6.2, h: 0.35,
      fontSize: 10, color: C.grey, italic: true, align: "center", margin: 0
    });
  }
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 3 – Lead Placement
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.green);
  slideTitle(s, "Lead Placement & the 12-Lead System", C.green);

  // Limb leads box
  card(s, 0.35, 1.0, 5.8, 3.0, C.lightbg);
  s.addText("LIMB LEADS (6)", { x: 0.55, y: 1.1, w: 5.4, h: 0.35, fontSize: 13, bold: true, color: C.yellow, charSpacing: 2, margin: 0 });
  const limbLeads = [
    "I   — Right arm (–) → Left arm (+)",
    "II  — Right arm (–) → Left leg (+)",
    "III — Left arm (–) → Left leg (+)",
    "aVR — Right arm (+)  [augmented]",
    "aVL — Left arm (+)   [augmented]",
    "aVF — Left leg (+)   [augmented]",
  ];
  limbLeads.forEach((l, i) => {
    s.addText(l, { x: 0.6, y: 1.52 + i * 0.38, w: 5.4, h: 0.35, fontSize: 12.5, color: C.white, fontFace: "Courier New", margin: 0 });
  });

  // Precordial box
  card(s, 0.35, 4.15, 5.8, 2.95, C.lightbg);
  s.addText("PRECORDIAL LEADS (V1–V6)", { x: 0.55, y: 4.25, w: 5.4, h: 0.35, fontSize: 13, bold: true, color: C.teal, charSpacing: 2, margin: 0 });
  const precLeads = [
    "V1 — 4th ICS, right sternal border",
    "V2 — 4th ICS, left sternal border",
    "V3 — Between V2 and V4",
    "V4 — 5th ICS, midclavicular line",
    "V5 — Anterior axillary line (= V4 level)",
    "V6 — Midaxillary line (= V4 level)",
  ];
  precLeads.forEach((l, i) => {
    s.addText(l, { x: 0.6, y: 4.68 + i * 0.38, w: 5.4, h: 0.35, fontSize: 12.5, color: C.white, fontFace: "Courier New", margin: 0 });
  });

  // image right
  if (fetchedImages.lead_placement) {
    s.addImage({ data: fetchedImages.lead_placement, x: 6.6, y: 1.0, w: 6.3, h: 6.1 });
    s.addText("Precordial lead positions (standard & high V1-V2 for Brugada)", {
      x: 6.6, y: 7.1, w: 6.3, h: 0.3, fontSize: 10, color: C.grey, italic: true, align: "center", margin: 0
    });
  }
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 4 – ECG Paper & Waves
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.yellow);
  slideTitle(s, "ECG Paper, Waveforms & Intervals", C.yellow);

  // Grid info
  card(s, 0.35, 1.0, 6.0, 1.7, C.lightbg);
  s.addText("ECG GRID", { x: 0.55, y: 1.1, w: 5.7, h: 0.35, fontSize: 13, bold: true, color: C.yellow, charSpacing: 2, margin: 0 });
  const grid = [
    "Small box = 1 mm = 0.04 sec (horizontal) = 0.1 mV (vertical)",
    "Large box = 5 mm = 0.20 sec = 0.5 mV",
    "Heart rate = 300 ÷ number of large boxes (R-R)",
  ];
  grid.forEach((g, i) => {
    s.addText("• " + g, { x: 0.6, y: 1.52 + i * 0.38, w: 5.6, h: 0.35, fontSize: 12.5, color: C.white, margin: 0 });
  });

  // Waves table
  const waves = [
    { wave: "P Wave",    duration: "< 0.12 sec",  amplitude: "< 2.5 mm",   meaning: "Atrial depolarization",         color: C.green },
    { wave: "PR Interval", duration: "0.12–0.20 sec", amplitude: "—",      meaning: "AV conduction time",            color: C.teal },
    { wave: "QRS Complex", duration: "< 0.12 sec", amplitude: "Variable",  meaning: "Ventricular depolarization",    color: C.yellow },
    { wave: "ST Segment",  duration: "Variable",   amplitude: "Isoelectric ±1mm", meaning: "Ventricular plateau",   color: C.white },
    { wave: "T Wave",      duration: "0.10–0.25 sec", amplitude: "< 10 mm", meaning: "Ventricular repolarization",  color: C.teal },
    { wave: "QT Interval", duration: "< 0.44 sec (QTc)", amplitude: "—",   meaning: "Total ventricular activity",  color: C.yellow },
    { wave: "U Wave",      duration: "Variable",   amplitude: "< 1 mm",    meaning: "Purkinje repolarization (sometimes)", color: C.grey },
  ];

  // Header
  card(s, 0.35, 2.85, 12.6, 0.42, "0D2137");
  ["Waveform", "Duration", "Amplitude", "Meaning"].forEach((h, i) => {
    const xs = [0.45, 2.85, 5.15, 7.3];
    const ws = [2.3, 2.2, 2.1, 5.5];
    s.addText(h, { x: xs[i], y: 2.9, w: ws[i], h: 0.3, fontSize: 12, bold: true, color: C.teal, margin: 0 });
  });

  waves.forEach((w, i) => {
    const yy = 3.32 + i * 0.53;
    const bg = i % 2 === 0 ? C.lightbg : C.deep;
    card(s, 0.35, yy, 12.6, 0.5, bg);
    s.addText(w.wave,      { x: 0.45, y: yy + 0.1, w: 2.3, h: 0.3, fontSize: 12.5, bold: true,  color: w.color, margin: 0 });
    s.addText(w.duration,  { x: 2.85, y: yy + 0.1, w: 2.2, h: 0.3, fontSize: 12,   color: C.white,  margin: 0 });
    s.addText(w.amplitude, { x: 5.15, y: yy + 0.1, w: 2.1, h: 0.3, fontSize: 12,   color: C.white,  margin: 0 });
    s.addText(w.meaning,   { x: 7.3,  y: yy + 0.1, w: 5.5, h: 0.3, fontSize: 12,   color: C.grey,   margin: 0 });
  });
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 5 – Systematic Interpretation
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.teal);
  slideTitle(s, "Systematic ECG Interpretation — 8-Step Method");

  const steps = [
    { num: "1", title: "Rate",         desc: "300 ÷ large boxes between R waves  |  Regular: count boxes; Irregular: 6-second strip × 10", color: C.teal },
    { num: "2", title: "Rhythm",       desc: "Regular or irregular?  Is every P followed by a QRS?",                                        color: C.green },
    { num: "3", title: "P Waves",      desc: "Present? Upright in I, II, aVF?  One before each QRS?  Morphology?",                         color: C.yellow },
    { num: "4", title: "PR Interval",  desc: "0.12–0.20 sec  |  Prolonged → AV block  |  Short → pre-excitation (WPW)",                    color: C.teal },
    { num: "5", title: "QRS Width",    desc: "Narrow (<0.12 sec) = supraventricular  |  Wide (≥0.12 sec) = BBB or ventricular",            color: C.yellow },
    { num: "6", title: "QRS Axis",     desc: "Normal: –30° to +90°  |  LAD: <–30°  |  RAD: >+90°  |  Use leads I and aVF",                color: C.green },
    { num: "7", title: "ST / T Waves", desc: "Elevation or depression?  T wave changes? — ischaemia, strain, electrolytes",                color: C.red },
    { num: "8", title: "QT Interval",  desc: "Corrected QTc (Bazett formula) normal < 440 ms  |  Long QT → risk of torsades",              color: C.teal },
  ];

  const cols = 2, rows = 4;
  const w = 6.25, h = 1.38, xStart = 0.35, yStart = 1.05, gap = 0.12;
  steps.forEach((st, i) => {
    const col = i % cols, row = Math.floor(i / cols);
    const x = xStart + col * (w + gap);
    const y = yStart + row * (h + gap);
    card(s, x, y, w, h, C.lightbg);
    // number circle
    s.addShape("ellipse", { x: x + 0.12, y: y + 0.35, w: 0.52, h: 0.52, fill: { color: st.color }, line: { type: "none" } });
    s.addText(st.num, { x: x + 0.12, y: y + 0.35, w: 0.52, h: 0.52, fontSize: 14, bold: true, color: C.navy, align: "center", valign: "middle", margin: 0 });
    s.addText(st.title, { x: x + 0.75, y: y + 0.12, w: w - 0.85, h: 0.42, fontSize: 14.5, bold: true, color: st.color, margin: 0 });
    s.addText(st.desc,  { x: x + 0.75, y: y + 0.55, w: w - 0.85, h: 0.75, fontSize: 11.5, color: C.white, wrap: true, margin: 0 });
  });
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 6 – Normal Sinus Rhythm
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.green);
  slideTitle(s, "Normal Sinus Rhythm (NSR)", C.green);

  const criteria = [
    "Rate:          60–100 bpm",
    "Rhythm:        Regular (R-R intervals equal)",
    "P Wave:        Upright in I, II, aVF  |  Inverted in aVR",
    "PR Interval:   0.12–0.20 sec  (constant)",
    "QRS:           Narrow < 0.12 sec",
    "ST Segment:    Isoelectric (at baseline)",
    "T Wave:        Upright in I, II, V3–V6",
  ];
  criteria.forEach((c, i) => {
    const yy = 1.1 + i * 0.55;
    card(s, 0.35, yy, 5.7, 0.48, C.lightbg);
    s.addText(c, { x: 0.55, y: yy + 0.09, w: 5.4, h: 0.3, fontSize: 13, color: C.white, fontFace: "Courier New", margin: 0 });
  });

  if (fetchedImages.normal_ecg) {
    s.addImage({ data: fetchedImages.normal_ecg, x: 6.35, y: 1.0, w: 6.6, h: 5.5 });
    s.addText("NSR — 12-lead ECG demonstrating normal sinus rhythm at ~64 bpm", {
      x: 6.35, y: 6.55, w: 6.6, h: 0.35, fontSize: 10, color: C.grey, italic: true, align: "center", margin: 0
    });
  }

  card(s, 0.35, 5.1, 5.7, 2.0, "0D2137");
  s.addText("KEY CLINICAL POINT", { x: 0.55, y: 5.2, w: 5.4, h: 0.35, fontSize: 12, bold: true, color: C.green, charSpacing: 2, margin: 0 });
  s.addText("NSR indicates the SA node is the dominant pacemaker. Any deviation from these criteria constitutes an abnormality requiring further evaluation.", {
    x: 0.55, y: 5.6, w: 5.4, h: 1.35, fontSize: 12.5, color: C.white, wrap: true, margin: 0
  });
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 7 – Atrial Fibrillation
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.red);
  slideTitle(s, "Atrial Fibrillation (AF)", C.red);

  const afPoints = [
    { label: "Rate",      val: "Atrial: 350–600 bpm  |  Ventricular: variable 60–180 bpm" },
    { label: "Rhythm",    val: "IRREGULARLY IRREGULAR — hallmark feature" },
    { label: "P Waves",   val: "Absent — replaced by fibrillatory (f) waves; chaotic baseline" },
    { label: "PR Interval", val: "Unmeasurable (no organised P wave)" },
    { label: "QRS",       val: "Usually narrow; may be wide if aberrant conduction" },
    { label: "Causes",    val: "HTN, mitral valve disease, thyrotoxicosis, alcohol, ischaemia" },
    { label: "Risk",      val: "Stroke risk — assess with CHA₂DS₂-VASc score" },
  ];

  afPoints.forEach((p, i) => {
    const yy = 1.05 + i * 0.56;
    card(s, 0.35, yy, 6.1, 0.5, C.lightbg);
    s.addText(p.label + ":", { x: 0.5, y: yy + 0.1, w: 1.4, h: 0.3, fontSize: 12, bold: true, color: C.teal, margin: 0 });
    s.addText(p.val, { x: 1.95, y: yy + 0.1, w: 4.4, h: 0.3, fontSize: 12.5, color: p.label === "Rhythm" ? C.red : C.white, bold: p.label === "Rhythm", margin: 0 });
  });

  if (fetchedImages.afib) {
    s.addImage({ data: fetchedImages.afib, x: 6.75, y: 1.0, w: 6.2, h: 6.0 });
    s.addText("AF — irregularly irregular rhythm, absent P waves, fibrillatory baseline", {
      x: 6.75, y: 7.05, w: 6.2, h: 0.35, fontSize: 10, color: C.grey, italic: true, align: "center", margin: 0
    });
  }
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 8 – AV Blocks
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.yellow);
  slideTitle(s, "AV Conduction Blocks", C.yellow);

  const blocks = [
    {
      name: "1st Degree",
      color: C.green,
      features: ["PR interval > 0.20 sec (>5 small boxes)", "Every P wave conducts to ventricles", "All QRS complexes present", "Usually benign — no treatment required"],
    },
    {
      name: "2nd Degree — Mobitz I (Wenckebach)",
      color: C.yellow,
      features: ["Progressive PR lengthening", "Eventually a QRS is DROPPED", "P:QRS ratio e.g. 3:2, 4:3", "Due to AV node disease (often reversible)"],
    },
    {
      name: "2nd Degree — Mobitz II",
      color: C.red,
      features: ["Constant PR interval", "Sudden non-conducted P wave (QRS dropped)", "Infranodal conduction disease", "Risk of progression → complete block"],
    },
    {
      name: "3rd Degree (Complete Heart Block)",
      color: C.red,
      features: ["Complete AV dissociation", "P waves and QRS at different rates", "Escape rhythm: junctional 40–60 bpm or ventricular 20–40 bpm", "EMERGENCY — pacemaker required"],
    },
  ];

  const bw = 6.25, bh = 2.6, gap2 = 0.15;
  blocks.forEach((b, i) => {
    const col = i % 2, row = Math.floor(i / 2);
    const x = 0.35 + col * (bw + gap2);
    const y = 1.05 + row * (bh + gap2);
    card(s, x, y, bw, bh, C.lightbg);
    // top colour bar
    s.addShape("rect", { x, y, w: bw, h: 0.38, fill: { color: b.color }, line: { type: "none" } });
    s.addText(b.name, { x: x + 0.15, y: y + 0.04, w: bw - 0.2, h: 0.3, fontSize: 13, bold: true, color: C.navy, margin: 0 });
    b.features.forEach((f, j) => {
      s.addText("• " + f, { x: x + 0.2, y: y + 0.5 + j * 0.5, w: bw - 0.35, h: 0.45, fontSize: 12, color: C.white, wrap: true, margin: 0 });
    });
  });

  if (fetchedImages.av_block) {
    // overlay a small chart below – but cards fill the space. Show image on right if room is tight
    // adjust layout: show AV block comparison image at bottom strip
    s.addImage({ data: fetchedImages.av_block, x: 0.35, y: 6.3, w: 12.6, h: 1.05 });
    s.addText("Comparison chart: 1st, 2nd, and 3rd degree AV blocks", {
      x: 0.35, y: 7.35, w: 12.6, h: 0.12, fontSize: 9, color: C.grey, italic: true, align: "center", margin: 0
    });
  }
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 9 – Ventricular Tachycardia
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.red);
  slideTitle(s, "Ventricular Tachycardia (VT) & Ventricular Fibrillation (VF)", C.red);

  const vtFeats = [
    ["Rate",       "VT: 100–250 bpm  |  VF: 300–500 bpm (chaotic)"],
    ["QRS",        "WIDE (≥0.12 sec)  |  Monomorphic or polymorphic"],
    ["P Waves",    "AV dissociation — P waves march through at slower rate"],
    ["Fusion beats","Capture beats and fusion beats confirm VT diagnosis"],
    ["Morphology", "LBBB or RBBB pattern depending on origin"],
    ["Causes",     "CAD, cardiomyopathy, electrolyte disturbance, drugs"],
    ["Management", "Unstable → DC cardioversion  |  Stable → amiodarone/lidocaine"],
    ["VF",         "No organised QRS — chaotic waveform  |  DEATH without defibrillation"],
  ];

  vtFeats.forEach((f, i) => {
    const yy = 1.05 + i * 0.56;
    card(s, 0.35, yy, 5.9, 0.5, i === 7 ? "2D0A0E" : C.lightbg);
    s.addText(f[0] + ":", { x: 0.5, y: yy + 0.1, w: 1.4, h: 0.3, fontSize: 12, bold: true, color: C.teal, margin: 0 });
    s.addText(f[1], { x: 1.95, y: yy + 0.1, w: 4.2, h: 0.3, fontSize: 12.5, color: i === 7 ? C.red : C.white, bold: i === 7, margin: 0, wrap: true });
  });

  if (fetchedImages.vt) {
    s.addImage({ data: fetchedImages.vt, x: 6.55, y: 1.0, w: 6.4, h: 5.8 });
    s.addText("Monomorphic VT — wide QRS complexes, AV dissociation, capture beat visible", {
      x: 6.55, y: 6.85, w: 6.4, h: 0.35, fontSize: 10, color: C.grey, italic: true, align: "center", margin: 0
    });
  }
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 10 – STEMI
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.red);
  slideTitle(s, "ST-Elevation Myocardial Infarction (STEMI)", C.red);

  // Criteria box
  card(s, 0.35, 1.0, 5.8, 1.9, C.lightbg);
  s.addText("DIAGNOSTIC CRITERIA FOR STEMI", { x: 0.55, y: 1.1, w: 5.5, h: 0.35, fontSize: 12, bold: true, color: C.red, charSpacing: 1, margin: 0 });
  s.addText("• ≥2 contiguous leads with ST elevation:\n  – ≥1 mm in limb leads\n  – ≥2 mm in V1–V3 (men), ≥1.5 mm (women)\n  – Or new LBBB + ischaemic symptoms", {
    x: 0.55, y: 1.52, w: 5.5, h: 1.3, fontSize: 12.5, color: C.white, wrap: true, margin: 0
  });

  // Lead territories table
  const territories = [
    { leads: "V1–V4",       wall: "Anterior",          artery: "LAD",         color: C.teal },
    { leads: "I, aVL, V5–V6", wall: "Lateral",         artery: "LCx / LAD",  color: C.green },
    { leads: "II, III, aVF",  wall: "Inferior",        artery: "RCA (85%)",  color: C.yellow },
    { leads: "V7–V9 (posterior)",wall: "Posterior",    artery: "RCA / LCx",  color: C.teal },
    { leads: "aVR ↑ + diffuse↓", wall: "LMCA / Proximal LAD", artery: "LMCA",color: C.red },
  ];

  card(s, 0.35, 3.05, 5.8, 0.38, "0D2137");
  ["Leads", "Wall", "Artery"].forEach((h, i) => {
    const xs2 = [0.45, 2.15, 3.95];
    const ws2 = [1.6, 1.7, 1.9];
    s.addText(h, { x: xs2[i], y: 3.1, w: ws2[i], h: 0.28, fontSize: 12, bold: true, color: C.teal, margin: 0 });
  });
  territories.forEach((t, i) => {
    const yy = 3.48 + i * 0.56;
    card(s, 0.35, yy, 5.8, 0.5, i % 2 === 0 ? C.lightbg : C.deep);
    s.addText(t.leads, { x: 0.45, y: yy + 0.1, w: 1.6, h: 0.3, fontSize: 11.5, color: t.color, bold: true, margin: 0 });
    s.addText(t.wall,  { x: 2.15, y: yy + 0.1, w: 1.7, h: 0.3, fontSize: 11.5, color: C.white, margin: 0 });
    s.addText(t.artery,{ x: 3.95, y: yy + 0.1, w: 1.9, h: 0.3, fontSize: 11.5, color: C.grey,  margin: 0 });
  });

  // Reciprocal changes note
  card(s, 0.35, 6.38, 5.8, 0.72, "1A0810");
  s.addText("⚡ Reciprocal changes: ST depression in leads 'opposite' the infarcted wall — confirms STEMI and excludes pericarditis", {
    x: 0.55, y: 6.45, w: 5.5, h: 0.6, fontSize: 12, color: C.yellow, wrap: true, margin: 0
  });

  if (fetchedImages.stemi_ant) {
    s.addImage({ data: fetchedImages.stemi_ant, x: 6.5, y: 1.0, w: 6.5, h: 6.3 });
    s.addText("Anterolateral STEMI — ST elevation V1–V6, I, aVL  with reciprocal changes II, III, aVF", {
      x: 6.5, y: 7.35, w: 6.5, h: 0.12, fontSize: 9.5, color: C.grey, italic: true, align: "center", margin: 0
    });
  }
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 11 – Other Arrhythmias (SVT, Sinus Brady/Tachy)
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.teal);
  slideTitle(s, "Common Arrhythmias — Quick Reference");

  const arrs = [
    {
      name: "Sinus Tachycardia",
      color: C.yellow,
      features: ["Rate: 100–180 bpm", "Normal P-QRS-T morphology", "Every P followed by QRS", "Causes: pain, fever, anaemia, thyrotoxicosis, PE, anxiety"]
    },
    {
      name: "Sinus Bradycardia",
      color: C.green,
      features: ["Rate: < 60 bpm", "Normal P-QRS-T morphology", "Regular rhythm", "Causes: athletes, hypothyroidism, beta-blockers, vasovagal, IHD"]
    },
    {
      name: "SVT (Narrow complex tachycardia)",
      color: C.teal,
      features: ["Rate: 150–250 bpm", "Regular narrow QRS", "P waves buried in / after QRS", "Types: AVNRT (most common), AVRT, AT"]
    },
    {
      name: "Atrial Flutter",
      color: C.yellow,
      features: ["Atrial rate ~300 bpm", "Sawtooth 'flutter' waves — best in II, III, aVF, V1", "2:1 conduction → ventricular rate ~150 bpm", "Regular rhythm unless variable block"]
    },
    {
      name: "Ventricular Fibrillation (VF)",
      color: C.red,
      features: ["Chaotic, irregular undulations", "No discernible QRS complexes", "Cardiac arrest — pulseless", "Immediate unsynchronised DC shock required"]
    },
    {
      name: "WPW (Wolf-Parkinson-White)",
      color: C.teal,
      features: ["Short PR interval < 0.12 sec", "Delta wave (slurred QRS upstroke)", "Wide QRS", "Risk of rapid AF via accessory pathway → VF"]
    },
  ];

  const cw = 4.2, ch = 2.2, startX = 0.3, startY = 1.05, gapX = 0.15, gapY = 0.15;
  arrs.forEach((a, i) => {
    const col = i % 3, row = Math.floor(i / 3);
    const x = startX + col * (cw + gapX);
    const y = startY + row * (ch + gapY);
    card(s, x, y, cw, ch, C.lightbg);
    s.addShape("rect", { x, y, w: cw, h: 0.35, fill: { color: a.color }, line: { type: "none" } });
    s.addText(a.name, { x: x + 0.12, y: y + 0.03, w: cw - 0.2, h: 0.28, fontSize: 11.5, bold: true, color: C.navy, margin: 0 });
    a.features.forEach((f, j) => {
      s.addText("• " + f, { x: x + 0.15, y: y + 0.43 + j * 0.43, w: cw - 0.25, h: 0.38, fontSize: 11, color: C.white, wrap: true, margin: 0 });
    });
  });
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 12 – Bundle Branch Blocks
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.yellow);
  slideTitle(s, "Bundle Branch Blocks (BBB)", C.yellow);

  // RBBB
  card(s, 0.35, 1.0, 6.1, 5.7, C.lightbg);
  s.addShape("rect", { x: 0.35, y: 1.0, w: 6.1, h: 0.4, fill: { color: C.teal }, line: { type: "none" } });
  s.addText("RIGHT BUNDLE BRANCH BLOCK (RBBB)", { x: 0.5, y: 1.05, w: 5.8, h: 0.3, fontSize: 13, bold: true, color: C.navy, margin: 0 });
  const rbbbFeats = [
    "QRS duration ≥ 0.12 sec (3 small boxes)",
    "RSR' pattern in V1 ('M' shaped / 'rabbit ears')",
    "Wide S wave in I, V5, V6",
    "Secondary ST-T changes in V1-V2",
    "Incomplete RBBB: QRS 0.10–0.12 sec + RSR'",
    "Causes: RVH, PE, anterior MI, ASD, normal variant",
  ];
  rbbbFeats.forEach((f, i) => {
    const yy = 1.52 + i * 0.62;
    card(s, 0.5, yy, 5.8, 0.55, i % 2 === 0 ? C.deep : C.lightbg);
    s.addText("• " + f, { x: 0.65, y: yy + 0.1, w: 5.5, h: 0.35, fontSize: 12.5, color: C.white, margin: 0, wrap: true });
  });
  card(s, 0.5, 5.38, 5.8, 1.0, "0D2137");
  s.addText("Mnemonic: 'MaRRoW' — RBBB has M in V1, W in V6\n'WiLLiaM' — LBBB has W in V1, M in V6", {
    x: 0.65, y: 5.48, w: 5.5, h: 0.85, fontSize: 13, color: C.yellow, italic: true, bold: true, wrap: true, margin: 0
  });

  // LBBB
  card(s, 6.75, 1.0, 6.2, 5.7, C.lightbg);
  s.addShape("rect", { x: 6.75, y: 1.0, w: 6.2, h: 0.4, fill: { color: C.red }, line: { type: "none" } });
  s.addText("LEFT BUNDLE BRANCH BLOCK (LBBB)", { x: 6.9, y: 1.05, w: 5.9, h: 0.3, fontSize: 13, bold: true, color: C.white, margin: 0 });
  const lbbbFeats = [
    "QRS duration ≥ 0.12 sec",
    "Broad, notched ('M') R wave in I, V5, V6",
    "Deep QS or rS in V1",
    "No septal Q in I, V5, V6",
    "Secondary ST-T changes (discordant)",
    "NEW LBBB + chest pain = STEMI equivalent",
  ];
  lbbbFeats.forEach((f, i) => {
    const yy = 1.52 + i * 0.62;
    const bg = i === 5 ? "2D0A0E" : (i % 2 === 0 ? C.deep : C.lightbg);
    card(s, 6.9, yy, 5.9, 0.55, bg);
    s.addText("• " + f, { x: 7.05, y: yy + 0.1, w: 5.6, h: 0.35, fontSize: 12.5, color: i === 5 ? C.red : C.white, bold: i === 5, margin: 0, wrap: true });
  });
  card(s, 6.9, 5.38, 5.9, 1.0, "0D2137");
  s.addText("'WiLLiaM MaRRoW' — this mnemonic covers both blocks simultaneously.\nLBBB masks ischaemia — use Sgarbossa criteria to detect MI behind LBBB.", {
    x: 7.05, y: 5.48, w: 5.7, h: 0.85, fontSize: 12, color: C.yellow, italic: true, wrap: true, margin: 0
  });
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 13 – Electrolyte & Other ECG Changes
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.teal);
  slideTitle(s, "ECG Changes in Electrolyte Disorders & Special Patterns");

  const items = [
    {
      title: "Hyperkalaemia (High K⁺)",
      color: C.red,
      x: 0.35, y: 1.05, w: 6.1, h: 2.5,
      pts: ["K⁺ 5.5–6.0: Peaked ('tent') T waves (earliest sign)", "K⁺ 6.0–7.0: Flat P waves, wide QRS", "K⁺ 7.0–8.0: Sine-wave pattern", "K⁺ >8.0: VF / asystole"],
    },
    {
      title: "Hypokalaemia (Low K⁺)",
      color: C.green,
      x: 6.75, y: 1.05, w: 6.2, h: 2.5,
      pts: ["Flat or inverted T waves", "Prominent U wave (after T wave)", "U wave > T wave amplitude", "Prolonged QU interval, risk of torsades"],
    },
    {
      title: "Hypercalcaemia",
      color: C.yellow,
      x: 0.35, y: 3.65, w: 6.1, h: 1.8,
      pts: ["Shortened QT interval", "Short ST segment / absent ST", "Bradycardia in severe cases"],
    },
    {
      title: "Hypocalcaemia",
      color: C.teal,
      x: 6.75, y: 3.65, w: 6.2, h: 1.8,
      pts: ["Prolonged QT interval (QTc >440 ms)", "Flat T waves", "Risk of torsades de pointes"],
    },
    {
      title: "Pericarditis",
      color: C.yellow,
      x: 0.35, y: 5.55, w: 6.1, h: 1.65,
      pts: ["Diffuse (saddle-shaped) ST elevation — all leads", "PR depression (especially aVR: PR elevation)", "No reciprocal changes (unlike STEMI)", "Evolves over hours-days"],
    },
    {
      title: "Pulmonary Embolism (PE)",
      color: C.red,
      x: 6.75, y: 5.55, w: 6.2, h: 1.65,
      pts: ["Sinus tachycardia (most common)", "S1Q3T3 pattern (S in I, Q in III, inverted T in III)", "RBBB (right heart strain)", "T-wave inversions V1–V4"],
    },
  ];

  items.forEach(item => {
    card(s, item.x, item.y, item.w, item.h, C.lightbg);
    s.addShape("rect", { x: item.x, y: item.y, w: item.w, h: 0.35, fill: { color: item.color }, line: { type: "none" } });
    s.addText(item.title, { x: item.x + 0.15, y: item.y + 0.04, w: item.w - 0.2, h: 0.28, fontSize: 12.5, bold: true, color: C.navy, margin: 0 });
    item.pts.forEach((p, j) => {
      s.addText("• " + p, { x: item.x + 0.2, y: item.y + 0.45 + j * 0.43, w: item.w - 0.3, h: 0.4, fontSize: 12, color: C.white, wrap: true, margin: 0 });
    });
  });
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 14 – Quick Reference Summary
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  accentBar(s, C.teal);
  slideTitle(s, "Quick Reference — ECG at a Glance");

  const rows2 = [
    ["Finding",                  "Think of...",                          "Key Diagnostic Feature"],
    ["Wide QRS + tachycardia",   "VT vs SVT with aberrancy",             "AV dissociation, fusion beats → VT"],
    ["Irregularly irregular",    "Atrial fibrillation",                  "No P waves, fibrillatory baseline"],
    ["Sawtooth flutter waves",   "Atrial flutter",                       "~300/min atrial rate, 2:1 conduction"],
    ["ST elevation",             "STEMI / pericarditis / Brugada",       "Contiguous leads + reciprocal = STEMI"],
    ["ST depression",            "Ischaemia / NSTEMI / digoxin",         "Horizontal/downsloping = ischaemia"],
    ["Prolonged PR",             "1st degree AV block",                  "PR > 0.20 sec, all QRS present"],
    ["Dropped QRS",              "2nd degree AV block",                  "Mobitz I (PR ↑) vs Mobitz II (constant PR)"],
    ["P-QRS dissociation",       "3rd degree / complete heart block",    "P rate ≠ QRS rate, independent"],
    ["Peaked T waves",           "Hyperkalaemia",                        "Symmetric, narrow peak — earliest sign"],
    ["Prominent U wave",         "Hypokalaemia",                         "U > T wave, prolonged QU"],
    ["Delta wave + short PR",    "WPW / pre-excitation",                 "Slurred QRS upstroke, risk AF→VF"],
    ["S1Q3T3 + sinus tachy",     "Pulmonary embolism",                   "RV strain pattern, RBBB possible"],
  ];

  const colWidths = [3.2, 3.8, 5.5];
  const colX = [0.35, 3.65, 7.55];
  const headerColors = [C.teal, C.teal, C.teal];

  rows2.forEach((row, ri) => {
    const yy = 1.0 + ri * 0.48;
    const isHeader = ri === 0;
    const bg = isHeader ? "0D2137" : (ri % 2 === 0 ? C.lightbg : C.deep);
    card(s, 0.35, yy, 12.6, 0.44, bg);
    row.forEach((cell, ci) => {
      s.addText(cell, {
        x: colX[ci] + 0.1, y: yy + 0.07, w: colWidths[ci] - 0.15, h: 0.3,
        fontSize: isHeader ? 12.5 : 11.5,
        bold: isHeader || ci === 0,
        color: isHeader ? C.teal : (ci === 0 ? C.yellow : (ci === 1 ? C.white : C.grey)),
        margin: 0, wrap: true
      });
    });
  });
}

// ════════════════════════════════════════════════════════════════════════════
// SLIDE 15 – Thank You / Approach Flowchart
// ════════════════════════════════════════════════════════════════════════════
{
  let s = pres.addSlide();
  darkBg(s);
  s.addShape("rect", { x: 0, y: 0, w: 13.3, h: 0.12, fill: { color: C.teal }, line: { type: "none" } });
  s.addShape("rect", { x: 0, y: 7.38, w: 13.3, h: 0.12, fill: { color: C.teal }, line: { type: "none" } });

  s.addText("ECG INTERPRETATION FLOWCHART", {
    x: 0.5, y: 0.25, w: 12, h: 0.5, fontSize: 18, bold: true, color: C.teal, align: "center", charSpacing: 4, margin: 0
  });

  const steps2 = [
    { label: "1. RATE", sub: "< 60 Brady  |  60–100 Normal  |  >100 Tachy", color: C.teal },
    { label: "2. RHYTHM", sub: "Regular? Irregular? Irregularly irregular?", color: C.green },
    { label: "3. P WAVES", sub: "Present, upright, 1:1?", color: C.yellow },
    { label: "4. PR INTERVAL", sub: "Normal 0.12–0.20 s", color: C.teal },
    { label: "5. QRS", sub: "Narrow < 0.12 s  vs  Wide ≥ 0.12 s", color: C.yellow },
    { label: "6. ST/T", sub: "Elevation / depression / T inversion", color: C.red },
    { label: "7. QTc", sub: "< 440 ms (men) / < 460 ms (women)", color: C.teal },
    { label: "8. CLINICAL CONTEXT", sub: "Symptoms + history + previous ECG", color: C.green },
  ];

  steps2.forEach((st, i) => {
    const col = i % 4, row = Math.floor(i / 4);
    const x = 0.35 + col * 3.25;
    const y = 0.98 + row * 3.05;
    const w2 = 3.0, h2 = 2.85;
    card(s, x, y, w2, h2, C.lightbg);
    s.addShape("ellipse", { x: x + w2/2 - 0.5, y: y + 0.2, w: 1.0, h: 1.0, fill: { color: st.color }, line: { type: "none" } });
    s.addText(st.label, { x: x + w2/2 - 0.5, y: y + 0.2, w: 1.0, h: 1.0, fontSize: 11, bold: true, color: C.navy, align: "center", valign: "middle", margin: 0, wrap: true });
    s.addText(st.sub, { x: x + 0.15, y: y + 1.35, w: w2 - 0.25, h: 1.35, fontSize: 11.5, color: C.white, align: "center", wrap: true, margin: 0 });
  });

  s.addText("Master the systematic approach — every ECG, every time.", {
    x: 0.5, y: 7.15, w: 12, h: 0.2, fontSize: 13, color: C.grey, italic: true, align: "center", margin: 0
  });
}

// ─── Write file ───────────────────────────────────────────────────────────────
pres.writeFile({ fileName: "/home/daytona/workspace/ecg-slides/ECG_Medical_Student_Guide.pptx" })
  .then(() => console.log("✅ Presentation saved!"))
  .catch(e => console.error("Error:", e));
Running Command

cd /home/daytona/workspace/ecg-slides && node build.js 2>&1

You've run out of free credits. Sign up for a free account to continue using Orris.
This is a shared conversation. Sign in to Orris to start your own chat.