Utkarsh Pathak commited on
Commit
ddd5ca4
·
1 Parent(s): 8c91789

Redesign 3: light mode, liquid glass, teal accent, hero orbs

Browse files
Files changed (1) hide show
  1. static/index.html +547 -501
static/index.html CHANGED
@@ -3,50 +3,47 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Trade Copilot — AI Market Scanner</title>
7
  <style>
8
- /* ── Design tokens ──────────────────────────────────────────────────────── */
9
  :root {
10
- /* Base palette — deep navy, not pure black */
11
- --bg: #080c14;
12
- --bg1: #0d1220;
13
- --bg2: #111827;
14
- --bg3: #1a2236;
15
- --bg4: #1f2a40;
16
-
17
- /* Borders */
18
- --b1: rgba(255,255,255,0.06);
19
- --b2: rgba(255,255,255,0.10);
20
- --b3: rgba(255,255,255,0.16);
21
-
22
- /* Text */
23
- --t1: #f0f4ff;
24
- --t2: #8b95b0;
25
- --t3: #4a5268;
26
-
27
- /* Brand — electric indigo + cyan accent */
28
- --indigo: #6366f1;
29
- --indigo2: #818cf8;
30
- --cyan: #22d3ee;
31
- --cyan2: #67e8f9;
32
 
33
  /* Semantic */
34
- --green: #10b981;
35
- --green2: #34d399;
36
- --red: #f43f5e;
37
- --red2: #fb7185;
38
- --amber: #f59e0b;
39
- --amber2: #fbbf24;
40
-
41
- --font: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Inter', 'Helvetica Neue', sans-serif;
42
- --mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
43
- --r: 12px;
44
- --r2: 20px;
45
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
 
46
  }
47
 
48
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
49
  html { scroll-behavior: smooth; }
 
50
  body {
51
  font-family: var(--font);
52
  background: var(--bg);
@@ -56,92 +53,134 @@ body {
56
  overflow-x: hidden;
57
  }
58
 
59
- /* ── Global noise texture overlay ──────────────────────────────────────── */
60
- body::after {
61
- content: '';
62
- position: fixed; inset: 0;
63
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
64
- opacity: 0.018;
65
  pointer-events: none;
66
- z-index: 9999;
67
  }
68
 
69
- /* ─── Navbar ─────────────────────────────────────────────────────────────── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  nav {
71
- position: fixed; top: 0; left: 0; right: 0;
72
- z-index: 100;
73
- height: 58px;
74
  display: flex; align-items: center;
75
- padding: 0 32px;
76
- gap: 16px;
77
- background: rgba(8,12,20,0.8);
78
- backdrop-filter: blur(24px) saturate(160%);
79
- -webkit-backdrop-filter: blur(24px) saturate(160%);
80
- border-bottom: 1px solid var(--b1);
81
  }
82
 
83
  .brand {
84
  display: flex; align-items: center; gap: 10px;
85
- flex-shrink: 0; text-decoration: none; color: inherit;
86
  }
87
- .brand-icon {
88
- width: 30px; height: 30px;
89
- background: linear-gradient(135deg, var(--indigo), var(--cyan));
90
- border-radius: 8px;
91
  display: grid; place-items: center;
92
- font-size: 14px;
93
- box-shadow: 0 0 20px rgba(99,102,241,0.4);
94
  }
95
  .brand-name {
96
- font-size: 15px; font-weight: 700;
97
- letter-spacing: -0.4px;
98
- background: linear-gradient(90deg, var(--t1), var(--t2));
99
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
100
  }
101
 
102
  #status-pill {
103
  display: inline-flex; align-items: center; gap: 6px;
104
- font-size: 11px; font-weight: 600;
105
  padding: 4px 12px; border-radius: 99px;
106
- border: 1px solid var(--b1);
107
- background: var(--bg2);
108
  color: var(--t3);
109
- transition: all 0.4s var(--ease);
110
- white-space: nowrap;
 
111
  }
112
- #status-pill.ok { color: var(--green2); border-color: rgba(16,185,129,0.2); background: rgba(16,185,129,0.06); }
113
- #status-pill.err { color: var(--red2); border-color: rgba(244,63,94,0.2); background: rgba(244,63,94,0.06); }
114
 
115
- .live-dot {
116
- width: 6px; height: 6px; border-radius: 50%;
117
- background: currentColor; flex-shrink: 0;
118
- }
119
  .ok .live-dot { animation: livePulse 2s ease-in-out infinite; }
120
  @keyframes livePulse {
121
- 0%,100% { box-shadow: 0 0 0 0 rgba(52,211,153,0.5); }
122
- 50% { box-shadow: 0 0 0 5px rgba(52,211,153,0); }
123
  }
124
 
125
- /* Nav controls */
126
- .nav-right {
127
- display: flex; align-items: center; gap: 10px;
128
  margin-left: auto;
129
  }
130
 
131
  .input-chip {
132
  display: flex; align-items: center; gap: 8px;
133
- background: var(--bg2);
134
- border: 1px solid var(--b1);
135
- border-radius: 10px;
136
- padding: 7px 14px;
137
  transition: border-color 0.2s, box-shadow 0.2s;
138
  }
139
  .input-chip:focus-within {
140
- border-color: rgba(99,102,241,0.4);
141
- box-shadow: 0 0 0 3px rgba(99,102,241,0.08);
142
  }
143
  .input-chip label {
144
- font-size: 10px; font-weight: 700; letter-spacing: 0.8px;
145
  text-transform: uppercase; color: var(--t3);
146
  user-select: none; white-space: nowrap;
147
  }
@@ -155,380 +194,410 @@ nav {
155
  .input-chip input::-webkit-inner-spin-button,
156
  .input-chip input::-webkit-outer-spin-button { -webkit-appearance: none; }
157
 
158
- #scan-btn {
159
- display: inline-flex; align-items: center; gap: 8px;
160
- background: linear-gradient(135deg, var(--indigo), #4f46e5);
161
  color: #fff; border: none;
162
  padding: 9px 22px; border-radius: 10px;
163
  font-size: 13px; font-weight: 700; font-family: var(--font);
164
- letter-spacing: -0.2px; cursor: pointer;
165
- transition: transform 0.2s var(--ease), box-shadow 0.2s, opacity 0.2s;
166
- box-shadow: 0 4px 24px rgba(99,102,241,0.35), inset 0 1px 0 rgba(255,255,255,0.15);
167
- white-space: nowrap; flex-shrink: 0;
168
  }
169
- #scan-btn:hover { transform: translateY(-1px); box-shadow: 0 8px 32px rgba(99,102,241,0.5), inset 0 1px 0 rgba(255,255,255,0.15); }
170
- #scan-btn:active { transform: translateY(0) scale(0.98); }
171
- #scan-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; }
172
 
173
- /* ── Hero / above-fold ──────────────────────────────────────────────────── */
174
  .hero {
175
- position: relative;
176
- padding: 120px 32px 60px;
177
  text-align: center;
178
- overflow: hidden;
179
- }
180
-
181
- /* Radial gradient hero bg */
182
- .hero::before {
183
- content: '';
184
- position: absolute; inset: 0;
185
- background:
186
- radial-gradient(ellipse 80% 50% at 50% -10%, rgba(99,102,241,0.18) 0%, transparent 60%),
187
- radial-gradient(ellipse 50% 40% at 20% 60%, rgba(34,211,238,0.07) 0%, transparent 50%),
188
- radial-gradient(ellipse 40% 40% at 80% 70%, rgba(99,102,241,0.06) 0%, transparent 50%);
189
- pointer-events: none;
190
- }
191
-
192
- /* Grid lines */
193
- .hero::after {
194
- content: '';
195
- position: absolute; inset: 0;
196
- background-image:
197
- linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
198
- linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
199
- background-size: 60px 60px;
200
- mask-image: radial-gradient(ellipse 90% 60% at 50% 0%, black 30%, transparent 80%);
201
- pointer-events: none;
202
  }
203
 
204
- .hero-eyebrow {
205
  display: inline-flex; align-items: center; gap: 8px;
206
  font-size: 11px; font-weight: 700; letter-spacing: 1.2px;
207
- text-transform: uppercase;
208
- color: var(--indigo2);
209
- background: rgba(99,102,241,0.1);
210
- border: 1px solid rgba(99,102,241,0.2);
211
  padding: 5px 14px; border-radius: 99px;
212
- margin-bottom: 24px;
213
- animation: fadeUp 0.6s var(--ease) 0.1s both;
214
  }
215
- .hero-eyebrow span { width: 5px; height: 5px; border-radius: 50%; background: var(--indigo2); }
216
 
217
- .hero-title {
218
- font-size: clamp(36px, 5vw, 62px);
219
- font-weight: 800;
220
- letter-spacing: -2px;
221
- line-height: 1.05;
222
- margin-bottom: 20px;
223
- animation: fadeUp 0.6s var(--ease) 0.2s both;
224
  }
225
- .hero-title .grad {
226
- background: linear-gradient(135deg, #fff 0%, var(--indigo2) 50%, var(--cyan) 100%);
227
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
228
  }
229
 
230
  .hero-sub {
231
- font-size: 17px; font-weight: 400;
232
- color: var(--t2); line-height: 1.6;
233
- max-width: 500px; margin: 0 auto 36px;
234
- animation: fadeUp 0.6s var(--ease) 0.3s both;
 
 
 
 
235
  }
236
 
237
- .hero-cta {
238
  display: inline-flex; align-items: center; gap: 10px;
239
- background: linear-gradient(135deg, var(--indigo), #4f46e5);
240
  color: #fff; border: none;
241
- padding: 14px 32px; border-radius: 14px;
242
- font-size: 15px; font-weight: 700; font-family: var(--font);
243
  cursor: pointer; letter-spacing: -0.3px;
244
- transition: transform 0.2s var(--ease), box-shadow 0.2s, opacity 0.15s;
245
- box-shadow: 0 8px 32px rgba(99,102,241,0.4), inset 0 1px 0 rgba(255,255,255,0.15);
246
- animation: fadeUp 0.6s var(--ease) 0.4s both;
247
- }
248
- .hero-cta:hover { transform: translateY(-2px); box-shadow: 0 16px 48px rgba(99,102,241,0.5), inset 0 1px 0 rgba(255,255,255,0.15); }
249
- .hero-cta:active { transform: scale(0.98); }
250
- .hero-cta:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
251
-
252
- /* Stats strip */
253
- .stats-strip {
254
- display: flex; justify-content: center; gap: 0;
255
- margin-top: 52px;
256
- border: 1px solid var(--b1);
257
- border-radius: 16px;
258
- background: var(--bg1);
259
- overflow: hidden;
260
- max-width: 600px; margin-left: auto; margin-right: auto;
261
- animation: fadeUp 0.6s var(--ease) 0.5s both;
262
  }
263
- .stat-cell {
264
- flex: 1; padding: 18px 24px; text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  }
266
- .stat-cell + .stat-cell { border-left: 1px solid var(--b1); }
267
  .stat-val {
268
- font-size: 22px; font-weight: 800; letter-spacing: -0.8px;
269
- color: var(--t1);
270
  }
271
  .stat-label {
272
  font-size: 11px; font-weight: 600; letter-spacing: 0.5px;
273
- color: var(--t3); text-transform: uppercase; margin-top: 3px;
274
  }
275
 
276
- @keyframes fadeUp {
277
- from { opacity: 0; transform: translateY(20px); }
278
  to { opacity: 1; transform: translateY(0); }
279
  }
280
 
281
- /* ── Scan section ───────────────────────────────────────────────────────── */
282
  #scan-section {
283
- padding: 0 32px 80px;
284
- max-width: 1480px;
285
- margin: 0 auto;
286
  }
287
 
288
  #meta {
289
- font-size: 12px; color: var(--t3);
290
- font-weight: 600; letter-spacing: 0.2px;
291
- margin-bottom: 24px; min-height: 16px;
292
  }
293
 
294
  #error-banner {
295
  display: none;
296
- background: rgba(244,63,94,0.06);
297
- border: 1px solid rgba(244,63,94,0.2);
298
- border-radius: 12px;
299
- padding: 14px 20px;
300
- color: var(--red2); font-size: 13px; font-weight: 600;
301
  margin-bottom: 20px;
302
- animation: fadeUp 0.3s var(--ease);
303
  }
304
 
305
- /* ── Loader ─────────────────────────────────────────────────────────────── */
306
  #loader {
307
- display: none;
308
- flex-direction: column; align-items: center;
309
- padding: 80px 0; gap: 20px;
310
- }
311
- .loader-orbit {
312
- position: relative; width: 56px; height: 56px;
313
- }
314
- .loader-orbit::before {
315
- content: '';
316
- position: absolute; inset: 0;
317
- border-radius: 50%;
318
- border: 2px solid var(--b2);
319
  }
320
- .loader-orbit::after {
321
  content: '';
322
  position: absolute; inset: 0;
323
- border-radius: 50%;
324
  border: 2px solid transparent;
325
- border-top-color: var(--indigo2);
326
- border-right-color: var(--cyan);
327
- animation: orbit 0.8s linear infinite;
 
 
 
 
 
 
 
 
 
 
 
328
  }
329
- @keyframes orbit { to { transform: rotate(360deg); } }
330
- .loader-inner {
331
- position: absolute; inset: 10px;
332
- border-radius: 50%;
333
- border: 2px solid transparent;
334
- border-bottom-color: rgba(99,102,241,0.3);
335
- animation: orbit 1.4s linear infinite reverse;
336
- }
337
- .loader-text h3 { font-size: 18px; font-weight: 700; letter-spacing: -0.3px; margin-bottom: 4px; text-align: center; }
338
- .loader-text p { font-size: 13px; color: var(--t3); text-align: center; }
339
- .progress-track { width: 180px; height: 2px; background: var(--bg3); border-radius: 1px; overflow: hidden; }
340
- .progress-fill { height: 100%; background: linear-gradient(90deg, var(--indigo), var(--cyan)); border-radius: 1px; animation: progressLoop 1.8s ease-in-out infinite; }
341
- @keyframes progressLoop {
342
- 0% { width: 0%; margin-left: 0%; }
343
  50% { width: 60%; margin-left: 20%; }
344
- 100% { width: 0%; margin-left: 100%; }
345
  }
346
 
347
- /* ── Empty state ────────────────────────────────────────────────────────── */
348
- #empty { display: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
 
350
- /* ── Cards grid ─────────────────────────────────────────────────────────── */
351
  #grid {
352
  display: grid;
353
- grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
354
  gap: 16px;
355
  }
356
 
357
- /* ── Trade Card ─────────────────────────────────────────────────────────── */
358
  .card {
359
  position: relative; overflow: hidden;
360
- background: var(--bg1);
361
- border: 1px solid var(--b1);
362
- border-radius: 20px;
 
 
 
 
 
 
 
 
 
 
363
  padding: 24px;
364
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease), border-color 0.3s;
365
- animation: cardReveal 0.5s var(--ease) both;
366
  }
367
- @keyframes cardReveal {
368
- from { opacity: 0; transform: translateY(24px) scale(0.97); }
369
  to { opacity: 1; transform: translateY(0) scale(1); }
370
  }
371
  .card:hover {
372
- transform: translateY(-4px);
373
- border-color: rgba(99,102,241,0.25);
374
  box-shadow:
375
- 0 0 0 1px rgba(99,102,241,0.12),
376
- 0 20px 60px rgba(0,0,0,0.5),
377
- 0 0 80px rgba(99,102,241,0.05);
 
378
  }
379
 
380
- /* Top accent stripe */
381
- .card-stripe {
382
- position: absolute; top: 0; left: 0; right: 0; height: 2px;
383
- background: linear-gradient(90deg, var(--indigo), var(--cyan));
384
- opacity: 0;
385
- transition: opacity 0.3s;
 
 
386
  }
387
- .card:hover .card-stripe { opacity: 1; }
388
 
389
- /* Card glow */
390
- .card-glow {
391
- position: absolute; inset: 0;
392
- background: radial-gradient(ellipse 70% 40% at 50% 0%, rgba(99,102,241,0.05), transparent);
393
- pointer-events: none; opacity: 0;
 
 
394
  transition: opacity 0.3s;
395
  }
396
- .card:hover .card-glow { opacity: 1; }
397
 
398
  /* ── Card header ── */
399
  .ch {
400
  display: flex; justify-content: space-between;
401
- align-items: flex-start; margin-bottom: 20px;
402
  }
403
- .ch-left {}
404
  .ch-symbol {
405
- font-size: 24px; font-weight: 800;
406
- letter-spacing: -1px; line-height: 1;
407
- color: var(--t1);
408
  }
409
- .ch-symbol .quote { font-size: 16px; font-weight: 500; color: var(--t3); letter-spacing: 0; }
410
- .ch-meta {
411
- display: flex; align-items: center; gap: 10px;
412
- margin-top: 6px; font-size: 12px;
413
  }
414
- .ch-change { font-weight: 700; }
415
- .ch-change.pos { color: var(--green2); }
416
- .ch-change.neg { color: var(--red2); }
417
- .ch-vol { color: var(--t3); font-weight: 500; }
418
 
419
  .dir-tag {
420
  font-size: 10px; font-weight: 800; letter-spacing: 1px;
421
- text-transform: uppercase; padding: 6px 14px;
422
- border-radius: 99px;
 
 
 
 
 
 
 
 
 
 
 
423
  }
424
- .dir-tag.long { background: rgba(16,185,129,0.1); color: var(--green2); border: 1px solid rgba(16,185,129,0.2); }
425
- .dir-tag.short { background: rgba(244,63,94,0.1); color: var(--red2); border: 1px solid rgba(244,63,94,0.2); }
426
 
427
  /* ── Confidence ── */
428
- .conf {
429
- margin-bottom: 20px;
430
- padding: 16px;
431
- background: var(--bg2);
432
- border-radius: 12px;
433
- border: 1px solid var(--b1);
 
434
  }
435
  .conf-top {
436
- display: flex; justify-content: space-between;
437
- align-items: baseline; margin-bottom: 10px;
438
  }
439
  .conf-lbl {
440
  font-size: 10px; font-weight: 700; letter-spacing: 0.8px;
441
  text-transform: uppercase; color: var(--t3);
442
  }
443
  .conf-score {
444
- font-size: 28px; font-weight: 800; letter-spacing: -1px; line-height: 1;
445
  }
446
- .conf-score .denom { font-size: 13px; font-weight: 500; color: var(--t3); }
447
  .conf-track {
448
- height: 4px; background: var(--bg3);
449
- border-radius: 2px; overflow: hidden;
450
  }
451
  .conf-fill {
452
- height: 100%; border-radius: 2px;
453
  transition: width 1s var(--ease);
454
  }
