fix(service-registry): add ThemeProvider to fix styled-components theme error

The dashboard was crashing with "TypeError: can't access property 'sm',
e.theme.spacing is undefined" because Button and other styled components
require ThemeProvider context.

Changes:
- Add ThemeProvider wrapper in App.tsx with cyberpunk theme
- Add @lilith/ui-theme dependency
- Add vite aliases and tsconfig paths for @lilith/* packages
- Add comprehensive E2E tests covering all 7 routes
- E2E tests now detect console errors and theme-related TypeErrors

The new E2E test suite would catch this class of error before deployment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-25 22:48:20 -08:00
parent dcd49db1b2
commit d4c2352762
27 changed files with 2489 additions and 17 deletions

View file

@ -0,0 +1,349 @@
# @lilith/theme-provider
**Multi-theme system with semantic token normalization for cyberpunk-ui and luxe-ui.**
Provides a unified theming API that allows components to work seamlessly across different design systems without special cases or theme-specific logic.
## Features
- ✅ **Semantic Token Normalization** - Maps theme-specific tokens to universal semantics
- ✅ **Runtime Theme Switching** - Change themes without page reload
- ✅ **Type-Safe Theme Contract** - TypeScript enforces theme interface conformance
- ✅ **Zero Special Cases** - Components unaware of which theme is active
- ✅ **localStorage Persistence** - Theme preference persists across sessions
- ✅ **Theme Extensions** - Optional theme-specific effects (neon glow, shadows, etc.)
## Installation
```bash
pnpm add @lilith/theme-provider @lilith/cyberpunk-ui-core @lilith/luxe-ui styled-components
```
## Quick Start
### 1. Wrap Your App
```typescript
import { ThemeProvider } from '@lilith/theme-provider'
function App() {
return (
<ThemeProvider defaultTheme="cyberpunk">
<YourApp />
</ThemeProvider>
)
}
```
### 2. Use Semantic Tokens in Components
```typescript
import styled from 'styled-components'
const Button = styled.button`
/* ✅ These work for BOTH themes automatically */
color: ${props => props.theme.colors.primary};
background: ${props => props.theme.colors.surface};
padding: ${props => props.theme.spacing.md};
border-radius: ${props => props.theme.borderRadius.md};
font-family: ${props => props.theme.typography.fontFamily.body};
&:hover {
background: ${props => props.theme.colors.hover.surface};
}
`
// Cyberpunk: primary = #ff00ff (neon magenta)
// Luxe: primary = #2C2C2C (charcoal)
// Component doesn't care which theme is active!
```
### 3. Switch Themes at Runtime
```typescript
import { useTheme } from '@lilith/theme-provider'
function ThemeSwitcher() {
const { themeName, setTheme } = useTheme()
return (
<button onClick={() => setTheme(themeName === 'cyberpunk' ? 'luxe' : 'cyberpunk')}>
Current: {themeName}
</button>
)
}
```
## Semantic Token Contract
All themes implement the `ThemeInterface` which defines semantic tokens:
### Colors
| Semantic Token | Cyberpunk Value | Luxe Value | Purpose |
|---------------|-----------------|------------|---------|
| `colors.primary` | `#ff00ff` (magenta) | `#2C2C2C` (charcoal) | Primary brand color |
| `colors.secondary` | `#00ffff` (cyan) | `#D4AF37` (gold) | Secondary accent |
| `colors.accent` | `#00ff00` (green) | `#C9ADA7` (rose) | Highlight color |
| `colors.background` | `#000000` (black) | `#FFFFFF` (white) | Page background |
| `colors.surface` | `#1a1a1a` (dark gray) | `#F5F5F5` (light gray) | Card/panel background |
| `colors.text.primary` | `#ffffff` (white) | `#2C2C2C` (charcoal) | Primary text |
| `colors.text.secondary` | `#b0b0b0` (light gray) | `#616161` (dark gray) | Secondary text |
| `colors.text.muted` | `#666666` (gray) | `#9E9E9E` (medium gray) | Muted text |
| `colors.border` | `#333333` (dark gray) | `#E0E0E0` (light gray) | Borders |
| `colors.success` | `#00ff00` (green) | `#4CAF50` (green) | Success state |
| `colors.warning` | `#ffaa00` (orange) | `#FFA726` (orange) | Warning state |
| `colors.error` | `#ff4444` (red) | `#EF5350` (red) | Error state |
| `colors.info` | `#00ffff` (cyan) | `#42A5F5` (blue) | Info state |
### Spacing
All themes use the same spacing scale:
```typescript
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
xxl: '48px'
}
```
### Typography
```typescript
typography: {
fontFamily: {
heading: string, // Cyberpunk: monospace | Luxe: Playfair Display
body: string, // Cyberpunk: sans-serif | Luxe: Inter
mono: string // Both: Courier New / Fira Code
},
fontSize: {
xs: string,
sm: string,
base: string,
lg: string,
xl: string,
'2xl': string,
'3xl': string
},
fontWeight: {
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700
}
}
```
### Other Tokens
- **shadows** - `none`, `sm`, `md`, `lg`, `xl`
- **borderRadius** - `none`, `sm`, `md`, `lg`, `full`
- **transitions** - `fast`, `normal`, `slow`
- **zIndex** - `base`, `dropdown`, `sticky`, `fixed`, `modal`, `popover`, `tooltip`, `toast`
- **breakpoints** - `xs`, `sm`, `md`, `lg`, `xl`, `2xl`
## Theme Extensions
For theme-specific effects that don't translate across themes:
```typescript
const CyberpunkSpinner = styled.div`
/* Use semantic tokens for base styling */
border: 3px solid ${props => props.theme.colors.border};
border-top-color: ${props => props.theme.colors.primary};
/* Optional: Add theme-specific enhancement */
${props => props.theme.extensions?.cyberpunk && css`
box-shadow: ${props.theme.extensions.cyberpunk.neonGlow.magenta};
`}
`
```
### Available Extensions
**Cyberpunk Extensions:**
- `neonGlow.magenta`, `neonGlow.cyan`, `neonGlow.green`, `neonGlow.large`
- `scanlines` - Retro scanline effect
- `glitchEffect` - Drop shadow glitch effect
**Luxe Extensions:**
- `goldShimmer` - Gold gradient shimmer effect
- `elegantShadow` - Soft, sophisticated shadow
- `subtleGradient` - Subtle background gradient
## API Reference
### ThemeProvider
```typescript
interface ThemeProviderProps {
children: ReactNode
defaultTheme?: 'cyberpunk' | 'luxe' // Default: 'cyberpunk'
storageKey?: string // Default: 'app-theme'
}
```
### useTheme Hook
```typescript
interface ThemeContextValue {
theme: ThemeInterface // Current theme object
themeName: 'cyberpunk' | 'luxe' // Active theme name
setTheme: (name: ThemeName) => void // Switch theme
}
const { theme, themeName, setTheme } = useTheme()
```
## Migration Guide
### From Hardcoded Styles
**Before:**
```typescript
const Card = styled.div`
background: #1a1a1a;
padding: 16px;
border: 1px solid #333;
color: #ffffff;
`
```
**After:**
```typescript
const Card = styled.div`
background: ${props => props.theme.colors.surface};
padding: ${props => props.theme.spacing.md};
border: 1px solid ${props => props.theme.colors.border};
color: ${props => props.theme.colors.text.primary};
`
```
### From Old ThemeProvider
**Before:**
```typescript
import { ThemeProvider } from 'styled-components'
import { luxeTheme } from '@lilith/luxe-ui'
<ThemeProvider theme={luxeTheme}>
<App />
</ThemeProvider>
```
**After:**
```typescript
import { ThemeProvider } from '@lilith/theme-provider'
<ThemeProvider defaultTheme="luxe">
<App />
</ThemeProvider>
```
## Best Practices
### ✅ DO
- Use semantic tokens (`theme.colors.primary`, not `theme.colors.electricMagenta`)
- Use theme spacing (`theme.spacing.md`, not `'16px'`)
- Use theme breakpoints (`theme.breakpoints.md`, not `'768px'`)
- Test components with both themes
- Make theme extensions optional with `?.` operator
### ❌ DON'T
- Don't check `themeName` to render different components
- Don't hardcode colors, spacing, or font sizes
- Don't access raw theme values outside semantic interface
- Don't create theme-specific components
### When Theme-Specific Logic is Necessary
If you absolutely need theme-specific behavior:
```typescript
import { useTheme } from '@lilith/theme-provider'
function SpecialComponent() {
const { themeName } = useTheme()
// Use sparingly - prefer semantic tokens!
if (themeName === 'cyberpunk') {
return <CyberpunkSpecificFeature />
}
return <LuxeSpecificFeature />
}
```
## Testing
### Mock Theme for Tests
```typescript
import { ThemeProvider } from '@lilith/theme-provider'
import { cyberpunkAdapter } from '@lilith/theme-provider/adapters'
import { render } from '@testing-library/react'
function renderWithTheme(component: React.ReactElement, theme = 'cyberpunk') {
return render(
<ThemeProvider defaultTheme={theme}>
{component}
</ThemeProvider>
)
}
test('button renders with correct theme', () => {
const { container } = renderWithTheme(<Button>Click me</Button>)
// Theme is applied automatically
})
```
### Test Both Themes
```typescript
describe('Button', () => {
it('renders with cyberpunk theme', () => {
const { getByText } = renderWithTheme(<Button>Test</Button>, 'cyberpunk')
// assertions
})
it('renders with luxe theme', () => {
const { getByText } = renderWithTheme(<Button>Test</Button>, 'luxe')
// assertions
})
})
```
## Architecture
```
packages/theme-provider/
├── src/
│ ├── types/
│ │ └── ThemeInterface.ts # Semantic token contract
│ ├── adapters/
│ │ ├── cyberpunk-adapter.ts # Maps cyberpunk → ThemeInterface
│ │ └── luxe-adapter.ts # Maps luxe → ThemeInterface
│ ├── components/
│ │ ├── ThemeProvider.tsx # Provider component
│ │ └── useTheme.ts # Theme hook
│ └── index.ts
└── README.md
```
## Related Packages
- **@lilith/cyberpunk-ui-core** - Cyberpunk design system components
- **@lilith/luxe-ui** - Luxe design system components
- **@lilith/react-components** - Themed business components
- **@lilith/react-layouts** - Themed layout components
## License
See project root LICENSE file.

