cyberai-1 commited on
Commit
fe2e5eb
·
1 Parent(s): 2390a6e
Files changed (2) hide show
  1. templates/index.html +425 -677
  2. templates/index_old.html +857 -648
templates/index.html CHANGED
@@ -7,507 +7,406 @@
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;500;600;700&family=Exo+2:wght@300;400;600&display=swap" rel="stylesheet">
9
  <style>
10
- /* ══════════════════════════════════════════════════════════════════
11
- TOKENS
12
- ══════════════════════════════════════════════════════════════════ */
13
  :root {
14
- --g0: #000000;
15
- --g1: #050d05;
16
- --g2: #0a150a;
17
- --g3: #0f1f0f;
18
- --g4: #1a2e1a;
19
- --green: #00ff41;
20
- --green2: #00cc33;
21
- --green3: #008f1f;
22
- --green-dim:#004d11;
23
- --green-glow: rgba(0,255,65,0.15);
24
- --border: rgba(0,255,65,0.18);
25
- --border2: rgba(0,255,65,0.35);
26
- --muted: rgba(0,255,65,0.45);
27
- --ff-mono: 'Share Tech Mono', monospace;
28
- --ff-head: 'Rajdhani', sans-serif;
29
- --ff-body: 'Exo 2', sans-serif;
30
- --r: 4px;
31
-
32
- /*new add*/
33
- --bg-main: #160a2f;
34
- --bg-deep: #0e061f;
35
- --pink: #ff4fa3;
36
- --pink-soft: rgba(255, 79, 163, 0.18);
37
- --pink-line: rgba(255, 79, 163, 0.28);
38
- --white-soft: rgba(255,255,255,0.06);
39
- }
40
-
41
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
42
- html { font-size: 16px; scroll-behavior: smooth; }
43
 
44
  body {
45
  background: var(--g0);
46
  color: var(--green);
47
  font-family: var(--ff-body);
48
- min-height: 100vh;
49
- overflow-x: hidden;
50
- cursor: default;
51
  }
52
 
53
- /* ── Plexus canvas background ── */
54
  #plexus-bg {
55
- position: fixed;
56
- inset: 0;
57
- width: 100%;
58
- height: 100%;
59
- z-index: 0;
60
- pointer-events: none;
61
  }
62
-
63
- /* ── Grid overlay ── */
64
  body::before {
65
- content: '';
66
- position: fixed; inset: 0; z-index: 0; pointer-events: none;
67
  background-image:
68
  linear-gradient(rgba(0,255,65,0.03) 1px, transparent 1px),
69
  linear-gradient(90deg, rgba(0,255,65,0.03) 1px, transparent 1px);
70
  background-size: 40px 40px;
71
  }
72
- /* Scanline overlay */
73
  body::after {
74
- content: '';
75
- position: fixed; inset: 0; z-index: 0; pointer-events: none;
76
  background: repeating-linear-gradient(
77
- 0deg,
78
- transparent,
79
- transparent 2px,
80
- rgba(0,0,0,0.07) 2px,
81
- rgba(0,0,0,0.07) 4px
82
  );
83
  }
84
 
85
- /* ══════════════════════════════════════════════════════════════════
86
- LAYOUT
87
- ══════════════════════════════════════════════════════════════════ */
88
  .wrapper {
89
  position: relative; z-index: 1;
90
- max-width: 1140px;
91
  margin: 0 auto;
92
- padding: 0 2rem;
 
 
 
 
93
  }
94
 
95
- /* ── HEADER ── */
96
  header {
 
97
  border-bottom: 1px solid var(--border);
98
- padding: 1.4rem 0;
99
- display: flex;
100
- align-items: center;
101
- justify-content: space-between;
102
- animation: fadeIn .6s ease both;
103
- }
104
- .logo {
105
- display: flex; align-items: center; gap: 1rem;
106
  }
 
107
  .logo-icon {
108
- width: 38px; height: 38px;
109
- border: 1.5px solid var(--green);
110
- display: grid; place-items: center;
111
- font-size: 1.1rem;
112
  box-shadow: 0 0 14px var(--green-glow), inset 0 0 8px var(--green-glow);
113
  animation: pulse-box 3s ease-in-out infinite;
114
  }
115
  @keyframes pulse-box {
116
- 0%,100% { box-shadow: 0 0 10px var(--green-glow), inset 0 0 6px var(--green-glow); }
117
- 50% { box-shadow: 0 0 22px rgba(0,255,65,.3), inset 0 0 14px rgba(0,255,65,.2); }
118
  }
119
  .logo-text {
120
- font-family: var(--ff-head);
121
- font-size: 1.8rem; font-weight: 700;
122
- letter-spacing: .15em;
123
- text-shadow: 0 0 20px rgba(0,255,65,.6);
124
  }
125
- .logo-text span { color: var(--green3); }
126
- .header-right {
127
- display: flex; align-items: center; gap: 1.5rem;
 
 
 
 
128
  }
 
 
129
  .status-dot {
130
- display: flex; align-items: center; gap: .45rem;
131
- font-family: var(--ff-mono); font-size: .65rem; color: var(--muted);
132
  }
133
  .dot {
134
- width: 6px; height: 6px; border-radius: 50%;
135
- background: var(--green);
136
- box-shadow: 0 0 8px var(--green);
137
- animation: blink 2s step-end infinite;
138
- }
139
- @keyframes blink { 0%,100%{opacity:1} 50%{opacity:.2} }
140
- .version {
141
- font-family: var(--ff-mono); font-size: .6rem;
142
- color: var(--green-dim); letter-spacing: .1em;
143
- }
144
-
145
- /* ── HERO ── */
146
- .hero {
147
- padding: 3rem 0 2rem;
148
- animation: fadeIn .7s ease .1s both;
149
- }
150
- .hero-label {
151
- font-family: var(--ff-mono); font-size: .62rem;
152
- color: var(--green3); letter-spacing: .2em;
153
- text-transform: uppercase; margin-bottom: .6rem;
154
- }
155
- .hero-title {
156
- font-family: var(--ff-head);
157
- font-size: clamp(2.2rem, 5vw, 3.4rem);
158
- font-weight: 700; letter-spacing: -.01em;
159
- line-height: 1.05;
160
- text-shadow: 0 0 40px rgba(0,255,65,.3);
161
- }
162
- .hero-title em {
163
- font-style: normal; color: var(--green2);
164
- text-shadow: 0 0 30px rgba(0,204,51,.6);
165
  }
166
- .hero-sub {
167
- margin-top: .8rem;
168
- font-size: .9rem; color: var(--muted);
169
- font-family: var(--ff-mono); letter-spacing: .04em;
 
 
 
170
  }
 
171
 
172
- /* ── MAIN GRID ── */
173
  .main-grid {
 
 
174
  display: grid;
175
  grid-template-columns: 1fr 1fr;
176
- gap: 1.5rem;
177
- padding-bottom: 4rem;
178
- animation: fadeIn .8s ease .2s both;
179
  }
180
 
181
- /* ── PANEL BASE ── */
182
  .panel {
183
  background: var(--g2);
184
  border: 1px solid var(--border);
185
  border-radius: var(--r);
186
- padding: 1.75rem;
187
  position: relative;
188
- overflow: hidden;
 
 
189
  }
190
  .panel::before {
191
- content: '';
192
- position: absolute; top: 0; left: 0; right: 0;
193
- height: 2px;
194
- background: linear-gradient(90deg, transparent, var(--green3), transparent);
195
  }
196
  .panel-title {
197
- font-family: var(--ff-mono); font-size: .6rem;
198
- letter-spacing: .2em; color: var(--green3);
199
- text-transform: uppercase; margin-bottom: 1.4rem;
200
- display: flex; align-items: center; gap: .6rem;
201
- }
202
- .panel-title::after {
203
- content: ''; flex: 1; height: 1px; background: var(--border);
 
 
 
 
 
 
 
 
204
  }
 
205
 
206
  /* ── MODEL SELECTOR ── */
207
  .model-grid {
208
- display: grid; grid-template-columns: 1fr 1fr; gap: .75rem;
209
- margin-bottom: 1.5rem;
210
  }
211
  .model-card {
212
- border: 1px solid var(--border);
213
- border-radius: var(--r);
214
- padding: 1rem .9rem;
215
- cursor: pointer;
216
- transition: all .2s;
217
- background: var(--g1);
218
- display: flex; flex-direction: column; gap: .3rem;
219
  }
220
- .model-card:hover { border-color: var(--green3); background: var(--g3); }
221
  .model-card.active {
222
- border-color: var(--green);
223
- background: rgba(0,255,65,.06);
224
- box-shadow: 0 0 16px rgba(0,255,65,.12), inset 0 0 12px rgba(0,255,65,.04);
225
  }
226
- .model-card.active .mc-name { color: var(--green); }
227
- .mc-icon { font-size: 1.2rem; }
228
- .mc-name {
229
- font-family: var(--ff-head); font-weight: 600; font-size: 1rem;
230
- color: var(--green2); transition: color .2s;
231
- }
232
- .mc-sub { font-family: var(--ff-mono); font-size: .58rem; color: var(--muted); }
233
 
234
  /* ── INPUT TABS ── */
235
  .input-tabs {
236
- display: flex; gap: 0; margin-bottom: 1rem;
237
- border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
238
  }
239
  .tab-btn {
240
- flex: 1; padding: .55rem; background: var(--g1);
241
- border: none; color: var(--muted); font-family: var(--ff-mono);
242
- font-size: .65rem; letter-spacing: .1em; cursor: pointer;
243
- transition: all .2s; text-transform: uppercase;
244
  }
245
- .tab-btn:hover { background: var(--g3); color: var(--green2); }
246
  .tab-btn.active {
247
- background: rgba(0,255,65,.08); color: var(--green);
248
- box-shadow: inset 0 -2px 0 var(--green);
249
  }
250
 
251
- /* ── DROP ZONE ── */
252
  .drop-zone {
253
- border: 1.5px dashed var(--border2);
254
- border-radius: var(--r);
255
- min-height: 190px;
256
- display: flex; flex-direction: column;
257
- align-items: center; justify-content: center;
258
- gap: .75rem; cursor: pointer;
259
- transition: all .25s; background: var(--g1);
260
- position: relative; overflow: hidden;
261
- }
262
- .drop-zone:hover, .drop-zone.drag {
263
- border-color: var(--green);
264
- background: rgba(0,255,65,.04);
265
- box-shadow: 0 0 20px rgba(0,255,65,.08);
266
- }
267
- .drop-zone.has-img .dz-ph { display: none; }
268
  #preview-img {
269
- position: absolute; inset: 0;
270
- width: 100%; height: 100%; object-fit: cover;
271
- display: none; border-radius: calc(var(--r) - 1px);
272
- opacity: .85;
273
- }
274
- .drop-zone.has-img #preview-img { display: block; }
275
- .dz-corner {
276
- position: absolute; width: 14px; height: 14px;
277
- border-color: var(--green3); border-style: solid;
278
- }
279
- .dz-corner.tl { top: 8px; left: 8px; border-width: 1.5px 0 0 1.5px; }
280
- .dz-corner.tr { top: 8px; right: 8px; border-width: 1.5px 1.5px 0 0; }
281
- .dz-corner.bl { bottom: 8px; left: 8px; border-width: 0 0 1.5px 1.5px; }
282
- .dz-corner.br { bottom: 8px; right: 8px; border-width: 0 1.5px 1.5px 0; }
283
- .dz-ph {
284
- display: flex; flex-direction: column; align-items: center; gap: .6rem;
285
- pointer-events: none;
286
- }
287
  .dz-icon {
288
- width: 48px; height: 48px; border: 1px solid var(--border2);
289
- border-radius: 50%; display: grid; place-items: center;
290
- font-size: 1.3rem; color: var(--green3);
291
  }
292
- .dz-ph p { font-family: var(--ff-mono); font-size: .72rem; color: var(--muted); }
293
- .dz-ph em { font-family: var(--ff-mono); font-size: .6rem; color: var(--green-dim); font-style: normal; }
294
- #file-input { display: none; }
295
 
296
  /* ── URL INPUT ── */
297
- .url-input-wrap { display: none; flex-direction: column; gap: .6rem; }
298
- .url-input-wrap.show { display: flex; }
299
  .url-field {
300
- display: flex; align-items: center;
301
- border: 1px solid var(--border2); border-radius: var(--r);
302
- background: var(--g1); overflow: hidden;
303
- }
304
- .url-prefix {
305
- font-family: var(--ff-mono); font-size: .65rem;
306
- color: var(--green3); padding: 0 .7rem; white-space: nowrap;
307
- border-right: 1px solid var(--border);
308
  }
 
309
  .url-input {
310
- flex: 1; background: transparent; border: none; outline: none;
311
- color: var(--green); font-family: var(--ff-mono); font-size: .75rem;
312
- padding: .75rem .8rem;
313
- caret-color: var(--green);
314
  }
315
- .url-input::placeholder { color: var(--green-dim); }
316
  .url-load-btn {
317
- padding: .65rem .9rem;
318
- background: rgba(0,255,65,.1); border: none;
319
- border-left: 1px solid var(--border);
320
- color: var(--green2); font-family: var(--ff-mono); font-size: .65rem;
321
- cursor: pointer; transition: background .2s; white-space: nowrap;
322
  }
323
- .url-load-btn:hover { background: rgba(0,255,65,.2); }
324
  .url-preview {
325
- width: 100%; height: 140px; object-fit: cover;
326
- border-radius: var(--r); border: 1px solid var(--border);
327
- display: none;
328
  }
329
- .url-preview.show { display: block; }
330
 
331
  /* ── CLASSIFY BTN ── */
332
  .classify-btn {
333
- width: 100%; margin-top: 1rem;
334
- padding: 1rem;
335
- background: linear-gradient(135deg, var(--green3), var(--green-dim));
336
- border: 1px solid var(--green3); border-radius: var(--r);
337
- color: var(--green); font-family: var(--ff-head);
338
- font-size: 1.1rem; font-weight: 700; letter-spacing: .1em;
339
- cursor: pointer; position: relative; overflow: hidden;
340
- transition: all .2s; text-transform: uppercase;
 
341
  }
342
  .classify-btn::before {
343
- content: '';
344
- position: absolute; top: -2px; left: -100%; width: 60%; height: calc(100% + 4px);
345
- background: linear-gradient(90deg, transparent, rgba(0,255,65,.18), transparent);
346
- transform: skewX(-15deg);
347
  }
348
- .classify-btn.loading::before {
349
- animation: sweep 1.2s linear infinite;
350
- }
351
- @keyframes sweep { to { left: 150%; } }
352
  .classify-btn:not(:disabled):hover {
353
- background: linear-gradient(135deg, var(--green2), var(--green3));
354
- box-shadow: 0 0 24px rgba(0,255,65,.3);
355
- transform: translateY(-1px);
356
  }
357
- .classify-btn:disabled { opacity: .35; cursor: default; }
358
 
359
- /* ══════════════════════════════════════════════════════════════════
360
- RIGHT PANEL — RESULTS
361
- ══════════════════════════════════════════════════════════════════ */
 
 
 
 
 
 
 
 
362
 
363
- /* Waiting state */
364
  .result-waiting {
365
- display: flex; flex-direction: column;
366
- align-items: flex-start; justify-content: center;
367
- min-height: 200px; gap: .6rem;
368
- padding: 1rem 0;
369
- }
370
- .result-waiting .rw-title {
371
- font-family: var(--ff-head); font-size: 1rem; font-weight: 600;
372
- color: var(--green3);
373
- }
374
- .result-waiting .rw-sub {
375
- font-family: var(--ff-mono); font-size: .7rem;
376
- color: var(--green-dim); line-height: 1.7;
377
  }
 
 
378
  .terminal-cursor {
379
- display: inline-block; width: 8px; height: 14px;
380
- background: var(--green); margin-left: 2px;
381
- animation: blink-c .8s step-end infinite;
382
- vertical-align: middle;
383
  }
384
- @keyframes blink-c { 0%,100%{opacity:1} 50%{opacity:0} }
385
 
386
- /* Result */
387
- .result-content { display: none; flex-direction: column; gap: 1.4rem; }
388
- .result-content.show { display: flex; animation: scanIn .4s ease; }
389
  @keyframes scanIn {
390
- from { opacity: 0; clip-path: inset(0 0 100% 0); }
391
- to { opacity: 1; clip-path: inset(0 0 0% 0); }
392
  }
393
 
394
- .rc-header { display: flex; flex-direction: column; gap: .25rem; }
395
- .rc-label {
396
- font-family: var(--ff-mono); font-size: .6rem;
397
- letter-spacing: .2em; color: var(--green3);
 
 
398
  }
399
- .rc-class {
400
- font-family: var(--ff-head);
401
- font-size: 3rem; font-weight: 700;
402
- letter-spacing: -.01em; line-height: 1;
403
- color: var(--green);
404
- text-shadow: 0 0 30px rgba(0,255,65,.5);
405
- }
406
- .rc-conf {
407
- font-family: var(--ff-mono); font-size: .78rem;
408
- color: var(--muted); margin-top: .1rem;
409
- }
410
- .rc-conf strong { color: var(--green2); font-size: .9rem; }
411
 
412
- /* Main confidence bar */
413
  .conf-track {
414
- height: 3px; background: var(--g4);
415
- border-radius: 99px; overflow: hidden; margin-top: .5rem;
416
  }
417
  .conf-fill {
418
- height: 100%;
419
- background: linear-gradient(90deg, var(--green3), var(--green));
420
- border-radius: 99px; width: 0%;
421
- transition: width 1s cubic-bezier(.4,0,.2,1);
422
- box-shadow: 0 0 8px var(--green);
423
  }
424
 
425
- /* Divider */
426
- .rc-divider {
427
- height: 1px; background: var(--border);
428
- }
429
 
430
- /* Probability bars */
431
- .prob-list { display: flex; flex-direction: column; gap: .85rem; }
432
- .prob-row { display: flex; flex-direction: column; gap: .22rem; }
433
- .prob-meta { display: flex; justify-content: space-between; align-items: baseline; }
434
  .prob-name {
435
- font-family: var(--ff-body); font-size: .8rem;
436
- font-weight: 400; color: var(--muted);
437
- display: flex; align-items: center; gap: .4rem; text-transform: capitalize;
438
- }
439
- .prob-row.top .prob-name { color: var(--green); font-weight: 600; }
440
- .prob-pct { font-family: var(--ff-mono); font-size: .68rem; color: var(--green-dim); }
441
- .prob-row.top .prob-pct { color: var(--green2); }
442
- .prob-track {
443
- height: 4px; background: var(--g4);
444
- border-radius: 99px; overflow: hidden;
445
- }
446
- .prob-fill {
447
- height: 100%; background: var(--green-dim);
448
- border-radius: 99px; width: 0%;
449
- transition: width .8s cubic-bezier(.4,0,.2,1);
450
  }
451
  .prob-row.top .prob-fill {
452
- background: linear-gradient(90deg, var(--green3), var(--green));
453
- box-shadow: 0 0 6px rgba(0,255,65,.4);
454
  }
455
 
456
- /* Error */
457
  .result-error {
458
- display: none; padding: 1rem;
459
- border: 1px solid rgba(255,50,50,.3);
460
- border-radius: var(--r); background: rgba(255,0,0,.05);
461
- font-family: var(--ff-mono); font-size: .75rem; color: #ff4444;
462
- line-height: 1.6;
463
  }
464
- .result-error.show { display: block; animation: fadeIn .3s ease; }
465
 
466
- /* ── CLASSES STRIP ── */
467
- .classes-strip {
 
468
  border-top: 1px solid var(--border);
469
- padding: 1rem 0;
470
- display: flex; align-items: center; gap: 1.2rem;
471
- flex-wrap: wrap;
472
- animation: fadeIn .9s ease .3s both;
473
- }
474
- .cs-label { font-family: var(--ff-mono); font-size: .55rem; color: var(--green-dim); letter-spacing: .15em; }
475
- .cs-pills { display: flex; gap: .5rem; flex-wrap: wrap; }
476
- .cs-pill {
477
- font-family: var(--ff-mono); font-size: .6rem;
478
- padding: .25rem .65rem;
479
- border: 1px solid var(--border); border-radius: 2px;
480
- color: var(--green-dim);
481
- transition: all .2s; cursor: default;
482
- }
483
- .cs-pill:hover { border-color: var(--green3); color: var(--green2); }
484
-
485
- /* ── FOOTER ── */
486
- footer {
487
- border-top: 1px solid var(--border);
488
- padding: 1rem 0;
489
  display: flex; justify-content: space-between; align-items: center;
490
- animation: fadeIn 1s ease .4s both;
491
  }
