diff --git a/frontend/src/pages/SignalsPage.tsx b/frontend/src/pages/SignalsPage.tsx
new file mode 100644
index 0000000..e03d188
--- /dev/null
+++ b/frontend/src/pages/SignalsPage.tsx
@@ -0,0 +1,32 @@
+import { useSearchParams } from 'react-router-dom';
+import { PageHeader } from '../components/ui/PageHeader';
+import { Tabs } from '../components/ui/Tabs';
+import { SetupsPanel } from '../components/signals/SetupsPanel';
+import { TrackRecordPanel } from '../components/signals/TrackRecordPanel';
+
+const tabs = ['Setups', 'Track Record'] as const;
+type Tab = (typeof tabs)[number];
+
+export default function SignalsPage() {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const activeTab: Tab = searchParams.get('tab') === 'track' ? 'Track Record' : 'Setups';
+
+ const setTab = (tab: Tab) => {
+ setSearchParams(tab === 'Track Record' ? { tab: 'track' } : {}, { replace: true });
+ };
+
+ return (
+
+
+
+
+
+
+ {activeTab === 'Setups' ? : }
+
+
+ );
+}
diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css
index 5c17663..a0e8da5 100644
--- a/frontend/src/styles/globals.css
+++ b/frontend/src/styles/globals.css
@@ -4,11 +4,15 @@
@layer base {
body {
- background: #0a0e1a;
+ background: #0e120f;
min-height: 100vh;
}
- /* Mesh gradient background */
+ h1, h2, h3 {
+ font-family: 'Bricolage Grotesque', system-ui, sans-serif;
+ }
+
+ /* Atmosphere: faint graph-paper grid + citron signal glow + film grain */
#root {
position: relative;
min-height: 100vh;
@@ -18,80 +22,108 @@
content: '';
position: fixed;
inset: 0;
- z-index: -1;
+ z-index: -2;
background:
- radial-gradient(ellipse 80% 60% at 10% 20%, rgba(56, 189, 248, 0.08) 0%, transparent 60%),
- radial-gradient(ellipse 60% 50% at 80% 10%, rgba(139, 92, 246, 0.07) 0%, transparent 50%),
- radial-gradient(ellipse 50% 40% at 50% 80%, rgba(16, 185, 129, 0.05) 0%, transparent 50%);
+ radial-gradient(ellipse 70% 50% at 15% 0%, rgba(196, 232, 46, 0.06) 0%, transparent 55%),
+ radial-gradient(ellipse 50% 40% at 90% 90%, rgba(52, 211, 153, 0.04) 0%, transparent 50%),
+ linear-gradient(rgba(223, 242, 178, 0.025) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(223, 242, 178, 0.025) 1px, transparent 1px);
+ background-size: 100% 100%, 100% 100%, 48px 48px, 48px 48px;
+ pointer-events: none;
+ }
+
+ /* Film grain — keeps large dark areas from feeling flat */
+ #root::after {
+ content: '';
+ position: fixed;
+ inset: 0;
+ z-index: -1;
+ opacity: 0.5;
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
pointer-events: none;
}
}
@layer components {
- /* Glass card — the core building block */
+ /* Panel — the core building block (kept class names so all components reskin) */
.glass {
- background: rgba(255, 255, 255, 0.04);
- backdrop-filter: blur(16px);
- -webkit-backdrop-filter: blur(16px);
- border: 1px solid rgba(255, 255, 255, 0.07);
- border-radius: 1rem;
+ background: rgba(238, 240, 233, 0.035);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border: 1px solid rgba(223, 242, 178, 0.09);
+ border-radius: 0.875rem;
}
.glass-sm {
- background: rgba(255, 255, 255, 0.03);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- border: 1px solid rgba(255, 255, 255, 0.06);
- border-radius: 0.75rem;
+ background: rgba(238, 240, 233, 0.028);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid rgba(223, 242, 178, 0.07);
+ border-radius: 0.625rem;
}
.glass-hover {
transition: all 0.2s ease;
}
.glass-hover:hover {
- background: rgba(255, 255, 255, 0.07);
- border-color: rgba(255, 255, 255, 0.12);
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
+ background: rgba(238, 240, 233, 0.06);
+ border-color: rgba(214, 242, 92, 0.22);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
}
/* Gradient text — reserved for the brand wordmark only */
.text-gradient {
- background: linear-gradient(135deg, #38bdf8, #60a5fa);
+ background: linear-gradient(120deg, #d6f25c, #6ee7b7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
- /* Primary button — single blue accent */
+ /* Primary button — luminous citron with ink text. The signature element. */
.btn-primary {
- background: rgba(59, 130, 246, 0.85);
- color: white;
- border: 1px solid rgba(96, 165, 250, 0.35);
+ background: #c3e82e;
+ color: #16190a;
+ border: 1px solid rgba(214, 242, 92, 0.5);
border-radius: 0.5rem;
- font-weight: 500;
+ font-weight: 600;
transition: all 0.2s ease;
}
.btn-primary:hover:not(:disabled) {
- background: rgb(59, 130, 246);
- box-shadow: 0 0 16px rgba(59, 130, 246, 0.25);
+ background: #d6f25c;
+ box-shadow: 0 0 20px rgba(196, 232, 46, 0.35);
}
- /* Glass input */
+ /* Inputs */
.input-glass {
- background: rgba(255, 255, 255, 0.04);
- border: 1px solid rgba(255, 255, 255, 0.1);
+ background: rgba(238, 240, 233, 0.04);
+ border: 1px solid rgba(223, 242, 178, 0.12);
border-radius: 0.5rem;
- color: #e2e8f0;
+ color: #eef0e9;
transition: all 0.2s ease;
}
.input-glass::placeholder {
- color: rgba(148, 163, 184, 0.5);
+ color: rgba(149, 157, 135, 0.55);
}
.input-glass:focus {
outline: none;
- border-color: rgba(99, 102, 241, 0.5);
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15), 0 0 20px rgba(99, 102, 241, 0.1);
- background: rgba(255, 255, 255, 0.06);
+ border-color: rgba(214, 242, 92, 0.5);
+ box-shadow: 0 0 0 3px rgba(196, 232, 46, 0.12);
+ background: rgba(238, 240, 233, 0.06);
+ }
+
+ /* Numbers — every metric reads in tabular mono */
+ .num {
+ font-family: 'IBM Plex Mono', ui-monospace, monospace;
+ font-variant-numeric: tabular-nums;
+ }
+
+ /* Section index label, e.g. "01 / Watchlist" */
+ .section-index {
+ font-family: 'IBM Plex Mono', ui-monospace, monospace;
+ font-size: 0.65rem;
+ letter-spacing: 0.18em;
+ color: #6e7562;
+ text-transform: uppercase;
}
/* Scrollbar styling */
@@ -103,10 +135,10 @@
background: transparent;
}
::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);
+ background: rgba(223, 242, 178, 0.12);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.2);
+ background: rgba(223, 242, 178, 0.22);
}
}
diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts
index 7f59519..9a58e59 100644
--- a/frontend/tailwind.config.ts
+++ b/frontend/tailwind.config.ts
@@ -6,14 +6,46 @@ export default {
theme: {
extend: {
fontFamily: {
- sans: ['Inter', 'system-ui', 'sans-serif'],
+ sans: ['"Instrument Sans"', 'system-ui', 'sans-serif'],
+ display: ['"Bricolage Grotesque"', 'system-ui', 'sans-serif'],
+ mono: ['"IBM Plex Mono"', 'ui-monospace', 'monospace'],
},
colors: {
+ // Warm ash neutrals (green-tinted) — replaces Tailwind's cool gray
+ // throughout the app, since components reference gray-* directly.
+ gray: {
+ 50: '#f7f8f4',
+ 100: '#eef0e9',
+ 200: '#dde1d4',
+ 300: '#bfc5b2',
+ 400: '#959d87',
+ 500: '#6e7562',
+ 600: '#4d5344',
+ 700: '#373c30',
+ 800: '#22261d',
+ 900: '#151911',
+ 950: '#0e120f',
+ },
+ // Citron signal accent — replaces blue so every accent reference
+ // (text-blue-*, bg-blue-*, ring-blue-*) lights up in the brand color.
+ blue: {
+ 50: '#fafee8',
+ 100: '#f2fcc6',
+ 200: '#e6f996',
+ 300: '#d6f25c',
+ 400: '#c3e82e',
+ 500: '#a4cd14',
+ 600: '#82a40b',
+ 700: '#627c0e',
+ 800: '#4e6212',
+ 900: '#425314',
+ 950: '#222e05',
+ },
surface: {
- DEFAULT: 'rgba(255, 255, 255, 0.05)',
- hover: 'rgba(255, 255, 255, 0.08)',
- active: 'rgba(255, 255, 255, 0.12)',
- border: 'rgba(255, 255, 255, 0.08)',
+ DEFAULT: 'rgba(255, 255, 255, 0.04)',
+ hover: 'rgba(255, 255, 255, 0.07)',
+ active: 'rgba(255, 255, 255, 0.1)',
+ border: 'rgba(223, 242, 178, 0.09)',
},
},
backdropBlur: {
@@ -23,6 +55,7 @@ export default {
'glow-pulse': 'glow-pulse 3s ease-in-out infinite',
'fade-in': 'fade-in 0.3s ease-out',
'slide-up': 'slide-up 0.4s ease-out',
+ 'signal-pulse': 'signal-pulse 2.4s ease-in-out infinite',
},
keyframes: {
'glow-pulse': {
@@ -37,6 +70,10 @@ export default {
from: { opacity: '0', transform: 'translateY(12px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
+ 'signal-pulse': {
+ '0%, 100%': { opacity: '1', transform: 'scale(1)' },
+ '50%': { opacity: '0.35', transform: 'scale(0.8)' },
+ },
},
},
},
diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo
index 4c3c3ea..b816b06 100644
--- a/frontend/tsconfig.tsbuildinfo
+++ b/frontend/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.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/ohlcv.ts","./src/api/performance.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/datacleanup.tsx","./src/components/admin/jobcontrols.tsx","./src/components/admin/pipelinereadinesspanel.tsx","./src/components/admin/recommendationsettings.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/layout/appshell.tsx","./src/components/layout/mobilenav.tsx","./src/components/layout/sidebar.tsx","./src/components/rankings/rankingstable.tsx","./src/components/rankings/weightsform.tsx","./src/components/scanner/tradetable.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/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/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/useadmin.ts","./src/hooks/useauth.ts","./src/hooks/usefetchsymboldata.ts","./src/hooks/useperformance.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/recommendation.ts","./src/lib/types.ts","./src/pages/adminpage.tsx","./src/pages/loginpage.tsx","./src/pages/performancepage.tsx","./src/pages/rankingspage.tsx","./src/pages/registerpage.tsx","./src/pages/scannerpage.tsx","./src/pages/tickerdetailpage.tsx","./src/pages/watchlistpage.tsx","./src/stores/authstore.ts"],"version":"5.6.3"}
\ No newline at end of file
+{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.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/ohlcv.ts","./src/api/performance.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/datacleanup.tsx","./src/components/admin/jobcontrols.tsx","./src/components/admin/pipelinereadinesspanel.tsx","./src/components/admin/recommendationsettings.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/layout/appshell.tsx","./src/components/layout/mobilenav.tsx","./src/components/layout/sidebar.tsx","./src/components/rankings/rankingstable.tsx","./src/components/rankings/weightsform.tsx","./src/components/scanner/tradetable.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/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/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/useadmin.ts","./src/hooks/useauth.ts","./src/hooks/usefetchsymboldata.ts","./src/hooks/useperformance.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/recommendation.ts","./src/lib/types.ts","./src/pages/adminpage.tsx","./src/pages/dashboardpage.tsx","./src/pages/loginpage.tsx","./src/pages/marketpage.tsx","./src/pages/registerpage.tsx","./src/pages/signalspage.tsx","./src/pages/tickerdetailpage.tsx","./src/stores/authstore.ts"],"version":"5.6.3"}
\ No newline at end of file