GOWREESH M G commited on
Commit
cb593da
·
verified ·
1 Parent(s): 9b0f046

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +177 -125
templates/index.html CHANGED
@@ -5,15 +5,10 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
  <title>RetinaScan</title>
7
 
8
- <!-- APP ICONS (Updated with Cache Busting) -->
9
- <!-- Browser Tab Icon -->
10
- <link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='logo.svg') }}?v=2">
11
-
12
- <!-- iOS Home Screen Icon (Forces reload) -->
13
- <link rel="apple-touch-icon" href="{{ url_for('static', filename='logo.png') }}?v=2">
14
-
15
- <!-- Android Manifest -->
16
- <link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}?v=2">
17
 
18
  <meta name="apple-mobile-web-app-title" content="RetinaScan">
19
  <meta name="apple-mobile-web-app-capable" content="yes">
@@ -33,24 +28,24 @@
33
  --bg-grad-end: #e5d1ff;
34
  --text-main: #2d004d;
35
  --text-secondary: #5a2e85;
36
- --glass-bg: rgba(255, 255, 255, 0.5);
37
- --glass-border: rgba(255, 255, 255, 0.8);
38
  --glass-shadow: 0 20px 50px rgba(0, 0, 0, 0.08);
39
  --icon-vibrant: #7c3aed;
40
- --btn-bg: rgba(255, 255, 255, 0.4);
41
  }
42
 
43
  [data-theme="dark"] {
44
  /* iOS Dark Theme */
45
- --bg-grad-start: #1c1c1e;
46
- --bg-grad-end: #0a0a0c;
47
  --text-main: #f3e8ff;
48
  --text-secondary: #c084fc;
49
- --glass-bg: rgba(44, 44, 46, 0.75);
50
  --glass-border: rgba(255, 255, 255, 0.12);
51
  --glass-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
52
  --icon-vibrant: #d946ef;
53
- --btn-bg: rgba(255, 255, 255, 0.05);
54
  }
55
 
56
  body {
@@ -62,16 +57,52 @@
62
  overflow-x: hidden;
63
  transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
64
  letter-spacing: -0.02em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  }
66
 
67
- /* iOS Glassmorphism - Premium Polish */
 
 
 
 
 
 
 
 
68
  .glass-island {
69
  background: var(--glass-bg);
70
- backdrop-filter: blur(45px) saturate(210%);
71
- -webkit-backdrop-filter: blur(45px) saturate(210%);
72
  border: 1px solid var(--glass-border);
73
  box-shadow: var(--glass-shadow);
74
- border-radius: 2.8rem;
75
  transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
76
  }
77
 
@@ -103,7 +134,7 @@
103
  .inner-btn:active:not(.disabled) { transform: scale(0.96) translateY(0); }
104
  .inner-btn.disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(1); }
105
 
106
- /* Vibrant Icons */
107
  .icon-vibrant { transition: all 0.4s ease; }
