Jimin Huang commited on
Commit
1462337
Β·
1 Parent(s): e9ff4a6

Change settings

Browse files
Files changed (1) hide show
  1. src/views/LiveView.vue +111 -104
src/views/LiveView.vue CHANGED
@@ -28,47 +28,58 @@
28
 
29
  <!-- Cards: Buy & Hold + top 4 agents (computed with perf helpers) -->
30
  <section class="panel panel--cards" v-if="cards.length">
31
- <div class="cards5">
32
  <div
33
  v-for="c in cards"
34
  :key="c.key"
35
- class="card"
36
- :class="{ 'card--bh': c.kind==='bh', 'card--winner': c.isWinner }"
37
  >
38
- <!-- HEADER (responsive) -->
39
- <div class="card__header">
40
- <div class="card__logo">
 
 
 
41
  <img v-if="c.logo" :src="c.logo" alt="" />
42
- <div v-else class="card__logo-fallback"></div>
43
- <span v-if="c.isWinner" class="card__badge" aria-label="Top performer">πŸ‘‘</span>
44
  </div>
45
 
46
- <div class="card__title-wrap">
47
- <div class="card__title" :title="c.title">{{ c.title }}</div>
48
- <div class="card__subtitle" :title="c.subtitle">{{ c.subtitle }}</div>
49
  </div>
50
 
51
- <div class="card__balance">{{ fmtUSD(c.balance) }}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  </div>
53
 
54
- <!-- META -->
55
- <div class="card__meta">
56
- <div class="meta__left">
57
- <div v-if="c.kind==='bh'" class="pill pill--neutral">Buy&nbsp;&amp;&nbsp;Hold</div>
58
- <div v-else class="pill pill--outline">Strategy</div>
59
- </div>
60
- <div class="meta__right" v-if="c.kind==='agent' && c.gapUsd != null">
61
- <div class="pill" :class="{ neg: c.gapUsd < 0 && !c.isWinner, pos: c.gapUsd >= 0 || c.isWinner }">
62
- <span v-if="mode==='usd'">{{ signedMoney(c.gapUsd) }}</span>
63
- <span v-else>{{ signedPct(c.gapPct) }}</span>
64
- </div>
65
  </div>
66
- <div class="meta__right" v-else></div>
67
  </div>
68
 
69
- <!-- FOOTER -->
70
- <div class="card__footer">
71
- <div class="card__foot">EOD {{ c.date ? new Date(c.date).toLocaleDateString() : '–' }}</div>
 
72
  </div>
73
  </div>
74
  </div>
