Spaces:
Running
Running
GOWREESH M G commited on
Update templates/index.html
Browse files- templates/index.html +304 -135
templates/index.html
CHANGED
|
@@ -69,6 +69,7 @@
|
|
| 69 |
@keyframes floatBlob { 0% { transform: translate(0, 0) scale(1); } 100% { transform: translate(50px, 80px) scale(1.1); } }
|
| 70 |
|
| 71 |
.glass-island { background: var(--glass-bg); backdrop-filter: blur(40px); border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow); border-radius: 2.5rem; transition: all 0.5s; }
|
|
|
|
| 72 |
.inner-btn { background: var(--btn-bg); border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 2.2rem; padding: 1.6rem; display: flex; align-items: center; gap: 1.5rem; cursor: pointer; transition: all 0.4s; }
|
| 73 |
.inner-btn:hover:not(.disabled) { transform: scale(1.04) translateY(-6px); background: rgba(255, 255, 255, 0.25); box-shadow: 0 15px 35px rgba(124, 58, 237, 0.15); }
|
| 74 |
html.dark .inner-btn:hover:not(.disabled) { background: rgba(255, 255, 255, 0.12); box-shadow: 0 15px 35px rgba(217, 70, 239, 0.2); }
|
|
@@ -78,10 +79,24 @@
|
|
| 78 |
.icon-vibrant { transition: all 0.4s ease; }
|
| 79 |
.icon-purple { color: #6d28d9; }
|
| 80 |
.icon-pink { color: #db2777; }
|
|
|
|
| 81 |
html.dark .icon-purple { color: #a855f7; filter: drop-shadow(0 0 12px rgba(168, 85, 247, 0.6)); }
|
| 82 |
html.dark .icon-pink { color: #f472b6; filter: drop-shadow(0 0 12px rgba(244, 114, 182, 0.6)); }
|
|
|
|
| 83 |
.inner-btn:hover:not(.disabled) .icon-vibrant { transform: scale(1.2) rotate(-5deg); }
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
.status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; background: rgba(255, 255, 255, 0.25); border: 1px solid rgba(255, 255, 255, 0.4); border-radius: 99px; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-secondary); backdrop-filter: blur(10px); }
|
| 86 |
html.dark .status-badge { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.15); }
|
| 87 |
.status-dot { width: 8px; height: 8px; background: #22c55e; border-radius: 50%; box-shadow: 0 0 10px #22c55e; animation: pulse 2s infinite; }
|
|
@@ -107,7 +122,6 @@
|
|
| 107 |
|
| 108 |
/* Modal Glass styles */
|
| 109 |
.modal-glass { background: rgba(30, 30, 30, 0.45); backdrop-filter: blur(40px); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 2rem; box-shadow: 0 25px 60px rgba(0,0,0,0.5); }
|
| 110 |
-
|
| 111 |
.img-magnifier-glass { position: absolute; border: 3px solid rgba(255,255,255,0.8); border-radius: 50%; cursor: none; width: 100px; height: 100px; display: none; box-shadow: 0 10px 20px rgba(0,0,0,0.5); pointer-events: none; z-index: 30; }
|
| 112 |
|
| 113 |
@keyframes scan { 0% { top: 0%; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } 100% { top: 100%; opacity: 0; } }
|
|
@@ -121,14 +135,17 @@
|
|
| 121 |
* { text-shadow: none !important; }
|
| 122 |
h2, p, span, div { color: black !important; }
|
| 123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
.print-header { display: block !important; text-align: center; margin-bottom: 25px; border-bottom: 2px solid #000; padding-bottom: 15px; }
|
| 125 |
.print-header h1 { font-size: 22px; margin-bottom: 5px; font-weight: bold; font-family: 'Times New Roman', serif; }
|
| 126 |
.print-header p { font-size: 12px; color: #555 !important; font-family: 'Times New Roman', serif; margin: 2px 0; }
|
| 127 |
|
| 128 |
.result-image-frame { margin: 0 auto 20px auto !important; width: 220px !important; height: 220px !important; border: 2px solid #000 !important; border-radius: 10px !important; box-shadow: none !important; }
|
| 129 |
-
#
|
| 130 |
-
#result-icon-bg
|
| 131 |
-
#result-icon-bg i { color: black !important; }
|
| 132 |
.bg-black\/5, .bg-blue-500\/10 { background: white !important; border: 1px solid #aaa !important; }
|
| 133 |
|
| 134 |
.print-details { display: block !important; margin-top: 25px; text-align: left; border-top: 2px solid #000; padding-top: 15px;}
|
|
@@ -139,10 +156,12 @@
|
|
| 139 |
</style>
|
| 140 |
</head>
|
| 141 |
<body>
|
|
|
|
| 142 |
<div id="initial-loader" class="fixed inset-0 z-[999] flex flex-col items-center justify-center">
|
| 143 |
<div class="relative flex flex-col items-center">
|
| 144 |
<div class="mb-4 relative">
|
| 145 |
<div class="absolute inset-0 bg-purple-500 blur-2xl opacity-40 rounded-full animate-pulse"></div>
|
|
|
|
| 146 |
<svg width="76" height="76" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" class="relative z-10 drop-shadow-2xl">
|
| 147 |
<defs>
|
| 148 |
<linearGradient id="loadMainGradient" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse"><stop offset="0%" stop-color="#7c3aed"/><stop offset="100%" stop-color="#d946ef"/></linearGradient>
|
|
@@ -174,6 +193,7 @@
|
|
| 174 |
</div>
|
| 175 |
</div>
|
| 176 |
|
|
|
|
| 177 |
<div class="bg-blob blob-1"></div><div class="bg-blob blob-2"></div>
|
| 178 |
|
| 179 |
<button id="theme-toggle-btn" onclick="toggleTheme()" class="fixed top-6 right-6 z-50 p-2.5 rounded-full glass-island hover:scale-110 active:scale-95 transition-all shadow-lg backdrop-blur-md">
|
|
@@ -181,13 +201,21 @@
|
|
| 181 |
</button>
|
| 182 |
|
| 183 |
<main class="relative z-10 w-full min-h-screen flex flex-col items-center pt-20 pb-12 px-6">
|
|
|
|
| 184 |
<!-- VIEW 1: HOME -->
|
| 185 |
<div id="view-home" class="view-section active">
|
| 186 |
-
<div class="text-center mb-
|
| 187 |
-
<
|
|
|
|
| 188 |
<defs>
|
| 189 |
-
<linearGradient id="mainGradient" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
</defs>
|
| 192 |
<rect x="32" y="32" width="448" height="448" rx="110" fill="url(#mainGradient)" />
|
| 193 |
<path d="M256 160C176 160 110 210 80 256C110 302 176 352 256 352C336 352 402 302 432 256C402 210 336 160 256 160Z" fill="white"/>
|
|
@@ -196,24 +224,31 @@
|
|
| 196 |
<circle cx="276" cy="236" r="10" fill="white" fill-opacity="0.9"/>
|
| 197 |
<ellipse cx="256" cy="110" rx="160" ry="80" fill="white" fill-opacity="0.15"/>
|
| 198 |
<g stroke="white" stroke-width="8" stroke-linecap="round" stroke-opacity="0.9">
|
| 199 |
-
<path d="M140 140 L110 140 L110 170" fill="none"/>
|
|
|
|
|
|
|
|
|
|
| 200 |
</g>
|
| 201 |
<rect x="110" y="254" width="292" height="4" rx="2" fill="white" fill-opacity="0.6"/>
|
| 202 |
</svg>
|
|
|
|
| 203 |
<div>
|
| 204 |
-
<h1 class="text-
|
| 205 |
-
<div class="status-badge"><span class="status-dot"></span>
|
| 206 |
</div>
|
| 207 |
</div>
|
| 208 |
|
|
|
|
| 209 |
<div class="glass-island p-8 flex flex-col gap-5 w-full max-w-[420px]" id="selection-island">
|
| 210 |
-
|
| 211 |
-
<i data-lucide="camera" class="w-10 h-10 icon-vibrant icon-purple"></i>
|
| 212 |
-
<div class="text-left"><span class="block text-xl font-bold">Take Photo</span><span class="sub-label opacity-70">PHONE CAMERA</span></div>
|
| 213 |
-
</div>
|
| 214 |
<div onclick="triggerUpload()" id="btn-upload" class="inner-btn group">
|
| 215 |
-
<i data-lucide="image" class="w-
|
| 216 |
-
<div class="text-left"><span class="block text-xl font-bold">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
</div>
|
| 218 |
</div>
|
| 219 |
|
|
@@ -221,11 +256,13 @@
|
|
| 221 |
<i data-lucide="lightbulb" class="w-5 h-5 text-yellow-500 mt-1 flex-shrink-0"></i>
|
| 222 |
<div>
|
| 223 |
<h3 class="font-bold text-sm mb-1">Quick Tip</h3>
|
| 224 |
-
<p class="text-xs opacity-70 leading-relaxed">
|
| 225 |
</div>
|
| 226 |
</div>
|
| 227 |
-
<footer class="mt-auto pt-8 text-center opacity-40 print-hide"><p class="text-[10px] font-mono uppercase tracking-widest">Powered by PyTorch Ensemble</p></footer>
|
| 228 |
|
|
|
|
|
|
|
|
|
|
| 229 |
<div id="scanning-section" class="w-full max-w-[420px] glass-island p-8 flex flex-col items-center">
|
| 230 |
<div class="relative w-56 h-56 rounded-[2rem] overflow-hidden shadow-2xl mb-6 bg-black/5 dark:bg-black/40 border border-black/10 dark:border-white/20">
|
| 231 |
<img id="scan-preview" src="" class="w-full h-full object-cover opacity-90" />
|
|
@@ -240,7 +277,7 @@
|
|
| 240 |
<input type="file" id="file-input" class="hidden" accept="image/*" onchange="handleFileSelect(event)" />
|
| 241 |
</div>
|
| 242 |
|
| 243 |
-
<!-- VIEW 2: RESULTS -->
|
| 244 |
<div id="view-result" class="view-section">
|
| 245 |
<div class="print-header">
|
| 246 |
<h1>RETINASCAN MEDICAL DIAGNOSTICS</h1>
|
|
@@ -249,7 +286,6 @@
|
|
| 249 |
</div>
|
| 250 |
|
| 251 |
<div class="result-image-frame print-hide group relative" onclick="openImageModal()">
|
| 252 |
-
<!-- Hover overlay icon indicating it's clickable -->
|
| 253 |
<div class="absolute inset-0 bg-black/40 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity z-10 rounded-[1.6rem]">
|
| 254 |
<i data-lucide="maximize-2" class="w-8 h-8 text-white"></i>
|
| 255 |
<span class="text-white text-xs font-bold mt-2 uppercase tracking-widest drop-shadow-md">View Full Screen</span>
|
|
@@ -266,7 +302,6 @@
|
|
| 266 |
<p id="result-severity" class="sub-label text-purple-600 dark:text-purple-400 mb-4 font-black text-xs print-hide">ANALYSIS COMPLETE</p>
|
| 267 |
<p id="result-desc" class="text-sm opacity-70 mb-6 font-medium text-center leading-relaxed max-w-[260px] print-hide">--</p>
|
| 268 |
|
| 269 |
-
<!-- Confidence Score (Hidden on Print) -->
|
| 270 |
<div class="w-full bg-black/5 dark:bg-white/5 p-4 rounded-2xl border border-black/10 dark:border-white/10 text-center mb-4 shadow-inner print-hide">
|
| 271 |
<p class="sub-label mb-1 opacity-50">AI Confidence Score</p>
|
| 272 |
<p id="result-conf" class="text-2xl font-mono font-bold">--%</p>
|
|
@@ -292,7 +327,6 @@
|
|
| 292 |
<p id="print-meds">---</p>
|
| 293 |
</div>
|
| 294 |
|
| 295 |
-
<!-- Sync'd Action Buttons (Main View) -->
|
| 296 |
<div class="flex gap-3 w-full mb-3 print-hide">
|
| 297 |
<button onclick="toggleHeatmap()" id="btn-main-heatmap" class="flex-[1] py-3.5 rounded-2xl bg-purple-500/10 hover:bg-purple-500/20 text-purple-700 dark:text-purple-300 font-bold text-sm border border-purple-500/20 transition-all hidden">
|
| 298 |
<i data-lucide="eye" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Heatmap
|
|
@@ -303,17 +337,149 @@
|
|
| 303 |
</div>
|
| 304 |
|
| 305 |
<button onclick="resetApp()" class="w-full py-4 rounded-2xl bg-purple-600 hover:bg-purple-700 text-white font-bold text-base shadow-xl shadow-purple-900/20 active:scale-95 transition-all print-hide">
|
| 306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
</button>
|
| 308 |
</div>
|
| 309 |
</div>
|
| 310 |
|
| 311 |
-
<!-- FULL SCREEN IMAGE MODAL
|
| 312 |
<div id="image-modal" class="hidden fixed inset-0 z-[200] flex items-center justify-center p-6">
|
| 313 |
<div class="absolute inset-0 bg-black/80 backdrop-blur-xl" onclick="closeImageModal()"></div>
|
| 314 |
|
| 315 |
<div class="relative w-full max-w-[420px] modal-glass p-6 flex flex-col items-center">
|
| 316 |
-
<!-- Close Button -->
|
| 317 |
<button onclick="closeImageModal()" class="absolute top-4 right-4 p-2.5 bg-white/10 hover:bg-white/20 rounded-full backdrop-blur text-white transition-all z-50">
|
| 318 |
<i data-lucide="x" class="w-5 h-5"></i>
|
| 319 |
</button>
|
|
@@ -322,45 +488,25 @@
|
|
| 322 |
<h3 class="text-white font-bold text-lg tracking-wider">Image Inspection</h3>
|
| 323 |
</div>
|
| 324 |
|
| 325 |
-
<!-- Large Image Frame with Interactive Magnifier -->
|
| 326 |
<div class="relative w-full aspect-square rounded-[1.5rem] overflow-hidden border-2 border-white/20 shadow-inner bg-black cursor-crosshair"
|
| 327 |
onmouseenter="showModalMagnifier(true)"
|
| 328 |
onmouseleave="showModalMagnifier(false)"
|
| 329 |
onmousemove="moveModalMagnifier(event)"
|
| 330 |
ontouchmove="moveModalMagnifier(event)">
|
| 331 |
-
|
| 332 |
<div id="modal-magnifier-glass" class="img-magnifier-glass"></div>
|
| 333 |
-
|
| 334 |
<img id="modal-photo" src="" class="w-full h-full object-cover transition-opacity duration-300" />
|
| 335 |
</div>
|
| 336 |
|
| 337 |
-
<!-- Heatmap Toggle Switch (Syncs with Main View) -->
|
| 338 |
<button onclick="toggleHeatmap()" id="btn-modal-heatmap" class="mt-6 w-full py-4 rounded-2xl bg-purple-500/20 hover:bg-purple-500/30 text-white font-bold text-sm border border-purple-500/30 transition-all hidden shadow-lg shadow-purple-500/10">
|
| 339 |
<i data-lucide="eye" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Toggle AI Heatmap
|
| 340 |
</button>
|
| 341 |
</div>
|
| 342 |
</div>
|
| 343 |
|
| 344 |
-
<!-- VIEW 3: CAMERA -->
|
| 345 |
-
<div id="view-camera" class="hidden fixed inset-0 bg-black z-[100] flex-col items-center justify-center">
|
| 346 |
-
<video id="camera-feed" autoplay playsinline class="absolute inset-0 w-full h-full object-cover opacity-80"></video>
|
| 347 |
-
<div class="absolute top-0 left-0 w-full p-8 flex justify-between items-start z-20">
|
| 348 |
-
<button onclick="stopCamera()" class="p-4 rounded-full bg-black/40 text-white backdrop-blur-md hover:bg-black/60"><i data-lucide="x" class="w-6 h-6"></i></button>
|
| 349 |
-
<button onclick="toggleTorch()" id="btn-torch" class="p-4 rounded-full bg-black/40 text-white backdrop-blur-md hover:bg-black/60 hidden"><i data-lucide="zap" class="w-6 h-6"></i></button>
|
| 350 |
-
</div>
|
| 351 |
-
<div class="relative z-10 w-72 h-72 rounded-full border-2 border-white/40 border-dashed shadow-[0_0_0_9999px_rgba(0,0,0,0.9)]"></div>
|
| 352 |
-
<div class="absolute bottom-40 z-20 flex flex-col items-center gap-2 text-center w-full px-6">
|
| 353 |
-
<p class="text-white font-bold text-lg tracking-wide drop-shadow-md">Align Eye in Circle</p>
|
| 354 |
-
<p class="text-white/70 text-xs font-medium max-w-[200px] bg-black/30 px-3 py-1 rounded-full backdrop-blur-sm">Ensure good lighting & no glare</p>
|
| 355 |
-
</div>
|
| 356 |
-
<div class="absolute bottom-12 flex justify-center w-full z-20">
|
| 357 |
-
<button onclick="capturePhoto()" class="w-20 h-20 rounded-full border-[4px] border-white flex items-center justify-center active:scale-90 hover:bg-white/10"><div class="w-16 h-16 rounded-full bg-white shadow-lg"></div></button>
|
| 358 |
-
</div>
|
| 359 |
-
<canvas id="camera-canvas" class="hidden"></canvas>
|
| 360 |
-
</div>
|
| 361 |
</main>
|
| 362 |
|
| 363 |
<script>
|
|
|
|
| 364 |
window.addEventListener('DOMContentLoaded', () => {
|
| 365 |
const isDark = document.documentElement.classList.contains('dark');
|
| 366 |
const metaTheme = document.getElementById('theme-color-meta');
|
|
@@ -397,18 +543,120 @@
|
|
| 397 |
setTimeout(() => { if(!v.classList.contains('active')) v.style.display = 'none'; }, 500);
|
| 398 |
});
|
| 399 |
const target = document.getElementById(id);
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
}
|
| 407 |
|
| 408 |
function resetApp() {
|
| 409 |
-
stopCamera();
|
| 410 |
document.getElementById('file-input').value = '';
|
| 411 |
-
document.getElementById('btn-camera').classList.remove('disabled');
|
| 412 |
document.getElementById('btn-upload').classList.remove('disabled');
|
| 413 |
document.getElementById('scanning-section').classList.remove('show');
|
| 414 |
setTimeout(() => {
|
|
@@ -419,58 +667,7 @@
|
|
| 419 |
switchView('view-home');
|
| 420 |
}
|
| 421 |
|
| 422 |
-
let originalImageSrc = '';
|
| 423 |
-
let heatmapImageSrc = '';
|
| 424 |
-
let isHeatmapShowing = false;
|
| 425 |
-
let stream = null; let track = null; let torchState = false;
|
| 426 |
-
|
| 427 |
-
async function startCameraFlow() {
|
| 428 |
-
if(document.getElementById('btn-camera').classList.contains('disabled')) return;
|
| 429 |
-
const camView = document.getElementById('view-camera');
|
| 430 |
-
camView.classList.remove('hidden'); camView.classList.add('flex');
|
| 431 |
-
document.getElementById('theme-toggle-btn').style.display = 'none';
|
| 432 |
-
try {
|
| 433 |
-
stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment', width: { ideal: 1920 }, height: { ideal: 1080 } } });
|
| 434 |
-
const videoEl = document.getElementById('camera-feed');
|
| 435 |
-
videoEl.srcObject = stream; track = stream.getVideoTracks()[0];
|
| 436 |
-
if (track.getCapabilities().torch) {
|
| 437 |
-
const torchBtn = document.getElementById('btn-torch');
|
| 438 |
-
torchBtn.classList.remove('hidden'); torchState = false;
|
| 439 |
-
}
|
| 440 |
-
} catch(e) {
|
| 441 |
-
alert("Could not access camera. Please check permissions.");
|
| 442 |
-
camView.classList.add('hidden'); document.getElementById('theme-toggle-btn').style.display = 'block';
|
| 443 |
-
}
|
| 444 |
-
}
|
| 445 |
-
|
| 446 |
-
async function toggleTorch() {
|
| 447 |
-
if (!track) return;
|
| 448 |
-
try {
|
| 449 |
-
torchState = !torchState;
|
| 450 |
-
await track.applyConstraints({ advanced: [{ torch: torchState }] });
|
| 451 |
-
const btn = document.getElementById('btn-torch');
|
| 452 |
-
if (torchState) { btn.classList.add('bg-yellow-500/80', 'text-black'); btn.classList.remove('bg-black/40', 'text-white'); }
|
| 453 |
-
else { btn.classList.remove('bg-yellow-500/80', 'text-black'); btn.classList.add('bg-black/40', 'text-white'); }
|
| 454 |
-
} catch (e) { torchState = !torchState; }
|
| 455 |
-
}
|
| 456 |
-
|
| 457 |
-
function stopCamera() {
|
| 458 |
-
if (track && torchState) {
|
| 459 |
-
track.applyConstraints({ advanced: [{ torch: false }] }).catch(() => {}); torchState = false;
|
| 460 |
-
const btn = document.getElementById('btn-torch');
|
| 461 |
-
btn.classList.remove('bg-yellow-500/80', 'text-black'); btn.classList.add('bg-black/40', 'text-white'); btn.classList.add('hidden');
|
| 462 |
-
}
|
| 463 |
-
if(stream) { stream.getTracks().forEach(t => t.stop()); stream = null; track = null; }
|
| 464 |
-
document.getElementById('view-camera').classList.add('hidden'); document.getElementById('view-camera').classList.remove('flex');
|
| 465 |
-
document.getElementById('theme-toggle-btn').style.display = 'block';
|
| 466 |
-
}
|
| 467 |
-
|
| 468 |
-
function capturePhoto() {
|
| 469 |
-
const video = document.getElementById('camera-feed'); const canvas = document.getElementById('camera-canvas');
|
| 470 |
-
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
|
| 471 |
-
const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 472 |
-
canvas.toBlob(blob => { stopCamera(); processImage(blob); }, 'image/jpeg', 0.95);
|
| 473 |
-
}
|
| 474 |
|
| 475 |
function triggerUpload() {
|
| 476 |
if(document.getElementById('btn-upload').classList.contains('disabled')) return;
|
|
@@ -482,17 +679,13 @@
|
|
| 482 |
function processImage(fileBlob) {
|
| 483 |
const reader = new FileReader();
|
| 484 |
reader.onload = (e) => {
|
| 485 |
-
document.getElementById('btn-camera').classList.add('disabled');
|
| 486 |
document.getElementById('btn-upload').classList.add('disabled');
|
| 487 |
-
|
| 488 |
originalImageSrc = e.target.result;
|
| 489 |
document.getElementById('scan-preview').src = originalImageSrc;
|
| 490 |
document.getElementById('result-photo').src = originalImageSrc;
|
| 491 |
-
|
| 492 |
const scanSection = document.getElementById('scanning-section');
|
| 493 |
scanSection.style.display = 'flex';
|
| 494 |
setTimeout(() => { scanSection.classList.add('show'); scanSection.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 50);
|
| 495 |
-
|
| 496 |
simulateScanProgress();
|
| 497 |
|
| 498 |
const formData = new FormData();
|
|
@@ -501,7 +694,7 @@
|
|
| 501 |
fetch('/analyze', { method: 'POST', body: formData })
|
| 502 |
.then(r => r.json())
|
| 503 |
.then(data => setTimeout(() => showResult(data), 3200))
|
| 504 |
-
.catch(err => {
|
| 505 |
};
|
| 506 |
reader.readAsDataURL(fileBlob);
|
| 507 |
}
|
|
@@ -517,7 +710,6 @@
|
|
| 517 |
steps.forEach((step, i) => { setTimeout(() => { status.innerText = step.s; detail.innerText = step.d; bar.style.width = step.w; }, i * 800); });
|
| 518 |
}
|
| 519 |
|
| 520 |
-
// Updated Database with Detailed Evidence and Stage Information for Reports
|
| 521 |
const medicalDatabase = {
|
| 522 |
"No DR": {
|
| 523 |
desc: "STAGE: NORMAL (No Diabetic Retinopathy).\n\nVISUAL EVIDENCE: The AI identified a healthy vascular structure. No microaneurysms, hemorrhages, or hard exudates were detected in the provided retinal image.",
|
|
@@ -582,31 +774,21 @@
|
|
| 582 |
switchView('view-result');
|
| 583 |
}
|
| 584 |
|
| 585 |
-
// --- UNIFIED HEATMAP TOGGLE (Syncs Main View and Modal) ---
|
| 586 |
function toggleHeatmap() {
|
| 587 |
isHeatmapShowing = !isHeatmapShowing;
|
| 588 |
-
|
| 589 |
const mainImg = document.getElementById('result-photo');
|
| 590 |
const modalImg = document.getElementById('modal-photo');
|
| 591 |
const mainBtn = document.getElementById('btn-main-heatmap');
|
| 592 |
const modalBtn = document.getElementById('btn-modal-heatmap');
|
| 593 |
-
|
| 594 |
const targetSrc = isHeatmapShowing ? heatmapImageSrc : originalImageSrc;
|
| 595 |
-
|
| 596 |
-
// Fade out
|
| 597 |
mainImg.style.opacity = '0';
|
| 598 |
modalImg.style.opacity = '0';
|
| 599 |
|
| 600 |
setTimeout(() => {
|
| 601 |
-
// Change source
|
| 602 |
mainImg.src = targetSrc;
|
| 603 |
modalImg.src = targetSrc;
|
| 604 |
-
|
| 605 |
-
// Fade in
|
| 606 |
mainImg.style.opacity = '1';
|
| 607 |
modalImg.style.opacity = '1';
|
| 608 |
-
|
| 609 |
-
// Ensure magnifier updates its background if modal is open
|
| 610 |
setTimeout(() => {
|
| 611 |
if(!document.getElementById('image-modal').classList.contains('hidden')) {
|
| 612 |
setupModalMagnifier();
|
|
@@ -615,22 +797,16 @@
|
|
| 615 |
}, 300);
|
| 616 |
|
| 617 |
if(isHeatmapShowing) {
|
| 618 |
-
// Main Btn UI
|
| 619 |
mainBtn.classList.add('bg-purple-500', 'text-white');
|
| 620 |
mainBtn.classList.remove('bg-purple-500/10', 'text-purple-700', 'dark:text-purple-300');
|
| 621 |
mainBtn.innerHTML = `<i data-lucide="eye-off" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Original`;
|
| 622 |
-
|
| 623 |
-
// Modal Btn UI
|
| 624 |
modalBtn.classList.add('bg-purple-500', 'text-white');
|
| 625 |
modalBtn.classList.remove('bg-purple-500/20', 'text-white');
|
| 626 |
modalBtn.innerHTML = `<i data-lucide="eye-off" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Hide AI Heatmap`;
|
| 627 |
} else {
|
| 628 |
-
// Main Btn UI
|
| 629 |
mainBtn.classList.remove('bg-purple-500', 'text-white');
|
| 630 |
mainBtn.classList.add('bg-purple-500/10', 'text-purple-700', 'dark:text-purple-300');
|
| 631 |
mainBtn.innerHTML = `<i data-lucide="eye" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Heatmap`;
|
| 632 |
-
|
| 633 |
-
// Modal Btn UI
|
| 634 |
modalBtn.classList.remove('bg-purple-500', 'text-white');
|
| 635 |
modalBtn.classList.add('bg-purple-500/20', 'text-white');
|
| 636 |
modalBtn.innerHTML = `<i data-lucide="eye" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Toggle AI Heatmap`;
|
|
@@ -638,14 +814,11 @@
|
|
| 638 |
lucide.createIcons();
|
| 639 |
}
|
| 640 |
|
| 641 |
-
// --- FULL SCREEN MODAL & MAGNIFIER LOGIC ---
|
| 642 |
-
|
| 643 |
function openImageModal() {
|
| 644 |
if(window.matchMedia('print').matches) return;
|
| 645 |
const modal = document.getElementById('image-modal');
|
| 646 |
const modalImg = document.getElementById('modal-photo');
|
| 647 |
modalImg.src = isHeatmapShowing ? heatmapImageSrc : originalImageSrc;
|
| 648 |
-
|
| 649 |
modal.classList.remove('hidden');
|
| 650 |
setTimeout(setupModalMagnifier, 150);
|
| 651 |
}
|
|
@@ -658,7 +831,6 @@
|
|
| 658 |
function setupModalMagnifier() {
|
| 659 |
const img = document.getElementById('modal-photo');
|
| 660 |
const glass = document.getElementById('modal-magnifier-glass');
|
| 661 |
-
|
| 662 |
glass.style.backgroundImage = "url('" + img.src + "')";
|
| 663 |
glass.style.backgroundRepeat = "no-repeat";
|
| 664 |
glass.style.backgroundSize = (img.clientWidth * zoomLevel) + "px " + (img.clientHeight * zoomLevel) + "px";
|
|
@@ -674,15 +846,12 @@
|
|
| 674 |
const glass = document.getElementById('modal-magnifier-glass');
|
| 675 |
const pos = getCursorPos(e, img);
|
| 676 |
let x = pos.x; let y = pos.y;
|
| 677 |
-
|
| 678 |
let w = glass.offsetWidth / 2; let h = glass.offsetHeight / 2;
|
| 679 |
let bw = 3;
|
| 680 |
-
|
| 681 |
if (x > img.clientWidth) { x = img.clientWidth; }
|
| 682 |
if (x < 0) { x = 0; }
|
| 683 |
if (y > img.clientHeight) { y = img.clientHeight; }
|
| 684 |
if (y < 0) { y = 0; }
|
| 685 |
-
|
| 686 |
glass.style.left = (x - w) + "px";
|
| 687 |
glass.style.top = (y - h) + "px";
|
| 688 |
glass.style.backgroundPosition = "-" + ((x * zoomLevel) - w + bw) + "px -" + ((y * zoomLevel) - h + bw) + "px";
|
|
|
|
| 69 |
@keyframes floatBlob { 0% { transform: translate(0, 0) scale(1); } 100% { transform: translate(50px, 80px) scale(1.1); } }
|
| 70 |
|
| 71 |
.glass-island { background: var(--glass-bg); backdrop-filter: blur(40px); border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow); border-radius: 2.5rem; transition: all 0.5s; }
|
| 72 |
+
|
| 73 |
.inner-btn { background: var(--btn-bg); border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 2.2rem; padding: 1.6rem; display: flex; align-items: center; gap: 1.5rem; cursor: pointer; transition: all 0.4s; }
|
| 74 |
.inner-btn:hover:not(.disabled) { transform: scale(1.04) translateY(-6px); background: rgba(255, 255, 255, 0.25); box-shadow: 0 15px 35px rgba(124, 58, 237, 0.15); }
|
| 75 |
html.dark .inner-btn:hover:not(.disabled) { background: rgba(255, 255, 255, 0.12); box-shadow: 0 15px 35px rgba(217, 70, 239, 0.2); }
|
|
|
|
| 79 |
.icon-vibrant { transition: all 0.4s ease; }
|
| 80 |
.icon-purple { color: #6d28d9; }
|
| 81 |
.icon-pink { color: #db2777; }
|
| 82 |
+
.icon-blue { color: #2563eb; }
|
| 83 |
html.dark .icon-purple { color: #a855f7; filter: drop-shadow(0 0 12px rgba(168, 85, 247, 0.6)); }
|
| 84 |
html.dark .icon-pink { color: #f472b6; filter: drop-shadow(0 0 12px rgba(244, 114, 182, 0.6)); }
|
| 85 |
+
html.dark .icon-blue { color: #60a5fa; filter: drop-shadow(0 0 12px rgba(96, 165, 250, 0.6)); }
|
| 86 |
.inner-btn:hover:not(.disabled) .icon-vibrant { transform: scale(1.2) rotate(-5deg); }
|
| 87 |
|
| 88 |
+
/* FORM INPUT STYLES */
|
| 89 |
+
.glass-input {
|
| 90 |
+
width: 100%; padding: 0.9rem 1.2rem; border-radius: 1.2rem; margin-top: 0.3rem;
|
| 91 |
+
background: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.1);
|
| 92 |
+
color: var(--text-main); font-weight: 600; font-size: 0.95rem; outline: none; transition: all 0.3s;
|
| 93 |
+
-webkit-appearance: none;
|
| 94 |
+
}
|
| 95 |
+
html.dark .glass-input { background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); color: white; }
|
| 96 |
+
.glass-input:focus { border-color: #a855f7; box-shadow: 0 0 0 3px rgba(168,85,247,0.2); background: rgba(255,255,255,0.5); }
|
| 97 |
+
html.dark .glass-input:focus { background: rgba(0,0,0,0.2); }
|
| 98 |
+
.glass-input option { color: black; }
|
| 99 |
+
|
| 100 |
.status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; background: rgba(255, 255, 255, 0.25); border: 1px solid rgba(255, 255, 255, 0.4); border-radius: 99px; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-secondary); backdrop-filter: blur(10px); }
|
| 101 |
html.dark .status-badge { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.15); }
|
| 102 |
.status-dot { width: 8px; height: 8px; background: #22c55e; border-radius: 50%; box-shadow: 0 0 10px #22c55e; animation: pulse 2s infinite; }
|
|
|
|
| 122 |
|
| 123 |
/* Modal Glass styles */
|
| 124 |
.modal-glass { background: rgba(30, 30, 30, 0.45); backdrop-filter: blur(40px); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 2rem; box-shadow: 0 25px 60px rgba(0,0,0,0.5); }
|
|
|
|
| 125 |
.img-magnifier-glass { position: absolute; border: 3px solid rgba(255,255,255,0.8); border-radius: 50%; cursor: none; width: 100px; height: 100px; display: none; box-shadow: 0 10px 20px rgba(0,0,0,0.5); pointer-events: none; z-index: 30; }
|
| 126 |
|
| 127 |
@keyframes scan { 0% { top: 0%; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } 100% { top: 100%; opacity: 0; } }
|
|
|
|
| 135 |
* { text-shadow: none !important; }
|
| 136 |
h2, p, span, div { color: black !important; }
|
| 137 |
|
| 138 |
+
/* Logic to exclusively print the active view */
|
| 139 |
+
.view-section { display: none !important; }
|
| 140 |
+
.view-section.active { display: block !important; opacity: 1 !important; transform: none !important; max-width: 100% !important; }
|
| 141 |
+
|
| 142 |
.print-header { display: block !important; text-align: center; margin-bottom: 25px; border-bottom: 2px solid #000; padding-bottom: 15px; }
|
| 143 |
.print-header h1 { font-size: 22px; margin-bottom: 5px; font-weight: bold; font-family: 'Times New Roman', serif; }
|
| 144 |
.print-header p { font-size: 12px; color: #555 !important; font-family: 'Times New Roman', serif; margin: 2px 0; }
|
| 145 |
|
| 146 |
.result-image-frame { margin: 0 auto 20px auto !important; width: 220px !important; height: 220px !important; border: 2px solid #000 !important; border-radius: 10px !important; box-shadow: none !important; }
|
| 147 |
+
#result-icon-bg, #calc-icon-bg { border: 2px solid #000 !important; box-shadow: none !important; background: transparent !important; border-radius: 50% !important;}
|
| 148 |
+
#result-icon-bg i, #calc-icon-bg i { color: black !important; }
|
|
|
|
| 149 |
.bg-black\/5, .bg-blue-500\/10 { background: white !important; border: 1px solid #aaa !important; }
|
| 150 |
|
| 151 |
.print-details { display: block !important; margin-top: 25px; text-align: left; border-top: 2px solid #000; padding-top: 15px;}
|
|
|
|
| 156 |
</style>
|
| 157 |
</head>
|
| 158 |
<body>
|
| 159 |
+
<!-- INITIAL LOADING SCREEN -->
|
| 160 |
<div id="initial-loader" class="fixed inset-0 z-[999] flex flex-col items-center justify-center">
|
| 161 |
<div class="relative flex flex-col items-center">
|
| 162 |
<div class="mb-4 relative">
|
| 163 |
<div class="absolute inset-0 bg-purple-500 blur-2xl opacity-40 rounded-full animate-pulse"></div>
|
| 164 |
+
<!-- SVG LOGO: Integrated from component -->
|
| 165 |
<svg width="76" height="76" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" class="relative z-10 drop-shadow-2xl">
|
| 166 |
<defs>
|
| 167 |
<linearGradient id="loadMainGradient" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse"><stop offset="0%" stop-color="#7c3aed"/><stop offset="100%" stop-color="#d946ef"/></linearGradient>
|
|
|
|
| 193 |
</div>
|
| 194 |
</div>
|
| 195 |
|
| 196 |
+
<!-- BACKGROUND BLOBS -->
|
| 197 |
<div class="bg-blob blob-1"></div><div class="bg-blob blob-2"></div>
|
| 198 |
|
| 199 |
<button id="theme-toggle-btn" onclick="toggleTheme()" class="fixed top-6 right-6 z-50 p-2.5 rounded-full glass-island hover:scale-110 active:scale-95 transition-all shadow-lg backdrop-blur-md">
|
|
|
|
| 201 |
</button>
|
| 202 |
|
| 203 |
<main class="relative z-10 w-full min-h-screen flex flex-col items-center pt-20 pb-12 px-6">
|
| 204 |
+
|
| 205 |
<!-- VIEW 1: HOME -->
|
| 206 |
<div id="view-home" class="view-section active">
|
| 207 |
+
<div class="text-center mb-8 flex flex-col items-center gap-4">
|
| 208 |
+
<!-- SVG LOGO: Restored to Home View -->
|
| 209 |
+
<svg width="80" height="80" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" class="drop-shadow-2xl hover:scale-105 transition-transform duration-500">
|
| 210 |
<defs>
|
| 211 |
+
<linearGradient id="mainGradient" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
|
| 212 |
+
<stop offset="0%" stop-color="#7c3aed"/>
|
| 213 |
+
<stop offset="100%" stop-color="#d946ef"/>
|
| 214 |
+
</linearGradient>
|
| 215 |
+
<linearGradient id="irisGradient" x1="256" y1="150" x2="256" y2="362" gradientUnits="userSpaceOnUse">
|
| 216 |
+
<stop offset="0%" stop-color="#4c1d95"/>
|
| 217 |
+
<stop offset="100%" stop-color="#7c3aed"/>
|
| 218 |
+
</linearGradient>
|
| 219 |
</defs>
|
| 220 |
<rect x="32" y="32" width="448" height="448" rx="110" fill="url(#mainGradient)" />
|
| 221 |
<path d="M256 160C176 160 110 210 80 256C110 302 176 352 256 352C336 352 402 302 432 256C402 210 336 160 256 160Z" fill="white"/>
|
|
|
|
| 224 |
<circle cx="276" cy="236" r="10" fill="white" fill-opacity="0.9"/>
|
| 225 |
<ellipse cx="256" cy="110" rx="160" ry="80" fill="white" fill-opacity="0.15"/>
|
| 226 |
<g stroke="white" stroke-width="8" stroke-linecap="round" stroke-opacity="0.9">
|
| 227 |
+
<path d="M140 140 L110 140 L110 170" fill="none"/>
|
| 228 |
+
<path d="M372 140 L402 140 L402 170" fill="none"/>
|
| 229 |
+
<path d="M140 372 L110 372 L110 342" fill="none"/>
|
| 230 |
+
<path d="M372 372 L402 372 L402 342" fill="none"/>
|
| 231 |
</g>
|
| 232 |
<rect x="110" y="254" width="292" height="4" rx="2" fill="white" fill-opacity="0.6"/>
|
| 233 |
</svg>
|
| 234 |
+
|
| 235 |
<div>
|
| 236 |
+
<h1 class="text-4xl font-black mb-2"><span class="logo-shimmer">RetinaScan</span></h1>
|
| 237 |
+
<div class="status-badge"><span class="status-dot"></span>Multi-Model AI</div>
|
| 238 |
</div>
|
| 239 |
</div>
|
| 240 |
|
| 241 |
+
<!-- SELECTION ISLAND -->
|
| 242 |
<div class="glass-island p-8 flex flex-col gap-5 w-full max-w-[420px]" id="selection-island">
|
| 243 |
+
|
|
|
|
|
|
|
|
|
|
| 244 |
<div onclick="triggerUpload()" id="btn-upload" class="inner-btn group">
|
| 245 |
+
<i data-lucide="image" class="w-8 h-8 icon-vibrant icon-pink"></i>
|
| 246 |
+
<div class="text-left"><span class="block text-xl font-bold">Upload Scan</span><span class="sub-label opacity-70">FUNDUS LIBRARY</span></div>
|
| 247 |
+
</div>
|
| 248 |
+
|
| 249 |
+
<div onclick="switchView('view-calculator')" id="btn-calculator" class="inner-btn group">
|
| 250 |
+
<i data-lucide="clipboard-list" class="w-8 h-8 icon-vibrant icon-blue"></i>
|
| 251 |
+
<div class="text-left"><span class="block text-xl font-bold">Clinical Risk Form</span><span class="sub-label opacity-70">DIABETES PREDICTOR</span></div>
|
| 252 |
</div>
|
| 253 |
</div>
|
| 254 |
|
|
|
|
| 256 |
<i data-lucide="lightbulb" class="w-5 h-5 text-yellow-500 mt-1 flex-shrink-0"></i>
|
| 257 |
<div>
|
| 258 |
<h3 class="font-bold text-sm mb-1">Quick Tip</h3>
|
| 259 |
+
<p class="text-xs opacity-70 leading-relaxed">Upload a clear, glare-free image for accurate retinal analysis, or use the clinical form to instantly calculate your diabetes risk.</p>
|
| 260 |
</div>
|
| 261 |
</div>
|
|
|
|
| 262 |
|
| 263 |
+
<footer class="mt-auto pt-8 text-center opacity-40 print-hide"><p class="text-[10px] font-mono uppercase tracking-widest">Powered by PyTorch & Random Forest</p></footer>
|
| 264 |
+
|
| 265 |
+
<!-- SCAN LOADER -->
|
| 266 |
<div id="scanning-section" class="w-full max-w-[420px] glass-island p-8 flex flex-col items-center">
|
| 267 |
<div class="relative w-56 h-56 rounded-[2rem] overflow-hidden shadow-2xl mb-6 bg-black/5 dark:bg-black/40 border border-black/10 dark:border-white/20">
|
| 268 |
<img id="scan-preview" src="" class="w-full h-full object-cover opacity-90" />
|
|
|
|
| 277 |
<input type="file" id="file-input" class="hidden" accept="image/*" onchange="handleFileSelect(event)" />
|
| 278 |
</div>
|
| 279 |
|
| 280 |
+
<!-- VIEW 2: RETINA RESULTS -->
|
| 281 |
<div id="view-result" class="view-section">
|
| 282 |
<div class="print-header">
|
| 283 |
<h1>RETINASCAN MEDICAL DIAGNOSTICS</h1>
|
|
|
|
| 286 |
</div>
|
| 287 |
|
| 288 |
<div class="result-image-frame print-hide group relative" onclick="openImageModal()">
|
|
|
|
| 289 |
<div class="absolute inset-0 bg-black/40 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity z-10 rounded-[1.6rem]">
|
| 290 |
<i data-lucide="maximize-2" class="w-8 h-8 text-white"></i>
|
| 291 |
<span class="text-white text-xs font-bold mt-2 uppercase tracking-widest drop-shadow-md">View Full Screen</span>
|
|
|
|
| 302 |
<p id="result-severity" class="sub-label text-purple-600 dark:text-purple-400 mb-4 font-black text-xs print-hide">ANALYSIS COMPLETE</p>
|
| 303 |
<p id="result-desc" class="text-sm opacity-70 mb-6 font-medium text-center leading-relaxed max-w-[260px] print-hide">--</p>
|
| 304 |
|
|
|
|
| 305 |
<div class="w-full bg-black/5 dark:bg-white/5 p-4 rounded-2xl border border-black/10 dark:border-white/10 text-center mb-4 shadow-inner print-hide">
|
| 306 |
<p class="sub-label mb-1 opacity-50">AI Confidence Score</p>
|
| 307 |
<p id="result-conf" class="text-2xl font-mono font-bold">--%</p>
|
|
|
|
| 327 |
<p id="print-meds">---</p>
|
| 328 |
</div>
|
| 329 |
|
|
|
|
| 330 |
<div class="flex gap-3 w-full mb-3 print-hide">
|
| 331 |
<button onclick="toggleHeatmap()" id="btn-main-heatmap" class="flex-[1] py-3.5 rounded-2xl bg-purple-500/10 hover:bg-purple-500/20 text-purple-700 dark:text-purple-300 font-bold text-sm border border-purple-500/20 transition-all hidden">
|
| 332 |
<i data-lucide="eye" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Heatmap
|
|
|
|
| 337 |
</div>
|
| 338 |
|
| 339 |
<button onclick="resetApp()" class="w-full py-4 rounded-2xl bg-purple-600 hover:bg-purple-700 text-white font-bold text-base shadow-xl shadow-purple-900/20 active:scale-95 transition-all print-hide">
|
| 340 |
+
Done
|
| 341 |
+
</button>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
+
|
| 345 |
+
<!-- VIEW 4: DIABETES CALCULATOR FORM -->
|
| 346 |
+
<div id="view-calculator" class="view-section">
|
| 347 |
+
<div class="w-full max-w-[420px] flex items-center justify-between mb-6">
|
| 348 |
+
<button onclick="switchView('view-home')" class="p-3 rounded-full bg-white/20 dark:bg-white/5 hover:bg-white/40 border border-white/20 transition-all">
|
| 349 |
+
<i data-lucide="arrow-left" class="w-5 h-5"></i>
|
| 350 |
+
</button>
|
| 351 |
+
<h2 class="text-xl font-bold tracking-tight">Clinical Risk Form</h2>
|
| 352 |
+
<div class="w-11"></div> <!-- spacer -->
|
| 353 |
+
</div>
|
| 354 |
+
|
| 355 |
+
<div class="glass-island p-8 w-full max-w-[420px]">
|
| 356 |
+
<form id="diabetes-form" onsubmit="submitDiabetesForm(event)" class="flex flex-col gap-5">
|
| 357 |
+
|
| 358 |
+
<div class="grid grid-cols-2 gap-4">
|
| 359 |
+
<div>
|
| 360 |
+
<label class="block sub-label mb-1">Gender</label>
|
| 361 |
+
<select id="calc-gender" required class="glass-input">
|
| 362 |
+
<option value="" disabled selected>Select</option>
|
| 363 |
+
<option value="Male">Male</option>
|
| 364 |
+
<option value="Female">Female</option>
|
| 365 |
+
<option value="Other">Other</option>
|
| 366 |
+
</select>
|
| 367 |
+
</div>
|
| 368 |
+
<div>
|
| 369 |
+
<label class="block sub-label mb-1">Age</label>
|
| 370 |
+
<input type="number" id="calc-age" required min="1" max="120" step="1" class="glass-input" placeholder="e.g. 45">
|
| 371 |
+
</div>
|
| 372 |
+
</div>
|
| 373 |
+
|
| 374 |
+
<div class="grid grid-cols-2 gap-4">
|
| 375 |
+
<div>
|
| 376 |
+
<label class="block sub-label mb-1">Hypertension</label>
|
| 377 |
+
<select id="calc-hyper" required class="glass-input">
|
| 378 |
+
<option value="" disabled selected>Select</option>
|
| 379 |
+
<option value="0">No</option>
|
| 380 |
+
<option value="1">Yes</option>
|
| 381 |
+
</select>
|
| 382 |
+
</div>
|
| 383 |
+
<div>
|
| 384 |
+
<label class="block sub-label mb-1">Heart Disease</label>
|
| 385 |
+
<select id="calc-heart" required class="glass-input">
|
| 386 |
+
<option value="" disabled selected>Select</option>
|
| 387 |
+
<option value="0">No</option>
|
| 388 |
+
<option value="1">Yes</option>
|
| 389 |
+
</select>
|
| 390 |
+
</div>
|
| 391 |
+
</div>
|
| 392 |
+
|
| 393 |
+
<div class="grid grid-cols-2 gap-4">
|
| 394 |
+
<div>
|
| 395 |
+
<label class="block sub-label mb-1">Smoking History</label>
|
| 396 |
+
<select id="calc-smoke" required class="glass-input">
|
| 397 |
+
<option value="" disabled selected>Select</option>
|
| 398 |
+
<option value="never">Never</option>
|
| 399 |
+
<option value="former">Former</option>
|
| 400 |
+
<option value="current">Current</option>
|
| 401 |
+
<option value="ever">Ever</option>
|
| 402 |
+
<option value="not current">Not Current</option>
|
| 403 |
+
<option value="No Info">No Info</option>
|
| 404 |
+
</select>
|
| 405 |
+
</div>
|
| 406 |
+
<div>
|
| 407 |
+
<label class="block sub-label mb-1">BMI</label>
|
| 408 |
+
<input type="number" id="calc-bmi" required min="10" max="80" step="0.01" class="glass-input" placeholder="e.g. 27.50">
|
| 409 |
+
</div>
|
| 410 |
+
</div>
|
| 411 |
+
|
| 412 |
+
<div class="grid grid-cols-2 gap-4">
|
| 413 |
+
<div>
|
| 414 |
+
<label class="block sub-label mb-1">HbA1c Level (%)</label>
|
| 415 |
+
<input type="number" id="calc-hba1c" required min="3" max="15" step="0.1" class="glass-input" placeholder="e.g. 6.5">
|
| 416 |
+
</div>
|
| 417 |
+
<div>
|
| 418 |
+
<label class="block sub-label mb-1">Blood Glucose</label>
|
| 419 |
+
<input type="number" id="calc-glucose" required min="50" max="400" step="1" class="glass-input" placeholder="e.g. 140">
|
| 420 |
+
</div>
|
| 421 |
+
</div>
|
| 422 |
+
|
| 423 |
+
<button type="submit" id="btn-submit-calc" class="mt-4 w-full py-4 rounded-2xl bg-blue-600 hover:bg-blue-700 text-white font-bold text-base shadow-xl active:scale-95 transition-all flex justify-center items-center gap-2">
|
| 424 |
+
<span>Calculate Risk</span>
|
| 425 |
+
<i data-lucide="calculator" class="w-5 h-5"></i>
|
| 426 |
+
</button>
|
| 427 |
+
</form>
|
| 428 |
+
</div>
|
| 429 |
+
</div>
|
| 430 |
+
|
| 431 |
+
<!-- VIEW 5: CALCULATOR RESULT -->
|
| 432 |
+
<div id="view-calc-result" class="view-section">
|
| 433 |
+
|
| 434 |
+
<!-- CALCULATOR PRINT HEADER -->
|
| 435 |
+
<div class="print-header">
|
| 436 |
+
<h1>CLINICAL DIABETES RISK REPORT</h1>
|
| 437 |
+
<p><strong>Patient ID:</strong> <span id="calc-print-id">---</span></p>
|
| 438 |
+
<p><strong>Date of Analysis:</strong> <span id="calc-print-date">---</span></p>
|
| 439 |
+
</div>
|
| 440 |
+
|
| 441 |
+
<div class="glass-island p-8 pt-10 w-full max-w-[420px] flex flex-col items-center">
|
| 442 |
+
<div id="calc-icon-bg" class="w-20 h-20 rounded-full flex items-center justify-center mb-6 shadow-xl transition-colors duration-500">
|
| 443 |
+
<i id="calc-icon" data-lucide="activity" class="w-10 h-10 text-white"></i>
|
| 444 |
+
</div>
|
| 445 |
+
|
| 446 |
+
<h2 id="calc-diagnosis" class="text-3xl font-bold mb-2 text-center tracking-tight">--</h2>
|
| 447 |
+
<p id="calc-severity" class="sub-label text-blue-600 dark:text-blue-400 mb-6 font-black text-xs print-hide">--</p>
|
| 448 |
+
<p id="calc-desc" class="text-sm opacity-80 mb-8 font-medium text-center leading-relaxed print-hide">--</p>
|
| 449 |
+
|
| 450 |
+
<!-- DIABETES DETAILED PRINT SECTION -->
|
| 451 |
+
<div class="print-details w-full" id="calc-print-medical-details">
|
| 452 |
+
<h3>Biomarker Analysis</h3>
|
| 453 |
+
<p id="calc-print-analysis">---</p>
|
| 454 |
+
|
| 455 |
+
<h3>Dietary Recommendations</h3>
|
| 456 |
+
<p id="calc-print-foods">---</p>
|
| 457 |
+
|
| 458 |
+
<h3>Lifestyle & Maintenance</h3>
|
| 459 |
+
<p id="calc-print-lifestyle">---</p>
|
| 460 |
+
|
| 461 |
+
<h3>Medical Interventions & Medicines</h3>
|
| 462 |
+
<p id="calc-print-meds">---</p>
|
| 463 |
+
</div>
|
| 464 |
+
|
| 465 |
+
<!-- ADDED PRINT OPTION BUTTONS -->
|
| 466 |
+
<div class="flex gap-3 w-full mb-3 print-hide">
|
| 467 |
+
<button onclick="window.print()" class="flex-[1] py-3.5 rounded-2xl bg-blue-500/10 hover:bg-blue-500/20 text-blue-700 dark:text-blue-300 font-bold text-sm border border-blue-500/20 transition-all">
|
| 468 |
+
<i data-lucide="printer" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Print Report
|
| 469 |
+
</button>
|
| 470 |
+
</div>
|
| 471 |
+
|
| 472 |
+
<button onclick="switchView('view-home')" class="w-full py-4 rounded-2xl bg-blue-600 hover:bg-blue-700 text-white font-bold text-base shadow-xl active:scale-95 transition-all print-hide">
|
| 473 |
+
Done
|
| 474 |
</button>
|
| 475 |
</div>
|
| 476 |
</div>
|
| 477 |
|
| 478 |
+
<!-- FULL SCREEN IMAGE MODAL -->
|
| 479 |
<div id="image-modal" class="hidden fixed inset-0 z-[200] flex items-center justify-center p-6">
|
| 480 |
<div class="absolute inset-0 bg-black/80 backdrop-blur-xl" onclick="closeImageModal()"></div>
|
| 481 |
|
| 482 |
<div class="relative w-full max-w-[420px] modal-glass p-6 flex flex-col items-center">
|
|
|
|
| 483 |
<button onclick="closeImageModal()" class="absolute top-4 right-4 p-2.5 bg-white/10 hover:bg-white/20 rounded-full backdrop-blur text-white transition-all z-50">
|
| 484 |
<i data-lucide="x" class="w-5 h-5"></i>
|
| 485 |
</button>
|
|
|
|
| 488 |
<h3 class="text-white font-bold text-lg tracking-wider">Image Inspection</h3>
|
| 489 |
</div>
|
| 490 |
|
|
|
|
| 491 |
<div class="relative w-full aspect-square rounded-[1.5rem] overflow-hidden border-2 border-white/20 shadow-inner bg-black cursor-crosshair"
|
| 492 |
onmouseenter="showModalMagnifier(true)"
|
| 493 |
onmouseleave="showModalMagnifier(false)"
|
| 494 |
onmousemove="moveModalMagnifier(event)"
|
| 495 |
ontouchmove="moveModalMagnifier(event)">
|
|
|
|
| 496 |
<div id="modal-magnifier-glass" class="img-magnifier-glass"></div>
|
|
|
|
| 497 |
<img id="modal-photo" src="" class="w-full h-full object-cover transition-opacity duration-300" />
|
| 498 |
</div>
|
| 499 |
|
|
|
|
| 500 |
<button onclick="toggleHeatmap()" id="btn-modal-heatmap" class="mt-6 w-full py-4 rounded-2xl bg-purple-500/20 hover:bg-purple-500/30 text-white font-bold text-sm border border-purple-500/30 transition-all hidden shadow-lg shadow-purple-500/10">
|
| 501 |
<i data-lucide="eye" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Toggle AI Heatmap
|
| 502 |
</button>
|
| 503 |
</div>
|
| 504 |
</div>
|
| 505 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
</main>
|
| 507 |
|
| 508 |
<script>
|
| 509 |
+
// --- Core UI Logic ---
|
| 510 |
window.addEventListener('DOMContentLoaded', () => {
|
| 511 |
const isDark = document.documentElement.classList.contains('dark');
|
| 512 |
const metaTheme = document.getElementById('theme-color-meta');
|
|
|
|
| 543 |
setTimeout(() => { if(!v.classList.contains('active')) v.style.display = 'none'; }, 500);
|
| 544 |
});
|
| 545 |
const target = document.getElementById(id);
|
| 546 |
+
|
| 547 |
+
document.getElementById('theme-toggle-btn').style.display = 'block';
|
| 548 |
+
setTimeout(() => { target.style.display = 'flex'; void target.offsetWidth; target.classList.add('active'); }, 500);
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
// --- DIABETES DYNAMIC REPORT GENERATOR ---
|
| 552 |
+
function generateDiabetesReport(payload) {
|
| 553 |
+
let analysis = [];
|
| 554 |
+
let foods = [];
|
| 555 |
+
let meds = [];
|
| 556 |
+
let lifestyle = [];
|
| 557 |
+
|
| 558 |
+
let bmi = parseFloat(payload.bmi);
|
| 559 |
+
if (bmi < 18.5) analysis.push(`• BMI: ${bmi} (Underweight)`);
|
| 560 |
+
else if (bmi <= 24.9) analysis.push(`• BMI: ${bmi} (Normal)`);
|
| 561 |
+
else {
|
| 562 |
+
analysis.push(`• BMI: ${bmi} (Overweight/Obese - HIGH)`);
|
| 563 |
+
foods.push("- Focus on a caloric deficit using lean proteins, high-fiber vegetables, and whole grains.");
|
| 564 |
+
lifestyle.push("- Aim for at least 150 minutes of moderate aerobic activity per week for weight management.");
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
let hba1c = parseFloat(payload.HbA1c_level);
|
| 568 |
+
if (hba1c < 5.7) analysis.push(`• HbA1c Level: ${hba1c}% (Normal)`);
|
| 569 |
+
else if (hba1c <= 6.4) {
|
| 570 |
+
analysis.push(`• HbA1c Level: ${hba1c}% (Pre-diabetes range - ELEVATED)`);
|
| 571 |
+
foods.push("- Reduce refined carbohydrates and sugary drinks. Opt for low-glycemic index (GI) foods.");
|
| 572 |
+
} else {
|
| 573 |
+
analysis.push(`• HbA1c Level: ${hba1c}% (Diabetic range - HIGH)`);
|
| 574 |
+
foods.push("- Strictly monitor carbohydrate intake. Incorporate foods like bitter gourd, fenugreek, and high-fiber greens.");
|
| 575 |
+
meds.push("- Consult an endocrinologist for potential initiation of oral hypoglycemics (e.g., Metformin) or insulin therapy.");
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
let glucose = parseFloat(payload.blood_glucose_level);
|
| 579 |
+
if (glucose < 140) analysis.push(`• Blood Glucose: ${glucose} mg/dL (Acceptable)`);
|
| 580 |
+
else {
|
| 581 |
+
analysis.push(`• Blood Glucose: ${glucose} mg/dL (ELEVATED)`);
|
| 582 |
+
lifestyle.push("- Monitor post-meal glucose levels regularly. Stay well hydrated to help kidneys flush excess sugar.");
|
| 583 |
+
if (meds.length === 0) meds.push("- Discuss glucose-lowering medications with a healthcare provider.");
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
if (payload.hypertension === "1") {
|
| 587 |
+
analysis.push(`• Hypertension: Present (High Risk Factor)`);
|
| 588 |
+
foods.push("- Adopt the DASH diet (Dietary Approaches to Stop Hypertension): strictly limit sodium and saturated fats.");
|
| 589 |
+
meds.push("- Review anti-hypertensive medications (e.g., ACE inhibitors/ARBs) with your cardiologist.");
|
| 590 |
+
}
|
| 591 |
+
if (payload.heart_disease === "1") {
|
| 592 |
+
analysis.push(`• Heart Disease: Present (High Risk Factor)`);
|
| 593 |
+
lifestyle.push("- Strict cardiovascular monitoring is critical. Consult a cardiologist before engaging in high-intensity exercise.");
|
| 594 |
}
|
| 595 |
+
|
| 596 |
+
if(foods.length === 0) foods.push("- Maintain a balanced diet rich in micronutrients to sustain your current healthy metrics.");
|
| 597 |
+
if(meds.length === 0) meds.push("- No specific anti-diabetic medications are indicated based on these metrics.");
|
| 598 |
+
if(lifestyle.length === 0) lifestyle.push("- Continue with regular annual check-ups and maintain an active lifestyle.");
|
| 599 |
+
|
| 600 |
+
document.getElementById('calc-print-analysis').innerText = analysis.join('\n');
|
| 601 |
+
document.getElementById('calc-print-foods').innerText = foods.join('\n');
|
| 602 |
+
document.getElementById('calc-print-meds').innerText = [...new Set(meds)].join('\n');
|
| 603 |
+
document.getElementById('calc-print-lifestyle').innerText = [...new Set(lifestyle)].join('\n');
|
| 604 |
+
|
| 605 |
+
document.getElementById('calc-print-date').textContent = new Date().toLocaleDateString();
|
| 606 |
+
document.getElementById('calc-print-id').textContent = 'DBT-' + Math.floor(Math.random() * 900000 + 100000);
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
function submitDiabetesForm(e) {
|
| 610 |
+
e.preventDefault();
|
| 611 |
+
const btn = document.getElementById('btn-submit-calc');
|
| 612 |
+
const originalHTML = btn.innerHTML;
|
| 613 |
+
btn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 animate-spin mx-auto"></i>`;
|
| 614 |
+
btn.classList.add('opacity-70', 'cursor-not-allowed');
|
| 615 |
+
lucide.createIcons();
|
| 616 |
+
|
| 617 |
+
const payload = {
|
| 618 |
+
gender: document.getElementById('calc-gender').value,
|
| 619 |
+
age: document.getElementById('calc-age').value,
|
| 620 |
+
hypertension: document.getElementById('calc-hyper').value,
|
| 621 |
+
heart_disease: document.getElementById('calc-heart').value,
|
| 622 |
+
smoking_history: document.getElementById('calc-smoke').value,
|
| 623 |
+
bmi: document.getElementById('calc-bmi').value,
|
| 624 |
+
HbA1c_level: document.getElementById('calc-hba1c').value,
|
| 625 |
+
blood_glucose_level: document.getElementById('calc-glucose').value
|
| 626 |
+
};
|
| 627 |
+
|
| 628 |
+
fetch('/predict_diabetes', {
|
| 629 |
+
method: 'POST',
|
| 630 |
+
headers: { 'Content-Type': 'application/json' },
|
| 631 |
+
body: JSON.stringify(payload)
|
| 632 |
+
})
|
| 633 |
+
.then(res => res.json())
|
| 634 |
+
.then(data => {
|
| 635 |
+
if(data.error) {
|
| 636 |
+
alert("Error: " + data.error);
|
| 637 |
+
} else {
|
| 638 |
+
const colors = { 'emerald': 'bg-emerald-500', 'orange': 'bg-orange-500', 'rose': 'bg-rose-500' };
|
| 639 |
+
document.getElementById('calc-icon-bg').className = `w-20 h-20 rounded-full flex items-center justify-center mb-6 shadow-xl transition-colors duration-500 ${colors[data.color]}`;
|
| 640 |
+
document.getElementById('calc-icon').setAttribute('data-lucide', data.icon);
|
| 641 |
+
document.getElementById('calc-diagnosis').textContent = data.diagnosis;
|
| 642 |
+
document.getElementById('calc-severity').textContent = data.severity;
|
| 643 |
+
document.getElementById('calc-desc').textContent = data.description;
|
| 644 |
+
|
| 645 |
+
generateDiabetesReport(payload);
|
| 646 |
+
lucide.createIcons();
|
| 647 |
+
switchView('view-calc-result');
|
| 648 |
+
}
|
| 649 |
+
})
|
| 650 |
+
.catch(err => alert("Connection failed."))
|
| 651 |
+
.finally(() => {
|
| 652 |
+
btn.innerHTML = originalHTML;
|
| 653 |
+
btn.classList.remove('opacity-70', 'cursor-not-allowed');
|
| 654 |
+
lucide.createIcons();
|
| 655 |
+
});
|
| 656 |
}
|
| 657 |
|
| 658 |
function resetApp() {
|
|
|
|
| 659 |
document.getElementById('file-input').value = '';
|
|
|
|
| 660 |
document.getElementById('btn-upload').classList.remove('disabled');
|
| 661 |
document.getElementById('scanning-section').classList.remove('show');
|
| 662 |
setTimeout(() => {
|
|
|
|
| 667 |
switchView('view-home');
|
| 668 |
}
|
| 669 |
|
| 670 |
+
let originalImageSrc = ''; let heatmapImageSrc = ''; let isHeatmapShowing = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
|
| 672 |
function triggerUpload() {
|
| 673 |
if(document.getElementById('btn-upload').classList.contains('disabled')) return;
|
|
|
|
| 679 |
function processImage(fileBlob) {
|
| 680 |
const reader = new FileReader();
|
| 681 |
reader.onload = (e) => {
|
|
|
|
| 682 |
document.getElementById('btn-upload').classList.add('disabled');
|
|
|
|
| 683 |
originalImageSrc = e.target.result;
|
| 684 |
document.getElementById('scan-preview').src = originalImageSrc;
|
| 685 |
document.getElementById('result-photo').src = originalImageSrc;
|
|
|
|
| 686 |
const scanSection = document.getElementById('scanning-section');
|
| 687 |
scanSection.style.display = 'flex';
|
| 688 |
setTimeout(() => { scanSection.classList.add('show'); scanSection.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 50);
|
|
|
|
| 689 |
simulateScanProgress();
|
| 690 |
|
| 691 |
const formData = new FormData();
|
|
|
|
| 694 |
fetch('/analyze', { method: 'POST', body: formData })
|
| 695 |
.then(r => r.json())
|
| 696 |
.then(data => setTimeout(() => showResult(data), 3200))
|
| 697 |
+
.catch(err => { resetApp(); });
|
| 698 |
};
|
| 699 |
reader.readAsDataURL(fileBlob);
|
| 700 |
}
|
|
|
|
| 710 |
steps.forEach((step, i) => { setTimeout(() => { status.innerText = step.s; detail.innerText = step.d; bar.style.width = step.w; }, i * 800); });
|
| 711 |
}
|
| 712 |
|
|
|
|
| 713 |
const medicalDatabase = {
|
| 714 |
"No DR": {
|
| 715 |
desc: "STAGE: NORMAL (No Diabetic Retinopathy).\n\nVISUAL EVIDENCE: The AI identified a healthy vascular structure. No microaneurysms, hemorrhages, or hard exudates were detected in the provided retinal image.",
|
|
|
|
| 774 |
switchView('view-result');
|
| 775 |
}
|
| 776 |
|
|
|
|
| 777 |
function toggleHeatmap() {
|
| 778 |
isHeatmapShowing = !isHeatmapShowing;
|
|
|
|
| 779 |
const mainImg = document.getElementById('result-photo');
|
| 780 |
const modalImg = document.getElementById('modal-photo');
|
| 781 |
const mainBtn = document.getElementById('btn-main-heatmap');
|
| 782 |
const modalBtn = document.getElementById('btn-modal-heatmap');
|
|
|
|
| 783 |
const targetSrc = isHeatmapShowing ? heatmapImageSrc : originalImageSrc;
|
|
|
|
|
|
|
| 784 |
mainImg.style.opacity = '0';
|
| 785 |
modalImg.style.opacity = '0';
|
| 786 |
|
| 787 |
setTimeout(() => {
|
|
|
|
| 788 |
mainImg.src = targetSrc;
|
| 789 |
modalImg.src = targetSrc;
|
|
|
|
|
|
|
| 790 |
mainImg.style.opacity = '1';
|
| 791 |
modalImg.style.opacity = '1';
|
|
|
|
|
|
|
| 792 |
setTimeout(() => {
|
| 793 |
if(!document.getElementById('image-modal').classList.contains('hidden')) {
|
| 794 |
setupModalMagnifier();
|
|
|
|
| 797 |
}, 300);
|
| 798 |
|
| 799 |
if(isHeatmapShowing) {
|
|
|
|
| 800 |
mainBtn.classList.add('bg-purple-500', 'text-white');
|
| 801 |
mainBtn.classList.remove('bg-purple-500/10', 'text-purple-700', 'dark:text-purple-300');
|
| 802 |
mainBtn.innerHTML = `<i data-lucide="eye-off" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Original`;
|
|
|
|
|
|
|
| 803 |
modalBtn.classList.add('bg-purple-500', 'text-white');
|
| 804 |
modalBtn.classList.remove('bg-purple-500/20', 'text-white');
|
| 805 |
modalBtn.innerHTML = `<i data-lucide="eye-off" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Hide AI Heatmap`;
|
| 806 |
} else {
|
|
|
|
| 807 |
mainBtn.classList.remove('bg-purple-500', 'text-white');
|
| 808 |
mainBtn.classList.add('bg-purple-500/10', 'text-purple-700', 'dark:text-purple-300');
|
| 809 |
mainBtn.innerHTML = `<i data-lucide="eye" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Heatmap`;
|
|
|
|
|
|
|
| 810 |
modalBtn.classList.remove('bg-purple-500', 'text-white');
|
| 811 |
modalBtn.classList.add('bg-purple-500/20', 'text-white');
|
| 812 |
modalBtn.innerHTML = `<i data-lucide="eye" class="w-4 h-4 inline-block -mt-0.5 mr-1"></i> Toggle AI Heatmap`;
|
|
|
|
| 814 |
lucide.createIcons();
|
| 815 |
}
|
| 816 |
|
|
|
|
|
|
|
| 817 |
function openImageModal() {
|
| 818 |
if(window.matchMedia('print').matches) return;
|
| 819 |
const modal = document.getElementById('image-modal');
|
| 820 |
const modalImg = document.getElementById('modal-photo');
|
| 821 |
modalImg.src = isHeatmapShowing ? heatmapImageSrc : originalImageSrc;
|
|
|
|
| 822 |
modal.classList.remove('hidden');
|
| 823 |
setTimeout(setupModalMagnifier, 150);
|
| 824 |
}
|
|
|
|
| 831 |
function setupModalMagnifier() {
|
| 832 |
const img = document.getElementById('modal-photo');
|
| 833 |
const glass = document.getElementById('modal-magnifier-glass');
|
|
|
|
| 834 |
glass.style.backgroundImage = "url('" + img.src + "')";
|
| 835 |
glass.style.backgroundRepeat = "no-repeat";
|
| 836 |
glass.style.backgroundSize = (img.clientWidth * zoomLevel) + "px " + (img.clientHeight * zoomLevel) + "px";
|
|
|
|
| 846 |
const glass = document.getElementById('modal-magnifier-glass');
|
| 847 |
const pos = getCursorPos(e, img);
|
| 848 |
let x = pos.x; let y = pos.y;
|
|
|
|
| 849 |
let w = glass.offsetWidth / 2; let h = glass.offsetHeight / 2;
|
| 850 |
let bw = 3;
|
|
|
|
| 851 |
if (x > img.clientWidth) { x = img.clientWidth; }
|
| 852 |
if (x < 0) { x = 0; }
|
| 853 |
if (y > img.clientHeight) { y = img.clientHeight; }
|
| 854 |
if (y < 0) { y = 0; }
|
|
|
|
| 855 |
glass.style.left = (x - w) + "px";
|
| 856 |
glass.style.top = (y - h) + "px";
|
| 857 |
glass.style.backgroundPosition = "-" + ((x * zoomLevel) - w + bw) + "px -" + ((y * zoomLevel) - h + bw) + "px";
|