492
- footer p { font-family: var(--ff-mono); font-size: .6rem; color: var(--green-dim); }
493
-
494
- /* ── ANIMATIONS ── */
495
- @keyframes fadeIn { from{opacity:0; transform:translateY(8px)} to{opacity:1;transform:none} }
496
 
497
- /* ── RESPONSIVE ── */
498
- @media (max-width: 740px) {
499
- .main-grid { grid-template-columns: 1fr; }
500
- .hero-title { font-size: 2rem; }
501
- .model-grid { grid-template-columns: 1fr 1fr; }
502
- }
503
  </style>
504
  </head>
505
  <body>
506
 
507
-
508
- <!-- Animated plexus background -->
509
  <canvas id="plexus-bg"></canvas>
510
 
 
 
511
  <!-- HEADER -->
512
  <header>
513
  <div class="logo">
@@ -520,11 +419,17 @@
520
  </div>
521
  </header>
522
 
523
- <!-- HERO -->
524
- <div class="hero">
525
- <div class="hero-label">// Intel Image Classification · CNN</div>
526
- <h1 class="hero-title">Scene Recognition<br><em>Powered by Neural Networks</em></h1>
527
- <p class="hero-sub">Upload an image or provide a URL — select your model — get instant predictions across 6 scene categories.</p>
 
 
 
 
 
 
528
  </div>
529
 
530
  <!-- MAIN GRID -->
@@ -534,56 +439,58 @@
534
  <div class="panel">
535
  <div class="panel-title">01 // MODEL_SELECT</div>
536
 
537
- <div class="model-grid">
538
- <div class="model-card active" data-fw="pytorch" onclick="selectModel(this)">
539
- <span class="mc-icon">⚡</span>
540
- <span class="mc-name">PyTorch</span>
541
- <span class="mc-sub">CNN_Torch · .pth</span>
 
 
 
 
 
 
 
 
542
  </div>
543
- <div class="model-card" data-fw="tensorflow" onclick="selectModel(this)">
544
- <span class="mc-icon">🧠</span>
545
- <span class="mc-name">TensorFlow</span>
546
- <span class="mc-sub">CNN_TF · .keras</span>
 
 
547
  </div>
548
- </div>
549
-
550
- <div class="panel-title">02 // INPUT_IMAGE</div>
551
-
552
- <!-- Tabs: File / URL -->
553
- <div class="input-tabs">
554
- <button class="tab-btn active" onclick="switchTab('file', this)">↑ FILE_UPLOAD</button>
555
- <button class="tab-btn" onclick="switchTab('url', this)">⬡ IMAGE_URL</button>
556
- </div>
557
-
558
- <!-- File drop zone -->
559
- <div id="tab-file">
560
- <div class="drop-zone" id="drop-zone" onclick="document.getElementById('file-input').click()">
561
- <div class="dz-corner tl"></div>
562
- <div class="dz-corner tr"></div>
563
- <div class="dz-corner bl"></div>
564
- <div class="dz-corner br"></div>
565
- <img id="preview-img" src="" alt="preview"/>
566
- <div class="dz-ph">
567
- <div class="dz-icon">↑</div>
568
- <p>Drop image here or click</p>
569
- <em>JPG · PNG · WEBP · GIF</em>
570
  </div>
 
571
  </div>
572
- <input type="file" id="file-input" accept="image/*"/>
573
- </div>
574
-
575
- <!-- URL input -->
576
- <div id="tab-url" class="url-input-wrap">
577
- <div class="url-field">
578
- <span class="url-prefix">URL://</span>
579
- <input type="text" class="url-input" id="url-input"
580
- placeholder="https://example.com/image.jpg"/>
581
- <button class="url-load-btn" onclick="loadFromUrl()">LOAD</button>
582
  </div>
583
- <img id="url-preview" class="url-preview" src="" alt="URL preview"/>
584
- </div>
585
 
586
- <div class="panel-title" style="margin-top:1.2rem;">03 // ANALYZE</div>
 
 
 
587
  <button class="classify-btn" id="classify-btn" disabled onclick="classify()">
588
  RUN CLASSIFICATION →
589
  </button>
@@ -593,92 +500,75 @@
593
  <div class="panel">
594
  <div class="panel-title">04 // SCAN_RESULTS</div>
595
 
596
- <!-- Waiting -->
597
- <div class="result-waiting" id="result-waiting">
598
- <div class="rw-title">AWAITING INPUT</div>
599
- <div class="rw-sub">
600
- &gt; select_model()<br>
601
- &gt; load_image() <span class="terminal-cursor"></span><br>
602
- &gt; predict()
603
- </div>
604
- </div>
605
-
606
- <!-- Results -->
607
- <div class="result-content" id="result-content">
608
- <div class="rc-header">
609
- <div class="rc-label">// PREDICTED_CLASS</div>
610
- <div class="rc-class" id="rc-class">—</div>
611
- <div class="rc-conf">confidence : <strong id="rc-conf">—</strong></div>
612
- <div class="conf-track"><div class="conf-fill" id="conf-fill"></div></div>
613
- </div>
614
- <div class="rc-divider"></div>
615
- <div>
616
- <div class="panel-title" style="margin-bottom:.9rem;">// CLASS_SCORES</div>
617
- <div class="prob-list" id="prob-list"></div>
618
  </div>
619
- </div>
620
 
621
- <!-- Error -->
622
- <div class="result-error" id="result-error"></div>
623
- </div>
 
 
 
 
 
 
 
 
 
 
624
 
625
- </div>
626
 
627
- <!-- CLASSES STRIP -->
628
- <div class="classes-strip">
629
- <span class="cs-label">CLASSES</span>
630
- <div class="cs-pills">
631
- <span class="cs-pill">🏙 buildings</span>
632
- <span class="cs-pill">🌲 forest</span>
633
- <span class="cs-pill">🧊 glacier</span>
634
- <span class="cs-pill">⛰ mountain</span>
635
- <span class="cs-pill">🌊 sea</span>
636
- <span class="cs-pill">🛣 street</span>
637
  </div>
638
- </div>
639
 
640
- <footer>
 
 
 
641
  <p>SCENEIQ · Intel Image Classification · CNN PyTorch &amp; TensorFlow</p>
642
  <p>by PARFAIT · seed=42 · reproducible</p>
643
- </footer>
644
 
645
  </div><!-- /wrapper -->
646
 
647
  <script>
648
- const EMOJIS = {buildings:"🏙",forest:"🌲",glacier:"🧊",mountain:"⛰",sea:"🌊",street:"🛣"};
649
  const CLASSES = ["buildings","forest","glacier","mountain","sea","street"];
650
 
651
- let selectedFw = "pytorch";
652
- let selectedFile = null;
653
  let urlImageReady = false;
654
- let currentTab = "file";
655
 
656
- /* ── Model selector ── */
657
  function selectModel(card) {
658
  document.querySelectorAll(".model-card").forEach(c => c.classList.remove("active"));
659
  card.classList.add("active");
660
  selectedFw = card.dataset.fw;
661
  }
662
 
663
- /* ── Tab switcher ── */
664
  function switchTab(tab, btn) {
665
  currentTab = tab;
666
  document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
667
  btn.classList.add("active");
668
  document.getElementById("tab-file").style.display = tab === "file" ? "block" : "none";
669
- const urlWrap = document.getElementById("tab-url");
670
- urlWrap.classList.toggle("show", tab === "url");
671
  updateBtn();
672
  }
673
 
674
- /* ── File drag & drop ── */
675
- const dropZone = document.getElementById("drop-zone");
676
  const fileInput = document.getElementById("file-input");
677
 
678
- fileInput.addEventListener("change", () => {
679
- if (fileInput.files[0]) loadFile(fileInput.files[0]);
680
- });
681
- dropZone.addEventListener("dragover", e => { e.preventDefault(); dropZone.classList.add("drag"); });
682
  dropZone.addEventListener("dragleave", () => dropZone.classList.remove("drag"));
683
  dropZone.addEventListener("drop", e => {
684
  e.preventDefault(); dropZone.classList.remove("drag");
@@ -690,61 +580,37 @@
690
  selectedFile = file;
691
  const reader = new FileReader();
692
  reader.onload = e => {
693
- const img = document.getElementById("preview-img");
694
- img.src = e.target.result;
695
  dropZone.classList.add("has-img");
696
  };
697
  reader.readAsDataURL(file);
698
- resetResult();
699
- updateBtn();
700
  }
701
 
702
- /* ── URL load ── */
703
  function loadFromUrl() {
704
- const url = document.getElementById("url-input").value.trim();
705
  if (!url) return;
706
  const preview = document.getElementById("url-preview");
707
- preview.onload = () => {
708
- preview.classList.add("show");
709
- urlImageReady = true;
710
- resetResult();
711
- updateBtn();
712
- };
713
- preview.onerror = () => {
714
- preview.classList.remove("show");
715
- urlImageReady = false;
716
- updateBtn();
717
- };
718
  preview.src = url;
719
  }
720
 
721
- // Allow Enter key on URL input
722
- document.getElementById("url-input").addEventListener("keydown", e => {
723
- if (e.key === "Enter") loadFromUrl();
724
- });
725
 
726
- /* ── Update classify button ── */
727
  function updateBtn() {
728
- const ready = (currentTab === "file" && selectedFile) ||
729
- (currentTab === "url" && urlImageReady);
730
  document.getElementById("classify-btn").disabled = !ready;
731
  }
732
 
733
- /* ── Classify ── */
734
  async function classify() {
735
  const btn = document.getElementById("classify-btn");
736
- btn.disabled = true;
737
- btn.textContent = "SCANNING…";
738
- btn.classList.add("loading");
739
 
740
  const form = new FormData();
741
  form.append("model", selectedFw);
742
-
743
- if (currentTab === "file" && selectedFile) {
744
- form.append("image", selectedFile);
745
- } else {
746
- form.append("image_url", document.getElementById("url-input").value.trim());
747
- }
748
 
749
  try {
750
  const res = await fetch("/predict", { method: "POST", body: form });
@@ -754,36 +620,26 @@
754
  } catch(err) {
755
  showError(err.message);
756
  } finally {
757
- btn.textContent = "RUN CLASSIFICATION →";
758
- btn.classList.remove("loading");
759
- btn.disabled = false;
760
  }
761
  }
762
 
763
- /* ── Show result ── */
764
  function showResult(data) {
765
- document.getElementById("result-waiting").style.display = "none";
766
  document.getElementById("result-error").classList.remove("show");
767
 
768
  const pct = Math.round(data.confidence * 100);
769
  document.getElementById("rc-class").textContent =
770
- (EMOJIS[data.class] || "") + " " + data.class.charAt(0).toUpperCase() + data.class.slice(1);
771
  document.getElementById("rc-conf").textContent = pct + "%";
772
 
773
  const content = document.getElementById("result-content");
774
- content.classList.remove("show");
775
- void content.offsetWidth; // force reflow for animation
776
- content.classList.add("show");
777
-
778
- setTimeout(() => {
779
- document.getElementById("conf-fill").style.width = pct + "%";
780
- }, 60);
781
 
782
- // Probability bars
783
- const list = document.getElementById("prob-list");
784
  list.innerHTML = "";
785
- const sorted = [...CLASSES].sort((a,b) => data.probabilities[b] - data.probabilities[a]);
786
-
787
  sorted.forEach((cls, i) => {
788
  const p = Math.round(data.probabilities[cls] * 100);
789
  const top = cls === data.class;
@@ -791,194 +647,86 @@
791
  row.className = "prob-row" + (top ? " top" : "");
792
  row.innerHTML = `
793
  <div class="prob-meta">
794
- <span class="prob-name">${EMOJIS[cls] || ""} ${cls}</span>
795
  <span class="prob-pct" id="pct-${cls}">0%</span>
796
  </div>
797
- <div class="prob-track">
798
- <div class="prob-fill" id="bar-${cls}"></div>
799
- </div>`;
800
  list.appendChild(row);
801
  setTimeout(() => {
802
- document.getElementById("bar-" + cls).style.width = p + "%";
803
- document.getElementById("pct-" + cls).textContent = p + "%";
804
- }, 100 + i * 50);
805
  });
806
  }
807
 
808
  function showError(msg) {
809
- document.getElementById("result-waiting").style.display = "none";
810
  document.getElementById("result-content").classList.remove("show");
811
  const err = document.getElementById("result-error");
812
- err.textContent = "ERROR > " + msg;
813
- err.classList.add("show");
814
  }
815
 
816
  function resetResult() {
817
- document.getElementById("result-waiting").style.display = "flex";
818
  document.getElementById("result-content").classList.remove("show");
819
  document.getElementById("result-error").classList.remove("show");
820
  document.getElementById("conf-fill").style.width = "0%";
821
  }
822
 
823
- // Init
824
  document.getElementById("tab-file").style.display = "block";
825
  </script>
826
 
827
- <!-- ══ PLEXUS BACKGROUND ANIMATION ══ -->
828
  <script>
829
  (function(){
830
  const canvas = document.getElementById('plexus-bg');
831
  const ctx = canvas.getContext('2d');
832
-
833
- const NODE_COLOR = 'rgba(0,255,65,';
834
- const LINE_COLOR = 'rgba(0,255,65,';
835
-
836
- const MAX_DIST = 160;
837
- const MOUSE_DIST = 220;
838
- const ATTRACT_FORCE = 0.012;
839
- const NODE_COUNT = 110;
840
- const SPEED = 0.4;
841
-
842
- let W, H, nodes = [];
843
- let mouse = { x: -9999, y: -9999 };
844
-
845
- window.addEventListener('mousemove', e => {
846
- mouse.x = e.clientX;
847
- mouse.y = e.clientY;
848
- });
849
- window.addEventListener('mouseleave', () => {
850
- mouse.x = -9999;
851
- mouse.y = -9999;
852
- });
853
-
854
- function resize() {
855
- W = canvas.width = window.innerWidth;
856
- H = canvas.height = window.innerHeight;
857
- }
858
-
859
- function Node() {
860
- this.x = Math.random() * W;
861
- this.y = Math.random() * H;
862
- this.vx = (Math.random() - 0.5) * SPEED;
863
- this.vy = (Math.random() - 0.5) * SPEED;
864
- this.r = Math.random() * 2 + 1.5;
865
- }
866
-
867
- function init() {
868
- nodes = [];
869
- for (let i = 0; i < NODE_COUNT; i++) nodes.push(new Node());
870
- }
871
-
872
- function draw() {
873
- ctx.clearRect(0, 0, W, H);
874
-
875
- for (const n of nodes) {
876
- const dxM = mouse.x - n.x;
877
- const dyM = mouse.y - n.y;
878
- const dM = Math.sqrt(dxM * dxM + dyM * dyM);
879
-
880
- if (dM < MOUSE_DIST && dM > 0) {
881
- const force = (1 - dM / MOUSE_DIST) * ATTRACT_FORCE;
882
- n.vx += dxM / dM * force;
883
- n.vy += dyM / dM * force;
884
- }
885
-
886
- const spd = Math.sqrt(n.vx * n.vx + n.vy * n.vy);
887
- const maxSpd = SPEED * 3;
888
- if (spd > maxSpd) {
889
- n.vx = (n.vx / spd) * maxSpd;
890
- n.vy = (n.vy / spd) * maxSpd;
891
- }
892
-
893
- if (dM >= MOUSE_DIST) {
894
- n.vx *= 0.996;
895
- n.vy *= 0.996;
896
- const spdNow = Math.sqrt(n.vx * n.vx + n.vy * n.vy);
897
- if (spdNow < SPEED * 0.3) {
898
- n.vx += (Math.random() - 0.5) * 0.06;
899
- n.vy += (Math.random() - 0.5) * 0.06;
900
- }
901
- }
902
-
903
- n.x += n.vx;
904
- n.y += n.vy;
905
- if (n.x < 0 || n.x > W) n.vx *= -1;
906
- if (n.y < 0 || n.y > H) n.vy *= -1;
907
  }
908
-
909
- // Edges between nodes
910
- for (let i = 0; i < nodes.length; i++) {
911
- for (let j = i + 1; j < nodes.length; j++) {
912
- const a = nodes[i], b = nodes[j];
913
- const dx = a.x - b.x, dy = a.y - b.y;
914
- const d = Math.sqrt(dx * dx + dy * dy);
915
- if (d < MAX_DIST) {
916
- const alpha = (1 - d / MAX_DIST) * 0.28;
917
- ctx.beginPath();
918
- ctx.moveTo(a.x, a.y);
919
- ctx.lineTo(b.x, b.y);
920
- ctx.strokeStyle = LINE_COLOR + alpha + ')';
921
- ctx.lineWidth = 0.8;
922
- ctx.stroke();
923
- }
924
- }
925
  }
926
-
927
- // Lines from cursor to nearby nodes
928
- for (const n of nodes) {
929
- const dxM = mouse.x - n.x;
930
- const dyM = mouse.y - n.y;
931
- const dM = Math.sqrt(dxM * dxM + dyM * dyM);
932
- if (dM < MOUSE_DIST) {
933
- const alpha = (1 - dM / MOUSE_DIST) * 0.35;
934
- ctx.beginPath();
935
- ctx.moveTo(mouse.x, mouse.y);
936
- ctx.lineTo(n.x, n.y);
937
- ctx.strokeStyle = LINE_COLOR + alpha + ')';
938
- ctx.lineWidth = 0.5;
939
- ctx.stroke();
940
- }
941
  }
942
-
943
- // Cursor glow dot
944
- if (mouse.x > 0) {
945
- const grdC = ctx.createRadialGradient(mouse.x, mouse.y, 0, mouse.x, mouse.y, 20);
946
- grdC.addColorStop(0, 'rgba(0,255,65,0.5)');
947
- grdC.addColorStop(1, 'rgba(0,255,65,0)');
948
- ctx.beginPath();
949
- ctx.arc(mouse.x, mouse.y, 20, 0, Math.PI * 2);
950
- ctx.fillStyle = grdC;
951
- ctx.fill();
952
-
953
- ctx.beginPath();
954
- ctx.arc(mouse.x, mouse.y, 2, 0, Math.PI * 2);
955
- ctx.fillStyle = 'rgba(0,255,65,0.95)';
956
- ctx.fill();
957
  }
958
-
959
- // Nodes with glow
960
- for (const n of nodes) {
961
- const grd = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 5);
962
- grd.addColorStop(0, NODE_COLOR + '0.3)');
963
- grd.addColorStop(1, NODE_COLOR + '0)');
964
- ctx.beginPath();
965
- ctx.arc(n.x, n.y, n.r * 5, 0, Math.PI * 2);
966
- ctx.fillStyle = grd;
967
- ctx.fill();
968
-
969
- ctx.beginPath();
970
- ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
971
- ctx.fillStyle = NODE_COLOR + '0.85)';
972
- ctx.fill();
973
  }
974
-
975
  requestAnimationFrame(draw);
976
  }
977
-
978
- resize();
979
- init();
980
- draw();
981
- window.addEventListener('resize', () => { resize(); init(); });
982
  })();
983
  </script>
984
  </body>
 
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;500;600;700&family=Exo+2:wght@300;400;600&display=swap" rel="stylesheet">
9
  <style>
10
+ /* ══ TOKENS ══ */
 
 
11
  :root {
12
+ --g0:#000000; --g1:#050d05; --g2:#0a150a; --g3:#0f1f0f; --g4:#1a2e1a;
13
+ --green:#00ff41; --green2:#00cc33; --green3:#008f1f;
14
+ --green-dim:#004d11; --green-glow:rgba(0,255,65,0.15);
15
+ --border:rgba(0,255,65,0.18); --border2:rgba(0,255,65,0.35);
16
+ --muted:rgba(0,255,65,0.45);
17
+ --ff-mono:'Share Tech Mono',monospace;
18
+ --ff-head:'Rajdhani',sans-serif;
19
+ --ff-body:'Exo 2',sans-serif;
20
+ --r:4px;
21
+ }
22
+
23
+ *, *::before, *::after { box-sizing:border-box; margin:0; padding:0; }
24
+
25
+ /* ── FULL VIEWPORT — no scroll ── */
26
+ html, body {
27
+ height: 100vh;
28
+ overflow: hidden;
29
+ }
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  body {
32
  background: var(--g0);
33
  color: var(--green);
34
  font-family: var(--ff-body);
35
+ display: flex;
36
+ flex-direction: column;
 
37
  }