108
  .icon-purple { color: #6d28d9; }
109
  .icon-pink { color: #db2777; }
@@ -113,6 +144,38 @@
113
 
114
  .inner-btn:hover:not(.disabled) .icon-vibrant { transform: scale(1.2) rotate(-5deg); }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  /* Logo Shimmer */
117
  .logo-shimmer {
118
  background: linear-gradient(90deg, #6d28d9, #d946ef, #6d28d9);
@@ -125,16 +188,16 @@
125
 
126
  @keyframes shimmer { to { background-position: 200% center; } }
127
 
128
- /* View Transitions */
129
  .view-section {
130
  display: none;
131
- opacity: 0;
132
- transform: translateY(20px);
133
- transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
134
  width: 100%;
135
- max-width: 32rem;
136
  flex-direction: column;
137
  align-items: center;
 
 
 
138
  }
139
 
140
  .view-section.active {
@@ -143,73 +206,68 @@
143
  transform: translateY(0);
144
  }
145
 
146
- /* Scanning Island Animation */
147
  #scanning-section {
148
  display: none;
 
149
  opacity: 0;
150
  transform: scale(0.95);
151
- transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
152
- margin-top: 2rem;
153
- }
154
- #scanning-section.show {
155
- display: flex;
156
- opacity: 1;
157
- transform: scale(1);
158
  }
 
159
 
160
  /* Progress Bar */
161
  .progress-track {
162
  width: 100%;
163
- height: 8px;
164
- background: rgba(0,0,0,0.06);
165
- border-radius: 10px;
166
  overflow: hidden;
167
  }
168
- [data-theme="dark"] .progress-track { background: rgba(255,255,255,0.12); }
169
  .progress-fill {
170
  height: 100%;
171
  width: 0%;
172
  background: linear-gradient(90deg, #7c3aed, #d946ef);
173
- transition: width 0.4s ease;
174
- box-shadow: 0 0 15px rgba(217, 70, 239, 0.5);
175
  }
176
 
177
- /* Result Image Frame */
178
  .result-image-frame {
179
- width: 180px;
180
- height: 180px;
181
  border-radius: 2rem;
182
  border: 4px solid var(--glass-border);
183
  overflow: hidden;
184
  margin-bottom: -40px;
185
  z-index: 20;
186
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
187
- background: #000;
188
  }
189
 
190
- /* Typography */
191
- h1 { letter-spacing: -0.05em; line-height: 1; }
192
- .sub-label { color: var(--text-secondary); font-weight: 700; text-transform: uppercase; font-size: 0.65rem; letter-spacing: 0.12em; }
193
-
194
  @keyframes scan { 0% { top: 0%; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } 100% { top: 100%; opacity: 0; } }
195
  </style>
196
  </head>
197
  <body data-theme="light">
198
 
 
 
 
 
199
  <!-- THEME TOGGLE -->
200
- <button onclick="toggleTheme()" class="fixed top-6 right-6 z-50 p-4 rounded-full glass-island hover:scale-110 active:scale-90 transition-all shadow-xl">
201
- <i data-lucide="moon" id="theme-btn-icon" class="w-6 h-6 text-purple-800 dark:text-purple-300"></i>
202
  </button>
203
 
204
- <main class="relative z-10 w-full min-h-screen flex flex-col items-center pt-24 pb-24 px-6">
205
 
206
  <!-- VIEW 1: HOME SCREEN -->
207
  <div id="view-home" class="view-section active">
208
 
209
- <!-- LOGO HEADER -->
210
- <div class="text-center mb-12 flex flex-col items-center gap-6">
211
  <!-- INLINE SVG LOGO -->
212
- <svg width="100" height="100" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" class="drop-shadow-2xl">
213
  <defs>
214
  <linearGradient id="mainGradient" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
215
  <stop offset="0%" stop-color="#7c3aed"/>
@@ -235,12 +293,18 @@
235
  <rect x="110" y="254" width="292" height="4" rx="2" fill="white" fill-opacity="0.6"/>
236
  </svg>
237
 
238
- <h1 class="text-5xl md:text-6xl font-black">
239
- <span class="logo-shimmer">RetinaScan</span>
240
- </h1>
 
 
 
 
 
 
241
  </div>
242
 
243
- <!-- SELECTION ISLAND -->
244
  <div class="glass-island p-8 flex flex-col gap-5 w-full max-w-[420px]" id="selection-island">
245
  <div onclick="startCameraFlow()" id="btn-camera" class="inner-btn group">
246
  <i data-lucide="camera" class="w-10 h-10 icon-vibrant icon-purple"></i>
@@ -259,16 +323,30 @@
259
  </div>
260
  </div>
261
 
262
- <!-- SCANNING ISLAND (Appears Below Buttons) -->
263
- <div id="scanning-section" class="w-full max-w-[420px] glass-island p-10 flex flex-col items-center">
264
- <div class="relative w-64 h-64 rounded-[2.8rem] overflow-hidden shadow-2xl mb-8 bg-black/30 border border-white/20">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  <img id="scan-preview" src="" class="w-full h-full object-cover opacity-90" />
266
  <div id="scan-laser" class="absolute top-0 left-0 w-full h-1.5 bg-purple-400 shadow-[0_0_25px_#c084fc] animate-[scan_2s_linear_infinite]"></div>
267
  </div>
268
 
269
  <div class="w-full text-center">
270
- <h2 id="scan-status" class="text-sm font-black tracking-widest text-purple-700 dark:text-purple-300 mb-2 uppercase">Neural Processing</h2>
271
- <p id="scan-detail" class="text-[11px] font-mono opacity-60 mb-5">Booting AI clusters...</p>
272
  <div class="progress-track">
273
  <div id="scan-bar" class="progress-fill"></div>
274
  </div>
@@ -278,47 +356,53 @@
278
  <input type="file" id="file-input" class="hidden" accept="image/*" onchange="handleFileSelect(event)" />
279
  </div>
280
 
281
- <!-- VIEW 2: RESULTS SCREEN (Separate Full View) -->
282
  <div id="view-result" class="view-section">
283
  <div class="result-image-frame">
284
  <img id="result-photo" src="" class="w-full h-full object-cover" />
285
  </div>
286
 
287
- <div class="glass-island p-10 pt-16 w-full max-w-[420px] flex flex-col items-center">
288
- <div id="result-icon-bg" class="w-20 h-20 rounded-[2.2rem] flex items-center justify-center mb-6 shadow-2xl">
289
- <i id="result-icon" data-lucide="activity" class="w-10 h-10 text-white"></i>
290
  </div>
291
 
292
- <h2 id="result-diagnosis" class="text-4xl font-bold mb-1 text-center">--</h2>
293
- <p id="result-severity" class="sub-label text-purple-600 dark:text-purple-400 mb-4 font-black">STAGE 0</p>
294
 
295
- <p id="result-desc" class="text-sm opacity-80 mb-10 font-medium text-center leading-relaxed max-w-[280px]">--</p>
296
 
297
- <div class="grid grid-cols-2 gap-4 w-full mb-10">
298
- <div class="bg-white/10 dark:bg-white/5 p-6 rounded-[2rem] border border-white/10 text-center">
299
- <p class="sub-label mb-2">Confidence</p>
300
- <p id="result-conf" class="text-2xl font-mono font-bold">--%</p>
301
  </div>
302
- <div class="bg-white/10 dark:bg-white/5 p-6 rounded-[2rem] border border-white/10 text-center">
303
- <p class="sub-label mb-2">Entropy</p>
304
- <p id="result-ent" class="text-2xl font-mono font-bold">--</p>
305
  </div>
306
  </div>
307
 
308
- <button onclick="resetApp()" class="w-full py-5 rounded-[2rem] bg-purple-700 text-white font-bold text-lg shadow-2xl shadow-purple-900/30 active:scale-95 transition-all">
309
- New Analysis
310
  </button>
311
  </div>
312
  </div>
313
 
314
- <!-- VIEW 3: CAMERA OVERLAY -->
315
- <div id="view-camera" class="hidden fixed inset-0 bg-black z-50 flex-col items-center justify-center">
316
  <video id="camera-feed" autoplay playsinline class="absolute inset-0 w-full h-full object-cover opacity-80"></video>
317
- <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.85)]"></div>
318
- <div class="absolute bottom-16 flex gap-14 items-center z-20">
319
- <button onclick="stopCamera()" class="p-6 rounded-full glass-island text-white"><i data-lucide="x" class="w-8 h-8"></i></button>
320
- <button onclick="capturePhoto()" class="w-24 h-24 rounded-full border-[5px] border-white flex items-center justify-center active:scale-90 transition-transform">
321
- <div class="w-20 h-20 rounded-full bg-white shadow-2xl"></div>
 
 
 
 
 
 
322
  </button>
323
  </div>
324
  <canvas id="camera-canvas" class="hidden"></canvas>
@@ -341,64 +425,49 @@
341
  }
