Files
Predictalytics/temp_new_design/Prediction Analytics.dc.html
T

876 lines
54 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>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Roboto+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { margin: 0; background: #0a0d14; font-family: 'Manrope', system-ui, sans-serif; overflow-x: auto; }
* { box-sizing: border-box; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 4px; }
::-webkit-scrollbar-track { background: transparent; }
a { color: #5b9dff; text-decoration: none; }
a:hover { color: #8ab8ff; }
table { border-collapse: collapse; }
.dual-range { -webkit-appearance: none; appearance: none; background: transparent; pointer-events: none; margin: 0; position: absolute; top: 0; left: 0; width: 100%; height: 20px; }
.dual-range::-webkit-slider-thumb { -webkit-appearance: none; pointer-events: auto; width: 15px; height: 15px; border-radius: 50%; background: #5b9dff; border: 2px solid #0a0d14; cursor: pointer; box-shadow: 0 0 6px rgba(91,157,255,0.7); margin-top: 0; }
.dual-range::-moz-range-thumb { pointer-events: auto; width: 15px; height: 15px; border-radius: 50%; background: #5b9dff; border: 2px solid #0a0d14; cursor: pointer; box-shadow: 0 0 6px rgba(91,157,255,0.7); }
.dual-range::-webkit-slider-runnable-track { background: transparent; }
.dual-range::-moz-range-track { background: transparent; }
</style>
</helmet>
<div style="position:relative; min-height:100vh; min-width:1440px; width:100%; background: radial-gradient(1100px 700px at 15% -10%, rgba(22,82,240,0.28), transparent 60%), radial-gradient(900px 600px at 110% 10%, rgba(18,212,138,0.10), transparent 55%), #0a0d14; color:#EDEFF5;">
<!-- decorative glow layer -->
<div style="position:fixed; inset:0; pointer-events:none; background: radial-gradient(600px 400px at 80% 85%, rgba(76,141,255,0.10), transparent 60%); z-index:0;"></div>
<div style="position:relative; z-index:1; display:flex; min-height:100vh;">
<!-- SIDEBAR -->
<aside style="width:236px; flex:none; padding:24px 16px; display:flex; flex-direction:column; gap:28px; border-right:1px solid rgba(255,255,255,0.07); background:rgba(255,255,255,0.015);">
<div style="display:flex; align-items:center; gap:10px; padding:4px 8px;">
<div style="width:30px; height:30px; border-radius:9px; background:linear-gradient(135deg,#1652F0,#4c8dff); box-shadow:0 0 20px rgba(22,82,240,0.6);"></div>
<div style="font-weight:800; font-size:17px; letter-spacing:-0.02em;">Lucid<span style="color:#5b9dff;">.</span></div>
</div>
<nav style="display:flex; flex-direction:column; gap:2px;">
<div style="font:600 10px/1 'Manrope'; letter-spacing:0.08em; color:#5B6377; padding:0 12px 8px;">NAVIGATION</div>
<sc-for list="{{ navItems }}" as="item" hint-placeholder-count="4">
<div onClick="{{ item.onClick }}" style="{{ item.style }}">
<div style="{{ item.dotStyle }}"></div>
<span>{{ item.label }}</span>
</div>
</sc-for>
</nav>
<div style="margin-top:auto; display:flex; flex-direction:column; gap:10px;">
<div style="padding:14px; border-radius:14px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(20px);">
<div style="font-size:11px; color:#8B93A7; margin-bottom:6px;">Data feed</div>
<div style="display:flex; align-items:center; gap:7px;">
<div style="width:7px; height:7px; border-radius:50%; background:#12D48A; box-shadow:0 0 8px #12D48A;"></div>
<div style="font-size:12.5px; font-weight:600;">Live · on-chain</div>
</div>
</div>
<div style="display:flex; align-items:center; gap:10px; padding:8px 10px;">
<div style="width:30px; height:30px; border-radius:50%; background:linear-gradient(135deg,#2a2f3d,#1a1e28); border:1px solid rgba(255,255,255,0.1);"></div>
<div>
<div style="font-size:12.5px; font-weight:700;">Analyst</div>
<div style="font-size:11px; color:#5B6377;">Pro workspace</div>
</div>
</div>
</div>
</aside>
<!-- MAIN -->
<main style="flex:1; min-width:0; padding:22px 32px 60px;">
<!-- TOPBAR -->
<div style="display:flex; align-items:center; gap:16px; margin-bottom:22px;">
<div style="flex:1; position:relative; max-width:420px;">
<input placeholder="Search markets, traders, wallets…" style="width:100%; padding:11px 14px 11px 38px; border-radius:11px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.09); color:#EDEFF5; font-size:13px; font-family:'Manrope'; outline:none;" />
<div style="position:absolute; left:13px; top:50%; transform:translateY(-50%); width:14px; height:14px; border-radius:50%; border:2px solid #5B6377;"></div>
</div>
<div style="margin-left:auto; display:flex; align-items:center; gap:10px;">
<div style="padding:8px 14px; border-radius:10px; background:rgba(18,212,138,0.10); border:1px solid rgba(18,212,138,0.25); font-size:12px; font-weight:700; color:#12D48A; font-family:'Roboto Mono';">24H VOL {{ headerVolume }}</div>
<div style="width:38px; height:38px; border-radius:11px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.09); display:flex; align-items:center; justify-content:center;">
<div style="width:8px; height:8px; border-radius:50%; background:#F6465D;"></div>
</div>
</div>
</div>
<!-- breadcrumb -->
<div style="display:flex; align-items:center; gap:8px; font-size:12.5px; margin-bottom:18px; font-weight:600;">
<sc-for list="{{ breadcrumbItems }}" as="crumb" hint-placeholder-count="1">
<span onClick="{{ crumb.onClick }}" style="{{ crumb.style }}">{{ crumb.label }}</span>
<sc-if value="{{ crumb.sep }}" hint-placeholder-val="{{ false }}">
<span style="color:#3a4152;"></span>
</sc-if>
</sc-for>
</div>
<sc-if value="{{ isOverview }}" hint-placeholder-val="{{ true }}">
<!-- ================= OVERVIEW ================= -->
<div>
<div style="display:flex; align-items:baseline; justify-content:space-between; margin-bottom:20px;">
<div>
<h1 style="margin:0 0 4px; font-size:26px; font-weight:800; letter-spacing:-0.02em;">Markets Overview</h1>
<div style="font-size:13px; color:#8B93A7;">Real-time analytics across all active prediction markets</div>
</div>
</div>
<!-- stat cards -->
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:20px;">
<sc-for list="{{ globalStats }}" as="stat" hint-placeholder-count="4">
<div style="padding:18px 20px; border-radius:16px; background:rgba(255,255,255,0.045); border:1px solid rgba(255,255,255,0.09); backdrop-filter:blur(24px); box-shadow:0 8px 30px rgba(0,0,0,0.25);">
<div style="font-size:12px; color:#8B93A7; font-weight:600; margin-bottom:10px;">{{ stat.label }}</div>
<div style="font-size:24px; font-weight:800; font-family:'Roboto Mono'; letter-spacing:-0.02em;">{{ stat.value }}</div>
<div style="font-size:12px; font-weight:700; margin-top:6px; font-family:'Roboto Mono'; color:{{ stat.deltaColor }};">{{ stat.delta }}</div>
</div>
</sc-for>
</div>
<div style="display:grid; grid-template-columns:1.7fr 1fr; gap:16px; align-items:start;">
<!-- markets table -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
<div style="padding:18px 22px 14px; font-size:15px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">All Markets</div>
<table style="width:100%; font-size:13px;">
<thead>
<tr style="color:#5B6377; font-size:11px; text-transform:uppercase; letter-spacing:0.04em;">
<td style="padding:10px 22px;">Market</td>
<td style="padding:10px 10px; text-align:right;">Yes %</td>
<td style="padding:10px 10px; text-align:right;">24h</td>
<td style="padding:10px 10px; text-align:right;">Volume</td>
<td style="padding:10px 22px; text-align:right;">Traders</td>
</tr>
</thead>
<tbody>
<sc-for list="{{ marketRows }}" as="m" hint-placeholder-count="6">
<tr onClick="{{ m.onClick }}" style="cursor:pointer; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
<td style="padding:13px 22px;">
<div style="font-weight:700; font-size:13.5px; margin-bottom:3px;">{{ m.question }}</div>
<div style="font-size:11.5px; color:#5B6377;">{{ m.category }} · {{ m.statusLabel }}</div>
</td>
<td style="padding:13px 10px; text-align:right; font-family:'Roboto Mono'; font-weight:700;">{{ m.yesPriceLabel }}</td>
<td style="padding:13px 10px; text-align:right; font-family:'Roboto Mono'; font-weight:700; color:{{ m.changeColor }};">{{ m.changeLabel }}</td>
<td style="padding:13px 10px; text-align:right; font-family:'Roboto Mono'; color:#C7CCDA;">{{ m.volumeLabel }}</td>
<td style="padding:13px 22px; text-align:right; font-family:'Roboto Mono'; color:#8B93A7;">{{ m.tradersLabel }}</td>
</tr>
</sc-for>
</tbody>
</table>
</div>
<!-- leaderboard -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
<div style="padding:18px 22px 14px; font-size:15px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">Top Traders</div>
<div style="display:flex; flex-direction:column;">
<sc-for list="{{ leaderboard }}" as="t" hint-placeholder-count="6">
<div onClick="{{ t.onClick }}" style="cursor:pointer; display:flex; align-items:center; gap:12px; padding:12px 22px; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
<div style="width:30px; height:30px; border-radius:9px; flex:none; background:{{ t.avatarBg }}; display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:800;">{{ t.initial }}</div>
<div style="min-width:0; flex:1;">
<div style="font-weight:700; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">{{ t.handle }}</div>
<div style="font-size:11px; color:#5B6377;">{{ t.trait }}</div>
</div>
<div style="text-align:right; flex:none;">
<div style="font-family:'Roboto Mono'; font-weight:700; font-size:13px; color:#12D48A;">{{ t.pnlLabel }}</div>
<div style="font-size:10.5px; color:#5B6377;">P&amp;L</div>
</div>
</div>
</sc-for>
</div>
</div>
</div>
</div>
</sc-if>
<sc-if value="{{ isMarket }}" hint-placeholder-val="{{ false }}">
<!-- ================= MARKET DETAIL ================= -->
<div>
<div style="display:flex; align-items:flex-start; justify-content:space-between; margin-bottom:20px; gap:20px;">
<div>
<div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;">
<div style="font-size:11px; font-weight:700; padding:4px 10px; border-radius:7px; background:rgba(22,82,240,0.14); border:1px solid rgba(22,82,240,0.3); color:#5b9dff;">{{ marketDetail.category }}</div>
<div style="font-size:11px; font-weight:700; padding:4px 10px; border-radius:7px; background:{{ marketDetail.statusBg }}; color:{{ marketDetail.statusColor }};">{{ marketDetail.status }}</div>
</div>
<h1 style="margin:0 0 6px; font-size:24px; font-weight:800; letter-spacing:-0.02em; max-width:640px;">{{ marketDetail.question }}</h1>
<div style="font-size:12.5px; color:#8B93A7;">Resolves {{ marketDetail.resolves }}</div>
</div>
<div style="text-align:right; flex:none;">
<div style="font-size:34px; font-weight:800; font-family:'Roboto Mono'; color:#12D48A;">{{ marketDetail.yesPriceBig }}</div>
<div style="font-size:12px; color:#8B93A7; font-weight:600;">YES probability</div>
</div>
</div>
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:18px;">
<sc-for list="{{ marketStats }}" as="stat" hint-placeholder-count="4">
<div style="padding:16px 18px; border-radius:14px; background:rgba(255,255,255,0.045); border:1px solid rgba(255,255,255,0.09); backdrop-filter:blur(24px);">
<div style="font-size:11.5px; color:#8B93A7; font-weight:600; margin-bottom:8px;">{{ stat.label }}</div>
<div style="font-size:19px; font-weight:800; font-family:'Roboto Mono';">{{ stat.value }}</div>
</div>
</sc-for>
</div>
<div style="display:grid; grid-template-columns:1.6fr 1fr; gap:16px; margin-bottom:16px; align-items:start;">
<!-- price chart -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:20px 22px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:14px;">
<div style="font-size:14px; font-weight:700;">Yes Price History</div>
<div style="display:flex; gap:6px;">
<div style="font-size:11px; padding:5px 10px; border-radius:7px; background:rgba(22,82,240,0.18); color:#5b9dff; font-weight:700;">30D</div>
</div>
</div>
<svg viewBox="0 0 600 220" style="width:100%; height:220px; overflow:visible;">
<defs>
<linearGradient id="fillGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#12D48A" stop-opacity="0.35"/>
<stop offset="100%" stop-color="#12D48A" stop-opacity="0"/>
</linearGradient>
<filter id="glow"><feGaussianBlur stdDeviation="3.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
</defs>
<path d="{{ marketDetail.areaPath }}" fill="url(#fillGrad)" stroke="none"></path>
<path d="{{ marketDetail.linePath }}" fill="none" stroke="#12D48A" stroke-width="2.5" filter="url(#glow)"></path>
</svg>
</div>
<!-- order book -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:18px 20px;">
<div style="font-size:14px; font-weight:700; margin-bottom:12px;">Order Book Depth</div>
<div style="font-size:10.5px; color:#5B6377; font-weight:700; text-transform:uppercase; margin-bottom:6px;">Asks</div>
<sc-for list="{{ marketDetail.asks }}" as="row" hint-placeholder-count="5">
<div style="position:relative; margin-bottom:3px; height:20px;">
<div style="position:absolute; right:0; top:0; bottom:0; width:{{ row.barWidth }}; background:rgba(246,70,93,0.16); border-radius:4px;"></div>
<div style="position:relative; display:flex; justify-content:space-between; padding:0 8px; font-size:11.5px; font-family:'Roboto Mono'; line-height:20px;">
<span style="color:#F6465D; font-weight:700;">{{ row.priceLabel }}</span>
<span style="color:#8B93A7;">{{ row.sizeLabel }}</span>
</div>
</div>
</sc-for>
<div style="height:1px; background:rgba(255,255,255,0.08); margin:8px 0;"></div>
<div style="font-size:10.5px; color:#5B6377; font-weight:700; text-transform:uppercase; margin-bottom:6px;">Bids</div>
<sc-for list="{{ marketDetail.bids }}" as="row" hint-placeholder-count="5">
<div style="position:relative; margin-bottom:3px; height:20px;">
<div style="position:absolute; right:0; top:0; bottom:0; width:{{ row.barWidth }}; background:rgba(18,212,138,0.16); border-radius:4px;"></div>
<div style="position:relative; display:flex; justify-content:space-between; padding:0 8px; font-size:11.5px; font-family:'Roboto Mono'; line-height:20px;">
<span style="color:#12D48A; font-weight:700;">{{ row.priceLabel }}</span>
<span style="color:#8B93A7;">{{ row.sizeLabel }}</span>
</div>
</div>
</sc-for>
</div>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:16px;">
<!-- biggest holders -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
<div style="padding:16px 20px 12px; font-size:14px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">Biggest Holders</div>
<table style="width:100%; font-size:12.5px;">
<tbody>
<sc-for list="{{ marketDetail.holders }}" as="h" hint-placeholder-count="5">
<tr onClick="{{ h.onClick }}" style="cursor:pointer; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
<td style="padding:11px 20px; font-family:'Roboto Mono'; font-weight:700;">{{ h.trader }}</td>
<td style="padding:11px 6px; text-align:center;"><span style="padding:3px 9px; border-radius:6px; font-size:11px; font-weight:700; background:{{ h.sideBg }}; color:{{ h.sideColor }};">{{ h.side }}</span></td>
<td style="padding:11px 20px; text-align:right; font-family:'Roboto Mono'; color:#C7CCDA;">{{ h.valueLabel }}</td>
</tr>
</sc-for>
</tbody>
</table>
</div>
<!-- top traders in market -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
<div style="padding:16px 20px 12px; font-size:14px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">Top Traders Here</div>
<div style="display:flex; flex-direction:column;">
<sc-for list="{{ marketDetail.topTraders }}" as="t" hint-placeholder-count="4">
<div onClick="{{ t.onClick }}" style="cursor:pointer; display:flex; align-items:center; gap:12px; padding:12px 20px; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
<div style="width:28px; height:28px; border-radius:8px; flex:none; background:{{ t.avatarBg }}; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:800;">{{ t.initial }}</div>
<div style="font-weight:700; font-size:13px; flex:1;">{{ t.handle }}</div>
<div style="font-size:11px; color:#8B93A7;">{{ t.trait }}</div>
</div>
</sc-for>
</div>
</div>
</div>
</div>
</sc-if>
<sc-if value="{{ isTraderSearch }}" hint-placeholder-val="{{ false }}">
<!-- ================= TRADER SEARCH ================= -->
<div>
<div style="margin-bottom:20px;">
<h1 style="margin:0 0 4px; font-size:26px; font-weight:800; letter-spacing:-0.02em;">Trader Search</h1>
<div style="font-size:13px; color:#8B93A7;">Filter by traits and metric ranges to find the traders you care about</div>
</div>
<div style="display:grid; grid-template-columns:300px 1fr; gap:16px; align-items:start;">
<!-- filter panel -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:20px; display:flex; flex-direction:column; gap:22px; position:sticky; top:22px;">
<div>
<input value="{{ searchText }}" onChange="{{ onSearchTextChange }}" placeholder="Handle or wallet…" style="width:100%; padding:10px 13px; border-radius:10px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:13px; font-family:'Manrope'; outline:none;" />
</div>
<div>
<div style="font-size:12px; font-weight:700; color:#C7CCDA; margin-bottom:10px;">Traits</div>
<div style="display:flex; flex-wrap:wrap; gap:7px;">
<sc-for list="{{ traitFilterChips }}" as="chip" hint-placeholder-count="5">
<div onClick="{{ chip.onClick }}" style="{{ chip.style }}">{{ chip.label }}</div>
</sc-for>
</div>
</div>
<div>
<div style="display:flex; justify-content:space-between; margin-bottom:10px;">
<span style="font-size:12px; font-weight:700; color:#C7CCDA;">Win Rate</span>
<span style="font-size:11px; font-family:'Roboto Mono'; color:#8B93A7;">{{ winRateSlider.minLabel }} {{ winRateSlider.maxLabel }}</span>
</div>
<div style="position:relative; height:20px; margin-bottom:10px;">
<div style="position:absolute; top:8px; left:0; right:0; height:4px; border-radius:2px; background:rgba(255,255,255,0.1);"></div>
<div style="position:absolute; top:8px; height:4px; border-radius:2px; background:#5b9dff; left:{{ winRateSlider.fillLeft }}; right:{{ winRateSlider.fillRight }};"></div>
<input class="dual-range" type="range" min="{{ winRateSlider.lo }}" max="{{ winRateSlider.hi }}" value="{{ winRateSlider.minVal }}" onInput="{{ winRateSlider.onMinInput }}" />
<input class="dual-range" type="range" min="{{ winRateSlider.lo }}" max="{{ winRateSlider.hi }}" value="{{ winRateSlider.maxVal }}" onInput="{{ winRateSlider.onMaxInput }}" />
</div>
<div style="display:flex; align-items:center; gap:8px;">
<input value="{{ winRateSlider.minVal }}" onBlur="{{ winRateSlider.onMinTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
<span style="color:#5B6377; font-size:11px;"></span>
<input value="{{ winRateSlider.maxVal }}" onBlur="{{ winRateSlider.onMaxTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
</div>
</div>
<div>
<div style="display:flex; justify-content:space-between; margin-bottom:10px;">
<span style="font-size:12px; font-weight:700; color:#C7CCDA;">Volume</span>
<span style="font-size:11px; font-family:'Roboto Mono'; color:#8B93A7;">{{ volumeSlider.minLabel }} {{ volumeSlider.maxLabel }}</span>
</div>
<div style="position:relative; height:20px; margin-bottom:10px;">
<div style="position:absolute; top:8px; left:0; right:0; height:4px; border-radius:2px; background:rgba(255,255,255,0.1);"></div>
<div style="position:absolute; top:8px; height:4px; border-radius:2px; background:#5b9dff; left:{{ volumeSlider.fillLeft }}; right:{{ volumeSlider.fillRight }};"></div>
<input class="dual-range" type="range" min="{{ volumeSlider.lo }}" max="{{ volumeSlider.hi }}" step="500000" value="{{ volumeSlider.minVal }}" onInput="{{ volumeSlider.onMinInput }}" />
<input class="dual-range" type="range" min="{{ volumeSlider.lo }}" max="{{ volumeSlider.hi }}" step="500000" value="{{ volumeSlider.maxVal }}" onInput="{{ volumeSlider.onMaxInput }}" />
</div>
<div style="display:flex; align-items:center; gap:8px;">
<input value="{{ volumeSlider.minVal }}" onBlur="{{ volumeSlider.onMinTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
<span style="color:#5B6377; font-size:11px;"></span>
<input value="{{ volumeSlider.maxVal }}" onBlur="{{ volumeSlider.onMaxTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
</div>
</div>
<div>
<div style="display:flex; justify-content:space-between; margin-bottom:10px;">
<span style="font-size:12px; font-weight:700; color:#C7CCDA;">Total P&amp;L</span>
<span style="font-size:11px; font-family:'Roboto Mono'; color:#8B93A7;">{{ pnlSlider.minLabel }} {{ pnlSlider.maxLabel }}</span>
</div>
<div style="position:relative; height:20px; margin-bottom:10px;">
<div style="position:absolute; top:8px; left:0; right:0; height:4px; border-radius:2px; background:rgba(255,255,255,0.1);"></div>
<div style="position:absolute; top:8px; height:4px; border-radius:2px; background:#5b9dff; left:{{ pnlSlider.fillLeft }}; right:{{ pnlSlider.fillRight }};"></div>
<input class="dual-range" type="range" min="{{ pnlSlider.lo }}" max="{{ pnlSlider.hi }}" step="10000" value="{{ pnlSlider.minVal }}" onInput="{{ pnlSlider.onMinInput }}" />
<input class="dual-range" type="range" min="{{ pnlSlider.lo }}" max="{{ pnlSlider.hi }}" step="10000" value="{{ pnlSlider.maxVal }}" onInput="{{ pnlSlider.onMaxInput }}" />
</div>
<div style="display:flex; align-items:center; gap:8px;">
<input value="{{ pnlSlider.minVal }}" onBlur="{{ pnlSlider.onMinTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
<span style="color:#5B6377; font-size:11px;"></span>
<input value="{{ pnlSlider.maxVal }}" onBlur="{{ pnlSlider.onMaxTextBlur }}" style="width:0; flex:1; min-width:0; padding:6px 8px; border-radius:8px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); color:#EDEFF5; font-size:12px; font-family:'Roboto Mono'; outline:none; text-align:center;" />
</div>
</div>
<div onClick="{{ onResetFilters }}" style="cursor:pointer; text-align:center; padding:10px; border-radius:10px; font-size:12.5px; font-weight:700; color:#8B93A7; border:1px solid rgba(255,255,255,0.1);">Reset filters</div>
</div>
<!-- results -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
<div style="padding:16px 22px; font-size:14px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06); display:flex; justify-content:space-between;">
<span>Results</span><span style="color:#5B6377; font-weight:600;">{{ searchResultsCount }} traders</span>
</div>
<table style="width:100%; font-size:13px;">
<thead>
<tr style="color:#5B6377; font-size:11px; text-transform:uppercase; letter-spacing:0.04em;">
<td style="padding:10px 22px;">Trader</td>
<td style="padding:10px 10px;">Traits</td>
<td style="padding:10px 10px; text-align:right;">Win Rate</td>
<td style="padding:10px 10px; text-align:right;">Volume</td>
<td style="padding:10px 22px; text-align:right;">P&amp;L</td>
</tr>
</thead>
<tbody>
<sc-for list="{{ searchResults }}" as="t" hint-placeholder-count="6">
<tr onClick="{{ t.onClick }}" style="cursor:pointer; border-top:1px solid rgba(255,255,255,0.05);" style-hover="background:rgba(255,255,255,0.03);">
<td style="padding:12px 22px;">
<div style="display:flex; align-items:center; gap:10px;">
<div style="width:26px; height:26px; border-radius:8px; flex:none; background:{{ t.avatarBg }}; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:800;">{{ t.initial }}</div>
<span style="font-weight:700;">{{ t.handle }}</span>
</div>
</td>
<td style="padding:12px 10px;">
<div style="display:flex; gap:5px; flex-wrap:wrap;">
<sc-for list="{{ t.traitChips }}" as="chip" hint-placeholder-count="2">
<div style="font-size:10.5px; font-weight:700; padding:3px 8px; border-radius:6px; background:{{ chip.bg }}; color:{{ chip.color }};">{{ chip.label }}</div>
</sc-for>
</div>
</td>
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono'; font-weight:700;">{{ t.winRateLabel }}</td>
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono'; color:#C7CCDA;">{{ t.volumeLabel }}</td>
<td style="padding:12px 22px; text-align:right; font-family:'Roboto Mono'; font-weight:700; color:{{ t.pnlColor }};">{{ t.pnlLabel }}</td>
</tr>
</sc-for>
</tbody>
</table>
</div>
</div>
</div>
</sc-if>
<sc-if value="{{ isTrader }}" hint-placeholder-val="{{ false }}">
<!-- ================= TRADER DETAIL ================= -->
<div>
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:20px; gap:20px;">
<div style="display:flex; align-items:center; gap:16px;">
<div style="width:56px; height:56px; border-radius:16px; background:{{ traderDetail.avatarBg }}; display:flex; align-items:center; justify-content:center; font-size:20px; font-weight:800; box-shadow:0 0 24px rgba(22,82,240,0.3);">{{ traderDetail.initial }}</div>
<div>
<div style="display:flex; align-items:center; gap:10px; margin-bottom:4px;">
<h1 style="margin:0; font-size:22px; font-weight:800; letter-spacing:-0.02em;">{{ traderDetail.handle }}</h1>
<div style="font-size:12px; font-family:'Roboto Mono'; color:#5B6377;">{{ traderDetail.wallet }}</div>
</div>
<div style="display:flex; gap:6px;">
<sc-for list="{{ traderDetail.traitChips }}" as="chip" hint-placeholder-count="2">
<div style="font-size:11px; font-weight:700; padding:4px 10px; border-radius:7px; background:{{ chip.bg }}; color:{{ chip.color }}; border:1px solid {{ chip.border }};">{{ chip.label }}</div>
</sc-for>
</div>
</div>
</div>
<div style="text-align:right;">
<div style="font-size:11.5px; color:#8B93A7; margin-bottom:2px;">Member since {{ traderDetail.joined }}</div>
<div style="font-size:11.5px; color:#8B93A7;">{{ traderDetail.activePositionsLabel }} active positions</div>
</div>
</div>
<div style="display:grid; grid-template-columns:repeat(5,1fr); gap:16px; margin-bottom:18px;">
<sc-for list="{{ traderStats }}" as="stat" hint-placeholder-count="5">
<div style="padding:16px 18px; border-radius:14px; background:rgba(255,255,255,0.045); border:1px solid rgba(255,255,255,0.09); backdrop-filter:blur(24px);">
<div style="font-size:11px; color:#8B93A7; font-weight:600; margin-bottom:8px;">{{ stat.label }}</div>
<div style="font-size:18px; font-weight:800; font-family:'Roboto Mono'; color:{{ stat.color }};">{{ stat.value }}</div>
</div>
</sc-for>
</div>
<div style="display:grid; grid-template-columns:1.6fr 1fr; gap:16px; margin-bottom:16px; align-items:start;">
<!-- pnl chart -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:20px 22px;">
<div style="font-size:14px; font-weight:700; margin-bottom:14px;">P&amp;L Over Time</div>
<svg viewBox="0 0 600 200" style="width:100%; height:200px; overflow:visible;">
<defs>
<linearGradient id="pnlFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#5b9dff" stop-opacity="0.35"/>
<stop offset="100%" stop-color="#5b9dff" stop-opacity="0"/>
</linearGradient>
<filter id="glow2"><feGaussianBlur stdDeviation="3.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
</defs>
<path d="{{ traderDetail.areaPath }}" fill="url(#pnlFill)" stroke="none"></path>
<path d="{{ traderDetail.linePath }}" fill="none" stroke="#5b9dff" stroke-width="2.5" filter="url(#glow2)"></path>
</svg>
</div>
<!-- network -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); padding:18px 20px;">
<div style="font-size:14px; font-weight:700; margin-bottom:10px;">Connected Wallets</div>
<sc-if value="{{ traderDetail.hasConnections }}" hint-placeholder-val="{{ true }}">
<sc-for list="{{ traderDetail.connections }}" as="w" hint-placeholder-count="2">
<div style="display:flex; align-items:center; gap:10px; padding:10px 0; border-top:1px solid rgba(255,255,255,0.05);">
<div style="width:8px; height:8px; border-radius:50%; background:#F6465D; box-shadow:0 0 6px #F6465D;"></div>
<div style="font-family:'Roboto Mono'; font-size:12.5px; color:#C7CCDA;">{{ w }}</div>
</div>
</sc-for>
</sc-if>
<sc-if value="{{ traderDetail.noConnections }}" hint-placeholder-val="{{ false }}">
<div style="font-size:12.5px; color:#5B6377; padding:10px 0;">No linked wallets detected.</div>
</sc-if>
</div>
</div>
<!-- positions table -->
<div style="border-radius:18px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); backdrop-filter:blur(24px); overflow:hidden;">
<div style="padding:16px 22px 12px; font-size:14px; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.06);">Positions</div>
<table style="width:100%; font-size:12.5px;">
<thead>
<tr style="color:#5B6377; font-size:11px; text-transform:uppercase; letter-spacing:0.04em;">
<td style="padding:10px 22px;">Market</td>
<td style="padding:10px 10px; text-align:center;">Side</td>
<td style="padding:10px 10px; text-align:right;">Size</td>
<td style="padding:10px 10px; text-align:right;">Entry</td>
<td style="padding:10px 10px; text-align:right;">Current</td>
<td style="padding:10px 22px; text-align:right;">P&amp;L</td>
</tr>
</thead>
<tbody>
<sc-for list="{{ traderDetail.positions }}" as="p" hint-placeholder-count="4">
<tr style="border-top:1px solid rgba(255,255,255,0.05);">
<td style="padding:12px 22px; font-weight:600;">{{ p.market }}</td>
<td style="padding:12px 10px; text-align:center;"><span style="padding:3px 9px; border-radius:6px; font-size:11px; font-weight:700; background:{{ p.sideBg }}; color:{{ p.sideColor }};">{{ p.side }}</span></td>
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono';">{{ p.sizeLabel }}</td>
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono'; color:#8B93A7;">{{ p.entryLabel }}</td>
<td style="padding:12px 10px; text-align:right; font-family:'Roboto Mono'; color:#8B93A7;">{{ p.currentLabel }}</td>
<td style="padding:12px 22px; text-align:right; font-family:'Roboto Mono'; font-weight:700; color:{{ p.pnlColor }};">{{ p.pnlLabel }}</td>
</tr>
</sc-for>
</tbody>
</table>
</div>
</div>
</sc-if>
</main>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script>
class Component extends DCLogic {
state = {
view: "overview",
selectedMarketId: null,
selectedTraderId: null,
markets: null,
traders: null,
searchText: "",
filterTraits: [],
winRateMin: 0,
winRateMax: 100,
volumeMin: 0,
volumeMax: 25000000,
pnlMin: -50000,
pnlMax: 500000,
};
componentDidMount() {
import("./market-data.js").then((m) => {
this.setState({ markets: m.MARKETS, traders: m.TRADERS });
});
}
goOverview = () => this.setState({ view: "overview" });
goMarket = (id) => this.setState({ view: "market", selectedMarketId: id });
goTrader = (id) => this.setState({ view: "trader", selectedTraderId: id });
goTraderSearch = () => this.setState({ view: "traderSearch" });
setSearchText = (e) => this.setState({ searchText: e.target.value });
toggleTrait = (trait) => this.setState((s) => ({
filterTraits: s.filterTraits.includes(trait) ? s.filterTraits.filter((t) => t !== trait) : [...s.filterTraits, trait],
}));
setRange = (key) => (e) => this.setState({ [key]: Number(e.target.value) });
resetFilters = () => this.setState({
searchText: "", filterTraits: [], winRateMin: 0, winRateMax: 100,
volumeMin: 0, volumeMax: 25000000, pnlMin: -50000, pnlMax: 500000,
});
parseClamp(raw, lo, hi) {
const n = Number(String(raw).replace(/[^0-9.\-]/g, ""));
if (Number.isNaN(n)) return null;
return Math.max(lo, Math.min(hi, n));
}
onMinTextBlur = (key, lo, hi) => (e) => {
const n = this.parseClamp(e.target.value, lo, hi);
this.setState({ [key]: n === null ? lo : n });
};
onMaxTextBlur = (key, lo, hi) => (e) => {
const n = this.parseClamp(e.target.value, lo, hi);
this.setState({ [key]: n === null ? hi : n });
};
fmtMoney(n) {
if (n >= 1000000) return "$" + (n / 1000000).toFixed(2) + "M";
if (n >= 1000) return "$" + (n / 1000).toFixed(1) + "K";
return "$" + Math.round(n);
}
fmtNum(n) {
if (n >= 1000) return (n / 1000).toFixed(1) + "K";
return String(Math.round(n));
}
fmtSigned(n, suffix) {
const s = n >= 0 ? "+" : "";
return s + n.toFixed(1) + (suffix || "");
}
traitStyle(trait) {
const map = {
"Arbitrage Bot": { bg: "rgba(22,82,240,0.14)", color: "#5b9dff", border: "rgba(22,82,240,0.3)" },
"Resolution Farming": { bg: "rgba(246,70,93,0.12)", color: "#F6465D", border: "rgba(246,70,93,0.28)" },
"Trading Bot": { bg: "rgba(255,255,255,0.08)", color: "#C7CCDA", border: "rgba(255,255,255,0.15)" },
"Human": { bg: "rgba(124,92,255,0.12)", color: "#a78bff", border: "rgba(124,92,255,0.28)" },
"High Volume": { bg: "rgba(18,212,138,0.12)", color: "#12D48A", border: "rgba(18,212,138,0.28)" },
};
return map[trait] || { bg: "rgba(255,255,255,0.08)", color: "#C7CCDA", border: "rgba(255,255,255,0.15)" };
}
ALL_TRAITS = ["Human", "Trading Bot", "Arbitrage Bot", "Resolution Farming", "High Volume"];
avatarBg(seed) {
const palettes = [
"linear-gradient(135deg,#1652F0,#4c8dff)",
"linear-gradient(135deg,#12D48A,#0a8f5f)",
"linear-gradient(135deg,#F6465D,#a8283a)",
"linear-gradient(135deg,#7c5cff,#4c8dff)",
"linear-gradient(135deg,#ff9d4c,#F6465D)",
];
let h = 0;
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
return palettes[h % palettes.length];
}
buildDualSlider(minKey, maxKey, lo, hi, fmt) {
const minVal = this.state[minKey];
const maxVal = this.state[maxKey];
const pctMin = ((minVal - lo) / (hi - lo)) * 100;
const pctMax = ((maxVal - lo) / (hi - lo)) * 100;
return {
lo, hi, minVal, maxVal,
minLabel: fmt(minVal), maxLabel: fmt(maxVal),
fillLeft: pctMin.toFixed(1) + "%",
fillRight: (100 - pctMax).toFixed(1) + "%",
onMinInput: (e) => this.setState({ [minKey]: Math.min(Number(e.target.value), this.state[maxKey]) }),
onMaxInput: (e) => this.setState({ [maxKey]: Math.max(Number(e.target.value), this.state[minKey]) }),
onMinTextBlur: this.onMinTextBlur(minKey, lo, hi),
onMaxTextBlur: this.onMaxTextBlur(maxKey, lo, hi),
};
}
buildPath(series, w, h, pad) {
if (!series || series.length < 2) return { line: "", area: "" };
const min = Math.min(...series);
const max = Math.max(...series);
const range = max - min || 1;
const innerW = w - pad * 2;
const innerH = h - pad * 2;
const pts = series.map((v, i) => {
const x = pad + (i / (series.length - 1)) * innerW;
const y = pad + innerH - ((v - min) / range) * innerH;
return [x, y];
});
let line = "M" + pts[0][0].toFixed(1) + "," + pts[0][1].toFixed(1);
for (let i = 1; i < pts.length; i++) line += " L" + pts[i][0].toFixed(1) + "," + pts[i][1].toFixed(1);
const area = line + ` L${pts[pts.length - 1][0].toFixed(1)},${h - pad} L${pts[0][0].toFixed(1)},${h - pad} Z`;
return { line, area };
}
renderVals() {
const markets = this.state.markets || [];
const traders = this.state.traders || [];
const view = this.state.view;
const navDefs = [
{ key: "overview", label: "Markets" },
{ key: "traders", label: "Traders" },
{ key: "watchlist", label: "Watchlist" },
{ key: "settings", label: "Settings" },
];
const navItems = navDefs.map((n) => {
const active = (n.key === "overview" && view === "overview") ||
(n.key === "traders" && (view === "trader" || view === "traderSearch")) ||
(n.key === "watchlist" && false) ||
(n.key === "settings" && false);
return {
label: n.label,
onClick: n.key === "overview" ? this.goOverview : (n.key === "traders" ? this.goTraderSearch : this.goOverview),
style: `display:flex; align-items:center; gap:10px; padding:10px 12px; border-radius:10px; font-size:13.5px; font-weight:600; cursor:pointer; color:${active ? "#EDEFF5" : "#8B93A7"}; background:${active ? "rgba(22,82,240,0.14)" : "transparent"};`,
dotStyle: `width:6px; height:6px; border-radius:50%; background:${active ? "#5b9dff" : "#3a4152"};`,
};
});
// ---- global stats
const totalVol24 = markets.reduce((s, m) => s + m.volume24h, 0);
const openMarkets = markets.filter((m) => m.status === "Active").length;
const totalTraders = new Set(traders.map((t) => t.id)).size + markets.reduce((s, m) => s + m.traders, 0) * 0; // display sum below
const totalActiveTraders = markets.reduce((s, m) => s + m.traders, 0);
const avgLiquidity = markets.length ? markets.reduce((s, m) => s + m.liquidity, 0) / markets.length : 0;
const globalStats = [
{ label: "24H Volume", value: this.fmtMoney(totalVol24), delta: this.fmtSigned(3.4, "%"), deltaColor: "#12D48A" },
{ label: "Open Markets", value: String(openMarkets), delta: "+2 today", deltaColor: "#12D48A" },
{ label: "Active Traders", value: this.fmtNum(totalActiveTraders), delta: this.fmtSigned(1.8, "%"), deltaColor: "#12D48A" },
{ label: "Avg. Liquidity", value: this.fmtMoney(avgLiquidity), delta: this.fmtSigned(-0.6, "%"), deltaColor: "#F6465D" },
];
const marketRows = markets.map((m) => ({
question: m.question,
category: m.category,
statusLabel: m.status,
yesPriceLabel: m.yesPrice.toFixed(1) + "%",
changeLabel: this.fmtSigned(m.change24h, "%"),
changeColor: m.change24h >= 0 ? "#12D48A" : "#F6465D",
volumeLabel: this.fmtMoney(m.volume24h),
tradersLabel: this.fmtNum(m.traders),
onClick: () => this.goMarket(m.id),
}));
const leaderboard = [...traders].sort((a, b) => (b.pnlRealized + b.pnlUnrealized) - (a.pnlRealized + a.pnlUnrealized)).slice(0, 6).map((t) => ({
handle: t.handle,
trait: t.traits[0],
initial: t.handle[0].toUpperCase(),
avatarBg: this.avatarBg(t.id),
pnlLabel: this.fmtMoney(t.pnlRealized + t.pnlUnrealized),
onClick: () => this.goTrader(t.id),
}));
const headerVolume = this.fmtMoney(totalVol24);
const crumbDefs = view === "overview" ? [["Markets Overview", null]] :
view === "market" ? [["Markets Overview", this.goOverview], ["Market Detail", null]] :
view === "traderSearch" ? [["Trader Search", null]] :
[["Trader Search", this.goTraderSearch], ["Trader Detail", null]];
const breadcrumbItems = crumbDefs.map(([label, onClick], i) => ({
label, onClick,
style: onClick ? "cursor:pointer; color:#8B93A7;" : "color:#EDEFF5; cursor:default;",
sep: i < crumbDefs.length - 1,
}));
// ---- trader search
let searchResults = [];
let traitFilterChips = [];
if (view === "traderSearch") {
const q = this.state.searchText.trim().toLowerCase();
traitFilterChips = this.ALL_TRAITS.map((tr) => {
const active = this.state.filterTraits.includes(tr);
const s = this.traitStyle(tr);
return {
label: tr,
onClick: () => this.toggleTrait(tr),
style: `cursor:pointer; font-size:12px; font-weight:700; padding:7px 13px; border-radius:9px; background:${active ? s.bg : "rgba(255,255,255,0.04)"}; color:${active ? s.color : "#8B93A7"}; border:1px solid ${active ? s.border : "rgba(255,255,255,0.09)"};`,
};
});
searchResults = traders.filter((t) => {
if (q && !t.handle.toLowerCase().includes(q) && !t.wallet.toLowerCase().includes(q)) return false;
if (this.state.filterTraits.length && !this.state.filterTraits.every((tr) => t.traits.includes(tr))) return false;
if (t.winRate < this.state.winRateMin || t.winRate > this.state.winRateMax) return false;
if (t.volume < this.state.volumeMin || t.volume > this.state.volumeMax) return false;
const totalPnl = t.pnlRealized + t.pnlUnrealized;
if (totalPnl < this.state.pnlMin || totalPnl > this.state.pnlMax) return false;
return true;
}).map((t) => ({
id: t.id,
handle: t.handle,
initial: t.handle[0].toUpperCase(),
avatarBg: this.avatarBg(t.id),
traitChips: t.traits.map((tr) => {
const s = this.traitStyle(tr);
return { label: tr, bg: s.bg, color: s.color, border: s.border };
}),
winRateLabel: t.winRate.toFixed(1) + "%",
volumeLabel: this.fmtMoney(t.volume),
pnlLabel: ((t.pnlRealized + t.pnlUnrealized) >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(t.pnlRealized + t.pnlUnrealized)),
pnlColor: (t.pnlRealized + t.pnlUnrealized) >= 0 ? "#12D48A" : "#F6465D",
onClick: () => this.goTrader(t.id),
}));
}
// ---- market detail
let marketDetail = null;
let marketStats = [];
if (view === "market") {
const m = markets.find((x) => x.id === this.state.selectedMarketId) || markets[0];
if (m) {
const { line, area } = this.buildPath(m.yesSeries, 600, 220, 14);
const maxBookSize = Math.max(1, ...m.orderBook.bids.map((b) => b[1]), ...m.orderBook.asks.map((a) => a[1]));
marketDetail = {
question: m.question,
category: m.category,
status: m.status,
statusBg: m.status === "Active" ? "rgba(18,212,138,0.12)" : "rgba(255,255,255,0.08)",
statusColor: m.status === "Active" ? "#12D48A" : "#8B93A7",
resolves: m.resolves,
yesPriceBig: m.yesPrice.toFixed(1) + "%",
linePath: line,
areaPath: area,
asks: [...m.orderBook.asks].reverse().map(([p, s]) => ({ priceLabel: p.toFixed(1) + "%", sizeLabel: this.fmtMoney(s), barWidth: Math.round((s / maxBookSize) * 100) + "%" })),
bids: m.orderBook.bids.map(([p, s]) => ({ priceLabel: p.toFixed(1) + "%", sizeLabel: this.fmtMoney(s), barWidth: Math.round((s / maxBookSize) * 100) + "%" })),
holders: m.holders.map((h) => ({
trader: h.trader,
side: h.side,
sideBg: h.side === "Yes" ? "rgba(18,212,138,0.14)" : "rgba(246,70,93,0.14)",
sideColor: h.side === "Yes" ? "#12D48A" : "#F6465D",
valueLabel: this.fmtMoney(h.value),
onClick: () => {
const t = traders.find((tr) => tr.handle === h.trader || tr.wallet === h.trader);
if (t) this.goTrader(t.id);
},
})),
topTraders: m.topTraders.map((handle) => {
const t = traders.find((tr) => tr.handle === handle);
return t ? {
handle: t.handle,
trait: t.traits[0],
initial: t.handle[0].toUpperCase(),
avatarBg: this.avatarBg(t.id),
onClick: () => this.goTrader(t.id),
} : { handle, trait: "", initial: handle[0].toUpperCase(), avatarBg: "#333", onClick: () => {} };
}),
};
marketStats = [
{ label: "24H Volume", value: this.fmtMoney(m.volume24h) },
{ label: "Total Volume", value: this.fmtMoney(m.volumeTotal) },
{ label: "Liquidity", value: this.fmtMoney(m.liquidity) },
{ label: "Open Interest", value: this.fmtMoney(m.openInterest) },
];
}
}
// ---- trader detail
let traderDetail = null;
let traderStats = [];
if (view === "trader") {
const t = traders.find((x) => x.id === this.state.selectedTraderId) || traders[0];
if (t) {
const { line, area } = this.buildPath(t.pnlSeries, 600, 200, 14);
const totalPnl = t.pnlRealized + t.pnlUnrealized;
traderDetail = {
handle: t.handle,
wallet: t.wallet,
initial: t.handle[0].toUpperCase(),
avatarBg: this.avatarBg(t.id),
joined: t.joined,
activePositionsLabel: String(t.activePositions),
traitChips: t.traits.map((tr) => {
const s = this.traitStyle(tr);
return { label: tr, bg: s.bg, color: s.color, border: s.border };
}),
linePath: line,
areaPath: area,
hasConnections: t.connectedWallets.length > 0,
noConnections: t.connectedWallets.length === 0,
connections: t.connectedWallets,
positions: t.positions.map((p) => ({
market: p.market,
side: p.side,
sideBg: p.side === "Yes" ? "rgba(18,212,138,0.14)" : "rgba(246,70,93,0.14)",
sideColor: p.side === "Yes" ? "#12D48A" : "#F6465D",
sizeLabel: this.fmtMoney(p.size),
entryLabel: p.entry.toFixed(1) + "%",
currentLabel: p.current.toFixed(1) + "%",
pnlLabel: this.fmtSigned(0, "").slice(0,0) + (p.pnl >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(p.pnl)),
pnlColor: p.pnl >= 0 ? "#12D48A" : "#F6465D",
})),
};
traderStats = [
{ label: "Realized P&L", value: (t.pnlRealized >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(t.pnlRealized)), color: t.pnlRealized >= 0 ? "#12D48A" : "#F6465D" },
{ label: "Unrealized P&L", value: (t.pnlUnrealized >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(t.pnlUnrealized)), color: t.pnlUnrealized >= 0 ? "#12D48A" : "#F6465D" },
{ label: "Win Rate", value: t.winRate.toFixed(1) + "%", color: "#EDEFF5" },
{ label: "Total Volume", value: this.fmtMoney(t.volume), color: "#EDEFF5" },
{ label: "Avg Position", value: this.fmtMoney(t.avgPositionSize), color: "#EDEFF5" },
];
}
}
return {
navItems,
headerVolume,
breadcrumbItems,
isOverview: view === "overview",
isMarket: view === "market",
isTrader: view === "trader",
isTraderSearch: view === "traderSearch",
searchResultsCount: String(searchResults.length),
searchResults,
traitFilterChips,
searchText: this.state.searchText,
onSearchTextChange: this.setSearchText,
winRateSlider: this.buildDualSlider("winRateMin", "winRateMax", 0, 100, (v) => v.toFixed(0) + "%"),
volumeSlider: this.buildDualSlider("volumeMin", "volumeMax", 0, 25000000, (v) => this.fmtMoney(v)),
pnlSlider: this.buildDualSlider("pnlMin", "pnlMax", -50000, 500000, (v) => (v >= 0 ? "+" : "-") + this.fmtMoney(Math.abs(v))),
onResetFilters: this.resetFilters,
globalStats,
marketRows,
leaderboard,
marketDetail,
marketStats,
traderDetail,
traderStats,
};
}
}
</script>
</body>
</html>