38
 
39
+ /* ── Background layers ── */
40
  #plexus-bg {
41
+ position: fixed; inset: 0; z-index: 0; pointer-events: none;
 
 
 
 
 
42
  }
 
 
43
  body::before {
44
+ content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
 
45
  background-image:
46
  linear-gradient(rgba(0,255,65,0.03) 1px, transparent 1px),
47
  linear-gradient(90deg, rgba(0,255,65,0.03) 1px, transparent 1px);
48
  background-size: 40px 40px;
49
  }
 
50
  body::after {
51
+ content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
 
52
  background: repeating-linear-gradient(
53
+ 0deg, transparent, transparent 2px,
54
+ rgba(0,0,0,0.07) 2px, rgba(0,0,0,0.07) 4px
 
 
 
55
  );
56
  }
57
 
58
+ /* ══ WRAPPER — flex column, full height ══ */
 
 
59
  .wrapper {
60
  position: relative; z-index: 1;
61
+ width: 100%; max-width: 1200px;
62
  margin: 0 auto;
63
+ padding: 0 1.5rem;
64
+ height: 100vh;
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 0;
68
  }
69
 
70
+ /* ══ HEADER compact, fixed height ══ */
71
  header {
72
+ flex-shrink: 0;
73
  border-bottom: 1px solid var(--border);
74
+ padding: .75rem 0;
75
+ display: flex; align-items: center; justify-content: space-between;
76
+ animation: fadeIn .5s ease both;
 
 
 
 
 
77
  }
78
+ .logo { display:flex; align-items:center; gap:.75rem; }
79
  .logo-icon {
80
+ width:32px; height:32px; border:1.5px solid var(--green);
81
+ display:grid; place-items:center; font-size:.95rem;
 
 
82
  box-shadow: 0 0 14px var(--green-glow), inset 0 0 8px var(--green-glow);
83
  animation: pulse-box 3s ease-in-out infinite;
84
  }
85
  @keyframes pulse-box {
86
+ 0%,100%{box-shadow:0 0 10px var(--green-glow),inset 0 0 6px var(--green-glow);}
87
+ 50%{box-shadow:0 0 22px rgba(0,255,65,.3),inset 0 0 14px rgba(0,255,65,.2);}
88
  }
89
  .logo-text {
90
+ font-family:var(--ff-head); font-size:1.5rem; font-weight:700;
91
+ letter-spacing:.15em; text-shadow:0 0 20px rgba(0,255,65,.6);
 
 
92
  }
93
+ .logo-text span { color:var(--green3); }
94
+
95
+ /* Hero tagline inline in header */
96
+ .hero-inline {
97
+ font-family:var(--ff-mono); font-size:0.5rem;
98
+ color:var(--muted); letter-spacing:.06em;
99
+ display:none;
100
  }
101
+
102
+ .header-right { display:flex; align-items:center; gap:1.2rem; }
103
  .status-dot {
104
+ display:flex; align-items:center; gap:.4rem;
105
+ font-family:var(--ff-mono); font-size:0.5rem; color:var(--muted);
106
  }
107
  .dot {
108
+ width:6px; height:6px; border-radius:50%;
109
+ background:var(--green); box-shadow:0 0 8px var(--green);
110
+ animation:blink 2s step-end infinite;
111
+ }
112
+ @keyframes blink{0%,100%{opacity:1}50%{opacity:.2}}
113
+ .version { font-family:var(--ff-mono); font-size:.58rem; color:var(--green-dim); letter-spacing:.1em; }
114
+
115
+ /* ── Classes strip row — sits between header and grid ── */
116
+ .classes-bar {
117
+ flex-shrink: 0;
118
+ display: flex; align-items: center; gap: .8rem;
119
+ padding: .4rem 0;
120
+ border-bottom: 1px solid var(--border);
121
+ animation: fadeIn .6s ease .1s both;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  }
123
+ .cs-label { font-family:var(--ff-mono); font-size:.52rem; color:var(--green-dim); letter-spacing:.15em; white-space:nowrap; }
124
+ .cs-pills { display:flex; gap:.35rem; flex-wrap:wrap; }
125
+ .cs-pill {
126
+ font-family:var(--ff-mono); font-size:.55rem;
127
+ padding:.15rem .5rem;
128
+ border:1px solid var(--border); border-radius:2px;
129
+ color:var(--green-dim); transition:all .2s; cursor:default;
130
  }
131
+ .cs-pill:hover { border-color:var(--green3); color:var(--green2); }
132
 
133
+ /* ══ MAIN GRID takes all remaining space ══ */
134
  .main-grid {
135
+ flex: 1;
136
+ min-height: 0; /* critical: allows flex child to shrink */
137
  display: grid;
138
  grid-template-columns: 1fr 1fr;
139
+ gap: 1rem;
140
+ padding: .75rem 0;
141
+ animation: fadeIn .7s ease .15s both;
142
  }
143
 
144
+ /* ══ PANELS fill their grid cell ══ */
145
  .panel {
146
  background: var(--g2);
147
  border: 1px solid var(--border);
148
  border-radius: var(--r);
149
+ padding: 1.1rem 1.25rem;
150
  position: relative;
151
+ overflow: hidden; /* no overflow on panel itself */
152
+ display: flex;
153
+ flex-direction: column;
154
  }
155
  .panel::before {
156
+ content:''; position:absolute; top:0; left:0; right:0; height:2px;
157
+ background:linear-gradient(90deg,transparent,var(--green3),transparent);
 
 
158
  }
159
  .panel-title {
160
+ flex-shrink: 0;
161
+ font-family:var(--ff-mono); font-size:.58rem;
162
+ letter-spacing:.2em; color:var(--green3);
163
+ text-transform:uppercase; margin-bottom:.75rem;
164
+ display:flex; align-items:center; gap:.6rem;
165
+ }
166
+ .panel-title::after { content:''; flex:1; height:1px; background:var(--border); }
167
+
168
+ /* ── Left panel: scrollable interior ── */
169
+ .panel-scroll {
170
+ flex: 1;
171
+ min-height: 0;
172
+ overflow-y: auto;
173
+ overflow-x: hidden;
174
+ scrollbar-width: none; /* Firefox */
175
  }
176
+ .panel-scroll::-webkit-scrollbar { display:none; }
177
 
178
  /* ── MODEL SELECTOR ── */
179
  .model-grid {
180
+ display:grid; grid-template-columns:1fr 1fr; gap:.6rem;
181
+ margin-bottom:.9rem;
182
  }
183
  .model-card {
184
+ border:1px solid var(--border); border-radius:var(--r);
185
+ padding:.6rem .7rem; cursor:pointer; transition:all .2s;
186
+ background:var(--g1);
187
+ display:flex; flex-direction:column; gap:.2rem;
 
 
 
188
  }
189
+ .model-card:hover { border-color:var(--green3); background:var(--g3); }
190
  .model-card.active {
191
+ border-color:var(--green); background:rgba(0,255,65,.06);
192
+ box-shadow:0 0 16px rgba(0,255,65,.12),inset 0 0 12px rgba(0,255,65,.04);
 
193
  }
194
+ .model-card.active .mc-name { color:var(--green); }
195
+ .mc-icon { font-size:1rem; }
196
+ .mc-name { font-family:var(--ff-head); font-weight:600; font-size:.9rem; color:var(--green2); transition:color .2s; }
197
+ .mc-sub { font-family:var(--ff-mono); font-size:.54rem; color:var(--muted); }
 
 
 
198
 
199
  /* ── INPUT TABS ── */
200
  .input-tabs {
201
+ display:flex; margin-bottom:.6rem;
202
+ border:1px solid var(--border); border-radius:var(--r); overflow:hidden;
203
  }
204
  .tab-btn {
205
+ flex:1; padding:.45rem; background:var(--g1);
206
+ border:none; color:var(--muted); font-family:var(--ff-mono);
207
+ font-size:0.5rem; letter-spacing:.1em; cursor:pointer;
208
+ transition:all .2s; text-transform:uppercase;
209
  }
210
+ .tab-btn:hover { background:var(--g3); color:var(--green2); }
211
  .tab-btn.active {
212
+ background:rgba(0,255,65,.08); color:var(--green);
213
+ box-shadow:inset 0 -2px 0 var(--green);
214
  }
215
 
216
+ /* ── DROP ZONE — reduced height ── */
217
  .drop-zone {
218
+ border:1.5px dashed var(--border2); border-radius:var(--r);
219
+ height: 130px; /* fixed, no min-height */
220
+ display:flex; flex-direction:column;
221
+ align-items:center; justify-content:center;
222
+ gap:.5rem; cursor:pointer;
223
+ transition:all .25s; background:var(--g1);
224
+ position:relative; overflow:hidden;
225
+ }
226
+ .drop-zone:hover,.drop-zone.drag {
227
+ border-color:var(--green); background:rgba(0,255,65,.04);
228
+ box-shadow:0 0 20px rgba(0,255,65,.08);
229
+ }
230
+ .drop-zone.has-img .dz-ph { display:none; }
 
 
231
  #preview-img {
232
+ position:absolute; inset:0; width:100%; height:100%;
233
+ object-fit:cover; display:none;
234
+ border-radius:calc(var(--r) - 1px); opacity:.85;
235
+ }
236
+ .drop-zone.has-img #preview-img { display:block; }
237
+ .dz-corner { position:absolute; width:12px; height:12px; border-color:var(--green3); border-style:solid; }
238
+ .dz-corner.tl{top:6px;left:6px;border-width:1.5px 0 0 1.5px;}
239
+ .dz-corner.tr{top:6px;right:6px;border-width:1.5px 1.5px 0 0;}
240
+ .dz-corner.bl{bottom:6px;left:6px;border-width:0 0 1.5px 1.5px;}
241
+ .dz-corner.br{bottom:6px;right:6px;border-width:0 1.5px 1.5px 0;}
242
+ .dz-ph { display:flex; flex-direction:column; align-items:center; gap:.4rem; pointer-events:none; }
 
 
 
 
 
 
 
243
  .dz-icon {
244
+ width:36px; height:36px; border:1px solid var(--border2);
245
+ border-radius:50%; display:grid; place-items:center;
246
+ font-size:1rem; color:var(--green3);
247
  }
248
+ .dz-ph p { font-family:var(--ff-mono); font-size:.65rem; color:var(--muted); }
249
+ .dz-ph em { font-family:var(--ff-mono); font-size:.56rem; color:var(--green-dim); font-style:normal; }
250
+ #file-input { display:none; }
251
 
252
  /* ── URL INPUT ── */
253
+ .url-input-wrap { display:none; flex-direction:column; gap:.5rem; }
254
+ .url-input-wrap.show { display:flex; }
255
  .url-field {
256
+ display:flex; align-items:center;
257
+ border:1px solid var(--border2); border-radius:var(--r);
258
+ background:var(--g1); overflow:hidden;
 
 
 
 
 
259
  }
260
+ .url-prefix { font-family:var(--ff-mono); font-size:0.5rem; color:var(--green3); padding:0 .6rem; white-space:nowrap; border-right:1px solid var(--border); }
261
  .url-input {
262
+ flex:1; background:transparent; border:none; outline:none;
263
+ color:var(--green); font-family:var(--ff-mono); font-size:.72rem;
264
+ padding:.6rem .7rem; caret-color:var(--green);
 
265
  }
266
+ .url-input::placeholder { color:var(--green-dim); }
267
  .url-load-btn {
268
+ padding:.55rem .8rem; background:rgba(0,255,65,.1); border:none;
269
+ border-left:1px solid var(--border);
270
+ color:var(--green2); font-family:var(--ff-mono); font-size:0.5rem;
271
+ cursor:pointer; transition:background .2s; white-space:nowrap;
 
272
  }
273
+ .url-load-btn:hover { background:rgba(0,255,65,.2); }
274
  .url-preview {
275
+ width:100%; height:110px; object-fit:cover;
276
+ border-radius:var(--r); border:1px solid var(--border); display:none;
 
277
  }
278
+ .url-preview.show { display:block; }
279
 
280
  /* ── CLASSIFY BTN ── */
281
  .classify-btn {
282
+ flex-shrink: 0;
283
+ width:100%; margin-top:.7rem;
284
+ padding:.7rem;
285
+ background:linear-gradient(135deg,var(--green3),var(--green-dim));
286
+ border:1px solid var(--green3); border-radius:var(--r);
287
+ color:var(--green); font-family:var(--ff-head);
288
+ font-size:1rem; font-weight:700; letter-spacing:.1em;
289
+ cursor:pointer; position:relative; overflow:hidden;
290
+ transition:all .2s; text-transform:uppercase;
291
  }
292
  .classify-btn::before {
293
+ content:''; position:absolute; top:-2px; left:-100%;
294
+ width:60%; height:calc(100% + 4px);
295
+ background:linear-gradient(90deg,transparent,rgba(0,255,65,.18),transparent);
296
+ transform:skewX(-15deg);
297
  }
298
+ .classify-btn.loading::before { animation:sweep 1.2s linear infinite; }
299
+ @keyframes sweep { to { left:150%; } }
 
 
300
  .classify-btn:not(:disabled):hover {
301
+ background:linear-gradient(135deg,var(--green2),var(--green3));
302
+ box-shadow:0 0 24px rgba(0,255,65,.3); transform:translateY(-1px);
 
303
  }
304
+ .classify-btn:disabled { opacity:.35; cursor:default; }
305
 
306
+ /* ══ RIGHT PANEL — results ══ */
307
+ .result-panel-inner {
308
+ flex: 1;
309
+ min-height: 0;
310
+ overflow-y: auto;
311
+ overflow-x: hidden;
312
+ scrollbar-width: none;
313
+ display: flex;
314
+ flex-direction: column;
315
+ }
316
+ .result-panel-inner::-webkit-scrollbar { display:none; }
317
 
 
318
  .result-waiting {
319
+ display:flex; flex-direction:column;
320
+ align-items:flex-start; justify-content:center;
321
+ flex:1; gap:.5rem; padding:.5rem 0;
 
 
 
 
 
 
 
 
 
322
  }
323
+ .result-waiting .rw-title { font-family:var(--ff-head); font-size:.95rem; font-weight:600; color:var(--green3); }
324
+ .result-waiting .rw-sub { font-family:var(--ff-mono); font-size:.66rem; color:var(--green-dim); line-height:1.75; }
325
  .terminal-cursor {
326
+ display:inline-block; width:7px; height:13px;
327
+ background:var(--green); margin-left:2px;
328
+ animation:blink-c .8s step-end infinite; vertical-align:middle;
 
329
  }
330
+ @keyframes blink-c{0%,100%{opacity:1}50%{opacity:0}}
331
 
332
+ .result-content { display:none; flex-direction:column; gap:1rem; }
333
+ .result-content.show { display:flex; animation:scanIn .4s ease; }
 
334
  @keyframes scanIn {
335
+ from{opacity:0;clip-path:inset(0 0 100% 0)}
336
+ to{opacity:1;clip-path:inset(0 0 0% 0)}
337
  }
338
 
339
+ .rc-header { display:flex; flex-direction:column; gap:.2rem; }
340
+ .rc-label { font-family:var(--ff-mono); font-size:.58rem; letter-spacing:.2em; color:var(--green3); }
341
+ .rc-class {
342
+ font-family:var(--ff-head); font-size:2.6rem; font-weight:700;
343
+ letter-spacing:-.01em; line-height:1; color:var(--green);
344
+ text-shadow:0 0 30px rgba(0,255,65,.5);
345
  }
346
+ .rc-conf { font-family:var(--ff-mono); font-size:.72rem; color:var(--muted); margin-top:.1rem; }
347
+ .rc-conf strong { color:var(--green2); font-size:.85rem; }
 
 
 
 
 
 
 
 
 
 
348
 
 
349
  .conf-track {
350
+ height:3px; background:var(--g4); border-radius:99px; overflow:hidden; margin-top:.4rem;
 
351
  }
352
  .conf-fill {
353
+ height:100%;
354
+ background:linear-gradient(90deg,var(--green3),var(--green));
355
+ border-radius:99px; width:0%;
356
+ transition:width 1s cubic-bezier(.4,0,.2,1);
357
+ box-shadow:0 0 8px var(--green);
358
  }
359
 
360
+ .rc-divider { height:1px; background:var(--border); flex-shrink:0; }
 
 
 
361
 
362
+ .prob-list { display:flex; flex-direction:column; gap:.65rem; }
363
+ .prob-row { display:flex; flex-direction:column; gap:.18rem; }
364
+ .prob-meta { display:flex; justify-content:space-between; align-items:baseline; }
 
365
  .prob-name {
366
+ font-family:var(--ff-body); font-size:.76rem; font-weight:400;
367
+ color:var(--muted); display:flex; align-items:center; gap:.35rem; text-transform:capitalize;
368
+ }
369
+ .prob-row.top .prob-name { color:var(--green); font-weight:600; }
370
+ .prob-pct { font-family:var(--ff-mono); font-size:.65rem; color:var(--green-dim); }
371
+ .prob-row.top .prob-pct { color:var(--green2); }
372
+ .prob-track { height:4px; background:var(--g4); border-radius:99px; overflow:hidden; }
373
+ .prob-fill {
374
+ height:100%; background:var(--green-dim);
375
+ border-radius:99px; width:0%;
376
+ transition:width .8s cubic-bezier(.4,0,.2,1);
 
 
 
 
377
  }
378
  .prob-row.top .prob-fill {
379
+ background:linear-gradient(90deg,var(--green3),var(--green));
380
+ box-shadow:0 0 6px rgba(0,255,65,.4);
381
  }
382
 
 
383
  .result-error {
384
+ display:none; padding:.75rem;
385
+ border:1px solid rgba(255,50,50,.3); border-radius:var(--r);
386
+ background:rgba(255,0,0,.05);
387
+ font-family:var(--ff-mono); font-size:.7rem; color:#ff4444; line-height:1.6;
 
388
  }
389
+ .result-error.show { display:block; animation:fadeIn .3s ease; }
390
 
391
+ /* ══ FOOTER BAR minimal, fixed ══ */
392
+ .footer-bar {
393
+ flex-shrink: 0;
394
  border-top: 1px solid var(--border);
395
+ padding: .35rem 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  display: flex; justify-content: space-between; align-items: center;
397
+ animation: fadeIn .8s ease .25s both;
398
  }
399
+ .footer-bar p { font-family:var(--ff-mono); font-size:.55rem; color:var(--green-dim); }
 
 
 
400
 
401
+ @keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
 
 
 
 
 
402
  </style>
403
  </head>
404
  <body>
405
 
 
 
406
  <canvas id="plexus-bg"></canvas>
407
 
408
+ <div class="wrapper">
409
+
410
  <!-- HEADER -->
411
  <header>
412
  <div class="logo">
 
419
  </div>
420
  </header>
421
 
422
+ <!-- CLASSES BAR (moved out of grid, compact strip) -->
423
+ <div class="classes-bar">
424
+ <span class="cs-label">CLASSES //</span>
425
+ <div class="cs-pills">
426
+ <span class="cs-pill">🏙 buildings</span>
427
+ <span class="cs-pill">🌲 forest</span>
428
+ <span class="cs-pill">🧊 glacier</span>
429
+ <span class="cs-pill">⛰ mountain</span>
430
+ <span class="cs-pill">🌊 sea</span>
431
+ <span class="cs-pill">🛣 street</span>
432
+ </div>
433
  </div>
434
 
435
  <!-- MAIN GRID -->
 
439
  <div class="panel">
440
  <div class="panel-title">01 // MODEL_SELECT</div>
441
 