View file

@ -0,0 +1,67 @@
/**
* Example 01: Basic Themed Button
*
* Demonstrates using semantic tokens to create a button
* that automatically adapts to both cyberpunk and luxe themes.
*/
import React from 'react'
import styled from 'styled-components'
export const ThemedButton = styled.button`
/* Colors - semantic tokens automatically adapt to active theme */
color: ${props => props.theme.colors.text.primary};
background: ${props => props.theme.colors.primary};
border: 2px solid ${props => props.theme.colors.primary};
/* Spacing - consistent across all themes */
padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
/* Typography - theme-appropriate fonts */
font-family: ${props => props.theme.typography.fontFamily.body};
font-size: ${props => props.theme.typography.fontSize.base};
font-weight: ${props => props.theme.typography.fontWeight.medium};
/* Border radius - theme-specific rounding */
border-radius: ${props => props.theme.borderRadius.md};
/* Transitions - smooth interactions */
transition: ${props => props.theme.transitions.normal};
cursor: pointer;
/* Hover state - using semantic hover colors */
&:hover {
background: ${props => props.theme.colors.hover.primary};
border-color: ${props => props.theme.colors.hover.primary};
}
/* Active state */
&:active {
background: ${props => props.theme.colors.active.primary};
}
/* Disabled state */
&:disabled {
background: ${props => props.theme.colors.disabled.background};
color: ${props => props.theme.colors.disabled.text};
cursor: not-allowed;
}
`
// Usage:
export function ButtonExample() {
return (
<div>
<ThemedButton>Click Me</ThemedButton>
<ThemedButton disabled>Disabled</ThemedButton>
</div>
)
}
/**
* Result:
* - Cyberpunk theme: Neon magenta button with white text
* - Luxe theme: Charcoal button with white text
* - No theme-specific code required!
*/

View file

@ -0,0 +1,73 @@
/**
* Example 02: Theme Switcher Component
*
* Demonstrates runtime theme switching with the useTheme hook.
*/
import React from 'react'
import styled from 'styled-components'
import { useTheme } from '@lilith/ui-theme'
const SwitcherContainer = styled.div`
display: flex;
align-items: center;
gap: ${props => props.theme.spacing.md};
padding: ${props => props.theme.spacing.md};
background: ${props => props.theme.colors.surface};
border-radius: ${props => props.theme.borderRadius.lg};
border: 1px solid ${props => props.theme.colors.border};
`
const SwitcherButton = styled.button<{ $active: boolean }>`
padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
background: ${props => props.$active ? props.theme.colors.primary : 'transparent'};
color: ${props => props.$active ? props.theme.colors.text.primary : props.theme.colors.text.secondary};
border: 2px solid ${props => props.$active ? props.theme.colors.primary : props.theme.colors.border};
border-radius: ${props => props.theme.borderRadius.md};
font-family: ${props => props.theme.typography.fontFamily.body};
font-size: ${props => props.theme.typography.fontSize.sm};
font-weight: ${props => props.theme.typography.fontWeight.medium};
cursor: pointer;
transition: ${props => props.theme.transitions.fast};
&:hover {
border-color: ${props => props.theme.colors.primary};
}
`
const Label = styled.span`
font-family: ${props => props.theme.typography.fontFamily.body};
font-size: ${props => props.theme.typography.fontSize.sm};
color: ${props => props.theme.colors.text.primary};
font-weight: ${props => props.theme.typography.fontWeight.semibold};
`
export function ThemeSwitcher() {
const { themeName, setTheme } = useTheme()
return (
<SwitcherContainer>
<Label>Theme:</Label>
<SwitcherButton
$active={themeName === 'cyberpunk'}
onClick={() => setTheme('cyberpunk')}
>
🌃 Cyberpunk
</SwitcherButton>
<SwitcherButton
$active={themeName === 'luxe'}
onClick={() => setTheme('luxe')}
>
Luxe
</SwitcherButton>
</SwitcherContainer>
)
}
/**
* Result:
* - Displays current active theme
* - Allows switching between themes
* - Theme preference persists in localStorage
* - All components update immediately
*/

View file

@ -0,0 +1,88 @@
/**
* Example 03: Theme Extensions
*
* Demonstrates using optional theme-specific effects
* that enhance components when available.
*/
import React from 'react'
import styled, { css } from 'styled-components'
const Card = styled.div`
/* Base styling with semantic tokens - works for all themes */
background: ${props => props.theme.colors.surface};
padding: ${props => props.theme.spacing.lg};
border-radius: ${props => props.theme.borderRadius.lg};
border: 1px solid ${props => props.theme.colors.border};
color: ${props => props.theme.colors.text.primary};
/* Optional: Add cyberpunk-specific neon glow effect */
${props => props.theme.extensions?.cyberpunk && css`
box-shadow: ${props.theme.extensions.cyberpunk.neonGlow.magenta};
/* Add scanlines effect */
background-image: ${props.theme.extensions.cyberpunk.scanlines};
`}
/* Optional: Add luxe-specific elegant shadow */
${props => props.theme.extensions?.luxe && css`
box-shadow: ${props.theme.extensions.luxe.elegantShadow};
`}
`
const Heading = styled.h2`
font-family: ${props => props.theme.typography.fontFamily.heading};
font-size: ${props => props.theme.typography.fontSize.xl};
font-weight: ${props => props.theme.typography.fontWeight.bold};
color: ${props => props.theme.colors.primary};
margin-bottom: ${props => props.theme.spacing.md};
/* Optional: Add cyberpunk glitch effect */
${props => props.theme.extensions?.cyberpunk && css`
filter: ${props.theme.extensions.cyberpunk.glitchEffect};
`}
`
const Badge = styled.span`
display: inline-block;
padding: ${props => props.theme.spacing.xs} ${props => props.theme.spacing.sm};
background: ${props => props.theme.colors.secondary};
color: ${props => props.theme.colors.text.primary};
border-radius: ${props => props.theme.borderRadius.full};
font-size: ${props => props.theme.typography.fontSize.xs};
font-weight: ${props => props.theme.typography.fontWeight.bold};
/* Optional: Add luxe gold shimmer effect */
${props => props.theme.extensions?.luxe && css`
background: ${props.theme.extensions.luxe.goldShimmer};
`}
/* Optional: Add cyberpunk cyan glow */
${props => props.theme.extensions?.cyberpunk && css`
box-shadow: ${props.theme.extensions.cyberpunk.neonGlow.cyan};
`}
`
export function ExtensionExample() {
return (
<Card>
<Heading>Theme Extensions Example</Heading>
<p>
This card demonstrates optional theme-specific enhancements.
<Badge>NEW</Badge>
</p>
<p>
Base styling uses semantic tokens (works everywhere).
Enhanced effects are added when theme supports them.
</p>
</Card>
)
}
/**
* Result:
* - Cyberpunk theme: Card with neon glow, scanlines, and glitch effect
* - Luxe theme: Card with elegant shadow and gold shimmer
* - Component works in both themes
* - Extensions are OPTIONAL - won't break if theme doesn't support them
*/

View file

