GOWREESH M G commited on
Commit
ba4caad
·
verified ·
1 Parent(s): 9b8b538

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +492 -440
templates/index.html CHANGED
@@ -1,441 +1,493 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>RetinaScan</title>
7
- <!-- Tailwind CSS -->
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <!-- Lucide Icons -->
10
- <script src="https://unpkg.com/lucide@latest"></script>
11
-
12
- <style>
13
- @import url('https://fonts.googleapis.com/css2?family=SF+Pro+Display:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
14
-
15
- :root {
16
- /* iOS Light Theme */
17
- --bg-grad-start: #fbf8ff;
18
- --bg-grad-end: #e5d1ff;
19
- --text-main: #2d004d;
20
- --text-secondary: #5a2e85;
21
- --glass-bg: rgba(255, 255, 255, 0.5);
22
- --glass-border: rgba(255, 255, 255, 0.8);
23
- --glass-shadow: 0 20px 50px rgba(0, 0, 0, 0.08);
24
- --icon-vibrant: #7c3aed;
25
- --btn-bg: rgba(255, 255, 255, 0.4);
26
- }
27
-
28
- [data-theme="dark"] {
29
- /* iOS Dark Theme */
30
- --bg-grad-start: #1c1c1e;
31
- --bg-grad-end: #0a0a0c;
32
- --text-main: #f3e8ff;
33
- --text-secondary: #c084fc;
34
- --glass-bg: rgba(44, 44, 46, 0.75);
35
- --glass-border: rgba(255, 255, 255, 0.12);
36
- --glass-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
37
- --icon-vibrant: #d946ef;
38
- --btn-bg: rgba(255, 255, 255, 0.05);
39
- }
40
-
41
- body {
42
- font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
43
- background: linear-gradient(180deg, var(--bg-grad-start), var(--bg-grad-end));
44
- color: var(--text-main);
45
- min-height: 100vh;
46
- margin: 0;
47
- overflow-x: hidden;
48
- transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
49
- letter-spacing: -0.02em;
50
- }
51
-
52
- /* iOS Glassmorphism - Premium Polish */
53
- .glass-island {
54
- background: var(--glass-bg);
55
- backdrop-filter: blur(45px) saturate(210%);
56
- -webkit-backdrop-filter: blur(45px) saturate(210%);
57
- border: 1px solid var(--glass-border);
58
- box-shadow: var(--glass-shadow);
59
- border-radius: 2.8rem;
60
- transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
61
- }
62
-
63
- /* Interactive Islands */
64
- .inner-btn {
65
- background: var(--btn-bg);
66
- border: 1px solid rgba(255, 255, 255, 0.2);
67
- border-radius: 2.2rem;
68
- padding: 1.6rem;
69
- display: flex;
70
- align-items: center;
71
- gap: 1.5rem;
72
- cursor: pointer;
73
- transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
74
- position: relative;
75
- }
76
-
77
- .inner-btn:hover:not(.disabled) {
78
- transform: scale(1.04) translateY(-6px);
79
- background: rgba(255, 255, 255, 0.25);
80
- box-shadow: 0 15px 35px rgba(124, 58, 237, 0.15);
81
- }
82
-
83
- [data-theme="dark"] .inner-btn:hover:not(.disabled) {
84
- background: rgba(255, 255, 255, 0.1);
85
- box-shadow: 0 15px 35px rgba(217, 70, 239, 0.2);
86
- }
87
-
88
- .inner-btn:active:not(.disabled) { transform: scale(0.96) translateY(0); }
89
- .inner-btn.disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(1); }
90
-
91
- /* Vibrant Icons */
92
- .icon-vibrant { transition: all 0.4s ease; }
93
- .icon-purple { color: #6d28d9; }
94
- .icon-pink { color: #db2777; }
95
-
96
- [data-theme="dark"] .icon-purple { color: #a855f7; filter: drop-shadow(0 0 12px rgba(168, 85, 247, 0.6)); }
97
- [data-theme="dark"] .icon-pink { color: #f472b6; filter: drop-shadow(0 0 12px rgba(244, 114, 182, 0.6)); }
98
-
99
- .inner-btn:hover:not(.disabled) .icon-vibrant { transform: scale(1.2) rotate(-5deg); }
100
-
101
- /* Logo Visibility */
102
- .logo-shimmer {
103
- background: linear-gradient(90deg, #6d28d9, #d946ef, #6d28d9);
104
- background-size: 200% auto;
105
- color: transparent;
106
- -webkit-background-clip: text;
107
- background-clip: text;
108
- animation: shimmer 4s linear infinite;
109
- }
110
-
111
- @keyframes shimmer { to { background-position: 200% center; } }
112
-
113
- /* View Switching Transitions */
114
- .view-section {
115
- display: none;
116
- opacity: 0;
117
- transform: translateY(20px);
118
- transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
119
- width: 100%;
120
- max-width: 32rem;
121
- flex-direction: column;
122
- align-items: center;
123
- }
124
-
125
- .view-section.active {
126
- display: flex;
127
- opacity: 1;
128
- transform: translateY(0);
129
- }
130
-
131
- /* Scanning Island Appear Animation */
132
- #scanning-section {
133
- display: none;
134
- opacity: 0;
135
- transform: scale(0.95);
136
- transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
137
- margin-top: 2rem;
138
- }
139
- #scanning-section.show {
140
- display: flex;
141
- opacity: 1;
142
- transform: scale(1);
143
- }
144
-
145
- /* Progress Bar */
146
- .progress-track {
147
- width: 100%;
148
- height: 8px;
149
- background: rgba(0,0,0,0.06);
150
- border-radius: 10px;
151
- overflow: hidden;
152
- }
153
- [data-theme="dark"] .progress-track { background: rgba(255,255,255,0.12); }
154
- .progress-fill {
155
- height: 100%;
156
- width: 0%;
157
- background: linear-gradient(90deg, #7c3aed, #d946ef);
158
- transition: width 0.4s ease;
159
- box-shadow: 0 0 15px rgba(217, 70, 239, 0.5);
160
- }
161
-
162
- /* Result View Specifics */
163
- .result-image-frame {
164
- width: 180px;
165
- height: 180px;
166
- border-radius: 2rem;
167
- border: 4px solid var(--glass-border);
168
- overflow: hidden;
169
- margin-bottom: -40px;
170
- z-index: 20;
171
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
172
- }
173
-
174
- /* Typography */
175
- h1 { letter-spacing: -0.05em; line-height: 1; }
176
- .sub-label { color: var(--text-secondary); font-weight: 700; text-transform: uppercase; font-size: 0.65rem; letter-spacing: 0.12em; }
177
-
178
- @keyframes scan { 0% { top: 0%; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } 100% { top: 100%; opacity: 0; } }
179
- </style>
180
- </head>
181
- <body data-theme="light">
182
-
183
- <!-- THEME TOGGLE -->
184
- <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">
185
- <i data-lucide="moon" id="theme-btn-icon" class="w-6 h-6 text-purple-800 dark:text-purple-300"></i>
186
- </button>
187
-
188
- <main class="relative z-10 w-full min-h-screen flex flex-col items-center pt-24 pb-24 px-6">
189
-
190
- <!-- VIEW 1: HOME SCREEN -->
191
- <div id="view-home" class="view-section active">
192
-
193
- <!-- LOGO -->
194
- <div class="text-center mb-12 flex items-center gap-6">
195
- <i data-lucide="scan-eye" class="w-16 h-16 text-purple-700 dark:text-purple-400 drop-shadow-2xl"></i>
196
- <h1 class="text-5xl md:text-6xl font-black">
197
- <span class="logo-shimmer">RetinaScan</span>
198
- </h1>
199
- </div>
200
-
201
- <!-- SELECTION ISLAND -->
202
- <div class="glass-island p-8 flex flex-col gap-5 w-full max-w-[420px]" id="selection-island">
203
- <div onclick="startCameraFlow()" id="btn-camera" class="inner-btn group">
204
- <i data-lucide="camera" class="w-10 h-10 icon-vibrant icon-purple"></i>
205
- <div class="text-left">
206
- <span class="block text-xl font-bold">Take Photo</span>
207
- <span class="sub-label opacity-70">PHONE CAMERA</span>
208
- </div>
209
- </div>
210
-
211
- <div onclick="triggerUpload()" id="btn-upload" class="inner-btn group">
212
- <i data-lucide="image" class="w-10 h-10 icon-vibrant icon-pink"></i>
213
- <div class="text-left">
214
- <span class="block text-xl font-bold">Choose Photo</span>
215
- <span class="sub-label opacity-70">Photo Library</span>
216
- </div>
217
- </div>
218
- </div>
219
-
220
- <!-- SCANNING ISLAND (Same screen, appears below buttons) -->
221
- <div id="scanning-section" class="w-full max-w-[420px] glass-island p-10 flex flex-col items-center">
222
- <div class="relative w-64 h-64 rounded-[2.8rem] overflow-hidden shadow-2xl mb-8 bg-black/30 border border-white/20">
223
- <img id="scan-preview" src="" class="w-full h-full object-cover opacity-90" />
224
- <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>
225
- </div>
226
-
227
- <div class="w-full text-center">
228
- <h2 id="scan-status" class="text-sm font-black tracking-widest text-purple-700 dark:text-purple-300 mb-2 uppercase">Neural Processing</h2>
229
- <p id="scan-detail" class="text-[11px] font-mono opacity-60 mb-5">Booting AI clusters...</p>
230
- <div class="progress-track">
231
- <div id="scan-bar" class="progress-fill"></div>
232
- </div>
233
- </div>
234
- </div>
235
-
236
- <input type="file" id="file-input" class="hidden" accept="image/*" onchange="handleFileSelect(event)" />
237
- </div>
238
-
239
- <!-- VIEW 2: RESULTS SCREEN -->
240
- <div id="view-result" class="view-section">
241
- <div class="result-image-frame">
242
- <img id="result-photo" src="" class="w-full h-full object-cover" />
243
- </div>
244
-
245
- <div class="glass-island p-10 pt-16 w-full flex flex-col items-center">
246
- <div id="result-icon-bg" class="w-20 h-20 rounded-[2.2rem] flex items-center justify-center mb-6 shadow-2xl">
247
- <i id="result-icon" data-lucide="activity" class="w-10 h-10 text-white"></i>
248
- </div>
249
-
250
- <h2 id="result-diagnosis" class="text-4xl font-bold mb-1 text-center">--</h2>
251
- <p id="result-severity" class="sub-label text-purple-600 dark:text-purple-400 mb-4 font-black">STAGE 0</p>
252
-
253
- <p id="result-desc" class="text-sm opacity-80 mb-10 font-medium text-center leading-relaxed max-w-[280px]">--</p>
254
-
255
- <div class="grid grid-cols-2 gap-4 w-full mb-10">
256
- <div class="bg-white/10 dark:bg-white/5 p-6 rounded-[2rem] border border-white/10 text-center">
257
- <p class="sub-label mb-2">Confidence</p>
258
- <p id="result-conf" class="text-2xl font-mono font-bold">--%</p>
259
- </div>
260
- <div class="bg-white/10 dark:bg-white/5 p-6 rounded-[2rem] border border-white/10 text-center">
261
- <p class="sub-label mb-2">Entropy</p>
262
- <p id="result-ent" class="text-2xl font-mono font-bold">--</p>
263
- </div>
264
- </div>
265
-
266
- <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">
267
- New Analysis
268
- </button>
269
- </div>
270
- </div>
271
-
272
- <!-- VIEW 3: CAMERA (Fullscreen) -->
273
- <div id="view-camera" class="hidden fixed inset-0 bg-black z-50 flex-col items-center justify-center">
274
- <video id="camera-feed" autoplay playsinline class="absolute inset-0 w-full h-full object-cover opacity-80"></video>
275
- <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>
276
- <div class="absolute bottom-16 flex gap-14 items-center z-20">
277
- <button onclick="stopCamera()" class="p-6 rounded-full glass-island text-white"><i data-lucide="x" class="w-8 h-8"></i></button>
278
- <button onclick="capturePhoto()" class="w-24 h-24 rounded-full border-[5px] border-white flex items-center justify-center active:scale-90 transition-transform">
279
- <div class="w-20 h-20 rounded-full bg-white shadow-2xl"></div>
280
- </button>
281
- </div>
282
- <canvas id="camera-canvas" class="hidden"></canvas>
283
- </div>
284
-
285
- </main>
286
-
287
- <script>
288
- lucide.createIcons();
289
-
290
- function toggleTheme() {
291
- const body = document.body;
292
- const current = body.getAttribute('data-theme');
293
- const target = current === 'light' ? 'dark' : 'light';
294
- body.setAttribute('data-theme', target);
295
-
296
- const icon = document.getElementById('theme-btn-icon');
297
- icon.setAttribute('data-lucide', target === 'light' ? 'moon' : 'sun');
298
- lucide.createIcons();
299
- }
300
-
301
- function switchView(id) {
302
- document.querySelectorAll('.view-section').forEach(v => {
303
- v.classList.remove('active');
304
- setTimeout(() => v.style.display = 'none', 600);
305
- });
306
-
307
- const target = document.getElementById(id);
308
- setTimeout(() => {
309
- target.style.display = 'flex';
310
- setTimeout(() => target.classList.add('active'), 50);
311
- }, 600);
312
-
313
- if(id === 'view-camera') {
314
- target.classList.remove('hidden');
315
- } else {
316
- document.getElementById('view-camera').classList.add('hidden');
317
- }
318
- }
319
-
320
- function resetApp() {
321
- stopCamera();
322
- document.getElementById('file-input').value = '';
323
- document.getElementById('scanning-section').classList.remove('show');
324
- document.getElementById('btn-camera').classList.remove('disabled');
325
- document.getElementById('btn-upload').classList.remove('disabled');
326
-
327
- switchView('view-home');
328
- setTimeout(() => {
329
- document.getElementById('scanning-section').style.display = 'none';
330
- document.getElementById('scan-bar').style.width = '0%';
331
- }, 650);
332
- }
333
-
334
- let stream = null;
335
- async function startCameraFlow() {
336
- if(document.getElementById('btn-camera').classList.contains('disabled')) return;
337
- document.getElementById('view-camera').classList.remove('hidden');
338
- document.getElementById('view-camera').classList.add('flex');
339
- try {
340
- stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
341
- document.getElementById('camera-feed').srcObject = stream;
342
- } catch(e) { document.getElementById('view-camera').classList.add('hidden'); }
343
- }
344
-
345
- function stopCamera() {
346
- if(stream) { stream.getTracks().forEach(t => t.stop()); stream = null; }
347
- document.getElementById('view-camera').classList.add('hidden');
348
- document.getElementById('view-camera').classList.remove('flex');
349
- }
350
-
351
- function capturePhoto() {
352
- const video = document.getElementById('camera-feed');
353
- const canvas = document.getElementById('camera-canvas');
354
- canvas.width = video.videoWidth; canvas.height = video.videoHeight;
355
- canvas.getContext('2d').drawImage(video, 0, 0);
356
- canvas.toBlob(blob => {
357
- stopCamera();
358
- processImage(blob);
359
- }, 'image/jpeg', 0.95);
360
- }
361
-
362
- function triggerUpload() {
363
- if(document.getElementById('btn-upload').classList.contains('disabled')) return;
364
- document.getElementById('file-input').click();
365
- }
366
-
367
- function handleFileSelect(e) { if(e.target.files[0]) processImage(e.target.files[0]); }
368
-
369
- function processImage(fileBlob) {
370
- const reader = new FileReader();
371
- reader.onload = (e) => {
372
- document.getElementById('btn-camera').classList.add('disabled');
373
- document.getElementById('btn-upload').classList.add('disabled');
374
-
375
- document.getElementById('scan-preview').src = e.target.result;
376
- document.getElementById('result-photo').src = e.target.result;
377
-
378
- const scanSection = document.getElementById('scanning-section');
379
- scanSection.style.display = 'flex';
380
-
381
- setTimeout(() => {
382
- scanSection.classList.add('show');
383
- scanSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
384
- }, 100);
385
-
386
- simulateScanProgress();
387
-
388
- const formData = new FormData();
389
- formData.append('image', fileBlob, 'scan.jpg');
390
-
391
- fetch('/analyze', { method: 'POST', body: formData })
392
- .then(r => r.json())
393
- .then(data => setTimeout(() => showResult(data), 3200))
394
- .catch(() => resetApp());
395
- };
396
- reader.readAsDataURL(fileBlob);
397
- }
398
-
399
- function simulateScanProgress() {
400
- const bar = document.getElementById('scan-bar');
401
- const status = document.getElementById('scan-status');
402
- const detail = document.getElementById('scan-detail');
403
- const steps = [
404
- { s: "Preprocessing", d: "Isolating Green Channel...", w: "30%" },
405
- { s: "Segmentation", d: "Mapping Vascular Geometry...", w: "65%" },
406
- { s: "AI Processing", d: "EfficientNet Analysis...", w: "95%" },
407
- { s: "Finalizing", d: "Generating Diagnosis...", w: "100%" }
408
- ];
409
- steps.forEach((step, i) => {
410
- setTimeout(() => {
411
- status.innerText = step.s;
412
- detail.innerText = step.d;
413
- bar.style.width = step.w;
414
- }, i * 800);
415
- });
416
- }
417
-
418
- function showResult(data) {
419
- const colors = {
420
- 'emerald': 'bg-emerald-500',
421
- 'yellow': 'bg-yellow-500',
422
- 'orange': 'bg-orange-500',
423
- 'rose': 'bg-rose-500'
424
- };
425
-
426
- 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'}`;
427
- document.getElementById('result-icon').setAttribute('data-lucide', data.icon);
428
- document.getElementById('result-diagnosis').textContent = data.diagnosis;
429
- document.getElementById('result-severity').textContent = data.severity;
430
- document.getElementById('result-desc').textContent = data.description;
431
- document.getElementById('result-conf').textContent = data.confidence;
432
- document.getElementById('result-ent').textContent = data.features?.entropy || (Math.random() * 2 + 3).toFixed(2);
433
-
434
- lucide.createIcons();
435
-
436
- // Switch to dedicated result view
437
- switchView('view-result');
438
- }
439
- </script>
440
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>RetinaScan</title>
7
+ <!-- Tailwind CSS -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <!-- Lucide Icons -->
10
+ <script src="https://unpkg.com/lucide@latest"></script>
11
+
12
+ <style>
13
+ @import url('https://fonts.googleapis.com/css2?family=SF+Pro+Display:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
14
+
15
+ :root {
16
+ /* iOS Light Theme */
17
+ --bg-grad-start: #fbf8ff;
18
+ --bg-grad-end: #e5d1ff;
19
+ --text-main: #2d004d;
20
+ --text-secondary: #5a2e85;
21
+ --glass-bg: rgba(255, 255, 255, 0.5);
22
+ --glass-border: rgba(255, 255, 255, 0.8);
23
+ --glass-shadow: 0 20px 50px rgba(0, 0, 0, 0.08);
24
+ --icon-vibrant: #7c3aed;
25
+ --btn-bg: rgba(255, 255, 255, 0.4);
26
+ }
27
+
28
+ [data-theme="dark"] {
29
+ /* iOS Dark Theme */
30
+ --bg-grad-start: #1c1c1e;
31
+ --bg-grad-end: #0a0a0c;
32
+ --text-main: #f3e8ff;
33
+ --text-secondary: #c084fc;
34
+ --glass-bg: rgba(44, 44, 46, 0.75);
35
+ --glass-border: rgba(255, 255, 255, 0.12);
36
+ --glass-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
37
+ --icon-vibrant: #d946ef;
38
+ --btn-bg: rgba(255, 255, 255, 0.05);
39
+ }
40
+
41
+ body {
42
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
43
+ background: linear-gradient(180deg, var(--bg-grad-start), var(--bg-grad-end));
44
+ color: var(--text-main);
45
+ min-height: 100vh;
46
+ margin: 0;
47
+ overflow-x: hidden;
48
+ transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
49
+ letter-spacing: -0.02em;
50
+ }
51
+
52
+ /* iOS Glassmorphism - Premium Polish */
53
+ .glass-island {
54
+ background: var(--glass-bg);
55
+ backdrop-filter: blur(45px) saturate(210%);
56
+ -webkit-backdrop-filter: blur(45px) saturate(210%);
57
+ border: 1px solid var(--glass-border);
58
+ box-shadow: var(--glass-shadow);
59
+ border-radius: 2.8rem;
60
+ transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
61
+ }
62
+
63
+ /* Interactive Islands */
64
+ .inner-btn {
65
+ background: var(--btn-bg);
66
+ border: 1px solid rgba(255, 255, 255, 0.2);
67
+ border-radius: 2.2rem;
68
+ padding: 1.6rem;
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 1.5rem;
72
+ cursor: pointer;
73
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
74
+ position: relative;
75
+ }
76
+
77
+ .inner-btn:hover:not(.disabled) {
78
+ transform: scale(1.04) translateY(-6px);
79
+ background: rgba(255, 255, 255, 0.25);
80
+ box-shadow: 0 15px 35px rgba(124, 58, 237, 0.15);
81
+ }
82
+
83
+ [data-theme="dark"] .inner-btn:hover:not(.disabled) {
84
+ background: rgba(255, 255, 255, 0.1);
85
+ box-shadow: 0 15px 35px rgba(217, 70, 239, 0.2);
86
+ }
87
+
88
+ .inner-btn:active:not(.disabled) { transform: scale(0.96) translateY(0); }
89
+ .inner-btn.disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(1); }
90
+
91
+ /* Vibrant Icons */
92
+ .icon-vibrant { transition: all 0.4s ease; }
93
+ .icon-purple { color: #6d28d9; }
94
+ .icon-pink { color: #db2777; }
95
+
96
+ [data-theme="dark"] .icon-purple { color: #a855f7; filter: drop-shadow(0 0 12px rgba(168, 85, 247, 0.6)); }
97
+ [data-theme="dark"] .icon-pink { color: #f472b6; filter: drop-shadow(0 0 12px rgba(244, 114, 182, 0.6)); }
98
+
99
+ .inner-btn:hover:not(.disabled) .icon-vibrant { transform: scale(1.2) rotate(-5deg); }
100
+
101
+ /* Logo Shimmer */
102
+ .logo-shimmer {
103
+ background: linear-gradient(90deg, #6d28d9, #d946ef, #6d28d9);
104
+ background-size: 200% auto;
105
+ color: transparent;
106
+ -webkit-background-clip: text;
107
+ background-clip: text;
108
+ animation: shimmer 4s linear infinite;
109
+ }
110
+
111
+ @keyframes shimmer { to { background-position: 200% center; } }
112
+
113
+ /* View Transitions */
114
+ .view-section {
115
+ display: none;
116
+ opacity: 0;
117
+ transform: translateY(20px);
118
+ transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
119
+ width: 100%;
120
+ max-width: 32rem;
121
+ flex-direction: column;
122
+ align-items: center;
123
+ }
124
+
125
+ .view-section.active {
126
+ display: flex;
127
+ opacity: 1;
128
+ transform: translateY(0);
129
+ }
130
+
131
+ /* Scanning Island Animation */
132
+ #scanning-section {
133
+ display: none;
134
+ opacity: 0;
135
+ transform: scale(0.95);
136
+ transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
137
+ margin-top: 2rem;
138
+ }
139
+ #scanning-section.show {
140
+ display: flex;
141
+ opacity: 1;
142
+ transform: scale(1);
143
+ }
144
+
145
+ /* Progress Bar */
146
+ .progress-track {
147
+ width: 100%;
148
+ height: 8px;
149
+ background: rgba(0,0,0,0.06);
150
+ border-radius: 10px;
151
+ overflow: hidden;
152
+ }
153
+ [data-theme="dark"] .progress-track { background: rgba(255,255,255,0.12); }
154
+ .progress-fill {
155
+ height: 100%;
156
+ width: 0%;
157
+ background: linear-gradient(90deg, #7c3aed, #d946ef);
158
+ transition: width 0.4s ease;
159
+ box-shadow: 0 0 15px rgba(217, 70, 239, 0.5);
160
+ }
161
+
162
+ /* Result Image Frame */
163
+ .result-image-frame {
164
+ width: 180px;
165
+ height: 180px;
166
+ border-radius: 2rem;
167
+ border: 4px solid var(--glass-border);
168
+ overflow: hidden;
169
+ margin-bottom: -40px;
170
+ z-index: 20;
171
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
172
+ background: #000;
173
+ }
174
+
175
+ /* Typography */
176
+ h1 { letter-spacing: -0.05em; 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
+ @keyframes scan { 0% { top: 0%; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } 100% { top: 100%; opacity: 0; } }
180
+ </style>
181
+ </head>
182
+ <body data-theme="light">
183
+
184
+ <!-- THEME TOGGLE -->
185
+ <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">
186
+ <i data-lucide="moon" id="theme-btn-icon" class="w-6 h-6 text-purple-800 dark:text-purple-300"></i>
187
+ </button>
188
+
189
+ <main class="relative z-10 w-full min-h-screen flex flex-col items-center pt-24 pb-24 px-6">
190
+
191
+ <!-- VIEW 1: HOME SCREEN -->
192
+ <div id="view-home" class="view-section active">
193
+
194
+ <!-- LOGO HEADER -->
195
+ <div class="text-center mb-12 flex flex-col items-center gap-6">
196
+ <!-- INLINE SVG LOGO -->
197
+ <svg width="100" height="100" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" class="drop-shadow-2xl">
198
+ <defs>
199
+ <linearGradient id="mainGradient" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
200
+ <stop offset="0%" stop-color="#7c3aed"/>
201
+ <stop offset="100%" stop-color="#d946ef"/>
202
+ </linearGradient>
203
+ <linearGradient id="irisGradient" x1="256" y1="150" x2="256" y2="362" gradientUnits="userSpaceOnUse">
204
+ <stop offset="0%" stop-color="#4c1d95"/>
205
+ <stop offset="100%" stop-color="#7c3aed"/>
206
+ </linearGradient>
207
+ </defs>
208
+ <rect x="32" y="32" width="448" height="448" rx="110" fill="url(#mainGradient)" />
209
+ <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"/>
210
+ <circle cx="256" cy="256" r="64" fill="url(#irisGradient)"/>
211
+ <circle cx="256" cy="256" r="28" fill="#1a0a33"/>
212
+ <circle cx="276" cy="236" r="10" fill="white" fill-opacity="0.9"/>
213
+ <ellipse cx="256" cy="110" rx="160" ry="80" fill="white" fill-opacity="0.15"/>
214
+ <g stroke="white" stroke-width="8" stroke-linecap="round" stroke-opacity="0.9">
215
+ <path d="M140 140 L110 140 L110 170" fill="none"/>
216
+ <path d="M372 140 L402 140 L402 170" fill="none"/>
217
+ <path d="M140 372 L110 372 L110 342" fill="none"/>
218
+ <path d="M372 372 L402 372 L402 342" fill="none"/>
219
+ </g>
220
+ <rect x="110" y="254" width="292" height="4" rx="2" fill="white" fill-opacity="0.6"/>
221
+ </svg>
222
+
223
+ <h1 class="text-5xl md:text-6xl font-black">
224
+ <span class="logo-shimmer">RetinaScan</span>
225
+ </h1>
226
+ </div>
227
+
228
+ <!-- SELECTION ISLAND -->
229
+ <div class="glass-island p-8 flex flex-col gap-5 w-full max-w-[420px]" id="selection-island">
230
+ <div onclick="startCameraFlow()" id="btn-camera" class="inner-btn group">
231
+ <i data-lucide="camera" class="w-10 h-10 icon-vibrant icon-purple"></i>
232
+ <div class="text-left">
233
+ <span class="block text-xl font-bold">Take Photo</span>
234
+ <span class="sub-label opacity-70">PHONE CAMERA</span>
235
+ </div>
236
+ </div>
237
+
238
+ <div onclick="triggerUpload()" id="btn-upload" class="inner-btn group">
239
+ <i data-lucide="image" class="w-10 h-10 icon-vibrant icon-pink"></i>
240
+ <div class="text-left">
241
+ <span class="block text-xl font-bold">Choose Photo</span>
242
+ <span class="sub-label opacity-70">Photo Library</span>
243
+ </div>
244
+ </div>
245
+ </div>
246
+
247
+ <!-- SCANNING ISLAND (Appears Below Buttons) -->
248
+ <div id="scanning-section" class="w-full max-w-[420px] glass-island p-10 flex flex-col items-center">
249
+ <div class="relative w-64 h-64 rounded-[2.8rem] overflow-hidden shadow-2xl mb-8 bg-black/30 border border-white/20">
250
+ <img id="scan-preview" src="" class="w-full h-full object-cover opacity-90" />
251
+ <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>
252
+ </div>
253
+
254
+ <div class="w-full text-center">
255
+ <h2 id="scan-status" class="text-sm font-black tracking-widest text-purple-700 dark:text-purple-300 mb-2 uppercase">Neural Processing</h2>
256
+ <p id="scan-detail" class="text-[11px] font-mono opacity-60 mb-5">Booting AI clusters...</p>
257
+ <div class="progress-track">
258
+ <div id="scan-bar" class="progress-fill"></div>
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ <input type="file" id="file-input" class="hidden" accept="image/*" onchange="handleFileSelect(event)" />
264
+ </div>
265
+
266
+ <!-- VIEW 2: RESULTS SCREEN (Separate Full View) -->
267
+ <div id="view-result" class="view-section">
268
+ <div class="result-image-frame">
269
+ <img id="result-photo" src="" class="w-full h-full object-cover" />
270
+ </div>
271
+
272
+ <div class="glass-island p-10 pt-16 w-full max-w-[420px] flex flex-col items-center">
273
+ <div id="result-icon-bg" class="w-20 h-20 rounded-[2.2rem] flex items-center justify-center mb-6 shadow-2xl">
274
+ <i id="result-icon" data-lucide="activity" class="w-10 h-10 text-white"></i>
275
+ </div>
276
+
277
+ <h2 id="result-diagnosis" class="text-4xl font-bold mb-1 text-center">--</h2>
278
+ <p id="result-severity" class="sub-label text-purple-600 dark:text-purple-400 mb-4 font-black">STAGE 0</p>
279
+
280
+ <p id="result-desc" class="text-sm opacity-80 mb-10 font-medium text-center leading-relaxed max-w-[280px]">--</p>
281
+
282
+ <div class="grid grid-cols-2 gap-4 w-full mb-10">
283
+ <div class="bg-white/10 dark:bg-white/5 p-6 rounded-[2rem] border border-white/10 text-center">
284
+ <p class="sub-label mb-2">Confidence</p>
285
+ <p id="result-conf" class="text-2xl font-mono font-bold">--%</p>
286
+ </div>
287
+ <div class="bg-white/10 dark:bg-white/5 p-6 rounded-[2rem] border border-white/10 text-center">
288
+ <p class="sub-label mb-2">Entropy</p>
289
+ <p id="result-ent" class="text-2xl font-mono font-bold">--</p>
290
+ </div>
291
+ </div>
292
+
293
+ <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">
294
+ New Analysis
295
+ </button>
296
+ </div>
297
+ </div>
298
+
299
+ <!-- VIEW 3: CAMERA OVERLAY -->
300
+ <div id="view-camera" class="hidden fixed inset-0 bg-black z-50 flex-col items-center justify-center">
301
+ <video id="camera-feed" autoplay playsinline class="absolute inset-0 w-full h-full object-cover opacity-80"></video>
302
+ <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>
303
+ <div class="absolute bottom-16 flex gap-14 items-center z-20">
304
+ <button onclick="stopCamera()" class="p-6 rounded-full glass-island text-white"><i data-lucide="x" class="w-8 h-8"></i></button>
305
+ <button onclick="capturePhoto()" class="w-24 h-24 rounded-full border-[5px] border-white flex items-center justify-center active:scale-90 transition-transform">
306
+ <div class="w-20 h-20 rounded-full bg-white shadow-2xl"></div>
307
+ </button>
308
+ </div>
309
+ <canvas id="camera-canvas" class="hidden"></canvas>
310
+ </div>
311
+
312
+ </main>
313
+
314
+ <script>
315
+ lucide.createIcons();
316
+
317
+ function toggleTheme() {
318
+ const body = document.body;
319
+ const current = body.getAttribute('data-theme');
320
+ const target = current === 'light' ? 'dark' : 'light';
321
+ body.setAttribute('data-theme', target);
322
+
323
+ const icon = document.getElementById('theme-btn-icon');
324
+ icon.setAttribute('data-lucide', target === 'light' ? 'moon' : 'sun');
325
+ lucide.createIcons();
326
+ }
327
+
328
+ function switchView(id) {
329
+ // Hide all views first with fade out
330
+ document.querySelectorAll('.view-section').forEach(v => {
331
+ v.classList.remove('active');
332
+ setTimeout(() => {
333
+ if(!v.classList.contains('active')) v.style.display = 'none';
334
+ }, 600);
335
+ });
336
+
337
+ // Show target view
338
+ const target = document.getElementById(id);
339
+ if(id !== 'view-camera') {
340
+ document.getElementById('view-camera').classList.add('hidden');
341
+ document.getElementById('view-camera').classList.remove('flex');
342
+
343
+ // Slight delay to allow fade out of previous view
344
+ setTimeout(() => {
345
+ target.style.display = 'flex';
346
+ // Trigger reflow
347
+ void target.offsetWidth;
348
+ target.classList.add('active');
349
+ }, 600);
350
+ }
351
+ }
352
+
353
+ function resetApp() {
354
+ stopCamera();
355
+ document.getElementById('file-input').value = '';
356
+
357
+ // Re-enable buttons
358
+ document.getElementById('btn-camera').classList.remove('disabled');
359
+ document.getElementById('btn-upload').classList.remove('disabled');
360
+
361
+ // Hide scanning section immediately
362
+ document.getElementById('scanning-section').classList.remove('show');
363
+ setTimeout(() => {
364
+ document.getElementById('scanning-section').style.display = 'none';
365
+ document.getElementById('scan-bar').style.width = '0%';
366
+ }, 300);
367
+
368
+ // Go home
369
+ switchView('view-home');
370
+ }
371
+
372
+ let stream = null;
373
+ async function startCameraFlow() {
374
+ if(document.getElementById('btn-camera').classList.contains('disabled')) return;
375
+
376
+ const camView = document.getElementById('view-camera');
377
+ camView.classList.remove('hidden');
378
+ camView.classList.add('flex');
379
+
380
+ try {
381
+ stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
382
+ document.getElementById('camera-feed').srcObject = stream;
383
+ } catch(e) {
384
+ console.error(e);
385
+ camView.classList.add('hidden');
386
+ }
387
+ }
388
+
389
+ function stopCamera() {
390
+ if(stream) { stream.getTracks().forEach(t => t.stop()); stream = null; }
391
+ document.getElementById('view-camera').classList.add('hidden');
392
+ document.getElementById('view-camera').classList.remove('flex');
393
+ }
394
+
395
+ function capturePhoto() {
396
+ const video = document.getElementById('camera-feed');
397
+ const canvas = document.getElementById('camera-canvas');
398
+ canvas.width = video.videoWidth; canvas.height = video.videoHeight;
399
+ canvas.getContext('2d').drawImage(video, 0, 0);
400
+ canvas.toBlob(blob => {
401
+ stopCamera();
402
+ processImage(blob);
403
+ }, 'image/jpeg', 0.95);
404
+ }
405
+
406
+ function triggerUpload() {
407
+ if(document.getElementById('btn-upload').classList.contains('disabled')) return;
408
+ document.getElementById('file-input').click();
409
+ }
410
+
411
+ function handleFileSelect(e) { if(e.target.files[0]) processImage(e.target.files[0]); }
412
+
413
+ function processImage(fileBlob) {
414
+ const reader = new FileReader();
415
+ reader.onload = (e) => {
416
+ // Disable buttons
417
+ document.getElementById('btn-camera').classList.add('disabled');
418
+ document.getElementById('btn-upload').classList.add('disabled');
419
+
420
+ // Set previews
421
+ document.getElementById('scan-preview').src = e.target.result;
422
+ document.getElementById('result-photo').src = e.target.result;
423
+
424
+ // Show scanning section below buttons
425
+ const scanSection = document.getElementById('scanning-section');
426
+ scanSection.style.display = 'flex';
427
+
428
+ // Animate in
429
+ setTimeout(() => {
430
+ scanSection.classList.add('show');
431
+ scanSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
432
+ }, 50);
433
+
434
+ simulateScanProgress();
435
+
436
+ const formData = new FormData();
437
+ formData.append('image', fileBlob, 'scan.jpg');
438
+
439
+ fetch('/analyze', { method: 'POST', body: formData })
440
+ .then(r => r.json())
441
+ .then(data => setTimeout(() => showResult(data), 3200))
442
+ .catch(err => {
443
+ console.error(err);
444
+ alert("Analysis failed. Please try again.");
445
+ resetApp();
446
+ });
447
+ };
448
+ reader.readAsDataURL(fileBlob);
449
+ }
450
+
451
+ function simulateScanProgress() {
452
+ const bar = document.getElementById('scan-bar');
453
+ const status = document.getElementById('scan-status');
454
+ const detail = document.getElementById('scan-detail');
455
+ const steps = [
456
+ { s: "Preprocessing", d: "Isolating Green Channel...", w: "30%" },
457
+ { s: "Segmentation", d: "Mapping Vascular Geometry...", w: "65%" },
458
+ { s: "AI Processing", d: "EfficientNet Analysis...", w: "95%" },
459
+ { s: "Finalizing", d: "Generating Diagnosis...", w: "100%" }
460
+ ];
461
+ steps.forEach((step, i) => {
462
+ setTimeout(() => {
463
+ status.innerText = step.s;
464
+ detail.innerText = step.d;
465
+ bar.style.width = step.w;
466
+ }, i * 800);
467
+ });
468
+ }
469
+
470
+ function showResult(data) {
471
+ const colors = {
472
+ 'emerald': 'bg-emerald-500',
473
+ 'yellow': 'bg-yellow-500',
474
+ 'orange': 'bg-orange-500',
475
+ 'rose': 'bg-rose-500'
476
+ };
477
+
478
+ 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'}`;
479
+ document.getElementById('result-icon').setAttribute('data-lucide', data.icon);
480
+ document.getElementById('result-diagnosis').textContent = data.diagnosis;
481
+ document.getElementById('result-severity').textContent = data.severity;
482
+ document.getElementById('result-desc').textContent = data.description;
483
+ document.getElementById('result-conf').textContent = data.confidence;
484
+ document.getElementById('result-ent').textContent = data.features?.entropy || "N/A";
485
+
486
+ lucide.createIcons();
487
+
488
+ // Switch to dedicated result view
489
+ switchView('view-result');
490
+ }
491
+ </script>
492
+ </body>
493
  </html>