rbeleze/surgical-amputation-simulator
0
1// Surgical Amputation Simulator JavaScript2 3class SurgicalSimulator {4 constructor() {5 this.currentStep = 0;6 this.selectedTool = null;7 this.isSurgeryActive = false;8 this.patientId = this.generatePatientId();9 this.caseData = this.generateCase();10 this.init();11 }12 13 generatePatientId() {14 return 'PT-' + Math.random().toString(36).substr(2, 8).toUpperCase();15 }16 17 generateCase() {18 const cases = [19 {20 diagnosis: 'Severe Peripheral Arterial Disease',21 procedure: 'Below-knee Amputation (BKA)',22 reason: 'Non-viable tissue due to chronic ischemia and gangrene. Patient has failed conservative management and revascularization attempts.',23 limb: 'rightLeg',24 severity: 'critical'25 },26 {27 diagnosis: 'Diabetic Foot Infection with Osteomyelitis',28 procedure: 'Trans-metatarsal Amputation (TMA)',29 reason: 'Progressive infection involving metatarsal bones. Failed antibiotic therapy and debridement procedures.',30 limb: 'rightLeg',31 severity: 'moderate'32 },33 {34 diagnosis: 'Traumatic Crush Injury',35 procedure: 'Above-knee Amputation (AKA)',36 reason: 'Irreparable soft tissue and vascular damage from industrial accident. Multiple failed replantation attempts.',37 limb: 'rightLeg',38 severity: 'acute'39 },40 {41 diagnosis: 'Malignant Soft Tissue Sarcoma',42 procedure: 'Forequarter Amputation',43 reason: 'Large soft tissue malignancy involving shoulder girdle. Requires wide surgical margin for curative intent.',44 limb: 'rightArm',45 severity: 'oncologic'46 },47 {48 diagnosis: 'Chronic Osteomyelitis',49 procedure: 'Below-elbow Amputation (BEA)',50 reason: 'Persistent bone infection despite multiple surgical debridements and long-term antibiotic therapy.',51 limb: 'rightArm',52 severity: 'chronic'53 }54 ];55 56 return cases[Math.floor(Math.random() * cases.length)];57 }58 59 init() {60 this.updatePatientInfo();61 this.setupEventListeners();62 this.startVitalsMonitoring();63 this.updateSurgeryInstruction('Select a surgical tool to begin the procedure');64 }65 66 updatePatientInfo() {67 document.getElementById('patientId').textContent = this.patientId;68 document.getElementById('diagnosis').textContent = this.caseData.diagnosis;69 document.getElementById('procedure').textContent = this.caseData.procedure;70 document.getElementById('amputationReason').textContent = this.caseData.reason;71 }72 73 setupEventListeners() {74 // Tool selection75 document.querySelectorAll('.tool-btn').forEach(btn => {76 btn.addEventListener('click', (e) => {77 this.selectTool(e.currentTarget.dataset.tool);78 });79 });80 81 // Operating field interactions82 const operatingField = document.querySelector('.operating-field');83 operatingField.addEventListener('mousemove', (e) => {84 this.updateSurgicalCursor(e);85 });86 87 operatingField.addEventListener('click', (e) => {88 this.handleSurgicalAction(e);89 });90 91 // Modal buttons92 document.getElementById('newProcedure').addEventListener('click', () => {93 this.newProcedure();94 });95 96 document.getElementById('viewReport').addEventListener('click', () => {97 this.viewReport();98 });99 }100 101 selectTool(tool) {102 // Remove active class from all tools103 document.querySelectorAll('.tool-btn').forEach(btn => {104 btn.classList.remove('active');105 });106 107 // Add active class to selected tool108 const selectedBtn = document.querySelector(`[data-tool="${tool}"]`);109 selectedBtn.classList.add('active');110 111 this.selectedTool = tool;112 this.updateSurgicalCursorStyle();113 114 // Show surgical cursor115 document.querySelector('.surgical-cursor').style.display = 'block';116 117 // Update instruction based on tool118 this.updateSurgeryInstruction(this.getInstructionForTool(tool));119 }120 121 getInstructionForTool(tool) {122 const instructions = {123 scalpel: 'Click and drag to make the initial skin incision. Follow the marked surgical line.',124 saw: 'Position over the bone and click to begin bone cutting. Maintain steady pressure.',125 retractor: 'Click to deploy retractors and improve surgical exposure.',126 clamp: 'Click to apply vascular clamps to control bleeding.'127 };128 129 return instructions[tool] || 'Select a tool to continue the procedure';130 }131 132 updateSurgicalCursor(e) {133 const cursor = document.querySelector('.surgical-cursor');134 cursor.style.left = e.clientX - 16 + 'px';135 cursor.style.top = e.clientY - 16 + 'px';136 }137 138 updateSurgicalCursorStyle() {139 const cursor = document.querySelector('.surgical-cursor');140 const icon = cursor.querySelector('i');141 142 // Reset all classes143 icon.className = 'w-full h-full text-red-500 animate-pulse';144 145 // Apply tool-specific styling146 switch (this.selectedTool) {147 case 'scalpel':148 icon.className = 'w-full h-full text-red-500';149 break;150 case 'saw':151 icon.className = 'w-full h-full text-gray-500';152 break;153 case 'retractor':154 icon.className = 'w-full h-full text-blue-500';155 break;156 case 'clamp':157 icon.className = 'w-full h-full text-green-500';158 break;159 }160 }161 162 handleSurgicalAction(e) {163 if (!this.selectedTool) {164 this.updateSurgeryInstruction('Please select a surgical tool first');165 return;166 }167 168 if (!this.isSurgeryActive && this.selectedTool === 'scalpel') {169 this.beginSurgery();170 }171 172 switch (this.selectedTool) {173 case 'scalpel':174 this.makeIncision(e);175 break;176 case 'saw':177 this.cutBone(e);178 break;179 case 'retractor':180 this.deployRetractor(e);181 break;182 case 'clamp':183 this.applyClamp(e);184 break;185 }186 }187 188 beginSurgery() {189 this.isSurgeryActive = true;190 this.updateSurgeryInstruction('Begin skin incision. Click and drag to cut through the skin and subcutaneous tissue.');191 192 // Show surgery area193 const surgeryArea = document.getElementById('surgeryArea');194 surgeryArea.style.display = 'block';195 196 // Position surgery area over target limb197 const targetLimb = document.getElementById(this.caseData.limb);198 const surgeryAreaEl = surgeryArea;199 200 if (this.caseData.limb === 'rightLeg') {201 surgeryAreaEl.style.left = '45%';202 surgeryAreaEl.style.top = '60%';203 surgeryAreaEl.style.width = '40px';204 surgeryAreaEl.style.height = '20px';205 } else {206 surgeryAreaEl.style.left = '70%';207 surgeryAreaEl.style.top = '35%';208 surgeryAreaEl.style.width = '20px';209 surgeryAreaEl.style.height = '40px';210 }211 }212 213 makeIncision(e) {214 if (!this.isSurgeryActive) {215 this.updateSurgeryInstruction('Begin the procedure by selecting the scalpel and clicking on the patient');216 return;217 }218 219 // Create incision line220 const incisionLine = document.getElementById('incisionLine');221 incisionLine.classList.add('incision-animation');222 223 // Add blood effect224 this.addBloodEffect(e);225 226 // Update step progress227 this.completeStep(1);228 this.currentStep = 1;229 230 this.updateSurgeryInstruction('Skin incision complete. Now select the bone saw to cut through the bone.');231 232 // Auto-advance to next tool suggestion233 setTimeout(() => {234 this.suggestNextTool('saw');235 }, 2000);236 }237 238 cutBone(e) {239 if (this.currentStep < 1) {240 this.updateSurgeryInstruction('Complete the skin incision first before cutting bone');241 return;242 }243 244 // Show bone area245 const boneArea = document.getElementById('boneArea');246 boneArea.style.display = 'block';247 248 // Create bone cutting effect249 const boneCut = document.createElement('div');250 boneCut.className = 'bone-cutting-effect';251 boneCut.style.position = 'absolute';252 boneCut.style.left = '20%';253 boneCut.style.top = '50%';254 boneCut.style.width = '60%';255 boneCut.style.transform = 'rotate(0deg)';256 document.querySelector('.operating-field').appendChild(boneCut);257 258 // Animate bone cutting259 gsap.fromTo(boneCut, { width: '0%' }, { width: '100%', duration: 1, ease: 'power2.inOut' });260 261 // Complete the amputation262 setTimeout(() => {263 this.completeAmputation();264 }, 1500);265 266 // Update step progress267 this.completeStep(3);268 this.currentStep = 3;269 270 this.updateSurgeryInstruction('Bone cutting in progress. Applying oscillating saw technique.');271 }272 273 deployRetractor(e) {274 this.updateSurgeryInstruction('Retractors deployed for better visualization of the surgical field.');275 }276 277 applyClamp(e) {278 this.updateSurgeryInstruction('Vascular clamps applied to control bleeding during the procedure.');279 }280 281 completeAmputation() {282 // Animate limb removal283 const targetLimb = document.getElementById(this.caseData.limb);284 gsap.to(targetLimb, {285 opacity: 0.3,286 scale: 0.8,287 duration: 1,288 ease: 'power2.inOut'289 });290 291 // Update progress292 this.completeStep(4);293 294 // Show completion295 setTimeout(() => {296 this.showResults();297 }, 2000);298 299 this.updateSurgeryInstruction('Amputation complete. Preparing for stump closure.');300 }301 302 completeStep(stepNumber) {303 const stepIndicator = document.getElementById(`step${stepNumber}`);304 if (stepIndicator) {305 stepIndicator.classList.add('step-complete');306 gsap.fromTo(stepIndicator, 307 { backgroundColor: '#d1d5db' },308 { backgroundColor: '#ef4444', duration: 0.5 }309 );310 }311 }312 313 suggestNextTool(tool) {314 const toolBtn = document.querySelector(`[data-tool="${tool}"]`);315 if (toolBtn) {316 // Highlight the suggested tool317 gsap.to(toolBtn, {318 scale: 1.1,319 duration: 0.3,320 yoyo: true,321 repeat: 3322 });323 }324 }325 326 addBloodEffect(e) {327 const blood = document.createElement('div');328 blood.className = 'blood-effect';329 blood.style.left = (e.clientX - 2) + 'px';330 blood.style.top = (e.clientY - 10) + 'px';331 332 document.querySelector('.operating-field').appendChild(blood);333 334 setTimeout(() => {335 blood.remove();336 }, 1000);337 }338 339 showResults() {340 const modal = document.getElementById('resultModal');341 const message = document.getElementById('resultMessage');342 343 message.textContent = `The ${this.caseData.procedure.toLowerCase()} has been completed successfully. Patient vitals remain stable.`;344 modal.style.display = 'flex';345 modal.querySelector('.bg-white').classList.add('modal-enter');346 347 // Add completion class to body348 document.body.classList.add('amputation-complete');349 }350 351 newProcedure() {352 // Reset everything353 document.getElementById('resultModal').style.display = 'none';354 document.body.classList.remove('amputation-complete');355 this.currentStep = 0;356 this.isSurgeryActive = false;357 this.patientId = this.generatePatientId();358 this.caseData = this.generateCase();359 360 // Reset UI elements361 document.querySelectorAll('[id^="step"]').forEach(step => {362 step.className = 'w-4 h-4 rounded-full bg-gray-300';363 step.style.backgroundColor = '#d1d5db';364 });365 366 // Reset limb367 const targetLimb = document.getElementById(this.caseData.limb);368 gsap.to(targetLimb, { opacity: 1, scale: 1, duration: 0.5 });369 370 // Reset surgery area371 document.getElementById('surgeryArea').style.display = 'none';372 373 // Reset tools374 document.querySelectorAll('.tool-btn').forEach(btn => {375 btn.classList.remove('active');376 });377 378 this.updatePatientInfo();379 this.updateSurgeryInstruction('New procedure ready. Select a surgical tool to begin.');380 }381 382 viewReport() {383 alert('Generating detailed surgical report...\n\nProcedure: ' + this.caseData.procedure + '\nDuration: ~45 minutes\nComplications: None\nPatient Status: Stable');384 }385 386 updateSurgeryInstruction(text) {387 const instruction = document.getElementById('surgeryInstruction');388 instruction.textContent = text;389 }390 391 startVitalsMonitoring() {392 setInterval(() => {393 this.updateVitals();394 }, 3000);395 }396 397 updateVitals() {398 // Simulate realistic vital signs with slight variations399 const bp = 110 + Math.random() * 30;400 const hr = 70 + Math.random() * 20;401 const o2 = 95 + Math.random() * 5;402 const temp = 98 + Math.random() * 2;403 404 document.getElementById('bp').textContent = Math.round(bp) + '/' + Math.round(bp - 20);405 document.getElementById('hr').textContent = Math.round(hr) + ' bpm';406 document.getElementById('o2').textContent = Math.round(o2) + '%';407 document.getElementById('temp').textContent = (temp).toFixed(1) + '°F';408 409 // Add update animation410 document.querySelector('.space-y-3').classList.add('vitals-monitoring');411 setTimeout(() => {412 document.querySelector('.space-y-3').classList.remove('vitals-monitoring');413 }, 300);414 }415}416 417// Initialize the simulator when DOM is loaded418document.addEventListener('DOMContentLoaded', () => {419 new SurgicalSimulator();420});