442
+ <!-- scrollable zone so nothing overflows -->
443
+ <div class="panel-scroll">
444
+ <div class="model-grid">
445
+ <div class="model-card active" data-fw="pytorch" onclick="selectModel(this)">
446
+ <span class="mc-icon"></span>
447
+ <span class="mc-name">PyTorch</span>
448
+ <span class="mc-sub">CNN_Torch · .pth</span>
449
+ </div>
450
+ <div class="model-card" data-fw="tensorflow" onclick="selectModel(this)">
451
+ <span class="mc-icon">🧠</span>
452
+ <span class="mc-name">TensorFlow</span>
453
+ <span class="mc-sub">CNN_TF · .keras</span>
454
+ </div>
455
  </div>
456
+
457
+ <div class="panel-title">02 // INPUT_IMAGE</div>
458
+
459
+ <div class="input-tabs">
460
+ <button class="tab-btn active" onclick="switchTab('file', this)">↑ FILE_UPLOAD</button>
461
+ <button class="tab-btn" onclick="switchTab('url', this)">⬡ IMAGE_URL</button>
462
  </div>
463
+
464
+ <!-- File drop zone -->
465
+ <div id="tab-file">
466
+ <div class="drop-zone" id="drop-zone" onclick="document.getElementById('file-input').click()">
467
+ <div class="dz-corner tl"></div><div class="dz-corner tr"></div>
468
+ <div class="dz-corner bl"></div><div class="dz-corner br"></div>
469
+ <img id="preview-img" src="" alt="preview"/>
470
+ <div class="dz-ph">
471
+ <div class="dz-icon">↑</div>
472
+ <p>Drop image here or click</p>
473
+ <em>JPG · PNG · WEBP · GIF</em>
474
+ </div>
 
 
 
 
 
 
 
 
 
 
475
  </div>
476
+ <input type="file" id="file-input" accept="image/*"/>
477
  </div>
478
+
479
+ <!-- URL input -->
480
+ <div id="tab-url" class="url-input-wrap">
481
+ <div class="url-field">
482
+ <span class="url-prefix">URL://</span>
483
+ <input type="text" class="url-input" id="url-input"
484
+ placeholder="https://example.com/image.jpg"/>
485
+ <button class="url-load-btn" onclick="loadFromUrl()">LOAD</button>
486
+ </div>
487
+ <img id="url-preview" class="url-preview" src="" alt="URL preview"/>
488
  </div>
 
 
489
 
490
+ <div class="panel-title" style="margin-top:.9rem;">03 // ANALYZE</div>
491
+ </div><!-- /panel-scroll -->
492
+
493
+ <!-- Btn outside scroll so it stays at bottom -->
494
  <button class="classify-btn" id="classify-btn" disabled onclick="classify()">
495
  RUN CLASSIFICATION →
496
  </button>
 
500
  <div class="panel">
501
  <div class="panel-title">04 // SCAN_RESULTS</div>
502
 
503
+ <div class="result-panel-inner">
504
+
505
+ <div class="result-waiting" id="result-waiting">
506
+ <div class="rw-title">AWAITING INPUT</div>
507
+ <div class="rw-sub">
508
+ &gt; select_model()<br>
509
+ &gt; load_image() <span class="terminal-cursor"></span><br>
510
+ &gt; predict()
511
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
512
  </div>
 
513
 
514
+ <div class="result-content" id="result-content">
515
+ <div class="rc-header">
516
+ <div class="rc-label">// PREDICTED_CLASS</div>
517
+ <div class="rc-class" id="rc-class">—</div>
518
+ <div class="rc-conf">confidence : <strong id="rc-conf">—</strong></div>
519
+ <div class="conf-track"><div class="conf-fill" id="conf-fill"></div></div>
520
+ </div>
521
+ <div class="rc-divider"></div>
522
+ <div>
523
+ <div class="panel-title" style="margin-bottom:.6rem;">// CLASS_SCORES</div>
524
+ <div class="prob-list" id="prob-list"></div>
525
+ </div>
526
+ </div>
527
 
528
+ <div class="result-error" id="result-error"></div>
529
 
530
+ </div><!-- /result-panel-inner -->
 
 
 
 
 
 
 
 
 
531
  </div>
 
532
 
533
+ </div><!-- /main-grid -->
534
+
535
+ <!-- FOOTER BAR -->
536
+ <div class="footer-bar">
537
  <p>SCENEIQ · Intel Image Classification · CNN PyTorch &amp; TensorFlow</p>
538
  <p>by PARFAIT · seed=42 · reproducible</p>
539
+ </div>
540
 
541
  </div><!-- /wrapper -->
542
 
543
  <script>
544
+ const EMOJIS = {buildings:"🏙",forest:"🌲",glacier:"🧊",mountain:"⛰",sea:"🌊",street:"🛣"};
545
  const CLASSES = ["buildings","forest","glacier","mountain","sea","street"];
546
 
547
+ let selectedFw = "pytorch";
548
+ let selectedFile = null;
549
  let urlImageReady = false;
550
+ let currentTab = "file";
551
 
 
552
  function selectModel(card) {
553
  document.querySelectorAll(".model-card").forEach(c => c.classList.remove("active"));
554
  card.classList.add("active");
555
  selectedFw = card.dataset.fw;
556
  }
557
 
 
558
  function switchTab(tab, btn) {
559
  currentTab = tab;
560
  document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
561
  btn.classList.add("active");
562
  document.getElementById("tab-file").style.display = tab === "file" ? "block" : "none";
563
+ document.getElementById("tab-url").classList.toggle("show", tab === "url");
 
564
  updateBtn();
565
  }
566
 
567
+ const dropZone = document.getElementById("drop-zone");
 
568
  const fileInput = document.getElementById("file-input");
569
 
570
+ fileInput.addEventListener("change", () => { if (fileInput.files[0]) loadFile(fileInput.files[0]); });
571
+ dropZone.addEventListener("dragover", e => { e.preventDefault(); dropZone.classList.add("drag"); });
 
 
572
  dropZone.addEventListener("dragleave", () => dropZone.classList.remove("drag"));
573
  dropZone.addEventListener("drop", e => {
574
  e.preventDefault(); dropZone.classList.remove("drag");
 
580
  selectedFile = file;
581
  const reader = new FileReader();
582
  reader.onload = e => {
583
+ document.getElementById("preview-img").src = e.target.result;
 
584
  dropZone.classList.add("has-img");
585
  };
586
  reader.readAsDataURL(file);
587
+ resetResult(); updateBtn();
 
588
  }
589
 
 
590
  function loadFromUrl() {
591
+ const url = document.getElementById("url-input").value.trim();
592
  if (!url) return;
593
  const preview = document.getElementById("url-preview");
594
+ preview.onload = () => { preview.classList.add("show"); urlImageReady = true; resetResult(); updateBtn(); };
595
+ preview.onerror = () => { preview.classList.remove("show"); urlImageReady = false; updateBtn(); };
 
 
 
 
 
 
 
 
 
596
  preview.src = url;
597
  }
598
 
599
+ document.getElementById("url-input").addEventListener("keydown", e => { if (e.key === "Enter") loadFromUrl(); });
 
 
 
600
 
 
601
  function updateBtn() {
602
+ const ready = (currentTab === "file" && selectedFile) || (currentTab === "url" && urlImageReady);
 
603
  document.getElementById("classify-btn").disabled = !ready;
604
  }
605
 
 
606
  async function classify() {
607
  const btn = document.getElementById("classify-btn");
608
+ btn.disabled = true; btn.textContent = "SCANNING…"; btn.classList.add("loading");
 
 
609
 
610
  const form = new FormData();
611
  form.append("model", selectedFw);
612
+ if (currentTab === "file" && selectedFile) form.append("image", selectedFile);
613
+ else form.append("image_url", document.getElementById("url-input").value.trim());
 
 
 
 
614
 
615
  try {
616
  const res = await fetch("/predict", { method: "POST", body: form });
 
620
  } catch(err) {
621
  showError(err.message);
622
  } finally {
623
+ btn.textContent = "RUN CLASSIFICATION →"; btn.classList.remove("loading"); btn.disabled = false;
 
 
624
  }
625
  }
626
 
 
627
  function showResult(data) {
628
+ document.getElementById("result-waiting").style.display = "none";
629
  document.getElementById("result-error").classList.remove("show");
630
 
631
  const pct = Math.round(data.confidence * 100);
632
  document.getElementById("rc-class").textContent =
633
+ (EMOJIS[data.class]||"") + " " + data.class.charAt(0).toUpperCase() + data.class.slice(1);
634
  document.getElementById("rc-conf").textContent = pct + "%";
635
 
636
  const content = document.getElementById("result-content");
637
+ content.classList.remove("show"); void content.offsetWidth; content.classList.add("show");
638
+ setTimeout(() => { document.getElementById("conf-fill").style.width = pct + "%"; }, 60);
 
 
 
 
 
639
 
640
+ const list = document.getElementById("prob-list");
 
641
  list.innerHTML = "";
642
+ const sorted = [...CLASSES].sort((a,b) => data.probabilities[b]-data.probabilities[a]);
 
643
  sorted.forEach((cls, i) => {
644
  const p = Math.round(data.probabilities[cls] * 100);
645
  const top = cls === data.class;
 
647
  row.className = "prob-row" + (top ? " top" : "");
648
  row.innerHTML = `
649
  <div class="prob-meta">
650
+ <span class="prob-name">${EMOJIS[cls]||""} ${cls}</span>
651
  <span class="prob-pct" id="pct-${cls}">0%</span>
652
  </div>
653
+ <div class="prob-track"><div class="prob-fill" id="bar-${cls}"></div></div>`;
 
 
654
  list.appendChild(row);
655
  setTimeout(() => {
656
+ document.getElementById("bar-"+cls).style.width = p+"%";
657
+ document.getElementById("pct-"+cls).textContent = p+"%";
658
+ }, 100 + i*50);
659
  });
660
  }
661
 
662
  function showError(msg) {
663
+ document.getElementById("result-waiting").style.display = "none";
664
  document.getElementById("result-content").classList.remove("show");
665
  const err = document.getElementById("result-error");
666
+ err.textContent = "ERROR > " + msg; err.classList.add("show");
 
667
  }
668
 
669
  function resetResult() {
670
+ document.getElementById("result-waiting").style.display = "flex";
671
  document.getElementById("result-content").classList.remove("show");
672
  document.getElementById("result-error").classList.remove("show");
673
  document.getElementById("conf-fill").style.width = "0%";
674
  }
675
 
 
676
  document.getElementById("tab-file").style.display = "block";
677
  </script>
678
 
679
+ <!-- PLEXUS BACKGROUND -->
680
  <script>
681
  (function(){
682
  const canvas = document.getElementById('plexus-bg');
683
  const ctx = canvas.getContext('2d');
684
+ const NODE_COLOR='rgba(0,255,65,', LINE_COLOR='rgba(0,255,65,';
685
+ const MAX_DIST=160, MOUSE_DIST=220, ATTRACT_FORCE=0.012, NODE_COUNT=110, SPEED=0.4;
686
+ let W, H, nodes=[], mouse={x:-9999,y:-9999};
687
+
688
+ window.addEventListener('mousemove', e=>{mouse.x=e.clientX;mouse.y=e.clientY;});
689
+ window.addEventListener('mouseleave',()=>{mouse.x=-9999;mouse.y=-9999;});
690
+
691
+ function resize(){W=canvas.width=window.innerWidth;H=canvas.height=window.innerHeight;}
692
+ function Node(){this.x=Math.random()*W;this.y=Math.random()*H;this.vx=(Math.random()-.5)*SPEED;this.vy=(Math.random()-.5)*SPEED;this.r=Math.random()*2+1.5;}
693
+ function init(){nodes=[];for(let i=0;i<NODE_COUNT;i++)nodes.push(new Node());}
694
+
695
+ function draw(){
696
+ ctx.clearRect(0,0,W,H);
697
+ for(const n of nodes){
698
+ const dxM=mouse.x-n.x,dyM=mouse.y-n.y,dM=Math.sqrt(dxM*dxM+dyM*dyM);
699
+ if(dM<MOUSE_DIST&&dM>0){const f=(1-dM/MOUSE_DIST)*ATTRACT_FORCE;n.vx+=dxM/dM*f;n.vy+=dyM/dM*f;}
700
+ const spd=Math.sqrt(n.vx*n.vx+n.vy*n.vy),maxSpd=SPEED*3;
701
+ if(spd>maxSpd){n.vx=(n.vx/spd)*maxSpd;n.vy=(n.vy/spd)*maxSpd;}
702
+ if(dM>=MOUSE_DIST){n.vx*=.996;n.vy*=.996;const s=Math.sqrt(n.vx*n.vx+n.vy*n.vy);if(s<SPEED*.3){n.vx+=(Math.random()-.5)*.06;n.vy+=(Math.random()-.5)*.06;}}
703
+ n.x+=n.vx;n.y+=n.vy;
704
+ if(n.x<0||n.x>W)n.vx*=-1;if(n.y<0||n.y>H)n.vy*=-1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
  }
706
+ for(let i=0;i<nodes.length;i++)for(let j=i+1;j<nodes.length;j++){
707
+ const a=nodes[i],b=nodes[j],dx=a.x-b.x,dy=a.y-b.y,d=Math.sqrt(dx*dx+dy*dy);
708
+ if(d<MAX_DIST){const al=(1-d/MAX_DIST)*.28;ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.strokeStyle=LINE_COLOR+al+')';ctx.lineWidth=.8;ctx.stroke();}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  }
710
+ for(const n of nodes){
711
+ const dxM=mouse.x-n.x,dyM=mouse.y-n.y,dM=Math.sqrt(dxM*dxM+dyM*dyM);
712
+ if(dM<MOUSE_DIST){const al=(1-dM/MOUSE_DIST)*.35;ctx.beginPath();ctx.moveTo(mouse.x,mouse.y);ctx.lineTo(n.x,n.y);ctx.strokeStyle=LINE_COLOR+al+')';ctx.lineWidth=.5;ctx.stroke();}
 
 
 
 
 
 
 
 
 
 
 
 
713
  }
714
+ if(mouse.x>0){
715
+ const g=ctx.createRadialGradient(mouse.x,mouse.y,0,mouse.x,mouse.y,20);
716
+ g.addColorStop(0,'rgba(0,255,65,0.5)');g.addColorStop(1,'rgba(0,255,65,0)');
717
+ ctx.beginPath();ctx.arc(mouse.x,mouse.y,20,0,Math.PI*2);ctx.fillStyle=g;ctx.fill();
718
+ ctx.beginPath();ctx.arc(mouse.x,mouse.y,2,0,Math.PI*2);ctx.fillStyle='rgba(0,255,65,0.95)';ctx.fill();
 
 
 
 
 
 
 
 
 
 
719
  }
720
+ for(const n of nodes){
721
+ const g=ctx.createRadialGradient(n.x,n.y,0,n.x,n.y,n.r*5);
722
+ g.addColorStop(0,NODE_COLOR+'0.3)');g.addColorStop(1,NODE_COLOR+'0)');
723
+ ctx.beginPath();ctx.arc(n.x,n.y,n.r*5,0,Math.PI*2);ctx.fillStyle=g;ctx.fill();
724
+ ctx.beginPath();ctx.arc(n.x,n.y,n.r,0,Math.PI*2);ctx.fillStyle=NODE_COLOR+'0.85)';ctx.fill();
 
 
 
 
 
 
 
 
 
 
725
  }
 
726
  requestAnimationFrame(draw);
727
  }
728
+ resize();init();draw();
729
+ window.addEventListener('resize',()=>{resize();init();});
 
 
 
730
  })();
731
  </script>
732
  </body>
templates/index_old.html CHANGED
@@ -7,922 +7,979 @@
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;500;600;700&family=Exo+2:wght@300;400;600&display=swap" rel="stylesheet">
9
  <style>
10
- /* ══════════════════════════════════════════════════════════════════
11
- TOKENS
12
- ══════════════════════════════════════════════════════════════════ */
13
  :root {
14
- --g0: #000000;
15
- --g1: #050d05;
16
- --g2: #0a150a;
17
- --g3: #0f1f0f;
18
- --g4: #1a2e1a;
19
- --green: #00ff41;
20
- --green2: #00cc33;
21
- --green3: #008f1f;
22
- --green-dim:#004d11;
23
- --green-glow: rgba(0,255,65,0.15);
24
- --border: rgba(0,255,65,0.18);
25
- --border2: rgba(0,255,65,0.35);
26
- --muted: rgba(0,255,65,0.45);
27
- --ff-mono: 'Share Tech Mono', monospace;
28
- --ff-head: 'Rajdhani', sans-serif;
29
- --ff-body: 'Exo 2', sans-serif;
30
- --r: 4px;
31
-
32
- /*new add*/
33
- --bg-main: #160a2f;
34
- --bg-deep: #0e061f;
35
- --pink: #ff4fa3;
36
- --pink-soft: rgba(255, 79, 163, 0.18);
37
- --pink-line: rgba(255, 79, 163, 0.28);
38
- --white-soft: rgba(255,255,255,0.06);
39
- }
40
-
41
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
42
- html { font-size: 16px; scroll-behavior: smooth; }
 
 
 
 
 
 
43
 
44
  body {
45
- background: var(--g0);
46
- color: var(--green);
 
 
 
 
47
  font-family: var(--ff-body);
48
  min-height: 100vh;
49
- overflow-x: hidden;
50
  cursor: default;
 
51
 
52
- /*New add 2*/
53
- #brain-bg {
54
- position: fixed;
55
- inset: 0;
56
- width: 100%;
57
- height: 100%;
58
- z-index: 0;
59
- pointer-events: none;
60
- opacity: 0.42;
61
- }
62
-
63
- .bg-vignette {
64
- position: fixed;
65
- inset: 0;
66
- z-index: 0;
67
- pointer-events: none;
68
- background:
69
- radial-gradient(circle at 20% 20%, rgba(0,255,65,0.07), transparent 30%),
70
- radial-gradient(circle at 80% 25%, rgba(0,255,65,0.05), transparent 28%),
71
- radial-gradient(circle at 50% 80%, rgba(0,255,65,0.04), transparent 35%),
72
- linear-gradient(180deg, rgba(0,0,0,0.12), rgba(0,0,0,0.35));
73
- }
74
-
75
- .bg-orb {
76
- position: fixed;
77
- border-radius: 50%;
78
- filter: blur(70px);
79
- pointer-events: none;
80
- z-index: 0;
81
- opacity: 0.12;
82
- animation: floatOrb 16s ease-in-out infinite;
83
- }
84
-
85
- .orb-1 {
86
- width: 260px;
87
- height: 260px;
88
- top: 8%;
89
- left: 6%;
90
- background: rgba(0,255,65,0.35);
91
- }
92
-
93
- .orb-2 {
94
- width: 320px;
95
- height: 320px;
96
- top: 52%;
97
- right: 8%;
98
- background: rgba(0,204,51,0.20);
99
- animation-delay: -5s;
100
- }
101
-
102
- .orb-3 {
103
- width: 220px;
104
- height: 220px;
105
- bottom: 4%;
106
- left: 34%;
107
- background: rgba(0,143,31,0.24);
108
- animation-delay: -10s;
109
- }
110
-
111
- @keyframes floatOrb {
112
- 0%, 100% {
113
- transform: translate3d(0, 0, 0) scale(1);
114
- }
115
- 25% {
116
- transform: translate3d(18px, -24px, 0) scale(1.04);
117
- }
118
- 50% {
119
- transform: translate3d(-12px, 16px, 0) scale(0.96);
120
- }
121
- 75% {
122
- transform: translate3d(22px, 8px, 0) scale(1.03);
123
- }
124
- }
125
-
126
- /* rendre le contenu plus premium */
127
- .wrapper {
128
- position: relative;
129
- z-index: 2;
130
- }
131
-
132
- .panel {
133
- background: linear-gradient(
134
- 180deg,
135
- rgba(10,21,10,0.82),
136
- rgba(5,13,5,0.86)
137
- );
138
- backdrop-filter: blur(8px);
139
- -webkit-backdrop-filter: blur(8px);
140
- border: 1px solid rgba(0,255,65,0.14);
141
- box-shadow:
142
- 0 10px 40px rgba(0,0,0,0.35),
143
- inset 0 1px 0 rgba(0,255,65,0.04);
144
- }
145
-
146
- header {
147
- backdrop-filter: blur(6px);
148
- -webkit-backdrop-filter: blur(6px);
149
- }
150
-
151
- .hero-title {
152
- max-width: 720px;
153
- }
154
-
155
- .hero-sub {
156
- max-width: 760px;
157
- line-height: 1.7;
158
- }
159
-
160
- /* petite amélioration sur les cartes */
161
- .model-card,
162
- .drop-zone,
163
- .url-field,
164
- .input-tabs,
165
- .classes-strip,
166
- footer {
167
- backdrop-filter: blur(4px);
168
- -webkit-backdrop-filter: blur(4px);
169
- }
170
-
171
- /* responsive */
172
- @media (max-width: 740px) {
173
- #brain-bg {
174
- opacity: 0.28;
175
- }
176
-
177
- .bg-orb {
178
- filter: blur(55px);
179
- }
180
- }
181
- }
182
 
