diff --git a/apps/website/app/globals.css b/apps/website/app/globals.css
index 77f60b44b..a754b0253 100644
--- a/apps/website/app/globals.css
+++ b/apps/website/app/globals.css
@@ -141,9 +141,9 @@
@layer utilities {
/* Precision Racing Utilities */
.glass-panel {
- background: rgba(20, 22, 25, 0.7);
- backdrop-filter: blur(12px);
- border: 1px solid var(--color-outline);
+ background: rgba(20, 22, 25, 0.85);
+ backdrop-filter: blur(16px);
+ border-bottom: 1px solid var(--color-outline);
}
.subtle-gradient {
@@ -161,7 +161,7 @@
left: 0;
width: 100%;
height: 1px;
- background: linear-gradient(90deg, var(--color-primary) 0%, transparent 100%);
+ background: linear-gradient(90deg, transparent 0%, var(--color-primary) 50%, transparent 100%);
opacity: 0.5;
}
diff --git a/apps/website/components/admin/AdminToolbar.tsx b/apps/website/components/admin/AdminToolbar.tsx
index 34ec81ce6..e7e02a51c 100644
--- a/apps/website/components/admin/AdminToolbar.tsx
+++ b/apps/website/components/admin/AdminToolbar.tsx
@@ -21,7 +21,6 @@ export function AdminToolbar({
return (
{children}
diff --git a/apps/website/components/layout/AppFooter.tsx b/apps/website/components/layout/AppFooter.tsx
index 829780307..1575d4c91 100644
--- a/apps/website/components/layout/AppFooter.tsx
+++ b/apps/website/components/layout/AppFooter.tsx
@@ -1,44 +1,72 @@
'use client';
-import { Surface } from '@/ui/Surface';
-import { Stack } from '@/ui/Stack';
-import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
+import { Text } from '@/ui/Text';
+import { Link } from '@/ui/Link';
+import { AppShellBar } from './AppShellBar';
+import { Activity, Database, Server, Wifi } from 'lucide-react';
export function AppFooter() {
- return (
-
-
-
-
- © {new Date().getFullYear()} GridPilot
-
-
-
- Session ready
-
-
+ const currentYear = new Date().getFullYear();
-
-
-
-
- System Normal
-
-
+ return (
+
+ {/* Left: System Info */}
+
+
+
+
+ GRIDPILOT OS v2.0
+
+
+
+ © {currentYear}
+
+
+
+ {/* Center: Telemetry Status */}
+
+
+
+
+
+
+
+ 12ms
-
+
+ {/* Right: Legal & Tools */}
+
+ Terms
+ Privacy
+ Status
+
+
+ );
+}
+
+function StatusIndicator({ icon: Icon, label, status }: { icon: any, label: string, status: 'good' | 'warn' | 'bad' }) {
+ const color = status === 'good' ? 'text-success-green' : status === 'warn' ? 'text-warning-amber' : 'text-critical-red';
+
+ return (
+
+
+
+ {label}
+
+
+ );
+}
+
+function FooterLink({ href, children }: { href: string, children: React.ReactNode }) {
+ return (
+
+ {children}
+
);
}
diff --git a/apps/website/components/layout/AppHeader.tsx b/apps/website/components/layout/AppHeader.tsx
index d3f8ef5e1..c70b9b098 100644
--- a/apps/website/components/layout/AppHeader.tsx
+++ b/apps/website/components/layout/AppHeader.tsx
@@ -1,72 +1,119 @@
'use client';
-import { BrandMark } from '@/ui/BrandMark';
-import { HeaderActions } from '@/components/layout/HeaderActions';
-import { PublicNav } from '@/components/layout/PublicNav';
+import { Box } from '@/ui/Box';
+import { Text } from '@/ui/Text';
+import { Search, Bell, User, ChevronDown, Command } from 'lucide-react';
+import { usePathname } from 'next/navigation';
import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
import { routes } from '@/lib/routing/RouteConfig';
-import { Box } from '@/ui/Box';
-import { Stack } from '@/ui/Stack';
-import { Text } from '@/ui/Text';
-import { Surface } from '@/ui/Surface';
-import { usePathname } from 'next/navigation';
+import { Button } from '@/ui/Button';
+import { useState, useEffect } from 'react';
+import { AppShellBar } from './AppShellBar';
export function AppHeader() {
const pathname = usePathname();
const { data: session } = useCurrentSession();
const isAuthenticated = !!session;
- const homeHref = isAuthenticated ? routes.protected.dashboard : routes.public.home;
+
+ // Simple breadcrumb logic
+ const pathSegments = pathname.split('/').filter(Boolean);
+ const breadcrumbs = pathSegments.length > 0
+ ? pathSegments.map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' / ')
+ : 'Home';
+
+ // Clock
+ const [time, setTime] = useState('');
+ useEffect(() => {
+ const updateTime = () => {
+ const now = new Date();
+ setTime(now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }));
+ };
+ updateTime();
+ const interval = setInterval(updateTime, 60000);
+ return () => clearInterval(interval);
+ }, []);
return (
-
-
- {/* Left: Brand & Context */}
-
-
-
- {isAuthenticated && (
-
-
- Workspace
-
-
- )}
-
+
+ {/* Left: Context & Search */}
+
+
+ {breadcrumbs}
+
- {/* Center: Navigation (if public) */}
- {!isAuthenticated && (
-
-
+ {/* Command Search - Refined */}
+
+
+
+
+
+ K
- )}
-
- {/* Right: Session Controls */}
-
-
-
-
- LIVE
-
-
-
-
+
+ {/* Right: System Status & User */}
+
+ {/* System Time */}
+
+
+ {time} UTC
+
+
+
+ {/* Notifications */}
+
+
+
+
+
+ {/* User Menu */}
+ {isAuthenticated ? (
+
+
+
+
+
+
+ {session.user.displayName || 'Driver'}
+
+
+
+
+ ) : (
+
+
+
+
+ )}
+
+
);
}
diff --git a/apps/website/components/layout/AppShellBar.tsx b/apps/website/components/layout/AppShellBar.tsx
new file mode 100644
index 000000000..c477905d2
--- /dev/null
+++ b/apps/website/components/layout/AppShellBar.tsx
@@ -0,0 +1,34 @@
+'use client';
+
+import { Box } from '@/ui/Box';
+import { ReactNode } from 'react';
+
+interface AppShellBarProps {
+ position: 'top' | 'bottom';
+ children: ReactNode;
+ sidebarOffset?: boolean;
+}
+
+export function AppShellBar({ position, children, sidebarOffset = true }: AppShellBarProps) {
+ const isTop = position === 'top';
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/website/components/layout/AppSidebar.tsx b/apps/website/components/layout/AppSidebar.tsx
index 827ba9c84..6659c73a9 100644
--- a/apps/website/components/layout/AppSidebar.tsx
+++ b/apps/website/components/layout/AppSidebar.tsx
@@ -1,50 +1,54 @@
'use client';
-import { AuthedNav } from '@/components/layout/AuthedNav';
-import { PublicNav } from '@/components/layout/PublicNav';
-import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
+import { BrandMark } from '@/ui/BrandMark';
+import { NavLink } from '@/ui/NavLink';
+import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box';
-import { DashboardRail } from '@/components/dashboard/DashboardRail';
-import { Text } from '@/ui/Text';
-import { Surface } from '@/ui/Surface';
+import { Stack } from '@/ui/Stack';
+import {
+ LayoutGrid,
+ Trophy,
+ Users,
+ Calendar,
+ Flag,
+ Home
+} from 'lucide-react';
import { usePathname } from 'next/navigation';
export function AppSidebar() {
const pathname = usePathname();
- const { data: session } = useCurrentSession();
- const isAuthenticated = !!session;
+
+ const navItems = [
+ { label: 'Home', href: routes.public.home, icon: Home },
+ { label: 'Leagues', href: routes.public.leagues, icon: Trophy },
+ { label: 'Drivers', href: routes.public.drivers, icon: Users },
+ { label: 'Leaderboards', href: routes.public.leaderboards, icon: LayoutGrid },
+ { label: 'Teams', href: routes.public.teams, icon: Flag },
+ { label: 'Races', href: routes.public.races, icon: Calendar },
+ ];
return (
-
-
-
-
-
-
-
- DASHBOARD
-
-
-
-
- {isAuthenticated ? (
-
- ) : (
-
- )}
-
-
-
-
+
+ {/* Brand Header */}
+
+
+
+
+ {/* Navigation */}
+
+
+ {navItems.map((item) => (
+
+ ))}
+
+
+
);
}
diff --git a/apps/website/components/layout/HeaderActions.tsx b/apps/website/components/layout/HeaderActions.tsx
index f72569ae0..0803c4951 100644
--- a/apps/website/components/layout/HeaderActions.tsx
+++ b/apps/website/components/layout/HeaderActions.tsx
@@ -1,45 +1,49 @@
import { routes } from '@/lib/routing/RouteConfig';
import { Button } from '@/ui/Button';
import { Stack } from '@/ui/Stack';
-import { LogIn, UserPlus } from 'lucide-react';
+import { Box } from '@/ui/Box';
interface HeaderActionsProps {
isAuthenticated: boolean;
}
-/**
- * HeaderActions provides the primary actions in the header (Login, Signup, Profile).
- */
export function HeaderActions({ isAuthenticated }: HeaderActionsProps) {
if (isAuthenticated) {
return (
-
-
-
+
);
}
return (
-
+
}
data-testid="public-nav-login"
+ className="text-text-med hover:text-text-high transition-colors duration-200"
>
- Login
+ Sign In
+
+
+
}
data-testid="public-nav-signup"
+ className="px-6 font-medium tracking-wide"
>
Sign Up
diff --git a/apps/website/components/layout/PublicNav.tsx b/apps/website/components/layout/PublicNav.tsx
index d7168addf..5ca2a3ce0 100644
--- a/apps/website/components/layout/PublicNav.tsx
+++ b/apps/website/components/layout/PublicNav.tsx
@@ -1,6 +1,6 @@
import { routes } from '@/lib/routing/RouteConfig';
import { Stack } from '@/ui/Stack';
-import { Calendar, Home, Layout, Trophy, Users } from 'lucide-react';
+import { Calendar, Home, LayoutGrid, Trophy, Users, Flag } from 'lucide-react';
import { NavLink } from '@/ui/NavLink';
interface PublicNavProps {
@@ -8,21 +8,18 @@ interface PublicNavProps {
direction?: 'row' | 'col';
}
-/**
- * PublicNav displays navigation items for unauthenticated users.
- */
export function PublicNav({ pathname, direction = 'col' }: PublicNavProps) {
const items = [
{ label: 'Home', href: routes.public.home, icon: Home },
{ label: 'Leagues', href: routes.public.leagues, icon: Trophy },
{ label: 'Drivers', href: routes.public.drivers, icon: Users },
- { label: 'Leaderboards', href: routes.public.leaderboards, icon: Layout },
- { label: 'Teams', href: routes.public.teams, icon: Users },
+ { label: 'Leaderboards', href: routes.public.leaderboards, icon: LayoutGrid },
+ { label: 'Teams', href: routes.public.teams, icon: Flag },
{ label: 'Races', href: routes.public.races, icon: Calendar },
];
return (
-
+
{items.map((item) => (
}
- sidebar={showSidebar ? : undefined}
+ sidebar={}
footer={}
- fixedHeader
- fixedSidebar
+ fixedHeader={true}
+ fixedSidebar={true}
+ fixedFooter={false}
>
-
{children}
-
+
);
}
diff --git a/apps/website/tsc_output.txt b/apps/website/tsc_output.txt
new file mode 100644
index 000000000..1c0833c18
--- /dev/null
+++ b/apps/website/tsc_output.txt
@@ -0,0 +1,2 @@
+components/admin/AdminToolbar.tsx(24,7): error TS2322: Type '{ children: ReactNode; leftContent: ReactNode; variant: string; }' is not assignable to type 'IntrinsicAttributes & ControlBarProps'.
+ Property 'variant' does not exist on type 'IntrinsicAttributes & ControlBarProps'.
diff --git a/apps/website/ui/BrandMark.tsx b/apps/website/ui/BrandMark.tsx
index 0642aafd8..22c36691e 100644
--- a/apps/website/ui/BrandMark.tsx
+++ b/apps/website/ui/BrandMark.tsx
@@ -7,29 +7,20 @@ interface BrandMarkProps {
priority?: boolean;
}
-/**
- * BrandMark provides the consistent logo/wordmark for the application.
- * Aligned with "Precision Racing Minimal" theme.
- */
export function BrandMark({ href = '/' }: BrandMarkProps) {
return (
-
-
-
-
-
+
+
+
diff --git a/apps/website/ui/Layout.tsx b/apps/website/ui/Layout.tsx
index ff515d28c..35a355c6f 100644
--- a/apps/website/ui/Layout.tsx
+++ b/apps/website/ui/Layout.tsx
@@ -6,78 +6,71 @@ export interface LayoutProps {
header?: ReactNode;
footer?: ReactNode;
sidebar?: ReactNode;
- /**
- * Whether the sidebar should be fixed to the side.
- * If true, the main content will be offset by the sidebar width.
- */
fixedSidebar?: boolean;
- /**
- * Whether the header should be fixed to the top.
- * If true, the main content will be offset by the header height.
- */
fixedHeader?: boolean;
+ fixedFooter?: boolean;
}
/**
* Layout is the canonical app frame component.
- * It orchestrates the high-level structure: Header, Sidebar, Main Content, and Footer.
+ * Redesigned for "Cockpit" layout: Sidebar is primary (full height), Header and Content sit to the right.
*/
export const Layout = ({
children,
header,
footer,
sidebar,
- fixedSidebar = false,
- fixedHeader = false
+ fixedSidebar = true,
+ fixedHeader = true,
+ fixedFooter = true // Default to true for AppShellBar
}: LayoutProps) => {
return (
-
- {header && (
+
+ {/* Sidebar - Primary Vertical Axis - Solid Background */}
+ {sidebar && (
- {header}
+ {sidebar}
)}
-
- {sidebar && (
-
- {sidebar}
-
- )}
+ {/* Main Content Area - Right of Sidebar */}
+
+ {/* Header - Rendered directly as it contains AppShellBar (fixed) */}
+ {header}
+ {/* Main Scrollable Content */}
-
+
{children}
-
- {footer && (
-
- {footer}
-
- )}
+
+ {/* Footer - Rendered directly as it contains AppShellBar (fixed) */}
+ {footer}
);
diff --git a/apps/website/ui/NavLink.tsx b/apps/website/ui/NavLink.tsx
index 6b884237a..03c781843 100644
--- a/apps/website/ui/NavLink.tsx
+++ b/apps/website/ui/NavLink.tsx
@@ -12,25 +12,56 @@ interface NavLinkProps {
variant?: 'sidebar' | 'top';
}
-/**
- * NavLink provides a consistent link component for navigation.
- * Supports both sidebar and top navigation variants.
- */
export function NavLink({ href, label, icon, isActive, variant = 'sidebar' }: NavLinkProps) {
+ const isTop = variant === 'top';
+
+ // Dieter Rams style: Unobtrusive, Honest, Thorough.
+ // No glows. No shadows. Just clear contrast and alignment.
+
const content = (
-
- {icon && }
-
+
+ {icon && (
+
+ )}
+
+
{label}
- {variant === 'sidebar' && isActive && (
-
+
+ {/* Minimal Active Indicator */}
+ {!isTop && isActive && (
+
)}
);
return (
-
+
{content}
);
diff --git a/docs/THEME.md b/docs/THEME.md
index cad4698e7..18fa5b496 100644
--- a/docs/THEME.md
+++ b/docs/THEME.md
@@ -1,134 +1,172 @@
-🎨 GridPilot Theme — “Precision Racing Minimal”
+GridPilot Theme — “Modern Precision with Obsessive Detail”
-A clean, modern racing interface that feels like a cockpit dashboard — calm, sharp, and serious.
+A meticulously crafted sim-racing interface built with absolute love to detail.
⸻
-1. Identity & Mood
+Core Essence
-GridPilot should feel like:
- • a real motorsport instrument, not a game launcher
- • calm and focused, like the moment before a qualifying lap
- • precise, like a telemetry screen
- • modern and minimal, without visual noise
- • slightly futuristic, but never “RGB gamer chaos”
+GridPilot should feel like a product where every pixel was placed on purpose.
+A UI that radiates care, intention, and craft — the way a beautifully machined steering wheel or pedal set feels when you touch it.
-It should appeal equally to sim racers, gamers, and casual fans.
+Not loud.
+Not flashy.
+Not corporate.
+But modern, obsessive, refined.
+
+The user should think:
+
+“Someone really cared when they designed this.”
⸻
-2. Visual Style
+1. The Kind of Modern We Want
-Core Aesthetic
- • matte dark surfaces
- • thin, crisp separators
- • soft blue/cyan glows on interaction
- • no aggressive neon
- • subtle gradients for depth
- • everything feels instrument-grade, not decorative
+Modern ≠ trendy
+GridPilot is modern in the way high-end hardware is modern:
+ • clean materials
+ • subtle gradients
+ • precision shadows
+ • invisible order
+ • absolute visual consistency
-Color System
- • Graphite Black — base background
- • Charcoal — panel surfaces
- • Steel Grey — separators & outlines
- • Electric Blue — primary actions
- • Telemetry Aqua — interactive highlights
- • Motorsport Amber — warnings & signals
+It feels engineered, not styled.
-Colors should feel like motorsport, not corporate SaaS.
+Everything is smooth, but never busy.
+Everything is polished, but never glossy.
+Everything is minimal, but never empty.
+
+It has the vibe of:
+ • Apple’s focus
+ • Porsche’s restraint
+ • Moza/Fanatec’s precision hardware aesthetics
+ • Telemetry dashboards
+ • Calm esports production graphics
+
+A blend of racing seriousness and premium digital craft.
⸻
-3. Components & Interaction
+2. Love to Detail (the signature of GridPilot)
-Panels & Cards
- • slightly inset or raised
- • reminiscent of cockpit modules
- • structured and clean
+This is where GridPilot stands out.
-Tables
- • information-dense
- • instantly scannable
- • light hover highlights
- • telemetry-style status colors
+Microspacing
-Buttons
- • flat by default
- • glow only on hover
- • snappy, “race-engineer” response speed
+Spacing is not “good enough” — it’s perfectly balanced.
+Margins breathe.
+Rows align with intent.
+Nothing floats randomly.
-Modals
- • soft frosted blur
- • fast open/close
- • subtle pit-lane lighting vibes
+Typography finesse
-⸻
+Numbers align sharply for standings.
+Headings sit exactly on their baseline rhythm.
+Secondary metadata uses softer brightness and tighter spacing.
-4. Motion & Feedback
+Shadow discipline
-Motion should feel racing-inspired:
- • short, crisp animations
- • no bounce or playful movement
- • hover = slight lift + color pulse
- • loading = a thin progress line (pit limiter style)
- • tab switching = sliding underline (chicane motion)
+No random shadows — only a small, soft, controlled spread.
+Layer depth is subtle but unmistakably refined.
-Responsive, not playful.
+Color temperature
-⸻
+Dark modes often feel muddy or flat.
+GridPilot should feel crisp, layered, and tuned —
+as if the entire palette has been color-graded like broadcast graphics.
-5. Layout Structure
+Interactive texture
-Think Telemetry Workspace:
- • Sidebar → control rail
- • Header → context + session controls
- • Main Area → race tables, session details, track maps
- • Right Panel → contextual info, drivers, actions
-
-Everything modular, readable, and effortless to navigate.
-
-⸻
-
-6. Tone & Copywriting
-
-The product tone is:
- • calm
- • concise
- • direct
- • technical but human
- • zero hype
-
-Examples:
- • “Race added.”
- • “Standings updated.”
- • “Session ready.”
- • “Review protest.”
- • “Sponsor payout queued.”
-
-Never corporate. Never salesy. Never loud.
-
-⸻
-
-7. Emotional Experience
-
-Users should feel:
- • in control
- • supported
- • efficient
- • connected to motorsport culture
- • confident in the system
-
-A disciplined, trustworthy tool — not a flashy app.
-
-⸻
-
-8. Overall Vibe
-
-“A premium cockpit dashboard — for people who actually race.”
- • minimal
+Every hover, highlight, or focus state is:
+ • soft
+ • smooth
+ • fast
• precise
- • clean
- • serious
- • attractive without noise
-A tool you’re happy to use, because it respects your time and your craft.
\ No newline at end of file
+Nothing is bouncy, glowy, or game-y.
+But everything feels alive when touched.
+
+Love to detail = you don’t notice it, but you feel it.
+
+⸻
+
+3. Emotional Atmosphere
+
+The moment the UI loads, the user should feel:
+ • This is premium.
+ • This was crafted, not slapped together.
+ • This tool respects my time.
+ • This belongs to sim racing, not generic SaaS.
+ • This is serious but not sterile.
+
+It’s that mixture of calm confidence and quiet intensity that sim racers crave before entering a session.
+
+⸻
+
+4. Visual Style Summary (crisp and modern)
+
+Surfaces
+ • matte
+ • low contrast
+ • ultra-clean edges
+ • subtle gradients only on interaction
+
+Accents
+ • electric blue or aqua, used sparingly
+ • amber for warnings (motorsport heritage)
+ • desaturated greys for hierarchy
+
+Lighting
+ • no neon
+ • no RGB vibes
+ • subtle glow only on very important interactive elements
+
+Depth
+ • controlled layering
+ • meaningful shadows
+ • frosted blur for modals (garage glass vibe)
+
+⸻
+
+5. Motion
+
+Animations reflect precision:
+ • fast acceleration
+ • quick settle
+ • no jitter
+ • no wasted frames
+
+Transitions should feel like:
+ • opening a garage screen
+ • switching telemetry pages
+ • selecting a gear cleanly
+
+A sense of mechanical smoothness.
+
+⸻
+
+6. Why This Theme Works for Our Audience
+
+For gamers:
+ • visually immersive
+ • polished like a AAA menu
+ • subtle effects that feel “alive”
+
+For sim racers:
+ • data clarity
+ • racing-inspired accents
+ • tool-like seriousness
+
+For devs:
+ • clean structure
+ • intentional simplicity
+ • maintainable aesthetic rules
+
+This theme hits the sweet spot:
+professional, modern, crafted, premium.
+
+⸻
+
+One-Line Summary
+
+GridPilot should feel like a high-end sim racing cockpit UI — obsessively detailed, modern, calm, and deeply cared for, where every interaction feels engineered with precision.
\ No newline at end of file