342
 
343
  function switchView(id) {
344
- // Hide all views first with fade out
345
  document.querySelectorAll('.view-section').forEach(v => {
346
  v.classList.remove('active');
347
  setTimeout(() => {
348
  if(!v.classList.contains('active')) v.style.display = 'none';
349
- }, 600);
350
  });
351
 
352
- // Show target view
353
  const target = document.getElementById(id);
354
  if(id !== 'view-camera') {
355
  document.getElementById('view-camera').classList.add('hidden');
356
  document.getElementById('view-camera').classList.remove('flex');
357
 
358
- // Slight delay to allow fade out of previous view
359
  setTimeout(() => {
360
  target.style.display = 'flex';
361
- // Trigger reflow
362
  void target.offsetWidth;
363
  target.classList.add('active');
364
- }, 600);
365
  }
366
  }
367
 
368
  function resetApp() {
369
  stopCamera();
370
  document.getElementById('file-input').value = '';
371
-
372
- // Re-enable buttons
373
  document.getElementById('btn-camera').classList.remove('disabled');
374
  document.getElementById('btn-upload').classList.remove('disabled');
375
-
376
- // Hide scanning section immediately
377
  document.getElementById('scanning-section').classList.remove('show');
378
  setTimeout(() => {
379
  document.getElementById('scanning-section').style.display = 'none';
380
  document.getElementById('scan-bar').style.width = '0%';
381
  }, 300);
382
-
383
- // Go home
384
  switchView('view-home');
385
  }
