themisfit21 commited on
Commit
cf1cc9c
·
verified ·
1 Parent(s): fe4b826

Update PulmoScanAI.html

Browse files
Files changed (1) hide show
  1. PulmoScanAI.html +382 -381
PulmoScanAI.html CHANGED
@@ -1,382 +1,383 @@
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>PulmoScanAI • AI Lung Cancer Detection</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
8
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
9
- <style>
10
- :root {
11
- --green: #00ffaa;
12
- --bg: #040d1a;
13
- --card: rgba(15, 25, 45, 0.75);
14
- --border: rgba(0, 255, 170, 0.35);
15
- --text: #e0fff8;
16
- }
17
-
18
- * { margin: 0; padding: 0; box-sizing: border-box; }
19
- body {
20
- font-family: 'Inter', sans-serif;
21
- background: var(--bg);
22
- color: var(--text);
23
- min-height: 100vh;
24
- overflow-y: auto;
25
- position: relative;
26
- }
27
-
28
- /* ULTRA-ELEGANT DIGITAL LAB BACKGROUND – NO SCAN LINES */
29
- .lab-bg {
30
- position: fixed;
31
- top: 0; left: 0; width: 100%; height: 100%;
32
- pointer-events: none;
33
- z-index: 0;
34
- overflow: hidden;
35
- }
36
-
37
- /* Molecular network – visible & beautiful */
38
- .network {
39
- position: absolute;
40
- width: 100%; height: 100%;
41
- background-image:
42
- radial-gradient(circle at 20% 80%, rgba(0,255,170,0.15) 1px, transparent 1px),
43
- radial-gradient(circle at 80% 20%, rgba(0,255,170,0.15) 1px, transparent 1px),
44
- radial-gradient(circle at 50% 50%, rgba(0,255,170,0.1) 1px, transparent 1px);
45
- background-size: 120px 120px, 160px 160px, 200px 200px;
46
- background-position: 0 0, 40px 70px, 80px 40px;
47
- animation: networkFlow 40s linear infinite;
48
- opacity: 0.6;
49
- }
50
- @keyframes networkFlow {
51
- 0% { background-position: 0 0, 40px 70px, 80px 40px; }
52
- 100% { background-position: 120px 120px, -120px -50px, -80px 100px; }
53
- }
54
-
55
- /* Energy pulse waves */
56
- .pulse-wave {
57
- position: absolute;
58
- width: 600px; height: 600px;
59
- border: 2px solid rgba(0,255,170,0.3);
60
- border-radius: 50%;
61
- top: 50%; left: 50%;
62
- transform: translate(-50%, -50%);
63
- animation: pulse 12s infinite ease-out;
64
- opacity: 0;
65
- }
66
- .pulse-wave:nth-child(2) { animation-delay: 4s; width: 800px; height: 800px; }
67
- .pulse-wave:nth-child(3) { animation-delay: 8s; width: 1000px; height: 1000px; }
68
- @keyframes pulse {
69
- 0% { transform: translate(-50%, -50%) scale(0.1); opacity: 0.6; }
70
- 80% { opacity: 0.2; }
71
- 100% { transform: translate(-50%, -50%) scale(1.8); opacity: 0; }
72
- }
73
-
74
- /* Floating luminous cells / particles – clearly visible */
75
- .cell {
76
- position: absolute;
77
- width: 10px; height: 10px;
78
- background: #00ffaa;
79
- border-radius: 50%;
80
- box-shadow: 0 0 30px #00ffaa, 0 0 60px #00ffaa;
81
- opacity: 0;
82
- animation: floatCell 22s infinite linear;
83
- }
84
- @keyframes floatCell {
85
- 0% { opacity: 0; transform: translateY(110vh) scale(0.2) rotate(0deg); }
86
- 10% { opacity: 1; }
87
- 90% { opacity: 1; }
88
- 100% { opacity: 0; transform: translateY(-150px) scale(1) rotate(360deg); }
89
- }
90
-
91
- /* Soft central glow */
92
- .core-glow {
93
- position: absolute;
94
- top: 50%; left: 50%;
95
- width: 900px; height: 900px;
96
- background: radial-gradient(circle, rgba(0,255,170,0.12) 0%, transparent 65%);
97
- border-radius: 50%;
98
- transform: translate(-50%, -50%);
99
- animation: breathe 15s infinite ease-in-out;
100
- }
101
- @keyframes breathe {
102
- 0%,100% { transform: translate(-50%, -50%) scale(1); opacity: 0.4; }
103
- 50% { transform: translate(-50%, -50%) scale(1.15); opacity: 0.6; }
104
- }
105
-
106
- header { text-align: center; margin: 110px 0 60px; z-index: 10; position: relative; }
107
- .main-title {
108
- font-family: 'Playfair Display', serif;
109
- font-size: clamp(3.5rem, 7vw, 5.5rem);
110
- font-weight: 700;
111
- background: linear-gradient(90deg, #ffffff, #00ffaa);
112
- -webkit-background-clip: text;
113
- -webkit-text-fill-color: transparent;
114
- text-shadow: 0 0 40px rgba(0,255,170,0.5);
115
- }
116
-
117
- .container {
118
- max-width: 1100px;
119
- margin: 0 auto;
120
- display: grid;
121
- grid-template-columns: 1fr 1fr;
122
- gap: 40px;
123
- z-index: 2;
124
- position: relative;
125
- }
126
-
127
- .upload-panel, .result-panel {
128
- background: var(--card);
129
- backdrop-filter: blur(28px);
130
- border: 1px solid var(--border);
131
- border-radius: 24px;
132
- padding: 36px;
133
- box-shadow: 0 20px 60px rgba(0,0,0,0.7);
134
- display: flex;
135
- flex-direction: column;
136
- align-items: stretch;
137
- }
138
-
139
- .upload-panel {
140
- max-height: fit-content;
141
- }
142
-
143
- .panel-title, .result-title { font-size: 1.4rem; color: white; margin-bottom: 8px; font-weight: 600; }
144
- .result-title { text-align: center; margin-bottom: 24px; }
145
- .panel-subtitle { font-size: 0.9rem; color: #bbbbbb; margin-bottom: 28px; }
146
-
147
- .upload-box {
148
- background: rgba(0,255,170,0.12);
149
- border: 2px solid rgba(0,255,170,0.4);
150
- border-radius: 20px;
151
- padding: 20px;
152
- text-align: center;
153
- cursor: pointer;
154
- transition: all 0.4s ease;
155
- display: flex;
156
- flex-direction: column;
157
- align-items: center;
158
- justify-content: center;
159
- gap: 12px;
160
- min-height: 180px;
161
- margin-bottom: 20px;
162
- }
163
- .upload-box:hover { background: rgba(0,255,170,0.22); border-color: var(--green); transform: translateY(-5px); }
164
- .upload-box.dragover { background: rgba(0,255,170,0.3); }
165
-
166
- .upload-icon { font-size: 2.2rem; color: var(--green); }
167
- .upload-text { font-size: 1rem; color: white; font-weight: 600; }
168
- .upload-info { font-size: 0.8rem; color: #aaa; margin-top: 4px; }
169
-
170
- #file-input { display: none; }
171
-
172
- .scanner-container {
173
- margin: 24px auto;
174
- width: 85%;
175
- height: 5px;
176
- background: rgba(255,255,255,0.1);
177
- border-radius: 10px;
178
- overflow: hidden;
179
- display: none;
180
- }
181
- .scanner-bar {
182
- height: 100%;
183
- background: linear-gradient(90deg, transparent, var(--green), transparent);
184
- animation: scan 1.8s linear infinite;
185
- box-shadow: 0 0 20px var(--green);
186
- }
187
- @keyframes scan { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
188
-
189
- .analyze-btn {
190
- width: 100%;
191
- padding: 18px;
192
- font-size: 1.2rem;
193
- font-weight: 700;
194
- background: var(--green);
195
- color: #000;
196
- border: none;
197
- border-radius: 50px;
198
- cursor: pointer;
199
- box-shadow: 0 12px 40px rgba(0,255,170,0.6);
200
- transition: all 0.4s;
201
- }
202
- .analyze-btn:hover:not(:disabled) {
203
- transform: translateY(-5px);
204
- box-shadow: 0 25px 60px rgba(0,255,170,0.8);
205
- }
206
-
207
- .result-panel { align-items: center; }
208
- .preview {
209
- display: flex;
210
- justify-content: center;
211
- align-items: center;
212
- padding-top: 6px;
213
- }
214
- .preview img {
215
- width: 240px;
216
- height: 240px;
217
- object-fit: cover;
218
- border-radius: 16px;
219
- border: 2px solid var(--border);
220
- box-shadow: 0 15px 40px rgba(0,0,0,0.5);
221
- display: block;
222
- }
223
-
224
- .result-box {
225
- padding: 18px 16px;
226
- background: rgba(0,0,0,0.4);
227
- border-radius: 16px;
228
- font-size: 1.2rem;
229
- font-weight: 700;
230
- text-align: center;
231
- min-height: 65px;
232
- display: flex;
233
- align-items: center;
234
- justify-content: center;
235
- margin: 24px auto 0;
236
- max-width: 260px;
237
- }
238
- .cancer { border: 3px solid #ff3366; color: #ff6b9d; }
239
- .normal { border: 3px solid var(--green); color: var(--green); }
240
-
241
- footer { text-align: center; margin: 90px 0 40px; color: #666; font-size: 0.9rem; z-index: 2; position: relative; }
242
-
243
- @media (max-width: 992px) { .container { grid-template-columns: 1fr; } }
244
- @media (max-width: 600px) { .upload-box { flex-direction: column; } }
245
- </style>
246
- </head>
247
- <body>
248
-
249
- <!-- ELEGANT DIGITAL LAB BACKGROUND -->
250
- <div class="lab-bg">
251
- <div class="network"></div>
252
- <div class="core-glow"></div>
253
- <div class="pulse-wave"></div>
254
- <div class="pulse-wave"></div>
255
- <div class="pulse-wave"></div>
256
- </div>
257
-
258
- <header>
259
- <h1 class="main-title">PulmoScanAI</h1>
260
- </header>
261
-
262
- <div class="container">
263
- <div class="upload-panel">
264
- <h2 class="panel-title">Upload Image</h2>
265
- <p class="panel-subtitle">Trained on 100,000+ histopathology samples</p>
266
-
267
- <label for="file-input" class="upload-box" id="upload-box">
268
- <i class="fas fa-microscope upload-icon"></i>
269
- <div>
270
- <div class="upload-text">Drop image or click to browse</div>
271
- <div class="upload-info">JPG, PNG, TIFF • Max 20MB</div>
272
- </div>
273
- </label>
274
- <input type="file" id="file-input" accept="image/*">
275
-
276
- <div class="scanner-container" id="scanner"><div class="scanner-bar"></div></div>
277
-
278
- <button class="analyze-btn" id="analyze-btn">
279
- <span id="btn-text">Analyze with AI</span>
280
- </button>
281
- </div>
282
-
283
- <div class="result-panel">
284
- <h2 class="result-title">Detection Result</h2>
285
- <div class="preview" id="preview"></div>
286
- <div class="result-box" id="result">Upload an image and click Analyze</div>
287
- </div>
288
- </div>
289
-
290
- <footer>© 2025 PulmoScanAI • Next-Gen AI Pathology Platform</footer>
291
-
292
- <script>
293
- // Create 20 beautiful floating cells
294
- for(let i = 0; i < 20; i++) {
295
- let cell = document.createElement('div');
296
- cell.className = 'cell';
297
- cell.style.left = Math.random() * 100 + '%';
298
- cell.style.animationDelay = Math.random() * 20 + 's';
299
- cell.style.animationDuration = 18 + Math.random() * 18 + 's';
300
- document.querySelector('.lab-bg').appendChild(cell);
301
- }
302
-
303
- // Functional script (unchanged)
304
- const input = document.getElementById('file-input');
305
- const uploadBox = document.getElementById('upload-box');
306
- const preview = document.getElementById('preview');
307
- const result = document.getElementById('result');
308
- const scanner = document.getElementById('scanner');
309
- const analyzeBtn = document.getElementById('analyze-btn');
310
- const btnText = document.getElementById('btn-text');
311
-
312
- ['dragenter','dragover'].forEach(e => uploadBox.addEventListener(e, ev => { ev.preventDefault(); uploadBox.classList.add('dragover'); }));
313
- ['dragleave','drop'].forEach(e => uploadBox.addEventListener(e, ev => { ev.preventDefault(); uploadBox.classList.remove('dragover'); }));
314
- uploadBox.addEventListener('drop', e => e.dataTransfer.files[0] && (input.files = e.dataTransfer.files) && handleFile(e.dataTransfer.files[0]));
315
- input.addEventListener('change', e => e.target.files[0] && handleFile(e.target.files[0]));
316
-
317
- function handleFile(file) {
318
- const reader = new FileReader();
319
- reader.onload = e => preview.innerHTML = `<img src="${e.target.result}" alt="Sample">`;
320
- reader.readAsDataURL(file);
321
- }
322
-
323
- analyzeBtn.addEventListener('click', () => {
324
- if (!input.files.length) return alert("Please upload an image first");
325
-
326
- const formData = new FormData();
327
- formData.append('image', input.files[0]);
328
-
329
- btnText.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Analyzing...`;
330
- analyzeBtn.disabled = true;
331
- scanner.style.display = 'block';
332
- result.innerHTML = "AI is analyzing tissue...";
333
- result.className = "result-box";
334
-
335
- // Send image to backend for real model prediction
336
- fetch('http://127.0.0.1:5000/api/predict', {
337
- method: 'POST',
338
- body: formData,
339
- cache: 'no-store', // Prevent caching
340
- headers: {
341
- 'Pragma': 'no-cache',
342
- 'Expires': '0'
343
- }
344
- })
345
- .then(response => response.json())
346
- .then(data => {
347
- scanner.style.display = 'none';
348
-
349
- // Clear previous result classes
350
- result.className = "result-box";
351
-
352
- if (data.error) {
353
- result.innerHTML = `Error: ${data.error}`;
354
- result.className = "result-box";
355
- } else {
356
- const diagnosis = data.diagnosis;
357
- const confidence = data.confidence_percentage;
358
- result.innerHTML = `${diagnosis}<br><span style="font-size: 0.9rem; opacity: 0.85;">${confidence}% Confidence</span>`;
359
-
360
- // Apply correct color based on prediction
361
- if (data.is_cancer) {
362
- result.classList.add('cancer');
363
- } else {
364
- result.classList.add('normal');
365
- }
366
- }
367
-
368
- btnText.textContent = "Analyze with AI";
369
- analyzeBtn.disabled = false;
370
- })
371
- .catch(error => {
372
- scanner.style.display = 'none';
373
- result.innerHTML = `Error: ${error.message}`;
374
- result.className = "result-box";
375
- btnText.textContent = "Analyze with AI";
376
- analyzeBtn.disabled = false;
377
- console.error('Prediction error:', error);
378
- });
379
- });
380
- </script>
381
- </body>
 
382
  </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>PulmoScanAI • AI Lung Cancer Detection</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
9
+ <style>
10
+ :root {
11
+ --green: #00ffaa;
12
+ --bg: #040d1a;
13
+ --card: rgba(15, 25, 45, 0.75);
14
+ --border: rgba(0, 255, 170, 0.35);
15
+ --text: #e0fff8;
16
+ }
17
+
18
+ * { margin: 0; padding: 0; box-sizing: border-box; }
19
+ body {
20
+ font-family: 'Inter', sans-serif;
21
+ background: var(--bg);
22
+ color: var(--text);
23
+ min-height: 100vh;
24
+ overflow-y: auto;
25
+ position: relative;
26
+ }
27
+
28
+ /* ULTRA-ELEGANT DIGITAL LAB BACKGROUND – NO SCAN LINES */
29
+ .lab-bg {
30
+ position: fixed;
31
+ top: 0; left: 0; width: 100%; height: 100%;
32
+ pointer-events: none;
33
+ z-index: 0;
34
+ overflow: hidden;
35
+ }
36
+
37
+ /* Molecular network – visible & beautiful */
38
+ .network {
39
+ position: absolute;
40
+ width: 100%; height: 100%;
41
+ background-image:
42
+ radial-gradient(circle at 20% 80%, rgba(0,255,170,0.15) 1px, transparent 1px),
43
+ radial-gradient(circle at 80% 20%, rgba(0,255,170,0.15) 1px, transparent 1px),
44
+ radial-gradient(circle at 50% 50%, rgba(0,255,170,0.1) 1px, transparent 1px);
45
+ background-size: 120px 120px, 160px 160px, 200px 200px;
46
+ background-position: 0 0, 40px 70px, 80px 40px;
47
+ animation: networkFlow 40s linear infinite;
48
+ opacity: 0.6;
49
+ }
50
+ @keyframes networkFlow {
51
+ 0% { background-position: 0 0, 40px 70px, 80px 40px; }
52
+ 100% { background-position: 120px 120px, -120px -50px, -80px 100px; }
53
+ }
54
+
55
+ /* Energy pulse waves */
56
+ .pulse-wave {
57
+ position: absolute;
58
+ width: 600px; height: 600px;
59
+ border: 2px solid rgba(0,255,170,0.3);
60
+ border-radius: 50%;
61
+ top: 50%; left: 50%;
62
+ transform: translate(-50%, -50%);
63
+ animation: pulse 12s infinite ease-out;
64
+ opacity: 0;
65
+ }
66
+ .pulse-wave:nth-child(2) { animation-delay: 4s; width: 800px; height: 800px; }
67
+ .pulse-wave:nth-child(3) { animation-delay: 8s; width: 1000px; height: 1000px; }
68
+ @keyframes pulse {
69
+ 0% { transform: translate(-50%, -50%) scale(0.1); opacity: 0.6; }
70
+ 80% { opacity: 0.2; }
71
+ 100% { transform: translate(-50%, -50%) scale(1.8); opacity: 0; }
72
+ }
73
+
74
+ /* Floating luminous cells / particles – clearly visible */
75
+ .cell {
76
+ position: absolute;
77
+ width: 10px; height: 10px;
78
+ background: #00ffaa;
79
+ border-radius: 50%;
80
+ box-shadow: 0 0 30px #00ffaa, 0 0 60px #00ffaa;
81
+ opacity: 0;
82
+ animation: floatCell 22s infinite linear;
83
+ }
84
+ @keyframes floatCell {
85
+ 0% { opacity: 0; transform: translateY(110vh) scale(0.2) rotate(0deg); }
86
+ 10% { opacity: 1; }
87
+ 90% { opacity: 1; }
88
+ 100% { opacity: 0; transform: translateY(-150px) scale(1) rotate(360deg); }
89
+ }
90
+
91
+ /* Soft central glow */
92
+ .core-glow {
93
+ position: absolute;
94
+ top: 50%; left: 50%;
95
+ width: 900px; height: 900px;
96
+ background: radial-gradient(circle, rgba(0,255,170,0.12) 0%, transparent 65%);
97
+ border-radius: 50%;
98
+ transform: translate(-50%, -50%);
99
+ animation: breathe 15s infinite ease-in-out;
100
+ }
101
+ @keyframes breathe {
102
+ 0%,100% { transform: translate(-50%, -50%) scale(1); opacity: 0.4; }
103
+ 50% { transform: translate(-50%, -50%) scale(1.15); opacity: 0.6; }
104
+ }
105
+
106
+ header { text-align: center; margin: 110px 0 60px; z-index: 10; position: relative; }
107
+ .main-title {
108
+ font-family: 'Playfair Display', serif;
109
+ font-size: clamp(3.5rem, 7vw, 5.5rem);
110
+ font-weight: 700;
111
+ background: linear-gradient(90deg, #ffffff, #00ffaa);
112
+ -webkit-background-clip: text;
113
+ -webkit-text-fill-color: transparent;
114
+ text-shadow: 0 0 40px rgba(0,255,170,0.5);
115
+ }
116
+
117
+ .container {
118
+ max-width: 1100px;
119
+ margin: 0 auto;
120
+ display: grid;
121
+ grid-template-columns: 1fr 1fr;
122
+ gap: 40px;
123
+ z-index: 2;
124
+ position: relative;
125
+ }
126
+
127
+ .upload-panel, .result-panel {
128
+ background: var(--card);
129
+ backdrop-filter: blur(28px);
130
+ border: 1px solid var(--border);
131
+ border-radius: 24px;
132
+ padding: 36px;
133
+ box-shadow: 0 20px 60px rgba(0,0,0,0.7);
134
+ display: flex;
135
+ flex-direction: column;
136
+ align-items: stretch;
137
+ }
138
+
139
+ .upload-panel {
140
+ max-height: fit-content;
141
+ }
142
+
143
+ .panel-title, .result-title { font-size: 1.4rem; color: white; margin-bottom: 8px; font-weight: 600; }
144
+ .result-title { text-align: center; margin-bottom: 24px; }
145
+ .panel-subtitle { font-size: 0.9rem; color: #bbbbbb; margin-bottom: 28px; }
146
+
147
+ .upload-box {
148
+ background: rgba(0,255,170,0.12);
149
+ border: 2px solid rgba(0,255,170,0.4);
150
+ border-radius: 20px;
151
+ padding: 20px;
152
+ text-align: center;
153
+ cursor: pointer;
154
+ transition: all 0.4s ease;
155
+ display: flex;
156
+ flex-direction: column;
157
+ align-items: center;
158
+ justify-content: center;
159
+ gap: 12px;
160
+ min-height: 180px;
161
+ margin-bottom: 20px;
162
+ }
163
+ .upload-box:hover { background: rgba(0,255,170,0.22); border-color: var(--green); transform: translateY(-5px); }
164
+ .upload-box.dragover { background: rgba(0,255,170,0.3); }
165
+
166
+ .upload-icon { font-size: 2.2rem; color: var(--green); }
167
+ .upload-text { font-size: 1rem; color: white; font-weight: 600; }
168
+ .upload-info { font-size: 0.8rem; color: #aaa; margin-top: 4px; }
169
+
170
+ #file-input { display: none; }
171
+
172
+ .scanner-container {
173
+ margin: 24px auto;
174
+ width: 85%;
175
+ height: 5px;
176
+ background: rgba(255,255,255,0.1);
177
+ border-radius: 10px;
178
+ overflow: hidden;
179
+ display: none;
180
+ }
181
+ .scanner-bar {
182
+ height: 100%;
183
+ background: linear-gradient(90deg, transparent, var(--green), transparent);
184
+ animation: scan 1.8s linear infinite;
185
+ box-shadow: 0 0 20px var(--green);
186
+ }
187
+ @keyframes scan { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
188
+
189
+ .analyze-btn {
190
+ width: 100%;
191
+ padding: 18px;
192
+ font-size: 1.2rem;
193
+ font-weight: 700;
194
+ background: var(--green);
195
+ color: #000;
196
+ border: none;
197
+ border-radius: 50px;
198
+ cursor: pointer;
199
+ box-shadow: 0 12px 40px rgba(0,255,170,0.6);
200
+ transition: all 0.4s;
201
+ }
202
+ .analyze-btn:hover:not(:disabled) {
203
+ transform: translateY(-5px);
204
+ box-shadow: 0 25px 60px rgba(0,255,170,0.8);
205
+ }
206
+
207
+ .result-panel { align-items: center; }
208
+ .preview {
209
+ display: flex;
210
+ justify-content: center;
211
+ align-items: center;
212
+ padding-top: 6px;
213
+ }
214
+ .preview img {
215
+ width: 240px;
216
+ height: 240px;
217
+ object-fit: cover;
218
+ border-radius: 16px;
219
+ border: 2px solid var(--border);
220
+ box-shadow: 0 15px 40px rgba(0,0,0,0.5);
221
+ display: block;
222
+ }
223
+
224
+ .result-box {
225
+ padding: 18px 16px;
226
+ background: rgba(0,0,0,0.4);
227
+ border-radius: 16px;
228
+ font-size: 1.2rem;
229
+ font-weight: 700;
230
+ text-align: center;
231
+ min-height: 65px;
232
+ display: flex;
233
+ align-items: center;
234
+ justify-content: center;
235
+ margin: 24px auto 0;
236
+ max-width: 260px;
237
+ }
238
+ .cancer { border: 3px solid #ff3366; color: #ff6b9d; }
239
+ .normal { border: 3px solid var(--green); color: var(--green); }
240
+
241
+ footer { text-align: center; margin: 90px 0 40px; color: #666; font-size: 0.9rem; z-index: 2; position: relative; }
242
+
243
+ @media (max-width: 992px) { .container { grid-template-columns: 1fr; } }
244
+ @media (max-width: 600px) { .upload-box { flex-direction: column; } }
245
+ </style>
246
+ </head>
247
+ <body>
248
+
249
+ <!-- ELEGANT DIGITAL LAB BACKGROUND -->
250
+ <div class="lab-bg">
251
+ <div class="network"></div>
252
+ <div class="core-glow"></div>
253
+ <div class="pulse-wave"></div>
254
+ <div class="pulse-wave"></div>
255
+ <div class="pulse-wave"></div>
256
+ </div>
257
+
258
+ <header>
259
+ <h1 class="main-title">PulmoScanAI</h1>
260
+ </header>
261
+
262
+ <div class="container">
263
+ <div class="upload-panel">
264
+ <h2 class="panel-title">Upload Image</h2>
265
+ <p class="panel-subtitle">Trained on 100,000+ histopathology samples</p>
266
+
267
+ <label for="file-input" class="upload-box" id="upload-box">
268
+ <i class="fas fa-microscope upload-icon"></i>
269
+ <div>
270
+ <div class="upload-text">Drop image or click to browse</div>
271
+ <div class="upload-info">JPG, PNG, TIFF • Max 20MB</div>
272
+ </div>
273
+ </label>
274
+ <input type="file" id="file-input" accept="image/*">
275
+
276
+ <div class="scanner-container" id="scanner"><div class="scanner-bar"></div></div>
277
+
278
+ <button class="analyze-btn" id="analyze-btn">
279
+ <span id="btn-text">Analyze with AI</span>
280
+ </button>
281
+ </div>
282
+
283
+ <div class="result-panel">
284
+ <h2 class="result-title">Detection Result</h2>
285
+ <div class="preview" id="preview"></div>
286
+ <div class="result-box" id="result">Upload an image and click Analyze</div>
287
+ </div>
288
+ </div>
289
+
290
+ <footer>© 2025 PulmoScanAI • Next-Gen AI Pathology Platform</footer>
291
+
292
+ <script>
293
+ // Create 20 beautiful floating cells
294
+ for(let i = 0; i < 20; i++) {
295
+ let cell = document.createElement('div');
296
+ cell.className = 'cell';
297
+ cell.style.left = Math.random() * 100 + '%';
298
+ cell.style.animationDelay = Math.random() * 20 + 's';
299
+ cell.style.animationDuration = 18 + Math.random() * 18 + 's';
300
+ document.querySelector('.lab-bg').appendChild(cell);
301
+ }
302
+
303
+ // Functional script (unchanged)
304
+ const input = document.getElementById('file-input');
305
+ const uploadBox = document.getElementById('upload-box');
306
+ const preview = document.getElementById('preview');
307
+ const result = document.getElementById('result');
308
+ const scanner = document.getElementById('scanner');
309
+ const analyzeBtn = document.getElementById('analyze-btn');
310
+ const btnText = document.getElementById('btn-text');
311
+
312
+ ['dragenter','dragover'].forEach(e => uploadBox.addEventListener(e, ev => { ev.preventDefault(); uploadBox.classList.add('dragover'); }));
313
+ ['dragleave','drop'].forEach(e => uploadBox.addEventListener(e, ev => { ev.preventDefault(); uploadBox.classList.remove('dragover'); }));
314
+ uploadBox.addEventListener('drop', e => e.dataTransfer.files[0] && (input.files = e.dataTransfer.files) && handleFile(e.dataTransfer.files[0]));
315
+ input.addEventListener('change', e => e.target.files[0] && handleFile(e.target.files[0]));
316
+
317
+ function handleFile(file) {
318
+ const reader = new FileReader();
319
+ reader.onload = e => preview.innerHTML = `<img src="${e.target.result}" alt="Sample">`;
320
+ reader.readAsDataURL(file);
321
+ }
322
+
323
+ analyzeBtn.addEventListener('click', () => {
324
+ if (!input.files.length) return alert("Please upload an image first");
325
+
326
+ const formData = new FormData();
327
+ formData.append('image', input.files[0]);
328
+
329
+ btnText.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Analyzing...`;
330
+ analyzeBtn.disabled = true;
331
+ scanner.style.display = 'block';
332
+ result.innerHTML = "AI is analyzing tissue...";
333
+ result.className = "result-box";
334
+
335
+ // Send image to backend for real model prediction
336
+ // Use relative URL to work with any deployment (localhost, Hugging Face, etc)
337
+ fetch('/api/predict', {
338
+ method: 'POST',
339
+ body: formData,
340
+ cache: 'no-store', // Prevent caching
341
+ headers: {
342
+ 'Pragma': 'no-cache',
343
+ 'Expires': '0'
344
+ }
345
+ })
346
+ .then(response => response.json())
347
+ .then(data => {
348
+ scanner.style.display = 'none';
349
+
350
+ // Clear previous result classes
351
+ result.className = "result-box";
352
+
353
+ if (data.error) {
354
+ result.innerHTML = `Error: ${data.error}`;
355
+ result.className = "result-box";
356
+ } else {
357
+ const diagnosis = data.diagnosis;
358
+ const confidence = data.confidence_percentage;
359
+ result.innerHTML = `${diagnosis}<br><span style="font-size: 0.9rem; opacity: 0.85;">${confidence}% Confidence</span>`;
360
+
361
+ // Apply correct color based on prediction
362
+ if (data.is_cancer) {
363
+ result.classList.add('cancer');
364
+ } else {
365
+ result.classList.add('normal');
366
+ }
367
+ }
368
+
369
+ btnText.textContent = "Analyze with AI";
370
+ analyzeBtn.disabled = false;
371
+ })
372
+ .catch(error => {
373
+ scanner.style.display = 'none';
374
+ result.innerHTML = `Error: ${error.message}`;
375
+ result.className = "result-box";
376
+ btnText.textContent = "Analyze with AI";
377
+ analyzeBtn.disabled = false;
378
+ console.error('Prediction error:', error);
379
+ });
380
+ });
381
+ </script>
382
+ </body>
383
  </html>