Performance chart under positions; remove obsolete files
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 1m6s
Deploy / deploy (push) Successful in 35s

- PerfChart moves into the left column under Open Positions
  (half-width), balancing the radar column.

Cleanup:
- Design mockup pages (DesignMockups / DesignHorizon / DesignOrbit)
  and their routes removed - the redesign has shipped; the mockups
  live on in git history. Main bundle shrinks ~97 kB and the
  chunk-size warning goes away.
- frontend/tsconfig.tsbuildinfo untracked and gitignored (incremental
  build artifact that churned in every commit).
- Dead npm test / test:watch scripts removed - vitest was never a
  dependency; CI already skips the step via its vitest guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 23:07:22 +02:00
co-authored by Claude Fable 5
parent 79010a5dae
commit c3dd80e203
8 changed files with 7 additions and 2220 deletions
+1 -3
View File
@@ -6,9 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"test": "vitest --run",
"test:watch": "vitest"
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
-6
View File
@@ -9,18 +9,12 @@ import SignalsPage from './pages/SignalsPage';
import RegimePage from './pages/RegimePage';
import TickerDetailPage from './pages/TickerDetailPage';
import AdminPage from './pages/AdminPage';
import DesignMockupsPage from './pages/DesignMockupsPage';
import DesignHorizonPage from './pages/DesignHorizonPage';
import DesignOrbitPage from './pages/DesignOrbitPage';
export default function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/design-mockups" element={<DesignMockupsPage />} />
<Route path="/design-horizon" element={<DesignHorizonPage />} />
<Route path="/design-orbit" element={<DesignOrbitPage />} />
<Route element={<ProtectedRoute />}>
<Route element={<AppShell />}>
<Route path="/" element={<DashboardPage />} />
+5 -5
View File
@@ -480,9 +480,12 @@ export default function DashboardPage() {
</div>
)}
{/* Open positions | Radar — side by side, half width each */}
{/* Open positions + performance | Radar — side by side, half width each */}
<div className="grid items-start gap-8 xl:grid-cols-2">
<OpenTradesPanel />
<div className="space-y-8">
<OpenTradesPanel />
<PerfChart />
</div>
<div>
<Section title="Radar" hint="ranked by strategy score · select a row to focus it">
@@ -572,9 +575,6 @@ export default function DashboardPage() {
</Section>
</div>
</div>
{/* Performance — the paper book vs the same dollars in SPY */}
<PerfChart />
</div>
);
}
File diff suppressed because it is too large Load Diff
-680
View File
@@ -1,680 +0,0 @@
import { Link } from 'react-router-dom';
const executionRows = [
{ symbol: 'NVDA', action: 'Long high', score: 91, rr: '1:3.6', entry: '$142.30', stop: '$137.10', target: '$161.20', tape: '+2.8%' },
{ symbol: 'MSFT', action: 'Long moderate', score: 78, rr: '1:2.4', entry: '$503.10', stop: '$488.40', target: '$538.70', tape: '+0.9%' },
{ symbol: 'TSLA', action: 'Short moderate', score: 73, rr: '1:2.1', entry: '$298.20', stop: '$309.60', target: '$274.10', tape: '-1.7%' },
{ symbol: 'AMD', action: 'Neutral', score: 54, rr: '1:1.3', entry: '$164.60', stop: '$158.80', target: '$172.10', tape: '+0.2%' },
];
const ledgerRows = [
{ symbol: 'AVGO', thesis: 'Momentum with earnings buffer', risk: '0.48R', quality: 'A-', decision: 'Watch entry' },
{ symbol: 'META', thesis: 'Pullback into support band', risk: '0.32R', quality: 'B+', decision: 'Size small' },
{ symbol: 'LLY', thesis: 'High trend, weak volume follow-through', risk: '0.71R', quality: 'B', decision: 'Wait' },
];
const radarSignals = [
{ symbol: 'CRWD', lane: 'Breakout', score: 88, delta: '+4.1R' },
{ symbol: 'SHOP', lane: 'Retest', score: 82, delta: '+2.7R' },
{ symbol: 'NFLX', lane: 'Fade', score: 75, delta: '+1.9R' },
];
const structureLevels = [
{ label: 'Support', price: '137.10', note: 'invalidates below', tone: 'danger' },
{ label: 'Entry', price: '142.30', note: 'active trigger', tone: 'focus' },
{ label: 'Target', price: '161.20', note: '+13.3% upside', tone: 'success' },
] as const;
const mockCandles = [
{ x: 24, open: 142, close: 149, high: 154, low: 138 },
{ x: 48, open: 149, close: 144, high: 151, low: 141 },
{ x: 72, open: 144, close: 152, high: 156, low: 143 },
{ x: 96, open: 152, close: 158, high: 162, low: 150 },
{ x: 120, open: 158, close: 155, high: 164, low: 153 },
{ x: 144, open: 155, close: 166, high: 169, low: 154 },
{ x: 168, open: 166, close: 171, high: 176, low: 164 },
{ x: 192, open: 171, close: 168, high: 173, low: 165 },
{ x: 216, open: 168, close: 178, high: 181, low: 167 },
{ x: 240, open: 178, close: 184, high: 188, low: 176 },
{ x: 264, open: 184, close: 181, high: 187, low: 179 },
{ x: 288, open: 181, close: 191, high: 196, low: 180 },
{ x: 312, open: 191, close: 198, high: 203, low: 189 },
{ x: 336, open: 198, close: 195, high: 201, low: 192 },
{ x: 360, open: 195, close: 207, high: 211, low: 193 },
{ x: 384, open: 207, close: 214, high: 218, low: 204 },
];
function MiniChart({ color = '#8bd6de' }: { color?: string }) {
return (
<svg viewBox="0 0 360 120" className="h-28 w-full" role="img" aria-label="Mock price chart">
<defs>
<linearGradient id={`fill-${color.replace('#', '')}`} x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity="0.28" />
<stop offset="100%" stopColor={color} stopOpacity="0" />
</linearGradient>
</defs>
<path
d="M0 88 L26 82 L52 91 L78 70 L104 72 L130 48 L156 55 L182 36 L208 42 L234 29 L260 34 L286 18 L312 27 L338 15 L360 22 L360 120 L0 120 Z"
fill={`url(#fill-${color.replace('#', '')})`}
/>
<path
d="M0 88 L26 82 L52 91 L78 70 L104 72 L130 48 L156 55 L182 36 L208 42 L234 29 L260 34 L286 18 L312 27 L338 15 L360 22"
fill="none"
stroke={color}
strokeLinecap="round"
strokeWidth="3"
/>
<path d="M0 72 H360 M0 40 H360" stroke="currentColor" strokeDasharray="4 8" strokeOpacity="0.18" />
</svg>
);
}
function StructureChip({ level }: { level: (typeof structureLevels)[number] }) {
const styles = {
danger: {
box: 'border-[#d86148]/45 bg-[#24100f]',
label: 'text-[#ff9b82]',
price: 'text-[#ffd5c7]',
line: 'from-[#d86148] to-transparent',
},
focus: {
box: 'border-[#8bd6de]/60 bg-[#0d2028] shadow-[0_0_28px_rgba(139,214,222,0.14)]',
label: 'text-[#a9f1f5]',
price: 'text-white',
line: 'from-[#8bd6de] to-transparent',
},
success: {
box: 'border-[#f0b46a]/45 bg-[#281b12]',
label: 'text-[#ffc985]',
price: 'text-[#ffe6bd]',
line: 'from-[#f0b46a] to-transparent',
},
}[level.tone];
return (
<div className={`relative overflow-hidden rounded-lg border p-3 text-left ${styles.box}`}>
<div className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r ${styles.line}`} />
<p className={`font-mono text-[10px] uppercase tracking-[0.18em] ${styles.label}`}>{level.label}</p>
<p className={`num mt-1 text-lg font-semibold ${styles.price}`}>${level.price}</p>
<p className="mt-1 text-[10px] text-[#9d94aa]">{level.note}</p>
</div>
);
}
function SectionIntro({
eyebrow,
title,
body,
bestFor,
}: {
eyebrow: string;
title: string;
body: string;
bestFor: string;
}) {
return (
<div className="mx-auto mb-5 flex max-w-7xl flex-col gap-3 px-4 sm:px-6 lg:px-8">
<p className="font-mono text-[11px] uppercase tracking-[0.26em] text-gray-500">{eyebrow}</p>
<div className="flex flex-col justify-between gap-4 lg:flex-row lg:items-end">
<div className="max-w-3xl">
<h2 className="font-display text-3xl font-semibold tracking-tight text-white sm:text-4xl">{title}</h2>
<p className="mt-2 max-w-2xl text-sm leading-6 text-gray-400">{body}</p>
</div>
<p className="max-w-sm rounded-lg border border-white/10 bg-white/[0.04] px-4 py-3 text-xs leading-5 text-gray-300">
<span className="font-mono uppercase tracking-[0.18em] text-gray-500">Best for</span>
<br />
{bestFor}
</p>
</div>
</div>
);
}
function ExecutionDeskMockup() {
return (
<section className="relative overflow-hidden bg-[#05050b] py-10 text-[#f7f0e8]">
<div
className="pointer-events-none absolute inset-0 opacity-70"
style={{
background:
'radial-gradient(circle at 82% 18%, rgba(205,83,55,0.32), transparent 18%), radial-gradient(circle at 88% 24%, rgba(255,166,105,0.18), transparent 10%), radial-gradient(circle at 14% 0%, rgba(105,120,177,0.14), transparent 26%), radial-gradient(circle at 20% 36%, rgba(255,255,255,0.10) 0 1px, transparent 1.5px), radial-gradient(circle at 64% 22%, rgba(255,255,255,0.09) 0 1px, transparent 1.5px), radial-gradient(circle at 78% 66%, rgba(255,255,255,0.10) 0 1px, transparent 1.5px), linear-gradient(180deg, #070711 0%, #05050b 58%, #09070a 100%)',
}}
/>
<div className="pointer-events-none absolute -right-32 top-10 h-80 w-80 rounded-full bg-[radial-gradient(circle_at_34%_32%,#ffb077_0%,#c65a3d_35%,#4b1b1c_68%,transparent_72%)] opacity-45 blur-[1px]" />
<div className="pointer-events-none absolute right-16 top-24 h-64 w-64 rounded-full border border-[#ff9a62]/10" />
<div className="relative">
<SectionIntro
eyebrow="Direction 01 / Execution deck"
title="Orbital trading deck with a Mars-warm focal glow"
body="Still a dense workstation, but the atmosphere shifts from green terminal to deep black-violet panels, rust-red warmth, sparse stars, and cool cyan only for live instrumentation."
bestFor="Power users who want speed and confidence, with a more premium, less neon visual language."
/>
</div>
<div className="relative mx-auto grid max-w-7xl gap-4 px-4 sm:px-6 lg:grid-cols-[220px_minmax(0,1fr)_300px] lg:px-8">
<aside className="rounded-lg border border-[#2c2334] bg-[#0b0a12]/90 p-4 shadow-[0_22px_70px_rgba(0,0,0,0.38)]">
<div className="mb-6 flex items-center gap-2">
<span className="h-2 w-2 rounded-full bg-[#8bd6de] shadow-[0_0_16px_rgba(139,214,222,0.75)]" />
<span className="font-mono text-xs uppercase tracking-[0.22em] text-[#9f96ad]">Signal live</span>
</div>
{['Overview', 'Market', 'Signals', 'Regime', 'Admin'].map((item, index) => (
<div
key={item}
className={`mb-1 flex items-center justify-between rounded-md px-3 py-2 text-sm ${
item === 'Signals'
? 'border border-[#c85d42]/25 bg-[#241416] text-[#ffd6c8]'
: 'border border-transparent text-[#8c8498] hover:bg-white/[0.035]'
}`}
>
<span>{item}</span>
<span className="font-mono text-[10px]">0{index + 1}</span>
</div>
))}
<div className="mt-8 border-t border-[#2c2334] pt-4">
<p className="font-mono text-[10px] uppercase tracking-[0.22em] text-[#71697d]">Scanner</p>
<div className="mt-3 h-2 rounded-full bg-[#17131d]">
<div className="h-2 w-3/4 rounded-full bg-gradient-to-r from-[#9f3f32] via-[#d96449] to-[#f3b36f] shadow-[0_0_18px_rgba(217,100,73,0.28)]" />
</div>
<p className="mt-2 font-mono text-xs text-[#cdb4a7]">73% complete</p>
</div>
</aside>
<main className="overflow-hidden rounded-lg border border-[#33253a] bg-[#0b0b13]/95 shadow-[0_24px_90px_rgba(0,0,0,0.58)]">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#2c2334] bg-[#101019]/80 px-5 py-4">
<div>
<p className="font-mono text-[10px] uppercase tracking-[0.24em] text-[#81778e]">Signals / Today</p>
<h3 className="mt-1 font-display text-2xl font-semibold text-[#fff8ef]">Qualified setups</h3>
</div>
<button className="rounded-md border border-[#ff9a62]/40 bg-[#d96449] px-4 py-2 text-sm font-semibold text-[#160808] shadow-[0_0_24px_rgba(217,100,73,0.30)]">
Run scanner
</button>
</div>
<div className="grid gap-0 border-b border-[#2c2334] md:grid-cols-4">
{[
['Active', '18'],
['Avg R:R', '2.42'],
['High conviction', '6'],
['Drift check', '+0.31R'],
].map(([label, value]) => (
<div key={label} className="border-b border-r border-[#2c2334] p-4 md:border-b-0">
<p className="font-mono text-[10px] uppercase tracking-[0.2em] text-[#81778e]">{label}</p>
<p className="num mt-2 text-2xl font-semibold text-[#fff8ef]">{value}</p>
</div>
))}
</div>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead className="font-mono text-[10px] uppercase tracking-[0.18em] text-[#81778e]">
<tr className="border-b border-[#2c2334]">
{['Symbol', 'Action', 'Score', 'R:R', 'Entry', 'Stop', 'Target', 'Tape'].map((heading) => (
<th key={heading} className="px-4 py-3 font-medium">{heading}</th>
))}
</tr>
</thead>
<tbody>
{executionRows.map((row) => (
<tr key={row.symbol} className="border-b border-[#1c1723] text-[#d8d1dc] hover:bg-[#17111b]/70">
<td className="px-4 py-4 font-semibold text-[#fff8ef]">{row.symbol}</td>
<td className="px-4 py-4 text-[#8bd6de]">{row.action}</td>
<td className="num px-4 py-4">{row.score}</td>
<td className="num px-4 py-4 text-[#f0b46a]">{row.rr}</td>
<td className="num px-4 py-4">{row.entry}</td>
<td className="num px-4 py-4 text-[#ff9b82]">{row.stop}</td>
<td className="num px-4 py-4 text-[#ffc985]">{row.target}</td>
<td className="num px-4 py-4">{row.tape}</td>
</tr>
))}
</tbody>
</table>
</div>
</main>
<aside className="space-y-4">
<div className="relative overflow-hidden rounded-lg border border-[#33253a] bg-[#0b0a12]/95 p-4">
<div className="absolute -right-8 -top-8 h-32 w-32 rounded-full bg-[radial-gradient(circle,#d96449_0%,rgba(217,100,73,0.18)_42%,transparent_68%)] opacity-70" />
<div className="relative flex items-start justify-between gap-3">
<div>
<p className="font-mono text-[10px] uppercase tracking-[0.22em] text-[#81778e]">NVDA structure</p>
<h4 className="mt-1 font-display text-xl font-semibold text-[#fff8ef]">Entry map</h4>
</div>
<span className="rounded-full border border-[#8bd6de]/30 bg-[#8bd6de]/10 px-2.5 py-1 font-mono text-[10px] text-[#b9f8ff]">
live
</span>
</div>
<div className="relative mt-2">
<MiniChart />
<div className="pointer-events-none absolute left-[38%] top-[18%] h-[58%] w-px bg-gradient-to-b from-transparent via-[#8bd6de] to-transparent shadow-[0_0_18px_rgba(139,214,222,0.62)]" />
<div className="pointer-events-none absolute left-[38%] top-[14%] rounded-full border border-[#8bd6de]/50 bg-[#0b0a12] px-2 py-1 font-mono text-[10px] text-[#d5fbff]">
trigger
</div>
</div>
<div className="mt-3 grid gap-2">
{structureLevels.map((level) => (
<StructureChip key={level.label} level={level} />
))}
</div>
</div>
<div className="rounded-lg border border-[#33253a] bg-[#0b0a12]/95 p-4">
<p className="font-mono text-[10px] uppercase tracking-[0.22em] text-[#81778e]">Decision note</p>
<p className="mt-3 text-sm leading-6 text-[#d8d1dc]">
Prioritize setups with clean stop distance, fresh support confirmation, and no regime warning.
</p>
</div>
</aside>
</div>
</section>
);
}
function LedgerMockup() {
return (
<section className="bg-[#eef5f1] py-10 text-[#18231f]">
<div className="mx-auto mb-5 flex max-w-7xl flex-col gap-3 px-4 sm:px-6 lg:px-8">
<p className="font-mono text-[11px] uppercase tracking-[0.26em] text-[#64756e]">Direction 02 / Risk ledger</p>
<div className="flex flex-col justify-between gap-4 lg:flex-row lg:items-end">
<div className="max-w-3xl">
<h2 className="font-display text-3xl font-semibold tracking-tight text-[#18231f] sm:text-4xl">Calm, research-led risk journal</h2>
<p className="mt-2 max-w-2xl text-sm leading-6 text-[#5d6d66]">
This direction treats signals as decisions to underwrite. It puts sizing, evidence, and trade quality ahead of spectacle.
</p>
</div>
<p className="max-w-sm rounded-lg border border-[#c7d7cf] bg-white/60 px-4 py-3 text-xs leading-5 text-[#33423c]">
<span className="font-mono uppercase tracking-[0.18em] text-[#718078]">Best for</span>
<br />
A slower, more trustworthy product posture with less adrenaline and more accountability.
</p>
</div>
</div>
<div className="mx-auto grid max-w-7xl gap-4 px-4 sm:px-6 lg:grid-cols-[1.15fr_0.85fr] lg:px-8">
<main className="rounded-lg border border-[#c7d7cf] bg-[#fbfdfb] p-5 shadow-[0_20px_70px_rgba(23,48,39,0.10)]">
<div className="flex flex-wrap items-start justify-between gap-4 border-b border-[#dbe6e0] pb-5">
<div>
<p className="font-mono text-[10px] uppercase tracking-[0.22em] text-[#718078]">SignalPlatform</p>
<h3 className="mt-2 font-display text-4xl font-semibold text-[#18231f]">Today's risk book</h3>
</div>
<div className="grid grid-cols-3 gap-2 text-center">
{[
['Budget used', '42%'],
['Open R', '+3.8'],
['Max new risk', '0.6R'],
].map(([label, value]) => (
<div key={label} className="rounded-md border border-[#dbe6e0] bg-[#f4faf7] px-3 py-2">
<p className="num text-lg font-semibold text-[#1d4f43]">{value}</p>
<p className="text-[10px] text-[#718078]">{label}</p>
</div>
))}
</div>
</div>
<div className="mt-5 overflow-hidden rounded-lg border border-[#dbe6e0]">
<table className="w-full text-left text-sm">
<thead className="bg-[#f1f7f4] text-[11px] uppercase tracking-[0.14em] text-[#718078]">
<tr>
{['Ticker', 'Thesis', 'Risk', 'Quality', 'Decision'].map((heading) => (
<th key={heading} className="px-4 py-3 font-semibold">{heading}</th>
))}
</tr>
</thead>
<tbody>
{ledgerRows.map((row) => (
<tr key={row.symbol} className="border-t border-[#dbe6e0]">
<td className="px-4 py-4 font-semibold text-[#153f35]">{row.symbol}</td>
<td className="px-4 py-4 text-[#40514a]">{row.thesis}</td>
<td className="num px-4 py-4 text-[#9a3b4f]">{row.risk}</td>
<td className="px-4 py-4">{row.quality}</td>
<td className="px-4 py-4">
<span className="rounded-full bg-[#dcece5] px-3 py-1 text-xs font-medium text-[#214f44]">{row.decision}</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</main>
<aside className="grid gap-4">
<div className="rounded-lg border border-[#c7d7cf] bg-[#fbfdfb] p-5">
<p className="font-mono text-[10px] uppercase tracking-[0.22em] text-[#718078]">Evidence stack</p>
<div className="mt-4 space-y-3">
{['Market regime supports longs', 'Sentiment positive but cooling', 'Support zone tested twice', 'Earnings window clear'].map((item) => (
<div key={item} className="flex items-center gap-3 rounded-md bg-[#f1f7f4] p-3 text-sm text-[#33423c]">
<span className="h-2 w-2 rounded-full bg-[#1f8a70]" />
{item}
</div>
))}
</div>
</div>
<div className="rounded-lg border border-[#c7d7cf] bg-[#18231f] p-5 text-[#eef5f1]">
<p className="font-mono text-[10px] uppercase tracking-[0.22em] text-[#9fb5ac]">Primary decision</p>
<p className="mt-3 text-3xl font-semibold">Let quality decide size.</p>
<p className="mt-3 text-sm leading-6 text-[#c7d7cf]">
The signature element is a risk budget strip that makes every trade feel accountable before it feels exciting.
</p>
</div>
</aside>
</div>
</section>
);
}
function RadarMockup() {
return (
<section className="bg-[#08090d] py-10 text-white">
<SectionIntro
eyebrow="Direction 03 / Signal radar"
title="Visual triage for what needs attention now"
body="The page is built around a radar map: signals are grouped by setup type, conviction, and urgency before the user ever opens a table."
bestFor="A more distinctive product identity where the scanner is the hero and tables become secondary detail."
/>
<div className="mx-auto grid max-w-7xl gap-4 px-4 sm:px-6 lg:grid-cols-[minmax(0,1fr)_360px] lg:px-8">
<main className="relative min-h-[560px] overflow-hidden rounded-lg border border-[#2b3040] bg-[#10121a] p-5">
<div
className="absolute left-1/2 top-1/2 h-[680px] w-[680px] -translate-x-1/2 -translate-y-1/2 rounded-full opacity-80"
style={{
background:
'radial-gradient(circle, rgba(75, 231, 208, 0.10) 0 2px, transparent 3px 100%), repeating-radial-gradient(circle, rgba(255,255,255,0.12) 0 1px, transparent 1px 110px), conic-gradient(from 18deg, rgba(75,231,208,0.35), rgba(255,90,139,0.10), rgba(244,208,111,0.22), rgba(75,231,208,0.35))',
}}
/>
<div className="relative z-10 flex flex-wrap items-center justify-between gap-4">
<div>
<p className="font-mono text-[10px] uppercase tracking-[0.24em] text-[#7a8298]">Scanner field</p>
<h3 className="mt-1 font-display text-4xl font-semibold">Signal radar</h3>
</div>
<div className="rounded-full border border-[#4be7d0]/30 bg-[#4be7d0]/10 px-4 py-2 font-mono text-xs text-[#90fff0]">
32 live candidates
</div>
</div>
{[
['Breakout', 'left-[18%] top-[34%]', '#4be7d0'],
['Retest', 'left-[58%] top-[22%]', '#f4d06f'],
['Fade', 'left-[68%] top-[62%]', '#ff5a8b'],
['Neutral', 'left-[30%] top-[70%]', '#9aa4bd'],
].map(([label, position, color]) => (
<div key={label} className={`absolute ${position} z-10`}>
<div className="rounded-full border bg-[#10121a]/90 px-3 py-2 text-xs font-semibold" style={{ borderColor: color, color }}>
{label}
</div>
</div>
))}
<div className="absolute bottom-5 left-5 right-5 z-10 grid gap-3 md:grid-cols-3">
{radarSignals.map((signal) => (
<div key={signal.symbol} className="rounded-lg border border-white/10 bg-[#08090d]/80 p-4 backdrop-blur">
<div className="flex items-center justify-between">
<span className="font-display text-2xl font-semibold">{signal.symbol}</span>
<span className="font-mono text-xs text-[#4be7d0]">{signal.delta}</span>
</div>
<p className="mt-1 text-xs text-[#9aa4bd]">{signal.lane}</p>
<div className="mt-4 h-1.5 rounded-full bg-white/10">
<div className="h-1.5 rounded-full bg-[#4be7d0]" style={{ width: `${signal.score}%` }} />
</div>
<p className="mt-2 font-mono text-[10px] text-[#7a8298]">Conviction {signal.score}</p>
</div>
))}
</div>
</main>
<aside className="space-y-4">
<div className="rounded-lg border border-[#2b3040] bg-[#10121a] p-5">
<p className="font-mono text-[10px] uppercase tracking-[0.24em] text-[#7a8298]">Triage filters</p>
<div className="mt-4 grid grid-cols-2 gap-2">
{['Qualified', 'R:R > 2', 'Regime ok', 'Fresh only'].map((item) => (
<button key={item} className="rounded-md border border-white/10 bg-white/[0.04] px-3 py-2 text-sm text-[#dfe6ff]">{item}</button>
))}
</div>
</div>
<div className="rounded-lg border border-[#2b3040] bg-[#10121a] p-5">
<p className="font-mono text-[10px] uppercase tracking-[0.24em] text-[#7a8298]">Queue</p>
<div className="mt-4 space-y-3">
{radarSignals.map((signal, index) => (
<div key={signal.symbol} className="flex items-center justify-between rounded-md bg-white/[0.04] p-3">
<div>
<p className="font-semibold">{signal.symbol}</p>
<p className="text-xs text-[#9aa4bd]">{signal.lane}</p>
</div>
<span className="num text-lg text-[#4be7d0]">{index + 1}</span>
</div>
))}
</div>
</div>
<div className="rounded-lg border border-[#ff5a8b]/25 bg-[#ff5a8b]/10 p-5">
<p className="font-mono text-[10px] uppercase tracking-[0.24em] text-[#ff9ab9]">Design risk</p>
<p className="mt-3 text-sm leading-6 text-[#ffd5e2]">
Most memorable, but it needs careful interaction design so the radar stays useful rather than decorative.
</p>
</div>
</aside>
</div>
</section>
);
}
function MockCandlestickChart() {
const minPrice = 134;
const maxPrice = 222;
const chartTop = 28;
const chartHeight = 284;
const y = (price: number) => chartTop + ((maxPrice - price) / (maxPrice - minPrice)) * chartHeight;
const candleWidth = 12;
const levels = [
{ label: 'Target', price: 214, color: '#f4d06f', dash: '8 5' },
{ label: 'Entry', price: 184, color: '#66e7c6', dash: '0' },
{ label: 'Stop', price: 165, color: '#ff7777', dash: '5 4' },
{ label: 'Support', price: 158, color: '#ff9f9f', dash: '2 6' },
];
return (
<svg viewBox="0 0 520 360" className="h-[360px] w-full" role="img" aria-label="Ticker detail candlestick mockup">
<defs>
<linearGradient id="ticker-reward-zone" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor="#f4d06f" stopOpacity="0.26" />
<stop offset="100%" stopColor="#66e7c6" stopOpacity="0.05" />
</linearGradient>
<linearGradient id="ticker-risk-zone" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor="#66e7c6" stopOpacity="0.04" />
<stop offset="100%" stopColor="#ff7777" stopOpacity="0.24" />
</linearGradient>
<filter id="soft-glow">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<rect x="0" y="0" width="520" height="360" rx="14" fill="#07110f" />
{[0, 1, 2, 3, 4].map((tick) => (
<line key={tick} x1="18" x2="472" y1={52 + tick * 58} y2={52 + tick * 58} stroke="#15352f" strokeWidth="1" />
))}
<rect
x="18"
y={y(214)}
width="454"
height={Math.max(y(184) - y(214), 1)}
fill="url(#ticker-reward-zone)"
/>
<rect
x="18"
y={y(184)}
width="454"
height={Math.max(y(165) - y(184), 1)}
fill="url(#ticker-risk-zone)"
/>
{levels.map((level) => (
<g key={level.label}>
<line
x1="18"
x2="472"
y1={y(level.price)}
y2={y(level.price)}
stroke={level.color}
strokeDasharray={level.dash}
strokeWidth={level.label === 'Entry' ? 2 : 1.3}
opacity={level.label === 'Entry' ? 1 : 0.76}
/>
<rect x="382" y={y(level.price) - 13} width="120" height="26" rx="6" fill="#07110f" stroke={level.color} opacity="0.96" />
<text x="392" y={y(level.price) + 4} fill={level.color} fontFamily="IBM Plex Mono, monospace" fontSize="10">
{level.label} ${level.price}
</text>
</g>
))}
{mockCandles.map((bar) => {
const bullish = bar.close >= bar.open;
const color = bullish ? '#66e7c6' : '#ff7777';
const bodyTop = Math.min(y(bar.open), y(bar.close));
const bodyHeight = Math.max(Math.abs(y(bar.close) - y(bar.open)), 2);
return (
<g key={bar.x}>
<line x1={bar.x} x2={bar.x} y1={y(bar.high)} y2={y(bar.low)} stroke={color} strokeWidth="1.6" opacity="0.92" />
<rect
x={bar.x - candleWidth / 2}
y={bodyTop}
width={candleWidth}
height={bodyHeight}
rx="2"
fill={color}
opacity={bullish ? 0.92 : 0.82}
/>
</g>
);
})}
<circle cx="240" cy={y(184)} r="5" fill="#66e7c6" filter="url(#soft-glow)" />
<path d={`M240 ${y(184)} C280 ${y(176)}, 320 ${y(184)}, 360 ${y(166)}`} fill="none" stroke="#66e7c6" strokeWidth="1.4" strokeDasharray="4 5" opacity="0.75" />
<text x="25" y="332" fill="#5f8f84" fontFamily="IBM Plex Mono, monospace" fontSize="10">
1Y view - drag/zoom preserved in production chart
</text>
</svg>
);
}
function TickerDetailMockup() {
return (
<section className="bg-[#030806] py-10 text-[#ecfff9]">
<SectionIntro
eyebrow="Direction 04 / Ticker detail"
title="Candles first, setup decision second"
body="A ticker page should make the chart explain the trade: the reward zone, risk zone, trigger and invalidation are visible before the supporting panels."
bestFor="The actual ticker screen where a user decides whether the scanner's setup deserves attention."
/>
<div className="mx-auto grid max-w-7xl gap-4 px-4 sm:px-6 lg:grid-cols-[minmax(0,1fr)_340px] lg:px-8">
<main className="overflow-hidden rounded-lg border border-[#1d463f] bg-[#081310] shadow-[0_24px_90px_rgba(0,0,0,0.55)]">
<div className="flex flex-wrap items-end justify-between gap-4 border-b border-[#15352f] p-5">
<div>
<div className="flex flex-wrap items-center gap-3">
<h3 className="font-display text-4xl font-semibold text-[#f4fffb]">NVDA</h3>
<span className="rounded-full border border-[#3ef1bd]/30 bg-[#3ef1bd]/10 px-3 py-1 font-mono text-[10px] uppercase tracking-[0.18em] text-[#8dffe8]">
Long high
</span>
<span className="rounded-full border border-[#f4d06f]/30 bg-[#f4d06f]/10 px-3 py-1 font-mono text-[10px] uppercase tracking-[0.18em] text-[#ffe79d]">
Top pick
</span>
</div>
<p className="mt-2 text-sm text-[#7ea79d]">NVIDIA Corp - momentum breakout with clean support shelf</p>
</div>
<div className="grid grid-cols-3 gap-2 text-right">
{[
['Last', '$207.40', 'text-[#f4fffb]'],
['Day', '+2.8%', 'text-[#66e7c6]'],
['R:R', '1:3.6', 'text-[#f4d06f]'],
].map(([label, value, color]) => (
<div key={label} className="rounded-lg border border-[#15352f] bg-[#07110f] px-3 py-2">
<p className="font-mono text-[10px] uppercase tracking-[0.16em] text-[#5f8f84]">{label}</p>
<p className={`num mt-1 text-lg font-semibold ${color}`}>{value}</p>
</div>
))}
</div>
</div>
<div className="p-5">
<div className="mb-3 flex flex-wrap items-center justify-between gap-3">
<div className="flex flex-wrap gap-1">
{['1M', '3M', '6M', '1Y', 'All'].map((range) => (
<button
key={range}
className={`rounded px-2.5 py-1 font-mono text-[11px] ${
range === '1Y' ? 'bg-[#3ef1bd]/15 text-[#8dffe8]' : 'text-[#5f8f84] hover:bg-white/[0.04]'
}`}
>
{range}
</button>
))}
</div>
<p className="font-mono text-[10px] uppercase tracking-[0.2em] text-[#5f8f84]">
overlay: auto setup
</p>
</div>
<MockCandlestickChart />
</div>
</main>
<aside className="space-y-4">
<div className="rounded-lg border border-[#3ef1bd]/35 bg-[#07110f] p-5 shadow-[0_0_45px_rgba(62,241,189,0.08)]">
<p className="font-mono text-[10px] uppercase tracking-[0.24em] text-[#5f8f84]">Trade ticket</p>
<h4 className="mt-2 font-display text-2xl font-semibold text-[#f4fffb]">Take the breakout only above entry.</h4>
<div className="mt-5 space-y-3">
{structureLevels.map((level) => (
<StructureChip key={level.label} level={level} />
))}
</div>
<button className="mt-5 w-full rounded-md border border-[#4adfba]/40 bg-[#2dd4aa] px-4 py-3 text-sm font-semibold text-[#03100d] shadow-[0_0_24px_rgba(45,212,170,0.28)]">
Add paper trade
</button>
</div>
<div className="rounded-lg border border-[#15352f] bg-[#07110f] p-5">
<p className="font-mono text-[10px] uppercase tracking-[0.24em] text-[#5f8f84]">Why it qualifies</p>
<div className="mt-4 space-y-3">
{[
['Momentum', '92nd percentile', '#66e7c6'],
['Composite', '88 / 100', '#f4d06f'],
['Regime', 'Risk-on confirmed', '#66e7c6'],
['Sentiment', 'Positive, not crowded', '#9bd8cb'],
].map(([label, value, color]) => (
<div key={label} className="flex items-center justify-between border-b border-[#15352f] pb-3 last:border-0 last:pb-0">
<span className="text-sm text-[#7ea79d]">{label}</span>
<span className="font-mono text-xs" style={{ color }}>{value}</span>
</div>
))}
</div>
</div>
<div className="rounded-lg border border-[#15352f] bg-[#07110f] p-5">
<p className="font-mono text-[10px] uppercase tracking-[0.24em] text-[#5f8f84]">Polish note</p>
<p className="mt-3 text-sm leading-6 text-[#cfeee6]">
The tiny distinction matters: Entry is the action, target is the reward, support is the line that says when the idea is wrong.
</p>
</div>
</aside>
</div>
</section>
);
}
export default function DesignMockupsPage() {
return (
<div className="min-h-screen bg-[#08090d] font-sans">
<header className="border-b border-white/10 bg-[#0d100f] px-4 py-5 text-white sm:px-6 lg:px-8">
<div className="mx-auto flex max-w-7xl flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="font-mono text-[11px] uppercase tracking-[0.26em] text-gray-500">Design exploration</p>
<h1 className="mt-1 font-display text-3xl font-semibold tracking-tight">Signal page mockups</h1>
<p className="mt-2 max-w-2xl text-sm text-gray-400">
Three different product philosophies for the trading signal experience. Static mockups, built for comparison before production adoption.
</p>
</div>
<Link
to="/signals"
className="w-fit rounded-md border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-medium text-gray-200 transition hover:bg-white/[0.08]"
>
Back to signals
</Link>
</div>
</header>
<ExecutionDeskMockup />
<LedgerMockup />
<RadarMockup />
<TickerDetailMockup />
</div>
);
}
-134
View File
@@ -1,134 +0,0 @@
import { useState } from 'react';
type Opportunity = {
symbol: string;
company: string;
direction: 'Long' | 'Short';
confidence: number;
change: string;
detail: string;
};
const opportunities: Opportunity[] = [
{ symbol: 'AAPL', company: 'Apple Inc.', direction: 'Long', confidence: 82, change: '+1.8%', detail: 'Clearing a three-week base with volume support.' },
{ symbol: 'LLY', company: 'Eli Lilly', direction: 'Long', confidence: 76, change: '+0.9%', detail: 'Holding above the 20-day trend after a quiet pullback.' },
{ symbol: 'NVDA', company: 'NVIDIA', direction: 'Long', confidence: 71, change: '+2.3%', detail: 'Strong relative momentum; entry is extended from support.' },
];
function ArrowUpRight() {
return <svg viewBox="0 0 20 20" aria-hidden="true"><path d="M5 15 15 5M7 5h8v8" /></svg>;
}
function ChevronRight() {
return <svg viewBox="0 0 20 20" aria-hidden="true"><path d="m7 4 6 6-6 6" /></svg>;
}
function Close() {
return <svg viewBox="0 0 20 20" aria-hidden="true"><path d="m5 5 10 10M15 5 5 15" /></svg>;
}
function PricePath() {
return (
<svg className="orbit-price-path" viewBox="0 0 680 205" preserveAspectRatio="none" aria-label="Illustrative AAPL price trend" role="img">
<defs>
<linearGradient id="orbit-fill" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor="#e55d45" stopOpacity=".3" />
<stop offset="100%" stopColor="#e55d45" stopOpacity="0" />
</linearGradient>
<filter id="orbit-line-glow"><feGaussianBlur stdDeviation="3" result="blur" /><feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge></filter>
</defs>
<path className="orbit-chart-grid" d="M0 42H680M0 102H680M0 162H680" />
<path className="orbit-chart-fill" d="M0 170C36 160 59 130 84 140S123 153 148 132s45-42 68-33 39 38 66 24 36-63 65-53 38 37 65 27 40-39 66-18 34 43 62 25 47-61 75-48 43 31 65 9v142H0Z" />
<path className="orbit-chart-line" d="M0 170C36 160 59 130 84 140S123 153 148 132s45-42 68-33 39 38 66 24 36-63 65-53 38 37 65 27 40-39 66-18 34 43 62 25 47-61 75-48 43 31 65 9" />
<circle cx="680" cy="63" r="5" className="orbit-chart-dot" filter="url(#orbit-line-glow)" />
</svg>
);
}
export default function DesignOrbitPage() {
const [selected, setSelected] = useState<Opportunity>(opportunities[0]);
const [detailOpen, setDetailOpen] = useState(false);
const openDetail = (opportunity: Opportunity) => {
setSelected(opportunity);
setDetailOpen(true);
};
return (
<main className="orbit-page">
<style>{orbitStyles}</style>
<div className="orbit-stars" aria-hidden="true" />
<aside className="orbit-nav" aria-label="Primary navigation">
<a className="orbit-mark" href="#overview" aria-label="Orbit home"><span />orbit</a>
<nav>
<a className="is-current" href="#overview"><i>01</i>Overview</a>
<a href="#opportunities"><i>02</i>Opportunities <b>3</b></a>
<a href="#positions"><i>03</i>Positions</a>
<a href="#watchlist"><i>04</i>Watchlist</a>
</nav>
<div className="orbit-nav-foot">
<span className="orbit-avatar">DN</span>
<div><strong>Dennis</strong><small>Personal account</small></div>
</div>
</aside>
<section className="orbit-content" id="overview">
<header className="orbit-topbar">
<div className="orbit-breadcrumb"><span className="orbit-live-dot" />Live market <em>·</em> Wed, May 22 <em>·</em> 10:42 ET</div>
<div className="orbit-top-actions"><button className="orbit-icon-button" aria-label="Search"> K</button><button className="orbit-profile">DN</button></div>
</header>
<div className="orbit-intro">
<div><p>Good morning, Dennis</p><h1>Make one clear move.</h1></div>
<button className="orbit-quiet-button" onClick={() => openDetail(opportunities[0])}>Review today&apos;s brief <ArrowUpRight /></button>
</div>
<section className="orbit-status" aria-label="Market permission">
<div className="orbit-status-orb" aria-hidden="true"><span /></div>
<div className="orbit-status-copy"><p>Market permission</p><h2>Constructive. New longs are in play.</h2><span>Trend and breadth are aligned. Keep position risk normal.</span></div>
<div className="orbit-regime"><span>Regime</span><strong>Risk on</strong><small>72 / 100</small></div>
<button className="orbit-round-button" onClick={() => openDetail(opportunities[0])} aria-label="View market detail"><ChevronRight /></button>
</section>
<div className="orbit-layout">
<section className="orbit-primary-card" aria-labelledby="focus-title">
<div className="orbit-card-head"><div><p className="orbit-overline">Focus now</p><h2 id="focus-title">AAPL <span>Apple Inc.</span></h2></div><button className="orbit-confidence" onClick={() => openDetail(opportunities[0])}><b>82</b><span>conviction</span></button></div>
<div className="orbit-price-row"><div><span>Last price</span><strong>$190.90</strong><small>+1.8% today</small></div><div className="orbit-call"><span>Suggested action</span><b>Enter on strength above $191.25</b></div></div>
<div className="orbit-chart-wrap"><PricePath /><div className="orbit-chart-label left">May 1</div><div className="orbit-chart-label mid">May 10</div><div className="orbit-chart-label right">Today</div><div className="orbit-target"><span>Target</span><b>$202.00</b></div></div>
<div className="orbit-metrics"><div><span>Risk to stop</span><strong>1.9%</strong></div><div><span>Reward / risk</span><strong>2.7<span>x</span></strong></div><div><span>Room to target</span><strong>5.8%</strong></div><button onClick={() => openDetail(opportunities[0])}>See setup <ChevronRight /></button></div>
</section>
<aside className="orbit-side-stack">
<section className="orbit-positions" id="positions"><div className="orbit-section-title"><div><p className="orbit-overline">Your exposure</p><h2>Two positions, calm risk.</h2></div><button onClick={() => openDetail(opportunities[1])}>View all <ChevronRight /></button></div><div className="orbit-position-meter"><div className="orbit-meter-track"><span style={{ width: '38%' }} /></div><div><b>38%</b><span>of risk budget in use</span></div></div><div className="orbit-position-values"><div><span>Open P&amp;L</span><b className="positive">+$428.60</b></div><div><span>Risk to stops</span><b>$316.00</b></div></div></section>
<section className="orbit-watch" id="watchlist"><div><p className="orbit-overline">Watchlist pulse</p><h2>Three names are moving.</h2></div><div className="orbit-watch-names"><button onClick={() => openDetail(opportunities[2])}><b>NVDA</b><span>+2.3%</span></button><button onClick={() => openDetail(opportunities[1])}><b>LLY</b><span>+0.9%</span></button><button onClick={() => openDetail(opportunities[0])}><b>MSFT</b><span>+0.7%</span></button></div></section>
</aside>
</div>
<section className="orbit-opportunities" id="opportunities">
<div className="orbit-section-title"><div><p className="orbit-overline">Worth a look</p><h2>Opportunities, in order.</h2><span>Only setups that meet your activation rules appear here.</span></div><button onClick={() => setDetailOpen(true)}>Open scanner <ArrowUpRight /></button></div>
<div className="orbit-opportunity-grid">
{opportunities.map((item, index) => <button className="orbit-opportunity" key={item.symbol} onClick={() => openDetail(item)}><span className="orbit-rank">0{index + 1}</span><div className="orbit-opportunity-name"><strong>{item.symbol}</strong><span>{item.company}</span></div><div className="orbit-signal"><i className={item.direction === 'Long' ? 'long' : 'short'} />{item.direction}</div><div className="orbit-opportunity-score"><b>{item.confidence}</b><span>conviction</span></div><ChevronRight /></button>)}
</div>
</section>
</section>
{detailOpen && <div className="orbit-drawer-backdrop" onMouseDown={() => setDetailOpen(false)}><aside className="orbit-drawer" aria-label={`${selected.symbol} setup details`} onMouseDown={(event) => event.stopPropagation()}><button className="orbit-drawer-close" onClick={() => setDetailOpen(false)} aria-label="Close details"><Close /></button><p className="orbit-overline">Setup detail</p><h2>{selected.symbol}<span>{selected.company}</span></h2><div className="orbit-drawer-price">$190.90 <b>{selected.change}</b></div><div className="orbit-drawer-action"><span>Suggested action</span><strong>{selected.direction === 'Long' ? 'Enter on a break above $191.25' : 'Wait for a loss of $189.20'}</strong></div><dl><div><dt>Conviction</dt><dd>{selected.confidence} / 100</dd></div><div><dt>Risk to stop</dt><dd>1.9%</dd></div><div><dt>Target</dt><dd>$202.00</dd></div></dl><p className="orbit-drawer-note">{selected.detail}</p><button className="orbit-primary-button" onClick={() => setDetailOpen(false)}>Open full analysis <ArrowUpRight /></button></aside></div>}
</main>
);
}
const orbitStyles = `
.orbit-page { --ink:#e9e8e5; --muted:#98999d; --faint:#6c6e73; --line:rgba(255,255,255,.09); --panel:rgba(19,20,25,.76); --ember:#e05c45; --ember-light:#ff8b70; min-height:100vh; overflow:hidden; position:relative; color:var(--ink); background:#101115; font-family:'Instrument Sans',system-ui,sans-serif; }
.orbit-page *{box-sizing:border-box}.orbit-page button,.orbit-page a{font:inherit}.orbit-page button{cursor:pointer}.orbit-stars{position:fixed;inset:0;pointer-events:none;opacity:.75;background-image:radial-gradient(circle at 8% 14%,rgba(255,255,255,.6) 0 1px,transparent 1.5px),radial-gradient(circle at 18% 78%,rgba(255,255,255,.42) 0 1px,transparent 1.5px),radial-gradient(circle at 76% 13%,rgba(255,255,255,.46) 0 1px,transparent 1.5px),radial-gradient(circle at 95% 43%,rgba(255,255,255,.32) 0 1px,transparent 1.5px),radial-gradient(ellipse 35% 30% at 72% 30%,rgba(184,53,37,.15),transparent 70%),radial-gradient(ellipse 35% 38% at 14% 102%,rgba(203,80,53,.11),transparent 74%);}
.orbit-nav{position:fixed;z-index:2;inset:0 auto 0 0;width:224px;display:flex;flex-direction:column;padding:30px 16px 22px;border-right:1px solid var(--line);background:rgba(14,15,19,.72);backdrop-filter:blur(20px)}.orbit-mark{display:flex;align-items:center;gap:9px;margin:0 12px 49px;color:var(--ink);font-family:'Bricolage Grotesque',sans-serif;font-size:22px;font-weight:600;letter-spacing:-.07em;text-decoration:none}.orbit-mark span{width:15px;height:15px;border-radius:50%;background:radial-gradient(circle at 34% 30%,#ffb090 0 8%,#ec765b 30%,#9d3227 74%);box-shadow:0 0 18px rgba(233,91,66,.5)}.orbit-nav nav{display:grid;gap:3px}.orbit-nav nav a{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;color:#898b91;font-size:13px;text-decoration:none;transition:.2s}.orbit-nav nav a:hover{color:#ddd}.orbit-nav nav a.is-current{background:rgba(255,255,255,.065);color:#f3f1ed}.orbit-nav nav i{width:18px;color:#595b61;font-style:normal;font-size:9px;font-family:'IBM Plex Mono',monospace}.orbit-nav nav b{margin-left:auto;display:grid;place-items:center;width:19px;height:19px;border-radius:50%;background:rgba(224,92,69,.15);color:#f1846e;font:500 10px 'IBM Plex Mono',monospace}.orbit-nav-foot{display:flex;gap:9px;align-items:center;margin-top:auto;padding:12px}.orbit-avatar,.orbit-profile{display:grid;place-items:center;width:28px;height:28px;border:1px solid rgba(255,255,255,.14);border-radius:50%;background:#292a30;color:#ddd;font:500 10px 'IBM Plex Mono',monospace}.orbit-nav-foot strong,.orbit-nav-foot small{display:block}.orbit-nav-foot strong{font-size:11px;font-weight:500}.orbit-nav-foot small{margin-top:2px;color:#62646a;font-size:10px}
.orbit-content{position:relative;z-index:1;max-width:1430px;margin-left:224px;padding:0 clamp(28px,5vw,76px) 80px}.orbit-topbar{height:80px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line)}.orbit-breadcrumb{display:flex;align-items:center;gap:8px;color:#8f9195;font:500 11px 'IBM Plex Mono',monospace}.orbit-breadcrumb em{color:#484a50;font-style:normal}.orbit-live-dot{width:6px;height:6px;border-radius:50%;background:#ef7c62;box-shadow:0 0 10px #ef7c62}.orbit-top-actions{display:flex;align-items:center;gap:10px}.orbit-icon-button{height:29px;padding:0 9px;border:1px solid var(--line);border-radius:5px;background:transparent;color:#929399;font:10px 'IBM Plex Mono',monospace}.orbit-profile{border-color:rgba(224,92,69,.3);background:rgba(224,92,69,.1);color:#f18a74}
.orbit-intro{display:flex;align-items:end;justify-content:space-between;padding:52px 0 33px}.orbit-intro p{margin:0 0 7px;color:#8e9096;font-size:14px}.orbit-intro h1{margin:0;color:#f1f0ed;font-family:'Bricolage Grotesque',sans-serif;font-size:clamp(32px,4vw,48px);font-weight:500;letter-spacing:-.055em;line-height:1}.orbit-quiet-button,.orbit-section-title>button{display:flex;align-items:center;gap:7px;border:0;background:none;color:#c6c5c5;font-size:12px}.orbit-quiet-button:hover,.orbit-section-title>button:hover{color:#fff}.orbit-quiet-button svg,.orbit-section-title svg,.orbit-metrics svg,.orbit-opportunity svg,.orbit-round-button svg,.orbit-drawer-close svg,.orbit-primary-button svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.orbit-status{position:relative;display:grid;grid-template-columns:52px 1fr auto 34px;align-items:center;gap:17px;min-height:113px;padding:19px 20px;border:1px solid rgba(224,92,69,.22);border-radius:12px;background:linear-gradient(100deg,rgba(116,38,29,.34),rgba(27,24,27,.72) 36%,rgba(20,21,25,.84));box-shadow:inset 0 1px rgba(255,255,255,.025),0 18px 45px rgba(0,0,0,.15);overflow:hidden}.orbit-status:after{content:'';position:absolute;width:230px;height:230px;right:-90px;top:-144px;border:1px solid rgba(255,139,112,.11);border-radius:50%;box-shadow:0 0 0 32px rgba(255,139,112,.025),0 0 0 64px rgba(255,139,112,.017)}.orbit-status-orb{position:relative;width:52px;height:52px;border-radius:50%;background:radial-gradient(circle at 34% 29%,#ffc1a8 0 5%,#ef815f 19%,#c04332 54%,#62201e 100%);box-shadow:inset -9px -7px 14px rgba(55,8,7,.6),0 0 30px rgba(227,80,56,.22)}.orbit-status-orb span{position:absolute;inset:-7px;border:1px solid rgba(255,162,136,.2);border-radius:50%;transform:rotate(-23deg) scaleX(1.58)}.orbit-status-copy{position:relative;z-index:1}.orbit-status-copy p,.orbit-overline{margin:0;color:#df826f;font:500 10px 'IBM Plex Mono',monospace;letter-spacing:.11em;text-transform:uppercase}.orbit-status-copy h2{margin:5px 0 4px;font-size:18px;font-weight:500;letter-spacing:-.025em}.orbit-status-copy>span{color:#929398;font-size:12px}.orbit-regime{position:relative;z-index:1;display:grid;gap:3px;min-width:85px;margin-right:9px}.orbit-regime span{color:#83858a;font-size:10px}.orbit-regime strong{font-size:13px;font-weight:500}.orbit-regime small{color:#e38470;font:10px 'IBM Plex Mono',monospace}.orbit-round-button{position:relative;z-index:1;display:grid;place-items:center;width:32px;height:32px;padding:0;border:1px solid rgba(255,255,255,.13);border-radius:50%;background:rgba(255,255,255,.04);color:#eee}.orbit-round-button:hover{background:rgba(255,255,255,.1)}
.orbit-layout{display:grid;grid-template-columns:minmax(0,1.54fr) minmax(290px,.74fr);gap:18px;margin-top:18px}.orbit-primary-card,.orbit-positions,.orbit-watch,.orbit-opportunity{border:1px solid var(--line);border-radius:12px;background:var(--panel);box-shadow:inset 0 1px rgba(255,255,255,.025)}.orbit-primary-card{overflow:hidden}.orbit-card-head{display:flex;align-items:start;justify-content:space-between;padding:24px 27px 8px}.orbit-card-head h2{margin:7px 0 0;font:500 30px 'Bricolage Grotesque',sans-serif;letter-spacing:-.06em}.orbit-card-head h2 span,.orbit-drawer h2 span{margin-left:10px;color:#8a8c91;font:400 12px 'Instrument Sans',sans-serif;letter-spacing:0}.orbit-confidence{display:grid;place-items:center;width:57px;height:57px;padding:0;border:1px solid rgba(224,92,69,.4);border-radius:50%;background:rgba(224,92,69,.08);color:#ff987d}.orbit-confidence b{font:500 17px 'IBM Plex Mono',monospace;line-height:1}.orbit-confidence span{margin-top:2px;font-size:8px}.orbit-price-row{display:flex;align-items:end;gap:38px;padding:4px 27px 11px}.orbit-price-row span,.orbit-metrics span{display:block;color:#85878c;font-size:10px}.orbit-price-row strong{display:inline-block;margin-top:5px;font:500 22px 'IBM Plex Mono',monospace;letter-spacing:-.05em}.orbit-price-row small{margin-left:8px;color:#ec7e67;font:11px 'IBM Plex Mono',monospace}.orbit-call{margin-bottom:1px;padding-left:15px;border-left:1px solid rgba(255,255,255,.16)}.orbit-call b{display:block;margin-top:4px;font-size:12px;font-weight:500}.orbit-chart-wrap{position:relative;height:205px;margin:0 20px}.orbit-price-path{display:block;width:100%;height:100%}.orbit-chart-grid{fill:none;stroke:rgba(255,255,255,.065);stroke-width:1}.orbit-chart-fill{fill:url(#orbit-fill)}.orbit-chart-line{fill:none;stroke:#ef745a;stroke-width:1.5}.orbit-chart-dot{fill:#ff9d84}.orbit-chart-label{position:absolute;bottom:8px;color:#6d6e73;font:9px 'IBM Plex Mono',monospace}.orbit-chart-label.left{left:7px}.orbit-chart-label.mid{left:49%}.orbit-chart-label.right{right:7px}.orbit-target{position:absolute;right:17px;top:21px;display:grid;gap:2px;padding:7px 9px;border:1px solid rgba(255,145,118,.2);border-radius:6px;background:rgba(47,25,26,.82)}.orbit-target span{color:#b7796d;font-size:9px}.orbit-target b{font:500 11px 'IBM Plex Mono',monospace}.orbit-metrics{display:grid;grid-template-columns:repeat(3,1fr) auto;gap:10px;align-items:center;padding:16px 27px 19px;border-top:1px solid var(--line)}.orbit-metrics strong{display:block;margin-top:4px;font:500 15px 'IBM Plex Mono',monospace}.orbit-metrics strong span{display:inline;color:#a6a7aa;font-size:10px}.orbit-metrics button{display:flex;align-items:center;gap:4px;border:0;background:none;color:#dedddb;font-size:11px;white-space:nowrap}.orbit-metrics button:hover{color:#ff9c84}
.orbit-side-stack{display:grid;grid-template-rows:1fr .9fr;gap:18px}.orbit-positions,.orbit-watch{padding:22px}.orbit-section-title{display:flex;align-items:start;justify-content:space-between}.orbit-section-title h2{margin:6px 0 0;font-size:17px;font-weight:500;letter-spacing:-.035em}.orbit-section-title>div>span{display:block;margin-top:6px;color:#898b91;font-size:11px}.orbit-section-title>button{padding:3px 0;font-size:10px}.orbit-position-meter{display:flex;align-items:center;gap:15px;margin-top:22px}.orbit-meter-track{width:58px;height:58px;padding:5px;border-radius:50%;background:conic-gradient(#e25d46 0 38%,#2b2d32 38% 100%)}.orbit-meter-track:before{content:'';display:block;width:100%;height:100%;border-radius:50%;background:#1a1b20}.orbit-position-meter>div:last-child{display:grid;gap:4px}.orbit-position-meter b{font:500 20px 'IBM Plex Mono',monospace}.orbit-position-meter span{color:#8b8d92;font-size:10px}.orbit-position-values{display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-top:20px;padding-top:15px;border-top:1px solid var(--line)}.orbit-position-values span{display:block;color:#85878a;font-size:10px}.orbit-position-values b{display:block;margin-top:4px;font:500 12px 'IBM Plex Mono',monospace}.positive{color:#ec816a}.orbit-watch{position:relative;overflow:hidden;background:linear-gradient(135deg,rgba(46,27,29,.8),rgba(21,22,27,.9) 70%)}.orbit-watch:before{content:'';position:absolute;width:160px;height:160px;right:-79px;bottom:-99px;border:1px solid rgba(239,116,89,.13);border-radius:50%;box-shadow:0 0 0 28px rgba(239,116,89,.025)}.orbit-watch-names{position:relative;z-index:1;display:flex;gap:7px;margin-top:19px}.orbit-watch-names button{display:grid;gap:4px;min-width:0;padding:7px 9px;border:1px solid rgba(255,255,255,.08);border-radius:6px;background:rgba(255,255,255,.035);color:#eceae7;text-align:left}.orbit-watch-names button:hover{border-color:rgba(255,139,112,.35)}.orbit-watch-names b{font:500 11px 'IBM Plex Mono',monospace}.orbit-watch-names span{color:#e6816c;font:9px 'IBM Plex Mono',monospace}
.orbit-opportunities{margin-top:52px}.orbit-opportunities>.orbit-section-title{align-items:end;margin-bottom:15px}.orbit-opportunities>.orbit-section-title h2{font-size:24px}.orbit-opportunity-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.orbit-opportunity{position:relative;display:grid;grid-template-columns:20px 1fr auto 15px;align-items:center;gap:12px;width:100%;padding:19px 17px;color:#e8e6e3;text-align:left;transition:border-color .2s,transform .2s,background .2s}.orbit-opportunity:hover{transform:translateY(-2px);border-color:rgba(224,92,69,.35);background:#202126}.orbit-rank{align-self:start;color:#65666c;font:9px 'IBM Plex Mono',monospace}.orbit-opportunity-name{display:grid;gap:4px}.orbit-opportunity-name strong{font:500 18px 'Bricolage Grotesque',sans-serif;letter-spacing:-.045em}.orbit-opportunity-name span{color:#83858b;font-size:10px}.orbit-signal{display:flex;gap:5px;align-items:center;color:#a6a7aa;font-size:10px}.orbit-signal i{width:5px;height:5px;border-radius:50%;background:#e97660}.orbit-signal i.short{background:#8e91a0}.orbit-opportunity-score{display:grid;justify-items:end;gap:3px}.orbit-opportunity-score b{font:500 16px 'IBM Plex Mono',monospace}.orbit-opportunity-score span{color:#7e8086;font-size:9px}.orbit-opportunity>svg{color:#898b8e}
.orbit-drawer-backdrop{position:fixed;z-index:10;inset:0;display:flex;justify-content:flex-end;background:rgba(3,4,6,.62);backdrop-filter:blur(3px);animation:orbit-fade .2s ease}.orbit-drawer{position:relative;width:min(440px,100%);height:100%;padding:70px 37px 32px;border-left:1px solid rgba(255,255,255,.1);background:#17181d;box-shadow:-25px 0 60px rgba(0,0,0,.35);animation:orbit-drawer-in .26s ease}.orbit-drawer-close{position:absolute;top:22px;right:24px;display:grid;place-items:center;width:32px;height:32px;padding:0;border:1px solid var(--line);border-radius:50%;background:transparent;color:#d7d6d3}.orbit-drawer h2{margin:10px 0 0;font:500 38px 'Bricolage Grotesque',sans-serif;letter-spacing:-.06em}.orbit-drawer-price{margin:24px 0 28px;font:500 25px 'IBM Plex Mono',monospace}.orbit-drawer-price b{margin-left:8px;color:#ed8068;font-size:12px;font-weight:400}.orbit-drawer-action{padding:15px;border:1px solid rgba(224,92,69,.25);border-radius:9px;background:rgba(224,92,69,.07)}.orbit-drawer-action span{display:block;color:#d77b68;font-size:10px}.orbit-drawer-action strong{display:block;margin-top:5px;font-size:14px;font-weight:500;line-height:1.4}.orbit-drawer dl{display:grid;grid-template-columns:repeat(3,1fr);gap:0;margin:28px 0;padding:17px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.orbit-drawer dt{color:#888a8f;font-size:10px}.orbit-drawer dd{margin:5px 0 0;font:500 12px 'IBM Plex Mono',monospace}.orbit-drawer-note{color:#a5a6ab;font-size:13px;line-height:1.6}.orbit-primary-button{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;margin-top:30px;padding:13px;border:0;border-radius:7px;background:#e36a50;color:#241513;font-weight:600;font-size:12px}.orbit-primary-button:hover{background:#f07b61}.orbit-primary-button svg{width:14px}@keyframes orbit-fade{from{opacity:0}to{opacity:1}}@keyframes orbit-drawer-in{from{transform:translateX(100%)}to{transform:translateX(0)}}
@media(max-width:1000px){.orbit-nav{width:185px}.orbit-content{margin-left:185px;padding-left:28px;padding-right:28px}.orbit-layout{grid-template-columns:1fr}.orbit-side-stack{grid-template-columns:1fr 1fr;grid-template-rows:none}.orbit-opportunity-grid{grid-template-columns:1fr}.orbit-opportunity{grid-template-columns:20px 1fr auto auto 15px}}
@media(max-width:700px){.orbit-nav{position:relative;width:100%;height:auto;min-height:65px;padding:14px 18px;flex-direction:row;align-items:center;border-right:0;border-bottom:1px solid var(--line)}.orbit-mark{margin:0;font-size:20px}.orbit-nav nav{display:none}.orbit-nav-foot{margin:0 0 0 auto;padding:0}.orbit-nav-foot div{display:none}.orbit-content{margin-left:0;padding:0 18px 48px}.orbit-topbar{height:58px}.orbit-breadcrumb{font-size:9px}.orbit-top-actions .orbit-icon-button{display:none}.orbit-intro{align-items:start;padding:36px 0 25px}.orbit-intro h1{font-size:35px}.orbit-quiet-button{display:none}.orbit-status{grid-template-columns:39px 1fr 30px;gap:12px;padding:16px}.orbit-status-orb{width:39px;height:39px}.orbit-regime{display:none}.orbit-status-copy h2{font-size:14px}.orbit-status-copy>span{font-size:10px;line-height:1.35;display:block}.orbit-card-head,.orbit-price-row{padding-left:18px;padding-right:18px}.orbit-card-head{padding-top:19px}.orbit-chart-wrap{height:160px;margin:0 11px}.orbit-price-row{gap:16px;align-items:start;flex-direction:column}.orbit-call{padding-left:10px}.orbit-metrics{grid-template-columns:repeat(3,1fr);padding:14px 18px}.orbit-metrics button{grid-column:1 / -1;margin-top:5px}.orbit-side-stack{grid-template-columns:1fr;grid-template-rows:auto}.orbit-opportunities{margin-top:38px}.orbit-opportunities>.orbit-section-title{align-items:end}.orbit-opportunities>.orbit-section-title>button{display:none}.orbit-opportunity{grid-template-columns:19px 1fr auto 13px;padding:15px}.orbit-signal{display:none}.orbit-drawer{padding:65px 25px 28px}.orbit-status-copy p{font-size:9px}}
@media(prefers-reduced-motion:reduce){.orbit-page *{animation:none!important;transition:none!important}}
`;
-1
View File
@@ -1 +0,0 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/activation.ts","./src/api/admin.ts","./src/api/auth.ts","./src/api/client.ts","./src/api/fundamentals.ts","./src/api/health.ts","./src/api/indicators.ts","./src/api/ingestion.ts","./src/api/jobs.ts","./src/api/market.ts","./src/api/ohlcv.ts","./src/api/papertrades.ts","./src/api/performance.ts","./src/api/regime.ts","./src/api/scores.ts","./src/api/sentiment.ts","./src/api/sr-levels.ts","./src/api/tickers.ts","./src/api/trades.ts","./src/api/watchlist.ts","./src/components/admin/activationsettings.tsx","./src/components/admin/alertsettings.tsx","./src/components/admin/datacleanup.tsx","./src/components/admin/exitpolicysettings.tsx","./src/components/admin/jobcontrols.tsx","./src/components/admin/pipelinereadinesspanel.tsx","./src/components/admin/recommendationsettings.tsx","./src/components/admin/schedulesettings.tsx","./src/components/admin/sentimentprovidersettings.tsx","./src/components/admin/settingsform.tsx","./src/components/admin/tickermanagement.tsx","./src/components/admin/tickeruniversebootstrap.tsx","./src/components/admin/usertable.tsx","./src/components/auth/protectedroute.tsx","./src/components/charts/candlestickchart.tsx","./src/components/charts/horizon.tsx","./src/components/dashboard/opentradespanel.tsx","./src/components/dashboard/perfchart.tsx","./src/components/layout/appshell.tsx","./src/components/layout/mobilenav.tsx","./src/components/layout/tickersearch.tsx","./src/components/layout/topbar.tsx","./src/components/rankings/rankingstable.tsx","./src/components/rankings/weightsform.tsx","./src/components/regime/regimequadrant.tsx","./src/components/regime/scorehistorychart.tsx","./src/components/scanner/tradetable.tsx","./src/components/signals/backtestpanel.tsx","./src/components/signals/mytradespanel.tsx","./src/components/signals/setupspanel.tsx","./src/components/signals/trackrecordpanel.tsx","./src/components/ticker/dimensionbreakdownpanel.tsx","./src/components/ticker/fundamentalspanel.tsx","./src/components/ticker/indicatorselector.tsx","./src/components/ticker/recommendationpanel.tsx","./src/components/ticker/sroverlay.tsx","./src/components/ticker/sentimentpanel.tsx","./src/components/ticker/standingmatrix.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/callout.tsx","./src/components/ui/confirmdialog.tsx","./src/components/ui/disclosure.tsx","./src/components/ui/dropdown.tsx","./src/components/ui/field.tsx","./src/components/ui/pageheader.tsx","./src/components/ui/scorecard.tsx","./src/components/ui/section.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/tabs.tsx","./src/components/ui/toast.tsx","./src/components/watchlist/addtickerform.tsx","./src/components/watchlist/watchlisttable.tsx","./src/hooks/useactivation.ts","./src/hooks/useadmin.ts","./src/hooks/useauth.ts","./src/hooks/usefetchsymboldata.ts","./src/hooks/usemarketregime.ts","./src/hooks/usepapertrades.ts","./src/hooks/useperformance.ts","./src/hooks/userisksettings.ts","./src/hooks/usescores.ts","./src/hooks/usetickerdetail.ts","./src/hooks/usetickers.ts","./src/hooks/usetrades.ts","./src/hooks/usewatchlist.ts","./src/lib/format.ts","./src/lib/ingestionstatus.ts","./src/lib/papertrade.ts","./src/lib/position.ts","./src/lib/qualification.ts","./src/lib/recommendation.ts","./src/lib/regime.ts","./src/lib/types.ts","./src/pages/adminpage.tsx","./src/pages/dashboardpage.tsx","./src/pages/designhorizonpage.tsx","./src/pages/designmockupspage.tsx","./src/pages/designorbitpage.tsx","./src/pages/loginpage.tsx","./src/pages/marketpage.tsx","./src/pages/regimepage.tsx","./src/pages/registerpage.tsx","./src/pages/signalspage.tsx","./src/pages/tickerdetailpage.tsx","./src/stores/authstore.ts"],"version":"5.6.3"}