/** * Design study "Horizon" — v2 mockup. * * Fully self-contained: static mock data, no hooks into the API, no auth. * View at /design-horizon with `npm run dev` — no backend or DB needed. * * Direction: space void + dim Mars horizon (glow stays atmosphere, never data), * one focal "what should I do today" card, zero classic tables, drill-down * everywhere. Chart colors validated (CVD + contrast) against the dark surface. */ import { useMemo, useRef, useState } from 'react'; /* ------------------------------------------------------------------ */ /* Mock data */ /* ------------------------------------------------------------------ */ const heroSetup = { symbol: 'VRT', name: 'Vertiv Holdings', direction: 'long' as const, entry: 92.1, stop: 86.4, current: 94.3, target: 107.0, rr: 2.6, targetProb: 58, residualPct: 94, conviction: 'High', sizeHint: 'vol-adjusted size 6.2% of book', }; const positions = [ { symbol: 'ANET', name: 'Arista Networks', direction: 'long', r: 1.4, entry: 118.4, current: 131.2, shares: 25, opened: 'May 28', stop: 121.8, stopNote: 'trailed from 111.10', target: 140.0, alphaUsd: 310, exit: 'ATR trailing stop', spark: [118.4, 119.2, 121.5, 120.8, 123.4, 125.1, 124.2, 126.8, 128.3, 127.5, 129.9, 131.2], stopTrail: [111.1, 111.1, 111.1, 112.6, 114.2, 114.2, 115.8, 117.4, 117.4, 119.5, 121.8, 121.8], }, { symbol: 'VRT', name: 'Vertiv Holdings', direction: 'long', r: 1.1, entry: 88.3, current: 94.3, shares: 40, opened: 'Jun 12', stop: 86.9, stopNote: 'trailed from 83.10', target: 103.0, alphaUsd: 205, exit: 'ATR trailing stop', spark: [88.3, 89.1, 88.6, 90.2, 91.4, 90.8, 92.1, 93.0, 92.4, 93.6, 94.1, 94.3], stopTrail: [83.1, 83.1, 84.0, 84.0, 84.9, 84.9, 85.6, 85.6, 86.2, 86.2, 86.9, 86.9], }, { symbol: 'CLS', name: 'Celestica', direction: 'long', r: 0.4, entry: 61.2, current: 62.9, shares: 60, opened: 'Jun 30', stop: 57.1, stopNote: 'initial stop', target: 71.5, alphaUsd: 84, exit: 'ATR trailing stop', spark: [61.2, 61.8, 61.1, 62.3, 62.0, 62.6, 63.1, 62.7, 62.4, 62.9, 63.2, 62.9], stopTrail: [57.1, 57.1, 57.1, 57.1, 57.1, 57.1, 57.1, 57.1, 57.1, 57.1, 57.1, 57.1], }, { symbol: 'SMCI', name: 'Super Micro Computer', direction: 'short', r: -0.6, entry: 44.8, current: 46.1, shares: 80, opened: 'Jul 2', stop: 47.0, stopNote: 'initial stop', target: 38.5, alphaUsd: -57, exit: 'ATR trailing stop', spark: [44.8, 44.2, 45.0, 45.6, 45.1, 45.9, 46.4, 45.8, 46.2, 46.0, 46.3, 46.1], stopTrail: [47.0, 47.0, 47.0, 47.0, 47.0, 47.0, 47.0, 47.0, 47.0, 47.0, 47.0, 47.0], }, ]; const radar = [ { rank: 2, symbol: 'CLS', name: 'Celestica', direction: 'long', residual: 91, rr: '2.2:1', prob: 61, qualified: true, note: 'clears EV gate', }, { rank: 3, symbol: 'APP', name: 'AppLovin', direction: 'long', residual: 89, rr: '2.4:1', prob: 55, qualified: true, note: 'clears EV gate', }, { rank: 4, symbol: 'NVDA', name: 'NVIDIA', direction: 'long', residual: 87, rr: '1.6:1', prob: 63, qualified: false, note: 'EV below gate — payoff too thin', }, { rank: 5, symbol: 'AMD', name: 'Advanced Micro Devices', direction: 'long', residual: 84, rr: '2.1:1', prob: 48, qualified: false, note: 'extended — 12% above entry zone', }, { rank: 6, symbol: 'PLTR', name: 'Palantir', direction: 'short', residual: 82, rr: '1.9:1', prob: 44, qualified: false, note: 'counter-trend in neutral regime', }, ]; const watchlist = [ { symbol: 'VRT', pct: 3.1 }, { symbol: 'ANET', pct: 2.4 }, { symbol: 'NVDA', pct: 1.8 }, { symbol: 'AVGO', pct: 1.5 }, { symbol: 'CRWD', pct: 0.9 }, { symbol: 'MSFT', pct: 0.3 }, { symbol: 'PLTR', pct: -0.9 }, { symbol: 'META', pct: -1.2 }, ]; /* Weekly closes since January, indexed to 100 — one axis, common base. */ const perfPortfolio = [100, 101.2, 100.6, 102.3, 103.8, 103.1, 105.0, 106.4, 105.2, 107.9, 109.6, 108.8, 111.3, 110.1, 112.6, 114.9, 113.8, 116.2, 118.4, 117.1, 119.8, 121.6, 120.4, 123.1, 125.0, 126.4]; const perfSpy = [100, 100.8, 100.2, 101.5, 102.1, 101.4, 102.6, 103.3, 102.5, 103.9, 104.6, 103.8, 104.9, 104.1, 105.2, 106.0, 105.3, 106.4, 107.2, 106.5, 107.4, 108.1, 107.3, 108.0, 108.6, 108.2]; const perfStart = new Date(2026, 0, 9); const perfDates = perfPortfolio.map((_, i) => { const d = new Date(perfStart); d.setDate(d.getDate() + i * 7); return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); }); const alphaSpark = [0, 42, 30, 95, 150, 128, 210, 265, 231, 340, 415, 388, 470, 442, 508, 570, 538, 590, 642]; /* ~3 months of daily VRT candles, seeded so the mock is stable across reloads. */ type Candle = { d: Date; o: number; h: number; l: number; c: number; v: number }; const vrtCandles: Candle[] = (() => { let s = 42 >>> 0; const rnd = () => { s = (Math.imul(s, 1664525) + 1013904223) >>> 0; return s / 4294967296; }; const days: Date[] = []; const cursor = new Date(2026, 6, 9); while (days.length < 64) { const dow = cursor.getDay(); if (dow !== 0 && dow !== 6) days.unshift(new Date(cursor)); cursor.setDate(cursor.getDate() - 1); } const drift = (i: number) => (i < 20 ? 0.30 : i < 28 ? -0.38 : i < 46 ? 0.44 : i < 53 ? -0.32 : 0.52); const out: Candle[] = []; let close = 73.6; for (let i = 0; i < 64; i++) { const chg = drift(i) + (rnd() - 0.5) * 2.4; const o = close; const c = o + chg; out.push({ d: days[i], o, c, h: Math.max(o, c) + rnd() * 1.2, l: Math.min(o, c) - rnd() * 1.2, v: 1.4e6 * (0.55 + rnd()) * (1 + Math.abs(chg) / 2.5), }); close = c; } const shift = 94.3 - close; return out.map((b) => ({ ...b, o: b.o + shift, h: b.h + shift, l: b.l + shift, c: b.c + shift })); })(); const tickerLevels = { resistance: 96.4, supportLo: 88.7, supportHi: 90.1, }; /* Score fingerprint — all axes 0–100 percentile, same order everywhere. */ const RADAR_AXES = ['Momentum', 'Trend', 'Setup', 'Sentiment', 'Fundam.']; const RADAR_AXES_FULL = [ 'Residual momentum', 'Trend strength', 'Setup quality', 'News sentiment', 'Fundamentals', ]; const heroScores = [94, 88, 85, 71, 62]; const liveMedian = [68, 61, 57, 52, 55]; const qualifiedCompare = [ { symbol: 'VRT', tag: 'top pick', scores: heroScores }, { symbol: 'CLS', tag: 'rank 2', scores: [91, 82, 72, 58, 74] }, { symbol: 'APP', tag: 'rank 3', scores: [89, 78, 69, 66, 81] }, ]; /* ------------------------------------------------------------------ */ /* Small building blocks */ /* ------------------------------------------------------------------ */ function fmt(n: number, digits = 2): string { return n.toLocaleString('en-US', { minimumFractionDigits: digits, maximumFractionDigits: digits }); } function signed(n: number, unit = ''): string { return `${n > 0 ? '+' : n < 0 ? '−' : ''}${fmt(Math.abs(n))}${unit}`; } function DirTag({ direction }: { direction: string }) { return ( {direction} ); } /** 2px sparkline, endpoint dot with surface ring. Single series — no legend. */ function Spark({ data, color, w = 130, h = 34 }: { data: number[]; color: string; w?: number; h?: number }) { const min = Math.min(...data); const max = Math.max(...data); const span = max - min || 1; const px = (i: number) => 3 + (i / (data.length - 1)) * (w - 10); const py = (v: number) => h - 5 - ((v - min) / span) * (h - 10); const path = data.map((v, i) => `${i === 0 ? 'M' : 'L'}${px(i).toFixed(1)},${py(v).toFixed(1)}`).join(' '); return ( ); } /** Diverging R-multiple bar: grows from a centered zero baseline. */ function RBar({ r, max = 1.6 }: { r: number; max?: number }) { const pct = Math.min(Math.abs(r) / max, 1) * 50; return (
= 0 ? '50%' : `${50 - pct}%`, width: `${pct}%`, background: r >= 0 ? 'var(--up)' : 'var(--down)', borderRadius: r >= 0 ? '0 4px 4px 0' : '4px 0 0 4px', }} />
); } /* ------------------------------------------------------------------ */ /* Radar — the score fingerprint (5 axes, 0–100 %ile, fixed order) */ /* ------------------------------------------------------------------ */ function RadarChart({ scores, reference, size = 110, labels = false, }: { scores: number[]; reference?: number[]; size?: number; labels?: boolean }) { const [hover, setHover] = useState(null); const pad = labels ? 78 : 10; const vb = size + pad * 2; const c = vb / 2; const r = size / 2; const pt = (v: number, i: number): [number, number] => { const a = ((-90 + i * 72) * Math.PI) / 180; return [c + (Math.cos(a) * r * v) / 100, c + (Math.sin(a) * r * v) / 100]; }; const poly = (vals: number[]) => vals.map((v, i) => pt(v, i).map((n) => n.toFixed(1)).join(',')).join(' '); const rendered = vb * (labels ? 0.95 : 1); const tip = hover !== null ? pt(scores[hover], hover) : null; return (
`${ax} ${scores[i]}`).join(', ')} (0–100 percentile)`} > {[50, 100].map((ring) => ( ))} {RADAR_AXES.map((ax, i) => { const [x, y] = pt(100, i); return ; })} {reference && ( )} {scores.map((v, i) => { const [x, y] = pt(v, i); return ( ); })} {labels && RADAR_AXES.map((ax, i) => { const a = ((-90 + i * 72) * Math.PI) / 180; const x = c + Math.cos(a) * (r + 14); const y = c + Math.sin(a) * (r + 14); const anchor = Math.cos(a) > 0.3 ? 'start' : Math.cos(a) < -0.3 ? 'end' : 'middle'; const dy = Math.sin(a) < -0.3 ? -2 : Math.sin(a) > 0.3 ? 9 : 4; return ( {ax} {scores[i]} ); })} {/* hit targets on top — comfortably larger than the dots, keyboard-reachable */} {scores.map((v, i) => { const [x, y] = pt(v, i); return ( setHover(i)} onMouseLeave={() => setHover(null)} onFocus={() => setHover(i)} onBlur={() => setHover(null)} /> ); })} {hover !== null && tip && (
{RADAR_AXES_FULL[hover]} {scores[hover]}th %ile {reference && live-setup median {reference[hover]}}
)}
); } /* ------------------------------------------------------------------ */ /* Trade chart — price since entry with entry / trailing stop / target */ /* ------------------------------------------------------------------ */ type Position = (typeof positions)[number]; function TradeChart({ p }: { p: Position }) { const w = 560; const h = 150; const padL = 8; const padR = 96; const padT = 10; const padB = 12; const isShort = p.direction === 'short'; const vals = [...p.spark, ...p.stopTrail, p.entry]; let lo = Math.min(...vals); let hi = Math.max(...vals); const range = hi - lo || 1; // Only stretch the scale to the target when it doesn't flatten the price action. const targetFits = isShort ? lo - p.target <= range * 0.9 : p.target - hi <= range * 0.9; if (targetFits) { lo = Math.min(lo, p.target); hi = Math.max(hi, p.target); } const pad = (hi - lo) * 0.07; lo -= pad; hi += pad; const plotW = w - padL - padR; const plotH = h - padT - padB; const px = (i: number) => padL + (i / (p.spark.length - 1)) * plotW; const py = (v: number) => padT + plotH - ((v - lo) / (hi - lo)) * plotH; const pricePath = p.spark.map((v, i) => `${i === 0 ? 'M' : 'L'}${px(i).toFixed(1)},${py(v).toFixed(1)}`).join(' '); let stopPath = `M${px(0).toFixed(1)},${py(p.stopTrail[0]).toFixed(1)}`; for (let i = 1; i < p.stopTrail.length; i++) { stopPath += `H${px(i).toFixed(1)}V${py(p.stopTrail[i]).toFixed(1)}`; } const col = p.r >= 0 ? 'var(--up)' : 'var(--down)'; const labelX = w - padR + 10; const entryY = py(p.entry); let stopY = py(p.stopTrail[p.stopTrail.length - 1]); if (Math.abs(stopY - entryY) < 11) stopY = entryY + (stopY >= entryY ? 11 : -11); const lastI = p.spark.length - 1; return ( entry {fmt(p.entry)} stop {fmt(p.stop)} {targetFits ? ( <> target {fmt(p.target)} ) : ( target {fmt(p.target)} {isShort ? '↓' : '↑'} )} ); } /* ------------------------------------------------------------------ */ /* The price rail — stop → entry → current → target, laid out spatially */ /* ------------------------------------------------------------------ */ function PriceRail({ s }: { s: typeof heroSetup }) { const lo = s.stop - (s.target - s.stop) * 0.06; const hi = s.target + (s.target - s.stop) * 0.06; const pct = (v: number) => ((v - lo) / (hi - lo)) * 100; const rNow = (s.current - s.entry) / (s.entry - s.stop); return (
{/* markers */}
stop {fmt(s.stop)} −1R
entry {fmt(s.entry)}
now {fmt(s.current)} {signed(rNow)}R
target {fmt(s.target)} +{fmt(s.rr, 1)}R
); } /* ------------------------------------------------------------------ */ /* Performance chart — indexed to 100, crosshair + tooltip on hover */ /* ------------------------------------------------------------------ */ const CHART = { w: 760, h: 220, top: 14, right: 64, bottom: 26, left: 40 }; function PerfChart() { const [hover, setHover] = useState(null); const svgRef = useRef(null); const { w, h, top, right, bottom, left } = CHART; const plotW = w - left - right; const plotH = h - top - bottom; const yMin = 95; const yMax = 130; const px = (i: number) => left + (i / (perfPortfolio.length - 1)) * plotW; const py = (v: number) => top + plotH - ((v - yMin) / (yMax - yMin)) * plotH; const line = (data: number[]) => data.map((v, i) => `${i === 0 ? 'M' : 'L'}${px(i).toFixed(1)},${py(v).toFixed(1)}`).join(' '); const yTicks = [100, 110, 120, 130]; const xTicks = useMemo( () => [0, 5, 10, 15, 20, 25].map((i) => ({ i, label: perfDates[i] })), [], ); const onMove = (e: React.MouseEvent) => { const rect = svgRef.current?.getBoundingClientRect(); if (!rect) return; const x = ((e.clientX - rect.left) / rect.width) * w; const i = Math.round(((x - left) / plotW) * (perfPortfolio.length - 1)); setHover(Math.max(0, Math.min(perfPortfolio.length - 1, i))); }; const last = perfPortfolio.length - 1; return (

Performance

Paper book vs S&P 500 · indexed to 100 · since January

Book S&P 500
setHover(null)} role="img" aria-label="Paper book up 26.4% versus S&P 500 up 8.2% since January, indexed to 100" > {yTicks.map((t) => ( {t} ))} {xTicks.map(({ i, label }) => ( {label} ))} {hover !== null && ( )} {/* direct end labels */} {fmt(perfPortfolio[last], 1)} {fmt(perfSpy[last], 1)}
{hover !== null ? <>week of {perfDates[hover]} — book {fmt(perfPortfolio[hover], 1)} · S&P 500 {fmt(perfSpy[hover], 1)} : <>hover for weekly values}
); } /* ------------------------------------------------------------------ */ /* Ticker drill-down — candlesticks + volume + trade levels */ /* ------------------------------------------------------------------ */ function TickerPanel() { const [hover, setHover] = useState(null); const svgRef = useRef(null); const w = 900; const padT = 10; const padB = 20; const padL = 8; const padR = 96; const priceH = 290; const volH = 54; const gap = 16; const H = padT + priceH + gap + volH + padB; const n = vrtCandles.length; const slot = (w - padL - padR) / n; const bodyW = Math.min(7, slot * 0.62); const hi = Math.max(...vrtCandles.map((b) => b.h), heroSetup.target + 1.5); const lo = Math.min(...vrtCandles.map((b) => b.l)) - 1; const maxV = Math.max(...vrtCandles.map((b) => b.v)); const px = (i: number) => padL + (i + 0.5) * slot; const py = (v: number) => padT + priceH - ((v - lo) / (hi - lo)) * priceH; const vy = (v: number) => padT + priceH + gap + volH - (v / maxV) * volH; // Skip ticks whose right-gutter label would collide with a level label. const levelYs = [ heroSetup.entry, heroSetup.stop, heroSetup.target, tickerLevels.resistance, (tickerLevels.supportLo + tickerLevels.supportHi) / 2, ]; const yTicks = [75, 85, 95, 105].filter( (t) => t > lo && t < hi && levelYs.every((l) => Math.abs(((t - l) / (hi - lo)) * priceH) >= 13), ); const monthTicks = vrtCandles .map((b, i) => ({ b, i })) .filter(({ b, i }) => i === 0 || b.d.getMonth() !== vrtCandles[i - 1].d.getMonth()) .map(({ b, i }) => ({ i, label: b.d.toLocaleDateString('en-US', { month: 'short' }) })); const onMove = (e: React.MouseEvent) => { const rect = svgRef.current?.getBoundingClientRect(); if (!rect) return; const x = ((e.clientX - rect.left) / rect.width) * w; const i = Math.floor((x - padL) / slot); setHover(Math.max(0, Math.min(n - 1, i))); }; const labelX = w - padR + 10; const hb = hover !== null ? vrtCandles[hover] : null; return (

Drill-down · Ticker

VRT Vertiv Holdings

94.30 +1.9% today

composite score 82 ATR 2.90 · 3.1% earnings Aug 5

VRT live-setup median

{(['1M', '3M', '6M', 'YTD', '1Y', 'All'] as const).map((r) => ( ))}
setHover(null)} role="img" aria-label="Daily candlestick chart for VRT with entry, trailing stop, target, support and resistance levels" > {yTicks.map((t) => ( {t} ))} {monthTicks.map(({ i, label }) => ( {label} ))} {/* support zone + resistance (nearest S/R only — full list stays in its tab) */} sup {fmt(tickerLevels.supportLo, 1)}–{fmt(tickerLevels.supportHi, 1)} resist {fmt(tickerLevels.resistance)} {/* trade levels */} entry {fmt(heroSetup.entry)} stop {fmt(heroSetup.stop)} target {fmt(heroSetup.target)} {/* candles */} {vrtCandles.map((b, i) => { const up = b.c >= b.o; const col = up ? 'var(--up)' : 'var(--down)'; const bodyY = py(Math.max(b.o, b.c)); const bodyH = Math.max(1.5, Math.abs(py(b.o) - py(b.c))); return ( ); })} {hover !== null && ( )}
{hb ? ( <> {hb.d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} — O {fmt(hb.o)} H {fmt(hb.h)} L {fmt(hb.l)} C {fmt(hb.c)} · vol {(hb.v / 1e6).toFixed(1)}M ) : ( <>hover for daily OHLC · volume below )}

prices 2h ago indicators 2h ago sentiment 6h ago fundamentals 3d ago manage data →

); } /* ------------------------------------------------------------------ */ /* Page */ /* ------------------------------------------------------------------ */ export default function DesignHorizonPage() { const [expanded, setExpanded] = useState('ANET'); const [showTicker, setShowTicker] = useState(true); return (
{/* atmosphere — stays behind everything, never carries data */}