← Back
/* Поликопи 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...