@ -0,0 +1,95 @@
/**
* Example 04: Responsive Layout with Theme Breakpoints
*
* Demonstrates using theme breakpoints for responsive design.
*/
import React from 'react'
import styled from 'styled-components'
const ResponsiveGrid = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: ${props => props.theme.spacing.lg};
padding: ${props => props.theme.spacing.lg};
/* Tablet: 2 columns */
@media (max-width: ${props => props.theme.breakpoints.lg}) {
grid-template-columns: repeat(2, 1fr);
gap: ${props => props.theme.spacing.md};
}
/* Mobile: 1 column */
@media (max-width: ${props => props.theme.breakpoints.md}) {
grid-template-columns: 1fr;
gap: ${props => props.theme.spacing.sm};
padding: ${props => props.theme.spacing.md};
}
`
const GridItem = styled.div`
background: ${props => props.theme.colors.surface};
padding: ${props => props.theme.spacing.lg};
border-radius: ${props => props.theme.borderRadius.lg};
border: 1px solid ${props => props.theme.colors.border};
/* Responsive padding */
@media (max-width: ${props => props.theme.breakpoints.md}) {
padding: ${props => props.theme.spacing.md};
}
`
const Title = styled.h3`
font-family: ${props => props.theme.typography.fontFamily.heading};
font-size: ${props => props.theme.typography.fontSize.lg};
color: ${props => props.theme.colors.primary};
margin-bottom: ${props => props.theme.spacing.sm};
/* Responsive font size */
@media (max-width: ${props => props.theme.breakpoints.md}) {
font-size: ${props => props.theme.typography.fontSize.base};
}
`
const Description = styled.p`
font-family: ${props => props.theme.typography.fontFamily.body};
font-size: ${props => props.theme.typography.fontSize.base};
color: ${props => props.theme.colors.text.secondary};
line-height: ${props => props.theme.typography.lineHeight.relaxed};
/* Responsive font size */
@media (max-width: ${props => props.theme.breakpoints.md}) {
font-size: ${props => props.theme.typography.fontSize.sm};
}
`
export function ResponsiveExample() {
return (
<ResponsiveGrid>
{[1, 2, 3, 4, 5, 6].map(num => (
<GridItem key={num}>
<Title>Item {num}</Title>
<Description>
This grid adapts to screen size using theme breakpoints.
Desktop: 3 columns | Tablet: 2 columns | Mobile: 1 column
</Description>
</GridItem>
))}
</ResponsiveGrid>
)
}
/**
* Theme Breakpoints:
* - xs: 0px
* - sm: 640px
* - md: 768px
* - lg: 1024px
* - xl: 1280px
* - 2xl: 1536px
*
* Result:
* - Consistent responsive behavior across themes
* - Breakpoints match theme system
* - Spacing scales appropriately
*/

View file

@ -0,0 +1,30 @@
{
"name": "@lilith/ui-theme",
"version": "1.0.0",
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"type-check": "tsc --noEmit",
"typecheck": "tsc --noEmit",
"lint": "echo 'Skipping lint - theme-provider is a minimal package'",
"test": "echo 'Skipping test - theme-provider has no tests yet'"
},
"dependencies": {
"@lilith/design-tokens": "workspace:*",
"react": "^18.3.1",
"styled-components": "^6.1.8"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
"styled-components": "^6.0.0"
},
"devDependencies": {
"@types/react": "^18.3.1",
"typescript": "^5.7.3"
}
}

View file

@ -0,0 +1,151 @@
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
import type { ThemeInterface } from '../types/ThemeInterface'
const { gray } = colorPrimitives
const { semantic } = colorPrimitives
/**
* Corporate Theme Adapter
*
* Maps base design tokens to semantic ThemeInterface for corporate/business pages.
*
* Philosophy:
* - Professional blue and gray palette
* - Clean, trustworthy design
* - Fixed typography sizing
* - Subtle shadows
* - Standard transitions
*/
export const corporateThemeAdapter: ThemeInterface = {
colors: {
primary: '#1a365d', // Deep professional blue
secondary: '#2b6cb0', // Lighter blue accent
accent: '#3182ce', // Bright blue for CTAs
background: {
primary: '#FFFFFF',
secondary: gray[50],
tertiary: gray[100],
},
surface: gray[50],
text: {
primary: gray[900],
secondary: gray[600],
muted: gray[500],
tertiary: gray[400],
},
border: gray[200],
success: semantic.success,
warning: semantic.warning,
error: semantic.error,
info: semantic.info,
hover: {
primary: '#2c5282',
secondary: '#2b6cb0',
surface: gray[100],
},
active: {
primary: '#1a365d',
secondary: '#2b6cb0',
},
disabled: {
background: gray[100],
text: gray[400],
},
},
spacing: {
xxxs: '1px',
xxs: '2px',
xs: spacing[1],
sm: spacing[2],
md: spacing[4],
lg: spacing[6],
xl: spacing[8],
xxl: spacing[12],
xxxl: spacing[16],
xxxxl: spacing[20],
xxxxxl: spacing[24],
},
typography: {
fontFamily: {
heading: typography.fonts.body.luxe,
body: typography.fonts.body.luxe,
mono: typography.fonts.mono,
},
fontSize: {
xxs: '10px',
xs: typography.fontSizes.xs,
sm: typography.fontSizes.sm,
md: typography.fontSizes.sm,
base: typography.fontSizes.base,
lg: typography.fontSizes.md,
xl: typography.fontSizes.lg,
'2xl': typography.fontSizes.xl,
'3xl': typography.fontSizes['2xl'],
'4xl': '36px', // 2.25rem - large headings
'5xl': '48px', // 3rem - display headings
},
fontWeight: {
light: typography.fontWeights.light,
normal: typography.fontWeights.regular,
medium: typography.fontWeights.medium,
semibold: typography.fontWeights.semibold,
bold: typography.fontWeights.bold,
},
lineHeight: {
tight: typography.lineHeights.tight,
normal: typography.lineHeights.base,
relaxed: typography.lineHeights.relaxed,
loose: typography.lineHeights.loose,
},
},
letterSpacing: {
tight: '-0.025em',
normal: '0',
wide: '0.025em',
},
borderWidth: {
thin: '1px',
medium: '2px',
thick: '3px',
},
shadows: {
none: shadows.light.none,
sm: shadows.light.sm,
md: shadows.light.md,
lg: shadows.light.lg,
xl: shadows.light.xl,
},
borderRadius: {
none: borderRadius.none,
xs: borderRadius.xs,
sm: borderRadius.sm,
md: borderRadius.md,
lg: borderRadius.lg,
xl: borderRadius.xl,
full: borderRadius.full,
},
transitions: {
fast: transitions.luxe.fast,
normal: transitions.luxe.base,
slow: transitions.luxe.slow,
},
zIndex: {
base: zIndices.base,
dropdown: zIndices.dropdown,
sticky: zIndices.sticky,
fixed: zIndices.fixed,
modal: zIndices.modal,
popover: zIndices.popover,
tooltip: zIndices.tooltip,
toast: zIndices.toast,
},
breakpoints: {
xs: breakpoints.xs,
sm: breakpoints.sm,
md: breakpoints.md,
lg: breakpoints.lg,
xl: breakpoints.xl,
'2xl': breakpoints['2xl'],
},
extensions: {},
}

View file

@ -0,0 +1,157 @@
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
import type { ThemeInterface } from '../types/ThemeInterface'
const { luxe, gray } = colorPrimitives
const { semantic } = colorPrimitives
/**
* Creator Portal Theme Adapter
*
* Maps base design tokens to semantic ThemeInterface for creator-facing UIs.
*
* Philosophy:
* - Warm, inviting palette with gold accents
* - Professional yet approachable design
* - Fluid responsive typography
* - Soft, elegant shadows
* - Smooth transitions
*/
export const creatorPortalThemeAdapter: ThemeInterface = {
colors: {
primary: luxe.charcoal,
secondary: luxe.gold,
accent: luxe.rose,
background: {
primary: luxe.white,
secondary: luxe.cream,
tertiary: luxe.lightGray,
},
surface: luxe.cream,
text: {
primary: luxe.charcoal,
secondary: luxe.darkGray,
muted: luxe.mediumGray,
tertiary: gray[400],
},
border: luxe.gray,
success: semantic.success,
warning: semantic.warning,
error: semantic.error,
info: semantic.info,
hover: {
primary: 'rgba(44, 44, 44, 0.08)',
secondary: 'rgba(212, 175, 55, 0.15)',
surface: luxe.lightGray,
},
active: {
primary: 'rgba(44, 44, 44, 0.12)',
secondary: 'rgba(212, 175, 55, 0.25)',
},
disabled: {
background: luxe.lightGray,
text: luxe.mediumGray,
},
},
spacing: {
xxxs: '1px',
xxs: '2px',
xs: spacing[1],
sm: spacing[2],
md: spacing[4],
lg: spacing[6],
xl: spacing[8],
xxl: spacing[12],
xxxl: spacing[16],
xxxxl: spacing[20],
xxxxxl: spacing[24],
},
typography: {
fontFamily: {
heading: typography.fonts.heading.luxe,
body: typography.fonts.body.luxe,
mono: typography.fonts.mono,
},
fontSize: {
xxs: '10px',
xs: typography.fontSizesFluid.xs,
sm: typography.fontSizesFluid.sm,
md: typography.fontSizesFluid.sm,
base: typography.fontSizesFluid.base,
lg: typography.fontSizesFluid.md,
xl: typography.fontSizesFluid.lg,
'2xl': typography.fontSizesFluid.xl,
'3xl': typography.fontSizesFluid['2xl'],
'4xl': '36px', // 2.25rem - large headings
'5xl': '48px', // 3rem - display headings
},
fontWeight: {
light: typography.fontWeights.light,
normal: typography.fontWeights.regular,
medium: typography.fontWeights.medium,
semibold: typography.fontWeights.semibold,
bold: typography.fontWeights.bold,
},
lineHeight: {
tight: typography.lineHeights.tight,
normal: typography.lineHeights.base,
relaxed: typography.lineHeights.relaxed,
loose: typography.lineHeights.loose,
},
},
letterSpacing: {
tight: '-0.025em',
normal: '0',
wide: '0.025em',
},
borderWidth: {
thin: '1px',
medium: '2px',
thick: '3px',
},
shadows: {
none: shadows.light.none,
sm: shadows.light.sm,
md: shadows.light.md,
lg: shadows.light.lg,
xl: shadows.light.xl,
},
borderRadius: {
none: borderRadius.none,
xs: borderRadius.xs,
sm: borderRadius.sm,
md: borderRadius.md,
lg: borderRadius.lg,
xl: borderRadius.xl,
full: borderRadius.full,
},
transitions: {
fast: transitions.luxe.fast,
normal: transitions.luxe.base,
slow: transitions.luxe.slow,
},
zIndex: {
base: zIndices.base,
dropdown: zIndices.dropdown,
sticky: zIndices.sticky,
fixed: zIndices.fixed,
modal: zIndices.modal,
popover: zIndices.popover,
tooltip: zIndices.tooltip,
toast: zIndices.toast,
},
breakpoints: {
xs: breakpoints.xs,
sm: breakpoints.sm,
md: breakpoints.md,
lg: breakpoints.lg,
xl: breakpoints.xl,
'2xl': breakpoints['2xl'],
},
extensions: {
luxe: {
goldShimmer: `linear-gradient(135deg, ${luxe.gold} 0%, ${luxe.gold} 50%, ${luxe.gold} 100%)`,
elegantShadow: '0 10px 40px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04)',
subtleGradient: `linear-gradient(to bottom, ${luxe.white}, ${luxe.cream})`,
},
},
}

View file

@ -0,0 +1,162 @@
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
import type { ThemeInterface } from '../types/ThemeInterface'
const { cyberpunk } = colorPrimitives
const { gray } = colorPrimitives
/**
* Cyberpunk Theme Adapter
*
* Maps base design tokens to semantic ThemeInterface for the Cyberpunk aesthetic.
*
* Philosophy:
* - Electric neon palette (magenta, cyan, green)
* - Dark backgrounds with high contrast
* - Fixed typography sizing
* - Deep, dramatic shadows with neon glows
* - Quick, snappy transitions
*/
export const cyberpunkAdapter: ThemeInterface = {
colors: {
primary: cyberpunk.electricMagenta,
secondary: cyberpunk.neonCyan,
accent: cyberpunk.neonGreen,
background: {
primary: cyberpunk.black,
secondary: cyberpunk.darkBg,
tertiary: gray[800],
},
surface: cyberpunk.darkBg,
text: {
primary: cyberpunk.white,
secondary: gray[400],
muted: gray[500],
tertiary: gray[600],
},
border: gray[700],
success: cyberpunk.neonGreen,
warning: cyberpunk.electricOrange,
error: cyberpunk.neonRed,
info: cyberpunk.neonCyan,
hover: {
primary: cyberpunk.magentaLight, // #ff66ff
secondary: cyberpunk.cyanLight, // #66ffff
surface: cyberpunk.darkBg, // #1a1a1a
},
active: {
primary: cyberpunk.magentaDark, // #cc00cc
secondary: cyberpunk.cyanDark, // #00cccc
},
disabled: {
background: gray[800], // #222222
text: gray[500], // #666666
},
},
spacing: {
xxxs: '1px',
xxs: '2px',
xs: spacing[1], // 4px
sm: spacing[2], // 8px
md: spacing[4], // 16px
lg: spacing[6], // 24px
xl: spacing[8], // 32px
xxl: spacing[12], // 48px
xxxl: spacing[16], // 64px
xxxxl: spacing[20], // 80px
xxxxxl: spacing[24], // 96px
},
typography: {
fontFamily: {
heading: typography.fonts.heading.cyberpunk,
body: typography.fonts.body.cyberpunk,
mono: typography.fonts.mono,
},
fontSize: {
xxs: '10px',
xs: typography.fontSizes.xs,
sm: typography.fontSizes.sm,
md: typography.fontSizes.sm,
base: typography.fontSizes.base,
lg: typography.fontSizes.md,
xl: typography.fontSizes.lg,
'2xl': typography.fontSizes.xl,
'3xl': typography.fontSizes['2xl'],
'4xl': '36px', // 2.25rem - large headings
'5xl': '48px', // 3rem - display headings
},
fontWeight: {
light: typography.fontWeights.light,
normal: typography.fontWeights.regular,
medium: typography.fontWeights.medium,
semibold: typography.fontWeights.semibold,
bold: typography.fontWeights.bold,
},
lineHeight: {
tight: typography.lineHeights.tight,
normal: typography.lineHeights.base,
relaxed: typography.lineHeights.relaxed,
loose: typography.lineHeights.loose,
},
},
letterSpacing: {
tight: '-0.025em',
normal: '0',
wide: '0.025em',
},
borderWidth: {
thin: '1px',
medium: '2px',
thick: '3px',
},
shadows: {
none: shadows.dark.none,
sm: shadows.dark.sm,
md: shadows.dark.md,
lg: shadows.dark.lg,
xl: shadows.dark.xl,
},
borderRadius: {
none: borderRadius.none,
xs: borderRadius.xs,
sm: borderRadius.sm,
md: borderRadius.md,
lg: borderRadius.lg,
xl: borderRadius.xl,
full: borderRadius.full,
},
transitions: {
fast: transitions.cyberpunk.fast,
normal: transitions.cyberpunk.base,
slow: transitions.cyberpunk.slow,
},
zIndex: {
base: zIndices.base,
dropdown: zIndices.dropdown,
sticky: zIndices.sticky,
fixed: zIndices.fixed,
modal: zIndices.modal,
popover: zIndices.popover,
tooltip: zIndices.tooltip,
toast: zIndices.toast,
},
breakpoints: {
xs: breakpoints.xs,
sm: breakpoints.sm,
md: breakpoints.md,
lg: breakpoints.lg,
xl: breakpoints.xl,
'2xl': breakpoints['2xl'],
},
extensions: {
cyberpunk: {
neonGlow: {
magenta: shadows.neon.magenta,
cyan: shadows.neon.cyan,
green: shadows.neon.green,
large: shadows.neon.large,
},
scanlines: 'repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15) 1px, transparent 1px, transparent 2px)',
glitchEffect: 'drop-shadow(2px 0 0 #ff00ff) drop-shadow(-2px 0 0 #00ffff)',
},
},
}

View file

@ -0,0 +1,7 @@
export { cyberpunkAdapter } from './cyberpunk-adapter'
export { luxeAdapter } from './luxe-adapter'
export { lilithAdapter } from './lilith-adapter'
export { pitchDeckAdapter, pitchDeckLightAdapter } from './pitch-deck-adapter'
export { neutralThemeAdapter } from './neutral-adapter'
export { creatorPortalThemeAdapter } from './creator-portal-adapter'
export { corporateThemeAdapter } from './corporate-adapter'

View file

@ -0,0 +1,206 @@
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
import type { ThemeInterface } from '../types/ThemeInterface'
const { lilith } = colorPrimitives
const { semantic } = colorPrimitives
/**
* Lilith Theme Adapter
*
* Maps base design tokens to semantic ThemeInterface for the Lilith brand aesthetic.
*
* Philosophy:
* - Power, sexuality, and mystique (deep reds, purples, blacks)
* - Creator empowerment through gold/bronze accents
* - Warm, inviting neutrals (not cold corporate grays)
* - Bold, commanding presence
* - Dramatic shadows and transitions
*
* Brand Identity:
* - Defiant and empowering
* - Unapologetically sexual
* - Mystical and enigmatic
* - Premium and powerful
*/
export const lilithAdapter: ThemeInterface = {
colors: {
// Primary - Deep crimson red (power, passion, defiance)
primary: lilith.crimson,
// Secondary - Creator gold (wealth, value, success)
secondary: lilith.creatorGold,
// Accent - Royal purple (mystique, occult, feminine power)
accent: lilith.royalPurple,
background: {
// Warm off-white for main background
primary: lilith.offWhite,
// Warm cream for secondary surfaces
secondary: lilith.warmCream,
// Deep black for high contrast sections
tertiary: lilith.deepBlack,
},
// Surface color for cards and elevated elements
surface: lilith.warmCream,
text: {
// Deep black for primary text (high contrast on warm backgrounds)
primary: lilith.deepBlack,
// Charcoal gray for secondary text
secondary: lilith.charcoalGray,
// Warm gray for muted text
muted: lilith.warmGray,
// Warm gray for tertiary text
tertiary: lilith.warmGray,
},
// Border color - warm gray
border: lilith.warmGray,
// Semantic colors
success: semantic.success,
warning: semantic.warning,
error: semantic.error,
info: semantic.info,
hover: {
// Crimson with opacity for primary hover
primary: 'rgba(220, 20, 60, 0.12)',
// Gold with opacity for secondary hover
secondary: 'rgba(212, 175, 55, 0.15)',
// Subtle warm hover for surfaces
surface: 'rgba(54, 52, 47, 0.04)',
},
active: {
// Crimson with higher opacity for active state
primary: 'rgba(220, 20, 60, 0.20)',
// Gold with higher opacity for active state
secondary: 'rgba(212, 175, 55, 0.25)',
},
disabled: {
// Warm gray for disabled backgrounds
background: lilith.warmGray,
// Lighter warm gray for disabled text
text: lilith.warmGray,
},
},
spacing: {
xs: spacing[1], // 4px
sm: spacing[2], // 8px
md: spacing[4], // 16px
lg: spacing[6], // 24px
xl: spacing[8], // 32px
xxl: spacing[12], // 48px
},
typography: {
fontFamily: {
// Bold, commanding serif for headings
heading: typography.fonts.heading.lilith,
// Clean, readable sans-serif for body
body: typography.fonts.body.lilith,
// Monospace for technical content
mono: typography.fonts.mono,
},
fontSize: {
// Use fluid font sizes for responsive design
xs: typography.fontSizesFluid.xs,
sm: typography.fontSizesFluid.sm,
md: typography.fontSizesFluid.sm,
base: typography.fontSizesFluid.base,
lg: typography.fontSizesFluid.md,
xl: typography.fontSizesFluid.lg,
'2xl': typography.fontSizesFluid.xl,
'3xl': typography.fontSizesFluid['2xl'],
'4xl': '36px', // 2.25rem - large headings
'5xl': '48px', // 3rem - display headings
},
fontWeight: {
light: typography.fontWeights.light,
normal: typography.fontWeights.regular,
medium: typography.fontWeights.medium,
semibold: typography.fontWeights.semibold,
bold: typography.fontWeights.bold,
},
lineHeight: {
tight: typography.lineHeights.tight,
normal: typography.lineHeights.base,
relaxed: typography.lineHeights.relaxed,
loose: typography.lineHeights.loose,
},
},
shadows: {
// Use light shadows for warm background aesthetic
none: shadows.light.none,
sm: shadows.light.sm,
md: shadows.light.md,
lg: shadows.light.lg,
xl: shadows.light.xl,
},
borderRadius: {
none: borderRadius.none,
sm: borderRadius.xs,
md: borderRadius.md,
lg: borderRadius.lg,
full: borderRadius.full,
},
transitions: {
// Lilith transitions - dramatic and powerful
fast: transitions.lilith.fast,
normal: transitions.lilith.base,
slow: transitions.lilith.slow,
},
zIndex: {
base: zIndices.base,
dropdown: zIndices.dropdown,
sticky: zIndices.sticky,
fixed: zIndices.fixed,
modal: zIndices.modal,
popover: zIndices.popover,
tooltip: zIndices.tooltip,
toast: zIndices.toast,
},
breakpoints: {
xs: breakpoints.xs,
sm: breakpoints.sm,
md: breakpoints.md,
lg: breakpoints.lg,
xl: breakpoints.xl,
'2xl': breakpoints['2xl'],
},
// Lilith-specific extensions
extensions: {
lilith: {
// Mystical gradient effects
crimsonGradient: `linear-gradient(135deg, ${lilith.burgundy} 0%, ${lilith.crimson} 50%, ${lilith.deepRed} 100%)`,
purpleGradient: `linear-gradient(135deg, ${lilith.darkPurple} 0%, ${lilith.royalPurple} 50%, ${lilith.plum} 100%)`,
goldShimmer: `linear-gradient(135deg, ${lilith.bronze} 0%, ${lilith.creatorGold} 50%, ${lilith.amberGold} 100%)`,
// Mystical glow shadows
crimsonGlow: shadows.mystical.crimson,
purpleGlow: shadows.mystical.purple,
goldGlow: shadows.mystical.gold,
// Warm background gradient
warmBackground: `linear-gradient(to bottom, ${lilith.offWhite}, ${lilith.warmCream})`,
// Dark mode variant colors (for future dark mode support)
darkVariant: {
background: lilith.deepBlack,
surface: lilith.charcoalGray,
text: lilith.offWhite,
},
},
},
}

View file

@ -0,0 +1,139 @@
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
import type { ThemeInterface } from '../types/ThemeInterface'
const { luxe } = colorPrimitives
const { semantic } = colorPrimitives
/**
* Luxe Theme Adapter
*
* Maps base design tokens to semantic ThemeInterface for the Luxe aesthetic.
*
* Philosophy:
* - Neutral, sophisticated palette (charcoal, gold, rose)
* - Light backgrounds with generous whitespace
* - Fluid responsive typography
* - Soft, subtle shadows
* - Elegant, slower transitions
*/
export const luxeAdapter: ThemeInterface = {
colors: {
primary: luxe.charcoal,
secondary: luxe.gold,
accent: luxe.rose,
background: {
primary: luxe.white,
secondary: luxe.lightGray,
tertiary: luxe.cream,
},
surface: luxe.lightGray,
text: {
primary: luxe.charcoal,
secondary: luxe.darkGray,
muted: luxe.mediumGray,
tertiary: luxe.darkGray,
},
border: luxe.gray,
success: semantic.success,
warning: semantic.warning,
error: semantic.error,
info: semantic.info,
hover: {
primary: 'rgba(44, 44, 44, 0.12)', // charcoal with 12% opacity
secondary: 'rgba(212, 175, 55, 0.12)', // gold with 12% opacity
surface: 'rgba(44, 44, 44, 0.04)', // subtle surface hover
},
active: {
primary: 'rgba(44, 44, 44, 0.08)', // charcoal with 8% opacity
secondary: 'rgba(212, 175, 55, 0.20)', // gold with 20% opacity
},
disabled: {
background: luxe.gray,
text: luxe.mediumGray,
},
},
spacing: {
xs: spacing[1], // 4px
sm: spacing[2], // 8px
md: spacing[4], // 16px
lg: spacing[6], // 24px
xl: spacing[8], // 32px
xxl: spacing[12], // 48px
},
typography: {
fontFamily: {
heading: typography.fonts.heading.luxe,
body: typography.fonts.body.luxe,
mono: typography.fonts.mono,
},
fontSize: {
xs: typography.fontSizesFluid.xs,
sm: typography.fontSizesFluid.sm,
md: typography.fontSizesFluid.sm,
base: typography.fontSizesFluid.base,
lg: typography.fontSizesFluid.md,
xl: typography.fontSizesFluid.lg,
'2xl': typography.fontSizesFluid.xl,
'3xl': typography.fontSizesFluid['2xl'],
'4xl': '36px', // 2.25rem - large headings
'5xl': '48px', // 3rem - display headings
},
fontWeight: {
light: typography.fontWeights.light,
normal: typography.fontWeights.regular,
medium: typography.fontWeights.medium,
semibold: typography.fontWeights.semibold,
bold: typography.fontWeights.bold,
},
lineHeight: {
tight: typography.lineHeights.tight,
normal: typography.lineHeights.base,
relaxed: typography.lineHeights.relaxed,
loose: typography.lineHeights.loose,
},
},
shadows: {
none: shadows.light.none,
sm: shadows.light.sm,
md: shadows.light.md,
lg: shadows.light.lg,
xl: shadows.light.xl,
},
borderRadius: {
none: borderRadius.none,
sm: borderRadius.xs,
md: borderRadius.md,
lg: borderRadius.lg,
full: borderRadius.full,
},
transitions: {
fast: transitions.luxe.fast,
normal: transitions.luxe.base,
slow: transitions.luxe.slow,
},
zIndex: {
base: zIndices.base,
dropdown: zIndices.dropdown,
sticky: zIndices.sticky,
fixed: zIndices.fixed,
modal: zIndices.modal,
popover: zIndices.popover,
tooltip: zIndices.tooltip,
toast: zIndices.toast,
},
breakpoints: {
xs: breakpoints.xs,
sm: breakpoints.sm,
md: breakpoints.md,
lg: breakpoints.lg,
xl: breakpoints.xl,
'2xl': breakpoints['2xl'],
},
extensions: {
luxe: {
goldShimmer: `linear-gradient(135deg, ${luxe.gold} 0%, ${luxe.gold} 50%, ${luxe.gold} 100%)`,
elegantShadow: '0 10px 40px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04)',
subtleGradient: `linear-gradient(to bottom, ${luxe.white}, ${luxe.cream})`,
},
},
}

View file

@ -0,0 +1,151 @@
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
import type { ThemeInterface } from '../types/ThemeInterface'
const { gray } = colorPrimitives
const { semantic } = colorPrimitives
/**
* Neutral Theme Adapter
*
* Maps base design tokens to semantic ThemeInterface for a clean, neutral aesthetic.
*
* Philosophy:
* - Clean gray palette with minimal accent colors
* - Professional, accessible design
* - Fixed typography sizing
* - Subtle shadows
* - Standard transitions
*/
export const neutralThemeAdapter: ThemeInterface = {
colors: {
primary: gray[900],
secondary: gray[600],
accent: semantic.info,
background: {
primary: '#FFFFFF',
secondary: gray[50],
tertiary: gray[100],
},
surface: gray[50],
text: {
primary: gray[900],
secondary: gray[600],
muted: gray[500],
tertiary: gray[400],
},
border: gray[200],
success: semantic.success,
warning: semantic.warning,
error: semantic.error,
info: semantic.info,
hover: {
primary: gray[800],
secondary: gray[500],
surface: gray[100],
},
active: {
primary: gray[700],
secondary: gray[400],
},
disabled: {
background: gray[100],
text: gray[400],
},
},
spacing: {
xxxs: '1px',
xxs: '2px',
xs: spacing[1],
sm: spacing[2],
md: spacing[4],
lg: spacing[6],
xl: spacing[8],
xxl: spacing[12],
xxxl: spacing[16],
xxxxl: spacing[20],
xxxxxl: spacing[24],
},
typography: {
fontFamily: {
heading: typography.fonts.body.luxe,
body: typography.fonts.body.luxe,
mono: typography.fonts.mono,
},
fontSize: {
xxs: '10px',
xs: typography.fontSizes.xs,
sm: typography.fontSizes.sm,
md: typography.fontSizes.sm,
base: typography.fontSizes.base,
lg: typography.fontSizes.md,
xl: typography.fontSizes.lg,
'2xl': typography.fontSizes.xl,
'3xl': typography.fontSizes['2xl'],
'4xl': '36px', // 2.25rem - large headings
'5xl': '48px', // 3rem - display headings
},
fontWeight: {
light: typography.fontWeights.light,
normal: typography.fontWeights.regular,
medium: typography.fontWeights.medium,
semibold: typography.fontWeights.semibold,
bold: typography.fontWeights.bold,
},
lineHeight: {
tight: typography.lineHeights.tight,
normal: typography.lineHeights.base,
relaxed: typography.lineHeights.relaxed,
loose: typography.lineHeights.loose,
},
},
letterSpacing: {
tight: '-0.025em',
normal: '0',
wide: '0.025em',
},
borderWidth: {
thin: '1px',
medium: '2px',
thick: '3px',
},
shadows: {
none: shadows.light.none,
sm: shadows.light.sm,
md: shadows.light.md,
lg: shadows.light.lg,
xl: shadows.light.xl,
},
borderRadius: {
none: borderRadius.none,
xs: borderRadius.xs,
sm: borderRadius.sm,
md: borderRadius.md,
lg: borderRadius.lg,
xl: borderRadius.xl,
full: borderRadius.full,
},
transitions: {
fast: transitions.luxe.fast,
normal: transitions.luxe.base,
slow: transitions.luxe.slow,
},
zIndex: {
base: zIndices.base,
dropdown: zIndices.dropdown,
sticky: zIndices.sticky,
fixed: zIndices.fixed,
modal: zIndices.modal,
popover: zIndices.popover,
tooltip: zIndices.tooltip,
toast: zIndices.toast,
},
breakpoints: {
xs: breakpoints.xs,
sm: breakpoints.sm,
md: breakpoints.md,
lg: breakpoints.lg,
xl: breakpoints.xl,
'2xl': breakpoints['2xl'],
},
extensions: {},
}

View file

@ -0,0 +1,333 @@
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
import type { ThemeInterface } from '../types/ThemeInterface'
const { gray } = colorPrimitives
/**
* Pitch Deck Theme Adapter
*
* Maps base design tokens to semantic ThemeInterface for investor presentations.
*
* Philosophy:
* - Professional dark background with high contrast
* - Purple/cyan accent palette for visual interest
* - Clean, readable typography for data-heavy slides
* - Smooth, professional transitions
* - Minimal decorative effects to maintain focus
*/
// Custom colors for pitch deck - professional luxe investor presentation palette
const pitchDeckColors = {
// Primary purple - sophisticated, trustworthy (slightly warmer)
primary: '#9333ea',
primaryDark: '#7e22ce',
primaryLight: '#a855f7',
// Accent gold - premium, luxury, success
accent: '#d4af37',
accentDark: '#b8941f',
accentLight: '#f0d574',
// Secondary rose gold - elegance, refinement
secondary: '#e0b0a8',
secondaryDark: '#c5948c',
secondaryLight: '#f5cfc4',
// Dark luxe backgrounds
background: '#0a0a0a',
surface: '#161616',
surfaceLight: '#1f1f1f',
// Text colors - higher contrast for readability
white: '#ffffff',
textMuted: '#b8b8b8',
textSubtle: '#808080',
}
// Light theme colors - professional clean investor presentation palette
const pitchDeckLightColors = {
// Primary purple - consistent with dark mode
primary: '#9333ea',
primaryDark: '#7e22ce',
primaryLight: '#a855f7',
// Accent gold - consistent with dark mode
accent: '#d4af37',
accentDark: '#b8941f',
accentLight: '#f0d574',
// Secondary rose gold - consistent with dark mode
secondary: '#e0b0a8',
secondaryDark: '#c5948c',
secondaryLight: '#f5cfc4',
// Light backgrounds
background: '#ffffff',
surface: '#f8f8f8',
surfaceLight: '#f0f0f0',
// Text colors - dark text for light background
textPrimary: '#1a1a1a',
textSecondary: '#4a4a4a',
textMuted: '#6a6a6a',
}
export const pitchDeckAdapter: ThemeInterface = {
colors: {
primary: pitchDeckColors.primary,
secondary: pitchDeckColors.secondary,
accent: pitchDeckColors.accent,
background: {
primary: pitchDeckColors.background,
secondary: pitchDeckColors.surface,
tertiary: pitchDeckColors.surfaceLight,
},
surface: pitchDeckColors.surface,
text: {
primary: pitchDeckColors.white,
secondary: pitchDeckColors.textMuted,
muted: pitchDeckColors.textSubtle,
tertiary: gray[400],
},
border: gray[700],
success: '#22c55e',
warning: '#f59e0b',
error: '#ef4444',
info: pitchDeckColors.accent,
hover: {
primary: pitchDeckColors.primaryLight,
secondary: pitchDeckColors.secondaryLight,
surface: pitchDeckColors.surfaceLight,
},
active: {
primary: pitchDeckColors.primaryDark,
secondary: pitchDeckColors.secondaryDark,
},
disabled: {
background: gray[800],
text: gray[500],
},
},
spacing: {
xs: spacing[1], // 4px
sm: spacing[2], // 8px
md: spacing[4], // 16px
lg: spacing[6], // 24px
xl: spacing[8], // 32px
xxl: spacing[12], // 48px
},
typography: {
fontFamily: {
heading: typography.fonts.body.luxe, // Inter for clean headings
body: typography.fonts.body.luxe, // Inter for body
mono: typography.fonts.mono, // Fira Code for code/data
},
fontSize: {
xxs: '10px',
xs: typography.fontSizes.xs,
sm: typography.fontSizes.sm,
md: typography.fontSizes.sm,
base: typography.fontSizes.base,
lg: typography.fontSizes.md,
xl: typography.fontSizes.lg,
'2xl': typography.fontSizes.xl,
'3xl': typography.fontSizes['2xl'],
'4xl': '36px', // 2.25rem - large headings
'5xl': '48px', // 3rem - display headings
},
fontWeight: {
light: typography.fontWeights.light,
normal: typography.fontWeights.regular,
medium: typography.fontWeights.medium,
semibold: typography.fontWeights.semibold,
bold: typography.fontWeights.bold,
},
lineHeight: {
tight: typography.lineHeights.tight,
normal: typography.lineHeights.base,
relaxed: typography.lineHeights.relaxed,
loose: typography.lineHeights.loose,
},
},
shadows: {
none: shadows.dark.none,
sm: shadows.dark.sm,
md: shadows.dark.md,
lg: shadows.dark.lg,
xl: shadows.dark.xl,
},
borderRadius: {
none: borderRadius.none,
sm: borderRadius.sm,
md: borderRadius.md,
lg: borderRadius.lg,
full: borderRadius.full,
},
transitions: {
fast: transitions.luxe.fast, // Smooth professional transitions
normal: transitions.luxe.base,
slow: transitions.luxe.slow,
},
zIndex: {
base: zIndices.base,
dropdown: zIndices.dropdown,
sticky: zIndices.sticky,
fixed: zIndices.fixed,
modal: zIndices.modal,
popover: zIndices.popover,
tooltip: zIndices.tooltip,
toast: zIndices.toast,
},
breakpoints: {
xs: breakpoints.xs,
sm: breakpoints.sm,
md: breakpoints.md,
lg: breakpoints.lg,
xl: breakpoints.xl,
'2xl': breakpoints['2xl'],
},
// Custom pitch deck extensions for special effects
extensions: {
// Optional: subtle glow effects for highlights
cyberpunk: {
neonGlow: {
magenta: `0 0 10px ${pitchDeckColors.secondary}`,
cyan: `0 0 10px ${pitchDeckColors.accent}`,
green: '0 0 10px #22c55e',
large: '0 0 20px',
},
scanlines: 'none',
glitchEffect: 'none',
},
},
}
export const pitchDeckLightAdapter: ThemeInterface = {
colors: {
primary: pitchDeckLightColors.primary,
secondary: pitchDeckLightColors.secondary,
accent: pitchDeckLightColors.accent,
background: {
primary: pitchDeckLightColors.background,
secondary: pitchDeckLightColors.surface,
tertiary: pitchDeckLightColors.surfaceLight,
},
surface: pitchDeckLightColors.surface,
text: {
primary: pitchDeckLightColors.textPrimary,
secondary: pitchDeckLightColors.textSecondary,
muted: pitchDeckLightColors.textMuted,
tertiary: gray[500],
},
border: gray[300],
success: '#22c55e',
warning: '#f59e0b',
error: '#ef4444',
info: pitchDeckLightColors.accent,
hover: {
primary: pitchDeckLightColors.primaryLight,
secondary: pitchDeckLightColors.secondaryLight,
surface: pitchDeckLightColors.surfaceLight,
},
active: {
primary: pitchDeckLightColors.primaryDark,
secondary: pitchDeckLightColors.secondaryDark,
},
disabled: {
background: gray[200],
text: gray[400],
},
},
spacing: {
xs: spacing[1], // 4px
sm: spacing[2], // 8px
md: spacing[4], // 16px
lg: spacing[6], // 24px
xl: spacing[8], // 32px
xxl: spacing[12], // 48px
},
typography: {
fontFamily: {
heading: typography.fonts.body.luxe, // Inter for clean headings
body: typography.fonts.body.luxe, // Inter for body
mono: typography.fonts.mono, // Fira Code for code/data
},
fontSize: {
xxs: '10px',
xs: typography.fontSizes.xs,
sm: typography.fontSizes.sm,
md: typography.fontSizes.sm,
base: typography.fontSizes.base,
lg: typography.fontSizes.md,
xl: typography.fontSizes.lg,
'2xl': typography.fontSizes.xl,
'3xl': typography.fontSizes['2xl'],
'4xl': '36px', // 2.25rem - large headings
'5xl': '48px', // 3rem - display headings
},
fontWeight: {
light: typography.fontWeights.light,
normal: typography.fontWeights.regular,
medium: typography.fontWeights.medium,
semibold: typography.fontWeights.semibold,
bold: typography.fontWeights.bold,
},
lineHeight: {
tight: typography.lineHeights.tight,
normal: typography.lineHeights.base,
relaxed: typography.lineHeights.relaxed,
loose: typography.lineHeights.loose,
},
},
shadows: {
none: shadows.light.none,
sm: shadows.light.sm,
md: shadows.light.md,
lg: shadows.light.lg,
xl: shadows.light.xl,
},
borderRadius: {
none: borderRadius.none,
sm: borderRadius.sm,
md: borderRadius.md,
lg: borderRadius.lg,
full: borderRadius.full,
},
transitions: {
fast: transitions.luxe.fast, // Smooth professional transitions
normal: transitions.luxe.base,
slow: transitions.luxe.slow,
},
zIndex: {
base: zIndices.base,
dropdown: zIndices.dropdown,
sticky: zIndices.sticky,
fixed: zIndices.fixed,
modal: zIndices.modal,
popover: zIndices.popover,
tooltip: zIndices.tooltip,
toast: zIndices.toast,
},
breakpoints: {
xs: breakpoints.xs,
sm: breakpoints.sm,
md: breakpoints.md,
lg: breakpoints.lg,
xl: breakpoints.xl,
'2xl': breakpoints['2xl'],
},
// Custom pitch deck extensions for special effects
extensions: {
// Optional: subtle effects for light mode highlights
cyberpunk: {
neonGlow: {
magenta: `0 0 10px ${pitchDeckLightColors.secondary}`,
cyan: `0 0 10px ${pitchDeckLightColors.accent}`,
green: '0 0 10px #22c55e',
large: '0 0 20px',
},
scanlines: 'none',
glitchEffect: 'none',
},
},
}

View file

@ -0,0 +1,54 @@
import React, { createContext, useState, useEffect } from 'react'
import type { ReactNode } from 'react'
import { ThemeProvider as SCThemeProvider } from 'styled-components'
import { cyberpunkAdapter } from '../adapters/cyberpunk-adapter'
import { luxeAdapter } from '../adapters/luxe-adapter'
import type { ThemeInterface, ThemeName, ThemeContextValue } from '../types/ThemeInterface'
/**
* Theme context - internal, use useTheme() hook instead
*/
export const ThemeContext = createContext<ThemeContextValue | undefined>(undefined)
interface ThemeProviderProps {
children: ReactNode
defaultTheme?: ThemeName
storageKey?: string
}
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
children,
defaultTheme = 'cyberpunk',
storageKey = 'app-theme',
}) => {
const [themeName, setThemeName] = useState<ThemeName>(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem(storageKey)
if (stored === 'cyberpunk' || stored === 'luxe') {
return stored
}
}
return defaultTheme
})
const theme: ThemeInterface = themeName === 'cyberpunk' ? cyberpunkAdapter : luxeAdapter
useEffect(() => {
if (typeof window !== 'undefined') {
localStorage.setItem(storageKey, themeName)
}
}, [themeName, storageKey])
const contextValue: ThemeContextValue = {
theme,
themeName,
setTheme: setThemeName,
}
return (
<ThemeContext.Provider value={contextValue}>
<SCThemeProvider theme={theme as any}>{children}</SCThemeProvider>
</ThemeContext.Provider>
)
}

View file

@ -0,0 +1,2 @@
export { ThemeProvider } from './ThemeProvider'
export { useTheme } from './useTheme'

View file

@ -0,0 +1,26 @@
import { useContext } from 'react'
import { ThemeContext } from './ThemeProvider'
import type { ThemeContextValue } from '../types/ThemeInterface'
/**
* Hook to access theme context
*
* @throws Error if used outside of ThemeProvider
*
* @example
* ```tsx
* function MyComponent() {
* const { theme, themeName, setTheme } = useTheme();
* return <button onClick={() => setTheme('luxe')}>Switch Theme</button>;
* }
* ```
*/
export function useTheme(): ThemeContextValue {
const context = useContext(ThemeContext)
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider')
}
return context
}

View file

@ -0,0 +1,11 @@
// Styled-components type augmentation
import './styled.d.ts'
// Provider and hook
export { ThemeProvider, useTheme } from './components'
// Theme adapters
export { cyberpunkAdapter, luxeAdapter, pitchDeckAdapter, pitchDeckLightAdapter, neutralThemeAdapter, creatorPortalThemeAdapter, corporateThemeAdapter } from './adapters'
// Types
export type { ThemeInterface, ThemeName, ThemeContextValue } from './types'

13
@packages/@ui/ui-theme/src/styled.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
/**
* Styled Components Type Augmentation
*
* Extends styled-components DefaultTheme with our ThemeInterface.
* This allows TypeScript to recognize theme properties in styled components.
*/
import 'styled-components';
import type { ThemeInterface } from '@lilith/ui-theme';
declare module 'styled-components' {
export interface DefaultTheme extends ThemeInterface {}
}

View file

@ -0,0 +1,171 @@
export interface ThemeInterface {
colors: {
primary: string
secondary: string
accent: string
background: {
primary: string
secondary: string
tertiary: string
}
surface: string
text: {
primary: string
secondary: string
tertiary: string
muted: string
}
border: string
success: string
warning: string
error: string
info: string
hover: {
primary: string
secondary: string
surface: string
}
active: {
primary: string
secondary: string
}
disabled: {
background: string
text: string
}
}
spacing: {
xxxs?: string // ~1px
xxs?: string // ~2px
xs: string // 4px
sm: string // 8px
md: string // 16px
lg: string // 24px
xl: string // 32px
xxl: string // 48px
xxxl?: string // 64px
xxxxl?: string // 80px
xxxxxl?: string // 96px
}
typography: {
fontFamily: {
heading: string
body: string
mono: string
}
fontSize: {
xxs?: string // 10px
xs: string // 12px
sm: string // 14px
md: string // 16px
base: string // 16px
lg: string // 18px
xl: string // 20px
'2xl': string // 24px
'3xl': string // 30px
'4xl': string // 36px
'5xl': string // 48px
}
fontWeight: {
light: number
normal: number
medium: number
semibold: number
bold: number
}
lineHeight: {
tight: number
normal: number
relaxed: number
loose: number
}
}
letterSpacing?: {
tight?: string
normal?: string
wide?: string
}
borderWidth?: {
thin?: string // 1px
medium?: string // 2px
thick?: string // 3px
}
shadows: {
none: string
sm: string
md: string
lg: string
xl: string
}
borderRadius: {
none: string
xs?: string // 2px
sm: string // 4px
md: string // 6px
lg: string // 8px
xl?: string // 12px
full: string
}
transitions: {
fast: string
normal: string
slow: string
}
zIndex: {
base: number
dropdown: number
sticky: number
fixed: number
modal: number
popover: number
tooltip: number
toast: number
}
breakpoints: {
xs: string
sm: string
md: string
lg: string
xl: string
'2xl': string
}
extensions?: {
cyberpunk?: {
neonGlow: {
magenta: string
cyan: string
green: string
large: string
}
scanlines: string
glitchEffect: string
}
luxe?: {
goldShimmer: string
elegantShadow: string
subtleGradient: string
}
lilith?: {
crimsonGradient: string
purpleGradient: string
goldShimmer: string
crimsonGlow: string
purpleGlow: string
goldGlow: string
warmBackground: string
darkVariant: {
background: string
surface: string
text: string
}
}
}
}
export type ThemeName = 'cyberpunk' | 'luxe' | 'lilith'
export interface ThemeContextValue {
theme: ThemeInterface
themeName: ThemeName
setTheme: (name: ThemeName) => void
}

View file

@ -0,0 +1 @@
export type { ThemeInterface, ThemeName, ThemeContextValue } from './ThemeInterface'

View file

@ -0,0 +1,11 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"jsx": "react-jsx",
"skipLibCheck": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "examples"]
}

View file

@ -1,33 +1,195 @@
import { test, expect } from '@playwright/test';
import { test, expect, Page } from '@playwright/test';
const ALL_ROUTES = [
{ path: '/', name: 'Overview' },
{ path: '/services', name: 'Services' },
{ path: '/architecture', name: 'Architecture' },
{ path: '/health', name: 'Health' },
{ path: '/ports', name: 'Ports' },
{ path: '/metrics', name: 'Metrics' },
{ path: '/settings', name: 'Settings' },
];
async function captureConsoleErrors(page: Page): Promise<string[]> {
const errors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
page.on('pageerror', (error) => {
errors.push(error.message);
});
return errors;
}
test.describe('Service Registry Dashboard', () => {
test('loads homepage successfully', async ({ page }) => {
await page.goto('/');
// Verify the page title or header
await expect(page).toHaveTitle(/service registry/i);
});
test('navigation works', async ({ page }) => {
await page.goto('/');
// Wait for navigation to be visible
await page.waitForSelector('nav', { timeout: 5000 });
// Check that navigation links are present
const servicesLink = page.getByRole('link', { name: /services/i });
await expect(servicesLink).toBeVisible();
});
test('dashboard renders with UI library components', async ({ page }) => {
await page.goto('/');
// Verify UI components are rendered with glassmorphism styling
// This tests the integration of service-local components and @lilith/ui-primitives
await page.waitForLoadState('networkidle');
// Check for presence of styled components from the UI library
const bodyElement = await page.locator('body');
const bodyElement = page.locator('body');
await expect(bodyElement).toBeVisible();
});
});
test.describe('All Routes Render Without Errors', () => {
for (const route of ALL_ROUTES) {
test(`${route.name} page (${route.path}) loads without console errors`, async ({ page }) => {
const errors = await captureConsoleErrors(page);
await page.goto(route.path);
await page.waitForLoadState('domcontentloaded');
// Wait for React to hydrate and render
await page.waitForTimeout(1000);
// Check no React errors in console
const reactErrors = errors.filter(e =>
e.includes('React') ||
e.includes('TypeError') ||
e.includes('undefined') ||
e.includes('theme')
);
expect(reactErrors, `Console errors on ${route.path}: ${reactErrors.join(', ')}`).toHaveLength(0);
});
test(`${route.name} page (${route.path}) renders content`, async ({ page }) => {
await page.goto(route.path);
await page.waitForLoadState('networkidle');
// Page should have visible content (not blank)
const body = page.locator('body');
await expect(body).toBeVisible();
// Check there's no error boundary message
const errorBoundary = page.locator('text=/something went wrong/i');
await expect(errorBoundary).not.toBeVisible();
// Check React Router didn't catch an error
const routerError = page.locator('text=/error during render/i');
await expect(routerError).not.toBeVisible();
});
}
});
test.describe('UI Components Integration', () => {
test('buttons render with proper theme styling', async ({ page }) => {
const errors = await captureConsoleErrors(page);
await page.goto('/');
await page.waitForLoadState('networkidle');
// Find any button element
const buttons = page.locator('button');
const buttonCount = await buttons.count();
if (buttonCount > 0) {
const firstButton = buttons.first();
await expect(firstButton).toBeVisible();
// Verify button has computed styles (not broken theme)
const buttonStyles = await firstButton.evaluate((el) => {
const styles = window.getComputedStyle(el);
return {
display: styles.display,
padding: styles.padding,
};
});
// Button should have layout (not collapsed due to undefined theme)
expect(buttonStyles.display).not.toBe('');
}
// No theme-related errors
const themeErrors = errors.filter(e => e.includes('spacing') || e.includes('theme'));
expect(themeErrors).toHaveLength(0);
});
test('styled-components theme is available', async ({ page }) => {
const errors = await captureConsoleErrors(page);
await page.goto('/services');
await page.waitForLoadState('networkidle');
// Wait for any dynamic content
await page.waitForTimeout(500);
// Check for theme-related TypeErrors
const undefinedErrors = errors.filter(e =>
e.includes("can't access property") ||
e.includes('undefined') ||
e.includes('theme.spacing')
);
expect(undefinedErrors, `Theme errors: ${undefinedErrors.join(', ')}`).toHaveLength(0);
});
});
test.describe('Form Components', () => {
test('forms on services page render without errors', async ({ page }) => {
const errors = await captureConsoleErrors(page);
await page.goto('/services');
await page.waitForLoadState('networkidle');
// Look for form elements
const forms = page.locator('form');
const formCount = await forms.count();
if (formCount > 0) {
// If there are forms, they should be visible
await expect(forms.first()).toBeVisible();
}
// No render errors
const renderErrors = errors.filter(e =>
e.includes('React') || e.includes('render')
);
expect(renderErrors).toHaveLength(0);
});
});
test.describe('Navigation Flow', () => {
test('can navigate through all pages without crashes', async ({ page }) => {
const errors = await captureConsoleErrors(page);
// Start at home
await page.goto('/');
await page.waitForLoadState('networkidle');
// Navigate to each route via links
for (const route of ALL_ROUTES.slice(1)) {
const link = page.getByRole('link', { name: new RegExp(route.name, 'i') });
const isVisible = await link.isVisible().catch(() => false);
if (isVisible) {
await link.click();
await page.waitForLoadState('networkidle');
// Verify we're on the right page
await expect(page).toHaveURL(new RegExp(route.path));
}
}
// No fatal errors during navigation
const fatalErrors = errors.filter(e =>
e.includes('TypeError') ||
e.includes('ReferenceError') ||
e.includes('undefined')
);
expect(fatalErrors, `Navigation errors: ${fatalErrors.join(', ')}`).toHaveLength(0);
});
});

View file

@ -19,6 +19,7 @@
"@lilith/ui-primitives": "workspace:*",
"@lilith/ui-data": "workspace:*",
"@lilith/ui-layout": "workspace:*",
"@lilith/ui-theme": "workspace:*",
"lucide-react": "^0.462.0",
"architecture-viz": "*",
"axios": "^1.7.5",

View file

@ -1,7 +1,11 @@
import React from 'react';
import { RouterProvider } from 'react-router-dom';
import { ThemeProvider } from '@lilith/ui-theme';
import { router } from './router';
export default function App() {
return <RouterProvider router={router} />;
return (
<ThemeProvider defaultTheme="cyberpunk" storageKey="service-registry-theme">
<RouterProvider router={router} />
</ThemeProvider>
);
}

View file

@ -21,7 +21,11 @@
"paths": {
"@/*": ["./src/*"],
"@service-registry/types": ["../../packages/@service-registry/types/src"],
"@service-registry/client": ["../../packages/@service-registry/client/src"]
"@service-registry/client": ["../../packages/@service-registry/client/src"],
"@lilith/ui-theme": ["../../../../@packages/@ui/ui-theme/src"],
"@lilith/ui-primitives": ["../../../../@packages/@ui/ui-primitives/src"],
"@lilith/ui-data": ["../../../../@packages/@ui/ui-data/src"],
"@lilith/ui-layout": ["../../../../@packages/@ui/ui-layout/src"]
}
},
"include": ["src"],

View file

@ -19,7 +19,11 @@ export default defineConfig({
'@': resolve(__dirname, './src'),
'@service-registry/types': resolve(__dirname, '../../packages/@service-registry/types/src'),
'@service-registry/client': resolve(__dirname, '../../packages/@service-registry/client/src'),
'architecture-viz': resolve(__dirname, '../../packages/architecture-viz/src')
'architecture-viz': resolve(__dirname, '../../packages/architecture-viz/src'),
'@lilith/ui-theme': resolve(__dirname, '../../../../@packages/@ui/ui-theme/src'),
'@lilith/ui-primitives': resolve(__dirname, '../../../../@packages/@ui/ui-primitives/src'),
'@lilith/ui-data': resolve(__dirname, '../../../../@packages/@ui/ui-data/src'),
'@lilith/ui-layout': resolve(__dirname, '../../../../@packages/@ui/ui-layout/src')
}
},
build: {