183
- /* ── Grid overlay ── */
184
  body::before {
185
  content: '';
186
- position: fixed; inset: 0; z-index: 0; pointer-events: none;
 
 
 
187
  background-image:
188
- linear-gradient(rgba(0,255,65,0.03) 1px, transparent 1px),
189
- linear-gradient(90deg, rgba(0,255,65,0.03) 1px, transparent 1px);
190
- background-size: 40px 40px;
191
  }
192
- /* Scanline overlay */
193
  body::after {
194
  content: '';
195
- position: fixed; inset: 0; z-index: 0; pointer-events: none;
196
- background: repeating-linear-gradient(
197
- 0deg,
198
- transparent,
199
- transparent 2px,
200
- rgba(0,0,0,0.07) 2px,
201
- rgba(0,0,0,0.07) 4px
202
- );
203
- }
204
-
205
- /* ══════════════════════════════════════════════════════════════════
206
- LAYOUT
207
- ══════════════════════════════════════════════════════════════════ */
 
 
 
 
 
 
 
 
 
 
 
208
  .wrapper {
209
- position: relative; z-index: 1;
210
- max-width: 1140px;
211
- margin: 0 auto;
212
- padding: 0 2rem;
 
 
 
 
 
 
 
 
 
213
  }
214
 
215
- /* ── HEADER ── */
216
  header {
217
- border-bottom: 1px solid var(--border);
218
- padding: 1.4rem 0;
 
219
  display: flex;
220
  align-items: center;
221
  justify-content: space-between;
 
 
 
222
  animation: fadeIn .6s ease both;
223
  }
 
224
  .logo {
225
- display: flex; align-items: center; gap: 1rem;
 
 
226
  }
 
227
  .logo-icon {
228
- width: 38px; height: 38px;
 
229
  border: 1.5px solid var(--green);
230
- display: grid; place-items: center;
 
 
231
  font-size: 1.1rem;
232
- box-shadow: 0 0 14px var(--green-glow), inset 0 0 8px var(--green-glow);
 
233
  animation: pulse-box 3s ease-in-out infinite;
234
  }
 
235
  @keyframes pulse-box {
236
  0%,100% { box-shadow: 0 0 10px var(--green-glow), inset 0 0 6px var(--green-glow); }
237
- 50% { box-shadow: 0 0 22px rgba(0,255,65,.3), inset 0 0 14px rgba(0,255,65,.2); }
238
  }
 
239
  .logo-text {
240
  font-family: var(--ff-head);
241
- font-size: 1.8rem; font-weight: 700;
242
- letter-spacing: .15em;
243
- text-shadow: 0 0 20px rgba(0,255,65,.6);
 
 
244
  }
245
- .logo-text span { color: var(--green3); }
 
 
246
  .header-right {
247
- display: flex; align-items: center; gap: 1.5rem;
 
 
 
248
  }
 
 
 
 
 
 
 
249
  .status-dot {
250
- display: flex; align-items: center; gap: .45rem;
251
- font-family: var(--ff-mono); font-size: .65rem; color: var(--muted);
 
252
  }
 
253
  .dot {
254
- width: 6px; height: 6px; border-radius: 50%;
 
 
255
  background: var(--green);
256
- box-shadow: 0 0 8px var(--green);
257
  animation: blink 2s step-end infinite;
258
  }
259
- @keyframes blink { 0%,100%{opacity:1} 50%{opacity:.2} }
260
- .version {
261
- font-family: var(--ff-mono); font-size: .6rem;
262
- color: var(--green-dim); letter-spacing: .1em;
263
- }
264
 
265
- /* ── HERO ── */
 
266
  .hero {
267
- padding: 3rem 0 2rem;
 
 
 
 
 
268
  animation: fadeIn .7s ease .1s both;
269
  }
 
270
  .hero-label {
271
- font-family: var(--ff-mono); font-size: .62rem;
272
- color: var(--green3); letter-spacing: .2em;
273
- text-transform: uppercase; margin-bottom: .6rem;
 
 
 
274
  }
 
275
  .hero-title {
276
  font-family: var(--ff-head);
277
- font-size: clamp(2.2rem, 5vw, 3.4rem);
278
- font-weight: 700; letter-spacing: -.01em;
279
- line-height: 1.05;
280
- text-shadow: 0 0 40px rgba(0,255,65,.3);
 
 
281
  }
 
282
  .hero-title em {
283
- font-style: normal; color: var(--green2);
284
- text-shadow: 0 0 30px rgba(0,204,51,.6);
 
285
  }
 
286
  .hero-sub {
287
- margin-top: .8rem;
288
- font-size: .9rem; color: var(--muted);
289
- font-family: var(--ff-mono); letter-spacing: .04em;
 
 
 
290
  }
291
 
292
- /* ── MAIN GRID ── */
293
  .main-grid {
294
  display: grid;
295
  grid-template-columns: 1fr 1fr;
296
- gap: 1.5rem;
297
- padding-bottom: 4rem;
298
  animation: fadeIn .8s ease .2s both;
299
  }
300
 
301
- /* ── PANEL BASE ── */
302
  .panel {
303
- background: var(--g2);
304
  border: 1px solid var(--border);
305
- border-radius: var(--r);
306
- padding: 1.75rem;
307
  position: relative;
308
  overflow: hidden;
 
 
 
 
309
  }
 
310
  .panel::before {
311
  content: '';
312
- position: absolute; top: 0; left: 0; right: 0;
 
313
  height: 2px;
314
  background: linear-gradient(90deg, transparent, var(--green3), transparent);
315
  }
 
316
  .panel-title {
317
- font-family: var(--ff-mono); font-size: .6rem;
318
- letter-spacing: .2em; color: var(--green3);
319
- text-transform: uppercase; margin-bottom: 1.4rem;
320
- display: flex; align-items: center; gap: .6rem;
 
 
 
 
 
321
  }
 
322
  .panel-title::after {
323
- content: ''; flex: 1; height: 1px; background: var(--border);
 
 
 
324
  }
325
 
326
- /* ── MODEL SELECTOR ── */
327
  .model-grid {
328
- display: grid; grid-template-columns: 1fr 1fr; gap: .75rem;
329
- margin-bottom: 1.5rem;
 
 
330
  }
 
331
  .model-card {
332
  border: 1px solid var(--border);
333
- border-radius: var(--r);
334
  padding: 1rem .9rem;
335
  cursor: pointer;
336
- transition: all .2s;
337
- background: var(--g1);
338
- display: flex; flex-direction: column; gap: .3rem;
 
 
339
  }
340
- .model-card:hover { border-color: var(--green3); background: var(--g3); }
 
 
 
 
 
 
341
  .model-card.active {
342
  border-color: var(--green);
343
- background: rgba(0,255,65,.06);
344
- box-shadow: 0 0 16px rgba(0,255,65,.12), inset 0 0 12px rgba(0,255,65,.04);
345
  }
346
- .model-card.active .mc-name { color: var(--green); }
 
 
347
  .mc-icon { font-size: 1.2rem; }
 
348
  .mc-name {
349
- font-family: var(--ff-head); font-weight: 600; font-size: 1rem;
350
- color: var(--green2); transition: color .2s;
 
 
 
 
 
 
 
 
351
  }
352
- .mc-sub { font-family: var(--ff-mono); font-size: .58rem; color: var(--muted); }
353
 
354
- /* ── INPUT TABS ── */
355
  .input-tabs {
356
- display: flex; gap: 0; margin-bottom: 1rem;
357
- border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
 
358
  }
 
359
  .tab-btn {
360
- flex: 1; padding: .55rem; background: var(--g1);
361
- border: none; color: var(--muted); font-family: var(--ff-mono);
362
- font-size: .65rem; letter-spacing: .1em; cursor: pointer;
363
- transition: all .2s; text-transform: uppercase;
 
 
 
 
 
 
 
 
364
  }
365
- .tab-btn:hover { background: var(--g3); color: var(--green2); }
 
 
 
 
 
366
  .tab-btn.active {
367
- background: rgba(0,255,65,.08); color: var(--green);
 
 
368
  box-shadow: inset 0 -2px 0 var(--green);
369
  }
370
 
371
- /* ── DROP ZONE ── */
372
  .drop-zone {
373
  border: 1.5px dashed var(--border2);
374
- border-radius: var(--r);
375
- min-height: 190px;
376
- display: flex; flex-direction: column;
377
- align-items: center; justify-content: center;
378
- gap: .75rem; cursor: pointer;
379
- transition: all .25s; background: var(--g1);
380
- position: relative; overflow: hidden;
 
 
 
 
 
381
  }
 
382
  .drop-zone:hover, .drop-zone.drag {
383
  border-color: var(--green);
384
  background: rgba(0,255,65,.04);
385
  box-shadow: 0 0 20px rgba(0,255,65,.08);
386
  }
 
387
  .drop-zone.has-img .dz-ph { display: none; }
 
388
  #preview-img {
389
- position: absolute; inset: 0;
390
- width: 100%; height: 100%; object-fit: cover;
391
- display: none; border-radius: calc(var(--r) - 1px);
392
- opacity: .85;
 
 
 
393
  }
 
394
  .drop-zone.has-img #preview-img { display: block; }
 
395
  .dz-corner {
396
- position: absolute; width: 14px; height: 14px;
397
- border-color: var(--green3); border-style: solid;
 
 
 
398
  }
 
399
  .dz-corner.tl { top: 8px; left: 8px; border-width: 1.5px 0 0 1.5px; }
400
  .dz-corner.tr { top: 8px; right: 8px; border-width: 1.5px 1.5px 0 0; }
401
  .dz-corner.bl { bottom: 8px; left: 8px; border-width: 0 0 1.5px 1.5px; }
402
  .dz-corner.br { bottom: 8px; right: 8px; border-width: 0 1.5px 1.5px 0; }
 
403
  .dz-ph {
404
- display: flex; flex-direction: column; align-items: center; gap: .6rem;
 
 
 
405
  pointer-events: none;
 
 
406
  }
 