386
 
387
  let stream = null;
388
  async function startCameraFlow() {
389
  if(document.getElementById('btn-camera').classList.contains('disabled')) return;
390
-
391
  const camView = document.getElementById('view-camera');
392
  camView.classList.remove('hidden');
393
  camView.classList.add('flex');
394
-
395
  try {
396
  stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
397
  document.getElementById('camera-feed').srcObject = stream;
398
- } catch(e) {
399
- console.error(e);
400
- camView.classList.add('hidden');
401
- }
402
  }
403
 
404
  function stopCamera() {
@@ -412,10 +481,7 @@
412
  const canvas = document.getElementById('camera-canvas');
413
  canvas.width = video.videoWidth; canvas.height = video.videoHeight;
414
  canvas.getContext('2d').drawImage(video, 0, 0);
415
- canvas.toBlob(blob => {
416
- stopCamera();
417
- processImage(blob);
418
- }, 'image/jpeg', 0.95);
419
  }
420
 
421
  function triggerUpload() {
@@ -428,19 +494,13 @@
428
  function processImage(fileBlob) {
429
  const reader = new FileReader();
430
  reader.onload = (e) => {
431
- // Disable buttons
432
  document.getElementById('btn-camera').classList.add('disabled');
433
  document.getElementById('btn-upload').classList.add('disabled');
434
-
435
- // Set previews
436
  document.getElementById('scan-preview').src = e.target.result;
437
  document.getElementById('result-photo').src = e.target.result;
438
 
439
- // Show scanning section below buttons
440
  const scanSection = document.getElementById('scanning-section');
441
  scanSection.style.display = 'flex';
442
-
443
- // Animate in
444
  setTimeout(() => {
445
  scanSection.classList.add('show');
446
  scanSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
@@ -454,11 +514,7 @@
454
  fetch('/analyze', { method: 'POST', body: formData })
455
  .then(r => r.json())
456
  .then(data => setTimeout(() => showResult(data), 3200))
457
- .catch(err => {
458
- console.error(err);
459
- alert("Analysis failed. Please try again.");
460
- resetApp();
461
- });
462
  };
463
  reader.readAsDataURL(fileBlob);
464
  }
@@ -489,18 +545,14 @@
489
  'orange': 'bg-orange-500',
490
  'rose': 'bg-rose-500'
491
  };
492
-
493
- document.getElementById('result-icon-bg').className = `w-20 h-20 rounded-[2.2rem] flex items-center justify-center mb-6 shadow-2xl ${colors[data.color] || 'bg-purple-600'}`;
494
  document.getElementById('result-icon').setAttribute('data-lucide', data.icon);
495
  document.getElementById('result-diagnosis').textContent = data.diagnosis;
496
  document.getElementById('result-severity').textContent = data.severity;
497
  document.getElementById('result-desc').textContent = data.description;
498
  document.getElementById('result-conf').textContent = data.confidence;
499
  document.getElementById('result-ent').textContent = data.features?.entropy || "N/A";
500
-
501
  lucide.createIcons();
502
-
503
- // Switch to dedicated result view
504
  switchView('view-result');
505
  }
506
  </script>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
  <title>RetinaScan</title>
7
 
8
+ <!-- APP ICONS -->
9
+ <link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='logo.svg') }}">
10
+ <link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='logo.png') }}">
11
+ <link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
 
 
 
 
 
12
 
13
  <meta name="apple-mobile-web-app-title" content="RetinaScan">
14
  <meta name="apple-mobile-web-app-capable" content="yes">
 
28
  --bg-grad-end: #e5d1ff;
29
  --text-main: #2d004d;
30
  --text-secondary: #5a2e85;
31
+ --glass-bg: rgba(255, 255, 255, 0.45);
32
+ --glass-border: rgba(255, 255, 255, 0.65);
33
  --glass-shadow: 0 20px 50px rgba(0, 0, 0, 0.08);
34
  --icon-vibrant: #7c3aed;
35
+ --btn-bg: rgba(255, 255, 255, 0.45);
36
  }
37
 