455
- .conf-fill.hi { background: linear-gradient(90deg, #059669, var(--green2)); }
456
  .conf-fill.md { background: linear-gradient(90deg, #b45309, var(--amber2)); }
457
- .conf-fill.lo { background: linear-gradient(90deg, #be123c, var(--red2)); }
458
 
459
  /* ── TF chips ── */
460
- .tf-row {
461
- display: flex; gap: 6px; margin-bottom: 20px; flex-wrap: wrap;
462
- }
463
  .tf-chip {
464
- font-size: 10px; font-weight: 700; letter-spacing: 0.4px;
465
  padding: 4px 10px; border-radius: 99px; text-transform: uppercase;
 
 
 
466
  }
467
- .tf-chip.up { background: rgba(16,185,129,0.08); color: var(--green2); border: 1px solid rgba(16,185,129,0.15); }
468
- .tf-chip.down { background: rgba(244,63,94,0.08); color: var(--red2); border: 1px solid rgba(244,63,94,0.15); }
469
- .tf-chip.range { background: rgba(245,158,11,0.08); color: var(--amber2); border: 1px solid rgba(245,158,11,0.15); }
470
- .tf-chip.na { background: var(--bg3); color: var(--t3); border: 1px solid var(--b1); }
471
 
472
  /* ── Rule ── */
473
- .rule { height: 1px; background: var(--b1); margin: 18px -24px; }
474
 
475
- /* ── Levels ── */
476
  .levels {
477
  display: grid; grid-template-columns: repeat(3,1fr);
478
- gap: 8px; margin-bottom: 16px;
479
  }
480
  .lv {
481
- background: var(--bg2); border-radius: 10px;
482
- padding: 11px 13px;
483
- border: 1px solid var(--b1);
 
484
  position: relative; overflow: hidden;
485
- transition: border-color 0.2s;
486
  }
487
- .lv:hover { border-color: var(--b2); }
488
- .lv-top {
489
- height: 2px; border-radius: 10px 10px 0 0;
490
- position: absolute; top: 0; left: 0; right: 0;
491
  }
492
- .lv.entry .lv-top { background: var(--indigo2); }
493
- .lv.sl .lv-top { background: var(--red2); }
494
- .lv.tp .lv-top { background: var(--green2); }
495
- .lv.rr .lv-top { background: var(--amber2); }
496
 
497
- .lv-label {
498
- font-size: 9px; font-weight: 700; letter-spacing: 0.8px;
499
- text-transform: uppercase; color: var(--t3); margin-bottom: 5px;
500
  }
501
  .lv-val {
502
- font-size: 14px; font-weight: 700;
503
- font-variant-numeric: tabular-nums; letter-spacing: -0.3px;
504
  }
505
- .lv.entry .lv-val { color: var(--indigo2); }
506
- .lv.sl .lv-val { color: var(--red2); }
507
- .lv.tp .lv-val { color: var(--green2); }
508
- .lv.rr .lv-val { color: var(--amber2); }
509
 
510
- /* ── Sizing row ── */
511
  .sizing {
512
- display: flex; background: var(--bg2);
 
 
513
  border-radius: 10px; overflow: hidden;
514
- border: 1px solid var(--b1); margin-bottom: 16px;
515
- }
516
- .sz { flex: 1; padding: 11px 13px; text-align: center; }
517
- .sz + .sz { border-left: 1px solid var(--b1); }
518
- .sz-label {
519
- font-size: 9px; font-weight: 700; letter-spacing: 0.8px;
520
- text-transform: uppercase; color: var(--t3); margin-bottom: 4px;
521
  }
 
 
 
522
  .sz-val { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
523
 
524
  /* ── Narrative ── */
525
  .narr {
526
  font-size: 12px; color: var(--t2); line-height: 1.6;
527
- padding: 12px 14px;
528
- background: rgba(99,102,241,0.04);
529
- border-left: 2px solid rgba(99,102,241,0.35);
530
  border-radius: 0 8px 8px 0;
531
- margin-bottom: 16px;
532
  }
533
 
534
  /* ── Evidence ── */
@@ -536,69 +605,76 @@ nav {
536
  font-size: 9px; font-weight: 700; letter-spacing: 0.8px;
537
  text-transform: uppercase; color: var(--t3); margin-bottom: 10px;
538
  }
539
- .ev-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
540
  .ev-key { font-size: 11px; font-weight: 600; color: var(--t2); width: 80px; flex-shrink: 0; }
541
- .ev-track { flex: 1; height: 3px; background: var(--bg3); border-radius: 2px; overflow: hidden; }
542
- .ev-fill { height: 100%; border-radius: 2px; background: linear-gradient(90deg, var(--indigo), var(--indigo2)); transition: width 0.8s var(--ease); }
543
- .ev-num { font-size: 11px; font-weight: 800; width: 20px; text-align: right; color: var(--t1); }
544
 
545
- /* ── Card actions ── */
546
- .card-acts { display: flex; gap: 8px; margin-top: 18px; }
547
  .ca {
548
- flex: 1; display: inline-flex; align-items: center;
549
- justify-content: center; gap: 6px;
550
- padding: 10px 14px; border-radius: 10px;
551
  font-size: 12px; font-weight: 700; font-family: var(--font);
552
- border: none; cursor: pointer;
553
- transition: transform 0.15s var(--ease), opacity 0.15s, background 0.15s;
554
- text-decoration: none; white-space: nowrap;
555
- }
556
- .ca:hover { transform: scale(1.03); }
557
- .ca:active { transform: scale(0.97); opacity: 0.85; }
558
- .ca-chart { background: rgba(99,102,241,0.1); color: var(--indigo2); border: 1px solid rgba(99,102,241,0.2); }
559
- .ca-copy { background: var(--bg3); color: var(--t1); border: 1px solid var(--b1); }
560
- .ca-copy:hover { background: var(--bg4); }
561
- .ca-ref { flex: 0; padding: 10px 12px; background: var(--bg3); color: var(--t3); border: 1px solid var(--b1); }
562
- .ca-ref:hover { color: var(--t1); border-color: var(--b2); }
 
563
 
564
  /* Error card */
565
  .err-card {
566
- background: rgba(244,63,94,0.06);
567
- border: 1px solid rgba(244,63,94,0.2);
568
- border-radius: 16px; padding: 20px;
569
- color: var(--red2); font-size: 13px; font-weight: 600;
 
570
  }
571
 
572
- /* ── Mobile ──────────────────────────────────────────────────────────────── */
573
  @media (max-width: 700px) {
574
- nav { padding: 0 16px; flex-wrap: wrap; height: auto; padding-top: 12px; padding-bottom: 12px; gap: 10px; }
575
- .nav-right { width: 100%; margin-left: 0; }
576
  .input-chip { flex: 1; min-width: 0; }
577
- .hero { padding: 100px 20px 40px; }
578
- .stats-strip { flex-direction: column; }
579
- .stat-cell + .stat-cell { border-left: none; border-top: 1px solid var(--b1); }
580
- #scan-section { padding: 0 16px 60px; }
 
581
  #grid { grid-template-columns: 1fr; }
582
- .card { padding: 18px; border-radius: 16px; }
583
- .rule { margin: 16px -18px; }
584
  }
585
  </style>
586
  </head>
587
  <body>
588
 
589
- <!-- ─── Nav ───────────────────────────────────────────────────────────────── -->
 
 
 
 
 
 
 
590
  <nav>
591
  <div class="brand">
592
- <div class="brand-icon">⚡</div>
593
  <span class="brand-name">Trade Copilot</span>
594
  </div>
595
-
596
- <div id="status-pill">
597
  <span class="live-dot"></span>
598
  <span id="status-text">Connecting…</span>
599
- </div>
600
-
601
- <div class="nav-right">
602
  <div class="input-chip">
603
  <label for="account">Account $</label>
604
  <input id="account" type="number" value="10000" min="100">
@@ -618,68 +694,63 @@ nav {
618
  </div>
619
  </nav>
620
 
621
- <!-- ── Hero ───────────────────────────────────────────────────────────────── -->
622
  <section class="hero">
623
- <div class="hero-eyebrow"><span></span>AI-Powered · Real-Time · Zero Hallucination</div>
624
  <h1 class="hero-title">
625
- Find your<br><span class="grad">next best trade.</span>
626
  </h1>
627
  <p class="hero-sub">
628
- Scans the entire crypto market in real-time. Surfaces top setups with entry, stop-loss, take-profit, sizing — all computed deterministically from live exchange data.
629
  </p>
630
- <button class="hero-cta" id="hero-cta">
631
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
632
- Scan Market Now
633
- </button>
634
-
635
- <div class="stats-strip">
636
- <div class="stat-cell">
637
- <div class="stat-val" id="stat-pairs">—</div>
638
- <div class="stat-label">Live Pairs</div>
639
- </div>
640
- <div class="stat-cell">
641
- <div class="stat-val">3</div>
642
- <div class="stat-label">Exchanges</div>
643
- </div>
644
- <div class="stat-cell">
645
- <div class="stat-val">3</div>
646
- <div class="stat-label">Timeframes</div>
647
- </div>
648
- <div class="stat-cell">
649
- <div class="stat-val">40/20/20/20</div>
650
- <div class="stat-label">Score Weight</div>
651
- </div>
652
  </div>
653
  </section>
654
 
655
- <!-- ── Scan results ────────────────────────────────────────────────────────── -->
656
  <section id="scan-section">
657
  <div id="meta"></div>
658
  <div id="error-banner"></div>
659
 
660
  <div id="loader">
661
- <div class="loader-orbit">
662
- <div class="loader-inner"></div>
663
- </div>
664
  <div class="loader-text">
665
  <h3>Scanning the market</h3>
666
- <p>Pulling live data · computing indicators · ranking setups</p>
667
  </div>
668
- <div class="progress-track"><div class="progress-fill"></div></div>
 
 
 
 
 
 
669
  </div>
670
 
671
- <div id="empty"></div>
672
  <div id="grid"></div>
673
  </section>
674
 
675
  <script>
676
- /* ── Helpers ────────────────────────────────────────────────────────────── */
677
  const $ = id => document.getElementById(id);
678
  const sf = n => {
679
  if (n == null) return '—';
680
  const v = +n;
681
- if (v >= 10000) return v.toLocaleString('en-US', {maximumFractionDigits:0});
682
- if (v >= 100) return v.toLocaleString('en-US', {maximumFractionDigits:2});
683
  if (v >= 1) return v.toFixed(4);
684
  if (v >= 0.001) return v.toFixed(6);
685
  return v.toFixed(8);
@@ -694,47 +765,40 @@ const fu = n => {
694
  };
695
  const pc = n => n == null ? null : (n>=0?'+':'')+( +n).toFixed(2)+'%';
696
 
697
- /* ── Status ─────────────────────────────────────────────────────────────── */
698
  async function checkStatus() {
699
  try {
700
- const d = await fetch('/api/status').then(r=>r.json());
701
  const pill = $('status-pill'), tx = $('status-text');
702
- if (d.ok) {
703
- tx.textContent = d.source.toUpperCase()+' · '+d.market;
704
- pill.className = 'ok';
705
- } else {
706
- tx.textContent = 'No exchange'; pill.className = 'err';
707
- }
708
  } catch { $('status-text').textContent='Offline'; $('status-pill').className='err'; }
709
  }
710
 
711
- /* ── Wire up both scan buttons ──────────────────────────────────────────── */
712
- function scanBtnHTML(loading) {
713
- if (loading) return `<div style="width:13px;height:13px;border:2px solid rgba(255,255,255,0.25);border-top-color:#fff;border-radius:50%;animation:orbit .7s linear infinite;flex-shrink:0"></div>Scanning…`;
714
- return `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>Scan`;
715
- }
716
- function heroBtnHTML(loading) {
717
- if (loading) return `<div style="width:16px;height:16px;border:2.5px solid rgba(255,255,255,0.25);border-top-color:#fff;border-radius:50%;animation:orbit .7s linear infinite;flex-shrink:0"></div>Scanning…`;
718
- return `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>Scan Market Now`;
719
- }
720
-
721
  $('scan-btn-nav').addEventListener('click', runScan);
722
- $('hero-cta').addEventListener('click', () => { $('scan-section').scrollIntoView({behavior:'smooth', block:'start'}); runScan(); });
 
 
 
 
 
 
 
 
 
 
 
723
 
724
  async function runScan() {
725
- const navBtn = $('scan-btn-nav');
726
- const heroBtn = $('hero-cta');
727
- const grid = $('grid'), meta = $('meta');
728
- const err = $('error-banner'), loader = $('loader');
729
-
730
- // State
731
- [navBtn, heroBtn].forEach(b => { b.disabled = true; });
732
- navBtn.innerHTML = scanBtnHTML(true);
733
- heroBtn.innerHTML = heroBtnHTML(true);
734
  grid.innerHTML = '';
735
  err.style.display = 'none';
 
736
  loader.style.display = 'flex';
737
- $('scan-section').scrollIntoView({behavior:'smooth', block:'start'});
738
 
739
  const account = +$('account').value || 10000;
740
  const risk = +$('risk').value || 1;
@@ -742,33 +806,27 @@ async function runScan() {
742
 
743
  try {
744
  const data = await fetch(`/api/scan?account=${account}&risk_pct=${risk}&top=${top}`)
745
- .then(r => { if(!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); });
746
 
747
  loader.style.display = 'none';
748
  const ts = new Date(data.fetched_at).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'});
749
  meta.textContent = `${data.cards.length} setups · ${data.source.toUpperCase()} · ${data.market} · ${ts}`;
 
750
 
751
- // Update live pairs stat
752
- const sp = $('stat-pairs');
753
- if (sp) sp.textContent = data.cards.length+'+ live';
754
-
755
- data.cards.forEach((c,i) => {
756
  const el = buildCard(c, account, risk);
757
- el.style.animationDelay = (i*60)+'ms';
758
  grid.appendChild(el);
759
  });
760
  } catch(e) {
761
  loader.style.display = 'none';
762
- err.textContent = '⚠︎ '+e.message;
 
763
  err.style.display = 'block';
764
- } finally {
765
- [navBtn, heroBtn].forEach(b => { b.disabled = false; });
766
- navBtn.innerHTML = scanBtnHTML(false);
767
- heroBtn.innerHTML = heroBtnHTML(false);
768
- }
769
  }
770
 
771
- /* ── Card builder ───────────────────────────────────────────────────────── */
772
  function buildCard(c, account, risk) {
773
  if (c.error) {
774
  const d = document.createElement('div');
@@ -777,30 +835,28 @@ function buildCard(c, account, risk) {
777
  return d;
778
  }
779
 
780
- const conf = +(c.confidence||0);
781
- const confPct = Math.min(100, conf*10);
782
- const confCls = conf>=7?'hi':conf>=5?'md':'lo';
783
- const confColor = conf>=7?'var(--green2)':conf>=5?'var(--amber2)':'var(--red2)';
784
- const dir = (c.direction||'long').toLowerCase();
785
- const lv = c.levels||{}, sz = c.sizing||{};
786
- const ev = c.evidence||{}, tf = c.timeframes||{};
787
- const ri = c.rank_info||{};
788
 
789
- const base = (c.symbol||'').replace(/USD$/,'');
790
  const chg = ri.change_24h_pct;
791
- const chgHTML = chg!=null
792
- ? `<span class="ch-change ${chg>=0?'pos':'neg'}">${pc(chg)}</span>`:'';
793
- const volHTML = ri.quote_volume_usd
794
- ? `<span class="ch-vol">${fu(ri.quote_volume_usd)}</span>`:'';
795
 
796
  const tfHTML = ['1h','15m','5m'].map(t => {
797
- const s = (tf[t]?.structure||'').toLowerCase();
798
  const cls = s==='uptrend'?'up':s==='downtrend'?'down':s?'range':'na';
799
  return `<span class="tf-chip ${cls}">${t} · ${s||'n/a'}</span>`;
800
  }).join('');
801
 
802
  const evHTML = ['structure','volume','positioning','catalyst'].map(k => {
803
- const e = ev[k]||{}, sc = +(e.score||0);
804
  return `<div class="ev-row">
805
  <span class="ev-key">${k[0].toUpperCase()+k.slice(1)}</span>
806
  <div class="ev-track"><div class="ev-fill" style="width:${sc*10}%"></div></div>
@@ -808,66 +864,59 @@ function buildCard(c, account, risk) {
808
  </div>`;
809
  }).join('');
810
 
811
- let narr = (c.narrative||'');
812
- if (narr.length>200) narr = narr.slice(0,197)+'…';
813
 
814
  const plan = [
815
  `${c.symbol} ${dir.toUpperCase()} | Confidence ${conf}/10`,
816
- `Entry ${sf(c.close)}`,
817
- `SL ${sf(lv.sl)}`,
818
- `TP1 ${sf(lv.tp1)}`,
819
- `TP2 ${sf(lv.tp2)}`,
820
- `TP3 ${sf(lv.tp3)}`,
821
- `R/R ${lv.rr1??'—'}×`,
822
- `Risk ${fu(sz.risk_usd)} | Units ${sz.units??'—'} | Notional ${fu(sz.notional)}`,
823
- `Time ${new Date().toLocaleString()}`
824
  ].join('\n');
825
 
826
- const tvUrl = `https://www.tradingview.com/chart/?symbol=${encodeURIComponent(c.tv_symbol||c.symbol)}`;
827
 
828
  const el = document.createElement('div');
829
  el.className = 'card';
830
  el.innerHTML = `
831
- <div class="card-stripe"></div>
832
- <div class="card-glow"></div>
833
-
834
  <div class="ch">
835
- <div class="ch-left">
836
- <div class="ch-symbol">${base}<span class="quote">USD</span></div>
837
- <div class="ch-meta">${chgHTML}${volHTML}</div>
838
  </div>
839
  <span class="dir-tag ${dir}">${dir.toUpperCase()}</span>
840
  </div>
841
 
842
- <div class="conf">
843
  <div class="conf-top">
844
  <span class="conf-lbl">Confidence</span>
845
- <span class="conf-score" style="color:${confColor}">${conf}<span class="denom">/10</span></span>
846
  </div>
847
  <div class="conf-track"><div class="conf-fill ${confCls}" style="width:${confPct}%"></div></div>
848
  </div>
849
 
850
  <div class="tf-row">${tfHTML}</div>
851
-
852
  <div class="rule"></div>
853
 
854
  <div class="levels">
855
- <div class="lv entry"><div class="lv-top"></div><div class="lv-label">Entry</div><div class="lv-val">${sf(c.close)}</div></div>
856
- <div class="lv sl"> <div class="lv-top"></div><div class="lv-label">Stop Loss</div><div class="lv-val">${sf(lv.sl)}</div></div>
857
- <div class="lv rr"> <div class="lv-top"></div><div class="lv-label">R / R</div><div class="lv-val">${lv.rr1??'—'}×</div></div>
858
- <div class="lv tp"> <div class="lv-top"></div><div class="lv-label">TP 1</div><div class="lv-val">${sf(lv.tp1)}</div></div>
859
- <div class="lv tp"> <div class="lv-top"></div><div class="lv-label">TP 2</div><div class="lv-val">${sf(lv.tp2)}</div></div>
860
- <div class="lv tp"> <div class="lv-top"></div><div class="lv-label">TP 3</div><div class="lv-val">${sf(lv.tp3)}</div></div>
861
  </div>
862
 
863
  <div class="sizing">
864
- <div class="sz"><div class="sz-label">Risk</div><div class="sz-val">${fu(sz.risk_usd)}</div></div>
865
- <div class="sz"><div class="sz-label">Units</div><div class="sz-val">${sz.units??'—'}</div></div>
866
- <div class="sz"><div class="sz-label">Notional</div><div class="sz-val">${fu(sz.notional)}</div></div>
867
  </div>
868
 
869
- ${narr?`<div class="narr">${narr}</div>`:''}
870
-
871
  <div class="rule"></div>
872
 
873
  <div class="ev-title">Score Breakdown</div>
@@ -882,37 +931,34 @@ function buildCard(c, account, risk) {
882
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
883
  Copy Plan
884
  </button>
885
- <button class="ca ca-ref" data-sym="${c.symbol}" data-acc="${account}" data-risk="${risk}" title="Refresh card">
886
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
887
  </button>
888
  </div>`;
889
 
890
- // Copy
891
  el.querySelector('.ca-copy').addEventListener('click', function() {
892
- const text = this.dataset.plan.replace(/\\n/g,'\n');
893
  navigator.clipboard.writeText(text).then(() => {
894
  const orig = this.innerHTML;
895
  this.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg> Copied!`;
896
- this.style.color = 'var(--green2)'; this.style.borderColor = 'rgba(16,185,129,0.3)';
897
  setTimeout(() => { this.innerHTML = orig; this.style.color=''; this.style.borderColor=''; }, 2200);
898
  }).catch(() => prompt('Copy:', text));
899
  });
900
 
901
- // Refresh
902
  el.querySelector('.ca-ref').addEventListener('click', async function() {
903
- const {sym, acc, risk} = this.dataset, orig = this.innerHTML;
904
  this.disabled = true;
905
- this.innerHTML = `<div style="width:13px;height:13px;border:2px solid rgba(255,255,255,0.15);border-top-color:var(--t2);border-radius:50%;animation:orbit .7s linear infinite"></div>`;
906
  try {
907
- const data = await fetch(`/api/card/${sym}?account=${acc}&risk_pct=${risk}`).then(r=>r.json());
908
- el.replaceWith(buildCard(data, +acc, +risk));
909
  } catch { this.disabled=false; this.innerHTML=orig; }
910
  });
911
 
912
  return el;
913
  }
914
 
915
- /* ── Init ───────────────────────────────────────────────────────────────── */
916
  checkStatus();
917
  setInterval(checkStatus, 60000);
918
  </script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Trade Copilot</title>
7
  <style>
8
+ /* ── Tokens ───────────────────────────────────────────────────────────── */
9
  :root {
10
+ --bg: #f2f4f8;
11
+ --white: #ffffff;
12
+ --glass: rgba(255,255,255,0.65);
13
+ --glass2: rgba(255,255,255,0.45);
14
+ --glass3: rgba(255,255,255,0.25);
15
+
16
+ --t1: #0a0f1e;
17
+ --t2: #3d4966;
18
+ --t3: #8892ab;
19
+
20
+ /* Teal-green — money, growth, trust */
21
+ --teal: #0d9488;
22
+ --teal2: #14b8a6;
23
+ --teal3: #99f6e4;
24
+
25
+ /* Accent blue — intelligence, calm */
26
+ --blue: #2563eb;
27
+ --blue2: #3b82f6;
 
 
 
 
28
 
29
  /* Semantic */
30
+ --green: #059669;
31
+ --green2: #10b981;
32
+ --red: #dc2626;
33
+ --red2: #ef4444;
34
+ --amber: #d97706;
35
+ --amber2: #f59e0b;
36
+
37
+ --r: 14px;
38
+ --r2: 22px;
39
+ --font: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif;
 
40
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
41
+ --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
42
  }
43
 
44
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
45
  html { scroll-behavior: smooth; }
46
+
47
  body {
48
  font-family: var(--font);
49
  background: var(--bg);
 
53
  overflow-x: hidden;
54
  }
55
 
56
+ /* ── Hero background ─────────────────────────────────────────────────── */
57
+ .hero-bg {
58
+ position: fixed; inset: 0; z-index: 0;
59
+ overflow: hidden;
 
 
60
  pointer-events: none;
 
61
  }
62
 
63
+ /* Large abstract gradient orbs — the "hero shape" */
64
+ .orb {
65
+ position: absolute;
66
+ border-radius: 50%;
67
+ filter: blur(80px);
68
+ opacity: 0.55;
69
+ }
70
+ .orb-1 {
71
+ width: 700px; height: 700px;
72
+ top: -200px; left: -150px;
73
+ background: radial-gradient(circle, #99f6e4, #5eead4, #0d9488);
74
+ animation: drift1 18s ease-in-out infinite alternate;
75
+ }
76
+ .orb-2 {
77
+ width: 600px; height: 600px;
78
+ top: -100px; right: -200px;
79
+ background: radial-gradient(circle, #bfdbfe, #93c5fd, #3b82f6);
80
+ opacity: 0.4;
81
+ animation: drift2 22s ease-in-out infinite alternate;
82
+ }
83
+ .orb-3 {
84
+ width: 500px; height: 500px;
85
+ bottom: 10%; left: 30%;
86
+ background: radial-gradient(circle, #d9f99d, #86efac, #22c55e);
87
+ opacity: 0.25;
88
+ animation: drift3 26s ease-in-out infinite alternate;
89
+ }
90
+
91
+ @keyframes drift1 {
92
+ from { transform: translate(0,0) scale(1); }
93
+ to { transform: translate(60px,40px) scale(1.08); }
94
+ }
95
+ @keyframes drift2 {
96
+ from { transform: translate(0,0) scale(1); }
97
+ to { transform: translate(-40px,60px) scale(1.1); }
98
+ }
99
+ @keyframes drift3 {
100
+ from { transform: translate(0,0) scale(1); }
101
+ to { transform: translate(30px,-50px) scale(0.95); }
102
+ }
103
+
104
+ /* Subtle mesh grid */
105
+ .hero-bg::after {
106
+ content: '';
107
+ position: absolute; inset: 0;
108
+ background-image:
109
+ linear-gradient(rgba(13,148,136,0.04) 1px, transparent 1px),
110
+ linear-gradient(90deg, rgba(13,148,136,0.04) 1px, transparent 1px);
111
+ background-size: 48px 48px;
112
+ }
113
+
114
+ /* ── Navbar — liquid glass ──────────────────────────────────────────── */
115
  nav {
116
+ position: fixed; top: 0; left: 0; right: 0; z-index: 200;
117
+ height: 60px;
 
118
  display: flex; align-items: center;
119
+ padding: 0 36px; gap: 16px;
120
+ background: rgba(255,255,255,0.72);
121
+ backdrop-filter: blur(24px) saturate(180%);
122
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
123
+ border-bottom: 1px solid rgba(13,148,136,0.1);
124
+ box-shadow: 0 1px 0 rgba(255,255,255,0.8), 0 4px 24px rgba(0,0,0,0.06);
125
  }
126
 
127
  .brand {
128
  display: flex; align-items: center; gap: 10px;
129
+ text-decoration: none; color: inherit; flex-shrink: 0;
130
  }
131
+ .brand-mark {
132
+ width: 32px; height: 32px;
133
+ background: linear-gradient(135deg, var(--teal), var(--blue2));
134
+ border-radius: 9px;
135
  display: grid; place-items: center;
136
+ font-size: 16px;
137
+ box-shadow: 0 2px 12px rgba(13,148,136,0.35), inset 0 1px 0 rgba(255,255,255,0.3);
138
  }
139
  .brand-name {
140
+ font-size: 16px; font-weight: 700; letter-spacing: -0.5px;
141
+ color: var(--t1);
 
 
142
  }
143
 
144
  #status-pill {
145
  display: inline-flex; align-items: center; gap: 6px;
146
+ font-size: 11px; font-weight: 600; letter-spacing: 0.1px;
147
  padding: 4px 12px; border-radius: 99px;
148
+ background: rgba(255,255,255,0.8);
149
+ border: 1px solid rgba(0,0,0,0.08);
150
  color: var(--t3);
151
+ transition: all 0.35s var(--ease);
152
+ backdrop-filter: blur(8px);
153
+ -webkit-backdrop-filter: blur(8px);
154
  }
155
+ #status-pill.ok { color: var(--green); border-color: rgba(5,150,105,0.2); background: rgba(209,250,229,0.7); }
156
+ #status-pill.err { color: var(--red); border-color: rgba(220,38,38,0.2); background: rgba(254,226,226,0.7); }
157
 
158
+ .live-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
 
 
 
159
  .ok .live-dot { animation: livePulse 2s ease-in-out infinite; }
160
  @keyframes livePulse {
161
+ 0%,100% { box-shadow: 0 0 0 0 rgba(5,150,105,0.5); }
162
+ 50% { box-shadow: 0 0 0 5px rgba(5,150,105,0); }
163
  }
164
 
165
+ .nav-controls {
166
+ display: flex; align-items: center; gap: 8px;
 
167
  margin-left: auto;
168
  }
169
 
170
  .input-chip {
171
  display: flex; align-items: center; gap: 8px;
172
+ background: rgba(255,255,255,0.8);
173
+ border: 1px solid rgba(0,0,0,0.08);
174
+ border-radius: 10px; padding: 6px 14px;
175
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
176
  transition: border-color 0.2s, box-shadow 0.2s;
177
  }
178
  .input-chip:focus-within {
179
+ border-color: rgba(13,148,136,0.4);
180
+ box-shadow: 0 0 0 3px rgba(13,148,136,0.08);
181
  }
182
  .input-chip label {
183
+ font-size: 10px; font-weight: 700; letter-spacing: 0.7px;
184
  text-transform: uppercase; color: var(--t3);
185
  user-select: none; white-space: nowrap;
186
  }
 
194
  .input-chip input::-webkit-inner-spin-button,
195
  .input-chip input::-webkit-outer-spin-button { -webkit-appearance: none; }
196
 
197
+ #scan-btn-nav {
198
+ display: inline-flex; align-items: center; gap: 7px;
199
+ background: linear-gradient(135deg, var(--teal), #0f766e);
200
  color: #fff; border: none;
201
  padding: 9px 22px; border-radius: 10px;
202
  font-size: 13px; font-weight: 700; font-family: var(--font);
203
+ cursor: pointer; white-space: nowrap; flex-shrink: 0;
204
+ transition: transform 0.2s var(--spring), box-shadow 0.2s;
205
+ box-shadow: 0 4px 20px rgba(13,148,136,0.35), inset 0 1px 0 rgba(255,255,255,0.2);
 
206
  }
207
+ #scan-btn-nav:hover { transform: translateY(-1px); box-shadow: 0 8px 28px rgba(13,148,136,0.45), inset 0 1px 0 rgba(255,255,255,0.2); }
208
+ #scan-btn-nav:active { transform: scale(0.98); }
209
+ #scan-btn-nav:disabled { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; }
210
 
211
+ /* ── Hero ────────────────────────────────────────────────────────────── */
212
  .hero {
213
+ position: relative; z-index: 1;
214
+ padding: 140px 36px 80px;
215
  text-align: center;
216
+ max-width: 900px; margin: 0 auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  }
218
 
219
+ .eyebrow {
220
  display: inline-flex; align-items: center; gap: 8px;
221
  font-size: 11px; font-weight: 700; letter-spacing: 1.2px;
222
+ text-transform: uppercase; color: var(--teal);
223
+ background: rgba(209,250,229,0.7);
224
+ border: 1px solid rgba(13,148,136,0.2);
225
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
226
  padding: 5px 14px; border-radius: 99px;
227
+ margin-bottom: 28px;
228
+ animation: riseIn 0.7s var(--ease) 0.1s both;
229
  }
230
+ .eyebrow-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--teal); }
231
 
232
+ h1.hero-title {
233
+ font-size: clamp(42px, 6vw, 76px);
234
+ font-weight: 800; letter-spacing: -2.5px; line-height: 1.02;
235
+ color: var(--t1); margin-bottom: 22px;
236
+ animation: riseIn 0.7s var(--ease) 0.2s both;
 
 
237
  }
238
+ h1.hero-title .accent {
239
+ background: linear-gradient(135deg, var(--teal) 0%, var(--blue2) 60%);
240
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
241
  }
242
 
243
  .hero-sub {
244
+ font-size: 18px; line-height: 1.65; color: var(--t2);
245
+ max-width: 540px; margin: 0 auto 40px; font-weight: 400;
246
+ animation: riseIn 0.7s var(--ease) 0.3s both;
247
+ }
248
+
249
+ .hero-actions {
250
+ display: flex; justify-content: center; gap: 12px; flex-wrap: wrap;
251
+ animation: riseIn 0.7s var(--ease) 0.4s both;
252
  }
253
 
254
+ .btn-primary {
255
  display: inline-flex; align-items: center; gap: 10px;
256
+ background: linear-gradient(135deg, var(--teal), #0f766e);
257
  color: #fff; border: none;
258
+ padding: 16px 36px; border-radius: 14px;
259
+ font-size: 16px; font-weight: 700; font-family: var(--font);
260
  cursor: pointer; letter-spacing: -0.3px;
261
+ transition: transform 0.2s var(--spring), box-shadow 0.2s;
262
+ box-shadow: 0 8px 32px rgba(13,148,136,0.35), inset 0 1px 0 rgba(255,255,255,0.2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  }
264
+ .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 16px 48px rgba(13,148,136,0.45), inset 0 1px 0 rgba(255,255,255,0.2); }
265
+ .btn-primary:active { transform: scale(0.98); }
266
+ .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; }
267
+
268
+ .btn-ghost {
269
+ display: inline-flex; align-items: center; gap: 8px;
270
+ background: rgba(255,255,255,0.8);
271
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
272
+ border: 1px solid rgba(0,0,0,0.1);
273
+ color: var(--t2); padding: 16px 28px; border-radius: 14px;
274
+ font-size: 16px; font-weight: 600; font-family: var(--font);
275
+ cursor: pointer;
276
+ transition: transform 0.2s var(--spring), box-shadow 0.2s, border-color 0.2s;
277
+ box-shadow: 0 2px 12px rgba(0,0,0,0.06);
278
+ }
279
+ .btn-ghost:hover { transform: translateY(-1px); border-color: rgba(13,148,136,0.3); box-shadow: 0 8px 24px rgba(0,0,0,0.08); }
280
+
281
+ /* Stats cards */
282
+ .stats {
283
+ display: flex; justify-content: center; gap: 12px; flex-wrap: wrap;
284
+ margin-top: 60px;
285
+ animation: riseIn 0.7s var(--ease) 0.55s both;
286
+ }
287
+ .stat-card {
288
+ background: var(--glass);
289
+ backdrop-filter: blur(20px) saturate(180%);
290
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
291
+ border: 1px solid rgba(255,255,255,0.9);
292
+ border-radius: 16px; padding: 18px 28px;
293
+ text-align: center;
294
+ box-shadow: 0 4px 24px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,1);
295
+ min-width: 130px;
296
  }
 
297
  .stat-val {
298
+ font-size: 26px; font-weight: 800; letter-spacing: -1px;
299
+ color: var(--t1); line-height: 1;
300
  }
301
  .stat-label {
302
  font-size: 11px; font-weight: 600; letter-spacing: 0.5px;
303
+ text-transform: uppercase; color: var(--t3); margin-top: 5px;
304
  }
305
 
306
+ @keyframes riseIn {
307
+ from { opacity: 0; transform: translateY(28px); }
308
  to { opacity: 1; transform: translateY(0); }
309
  }
310
 
311
+ /* ── Section container ──────────────────────────────────────────────── */
312
  #scan-section {
313
+ position: relative; z-index: 1;
314
+ max-width: 1480px; margin: 0 auto;
315
+ padding: 0 36px 100px;
316
  }
317
 
318
  #meta {
319
+ font-size: 12px; color: var(--t3); font-weight: 600;
320
+ letter-spacing: 0.2px; margin-bottom: 24px; min-height: 16px;
 
321
  }
322
 
323
  #error-banner {
324
  display: none;
325
+ background: rgba(254,226,226,0.8);
326
+ border: 1px solid rgba(220,38,38,0.2);
327
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
328
+ border-radius: 12px; padding: 14px 20px;
329
+ color: var(--red); font-size: 13px; font-weight: 600;
330
  margin-bottom: 20px;
 
331
  }
332
 
333
+ /* ── Loader ──────────────────────────────────────────────────────────── */
334
  #loader {
335
+ display: none; flex-direction: column; align-items: center;
336
+ padding: 80px 0; gap: 22px;
337
+ }
338
+ .loader-glass {
339
+ width: 72px; height: 72px;
340
+ background: var(--glass);
341
+ backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
342
+ border: 1px solid rgba(255,255,255,0.9);
343
+ border-radius: 20px;
344
+ display: grid; place-items: center;
345
+ box-shadow: 0 8px 32px rgba(0,0,0,0.08), inset 0 1px 0 white;
346
+ position: relative; overflow: hidden;
347
  }
348
+ .loader-glass::after {
349
  content: '';
350
  position: absolute; inset: 0;
351
+ border-radius: 20px;
352
  border: 2px solid transparent;
353
+ border-top-color: var(--teal);
354
+ border-right-color: var(--blue2);
355
+ animation: spin 1s linear infinite;
356
+ }
357
+ @keyframes spin { to { transform: rotate(360deg); } }
358
+ .loader-icon { font-size: 28px; }
359
+ .loader-text { text-align: center; }
360
+ .loader-text h3 { font-size: 18px; font-weight: 700; letter-spacing: -0.4px; margin-bottom: 4px; }
361
+ .loader-text p { font-size: 13px; color: var(--t3); }
362
+ .prog-track { width: 200px; height: 3px; background: rgba(0,0,0,0.08); border-radius: 2px; overflow: hidden; }
363
+ .prog-fill {
364
+ height: 100%; border-radius: 2px;
365
+ background: linear-gradient(90deg, var(--teal), var(--blue2));
366
+ animation: prog 1.8s ease-in-out infinite;
367
  }
368
+ @keyframes prog {
369
+ 0% { width: 0; margin-left: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
370
  50% { width: 60%; margin-left: 20%; }
371
+ 100% { width: 0; margin-left: 100%; }
372
  }
373
 
374
+ /* ── Empty state ──────────────────────────────────────────────────────── */
375
+ #empty {
376
+ display: flex; flex-direction: column; align-items: center;
377
+ padding: 80px 0; gap: 12px;
378
+ }
379
+ .empty-glass {
380
+ width: 88px; height: 88px;
381
+ background: var(--glass);
382
+ backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
383
+ border: 1px solid rgba(255,255,255,0.9);
384
+ border-radius: 24px;
385
+ display: grid; place-items: center;
386
+ font-size: 40px;
387
+ box-shadow: 0 8px 32px rgba(0,0,0,0.06), inset 0 1px 0 white;
388
+ margin-bottom: 4px;
389
+ animation: float 3s ease-in-out infinite;
390
+ }
391
+ @keyframes float {
392
+ 0%,100% { transform: translateY(0); }
393
+ 50% { transform: translateY(-10px); }
394
+ }
395
+ .empty-title { font-size: 20px; font-weight: 700; letter-spacing: -0.5px; color: var(--t1); opacity: 0.5; }
396
+ .empty-sub { font-size: 14px; color: var(--t3); }
397
 
398
+ /* ── Cards grid ──────────────────────────────────────────────────────── */
399
  #grid {
400
  display: grid;
401
+ grid-template-columns: repeat(auto-fill, minmax(370px, 1fr));
402
  gap: 16px;
403
  }
404
 
405
+ /* ── Trade Card — LIQUID GLASS ───────────────────────────────────────── */
406
  .card {
407
  position: relative; overflow: hidden;
408
+
409
+ /* Liquid glass effect */
410
+ background: var(--glass);
411
+ backdrop-filter: blur(28px) saturate(180%);
412
+ -webkit-backdrop-filter: blur(28px) saturate(180%);
413
+ border: 1px solid rgba(255,255,255,0.85);
414
+ border-radius: var(--r2);
415
+ box-shadow:
416
+ 0 1px 0 rgba(255,255,255,1) inset, /* top highlight */
417
+ 0 -1px 0 rgba(0,0,0,0.04) inset, /* bottom inner shadow */
418
+ 0 8px 32px rgba(0,0,0,0.08), /* soft drop shadow */
419
+ 0 2px 8px rgba(0,0,0,0.04);
420
+
421
  padding: 24px;
422
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease), border-color 0.3s;
423
+ animation: cardIn 0.5s var(--ease) both;
424
  }
425
+ @keyframes cardIn {
426
+ from { opacity: 0; transform: translateY(20px) scale(0.98); }
427
  to { opacity: 1; transform: translateY(0) scale(1); }
428
  }
429
  .card:hover {
430
+ transform: translateY(-4px) scale(1.005);
431
+ border-color: rgba(255,255,255,1);
432
  box-shadow:
433
+ 0 1px 0 rgba(255,255,255,1) inset,
434
+ 0 -1px 0 rgba(0,0,0,0.04) inset,
435
+ 0 20px 60px rgba(0,0,0,0.12),
436
+ 0 0 0 1px rgba(13,148,136,0.15);
437
  }
438
 
439
+ /* Inner specular sheen — the "liquid" part */
440
+ .card::before {
441
+ content: '';
442
+ position: absolute; top: 0; left: 0; right: 0;
443
+ height: 50%;
444
+ background: linear-gradient(180deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 100%);
445
+ border-radius: var(--r2) var(--r2) 0 0;
446
+ pointer-events: none; z-index: 0;
447
  }
448
+ .card > * { position: relative; z-index: 1; }
449
 
450
+ /* Teal top-edge glow on hover */
451
+ .card::after {
452
+ content: '';
453
+ position: absolute; top: 0; left: 10%; right: 10%;
454
+ height: 2px; border-radius: 0 0 2px 2px;
455
+ background: linear-gradient(90deg, transparent, var(--teal), transparent);
456
+ opacity: 0;
457
  transition: opacity 0.3s;
458
  }
459
+ .card:hover::after { opacity: 1; }
460
 
461
  /* ── Card header ── */
462
  .ch {
463
  display: flex; justify-content: space-between;
464
+ align-items: flex-start; margin-bottom: 18px;
465
  }
 
466
  .ch-symbol {
467
+ font-size: 26px; font-weight: 800; letter-spacing: -1px;
468
+ color: var(--t1); line-height: 1;
 
469
  }
470
+ .ch-symbol .q { font-size: 17px; font-weight: 500; color: var(--t3); letter-spacing: 0; }
471
+ .ch-sub {
472
+ display: flex; align-items: center; gap: 8px;
473
+ margin-top: 5px; font-size: 12px; font-weight: 600;
474
  }
475
+ .chg.pos { color: var(--green); }
476
+ .chg.neg { color: var(--red); }
477
+ .vol { color: var(--t3); }
 
478
 
479
  .dir-tag {
480
  font-size: 10px; font-weight: 800; letter-spacing: 1px;
481
+ text-transform: uppercase; padding: 6px 14px; border-radius: 99px;
482
+ /* glass pill */
483
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
484
+ }
485
+ .dir-tag.long {
486
+ background: rgba(209,250,229,0.7);
487
+ color: var(--green); border: 1px solid rgba(5,150,105,0.2);
488
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.6);
489
+ }
490
+ .dir-tag.short {
491
+ background: rgba(254,226,226,0.7);
492
+ color: var(--red); border: 1px solid rgba(220,38,38,0.2);
493
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.6);
494
  }
 
 
495
 
496
  /* ── Confidence ── */
497
+ .conf-wrap {
498
+ background: rgba(255,255,255,0.5);
499
+ border: 1px solid rgba(255,255,255,0.8);
500
+ border-radius: var(--r);
501
+ padding: 14px 16px;
502
+ margin-bottom: 16px;
503
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.8);
504
  }
505
  .conf-top {
506
+ display: flex; justify-content: space-between; align-items: baseline;
507
+ margin-bottom: 10px;
508
  }
509
  .conf-lbl {
510
  font-size: 10px; font-weight: 700; letter-spacing: 0.8px;
511
  text-transform: uppercase; color: var(--t3);
512
  }
513
  .conf-score {
514
+ font-size: 30px; font-weight: 800; letter-spacing: -1.2px; line-height: 1;
515
  }
516
+ .conf-denom { font-size: 14px; font-weight: 500; color: var(--t3); }
517
  .conf-track {
518
+ height: 5px; background: rgba(0,0,0,0.06);
519
+ border-radius: 3px; overflow: hidden;
520
  }
521
  .conf-fill {
522
+ height: 100%; border-radius: 3px;
523
  transition: width 1s var(--ease);
524
  }
525
+ .conf-fill.hi { background: linear-gradient(90deg, #047857, var(--green2)); }
526
  .conf-fill.md { background: linear-gradient(90deg, #b45309, var(--amber2)); }
527
+ .conf-fill.lo { background: linear-gradient(90deg, #b91c1c, var(--red2)); }
528
 
529
  /* ── TF chips ── */
530
+ .tf-row { display: flex; gap: 6px; margin-bottom: 16px; flex-wrap: wrap; }
 
 
531
  .tf-chip {
532
+ font-size: 10px; font-weight: 700; letter-spacing: 0.3px;
533
  padding: 4px 10px; border-radius: 99px; text-transform: uppercase;
534
+ background: rgba(255,255,255,0.6);
535
+ backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
536
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.8);
537
  }
538
+ .tf-chip.up { color: var(--green); border: 1px solid rgba(5,150,105,0.2); background: rgba(209,250,229,0.6); }
539
+ .tf-chip.down { color: var(--red); border: 1px solid rgba(220,38,38,0.2); background: rgba(254,226,226,0.6); }
540
+ .tf-chip.range { color: var(--amber); border: 1px solid rgba(217,119,6,0.2); background: rgba(254,243,199,0.6); }
541
+ .tf-chip.na { color: var(--t3); border: 1px solid rgba(0,0,0,0.07); }
542
 
543
  /* ── Rule ── */
544
+ .rule { height: 1px; background: rgba(0,0,0,0.06); margin: 16px -24px; }
545
 
546
+ /* ── Levels grid ── */
547
  .levels {
548
  display: grid; grid-template-columns: repeat(3,1fr);
549
+ gap: 8px; margin-bottom: 12px;
550
  }
551
  .lv {
552
+ background: rgba(255,255,255,0.55);
553
+ border: 1px solid rgba(255,255,255,0.8);
554
+ border-radius: 10px; padding: 10px 12px;
555
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.9);
556
  position: relative; overflow: hidden;
 
557
  }
558
+ .lv::before {
559
+ content: '';
560
+ position: absolute; top: 0; left: 0; right: 0; height: 2px;
 
561
  }
562
+ .lv.entry::before { background: var(--teal2); }
563
+ .lv.sl::before { background: var(--red2); }
564
+ .lv.tp::before { background: var(--green2); }
565
+ .lv.rr::before { background: var(--amber2); }
566
 
567
+ .lv-lbl {
568
+ font-size: 9px; font-weight: 700; letter-spacing: 0.7px;
569
+ text-transform: uppercase; color: var(--t3); margin-bottom: 4px;
570
  }
571
  .lv-val {
572
+ font-size: 14px; font-weight: 700; letter-spacing: -0.3px;
573
+ font-variant-numeric: tabular-nums;
574
  }
575
+ .lv.entry .lv-val { color: var(--teal); }
576
+ .lv.sl .lv-val { color: var(--red); }
577
+ .lv.tp .lv-val { color: var(--green); }
578
+ .lv.rr .lv-val { color: var(--amber); }
579
 
580
+ /* ── Sizing ── */
581
  .sizing {
582
+ display: flex;
583
+ background: rgba(255,255,255,0.55);
584
+ border: 1px solid rgba(255,255,255,0.8);
585
  border-radius: 10px; overflow: hidden;
586
+ margin-bottom: 12px;
587
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.9);
 
 
 
 
 
588
  }
589
+ .sz { flex: 1; padding: 10px 12px; text-align: center; }
590
+ .sz + .sz { border-left: 1px solid rgba(0,0,0,0.06); }
591
+ .sz-lbl { font-size: 9px; font-weight: 700; letter-spacing: 0.7px; text-transform: uppercase; color: var(--t3); margin-bottom: 3px; }
592
  .sz-val { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
593
 
594
  /* ── Narrative ── */
595
  .narr {
596
  font-size: 12px; color: var(--t2); line-height: 1.6;
597
+ padding: 11px 14px; margin-bottom: 12px;
598
+ background: rgba(209,250,229,0.3);
599
+ border-left: 2px solid rgba(13,148,136,0.4);
600
  border-radius: 0 8px 8px 0;
 
601
  }
602
 
603
  /* ── Evidence ── */
 
605
  font-size: 9px; font-weight: 700; letter-spacing: 0.8px;
606
  text-transform: uppercase; color: var(--t3); margin-bottom: 10px;
607
  }
608
+ .ev-row { display: flex; align-items: center; gap: 10px; margin-bottom: 7px; }
609
  .ev-key { font-size: 11px; font-weight: 600; color: var(--t2); width: 80px; flex-shrink: 0; }
610
+ .ev-track { flex: 1; height: 3px; background: rgba(0,0,0,0.07); border-radius: 2px; overflow: hidden; }
611
+ .ev-fill { height: 100%; border-radius: 2px; background: linear-gradient(90deg, var(--teal), var(--teal2)); transition: width 0.8s var(--ease); }
612
+ .ev-num { font-size: 11px; font-weight: 800; width: 22px; text-align: right; color: var(--t1); }
613
 
614
+ /* ── Actions ── */
615
+ .card-acts { display: flex; gap: 8px; margin-top: 16px; }
616
  .ca {
617
+ flex: 1; display: inline-flex; align-items: center; justify-content: center;
618
+ gap: 6px; padding: 10px 14px; border-radius: 10px;
 
619
  font-size: 12px; font-weight: 700; font-family: var(--font);
620
+ border: none; cursor: pointer; text-decoration: none; white-space: nowrap;
621
+ transition: transform 0.15s var(--spring), box-shadow 0.15s;
622
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
623
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.7);
624
+ }
625
+ .ca:hover { transform: scale(1.03); }
626
+ .ca:active { transform: scale(0.97); }
627
+ .ca-chart { background: rgba(209,250,229,0.6); color: var(--teal); border: 1px solid rgba(13,148,136,0.2); }
628
+ .ca-copy { background: rgba(255,255,255,0.7); color: var(--t1); border: 1px solid rgba(0,0,0,0.08); }
629
+ .ca-copy:hover { background: rgba(255,255,255,0.9); }
630
+ .ca-ref { flex: 0; padding: 10px 12px; background: rgba(255,255,255,0.7); color: var(--t3); border: 1px solid rgba(0,0,0,0.08); }
631
+ .ca-ref:hover { color: var(--t1); }
632
 
633
  /* Error card */
634
  .err-card {
635
+ background: rgba(254,226,226,0.7);
636
+ border: 1px solid rgba(220,38,38,0.2);
637
+ border-radius: var(--r2); padding: 20px;
638
+ color: var(--red); font-size: 13px; font-weight: 600;
639
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
640
  }
641
 
642
+ /* ── Mobile ──────────────────────────────────────────────────────────── */
643
  @media (max-width: 700px) {
644
+ nav { padding: 0 20px; height: auto; flex-wrap: wrap; padding-top: 12px; padding-bottom: 12px; gap: 10px; }
645
+ .nav-controls { width: 100%; margin-left: 0; }
646
  .input-chip { flex: 1; min-width: 0; }
647
+ .hero { padding: 110px 20px 60px; }
648
+ h1.hero-title { letter-spacing: -1.5px; }
649
+ .stats { gap: 8px; }
650
+ .stat-card { padding: 14px 18px; min-width: 100px; }
651
+ #scan-section { padding: 0 16px 80px; }
652
  #grid { grid-template-columns: 1fr; }
653
+ .card { padding: 18px; border-radius: 18px; }
654
+ .rule { margin: 14px -18px; }
655
  }
656
  </style>
657
  </head>
658
  <body>
659
 
660
+ <!-- Animated background orbs -->
661
+ <div class="hero-bg">
662
+ <div class="orb orb-1"></div>
663
+ <div class="orb orb-2"></div>
664
+ <div class="orb orb-3"></div>
665
+ </div>
666
+
667
+ <!-- ── Nav ───────────────────────────────────────────────────────────── -->
668
  <nav>
669
  <div class="brand">
670
+ <div class="brand-mark">⚡</div>
671
  <span class="brand-name">Trade Copilot</span>
672
  </div>
673
+ <span id="status-pill">
 
674
  <span class="live-dot"></span>
675
  <span id="status-text">Connecting…</span>
676
+ </span>
677
+ <div class="nav-controls">
 
678
  <div class="input-chip">
679
  <label for="account">Account $</label>
680
  <input id="account" type="number" value="10000" min="100">
 
694
  </div>
695
  </nav>
696
 
697
+ <!-- ── Hero ──────────────────────────────────────────────────────────── -->
698
  <section class="hero">
699
+ <div class="eyebrow"><span class="eyebrow-dot"></span>Real-Time · AI-Powered · Zero Hallucination</div>
700
  <h1 class="hero-title">
701
+ Find your<br><span class="accent">next best trade.</span>
702
  </h1>
703
  <p class="hero-sub">
704
+ Scans the entire crypto market live. Surfaces top setups with precise entry, stop-loss, take-profit and sizing — computed deterministically from real exchange data.
705
  </p>
706
+ <div class="hero-actions">
707
+ <button class="btn-primary" id="hero-cta">
708
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
709
+ Scan Market
710
+ </button>
711
+ <button class="btn-ghost" onclick="document.getElementById('scan-section').scrollIntoView({behavior:'smooth'})">
712
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
713
+ See setups
714
+ </button>
715
+ </div>
716
+ <div class="stats">
717
+ <div class="stat-card"><div class="stat-val" id="stat-pairs"></div><div class="stat-label">Live Pairs</div></div>
718
+ <div class="stat-card"><div class="stat-val">3</div><div class="stat-label">Exchanges</div></div>
719
+ <div class="stat-card"><div class="stat-val">3</div><div class="stat-label">Timeframes</div></div>
720
+ <div class="stat-card"><div class="stat-val">40/20/20/20</div><div class="stat-label">Score Weight</div></div>
 
 
 
 
 
 
 
721
  </div>
722
  </section>
723
 
724
+ <!-- ── Results ────────────────────────────────────────────────────────── -->
725
  <section id="scan-section">
726
  <div id="meta"></div>
727
  <div id="error-banner"></div>
728
 
729
  <div id="loader">
730
+ <div class="loader-glass"><div class="loader-icon">📡</div></div>
 
 
731
  <div class="loader-text">
732
  <h3>Scanning the market</h3>
733
+ <p>Fetching live data · computing indicators · ranking setups</p>
734
  </div>
735
+ <div class="prog-track"><div class="prog-fill"></div></div>
736
+ </div>
737
+
738
+ <div id="empty">
739
+ <div class="empty-glass">📡</div>
740
+ <div class="empty-title">Ready to scan</div>
741
+ <div class="empty-sub">Hit Scan Market to surface top trade setups</div>
742
  </div>
743
 
 
744
  <div id="grid"></div>
745
  </section>
746
 
747
  <script>
 
748
  const $ = id => document.getElementById(id);
749
  const sf = n => {
750
  if (n == null) return '—';
751
  const v = +n;
752
+ if (v >= 10000) return v.toLocaleString('en-US', {maximumFractionDigits: 0});
753
+ if (v >= 100) return v.toLocaleString('en-US', {maximumFractionDigits: 2});
754
  if (v >= 1) return v.toFixed(4);
755
  if (v >= 0.001) return v.toFixed(6);
756
  return v.toFixed(8);
 
765
  };
766
  const pc = n => n == null ? null : (n>=0?'+':'')+( +n).toFixed(2)+'%';
767
 
768
+ /* Status */
769
  async function checkStatus() {
770
  try {
771
+ const d = await fetch('/api/status').then(r => r.json());
772
  const pill = $('status-pill'), tx = $('status-text');
773
+ if (d.ok) { tx.textContent = d.source.toUpperCase()+' · '+d.market; pill.className='ok'; }
774
+ else { tx.textContent = 'No exchange'; pill.className='err'; }
 
 
 
 
775
  } catch { $('status-text').textContent='Offline'; $('status-pill').className='err'; }
776
  }
777
 
778
+ /* Scan buttons */
 
 
 
 
 
 
 
 
 
779
  $('scan-btn-nav').addEventListener('click', runScan);
780
+ $('hero-cta').addEventListener('click', () => { $('scan-section').scrollIntoView({behavior:'smooth'}); setTimeout(runScan, 300); });
781
+
782
+ function setLoading(on) {
783
+ const nb = $('scan-btn-nav'), hb = $('hero-cta');
784
+ nb.disabled = hb.disabled = on;
785
+ nb.innerHTML = on
786
+ ? `<div style="width:13px;height:13px;border:2px solid rgba(255,255,255,0.35);border-top-color:#fff;border-radius:50%;animation:spin 0.7s linear infinite;flex-shrink:0"></div>Scanning…`
787
+ : `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>Scan`;
788
+ hb.innerHTML = on
789
+ ? `<div style="width:16px;height:16px;border:2.5px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.7s linear infinite;flex-shrink:0"></div>Scanning…`
790
+ : `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>Scan Market`;
791
+ }
792
 
793
  async function runScan() {
794
+ const grid = $('grid'), meta = $('meta'), err = $('error-banner');
795
+ const loader = $('loader'), empty = $('empty');
796
+
797
+ setLoading(true);
 
 
 
 
 
798
  grid.innerHTML = '';
799
  err.style.display = 'none';
800
+ empty.style.display = 'none';
801
  loader.style.display = 'flex';
 
802
 
803
  const account = +$('account').value || 10000;
804
  const risk = +$('risk').value || 1;
 
806
 
807
  try {
808
  const data = await fetch(`/api/scan?account=${account}&risk_pct=${risk}&top=${top}`)
809
+ .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); });
810
 
811
  loader.style.display = 'none';
812
  const ts = new Date(data.fetched_at).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'});
813
  meta.textContent = `${data.cards.length} setups · ${data.source.toUpperCase()} · ${data.market} · ${ts}`;
814
+ const sp = $('stat-pairs'); if (sp) sp.textContent = data.cards.length+'+';
815
 
816
+ if (!data.cards.length) { empty.style.display = 'flex'; return; }
817
+ data.cards.forEach((c, i) => {
 
 
 
818
  const el = buildCard(c, account, risk);
819
+ el.style.animationDelay = (i * 55) + 'ms';
820
  grid.appendChild(el);
821
  });
822
  } catch(e) {
823
  loader.style.display = 'none';
824
+ empty.style.display = 'flex';
825
+ err.textContent = '⚠︎ ' + e.message;
826
  err.style.display = 'block';
827
+ } finally { setLoading(false); }
 
 
 
 
828
  }
829
 
 
830
  function buildCard(c, account, risk) {
831
  if (c.error) {
832
  const d = document.createElement('div');
 
835
  return d;
836
  }
837
 
838
+ const conf = +(c.confidence || 0);
839
+ const confPct = Math.min(100, conf * 10);
840
+ const confCls = conf >= 7 ? 'hi' : conf >= 5 ? 'md' : 'lo';
841
+ const confColor = conf >= 7 ? 'var(--green)' : conf >= 5 ? 'var(--amber)' : 'var(--red)';
842
+ const dir = (c.direction || 'long').toLowerCase();
843
+ const lv = c.levels || {}, sz = c.sizing || {};
844
+ const ev = c.evidence || {}, tf = c.timeframes || {};
845
+ const ri = c.rank_info || {};
846
 
847
+ const base = (c.symbol || '').replace(/USD$/, '');
848
  const chg = ri.change_24h_pct;
849
+ const chgHTML = chg != null ? `<span class="chg ${chg>=0?'pos':'neg'}">${pc(chg)}</span>` : '';
850
+ const volHTML = ri.quote_volume_usd ? `<span class="vol">${fu(ri.quote_volume_usd)}</span>` : '';
 
 
851
 
852
  const tfHTML = ['1h','15m','5m'].map(t => {
853
+ const s = (tf[t]?.structure || '').toLowerCase();
854
  const cls = s==='uptrend'?'up':s==='downtrend'?'down':s?'range':'na';
855
  return `<span class="tf-chip ${cls}">${t} · ${s||'n/a'}</span>`;
856
  }).join('');
857
 
858
  const evHTML = ['structure','volume','positioning','catalyst'].map(k => {
859
+ const e = ev[k] || {}, sc = +(e.score || 0);
860
  return `<div class="ev-row">
861
  <span class="ev-key">${k[0].toUpperCase()+k.slice(1)}</span>
862
  <div class="ev-track"><div class="ev-fill" style="width:${sc*10}%"></div></div>
 
864
  </div>`;
865
  }).join('');
866
 
867
+ let narr = (c.narrative || '');
868
+ if (narr.length > 200) narr = narr.slice(0, 197) + '…';
869
 
870
  const plan = [
871
  `${c.symbol} ${dir.toUpperCase()} | Confidence ${conf}/10`,
872
+ `Entry ${sf(c.close)}`,
873
+ `SL ${sf(lv.sl)}`,
874
+ `TP1 ${sf(lv.tp1)}`, `TP2 ${sf(lv.tp2)}`, `TP3 ${sf(lv.tp3)}`,
875
+ `R/R ${lv.rr1??'—'}×`,
876
+ `Risk ${fu(sz.risk_usd)} | Units ${sz.units??'—'} | Notional ${fu(sz.notional)}`,
877
+ `Time ${new Date().toLocaleString()}`
 
 
878
  ].join('\n');
879
 
880
+ const tvUrl = `https://www.tradingview.com/chart/?symbol=${encodeURIComponent(c.tv_symbol || c.symbol)}`;
881
 
882
  const el = document.createElement('div');
883
  el.className = 'card';
884
  el.innerHTML = `
 
 
 
885
  <div class="ch">
886
+ <div>
887
+ <div class="ch-symbol">${base}<span class="q">USD</span></div>
888
+ <div class="ch-sub">${chgHTML}${volHTML}</div>
889
  </div>
890
  <span class="dir-tag ${dir}">${dir.toUpperCase()}</span>
891
  </div>
892
 
893
+ <div class="conf-wrap">
894
  <div class="conf-top">
895
  <span class="conf-lbl">Confidence</span>
896
+ <span class="conf-score" style="color:${confColor}">${conf}<span class="conf-denom">/10</span></span>
897
  </div>
898
  <div class="conf-track"><div class="conf-fill ${confCls}" style="width:${confPct}%"></div></div>
899
  </div>
900
 
901
  <div class="tf-row">${tfHTML}</div>
 
902
  <div class="rule"></div>
903
 
904
  <div class="levels">
905
+ <div class="lv entry"><div class="lv-lbl">Entry</div><div class="lv-val">${sf(c.close)}</div></div>
906
+ <div class="lv sl"> <div class="lv-lbl">Stop Loss</div><div class="lv-val">${sf(lv.sl)}</div></div>
907
+ <div class="lv rr"> <div class="lv-lbl">R / R</div><div class="lv-val">${lv.rr1??'—'}×</div></div>
908
+ <div class="lv tp"> <div class="lv-lbl">TP 1</div><div class="lv-val">${sf(lv.tp1)}</div></div>
909
+ <div class="lv tp"> <div class="lv-lbl">TP 2</div><div class="lv-val">${sf(lv.tp2)}</div></div>
910
+ <div class="lv tp"> <div class="lv-lbl">TP 3</div><div class="lv-val">${sf(lv.tp3)}</div></div>
911
  </div>
912
 
913
  <div class="sizing">
914
+ <div class="sz"><div class="sz-lbl">Risk</div><div class="sz-val">${fu(sz.risk_usd)}</div></div>
915
+ <div class="sz"><div class="sz-lbl">Units</div><div class="sz-val">${sz.units??'—'}</div></div>
916
+ <div class="sz"><div class="sz-lbl">Notional</div><div class="sz-val">${fu(sz.notional)}</div></div>
917
  </div>
918
 
919
+ ${narr ? `<div class="narr">${narr}</div>` : ''}
 
920
  <div class="rule"></div>
921
 
922
  <div class="ev-title">Score Breakdown</div>
 
931
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
932
  Copy Plan
933
  </button>
934
+ <button class="ca ca-ref" data-sym="${c.symbol}" data-acc="${account}" data-risk="${risk}" title="Refresh">
935
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
936
  </button>
937
  </div>`;
938
 
 
939
  el.querySelector('.ca-copy').addEventListener('click', function() {
940
+ const text = this.dataset.plan.replace(/\\n/g, '\n');
941
  navigator.clipboard.writeText(text).then(() => {
942
  const orig = this.innerHTML;
943
  this.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg> Copied!`;
944
+ this.style.color = 'var(--green)'; this.style.borderColor = 'rgba(5,150,105,0.3)';
945
  setTimeout(() => { this.innerHTML = orig; this.style.color=''; this.style.borderColor=''; }, 2200);
946
  }).catch(() => prompt('Copy:', text));
947
  });
948
 
 
949
  el.querySelector('.ca-ref').addEventListener('click', async function() {
950
+ const {sym, acc, risk: r} = this.dataset, orig = this.innerHTML;
951
  this.disabled = true;
952
+ this.innerHTML = `<div style="width:13px;height:13px;border:2px solid rgba(0,0,0,0.1);border-top-color:var(--teal);border-radius:50%;animation:spin .7s linear infinite"></div>`;
953
  try {
954
+ const data = await fetch(`/api/card/${sym}?account=${acc}&risk_pct=${r}`).then(x=>x.json());
955
+ el.replaceWith(buildCard(data, +acc, +r));
956
  } catch { this.disabled=false; this.innerHTML=orig; }
957
  });
958
 
959
  return el;
960
  }
961
 
 
962
  checkStatus();
963
  setInterval(checkStatus, 60000);
964
  </script>