407
  .dz-icon {
408
- width: 48px; height: 48px; border: 1px solid var(--border2);
409
- border-radius: 50%; display: grid; place-items: center;
410
- font-size: 1.3rem; color: var(--green3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  }
412
- .dz-ph p { font-family: var(--ff-mono); font-size: .72rem; color: var(--muted); }
413
- .dz-ph em { font-family: var(--ff-mono); font-size: .6rem; color: var(--green-dim); font-style: normal; }
414
  #file-input { display: none; }
415
 
416
- /* ── URL INPUT ── */
417
- .url-input-wrap { display: none; flex-direction: column; gap: .6rem; }
418
  .url-input-wrap.show { display: flex; }
 
419
  .url-field {
420
- display: flex; align-items: center;
421
- border: 1px solid var(--border2); border-radius: var(--r);
422
- background: var(--g1); overflow: hidden;
 
 
 
423
  }
 
424
  .url-prefix {
425
- font-family: var(--ff-mono); font-size: .65rem;
426
- color: var(--green3); padding: 0 .7rem; white-space: nowrap;
 
 
 
427
  border-right: 1px solid var(--border);
428
  }
 
429
  .url-input {
430
- flex: 1; background: transparent; border: none; outline: none;
431
- color: var(--green); font-family: var(--ff-mono); font-size: .75rem;
432
- padding: .75rem .8rem;
 
 
 
 
 
433
  caret-color: var(--green);
434
  }
435
- .url-input::placeholder { color: var(--green-dim); }
 
 
436
  .url-load-btn {
437
- padding: .65rem .9rem;
438
- background: rgba(0,255,65,.1); border: none;
 
439
  border-left: 1px solid var(--border);
440
- color: var(--green2); font-family: var(--ff-mono); font-size: .65rem;
441
- cursor: pointer; transition: background .2s; white-space: nowrap;
 
 
 
 
442
  }
443
- .url-load-btn:hover { background: rgba(0,255,65,.2); }
 
 
444
  .url-preview {
445
- width: 100%; height: 140px; object-fit: cover;
446
- border-radius: var(--r); border: 1px solid var(--border);
 
 
 
447
  display: none;
448
  }
 
449
  .url-preview.show { display: block; }
450
 
451
- /* ── CLASSIFY BTN ── */
452
  .classify-btn {
453
- width: 100%; margin-top: 1rem;
 
454
  padding: 1rem;
455
- background: linear-gradient(135deg, var(--green3), var(--green-dim));
456
- border: 1px solid var(--green3); border-radius: var(--r);
457
- color: var(--green); font-family: var(--ff-head);
458
- font-size: 1.1rem; font-weight: 700; letter-spacing: .1em;
459
- cursor: pointer; position: relative; overflow: hidden;
460
- transition: all .2s; text-transform: uppercase;
 
 
 
 
 
 
 
 
461
  }
 
462
  .classify-btn::before {
463
  content: '';
464
- position: absolute; top: -2px; left: -100%; width: 60%; height: calc(100% + 4px);
465
- background: linear-gradient(90deg, transparent, rgba(0,255,65,.18), transparent);
 
 
 
 
466
  transform: skewX(-15deg);
467
  }
468
- .classify-btn.loading::before {
469
- animation: sweep 1.2s linear infinite;
470
- }
471
  @keyframes sweep { to { left: 150%; } }
 
472
  .classify-btn:not(:disabled):hover {
473
  background: linear-gradient(135deg, var(--green2), var(--green3));
474
- box-shadow: 0 0 24px rgba(0,255,65,.3);
 
475
  transform: translateY(-1px);
476
  }
477
- .classify-btn:disabled { opacity: .35; cursor: default; }
478
 
479
- /* ══════════════════════════════════════════════════════════════════
480
- RIGHT PANEL — RESULTS
481
- ══════════════════════════════════════════════════════════════════ */
482
 
483
- /* Waiting state */
484
  .result-waiting {
485
- display: flex; flex-direction: column;
486
- align-items: flex-start; justify-content: center;
487
- min-height: 200px; gap: .6rem;
 
 
 
488
  padding: 1rem 0;
489
  }
 
490
  .result-waiting .rw-title {
491
- font-family: var(--ff-head); font-size: 1rem; font-weight: 600;
492
- color: var(--green3);
 
 
493
  }
 
494
  .result-waiting .rw-sub {
495
- font-family: var(--ff-mono); font-size: .7rem;
496
- color: var(--green-dim); line-height: 1.7;
 
 
497
  }
 
498
  .terminal-cursor {
499
- display: inline-block; width: 8px; height: 14px;
500
- background: var(--green); margin-left: 2px;
 
 
 
501
  animation: blink-c .8s step-end infinite;
502
  vertical-align: middle;
503
  }
 
504
  @keyframes blink-c { 0%,100%{opacity:1} 50%{opacity:0} }
505
 
506
- /* Result */
507
  .result-content { display: none; flex-direction: column; gap: 1.4rem; }
508
  .result-content.show { display: flex; animation: scanIn .4s ease; }
 
509
  @keyframes scanIn {
510
  from { opacity: 0; clip-path: inset(0 0 100% 0); }
511
- to { opacity: 1; clip-path: inset(0 0 0% 0); }
512
  }
513
 
514
  .rc-header { display: flex; flex-direction: column; gap: .25rem; }
 
515
  .rc-label {
516
- font-family: var(--ff-mono); font-size: .6rem;
517
- letter-spacing: .2em; color: var(--green3);
 
 
518
  }
 
519
  .rc-class {
520
  font-family: var(--ff-head);
521
- font-size: 3rem; font-weight: 700;
522
- letter-spacing: -.01em; line-height: 1;
523
- color: var(--green);
524
- text-shadow: 0 0 30px rgba(0,255,65,.5);
 
 
525
  }
 
526
  .rc-conf {
527
- font-family: var(--ff-mono); font-size: .78rem;
528
- color: var(--muted); margin-top: .1rem;
 
 
529
  }
530
- .rc-conf strong { color: var(--green2); font-size: .9rem; }
531
 
532
- /* Main confidence bar */
 
533
  .conf-track {
534
- height: 3px; background: var(--g4);
535
- border-radius: 99px; overflow: hidden; margin-top: .5rem;
 
 
 
536
  }
 
537
  .conf-fill {
538
  height: 100%;
539
- background: linear-gradient(90deg, var(--green3), var(--green));
540
- border-radius: 99px; width: 0%;
 
541
  transition: width 1s cubic-bezier(.4,0,.2,1);
542
- box-shadow: 0 0 8px var(--green);
543
  }
544
 
545
- /* Divider */
546
  .rc-divider {
547
- height: 1px; background: var(--border);
 
548
  }
549
 
550
- /* Probability bars */
551
  .prob-list { display: flex; flex-direction: column; gap: .85rem; }
552
- .prob-row { display: flex; flex-direction: column; gap: .22rem; }
553
- .prob-meta { display: flex; justify-content: space-between; align-items: baseline; }
 
 
 
 
 
 
 
 
554
  .prob-name {
555
- font-family: var(--ff-body); font-size: .8rem;
556
- font-weight: 400; color: var(--muted);
557
- display: flex; align-items: center; gap: .4rem; text-transform: capitalize;
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  }
559
- .prob-row.top .prob-name { color: var(--green); font-weight: 600; }
560
- .prob-pct { font-family: var(--ff-mono); font-size: .68rem; color: var(--green-dim); }
561
  .prob-row.top .prob-pct { color: var(--green2); }
 
562
  .prob-track {
563
- height: 4px; background: var(--g4);
564
- border-radius: 99px; overflow: hidden;
 
 
565
  }
 
566
  .prob-fill {
567
- height: 100%; background: var(--green-dim);
568
- border-radius: 99px; width: 0%;
 
 
569
  transition: width .8s cubic-bezier(.4,0,.2,1);
570
  }
 
571
  .prob-row.top .prob-fill {
572
- background: linear-gradient(90deg, var(--green3), var(--green));
573
- box-shadow: 0 0 6px rgba(0,255,65,.4);
574
  }
575
 
576
- /* Error */
577
  .result-error {
578
- display: none; padding: 1rem;
579
- border: 1px solid rgba(255,50,50,.3);
580
- border-radius: var(--r); background: rgba(255,0,0,.05);
581
- font-family: var(--ff-mono); font-size: .75rem; color: #ff4444;
 
 
 
 
582
  line-height: 1.6;
583
  }
 
584
  .result-error.show { display: block; animation: fadeIn .3s ease; }
585
 
586
- /* ── CLASSES STRIP ── */
587
  .classes-strip {
588
- border-top: 1px solid var(--border);
589
- padding: 1rem 0;
590
- display: flex; align-items: center; gap: 1.2rem;
 
 
 
591
  flex-wrap: wrap;
 
 
592
  animation: fadeIn .9s ease .3s both;
593
  }
594
- .cs-label { font-family: var(--ff-mono); font-size: .55rem; color: var(--green-dim); letter-spacing: .15em; }
 
 
 
 
 
 
 
595
  .cs-pills { display: flex; gap: .5rem; flex-wrap: wrap; }
 
596
  .cs-pill {
597
- font-family: var(--ff-mono); font-size: .6rem;
598
- padding: .25rem .65rem;
599
- border: 1px solid var(--border); border-radius: 2px;
600
- color: var(--green-dim);
601
- transition: all .2s; cursor: default;
 
 
 
 
 
 
 
 
 
 
602
  }
603
- .cs-pill:hover { border-color: var(--green3); color: var(--green2); }
604
 
605
- /* ── FOOTER ── */
606
  footer {
607
- border-top: 1px solid var(--border);
608
- padding: 1rem 0;
609
- display: flex; justify-content: space-between; align-items: center;
 
 
 
 
 
 
 
610
  animation: fadeIn 1s ease .4s both;
611
  }
612
- footer p { font-family: var(--ff-mono); font-size: .6rem; color: var(--green-dim); }
613
 
614
- /* ── ANIMATIONS ── */
615
- @keyframes fadeIn { from{opacity:0; transform:translateY(8px)} to{opacity:1;transform:none} }
 
 
 
 
 
 
 
 
616
 
617
- /* ── RESPONSIVE ── */
618
- @media (max-width: 740px) {
619
  .main-grid { grid-template-columns: 1fr; }
 
 
 
 
 
 
 
 
 
 
 
 
620
  .hero-title { font-size: 2rem; }
621
- .model-grid { grid-template-columns: 1fr 1fr; }
 
 
 
622
  }
623
  </style>
624
  </head>
625
  <body>
 
626
 
 
 
627
 
628
- <!-- New add end -->
629
- <canvas id="brain-bg"></canvas>
630
- <div class="bg-vignette"></div>
631
- <div class="bg-orb orb-1"></div>
632
- <div class="bg-orb orb-2"></div>
633
- <div class="bg-orb orb-3"></div>
634
-
635
-
636
- <canvas id="network-bg"></canvas>
637
- <div class="bg-overlay"></div>
638
- <!-- New add-->
639
- <script>
640
- const brainCanvas = document.getElementById("brain-bg");
641
- const brainCtx = brainCanvas.getContext("2d");
642
-
643
- let brainW = 0;
644
- let brainH = 0;
645
- let brainNodes = [];
646
- let mouse = { x: null, y: null, radius: 120 };
647
-
648
- function resizeBrainCanvas() {
649
- brainW = brainCanvas.width = window.innerWidth;
650
- brainH = brainCanvas.height = window.innerHeight;
651
- createBrainNodes();
652
- }
653
-
654
- function createBrainNodes() {
655
- const count = Math.max(36, Math.floor((brainW * brainH) / 38000));
656
- brainNodes = [];
657
-
658
- for (let i = 0; i < count; i++) {
659
- brainNodes.push({
660
- x: Math.random() * brainW,
661
- y: Math.random() * brainH,
662
- vx: (Math.random() - 0.5) * 0.45,
663
- vy: (Math.random() - 0.5) * 0.45,
664
- r: Math.random() * 2.2 + 1.2,
665
- glow: Math.random() * 0.6 + 0.4
666
- });
667
- }
668
- }
669
-
670
- function drawBrainBackground() {
671
- brainCtx.clearRect(0, 0, brainW, brainH);
672
-
673
- for (let i = 0; i < brainNodes.length; i++) {
674
- const a = brainNodes[i];
675
-
676
- a.x += a.vx;
677
- a.y += a.vy;
678
-
679
- if (a.x < 0 || a.x > brainW) a.vx *= -1;
680
- if (a.y < 0 || a.y > brainH) a.vy *= -1;
681
-
682
- for (let j = i + 1; j < brainNodes.length; j++) {
683
- const b = brainNodes[j];
684
- const dx = a.x - b.x;
685
- const dy = a.y - b.y;
686
- const dist = Math.sqrt(dx * dx + dy * dy);
687
-
688
- if (dist < 120) {
689
- const alpha = (1 - dist / 120) * 0.18;
690
- brainCtx.beginPath();
691
- brainCtx.moveTo(a.x, a.y);
692
- brainCtx.lineTo(b.x, b.y);
693
- brainCtx.strokeStyle = `rgba(0,255,65,${alpha})`;
694
- brainCtx.lineWidth = 1;
695
- brainCtx.stroke();
696
- }
697
- }
698
-
699
- if (mouse.x !== null && mouse.y !== null) {
700
- const mdx = a.x - mouse.x;
701
- const mdy = a.y - mouse.y;
702
- const mdist = Math.sqrt(mdx * mdx + mdy * mdy);
703
-
704
- if (mdist < mouse.radius) {
705
- brainCtx.beginPath();
706
- brainCtx.moveTo(a.x, a.y);
707
- brainCtx.lineTo(mouse.x, mouse.y);
708
- brainCtx.strokeStyle = `rgba(0,255,65,${(1 - mdist / mouse.radius) * 0.22})`;
709
- brainCtx.lineWidth = 1;
710
- brainCtx.stroke();
711
- }
712
- }
713
-
714
- const gradient = brainCtx.createRadialGradient(a.x, a.y, 0, a.x, a.y, a.r * 8);
715
- gradient.addColorStop(0, `rgba(0,255,65,${0.95 * a.glow})`);
716
- gradient.addColorStop(0.35, `rgba(0,255,65,${0.22 * a.glow})`);
717
- gradient.addColorStop(1, "rgba(0,255,65,0)");
718
-
719
- brainCtx.beginPath();
720
- brainCtx.fillStyle = gradient;
721
- brainCtx.arc(a.x, a.y, a.r * 8, 0, Math.PI * 2);
722
- brainCtx.fill();
723
-
724
- brainCtx.beginPath();
725
- brainCtx.fillStyle = "rgba(170,255,195,0.9)";
726
- brainCtx.arc(a.x, a.y, a.r, 0, Math.PI * 2);
727
- brainCtx.fill();
728
- }
729
-
730
- requestAnimationFrame(drawBrainBackground);
731
- }
732
-
733
- window.addEventListener("resize", resizeBrainCanvas);
734
-
735
- window.addEventListener("mousemove", (e) => {
736
- mouse.x = e.clientX;
737
- mouse.y = e.clientY;
738
- });
739
-
740
- window.addEventListener("mouseleave", () => {
741
- mouse.x = null;
742
- mouse.y = null;
743
- });
744
-
745
- resizeBrainCanvas();
746
- drawBrainBackground();
747
- </script>
748
-
749
-
750
- <div class="wrapper">
751
-
752
- <!-- HEADER -->
753
- <header>
754
- <div class="logo">
755
- <div class="logo-icon">⬡</div>
756
- <div class="logo-text">SCENE<span>IQ</span></div>
757
- </div>
758
- <div class="header-right">
759
- <div class="status-dot"><div class="dot"></div> SYSTEM ONLINE</div>
760
- <div class="version">v4.0 · PARFAIT</div>
761
  </div>
762
- </header>
763
 
764
- <!-- HERO -->
765
- <div class="hero">
766
- <div class="hero-label">// Intel Image Classification · CNN</div>
767
- <h1 class="hero-title">Scene Recognition<br><em>Powered by Neural Networks</em></h1>
768
- <p class="hero-sub">Upload an image or provide a URL — select your model — get instant predictions across 6 scene categories.</p>
769
- </div>
770
-
771
- <!-- MAIN GRID -->
772
- <div class="main-grid">
773
-
774
- <!-- LEFT : inputs -->
775
- <div class="panel">
776
- <div class="panel-title">01 // MODEL_SELECT</div>
777
 
778
- <div class="model-grid">
779
- <div class="model-card active" data-fw="pytorch" onclick="selectModel(this)">
780
- <span class="mc-icon">⚡</span>
781
- <span class="mc-name">PyTorch</span>
782
- <span class="mc-sub">CNN_Torch · .pth</span>
783
- </div>
784
- <div class="model-card" data-fw="tensorflow" onclick="selectModel(this)">
785
- <span class="mc-icon">🧠</span>
786
- <span class="mc-name">TensorFlow</span>
787
- <span class="mc-sub">CNN_TF · .keras</span>
 
788
  </div>
789
- </div>
790
 
791
- <div class="panel-title">02 // INPUT_IMAGE</div>
792
 
793
- <!-- Tabs: File / URL -->
794
- <div class="input-tabs">
795
- <button class="tab-btn active" onclick="switchTab('file', this)"> FILE_UPLOAD</button>
796
- <button class="tab-btn" onclick="switchTab('url', this)">⬡ IMAGE_URL</button>
797
- </div>
798
 
799
- <!-- File drop zone -->
800
- <div id="tab-file">
801
- <div class="drop-zone" id="drop-zone" onclick="document.getElementById('file-input').click()">
802
- <div class="dz-corner tl"></div>
803
- <div class="dz-corner tr"></div>
804
- <div class="dz-corner bl"></div>
805
- <div class="dz-corner br"></div>
806
- <img id="preview-img" src="" alt="preview"/>
807
- <div class="dz-ph">
808
- <div class="dz-icon"></div>
809
- <p>Drop image here or click</p>
810
- <em>JPG · PNG · WEBP · GIF</em>
811
  </div>
 
812
  </div>
813
- <input type="file" id="file-input" accept="image/*"/>
814
- </div>
815
 
816
- <!-- URL input -->
817
- <div id="tab-url" class="url-input-wrap">
818
- <div class="url-field">
819
- <span class="url-prefix">URL://</span>
820
- <input type="text" class="url-input" id="url-input"
821
- placeholder="https://example.com/image.jpg"/>
822
- <button class="url-load-btn" onclick="loadFromUrl()">LOAD</button>
823
  </div>
824
- <img id="url-preview" class="url-preview" src="" alt="URL preview"/>
 
 
 
 
825
  </div>
826
 
827
- <div class="panel-title" style="margin-top:1.2rem;">03 // ANALYZE</div>
828
- <button class="classify-btn" id="classify-btn" disabled onclick="classify()">
829
- RUN CLASSIFICATION →
830
- </button>
831
- </div>
832
 
833
- <!-- RIGHT : results -->
834
- <div class="panel">
835
- <div class="panel-title">04 // SCAN_RESULTS</div>
836
-
837
- <!-- Waiting -->
838
- <div class="result-waiting" id="result-waiting">
839
- <div class="rw-title">AWAITING INPUT</div>
840
- <div class="rw-sub">
841
- &gt; select_model()<br>
842
- &gt; load_image() <span class="terminal-cursor"></span><br>
843
- &gt; predict()
844
  </div>
845
- </div>
846
 
847
- <!-- Results -->
848
- <div class="result-content" id="result-content">
849
- <div class="rc-header">
850
- <div class="rc-label">// PREDICTED_CLASS</div>
851
- <div class="rc-class" id="rc-class">—</div>
852
- <div class="rc-conf">confidence : <strong id="rc-conf"></strong></div>
853
- <div class="conf-track"><div class="conf-fill" id="conf-fill"></div></div>
854
- </div>
855
- <div class="rc-divider"></div>
856
- <div>
857
- <div class="panel-title" style="margin-bottom:.9rem;">// CLASS_SCORES</div>
858
- <div class="prob-list" id="prob-list"></div>
859
  </div>
860
- </div>
861
 
862
- <!-- Error -->
863
- <div class="result-error" id="result-error"></div>
864
  </div>
865
 
866
- </div>
867
-
868
- <!-- CLASSES STRIP -->
869
- <div class="classes-strip">
870
- <span class="cs-label">CLASSES</span>
871
- <div class="cs-pills">
872
- <span class="cs-pill">🏙 buildings</span>
873
- <span class="cs-pill">🌲 forest</span>
874
- <span class="cs-pill">🧊 glacier</span>
875
- <span class="cs-pill">⛰ mountain</span>
876
- <span class="cs-pill">🌊 sea</span>
877
- <span class="cs-pill">🛣 street</span>
878
  </div>
879
- </div>
880
 
881
- <footer>
882
- <p>SCENEIQ · Intel Image Classification · CNN PyTorch &amp; TensorFlow</p>
883
- <p>by PARFAIT · seed=42 · reproducible</p>
884
- </footer>
885
 
886
- </div><!-- /wrapper -->
 
887
 
888
  <script>
889
  const EMOJIS = {buildings:"🏙",forest:"🌲",glacier:"🧊",mountain:"⛰",sea:"🌊",street:"🛣"};
890
  const CLASSES = ["buildings","forest","glacier","mountain","sea","street"];
891
 
892
- let selectedFw = "pytorch";
893
  let selectedFile = null;
894
  let urlImageReady = false;
895
- let currentTab = "file";
896
 
897
- /* ── Model selector ── */
898
  function selectModel(card) {
899
  document.querySelectorAll(".model-card").forEach(c => c.classList.remove("active"));
900
  card.classList.add("active");
901
  selectedFw = card.dataset.fw;
902
  }
903
 
904
- /* ── Tab switcher ── */
905
  function switchTab(tab, btn) {
906
  currentTab = tab;
907
  document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
908
  btn.classList.add("active");
909
  document.getElementById("tab-file").style.display = tab === "file" ? "block" : "none";
910
- const urlWrap = document.getElementById("tab-url");
911
- urlWrap.classList.toggle("show", tab === "url");
912
  updateBtn();
 
913
  }
914
 
915
- /* ── File drag & drop ── */
916
  const dropZone = document.getElementById("drop-zone");
917
  const fileInput = document.getElementById("file-input");
918
 
919
  fileInput.addEventListener("change", () => {
920
  if (fileInput.files[0]) loadFile(fileInput.files[0]);
921
  });
922
- dropZone.addEventListener("dragover", e => { e.preventDefault(); dropZone.classList.add("drag"); });
 
 
 
 
 
923
  dropZone.addEventListener("dragleave", () => dropZone.classList.remove("drag"));
 
924
  dropZone.addEventListener("drop", e => {
925
- e.preventDefault(); dropZone.classList.remove("drag");
 
926
  const f = e.dataTransfer.files[0];
927
  if (f && f.type.startsWith("image/")) loadFile(f);
928
  });
@@ -940,10 +997,10 @@
940
  updateBtn();
941
  }
942
 
943
- /* ── URL load ── */
944
  function loadFromUrl() {
945
  const url = document.getElementById("url-input").value.trim();
946
  if (!url) return;
 
947
  const preview = document.getElementById("url-preview");
948
  preview.onload = () => {
949
  preview.classList.add("show");
@@ -951,27 +1008,26 @@
951
  resetResult();
952
  updateBtn();
953
  };
 
954
  preview.onerror = () => {
955
  preview.classList.remove("show");
956
  urlImageReady = false;
957
  updateBtn();
958
  };
 
959
  preview.src = url;
960
  }
961
 
962
- // Allow Enter key on URL input
963
  document.getElementById("url-input").addEventListener("keydown", e => {
964
  if (e.key === "Enter") loadFromUrl();
965
  });
966
 
967
- /* ── Update classify button ── */
968
  function updateBtn() {
969
  const ready = (currentTab === "file" && selectedFile) ||
970
- (currentTab === "url" && urlImageReady);
971
  document.getElementById("classify-btn").disabled = !ready;
972
  }
973
 
974
- /* ── Classify ── */
975
  async function classify() {
976
  const btn = document.getElementById("classify-btn");
977
  btn.disabled = true;
@@ -988,22 +1044,21 @@
988
  }
989
 
990
  try {
991
- const res = await fetch("/predict", { method: "POST", body: form });
992
  const data = await res.json();
993
  if (data.error) throw new Error(data.error);
994
  showResult(data);
995
- } catch(err) {
996
  showError(err.message);
997
  } finally {
998
  btn.textContent = "RUN CLASSIFICATION →";
999
  btn.classList.remove("loading");
1000
- btn.disabled = false;
1001
  }
1002
  }
1003
 
