Spaces:
Running
Running
Jimin Huang
commited on
Commit
Β·
a1c02fd
1
Parent(s):
50c8fe7
Change settings
Browse files- src/views/LiveView.vue +65 -54
src/views/LiveView.vue
CHANGED
|
@@ -35,53 +35,47 @@
|
|
| 35 |
class="card2"
|
| 36 |
:class="{ 'is-bh': c.kind==='bh', 'is-winner': c.isWinner }"
|
| 37 |
>
|
| 38 |
-
<!--
|
| 39 |
<div v-if="c.isWinner" class="card2__ribbon" aria-label="Top performer">π</div>
|
| 40 |
|
| 41 |
-
<!--
|
| 42 |
-
<div class="
|
| 43 |
-
<div class="
|
| 44 |
-
<
|
| 45 |
-
|
| 46 |
-
<div v-else class="avatar__fallback" aria-hidden="true"></div>
|
| 47 |
-
</div>
|
| 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 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
</div>
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
<template v-if="mode==='usd'">{{ signedMoney(c.gapUsd) }}</template>
|
| 65 |
<template v-else>{{ signedPct(c.gapPct) }}</template>
|
| 66 |
-
</
|
| 67 |
-
<
|
| 68 |
</div>
|
| 69 |
</div>
|
| 70 |
</div>
|
| 71 |
|
| 72 |
-
<!--
|
| 73 |
-
<div class="
|
| 74 |
-
<div class="chips">
|
| 75 |
-
<span v-if="c.kind==='bh'" class="chip chip--neutral">Buy & Hold</span>
|
| 76 |
-
<span v-else class="chip chip--outline">Strategy</span>
|
| 77 |
-
</div>
|
| 78 |
-
</div>
|
| 79 |
-
|
| 80 |
-
<!-- FOOTER: date -->
|
| 81 |
-
<div class="card2__row card2__row--foot">
|
| 82 |
-
<div class="foot__left"></div>
|
| 83 |
-
<div class="foot__right">EOD {{ c.date ? new Date(c.date).toLocaleDateString() : 'β' }}</div>
|
| 84 |
-
</div>
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
</section>
|
|
@@ -89,6 +83,8 @@
|
|
| 89 |
<section v-else class="panel panel--cards">
|
| 90 |
<div class="empty">No card data yet for <strong>{{ asset }}</strong>.</div>
|
| 91 |
</section>
|
|
|
|
|
|
|
| 92 |
</div>
|
| 93 |
</template>
|
| 94 |
|
|
@@ -259,11 +255,18 @@ watch(
|
|
| 259 |
balance: first.perf.bhLast,
|
| 260 |
date: first.perf.date,
|
| 261 |
logo: ASSET_ICONS[assetCode] || null,
|
|
|
|
|
|
|
|
|
|
| 262 |
isWinner: false
|
| 263 |
}
|
| 264 |
|
| 265 |
// Agent cards (gap vs BH)
|
| 266 |
const agentCards = perfs.map(({ sel, perf }) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
const gapUsd = perf.stratLast - perf.bhLast
|
| 268 |
const gapPct = perf.bhLast > 0 ? (perf.stratLast / perf.bhLast - 1) : 0
|
| 269 |
return {
|
|
@@ -318,17 +321,18 @@ watch(
|
|
| 318 |
.empty { padding: 14px; border: 1px dashed #D6DAE1; border-radius: 12px; color: #6B7280; font-size: .92rem; }
|
| 319 |
|
| 320 |
/* NEW GRID: fluid, auto-fit tiles */
|
| 321 |
-
.cards-grid { display: grid; gap:
|
| 322 |
|
| 323 |
/* CARD v2 */
|
| 324 |
.card2 {
|
| 325 |
position: relative;
|
| 326 |
display: grid;
|
| 327 |
grid-template-rows: auto auto auto; /* rows: top, meta, foot */
|
| 328 |
-
gap:
|
| 329 |
-
padding:
|
|
|
|
| 330 |
border: 1px solid #EEF1F6;
|
| 331 |
-
border-radius:
|
| 332 |
background: #fff;
|
| 333 |
box-shadow: 0 1px 2px rgba(0,0,0,.04);
|
| 334 |
transition: box-shadow .15s ease, transform .15s ease, border-color .15s ease;
|
|
@@ -343,33 +347,30 @@ watch(
|
|
| 343 |
/* row layout */
|
| 344 |
.card2__row { display: grid; align-items: center; }
|
| 345 |
.card2__row--top {
|
| 346 |
-
grid-template-columns:
|
| 347 |
column-gap: 12px;
|
| 348 |
}
|
| 349 |
.card2__row--meta { grid-template-columns: 1fr; }
|
| 350 |
.card2__row--foot { grid-template-columns: 1fr auto; font-size: 12px; color: #5B6476; opacity: .9; margin-top: -4px; }
|
| 351 |
|
| 352 |
/* avatar */
|
| 353 |
-
.avatar { width:
|
| 354 |
.avatar img { width: 100%; height: 100%; object-fit: contain; }
|
| 355 |
.avatar__fallback { width: 60%; height: 60%; border-radius: 999px; background: #E5E7EB; }
|
| 356 |
|
| 357 |
/* titles */
|
| 358 |
-
.
|
| 359 |
-
.
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
overflow: hidden; font-size: clamp(16px, 1.4vw, 18px);
|
| 363 |
-
}
|
| 364 |
-
.subtitle { font-size: 12px; color: #5B6476; opacity: .9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 365 |
|
| 366 |
/* primary metric */
|
| 367 |
.primary { text-align: right; }
|
| 368 |
-
.primary__label { font-size:
|
| 369 |
-
.primary__value { font-weight: 900; color: #0F172A; font-size: clamp(
|
| 370 |
|
| 371 |
-
/* metrics wrapper
|
| 372 |
-
.metrics { display: flex; align-items:
|
| 373 |
|
| 374 |
/* chips & delta */
|
| 375 |
.chips { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
@@ -387,6 +388,16 @@ watch(
|
|
| 387 |
.chip--neutral { background: #EEF2F7; color: #0F172A; }
|
| 388 |
.chip--outline { background: transparent; color: #475569; border: 1px solid #E5E7EB; padding: 3px 8px; }
|
| 389 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
.delta { display: flex; align-items: baseline; gap: 8px; }
|
| 391 |
.delta__value { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A; }
|
| 392 |
.delta__value.pos { background: #DCFCE7; color: #166534; }
|
|
|
|
| 35 |
class="card2"
|
| 36 |
:class="{ 'is-bh': c.kind==='bh', 'is-winner': c.isWinner }"
|
| 37 |
>
|
| 38 |
+
<!-- Crown -->
|
| 39 |
<div v-if="c.isWinner" class="card2__ribbon" aria-label="Top performer">π</div>
|
| 40 |
|
| 41 |
+
<!-- Header: LOGO + title -->
|
| 42 |
+
<div class="card2__header">
|
| 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 |
+
<div class="titleblock">
|
| 48 |
+
<div class="title" :title="c.kind==='bh' ? 'Buy & Hold' : c.title">{{ c.kind==='bh' ? 'Buy & Hold' : c.title }}</div>
|
| 49 |
+
<div class="subtitle" :title="c.subtitle">{{ c.subtitle }}</div>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
|
| 53 |
+
<!-- KPIs: Net value, Profit, Delta Profit -->
|
| 54 |
+
<div class="card2__kpis">
|
| 55 |
+
<div class="kpi kpi--net">
|
| 56 |
+
<div class="kpi__label">Net value</div>
|
| 57 |
+
<div class="kpi__value">{{ fmtUSD(c.balance) }}</div>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<div class="kpi kpi--profit">
|
| 61 |
+
<div class="kpi__label">Profit</div>
|
| 62 |
+
<div class="pill" :class="{ pos: c.profitUsd >= 0, neg: c.profitUsd < 0 }">{{ signedMoney(c.profitUsd) }}</div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<div class="kpi kpi--delta">
|
| 66 |
+
<div class="kpi__label">Delta profit</div>
|
| 67 |
+
<div class="pill" :class="{ pos: (c.gapUsd ?? 0) >= 0, neg: (c.gapUsd ?? 0) < 0 }">
|
| 68 |
+
<template v-if="c.kind==='agent'">
|
| 69 |
<template v-if="mode==='usd'">{{ signedMoney(c.gapUsd) }}</template>
|
| 70 |
<template v-else>{{ signedPct(c.gapPct) }}</template>
|
| 71 |
+
</template>
|
| 72 |
+
<template v-else>β</template>
|
| 73 |
</div>
|
| 74 |
</div>
|
| 75 |
</div>
|
| 76 |
|
| 77 |
+
<!-- EOD bottom-right -->
|
| 78 |
+
<div class="card2__eod">EOD {{ c.date ? new Date(c.date).toLocaleDateString() : 'β' }}</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
</div>
|
| 80 |
</div>
|
| 81 |
</section>
|
|
|
|
| 83 |
<section v-else class="panel panel--cards">
|
| 84 |
<div class="empty">No card data yet for <strong>{{ asset }}</strong>.</div>
|
| 85 |
</section>
|
| 86 |
+
<div class="empty">No card data yet for <strong>{{ asset }}</strong>.</div>
|
| 87 |
+
</section>
|
| 88 |
</div>
|
| 89 |
</template>
|
| 90 |
|
|
|
|
| 255 |
balance: first.perf.bhLast,
|
| 256 |
date: first.perf.date,
|
| 257 |
logo: ASSET_ICONS[assetCode] || null,
|
| 258 |
+
profitUsd: (first.perf.bhLast ?? 0) - 100000,
|
| 259 |
+
gapUsd: 0,
|
| 260 |
+
gapPct: 0,
|
| 261 |
isWinner: false
|
| 262 |
}
|
| 263 |
|
| 264 |
// Agent cards (gap vs BH)
|
| 265 |
const agentCards = perfs.map(({ sel, perf }) => {
|
| 266 |
+
const gapUsd = perf.stratLast - perf.bhLast
|
| 267 |
+
const gapPct = perf.bhLast > 0 ? (perf.stratLast / perf.bhLast - 1) : 0
|
| 268 |
+
const profitUsd = (perf.stratLast ?? 0) - 100000
|
| 269 |
+
return { => {
|
| 270 |
const gapUsd = perf.stratLast - perf.bhLast
|
| 271 |
const gapPct = perf.bhLast > 0 ? (perf.stratLast / perf.bhLast - 1) : 0
|
| 272 |
return {
|
|
|
|
| 321 |
.empty { padding: 14px; border: 1px dashed #D6DAE1; border-radius: 12px; color: #6B7280; font-size: .92rem; }
|
| 322 |
|
| 323 |
/* NEW GRID: fluid, auto-fit tiles */
|
| 324 |
+
.cards-grid { display: grid; gap: 16px; grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
| 325 |
|
| 326 |
/* CARD v2 */
|
| 327 |
.card2 {
|
| 328 |
position: relative;
|
| 329 |
display: grid;
|
| 330 |
grid-template-rows: auto auto auto; /* rows: top, meta, foot */
|
| 331 |
+
gap: 14px;
|
| 332 |
+
padding: 16px;
|
| 333 |
+
min-height: 200px;
|
| 334 |
border: 1px solid #EEF1F6;
|
| 335 |
+
border-radius: 16px;
|
| 336 |
background: #fff;
|
| 337 |
box-shadow: 0 1px 2px rgba(0,0,0,.04);
|
| 338 |
transition: box-shadow .15s ease, transform .15s ease, border-color .15s ease;
|
|
|
|
| 347 |
/* row layout */
|
| 348 |
.card2__row { display: grid; align-items: center; }
|
| 349 |
.card2__row--top {
|
| 350 |
+
grid-template-columns: 1fr auto; /* info | metrics */
|
| 351 |
column-gap: 12px;
|
| 352 |
}
|
| 353 |
.card2__row--meta { grid-template-columns: 1fr; }
|
| 354 |
.card2__row--foot { grid-template-columns: 1fr auto; font-size: 12px; color: #5B6476; opacity: .9; margin-top: -4px; }
|
| 355 |
|
| 356 |
/* avatar */
|
| 357 |
+
.avatar { width: 56px; height: 56px; border-radius: 999px; background: #F3F4F6; display: grid; place-items: center; overflow: hidden; }
|
| 358 |
.avatar img { width: 100%; height: 100%; object-fit: contain; }
|
| 359 |
.avatar__fallback { width: 60%; height: 60%; border-radius: 999px; background: #E5E7EB; }
|
| 360 |
|
| 361 |
/* titles */
|
| 362 |
+
.info { display: grid; grid-template-columns: 56px minmax(0,1fr); align-items: center; gap: 12px; min-width: 0; }
|
| 363 |
+
.titles { min-width: 0; display: grid; gap: 4px; }
|
| 364 |
+
.title { font-weight: 900; color: #0F172A; line-height: 1.1; font-size: clamp(18px, 1.6vw, 22px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 365 |
+
.subtitle { font-size: 13px; color: #64748B; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
|
|
|
|
|
| 366 |
|
| 367 |
/* primary metric */
|
| 368 |
.primary { text-align: right; }
|
| 369 |
+
.primary__label { font-size: 12px; letter-spacing: .02em; color: #6B7280; }
|
| 370 |
+
.primary__value { font-weight: 900; color: #0F172A; font-size: clamp(22px, 2.2vw, 28px); white-space: nowrap; }
|
| 371 |
|
| 372 |
+
/* metrics wrapper */
|
| 373 |
+
.metrics { display: flex; align-items: center; gap: 12px; justify-content: flex-end; }
|
| 374 |
|
| 375 |
/* chips & delta */
|
| 376 |
.chips { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
|
|
| 388 |
.chip--neutral { background: #EEF2F7; color: #0F172A; }
|
| 389 |
.chip--outline { background: transparent; color: #475569; border: 1px solid #E5E7EB; padding: 3px 8px; }
|
| 390 |
|
| 391 |
+
.delta { display: flex; align-items: baseline; gap: 8px; }
|
| 392 |
+
.delta__value { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A; }
|
| 393 |
+
.delta__value.pos { background: #DCFCE7; color: #166534; }
|
| 394 |
+
.delta__value.neg { background: #FEE2E2; color: #B91C1C; }
|
| 395 |
+
.delta__label { font-size: 11px; color: #64748B; }
|
| 396 |
+
.chips { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
| 397 |
+
.chip { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A; }
|
| 398 |
+
.chip--neutral { background: #EEF2F7; color: #0F172A; }
|
| 399 |
+
.chip--outline { background: transparent; color: #475569; border: 1px solid #E5E7EB; padding: 3px 8px; }
|
| 400 |
+
|
| 401 |
.delta { display: flex; align-items: baseline; gap: 8px; }
|
| 402 |
.delta__value { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap; background: #EEF2F7; color: #0F172A; }
|
| 403 |
.delta__value.pos { background: #DCFCE7; color: #166534; }
|