adema5051 commited on
Commit
e3671cf
·
verified ·
1 Parent(s): 35a9d6e

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +2 -1625
templates/index.html CHANGED
@@ -5,1050 +5,7 @@
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Flood Vulnerability Assessment</title>
8
- <style>
9
- * {
10
- margin: 0;
11
- padding: 0;
12
- box-sizing: border-box;
13
- }
14
-
15
- html,
16
- body {
17
- height: 100%;
18
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
19
- }
20
-
21
- body {
22
- background: #000;
23
- color: #fff;
24
- position: relative;
25
- overflow-x: hidden;
26
- }
27
-
28
- /* Animated background */
29
- .hero-background {
30
- position: fixed;
31
- top: 0;
32
- left: 0;
33
- width: 100%;
34
- height: 100%;
35
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
36
- z-index: 0;
37
- }
38
-
39
- .hero-background::before {
40
- content: '';
41
- position: absolute;
42
- top: 0;
43
- left: 0;
44
- width: 100%;
45
- height: 100%;
46
- background:
47
- radial-gradient(circle at 20% 50%, rgba(59, 130, 246, 0.15) 0%, transparent 50%),
48
- radial-gradient(circle at 80% 50%, rgba(139, 92, 246, 0.15) 0%, transparent 50%);
49
- animation: pulse 8s ease-in-out infinite;
50
- }
51
-
52
- @keyframes pulse {
53
-
54
- 0%,
55
- 100% {
56
- opacity: 1;
57
- }
58
-
59
- 50% {
60
- opacity: 0.5;
61
- }
62
- }
63
-
64
- .page-wrapper {
65
- position: relative;
66
- z-index: 1;
67
- min-height: 100vh;
68
- }
69
-
70
- /* Header with hero section */
71
- .hero-header {
72
- position: relative;
73
- padding: 4rem 2rem;
74
- text-align: center;
75
- background: linear-gradient(180deg, rgba(15, 23, 42, 0.9) 0%, rgba(15, 23, 42, 0.7) 100%);
76
- border-bottom: 2px solid rgba(59, 130, 246, 0.3);
77
- }
78
-
79
- .hero-header h1 {
80
- font-size: 3.5em;
81
- font-weight: 800;
82
- margin-bottom: 1rem;
83
- background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 50%, #60a5fa 100%);
84
- -webkit-background-clip: text;
85
- -webkit-text-fill-color: transparent;
86
- background-clip: text;
87
- background-size: 200% auto;
88
- animation: shine 3s linear infinite;
89
- }
90
-
91
- @keyframes shine {
92
- to {
93
- background-position: 200% center;
94
- }
95
- }
96
-
97
- .hero-header .subtitle {
98
- font-size: 1.3em;
99
- color: #94a3b8;
100
- font-weight: 300;
101
- max-width: 700px;
102
- margin: 0 auto;
103
- }
104
-
105
- /* Side navigation */
106
- .main-container {
107
- display: flex;
108
- max-width: 1400px;
109
- margin: 0 auto;
110
- padding: 2rem;
111
- gap: 2rem;
112
- }
113
-
114
- .side-nav {
115
- width: 280px;
116
- flex-shrink: 0;
117
- position: sticky;
118
- top: 2rem;
119
- height: fit-content;
120
- }
121
-
122
- .nav-card {
123
- background: rgba(30, 41, 59, 0.8);
124
- backdrop-filter: blur(20px);
125
- border-radius: 20px;
126
- padding: 1.5rem;
127
- border: 1px solid rgba(59, 130, 246, 0.2);
128
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
129
- }
130
-
131
- .nav-card h3 {
132
- color: #e2e8f0;
133
- font-size: 1.1em;
134
- margin-bottom: 1.5rem;
135
- padding-bottom: 1rem;
136
- border-bottom: 1px solid rgba(59, 130, 246, 0.2);
137
- }
138
-
139
- .nav-link {
140
- display: block;
141
- padding: 1rem 1.25rem;
142
- margin-bottom: 0.5rem;
143
- background: transparent;
144
- border: none;
145
- color: #94a3b8;
146
- text-align: left;
147
- cursor: pointer;
148
- border-radius: 12px;
149
- font-size: 0.95em;
150
- transition: all 0.3s ease;
151
- position: relative;
152
- overflow: hidden;
153
- width: 100%;
154
- }
155
-
156
- .nav-link::before {
157
- content: '';
158
- position: absolute;
159
- left: 0;
160
- top: 0;
161
- height: 100%;
162
- width: 3px;
163
- background: linear-gradient(180deg, #3b82f6, #8b5cf6);
164
- transform: scaleY(0);
165
- transition: transform 0.3s ease;
166
- }
167
-
168
- .nav-link:hover {
169
- background: rgba(59, 130, 246, 0.1);
170
- color: #e2e8f0;
171
- transform: translateX(5px);
172
- }
173
-
174
- .nav-link.active {
175
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(139, 92, 246, 0.2));
176
- color: #fff;
177
- font-weight: 600;
178
- }
179
-
180
- .nav-link.active::before {
181
- transform: scaleY(1);
182
- }
183
-
184
- /* Content area */
185
- .content-area {
186
- flex: 1;
187
- min-width: 0;
188
- }
189
-
190
- .assessment-card {
191
- display: none;
192
- background: rgba(30, 41, 59, 0.8);
193
- backdrop-filter: blur(20px);
194
- border-radius: 20px;
195
- padding: 3rem;
196
- border: 1px solid rgba(59, 130, 246, 0.2);
197
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
198
- animation: slideIn 0.4s ease;
199
- }
200
-
201
- .assessment-card.active {
202
- display: block;
203
- }
204
-
205
- @keyframes slideIn {
206
- from {
207
- opacity: 0;
208
- transform: translateY(20px);
209
- }
210
-
211
- to {
212
- opacity: 1;
213
- transform: translateY(0);
214
- }
215
- }
216
-
217
- .card-header {
218
- margin-bottom: 2rem;
219
- }
220
-
221
- .card-header h2 {
222
- font-size: 2em;
223
- color: #e2e8f0;
224
- margin-bottom: 0.5rem;
225
- }
226
-
227
- .card-header p {
228
- color: #94a3b8;
229
- font-size: 1.05em;
230
- }
231
-
232
- /* Form styling */
233
- .form-grid {
234
- display: grid;
235
- grid-template-columns: repeat(2, 1fr);
236
- gap: 1.5rem;
237
- margin-bottom: 2rem;
238
- }
239
-
240
- .input-card {
241
- background: rgba(15, 23, 42, 0.6);
242
- border: 1px solid rgba(59, 130, 246, 0.2);
243
- border-radius: 16px;
244
- padding: 1.5rem;
245
- transition: all 0.3s ease;
246
- }
247
-
248
- .input-card:hover {
249
- border-color: rgba(59, 130, 246, 0.4);
250
- background: rgba(15, 23, 42, 0.8);
251
- transform: translateY(-2px);
252
- }
253
-
254
- .input-card label {
255
- display: flex;
256
- align-items: center;
257
- gap: 0.5rem;
258
- margin-bottom: 0.75rem;
259
- color: #cbd5e1;
260
- font-weight: 600;
261
- font-size: 0.95em;
262
- }
263
-
264
- .input-card label::before {
265
- content: '';
266
- width: 4px;
267
- height: 16px;
268
- background: linear-gradient(180deg, #3b82f6, #8b5cf6);
269
- border-radius: 2px;
270
- }
271
-
272
- .input-card input,
273
- .input-card select {
274
- width: 100%;
275
- padding: 0.875rem;
276
- background: rgba(0, 0, 0, 0.3);
277
- border: 1px solid rgba(148, 163, 184, 0.2);
278
- border-radius: 10px;
279
- color: #e2e8f0;
280
- font-size: 1em;
281
- transition: all 0.3s ease;
282
- }
283
-
284
- .input-card input:focus,
285
- .input-card select:focus {
286
- outline: none;
287
- border-color: #3b82f6;
288
- background: rgba(0, 0, 0, 0.5);
289
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
290
- }
291
-
292
- .input-card .helper-text {
293
- margin-top: 0.5rem;
294
- font-size: 0.8em;
295
- color: #64748b;
296
- font-style: italic;
297
- }
298
-
299
- /* Height group with predict button */
300
- .height-group {
301
- display: flex;
302
- align-items: stretch;
303
- gap: 0;
304
- background: rgba(0, 0, 0, 0.3);
305
- border: 1px solid rgba(148, 163, 184, 0.2);
306
- border-radius: 10px;
307
- overflow: visible;
308
- transition: all 0.3s ease;
309
- }
310
-
311
- .height-group input {
312
- border-radius: 10px 0 0 10px;
313
- }
314
-
315
- .height-group button:last-child {
316
- border-radius: 0 10px 10px 0;
317
- }
318
-
319
- .input-card {
320
- overflow: visible;
321
- }
322
-
323
- .height-group:focus-within {
324
- border-color: #3b82f6;
325
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
326
- }
327
-
328
- .height-group input {
329
- flex: 1;
330
- padding: 0.875rem;
331
- background: transparent !important;
332
- border: none !important;
333
- border-radius: 0 !important;
334
- color: #e2e8f0;
335
- font-size: 1em;
336
- box-shadow: none !important;
337
- }
338
-
339
- .height-group input:focus {
340
- outline: none;
341
- }
342
-
343
- .height-group button {
344
- width: auto !important;
345
- padding: 0.875rem 1.5rem !important;
346
- background: linear-gradient(135deg, #3b82f6, #8b5cf6) !important;
347
- color: white;
348
- border: none;
349
- border-left: 1px solid rgba(148, 163, 184, 0.3);
350
- cursor: pointer;
351
- font-size: 0.9em;
352
- font-weight: 600;
353
- white-space: nowrap;
354
- transition: opacity 0.2s ease;
355
- border-radius: 0 10px 10px 0 !important;
356
- transform: none !important;
357
- }
358
-
359
- .height-group button:hover {
360
- opacity: 0.9;
361
- transform: none !important;
362
- }
363
-
364
- .height-group button:disabled {
365
- opacity: 0.5;
366
- cursor: not-allowed;
367
- }
368
-
369
- .gba-height-btn {
370
- margin-top: 0.4rem;
371
- align-self: flex-start;
372
-
373
- background: rgba(96, 165, 250, 0.08);
374
- border: 1px solid rgba(96, 165, 250, 0.25);
375
- border-radius: 8px;
376
-
377
- padding: 0.25rem 0.6rem;
378
- font-size: 0.7em;
379
- font-weight: 500;
380
-
381
- color: #93c5fd;
382
- cursor: pointer;
383
-
384
- transition: background 0.2s ease, border 0.2s ease, color 0.2s ease;
385
- }
386
-
387
- .secondary-btn-style {
388
- margin-top: 0.4rem;
389
- align-self: flex-start;
390
-
391
- background: rgba(96, 165, 250, 0.08);
392
- border: 1px solid rgba(96, 165, 250, 0.25);
393
- border-radius: 8px;
394
-
395
- padding: 0.25rem 0.6rem;
396
- font-size: 0.7em;
397
- font-weight: 500;
398
-
399
- color: #93c5fd;
400
- cursor: pointer;
401
-
402
- transition: background 0.2s ease, border 0.2s ease, color 0.2s ease;
403
- }
404
-
405
- .gba-height-btn:hover,
406
- .secondary-btn-style:hover {
407
- background: rgba(96, 165, 250, 0.18);
408
- border-color: rgba(96, 165, 250, 0.5);
409
- color: #bfdbfe;
410
- }
411
-
412
-
413
- .gba-height-btn:disabled,
414
- .secondary-btn-style:disabled {
415
- opacity: 0.4;
416
- cursor: not-allowed;
417
- }
418
-
419
-
420
- .height-group .gba-height-btn {
421
- margin-top: 0;
422
- align-self: auto;
423
- border-radius: 0;
424
- border-left: 1px solid rgba(148, 163, 184, 0.3);
425
- height: auto;
426
- background: linear-gradient(135deg, #3b82f6, #8b5cf6);
427
- color: white;
428
- border: none;
429
- padding: 0.875rem 1.5rem;
430
- }
431
-
432
- .height-group .gba-height-btn:hover {
433
- background: linear-gradient(135deg, #3b82f6, #8b5cf6);
434
- opacity: 0.9;
435
- color: white;
436
- border-color: transparent;
437
- }
438
-
439
- /* Custom tooltip styling for buttons */
440
- .tooltip-btn {
441
- position: relative;
442
- }
443
-
444
- .tooltip-btn::after {
445
- content: attr(data-tooltip);
446
- position: absolute;
447
- bottom: calc(100% + 10px);
448
- left: 50%;
449
- transform: translateX(-50%);
450
- padding: 10px 14px;
451
- background: rgba(15, 23, 42, 0.95);
452
- color: #e2e8f0;
453
- font-size: 1em;
454
- font-weight: 400;
455
- border-radius: 8px;
456
- border: 1px solid rgba(59, 130, 246, 0.3);
457
- white-space: normal;
458
- width: max-content;
459
- max-width: 280px;
460
- text-align: center;
461
- opacity: 0;
462
- visibility: hidden;
463
- transition: opacity 0.2s ease, visibility 0.2s ease;
464
- z-index: 1000;
465
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
466
- pointer-events: none;
467
- line-height: 1.4;
468
- }
469
-
470
- .tooltip-btn::before {
471
- content: '';
472
- position: absolute;
473
- bottom: calc(100% + 2px);
474
- left: 50%;
475
- transform: translateX(-50%);
476
- border-width: 8px;
477
- border-style: solid;
478
- border-color: rgba(15, 23, 42, 0.95) transparent transparent transparent;
479
- opacity: 0;
480
- visibility: hidden;
481
- transition: opacity 0.2s ease, visibility 0.2s ease;
482
- z-index: 1001;
483
- }
484
-
485
- .tooltip-btn:hover::after,
486
- .tooltip-btn:hover::before {
487
- opacity: 1;
488
- visibility: visible;
489
- }
490
-
491
- /* Checkbox styling */
492
- .checkbox-row {
493
- display: flex;
494
- align-items: center;
495
- gap: 0.75rem;
496
- padding: 1rem;
497
- background: rgba(15, 23, 42, 0.6);
498
- border: 1px solid rgba(59, 130, 246, 0.2);
499
- border-radius: 12px;
500
- cursor: pointer;
501
- transition: all 0.3s ease;
502
- }
503
-
504
- .checkbox-row:hover {
505
- background: rgba(15, 23, 42, 0.8);
506
- border-color: rgba(59, 130, 246, 0.4);
507
- }
508
-
509
- .checkbox-row input[type="checkbox"] {
510
- width: 20px;
511
- height: 20px;
512
- cursor: pointer;
513
- accent-color: #3b82f6;
514
- }
515
-
516
- .checkbox-row label {
517
- margin: 0 !important;
518
- color: #cbd5e1;
519
- font-weight: 500;
520
- cursor: pointer;
521
- flex: 1;
522
- }
523
-
524
- .checkbox-row label::before {
525
- display: none;
526
- }
527
-
528
- /* Action buttons */
529
- .action-section {
530
- margin-top: 2rem;
531
- padding-top: 2rem;
532
- border-top: 1px solid rgba(59, 130, 246, 0.2);
533
- }
534
-
535
- .primary-button {
536
- position: relative;
537
- width: 100%;
538
- padding: 1.25rem 2rem;
539
- background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
540
- color: white;
541
- border: none;
542
- border-radius: 12px;
543
- font-size: 1.1em;
544
- font-weight: 700;
545
- cursor: pointer;
546
- overflow: hidden;
547
- transition: all 0.3s ease;
548
- }
549
-
550
- .primary-button::before {
551
- content: '';
552
- position: absolute;
553
- top: 0;
554
- left: -100%;
555
- width: 100%;
556
- height: 100%;
557
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
558
- transition: left 0.5s;
559
- }
560
-
561
- .primary-button:hover {
562
- transform: translateY(-3px);
563
- box-shadow: 0 10px 30px rgba(59, 130, 246, 0.4);
564
- }
565
-
566
- .primary-button:hover::before {
567
- left: 100%;
568
- }
569
-
570
- .primary-button:active {
571
- transform: translateY(-1px);
572
- }
573
-
574
- .primary-button:disabled {
575
- background: #334155;
576
- cursor: not-allowed;
577
- transform: none;
578
- }
579
-
580
- /* Loading state */
581
- .loading-state {
582
- display: none;
583
- text-align: center;
584
- padding: 3rem;
585
- }
586
-
587
- .loading-spinner {
588
- width: 60px;
589
- height: 60px;
590
- margin: 0 auto 1rem;
591
- border: 4px solid rgba(59, 130, 246, 0.2);
592
- border-top-color: #3b82f6;
593
- border-radius: 50%;
594
- animation: spin 1s linear infinite;
595
- }
596
-
597
- @keyframes spin {
598
- to {
599
- transform: rotate(360deg);
600
- }
601
- }
602
-
603
- .loading-state p {
604
- color: #94a3b8;
605
- font-size: 1.1em;
606
- }
607
-
608
- /* Results section */
609
- .results-section {
610
- display: none;
611
- margin-top: 2rem;
612
- }
613
-
614
- .results-header {
615
- text-align: center;
616
- padding: 2rem;
617
- margin-bottom: 2rem;
618
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(139, 92, 246, 0.15));
619
- border-radius: 16px;
620
- border: 1px solid rgba(59, 130, 246, 0.3);
621
- }
622
-
623
- .results-header h2 {
624
- font-size: 2em;
625
- margin-bottom: 1rem;
626
- color: #e2e8f0;
627
- }
628
-
629
- .risk-badge {
630
- display: inline-block;
631
- padding: 0.75rem 2rem;
632
- border-radius: 30px;
633
- font-weight: 700;
634
- font-size: 1.2em;
635
- text-transform: uppercase;
636
- letter-spacing: 1px;
637
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
638
- }
639
-
640
- .risk-very-high {
641
- background: linear-gradient(135deg, #dc2626, #b91c1c);
642
- box-shadow: 0 4px 15px rgba(220, 38, 38, 0.4);
643
- }
644
-
645
- .risk-high {
646
- background: linear-gradient(135deg, #ea580c, #c2410c);
647
- box-shadow: 0 4px 15px rgba(234, 88, 12, 0.4);
648
- }
649
-
650
- .risk-moderate {
651
- background: linear-gradient(135deg, #ca8a04, #a16207);
652
- box-shadow: 0 4px 15px rgba(202, 138, 4, 0.4);
653
- }
654
-
655
- .risk-low {
656
- background: linear-gradient(135deg, #16a34a, #15803d);
657
- box-shadow: 0 4px 15px rgba(22, 163, 74, 0.4);
658
- }
659
-
660
- .risk-very-low {
661
- background: linear-gradient(135deg, #0891b2, #0e7490);
662
- box-shadow: 0 4px 15px rgba(8, 145, 178, 0.4);
663
- }
664
-
665
- /* Stats grid */
666
- .stats-grid {
667
- display: grid;
668
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
669
- gap: 1.5rem;
670
- margin-bottom: 2rem;
671
- }
672
-
673
- .stat-card {
674
- background: rgba(15, 23, 42, 0.6);
675
- border: 1px solid rgba(59, 130, 246, 0.2);
676
- border-radius: 16px;
677
- padding: 1.5rem;
678
- transition: all 0.3s ease;
679
- }
680
-
681
- .stat-card:hover {
682
- border-color: rgba(59, 130, 246, 0.5);
683
- transform: translateY(-5px);
684
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
685
- }
686
-
687
- .stat-label {
688
- font-size: 0.9em;
689
- color: #94a3b8;
690
- text-transform: uppercase;
691
- letter-spacing: 0.5px;
692
- margin-bottom: 0.5rem;
693
- }
694
-
695
- .stat-value {
696
- font-size: 2em;
697
- font-weight: 700;
698
- background: linear-gradient(135deg, #60a5fa, #a78bfa);
699
- -webkit-background-clip: text;
700
- -webkit-text-fill-color: transparent;
701
- background-clip: text;
702
- }
703
-
704
- /* Detail sections */
705
- .detail-section {
706
- background: rgba(15, 23, 42, 0.6);
707
- border: 1px solid rgba(59, 130, 246, 0.2);
708
- border-radius: 16px;
709
- padding: 2rem;
710
- margin-bottom: 1.5rem;
711
- }
712
-
713
- .detail-section h3 {
714
- font-size: 1.5em;
715
- color: #e2e8f0;
716
- margin-bottom: 1.5rem;
717
- padding-bottom: 1rem;
718
- border-bottom: 1px solid rgba(59, 130, 246, 0.2);
719
- }
720
-
721
- .metric-row {
722
- display: flex;
723
- justify-content: space-between;
724
- align-items: center;
725
- padding: 1rem 0;
726
- border-bottom: 1px solid rgba(59, 130, 246, 0.1);
727
- }
728
-
729
- .metric-row:last-child {
730
- border-bottom: none;
731
- }
732
-
733
- .metric-label {
734
- color: #94a3b8;
735
- font-weight: 500;
736
- }
737
-
738
- .metric-value {
739
- color: #e2e8f0;
740
- font-weight: 700;
741
- font-size: 1.1em;
742
- }
743
-
744
- /* Confidence visualization */
745
- .confidence-section {
746
- background: rgba(15, 23, 42, 0.6);
747
- border: 1px solid rgba(59, 130, 246, 0.2);
748
- border-radius: 16px;
749
- padding: 2rem;
750
- margin-bottom: 1.5rem;
751
- }
752
-
753
- .confidence-bar-wrapper {
754
- display: flex;
755
- align-items: center;
756
- gap: 1rem;
757
- margin: 1.5rem 0;
758
- }
759
-
760
- .confidence-bar-wrapper span {
761
- font-size: 0.85em;
762
- color: #64748b;
763
- font-weight: 600;
764
- }
765
-
766
- .confidence-bar {
767
- flex: 1;
768
- height: 40px;
769
- background: rgba(0, 0, 0, 0.4);
770
- border-radius: 20px;
771
- overflow: hidden;
772
- position: relative;
773
- border: 1px solid rgba(59, 130, 246, 0.2);
774
- }
775
-
776
- .confidence-fill {
777
- height: 100%;
778
- transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
779
- position: relative;
780
- }
781
-
782
- .confidence-high {
783
- background: linear-gradient(90deg, #16a34a, #22c55e);
784
- box-shadow: 0 0 20px rgba(34, 197, 94, 0.4);
785
- }
786
-
787
- .confidence-moderate-fill {
788
- background: linear-gradient(90deg, #ca8a04, #fbbf24);
789
- box-shadow: 0 0 20px rgba(251, 191, 36, 0.4);
790
- }
791
-
792
- .confidence-low-fill {
793
- background: linear-gradient(90deg, #ea580c, #f97316);
794
- box-shadow: 0 0 20px rgba(249, 115, 22, 0.4);
795
- }
796
-
797
- .confidence-text {
798
- position: absolute;
799
- left: 50%;
800
- top: 50%;
801
- transform: translate(-50%, -50%);
802
- font-weight: 800;
803
- color: white;
804
- font-size: 1.1em;
805
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
806
- z-index: 2;
807
- }
808
-
809
- /* Hazard breakdown */
810
- .hazard-grid {
811
- display: grid;
812
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
813
- gap: 1.5rem;
814
- margin: 1.5rem 0;
815
- }
816
-
817
- .hazard-card {
818
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1));
819
- border: 1px solid rgba(59, 130, 246, 0.3);
820
- border-radius: 16px;
821
- padding: 2rem;
822
- text-align: center;
823
- transition: all 0.3s ease;
824
- }
825
-
826
- .hazard-card:hover {
827
- transform: scale(1.05);
828
- border-color: rgba(59, 130, 246, 0.5);
829
- box-shadow: 0 10px 30px rgba(59, 130, 246, 0.3);
830
- }
831
-
832
- .hazard-type {
833
- font-size: 0.9em;
834
- color: #94a3b8;
835
- text-transform: uppercase;
836
- letter-spacing: 0.5px;
837
- margin-bottom: 1rem;
838
- }
839
-
840
- .hazard-value {
841
- font-size: 3em;
842
- font-weight: 800;
843
- background: linear-gradient(135deg, #60a5fa, #a78bfa);
844
- -webkit-background-clip: text;
845
- -webkit-text-fill-color: transparent;
846
- background-clip: text;
847
- }
848
-
849
- /* Explanation section */
850
- .explanation-section {
851
- background: rgba(15, 23, 42, 0.6);
852
- border: 1px solid rgba(59, 130, 246, 0.2);
853
- border-radius: 16px;
854
- padding: 2rem;
855
- margin-bottom: 1.5rem;
856
- }
857
-
858
- .factor-item {
859
- display: flex;
860
- align-items: center;
861
- gap: 1rem;
862
- margin: 1rem 0;
863
- padding: 1rem;
864
- background: rgba(0, 0, 0, 0.3);
865
- border-radius: 12px;
866
- }
867
-
868
- .factor-name {
869
- min-width: 200px;
870
- color: #cbd5e1;
871
- font-weight: 600;
872
- }
873
-
874
- .factor-bar {
875
- flex: 1;
876
- height: 28px;
877
- background: rgba(59, 130, 246, 0.1);
878
- border-radius: 14px;
879
- overflow: hidden;
880
- border: 1px solid rgba(59, 130, 246, 0.2);
881
- }
882
-
883
- .factor-fill {
884
- height: 100%;
885
- background: linear-gradient(90deg, #3b82f6, #8b5cf6);
886
- transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
887
- box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
888
- }
889
-
890
- .factor-percentage {
891
- min-width: 60px;
892
- text-align: right;
893
- color: #e2e8f0;
894
- font-weight: 700;
895
- font-size: 1.05em;
896
- }
897
-
898
- /* Quality warnings */
899
- .quality-warning {
900
- background: linear-gradient(135deg, rgba(202, 138, 4, 0.2), rgba(161, 98, 7, 0.2));
901
- border: 1px solid rgba(202, 138, 4, 0.4);
902
- border-left: 4px solid #ca8a04;
903
- border-radius: 12px;
904
- padding: 1.5rem;
905
- margin: 1.5rem 0;
906
- }
907
-
908
- .quality-warning h4 {
909
- color: #fbbf24;
910
- margin-bottom: 1rem;
911
- display: flex;
912
- align-items: center;
913
- gap: 0.5rem;
914
- }
915
-
916
- .quality-warning ul {
917
- list-style: none;
918
- padding: 0;
919
- }
920
-
921
- .quality-warning li {
922
- padding: 0.5rem 0;
923
- color: #fde047;
924
- padding-left: 1.5rem;
925
- position: relative;
926
- }
927
-
928
- .quality-warning li::before {
929
- content: '⚠';
930
- position: absolute;
931
- left: 0;
932
- }
933
-
934
- /* Error state */
935
- .error-message {
936
- background: linear-gradient(135deg, rgba(220, 38, 38, 0.2), rgba(185, 28, 28, 0.2));
937
- border: 1px solid rgba(220, 38, 38, 0.4);
938
- border-left: 4px solid #dc2626;
939
- color: #fca5a5;
940
- padding: 1.5rem;
941
- border-radius: 12px;
942
- margin: 1.5rem 0;
943
- display: none;
944
- }
945
-
946
- /* Footer */
947
- footer {
948
- margin-top: 4rem;
949
- padding: 2rem;
950
- text-align: center;
951
- color: #64748b;
952
- border-top: 1px solid rgba(59, 130, 246, 0.2);
953
- background: rgba(15, 23, 42, 0.6);
954
- }
955
-
956
- /* File upload styling */
957
- input[type="file"] {
958
- cursor: pointer;
959
- padding: 1rem !important;
960
- }
961
-
962
- input[type="file"]::file-selector-button {
963
- padding: 0.5rem 1rem;
964
- background: linear-gradient(135deg, #3b82f6, #8b5cf6);
965
- color: white;
966
- border: none;
967
- border-radius: 8px;
968
- cursor: pointer;
969
- margin-right: 1rem;
970
- transition: all 0.3s;
971
- }
972
-
973
- input[type="file"]::file-selector-button:hover {
974
- transform: translateY(-2px);
975
- box-shadow: 0 4px 15px rgba(59, 130, 246, 0.4);
976
- }
977
-
978
- /* Pulse animation for predicted height */
979
- @keyframes height-pulse {
980
-
981
- 0%,
982
- 100% {
983
- background: rgba(0, 0, 0, 0.3);
984
- }
985
-
986
- 50% {
987
- background: rgba(59, 130, 246, 0.3);
988
- }
989
- }
990
-
991
- .height-pulse {
992
- animation: height-pulse 0.8s ease;
993
- }
994
-
995
- input[type="text"]::-webkit-calendar-picker-indicator,
996
- input[type="number"]::-webkit-calendar-picker-indicator {
997
- filter: invert(1);
998
- cursor: pointer;
999
- }
1000
-
1001
- /* Responsive design */
1002
- @media (max-width: 1024px) {
1003
- .main-container {
1004
- flex-direction: column;
1005
- }
1006
-
1007
- .side-nav {
1008
- width: 100%;
1009
- position: static;
1010
- }
1011
-
1012
- .nav-card {
1013
- display: flex;
1014
- overflow-x: auto;
1015
- padding: 1rem;
1016
- }
1017
-
1018
- .nav-card h3 {
1019
- display: none;
1020
- }
1021
-
1022
- .nav-link {
1023
- white-space: nowrap;
1024
- margin-right: 0.5rem;
1025
- margin-bottom: 0;
1026
- }
1027
- }
1028
-
1029
- @media (max-width: 768px) {
1030
- .hero-header h1 {
1031
- font-size: 2em;
1032
- }
1033
-
1034
- .form-grid {
1035
- grid-template-columns: 1fr;
1036
- }
1037
-
1038
- .stats-grid {
1039
- grid-template-columns: 1fr;
1040
- }
1041
-
1042
- .assessment-card {
1043
- padding: 1.5rem;
1044
- }
1045
-
1046
- .factor-name {
1047
- min-width: 120px;
1048
- font-size: 0.9em;
1049
- }
1050
- }
1051
- </style>
1052
  </head>
1053
 
1054
  <body>
@@ -1056,8 +13,6 @@
1056
 
1057
  <div class="page-wrapper">
1058
  <header class="hero-header" style="position: relative; padding-top: 40px; text-align: center;">
1059
-
1060
-
1061
  <div style="display: inline-block; text-align: center; max-width: 90%; margin: 0 auto;">
1062
  <h1 style="margin: 0; font-size: 3rem; line-height: 1.2;">
1063
  Flood Vulnerability Assessment
@@ -1066,7 +21,6 @@
1066
  Global building-level assessment
1067
  </p>
1068
  </div>
1069
-
1070
  </header>
1071
 
1072
  <div class="main-container">
@@ -1338,584 +292,7 @@
1338
  <datalist id="height-suggestions"></datalist>
1339
  <datalist id="basement-suggestions"></datalist>
1340
 
1341
- <script>
1342
- const CACHE_KEY = 'flood_assessment_cache';
1343
- const MAX_CACHE_SIZE = 5;
1344
-
1345
- // Get cached entries
1346
- function getCachedEntries() {
1347
- try {
1348
- const cached = localStorage.getItem(CACHE_KEY);
1349
- return cached ? JSON.parse(cached) : [];
1350
- } catch (e) {
1351
- console.error('Error reading cache:', e);
1352
- return [];
1353
- }
1354
- }
1355
-
1356
- function saveToCacheFunction(latitude, longitude, height, basement) {
1357
- try {
1358
- let entries = getCachedEntries();
1359
-
1360
- const newEntry = {
1361
- latitude: parseFloat(latitude).toFixed(6),
1362
- longitude: parseFloat(longitude).toFixed(6),
1363
- height: parseFloat(height).toFixed(2),
1364
- basement: parseFloat(basement).toFixed(2),
1365
- timestamp: Date.now()
1366
- };
1367
-
1368
- // checknig if entry already exists
1369
- const exists = entries.some(entry =>
1370
- entry.latitude === newEntry.latitude &&
1371
- entry.longitude === newEntry.longitude &&
1372
- entry.height === newEntry.height &&
1373
- entry.basement === newEntry.basement
1374
- );
1375
-
1376
- if (!exists) {
1377
- entries.unshift(newEntry);
1378
-
1379
- if (entries.length > MAX_CACHE_SIZE) {
1380
- entries = entries.slice(0, MAX_CACHE_SIZE);
1381
- }
1382
-
1383
- localStorage.setItem(CACHE_KEY, JSON.stringify(entries));
1384
- updateDatalistSuggestions();
1385
- }
1386
- } catch (e) {
1387
- console.error('Error saving to cache:', e);
1388
- }
1389
- }
1390
-
1391
- function updateDatalistSuggestions() {
1392
- const entries = getCachedEntries();
1393
-
1394
- updateDatalist('lat-suggestions', entries, 'latitude');
1395
-
1396
- updateDatalist('lon-suggestions', entries, 'longitude');
1397
-
1398
- updateDatalist('height-suggestions', entries, 'height');
1399
-
1400
- updateDatalist('basement-suggestions', entries, 'basement');
1401
- }
1402
-
1403
- function updateDatalist(datalistId, entries, field) {
1404
- const datalist = document.getElementById(datalistId);
1405
- if (!datalist) return;
1406
-
1407
- datalist.innerHTML = '';
1408
-
1409
- entries.forEach((entry) => {
1410
- const option = document.createElement('option');
1411
- option.value = entry[field];
1412
-
1413
- if (field === 'latitude') {
1414
- option.label = `${entry.latitude} | Lon: ${entry.longitude}, H: ${entry.height}m, B: ${entry.basement}m`;
1415
- } else if (field === 'longitude') {
1416
- option.label = `${entry.longitude} | Lat: ${entry.latitude}, H: ${entry.height}m, B: ${entry.basement}m`;
1417
- } else if (field === 'height') {
1418
- option.label = `${entry.height}m | At: ${entry.latitude}, ${entry.longitude}`;
1419
- } else if (field === 'basement') {
1420
- option.label = `${entry.basement}m | At: ${entry.latitude}, ${entry.longitude}`;
1421
- }
1422
-
1423
- datalist.appendChild(option);
1424
- });
1425
- }
1426
-
1427
- function setupAutoFill() {
1428
- const forms = [
1429
- { suffix: '', latId: 'latitude', lonId: 'longitude' },
1430
- { suffix: '2', latId: 'latitude2', lonId: 'longitude2' },
1431
- { suffix: '3', latId: 'latitude3', lonId: 'longitude3' }
1432
- ];
1433
-
1434
- forms.forEach(({ suffix, latId, lonId }) => {
1435
- const latInput = document.getElementById(latId);
1436
- const lonInput = document.getElementById(lonId);
1437
-
1438
- if (!latInput || !lonInput) return;
1439
-
1440
- let lastChangeTime = 0;
1441
- let pendingAutoFill = null;
1442
-
1443
- const tryAutoFill = () => {
1444
- const latValue = parseNumber(latInput.value);
1445
- const lonValue = parseNumber(lonInput.value);
1446
-
1447
- if (isNaN(latValue) || isNaN(lonValue)) return;
1448
-
1449
- const entries = getCachedEntries();
1450
- const normalizedLat = latValue.toFixed(6);
1451
- const normalizedLon = lonValue.toFixed(6);
1452
-
1453
- // Find entry matching BOTH lat and lon
1454
- const match = entries.find(entry =>
1455
- entry.latitude === normalizedLat &&
1456
- entry.longitude === normalizedLon
1457
- );
1458
-
1459
- if (match) {
1460
- const heightInput = document.getElementById('height' + suffix);
1461
- const basementInput = document.getElementById('basement' + suffix);
1462
-
1463
- if (heightInput && basementInput) {
1464
- heightInput.value = match.height;
1465
- basementInput.value = match.basement;
1466
-
1467
- [latInput, lonInput, heightInput, basementInput].forEach(input => {
1468
- input.classList.add('height-pulse');
1469
- setTimeout(() => input.classList.remove('height-pulse'), 800);
1470
- });
1471
- }
1472
- }
1473
- };
1474
-
1475
- latInput.addEventListener('change', () => {
1476
- setTimeout(tryAutoFill, 100);
1477
- });
1478
-
1479
- lonInput.addEventListener('change', () => {
1480
- setTimeout(tryAutoFill, 100);
1481
- });
1482
- });
1483
- }
1484
-
1485
- document.addEventListener('DOMContentLoaded', () => {
1486
- updateDatalistSuggestions();
1487
- setupAutoFill();
1488
-
1489
- const buttons = document.querySelectorAll('.predict-height-btn');
1490
- if (buttons.length > 0) {
1491
- buttons.forEach(button => {
1492
- const latId = button.dataset.latId;
1493
- const lonId = button.dataset.lonId;
1494
- const heightId = button.dataset.heightId;
1495
- const errorId = button.dataset.errorId;
1496
-
1497
- button.addEventListener('click', () => {
1498
- predictHeight(latId, lonId, heightId, errorId, button);
1499
- });
1500
- });
1501
- }
1502
- });
1503
-
1504
- async function predictHeight(latId, lonId, heightId, errorId, button) {
1505
- const latInput = document.getElementById(latId);
1506
- const lonInput = document.getElementById(lonId);
1507
- const heightInput = document.getElementById(heightId);
1508
- const errorBox = document.getElementById(errorId);
1509
-
1510
- if (!latInput || !lonInput || !heightInput || !errorBox) {
1511
- return;
1512
- }
1513
-
1514
- errorBox.style.display = 'none';
1515
- errorBox.textContent = '';
1516
-
1517
- const latitude = parseFloat(latInput.value);
1518
- const longitude = parseFloat(lonInput.value);
1519
-
1520
- if (isNaN(latitude) || isNaN(longitude)) {
1521
- errorBox.textContent = 'Please enter latitude and longitude first.';
1522
- errorBox.style.display = 'block';
1523
- return;
1524
- }
1525
-
1526
- const originalText = button.textContent;
1527
- button.disabled = true;
1528
- button.textContent = 'Predicting...';
1529
-
1530
- try {
1531
- const response = await fetch('/predict_height', {
1532
- method: 'POST',
1533
- headers: { 'Content-Type': 'application/json' },
1534
- body: JSON.stringify({
1535
- latitude,
1536
- longitude,
1537
- height: 0,
1538
- basement: 0
1539
- })
1540
- });
1541
-
1542
- const data = await response.json();
1543
- if (!response.ok || data.status !== 'success' || data.predicted_height == null) {
1544
- const message = data.detail || data.error || 'Height prediction failed.';
1545
- throw new Error(message);
1546
- }
1547
-
1548
- const h = Number(data.predicted_height);
1549
- heightInput.value = h.toFixed(2);
1550
- heightInput.classList.add('height-pulse');
1551
- setTimeout(() => {
1552
- heightInput.classList.remove('height-pulse');
1553
- }, 800);
1554
- } catch (err) {
1555
- errorBox.textContent = err.message || 'Height prediction failed.';
1556
- errorBox.style.display = 'block';
1557
- } finally {
1558
- button.disabled = false;
1559
- button.textContent = originalText;
1560
- }
1561
- }
1562
-
1563
- // function to parse numbers with comma or dot as decimal separator
1564
- function parseNumber(value) {
1565
- if (typeof value === 'string') {
1566
- value = value.replace(',', '.');
1567
- }
1568
- return parseFloat(value);
1569
- }
1570
-
1571
- function switchTab(tabName) {
1572
- document.querySelectorAll('.assessment-card').forEach(card => {
1573
- card.classList.remove('active');
1574
- });
1575
- document.querySelectorAll('.nav-link').forEach(link => {
1576
- link.classList.remove('active');
1577
- });
1578
-
1579
- document.getElementById(tabName + '-card').classList.add('active');
1580
- event.target.classList.add('active');
1581
- }
1582
-
1583
- async function assessLocation(event, endpoint, resultsId) {
1584
- event.preventDefault();
1585
-
1586
- const tabName = resultsId.split('-')[0];
1587
- const suffix = endpoint === '/assess' ? '' : (endpoint === '/explain' ? '2' : '3');
1588
- const latitude = parseFloat(document.getElementById('latitude' + suffix).value);
1589
- const longitude = parseFloat(document.getElementById('longitude' + suffix).value);
1590
- const height = parseFloat(document.getElementById('height' + suffix).value) || 0;
1591
- const basement = parseFloat(document.getElementById('basement' + suffix).value) || 0;
1592
-
1593
- document.getElementById(tabName + '-loading').style.display = 'block';
1594
- document.getElementById(resultsId).style.display = 'none';
1595
- document.getElementById(tabName + '-error').style.display = 'none';
1596
-
1597
- try {
1598
- const response = await fetch(endpoint, {
1599
- method: 'POST',
1600
- headers: { 'Content-Type': 'application/json' },
1601
- body: JSON.stringify({ latitude, longitude, height, basement })
1602
- });
1603
-
1604
- const data = await response.json();
1605
-
1606
- if (data.status === 'success') {
1607
- // Save to cache on successful assessment
1608
- saveToCacheFunction(latitude, longitude, height, basement);
1609
- displayResults(data, resultsId, endpoint);
1610
- } else {
1611
- throw new Error(data.detail || 'Assessment failed');
1612
- }
1613
- } catch (error) {
1614
- document.getElementById(tabName + '-error').textContent = error.message;
1615
- document.getElementById(tabName + '-error').style.display = 'block';
1616
- } finally {
1617
- document.getElementById(tabName + '-loading').style.display = 'none';
1618
- }
1619
- }
1620
-
1621
- function formatFlag(flag) {
1622
- const flagMessages = {
1623
- 'missing_elevation': 'Elevation data unavailable',
1624
- 'missing_tpi': 'Topographic position data incomplete',
1625
- 'missing_slope': 'Slope data incomplete',
1626
- 'water_distance_unknown': 'Water proximity uncertain',
1627
- 'far_from_water_search_limited': 'Far from major water bodies (search radius limited)',
1628
- 'steep_terrain_dem_error_high': 'Steep terrain increases measurement uncertainty',
1629
- 'coastal_surge_risk_not_modeled': 'Coastal surge dynamics not fully captured'
1630
- };
1631
- return flagMessages[flag] || flag.replace(/_/g, ' ');
1632
- }
1633
-
1634
- function displayResults(data, resultsId, endpoint) {
1635
- const resultsDiv = document.getElementById(resultsId);
1636
- const assessment = data.assessment;
1637
-
1638
- let html = '<div class="results-header">';
1639
- html += '<h2>Assessment Complete</h2>';
1640
-
1641
- const riskClass = 'risk-' + assessment.risk_level.replace(/_/g, '-');
1642
- html += `<div class="risk-badge ${riskClass}">${assessment.risk_level.replace(/_/g, ' ')}</div>`;
1643
- html += '</div>';
1644
-
1645
- html += '<div class="stats-grid">';
1646
-
1647
- if (assessment.confidence_interval) {
1648
- const ci = assessment.confidence_interval;
1649
- html += `
1650
- <div class="stat-card">
1651
- <div class="stat-label">Vulnerability Index</div>
1652
- <div class="stat-value">${ci.point_estimate}</div>
1653
- <p style="font-size: 0.85em; color: #64748b; margin-top: 0.5rem;">
1654
- 95% CI: ${ci.lower_bound_95}–${ci.upper_bound_95}
1655
- </p>
1656
- </div>
1657
- `;
1658
- } else {
1659
- html += `
1660
- <div class="stat-card">
1661
- <div class="stat-label">Vulnerability Index</div>
1662
- <div class="stat-value">${assessment.vulnerability_index}</div>
1663
- </div>
1664
- `;
1665
- }
1666
-
1667
- html += `
1668
- <div class="stat-card">
1669
- <div class="stat-label">Elevation</div>
1670
- <div class="stat-value">${assessment.elevation_m}m</div>
1671
- </div>
1672
- `;
1673
-
1674
- if (assessment.distance_to_water_m !== null) {
1675
- html += `
1676
- <div class="stat-card">
1677
- <div class="stat-label">Distance to Water</div>
1678
- <div class="stat-value">${assessment.distance_to_water_m}m</div>
1679
- </div>
1680
- `;
1681
- }
1682
-
1683
- html += '</div>';
1684
-
1685
- if (assessment.uncertainty_analysis) {
1686
- const ua = assessment.uncertainty_analysis;
1687
- const confidenceValue = parseFloat(ua.confidence) || 0;
1688
- const barWidth = Math.round(confidenceValue * 100);
1689
-
1690
- let confidenceClass = 'confidence-low-fill';
1691
- if (confidenceValue >= 0.75) confidenceClass = 'confidence-high';
1692
- else if (confidenceValue >= 0.55) confidenceClass = 'confidence-moderate-fill';
1693
-
1694
- html += `
1695
- <div class="confidence-section">
1696
- <h3>Assessment Confidence</h3>
1697
- <div class="confidence-bar-wrapper">
1698
- <span>Low</span>
1699
- <div class="confidence-bar">
1700
- <div class="confidence-fill ${confidenceClass}" style="width: ${barWidth}%;"></div>
1701
- <span class="confidence-text">${barWidth}%</span>
1702
- </div>
1703
- <span>High</span>
1704
- </div>
1705
- <p style="margin-top: 1rem; color: #94a3b8; font-style: italic;">
1706
- ${ua.interpretation}
1707
- </p>
1708
- </div>
1709
- `;
1710
-
1711
- if (ua.data_quality_flags && ua.data_quality_flags.length > 0) {
1712
- const criticalFlags = ua.data_quality_flags.filter(flag =>
1713
- flag === 'steep_terrain_dem_error_high' ||
1714
- flag === 'coastal_surge_risk_not_modeled'
1715
- );
1716
-
1717
- if (criticalFlags.length > 0) {
1718
- html += '<div class="quality-warning"><h4>⚠ Data Quality Notes</h4><ul>';
1719
- criticalFlags.forEach(flag => {
1720
- html += `<li>${formatFlag(flag)}</li>`;
1721
- });
1722
- html += '</ul></div>';
1723
- }
1724
- }
1725
- }
1726
-
1727
- html += '<div class="detail-section"><h3>Terrain Analysis</h3>';
1728
- html += `
1729
- <div class="metric-row">
1730
- <span class="metric-label">Elevation</span>
1731
- <span class="metric-value">${assessment.elevation_m} m</span>
1732
- </div>
1733
- <div class="metric-row">
1734
- <span class="metric-label">Relative Elevation (TPI)</span>
1735
- <span class="metric-value">${assessment.relative_elevation_m !== null ? assessment.relative_elevation_m + ' m' : 'N/A'}</span>
1736
- </div>
1737
- <div class="metric-row">
1738
- <span class="metric-label">Slope</span>
1739
- <span class="metric-value">${assessment.slope_degrees !== null ? assessment.slope_degrees + '°' : 'N/A'}</span>
1740
- </div>
1741
- <div class="metric-row">
1742
- <span class="metric-label">Distance to Water</span>
1743
- <span class="metric-value">${assessment.distance_to_water_m !== null ? assessment.distance_to_water_m + ' m' : 'N/A'}</span>
1744
- </div>
1745
- `;
1746
- html += '</div>';
1747
-
1748
- if (assessment.hazard_breakdown) {
1749
- const hb = assessment.hazard_breakdown;
1750
- html += '<div class="detail-section"><h3>Hazard Breakdown</h3>';
1751
- html += '<div class="hazard-grid">';
1752
- html += `
1753
- <div class="hazard-card">
1754
- <div class="hazard-type">Fluvial/Riverine</div>
1755
- <div class="hazard-value">${hb.fluvial_riverine}</div>
1756
- </div>
1757
- <div class="hazard-card">
1758
- <div class="hazard-type">Coastal Surge</div>
1759
- <div class="hazard-value">${hb.coastal_surge}</div>
1760
- </div>
1761
- <div class="hazard-card">
1762
- <div class="hazard-type">Pluvial/Drainage</div>
1763
- <div class="hazard-value">${hb.pluvial_drainage}</div>
1764
- </div>
1765
- `;
1766
- html += '</div>';
1767
- html += `<p style="margin-top: 1.5rem;"><strong>Dominant Hazard:</strong> ${assessment.dominant_hazard.replace(/_/g, ' ').toUpperCase()}</p>`;
1768
- html += '</div>';
1769
- }
1770
-
1771
- if (data.explanation) {
1772
- const exp = data.explanation;
1773
- html += '<div class="explanation-section">';
1774
- html += '<h3>Risk Factor Analysis</h3>';
1775
- html += `<p style="margin-bottom: 1.5rem; color: #cbd5e1;"><strong>Top Risk Driver:</strong> ${exp.top_risk_driver}</p>`;
1776
-
1777
- exp.explanations.forEach(factor => {
1778
- html += `
1779
- <div class="factor-item">
1780
- <span class="factor-name">${factor.factor}</span>
1781
- <div class="factor-bar">
1782
- <div class="factor-fill" style="width: ${factor.contribution_pct}%"></div>
1783
- </div>
1784
- <span class="factor-percentage">${factor.contribution_pct}%</span>
1785
- </div>
1786
- `;
1787
- });
1788
-
1789
- html += '</div>';
1790
- }
1791
-
1792
- resultsDiv.innerHTML = html;
1793
- resultsDiv.style.display = 'block';
1794
- }
1795
-
1796
- async function getHeightFromGBA(latId, lonId, heightId, errorId, button) {
1797
- const lat = parseNumber(document.getElementById(latId).value);
1798
- const lon = parseNumber(document.getElementById(lonId).value);
1799
-
1800
- const errorBox = document.getElementById(errorId);
1801
- if (errorBox) errorBox.textContent = '';
1802
-
1803
- if (Number.isNaN(lat) || Number.isNaN(lon)) {
1804
- if (errorBox) errorBox.textContent = 'Please enter valid latitude and longitude.';
1805
- return;
1806
- }
1807
-
1808
- const originalText = button.textContent;
1809
- button.disabled = true;
1810
- button.textContent = 'Fetching...';
1811
-
1812
- try {
1813
- const resp = await fetch('/get_height_gba', {
1814
- method: 'POST',
1815
- headers: { 'Content-Type': 'application/json' },
1816
- body: JSON.stringify({ latitude: lat, longitude: lon, height: 0, basement: 0 })
1817
- });
1818
-
1819
- const data = await resp.json();
1820
- if (!resp.ok) {
1821
- const msg = data?.detail || 'Failed to get GBA height';
1822
- throw new Error(msg);
1823
- }
1824
-
1825
- const h = data.predicted_height;
1826
- if (h === null || h === undefined) {
1827
- throw new Error('No height returned');
1828
- }
1829
-
1830
- document.getElementById(heightId).value = Number(h).toFixed(2);
1831
- } catch (e) {
1832
- if (errorBox) {
1833
- errorBox.textContent = String(e.message || e);
1834
- errorBox.style.display = 'block';
1835
- }
1836
- } finally {
1837
- button.disabled = false;
1838
- button.textContent = originalText;
1839
- }
1840
- }
1841
-
1842
- document.querySelectorAll('.gba-height-btn').forEach(btn => {
1843
- btn.addEventListener('click', () => {
1844
- getHeightFromGBA(
1845
- btn.dataset.latId,
1846
- btn.dataset.lonId,
1847
- btn.dataset.heightId,
1848
- btn.dataset.errorId,
1849
- btn
1850
- );
1851
- });
1852
- });
1853
-
1854
- async function uploadBatch() {
1855
- const fileInput = document.getElementById('csvFile');
1856
- const file = fileInput.files[0];
1857
-
1858
- if (!file) {
1859
- alert('Please select a CSV file');
1860
- return;
1861
- }
1862
-
1863
- document.getElementById('batch-loading').style.display = 'block';
1864
- document.getElementById('batch-results').style.display = 'none';
1865
- document.getElementById('batch-error').style.display = 'none';
1866
-
1867
- const formData = new FormData();
1868
- formData.append('file', file);
1869
-
1870
- try {
1871
- const mode = document.getElementById('batchMode').value;
1872
- const heightSource = document.getElementById('heightSource').value;
1873
- let endpoint = mode === 'multihazard' ? '/assess_batch_multihazard' : '/assess_batch';
1874
-
1875
- if (heightSource === 'gba') {
1876
- const sep = endpoint.includes('?') ? '&' : '?';
1877
- endpoint = endpoint + sep + 'use_gba_height=true';
1878
- } else if (heightSource === 'predicted') {
1879
- const sep = endpoint.includes('?') ? '&' : '?';
1880
- endpoint = endpoint + sep + 'use_predicted_height=true';
1881
- }
1882
-
1883
- const response = await fetch(endpoint, {
1884
- method: 'POST',
1885
- body: formData
1886
- });
1887
-
1888
- if (response.ok) {
1889
- const blob = await response.blob();
1890
- const url = window.URL.createObjectURL(blob);
1891
- const a = document.createElement('a');
1892
- a.href = url;
1893
- const filename = mode === 'multihazard'
1894
- ? 'multihazard_results.csv'
1895
- : 'vulnerability_results.csv';
1896
- a.download = filename;
1897
- document.body.appendChild(a);
1898
- a.click();
1899
- window.URL.revokeObjectURL(url);
1900
-
1901
- document.getElementById('batch-results').innerHTML = `
1902
- <div class="results-header">
1903
- <h2>✓ Processing Complete</h2>
1904
- <p style="color: #94a3b8; margin-top: 1rem;">Results downloaded as ${filename}</p>
1905
- </div>
1906
- `;
1907
- document.getElementById('batch-results').style.display = 'block';
1908
- } else {
1909
- throw new Error('Batch processing failed');
1910
- }
1911
- } catch (error) {
1912
- document.getElementById('batch-error').textContent = error.message;
1913
- document.getElementById('batch-error').style.display = 'block';
1914
- } finally {
1915
- document.getElementById('batch-loading').style.display = 'none';
1916
- }
1917
- }
1918
- </script>
1919
  </body>
1920
 
1921
  </html>
 
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Flood Vulnerability Assessment</title>
8
+ <link rel="stylesheet" href="/static/css/styles.css">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  </head>
10
 
11
  <body>
 
13
 
14
  <div class="page-wrapper">
15
  <header class="hero-header" style="position: relative; padding-top: 40px; text-align: center;">
 
 
16
  <div style="display: inline-block; text-align: center; max-width: 90%; margin: 0 auto;">
17
  <h1 style="margin: 0; font-size: 3rem; line-height: 1.2;">
18
  Flood Vulnerability Assessment
 
21
  Global building-level assessment
22
  </p>
23
  </div>
 
24
  </header>
25
 
26
  <div class="main-container">
 
292
  <datalist id="height-suggestions"></datalist>
293
  <datalist id="basement-suggestions"></datalist>
294
 
295
+ <script src="/static/js/app.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  </body>
297
 
298
  </html>