1004
- /* ── Show result ── */
1005
  function showResult(data) {
1006
- document.getElementById("result-waiting").style.display = "none";
1007
  document.getElementById("result-error").classList.remove("show");
1008
 
1009
  const pct = Math.round(data.confidence * 100);
@@ -1013,20 +1068,20 @@
1013
 
1014
  const content = document.getElementById("result-content");
1015
  content.classList.remove("show");
1016
- void content.offsetWidth; // force reflow for animation
1017
  content.classList.add("show");
1018
 
1019
  setTimeout(() => {
1020
  document.getElementById("conf-fill").style.width = pct + "%";
1021
  }, 60);
1022
 
1023
- // Probability bars
1024
  const list = document.getElementById("prob-list");
1025
  list.innerHTML = "";
1026
- const sorted = [...CLASSES].sort((a,b) => data.probabilities[b] - data.probabilities[a]);
 
1027
 
1028
  sorted.forEach((cls, i) => {
1029
- const p = Math.round(data.probabilities[cls] * 100);
1030
  const top = cls === data.class;
1031
  const row = document.createElement("div");
1032
  row.className = "prob-row" + (top ? " top" : "");
@@ -1047,7 +1102,7 @@
1047
  }
1048
 
1049
  function showError(msg) {
1050
- document.getElementById("result-waiting").style.display = "none";
1051
  document.getElementById("result-content").classList.remove("show");
1052
  const err = document.getElementById("result-error");
1053
  err.textContent = "ERROR > " + msg;
@@ -1055,14 +1110,168 @@
1055
  }
1056
 
1057
  function resetResult() {
1058
- document.getElementById("result-waiting").style.display = "flex";
1059
  document.getElementById("result-content").classList.remove("show");
1060
  document.getElementById("result-error").classList.remove("show");
1061
  document.getElementById("conf-fill").style.width = "0%";
1062
  }
1063
 
1064
- // Init
1065
  document.getElementById("tab-file").style.display = "block";
1066
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1067
  </body>
1068
- </html>
 
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;500;600;700&family=Exo+2:wght@300;400;600&display=swap" rel="stylesheet">
9
  <style>
 
 
 
10
  :root {
11
+ --g0: #000000;
12
+ --g1: #040904;
13
+ --g2: #081108;
14
+ --g3: #0d1b0d;
15
+ --g4: #122512;
16
+
17
+ --green: #00ff41;
18
+ --green2: #39ff72;
19
+ --green3: #00cc33;
20
+ --green4: #00a329;
21
+ --green-dim: #4cff88;
22
+ --green-glow: rgba(0, 255, 65, 0.18);
23
+
24
+ --text: #eafff0;
25
+ --muted: rgba(160, 255, 190, 0.72);
26
+ --muted-2: rgba(130, 220, 150, 0.48);
27
+
28
+ --border: rgba(0, 255, 65, 0.14);
29
+ --border2: rgba(0, 255, 65, 0.28);
30
+ --panel-bg: rgba(8, 17, 8, 0.72);
31
+ --panel-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
32
+
33
+ --ff-mono: 'Share Tech Mono', monospace;
34
+ --ff-head: 'Rajdhani', sans-serif;
35
+ --ff-body: 'Exo 2', sans-serif;
36
+ --r: 18px;
37
+ }
38
+
39
+ * { box-sizing: border-box; margin: 0; padding: 0; }
40
+
41
+ html, body {
42
+ width: 100%;
43
+ min-height: 100%;
44
+ overflow-x: hidden;
45
+ }
46
 
47
  body {
48
+ background:
49
+ radial-gradient(circle at 15% 20%, rgba(0,255,65,0.07), transparent 28%),
50
+ radial-gradient(circle at 85% 15%, rgba(0,255,65,0.05), transparent 24%),
51
+ radial-gradient(circle at 50% 100%, rgba(0,255,65,0.04), transparent 35%),
52
+ linear-gradient(180deg, #030603 0%, #050b05 45%, #000000 100%);
53
+ color: var(--text);
54
  font-family: var(--ff-body);
55
  min-height: 100vh;
 
56
  cursor: default;
57
+ }
58
 
59
+ #plexus-bg {
60
+ position: fixed;
61
+ inset: 0;
62
+ width: 100%;
63
+ height: 100%;
64
+ z-index: 0;
65
+ pointer-events: none;
66
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
 
68
  body::before {
69
  content: '';
70
+ position: fixed;
71
+ inset: 0;
72
+ z-index: 0;
73
+ pointer-events: none;
74
  background-image:
75
+ linear-gradient(rgba(0,255,65,0.025) 1px, transparent 1px),
76
+ linear-gradient(90deg, rgba(0,255,65,0.025) 1px, transparent 1px);
77
+ background-size: 42px 42px;
78
  }
79
+
80
  body::after {
81
  content: '';
82
+ position: fixed;
83
+ inset: 0;
84
+ z-index: 0;
85
+ pointer-events: none;
86
+ background:
87
+ radial-gradient(circle at center, transparent 0%, rgba(0,0,0,0.12) 65%, rgba(0,0,0,0.3) 100%),
88
+ repeating-linear-gradient(
89
+ 0deg,
90
+ transparent,
91
+ transparent 2px,
92
+ rgba(0, 0, 0, 0.05) 2px,
93
+ rgba(0, 0, 0, 0.05) 4px
94
+ );
95
+ }
96
+
97
+ .page {
98
+ position: relative;
99
+ z-index: 1;
100
+ min-height: 100vh;
101
+ display: grid;
102
+ place-items: center;
103
+ padding: 24px;
104
+ }
105
+
106
  .wrapper {
107
+ width: min(1140px, 100%);
108
+ display: flex;
109
+ flex-direction: column;
110
+ gap: 18px;
111
+ }
112
+
113
+ header,
114
+ .hero,
115
+ .main-grid,
116
+ .classes-strip,
117
+ footer {
118
+ backdrop-filter: blur(10px);
119
+ -webkit-backdrop-filter: blur(10px);
120
  }
121
 
 
122
  header {
123
+ border: 1px solid var(--border);
124
+ border-radius: var(--r);
125
+ padding: 1.2rem 1.4rem;
126
  display: flex;
127
  align-items: center;
128
  justify-content: space-between;
129
+ gap: 1rem;
130
+ background: rgba(8, 17, 8, 0.62);
131
+ box-shadow: var(--panel-shadow);
132
  animation: fadeIn .6s ease both;
133
  }
134
+
135
  .logo {
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 1rem;
139
  }
140
+
141
  .logo-icon {
142
+ width: 42px;
143
+ height: 42px;
144
  border: 1.5px solid var(--green);
145
+ display: grid;
146
+ place-items: center;
147
+ border-radius: 12px;
148
  font-size: 1.1rem;
149
+ color: var(--green);
150
+ box-shadow: 0 0 16px var(--green-glow), inset 0 0 10px var(--green-glow);
151
  animation: pulse-box 3s ease-in-out infinite;
152
  }
153
+
154
  @keyframes pulse-box {
155
  0%,100% { box-shadow: 0 0 10px var(--green-glow), inset 0 0 6px var(--green-glow); }
156
+ 50% { box-shadow: 0 0 22px rgba(0,255,65,.24), inset 0 0 12px rgba(0,255,65,.18); }
157
  }
158
+
159
  .logo-text {
160
  font-family: var(--ff-head);
161
+ font-size: 1.9rem;
162
+ font-weight: 700;
163
+ letter-spacing: .12em;
164
+ color: var(--text);
165
+ text-shadow: 0 0 20px rgba(0,255,65,.22);
166
  }
167
+
168
+ .logo-text span { color: var(--green2); }
169
+
170
  .header-right {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 1rem;
174
+ flex-wrap: wrap;
175
  }
176
+
177
+ .status-dot, .version {
178
+ font-family: var(--ff-mono);
179
+ font-size: .68rem;
180
+ color: var(--muted);
181
+ }
182
+
183
  .status-dot {
184
+ display: flex;
185
+ align-items: center;
186
+ gap: .45rem;
187
  }
188
+
189
  .dot {
190
+ width: 8px;
191
+ height: 8px;
192
+ border-radius: 50%;
193
  background: var(--green);
194
+ box-shadow: 0 0 10px var(--green);
195
  animation: blink 2s step-end infinite;
196
  }
 
 
 
 
 
197
 
198
+ @keyframes blink { 0%,100%{opacity:1} 50%{opacity:.25} }
199
+
200
  .hero {
201
+ border: 1px solid var(--border);
202
+ border-radius: 24px;
203
+ padding: 2.4rem 2rem 2rem;
204
+ background: rgba(8, 17, 8, 0.56);
205
+ text-align: center;
206
+ box-shadow: var(--panel-shadow);
207
  animation: fadeIn .7s ease .1s both;
208
  }
209
+
210
  .hero-label {
211
+ font-family: var(--ff-mono);
212
+ font-size: .68rem;
213
+ color: var(--green2);
214
+ letter-spacing: .22em;
215
+ text-transform: uppercase;
216
+ margin-bottom: .8rem;
217
  }
218
+
219
  .hero-title {
220
  font-family: var(--ff-head);
221
+ font-size: clamp(2.2rem, 4vw, 3.6rem);
222
+ font-weight: 700;
223
+ letter-spacing: -.01em;
224
+ line-height: 1.02;
225
+ color: var(--text);
226
+ text-shadow: 0 0 36px rgba(0,255,65,.12);
227
  }
228
+
229
  .hero-title em {
230
+ font-style: normal;
231
+ color: var(--green2);
232
+ text-shadow: 0 0 26px rgba(0,255,65,.25);
233
  }
234
+
235
  .hero-sub {
236
+ margin: .9rem auto 0;
237
+ max-width: 760px;
238
+ font-size: .98rem;
239
+ color: var(--muted);
240
+ font-family: var(--ff-mono);
241
+ line-height: 1.7;
242
  }
243
 
 
244
  .main-grid {
245
  display: grid;
246
  grid-template-columns: 1fr 1fr;
247
+ gap: 1.2rem;
 
248
  animation: fadeIn .8s ease .2s both;
249
  }
250
 
 
251
  .panel {
252
+ background: var(--panel-bg);
253
  border: 1px solid var(--border);
254
+ border-radius: 24px;
255
+ padding: 1.4rem;
256
  position: relative;
257
  overflow: hidden;
258
+ box-shadow: var(--panel-shadow);
259
+ min-height: 540px;
260
+ display: flex;
261
+ flex-direction: column;
262
  }
263
+
264
  .panel::before {
265
  content: '';
266
+ position: absolute;
267
+ top: 0; left: 0; right: 0;
268
  height: 2px;
269
  background: linear-gradient(90deg, transparent, var(--green3), transparent);
270
  }
271
+
272
  .panel-title {
273
+ font-family: var(--ff-mono);
274
+ font-size: .64rem;
275
+ letter-spacing: .2em;
276
+ color: var(--green2);
277
+ text-transform: uppercase;
278
+ margin-bottom: 1rem;
279
+ display: flex;
280
+ align-items: center;
281
+ gap: .6rem;
282
  }
283
+
284
  .panel-title::after {
285
+ content: '';
286
+ flex: 1;
287
+ height: 1px;
288
+ background: var(--border);
289
  }
290
 
 
291
  .model-grid {
292
+ display: grid;
293
+ grid-template-columns: 1fr 1fr;
294
+ gap: .75rem;
295
+ margin-bottom: 1.2rem;
296
  }
297
+
298
  .model-card {
299
  border: 1px solid var(--border);
300
+ border-radius: 16px;
301
  padding: 1rem .9rem;
302
  cursor: pointer;
303
+ transition: all .22s;
304
+ background: rgba(255,255,255,0.02);
305
+ display: flex;
306
+ flex-direction: column;
307
+ gap: .3rem;
308
  }
309
+
310
+ .model-card:hover {
311
+ border-color: var(--green3);
312
+ background: rgba(0,255,65,0.04);
313
+ transform: translateY(-1px);
314
+ }
315
+
316
  .model-card.active {
317
  border-color: var(--green);
318
+ background: rgba(0,255,65,.08);
319
+ box-shadow: 0 0 16px rgba(0,255,65,.10), inset 0 0 12px rgba(0,255,65,.03);
320
  }
321
+
322
+ .model-card.active .mc-name { color: var(--green2); }
323
+
324
  .mc-icon { font-size: 1.2rem; }
325
+
326
  .mc-name {
327
+ font-family: var(--ff-head);
328
+ font-weight: 600;
329
+ font-size: 1rem;
330
+ color: var(--text);
331
+ }
332
+
333
+ .mc-sub {
334
+ font-family: var(--ff-mono);
335
+ font-size: .58rem;
336
+ color: var(--muted);
337
  }
 
338
 
 
339
  .input-tabs {
340
+ display: flex;
341
+ gap: .7rem;
342
+ margin-bottom: 1rem;
343
  }
344
+
345
  .tab-btn {
346
+ flex: 1;
347
+ padding: .75rem;
348
+ background: rgba(255,255,255,0.025);
349
+ border: 1px solid var(--border);
350
+ border-radius: 14px;
351
+ color: var(--muted);
352
+ font-family: var(--ff-mono);
353
+ font-size: .68rem;
354
+ letter-spacing: .08em;
355
+ cursor: pointer;
356
+ transition: all .2s;
357
+ text-transform: uppercase;
358
  }
359
+
360
+ .tab-btn:hover {
361
+ background: rgba(0,255,65,.04);
362
+ color: var(--green2);
363
+ }
364
+
365
  .tab-btn.active {
366
+ background: rgba(0,255,65,.08);
367
+ color: var(--green2);
368
+ border-color: var(--green3);
369
  box-shadow: inset 0 -2px 0 var(--green);
370
  }
371
 
 
372
  .drop-zone {
373
  border: 1.5px dashed var(--border2);
374
+ border-radius: 20px;
375
+ min-height: 220px;
376
+ display: flex;
377
+ flex-direction: column;
378
+ align-items: center;
379
+ justify-content: center;
380
+ gap: .75rem;
381
+ cursor: pointer;
382
+ transition: all .25s;
383
+ background: rgba(255,255,255,0.02);
384
+ position: relative;
385
+ overflow: hidden;
386
  }
387
+
388
  .drop-zone:hover, .drop-zone.drag {
389
  border-color: var(--green);
390
  background: rgba(0,255,65,.04);
391
  box-shadow: 0 0 20px rgba(0,255,65,.08);
392
  }
393
+
394
  .drop-zone.has-img .dz-ph { display: none; }
395
+
396
  #preview-img {
397
+ position: absolute;
398
+ inset: 0;
399
+ width: 100%;
400
+ height: 100%;
401
+ object-fit: cover;
402
+ display: none;
403
+ opacity: .9;
404
  }
405
+
406
  .drop-zone.has-img #preview-img { display: block; }
407
+
408
  .dz-corner {
409
+ position: absolute;
410
+ width: 14px;
411
+ height: 14px;
412
+ border-color: var(--green3);
413
+ border-style: solid;
414
  }
415
+
416
  .dz-corner.tl { top: 8px; left: 8px; border-width: 1.5px 0 0 1.5px; }
417
  .dz-corner.tr { top: 8px; right: 8px; border-width: 1.5px 1.5px 0 0; }
418
  .dz-corner.bl { bottom: 8px; left: 8px; border-width: 0 0 1.5px 1.5px; }
419
  .dz-corner.br { bottom: 8px; right: 8px; border-width: 0 1.5px 1.5px 0; }
420
+
421
  .dz-ph {
422
+ display: flex;
423
+ flex-direction: column;
424
+ align-items: center;
425
+ gap: .6rem;
426
  pointer-events: none;
427
+ text-align: center;
428
+ padding: 1rem;
429
  }
430
+
431
  .dz-icon {
432
+ width: 52px;
433
+ height: 52px;
434
+ border: 1px solid var(--border2);
435
+ border-radius: 50%;
436
+ display: grid;
437
+ place-items: center;
438
+ font-size: 1.3rem;
439
+ color: var(--green2);
440
+ box-shadow: inset 0 0 12px rgba(0,255,65,.05);
441
+ }
442
+
443
+ .dz-ph p {
444
+ font-family: var(--ff-mono);
445
+ font-size: .76rem;
446
+ color: var(--text);
447
+ }
448
+
449
+ .dz-ph em {
450
+ font-family: var(--ff-mono);
451
+ font-size: .62rem;
452
+ color: var(--muted-2);
453
+ font-style: normal;
454
  }
455
+
 
456
  #file-input { display: none; }
457
 
458
+ .url-input-wrap { display: none; flex-direction: column; gap: .8rem; }
 
459
  .url-input-wrap.show { display: flex; }
460
+
461
  .url-field {
462
+ display: flex;
463
+ align-items: center;
464
+ border: 1px solid var(--border2);
465
+ border-radius: 16px;
466
+ background: rgba(255,255,255,0.02);
467
+ overflow: hidden;
468
  }
469
+
470
  .url-prefix {
471
+ font-family: var(--ff-mono);
472
+ font-size: .65rem;
473
+ color: var(--green2);
474
+ padding: 0 .8rem;
475
+ white-space: nowrap;
476
  border-right: 1px solid var(--border);
477
  }
478
+
479
  .url-input {
480
+ flex: 1;
481
+ background: transparent;
482
+ border: none;
483
+ outline: none;
484
+ color: var(--text);
485
+ font-family: var(--ff-mono);
486
+ font-size: .76rem;
487
+ padding: .85rem .9rem;
488
  caret-color: var(--green);
489
  }
490
+
491
+ .url-input::placeholder { color: var(--muted-2); }
492
+
493
  .url-load-btn {
494
+ padding: .8rem 1rem;
495
+ background: rgba(0,255,65,.08);
496
+ border: none;
497
  border-left: 1px solid var(--border);
498
+ color: var(--green2);
499
+ font-family: var(--ff-mono);
500
+ font-size: .68rem;
501
+ cursor: pointer;
502
+ transition: background .2s;
503
+ white-space: nowrap;
504
  }
505
+
506
+ .url-load-btn:hover { background: rgba(0,255,65,.16); }
507
+
508
  .url-preview {
509
+ width: 100%;
510
+ height: 170px;
511
+ object-fit: cover;
512
+ border-radius: 16px;
513
+ border: 1px solid var(--border);
514
  display: none;
515
  }
516
+
517
  .url-preview.show { display: block; }
518
 
 
519
  .classify-btn {
520
+ width: 100%;
521
+ margin-top: auto;
522
  padding: 1rem;
523
+ background: linear-gradient(135deg, var(--green3), #053b12);
524
+ border: 1px solid var(--green3);
525
+ border-radius: 16px;
526
+ color: var(--text);
527
+ font-family: var(--ff-head);
528
+ font-size: 1.05rem;
529
+ font-weight: 700;
530
+ letter-spacing: .08em;
531
+ cursor: pointer;
532
+ position: relative;
533
+ overflow: hidden;
534
+ transition: all .2s;
535
+ text-transform: uppercase;
536
+ box-shadow: 0 12px 24px rgba(0,255,65,.08);
537
  }
538
+
539
  .classify-btn::before {
540
  content: '';
541
+ position: absolute;
542
+ top: -2px;
543
+ left: -100%;
544
+ width: 60%;
545
+ height: calc(100% + 4px);
546
+ background: linear-gradient(90deg, transparent, rgba(0,255,65,.16), transparent);
547
  transform: skewX(-15deg);
548
  }
549
+
550
+ .classify-btn.loading::before { animation: sweep 1.2s linear infinite; }
551
+
552
  @keyframes sweep { to { left: 150%; } }
553
+
554
  .classify-btn:not(:disabled):hover {
555
  background: linear-gradient(135deg, var(--green2), var(--green3));
556
+ color: #031108;
557
+ box-shadow: 0 0 24px rgba(0,255,65,.22);
558
  transform: translateY(-1px);
559
  }
 
560
 
561
+ .classify-btn:disabled { opacity: .35; cursor: default; }
 
 
562
 
 
563
  .result-waiting {
564
+ display: flex;
565
+ flex-direction: column;
566
+ align-items: flex-start;
567
+ justify-content: center;
568
+ min-height: 220px;
569
+ gap: .6rem;
570
  padding: 1rem 0;
571
  }
572
+
573
  .result-waiting .rw-title {
574
+ font-family: var(--ff-head);
575
+ font-size: 1.15rem;
576
+ font-weight: 600;
577
+ color: var(--green2);
578
  }
579
+
580
  .result-waiting .rw-sub {
581
+ font-family: var(--ff-mono);
582
+ font-size: .76rem;
583
+ color: var(--muted);
584
+ line-height: 1.8;
585
  }
586
+
587
  .terminal-cursor {
588
+ display: inline-block;
589
+ width: 8px;
590
+ height: 14px;
591
+ background: var(--green);
592
+ margin-left: 2px;
593
  animation: blink-c .8s step-end infinite;
594
  vertical-align: middle;
595
  }
596
+
597
  @keyframes blink-c { 0%,100%{opacity:1} 50%{opacity:0} }
598
 
 
599
  .result-content { display: none; flex-direction: column; gap: 1.4rem; }
600
  .result-content.show { display: flex; animation: scanIn .4s ease; }
601
+
602
  @keyframes scanIn {
603
  from { opacity: 0; clip-path: inset(0 0 100% 0); }
604
+ to { opacity: 1; clip-path: inset(0 0 0% 0); }
605
  }
606
 
607
  .rc-header { display: flex; flex-direction: column; gap: .25rem; }
608
+
609
  .rc-label {
610
+ font-family: var(--ff-mono);
611
+ font-size: .62rem;
612
+ letter-spacing: .2em;
613
+ color: var(--green2);
614
  }
615
+
616
  .rc-class {
617
  font-family: var(--ff-head);
618
+ font-size: 3rem;
619
+ font-weight: 700;
620
+ letter-spacing: -.01em;
621
+ line-height: 1;
622
+ color: var(--green2);
623
+ text-shadow: 0 0 30px rgba(0,255,65,.28);
624
  }
625
+
626
  .rc-conf {
627
+ font-family: var(--ff-mono);
628
+ font-size: .82rem;
629
+ color: var(--muted);
630
+ margin-top: .1rem;
631
  }
 
632
 
633
+ .rc-conf strong { color: var(--text); font-size: .92rem; }
634
+
635
  .conf-track {
636
+ height: 6px;
637
+ background: var(--g4);
638
+ border-radius: 99px;
639
+ overflow: hidden;
640
+ margin-top: .6rem;
641
  }
642
+
643
  .conf-fill {
644
  height: 100%;
645
+ background: linear-gradient(90deg, var(--green3), var(--green2));
646
+ border-radius: 99px;
647
+ width: 0%;
648
  transition: width 1s cubic-bezier(.4,0,.2,1);
649
+ box-shadow: 0 0 8px rgba(0,255,65,.28);
650
  }
651
 
 
652
  .rc-divider {
653
+ height: 1px;
654
+ background: var(--border);
655
  }
656
 
 
657
  .prob-list { display: flex; flex-direction: column; gap: .85rem; }
658
+
659
+ .prob-row { display: flex; flex-direction: column; gap: .22rem; }
660
+
661
+ .prob-meta {
662
+ display: flex;
663
+ justify-content: space-between;
664
+ align-items: baseline;
665
+ gap: 1rem;
666
+ }
667
+
668
  .prob-name {
669
+ font-family: var(--ff-body);
670
+ font-size: .86rem;
671
+ font-weight: 400;
672
+ color: var(--muted);
673
+ display: flex;
674
+ align-items: center;
675
+ gap: .4rem;
676
+ text-transform: capitalize;
677
+ }
678
+
679
+ .prob-row.top .prob-name { color: var(--text); font-weight: 600; }
680
+
681
+ .prob-pct {
682
+ font-family: var(--ff-mono);
683
+ font-size: .7rem;
684
+ color: var(--muted-2);
685
  }
686
+
 
687
  .prob-row.top .prob-pct { color: var(--green2); }
688
+
689
  .prob-track {
690
+ height: 5px;
691
+ background: var(--g4);
692
+ border-radius: 99px;
693
+ overflow: hidden;
694
  }
695
+
696
  .prob-fill {
697
+ height: 100%;
698
+ background: #0f3018;
699
+ border-radius: 99px;
700
+ width: 0%;
701
  transition: width .8s cubic-bezier(.4,0,.2,1);
702
  }
703
+
704
  .prob-row.top .prob-fill {
705
+ background: linear-gradient(90deg, var(--green3), var(--green2));
706
+ box-shadow: 0 0 6px rgba(0,255,65,.25);
707
  }
708
 
 
709
  .result-error {
710
+ display: none;
711
+ padding: 1rem;
712
+ border: 1px solid rgba(255,80,80,.25);
713
+ border-radius: 16px;
714
+ background: rgba(255,0,0,.05);
715
+ font-family: var(--ff-mono);
716
+ font-size: .76rem;
717
+ color: #ff7c7c;
718
  line-height: 1.6;
719
  }
720
+
721
  .result-error.show { display: block; animation: fadeIn .3s ease; }
722
 
 
723
  .classes-strip {
724
+ border: 1px solid var(--border);
725
+ border-radius: 18px;
726
+ padding: 1rem 1.2rem;
727
+ display: flex;
728
+ align-items: center;
729
+ gap: 1rem;
730
  flex-wrap: wrap;
731
+ background: rgba(8, 17, 8, 0.5);
732
+ box-shadow: var(--panel-shadow);
733
  animation: fadeIn .9s ease .3s both;
734
  }
735
+
736
+ .cs-label {
737
+ font-family: var(--ff-mono);
738
+ font-size: .58rem;
739
+ color: var(--muted-2);
740
+ letter-spacing: .15em;
741
+ }
742
+
743
  .cs-pills { display: flex; gap: .5rem; flex-wrap: wrap; }
744
+
745
  .cs-pill {
746
+ font-family: var(--ff-mono);
747
+ font-size: .62rem;
748
+ padding: .34rem .72rem;
749
+ border: 1px solid var(--border);
750
+ border-radius: 999px;
751
+ color: var(--muted);
752
+ transition: all .2s;
753
+ cursor: default;
754
+ background: rgba(255,255,255,0.02);
755
+ }
756
+
757
+ .cs-pill:hover {
758
+ border-color: var(--green3);
759
+ color: var(--text);
760
+ background: rgba(0,255,65,.05);
761
  }
 
762
 
 
763
  footer {
764
+ border: 1px solid var(--border);
765
+ border-radius: 18px;
766
+ padding: 1rem 1.2rem;
767
+ display: flex;
768
+ justify-content: space-between;
769
+ align-items: center;
770
+ gap: 1rem;
771
+ flex-wrap: wrap;
772
+ background: rgba(8, 17, 8, 0.5);
773
+ box-shadow: var(--panel-shadow);
774
  animation: fadeIn 1s ease .4s both;
775
  }
 
776
 
777
+ footer p {
778
+ font-family: var(--ff-mono);
779
+ font-size: .64rem;
780
+ color: var(--muted-2);
781
+ }
782
+
783
+ @keyframes fadeIn {
784
+ from { opacity: 0; transform: translateY(8px); }
785
+ to { opacity: 1; transform: none; }
786
+ }
787
 
788
+ @media (max-width: 900px) {
 
789
  .main-grid { grid-template-columns: 1fr; }
790
+ .panel { min-height: auto; }
791
+ }
792
+
793
+ @media (max-width: 640px) {
794
+ .page { padding: 14px; }
795
+ .wrapper { gap: 14px; }
796
+ header { padding: 1rem; }
797
+ .hero { padding: 1.6rem 1rem; }
798
+ .panel { padding: 1rem; }
799
+ .model-grid { grid-template-columns: 1fr; }
800
+ .input-tabs { flex-direction: column; }
801
+ .header-right { gap: .6rem; }
802
  .hero-title { font-size: 2rem; }
803
+ .rc-class { font-size: 2.1rem; }
804
+ .url-field { flex-direction: column; align-items: stretch; }
805
+ .url-prefix { border-right: none; border-bottom: 1px solid var(--border); padding: .8rem; }
806
+ .url-load-btn { border-left: none; border-top: 1px solid var(--border); }
807
  }
808
  </style>
809
  </head>
810
  <body>
811
+ <canvas id="plexus-bg"></canvas>
812
 
813
+ <div class="page">
814
+ <div class="wrapper">
815
 
816
+ <header>
817
+ <div class="logo">
818
+ <div class="logo-icon"></div>
819
+ <div class="logo-text">SCENE<span>IQ</span></div>
820
+ </div>
821
+ <div class="header-right">
822
+ <div class="status-dot"><div class="dot"></div> SYSTEM ONLINE</div>
823
+ <div class="version">v4.0 · PARFAIT</div>
824
+ </div>
825
+ </header>
826
+
827
+ <div class="hero">
828
+ <div class="hero-label">// AI-Powered Visual Intelligence</div>
829
+ <h1 class="hero-title">Interactive Scene Recognition<br><em>Powered by Neural Networks</em></h1>
830
+ <p class="hero-sub">
831
+ Upload an image or provide a URL, choose your model, and get instant scene predictions
832
+ through a clean, modern, and professional AI interface.
833
+ </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
834
  </div>
 
835
 
836
+ <div class="main-grid">
837
+ <div class="panel">
838
+ <div class="panel-title">01 // MODEL_SELECT</div>
 
 
 
 
 
 
 
 
 
 
839
 
840
+ <div class="model-grid">
841
+ <div class="model-card active" data-fw="pytorch" onclick="selectModel(this)">
842
+ <span class="mc-icon">⚡</span>
843
+ <span class="mc-name">PyTorch</span>
844
+ <span class="mc-sub">CNN_Torch · .pth</span>
845
+ </div>
846
+ <div class="model-card" data-fw="tensorflow" onclick="selectModel(this)">
847
+ <span class="mc-icon">🧠</span>
848
+ <span class="mc-name">TensorFlow</span>
849
+ <span class="mc-sub">CNN_TF · .keras</span>
850
+ </div>
851
  </div>
 
852
 
853
+ <div class="panel-title">02 // INPUT_IMAGE</div>
854
 
855
+ <div class="input-tabs">
856
+ <button class="tab-btn active" onclick="switchTab('file', this)">↑ FILE_UPLOAD</button>
857
+ <button class="tab-btn" onclick="switchTab('url', this)"> IMAGE_URL</button>
858
+ </div>
 
859
 
860
+ <div id="tab-file">
861
+ <div class="drop-zone" id="drop-zone" onclick="document.getElementById('file-input').click()">
862
+ <div class="dz-corner tl"></div>
863
+ <div class="dz-corner tr"></div>
864
+ <div class="dz-corner bl"></div>
865
+ <div class="dz-corner br"></div>
866
+ <img id="preview-img" src="" alt="preview"/>
867
+ <div class="dz-ph">
868
+ <div class="dz-icon">↑</div>
869
+ <p>Drop image here or click</p>
870
+ <em>JPG · PNG · WEBP · GIF</em>
871
+ </div>
872
  </div>
873
+ <input type="file" id="file-input" accept="image/*"/>
874
  </div>
 
 
875
 
876
+ <div id="tab-url" class="url-input-wrap">
877
+ <div class="url-field">
878
+ <span class="url-prefix">URL://</span>
879
+ <input type="text" class="url-input" id="url-input" placeholder="https://example.com/image.jpg"/>
880
+ <button class="url-load-btn" onclick="loadFromUrl()">LOAD</button>
881
+ </div>
882
+ <img id="url-preview" class="url-preview" src="" alt="URL preview"/>
883
  </div>
884
+
885
+ <div class="panel-title" style="margin-top:1.2rem;">03 // ANALYZE</div>
886
+ <button class="classify-btn" id="classify-btn" disabled onclick="classify()">
887
+ RUN CLASSIFICATION →
888
+ </button>
889
  </div>
890
 
891
+ <div class="panel">
892
+ <div class="panel-title">04 // SCAN_RESULTS</div>
 
 
 
893
 
894
+ <div class="result-waiting" id="result-waiting">
895
+ <div class="rw-title">AWAITING INPUT</div>
896
+ <div class="rw-sub">
897
+ &gt; select_model()<br>
898
+ &gt; load_image() <span class="terminal-cursor"></span><br>
899
+ &gt; predict()
900
+ </div>
 
 
 
 
901
  </div>
 
902
 
903
+ <div class="result-content" id="result-content">
904
+ <div class="rc-header">
905
+ <div class="rc-label">// PREDICTED_CLASS</div>
906
+ <div class="rc-class" id="rc-class">—</div>
907
+ <div class="rc-conf">confidence : <strong id="rc-conf">—</strong></div>
908
+ <div class="conf-track"><div class="conf-fill" id="conf-fill"></div></div>
909
+ </div>
910
+ <div class="rc-divider"></div>
911
+ <div>
912
+ <div class="panel-title" style="margin-bottom:.9rem;">// CLASS_SCORES</div>
913
+ <div class="prob-list" id="prob-list"></div>
914
+ </div>
915
  </div>
 
916
 
917
+ <div class="result-error" id="result-error"></div>
918
+ </div>
919
  </div>
920
 
921
+ <div class="classes-strip">
922
+ <span class="cs-label">CLASSES</span>
923
+ <div class="cs-pills">
924
+ <span class="cs-pill">🏙 buildings</span>
925
+ <span class="cs-pill">🌲 forest</span>
926
+ <span class="cs-pill">🧊 glacier</span>
927
+ <span class="cs-pill"> mountain</span>
928
+ <span class="cs-pill">🌊 sea</span>
929
+ <span class="cs-pill">🛣 street</span>
930
+ </div>
 
 
931
  </div>
 
932
 
933
+ <footer>
934
+ <p>SCENEIQ · Intel Image Classification · CNN PyTorch &amp; TensorFlow</p>
935
+ <p>by PARFAIT · seed=42 · reproducible</p>
936
+ </footer>
937
 
938
+ </div>
939
+ </div>
940
 
941
  <script>
942
  const EMOJIS = {buildings:"🏙",forest:"🌲",glacier:"🧊",mountain:"⛰",sea:"🌊",street:"🛣"};
943
  const CLASSES = ["buildings","forest","glacier","mountain","sea","street"];
944
 
945
+ let selectedFw = "pytorch";
946
  let selectedFile = null;
947
  let urlImageReady = false;
948
+ let currentTab = "file";
949
 
 
950
  function selectModel(card) {
951
  document.querySelectorAll(".model-card").forEach(c => c.classList.remove("active"));
952
  card.classList.add("active");
953
  selectedFw = card.dataset.fw;
954
  }
955
 
 
956
  function switchTab(tab, btn) {
957
  currentTab = tab;
958
  document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
959
  btn.classList.add("active");
960
  document.getElementById("tab-file").style.display = tab === "file" ? "block" : "none";
961
+ document.getElementById("tab-url").classList.toggle("show", tab === "url");
 
962
  updateBtn();
963
+ resetResult();
964
  }
965
 
 
966
  const dropZone = document.getElementById("drop-zone");
967
  const fileInput = document.getElementById("file-input");
968
 
969
  fileInput.addEventListener("change", () => {
970
  if (fileInput.files[0]) loadFile(fileInput.files[0]);
971
  });
972
+
973
+ dropZone.addEventListener("dragover", e => {
974
+ e.preventDefault();
975
+ dropZone.classList.add("drag");
976
+ });
977
+
978
  dropZone.addEventListener("dragleave", () => dropZone.classList.remove("drag"));
979
+
980
  dropZone.addEventListener("drop", e => {
981
+ e.preventDefault();
982
+ dropZone.classList.remove("drag");
983
  const f = e.dataTransfer.files[0];
984
  if (f && f.type.startsWith("image/")) loadFile(f);
985
  });
 
997
  updateBtn();
998
  }
999
 
 
1000
  function loadFromUrl() {
1001
  const url = document.getElementById("url-input").value.trim();
1002
  if (!url) return;
1003
+
1004
  const preview = document.getElementById("url-preview");
1005
  preview.onload = () => {
1006
  preview.classList.add("show");
 
1008
  resetResult();
1009
  updateBtn();
1010
  };
1011
+
1012
  preview.onerror = () => {
1013
  preview.classList.remove("show");
1014
  urlImageReady = false;
1015
  updateBtn();
1016
  };
1017
+
1018
  preview.src = url;
1019
  }
1020
 
 
1021
  document.getElementById("url-input").addEventListener("keydown", e => {
1022
  if (e.key === "Enter") loadFromUrl();
1023
  });
1024
 
 
1025
  function updateBtn() {
1026
  const ready = (currentTab === "file" && selectedFile) ||
1027
+ (currentTab === "url" && urlImageReady);
1028
  document.getElementById("classify-btn").disabled = !ready;
1029
  }
1030
 
 
1031
  async function classify() {
1032
  const btn = document.getElementById("classify-btn");
1033
  btn.disabled = true;
 
1044
  }
1045
 
1046
  try {
1047
+ const res = await fetch("/predict", { method: "POST", body: form });
1048
  const data = await res.json();
1049
  if (data.error) throw new Error(data.error);
1050
  showResult(data);
1051
+ } catch (err) {
1052
  showError(err.message);
1053
  } finally {
1054
  btn.textContent = "RUN CLASSIFICATION →";
1055
  btn.classList.remove("loading");
1056
+ updateBtn();
1057
  }
1058
  }
1059
 
 
1060
  function showResult(data) {
1061
+ document.getElementById("result-waiting").style.display = "none";
1062
  document.getElementById("result-error").classList.remove("show");
1063
 
1064
  const pct = Math.round(data.confidence * 100);
 
1068
 
1069
  const content = document.getElementById("result-content");
1070
  content.classList.remove("show");
1071
+ void content.offsetWidth;
1072
  content.classList.add("show");
1073
 
1074
  setTimeout(() => {
1075
  document.getElementById("conf-fill").style.width = pct + "%";
1076
  }, 60);
1077
 
 
1078
  const list = document.getElementById("prob-list");
1079
  list.innerHTML = "";
1080
+
1081
+ const sorted = [...CLASSES].sort((a, b) => data.probabilities[b] - data.probabilities[a]);
1082
 
1083
  sorted.forEach((cls, i) => {
1084
+ const p = Math.round(data.probabilities[cls] * 100);
1085
  const top = cls === data.class;
1086
  const row = document.createElement("div");
1087
  row.className = "prob-row" + (top ? " top" : "");
 
1102
  }
1103
 
1104
  function showError(msg) {
1105
+ document.getElementById("result-waiting").style.display = "none";
1106
  document.getElementById("result-content").classList.remove("show");
1107
  const err = document.getElementById("result-error");
1108
  err.textContent = "ERROR > " + msg;
 
1110
  }
1111
 
1112
  function resetResult() {
1113
+ document.getElementById("result-waiting").style.display = "flex";
1114
  document.getElementById("result-content").classList.remove("show");
1115
  document.getElementById("result-error").classList.remove("show");
1116
  document.getElementById("conf-fill").style.width = "0%";
1117
  }
1118
 
 
1119
  document.getElementById("tab-file").style.display = "block";
1120
  </script>
1121
+
1122
+ <script>
1123
+ (function() {
1124
+ const canvas = document.getElementById('plexus-bg');
1125
+ const ctx = canvas.getContext('2d');
1126
+
1127
+ const NODE_COLOR = 'rgba(0,255,65,';
1128
+ const LINE_COLOR = 'rgba(0,255,65,';
1129
+
1130
+ const MAX_DIST = 150;
1131
+ const MOUSE_DIST = 220;
1132
+ const ATTRACT_FORCE = 0.012;
1133
+ const SPEED = 0.4;
1134
+
1135
+ let W, H, nodes = [];
1136
+ let mouse = { x: -9999, y: -9999 };
1137
+
1138
+ function resize() {
1139
+ W = canvas.width = window.innerWidth;
1140
+ H = canvas.height = window.innerHeight;
1141
+ init();
1142
+ }
1143
+
1144
+ function getNodeCount() {
1145
+ return Math.max(70, Math.floor((W * H) / 18000));
1146
+ }
1147
+
1148
+ function Node() {
1149
+ this.x = Math.random() * W;
1150
+ this.y = Math.random() * H;
1151
+ this.vx = (Math.random() - 0.5) * SPEED;
1152
+ this.vy = (Math.random() - 0.5) * SPEED;
1153
+ this.r = Math.random() * 1.8 + 1.2;
1154
+ }
1155
+
1156
+ function init() {
1157
+ nodes = [];
1158
+ for (let i = 0; i < getNodeCount(); i++) nodes.push(new Node());
1159
+ }
1160
+
1161
+ window.addEventListener('mousemove', e => {
1162
+ mouse.x = e.clientX;
1163
+ mouse.y = e.clientY;
1164
+ });
1165
+
1166
+ window.addEventListener('mouseleave', () => {
1167
+ mouse.x = -9999;
1168
+ mouse.y = -9999;
1169
+ });
1170
+
1171
+ function draw() {
1172
+ ctx.clearRect(0, 0, W, H);
1173
+
1174
+ for (const n of nodes) {
1175
+ const dxM = mouse.x - n.x;
1176
+ const dyM = mouse.y - n.y;
1177
+ const dM = Math.sqrt(dxM * dxM + dyM * dyM);
1178
+
1179
+ if (dM < MOUSE_DIST && dM > 0) {
1180
+ const force = (1 - dM / MOUSE_DIST) * ATTRACT_FORCE;
1181
+ n.vx += dxM / dM * force;
1182
+ n.vy += dyM / dM * force;
1183
+ }
1184
+
1185
+ const spd = Math.sqrt(n.vx * n.vx + n.vy * n.vy);
1186
+ const maxSpd = SPEED * 3;
1187
+ if (spd > maxSpd) {
1188
+ n.vx = (n.vx / spd) * maxSpd;
1189
+ n.vy = (n.vy / spd) * maxSpd;
1190
+ }
1191
+
1192
+ if (dM >= MOUSE_DIST) {
1193
+ n.vx *= 0.996;
1194
+ n.vy *= 0.996;
1195
+ const spdNow = Math.sqrt(n.vx * n.vx + n.vy * n.vy);
1196
+ if (spdNow < SPEED * 0.3) {
1197
+ n.vx += (Math.random() - 0.5) * 0.05;
1198
+ n.vy += (Math.random() - 0.5) * 0.05;
1199
+ }
1200
+ }
1201
+
1202
+ n.x += n.vx;
1203
+ n.y += n.vy;
1204
+
1205
+ if (n.x < 0 || n.x > W) n.vx *= -1;
1206
+ if (n.y < 0 || n.y > H) n.vy *= -1;
1207
+ }
1208
+
1209
+ for (let i = 0; i < nodes.length; i++) {
1210
+ for (let j = i + 1; j < nodes.length; j++) {
1211
+ const a = nodes[i], b = nodes[j];
1212
+ const dx = a.x - b.x, dy = a.y - b.y;
1213
+ const d = Math.sqrt(dx * dx + dy * dy);
1214
+
1215
+ if (d < MAX_DIST) {
1216
+ const alpha = (1 - d / MAX_DIST) * 0.22;
1217
+ ctx.beginPath();
1218
+ ctx.moveTo(a.x, a.y);
1219
+ ctx.lineTo(b.x, b.y);
1220
+ ctx.strokeStyle = LINE_COLOR + alpha + ')';
1221
+ ctx.lineWidth = 0.8;
1222
+ ctx.stroke();
1223
+ }
1224
+ }
1225
+ }
1226
+
1227
+ for (const n of nodes) {
1228
+ const dxM = mouse.x - n.x;
1229
+ const dyM = mouse.y - n.y;
1230
+ const dM = Math.sqrt(dxM * dxM + dyM * dyM);
1231
+
1232
+ if (dM < MOUSE_DIST) {
1233
+ const alpha = (1 - dM / MOUSE_DIST) * 0.28;
1234
+ ctx.beginPath();
1235
+ ctx.moveTo(mouse.x, mouse.y);
1236
+ ctx.lineTo(n.x, n.y);
1237
+ ctx.strokeStyle = LINE_COLOR + alpha + ')';
1238
+ ctx.lineWidth = 0.5;
1239
+ ctx.stroke();
1240
+ }
1241
+ }
1242
+
1243
+ if (mouse.x > 0) {
1244
+ const grdC = ctx.createRadialGradient(mouse.x, mouse.y, 0, mouse.x, mouse.y, 20);
1245
+ grdC.addColorStop(0, 'rgba(0,255,65,0.35)');
1246
+ grdC.addColorStop(1, 'rgba(0,255,65,0)');
1247
+ ctx.beginPath();
1248
+ ctx.arc(mouse.x, mouse.y, 20, 0, Math.PI * 2);
1249
+ ctx.fillStyle = grdC;
1250
+ ctx.fill();
1251
+ }
1252
+
1253
+ for (const n of nodes) {
1254
+ const grd = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 5);
1255
+ grd.addColorStop(0, NODE_COLOR + '0.22)');
1256
+ grd.addColorStop(1, NODE_COLOR + '0)');
1257
+ ctx.beginPath();
1258
+ ctx.arc(n.x, n.y, n.r * 5, 0, Math.PI * 2);
1259
+ ctx.fillStyle = grd;
1260
+ ctx.fill();
1261
+
1262
+ ctx.beginPath();
1263
+ ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
1264
+ ctx.fillStyle = NODE_COLOR + '0.75)';
1265
+ ctx.fill();
1266
+ }
1267
+
1268
+ requestAnimationFrame(draw);
1269
+ }
1270
+
1271
+ resize();
1272
+ draw();
1273
+ window.addEventListener('resize', resize);
1274
+ })();
1275
+ </script>
1276
  </body>
1277
+ </html>