38
  [data-theme="dark"] {
39
  /* iOS Dark Theme */
40
+ --bg-grad-start: #111113;
41
+ --bg-grad-end: #050505;
42
  --text-main: #f3e8ff;
43
  --text-secondary: #c084fc;
44
+ --glass-bg: rgba(35, 35, 40, 0.65);
45
  --glass-border: rgba(255, 255, 255, 0.12);
46
  --glass-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
47
  --icon-vibrant: #d946ef;
48
+ --btn-bg: rgba(255, 255, 255, 0.08);
49
  }
50
 
51
  body {
 
57
  overflow-x: hidden;
58
  transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
59
  letter-spacing: -0.02em;
60
+ position: relative;
61
+ }
62
+
63
+ /* BACKGROUND BLOBS */
64
+ .bg-blob {
65
+ position: fixed;
66
+ border-radius: 50%;
67
+ filter: blur(90px);
68
+ z-index: -1;
69
+ opacity: 0.5;
70
+ animation: floatBlob 15s infinite alternate ease-in-out;
71
+ }
72
+
73
+ .blob-1 {
74
+ top: -10%;
75
+ left: -20%;
76
+ width: 500px;
77
+ height: 500px;
78
+ background: #a855f7; /* Purple */
79
+ }
80
+
81
+ .blob-2 {
82
+ bottom: -10%;
83
+ right: -20%;
84
+ width: 400px;
85
+ height: 400px;
86
+ background: #ec4899; /* Pink */
87
+ animation-delay: -5s;
88
  }
89
 
