Redesign: Nexus AI theme - deep black background, neon green accent, modern typography
parent
003a913186
commit
5d0b8736fc
|
|
@ -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 & education only. Not financial advice. Data is simulated
|
Analytics & education only. Not financial advice. Data is simulated
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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" ? (
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue