diff --git a/apps/website/app/globals.css b/apps/website/app/globals.css
index 4651e67ed..288203d7a 100644
--- a/apps/website/app/globals.css
+++ b/apps/website/app/globals.css
@@ -1,4 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+@import '../ui/theme/theme.css';
@tailwind base;
@tailwind components;
diff --git a/apps/website/app/layout.tsx b/apps/website/app/layout.tsx
index d33301091..cd8361073 100644
--- a/apps/website/app/layout.tsx
+++ b/apps/website/app/layout.tsx
@@ -71,7 +71,7 @@ export default async function RootLayout({
const enabledFlags = featureService.getEnabledFlags();
return (
-
+
diff --git a/apps/website/components/AppWrapper.tsx b/apps/website/components/AppWrapper.tsx
index 1763ab698..7a165b486 100644
--- a/apps/website/components/AppWrapper.tsx
+++ b/apps/website/components/AppWrapper.tsx
@@ -8,6 +8,7 @@ import { NotificationProvider } from '@/components/notifications/NotificationPro
import { NotificationIntegration } from '@/components/errors/NotificationIntegration';
import { EnhancedErrorBoundary } from '@/components/errors/EnhancedErrorBoundary';
import { DevToolbar } from '@/components/dev/DevToolbar';
+import { ThemeProvider } from '@/ui/theme/ThemeProvider';
import React from 'react';
interface AppWrapperProps {
@@ -19,17 +20,19 @@ export function AppWrapper({ children, enabledFlags }: AppWrapperProps) {
return (
-
-
-
-
-
- {children}
- {process.env.NODE_ENV === 'development' && }
-
-
-
-
+
+
+
+
+
+
+ {children}
+ {process.env.NODE_ENV === 'development' && }
+
+
+
+
+
);
diff --git a/apps/website/ui/theme/Theme.ts b/apps/website/ui/theme/Theme.ts
new file mode 100644
index 000000000..c1d95ac55
--- /dev/null
+++ b/apps/website/ui/theme/Theme.ts
@@ -0,0 +1,57 @@
+export interface ThemeColors {
+ bg: {
+ base: string;
+ surface: string;
+ surfaceMuted: string;
+ };
+ border: {
+ default: string;
+ muted: string;
+ };
+ text: {
+ high: string;
+ med: string;
+ low: string;
+ };
+ intent: {
+ primary: string;
+ telemetry: string;
+ warning: string;
+ success: string;
+ critical: string;
+ };
+}
+
+export interface ThemeRadii {
+ none: string;
+ sm: string;
+ md: string;
+ lg: string;
+ xl: string;
+ full: string;
+}
+
+export interface ThemeShadows {
+ none: string;
+ sm: string;
+ md: string;
+ lg: string;
+ xl: string;
+ focus: string;
+}
+
+export interface ThemeTypography {
+ fontFamily: {
+ sans: string;
+ mono: string;
+ };
+}
+
+export interface Theme {
+ id: string;
+ name: string;
+ colors: ThemeColors;
+ radii: ThemeRadii;
+ shadows: ThemeShadows;
+ typography: ThemeTypography;
+}
diff --git a/apps/website/ui/theme/ThemeProvider.tsx b/apps/website/ui/theme/ThemeProvider.tsx
new file mode 100644
index 000000000..8c62f0218
--- /dev/null
+++ b/apps/website/ui/theme/ThemeProvider.tsx
@@ -0,0 +1,33 @@
+'use client';
+
+import React, { createContext, useContext, ReactNode } from 'react';
+import { Theme } from './Theme';
+import { defaultTheme } from './themes/default';
+
+interface ThemeContextType {
+ theme: Theme;
+}
+
+const ThemeContext = createContext(undefined);
+
+export function ThemeProvider({ children }: { children: ReactNode }) {
+ // For now, we only have the default theme.
+ // In the future, this could be driven by state, cookies, or user preferences.
+ const value = {
+ theme: defaultTheme,
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+}
diff --git a/apps/website/ui/theme/theme.css b/apps/website/ui/theme/theme.css
new file mode 100644
index 000000000..224f30d83
--- /dev/null
+++ b/apps/website/ui/theme/theme.css
@@ -0,0 +1,41 @@
+:root {
+ /* Base tokens mapped to default theme */
+ --ui-color-bg-base: #0C0D0F;
+ --ui-color-bg-surface: #141619;
+ --ui-color-bg-surface-muted: rgba(20, 22, 25, 0.7);
+
+ --ui-color-border-default: #23272B;
+ --ui-color-border-muted: rgba(35, 39, 43, 0.5);
+
+ --ui-color-text-high: #FFFFFF;
+ --ui-color-text-med: #A1A1AA;
+ --ui-color-text-low: #71717A;
+
+ --ui-color-intent-primary: #198CFF;
+ --ui-color-intent-telemetry: #4ED4E0;
+ --ui-color-intent-warning: #FFBE4D;
+ --ui-color-intent-success: #6FE37A;
+ --ui-color-intent-critical: #E35C5C;
+
+ --ui-radius-none: 0;
+ --ui-radius-sm: 0.125rem;
+ --ui-radius-md: 0.375rem;
+ --ui-radius-lg: 0.5rem;
+ --ui-radius-xl: 0.75rem;
+ --ui-radius-full: 9999px;
+
+ --ui-shadow-none: none;
+ --ui-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+ --ui-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ --ui-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+ --ui-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+ --ui-shadow-focus: 0 0 0 4px rgba(25, 140, 255, 0.5);
+
+ --ui-font-sans: 'Inter', system-ui, sans-serif;
+ --ui-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
+}
+
+/* Theme override block */
+[data-theme='default'] {
+ /* Currently same as root, but allows for future overrides */
+}
diff --git a/apps/website/ui/theme/themes/default.ts b/apps/website/ui/theme/themes/default.ts
new file mode 100644
index 000000000..f0a852f7a
--- /dev/null
+++ b/apps/website/ui/theme/themes/default.ts
@@ -0,0 +1,51 @@
+import { Theme } from '../Theme';
+
+export const defaultTheme: Theme = {
+ id: 'default',
+ name: 'Precision Racing (Dark)',
+ colors: {
+ bg: {
+ base: '#0C0D0F',
+ surface: '#141619',
+ surfaceMuted: 'rgba(20, 22, 25, 0.7)',
+ },
+ border: {
+ default: '#23272B',
+ muted: 'rgba(35, 39, 43, 0.5)',
+ },
+ text: {
+ high: '#FFFFFF',
+ med: '#A1A1AA',
+ low: '#71717A',
+ },
+ intent: {
+ primary: '#198CFF',
+ telemetry: '#4ED4E0',
+ warning: '#FFBE4D',
+ success: '#6FE37A',
+ critical: '#E35C5C',
+ },
+ },
+ radii: {
+ none: '0',
+ sm: '0.125rem',
+ md: '0.375rem',
+ lg: '0.5rem',
+ xl: '0.75rem',
+ full: '9999px',
+ },
+ shadows: {
+ none: 'none',
+ sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
+ md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
+ lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
+ xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
+ focus: '0 0 0 4px rgba(25, 140, 255, 0.5)',
+ },
+ typography: {
+ fontFamily: {
+ sans: "'Inter', system-ui, sans-serif",
+ mono: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
+ },
+ },
+};
diff --git a/docs/architecture/website/UI_THEMING.md b/docs/architecture/website/UI_THEMING.md
new file mode 100644
index 000000000..be9fa6f47
--- /dev/null
+++ b/docs/architecture/website/UI_THEMING.md
@@ -0,0 +1,224 @@
+# Website UI Theming Concept
+
+This document defines the *conceptual* theming architecture for the Website UI layer. It is written to make the UI maintainable, themeable, and **decoupled from Tailwind** (Tailwind may exist as an implementation detail, never as the source of truth).
+
+For the existing visual direction and palette intent, see [`docs/THEME.md`](docs/THEME.md).
+
+---
+
+## Problem
+
+The current UI style surface mixes:
+
+- semantic intent (primary, surface, outline)
+- concrete implementation (Tailwind class strings)
+- and multiple naming systems (Tailwind tokens, ad-hoc names, CSS variables)
+
+This makes it hard to:
+
+- change colors consistently,
+- introduce additional themes,
+- reason about component contracts,
+- and enforce architectural boundaries in [`apps/website/ui`](apps/website/ui).
+
+---
+
+## Goals
+
+1. **Single source of truth** for theme tokens (colors, typography, radii, shadows).
+2. **Semantic tokens** (intent-based), not implementation tokens (utility-class-based).
+3. **Runtime theme selection** via CSS variables (SSR-correct by default).
+4. UI components remain **pure** and follow [`docs/architecture/website/COMPONENT_ARCHITECTURE.md`](docs/architecture/website/COMPONENT_ARCHITECTURE.md).
+5. Tailwind is optional and must remain a **consumer/adapter**, not the authoring surface.
+
+Non-goal for this iteration: shipping a user-facing theme switcher. We implement a single default theme but design the architecture to add more later.
+
+---
+
+## Core idea: Theme contract + CSS variables boundary
+
+### 1) Theme contract (TypeScript)
+
+We define a typed theme contract, e.g. [`Theme`](apps/website/ui/theme/Theme.ts:1), that expresses the semantic palette and style tokens.
+
+Principle:
+
+- UI components accept **semantic props** (variant, intent, emphasis).
+- UI components **do not accept** Tailwind token strings like `bg-panel-gray`.
+
+Example shape (conceptual):
+
+- [`Theme`](apps/website/ui/theme/Theme.ts:1)
+ - `color`
+ - `bg.base`
+ - `bg.surface`
+ - `bg.surfaceMuted`
+ - `border.default`
+ - `text.high`
+ - `text.med`
+ - `text.low`
+ - `intent.primary`
+ - `intent.warning`
+ - `intent.success`
+ - `intent.critical`
+ - `radius`
+ - `sm` `md` `lg` `xl`
+ - `shadow`
+ - `sm` `md` `lg` `focus`
+ - `font`
+ - `body` `mono` `heading`
+
+The *exact* token taxonomy can evolve, but the rule is stable: tokens represent meaning, not implementation.
+
+### 2) CSS variables as the runtime boundary
+
+The theme is applied by setting CSS custom properties. UI components reference variables, not Tailwind classes.
+
+The canonical variables are defined in a dedicated stylesheet, e.g. [`docs/architecture/website/UI_THEMING.md`](docs/architecture/website/UI_THEMING.md) describes this as: `--ui-color-bg-base`, `--ui-color-text-high`, etc.
+
+Implementation typically lives in [`apps/website/ui/theme/theme.css`](apps/website/ui/theme/theme.css) and is imported by [`apps/website/app/globals.css`](apps/website/app/globals.css:1).
+
+The existing variables in [`apps/website/app/globals.css`](apps/website/app/globals.css:1) are already a strong start. The key change is to treat them as the *UI contract* and stop treating Tailwind names as the contract.
+
+---
+
+## Where theme lives (layering)
+
+This respects the UI purity rules in [`docs/architecture/website/COMPONENT_ARCHITECTURE.md`](docs/architecture/website/COMPONENT_ARCHITECTURE.md).
+
+### Authoritative token source
+
+- `CSS variables` are the runtime truth.
+- `TypeScript` theme contract is the compile-time truth.
+
+The theme is a public contract in the UI layer:
+
+- [`apps/website/ui/theme/Theme.ts`](apps/website/ui/theme/Theme.ts:1)
+- [`apps/website/ui/theme/themes/default.ts`](apps/website/ui/theme/themes/default.ts)
+- [`apps/website/ui/theme/theme.css`](apps/website/ui/theme/theme.css)
+
+### Applying the theme (SSR correctness)
+
+SSR correctness means: the initial HTML response already knows which theme is active.
+
+For a single default theme, the simplest SSR-correct approach is:
+
+- set `data-theme=default` on the `` element in [`RootLayout`](apps/website/app/layout.tsx:45).
+
+Later, if theme becomes user-selectable, the server can choose the theme per request using cookies/headers in [`RootLayout`](apps/website/app/layout.tsx:45), still using the same CSS-variable mechanism.
+
+---
+
+## Tailwind’s role (strictly non-authoritative)
+
+Tailwind may remain in the project for page/layout scaffolding or legacy areas, but it must be treated as:
+
+- a *consumer* of theme variables (e.g. Tailwind config maps colors to `var(--ui-...)`), or
+- an *adapter* for non-UI-layer code.
+
+In other words:
+
+- UI components inside [`apps/website/ui`](apps/website/ui) never rely on Tailwind class name semantics.
+- If Tailwind is used, it only references CSS variables (so switching the theme changes the look without changing class names).
+
+This prevents “utility token drift” where a Tailwind rename becomes a breaking theme change.
+
+---
+
+## Component authoring rules
+
+### Components are theme-driven, not class-driven
+
+Inside [`apps/website/ui`](apps/website/ui):
+
+1. Use CSS Modules for component styling.
+2. Use semantic variants and intent props.
+3. Use CSS variables for actual values.
+
+Example conceptually:
+
+- [`Button`](apps/website/ui/Button.tsx)
+ - `variant: primary | secondary | ghost`
+ - `intent: default | danger`
+ - CSS module uses variables like `background: var(--ui-color-intent-primary)`.
+
+### Primitives
+
+The primitives in [`apps/website/ui/primitives`](apps/website/ui/primitives) exist to build consistent semantics. They should not encode a “theme” directly; they expose minimal layout/styling mechanics.
+
+Examples in code today:
+
+- [`Box`](apps/website/ui/primitives/Box.tsx:1)
+- [`Stack`](apps/website/ui/primitives/Stack.tsx:1)
+- [`Surface`](apps/website/ui/primitives/Surface.tsx:1)
+
+Conceptually, primitives should either:
+
+- map typed props to standard CSS properties (style object), or
+- map typed props to CSS variables that are already in the theme contract.
+
+What primitives should **not** do: generate Tailwind class strings as their main behavior.
+
+---
+
+## Token naming: semantic + stable
+
+Recommended CSS variable naming:
+
+- `--ui-color-bg-base`
+- `--ui-color-bg-surface`
+- `--ui-color-border-default`
+- `--ui-color-text-high`
+- `--ui-color-intent-primary`
+- `--ui-radius-md`
+- `--ui-shadow-md`
+- `--ui-font-body`
+
+Rules:
+
+1. Prefix with `--ui-` to avoid collisions.
+2. Use semantic names (surface, text, intent), not “brand” names.
+3. Avoid numeric-only palettes in component code (no `gray-600` in UI internals).
+
+---
+
+## Adding themes (extensibility model)
+
+To add a new theme:
+
+1. Keep the token contract stable (same variables).
+2. Provide a new theme override block in [`apps/website/ui/theme/theme.css`](apps/website/ui/theme/theme.css), e.g.:
+ - `:root[data-theme=default] { ... }`
+ - `:root[data-theme=brand-x] { ... }`
+3. Optionally provide a matching TypeScript definition in [`apps/website/ui/theme/themes`](apps/website/ui/theme/themes) for tooling and validation.
+
+The important property: **components do not change** when a new theme is added.
+
+---
+
+## Architecture view
+
+```mermaid
+flowchart TD
+ A[Semantic theme contract] --> B[CSS variables]
+ B --> C[UI components]
+ B --> D[Tailwind adapter]
+ C --> E[App components]
+```
+
+Interpretation:
+
+- Theme contract defines the tokens we promise.
+- CSS variables implement those tokens at runtime.
+- UI components consume tokens directly.
+- Tailwind, if present, only consumes variables.
+
+---
+
+## Consequences (why this improves maintainability)
+
+1. **Token changes are localized** to the theme definition (CSS vars) rather than spread across UI components.
+2. UI components become **portable**: they do not depend on any particular styling framework.
+3. It becomes possible to add enforcement (lint rules, conventions) around “no Tailwind tokens inside UI”.
+4. Visual identity evolves without turning into a codebase-wide search-and-replace.
+