Redesign: Nexus AI theme - deep black background, neon green accent, modern typography

master
isnowglobal-admin 2026-05-21 03:48:41 -04:00
parent 003a913186
commit 5d0b8736fc
6 changed files with 221 additions and 176 deletions

View File

@ -26,29 +26,29 @@ const NAV = [
export function AppSidebar() { export function AppSidebar() {
const [location] = useLocation(); const [location] = useLocation();
return ( return (
<Sidebar data-testid="sidebar-main"> <Sidebar data-testid="sidebar-main" className="border-r-0">
<SidebarHeader className="px-3 pt-4 pb-3"> <SidebarHeader className="px-4 pt-5 pb-4">
<Link <Link
href="/" href="/"
className="flex items-center gap-2 px-2" className="flex items-center gap-3 px-1"
data-testid="link-home" data-testid="link-home"
> >
<span className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-primary/15 text-primary"> <span className="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10 border border-primary/20 text-primary neon-glow-sm">
<Logo className="h-5 w-5" /> <Logo className="h-6 w-6" />
</span> </span>
<span className="flex flex-col leading-tight"> <span className="flex flex-col leading-tight">
<span className="text-sm font-semibold tracking-tight">GammaDesk</span> <span className="text-base font-bold tracking-tight text-foreground">GammaDesk</span>
<span className="text-[11px] text-muted-foreground tracking-wide uppercase"> <span className="text-[10px] text-muted-foreground tracking-[0.15em] uppercase font-medium">
Options Analytics Options Analytics
</span> </span>
</span> </span>
</Link> </Link>
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent className="px-2">
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel>Workspace</SidebarGroupLabel> <SidebarGroupLabel className="px-3 text-[10px] tracking-[0.15em] uppercase font-semibold text-muted-foreground">Workspace</SidebarGroupLabel>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu className="gap-1">
{NAV.map((item) => { {NAV.map((item) => {
const active = const active =
item.url === "/" item.url === "/"
@ -56,10 +56,19 @@ export function AppSidebar() {
: location.startsWith(item.url); : location.startsWith(item.url);
return ( return (
<SidebarMenuItem key={item.title}> <SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={active} data-testid={item.testid}> <SidebarMenuButton
<Link href={item.url}> asChild
<item.icon className="h-4 w-4" /> isActive={active}
<span>{item.title}</span> data-testid={item.testid}
className={`h-10 rounded-lg transition-all ${
active
? "bg-primary/10 text-primary border-l-2 border-primary"
: "text-muted-foreground hover:text-foreground hover:bg-secondary"
}`}
>
<Link href={item.url} className="flex items-center gap-3 px-3 py-2">
<item.icon className={`h-4 w-4 ${active ? "text-primary" : "text-muted-foreground"}`} />
<span className={`text-sm font-medium ${active ? "font-semibold" : ""}`}>{item.title}</span>
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
@ -69,9 +78,9 @@ export function AppSidebar() {
</SidebarGroupContent> </SidebarGroupContent>
</SidebarGroup> </SidebarGroup>
</SidebarContent> </SidebarContent>
<SidebarFooter className="px-3 pb-4"> <SidebarFooter className="px-4 pb-5 pt-3 border-t border-border/50">
<p <p
className="text-[11px] leading-snug text-muted-foreground" className="text-[10px] leading-relaxed text-muted-foreground"
data-testid="text-sidebar-disclaimer" data-testid="text-sidebar-disclaimer"
> >
Analytics &amp; education only. Not financial advice. Data is simulated Analytics &amp; education only. Not financial advice. Data is simulated

View File

@ -31,16 +31,16 @@ export function MetricCard({
}: MetricCardProps) { }: MetricCardProps) {
return ( return (
<Card <Card
className="flex flex-col gap-2 p-4" className="flex flex-col gap-3 p-5 border-border/50 bg-card/50 backdrop-blur-sm shadow-lg"
data-testid={testId} data-testid={testId}
> >
<div className="flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground"> <div className="flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-[0.15em] text-muted-foreground">
<span>{label}</span> <span>{label}</span>
{metric && <MetricTooltip metric={metric} />} {metric && <MetricTooltip metric={metric} />}
</div> </div>
<div <div
className={cn( className={cn(
"num text-lg font-semibold leading-none sm:text-xl", "num text-2xl font-bold leading-none",
ACCENT_CLASSES[accent], ACCENT_CLASSES[accent],
)} )}
data-testid={testId ? `${testId}-value` : undefined} data-testid={testId ? `${testId}-value` : undefined}
@ -48,7 +48,7 @@ export function MetricCard({
{value} {value}
</div> </div>
{hint && ( {hint && (
<div className="text-xs text-muted-foreground num">{hint}</div> <div className="text-xs text-muted-foreground num font-medium">{hint}</div>
)} )}
</Card> </Card>
); );

View File

@ -14,11 +14,11 @@ export function TopBar({ title }: { title: string }) {
const isAuthPage = location === "/login"; const isAuthPage = location === "/login";
return ( return (
<header className="flex h-14 items-center gap-3 border-b border-border bg-background/80 px-3 backdrop-blur sm:px-5"> <header className="flex h-14 items-center gap-3 border-b border-border/50 bg-background/80 px-4 backdrop-blur-sm sm:px-6">
{!isAuthPage && <SidebarTrigger data-testid="button-sidebar-toggle" />} {!isAuthPage && <SidebarTrigger data-testid="button-sidebar-toggle" />}
{!isAuthPage && <Separator orientation="vertical" className="h-6" />} {!isAuthPage && <Separator orientation="vertical" className="h-5 border-border/50" />}
<h1 <h1
className="truncate text-sm font-semibold tracking-tight sm:text-base" className="truncate text-base font-bold tracking-tight text-foreground sm:text-lg"
data-testid="text-page-title" data-testid="text-page-title"
> >
{title} {title}
@ -26,12 +26,12 @@ export function TopBar({ title }: { title: string }) {
<div className="ml-auto flex items-center gap-2 sm:gap-3"> <div className="ml-auto flex items-center gap-2 sm:gap-3">
{!isAuthPage && ( {!isAuthPage && (
<> <>
<span className="text-xs text-muted-foreground hidden sm:inline">{user?.name}</span> <span className="text-xs text-muted-foreground hidden sm:inline font-medium">{user?.name}</span>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
className="text-xs h-8 px-2" className="text-xs h-8 px-3 font-medium hover:bg-primary/10 hover:text-primary"
onClick={() => setLocation("/#/account")} onClick={() => setLocation("/account")}
data-testid="button-account" data-testid="button-account"
> >
Account Account
@ -41,6 +41,7 @@ export function TopBar({ title }: { title: string }) {
size="icon" size="icon"
aria-label="Sign out" aria-label="Sign out"
onClick={() => logout()} onClick={() => logout()}
className="h-8 w-8 hover:bg-destructive/10 hover:text-destructive"
data-testid="button-logout" data-testid="button-logout"
> >
<LogOut className="h-4 w-4" /> <LogOut className="h-4 w-4" />
@ -53,6 +54,7 @@ export function TopBar({ title }: { title: string }) {
size="icon" size="icon"
aria-label="Toggle theme" aria-label="Toggle theme"
onClick={toggle} onClick={toggle}
className="h-8 w-8 hover:bg-primary/10 hover:text-primary"
data-testid="button-theme-toggle" data-testid="button-theme-toggle"
> >
{theme === "dark" ? ( {theme === "dark" ? (

View File

@ -2,93 +2,86 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
/* GammaDesk palette /* GammaDesk Theme Nexus AI Style
---------------------------------------------------------- ----------------------------------------------------------
Finance dashboard aesthetic: cool slate surfaces, sharp Dark-first finance dashboard with neon green accent.
high-contrast text, restrained teal/blue accent. Reserved Inspired by modern AI/tech branding: deep black surfaces,
semantic colors map to a trading floor mental model: high-contrast white text, electric green highlights.
- positive (long gamma, call side) -> emerald Semantic trading colors preserved:
- negative (short gamma, put side) -> rose - positive (long gamma) -> emerald-green
- negative (short gamma) -> rose-red
- call wall -> cyan - call wall -> cyan
- put wall -> magenta-rose - put wall -> magenta
- HVL / gamma flip -> gold - HVL / gamma flip -> gold/amber
- spot marker -> primary text color - spot marker -> white
Values are HSL (H S% L%) without hsl() wrapper, per the Values are HSL (H S% L%) without hsl() wrapper. */
shadcn/Tailwind v3 convention. */
/* LIGHT MODE */ /* LIGHT MODE (fallback — app is primarily dark) */
:root { :root {
--button-outline: hsl(220 14% 90% / 0.8); --button-outline: hsl(130 70% 50% / 0.8);
--badge-outline: hsl(220 14% 90% / 0.4); --badge-outline: hsl(130 70% 50% / 0.4);
--opaque-button-border-intensity: -8; --opaque-button-border-intensity: -8;
--elevate-1: hsl(220 14% 14% / 0.04); --elevate-1: hsl(130 70% 50% / 0.04);
--elevate-2: hsl(220 14% 14% / 0.08); --elevate-2: hsl(130 70% 50% / 0.08);
--background: 220 24% 98%; --background: 0 0% 100%;
--foreground: 222 32% 12%; --foreground: 130 70% 12%;
--border: 220 14% 90%; --border: 0 0% 85%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222 32% 12%; --card-foreground: 130 70% 12%;
--card-border: 220 14% 92%; --card-border: 0 0% 88%;
--sidebar: 220 22% 96%; --sidebar: 0 0% 97%;
--sidebar-foreground: 222 32% 12%; --sidebar-foreground: 130 70% 12%;
--sidebar-border: 220 14% 90%; --sidebar-border: 0 0% 85%;
--sidebar-primary: 192 80% 36%; --sidebar-primary: 130 70% 50%;
--sidebar-primary-foreground: 0 0% 100%; --sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 220 16% 92%; --sidebar-accent: 0 0% 93%;
--sidebar-accent-foreground: 222 32% 12%; --sidebar-accent-foreground: 130 70% 12%;
--sidebar-ring: 192 80% 36%; --sidebar-ring: 130 70% 50%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222 32% 12%; --popover-foreground: 130 70% 12%;
--popover-border: 220 14% 90%; --popover-border: 0 0% 85%;
--primary: 192 80% 36%; --primary: 130 70% 50%;
--primary-foreground: 0 0% 100%; --primary-foreground: 0 0% 100%;
--secondary: 220 16% 92%; --secondary: 0 0% 93%;
--secondary-foreground: 222 32% 12%; --secondary-foreground: 130 70% 12%;
--muted: 220 16% 94%; --muted: 0 0% 95%;
--muted-foreground: 220 8% 42%; --muted-foreground: 0 0% 45%;
--accent: 220 16% 92%; --accent: 130 70% 95%;
--accent-foreground: 222 32% 12%; --accent-foreground: 130 70% 50%;
--destructive: 348 75% 48%; --destructive: 348 75% 48%;
--destructive-foreground: 0 0% 100%; --destructive-foreground: 0 0% 100%;
--input: 220 14% 85%; --input: 0 0% 85%;
--ring: 192 80% 36%; --ring: 130 70% 50%;
/* Chart palette tuned for gamma analytics /* Chart palette — trading analytics */
1 long-gamma / positive (emerald)
2 short-gamma / negative (rose)
3 call wall (cyan)
4 put wall (magenta)
5 HVL / gamma flip (amber)
*/
--chart-1: 152 65% 38%; --chart-1: 152 65% 38%;
--chart-2: 348 75% 48%; --chart-2: 348 75% 48%;
--chart-3: 188 76% 42%; --chart-3: 188 76% 42%;
--chart-4: 326 70% 50%; --chart-4: 326 70% 50%;
--chart-5: 38 92% 50%; --chart-5: 38 92% 50%;
/* Semantic data colors (consumed via tailwind config) */
--pos: 152 65% 38%; --pos: 152 65% 38%;
--neg: 348 75% 48%; --neg: 348 75% 48%;
--call-wall: 188 76% 42%; --call-wall: 188 76% 42%;
--put-wall: 326 70% 50%; --put-wall: 326 70% 50%;
--hvl: 38 92% 50%; --hvl: 38 92% 50%;
--spot: 222 32% 12%; --spot: 130 70% 12%;
--font-sans: 'Inter', 'Helvetica Neue', sans-serif; --font-sans: 'Outfit', 'Inter', 'Helvetica Neue', sans-serif;
--font-serif: Georgia, serif; --font-serif: Georgia, serif;
--font-mono: 'JetBrains Mono', 'Menlo', monospace; --font-mono: 'JetBrains Mono', 'Menlo', monospace;
--radius: 0.5rem; --radius: 0.5rem;
--shadow-2xs: 0 1px 0 0 hsl(220 14% 14% / 0.04); --shadow-2xs: 0 1px 0 0 hsl(0 0% 0% / 0.04);
--shadow-xs: 0 1px 2px 0 hsl(220 14% 14% / 0.05); --shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / 0.05);
--shadow-sm: 0 1px 2px 0 hsl(220 14% 14% / 0.06), 0 1px 1px -1px hsl(220 14% 14% / 0.04); --shadow-sm: 0 1px 2px 0 hsl(0 0% 0% / 0.06), 0 1px 1px -1px hsl(0 0% 0% / 0.04);
--shadow: 0 2px 4px 0 hsl(220 14% 14% / 0.06), 0 1px 2px -1px hsl(220 14% 14% / 0.04); --shadow: 0 2px 4px 0 hsl(0 0% 0% / 0.06), 0 1px 2px -1px hsl(0 0% 0% / 0.04);
--shadow-md: 0 4px 8px 0 hsl(220 14% 14% / 0.08), 0 2px 4px -1px hsl(220 14% 14% / 0.05); --shadow-md: 0 4px 8px 0 hsl(0 0% 0% / 0.08), 0 2px 4px -1px hsl(0 0% 0% / 0.05);
--shadow-lg: 0 8px 24px 0 hsl(220 14% 14% / 0.12), 0 4px 8px -2px hsl(220 14% 14% / 0.06); --shadow-lg: 0 8px 24px 0 hsl(0 0% 0% / 0.12), 0 4px 8px -2px hsl(0 0% 0% / 0.06);
--shadow-xl: 0 16px 40px 0 hsl(220 14% 14% / 0.16); --shadow-xl: 0 16px 40px 0 hsl(0 0% 0% / 0.16);
--shadow-2xl: 0 24px 64px 0 hsl(220 14% 14% / 0.24); --shadow-2xl: 0 24px 64px 0 hsl(0 0% 0% / 0.24);
--tracking-normal: 0em; --tracking-normal: 0em;
--spacing: 0.25rem; --spacing: 0.25rem;
@ -122,66 +115,67 @@
); );
} }
/* DARK MODE — primary surface for the dashboard */ /* DARK MODE — Primary surface. Deep black + neon green */
.dark { .dark {
--button-outline: hsl(220 14% 24% / 0.8); --button-outline: hsl(130 70% 50% / 0.8);
--badge-outline: hsl(220 14% 24% / 0.6); --badge-outline: hsl(130 70% 50% / 0.3);
--opaque-button-border-intensity: 9; --opaque-button-border-intensity: 9;
--elevate-1: hsl(0 0% 100% / 0.04); --elevate-1: hsl(130 70% 50% / 0.04);
--elevate-2: hsl(0 0% 100% / 0.08); --elevate-2: hsl(130 70% 50% / 0.08);
/* Surfaces: layered slate / near-black gradient */ /* Deep black surfaces */
--background: 222 24% 6%; --background: 0 0% 4%;
--foreground: 220 14% 96%; --foreground: 0 0% 98%;
--border: 220 12% 18%; --border: 0 0% 14%;
--card: 222 20% 9%; --card: 0 0% 7%;
--card-foreground: 220 14% 96%; --card-foreground: 0 0% 98%;
--card-border: 220 12% 16%; --card-border: 0 0% 12%;
--sidebar: 222 28% 7%; --sidebar: 0 0% 5%;
--sidebar-foreground: 220 14% 92%; --sidebar-foreground: 0 0% 96%;
--sidebar-border: 220 12% 14%; --sidebar-border: 0 0% 10%;
--sidebar-primary: 188 72% 56%; --sidebar-primary: 130 70% 50%;
--sidebar-primary-foreground: 222 32% 8%; --sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 220 14% 14%; --sidebar-accent: 0 0% 12%;
--sidebar-accent-foreground: 220 14% 96%; --sidebar-accent-foreground: 0 0% 98%;
--sidebar-ring: 188 72% 56%; --sidebar-ring: 130 70% 50%;
--popover: 222 22% 14%; --popover: 0 0% 8%;
--popover-foreground: 220 14% 96%; --popover-foreground: 0 0% 98%;
--popover-border: 220 12% 26%; --popover-border: 0 0% 18%;
--primary: 188 72% 56%; --primary: 130 70% 50%;
--primary-foreground: 222 32% 8%; --primary-foreground: 0 0% 100%;
--secondary: 220 14% 14%; --secondary: 0 0% 12%;
--secondary-foreground: 220 14% 96%; --secondary-foreground: 0 0% 98%;
--muted: 220 14% 12%; --muted: 0 0% 8%;
--muted-foreground: 220 8% 64%; --muted-foreground: 0 0% 55%;
--accent: 220 14% 14%; --accent: 130 70% 15%;
--accent-foreground: 220 14% 96%; --accent-foreground: 130 70% 60%;
--destructive: 348 70% 56%; --destructive: 348 70% 56%;
--destructive-foreground: 0 0% 100%; --destructive-foreground: 0 0% 100%;
--input: 220 12% 22%; --input: 0 0% 14%;
--ring: 188 72% 56%; --ring: 130 70% 50%;
/* Chart palette — brighter for dark bg */
--chart-1: 152 62% 52%; --chart-1: 152 62% 52%;
--chart-2: 348 75% 62%; --chart-2: 348 75% 62%;
--chart-3: 188 72% 56%; --chart-3: 130 70% 50%;
--chart-4: 326 68% 62%; --chart-4: 326 68% 62%;
--chart-5: 38 92% 60%; --chart-5: 38 92% 60%;
--pos: 152 62% 52%; --pos: 152 62% 52%;
--neg: 348 75% 62%; --neg: 348 75% 62%;
--call-wall: 188 72% 56%; --call-wall: 130 70% 50%;
--put-wall: 326 68% 62%; --put-wall: 326 68% 62%;
--hvl: 38 92% 60%; --hvl: 38 92% 60%;
--spot: 220 14% 96%; --spot: 0 0% 98%;
--shadow-2xs: 0 1px 0 0 hsl(0 0% 0% / 0.4); --shadow-2xs: 0 1px 0 0 hsl(0 0% 0% / 0.6);
--shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / 0.5); --shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / 0.6);
--shadow-sm: 0 1px 2px 0 hsl(0 0% 0% / 0.5), 0 1px 1px -1px hsl(0 0% 0% / 0.4); --shadow-sm: 0 1px 2px 0 hsl(0 0% 0% / 0.6), 0 1px 1px -1px hsl(0 0% 0% / 0.5);
--shadow: 0 2px 4px 0 hsl(0 0% 0% / 0.5), 0 1px 2px -1px hsl(0 0% 0% / 0.4); --shadow: 0 2px 4px 0 hsl(0 0% 0% / 0.6), 0 1px 2px -1px hsl(0 0% 0% / 0.5);
--shadow-md: 0 4px 8px 0 hsl(0 0% 0% / 0.55), 0 2px 4px -1px hsl(0 0% 0% / 0.45); --shadow-md: 0 4px 8px 0 hsl(0 0% 0% / 0.65), 0 2px 4px -1px hsl(0 0% 0% / 0.55);
--shadow-lg: 0 8px 24px 0 hsl(0 0% 0% / 0.6), 0 4px 8px -2px hsl(0 0% 0% / 0.5); --shadow-lg: 0 8px 24px 0 hsl(0 0% 0% / 0.7), 0 4px 8px -2px hsl(0 0% 0% / 0.6);
--shadow-xl: 0 16px 40px 0 hsl(0 0% 0% / 0.7); --shadow-xl: 0 16px 40px 0 hsl(0 0% 0% / 0.8);
--shadow-2xl: 0 24px 64px 0 hsl(0 0% 0% / 0.8); --shadow-2xl: 0 24px 64px 0 hsl(0 0% 0% / 0.9);
--sidebar-primary-border: hsl(var(--sidebar-primary)); --sidebar-primary-border: hsl(var(--sidebar-primary));
--sidebar-primary-border: hsl( --sidebar-primary-border: hsl(
@ -309,10 +303,20 @@
inset: -1px; inset: -1px;
} }
/* Subtle grid backdrop for hero / nav header */ /* Subtle grid backdrop */
.grid-backdrop { .grid-backdrop {
background-image: linear-gradient(hsl(var(--border) / 0.4) 1px, transparent 1px), background-image: linear-gradient(hsl(var(--border) / 0.3) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--border) / 0.4) 1px, transparent 1px); linear-gradient(90deg, hsl(var(--border) / 0.3) 1px, transparent 1px);
background-size: 32px 32px; background-size: 32px 32px;
} }
/* Neon glow effect for primary elements */
.neon-glow {
box-shadow: 0 0 20px hsl(130 70% 50% / 0.3),
0 0 40px hsl(130 70% 50% / 0.1);
}
.neon-glow-sm {
box-shadow: 0 0 10px hsl(130 70% 50% / 0.3);
}
} }

View File

@ -21,13 +21,13 @@ export default function DashboardPage() {
const regimePositive = s?.gammaRegime === "positive"; const regimePositive = s?.gammaRegime === "positive";
return ( return (
<div className="flex flex-col gap-5 p-4 sm:p-6"> <div className="flex flex-col gap-6 p-5 sm:p-6">
{/* Header row */} {/* Header row */}
<div className="flex flex-wrap items-end justify-between gap-3"> <div className="flex flex-wrap items-end justify-between gap-3">
<div> <div>
<div className="flex items-baseline gap-3"> <div className="flex items-baseline gap-3">
<h2 <h2
className="font-mono text-lg font-semibold tracking-tight" className="font-mono text-xl font-bold tracking-tight text-foreground"
data-testid="text-symbol" data-testid="text-symbol"
> >
{symbol} {symbol}
@ -35,7 +35,7 @@ export default function DashboardPage() {
{s && ( {s && (
<span <span
className={cn( className={cn(
"num text-sm font-medium", "num text-base font-bold",
s.spotChangePct >= 0 ? "text-pos" : "text-neg", s.spotChangePct >= 0 ? "text-pos" : "text-neg",
)} )}
data-testid="text-spot-change" data-testid="text-spot-change"
@ -44,7 +44,7 @@ export default function DashboardPage() {
</span> </span>
)} )}
</div> </div>
<p className="mt-1 text-xs text-muted-foreground"> <p className="mt-1 text-sm text-muted-foreground">
Snapshot of dealer positioning, expected move, and key gamma levels. Snapshot of dealer positioning, expected move, and key gamma levels.
</p> </p>
</div> </div>
@ -52,10 +52,10 @@ export default function DashboardPage() {
<Badge <Badge
variant="outline" variant="outline"
className={cn( className={cn(
"gap-1.5 border-border px-2.5 py-1 text-xs font-medium uppercase tracking-wide", "gap-1.5 px-3 py-1.5 text-xs font-semibold uppercase tracking-wider rounded-lg border-border/50",
regimePositive regimePositive
? "bg-pos/10 text-pos" ? "bg-pos/10 text-pos border-pos/20"
: "bg-neg/10 text-neg", : "bg-neg/10 text-neg border-neg/20",
)} )}
data-testid="badge-gamma-regime" data-testid="badge-gamma-regime"
> >
@ -151,15 +151,15 @@ export default function DashboardPage() {
)} )}
{/* GEX profile chart */} {/* GEX profile chart */}
<Card> <Card className="border-border/50 bg-card/50 backdrop-blur-sm shadow-xl">
<CardHeader className="flex flex-row items-center justify-between gap-3 pb-2"> <CardHeader className="flex flex-row items-center justify-between gap-3 pb-3">
<div> <div>
<CardTitle className="text-base font-semibold">Gamma exposure by strike</CardTitle> <CardTitle className="text-lg font-bold text-foreground">Gamma exposure by strike</CardTitle>
<p className="mt-1 text-xs text-muted-foreground"> <p className="mt-1 text-xs text-muted-foreground">
Estimated dealer gamma contributions. Green bars show call-side positive GEX; pink bars show put-side negative GEX. Vertical guides mark HVL and dominant walls. Estimated dealer gamma contributions. Green bars show call-side positive GEX; pink bars show put-side negative GEX. Vertical guides mark HVL and dominant walls.
</p> </p>
</div> </div>
<div className="hidden flex-wrap justify-end gap-x-3 gap-y-1 text-[11px] md:flex"> <div className="hidden flex-wrap justify-end gap-x-3 gap-y-1 text-[10px] font-semibold md:flex">
<LegendDot color="hsl(var(--pos))" label="Calls" /> <LegendDot color="hsl(var(--pos))" label="Calls" />
<LegendDot color="hsl(var(--neg))" label="Puts" /> <LegendDot color="hsl(var(--neg))" label="Puts" />
<LegendDot color="hsl(var(--hvl))" label="HVL" /> <LegendDot color="hsl(var(--hvl))" label="HVL" />

View File

@ -6,8 +6,7 @@ import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Separator } from "@/components/ui/separator"; import { AlertCircle, Eye, EyeOff, Loader2, ArrowRight } from "lucide-react";
import { AlertCircle, Eye, EyeOff, Loader2 } from "lucide-react";
export default function LoginPage() { export default function LoginPage() {
const { login, register, error, clearError } = useAuth(); const { login, register, error, clearError } = useAuth();
@ -40,36 +39,47 @@ export default function LoginPage() {
}, [regName, regEmail, regPassword, register, clearError]); }, [regName, regEmail, regPassword, register, clearError]);
return ( return (
<div className="flex min-h-screen items-center justify-center bg-muted/30 px-4"> <div className="flex min-h-screen items-center justify-center bg-background px-4">
<div className="w-full max-w-md"> {/* Background subtle gradient */}
<div className="mb-6 text-center"> <div className="absolute inset-0 bg-gradient-to-b from-primary/5 via-transparent to-transparent pointer-events-none" />
<h1 className="text-2xl font-bold tracking-tight">GammaDesk</h1>
<p className="text-sm text-muted-foreground">Options analytics dashboard</p> <div className="relative w-full max-w-md">
{/* Logo / Brand */}
<div className="mb-8 text-center">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary/10 border border-primary/20 mb-4">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M16 4L4 16L16 28L28 16L16 4Z" fill="hsl(130 70% 50%)" fillOpacity="0.2"/>
<path d="M16 8L8 16L16 24L24 16L16 8Z" fill="hsl(130 70% 50%)"/>
<text x="16" y="19" textAnchor="middle" fill="white" fontSize="10" fontWeight="bold">G</text>
</svg>
</div>
<h1 className="text-2xl font-bold tracking-tight text-foreground">GammaDesk</h1>
<p className="text-sm text-muted-foreground mt-1">Options gamma analytics dashboard</p>
</div> </div>
{error && ( {error && (
<Alert variant="destructive" className="mb-4"> <Alert variant="destructive" className="mb-4 border-destructive/50 bg-destructive/10">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription> <AlertDescription>{error}</AlertDescription>
</Alert> </Alert>
)} )}
<Tabs defaultValue="login" className="w-full" onValueChange={() => clearError()}> <Tabs defaultValue="login" className="w-full" onValueChange={() => clearError()}>
<TabsList className="grid w-full grid-cols-2"> <TabsList className="grid w-full grid-cols-2 bg-card border border-border mb-6">
<TabsTrigger value="login">Sign In</TabsTrigger> <TabsTrigger value="login" className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-lg">Sign In</TabsTrigger>
<TabsTrigger value="register">Create Account</TabsTrigger> <TabsTrigger value="register" className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-lg">Create Account</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="login"> <TabsContent value="login">
<Card> <Card className="border-border bg-card/50 backdrop-blur-sm shadow-xl">
<CardHeader className="pb-3"> <CardHeader className="pb-4">
<CardTitle className="text-base">Welcome back</CardTitle> <CardTitle className="text-xl font-semibold">Welcome back</CardTitle>
<CardDescription className="text-xs">Enter your email and password to continue</CardDescription> <CardDescription className="text-sm text-muted-foreground">Enter your credentials to continue</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form onSubmit={handleLogin} className="flex flex-col gap-4"> <form onSubmit={handleLogin} className="flex flex-col gap-5">
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-2">
<Label htmlFor="login-email" className="text-xs">Email</Label> <Label htmlFor="login-email" className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Email</Label>
<Input <Input
id="login-email" id="login-email"
type="email" type="email"
@ -78,10 +88,11 @@ export default function LoginPage() {
placeholder="you@example.com" placeholder="you@example.com"
required required
autoComplete="email" autoComplete="email"
className="h-12 bg-background border-border focus:border-primary focus:ring-primary/20"
/> />
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-2">
<Label htmlFor="login-password" className="text-xs">Password</Label> <Label htmlFor="login-password" className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Password</Label>
<div className="relative"> <div className="relative">
<Input <Input
id="login-password" id="login-password"
@ -91,19 +102,25 @@ export default function LoginPage() {
placeholder="••••••••" placeholder="••••••••"
required required
autoComplete="current-password" autoComplete="current-password"
className="h-12 bg-background border-border focus:border-primary focus:ring-primary/20 pr-10"
/> />
<button <button
type="button" type="button"
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground" className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
> >
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />} {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button> </button>
</div> </div>
</div> </div>
<Button type="submit" className="w-full" disabled={loading}> <Button
type="submit"
className="w-full h-12 text-base font-semibold bg-primary hover:bg-primary/90 text-primary-foreground rounded-xl neon-glow-sm transition-all"
disabled={loading}
>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Sign In Sign In
{!loading && <ArrowRight className="ml-2 h-4 w-4" />}
</Button> </Button>
</form> </form>
</CardContent> </CardContent>
@ -111,15 +128,15 @@ export default function LoginPage() {
</TabsContent> </TabsContent>
<TabsContent value="register"> <TabsContent value="register">
<Card> <Card className="border-border bg-card/50 backdrop-blur-sm shadow-xl">
<CardHeader className="pb-3"> <CardHeader className="pb-4">
<CardTitle className="text-base">Create your account</CardTitle> <CardTitle className="text-xl font-semibold">Create your account</CardTitle>
<CardDescription className="text-xs">Sign up to access the dashboard</CardDescription> <CardDescription className="text-sm text-muted-foreground">Sign up to access the dashboard</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form onSubmit={handleRegister} className="flex flex-col gap-4"> <form onSubmit={handleRegister} className="flex flex-col gap-5">
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-2">
<Label htmlFor="reg-name" className="text-xs">Name</Label> <Label htmlFor="reg-name" className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Name</Label>
<Input <Input
id="reg-name" id="reg-name"
type="text" type="text"
@ -128,10 +145,11 @@ export default function LoginPage() {
placeholder="John Doe" placeholder="John Doe"
required required
autoComplete="name" autoComplete="name"
className="h-12 bg-background border-border focus:border-primary focus:ring-primary/20"
/> />
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-2">
<Label htmlFor="reg-email" className="text-xs">Email</Label> <Label htmlFor="reg-email" className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Email</Label>
<Input <Input
id="reg-email" id="reg-email"
type="email" type="email"
@ -140,10 +158,11 @@ export default function LoginPage() {
placeholder="you@example.com" placeholder="you@example.com"
required required
autoComplete="email" autoComplete="email"
className="h-12 bg-background border-border focus:border-primary focus:ring-primary/20"
/> />
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-2">
<Label htmlFor="reg-password" className="text-xs">Password</Label> <Label htmlFor="reg-password" className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Password</Label>
<Input <Input
id="reg-password" id="reg-password"
type="password" type="password"
@ -153,17 +172,28 @@ export default function LoginPage() {
required required
minLength={6} minLength={6}
autoComplete="new-password" autoComplete="new-password"
className="h-12 bg-background border-border focus:border-primary focus:ring-primary/20"
/> />
</div> </div>
<Button type="submit" className="w-full" disabled={loading}> <Button
type="submit"
className="w-full h-12 text-base font-semibold bg-primary hover:bg-primary/90 text-primary-foreground rounded-xl neon-glow-sm transition-all"
disabled={loading}
>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Create Account Create Account
{!loading && <ArrowRight className="ml-2 h-4 w-4" />}
</Button> </Button>
</form> </form>
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
{/* Footer */}
<p className="text-center text-xs text-muted-foreground mt-8">
Analytics & education only. Not financial advice.
</p>
</div> </div>
</div> </div>
); );