/* Поликопи editorial redesign — LIVE preview (chunk 2).
* Additive route /preview — does NOT touch existing live screens. Leaders is wired to
* real data (useTopTraders); Profile/Portfolio use representative data for now.
* Style: redesign/STYLE_GUIDE.md via editorial.css. */
import { useState, useMemo } from 'react';
import { useTopTraders, type Trader } from '../hooks/useTopTraders';
import '../redesign/editorial.css';
// chrome shark/skull crops from the brand asset (placeholder until clean cut-out PNGs)
const CHROME = '/brand/chrome.jpeg';
const avShark = (i: number): React.CSSProperties => ({
backgroundImage: `url(${CHROME})`, backgroundSize: '360% 360%',
backgroundPosition: i % 2 ? '13% 78%' : '7% 13%',
});
const avSkull = (i: number): React.CSSProperties => ({
backgroundImage: `url(${CHROME})`, backgroundSize: '360% 360%',
backgroundPosition: i % 2 ? '90% 80%' : '94% 9%',
});
/** Lightweight preview EDGE: blends win-rate momentum + 7d PnL sign into 0–99.
* (Real screen uses the confidence-shrunk edgeScore from LeadersPage.) */
function previewEdge(t: Trader): number {
const wr = t.winRatePos ?? t.winRate ?? 0.5;
const mom = (t.pnl7d ?? 0) > 0 ? 1 : (t.pnl7d ?? 0) < 0 ? -1 : 0;
const base = 55 + (wr - 0.5) * 70 + mom * 6;
return Math.max(40, Math.min(99, Math.round(base)));
}
const shortName = (t: Trader) =>
t.label || `Trader ${t.address.slice(2, 6).toUpperCase()}`;
function StatusBar({ dark = true }: { dark?: boolean }) {
return (
<div className="pk-sb" style={{ color: dark ? '#fff' : '#1a1a16' }}>
<span>9:41</span><span>●●● ⌃ ▮</span>
</div>
);
}
function ScreenLeaders() {
const { traders, loading } = useTopTraders('volume', 12);
const [side, setSide] = useState<'copy' | 'fade'>('copy');
const rows = useMemo(() => traders.slice(0, 8), [traders]);
return (
<div className="pk-screen">
<StatusBar />
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 14 }}>
<span className="pk-toptag">Prediction Market</span>
<div className="pk-toggle" data-side={side}
onClick={() => setSide(s => (s === 'copy' ? 'fade' : 'copy'))}>
<span className="pk-t-copy">⇄ COPY</span>
<span className="pk-t-fade">FADE</span>
</div>
</div>
<div className="pk-h1 pk-serif">Two sides.<br />One market.</div>
<div className="pk-lbl">Leaders · by volume</div>
{loading && <div style={{ color: 'var(--mut)', padding: '20px 0' }}>Loading leaders…</div>}
{rows.map((t, i) => {
const edge = previewEdge(t);
const isFade = side === 'fade';
return (
<div className="pk-leader-row" key={t.address}>
<span className="pk-rk pk-serif">{i + 1}</span>
<div className="pk-av" style={isFade ? avSkull(i) : avShark(i)} />
<div className="pk-who">
<div className="pk-nm pk-serif">{shortName(t)}</div>
<span className={`pk-pill ${isFade ? 'pk-f' : 'pk-c'}`}>{isFade ? 'FADE' : 'COPY'}</span>
</div>
<div className="pk-edge">
<div className="pk-e">EDGE</div>
<div className={`pk-v ${isFade ? 'pk-neg-num' : 'pk-pos-num'}`}>{edge}</div>
</div>
</div>
);
})}
</div>
);
}
function ScreenProfile() {
return (
<div className="pk-screen pk-cream">
<StatusBar dark={false} />
<div style={{ display: 'flex', alignItems: 'center', padding: '6px 0 0' }}>
<span style={{ fontSize: 20 }}>‹</span>
<span style={{ flex: 1, textAlign: 'center', fontSize: 13, letterSpacing: 2, color: '#8a8576', fontWeight: 700, textTransform: 'uppercase' }}>Trader</span>
<span style={{ width: 14 }} />
</div>
<div className="pk-hero" style={{ backgroundImage: `url(${CHROME})`, backgroundSize: '240% 240%', backgroundPosition: '50% 33%' }} />
<div className="pk-tn pk-serif">Veritas Capital</div>
<div className="pk-tradertag"><span>✓ COPY TRADER</span></div>
<div className="pk-edgebig">
<div className="pk-l">EDGE</div>
<div className="pk-n pk-serif">92</div>
<div className="pk-t3">TOP 3%</div>
</div>
<div style={{ marginTop: 14 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: '#8a8576', letterSpacing: 1, textTransform: 'uppercase', marginBottom: 4 }}>
<span>Equity · 30d</span><span className="pk-mono" style={{ color: 'var(--g)' }}>+28.4%</span>
</div>
<svg viewBox="0 0 320 70" preserveAspectRatio="none" style={{ width: '100%', height: 62 }}>
<defs><linearGradient id="pkg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="#1f9e57" stopOpacity=".25" /><stop offset="1" stopColor="#1f9e57" stopOpacity="0" />
</linearGradient></defs>
<path d="M0 58 L32 54 L64 56 L96 44 L128 47 L160 34 L192 37 L224 24 L256 27 L288 14 L320 9 L320 70 L0 70Z" fill="url(#pkg)" />
<path d="M0 58 L32 54 L64 56 L96 44 L128 47 L160 34 L192 37 L224 24 L256 27 L288 14 L320 9" fill="none" stroke="#1f9e57" strokeWidth="2" />
</svg>
</div>
<div className="pk-actions">
<button className="pk-btn pk-btn-yes"><div className="pk-big2 pk-serif">YES</div><div className="pk-sm">copy</div></button>
<button className="pk-btn pk-btn-no"><div className="pk-big2 pk-serif">NO</div><div className="pk-sm">fade</div></button>
</div>
</div>
);
}
function ScreenPortfolio() {
const pos = [
{ q: 'Monetary Policy Decision', side: 'yes', px: '0.55', via: 'vs Veritas', pl: '+$2.3k', neg: false },
{ q: 'Election Outcome', side: 'no', px: '0.38', via: 'fade Ellington', pl: '+$1.6k', neg: false },
{ q: 'Economic Indicator', side: 'yes', px: '0.48', via: 'vs Pembroke', pl: '+$0.9k', neg: false },
{ q: 'Rate Cut in September', side: 'no', px: '0.41', via: 'fade Northmoor', pl: '−$0.2k', neg: true },
];
return (
<div className="pk-screen">
<StatusBar />
<div className="pk-toptag" style={{ marginTop: 14 }}>Portfolio · live</div>
<div className="pk-big pk-serif">+23.41%<span className="pk-u"> +$11.7k</span></div>
<div className="pk-rule" />
<div className="pk-lbl">Open Positions</div>
{pos.map((p, i) => (
<div className="pk-pos" key={i}>
<div className="pk-av" style={p.side === 'no' ? avSkull(i) : avShark(i)} />
<div className="pk-t">
<div className="pk-q pk-serif">{p.q}</div>
<div className="pk-yn">
<span className={p.side === 'yes' ? 'pk-yes' : 'pk-no'}>{p.side.toUpperCase()} @ {p.px}</span> · {p.via}
</div>
</div>
<div className="pk-pl" style={{ color: p.neg ? 'var(--r)' : 'var(--g-soft)' }}>{p.pl}</div>
</div>
))}
</div>
);
}
const TABS = ['Leaders', 'Profile', 'Portfolio'] as const;
export default function PreviewPage() {
const [tab, setTab] = useState<(typeof TABS)[number]>('Leaders');
return (
<div className="pk-root">
{tab === 'Leaders' && <ScreenLeaders />}
{tab === 'Profile' && <ScreenProfile />}
{tab === 'Portfolio' && <ScreenPortfolio />}
<div className="pk-nav">
{TABS.map(t => (
<button key={t} onClick={() => setTab(t)}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'var(--pk-serif)', fontSize: 13, color: tab === t ? 'var(--gold)' : '#5a5f6b' }}>
{t}
</button>
))}
</div>
</div>
);
}
📜 Git History
a685fb9feat(poli): live editorial preview at /preview (chunk 2)11 days ago
Show last diff
Loading...