pavan1221 commited on
Commit
f7c4d7a
Β·
verified Β·
1 Parent(s): 3992eb1

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +533 -0
index.html ADDED
@@ -0,0 +1,533 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <meta name="theme-color" content="#f7f3ee">
7
+ <title>EucalyptusLens</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=DM+Mono:wght@300;400;500&family=Outfit:wght@200;300;400;500&display=swap" rel="stylesheet">
9
+ <style>
10
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
11
+ :root{
12
+ --bg:#f7f3ee;--bg2:#f0eae0;--surface:#ffffff;--surface2:#faf7f2;
13
+ --border:#e2d9cc;--border2:#d4c8b8;
14
+ --rose:#c97ba8;--mauve:#9b6fa0;--periwinkle:#7b8fd4;
15
+ --teal:#4aabb5;--amber:#d4955a;--sage:#8aaa6a;--lilac:#c4b8e8;
16
+ --ink:#2a2035;--text:#3a2f48;--text2:#7a6d88;--text3:#b0a4bc
17
+ }
18
+ html{scroll-behavior:smooth;-webkit-tap-highlight-color:transparent}
19
+ body{background:var(--bg);color:var(--text);font-family:'Outfit',sans-serif;font-weight:300;min-height:100dvh;overflow-x:hidden}
20
+ body::before{content:'';position:fixed;inset:0;
21
+ background:
22
+ radial-gradient(ellipse 70% 50% at 0% 0%,rgba(123,143,212,.12) 0%,transparent 55%),
23
+ radial-gradient(ellipse 60% 60% at 100% 0%,rgba(201,123,168,.10) 0%,transparent 50%),
24
+ radial-gradient(ellipse 50% 40% at 50% 100%,rgba(74,171,181,.08) 0%,transparent 50%);
25
+ pointer-events:none;z-index:0}
26
+ *{position:relative;z-index:1}
27
+
28
+ /* NAV */
29
+ nav{display:flex;align-items:center;justify-content:space-between;padding:18px 32px;
30
+ background:rgba(247,243,238,.88);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);
31
+ border-bottom:1px solid var(--border);position:sticky;top:0;z-index:200}
32
+ .logo{font-family:'Cormorant Garamond',serif;font-size:22px;font-weight:600;
33
+ background:linear-gradient(135deg,var(--mauve),var(--periwinkle));
34
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
35
+ .logo span{font-style:italic;background:linear-gradient(135deg,var(--teal),var(--sage));
36
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
37
+ .nav-badge{font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.1em;padding:5px 12px;
38
+ border-radius:20px;background:linear-gradient(135deg,rgba(123,143,212,.12),rgba(201,123,168,.12));
39
+ border:1px solid var(--lilac);color:var(--mauve)}
40
+
41
+ /* HERO */
42
+ .hero{text-align:center;padding:56px 24px 44px;animation:fadeUp .7s ease both}
43
+ .hero-tag{font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.25em;text-transform:uppercase;
44
+ color:var(--teal);margin-bottom:16px;display:flex;align-items:center;justify-content:center;gap:12px}
45
+ .hero-tag::before,.hero-tag::after{content:'';width:28px;height:1px;background:var(--border2)}
46
+ .hero h1{font-family:'Cormorant Garamond',serif;font-size:clamp(40px,8vw,80px);font-weight:300;
47
+ line-height:1.05;color:var(--ink);margin-bottom:14px;letter-spacing:-.02em}
48
+ .hero h1 em{font-style:italic;background:linear-gradient(135deg,var(--rose),var(--periwinkle));
49
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
50
+ .hero p{font-size:15px;color:var(--text2);max-width:420px;margin:0 auto;line-height:1.75}
51
+
52
+ .tagline{text-align:center;padding:28px 24px 20px;
53
+ font-family:'DM Mono',monospace;font-size:13px;
54
+ letter-spacing:.2em;color:var(--text3);text-transform:uppercase}
55
+ /* MAIN */
56
+ .main{max-width:960px;margin:0 auto;padding:0 24px 80px}
57
+
58
+ /* UPLOAD GRID */
59
+ .upload-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px}
60
+
61
+ /* FILE UPLOAD BOX */
62
+ .upload-box{border:2px dashed var(--border2);border-radius:16px;padding:44px 20px;
63
+ text-align:center;cursor:pointer;background:var(--surface);
64
+ transition:all .3s;overflow:hidden;display:block;
65
+ text-decoration:none;color:inherit}
66
+ .upload-box:hover{border-color:var(--periwinkle);transform:translateY(-2px);
67
+ box-shadow:0 8px 28px rgba(123,143,212,.12)}
68
+ .upload-box:active{transform:scale(.98)}
69
+ .upload-box input[type=file]{position:absolute;inset:0;width:100%;height:100%;
70
+ opacity:0;cursor:pointer;font-size:0}
71
+
72
+ /* CAMERA BOX */
73
+ .camera-box{border:2px dashed var(--border2);border-radius:16px;overflow:hidden;
74
+ background:var(--surface);display:flex;flex-direction:column;min-height:200px}
75
+ #cameraFeed{width:100%;flex:1;object-fit:cover;display:none}
76
+ #cameraCanvas{display:none}
77
+ .camera-placeholder{flex:1;display:flex;flex-direction:column;align-items:center;
78
+ justify-content:center;padding:28px 20px;text-align:center}
79
+ .camera-controls{display:flex;gap:8px;padding:12px;background:var(--bg2);
80
+ border-top:1px solid var(--border);justify-content:center}
81
+
82
+ /* ZONE ICON */
83
+ .zone-icon{width:52px;height:52px;margin:0 auto 14px;border-radius:14px;
84
+ display:flex;align-items:center;justify-content:center}
85
+ .upload-icon{background:linear-gradient(135deg,rgba(123,143,212,.15),rgba(201,123,168,.15))}
86
+ .camera-icon-bg{background:linear-gradient(135deg,rgba(74,171,181,.15),rgba(138,170,106,.15))}
87
+ .zone-h3{font-family:'Cormorant Garamond',serif;font-size:18px;font-weight:500;color:var(--ink);margin-bottom:5px}
88
+ .zone-p{font-size:12px;color:var(--text3);line-height:1.5}
89
+ .formats{font-family:'DM Mono',monospace;font-size:9px;color:var(--text3);margin-top:10px;letter-spacing:.1em}
90
+
91
+ /* PREVIEW */
92
+ .preview-container{display:none;margin-bottom:14px;border:1px solid var(--border);
93
+ border-radius:16px;overflow:hidden;background:var(--surface);
94
+ box-shadow:0 4px 20px rgba(123,143,212,.08);animation:fadeUp .3s ease both}
95
+ .preview-header{display:flex;align-items:center;justify-content:space-between;
96
+ padding:10px 18px;border-bottom:1px solid var(--border);background:var(--bg2)}
97
+ .preview-header span{font-family:'DM Mono',monospace;font-size:10px;color:var(--text3);
98
+ letter-spacing:.12em;text-transform:uppercase}
99
+ #previewImg{width:100%;max-height:260px;object-fit:contain;padding:16px;display:block}
100
+
101
+ /* BUTTONS */
102
+ .btn{padding:13px 24px;border:none;border-radius:10px;cursor:pointer;
103
+ font-family:'DM Mono',monospace;font-size:11px;letter-spacing:.12em;
104
+ text-transform:uppercase;transition:all .2s;touch-action:manipulation}
105
+ .btn-primary{background:linear-gradient(135deg,var(--periwinkle),var(--mauve));color:#fff;
106
+ width:100%;font-size:13px;padding:16px;
107
+ box-shadow:0 4px 16px rgba(123,143,212,.25);border-radius:12px}
108
+ .btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(123,143,212,.35)}
109
+ .btn-primary:active{transform:scale(.98)}
110
+ .btn-primary:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}
111
+ .btn-sm{background:var(--surface);color:var(--text2);border:1px solid var(--border);
112
+ font-size:10px;padding:9px 16px;border-radius:8px}
113
+ .btn-sm:hover{background:var(--bg2);border-color:var(--periwinkle);color:var(--periwinkle)}
114
+ .btn-danger{background:transparent;color:var(--rose);border:1px solid rgba(201,123,168,.3);
115
+ font-size:10px;padding:6px 14px;border-radius:8px}
116
+ .btn-danger:hover{background:rgba(201,123,168,.08)}
117
+
118
+ /* LOADING */
119
+ .loading{display:none;text-align:center;padding:48px 24px;border:1px solid var(--border);
120
+ border-radius:16px;background:var(--surface);margin-top:14px}
121
+ .spinner{width:44px;height:44px;border-radius:50%;border:2px solid var(--border);
122
+ border-top-color:var(--periwinkle);border-right-color:var(--rose);
123
+ animation:spin 1s linear infinite;margin:0 auto 18px}
124
+ .loading p{font-family:'DM Mono',monospace;font-size:11px;color:var(--text3);letter-spacing:.15em}
125
+ .loading-steps{margin-top:14px;display:flex;flex-direction:column;gap:7px;align-items:center}
126
+ .loading-step{font-size:11px;color:var(--text3);font-family:'DM Mono',monospace;
127
+ opacity:0;animation:fadeIn .5s ease forwards}
128
+
129
+ /* TOOLTIP */
130
+ .img-wrapper{position:relative;display:inline-block;width:100%}
131
+ .img-wrapper img{width:100%;display:block}
132
+ .tooltip-dot{position:absolute;width:0;height:0;cursor:pointer;transform:translate(-50%,-50%)}
133
+ .plant-tooltip{
134
+ position:absolute;
135
+ background:rgba(30,24,40,.85);
136
+ color:#fff;
137
+ font-family:'DM Mono',monospace;
138
+ font-size:11px;
139
+ letter-spacing:.05em;
140
+ padding:6px 12px;
141
+ border-radius:8px;
142
+ pointer-events:none;
143
+ white-space:nowrap;
144
+ opacity:0;
145
+ transition:opacity .15s;
146
+ z-index:10;
147
+ transform:translate(-50%, -110%);
148
+ }
149
+ .plant-tooltip.visible{opacity:1}
150
+ /* RESULT */
151
+ .result-area{display:none;margin-top:14px;animation:fadeUp .5s ease both}
152
+ .result-annotated{border:1px solid var(--border);border-radius:16px;overflow:hidden;
153
+ background:var(--surface);margin-bottom:20px;
154
+ box-shadow:0 8px 32px rgba(123,143,212,.1)}
155
+ .result-annotated-header{display:flex;align-items:center;justify-content:space-between;
156
+ padding:12px 20px;border-bottom:1px solid var(--border);background:var(--bg2)}
157
+ .result-annotated-header span{font-family:'DM Mono',monospace;font-size:10px;
158
+ color:var(--text3);letter-spacing:.12em;text-transform:uppercase}
159
+ .count-badge{font-family:'DM Mono',monospace;font-size:10px;padding:4px 12px;border-radius:20px;
160
+ background:linear-gradient(135deg,rgba(74,171,181,.15),rgba(138,170,106,.15));
161
+ border:1px solid rgba(74,171,181,.3);color:var(--teal)}
162
+ #annotatedImg{width:100%;display:block;max-height:520px;object-fit:contain;padding:12px}
163
+ .plants-label{font-family:'DM Mono',monospace;font-size:10px;color:var(--text3);
164
+ letter-spacing:.2em;text-transform:uppercase;margin-bottom:12px}
165
+ .plants-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
166
+
167
+ /* PLANT CARD */
168
+ .plant-card{border-radius:16px;overflow:hidden;background:var(--surface);
169
+ border:1px solid var(--border);box-shadow:0 4px 18px rgba(0,0,0,.05)}
170
+ .card-header{padding:16px 18px 14px;border-bottom:1px solid var(--border)}
171
+ .card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px}
172
+ .card-name{font-family:'Cormorant Garamond',serif;font-size:22px;font-weight:500;color:var(--ink);line-height:1.1}
173
+ .card-sci{font-family:'Cormorant Garamond',serif;font-size:13px;font-style:italic;color:var(--mauve)}
174
+ .conf-badge{font-family:'DM Mono',monospace;font-size:9px;padding:4px 10px;border-radius:20px;
175
+ letter-spacing:.1em;text-transform:uppercase;white-space:nowrap;flex-shrink:0}
176
+ .conf-high{background:linear-gradient(135deg,rgba(74,171,181,.15),rgba(138,170,106,.15));
177
+ color:var(--teal);border:1px solid rgba(74,171,181,.3)}
178
+ .conf-medium{background:rgba(212,149,90,.12);color:var(--amber);border:1px solid rgba(212,149,90,.3)}
179
+ .conf-low{background:rgba(201,123,168,.12);color:var(--rose);border:1px solid rgba(201,123,168,.3)}
180
+ .card-body{padding:14px 18px}
181
+ .card-field{margin-bottom:12px}
182
+ .card-field h5{font-family:'DM Mono',monospace;font-size:9px;color:var(--text3);
183
+ letter-spacing:.2em;text-transform:uppercase;margin-bottom:5px}
184
+ .card-field p{font-size:13px;color:var(--text);line-height:1.65}
185
+ .features{display:flex;flex-direction:column;gap:5px}
186
+ .feature{display:flex;align-items:flex-start;gap:8px;font-size:12px;color:var(--text2);line-height:1.4}
187
+ .dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;margin-top:4px}
188
+ .wiki-text{font-size:12px;color:var(--text2);line-height:1.8;
189
+ display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}
190
+ .wiki-link{display:inline-flex;align-items:center;gap:4px;margin-top:8px;
191
+ font-family:'DM Mono',monospace;font-size:10px;color:var(--periwinkle);
192
+ text-decoration:none;transition:color .2s}
193
+ .wiki-link:hover{color:var(--mauve)}
194
+ .no-plants{text-align:center;padding:44px 24px;border:1px dashed var(--border2);
195
+ border-radius:16px;color:var(--text3)}
196
+ .no-plants p{font-family:'Cormorant Garamond',serif;font-size:20px;font-style:italic}
197
+
198
+ /* FOOTER */
199
+ footer{border-top:1px solid var(--border);padding:24px 32px;
200
+ display:flex;align-items:center;justify-content:space-between;
201
+ background:var(--surface2);flex-wrap:wrap;gap:12px}
202
+ footer p{font-family:'DM Mono',monospace;font-size:10px;color:var(--text3);letter-spacing:.08em}
203
+ .stack{display:flex;gap:6px;flex-wrap:wrap}
204
+ .stack-badge{font-family:'DM Mono',monospace;font-size:9px;color:var(--text3);
205
+ border:1px solid var(--border);padding:4px 10px;border-radius:20px;background:var(--surface)}
206
+
207
+ /* ANIMATIONS */
208
+ @keyframes fadeUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
209
+ @keyframes fadeIn{from{opacity:0}to{opacity:.7}}
210
+ @keyframes spin{to{transform:rotate(360deg)}}
211
+
212
+ /* RESPONSIVE */
213
+ @media(max-width:540px){
214
+ nav{padding:12px 16px}
215
+ .hero{padding:32px 16px 28px}
216
+ .main{padding:0 12px 60px}
217
+ .upload-grid{grid-template-columns:1fr}
218
+ .plants-grid{grid-template-columns:1fr}
219
+ footer{padding:20px 16px}
220
+ }
221
+ </style>
222
+ </head>
223
+ <body>
224
+
225
+ <nav>
226
+ <div class="logo">Eucalyptus<span>Lens</span></div>
227
+ <div class="nav-badge">LLaMA 4 Scout Β· Multi-Plant</div>
228
+ </nav>
229
+
230
+ <p class="tagline">Photo. Detect. Identify.</p>
231
+
232
+ <div class="main">
233
+
234
+ <div class="upload-grid">
235
+
236
+ <!-- Upload Box β€” label wraps the input for native browser file picking -->
237
+ <label class="upload-box" style="position:relative">
238
+ <input type="file" id="fileInput" accept="image/*">
239
+ <div class="zone-icon upload-icon" style="pointer-events:none">
240
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
241
+ <path d="M12 15V5M8 9l4-4 4 4" stroke="url(#g1)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
242
+ <path d="M4 17v1a2 2 0 002 2h12a2 2 0 002-2v-1" stroke="url(#g1)" stroke-width="1.5" stroke-linecap="round"/>
243
+ <defs><linearGradient id="g1" x1="4" y1="4" x2="20" y2="20">
244
+ <stop stop-color="#7b8fd4"/><stop offset="1" stop-color="#c97ba8"/>
245
+ </linearGradient></defs>
246
+ </svg>
247
+ </div>
248
+ <h3 class="zone-h3">Upload Image</h3>
249
+ <p class="zone-p">Click to browse or drop a photo</p>
250
+ <div class="formats">JPG Β· PNG Β· WEBP Β· HEIC</div>
251
+ </label>
252
+
253
+ <!-- Camera Box -->
254
+ <div class="camera-box">
255
+ <video id="cameraFeed" autoplay playsinline></video>
256
+ <canvas id="cameraCanvas"></canvas>
257
+ <div class="camera-placeholder" id="cameraPlaceholder">
258
+ <div class="zone-icon camera-icon-bg">
259
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
260
+ <rect x="2" y="7" width="20" height="13" rx="3" stroke="url(#g2)" stroke-width="1.5" fill="none"/>
261
+ <circle cx="12" cy="13" r="3.5" stroke="url(#g2)" stroke-width="1.5" fill="none"/>
262
+ <path d="M8 7l1.5-2.5h5L16 7" stroke="url(#g2)" stroke-width="1.5" stroke-linecap="round"/>
263
+ <defs><linearGradient id="g2" x1="2" y1="7" x2="22" y2="20">
264
+ <stop stop-color="#4aabb5"/><stop offset="1" stop-color="#8aaa6a"/>
265
+ </linearGradient></defs>
266
+ </svg>
267
+ </div>
268
+ <h3 class="zone-h3">Use Camera</h3>
269
+ <p class="zone-p">Take a live photo</p>
270
+ </div>
271
+ <div class="camera-controls">
272
+ <button class="btn btn-sm" id="btnStart" onclick="startCamera()">Start</button>
273
+ <button class="btn btn-sm" id="btnCapture" onclick="capturePhoto()" style="display:none">Capture</button>
274
+ <button class="btn btn-sm" id="btnStop" onclick="stopCamera()" style="display:none">Stop</button>
275
+ </div>
276
+ </div>
277
+
278
+ </div>
279
+
280
+ <!-- Preview -->
281
+ <div class="preview-container" id="previewBox">
282
+ <div class="preview-header">
283
+ <span>Preview</span>
284
+ <button class="btn btn-danger" onclick="clearImage()">Clear</button>
285
+ </div>
286
+ <img id="previewImg" src="" alt="Preview">
287
+ </div>
288
+
289
+ <!-- Analyze button -->
290
+ <button class="btn btn-primary" id="btnAnalyze" onclick="analyze()" disabled>
291
+ Detect All Plants
292
+ </button>
293
+
294
+ <!-- Loading -->
295
+ <div class="loading" id="loadingBox">
296
+ <div class="spinner"></div>
297
+ <p>Scanning for plants...</p>
298
+ <div class="loading-steps">
299
+ <div class="loading-step" style="animation-delay:.5s">Processing image with LLaMA 4 Scout</div>
300
+ <div class="loading-step" style="animation-delay:2s">Identifying all plants in frame</div>
301
+ <div class="loading-step" style="animation-delay:3.5s">Fetching Wikipedia botanical data</div>
302
+ <div class="loading-step" style="animation-delay:5s">Annotating image with labels</div>
303
+ </div>
304
+ </div>
305
+
306
+ <!-- Results -->
307
+ <div class="result-area" id="resultArea">
308
+
309
+ <div class="result-annotated">
310
+ <div class="result-annotated-header">
311
+ <span>Annotated Result</span>
312
+ <div class="count-badge" id="countBadge">0 plants</div>
313
+ </div>
314
+ <div class="img-wrapper" id="imgWrapper">
315
+ <img id="annotatedImg" src="" alt="Annotated">
316
+ <div id="tooltipDots"></div>
317
+ <div class="plant-tooltip" id="plantTooltip"></div>
318
+ </div>
319
+ </div>
320
+
321
+ <div class="plants-label" id="plantsLabel"></div>
322
+ <div class="plants-grid" id="plantsGrid"></div>
323
+
324
+ </div>
325
+
326
+ </div>
327
+
328
+ <footer>
329
+ <p>EucalyptusLens β€” AI Botanical Intelligence</p>
330
+ <div class="stack">
331
+ <span class="stack-badge">LLaMA 4 Scout</span>
332
+ <span class="stack-badge">Groq</span>
333
+ <span class="stack-badge">Wikipedia</span>
334
+ <span class="stack-badge">Flask</span>
335
+ </div>
336
+ </footer>
337
+
338
+ <script>
339
+ let currentFile = null;
340
+ let stream = null;
341
+
342
+ const COLORS = [
343
+ 'rgb(99,143,212)','rgb(201,123,168)','rgb(74,171,181)',
344
+ 'rgb(212,149,90)','rgb(138,170,106)','rgb(220,120,100)'
345
+ ];
346
+
347
+ // ── File Input ────────────────────────────────────────────────────────────────
348
+ document.getElementById('fileInput').onchange = function() {
349
+ if (this.files && this.files[0]) showPreview(this.files[0]);
350
+ };
351
+
352
+ function showPreview(file) {
353
+ currentFile = file;
354
+ const reader = new FileReader();
355
+ reader.onload = function(e) {
356
+ document.getElementById('previewImg').src = e.target.result;
357
+ document.getElementById('previewBox').style.display = 'block';
358
+ document.getElementById('btnAnalyze').disabled = false;
359
+ document.getElementById('resultArea').style.display = 'none';
360
+ };
361
+ reader.readAsDataURL(file);
362
+ }
363
+
364
+ function clearImage() {
365
+ currentFile = null;
366
+ document.getElementById('previewBox').style.display = 'none';
367
+ document.getElementById('btnAnalyze').disabled = true;
368
+ document.getElementById('resultArea').style.display = 'none';
369
+ document.getElementById('fileInput').value = '';
370
+ }
371
+
372
+ // ── Camera ────────────────────────────────────────────────────────────────────
373
+ async function startCamera() {
374
+ try {
375
+ stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
376
+ const v = document.getElementById('cameraFeed');
377
+ v.srcObject = stream;
378
+ v.style.display = 'block';
379
+ document.getElementById('cameraPlaceholder').style.display = 'none';
380
+ document.getElementById('btnStart').style.display = 'none';
381
+ document.getElementById('btnCapture').style.display = 'inline-block';
382
+ document.getElementById('btnStop').style.display = 'inline-block';
383
+ } catch(e) {
384
+ alert('Camera access denied: ' + e.message);
385
+ }
386
+ }
387
+
388
+ function capturePhoto() {
389
+ const v = document.getElementById('cameraFeed');
390
+ const c = document.getElementById('cameraCanvas');
391
+ c.width = v.videoWidth;
392
+ c.height = v.videoHeight;
393
+ c.getContext('2d').drawImage(v, 0, 0);
394
+ c.toBlob(blob => {
395
+ showPreview(new File([blob], 'camera.jpg', { type: 'image/jpeg' }));
396
+ stopCamera();
397
+ }, 'image/jpeg', 0.92);
398
+ }
399
+
400
+ function stopCamera() {
401
+ if (stream) { stream.getTracks().forEach(t => t.stop()); stream = null; }
402
+ document.getElementById('cameraFeed').style.display = 'none';
403
+ document.getElementById('cameraPlaceholder').style.display = 'flex';
404
+ document.getElementById('btnStart').style.display = 'inline-block';
405
+ document.getElementById('btnCapture').style.display = 'none';
406
+ document.getElementById('btnStop').style.display = 'none';
407
+ }
408
+
409
+ // ── Analyze ───────────────────────────────────────────────────────────────────
410
+ async function analyze() {
411
+ if (!currentFile) return;
412
+
413
+ document.getElementById('loadingBox').style.display = 'block';
414
+ document.getElementById('resultArea').style.display = 'none';
415
+ document.getElementById('btnAnalyze').disabled = true;
416
+
417
+ const fd = new FormData();
418
+ fd.append('file', currentFile);
419
+
420
+ try {
421
+ const res = await fetch('/analyze', { method: 'POST', body: fd });
422
+ const data = await res.json();
423
+
424
+ if (!res.ok || data.error) {
425
+ alert('Error: ' + (data.error || res.statusText));
426
+ return;
427
+ }
428
+ showResults(data);
429
+ } catch(e) {
430
+ alert('Connection error: ' + e.message);
431
+ } finally {
432
+ document.getElementById('loadingBox').style.display = 'none';
433
+ document.getElementById('btnAnalyze').disabled = false;
434
+ }
435
+ }
436
+
437
+ // ── Show Results ──────────────────────────────────────────────────────────────
438
+ function showResults(data) {
439
+ const plants = data.plants || [];
440
+ const count = plants.length;
441
+ const dots = data.dot_positions || [];
442
+
443
+ document.getElementById('annotatedImg').src = data.annotated_image || '';
444
+ document.getElementById('countBadge').textContent = count === 1 ? '1 plant found' : count + ' plants found';
445
+ document.getElementById('plantsLabel').textContent = count > 0 ? 'Identified Plants' : '';
446
+
447
+ // Build tooltip dots
448
+ const dotsEl = document.getElementById('tooltipDots');
449
+ const tooltip = document.getElementById('plantTooltip');
450
+ dotsEl.innerHTML = '';
451
+
452
+ dots.forEach(d => {
453
+ const dot = document.createElement('div');
454
+ dot.className = 'tooltip-dot';
455
+ dot.style.left = d.x_pct + '%';
456
+ dot.style.top = d.y_pct + '%';
457
+ dot.style.width = '40px';
458
+ dot.style.height = '40px';
459
+ dot.style.transform = 'translate(-50%,-50%)';
460
+ dot.style.position = 'absolute';
461
+
462
+ dot.addEventListener('mouseenter', function() {
463
+ tooltip.textContent = d.name;
464
+ tooltip.style.left = d.x_pct + '%';
465
+ tooltip.style.top = d.y_pct + '%';
466
+ tooltip.classList.add('visible');
467
+ });
468
+ dot.addEventListener('mouseleave', function() {
469
+ tooltip.classList.remove('visible');
470
+ });
471
+ // Mobile tap support
472
+ dot.addEventListener('touchstart', function(e) {
473
+ e.preventDefault();
474
+ tooltip.textContent = d.name;
475
+ tooltip.style.left = d.x_pct + '%';
476
+ tooltip.style.top = d.y_pct + '%';
477
+ tooltip.classList.add('visible');
478
+ setTimeout(() => tooltip.classList.remove('visible'), 2000);
479
+ });
480
+
481
+ dotsEl.appendChild(dot);
482
+ });
483
+
484
+ const grid = document.getElementById('plantsGrid');
485
+ grid.innerHTML = '';
486
+
487
+ if (count === 0) {
488
+ grid.innerHTML = '<div class="no-plants"><p>No plants detected in this image.</p></div>';
489
+ } else {
490
+ plants.forEach((p, i) => grid.appendChild(makeCard(p, i)));
491
+ }
492
+
493
+ document.getElementById('resultArea').style.display = 'block';
494
+ document.getElementById('resultArea').scrollIntoView({ behavior: 'smooth', block: 'start' });
495
+ }
496
+
497
+ function makeCard(p, i) {
498
+ const color = COLORS[i % COLORS.length];
499
+ const conf = (p.confidence || 'low').toLowerCase();
500
+ const div = document.createElement('div');
501
+ div.className = 'plant-card';
502
+ div.style.borderTop = '3px solid ' + color;
503
+
504
+ const features = (p.key_features || []).slice(0, 4)
505
+ .map(f => `<div class="feature"><div class="dot" style="background:${color}"></div><span>${f}</span></div>`)
506
+ .join('');
507
+
508
+ div.innerHTML = `
509
+ <div class="card-header" style="background:linear-gradient(135deg,${color}18,${color}08)">
510
+ <div class="card-top">
511
+ <div>
512
+ <div class="card-name">${p.common_name || 'Unknown'}</div>
513
+ <div class="card-sci">${p.scientific_name || ''}</div>
514
+ </div>
515
+ <div class="conf-badge conf-${conf}">${conf}</div>
516
+ </div>
517
+ </div>
518
+ <div class="card-body">
519
+ ${p.family ? `<div class="card-field"><h5>Family</h5><p>${p.family}</p></div>` : ''}
520
+ ${features ? `<div class="card-field"><h5>Key Features</h5><div class="features">${features}</div></div>` : ''}
521
+ ${p.wiki_summary ? `
522
+ <div class="card-field">
523
+ <h5>Botanical Overview</h5>
524
+ <p class="wiki-text">${p.wiki_summary}</p>
525
+ ${p.wiki_url ? `<a class="wiki-link" href="${p.wiki_url}" target="_blank" rel="noopener">Read on Wikipedia β†’</a>` : ''}
526
+ </div>` : ''}
527
+ </div>`;
528
+
529
+ return div;
530
+ }
531
+ </script>
532
+ </body>
533
+ </html>