@@ -305,97 +316,93 @@ watch(
305
  /* empty */
306
  .empty { padding: 14px; border: 1px dashed #D6DAE1; border-radius: 12px; color: #6B7280; font-size: .92rem; }
307
 
308
- /* grid of 5 cards */
309
- .cards5 { display: grid; gap: 12px; grid-template-columns: repeat(5, minmax(0, 1fr)); }
310
- @media (max-width: 1280px) { .cards5 { grid-template-columns: repeat(3, minmax(0,1fr)); } }
311
- @media (max-width: 960px) { .cards5 { grid-template-columns: repeat(2, minmax(0,1fr)); } }
312
- @media (max-width: 640px) { .cards5 { grid-template-columns: 1fr; } }
313
 
314
- /* card shell */
315
- .card {
 
316
  display: grid;
317
- grid-template-rows: auto auto auto; /* header, meta, footer */
318
- gap: 10px;
319
- padding: 12px 14px;
320
  border: 1px solid #EEF1F6;
321
  border-radius: 14px;
322
  background: #fff;
323
  box-shadow: 0 1px 2px rgba(0,0,0,.04);
324
- min-width: 0; /* allow truncation inside */
325
  }
326
- .card--bh { outline: 2px dashed rgba(15,23,42,.08); }
327
- .card--winner { border-color: #16a34a; box-shadow: 0 0 0 3px rgba(22,163,74,.12); }
328
-
329
- /* HEADER β€” responsive grid areas
330
- wide: "logo title balance"
331
- tight: "logo title balance"
332
- "logo . balance"
333
- */
334
- .card__header {
335
- display: grid;
336
- grid-template-columns: 48px minmax(0,1fr) auto;
337
- grid-template-areas: "logo title balance";
338
- align-items: center;
339
  column-gap: 12px;
340
- row-gap: 6px;
341
- }
342
- @media (max-width: 1200px) {
343
- .card__header {
344
- grid-template-areas:
345
- "logo title balance"
346
- "logo . balance"; /* lets balance move down, title gets more space */
347
- }
348
  }
 
 
349
 
350
- /* logo */
351
- .card__logo { grid-area: logo; width: 44px; height: 44px; border-radius: 999px; background: #F3F4F6;
352
- display: grid; place-items: center; overflow: hidden; position: relative; }
353
- .card__logo img { width: 100%; height: 100%; object-fit: contain; }
354
- .card__logo-fallback { width: 60%; height: 60%; border-radius: 999px; background: #E5E7EB; }
355
- .card__badge { position: absolute; right: -6px; top: -6px; font-size: 16px; filter: drop-shadow(0 1px 1px rgba(0,0,0,.15)); }
356
 
357
- /* title block */
358
- .card__title-wrap { grid-area: title; min-width: 0; display: grid; gap: 2px; }
359
- .card__title {
360
  font-weight: 800; color: #0F172A; line-height: 1.15;
361
- display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; /* 2-line clamp */
362
- overflow: hidden;
363
- font-size: clamp(16px, 1.6vw, 19px);
364
  }
365
- .card__subtitle {
366
- font-size: 12px; color: #5B6476; opacity: .85; line-height: 1.1;
367
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  }
369
-
370
- /* balance (never wraps; can move to its own row on tight cards) */
371
- .card__balance {
372
- grid-area: balance;
373
- white-space: nowrap;
374
- font-weight: 900; color: #0F172A;
375
- font-size: clamp(18px, 1.9vw, 22px);
376
- align-self: start;
377
  }
378
-
379
- /* meta row */
380
- .card__meta {
381
- display: grid;
382
- grid-template-columns: 1fr auto;
383
- align-items: center;
384
- gap: 10px;
385
- }
386
- .meta__left, .meta__right { min-width: 0; display: flex; align-items: center; gap: 8px; }
387
-
388
- /* footer */
389
- .card__footer { margin-top: -2px; }
390
- .card__foot { font-size: 12px; color: #5B6476; opacity: .85; }
391
-
392
- /* pills */
393
- .pill {
394
- padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800;
395
- line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A;
396
  }
397
- .pill.pos { background: #DCFCE7; color: #166534; }
398
- .pill.neg { background: #FEE2E2; color: #B91C1C; }
399
- .pill--neutral { background: #EEF2F7; color: #0F172A; }
400
- .pill--outline { background: transparent; color: #475569; border: 1px solid #E5E7EB; padding: 3px 8px; }
401
  </style>
 
28
 
29
  <!-- Cards: Buy & Hold + top 4 agents (computed with perf helpers) -->
30
  <section class="panel panel--cards" v-if="cards.length">
31
+ <div class="cards-grid">
32
  <div
33
  v-for="c in cards"
34
  :key="c.key"
35
+ class="card2"
36
+ :class="{ 'is-bh': c.kind==='bh', 'is-winner': c.isWinner }"
37
  >
38
+ <!-- WINNER RIBBON -->
39
+ <div v-if="c.isWinner" class="card2__ribbon" aria-label="Top performer">πŸ‘‘</div>
40
+
41
+ <!-- TOP ROW: avatar + titles + primary metric -->
42
+ <div class="card2__row card2__row--top">
43
+ <div class="avatar">
44
  <img v-if="c.logo" :src="c.logo" alt="" />
45
+ <div v-else class="avatar__fallback" aria-hidden="true"></div>
 
46
  </div>
47
 
48
+ <div class="titles">
49
+ <div class="title" :title="c.title">{{ c.title }}</div>
50
+ <div class="subtitle" :title="c.subtitle">{{ c.subtitle }}</div>
51
  </div>
52
 
53
+ <div class="metrics">
54
+ <div class="primary">
55
+ <div class="primary__label">Balance</div>
56
+ <div class="primary__value">{{ fmtUSD(c.balance) }}</div>
57
+ </div>
58
+ <div class="delta" v-if="c.kind==='agent' && c.gapUsd != null">
59
+ <span
60
+ class="delta__value"
61
+ :class="{ neg: c.gapUsd < 0 && !c.isWinner, pos: c.gapUsd >= 0 || c.isWinner }"
62
+ >
63
+ <template v-if="mode==='usd'">{{ signedMoney(c.gapUsd) }}</template>
64
+ <template v-else>{{ signedPct(c.gapPct) }}</template>
65
+ </span>
66
+ <span class="delta__label">vs B&H</span>
67
+ </div>
68
+ </div>
69
  </div>
70
 
71
+ <!-- META ROW: type chip -->
72
+ <div class="card2__row card2__row--meta">
73
+ <div class="chips">
74
+ <span v-if="c.kind==='bh'" class="chip chip--neutral">Buy & Hold</span>
75
+ <span v-else class="chip chip--outline">Strategy</span>
 
 
 
 
 
 
76
  </div>
 
77
  </div>
78
 
79
+ <!-- FOOTER: date -->
80
+ <div class="card2__row card2__row--foot">
81
+ <div class="foot__left"></div>
82
+ <div class="foot__right">EOD {{ c.date ? new Date(c.date).toLocaleDateString() : '–' }}</div>
83
  </div>
84
  </div>
85
  </div>
 
316
  /* empty */
317
  .empty { padding: 14px; border: 1px dashed #D6DAE1; border-radius: 12px; color: #6B7280; font-size: .92rem; }
318
 
319
+ /* NEW GRID: fluid, auto-fit tiles */
320
+ .cards-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
 
 
 
321
 
322
+ /* CARD v2 */
323
+ .card2 {
324
+ position: relative;
325
  display: grid;
326
+ grid-template-rows: auto auto auto; /* rows: top, meta, foot */
327
+ gap: 12px;
328
+ padding: 14px;
329
  border: 1px solid #EEF1F6;
330
  border-radius: 14px;
331
  background: #fff;
332
  box-shadow: 0 1px 2px rgba(0,0,0,.04);
333
+ transition: box-shadow .15s ease, transform .15s ease, border-color .15s ease;
334
  }
335
+ .card2:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(0,0,0,.08); }
336
+ .card2.is-bh { outline: 2px dashed rgba(15,23,42,.08); }
337
+ .card2.is-winner { border-color: #16a34a; box-shadow: 0 0 0 3px rgba(22,163,74,.12); }
338
+
339
+ /* ribbon */
340
+ .card2__ribbon { position: absolute; top: 10px; right: 10px; font-size: 18px; filter: drop-shadow(0 1px 1px rgba(0,0,0,.15)); }
341
+
342
+ /* row layout */
343
+ .card2__row { display: grid; align-items: center; }
344
+ .card2__row--top {
345
+ grid-template-columns: 48px minmax(0,1fr) auto; /* avatar | titles | metrics */
 
 
346
  column-gap: 12px;
 
 
 
 
 
 
 
 
347
  }
348
+ .card2__row--meta { grid-template-columns: 1fr; }
349
+ .card2__row--foot { grid-template-columns: 1fr auto; font-size: 12px; color: #5B6476; opacity: .9; margin-top: -4px; }
350
 
351
+ /* avatar */
352
+ .avatar { width: 44px; height: 44px; border-radius: 999px; background: #F3F4F6; display: grid; place-items: center; overflow: hidden; }
353
+ .avatar img { width: 100%; height: 100%; object-fit: contain; }
354
+ .avatar__fallback { width: 60%; height: 60%; border-radius: 999px; background: #E5E7EB; }
 
 
355
 
356
+ /* titles */
357
+ .titles { min-width: 0; display: grid; gap: 2px; }
358
+ .title {
359
  font-weight: 800; color: #0F172A; line-height: 1.15;
360
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
361
+ overflow: hidden; font-size: clamp(16px, 1.4vw, 18px);
 
362
  }
363
+ .subtitle { font-size: 12px; color: #5B6476; opacity: .9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
364
+
365
+ /* primary metric */
366
+ .primary { text-align: right; }
367
+ .primary__label { font-size: 11px; letter-spacing: .02em; color: #6B7280; }
368
+ .primary__value { font-weight: 900; color: #0F172A; font-size: clamp(18px, 1.8vw, 22px); white-space: nowrap; }
369
+
370
+ /* metrics wrapper (balance + delta in same row) */
371
+ .metrics { display: flex; align-items: baseline; gap: 12px; justify-content: flex-end; }
372
+
373
+ /* chips & delta */
374
+ .chips { display: flex; align-items: center; gap: 8px; min-width: 0; }
375
+ .chip { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A; }
376
+ .chip--neutral { background: #EEF2F7; color: #0F172A; }
377
+ .chip--outline { background: transparent; color: #475569; border: 1px solid #E5E7EB; padding: 3px 8px; }
378
+
379
+ .delta { display: flex; align-items: baseline; gap: 8px; }
380
+ .delta__value { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A; }
381
+ .delta__value.pos { background: #DCFCE7; color: #166534; }
382
+ .delta__value.neg { background: #FEE2E2; color: #B91C1C; }
383
+ .delta__label { font-size: 11px; color: #64748B; }
384
+ .chips { display: flex; align-items: center; gap: 8px; min-width: 0; }
385
+ .chip { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A; }
386
+ .chip--neutral { background: #EEF2F7; color: #0F172A; }
387
+ .chip--outline { background: transparent; color: #475569; border: 1px solid #E5E7EB; padding: 3px 8px; }
388
+
389
+ .delta { display: flex; align-items: baseline; gap: 8px; }
390
+ .delta__value { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A; }
391
+ .delta__value.pos { background: #DCFCE7; color: #166534; }
392
+ .delta__value.neg { background: #FEE2E2; color: #B91C1C; }
393
+ .delta__label { font-size: 11px; color: #64748B; }
394
+
395
+ /* foot */
396
+ .foot__right { white-space: nowrap; }
397
+
398
+ /* responsive nits */
399
+ @media (max-width: 1200px) {
400
+ .card2__row--top { grid-template-columns: 44px minmax(0,1fr) auto; }
401
  }
402
+ @media (max-width: 960px) {
403
+ .primary__value { font-size: 20px; }
 
 
 
 
 
 
404
  }
405
+ @media (max-width: 640px) {
406
+ .cards-grid { grid-template-columns: 1fr; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  }
 
 
 
 
408
  </style>