/**
* 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 (
{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 (
);
}
/* ------------------------------------------------------------------ */
/* 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
{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
{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 */}
{/* command bar */}
{/* the verdict */}
Thursday, July 9
One setup clears the gate today.
3 qualified of 14 live setups · 4 open positions · +2.3R unrealized
{/* focal setup card */}
top pick
{heroSetup.symbol}
{heroSetup.name}
conviction {heroSetup.conviction}
residual momentum {heroSetup.residualPct}th %ile
reward / risk
{fmt(heroSetup.rr, 1)} : 1
target probability
{heroSetup.targetProb}%
cleared the expected-value gate · {heroSetup.sizeHint}
All 14 setups
{showTicker &&
}
{/* KPI band */}
open risk
$1,240
4 open · distance to stops
unrealized
+2.3R
+$1,875 · 3 up, 1 down
alpha vs S&P 500
+$642
open trades vs buy-and-hold SPY
gate this week
3 / 14
setups clearing the EV gate
{/* open positions — diverging R bars, expandable rows */}
Open positions
R-multiple to entry · select a row for detail
{positions.map((p) => {
const open = expanded === p.symbol;
return (
-
{open && (
- opened
- {p.opened} · {p.shares} shares
- stop
- {fmt(p.stop)} {p.stopNote}
- target
- {fmt(p.target)}
- exit rule
- {p.exit}
- alpha vs SPY
- = 0 ? 'hzn-up' : 'hzn-down'}`}>{`${p.alphaUsd >= 0 ? '+' : '−'}$${fmt(Math.abs(p.alphaUsd), 0)}`}
since entry · entry / trailing stop / target
)}
);
})}
{/* radar — ranked next setups with reasons */}
Radar
ranked by strategy score · why each does or doesn't qualify
{qualifiedCompare.map((q) => (
{q.symbol}
{q.tag}
))}
the 3 qualified setups
same axes as the ticker fingerprint · 0–100 %ile
{radar.map((r) => (
-
{r.rank}
{r.symbol}
{r.name}
{r.residual}%ile
{r.rr}
{r.qualified ? '✓ ' : ''}{r.note}
))}
{/* watchlist strip */}
);
}
/* ------------------------------------------------------------------ */
/* Styles — scoped under .hzn, independent of the app theme */
/* ------------------------------------------------------------------ */
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
.hzn {
/* tokens — chart colors validated on the dark surface (#11131c) */
--void: #0a0b11;
--surface: #11131c;
--panel: rgba(226, 231, 245, 0.032);
--panel-glass: rgba(15, 17, 26, 0.72);
--panel-line: rgba(226, 231, 245, 0.085);
--grid: rgba(226, 231, 245, 0.07);
--ink: #edeef3;
--ink-2: #9aa0b0;
--ink-3: #5d6373;
--up: #2f9db2; /* marks */
--up-text: #6ec9db; /* small text tint */
--down: #e36a58;
--down-text: #ef9182;
--amber: #c67f22;
--amber-text: #dfa356;
--ember: #ff6a45; /* reserved: horizon, wordmark, top-pick */
position: relative;
z-index: 0;
min-height: 100vh;
background: var(--void);
color: var(--ink);
font-family: 'Instrument Sans', system-ui, sans-serif;
font-size: 15px;
line-height: 1.5;
overflow-x: hidden;
}
.hzn h1, .hzn h2 { font-family: 'Space Grotesk', system-ui, sans-serif; }
.hzn .num { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; }
.hzn a { color: inherit; text-decoration: none; }
.hzn :focus-visible { outline: 2px solid var(--up-text); outline-offset: 2px; border-radius: 4px; }
/* ------- atmosphere ------- */
.hzn-stars {
position: fixed; inset: 0; z-index: 0; pointer-events: none;
background-image:
radial-gradient(1px 1px at 12% 18%, rgba(237,238,243,.55) 50%, transparent 51%),
radial-gradient(1px 1px at 68% 8%, rgba(237,238,243,.4) 50%, transparent 51%),
radial-gradient(1.5px 1.5px at 84% 32%, rgba(159,216,232,.5) 50%, transparent 51%),
radial-gradient(1px 1px at 33% 44%, rgba(237,238,243,.35) 50%, transparent 51%),
radial-gradient(1px 1px at 91% 64%, rgba(237,238,243,.3) 50%, transparent 51%),
radial-gradient(1.5px 1.5px at 22% 71%, rgba(237,238,243,.42) 50%, transparent 51%),
radial-gradient(1px 1px at 55% 27%, rgba(255,150,120,.35) 50%, transparent 51%),
radial-gradient(1px 1px at 45% 87%, rgba(237,238,243,.3) 50%, transparent 51%),
radial-gradient(1px 1px at 76% 82%, rgba(159,216,232,.35) 50%, transparent 51%),
radial-gradient(1px 1px at 5% 55%, rgba(237,238,243,.35) 50%, transparent 51%);
background-size: 900px 700px;
background-repeat: repeat;
opacity: .8;
}
.hzn-horizon {
position: fixed; left: 0; right: 0; bottom: 0; height: 24vh;
z-index: 0; pointer-events: none; overflow: hidden;
}
.hzn-planet {
position: absolute; left: 50%; top: 58%;
width: 300vw; height: 300vw; transform: translateX(-50%);
border-radius: 50%;
background:
radial-gradient(ellipse 55% 14% at 50% 0.4%, rgba(255,120,80,.22), transparent 60%),
radial-gradient(ellipse 120% 26% at 50% 0%, #3f130b 0%, #260a06 32%, #130504 58%, #0a0b11 86%);
}
.hzn-rim {
position: absolute; left: 50%; top: calc(58% - 2px);
width: 300vw; height: 300vw; transform: translateX(-50%);
border-radius: 50%;
background: linear-gradient(180deg, rgba(213,240,250,.55), rgba(159,216,232,.16) 40%, transparent 60%);
filter: blur(1px);
box-shadow: 0 -14px 50px rgba(159,216,232,.10), 0 -50px 140px rgba(228,87,61,.08);
}
/* ------- layout ------- */
.hzn-page {
position: relative; z-index: 1;
max-width: 1180px; margin: 0 auto;
padding: 28px 32px 22vh;
display: flex; flex-direction: column; gap: 34px;
}
/* ------- command bar ------- */
.hzn-bar { display: flex; align-items: center; gap: 28px; flex-wrap: wrap; }
.hzn-wordmark {
font-family: 'Space Grotesk', sans-serif; font-weight: 700;
letter-spacing: .42em; font-size: 15px;
}
.hzn-wordmark i {
display: inline-block; width: 7px; height: 7px; border-radius: 50%;
background: var(--ember); margin-left: 2px;
box-shadow: 0 0 10px rgba(255,106,69,.7);
}
.hzn-nav { display: flex; gap: 22px; font-size: 13.5px; color: var(--ink-2); }
.hzn-nav a:hover { color: var(--ink); }
.hzn-nav-on { color: var(--ink); border-bottom: 1px solid var(--up); padding-bottom: 2px; }
.hzn-bar-right { margin-left: auto; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.hzn-regime { font-size: 12.5px; color: var(--ink-2); display: inline-flex; align-items: center; gap: 8px; }
.hzn-regime i { width: 7px; height: 7px; border-radius: 50%; background: var(--amber-text); display: inline-block; }
.hzn-mock {
font-family: 'IBM Plex Mono', monospace; font-size: 10px; letter-spacing: .14em;
text-transform: uppercase; color: var(--ink-3);
border: 1px solid var(--panel-line); border-radius: 99px; padding: 3px 10px;
}
/* ------- hero ------- */
.hzn-eyebrow {
font-family: 'IBM Plex Mono', monospace; font-size: 10.5px;
letter-spacing: .28em; text-transform: uppercase; color: var(--ink-3);
}
.hzn-hero { margin-top: 6px; }
.hzn-hero h1 {
font-size: clamp(28px, 4.2vw, 44px); font-weight: 600;
letter-spacing: -0.015em; margin-top: 10px;
}
.hzn-hero-sub { margin-top: 8px; color: var(--ink-2); font-size: 15px; }
.hzn-up { color: var(--up-text); }
.hzn-down { color: var(--down-text); }
/* ------- focal setup ------- */
.hzn-focus {
background: var(--panel-glass); border: 1px solid var(--panel-line);
border-radius: 16px; padding: 26px 28px 22px;
backdrop-filter: blur(14px);
}
.hzn-focus-head { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
.hzn-focus-id { display: flex; align-items: flex-start; gap: 18px; }
.hzn-pick {
font-family: 'IBM Plex Mono', monospace; font-size: 9.5px; letter-spacing: .2em;
text-transform: uppercase; color: var(--ember);
border: 1px solid rgba(255,106,69,.4); border-radius: 99px;
padding: 3px 9px; margin-top: 10px; white-space: nowrap;
}
.hzn-focus h2 { font-size: 46px; font-weight: 700; letter-spacing: -0.01em; line-height: 1; }
.hzn-focus-name { color: var(--ink-2); font-size: 13.5px; margin-top: 4px; }
.hzn-focus-tags { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.hzn-tag {
font-family: 'IBM Plex Mono', monospace; font-size: 9.5px; font-weight: 600;
letter-spacing: .12em; text-transform: uppercase;
border-radius: 4px; padding: 2.5px 7px;
}
.hzn-tag-long { color: var(--up-text); background: rgba(47,157,178,.14); }
.hzn-tag-short { color: var(--down-text); background: rgba(227,106,88,.14); }
.hzn-chip {
font-size: 11.5px; color: var(--ink-2);
border: 1px solid var(--panel-line); border-radius: 99px; padding: 2.5px 10px;
}
.hzn-focus-stats { display: flex; gap: 40px; text-align: right; }
.hzn-stat-label {
font-family: 'IBM Plex Mono', monospace; font-size: 10px;
letter-spacing: .18em; text-transform: uppercase; color: var(--ink-3);
}
.hzn-stat-value {
font-family: 'Space Grotesk', sans-serif; font-size: 30px; font-weight: 600;
margin-top: 4px; line-height: 1.1;
}
.hzn-stat-value span { font-size: 17px; color: var(--ink-2); }
.hzn-stat-dim { color: var(--ink-3); }
.hzn-meter {
width: 120px; height: 4px; border-radius: 2px; margin-top: 8px; margin-left: auto;
background: rgba(47,157,178,.16);
}
.hzn-meter span { display: block; height: 100%; border-radius: 2px; background: var(--up); }
/* ------- price rail ------- */
.hzn-rail { position: relative; height: 108px; margin: 34px 8px 4px; }
.hzn-rail-track {
position: absolute; left: 0; right: 0; top: 50%; height: 1px;
background: var(--grid);
}
.hzn-rail-risk {
position: absolute; top: calc(50% - 2px); height: 4px;
background: rgba(227,106,88,.16); border-radius: 2px;
}
.hzn-rail-progress {
position: absolute; top: calc(50% - 2px); height: 4px;
background: var(--up); border-radius: 2px;
}
.hzn-rail-mark { position: absolute; top: 0; bottom: 0; width: 0; }
.hzn-rail-tick {
position: absolute; top: calc(50% - 7px); left: -1px;
width: 2px; height: 14px; border-radius: 1px;
}
.hzn-rail-dot {
position: absolute; top: calc(50% - 6px); left: -6px;
width: 12px; height: 12px; border-radius: 50%;
background: var(--up); border: 2px solid var(--surface);
box-shadow: 0 0 0 1px var(--up), 0 0 14px rgba(110,201,219,.55);
}
.hzn-rail-ring {
position: absolute; top: calc(50% - 6px); left: -6px;
width: 12px; height: 12px; border-radius: 50%;
border: 2px solid var(--up); background: var(--void);
}
.hzn-rail-label {
position: absolute; top: calc(50% + 16px); left: 0; transform: translateX(-50%);
display: flex; flex-direction: column; align-items: center; gap: 1px;
white-space: nowrap;
}
.hzn-rail-label-top { top: auto; bottom: calc(50% + 16px); }
.hzn-rail-label em {
font-style: normal; font-family: 'IBM Plex Mono', monospace; font-size: 9.5px;
letter-spacing: .18em; text-transform: uppercase; color: var(--ink-3);
}
.hzn-rail-label b {
font-family: 'IBM Plex Mono', monospace; font-weight: 500; font-size: 13px;
color: var(--ink);
}
.hzn-rail-label i {
font-style: normal; font-family: 'IBM Plex Mono', monospace; font-size: 10.5px;
color: var(--ink-2);
}
.hzn-rail-now .hzn-rail-label b { color: var(--up-text); }
.hzn-focus-foot {
display: flex; justify-content: space-between; align-items: center;
gap: 16px; flex-wrap: wrap; margin-top: 18px;
border-top: 1px solid var(--grid); padding-top: 16px;
}
.hzn-note { font-size: 12.5px; color: var(--ink-3); }
.hzn-actions { display: flex; gap: 10px; }
.hzn-btn {
font-size: 13px; font-weight: 600; border-radius: 8px; padding: 7px 16px;
background: rgba(47,157,178,.16); color: var(--up-text);
border: 1px solid rgba(47,157,178,.35);
transition: background .15s ease, border-color .15s ease;
font-family: inherit; cursor: pointer;
}
.hzn-btn:hover { background: rgba(47,157,178,.26); border-color: rgba(110,201,219,.5); }
.hzn-btn-ghost { background: transparent; color: var(--ink-2); border-color: var(--panel-line); }
.hzn-btn-ghost:hover { color: var(--ink); background: rgba(226,231,245,.04); }
/* ------- KPI band ------- */
.hzn-kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
.hzn-tile {
background: var(--panel-glass); border: 1px solid var(--panel-line);
border-radius: 14px; padding: 18px 20px;
backdrop-filter: blur(14px);
}
.hzn-tile .hzn-stat-value { font-size: 26px; }
.hzn-tile-sub { font-size: 12px; color: var(--ink-3); margin-top: 4px; }
.hzn-tile-spark { display: flex; justify-content: space-between; align-items: flex-end; gap: 10px; }
/* ------- panels / columns ------- */
.hzn-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.hzn-panel {
background: var(--panel-glass); border: 1px solid var(--panel-line);
border-radius: 14px; padding: 20px 22px;
backdrop-filter: blur(14px);
}
.hzn-panel-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.hzn-panel-hint { font-size: 11.5px; color: var(--ink-3); }
/* positions */
.hzn-poslist { list-style: none; display: flex; flex-direction: column; }
.hzn-poslist > li { border-top: 1px solid var(--grid); }
.hzn-poslist > li:first-child { border-top: 0; }
.hzn-pos {
display: grid; grid-template-columns: 92px 1fr 130px 64px 16px;
align-items: center; gap: 12px;
width: 100%; text-align: left; background: none; border: 0; color: inherit;
font: inherit; padding: 11px 4px; cursor: pointer;
transition: background .15s ease; border-radius: 8px;
}
.hzn-pos:hover { background: rgba(226,231,245,.035); }
.hzn-pos-id { display: flex; align-items: center; gap: 8px; }
.hzn-pos-id b { font-weight: 600; }
.hzn-pos-px { font-size: 12px; color: var(--ink-2); }
.hzn-pos-r { text-align: right; font-size: 13px; font-weight: 600; }
.hzn-caret { color: var(--ink-3); font-size: 10px; transition: transform .15s ease; }
.hzn-caret-open { transform: rotate(180deg); }
.hzn-rbar { position: relative; height: 10px; }
.hzn-rbar-zero {
position: absolute; left: 50%; top: -3px; bottom: -3px; width: 1px;
background: var(--panel-line);
}
.hzn-rbar-fill { position: absolute; top: 0; bottom: 0; }
.hzn-pos-detail {
display: flex; flex-direction: column; gap: 16px;
padding: 6px 8px 16px; font-size: 12.5px;
}
.hzn-pos-detail dl { display: grid; grid-template-columns: repeat(3, minmax(0, auto)); gap: 10px 34px; }
.hzn-pos-detail dt {
font-family: 'IBM Plex Mono', monospace; font-size: 9.5px; letter-spacing: .16em;
text-transform: uppercase; color: var(--ink-3);
}
.hzn-pos-detail dd { margin-top: 2px; color: var(--ink); }
.hzn-pos-detail dd i { font-style: normal; color: var(--ink-3); font-size: 11px; margin-left: 6px; }
.hzn-pos-chart { width: 100%; }
.hzn-tradechart { width: 100%; height: auto; display: block; margin-top: 8px; }
.hzn-lvl {
fill: var(--ink-3); font-family: 'IBM Plex Mono', monospace; font-size: 10px;
}
.hzn-lvl-up { fill: var(--up-text); }
.hzn-lvl-down { fill: var(--down-text); }
.hzn-lvl-ink { fill: var(--ink-2); }
/* radar */
.hzn-radar { list-style: none; display: flex; flex-direction: column; }
.hzn-radar li {
display: grid; grid-template-columns: 20px 118px 44px 1fr 52px minmax(120px, 1.2fr);
align-items: center; gap: 10px;
padding: 10px 4px; border-top: 1px solid var(--grid);
border-radius: 8px; transition: background .15s ease;
}
.hzn-radar li:first-child { border-top: 0; }
.hzn-radar li:hover { background: rgba(226,231,245,.035); }
.hzn-radar-dim { opacity: .62; }
.hzn-radar-rank { color: var(--ink-3); font-size: 11px; }
.hzn-radar-id b { font-weight: 600; display: block; }
.hzn-radar-id small { color: var(--ink-3); font-size: 10.5px; display: block; max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hzn-radar-mom { display: flex; align-items: center; gap: 8px; }
.hzn-radar-momtrack {
flex: 1; height: 4px; border-radius: 2px; background: rgba(226,231,245,.07);
overflow: hidden;
}
.hzn-radar-momtrack i { display: block; height: 100%; border-radius: 2px; }
.hzn-radar-mom small { color: var(--ink-3); font-size: 10px; }
.hzn-radar-rr { text-align: right; font-size: 12.5px; color: var(--ink-2); }
.hzn-radar-note { font-size: 11.5px; color: var(--ink-3); text-align: right; }
.hzn-radar-yes { color: var(--up-text); }
/* ------- chart ------- */
.hzn-chart {
background: var(--panel-glass); border: 1px solid var(--panel-line);
border-radius: 14px; padding: 20px 22px 12px;
backdrop-filter: blur(14px);
}
.hzn-chart-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.hzn-chart-title { font-size: 13px; color: var(--ink-2); margin-top: 5px; }
.hzn-legend { display: flex; gap: 16px; font-size: 12px; color: var(--ink-2); }
.hzn-legend i {
display: inline-block; width: 9px; height: 9px; border-radius: 50%;
margin-right: 6px; vertical-align: -1px;
}
.hzn-chart-svg { width: 100%; height: auto; margin-top: 10px; display: block; }
.hzn-axis { fill: var(--ink-3); font-family: 'IBM Plex Mono', monospace; font-size: 10px; }
.hzn-endlabel { fill: var(--ink); font-family: 'IBM Plex Mono', monospace; font-size: 11px; font-weight: 600; }
.hzn-endlabel-dim { fill: var(--ink-2); font-weight: 400; }
.hzn-chart-tip {
font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--ink-3);
padding: 8px 2px 4px; min-height: 28px;
}
.hzn-chart-tip b { color: var(--ink); font-weight: 500; }
/* ------- radar fingerprint ------- */
.hzn-radar-wrap { position: relative; display: inline-block; }
.hzn-radar-tip {
position: absolute; transform: translate(-50%, calc(-100% - 12px));
background: rgba(21, 24, 35, 0.96); border: 1px solid var(--panel-line);
border-radius: 8px; padding: 6px 10px; white-space: nowrap;
pointer-events: none; z-index: 5; box-shadow: 0 6px 24px rgba(0,0,0,.45);
font-size: 12px;
}
.hzn-radar-tip b { display: block; color: var(--ink); font-weight: 600; }
.hzn-radar-tip span { display: block; color: var(--up-text); font-size: 11px; margin-top: 1px; }
.hzn-radar-tip i { display: block; font-style: normal; color: var(--ink-3); font-size: 10px; margin-top: 1px; }
.hzn-radar-axis {
fill: var(--ink-3); font-family: 'IBM Plex Mono', monospace; font-size: 9.5px;
letter-spacing: .04em;
}
.hzn-radar-axisval { fill: var(--ink-2); font-weight: 600; }
.hzn-tk-radar { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.hzn-cmp-row {
display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
border-bottom: 1px solid var(--grid); padding-bottom: 12px; margin-bottom: 10px;
}
.hzn-cmp-fig { text-align: center; }
.hzn-cmp-fig figcaption b { display: block; font-size: 13px; font-weight: 600; margin-top: -2px; }
.hzn-cmp-fig figcaption span {
font-family: 'IBM Plex Mono', monospace; font-size: 9px; letter-spacing: .12em;
text-transform: uppercase; color: var(--ink-3);
}
.hzn-cmp-fig .hzn-cmp-pick { color: var(--ember); }
.hzn-cmp-axes {
font-family: 'IBM Plex Mono', monospace; font-size: 10px; line-height: 1.7;
color: var(--ink-3); margin-left: auto; text-align: right; max-width: 220px;
}
/* ------- ticker drill-down ------- */
.hzn-ticker-top { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.hzn-tk-title { display: flex; align-items: baseline; gap: 10px; margin-top: 8px; }
.hzn-tk-title b { font-family: 'Space Grotesk', sans-serif; font-size: 30px; font-weight: 700; }
.hzn-tk-title span { color: var(--ink-2); font-size: 13px; }
.hzn-tk-price { display: flex; align-items: baseline; gap: 10px; margin-top: 4px; }
.hzn-tk-price .num:first-child { font-size: 18px; color: var(--ink); }
.hzn-tk-price b { font-size: 13px; }
.hzn-tk-price i { font-style: normal; font-size: 11.5px; color: var(--ink-3); }
.hzn-tk-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.hzn-tk-bars {
display: flex; justify-content: space-between; align-items: center; gap: 14px;
flex-wrap: wrap; margin-top: 16px; border-bottom: 1px solid var(--grid); padding-bottom: 10px;
}
.hzn-tk-tabs { display: flex; gap: 18px; font-size: 13px; color: var(--ink-2); }
.hzn-tk-tabs a:hover { color: var(--ink); }
.hzn-tk-tab-on { color: var(--ink); border-bottom: 1px solid var(--up); padding-bottom: 2px; }
.hzn-tk-ranges { display: flex; gap: 4px; }
.hzn-tk-ranges button {
font-family: 'IBM Plex Mono', monospace; font-size: 10.5px; color: var(--ink-3);
background: none; border: 1px solid transparent; border-radius: 6px;
padding: 3px 8px; cursor: pointer;
}
.hzn-tk-ranges button:hover { color: var(--ink-2); }
.hzn-tk-ranges .hzn-tk-range-on {
color: var(--up-text); border-color: rgba(47,157,178,.35); background: rgba(47,157,178,.12);
}
.hzn-fresh {
display: flex; gap: 16px; flex-wrap: wrap; align-items: center;
font-family: 'IBM Plex Mono', monospace; font-size: 10px; letter-spacing: .06em;
color: var(--ink-3); border-top: 1px solid var(--grid); padding-top: 10px; margin-top: 4px;
}
.hzn-fresh span { display: inline-flex; align-items: center; gap: 6px; }
.hzn-fresh i {
width: 5px; height: 5px; border-radius: 50%; background: var(--up); display: inline-block;
}
.hzn-fresh a { color: var(--ink-2); margin-left: auto; }
.hzn-fresh a:hover { color: var(--ink); }
/* ------- watchlist ------- */
.hzn-watch-chips { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.hzn-wchip {
display: inline-flex; align-items: baseline; gap: 8px;
border: 1px solid var(--panel-line); border-radius: 99px;
padding: 6px 14px; font-size: 13px;
transition: border-color .15s ease, background .15s ease;
}
.hzn-wchip:hover { border-color: rgba(110,201,219,.4); background: rgba(226,231,245,.03); }
.hzn-wchip b { font-weight: 600; }
.hzn-wchip span { font-size: 11.5px; color: var(--ink-2); }
/* ------- footer ------- */
.hzn-foot {
font-family: 'IBM Plex Mono', monospace; font-size: 10px;
letter-spacing: .3em; text-transform: uppercase; color: var(--ink-3);
text-align: center; margin-top: 10px;
}
/* ------- responsive ------- */
@media (max-width: 980px) {
.hzn-kpis { grid-template-columns: repeat(2, 1fr); }
.hzn-cols { grid-template-columns: 1fr; }
.hzn-radar li { grid-template-columns: 20px 110px 44px 1fr 48px; }
.hzn-radar-note { grid-column: 2 / -1; text-align: left; padding-bottom: 2px; }
}
@media (max-width: 640px) {
.hzn-page { padding: 20px 16px 24vh; }
.hzn-kpis { grid-template-columns: 1fr; }
.hzn-focus-stats { text-align: left; }
.hzn-meter { margin-left: 0; }
.hzn-pos { grid-template-columns: 86px 1fr 60px 14px; }
.hzn-pos-px { display: none; }
}
/* ------- motion ------- */
@media (prefers-reduced-motion: no-preference) {
.hzn-page { animation: hzn-rise .5s ease-out both; }
@keyframes hzn-rise {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
}
`;