Files
workbuddy_skills/open-design/design-templates/live-artifact/examples/crypto-dashboard.html
T
admin 5afeb6b78a ④段整套同步(六节 + P/S/F/J + 模板)+ open-design 移到技能根
一、④段整套同步(用户选候选 A)
- 新增 `references/stage-proto-doc/assets/产品文档模板.html`(16 KB/247 行,从备份拷、转 LF)。
- ④段 SKILL.md 换成备份 15:28 那版,并按模板口径改齐:
  · 「产品文档框架」从「四块+一附录」改成**六节** —— 页面结构/功能清单/使用场景/操作流程/
    跨页与界面状态/待确认与偏离;与模板同构,⛔ 不从空白页重写结构。
  · 编号从 R/S/F 改成 **P 页面/S 场景/F 功能/J 二级功能**,各自唯一、按展示顺序通排。
  · 保留旧版有价值的要求:每项功能补齐 做什么/入口在哪/输入边界/异常与恢复;重点功能三处标红;
    横向流程图每步写清页面/功能/操作/完成判据。
  · 完成标准「七节」→「六节」,自查清单同步换掉「需求」那批条目,补模板与配图两条。
  · 路径 `designs/<项目>/` → `docs/pm/<项目>/ui/`(2 处)。
  · 补回本机那条「每份产出文档开头都要有一张图」的引用(指向主 skill,段内不复述)。
- 主 skill 同步:四段结构表④行、④段沿革、画图小节(④段画页面结构图 + 用户路径)。

二、open-design 移到技能根(用户选候选 A)
- `git mv product-planning/references/stage-delivery/assets/open-design → open-design/`(4178 文件)。
- `.gitignore` 白名单加 `!/open-design/` —— 否则移出去就不再纳入版本控制。
- 引用改 11 处:路径形态 `assets/open-design/...` → `<技能根>/open-design/...`;
  「随段携带的自带件」改称「同根技能」;「路径怎么找」那条改成自带件(段内相对路径)与
  同根技能(`<技能根>/<技能名>/`)两类分开写。
- open-design 自己的 ATTRIBUTION.md 两处同步(它现在与 oil-ui-pro 同层)。
- 副作用(有益):命名闸门读数由 2063 降到 225 —— 此前它把 open-design 里 919 个第三方 md
  当成规则一起扫了;现在只扫本包 23 个 md。已确认④段的 H1 与 4a/4b/4c 子步仍在检查范围内。

三、验收
- check_naming.py --ws E:/ProgramData/AIProject/contentm_agent = 通过 225 / 失败 0 / 提示 27。
- 活文件里 `assets/open-design` 残留 0;工作区 CRLF 文件 0。
2026-10-15 21:17:28 +08:00

