CoolFace
Apppublic

rbeleze/surgical-amputation-simulator

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
script.js420 linesDownload Raw Back to root
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});