90
+ [data-theme="dark"] .blob-1 { opacity: 0.2; background: #581c87; }
91
+ [data-theme="dark"] .blob-2 { opacity: 0.2; background: #831843; }
92
+
93
+ @keyframes floatBlob {
94
+ 0% { transform: translate(0, 0) scale(1); }
95
+ 100% { transform: translate(50px, 80px) scale(1.1); }
96
+ }
97
+
98
+ /* iOS Glassmorphism */
99
  .glass-island {
100
  background: var(--glass-bg);
101
+ backdrop-filter: blur(40px) saturate(180%);
102
+ -webkit-backdrop-filter: blur(40px) saturate(180%);
103
  border: 1px solid var(--glass-border);
104
  box-shadow: var(--glass-shadow);
105
+ border-radius: 2.5rem;
106
  transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
107
  }
108
 
 
134
  .inner-btn:active:not(.disabled) { transform: scale(0.96) translateY(0); }
135
  .inner-btn.disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(1); }
136
 
137
+ /* Vibrant Icons (Restored) */
138
  .icon-vibrant { transition: all 0.4s ease; }
139
  .icon-purple { color: #6d28d9; }
140
  .icon-pink { color: #db2777; }
 
144
 
145
  .inner-btn:hover:not(.disabled) .icon-vibrant { transform: scale(1.2) rotate(-5deg); }
146
 
147
+ /* Status Badge */
148
+ .status-badge {
149
+ display: inline-flex;
150
+ align-items: center;
151
+ gap: 6px;
152
+ padding: 6px 14px;
153
+ background: rgba(255, 255, 255, 0.25);
154
+ border: 1px solid rgba(255, 255, 255, 0.4);
155
+ border-radius: 99px;
156
+ font-size: 0.7rem;
157
+ font-weight: 700;
158
+ letter-spacing: 0.05em;
159
+ text-transform: uppercase;
160
+ color: var(--text-secondary);
161
+ backdrop-filter: blur(10px);
162
+ }
163
+
164
+ .status-dot {
165
+ width: 8px;
166
+ height: 8px;
167
+ background: #22c55e;
168
+ border-radius: 50%;
169
+ box-shadow: 0 0 10px #22c55e;
170
+ animation: pulse 2s infinite;
171
+ }
172
+
173
+ @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
174
+
175
+ /* Typography */
176
+ h1 { letter-spacing: -0.04em; line-height: 1; }
177
+ .sub-label { color: var(--text-secondary); font-weight: 700; text-transform: uppercase; font-size: 0.65rem; letter-spacing: 0.12em; }
178
+
179
  /* Logo Shimmer */
180
  .logo-shimmer {
181
  background: linear-gradient(90deg, #6d28d9, #d946ef, #6d28d9);
 
188
 
189
  @keyframes shimmer { to { background-position: 200% center; } }
190
 
191
+ /* View Switching */
192
  .view-section {
193
  display: none;
 
 
 
194
  width: 100%;
195
+ max-width: 28rem;
196
  flex-direction: column;
197
  align-items: center;
198
+ opacity: 0;
199
+ transform: translateY(10px);
200
+ transition: opacity 0.5s ease, transform 0.5s ease;
201
  }
202
 
203
  .view-section.active {
 
206
  transform: translateY(0);
207
  }
208
 
209
+ /* Scan Animation */
210
  #scanning-section {
211
  display: none;
212
+ margin-top: 1.5rem;
213
  opacity: 0;
214
  transform: scale(0.95);
215
+ transition: all 0.5s ease;
 
 
 
 
 
 
216
  }
217
+ #scanning-section.show { display: flex; opacity: 1; transform: scale(1); }
218
 
219
  /* Progress Bar */
220
  .progress-track {
221
  width: 100%;
222
+ height: 6px;
223
+ background: rgba(0,0,0,0.08);
224
+ border-radius: 99px;
225
  overflow: hidden;
226
  }
227
+ [data-theme="dark"] .progress-track { background: rgba(255,255,255,0.15); }
228
  .progress-fill {
229
  height: 100%;
230
  width: 0%;
231
  background: linear-gradient(90deg, #7c3aed, #d946ef);
232
+ transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
 
233
  }
234
 
235
+ /* Result Frame */
236
  .result-image-frame {
237
+ width: 160px;
238
+ height: 160px;
239
  border-radius: 2rem;
240
  border: 4px solid var(--glass-border);
241
  overflow: hidden;
242
  margin-bottom: -40px;
243
  z-index: 20;
244
+ box-shadow: 0 15px 40px rgba(0,0,0,0.25);
245
+ background: #111;
246
  }
247
 
 
 
 
 
248
  @keyframes scan { 0% { top: 0%; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } 100% { top: 100%; opacity: 0; } }
249
  </style>
250
  </head>
251
  <body data-theme="light">
252
 
253
+ <!-- BACKGROUND BLOBS -->
254
+ <div class="bg-blob blob-1"></div>
255
+ <div class="bg-blob blob-2"></div>
256
+
257
  <!-- THEME TOGGLE -->
258
+ <button onclick="toggleTheme()" class="fixed top-6 right-6 z-50 p-3 rounded-full glass-island hover:scale-110 active:scale-95 transition-all shadow-lg backdrop-blur-md">
259
+ <i data-lucide="moon" id="theme-btn-icon" class="w-5 h-5 text-purple-800 dark:text-purple-300"></i>
260
  </button>
261
 
262
+ <main class="relative z-10 w-full min-h-screen flex flex-col items-center pt-20 pb-12 px-6">
263
 
264
  <!-- VIEW 1: HOME SCREEN -->
265
  <div id="view-home" class="view-section active">
266
 
267
+ <!-- HEADER -->
268
+ <div class="text-center mb-10 flex flex-col items-center gap-6">
269
  <!-- INLINE SVG LOGO -->
270
+ <svg width="90" height="90" 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">
271
  <defs>
272
  <linearGradient id="mainGradient" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
273
  <stop offset="0%" stop-color="#7c3aed"/>
 
293
  <rect x="110" y="254" width="292" height="4" rx="2" fill="white" fill-opacity="0.6"/>
294
  </svg>
295
 
296
+ <div>
297
+ <h1 class="text-5xl font-black mb-2">
298
+ <span class="logo-shimmer">RetinaScan</span>
299
+ </h1>
300
+ <div class="status-badge">
301
+ <span class="status-dot"></span>
302
+ AI System Active
303
+ </div>
304
+ </div>
305
  </div>
306
 
307
+ <!-- ACTION MENU (Restored Buttons) -->
308
  <div class="glass-island p-8 flex flex-col gap-5 w-full max-w-[420px]" id="selection-island">
309
  <div onclick="startCameraFlow()" id="btn-camera" class="inner-btn group">
310
  <i data-lucide="camera" class="w-10 h-10 icon-vibrant icon-purple"></i>
 
323
  </div>
324
  </div>
325
 
326
+ <!-- INFO CARD -->
327
+ <div class="mt-6 w-full max-w-[420px] glass-island p-5 flex items-start gap-4">
328
+ <i data-lucide="lightbulb" class="w-5 h-5 text-yellow-500 mt-1 flex-shrink-0"></i>
329
+ <div>
330
+ <h3 class="font-bold text-sm mb-1">Quick Tip</h3>
331
+ <p class="text-xs opacity-70 leading-relaxed">Ensure the room is well-lit and avoid glare on the screen for the most accurate AI diagnosis.</p>
332
+ </div>
333
+ </div>
334
+
335
+ <!-- FOOTER -->
336
+ <footer class="mt-auto pt-8 text-center opacity-40">
337
+ <p class="text-[10px] font-mono uppercase tracking-widest">Powered by EfficientNet v2</p>
338
+ </footer>
339
+
340
+ <!-- SCANNING SECTION (Initially Hidden) -->
341
+ <div id="scanning-section" class="w-full max-w-[420px] glass-island p-8 flex flex-col items-center">
342
+ <div class="relative w-56 h-56 rounded-[2rem] overflow-hidden shadow-2xl mb-6 bg-black/40 border border-white/20">
343
  <img id="scan-preview" src="" class="w-full h-full object-cover opacity-90" />
344
  <div id="scan-laser" class="absolute top-0 left-0 w-full h-1.5 bg-purple-400 shadow-[0_0_25px_#c084fc] animate-[scan_2s_linear_infinite]"></div>
345
  </div>
346
 
347
  <div class="w-full text-center">
348
+ <h2 id="scan-status" class="text-xs font-black tracking-widest text-purple-700 dark:text-purple-300 mb-2 uppercase">Processing</h2>
349
+ <p id="scan-detail" class="text-[10px] font-mono opacity-60 mb-4">Initializing neural matrix...</p>
350
  <div class="progress-track">
351
  <div id="scan-bar" class="progress-fill"></div>
352
  </div>
 
356
  <input type="file" id="file-input" class="hidden" accept="image/*" onchange="handleFileSelect(event)" />
357
  </div>
358
 
359
+ <!-- VIEW 2: RESULTS -->
360
  <div id="view-result" class="view-section">
361
  <div class="result-image-frame">
362
  <img id="result-photo" src="" class="w-full h-full object-cover" />
363
  </div>
364
 
365
+ <div class="glass-island p-8 pt-14 w-full flex flex-col items-center">
366
+ <div id="result-icon-bg" class="w-16 h-16 rounded-[1.5rem] flex items-center justify-center mb-4 shadow-xl transition-colors duration-500">
367
+ <i id="result-icon" data-lucide="activity" class="w-8 h-8 text-white"></i>
368
  </div>
369
 
370
+ <h2 id="result-diagnosis" class="text-3xl font-bold mb-1 text-center tracking-tight">--</h2>
371
+ <p id="result-severity" class="sub-label text-purple-600 dark:text-purple-400 mb-4 font-black text-xs">ANALYSIS COMPLETE</p>
372
 
373
+ <p id="result-desc" class="text-sm opacity-70 mb-8 font-medium text-center leading-relaxed max-w-[260px]">--</p>
374
 
375
+ <div class="grid grid-cols-2 gap-3 w-full mb-8">
376
+ <div class="bg-white/10 dark:bg-white/5 p-4 rounded-2xl border border-white/10 text-center">
377
+ <p class="sub-label mb-1 opacity-50">Confidence</p>
378
+ <p id="result-conf" class="text-xl font-mono font-bold">--%</p>
379
  </div>
380
+ <div class="bg-white/10 dark:bg-white/5 p-4 rounded-2xl border border-white/10 text-center">
381
+ <p class="sub-label mb-1 opacity-50">Entropy</p>
382
+ <p id="result-ent" class="text-xl font-mono font-bold">--</p>
383
  </div>
384
  </div>
385
 
386
+ <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">
387
+ Start New Scan
388
  </button>
389
  </div>
390
  </div>
391
 
392
+ <!-- VIEW 3: CAMERA -->
393
+ <div id="view-camera" class="hidden fixed inset-0 bg-black z-[100] flex-col items-center justify-center">
394
  <video id="camera-feed" autoplay playsinline class="absolute inset-0 w-full h-full object-cover opacity-80"></video>
395
+ <!-- Camera UI Overlays -->
396
+ <div class="absolute top-0 left-0 w-full p-8 flex justify-between items-start z-20">
397
+ <button onclick="stopCamera()" class="p-4 rounded-full bg-black/40 text-white backdrop-blur-md"><i data-lucide="x" class="w-6 h-6"></i></button>
398
+ </div>
399
+
400
+ <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>
401
+ <p class="absolute bottom-40 z-20 text-white/70 text-xs font-bold tracking-widest uppercase">Align Eye in Circle</p>
402
+
403
+ <div class="absolute bottom-12 flex justify-center w-full z-20">
404
+ <button onclick="capturePhoto()" class="w-20 h-20 rounded-full border-[4px] border-white flex items-center justify-center active:scale-90 transition-transform">
405
+ <div class="w-16 h-16 rounded-full bg-white shadow-lg"></div>
406
  </button>
407
  </div>
408
  <canvas id="camera-canvas" class="hidden"></canvas>
 
425
  }
426
 
427
  function switchView(id) {
 
428
  document.querySelectorAll('.view-section').forEach(v => {
429
  v.classList.remove('active');
430
  setTimeout(() => {
431
  if(!v.classList.contains('active')) v.style.display = 'none';
432
+ }, 500);
433
  });
434
 
 
435
  const target = document.getElementById(id);
436
  if(id !== 'view-camera') {
437
  document.getElementById('view-camera').classList.add('hidden');
438
  document.getElementById('view-camera').classList.remove('flex');
439
 
 
440
  setTimeout(() => {
441
  target.style.display = 'flex';
 
442
  void target.offsetWidth;
443
  target.classList.add('active');
444
+ }, 500);
445
  }
446
  }
447
 
448
  function resetApp() {
449
  stopCamera();
450
  document.getElementById('file-input').value = '';
 
 
451
  document.getElementById('btn-camera').classList.remove('disabled');
452
  document.getElementById('btn-upload').classList.remove('disabled');
 
 
453
  document.getElementById('scanning-section').classList.remove('show');
454
  setTimeout(() => {
455
  document.getElementById('scanning-section').style.display = 'none';
456
  document.getElementById('scan-bar').style.width = '0%';
457
  }, 300);
 
 
458
  switchView('view-home');
459
  }
460
 
461
  let stream = null;
462
  async function startCameraFlow() {
463
  if(document.getElementById('btn-camera').classList.contains('disabled')) return;
 
464
  const camView = document.getElementById('view-camera');
465
  camView.classList.remove('hidden');
466
  camView.classList.add('flex');
 
467
  try {
468
  stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
469
  document.getElementById('camera-feed').srcObject = stream;
470
+ } catch(e) { console.error(e); camView.classList.add('hidden'); }
 
 
 
471
  }
472
 
473
  function stopCamera() {
 
481
  const canvas = document.getElementById('camera-canvas');
482
  canvas.width = video.videoWidth; canvas.height = video.videoHeight;
483
  canvas.getContext('2d').drawImage(video, 0, 0);
484
+ canvas.toBlob(blob => { stopCamera(); processImage(blob); }, 'image/jpeg', 0.95);
 
 
 
485
  }
486
 
487
  function triggerUpload() {
 
494
  function processImage(fileBlob) {
495
  const reader = new FileReader();
496
  reader.onload = (e) => {
 
497
  document.getElementById('btn-camera').classList.add('disabled');
498
  document.getElementById('btn-upload').classList.add('disabled');
 
 
499
  document.getElementById('scan-preview').src = e.target.result;
500
  document.getElementById('result-photo').src = e.target.result;
501
 
 
502
  const scanSection = document.getElementById('scanning-section');
503
  scanSection.style.display = 'flex';
 
 
504
  setTimeout(() => {
505
  scanSection.classList.add('show');
506
  scanSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
 
514
  fetch('/analyze', { method: 'POST', body: formData })
515
  .then(r => r.json())
516
  .then(data => setTimeout(() => showResult(data), 3200))
517
+ .catch(err => { alert("Analysis failed."); resetApp(); });
 
 
 
 
518
  };
519
  reader.readAsDataURL(fileBlob);
520
  }
 
545
  'orange': 'bg-orange-500',
546
  'rose': 'bg-rose-500'
547
  };
548
+ document.getElementById('result-icon-bg').className = `w-16 h-16 rounded-[1.5rem] flex items-center justify-center mb-4 shadow-xl ${colors[data.color] || 'bg-purple-600'}`;
 
549
  document.getElementById('result-icon').setAttribute('data-lucide', data.icon);
550
  document.getElementById('result-diagnosis').textContent = data.diagnosis;
551
  document.getElementById('result-severity').textContent = data.severity;
552
  document.getElementById('result-desc').textContent = data.description;
553
  document.getElementById('result-conf').textContent = data.confidence;
554
  document.getElementById('result-ent').textContent = data.features?.entropy || "N/A";
 
555
  lucide.createIcons();
 
 
556
  switchView('view-result');
557
  }
558
  </script>