2317 lines
119 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Quiver · Crypto Live Portfolio</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600;700&family=Instrument+Serif&display=swap" rel="stylesheet">
<style>
:root {
/* DeFi terminal — deeper, warmer black with crypto-native accents */
--bg: #060309; /* almost-black with magenta undertone */
--bg-grid: #0a050f;
--surface: #110815; /* card warm-dark */
--surface-2: #170a1c;
--surface-3: #1f0d28;
--surface-glow: #261034;
--line: #28163a; /* purple-tinted lines */
--line-strong: #3b1f55;
--hairline: rgba(255,0,170,0.06);
/* Ink */
--ink: #f4ecff; /* slightly warmer/violet white */
--ink-soft: #cdb8e3;
--muted: #8a7099;
--muted-2: #5b3f6e;
--dim: #2c1c3a;
/* Semantic */
--up: #00ff9d; /* matrix-neon green — terminal feel */
--up-soft: rgba(0,255,157,0.14);
--up-line: rgba(0,255,157,0.55);
--down: #ff2e6b; /* hot pink-red */
--down-soft: rgba(255,46,107,0.14);
--down-line: rgba(255,46,107,0.55);
/* Crypto-native accents */
--accent: #f7931a; /* Bitcoin orange — data highlight */
--accent-soft: rgba(247,147,26,0.14);
--magenta: #ff00aa; /* DeFi magenta — primary brand */
--magenta-soft: rgba(255,0,170,0.14);
--cyan: #00e0ff; /* electric blue — chart primary */
--cyan-soft: rgba(0,224,255,0.14);
--violet: #c084fc; /* AI / ETH band */
--violet-soft: rgba(192,132,252,0.16);
--matrix: #00ff9d; /* matrix green */
--solana: #14f195; /* SOL teal */
/* Type — heavier monospace presence for terminal/explorer feel */
--display: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--body: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--mono: 'JetBrains Mono', 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
--serif: 'Instrument Serif', 'Iowan Old Style', Georgia, serif;
--r-card: 6px; /* sharper corners — engineering / terminal */
--r-pill: 999px;
--r-chip: 4px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background:
/* Hex grid SVG overlay — DeFi/web3 aesthetic */
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='56' height='48' viewBox='0 0 56 48'><path d='M28 0 L52 14 L52 38 L28 52 L4 38 L4 14 Z' fill='none' stroke='%23ff00aa' stroke-opacity='0.04' stroke-width='0.7'/></svg>"),
radial-gradient(1200px 600px at 80% -10%, rgba(255,0,170,0.10), transparent 60%),
radial-gradient(900px 500px at -10% 30%, rgba(247,147,26,0.07), transparent 60%),
radial-gradient(1200px 800px at 50% 110%, rgba(0,255,157,0.05), transparent 60%),
radial-gradient(800px 400px at 100% 60%, rgba(0,224,255,0.06), transparent 60%),
var(--bg);
color: var(--ink);
font-family: var(--body);
font-size: 13.5px;
line-height: 1.5;
letter-spacing: -0.005em;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
min-height: 100vh;
}
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
/* ─────── On-chain ribbon (crypto-specific) ─────── */
.onchain-ribbon {
border-bottom: 1px solid var(--hairline);
background:
linear-gradient(180deg, rgba(255,0,170,0.04), rgba(247,147,26,0.02)),
var(--bg-grid);
padding: 8px 28px;
display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
font-family: var(--mono); font-size: 10.5px;
color: var(--muted);
text-transform: uppercase; letter-spacing: 0.1em;
}
.onchain-ribbon .label { color: var(--muted-2); }
.onchain-ribbon .v { color: var(--ink-soft); font-weight: 500; }
.onchain-ribbon .item { display: inline-flex; gap: 6px; align-items: center; }
.onchain-ribbon .item.live::before {
content: ""; width: 6px; height: 6px; border-radius: 50%;
background: var(--matrix);
box-shadow: 0 0 8px var(--matrix);
animation: matrixBlink 1.4s ease-in-out infinite;
}
@keyframes matrixBlink {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.onchain-ribbon .block-h {
font-family: var(--mono); font-weight: 700; color: var(--matrix);
text-shadow: 0 0 12px rgba(0,255,157,0.4);
letter-spacing: 0.04em;
}
.onchain-ribbon .gas {
color: var(--accent);
font-weight: 600;
}
.onchain-ribbon .item .v.cyan { color: var(--cyan); }
.onchain-ribbon .item .v.magenta { color: var(--magenta); text-shadow: 0 0 8px rgba(255,0,170,0.4); }
/* ─────── Top ribbon: ticker tape ─────── */
.ticker-tape {
border-bottom: 1px solid var(--hairline);
background: linear-gradient(180deg, #0a0d14, #07090d);
overflow: hidden;
position: relative;
height: 32px;
}
.ticker-tape::before, .ticker-tape::after {
content: ""; position: absolute; top: 0; bottom: 0; width: 60px; z-index: 2; pointer-events: none;
}
.ticker-tape::before { left: 0; background: linear-gradient(90deg, var(--bg), transparent); }
.ticker-tape::after { right: 0; background: linear-gradient(270deg, var(--bg), transparent); }
.tape {
display: inline-flex; align-items: center; height: 32px; gap: 28px; padding-right: 28px;
animation: tape 60s linear infinite;
white-space: nowrap;
font-family: var(--mono);
font-size: 11.5px;
}
.tape-item { display: inline-flex; gap: 8px; align-items: center; color: var(--muted); }
.tape-item .sym { color: var(--ink); font-weight: 600; letter-spacing: 0.02em; }
.tape-item .px { color: var(--ink-soft); }
.tape-item .pct.up { color: var(--up); }
.tape-item .pct.down { color: var(--down); }
@keyframes tape { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* ─────── App header ─────── */
header.app {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 28px;
border-bottom: 1px solid var(--hairline);
background: rgba(7,9,13,0.6);
backdrop-filter: blur(18px) saturate(140%);
position: sticky; top: 0; z-index: 50;
}
.brand { display: flex; align-items: center; gap: 12px; }
.logo {
width: 30px; height: 30px;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
background:
radial-gradient(circle at 30% 30%, #ffb86b, transparent 55%),
conic-gradient(from 200deg, #f7931a, #ff00aa, #00e0ff, #00ff9d, #f7931a);
box-shadow: 0 0 24px rgba(255,0,170,0.4), 0 0 12px rgba(247,147,26,0.3);
filter: saturate(1.15);
}
.brand .word { font-family: var(--display); font-weight: 700; letter-spacing: -0.02em; font-size: 17px; }
.brand .pro {
font-family: var(--mono); font-size: 10px;
color: var(--magenta); border: 1px solid rgba(255,0,170,0.55);
padding: 2px 6px; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.16em;
background: var(--magenta-soft);
box-shadow: 0 0 12px rgba(255,0,170,0.2), inset 0 0 12px rgba(255,0,170,0.05);
}
.nav { display: flex; gap: 4px; margin-left: 16px; }
.nav a {
font-family: var(--mono); font-size: 12px; padding: 8px 12px; border-radius: 8px;
color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em;
}
.nav a:hover { color: var(--ink); background: var(--surface); }
.nav a.active { color: var(--ink); background: var(--surface-2); border: 1px solid var(--line); }
.head-right { display: flex; align-items: center; gap: 10px; }
.market-state {
display: inline-flex; gap: 8px; align-items: center;
padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--r-pill);
font-family: var(--mono); font-size: 11px; color: var(--ink-soft);
background: var(--surface);
}
.pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); position: relative; box-shadow: 0 0 8px var(--accent); }
.pulse::after {
content: ""; position: absolute; inset: -4px; border-radius: 50%;
box-shadow: 0 0 0 0 rgba(247,147,26,0.55);
animation: pulse 1.8s ease-out infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(247,147,26,0.55); }
70% { box-shadow: 0 0 0 12px rgba(247,147,26,0); }
100% { box-shadow: 0 0 0 0 rgba(247,147,26,0); }
}
.search {
display: inline-flex; gap: 8px; align-items: center;
padding: 6px 10px 6px 12px; border-radius: var(--r-pill);
background: var(--surface); border: 1px solid var(--line);
color: var(--muted); font-family: var(--mono); font-size: 11.5px;
}
.search .kbd {
display: inline-flex; gap: 2px;
}
.search kbd {
font-family: var(--mono); font-size: 10px; padding: 1px 5px; border: 1px solid var(--line-strong); border-radius: 4px; color: var(--muted);
}
.avatar {
width: 30px; height: 30px; border-radius: 50%;
background:
radial-gradient(circle at 30% 30%, #fde58a, #facc15 40%, #c98a00 100%);
border: 1px solid rgba(255,255,255,0.12);
display: inline-flex; align-items: center; justify-content: center;
color: #2a1a00; font-weight: 700; font-size: 12px; letter-spacing: 0.04em;
}
/* ─────── Layout ─────── */
main {
padding: 24px 28px 60px;
display: grid;
grid-template-columns: 1fr;
gap: 18px;
max-width: 1480px; margin: 0 auto;
}
/* ─────── Hero strip ─────── */
.hero {
display: grid;
grid-template-columns: 1.6fr 1fr;
gap: 18px;
}
.card {
background: linear-gradient(180deg, var(--surface), var(--surface-2));
border: 1px solid var(--line);
border-radius: var(--r-card);
padding: 22px;
position: relative;
overflow: hidden;
box-shadow:
0 1px 0 rgba(255,255,255,0.02) inset,
0 0 24px rgba(255,0,170,0.04);
}
.card::after {
content: ""; position: absolute; inset: 0; pointer-events: none;
border-radius: inherit;
box-shadow: inset 0 0 0 1px rgba(255,0,170,0.02), inset 0 1px 0 rgba(255,255,255,0.03);
}
.card.glass::before {
content: ""; position: absolute; inset: 0;
background:
radial-gradient(700px 200px at 0% 0%, rgba(255,0,170,0.06), transparent 60%),
radial-gradient(600px 200px at 100% 100%, rgba(0,224,255,0.05), transparent 60%);
pointer-events: none;
}
.pf-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.pf-meta { font-family: var(--mono); font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.12em; }
.pf-title { font-family: var(--display); font-weight: 600; font-size: 14.5px; margin-top: 6px; color: var(--ink-soft); display: flex; gap: 8px; align-items: center; }
.pf-value {
font-family: var(--display); font-weight: 600;
font-size: 56px; line-height: 1; margin-top: 10px;
letter-spacing: -0.03em;
background: linear-gradient(180deg, #fff, #b9c1d2);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.pf-value .cents { color: var(--muted); font-weight: 500; font-size: 28px; vertical-align: 0.18em; }
.pf-deltas { display: flex; gap: 22px; margin-top: 14px; align-items: center; }
.delta {
display: inline-flex; gap: 8px; align-items: baseline;
font-family: var(--mono); font-size: 13px;
}
.delta .v { font-weight: 600; }
.delta.up .v, .delta.up .arr { color: var(--up); }
.delta.down .v, .delta.down .arr { color: var(--down); }
.delta .lbl { color: var(--muted); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.08em; }
.pf-chart { margin-top: 18px; }
.pf-chart svg { width: 100%; height: 160px; display: block; }
.pf-axis { display: flex; justify-content: space-between; font-family: var(--mono); font-size: 10.5px; color: var(--muted); margin-top: 4px; padding: 0 2px; }
.range-tabs { display: inline-flex; padding: 3px; background: var(--surface); border: 1px solid var(--line); border-radius: 8px; gap: 0; }
.range-tabs button {
border: 0; background: transparent; color: var(--muted);
font-family: var(--mono); font-size: 11px; padding: 5px 9px; border-radius: 6px; letter-spacing: 0.06em;
}
.range-tabs button.active { background: var(--surface-3); color: var(--ink); box-shadow: 0 1px 0 rgba(255,255,255,0.04); }
/* KPI strip on the right */
.kpis { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.kpi {
border: 1px solid var(--line);
background: var(--surface);
border-radius: 12px;
padding: 14px 14px 16px;
position: relative;
overflow: hidden;
}
.kpi .k-label { font-family: var(--mono); font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.12em; }
.kpi .k-val { font-family: var(--display); font-weight: 600; font-size: 22px; letter-spacing: -0.01em; margin-top: 6px; }
.kpi .k-sub { font-family: var(--mono); font-size: 11px; margin-top: 4px; }
.kpi.spark svg { width: 100%; height: 36px; margin-top: 6px; }
.kpi.alpha .k-val { color: var(--accent); }
/* ─────── Main grid: chart + side ─────── */
.grid-2 {
display: grid;
grid-template-columns: 1.7fr 1fr;
gap: 18px;
}
/* Chart card */
.chart-card { padding: 0; }
.chart-head {
display: flex; justify-content: space-between; align-items: flex-start; gap: 16px;
padding: 20px 22px 14px;
border-bottom: 1px solid var(--hairline);
}
.ticker {
display: flex; gap: 16px; align-items: center;
}
.ticker .icon {
width: 44px; height: 44px; border-radius: 10px;
background: linear-gradient(135deg, #76b900, #4d8400);
display: inline-flex; align-items: center; justify-content: center;
color: #0a1502; font-family: var(--display); font-weight: 700; font-size: 18px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.3);
}
.ticker .meta { display: flex; flex-direction: column; gap: 2px; }
.ticker .sym-row { display: flex; align-items: baseline; gap: 10px; }
.ticker .sym { font-family: var(--display); font-weight: 700; font-size: 22px; letter-spacing: -0.01em; }
.ticker .ext {
font-family: var(--mono); font-size: 10.5px; color: var(--muted);
border: 1px solid var(--line); border-radius: 6px; padding: 2px 6px;
text-transform: uppercase; letter-spacing: 0.08em;
}
.ticker .ext:hover { color: var(--ink); border-color: var(--line-strong); }
.ticker .ext .ico { font-size: 9px; opacity: 0.7; margin-left: 2px; }
.ticker .name { color: var(--muted); font-size: 12.5px; }
.price-block { text-align: right; }
.px-now { font-family: var(--display); font-weight: 600; font-size: 30px; letter-spacing: -0.02em; }
.px-delta { font-family: var(--mono); font-size: 12.5px; margin-top: 2px; }
.chart-stats {
display: grid; grid-template-columns: repeat(6, 1fr); gap: 1px;
background: var(--hairline);
border-bottom: 1px solid var(--hairline);
}
.stat { background: var(--surface); padding: 10px 16px; }
.stat .lbl { font-family: var(--mono); font-size: 9.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; }
.stat .val { font-family: var(--mono); font-size: 12.5px; color: var(--ink); margin-top: 2px; font-weight: 500; }
.chart-body { padding: 16px 22px 18px; }
.chart-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.chart-tools { display: flex; gap: 6px; }
.chip {
font-family: var(--mono); font-size: 10.5px; padding: 5px 9px; border-radius: 6px;
border: 1px solid var(--line); background: var(--surface); color: var(--muted);
text-transform: uppercase; letter-spacing: 0.08em;
}
.chip.on { color: var(--cyan); border-color: rgba(34,211,238,0.4); background: var(--cyan-soft); }
.candles { width: 100%; height: 320px; display: block; }
.vol { width: 100%; height: 70px; display: block; margin-top: 2px; }
/* ─────── AI Recommendation card ─────── */
.ai-card { padding: 0; overflow: hidden; }
.ai-head {
padding: 18px 20px 14px;
background:
radial-gradient(360px 140px at 100% 0%, rgba(167,139,250,0.18), transparent 65%),
radial-gradient(280px 120px at 0% 100%, rgba(34,229,140,0.12), transparent 70%),
var(--surface-2);
border-bottom: 1px solid var(--hairline);
}
.ai-tag {
display: inline-flex; gap: 8px; align-items: center;
font-family: var(--mono); font-size: 10.5px; color: var(--violet);
text-transform: uppercase; letter-spacing: 0.14em;
}
.ai-tag::before {
content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--violet);
box-shadow: 0 0 12px var(--violet);
}
.ai-headline {
font-family: var(--display); font-weight: 600; font-size: 22px; letter-spacing: -0.015em;
margin-top: 10px; line-height: 1.25;
}
.ai-headline .accent {
background: linear-gradient(90deg, var(--magenta), var(--cyan), var(--matrix));
-webkit-background-clip: text; background-clip: text; color: transparent;
filter: drop-shadow(0 0 12px rgba(255,0,170,0.3));
}
.ai-sub { color: var(--ink-soft); font-size: 12.5px; margin-top: 6px; max-width: 46ch; }
.ai-stat-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0; border-top: 1px solid var(--hairline); }
.ai-stat { padding: 14px 16px; border-right: 1px solid var(--hairline); }
.ai-stat:last-child { border-right: 0; }
.ai-stat .lbl { font-family: var(--mono); font-size: 9.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; }
.ai-stat .v { font-family: var(--display); font-weight: 600; font-size: 18px; margin-top: 4px; letter-spacing: -0.01em; }
.ai-stat .v.up { color: var(--up); }
.ai-stat.conviction .bar { height: 4px; background: var(--surface-3); border-radius: 999px; margin-top: 8px; overflow: hidden; }
.ai-stat.conviction .fill { height: 100%; background: linear-gradient(90deg, var(--up), var(--cyan)); border-radius: 999px; box-shadow: 0 0 14px rgba(34,211,238,0.5); }
.ai-thesis { padding: 16px 20px; }
.ai-thesis h4 { font-family: var(--mono); font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.12em; margin: 0 0 10px; font-weight: 500; }
.thesis-list { display: flex; flex-direction: column; gap: 10px; }
.thesis-item { display: grid; grid-template-columns: 22px 1fr; gap: 10px; align-items: flex-start; }
.thesis-num {
font-family: var(--mono); font-size: 10px; color: var(--accent);
border: 1px solid rgba(250,204,21,0.4); border-radius: 6px;
padding: 1px 0; width: 22px; text-align: center;
background: var(--accent-soft);
}
.thesis-text { font-size: 12.5px; color: var(--ink-soft); line-height: 1.55; }
.thesis-text strong { color: var(--ink); font-weight: 600; }
.ai-actions { display: flex; gap: 8px; padding: 14px 20px 18px; border-top: 1px solid var(--hairline); }
.btn {
flex: 1; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line);
background: var(--surface); color: var(--ink); font-family: var(--display); font-weight: 500; font-size: 12.5px;
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
transition: transform 0.06s ease, border-color 0.15s ease;
}
.btn:hover { border-color: var(--line-strong); transform: translateY(-1px); }
.btn.primary {
background: linear-gradient(180deg, #2dffa0, #18b673);
color: #00200f; border-color: rgba(34,229,140,0.5);
box-shadow: 0 8px 24px rgba(34,229,140,0.25), inset 0 1px 0 rgba(255,255,255,0.35);
font-weight: 700;
}
/* ─────── Holdings table ─────── */
.panel-head { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--hairline); }
.panel-title { font-family: var(--display); font-weight: 600; font-size: 14.5px; letter-spacing: -0.005em; }
.panel-sub { font-family: var(--mono); font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; }
table.holdings { width: 100%; border-collapse: collapse; }
table.holdings th, table.holdings td {
padding: 12px 16px; text-align: left; font-size: 12.5px;
border-bottom: 1px solid var(--hairline);
}
table.holdings th {
font-family: var(--mono); font-size: 10px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.12em; font-weight: 500;
background: var(--surface);
}
table.holdings tr:last-child td { border-bottom: 0; }
table.holdings tr:hover td { background: rgba(34,211,238,0.025); }
td.num, th.num { text-align: right; font-family: var(--mono); }
.sym-cell { display: flex; align-items: center; gap: 12px; }
.sym-mark {
width: 30px; height: 30px; border-radius: 8px;
display: inline-flex; align-items: center; justify-content: center;
font-family: var(--display); font-weight: 700; font-size: 12px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.18);
}
.sym-cell .name-block { display: flex; flex-direction: column; }
.sym-cell .sym-name { font-weight: 600; letter-spacing: -0.005em; font-size: 13px; }
.sym-cell .sym-co { font-size: 11px; color: var(--muted); }
.sym-cell a.ext-mini {
font-family: var(--mono); font-size: 10px; color: var(--muted);
margin-left: 6px;
border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px;
}
.sym-cell a.ext-mini:hover { color: var(--cyan); border-color: rgba(34,211,238,0.45); }
.pl-cell { display: inline-flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.pl-pct { font-size: 11px; opacity: 0.85; }
.pl-cell.up .pl-val, .pl-cell.up .pl-pct { color: var(--up); }
.pl-cell.down .pl-val, .pl-cell.down .pl-pct { color: var(--down); }
.alloc-bar { width: 80px; height: 4px; border-radius: 999px; background: var(--surface-3); overflow: hidden; display: inline-block; vertical-align: middle; }
.alloc-fill { height: 100%; background: linear-gradient(90deg, var(--cyan), var(--violet)); border-radius: 999px; }
.mini-spark svg { width: 70px; height: 24px; display: block; }
/* ─────── Watchlist grid ─────── */
.watch-grid {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
}
.watch-card {
border: 1px solid var(--line); border-radius: 12px;
background: linear-gradient(180deg, var(--surface), var(--surface-2));
padding: 14px 16px;
position: relative;
overflow: hidden;
}
.watch-card::before {
content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
background: linear-gradient(90deg, transparent, rgba(34,211,238,0.4), transparent);
opacity: 0; transition: opacity 0.2s ease;
}
.watch-card:hover::before { opacity: 1; }
.watch-card:hover { border-color: var(--line-strong); }
.watch-top { display: flex; justify-content: space-between; align-items: flex-start; }
.watch-sym { font-family: var(--display); font-weight: 700; font-size: 15px; letter-spacing: -0.01em; }
.watch-co { font-size: 11px; color: var(--muted); margin-top: 2px; }
.watch-pct {
font-family: var(--mono); font-size: 11.5px;
padding: 3px 7px; border-radius: 6px; font-weight: 600;
}
.watch-pct.up { color: var(--up); background: var(--up-soft); }
.watch-pct.down { color: var(--down); background: var(--down-soft); }
.watch-px { font-family: var(--display); font-weight: 600; font-size: 22px; margin-top: 10px; letter-spacing: -0.015em; }
.watch-spark { margin-top: 8px; }
.watch-spark svg { width: 100%; height: 40px; display: block; }
.watch-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 10px; font-family: var(--mono); font-size: 10.5px; color: var(--muted); }
.watch-foot a { color: var(--muted); }
.watch-foot a:hover { color: var(--cyan); }
/* ─────── News / signals feed ─────── */
.news-list { display: flex; flex-direction: column; }
.news-item {
padding: 14px 20px; display: grid; grid-template-columns: 56px 1fr auto; gap: 14px; align-items: flex-start;
border-bottom: 1px solid var(--hairline);
}
.news-item:last-child { border-bottom: 0; }
.news-time { font-family: var(--mono); font-size: 10.5px; color: var(--muted); padding-top: 2px; }
.news-body .src { font-family: var(--mono); font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; }
.news-body .src .tag {
color: var(--accent); border: 1px solid rgba(250,204,21,0.4); background: var(--accent-soft);
padding: 1px 5px; border-radius: 4px; margin-right: 6px;
}
.news-body .head { font-size: 13px; color: var(--ink); margin-top: 4px; line-height: 1.45; }
.news-body .head .t-up { color: var(--up); }
.news-body .head .t-down { color: var(--down); }
.news-body .syms { margin-top: 6px; display: flex; gap: 5px; flex-wrap: wrap; }
.news-body .syms .s {
font-family: var(--mono); font-size: 10px;
padding: 1px 6px; border: 1px solid var(--line); border-radius: 4px; color: var(--ink-soft);
}
.impact { font-family: var(--mono); font-size: 10.5px; padding: 2px 7px; border-radius: 5px; }
.impact.high { color: var(--down); border: 1px solid rgba(255,79,109,0.35); background: var(--down-soft); }
.impact.pos { color: var(--up); border: 1px solid rgba(34,229,140,0.35); background: var(--up-soft); }
.impact.med { color: var(--accent); border: 1px solid rgba(250,204,21,0.35); background: var(--accent-soft); }
/* ─────── Footer ─────── */
footer.app {
margin-top: 14px; padding: 18px 28px;
border-top: 1px solid var(--hairline);
display: flex; justify-content: space-between; align-items: center;
font-family: var(--mono); font-size: 11px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.1em;
}
footer .left { display: flex; gap: 14px; align-items: center; }
footer .live { display: inline-flex; gap: 6px; align-items: center; color: var(--up); }
footer .live::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--up); box-shadow: 0 0 8px var(--up); }
footer .right { display: flex; gap: 16px; }
/* Responsive */
@media (max-width: 1180px) {
.grid-2 { grid-template-columns: 1fr; }
.hero { grid-template-columns: 1fr; }
.watch-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 720px) {
main { padding: 16px; }
.chart-stats { grid-template-columns: repeat(3, 1fr); }
.ai-stat-row { grid-template-columns: 1fr; }
.ai-stat { border-right: 0; border-bottom: 1px solid var(--hairline); }
.nav, .search { display: none; }
.pf-value { font-size: 40px; }
.watch-grid { grid-template-columns: 1fr; }
}
/* small icon helpers */
.ic { width: 14px; height: 14px; display: inline-block; vertical-align: -2px; }
/* number flicker on the live price */
.flicker { animation: flicker 2.6s ease-in-out infinite; }
@keyframes flicker {
0%, 96%, 100% { opacity: 1; }
97% { opacity: 0.55; }
98% { opacity: 1; }
}
/* subtle grid background under candles */
.gridlines line { stroke: rgba(255,255,255,0.04); stroke-dasharray: 2 4; }
/* ─────── Interactive layer ─────── */
/* Refresh button */
.icon-btn {
display: inline-flex; align-items: center; gap: 8px;
padding: 6px 12px; border-radius: var(--r-pill);
background: var(--surface); border: 1px solid var(--line); color: var(--ink-soft);
font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em;
transition: all 0.15s ease;
}
.icon-btn:hover { color: var(--ink); border-color: var(--line-strong); transform: translateY(-1px); }
.icon-btn.primary { color: var(--cyan); border-color: rgba(34,211,238,0.45); background: var(--cyan-soft); }
.icon-btn.primary:hover { box-shadow: 0 0 16px rgba(34,211,238,0.25); }
.icon-btn .ico { width: 13px; height: 13px; transition: transform 0.6s ease; }
.icon-btn.spin .ico { animation: spin 0.8s linear; }
@keyframes spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
/* Price flash */
.flash-up { animation: flashUp 0.9s ease-out; }
.flash-down { animation: flashDown 0.9s ease-out; }
@keyframes flashUp {
0% { background: rgba(34,229,140,0.22); box-shadow: inset 0 0 0 1px rgba(34,229,140,0.4); }
100% { background: transparent; box-shadow: none; }
}
@keyframes flashDown {
0% { background: rgba(255,79,109,0.22); box-shadow: inset 0 0 0 1px rgba(255,79,109,0.4); }
100% { background: transparent; box-shadow: none; }
}
/* AI tabs */
.ai-tabs {
display: flex; gap: 0;
padding: 0 20px;
background: var(--surface);
border-bottom: 1px solid var(--hairline);
}
.ai-tab {
background: transparent; border: 0;
padding: 12px 14px;
font-family: var(--mono); font-size: 10.5px;
color: var(--muted);
text-transform: uppercase; letter-spacing: 0.1em;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: color 0.15s ease, border-color 0.15s ease;
}
.ai-tab:hover { color: var(--ink-soft); }
.ai-tab.active { color: var(--violet); border-bottom-color: var(--violet); }
.ai-tab .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-left: 6px; vertical-align: middle; }
.ai-tab .dot.bull { background: var(--up); }
.ai-tab .dot.risk { background: var(--down); }
.ai-tab .dot.amber { background: var(--accent); }
.ai-tab .dot.violet { background: var(--violet); }
.ai-panel { display: none; }
.ai-panel.active { display: block; animation: panelIn 0.25s ease; }
@keyframes panelIn { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } }
/* AI regenerate */
.ai-regen {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px; border-radius: var(--r-pill);
background: rgba(167,139,250,0.1); border: 1px solid rgba(167,139,250,0.35); color: var(--violet);
font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
margin-left: 10px; cursor: pointer;
transition: all 0.15s ease;
}
.ai-regen:hover { background: rgba(167,139,250,0.18); }
.ai-regen .ico { width: 10px; height: 10px; }
.ai-regen.thinking .ico { animation: spin 0.8s linear infinite; }
.thinking-dots { display: inline-flex; gap: 3px; align-items: center; }
.thinking-dots .d { width: 4px; height: 4px; border-radius: 50%; background: var(--violet); opacity: 0.6; animation: dotpulse 1.2s ease-in-out infinite; }
.thinking-dots .d:nth-child(2) { animation-delay: 0.15s; }
.thinking-dots .d:nth-child(3) { animation-delay: 0.3s; }
@keyframes dotpulse { 0%,100% { opacity: 0.3; transform: scale(0.85); } 50% { opacity: 1; transform: scale(1.15); } }
/* Risk / position-read / trade-plan content */
.risk-list { display: flex; flex-direction: column; gap: 10px; padding: 16px 20px; }
.risk-row {
display: grid; grid-template-columns: 56px 1fr auto; gap: 12px; align-items: flex-start;
padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface);
}
.risk-row .lvl {
font-family: var(--mono); font-size: 9.5px; padding: 3px 6px; border-radius: 5px;
text-transform: uppercase; letter-spacing: 0.1em; text-align: center; font-weight: 600;
}
.risk-row .lvl.high { color: var(--down); border: 1px solid rgba(255,79,109,0.4); background: var(--down-soft); }
.risk-row .lvl.med { color: var(--accent); border: 1px solid rgba(250,204,21,0.4); background: var(--accent-soft); }
.risk-row .lvl.low { color: var(--up); border: 1px solid rgba(34,229,140,0.4); background: var(--up-soft); }
.risk-row .body { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }
.risk-row .body strong { color: var(--ink); }
.risk-row .prob { font-family: var(--mono); font-size: 11px; color: var(--muted); white-space: nowrap; padding-top: 2px; }
/* Trade plan stepper */
.plan-grid { padding: 16px 20px; display: flex; flex-direction: column; gap: 0; }
.plan-step { display: grid; grid-template-columns: 24px 100px 1fr; gap: 12px; padding: 12px 0; border-bottom: 1px dashed var(--line); align-items: center; }
.plan-step:last-child { border-bottom: 0; }
.plan-bullet { width: 22px; height: 22px; border-radius: 50%; background: var(--surface-3); border: 1px solid var(--line); display: inline-flex; align-items: center; justify-content: center; font-family: var(--mono); font-size: 10px; color: var(--muted); }
.plan-step.entry .plan-bullet { background: var(--up-soft); border-color: rgba(34,229,140,0.4); color: var(--up); }
.plan-step.stop .plan-bullet { background: var(--down-soft); border-color: rgba(255,79,109,0.4); color: var(--down); }
.plan-step.target .plan-bullet { background: var(--accent-soft); border-color: rgba(250,204,21,0.4); color: var(--accent); }
.plan-label { font-family: var(--mono); font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; }
.plan-zone { font-family: var(--mono); font-size: 12px; color: var(--ink); }
.plan-zone .px { font-weight: 600; color: var(--ink); }
.plan-zone .pct.up { color: var(--up); }
.plan-zone .pct.down { color: var(--down); }
/* Position-read facts */
.pos-read { padding: 16px 20px; }
.pos-read .pos-line { display: grid; grid-template-columns: 1fr auto; padding: 8px 0; border-bottom: 1px dashed var(--line); font-size: 12.5px; }
.pos-read .pos-line:last-child { border-bottom: 0; }
.pos-read .pos-line .lbl { color: var(--muted); }
.pos-read .pos-line .val { font-family: var(--mono); color: var(--ink); }
.pos-read .pos-line .val.up { color: var(--up); }
.pos-read .pos-line .val.down { color: var(--down); }
.pos-read .pos-narr {
margin-top: 12px; padding: 12px 14px; border-radius: 8px;
background: rgba(167,139,250,0.06); border-left: 2px solid var(--violet);
color: var(--ink-soft); font-size: 12.5px; line-height: 1.55;
}
.pos-read .pos-narr .quote { font-family: var(--serif); font-style: italic; color: var(--ink); font-size: 14px; line-height: 1.4; }
/* Toast */
.toast-box {
position: fixed; bottom: 28px; right: 28px; z-index: 200;
display: flex; flex-direction: column; gap: 10px;
pointer-events: none;
}
.toast {
pointer-events: auto;
min-width: 280px; max-width: 360px;
padding: 12px 16px;
background: rgba(15,19,28,0.92);
border: 1px solid var(--line);
border-left: 3px solid var(--up);
border-radius: 10px;
backdrop-filter: blur(16px) saturate(140%);
box-shadow: 0 20px 50px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.04);
font-family: var(--body); font-size: 12.5px;
color: var(--ink);
animation: toastIn 0.32s cubic-bezier(0.2, 0.8, 0.2, 1);
transition: opacity 0.25s ease, transform 0.25s ease;
}
.toast.error { border-left-color: var(--down); }
.toast.warn { border-left-color: var(--accent); }
.toast.info { border-left-color: var(--cyan); }
.toast.violet { border-left-color: var(--violet); }
.toast .t-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.toast .t-title { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); }
.toast .t-close { background: transparent; border: 0; color: var(--muted); font-size: 14px; padding: 0; line-height: 1; cursor: pointer; }
.toast .t-close:hover { color: var(--ink); }
.toast .t-body { margin-top: 4px; line-height: 1.4; color: var(--ink-soft); }
.toast .t-body strong { color: var(--ink); }
.toast.fade { opacity: 0; transform: translateX(20px); }
@keyframes toastIn {
from { opacity: 0; transform: translateX(20px) scale(0.96); }
to { opacity: 1; transform: translateX(0) scale(1); }
}
/* Selection highlight on switch */
.swap-highlight { animation: swapPulse 0.7s ease-out; }
@keyframes swapPulse {
0% { box-shadow: inset 0 0 0 1px rgba(34,211,238,0.6), 0 0 0 0 rgba(34,211,238,0.5); }
100% { box-shadow: inset 0 0 0 1px transparent, 0 0 0 12px transparent; }
}
/* clickable affordances */
table.holdings tbody tr { cursor: pointer; }
table.holdings tbody tr.active td { background: rgba(34,211,238,0.06); }
table.holdings tbody tr.active td:first-child { box-shadow: inset 3px 0 0 var(--cyan); }
.watch-card { cursor: pointer; transition: transform 0.15s ease, border-color 0.15s ease; }
.watch-card:hover { transform: translateY(-2px); }
.watch-card.active { border-color: rgba(34,211,238,0.55); box-shadow: 0 0 24px rgba(34,211,238,0.18); }
.tape-item { cursor: pointer; }
.tape-item:hover .sym { color: var(--cyan); }
.range-tabs button { cursor: pointer; }
/* Market regime mini badge inside AI head */
.regime-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.regime-pill {
font-family: var(--mono); font-size: 10px;
padding: 4px 9px; border-radius: var(--r-pill);
border: 1px solid var(--line); background: var(--surface);
color: var(--ink-soft); letter-spacing: 0.06em;
display: inline-flex; gap: 6px; align-items: center;
}
.regime-pill .lbl { color: var(--muted); }
.regime-pill.up .v { color: var(--up); }
.regime-pill.down .v { color: var(--down); }
.regime-pill.neut .v { color: var(--accent); }
</style>
</head>
<body>
<!-- ─────── Ticker tape ─────── -->
<div class="ticker-tape">
<div class="tape">
<span class="tape-item"><span class="sym">BTC</span><span class="px">68,412</span><span class="pct up">▲ 1.84%</span></span>
<span class="tape-item"><span class="sym">ETH</span><span class="px">3,612</span><span class="pct up">▲ 2.10%</span></span>
<span class="tape-item"><span class="sym">SOL</span><span class="px">186.42</span><span class="pct up">▲ 4.62%</span></span>
<span class="tape-item"><span class="sym">BNB</span><span class="px">612.40</span><span class="pct up">▲ 0.94%</span></span>
<span class="tape-item"><span class="sym">XRP</span><span class="px">0.5824</span><span class="pct down">▼ 0.42%</span></span>
<span class="tape-item"><span class="sym">ADA</span><span class="px">0.4318</span><span class="pct up">▲ 1.20%</span></span>
<span class="tape-item"><span class="sym">DOGE</span><span class="px">0.1842</span><span class="pct up">▲ 6.84%</span></span>
<span class="tape-item"><span class="sym">AVAX</span><span class="px">36.84</span><span class="pct up">▲ 2.86%</span></span>
<span class="tape-item"><span class="sym">LINK</span><span class="px">14.92</span><span class="pct up">▲ 1.40%</span></span>
<span class="tape-item"><span class="sym">DOT</span><span class="px">7.18</span><span class="pct down">▼ 0.62%</span></span>
<span class="tape-item"><span class="sym">SUI</span><span class="px">2.18</span><span class="pct up">▲ 8.40%</span></span>
<span class="tape-item"><span class="sym">NEAR</span><span class="px">5.42</span><span class="pct up">▲ 3.20%</span></span>
<span class="tape-item"><span class="sym">TIA</span><span class="px">7.84</span><span class="pct down">▼ 1.18%</span></span>
<span class="tape-item"><span class="sym">ARB</span><span class="px">0.6420</span><span class="pct up">▲ 0.95%</span></span>
<!-- duplicate for seamless scroll -->
<span class="tape-item"><span class="sym">BTC</span><span class="px">68,412</span><span class="pct up">▲ 1.84%</span></span>
<span class="tape-item"><span class="sym">ETH</span><span class="px">3,612</span><span class="pct up">▲ 2.10%</span></span>
<span class="tape-item"><span class="sym">SOL</span><span class="px">186.42</span><span class="pct up">▲ 4.62%</span></span>
<span class="tape-item"><span class="sym">BNB</span><span class="px">612.40</span><span class="pct up">▲ 0.94%</span></span>
<span class="tape-item"><span class="sym">XRP</span><span class="px">0.5824</span><span class="pct down">▼ 0.42%</span></span>
<span class="tape-item"><span class="sym">ADA</span><span class="px">0.4318</span><span class="pct up">▲ 1.20%</span></span>
<span class="tape-item"><span class="sym">DOGE</span><span class="px">0.1842</span><span class="pct up">▲ 6.84%</span></span>
<span class="tape-item"><span class="sym">AVAX</span><span class="px">36.84</span><span class="pct up">▲ 2.86%</span></span>
<span class="tape-item"><span class="sym">LINK</span><span class="px">14.92</span><span class="pct up">▲ 1.40%</span></span>
<span class="tape-item"><span class="sym">DOT</span><span class="px">7.18</span><span class="pct down">▼ 0.62%</span></span>
<span class="tape-item"><span class="sym">SUI</span><span class="px">2.18</span><span class="pct up">▲ 8.40%</span></span>
<span class="tape-item"><span class="sym">NEAR</span><span class="px">5.42</span><span class="pct up">▲ 3.20%</span></span>
<span class="tape-item"><span class="sym">TIA</span><span class="px">7.84</span><span class="pct down">▼ 1.18%</span></span>
<span class="tape-item"><span class="sym">ARB</span><span class="px">0.6420</span><span class="pct up">▲ 0.95%</span></span>
</div>
</div>
<!-- ─────── On-chain ribbon ─────── -->
<div class="onchain-ribbon">
<span class="item live"><span class="label">Block</span><span class="block-h" id="blockH">287,418,612</span></span>
<span class="item"><span class="label">Gas</span><span class="gas">12 gwei</span><span class="label">· low</span></span>
<span class="item"><span class="label">Mempool</span><span class="v">142,840 tx</span></span>
<span class="item"><span class="label">BTC hash</span><span class="v cyan">612 EH/s</span></span>
<span class="item"><span class="label">DeFi TVL</span><span class="v magenta">$84.2B</span></span>
<span class="item"><span class="label">ETH staking</span><span class="v">28.4% (34M ETH)</span></span>
<span class="item"><span class="label">Fear &amp; Greed</span><span class="v" style="color:var(--accent)">78 · Greed</span></span>
<span class="item" style="margin-left:auto"><span class="label">Stables MC</span><span class="v">$172.8B</span></span>
</div>
<!-- ─────── Header ─────── -->
<header class="app">
<div class="brand">
<div class="logo"></div>
<div class="word">quiver</div>
<div class="pro">Crypto</div>
<nav class="nav">
<a href="#" class="active">Portfolio</a>
<a href="#">Markets</a>
<a href="#">On-chain</a>
<a href="#">DeFi</a>
<a href="#">Alerts</a>
</nav>
</div>
<div class="head-right">
<span class="market-state">
<span class="pulse"></span>
<span>24/7 markets · <span id="liveClock">14:32:18 UTC</span></span>
</span>
<span class="search">
<svg class="ic" viewBox="0 0 16 16" fill="none"><circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.4"/><path d="M11 11l3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
<span>Search ticker, news, analyst…</span>
<span class="kbd"><kbd>⌘</kbd><kbd>K</kbd></span>
</span>
<button class="icon-btn primary" id="refreshBtn" title="Refresh quotes & analysis">
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M14 8a6 6 0 1 1-1.76-4.24M14 3v3.5h-3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>Refresh</span>
</button>
<span class="avatar">PT</span>
</div>
</header>
<main>
<!-- ─────── Hero: portfolio value + KPIs ─────── -->
<section class="hero">
<div class="card glass">
<div class="pf-head">
<div>
<div class="pf-meta">Total crypto net worth · USD</div>
<div class="pf-title">Coinbase · Ledger · MetaMask &nbsp;<span class="market-state" style="padding:3px 8px;font-size:10.5px;"><span class="pulse"></span> On-chain live</span></div>
</div>
<div class="range-tabs">
<button>24H</button>
<button>1W</button>
<button>1M</button>
<button>3M</button>
<button>6M</button>
<button class="active">1Y</button>
<button>ALL</button>
</div>
</div>
<div class="pf-value flicker">
$186,420<span class="cents">.18</span>
</div>
<div class="pf-deltas">
<span class="delta up"><span class="arr">▲</span> <span class="v">+$5,842.16</span> <span class="lbl">today · +3.23%</span></span>
<span class="delta up"><span class="arr">▲</span> <span class="v">+$112,420.18</span> <span class="lbl">all time · +151.9%</span></span>
<span class="delta"><span class="lbl" style="color:var(--muted)">vs BTC</span> <span class="v" style="color:var(--accent);">+22.4pp</span></span>
</div>
<div class="pf-chart">
<svg viewBox="0 0 880 170" preserveAspectRatio="none">
<defs>
<linearGradient id="pfGrad" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#22e58c" stop-opacity="0.32"/>
<stop offset="60%" stop-color="#22e58c" stop-opacity="0.06"/>
<stop offset="100%" stop-color="#22e58c" stop-opacity="0"/>
</linearGradient>
<linearGradient id="pfStroke" x1="0" x2="1" y1="0" y2="0">
<stop offset="0%" stop-color="#ff00aa"/>
<stop offset="55%" stop-color="#00e0ff"/>
<stop offset="100%" stop-color="#00ff9d"/>
</linearGradient>
<pattern id="grid" x="0" y="0" width="60" height="34" patternUnits="userSpaceOnUse">
<path d="M 60 0 L 0 0 0 34" fill="none" stroke="rgba(255,255,255,0.035)" stroke-width="1"/>
</pattern>
</defs>
<rect width="880" height="170" fill="url(#grid)"/>
<!-- Benchmark (S&P) ghost line -->
<polyline fill="none" stroke="rgba(255,255,255,0.22)" stroke-width="1.2" stroke-dasharray="3 4"
points="10,130 60,128 110,124 160,122 210,118 260,114 310,108 360,104 410,98 460,92 510,86 560,82 610,78 660,72 710,66 760,62 810,56 860,52" />
<!-- Portfolio area -->
<polygon fill="url(#pfGrad)"
points="10,150 10,128 60,124 110,116 160,118 210,108 260,114 310,98 360,90 410,96 460,80 510,72 560,76 610,60 660,54 710,46 760,40 810,32 860,28 870,28 870,150" />
<polyline fill="none" stroke="url(#pfStroke)" stroke-width="2.4" stroke-linejoin="round" stroke-linecap="round"
points="10,128 60,124 110,116 160,118 210,108 260,114 310,98 360,90 410,96 460,80 510,72 560,76 610,60 660,54 710,46 760,40 810,32 860,28" />
<!-- "Now" marker -->
<circle cx="860" cy="28" r="4" fill="#22e58c"/>
<circle cx="860" cy="28" r="9" fill="none" stroke="#22e58c" stroke-opacity="0.5"/>
<line x1="860" y1="28" x2="860" y2="160" stroke="rgba(34,229,140,0.35)" stroke-dasharray="2 3"/>
<!-- Buy markers -->
<g>
<circle cx="210" cy="108" r="3.5" fill="#facc15"/>
<circle cx="460" cy="80" r="3.5" fill="#facc15"/>
<circle cx="660" cy="54" r="3.5" fill="#facc15"/>
</g>
</svg>
<div class="pf-axis">
<span>May '24</span><span>Jul</span><span>Sep</span><span>Nov</span><span>Jan '25</span><span>Mar</span><span>Today</span>
</div>
</div>
</div>
<div class="kpis">
<div class="kpi">
<div class="k-label">24h P&L</div>
<div class="k-val" style="color:var(--up)">+$5,842.16</div>
<div class="k-sub" style="color:var(--up)">+3.23% · 9 green / 2 red</div>
</div>
<div class="kpi">
<div class="k-label">Stables · dry powder</div>
<div class="k-val">$24,180</div>
<div class="k-sub" style="color:var(--muted)">USDC 18.4k · USDT 5.78k · 4.92% APY</div>
</div>
<div class="kpi spark">
<div class="k-label">Best holding · SOL</div>
<div class="k-val" style="color:var(--up)">+812.4%</div>
<svg viewBox="0 0 200 36" preserveAspectRatio="none">
<defs><linearGradient id="sg1" x1="0" x2="0" y1="0" y2="1"><stop offset="0%" stop-color="#22e58c" stop-opacity="0.4"/><stop offset="100%" stop-color="#22e58c" stop-opacity="0"/></linearGradient></defs>
<polygon fill="url(#sg1)" points="0,32 0,30 20,28 40,26 60,22 80,20 100,18 120,14 140,12 160,8 180,5 200,3 200,36 0,36"/>
<polyline fill="none" stroke="#22e58c" stroke-width="1.6" points="0,30 20,28 40,26 60,22 80,20 100,18 120,14 140,12 160,8 180,5 200,3"/>
</svg>
</div>
<div class="kpi alpha">
<div class="k-label">Alpha vs BTC · YTD</div>
<div class="k-val">+22.4pp</div>
<div class="k-sub" style="color:var(--muted)">Sharpe 1.86 · Vol 64% · Max DD −34%</div>
</div>
</div>
</section>
<!-- ─────── Main grid: chart + AI rec ─────── -->
<section class="grid-2">
<!-- Chart -->
<div class="card chart-card" id="chartCard">
<div class="chart-head">
<div class="ticker">
<div class="icon" id="tkIcon" style="background:linear-gradient(135deg,#f7931a,#a35e00);color:#1a0e00;">₿</div>
<div class="meta">
<div class="sym-row">
<span class="sym" id="tkSym">BTC</span>
<a class="ext" id="tkLinkExch" href="https://www.coinbase.com/price/bitcoin" target="_blank" rel="noopener">Coinbase <span class="ico">↗</span></a>
<a class="ext" id="tkLinkYahoo" href="https://www.binance.com/en/price/bitcoin" target="_blank" rel="noopener">Binance <span class="ico">↗</span></a>
<a class="ext" id="tkLinkTV" href="https://coinmarketcap.com/currencies/bitcoin/" target="_blank" rel="noopener">CoinMarketCap <span class="ico">↗</span></a>
</div>
<div class="name" id="tkName">Bitcoin · L1 · Mkt cap $1.35T · Dominance 54.2%</div>
</div>
</div>
<div class="price-block">
<div class="px-now flicker" id="tkPx" style="color:var(--up)">$68,412</div>
<div class="px-delta" id="tkDelta" style="color:var(--up)">▲ +$1,236.40 &nbsp; +1.84% 24h</div>
</div>
</div>
<div class="chart-stats" id="tkStats">
<div class="stat"><div class="lbl">24h open</div><div class="val" data-stat="open">67,176</div></div>
<div class="stat"><div class="lbl">24h high</div><div class="val" data-stat="high">68,920</div></div>
<div class="stat"><div class="lbl">24h low</div><div class="val" data-stat="low">66,840</div></div>
<div class="stat"><div class="lbl">24h volume</div><div class="val" data-stat="vol">$28.4B</div></div>
<div class="stat"><div class="lbl">Circ supply</div><div class="val" data-stat="pe">19.74M / 21M</div></div>
<div class="stat"><div class="lbl">ATH</div><div class="val" data-stat="range52">$73,750 (Mar)</div></div>
</div>
<div class="chart-body">
<div class="chart-toolbar">
<div class="chart-tools">
<span class="chip on">Candles</span>
<span class="chip">Line</span>
<span class="chip">Volume</span>
<span class="chip">SMA 50</span>
<span class="chip">RSI</span>
</div>
<div class="range-tabs">
<button>1D</button>
<button>5D</button>
<button class="active">1M</button>
<button>3M</button>
<button>1Y</button>
<button>5Y</button>
</div>
</div>
<!-- Candlestick -->
<svg class="candles" id="candleSvg" viewBox="0 0 880 320" preserveAspectRatio="none">
<defs>
<pattern id="grid2" x="0" y="0" width="80" height="40" patternUnits="userSpaceOnUse">
<path d="M 80 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.035)" stroke-width="1"/>
</pattern>
</defs>
<rect width="880" height="320" fill="url(#grid2)"/>
<!-- Y axis labels -->
<g font-family="Geist Mono,monospace" font-size="10" fill="#6b7689">
<text x="6" y="36">940</text>
<text x="6" y="96">900</text>
<text x="6" y="156">860</text>
<text x="6" y="216">820</text>
<text x="6" y="276">780</text>
</g>
<!-- SMA50 trend line -->
<polyline fill="none" stroke="#facc15" stroke-opacity="0.55" stroke-width="1.4" stroke-dasharray="3 3"
points="40,220 80,214 120,208 160,200 200,196 240,188 280,180 320,170 360,160 400,150 440,140 480,128 520,118 560,108 600,96 640,84 680,76 720,70 760,62 800,54 840,46"/>
<!-- Candles: x ranges from 40 to 840 (stride 40), 21 candles -->
<!-- format: wick line + body rect; up = green, down = red -->
<g>
<!-- 1 -->
<line x1="44" y1="248" x2="44" y2="222" stroke="#22e58c" stroke-width="1.2"/><rect x="38" y="230" width="12" height="14" fill="#22e58c"/>
<!-- 2 -->
<line x1="84" y1="240" x2="84" y2="208" stroke="#22e58c" stroke-width="1.2"/><rect x="78" y="216" width="12" height="20" fill="#22e58c"/>
<!-- 3 down -->
<line x1="124" y1="222" x2="124" y2="246" stroke="#ff4f6d" stroke-width="1.2"/><rect x="118" y="224" width="12" height="14" fill="#ff4f6d"/>
<!-- 4 -->
<line x1="164" y1="232" x2="164" y2="200" stroke="#22e58c" stroke-width="1.2"/><rect x="158" y="206" width="12" height="22" fill="#22e58c"/>
<!-- 5 -->
<line x1="204" y1="218" x2="204" y2="190" stroke="#22e58c" stroke-width="1.2"/><rect x="198" y="196" width="12" height="18" fill="#22e58c"/>
<!-- 6 down -->
<line x1="244" y1="200" x2="244" y2="226" stroke="#ff4f6d" stroke-width="1.2"/><rect x="238" y="204" width="12" height="16" fill="#ff4f6d"/>
<!-- 7 -->
<line x1="284" y1="220" x2="284" y2="184" stroke="#22e58c" stroke-width="1.2"/><rect x="278" y="190" width="12" height="22" fill="#22e58c"/>
<!-- 8 -->
<line x1="324" y1="200" x2="324" y2="170" stroke="#22e58c" stroke-width="1.2"/><rect x="318" y="178" width="12" height="18" fill="#22e58c"/>
<!-- 9 down -->
<line x1="364" y1="180" x2="364" y2="208" stroke="#ff4f6d" stroke-width="1.2"/><rect x="358" y="184" width="12" height="18" fill="#ff4f6d"/>
<!-- 10 -->
<line x1="404" y1="206" x2="404" y2="158" stroke="#22e58c" stroke-width="1.2"/><rect x="398" y="166" width="12" height="34" fill="#22e58c"/>
<!-- 11 -->
<line x1="444" y1="170" x2="444" y2="146" stroke="#22e58c" stroke-width="1.2"/><rect x="438" y="152" width="12" height="14" fill="#22e58c"/>
<!-- 12 down -->
<line x1="484" y1="148" x2="484" y2="174" stroke="#ff4f6d" stroke-width="1.2"/><rect x="478" y="152" width="12" height="16" fill="#ff4f6d"/>
<!-- 13 -->
<line x1="524" y1="166" x2="524" y2="128" stroke="#22e58c" stroke-width="1.2"/><rect x="518" y="136" width="12" height="24" fill="#22e58c"/>
<!-- 14 -->
<line x1="564" y1="142" x2="564" y2="116" stroke="#22e58c" stroke-width="1.2"/><rect x="558" y="124" width="12" height="14" fill="#22e58c"/>
<!-- 15 down -->
<line x1="604" y1="120" x2="604" y2="146" stroke="#ff4f6d" stroke-width="1.2"/><rect x="598" y="124" width="12" height="16" fill="#ff4f6d"/>
<!-- 16 -->
<line x1="644" y1="142" x2="644" y2="100" stroke="#22e58c" stroke-width="1.2"/><rect x="638" y="106" width="12" height="30" fill="#22e58c"/>
<!-- 17 -->
<line x1="684" y1="112" x2="684" y2="84" stroke="#22e58c" stroke-width="1.2"/><rect x="678" y="92" width="12" height="14" fill="#22e58c"/>
<!-- 18 down -->
<line x1="724" y1="84" x2="724" y2="106" stroke="#ff4f6d" stroke-width="1.2"/><rect x="718" y="86" width="12" height="14" fill="#ff4f6d"/>
<!-- 19 -->
<line x1="764" y1="106" x2="764" y2="68" stroke="#22e58c" stroke-width="1.2"/><rect x="758" y="74" width="12" height="26" fill="#22e58c"/>
<!-- 20 -->
<line x1="804" y1="80" x2="804" y2="52" stroke="#22e58c" stroke-width="1.2"/><rect x="798" y="58" width="12" height="20" fill="#22e58c"/>
<!-- 21 (today) -->
<line x1="844" y1="68" x2="844" y2="38" stroke="#22e58c" stroke-width="1.2"/><rect x="838" y="44" width="12" height="22" fill="#22e58c"/>
</g>
<!-- Buy/sell markers from journal -->
<g font-family="Geist Mono,monospace" font-size="10">
<circle cx="284" cy="200" r="5" fill="#facc15"/>
<text x="296" y="204" fill="#facc15">BUY · 60 @ 812</text>
<circle cx="524" cy="166" r="5" fill="#facc15"/>
<text x="536" y="170" fill="#facc15">BUY · 40 @ 845</text>
</g>
<!-- Current price marker -->
<g id="pxMarker">
<line x1="40" y1="44" x2="844" y2="44" stroke="rgba(34,229,140,0.25)" stroke-dasharray="2 3"/>
<rect x="836" y="36" width="40" height="18" fill="#22e58c" rx="3"/>
<text id="pxMarkerText" x="856" y="49" font-family="Geist Mono,monospace" font-size="10" fill="#00200f" text-anchor="middle" font-weight="700">924.31</text>
</g>
</svg>
<!-- Volume -->
<svg class="vol" id="volSvg" viewBox="0 0 880 70" preserveAspectRatio="none">
<g>
<rect x="38" y="50" width="12" height="20" fill="#22e58c" opacity="0.5"/>
<rect x="78" y="44" width="12" height="26" fill="#22e58c" opacity="0.5"/>
<rect x="118" y="48" width="12" height="22" fill="#ff4f6d" opacity="0.55"/>
<rect x="158" y="40" width="12" height="30" fill="#22e58c" opacity="0.5"/>
<rect x="198" y="46" width="12" height="24" fill="#22e58c" opacity="0.5"/>
<rect x="238" y="50" width="12" height="20" fill="#ff4f6d" opacity="0.55"/>
<rect x="278" y="36" width="12" height="34" fill="#22e58c" opacity="0.5"/>
<rect x="318" y="42" width="12" height="28" fill="#22e58c" opacity="0.5"/>
<rect x="358" y="44" width="12" height="26" fill="#ff4f6d" opacity="0.55"/>
<rect x="398" y="22" width="12" height="48" fill="#22e58c" opacity="0.65"/>
<rect x="438" y="46" width="12" height="24" fill="#22e58c" opacity="0.5"/>
<rect x="478" y="44" width="12" height="26" fill="#ff4f6d" opacity="0.55"/>
<rect x="518" y="30" width="12" height="40" fill="#22e58c" opacity="0.6"/>
<rect x="558" y="44" width="12" height="26" fill="#22e58c" opacity="0.5"/>
<rect x="598" y="46" width="12" height="24" fill="#ff4f6d" opacity="0.55"/>
<rect x="638" y="20" width="12" height="50" fill="#22e58c" opacity="0.7"/>
<rect x="678" y="42" width="12" height="28" fill="#22e58c" opacity="0.5"/>
<rect x="718" y="48" width="12" height="22" fill="#ff4f6d" opacity="0.55"/>
<rect x="758" y="26" width="12" height="44" fill="#22e58c" opacity="0.6"/>
<rect x="798" y="34" width="12" height="36" fill="#22e58c" opacity="0.5"/>
<rect x="838" y="14" width="12" height="56" fill="#22e58c" opacity="0.85"/>
</g>
<text x="6" y="14" font-family="Geist Mono,monospace" font-size="10" fill="#6b7689">VOL</text>
<text x="6" y="64" font-family="Geist Mono,monospace" font-size="10" fill="#6b7689">38.4M</text>
</svg>
</div>
</div>
<!-- AI Recommendation -->
<div class="card ai-card">
<div class="ai-head">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
<div class="ai-tag">Quiver AI · Signal #2417</div>
<button class="ai-regen" id="regenBtn" title="Regenerate analysis">
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M14 8a6 6 0 1 1-1.76-4.24M14 3v3.5h-3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="regenLabel">Regenerate</span>
</button>
</div>
<div class="ai-headline" id="aiHeadline">
<span class="accent">Rotate stables → SOL</span> on DePIN + Firedancer accelerator.
</div>
<div class="ai-sub" id="aiSub">
On-chain activity is breaking out while ETF spot demand is steady. Risk-reward favors adding SOL while keeping BTC core untouched.
</div>
<div class="regime-row">
<span class="regime-pill up"><span class="lbl">Trend</span><span class="v">▲ Bullish</span></span>
<span class="regime-pill neut"><span class="lbl">Funding</span><span class="v">+0.012% 8h</span></span>
<span class="regime-pill up"><span class="lbl">ETF flow</span><span class="v">+$418M / 5d</span></span>
<span class="regime-pill down"><span class="lbl">Sentiment</span><span class="v">Greed 78</span></span>
</div>
</div>
<div class="ai-stat-row">
<div class="ai-stat conviction">
<div class="lbl">Conviction</div>
<div class="v" style="color:var(--cyan)" id="convVal">82%</div>
<div class="bar"><div class="fill" id="convFill" style="width:82%"></div></div>
</div>
<div class="ai-stat">
<div class="lbl">3-mo target</div>
<div class="v up">$240</div>
<div class="lbl" style="margin-top:2px;color:var(--up)">+28.7% upside</div>
</div>
<div class="ai-stat">
<div class="lbl">Suggested size</div>
<div class="v">+45 SOL</div>
<div class="lbl" style="margin-top:2px;color:var(--muted)">≈ $8,389 · 4.5% port</div>
</div>
</div>
<!-- Tabs -->
<div class="ai-tabs" role="tablist">
<button class="ai-tab active" data-tab="catalysts">Catalysts <span class="dot bull"></span></button>
<button class="ai-tab" data-tab="risks">Risks <span class="dot risk"></span></button>
<button class="ai-tab" data-tab="position">Position read <span class="dot violet"></span></button>
<button class="ai-tab" data-tab="plan">Trade plan <span class="dot amber"></span></button>
</div>
<!-- Catalysts panel -->
<div class="ai-panel active" data-panel="catalysts">
<div class="ai-thesis">
<h4>Why now · 3 catalysts</h4>
<div class="thesis-list">
<div class="thesis-item">
<div class="thesis-num">01</div>
<div class="thesis-text"><strong>Firedancer mainnet imminent</strong> — Jump's validator client is in canary on testnet. Once live, expect a step-change in throughput narrative. Historical analogs (Sui mainnet, Sept '23) priced 28% in 14 days.</div>
</div>
<div class="thesis-item">
<div class="thesis-num">02</div>
<div class="thesis-text"><strong>DePIN flywheel printing real revenue</strong> — Helium, Render, Hivemapper combined paid <span style="color:var(--up)">$32M+</span> in last quarter. Settlement is on Solana; fee accrual is sticky.</div>
</div>
<div class="thesis-item">
<div class="thesis-num">03</div>
<div class="thesis-text"><strong>BTC ETF flows holding</strong> — net <span style="color:var(--up)">+$418M</span> over the last 5 sessions. Macro tape is risk-on; BTC dominance compressing — usually a green light for L1 rotation.</div>
</div>
</div>
</div>
</div>
<!-- Risks panel -->
<div class="ai-panel" data-panel="risks">
<div class="risk-list">
<div class="risk-row">
<div class="lvl high">High</div>
<div class="body"><strong>Mainnet outage.</strong> Solana has had 6 partial halts in 2 years. A Firedancer rollout could either harden or briefly fracture the network.</div>
<div class="prob">P 24%</div>
</div>
<div class="risk-row">
<div class="lvl med">Med</div>
<div class="body"><strong>FTX estate distributions.</strong> ~14M SOL still unlocked to creditors over 18 months. Recent prints have been absorbed; a fast-paced sale would pressure spot 8–12%.</div>
<div class="prob">P 32%</div>
</div>
<div class="risk-row">
<div class="lvl med">Med</div>
<div class="body"><strong>L2 vs L1 narrative whipsaw.</strong> If Base/Arbitrum capture re-flips dominance, SOL beta to ETH could turn negative for a quarter.</div>
<div class="prob">P 22%</div>
</div>
<div class="risk-row">
<div class="lvl low">Low</div>
<div class="body"><strong>Macro risk-off.</strong> Crypto beta to NDX is ~2.4. A 5% NDX correction would mark down crypto book ~12% even on unchanged thesis. Use stables ladder as buffer.</div>
<div class="prob">P 18%</div>
</div>
</div>
</div>
<!-- Position read panel -->
<div class="ai-panel" data-panel="position">
<div class="pos-read">
<div class="pos-line"><span class="lbl">Avg cost basis · SOL</span><span class="val">$20.46 · 240 SOL</span></div>
<div class="pos-line"><span class="lbl">Mark-to-market</span><span class="val up">$44,740 (+812.4%)</span></div>
<div class="pos-line"><span class="lbl">Holding period</span><span class="val">692 days · earned 18.4 SOL staking</span></div>
<div class="pos-line"><span class="lbl">Position vs target</span><span class="val">24.0% / 30% target — <span style="color:var(--up)">room to add</span></span></div>
<div class="pos-line"><span class="lbl">Wallet</span><span class="val">Phantom · Ledger Nano X</span></div>
<div class="pos-line"><span class="lbl">Beta to BTC</span><span class="val">1.84</span></div>
<div class="pos-narr">
<div class="quote">"Conviction trade in a portfolio that's already worked. Don't let recency bias trim a thesis that's strengthening on every datapoint."</div>
Quiver AI flags <strong>+$418M ETF inflows + Firedancer testnet</strong> as the recent regime change. Funding is calm (+0.012%) — no froth — and on-chain DAU just hit a 90-day high. The cleanest add is on a <strong>−5% wick into $176</strong>; at-mkt is acceptable.
</div>
</div>
</div>
<!-- Trade plan panel -->
<div class="ai-panel" data-panel="plan">
<div class="plan-grid">
<div class="plan-step entry">
<div class="plan-bullet">▲</div>
<div class="plan-label">Entry zone</div>
<div class="plan-zone"><span class="px">$182 — $190</span> &nbsp;<span class="pct up">at-mkt OK</span></div>
</div>
<div class="plan-step entry">
<div class="plan-bullet">+</div>
<div class="plan-label">Add lot 2</div>
<div class="plan-zone"><span class="px">$176</span> on -5% wick &nbsp;<span class="pct">+30 SOL</span></div>
</div>
<div class="plan-step stop">
<div class="plan-bullet">▼</div>
<div class="plan-label">Invalidation</div>
<div class="plan-zone"><span class="px">$162</span> daily close &nbsp;<span class="pct down">−13.0% from spot</span></div>
</div>
<div class="plan-step target">
<div class="plan-bullet">①</div>
<div class="plan-label">Target 1</div>
<div class="plan-zone"><span class="px">$220</span> trim 25% &nbsp;<span class="pct up">+18.0%</span></div>
</div>
<div class="plan-step target">
<div class="plan-bullet">②</div>
<div class="plan-label">Target 2</div>
<div class="plan-zone"><span class="px">$240</span> trim 25% &nbsp;<span class="pct up">+28.7%</span></div>
</div>
<div class="plan-step target">
<div class="plan-bullet">③</div>
<div class="plan-label">Cycle target</div>
<div class="plan-zone"><span class="px">$295</span> trim 50% &nbsp;<span class="pct up">+58.2%</span></div>
</div>
</div>
</div>
<div class="ai-actions">
<button class="btn primary" data-action="add">+ Buy 45 SOL</button>
<button class="btn" data-action="alert">Set price alert</button>
<button class="btn" data-action="thesis">View on-chain thesis</button>
</div>
</div>
</section>
<!-- ─────── Holdings ─────── -->
<div class="card" style="padding:0;">
<div class="panel-head">
<div class="panel-title">Holdings · 9 wallets</div>
<div class="panel-sub">On-chain · last sync 14:32:18 UTC</div>
</div>
<table class="holdings" id="holdingsTable">
<thead>
<tr>
<th>Asset</th>
<th class="num">Amount</th>
<th class="num">Avg cost</th>
<th class="num">Last</th>
<th>Trend · 30d</th>
<th class="num">Market value</th>
<th class="num">Unrealized P&L</th>
<th>Allocation</th>
</tr>
</thead>
<tbody>
<tr data-symbol="BTC" class="active">
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#f7931a,#a35e00);color:#1a0e00;">₿</span>
<span class="name-block">
<span class="sym-name">BTC <a class="ext-mini" href="https://www.coinbase.com/price/bitcoin" target="_blank" rel="noopener" onclick="event.stopPropagation()">Coinbase ↗</a></span>
<span class="sym-co">Bitcoin · Ledger Nano X</span>
</span>
</div>
</td>
<td class="num">1.0842</td>
<td class="num">28,440</td>
<td class="num" style="color:var(--up);font-weight:600;">68,412</td>
<td class="mini-spark">
<svg viewBox="0 0 70 24" preserveAspectRatio="none"><polyline fill="none" stroke="#22e58c" stroke-width="1.4" points="0,18 6,17 12,16 18,15 24,14 30,15 36,13 42,11 48,10 54,8 60,7 66,6 70,5"/></svg>
</td>
<td class="num">$74,170</td>
<td class="num"><span class="pl-cell up"><span class="pl-val">+$43,738</span><span class="pl-pct">+143.7%</span></span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:40%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">39.8%</span></td>
</tr>
<tr data-symbol="SOL">
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#14f195,#9945ff);color:#0a0020;">◎</span>
<span class="name-block">
<span class="sym-name">SOL <a class="ext-mini" href="https://www.coinbase.com/price/solana" target="_blank" rel="noopener" onclick="event.stopPropagation()">Coinbase ↗</a></span>
<span class="sym-co">Solana · Phantom (staked 18.4)</span>
</span>
</div>
</td>
<td class="num">240.00</td>
<td class="num">20.46</td>
<td class="num" style="color:var(--up);font-weight:600;">186.42</td>
<td class="mini-spark">
<svg viewBox="0 0 70 24" preserveAspectRatio="none"><polyline fill="none" stroke="#22e58c" stroke-width="1.4" points="0,20 6,18 12,17 18,15 24,14 30,12 36,10 42,8 48,9 54,6 60,5 66,3 70,2"/></svg>
</td>
<td class="num">$44,740</td>
<td class="num"><span class="pl-cell up"><span class="pl-val">+$39,830</span><span class="pl-pct">+812.4%</span></span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:24%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">24.0%</span></td>
</tr>
<tr data-symbol="ETH">
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#627eea,#3c5ed7);color:#fff;">Ξ</span>
<span class="name-block">
<span class="sym-name">ETH <a class="ext-mini" href="https://www.coinbase.com/price/ethereum" target="_blank" rel="noopener" onclick="event.stopPropagation()">Coinbase ↗</a></span>
<span class="sym-co">Ethereum · MetaMask · 8.4 staked</span>
</span>
</div>
</td>
<td class="num">10.62</td>
<td class="num">1,820</td>
<td class="num" style="color:var(--up);font-weight:600;">3,612</td>
<td class="mini-spark">
<svg viewBox="0 0 70 24" preserveAspectRatio="none"><polyline fill="none" stroke="#22e58c" stroke-width="1.4" points="0,17 6,16 12,17 18,15 24,14 30,15 36,12 42,11 48,12 54,9 60,8 66,7 70,6"/></svg>
</td>
<td class="num">$38,360</td>
<td class="num"><span class="pl-cell up"><span class="pl-val">+$19,032</span><span class="pl-pct">+98.5%</span></span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:21%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">20.6%</span></td>
</tr>
<tr data-symbol="LINK">
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#2a5ada,#1d3ea0);color:#fff;">L</span>
<span class="name-block">
<span class="sym-name">LINK <a class="ext-mini" href="https://www.coinbase.com/price/chainlink" target="_blank" rel="noopener" onclick="event.stopPropagation()">Coinbase ↗</a></span>
<span class="sym-co">Chainlink · MetaMask</span>
</span>
</div>
</td>
<td class="num">680</td>
<td class="num">8.42</td>
<td class="num" style="color:var(--up);font-weight:600;">14.92</td>
<td class="mini-spark">
<svg viewBox="0 0 70 24" preserveAspectRatio="none"><polyline fill="none" stroke="#22e58c" stroke-width="1.4" points="0,18 6,17 12,15 18,14 24,15 30,12 36,11 42,12 48,10 54,8 60,7 66,6 70,5"/></svg>
</td>
<td class="num">$10,146</td>
<td class="num"><span class="pl-cell up"><span class="pl-val">+$4,420</span><span class="pl-pct">+77.2%</span></span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:6%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">5.4%</span></td>
</tr>
<tr data-symbol="AVAX">
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#e84142,#7a0d0e);color:#fff;">A</span>
<span class="name-block">
<span class="sym-name">AVAX <a class="ext-mini" href="https://www.coinbase.com/price/avalanche" target="_blank" rel="noopener" onclick="event.stopPropagation()">Coinbase ↗</a></span>
<span class="sym-co">Avalanche · Core wallet</span>
</span>
</div>
</td>
<td class="num">220</td>
<td class="num">28.40</td>
<td class="num" style="color:var(--up);font-weight:600;">36.84</td>
<td class="mini-spark">
<svg viewBox="0 0 70 24" preserveAspectRatio="none"><polyline fill="none" stroke="#22e58c" stroke-width="1.4" points="0,16 6,15 12,17 18,14 24,12 30,13 36,11 42,10 48,12 54,9 60,8 66,7 70,7"/></svg>
</td>
<td class="num">$8,105</td>
<td class="num"><span class="pl-cell up"><span class="pl-val">+$1,857</span><span class="pl-pct">+29.7%</span></span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:5%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">4.4%</span></td>
</tr>
<tr data-symbol="DOT">
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#e6007a,#860146);color:#fff;">●</span>
<span class="name-block">
<span class="sym-name">DOT <a class="ext-mini" href="https://www.coinbase.com/price/polkadot" target="_blank" rel="noopener" onclick="event.stopPropagation()">Coinbase ↗</a></span>
<span class="sym-co">Polkadot · Polkadot.js</span>
</span>
</div>
</td>
<td class="num">600</td>
<td class="num">9.10</td>
<td class="num" style="color:var(--down);font-weight:600;">7.18</td>
<td class="mini-spark">
<svg viewBox="0 0 70 24" preserveAspectRatio="none"><polyline fill="none" stroke="#ff4f6d" stroke-width="1.4" points="0,10 6,11 12,9 18,12 24,13 30,11 36,14 42,15 48,13 54,16 60,17 66,18 70,18"/></svg>
</td>
<td class="num">$4,308</td>
<td class="num"><span class="pl-cell down"><span class="pl-val">−$1,152</span><span class="pl-pct">−21.1%</span></span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:3%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">2.3%</span></td>
</tr>
<tr data-symbol="DOGE">
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#c2a633,#8a7321);color:#1a1100;">Ð</span>
<span class="name-block">
<span class="sym-name">DOGE <a class="ext-mini" href="https://www.coinbase.com/price/dogecoin" target="_blank" rel="noopener" onclick="event.stopPropagation()">Coinbase ↗</a></span>
<span class="sym-co">Dogecoin · Hot wallet</span>
</span>
</div>
</td>
<td class="num">12,400</td>
<td class="num">0.082</td>
<td class="num" style="color:var(--up);font-weight:600;">0.1842</td>
<td class="mini-spark">
<svg viewBox="0 0 70 24" preserveAspectRatio="none"><polyline fill="none" stroke="#22e58c" stroke-width="1.4" points="0,20 6,18 12,17 18,15 24,18 30,14 36,12 42,15 48,10 54,8 60,7 66,5 70,4"/></svg>
</td>
<td class="num">$2,284</td>
<td class="num"><span class="pl-cell up"><span class="pl-val">+$1,267</span><span class="pl-pct">+124.6%</span></span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:1%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">1.2%</span></td>
</tr>
<tr>
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#2775ca,#1a4f8a);color:#fff;">$</span>
<span class="name-block">
<span class="sym-name">USDC stables</span>
<span class="sym-co">Coinbase + Aave (4.92% APY)</span>
</span>
</div>
</td>
<td class="num">18,420</td>
<td class="num">1.00</td>
<td class="num">1.00</td>
<td>—</td>
<td class="num">$18,420</td>
<td class="num"><span style="color:var(--up);font-family:var(--mono);">+$842 yield</span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:9%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">9.9%</span></td>
</tr>
<tr>
<td>
<div class="sym-cell">
<span class="sym-mark" style="background:linear-gradient(135deg,#26a17b,#136f50);color:#fff;">T</span>
<span class="name-block">
<span class="sym-name">USDT stables</span>
<span class="sym-co">Tron · cold storage</span>
</span>
</div>
</td>
<td class="num">5,780</td>
<td class="num">1.00</td>
<td class="num">1.00</td>
<td>—</td>
<td class="num">$5,780</td>
<td class="num"><span style="color:var(--muted);font-family:var(--mono);">—</span></td>
<td><span class="alloc-bar"><span class="alloc-fill" style="width:3%"></span></span> &nbsp;<span style="font-family:var(--mono);font-size:11px;color:var(--muted)">3.1%</span></td>
</tr>
</tbody>
</table>
</div>
<!-- ─────── Watchlist + News two-column ─────── -->
<section class="grid-2">
<div class="card" style="padding:0;">
<div class="panel-head">
<div class="panel-title">Watchlist · L1 + L2 + DePIN</div>
<div class="panel-sub">14 tickers · 14:32 UTC</div>
</div>
<div class="watch-grid" style="padding:14px;">
<div class="watch-card">
<div class="watch-top">
<div><div class="watch-sym">SUI</div><div class="watch-co">Sui Network</div></div>
<span class="watch-pct up">▲ 8.40%</span>
</div>
<div class="watch-px">$2.18</div>
<div class="watch-spark">
<svg viewBox="0 0 200 40" preserveAspectRatio="none">
<defs><linearGradient id="ws1" x1="0" x2="0" y1="0" y2="1"><stop offset="0%" stop-color="#22e58c" stop-opacity="0.4"/><stop offset="100%" stop-color="#22e58c" stop-opacity="0"/></linearGradient></defs>
<polygon fill="url(#ws1)" points="0,32 0,28 20,30 40,26 60,24 80,28 100,22 120,18 140,20 160,14 180,10 200,6 200,40 0,40"/>
<polyline fill="none" stroke="#22e58c" stroke-width="1.6" points="0,28 20,30 40,26 60,24 80,28 100,22 120,18 140,20 160,14 180,10 200,6"/>
</svg>
</div>
<div class="watch-foot">
<span>Vol $812M</span>
<a href="https://www.coinbase.com/price/sui" target="_blank" rel="noopener">Coinbase ↗</a>
</div>
</div>
<div class="watch-card">
<div class="watch-top">
<div><div class="watch-sym">DOGE</div><div class="watch-co">Dogecoin</div></div>
<span class="watch-pct up">▲ 6.84%</span>
</div>
<div class="watch-px">$0.1842</div>
<div class="watch-spark">
<svg viewBox="0 0 200 40" preserveAspectRatio="none">
<polygon fill="url(#ws1)" points="0,30 0,26 20,28 40,22 60,24 80,20 100,22 120,18 140,16 160,12 180,14 200,10 200,40 0,40"/>
<polyline fill="none" stroke="#22e58c" stroke-width="1.6" points="0,26 20,28 40,22 60,24 80,20 100,22 120,18 140,16 160,12 180,14 200,10"/>
</svg>
</div>
<div class="watch-foot">
<span>Vol $1.4B</span>
<a href="https://www.coinbase.com/price/dogecoin" target="_blank" rel="noopener">Coinbase ↗</a>
</div>
</div>
<div class="watch-card">
<div class="watch-top">
<div><div class="watch-sym">AVAX</div><div class="watch-co">Avalanche</div></div>
<span class="watch-pct up">▲ 2.86%</span>
</div>
<div class="watch-px">$36.84</div>
<div class="watch-spark">
<svg viewBox="0 0 200 40" preserveAspectRatio="none">
<polygon fill="url(#ws1)" points="0,28 0,24 20,22 40,26 60,20 80,18 100,20 120,16 140,14 160,12 180,10 200,8 200,40 0,40"/>
<polyline fill="none" stroke="#22e58c" stroke-width="1.6" points="0,24 20,22 40,26 60,20 80,18 100,20 120,16 140,14 160,12 180,10 200,8"/>
</svg>
</div>
<div class="watch-foot">
<span>Vol $640M</span>
<a href="https://www.coinbase.com/price/avalanche" target="_blank" rel="noopener">Coinbase ↗</a>
</div>
</div>
<div class="watch-card">
<div class="watch-top">
<div><div class="watch-sym">NEAR</div><div class="watch-co">NEAR Protocol</div></div>
<span class="watch-pct up">▲ 3.20%</span>
</div>
<div class="watch-px">$5.42</div>
<div class="watch-spark">
<svg viewBox="0 0 200 40" preserveAspectRatio="none">
<polygon fill="url(#ws1)" points="0,32 0,28 20,30 40,26 60,28 80,22 100,18 120,20 140,14 160,10 180,12 200,6 200,40 0,40"/>
<polyline fill="none" stroke="#22e58c" stroke-width="1.6" points="0,28 20,30 40,26 60,28 80,22 100,18 120,20 140,14 160,10 180,12 200,6"/>
</svg>
</div>
<div class="watch-foot">
<span>Vol $312M</span>
<a href="https://www.coinbase.com/price/near-protocol" target="_blank" rel="noopener">Coinbase ↗</a>
</div>
</div>
<div class="watch-card">
<div class="watch-top">
<div><div class="watch-sym">TIA</div><div class="watch-co">Celestia</div></div>
<span class="watch-pct down">▼ 1.18%</span>
</div>
<div class="watch-px">$7.84</div>
<div class="watch-spark">
<svg viewBox="0 0 200 40" preserveAspectRatio="none">
<defs><linearGradient id="ws2" x1="0" x2="0" y1="0" y2="1"><stop offset="0%" stop-color="#ff4f6d" stop-opacity="0.4"/><stop offset="100%" stop-color="#ff4f6d" stop-opacity="0"/></linearGradient></defs>
<polygon fill="url(#ws2)" points="0,10 0,14 20,12 40,16 60,14 80,18 100,20 120,18 140,22 160,24 180,28 200,30 200,40 0,40"/>
<polyline fill="none" stroke="#ff4f6d" stroke-width="1.6" points="0,14 20,12 40,16 60,14 80,18 100,20 120,18 140,22 160,24 180,28 200,30"/>
</svg>
</div>
<div class="watch-foot">
<span>Vol $84M</span>
<a href="https://www.coinbase.com/price/celestia" target="_blank" rel="noopener">Coinbase ↗</a>
</div>
</div>
<div class="watch-card">
<div class="watch-top">
<div><div class="watch-sym">ARB</div><div class="watch-co">Arbitrum</div></div>
<span class="watch-pct up">▲ 0.95%</span>
</div>
<div class="watch-px">$0.6420</div>
<div class="watch-spark">
<svg viewBox="0 0 200 40" preserveAspectRatio="none">
<polygon fill="url(#ws1)" points="0,34 0,30 20,32 40,28 60,30 80,26 100,20 120,16 140,18 160,12 180,8 200,4 200,40 0,40"/>
<polyline fill="none" stroke="#22e58c" stroke-width="1.6" points="0,30 20,32 40,28 60,30 80,26 100,20 120,16 140,18 160,12 180,8 200,4"/>
</svg>
</div>
<div class="watch-foot">
<span>Vol $186M</span>
<a href="https://www.coinbase.com/price/arbitrum" target="_blank" rel="noopener">Coinbase ↗</a>
</div>
</div>
<div class="watch-card">
<div class="watch-top">
<div><div class="watch-sym">BNB</div><div class="watch-co">BNB Chain</div></div>
<span class="watch-pct up">▲ 0.94%</span>
</div>
<div class="watch-px">$612.40</div>
<div class="watch-spark">
<svg viewBox="0 0 200 40" preserveAspectRatio="none">
<polygon fill="url(#ws1)" points="0,28 0,24 20,26 40,22 60,20 80,22 100,18 120,16 140,18 160,14 180,12 200,12 200,40 0,40"/>
<polyline fill="none" stroke="#22e58c" stroke-width="1.6" points="0,24 20,26 40,22 60,20 80,22 100,18 120,16 140,18 160,14 180,12 200,12"/>
</svg>
</div>
<div class="watch-foot">
<span>Vol $920M</span>
<a href="https://www.coinbase.com/price/bnb" target="_blank" rel="noopener">Coinbase ↗</a>
</div>
</div>
<div class="watch-card">
<div class="watch-top">
<div><div class="watch-sym">XRP</div><div class="watch-co">Ripple</div></div>
<span class="watch-pct down">▼ 0.42%</span>
</div>
<div class="watch-px">$0.5824</div>
<div class="watch-spark">
<svg viewBox="0 0 200 40" preserveAspectRatio="none">
<polygon fill="url(#ws2)" points="0,12 0,16 20,14 40,18 60,16 80,18 100,16 120,20 140,18 160,22 180,20 200,24 200,40 0,40"/>
<polyline fill="none" stroke="#ff4f6d" stroke-width="1.6" points="0,16 20,14 40,18 60,16 80,18 100,16 120,20 140,18 160,22 180,20 200,24"/>
</svg>
</div>
<div class="watch-foot">
<span>Vol $1.2B</span>
<a href="https://www.coinbase.com/price/xrp" target="_blank" rel="noopener">Coinbase ↗</a>
</div>
</div>
</div>
</div>
<!-- News / signals feed -->
<div class="card" style="padding:0;">
<div class="panel-head">
<div class="panel-title">Live signals & news</div>
<div class="panel-sub">Filtered to your book</div>
</div>
<div class="news-list">
<div class="news-item">
<div class="news-time">14:31</div>
<div class="news-body">
<div class="src"><span class="tag">AI</span> On-chain anomaly</div>
<div class="head"><span class="t-up">▲</span> Solana DEX volume rolling 7d up <strong>+62%</strong> — Jupiter + Raydium printing record fee days. Settlement layer thesis intact.</div>
<div class="syms"><span class="s">SOL</span><span class="s">JUP</span></div>
</div>
<span class="impact pos">+ Bull</span>
</div>
<div class="news-item">
<div class="news-time">14:18</div>
<div class="news-body">
<div class="src">CoinDesk</div>
<div class="head">Spot BTC ETFs see <strong>+$418M</strong> net inflows over 5 sessions; IBIT alone takes 70%, FBTC 22%.</div>
<div class="syms"><span class="s">BTC</span><span class="s">IBIT</span></div>
</div>
<span class="impact pos">+ Bull</span>
</div>
<div class="news-item">
<div class="news-time">13:54</div>
<div class="news-body">
<div class="src">The Block</div>
<div class="head">Firedancer canary deployment hits <strong>1.2M TPS</strong> in stress test — full mainnet rollout targeted Q4.</div>
<div class="syms"><span class="s">SOL</span></div>
</div>
<span class="impact pos">+ Bull</span>
</div>
<div class="news-item">
<div class="news-time">13:30</div>
<div class="news-body">
<div class="src"><span class="tag">AI</span> Funding tracker</div>
<div class="head">Perp funding turning slightly positive across L1s; ETH 8h funding now <strong>+0.014%</strong>. Watch for crowding on next 5% leg.</div>
<div class="syms"><span class="s">ETH</span><span class="s">SOL</span></div>
</div>
<span class="impact med">Watch</span>
</div>
<div class="news-item">
<div class="news-time">12:48</div>
<div class="news-body">
<div class="src">Bloomberg</div>
<div class="head">SEC granted certificate of effectiveness on <strong>spot ETH staking ETF</strong> filing — issuer confirmation expected within 7 days.</div>
<div class="syms"><span class="s">ETH</span></div>
</div>
<span class="impact pos">+ Bull</span>
</div>
<div class="news-item">
<div class="news-time">11:22</div>
<div class="news-body">
<div class="src"><span class="tag">AI</span> Whale flow</div>
<div class="head"><span class="t-down">▼</span> 12,400 ETH moved from Genesis estate cold wallet to OKX — 6h. Possible distribution; watch order books.</div>
<div class="syms"><span class="s">ETH</span></div>
</div>
<span class="impact high">High risk</span>
</div>
</div>
</div>
</section>
</main>
<!-- Toast container -->
<div class="toast-box" id="toastBox" aria-live="polite"></div>
<footer class="app">
<div class="left">
<span class="live">On-chain feed — block 287,418,612</span>
<span>Quotes via Coinbase + CoinGecko · Glassnode for on-chain</span>
</div>
<div class="right">
<span>Quiver Crypto · v1.4.0</span>
<span>Source: Coinbase + Phantom + Ledger linked</span>
<span>Last sync <span id="lastSync">14:32:18 UTC</span></span>
</div>
</footer>
<script>
(() => {
// ─────── Ticker registry (crypto) ───────
// Each asset carries display data + archetype for candle rendering.
// Three external links per asset: primary exchange, secondary exchange, market data site.
const TICKERS = {
BTC: {
name: 'Bitcoin', sector: 'L1 · Mkt cap $1.35T · Dominance 54.2%',
icon: '₿', bg: 'linear-gradient(135deg,#f7931a,#a35e00)', fg: '#1a0e00',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/bitcoin',
yahoo: 'https://www.binance.com/en/price/bitcoin',
tv: 'https://coinmarketcap.com/currencies/bitcoin/' },
px: 68412, dayChg: 1236.40, dayPct: 1.84, dir: 'up',
open: 67176, high: 68920, low: 66840, vol: '$28.4B', pe: '19.74M / 21M', range52: '$73,750 (Mar)',
arch: 'bull-strong'
},
ETH: {
name: 'Ethereum', sector: 'L1 · Mkt cap $435B · Gas 12 gwei',
icon: 'Ξ', bg: 'linear-gradient(135deg,#627eea,#3c5ed7)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/ethereum',
yahoo: 'https://www.binance.com/en/price/ethereum',
tv: 'https://coinmarketcap.com/currencies/ethereum/' },
px: 3612, dayChg: 74.20, dayPct: 2.10, dir: 'up',
open: 3540, high: 3624, low: 3522, vol: '$14.2B', pe: '120.4M', range52: '$4,890 (Mar)',
arch: 'bull-mild'
},
SOL: {
name: 'Solana', sector: 'L1 · Mkt cap $87B · DePIN settlement',
icon: '◎', bg: 'linear-gradient(135deg,#14f195,#9945ff)', fg: '#0a0020',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/solana',
yahoo: 'https://www.binance.com/en/price/solana',
tv: 'https://coinmarketcap.com/currencies/solana/' },
px: 186.42, dayChg: 8.22, dayPct: 4.62, dir: 'up',
open: 178.20, high: 188.40, low: 177.30, vol: '$3.8B', pe: '466.4M', range52: '$259 (Mar)',
arch: 'bull-strong'
},
BNB: {
name: 'BNB', sector: 'L1 · Mkt cap $89B · Native to BNB Chain',
icon: 'B', bg: 'linear-gradient(135deg,#f3ba2f,#a17500)', fg: '#1a0f00',
exch: 'Binance',
links: { exch: 'https://www.binance.com/en/price/bnb',
yahoo: 'https://www.coinbase.com/price/bnb',
tv: 'https://coinmarketcap.com/currencies/bnb/' },
px: 612.40, dayChg: 5.72, dayPct: 0.94, dir: 'up',
open: 606.80, high: 614.20, low: 604.40, vol: '$920M', pe: '147.6M', range52: '$721 (Jun)',
arch: 'bull-mild'
},
XRP: {
name: 'XRP', sector: 'Payments · Mkt cap $33B',
icon: 'X', bg: 'linear-gradient(135deg,#23292f,#000)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/xrp',
yahoo: 'https://www.binance.com/en/price/xrp',
tv: 'https://coinmarketcap.com/currencies/xrp/' },
px: 0.5824, dayChg: -0.0024, dayPct: -0.42, dir: 'down',
open: 0.5848, high: 0.5896, low: 0.5780, vol: '$1.2B', pe: '56.6B', range52: '$0.74 (Jul)',
arch: 'choppy'
},
ADA: {
name: 'Cardano', sector: 'L1 · Mkt cap $15.4B',
icon: 'A', bg: 'linear-gradient(135deg,#0033ad,#001a5e)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/cardano',
yahoo: 'https://www.binance.com/en/price/cardano',
tv: 'https://coinmarketcap.com/currencies/cardano/' },
px: 0.4318, dayChg: 0.0051, dayPct: 1.20, dir: 'up',
open: 0.4267, high: 0.4332, low: 0.4240, vol: '$284M', pe: '35.7B', range52: '$0.81 (Mar)',
arch: 'bull-mild'
},
DOGE: {
name: 'Dogecoin', sector: 'Memecoin · Mkt cap $27B',
icon: 'Ð', bg: 'linear-gradient(135deg,#c2a633,#8a7321)', fg: '#1a1100',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/dogecoin',
yahoo: 'https://www.binance.com/en/price/dogecoin',
tv: 'https://coinmarketcap.com/currencies/dogecoin/' },
px: 0.1842, dayChg: 0.0118, dayPct: 6.84, dir: 'up',
open: 0.1724, high: 0.1872, low: 0.1716, vol: '$1.4B', pe: '146.6B', range52: '$0.225 (Mar)',
arch: 'bull-strong'
},
AVAX: {
name: 'Avalanche', sector: 'L1 · Mkt cap $14.4B',
icon: 'A', bg: 'linear-gradient(135deg,#e84142,#7a0d0e)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/avalanche',
yahoo: 'https://www.binance.com/en/price/avalanche',
tv: 'https://coinmarketcap.com/currencies/avalanche/' },
px: 36.84, dayChg: 1.02, dayPct: 2.86, dir: 'up',
open: 35.82, high: 37.20, low: 35.40, vol: '$640M', pe: '391.2M', range52: '$58.20 (Mar)',
arch: 'bull-mild'
},
LINK: {
name: 'Chainlink', sector: 'Oracle · Mkt cap $9.0B',
icon: 'L', bg: 'linear-gradient(135deg,#2a5ada,#1d3ea0)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/chainlink',
yahoo: 'https://www.binance.com/en/price/chainlink',
tv: 'https://coinmarketcap.com/currencies/chainlink/' },
px: 14.92, dayChg: 0.21, dayPct: 1.40, dir: 'up',
open: 14.71, high: 14.96, low: 14.62, vol: '$248M', pe: '604.7M', range52: '$22.30 (Mar)',
arch: 'bull-mild'
},
DOT: {
name: 'Polkadot', sector: 'L0 · Mkt cap $9.6B',
icon: '●', bg: 'linear-gradient(135deg,#e6007a,#860146)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/polkadot',
yahoo: 'https://www.binance.com/en/price/polkadot',
tv: 'https://coinmarketcap.com/currencies/polkadot/' },
px: 7.18, dayChg: -0.045, dayPct: -0.62, dir: 'down',
open: 7.225, high: 7.26, low: 7.14, vol: '$192M', pe: '1.34B', range52: '$11.84 (Mar)',
arch: 'bear'
},
SUI: {
name: 'Sui Network', sector: 'L1 · Mkt cap $5.8B',
icon: 'S', bg: 'linear-gradient(135deg,#4ca3df,#1a4f8a)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/sui',
yahoo: 'https://www.binance.com/en/price/sui',
tv: 'https://coinmarketcap.com/currencies/sui/' },
px: 2.18, dayChg: 0.169, dayPct: 8.40, dir: 'up',
open: 2.011, high: 2.21, low: 1.992, vol: '$812M', pe: '2.66B', range52: '$2.21 (today)',
arch: 'bull-strong'
},
NEAR: {
name: 'NEAR Protocol', sector: 'L1 · Mkt cap $5.9B',
icon: 'N', bg: 'linear-gradient(135deg,#000,#5f6368)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/near-protocol',
yahoo: 'https://www.binance.com/en/price/near-protocol',
tv: 'https://coinmarketcap.com/currencies/near-protocol/' },
px: 5.42, dayChg: 0.168, dayPct: 3.20, dir: 'up',
open: 5.252, high: 5.46, low: 5.21, vol: '$312M', pe: '1.09B', range52: '$8.46 (Mar)',
arch: 'bull-strong'
},
TIA: {
name: 'Celestia', sector: 'Modular DA · Mkt cap $1.6B',
icon: 'T', bg: 'linear-gradient(135deg,#7b2bf9,#3d0e8c)', fg: '#ffffff',
exch: 'Binance',
links: { exch: 'https://www.binance.com/en/price/celestia',
yahoo: 'https://www.coinbase.com/price/celestia',
tv: 'https://coinmarketcap.com/currencies/celestia/' },
px: 7.84, dayChg: -0.094, dayPct: -1.18, dir: 'down',
open: 7.934, high: 7.96, low: 7.74, vol: '$84M', pe: '204M', range52: '$20.80 (Feb)',
arch: 'bear'
},
ARB: {
name: 'Arbitrum', sector: 'L2 · Mkt cap $2.7B',
icon: 'A', bg: 'linear-gradient(135deg,#28a0f0,#1565a8)', fg: '#ffffff',
exch: 'Coinbase',
links: { exch: 'https://www.coinbase.com/price/arbitrum',
yahoo: 'https://www.binance.com/en/price/arbitrum',
tv: 'https://coinmarketcap.com/currencies/arbitrum/' },
px: 0.6420, dayChg: 0.006, dayPct: 0.95, dir: 'up',
open: 0.636, high: 0.648, low: 0.632, vol: '$186M', pe: '4.21B', range52: '$2.39 (Jan)',
arch: 'bull-mild'
}
};
// ─────── Candle templates ───────
// Each template is a series of SVG strings rendering 21 candles + SMA line.
// Coordinates fit the existing chart viewBox 880×320, padding-left 40.
// Generate procedurally based on archetype for variety without bloating HTML.
function generateCandles(arch) {
// closes[] is 21 normalised closes (0=top of band, 1=bottom)
let closes;
switch (arch) {
case 'bull-strong':
closes = [0.78, 0.72, 0.74, 0.66, 0.62, 0.66, 0.58, 0.54, 0.58, 0.46, 0.40, 0.44, 0.36, 0.30, 0.34, 0.26, 0.22, 0.26, 0.18, 0.14, 0.10];
break;
case 'bull-mild':
closes = [0.66, 0.64, 0.68, 0.60, 0.58, 0.62, 0.54, 0.52, 0.54, 0.48, 0.46, 0.50, 0.42, 0.40, 0.44, 0.36, 0.34, 0.38, 0.30, 0.28, 0.24];
break;
case 'choppy':
closes = [0.42, 0.50, 0.40, 0.46, 0.54, 0.48, 0.58, 0.50, 0.56, 0.46, 0.52, 0.42, 0.50, 0.58, 0.50, 0.56, 0.48, 0.54, 0.44, 0.50, 0.46];
break;
case 'bear':
closes = [0.22, 0.26, 0.30, 0.28, 0.34, 0.38, 0.36, 0.42, 0.46, 0.44, 0.50, 0.54, 0.52, 0.58, 0.62, 0.60, 0.66, 0.70, 0.68, 0.74, 0.78];
break;
default:
closes = Array.from({length:21}, (_,i)=>0.5);
}
// map normalised position to y in [38, 248] (top=high price area)
const yMap = (n) => 38 + n * 210;
let parts = [];
let prev = 0.5; // open of first bar
let smaPts = [];
for (let i = 0; i < closes.length; i++) {
const x = 44 + i * 40;
const close = closes[i];
const open = prev;
const yOpen = yMap(open);
const yClose = yMap(close);
const isUp = close < open; // close higher (lower y) = up
const color = isUp ? '#22e58c' : '#ff4f6d';
// wick: tiny extension above and below body
const wickHi = Math.min(yOpen, yClose) - 6 - Math.random()*4;
const wickLo = Math.max(yOpen, yClose) + 6 + Math.random()*4;
const top = Math.min(yOpen, yClose);
const bot = Math.max(yOpen, yClose);
parts.push(`<line x1="${x}" y1="${wickHi}" x2="${x}" y2="${wickLo}" stroke="${color}" stroke-width="1.2"/>`);
parts.push(`<rect x="${x-6}" y="${top}" width="12" height="${Math.max(2, bot-top)}" fill="${color}"/>`);
smaPts.push(`${x},${yMap(close)+8}`);
prev = close;
}
const smaLine = `<polyline fill="none" stroke="#facc15" stroke-opacity="0.55" stroke-width="1.4" stroke-dasharray="3 3" points="${smaPts.join(' ')}"/>`;
// last close y for current px marker
const lastY = yMap(closes[closes.length - 1]);
return { candles: parts.join(''), sma: smaLine, lastY };
}
function renderCandleSvg(arch, displayPx) {
const { candles, sma, lastY } = generateCandles(arch);
const inner = `
<defs>
<pattern id="grid2" x="0" y="0" width="80" height="40" patternUnits="userSpaceOnUse">
<path d="M 80 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.035)" stroke-width="1"/>
</pattern>
</defs>
<rect width="880" height="320" fill="url(#grid2)"/>
<g font-family="Geist Mono,monospace" font-size="10" fill="#6b7689">
<text x="6" y="36">High</text>
<text x="6" y="96">·</text>
<text x="6" y="156">Mid</text>
<text x="6" y="216">·</text>
<text x="6" y="276">Low</text>
</g>
${sma}
${candles}
<g font-family="Geist Mono,monospace" font-size="10">
<circle cx="284" cy="200" r="5" fill="#facc15"/>
<text x="296" y="204" fill="#facc15">BUY · journal entry</text>
</g>
<g id="pxMarker">
<line x1="40" y1="${lastY}" x2="844" y2="${lastY}" stroke="rgba(34,229,140,0.25)" stroke-dasharray="2 3"/>
<rect x="836" y="${lastY-9}" width="40" height="18" fill="#22e58c" rx="3"/>
<text id="pxMarkerText" x="856" y="${lastY+4}" font-family="Geist Mono,monospace" font-size="10" fill="#00200f" text-anchor="middle" font-weight="700">${displayPx}</text>
</g>
`;
return inner;
}
function renderVolumeSvg(arch) {
// Rough volume pattern: amplify with archetype.
let heights;
if (arch === 'bull-strong') heights = [20, 26, 22, 30, 24, 20, 34, 28, 26, 48, 24, 26, 40, 26, 24, 50, 28, 22, 44, 36, 56];
else if (arch === 'bull-mild') heights = [18, 22, 26, 24, 20, 28, 22, 24, 18, 32, 28, 24, 22, 30, 26, 20, 28, 24, 30, 26, 32];
else if (arch === 'choppy') heights = [22, 36, 18, 42, 28, 32, 24, 38, 22, 30, 36, 22, 30, 26, 38, 22, 32, 28, 36, 22, 30];
else if (arch === 'bear') heights = [40, 32, 36, 28, 30, 26, 24, 30, 28, 26, 32, 24, 30, 26, 22, 28, 22, 24, 22, 26, 28];
else heights = Array.from({length: 21}, () => 24);
const bars = heights.map((h, i) => {
const x = 38 + i * 40;
const y = 70 - h;
const upish = (i % 3) !== 1; // arbitrary
const c = upish ? '#22e58c' : '#ff4f6d';
const op = upish ? 0.55 : 0.55;
return `<rect x="${x}" y="${y}" width="12" height="${h}" fill="${c}" opacity="${op}"/>`;
}).join('');
return `<g>${bars}</g>
<text x="6" y="14" font-family="Geist Mono,monospace" font-size="10" fill="#6b7689">VOL</text>
<text x="6" y="64" font-family="Geist Mono,monospace" font-size="10" fill="#6b7689">avg</text>`;
}
// ─────── Toast ───────
const toastBox = document.getElementById('toastBox');
function toast(opts) {
const { kind = 'info', title = 'Notice', body = '' } = (typeof opts === 'string') ? { body: opts } : opts;
const node = document.createElement('div');
node.className = `toast ${kind}`;
node.innerHTML = `
<div class="t-head">
<span class="t-title">${title}</span>
<button class="t-close" aria-label="Dismiss">×</button>
</div>
<div class="t-body">${body}</div>
`;
toastBox.appendChild(node);
const close = () => {
node.classList.add('fade');
setTimeout(() => node.remove(), 280);
};
node.querySelector('.t-close').addEventListener('click', close);
setTimeout(close, 4200);
}
// ─────── Live clock + block ───────
const clockEl = document.getElementById('liveClock');
const lastSyncEl = document.getElementById('lastSync');
const blockHEl = document.getElementById('blockH');
let blockH = 287418612;
// Block ticks roughly every 12 seconds (Ethereum-ish cadence).
setInterval(() => {
blockH += 1;
if (blockHEl) blockHEl.textContent = blockH.toLocaleString('en-US');
}, 12000);
// Anchor to a fake market-day start so it ticks like a real session.
let clockSeconds = 14 * 3600 + 32 * 60 + 18;
function fmtClock(s) {
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')} UTC`;
}
setInterval(() => {
clockSeconds++;
if (clockEl) clockEl.textContent = fmtClock(clockSeconds);
}, 1000);
// ─────── Chart switcher ───────
let activeSym = 'BTC';
const tkIcon = document.getElementById('tkIcon');
const tkSym = document.getElementById('tkSym');
const tkName = document.getElementById('tkName');
const tkPx = document.getElementById('tkPx');
const tkDelta = document.getElementById('tkDelta');
const tkLinkExch = document.getElementById('tkLinkExch');
const tkLinkYahoo = document.getElementById('tkLinkYahoo');
const tkLinkTV = document.getElementById('tkLinkTV');
const tkStats = document.getElementById('tkStats');
const candleSvg = document.getElementById('candleSvg');
const volSvg = document.getElementById('volSvg');
const chartCard = document.getElementById('chartCard');
// Smart precision: high-priced coins (BTC, ETH) round to whole dollars;
// mid-priced (e.g. SOL) keep 2 decimals; sub-dollar (XRP, ADA, DOGE) use 4.
function fmtPx(p) {
if (p == null) return '$0.00';
const a = Math.abs(p);
if (a >= 1000) return '$' + p.toLocaleString('en-US', { maximumFractionDigits: 0 });
if (a >= 1) return '$' + p.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
return '$' + p.toLocaleString('en-US', { minimumFractionDigits: 4, maximumFractionDigits: 4 });
}
function fmtDelta(d) {
const sign = d >= 0 ? '+' : '−';
const v = Math.abs(d);
let s;
if (v >= 1000) s = v.toLocaleString('en-US', { maximumFractionDigits: 0 });
else if (v >= 1) s = v.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
else s = v.toLocaleString('en-US', { minimumFractionDigits: 4, maximumFractionDigits: 4 });
return `${sign}$${s}`;
}
function fmtPct(p) {
const sign = p >= 0 ? '+' : '−';
return `${sign}${Math.abs(p).toFixed(2)}%`;
}
function applyTicker(sym) {
const t = TICKERS[sym];
if (!t) return;
activeSym = sym;
tkIcon.textContent = t.icon;
tkIcon.style.background = t.bg;
tkIcon.style.color = t.fg;
tkSym.textContent = sym;
tkName.textContent = `${t.name} · ${t.sector}`;
const upish = t.dir === 'up';
tkPx.style.color = upish ? 'var(--up)' : 'var(--down)';
tkPx.textContent = fmtPx(t.px);
tkDelta.style.color = upish ? 'var(--up)' : 'var(--down)';
tkDelta.innerHTML = `${upish ? '▲' : '▼'} ${fmtDelta(t.dayChg)} &nbsp; ${fmtPct(t.dayPct)} today`;
tkLinkExch.href = t.links.exch;
tkLinkExch.firstChild.textContent = `${t.exch} `;
tkLinkYahoo.href = t.links.yahoo;
tkLinkTV.href = t.links.tv;
// Stats
const setStat = (k, v) => {
const el = tkStats.querySelector(`[data-stat="${k}"]`);
if (el) el.textContent = v;
};
const fmtBare = (n) => {
const a = Math.abs(n);
if (a >= 1000) return n.toLocaleString('en-US', { maximumFractionDigits: 0 });
if (a >= 1) return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
return n.toLocaleString('en-US', { minimumFractionDigits: 4, maximumFractionDigits: 4 });
};
setStat('open', fmtBare(t.open));
setStat('high', fmtBare(t.high));
setStat('low', fmtBare(t.low));
setStat('vol', t.vol);
setStat('pe', t.pe);
setStat('range52', t.range52);
// Repaint candles and volume
candleSvg.innerHTML = renderCandleSvg(t.arch, t.px.toFixed(2));
volSvg.innerHTML = renderVolumeSvg(t.arch);
// Highlight pulse
chartCard.classList.remove('swap-highlight');
void chartCard.offsetWidth;
chartCard.classList.add('swap-highlight');
// Sync holdings table active row + watchlist
document.querySelectorAll('table.holdings tbody tr').forEach(tr => {
const s = tr.querySelector('.sym-name');
const m = s ? s.firstChild.textContent.trim() : '';
tr.classList.toggle('active', m.split(' ')[0] === sym);
});
document.querySelectorAll('.watch-card').forEach(card => {
const s = card.querySelector('.watch-sym');
card.classList.toggle('active', s && s.textContent.trim() === sym);
});
}
// First-paint candle render so the svg is JS-controlled (allows variation later)
applyTicker('BTC');
// ─────── Wire holdings rows ───────
document.querySelectorAll('table.holdings tbody tr').forEach(tr => {
const link = tr.querySelector('.sym-name a');
if (link) link.addEventListener('click', e => e.stopPropagation());
const symEl = tr.querySelector('.sym-name');
if (!symEl) return;
const sym = symEl.firstChild.textContent.trim().split(/\s+/)[0];
if (!TICKERS[sym]) return;
tr.addEventListener('click', () => {
applyTicker(sym);
toast({ kind: 'info', title: 'Switched chart', body: `Now viewing <strong>${sym}</strong> — ${TICKERS[sym].name}` });
});
});
// ─────── Wire watchlist cards ───────
document.querySelectorAll('.watch-card').forEach(card => {
const link = card.querySelector('.watch-foot a');
if (link) link.addEventListener('click', e => e.stopPropagation());
const symEl = card.querySelector('.watch-sym');
if (!symEl) return;
const sym = symEl.textContent.trim();
if (!TICKERS[sym]) return;
card.addEventListener('click', () => {
applyTicker(sym);
toast({ kind: 'info', title: 'Switched chart', body: `Now viewing <strong>${sym}</strong> from watchlist` });
});
});
// ─────── Wire ticker tape ───────
document.querySelectorAll('.tape-item').forEach(item => {
const symEl = item.querySelector('.sym');
if (!symEl) return;
const sym = symEl.textContent.trim();
if (!TICKERS[sym]) return;
item.addEventListener('click', () => {
applyTicker(sym);
toast({ kind: 'info', title: 'Switched chart', body: `Pinned <strong>${sym}</strong> from market tape` });
});
});
// ─────── AI tabs ───────
document.querySelectorAll('.ai-tab').forEach(tab => {
tab.addEventListener('click', () => {
const target = tab.dataset.tab;
document.querySelectorAll('.ai-tab').forEach(t => t.classList.toggle('active', t === tab));
document.querySelectorAll('.ai-panel').forEach(p => p.classList.toggle('active', p.dataset.panel === target));
});
});
// ─────── Range tabs (visual) ───────
document.querySelectorAll('.range-tabs').forEach(group => {
group.addEventListener('click', e => {
const btn = e.target.closest('button');
if (!btn) return;
group.querySelectorAll('button').forEach(b => b.classList.toggle('active', b === btn));
});
});
// ─────── Action buttons ───────
document.querySelectorAll('[data-action]').forEach(btn => {
btn.addEventListener('click', () => {
const a = btn.dataset.action;
if (a === 'add') {
toast({ kind: 'info', title: 'Swap ticket', body: '<strong>SWAP 8,389 USDC → 45 SOL</strong> on Jupiter — slippage 0.3%, ready to sign.' });
} else if (a === 'alert') {
toast({ kind: 'warn', title: 'Alert set', body: 'On-chain alert created — <strong>SOL above $200</strong> or DEX vol &gt; $1B/day. We\'ll ping you in real time.' });
} else if (a === 'thesis') {
toast({ kind: 'violet', title: 'On-chain thesis', body: 'Opening full thesis with Glassnode, Artemis, and Dune dashboards.' });
}
});
});
// ─────── Refresh ───────
const refreshBtn = document.getElementById('refreshBtn');
function jitter(p, magPct = 0.6) {
// ±magPct% jitter, with up-bias on bull tickers
const jp = (Math.random() * 2 - 1) * (magPct / 100);
return Math.max(0.01, p * (1 + jp));
}
function refreshQuotes() {
refreshBtn.classList.add('spin');
setTimeout(() => refreshBtn.classList.remove('spin'), 900);
// Update each ticker's price with small jitter + flash main price if ticker is active
Object.entries(TICKERS).forEach(([sym, t]) => {
const newPx = +(jitter(t.px, 0.45)).toFixed(2);
const dPct = ((newPx - (t.px - t.dayChg)) / (t.px - t.dayChg)) * 100;
const dChg = newPx - (t.px - t.dayChg);
t.px = newPx;
t.dayChg = +dChg.toFixed(2);
t.dayPct = +dPct.toFixed(2);
t.dir = t.dayChg >= 0 ? 'up' : 'down';
});
// Update tape
document.querySelectorAll('.tape-item').forEach(item => {
const symEl = item.querySelector('.sym');
const pxEl = item.querySelector('.px');
const pctEl = item.querySelector('.pct');
if (!symEl || !pxEl || !pctEl) return;
const sym = symEl.textContent.trim();
const t = TICKERS[sym];
if (!t) return;
pxEl.textContent = t.px.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
pctEl.className = 'pct ' + (t.dir === 'up' ? 'up' : 'down');
pctEl.textContent = `${t.dir === 'up' ? '▲' : '▼'} ${Math.abs(t.dayPct).toFixed(2)}%`;
});
// Re-apply active ticker (refresh main chart price + delta) with flash
const oldFlashClass = tkPx.classList.contains('flash-up') ? 'flash-up' : (tkPx.classList.contains('flash-down') ? 'flash-down' : null);
if (oldFlashClass) tkPx.classList.remove(oldFlashClass);
applyTicker(activeSym);
void tkPx.offsetWidth;
tkPx.classList.add(TICKERS[activeSym].dir === 'up' ? 'flash-up' : 'flash-down');
// Update watchlist sparkline cards' price + pct
document.querySelectorAll('.watch-card').forEach(card => {
const symEl = card.querySelector('.watch-sym');
if (!symEl) return;
const sym = symEl.textContent.trim();
const t = TICKERS[sym];
if (!t) return;
const pxEl = card.querySelector('.watch-px');
const pctEl = card.querySelector('.watch-pct');
if (pxEl) pxEl.textContent = fmtPx(t.px);
if (pctEl) {
pctEl.className = 'watch-pct ' + (t.dir === 'up' ? 'up' : 'down');
pctEl.textContent = `${t.dir === 'up' ? '▲' : '▼'} ${Math.abs(t.dayPct).toFixed(2)}%`;
const old = pctEl.classList.contains('flash-up') ? 'flash-up' : (pctEl.classList.contains('flash-down') ? 'flash-down' : null);
if (old) pctEl.classList.remove(old);
void pctEl.offsetWidth;
pctEl.classList.add(t.dir === 'up' ? 'flash-up' : 'flash-down');
}
});
// Update sync timestamps
if (lastSyncEl) lastSyncEl.textContent = fmtClock(clockSeconds);
toast({ kind: 'info', title: 'Quotes refreshed', body: `${Object.keys(TICKERS).length} assets updated · Coinbase + CoinGecko · ${fmtClock(clockSeconds)}` });
}
refreshBtn.addEventListener('click', refreshQuotes);
// ─────── AI regenerate ───────
const regenBtn = document.getElementById('regenBtn');
const regenLabel = document.getElementById('regenLabel');
const aiHeadline = document.getElementById('aiHeadline');
const aiSub = document.getElementById('aiSub');
const convVal = document.getElementById('convVal');
const convFill = document.getElementById('convFill');
const AI_VARIANTS = [
{
headline: '<span class="accent">Rotate stables → SOL</span> on DePIN + Firedancer accelerator.',
sub: 'On-chain activity is breaking out while ETF spot demand is steady. Risk-reward favors adding SOL while keeping BTC core untouched.',
conv: 82
},
{
headline: '<span class="accent">Hold SOL</span> · target $220 first scale-out.',
sub: 'Backlog of catalysts intact, but the move has tightened R/R. Trim 25% on a $220 print; preserve cycle exposure.',
conv: 71
},
{
headline: '<span class="accent">Add to ETH</span> ahead of staking ETF effective date.',
sub: 'SEC certificate already issued. Issuer confirmation expected in 7 days. Spot setup mirrors BTC ETF Jan \'24 base.',
conv: 78
},
{
headline: '<span class="accent">Reduce DOGE risk</span> — funding overheated.',
sub: '8h funding hit +0.082% — historically a reliable late-stage signal on memecoin runs. Trim 30%, redeploy to majors.',
conv: 66
}
];
let aiIdx = 0;
regenBtn.addEventListener('click', () => {
if (regenBtn.classList.contains('thinking')) return;
regenBtn.classList.add('thinking');
regenLabel.innerHTML = `Analyzing <span class="thinking-dots"><span class="d"></span><span class="d"></span><span class="d"></span></span>`;
setTimeout(() => {
aiIdx = (aiIdx + 1) % AI_VARIANTS.length;
const v = AI_VARIANTS[aiIdx];
aiHeadline.innerHTML = v.headline;
aiSub.textContent = v.sub;
convVal.textContent = `${v.conv}%`;
convFill.style.width = `${v.conv}%`;
regenBtn.classList.remove('thinking');
regenLabel.textContent = 'Regenerate';
toast({ kind: 'violet', title: 'AI updated', body: 'New analysis run · Glassnode + Artemis + Dune · 412 tokens · 1.4s' });
}, 1400);
});
})();
</script>
</body>
</html>