omkhk commited on
Commit
3584c0b
Β·
verified Β·
1 Parent(s): 609b44c

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. static/css/style.css +1206 -0
  2. static/js/app.js +376 -0
static/css/style.css ADDED
@@ -0,0 +1,1206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ═══════════════════════════════════════════════════════════════════════════
2
+ VrukshaVed β€” Ayurvedic Plant Intelligence
3
+ Design: Forest-earth tones, sacred geometry, Cormorant + Yatra One typography
4
+ ═══════════════════════════════════════════════════════════════════════════ */
5
+
6
+ /* ── Reset & Variables ──────────────────────────────────────────────────── */
7
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
8
+
9
+ :root {
10
+ --ink: #1a1a0e;
11
+ --deep-green: #1e4d1a;
12
+ --mid-green: #3a7a30;
13
+ --leaf-green: #5aab40;
14
+ --lime: #a8d85c;
15
+ --cream: #faf6ee;
16
+ --parchment: #f2ead8;
17
+ --earth: #8b6b3d;
18
+ --gold: #c9953a;
19
+ --saffron: #e8a020;
20
+ --red-tika: #b83030;
21
+ --muted: #6b7a5e;
22
+ --border: rgba(90, 171, 64, 0.2);
23
+ --shadow: rgba(30, 77, 26, 0.12);
24
+ --glass: rgba(255, 255, 255, 0.72);
25
+ --glass-dark: rgba(30, 40, 20, 0.65);
26
+
27
+ --ff-head: 'Yatra One', serif;
28
+ --ff-body: 'Cormorant Garamond', serif;
29
+ --ff-ui: 'DM Sans', sans-serif;
30
+
31
+ --radius: 16px;
32
+ --radius-sm: 8px;
33
+ --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
34
+ }
35
+
36
+ html { scroll-behavior: smooth; }
37
+
38
+ body {
39
+ font-family: var(--ff-body);
40
+ background: var(--cream);
41
+ color: var(--ink);
42
+ min-height: 100vh;
43
+ overflow-x: hidden;
44
+ line-height: 1.7;
45
+ }
46
+
47
+ /* ── Background Layer ───────────────────────────────────────────────────── */
48
+ .bg-layer {
49
+ position: fixed;
50
+ inset: 0;
51
+ pointer-events: none;
52
+ z-index: 0;
53
+ overflow: hidden;
54
+ background:
55
+ radial-gradient(ellipse 80% 60% at 10% 20%, rgba(168, 216, 92, 0.08) 0%, transparent 60%),
56
+ radial-gradient(ellipse 60% 80% at 90% 80%, rgba(201, 149, 58, 0.06) 0%, transparent 60%),
57
+ var(--cream);
58
+ }
59
+
60
+ .mandala-ring {
61
+ position: absolute;
62
+ border-radius: 50%;
63
+ border: 1px solid rgba(90, 171, 64, 0.12);
64
+ top: 50%;
65
+ left: 50%;
66
+ transform: translate(-50%, -50%);
67
+ animation: mandala-spin linear infinite;
68
+ }
69
+ .ring-1 { width: 600px; height: 600px; animation-duration: 80s; border-style: dashed; }
70
+ .ring-2 { width: 1000px; height: 1000px; animation-duration: 140s; animation-direction: reverse; }
71
+ .ring-3 { width: 1400px; height: 1400px; animation-duration: 200s; }
72
+
73
+ @keyframes mandala-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
74
+
75
+ .leaf-scatter {
76
+ position: absolute;
77
+ inset: 0;
78
+ }
79
+
80
+ /* ── Header ─────────────────────────────────────────────────────────────── */
81
+ .site-header {
82
+ position: sticky;
83
+ top: 0;
84
+ z-index: 100;
85
+ background: var(--glass);
86
+ backdrop-filter: blur(16px);
87
+ -webkit-backdrop-filter: blur(16px);
88
+ border-bottom: 1px solid var(--border);
89
+ }
90
+
91
+ .header-inner {
92
+ max-width: 1200px;
93
+ margin: 0 auto;
94
+ padding: 0 2rem;
95
+ height: 72px;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: space-between;
99
+ }
100
+
101
+ .logo-group {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 12px;
105
+ }
106
+
107
+ .logo-icon {
108
+ width: 44px;
109
+ height: 44px;
110
+ filter: drop-shadow(0 2px 8px rgba(58, 122, 48, 0.3));
111
+ }
112
+
113
+ .logo-icon svg { width: 100%; height: 100%; }
114
+
115
+ .site-title {
116
+ font-family: var(--ff-head);
117
+ font-size: 1.6rem;
118
+ color: var(--deep-green);
119
+ line-height: 1;
120
+ }
121
+
122
+ .site-tagline {
123
+ font-family: var(--ff-ui);
124
+ font-size: 0.72rem;
125
+ color: var(--muted);
126
+ letter-spacing: 0.05em;
127
+ margin-top: 2px;
128
+ }
129
+
130
+ .header-nav {
131
+ display: flex;
132
+ gap: 2rem;
133
+ }
134
+
135
+ .header-nav a {
136
+ font-family: var(--ff-ui);
137
+ font-size: 0.88rem;
138
+ font-weight: 500;
139
+ color: var(--mid-green);
140
+ text-decoration: none;
141
+ letter-spacing: 0.03em;
142
+ transition: color var(--transition);
143
+ }
144
+ .header-nav a:hover { color: var(--deep-green); }
145
+
146
+ /* ── Hero ───────────────────────────────────────────────────────────────── */
147
+ .hero {
148
+ position: relative;
149
+ z-index: 1;
150
+ min-height: 88vh;
151
+ display: flex;
152
+ align-items: center;
153
+ padding: 6rem 2rem 4rem;
154
+ max-width: 1200px;
155
+ margin: 0 auto;
156
+ }
157
+
158
+ .hero-content { max-width: 620px; }
159
+
160
+ .hero-badge {
161
+ display: inline-block;
162
+ font-family: var(--ff-ui);
163
+ font-size: 0.75rem;
164
+ font-weight: 500;
165
+ letter-spacing: 0.12em;
166
+ text-transform: uppercase;
167
+ color: var(--mid-green);
168
+ background: rgba(90, 171, 64, 0.12);
169
+ border: 1px solid rgba(90, 171, 64, 0.3);
170
+ padding: 6px 14px;
171
+ border-radius: 100px;
172
+ margin-bottom: 1.5rem;
173
+ }
174
+
175
+ .hero-headline {
176
+ font-family: var(--ff-head);
177
+ font-size: clamp(2.8rem, 6vw, 5.2rem);
178
+ line-height: 1.05;
179
+ color: var(--deep-green);
180
+ margin-bottom: 1.5rem;
181
+ }
182
+
183
+ .hero-headline em {
184
+ font-style: italic;
185
+ color: var(--gold);
186
+ font-family: 'Cormorant Garamond', serif;
187
+ }
188
+
189
+ .hero-subtext {
190
+ font-size: 1.15rem;
191
+ color: var(--muted);
192
+ margin-bottom: 2.5rem;
193
+ max-width: 520px;
194
+ font-weight: 300;
195
+ }
196
+
197
+ .cta-btn {
198
+ display: inline-flex;
199
+ align-items: center;
200
+ gap: 10px;
201
+ font-family: var(--ff-ui);
202
+ font-size: 1rem;
203
+ font-weight: 500;
204
+ color: #fff;
205
+ background: linear-gradient(135deg, var(--mid-green), var(--deep-green));
206
+ padding: 14px 32px;
207
+ border-radius: 100px;
208
+ text-decoration: none;
209
+ box-shadow: 0 8px 32px rgba(30, 77, 26, 0.3);
210
+ transition: transform var(--transition), box-shadow var(--transition);
211
+ }
212
+ .cta-btn:hover {
213
+ transform: translateY(-2px);
214
+ box-shadow: 0 12px 40px rgba(30, 77, 26, 0.4);
215
+ }
216
+ .cta-btn svg { width: 18px; height: 18px; }
217
+
218
+ .hero-decoration {
219
+ position: absolute;
220
+ right: 0;
221
+ top: 50%;
222
+ transform: translateY(-50%);
223
+ width: 480px;
224
+ height: 480px;
225
+ pointer-events: none;
226
+ }
227
+
228
+ .deco-circle {
229
+ position: absolute;
230
+ border-radius: 50%;
231
+ opacity: 0.12;
232
+ }
233
+ .c1 {
234
+ width: 380px; height: 380px;
235
+ top: 50%; left: 50%;
236
+ transform: translate(-50%, -50%);
237
+ background: radial-gradient(circle, var(--leaf-green) 0%, transparent 70%);
238
+ }
239
+ .c2 {
240
+ width: 200px; height: 200px;
241
+ top: 20%; left: 60%;
242
+ background: radial-gradient(circle, var(--gold) 0%, transparent 70%);
243
+ opacity: 0.18;
244
+ }
245
+
246
+ .rotating-mandala {
247
+ position: absolute;
248
+ top: 50%; left: 50%;
249
+ transform: translate(-50%, -50%);
250
+ font-size: 14rem;
251
+ color: var(--leaf-green);
252
+ opacity: 0.06;
253
+ animation: mandala-spin 60s linear infinite;
254
+ line-height: 1;
255
+ }
256
+
257
+ /* ── Section Commons ────────────────────────────────────────────────────── */
258
+ .section-container {
259
+ position: relative;
260
+ z-index: 1;
261
+ max-width: 1200px;
262
+ margin: 0 auto;
263
+ padding: 5rem 2rem;
264
+ }
265
+
266
+ .section-header {
267
+ text-align: center;
268
+ margin-bottom: 3rem;
269
+ }
270
+
271
+ .section-label {
272
+ display: inline-block;
273
+ font-family: var(--ff-ui);
274
+ font-size: 0.72rem;
275
+ font-weight: 600;
276
+ letter-spacing: 0.15em;
277
+ text-transform: uppercase;
278
+ color: var(--gold);
279
+ margin-bottom: 0.75rem;
280
+ }
281
+
282
+ .section-header h2 {
283
+ font-family: var(--ff-head);
284
+ font-size: clamp(1.8rem, 4vw, 3rem);
285
+ color: var(--deep-green);
286
+ line-height: 1.1;
287
+ margin-bottom: 0.75rem;
288
+ }
289
+
290
+ .section-header p {
291
+ font-size: 1rem;
292
+ color: var(--muted);
293
+ max-width: 500px;
294
+ margin: 0 auto;
295
+ }
296
+
297
+ /* ── Upload Section ─────────────────────────────────────────────────────── */
298
+ .upload-section { background: var(--parchment); }
299
+
300
+ .upload-area {
301
+ max-width: 680px;
302
+ margin: 0 auto;
303
+ background: white;
304
+ border: 2px dashed var(--border);
305
+ border-radius: 24px;
306
+ min-height: 320px;
307
+ display: flex;
308
+ align-items: center;
309
+ justify-content: center;
310
+ transition: border-color var(--transition), box-shadow var(--transition);
311
+ box-shadow: 0 4px 32px var(--shadow);
312
+ overflow: hidden;
313
+ }
314
+
315
+ .upload-area.drag-over {
316
+ border-color: var(--leaf-green);
317
+ box-shadow: 0 8px 48px rgba(90, 171, 64, 0.2);
318
+ }
319
+
320
+ .upload-idle {
321
+ text-align: center;
322
+ padding: 3rem 2rem;
323
+ }
324
+
325
+ .upload-icon {
326
+ width: 80px;
327
+ height: 80px;
328
+ margin: 0 auto 1.5rem;
329
+ color: var(--leaf-green);
330
+ opacity: 0.6;
331
+ }
332
+ .upload-icon svg { width: 100%; height: 100%; }
333
+
334
+ .upload-main-text {
335
+ font-family: var(--ff-head);
336
+ font-size: 1.4rem;
337
+ color: var(--deep-green);
338
+ margin-bottom: 0.5rem;
339
+ }
340
+
341
+ .upload-sub-text {
342
+ font-size: 0.9rem;
343
+ color: var(--muted);
344
+ margin-bottom: 1.5rem;
345
+ }
346
+
347
+ .upload-buttons {
348
+ display: flex;
349
+ gap: 1rem;
350
+ justify-content: center;
351
+ flex-wrap: wrap;
352
+ }
353
+
354
+ .btn-upload, .btn-camera {
355
+ display: inline-flex;
356
+ align-items: center;
357
+ gap: 8px;
358
+ font-family: var(--ff-ui);
359
+ font-size: 0.9rem;
360
+ font-weight: 500;
361
+ padding: 10px 22px;
362
+ border-radius: 100px;
363
+ cursor: pointer;
364
+ transition: all var(--transition);
365
+ border: none;
366
+ }
367
+
368
+ .btn-upload {
369
+ background: var(--deep-green);
370
+ color: #fff;
371
+ }
372
+ .btn-upload:hover { background: var(--mid-green); transform: translateY(-1px); }
373
+
374
+ .btn-camera {
375
+ background: transparent;
376
+ color: var(--mid-green);
377
+ border: 1.5px solid var(--mid-green);
378
+ }
379
+ .btn-camera:hover { background: rgba(58, 122, 48, 0.08); }
380
+
381
+ .btn-upload svg, .btn-camera svg { width: 16px; height: 16px; }
382
+
383
+ /* Camera */
384
+ .camera-view {
385
+ width: 100%;
386
+ position: relative;
387
+ }
388
+
389
+ .camera-view video {
390
+ width: 100%;
391
+ max-height: 420px;
392
+ object-fit: cover;
393
+ display: block;
394
+ }
395
+
396
+ .camera-overlay {
397
+ position: absolute;
398
+ inset: 0;
399
+ display: flex;
400
+ flex-direction: column;
401
+ align-items: center;
402
+ justify-content: center;
403
+ pointer-events: none;
404
+ }
405
+
406
+ .camera-frame {
407
+ width: 260px;
408
+ height: 260px;
409
+ border: 2px solid rgba(168, 216, 92, 0.8);
410
+ border-radius: 12px;
411
+ box-shadow: 0 0 0 9999px rgba(0,0,0,0.4);
412
+ }
413
+
414
+ .camera-hint {
415
+ margin-top: 12px;
416
+ color: rgba(255,255,255,0.9);
417
+ font-family: var(--ff-ui);
418
+ font-size: 0.85rem;
419
+ }
420
+
421
+ .camera-controls {
422
+ display: flex;
423
+ align-items: center;
424
+ justify-content: center;
425
+ gap: 1.5rem;
426
+ padding: 1.5rem;
427
+ background: var(--deep-green);
428
+ }
429
+
430
+ .btn-capture {
431
+ display: flex;
432
+ align-items: center;
433
+ gap: 10px;
434
+ background: var(--lime);
435
+ color: var(--deep-green);
436
+ font-family: var(--ff-ui);
437
+ font-weight: 600;
438
+ font-size: 1rem;
439
+ padding: 12px 28px;
440
+ border-radius: 100px;
441
+ border: none;
442
+ cursor: pointer;
443
+ transition: all var(--transition);
444
+ }
445
+ .btn-capture:hover { transform: scale(1.04); }
446
+
447
+ .capture-ring {
448
+ display: block;
449
+ width: 16px;
450
+ height: 16px;
451
+ border: 2.5px solid var(--deep-green);
452
+ border-radius: 50%;
453
+ }
454
+
455
+ .btn-cancel-camera {
456
+ background: none;
457
+ color: rgba(255,255,255,0.7);
458
+ border: 1px solid rgba(255,255,255,0.3);
459
+ font-family: var(--ff-ui);
460
+ font-size: 0.85rem;
461
+ padding: 10px 20px;
462
+ border-radius: 100px;
463
+ cursor: pointer;
464
+ transition: all var(--transition);
465
+ }
466
+ .btn-cancel-camera:hover { color: #fff; border-color: #fff; }
467
+
468
+ /* Preview */
469
+ .image-preview {
470
+ width: 100%;
471
+ padding: 1.5rem;
472
+ display: flex;
473
+ flex-direction: column;
474
+ align-items: center;
475
+ gap: 1.5rem;
476
+ }
477
+
478
+ .image-preview img {
479
+ max-width: 100%;
480
+ max-height: 360px;
481
+ object-fit: contain;
482
+ border-radius: 12px;
483
+ box-shadow: 0 8px 32px var(--shadow);
484
+ }
485
+
486
+ .preview-actions {
487
+ display: flex;
488
+ gap: 1rem;
489
+ align-items: center;
490
+ }
491
+
492
+ .btn-analyze {
493
+ font-family: var(--ff-ui);
494
+ font-size: 1rem;
495
+ font-weight: 600;
496
+ color: #fff;
497
+ background: linear-gradient(135deg, var(--mid-green), var(--deep-green));
498
+ padding: 12px 36px;
499
+ border-radius: 100px;
500
+ border: none;
501
+ cursor: pointer;
502
+ box-shadow: 0 6px 24px rgba(30, 77, 26, 0.3);
503
+ transition: all var(--transition);
504
+ display: flex;
505
+ align-items: center;
506
+ gap: 8px;
507
+ }
508
+ .btn-analyze:hover { transform: translateY(-2px); box-shadow: 0 10px 32px rgba(30, 77, 26, 0.4); }
509
+ .btn-analyze:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
510
+
511
+ .btn-spinner { display: flex; align-items: center; gap: 8px; }
512
+ .btn-spinner svg { width: 18px; height: 18px; animation: spin 1s linear infinite; }
513
+ @keyframes spin { to { transform: rotate(360deg); } }
514
+
515
+ .btn-clear {
516
+ font-family: var(--ff-ui);
517
+ font-size: 0.88rem;
518
+ color: var(--muted);
519
+ background: none;
520
+ border: 1px solid var(--border);
521
+ padding: 10px 20px;
522
+ border-radius: 100px;
523
+ cursor: pointer;
524
+ transition: all var(--transition);
525
+ }
526
+ .btn-clear:hover { color: var(--red-tika); border-color: var(--red-tika); }
527
+
528
+ /* ── Results Section ─────────────────────────────────────────────────────── */
529
+ .results-section {
530
+ background: white;
531
+ border-top: 1px solid var(--border);
532
+ }
533
+
534
+ .result-hero-card {
535
+ display: grid;
536
+ grid-template-columns: 280px 1fr;
537
+ gap: 3rem;
538
+ background: linear-gradient(135deg, var(--parchment) 0%, white 100%);
539
+ border: 1px solid var(--border);
540
+ border-radius: 24px;
541
+ padding: 2.5rem;
542
+ margin-bottom: 3rem;
543
+ box-shadow: 0 8px 48px var(--shadow);
544
+ animation: fadeSlideUp 0.5s ease forwards;
545
+ }
546
+
547
+ @keyframes fadeSlideUp {
548
+ from { opacity: 0; transform: translateY(20px); }
549
+ to { opacity: 1; transform: translateY(0); }
550
+ }
551
+
552
+ .result-leaf-thumb {
553
+ position: relative;
554
+ }
555
+
556
+ .result-leaf-thumb img {
557
+ width: 100%;
558
+ aspect-ratio: 1;
559
+ object-fit: cover;
560
+ border-radius: 16px;
561
+ box-shadow: 0 8px 32px var(--shadow);
562
+ }
563
+
564
+ .confidence-badge {
565
+ position: absolute;
566
+ bottom: 12px;
567
+ right: 12px;
568
+ background: var(--deep-green);
569
+ color: #fff;
570
+ font-family: var(--ff-ui);
571
+ font-size: 0.82rem;
572
+ font-weight: 600;
573
+ padding: 5px 12px;
574
+ border-radius: 100px;
575
+ }
576
+
577
+ .result-family {
578
+ font-family: var(--ff-ui);
579
+ font-size: 0.8rem;
580
+ font-weight: 500;
581
+ letter-spacing: 0.1em;
582
+ text-transform: uppercase;
583
+ color: var(--gold);
584
+ margin-bottom: 0.5rem;
585
+ }
586
+
587
+ .result-common-name {
588
+ font-family: var(--ff-head);
589
+ font-size: clamp(1.8rem, 3vw, 2.8rem);
590
+ color: var(--deep-green);
591
+ line-height: 1.1;
592
+ margin-bottom: 0.5rem;
593
+ }
594
+
595
+ .result-botanical {
596
+ font-family: var(--ff-body);
597
+ font-style: italic;
598
+ font-size: 1.1rem;
599
+ color: var(--muted);
600
+ margin-bottom: 0.5rem;
601
+ }
602
+
603
+ .result-ayurvedic {
604
+ font-family: var(--ff-ui);
605
+ font-size: 0.88rem;
606
+ color: var(--earth);
607
+ margin-bottom: 1rem;
608
+ }
609
+
610
+ .result-habitat {
611
+ font-size: 0.95rem;
612
+ color: var(--muted);
613
+ line-height: 1.6;
614
+ }
615
+
616
+ .demo-warning {
617
+ margin-top: 1rem;
618
+ background: rgba(232, 160, 32, 0.1);
619
+ border: 1px solid rgba(232, 160, 32, 0.3);
620
+ color: var(--earth);
621
+ padding: 10px 16px;
622
+ border-radius: var(--radius-sm);
623
+ font-family: var(--ff-ui);
624
+ font-size: 0.85rem;
625
+ }
626
+ .demo-warning code { background: rgba(0,0,0,0.06); padding: 2px 6px; border-radius: 4px; }
627
+
628
+ /* Medicinal Grid */
629
+ .medicinal-grid {
630
+ display: grid;
631
+ grid-template-columns: repeat(2, 1fr);
632
+ gap: 1.5rem;
633
+ margin-bottom: 3rem;
634
+ }
635
+
636
+ .med-card {
637
+ background: var(--parchment);
638
+ border: 1px solid var(--border);
639
+ border-radius: var(--radius);
640
+ padding: 1.75rem;
641
+ animation: fadeSlideUp 0.5s ease forwards;
642
+ }
643
+
644
+ .med-card-header {
645
+ display: flex;
646
+ align-items: center;
647
+ gap: 10px;
648
+ margin-bottom: 1.25rem;
649
+ }
650
+
651
+ .med-icon { font-size: 1.3rem; }
652
+
653
+ .med-card-header h3 {
654
+ font-family: var(--ff-head);
655
+ font-size: 1.1rem;
656
+ color: var(--deep-green);
657
+ }
658
+
659
+ .uses-list {
660
+ list-style: none;
661
+ display: flex;
662
+ flex-direction: column;
663
+ gap: 8px;
664
+ }
665
+
666
+ .uses-list li {
667
+ font-size: 0.95rem;
668
+ color: var(--ink);
669
+ padding-left: 20px;
670
+ position: relative;
671
+ line-height: 1.5;
672
+ }
673
+
674
+ .uses-list li::before {
675
+ content: "✦";
676
+ position: absolute;
677
+ left: 0;
678
+ color: var(--leaf-green);
679
+ font-size: 0.65rem;
680
+ top: 5px;
681
+ }
682
+
683
+ .compounds-tags, .parts-tags {
684
+ display: flex;
685
+ flex-wrap: wrap;
686
+ gap: 8px;
687
+ }
688
+
689
+ .compound-tag, .part-tag {
690
+ font-family: var(--ff-ui);
691
+ font-size: 0.78rem;
692
+ font-weight: 500;
693
+ padding: 5px 13px;
694
+ border-radius: 100px;
695
+ }
696
+
697
+ .compound-tag {
698
+ background: rgba(58, 122, 48, 0.1);
699
+ color: var(--deep-green);
700
+ border: 1px solid rgba(58, 122, 48, 0.2);
701
+ }
702
+
703
+ .part-tag {
704
+ background: rgba(201, 149, 58, 0.1);
705
+ color: var(--earth);
706
+ border: 1px solid rgba(201, 149, 58, 0.2);
707
+ }
708
+
709
+ /* Rasa Grid */
710
+ .rasa-grid {
711
+ display: grid;
712
+ grid-template-columns: repeat(2, 1fr);
713
+ gap: 10px;
714
+ }
715
+
716
+ .rasa-item {
717
+ background: white;
718
+ border: 1px solid var(--border);
719
+ border-radius: var(--radius-sm);
720
+ padding: 10px 14px;
721
+ }
722
+
723
+ .rasa-label {
724
+ font-family: var(--ff-ui);
725
+ font-size: 0.7rem;
726
+ font-weight: 600;
727
+ letter-spacing: 0.08em;
728
+ text-transform: uppercase;
729
+ color: var(--gold);
730
+ display: block;
731
+ margin-bottom: 4px;
732
+ }
733
+
734
+ .rasa-value {
735
+ font-family: var(--ff-body);
736
+ font-size: 0.95rem;
737
+ color: var(--deep-green);
738
+ }
739
+
740
+ /* Dosage Card */
741
+ .dosage-text {
742
+ font-size: 0.95rem;
743
+ color: var(--ink);
744
+ margin-bottom: 1rem;
745
+ line-height: 1.6;
746
+ }
747
+
748
+ .precaution-box {
749
+ margin-top: 1rem;
750
+ background: rgba(184, 48, 48, 0.06);
751
+ border-left: 3px solid var(--red-tika);
752
+ padding: 10px 14px;
753
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
754
+ font-size: 0.88rem;
755
+ color: var(--ink);
756
+ line-height: 1.5;
757
+ }
758
+
759
+ /* Top 5 Bars */
760
+ .top5-section {
761
+ background: var(--parchment);
762
+ border: 1px solid var(--border);
763
+ border-radius: var(--radius);
764
+ padding: 2rem;
765
+ margin-bottom: 2rem;
766
+ }
767
+
768
+ .top5-title {
769
+ font-family: var(--ff-head);
770
+ font-size: 1.1rem;
771
+ color: var(--deep-green);
772
+ margin-bottom: 1.5rem;
773
+ }
774
+
775
+ .top5-bars {
776
+ display: flex;
777
+ flex-direction: column;
778
+ gap: 12px;
779
+ }
780
+
781
+ .pred-bar {
782
+ display: grid;
783
+ grid-template-columns: 180px 1fr 60px;
784
+ align-items: center;
785
+ gap: 14px;
786
+ }
787
+
788
+ .pred-name {
789
+ font-family: var(--ff-ui);
790
+ font-size: 0.88rem;
791
+ font-weight: 500;
792
+ color: var(--ink);
793
+ white-space: nowrap;
794
+ overflow: hidden;
795
+ text-overflow: ellipsis;
796
+ }
797
+
798
+ .pred-track {
799
+ height: 8px;
800
+ background: rgba(0,0,0,0.06);
801
+ border-radius: 100px;
802
+ overflow: hidden;
803
+ }
804
+
805
+ .pred-fill {
806
+ height: 100%;
807
+ background: linear-gradient(90deg, var(--leaf-green), var(--deep-green));
808
+ border-radius: 100px;
809
+ transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
810
+ }
811
+
812
+ .pred-fill.top-fill {
813
+ background: linear-gradient(90deg, var(--lime), var(--mid-green));
814
+ }
815
+
816
+ .pred-pct {
817
+ font-family: var(--ff-ui);
818
+ font-size: 0.82rem;
819
+ color: var(--muted);
820
+ text-align: right;
821
+ }
822
+
823
+ .result-actions {
824
+ text-align: center;
825
+ }
826
+
827
+ .btn-new-scan {
828
+ font-family: var(--ff-ui);
829
+ font-size: 0.95rem;
830
+ font-weight: 500;
831
+ color: var(--mid-green);
832
+ background: none;
833
+ border: 2px solid var(--mid-green);
834
+ padding: 12px 32px;
835
+ border-radius: 100px;
836
+ cursor: pointer;
837
+ transition: all var(--transition);
838
+ }
839
+ .btn-new-scan:hover { background: var(--mid-green); color: #fff; }
840
+
841
+ /* ── About Section ──────────────────────────────────────────────────────── */
842
+ .about-section {
843
+ background:
844
+ linear-gradient(160deg, #f8f4e8 0%, #f1ead7 48%, #e7dcc2 100%);
845
+ color: var(--deep-green);
846
+ border-top: 1px solid rgba(90, 171, 64, 0.2);
847
+ border-bottom: 1px solid rgba(90, 171, 64, 0.2);
848
+ }
849
+
850
+ .about-grid {
851
+ display: grid;
852
+ grid-template-columns: 1fr 360px;
853
+ gap: 4rem;
854
+ align-items: center;
855
+ }
856
+
857
+ .about-text .section-label { color: var(--mid-green); }
858
+
859
+ .about-text h2 {
860
+ font-family: var(--ff-head);
861
+ font-size: clamp(1.8rem, 3.5vw, 2.8rem);
862
+ color: var(--deep-green);
863
+ margin-bottom: 1.5rem;
864
+ line-height: 1.1;
865
+ }
866
+
867
+ .about-text p {
868
+ font-size: 1rem;
869
+ color: #395637;
870
+ margin-bottom: 1rem;
871
+ line-height: 1.75;
872
+ }
873
+
874
+ .about-text strong { color: var(--mid-green); font-weight: 700; }
875
+
876
+ .stats-grid {
877
+ display: grid;
878
+ grid-template-columns: 1fr 1fr;
879
+ gap: 1rem;
880
+ }
881
+
882
+ .stat-box {
883
+ background: rgba(255, 255, 255, 0.72);
884
+ border: 1px solid rgba(58, 122, 48, 0.18);
885
+ border-radius: var(--radius);
886
+ padding: 1.5rem;
887
+ text-align: center;
888
+ transition: all var(--transition);
889
+ }
890
+ .stat-box:hover {
891
+ background: rgba(255, 255, 255, 0.92);
892
+ border-color: rgba(58, 122, 48, 0.35);
893
+ }
894
+
895
+ .stat-number {
896
+ display: block;
897
+ font-family: var(--ff-head);
898
+ font-size: 2.2rem;
899
+ color: var(--lime);
900
+ line-height: 1.1;
901
+ }
902
+
903
+ .stat-label {
904
+ display: block;
905
+ font-family: var(--ff-ui);
906
+ font-size: 0.78rem;
907
+ color: #54704f;
908
+ margin-top: 4px;
909
+ letter-spacing: 0.04em;
910
+ }
911
+
912
+ /* ── Plants Browser ──────────────────────────────────────────────────────── */
913
+ .plants-section { background: var(--parchment); }
914
+
915
+ .search-bar-wrap {
916
+ max-width: 480px;
917
+ margin: 0 auto 2.5rem;
918
+ }
919
+
920
+ .plant-search {
921
+ width: 100%;
922
+ font-family: var(--ff-ui);
923
+ font-size: 1rem;
924
+ padding: 12px 20px;
925
+ border: 2px solid var(--border);
926
+ border-radius: 100px;
927
+ background: white;
928
+ color: var(--ink);
929
+ outline: none;
930
+ transition: border-color var(--transition);
931
+ }
932
+ .plant-search:focus { border-color: var(--mid-green); }
933
+ .plant-search::placeholder { color: var(--muted); }
934
+
935
+ .plant-grid {
936
+ display: grid;
937
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
938
+ gap: 1rem;
939
+ }
940
+
941
+ .plant-pill {
942
+ background: white;
943
+ border: 1px solid var(--border);
944
+ border-radius: var(--radius-sm);
945
+ padding: 1rem 1.25rem;
946
+ cursor: pointer;
947
+ transition: all var(--transition);
948
+ }
949
+ .plant-pill:hover {
950
+ border-color: var(--leaf-green);
951
+ box-shadow: 0 4px 16px var(--shadow);
952
+ transform: translateY(-2px);
953
+ }
954
+
955
+ .plant-pill.active {
956
+ background: linear-gradient(135deg, rgba(90, 171, 64, 0.14), rgba(58, 122, 48, 0.08));
957
+ border-color: rgba(58, 122, 48, 0.42);
958
+ }
959
+
960
+ .pill-name {
961
+ font-family: var(--ff-head);
962
+ font-size: 0.95rem;
963
+ color: var(--deep-green);
964
+ display: block;
965
+ margin-bottom: 3px;
966
+ }
967
+
968
+ .pill-botanical {
969
+ font-family: var(--ff-body);
970
+ font-style: italic;
971
+ font-size: 0.78rem;
972
+ color: var(--muted);
973
+ display: block;
974
+ }
975
+
976
+ .plant-detail-card {
977
+ margin-top: 2rem;
978
+ background: white;
979
+ border: 1px solid var(--border);
980
+ border-radius: 20px;
981
+ box-shadow: 0 8px 36px var(--shadow);
982
+ overflow: hidden;
983
+ }
984
+
985
+ .plant-detail-head {
986
+ padding: 1.8rem 2rem 1.4rem;
987
+ background: linear-gradient(160deg, rgba(90, 171, 64, 0.14), rgba(255, 255, 255, 0.94));
988
+ border-bottom: 1px solid var(--border);
989
+ }
990
+
991
+ .plant-detail-family {
992
+ font-family: var(--ff-ui);
993
+ font-size: 0.78rem;
994
+ letter-spacing: 0.09em;
995
+ text-transform: uppercase;
996
+ color: #567050;
997
+ }
998
+
999
+ .plant-detail-name {
1000
+ font-family: var(--ff-head);
1001
+ font-size: clamp(1.6rem, 3vw, 2.3rem);
1002
+ color: var(--deep-green);
1003
+ margin-top: 0.25rem;
1004
+ }
1005
+
1006
+ .plant-detail-botanical {
1007
+ font-family: var(--ff-body);
1008
+ font-style: italic;
1009
+ color: #456142;
1010
+ margin-top: 0.2rem;
1011
+ }
1012
+
1013
+ .plant-detail-ayurvedic,
1014
+ .plant-detail-habitat {
1015
+ font-family: var(--ff-ui);
1016
+ font-size: 0.92rem;
1017
+ color: #496346;
1018
+ margin-top: 0.45rem;
1019
+ }
1020
+
1021
+ .plant-detail-grid {
1022
+ padding: 1.5rem 1.5rem 1.8rem;
1023
+ display: grid;
1024
+ grid-template-columns: repeat(2, 1fr);
1025
+ gap: 1rem;
1026
+ }
1027
+
1028
+ .plant-detail-block {
1029
+ background: #f9f5ea;
1030
+ border: 1px solid rgba(90, 171, 64, 0.16);
1031
+ border-radius: var(--radius);
1032
+ padding: 1rem 1.1rem;
1033
+ }
1034
+
1035
+ .plant-detail-block h4 {
1036
+ font-family: var(--ff-head);
1037
+ color: var(--deep-green);
1038
+ font-size: 1.02rem;
1039
+ margin-bottom: 0.65rem;
1040
+ }
1041
+
1042
+ .plant-detail-block ul {
1043
+ list-style: none;
1044
+ display: flex;
1045
+ flex-direction: column;
1046
+ gap: 0.45rem;
1047
+ }
1048
+
1049
+ .plant-detail-block li {
1050
+ font-size: 0.95rem;
1051
+ line-height: 1.45;
1052
+ color: var(--ink);
1053
+ padding-left: 1rem;
1054
+ position: relative;
1055
+ }
1056
+
1057
+ .plant-detail-block li::before {
1058
+ content: "β€’";
1059
+ position: absolute;
1060
+ left: 0;
1061
+ color: var(--mid-green);
1062
+ }
1063
+
1064
+ .detail-tags {
1065
+ display: flex;
1066
+ flex-wrap: wrap;
1067
+ gap: 8px;
1068
+ }
1069
+
1070
+ .detail-tag {
1071
+ font-family: var(--ff-ui);
1072
+ font-size: 0.77rem;
1073
+ font-weight: 500;
1074
+ padding: 4px 11px;
1075
+ border-radius: 999px;
1076
+ border: 1px solid rgba(58, 122, 48, 0.22);
1077
+ background: rgba(90, 171, 64, 0.1);
1078
+ color: var(--deep-green);
1079
+ }
1080
+
1081
+ .detail-tag.parts {
1082
+ border-color: rgba(201, 149, 58, 0.3);
1083
+ background: rgba(201, 149, 58, 0.12);
1084
+ color: #7a5d32;
1085
+ }
1086
+
1087
+ .detail-rasa-grid {
1088
+ display: grid;
1089
+ grid-template-columns: repeat(2, 1fr);
1090
+ gap: 8px;
1091
+ }
1092
+
1093
+ .detail-rasa-item {
1094
+ background: #fff;
1095
+ border: 1px solid rgba(90, 171, 64, 0.2);
1096
+ border-radius: 8px;
1097
+ padding: 7px 9px;
1098
+ }
1099
+
1100
+ .detail-rasa-label {
1101
+ display: block;
1102
+ font-family: var(--ff-ui);
1103
+ font-size: 0.68rem;
1104
+ text-transform: uppercase;
1105
+ letter-spacing: 0.08em;
1106
+ color: #718468;
1107
+ }
1108
+
1109
+ .detail-rasa-value {
1110
+ font-size: 0.9rem;
1111
+ color: var(--deep-green);
1112
+ }
1113
+
1114
+ .detail-dosage,
1115
+ .detail-precautions {
1116
+ font-size: 0.92rem;
1117
+ color: #3d563a;
1118
+ }
1119
+
1120
+ .detail-precautions {
1121
+ margin-top: 0.7rem;
1122
+ border-left: 3px solid #be4c4c;
1123
+ padding-left: 0.6rem;
1124
+ }
1125
+
1126
+ /* ── Footer ─────────────────────────────────────────────────────────────── */
1127
+ .site-footer {
1128
+ background: var(--ink);
1129
+ color: rgba(255,255,255,0.5);
1130
+ }
1131
+
1132
+ .footer-inner {
1133
+ max-width: 1200px;
1134
+ margin: 0 auto;
1135
+ padding: 3rem 2rem;
1136
+ text-align: center;
1137
+ }
1138
+
1139
+ .footer-logo {
1140
+ display: flex;
1141
+ align-items: baseline;
1142
+ justify-content: center;
1143
+ gap: 10px;
1144
+ margin-bottom: 1rem;
1145
+ }
1146
+
1147
+ .footer-title {
1148
+ font-family: var(--ff-head);
1149
+ font-size: 1.5rem;
1150
+ color: var(--lime);
1151
+ }
1152
+
1153
+ .footer-sub {
1154
+ font-family: var(--ff-body);
1155
+ font-style: italic;
1156
+ color: rgba(255,255,255,0.3);
1157
+ }
1158
+
1159
+ .footer-note, .footer-disclaimer {
1160
+ font-family: var(--ff-ui);
1161
+ font-size: 0.82rem;
1162
+ line-height: 1.7;
1163
+ margin-bottom: 0.5rem;
1164
+ }
1165
+
1166
+ .footer-note em { color: rgba(255,255,255,0.7); }
1167
+
1168
+ /* ── Floating leaf animation ─────────────────────────────────────────────── */
1169
+ .floating-leaf {
1170
+ position: absolute;
1171
+ width: 24px;
1172
+ height: 24px;
1173
+ opacity: 0.06;
1174
+ animation: floatLeaf linear infinite;
1175
+ color: var(--leaf-green);
1176
+ font-size: 20px;
1177
+ pointer-events: none;
1178
+ }
1179
+
1180
+ @keyframes floatLeaf {
1181
+ 0% { transform: translateY(100vh) rotate(0deg); opacity: 0; }
1182
+ 10% { opacity: 0.06; }
1183
+ 90% { opacity: 0.06; }
1184
+ 100% { transform: translateY(-100px) rotate(360deg); opacity: 0; }
1185
+ }
1186
+
1187
+ /* ── Responsive ──────────────────────────────────────────────────────────── */
1188
+ @media (max-width: 900px) {
1189
+ .hero { flex-direction: column; min-height: auto; padding-top: 4rem; }
1190
+ .hero-decoration { display: none; }
1191
+
1192
+ .result-hero-card { grid-template-columns: 1fr; }
1193
+ .result-leaf-thumb img { max-height: 240px; }
1194
+
1195
+ .medicinal-grid { grid-template-columns: 1fr; }
1196
+ .about-grid { grid-template-columns: 1fr; }
1197
+ .stats-grid { grid-template-columns: repeat(4, 1fr); }
1198
+ .header-nav { display: none; }
1199
+ }
1200
+
1201
+ @media (max-width: 600px) {
1202
+ .stats-grid { grid-template-columns: 1fr 1fr; }
1203
+ .rasa-grid { grid-template-columns: 1fr; }
1204
+ .pred-bar { grid-template-columns: 120px 1fr 50px; }
1205
+ .plant-detail-grid { grid-template-columns: 1fr; }
1206
+ }
static/js/app.js ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ═══════════════════════════════════════════════════════════════════════════
2
+ VrukshaVed Frontend β€” Plant Identification App
3
+ ═══════════════════════════════════════════════════════════════════════════ */
4
+
5
+ const API_BASE = window.location.origin;
6
+
7
+ // ── State ──────────────────────────────────────────────────────────────────
8
+ let currentImageBlob = null;
9
+ let mediaStream = null;
10
+
11
+ // ── DOM References ─────────────────────────────────────────────────────────
12
+ const uploadArea = document.getElementById('uploadArea');
13
+ const uploadIdle = document.getElementById('uploadIdle');
14
+ const fileInput = document.getElementById('fileInput');
15
+ const cameraBtn = document.getElementById('cameraBtn');
16
+ const cameraView = document.getElementById('cameraView');
17
+ const cameraFeed = document.getElementById('cameraFeed');
18
+ const cameraCanvas = document.getElementById('cameraCanvas');
19
+ const captureBtn = document.getElementById('captureBtn');
20
+ const cancelCameraBtn = document.getElementById('cancelCameraBtn');
21
+ const imagePreview = document.getElementById('imagePreview');
22
+ const previewImg = document.getElementById('previewImg');
23
+ const analyzeBtn = document.getElementById('analyzeBtn');
24
+ const clearBtn = document.getElementById('clearBtn');
25
+ const resultsSection = document.getElementById('resultsSection');
26
+
27
+ // ── Leaf Scatter Animation ─────────────────────────────────────────────────
28
+ function initLeafScatter() {
29
+ const container = document.getElementById('leafScatter');
30
+ const symbols = ['🌿', 'πŸƒ', '🌱', 'πŸ€', '☘️'];
31
+ for (let i = 0; i < 14; i++) {
32
+ const el = document.createElement('div');
33
+ el.className = 'floating-leaf';
34
+ el.textContent = symbols[Math.floor(Math.random() * symbols.length)];
35
+ el.style.left = Math.random() * 100 + 'vw';
36
+ el.style.animationDuration = (20 + Math.random() * 25) + 's';
37
+ el.style.animationDelay = -(Math.random() * 30) + 's';
38
+ el.style.fontSize = (14 + Math.random() * 14) + 'px';
39
+ container.appendChild(el);
40
+ }
41
+ }
42
+
43
+ // ── File Upload ───────────────────────────────────────────────────────────
44
+ fileInput.addEventListener('change', e => {
45
+ const file = e.target.files[0];
46
+ if (file) handleImageFile(file);
47
+ });
48
+
49
+ // Drag & Drop
50
+ uploadArea.addEventListener('dragover', e => {
51
+ e.preventDefault();
52
+ uploadArea.classList.add('drag-over');
53
+ });
54
+ uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('drag-over'));
55
+ uploadArea.addEventListener('drop', e => {
56
+ e.preventDefault();
57
+ uploadArea.classList.remove('drag-over');
58
+ const file = e.dataTransfer.files[0];
59
+ if (file && file.type.startsWith('image/')) handleImageFile(file);
60
+ });
61
+
62
+ function handleImageFile(file) {
63
+ const reader = new FileReader();
64
+ reader.onload = evt => {
65
+ currentImageBlob = file;
66
+ showPreview(evt.target.result);
67
+ };
68
+ reader.readAsDataURL(file);
69
+ }
70
+
71
+ // ── Camera ────────────────────────────────────────────────────────────────
72
+ cameraBtn.addEventListener('click', async () => {
73
+ try {
74
+ mediaStream = await navigator.mediaDevices.getUserMedia({
75
+ video: { facingMode: 'environment', width: { ideal: 1280 }, height: { ideal: 720 } }
76
+ });
77
+ cameraFeed.srcObject = mediaStream;
78
+ uploadIdle.style.display = 'none';
79
+ cameraView.style.display = 'block';
80
+ } catch (err) {
81
+ alert('Camera access denied or not available. Please upload an image instead.');
82
+ }
83
+ });
84
+
85
+ captureBtn.addEventListener('click', () => {
86
+ const ctx = cameraCanvas.getContext('2d');
87
+ cameraCanvas.width = cameraFeed.videoWidth;
88
+ cameraCanvas.height = cameraFeed.videoHeight;
89
+ ctx.drawImage(cameraFeed, 0, 0);
90
+
91
+ cameraCanvas.toBlob(blob => {
92
+ currentImageBlob = blob;
93
+ const url = URL.createObjectURL(blob);
94
+ showPreview(url);
95
+ stopCamera();
96
+ }, 'image/jpeg', 0.92);
97
+ });
98
+
99
+ cancelCameraBtn.addEventListener('click', () => {
100
+ stopCamera();
101
+ cameraView.style.display = 'none';
102
+ uploadIdle.style.display = 'block';
103
+ });
104
+
105
+ function stopCamera() {
106
+ if (mediaStream) {
107
+ mediaStream.getTracks().forEach(t => t.stop());
108
+ mediaStream = null;
109
+ }
110
+ }
111
+
112
+ // ── Preview ───────────────────────────────────────────────────────────────
113
+ function showPreview(src) {
114
+ uploadIdle.style.display = 'none';
115
+ cameraView.style.display = 'none';
116
+ imagePreview.style.display = 'flex';
117
+ previewImg.src = src;
118
+ resultsSection.style.display = 'none';
119
+ }
120
+
121
+ clearBtn.addEventListener('click', resetApp);
122
+
123
+ // ── Analyze ───────────────────────────────────────────────────────────────
124
+ analyzeBtn.addEventListener('click', async () => {
125
+ if (!currentImageBlob) return;
126
+
127
+ const btnText = analyzeBtn.querySelector('.btn-text');
128
+ const spinner = analyzeBtn.querySelector('.btn-spinner');
129
+ btnText.style.display = 'none';
130
+ spinner.style.display = 'flex';
131
+ analyzeBtn.disabled = true;
132
+
133
+ try {
134
+ const formData = new FormData();
135
+ formData.append('file', currentImageBlob, 'leaf.jpg');
136
+
137
+ const res = await fetch(`${API_BASE}/api/predict`, {
138
+ method: 'POST',
139
+ body: formData
140
+ });
141
+
142
+ if (!res.ok) {
143
+ const err = await res.json();
144
+ throw new Error(err.error || 'Server error');
145
+ }
146
+
147
+ const data = await res.json();
148
+ displayResults(data);
149
+
150
+ } catch (err) {
151
+ alert('Error: ' + err.message + '\n\nMake sure the backend server is running on port 5000.');
152
+ } finally {
153
+ btnText.style.display = 'inline';
154
+ spinner.style.display = 'none';
155
+ analyzeBtn.disabled = false;
156
+ }
157
+ });
158
+
159
+ // ── Display Results ───────────────────────────────────────────────────────
160
+ function displayResults(data) {
161
+ resultsSection.style.display = 'block';
162
+ resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
163
+
164
+ const top = data.top_prediction;
165
+ const info = data.plant_info || {};
166
+
167
+ // Thumbnail
168
+ document.getElementById('resultThumb').src = previewImg.src;
169
+ document.getElementById('confidenceBadge').textContent = top.confidence_pct;
170
+
171
+ // Identity
172
+ document.getElementById('resultFamily').textContent =
173
+ info.family ? `Family: ${info.family}` : '';
174
+ document.getElementById('resultCommonName').textContent =
175
+ top.plant_name.replace(/_/g, ' ');
176
+ document.getElementById('resultBotanical').textContent =
177
+ info.botanical_name ? info.botanical_name : '';
178
+ document.getElementById('resultAyurvedic').textContent =
179
+ info.ayurvedic_name ? `Ayurvedic name: ${info.ayurvedic_name}` : '';
180
+ document.getElementById('resultHabitat').textContent =
181
+ info.habitat || info.description || '';
182
+
183
+ // Demo mode warning
184
+ document.getElementById('demoWarning').style.display =
185
+ data.demo_mode ? 'block' : 'none';
186
+
187
+ // Medicinal Uses
188
+ const ul = document.getElementById('usesList');
189
+ ul.innerHTML = '';
190
+ (info.medicinal_uses || ['Information not available']).forEach(use => {
191
+ const li = document.createElement('li');
192
+ li.textContent = use;
193
+ ul.appendChild(li);
194
+ });
195
+
196
+ // Active Compounds
197
+ const ct = document.getElementById('compoundsTags');
198
+ ct.innerHTML = '';
199
+ (info.active_compounds || []).forEach(c => {
200
+ const span = document.createElement('span');
201
+ span.className = 'compound-tag';
202
+ span.textContent = c;
203
+ ct.appendChild(span);
204
+ });
205
+
206
+ // Rasa Guna
207
+ const rg = document.getElementById('rasaGrid');
208
+ rg.innerHTML = '';
209
+ if (info.rasa_guna) {
210
+ Object.entries(info.rasa_guna).forEach(([label, value]) => {
211
+ rg.innerHTML += `
212
+ <div class="rasa-item">
213
+ <span class="rasa-label">${label}</span>
214
+ <span class="rasa-value">${value}</span>
215
+ </div>`;
216
+ });
217
+ }
218
+
219
+ // Dosage
220
+ document.getElementById('dosageText').textContent = info.dosage_form || '';
221
+
222
+ const pt = document.getElementById('partsTags');
223
+ pt.innerHTML = '';
224
+ (info.parts_used || []).forEach(p => {
225
+ const span = document.createElement('span');
226
+ span.className = 'part-tag';
227
+ span.textContent = p;
228
+ pt.appendChild(span);
229
+ });
230
+
231
+ document.getElementById('precautionBox').textContent =
232
+ info.precautions ? '⚠️ ' + info.precautions : '';
233
+
234
+ // Top 5 Bars
235
+ const bars = document.getElementById('top5Bars');
236
+ bars.innerHTML = '';
237
+ data.all_predictions.forEach((pred, i) => {
238
+ const pct = (pred.confidence * 100).toFixed(1);
239
+ bars.innerHTML += `
240
+ <div class="pred-bar">
241
+ <span class="pred-name">${pred.plant}</span>
242
+ <div class="pred-track">
243
+ <div class="pred-fill ${i === 0 ? 'top-fill' : ''}" style="width: 0%"
244
+ data-width="${pct}%"></div>
245
+ </div>
246
+ <span class="pred-pct">${pct}%</span>
247
+ </div>`;
248
+ });
249
+
250
+ // Animate bars after render
251
+ requestAnimationFrame(() => {
252
+ document.querySelectorAll('.pred-fill').forEach(el => {
253
+ el.style.width = el.dataset.width;
254
+ });
255
+ });
256
+ }
257
+
258
+ // ── Plants Grid ─���─────────────────────────────────────────────────────────
259
+ async function loadPlantsGrid() {
260
+ try {
261
+ const res = await fetch(`${API_BASE}/api/plants`);
262
+ const data = await res.json();
263
+ renderPlantsGrid(data.plants);
264
+
265
+ document.getElementById('plantSearch').addEventListener('input', e => {
266
+ const q = e.target.value.toLowerCase();
267
+ renderPlantsGrid(data.plants.filter(p => p.toLowerCase().includes(q)));
268
+ });
269
+ } catch (err) {
270
+ // Silent fail - backend may not be running
271
+ }
272
+ }
273
+
274
+ function renderPlantsGrid(plants) {
275
+ const grid = document.getElementById('plantGrid');
276
+ grid.innerHTML = '';
277
+ plants.forEach(name => {
278
+ const div = document.createElement('div');
279
+ div.className = 'plant-pill';
280
+ div.dataset.plantName = name;
281
+ div.innerHTML = `
282
+ <span class="pill-name">${name}</span>
283
+ `;
284
+ div.addEventListener('click', () => showPlantDetails(name));
285
+ grid.appendChild(div);
286
+ });
287
+ }
288
+
289
+ async function showPlantDetails(name) {
290
+ try {
291
+ const res = await fetch(`${API_BASE}/api/plant/${encodeURIComponent(name)}`);
292
+ const data = await res.json();
293
+ if (data.found && data.info) {
294
+ renderPlantDetails(name, data.info);
295
+
296
+ document
297
+ .querySelectorAll('.plant-pill.active')
298
+ .forEach(el => el.classList.remove('active'));
299
+
300
+ const selected = document.querySelector(`.plant-pill[data-plant-name="${CSS.escape(name)}"]`);
301
+ if (selected) selected.classList.add('active');
302
+ }
303
+ } catch (e) { /* silent */ }
304
+ }
305
+
306
+ function renderPlantDetails(name, info) {
307
+ const card = document.getElementById('plantDetailCard');
308
+ card.style.display = 'block';
309
+
310
+ document.getElementById('detailName').textContent = name;
311
+ document.getElementById('detailFamily').textContent = info.family ? `Family: ${info.family}` : '';
312
+ document.getElementById('detailBotanical').textContent = info.botanical_name || '';
313
+ document.getElementById('detailAyurvedic').textContent = info.ayurvedic_name ? `Ayurvedic name: ${info.ayurvedic_name}` : '';
314
+ document.getElementById('detailHabitat').textContent = info.habitat || info.description || '';
315
+ document.getElementById('detailDosage').textContent = info.dosage_form || 'Dosage information not available.';
316
+ document.getElementById('detailPrecautions').textContent =
317
+ info.precautions ? `Precautions: ${info.precautions}` : 'Precautions: Not specified.';
318
+
319
+ const uses = document.getElementById('detailUses');
320
+ uses.innerHTML = '';
321
+ (info.medicinal_uses || ['Information not available']).forEach(use => {
322
+ const li = document.createElement('li');
323
+ li.textContent = use;
324
+ uses.appendChild(li);
325
+ });
326
+
327
+ const compounds = document.getElementById('detailCompounds');
328
+ compounds.innerHTML = '';
329
+ (info.active_compounds || []).forEach(compound => {
330
+ const tag = document.createElement('span');
331
+ tag.className = 'detail-tag';
332
+ tag.textContent = compound;
333
+ compounds.appendChild(tag);
334
+ });
335
+
336
+ const parts = document.getElementById('detailParts');
337
+ parts.innerHTML = '';
338
+ (info.parts_used || []).forEach(part => {
339
+ const tag = document.createElement('span');
340
+ tag.className = 'detail-tag parts';
341
+ tag.textContent = part;
342
+ parts.appendChild(tag);
343
+ });
344
+
345
+ const rasa = document.getElementById('detailRasa');
346
+ rasa.innerHTML = '';
347
+ Object.entries(info.rasa_guna || {}).forEach(([label, value]) => {
348
+ const item = document.createElement('div');
349
+ item.className = 'detail-rasa-item';
350
+ item.innerHTML = `
351
+ <span class="detail-rasa-label">${label}</span>
352
+ <span class="detail-rasa-value">${value}</span>
353
+ `;
354
+ rasa.appendChild(item);
355
+ });
356
+
357
+ card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
358
+ }
359
+
360
+ // ── Reset ─────────────────────────────────────────────────────────────────
361
+ function resetApp() {
362
+ currentImageBlob = null;
363
+ fileInput.value = '';
364
+ imagePreview.style.display = 'none';
365
+ uploadIdle.style.display = 'block';
366
+ resultsSection.style.display = 'none';
367
+ document.getElementById('uploadSection') &&
368
+ document.getElementById('uploadSection').scrollIntoView({ behavior: 'smooth' });
369
+ window.scrollTo({ top: document.getElementById('upload-section').offsetTop - 80, behavior: 'smooth' });
370
+ }
371
+
372
+ // ── Init ──────────────────────────────────────────────────────────────────
373
+ document.addEventListener('DOMContentLoaded', () => {
374
+ initLeafScatter();
375
+ loadPlantsGrid();
376
+ });