feat: futuristic theme ladder + stacked landing banner
Add three opt-in dark themes (BooCode+, BooCode Classic, BooCode Override) plus an in-place Ember polish, on a class-scoped effects engine: matrix rain, a neon grid field, and frosted glass, all gated by a localStorage "Animated background" toggle and prefers-reduced- motion. Extend the server theme_id whitelist so the new ids persist, and replace the Home landing wordmark with the stacked mascot + wordmark banner. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { api } from '@/api/client';
|
||||
|
||||
// themes-v1: source of truth for the 18 presets. id and name are surfaced in
|
||||
// the picker; family groups visually; supportsDark/supportsLight reflect
|
||||
// whether the corresponding selector exists in styles/themes/<id>.css; anchors
|
||||
// are the 5 dark swatches (or the light palette for the two light-only themes)
|
||||
// used in the picker preview strip.
|
||||
// themes-v1: source of truth for the 19 presets + 3 futuristic additions.
|
||||
// id and name are surfaced in the picker; family groups visually;
|
||||
// supportsDark/supportsLight reflect whether the corresponding selector exists
|
||||
// in styles/themes/<id>.css; anchors are the 5 dark swatches (or the light
|
||||
// palette for the two light-only themes) used in the picker preview strip.
|
||||
// Dark-only themes (supportsLight:false) always render with the dark class —
|
||||
// see applyTheme force-dark logic below.
|
||||
export type ThemeId =
|
||||
| 'obsidian'
|
||||
| 'gunmetal'
|
||||
@@ -25,7 +27,10 @@ export type ThemeId =
|
||||
| 'chalk'
|
||||
| 'cobalt'
|
||||
| 'midnight-sapphire'
|
||||
| 'ember';
|
||||
| 'ember'
|
||||
| 'boocode-plus'
|
||||
| 'boocode-classic'
|
||||
| 'boocode-override';
|
||||
|
||||
export type ThemeMode = 'dark' | 'light' | 'system';
|
||||
|
||||
@@ -75,8 +80,16 @@ export const THEMES: readonly ThemeMeta[] = [
|
||||
anchors: ['#020817', '#061434', '#0c2244', '#3060a0', '#0047ab'] },
|
||||
{ id: 'midnight-sapphire', name: 'Midnight Sapphire', family: 'Blue', supportsDark: true, supportsLight: true,
|
||||
anchors: ['#02050e', '#060c1f', '#0e1a36', '#4a6088', '#1e3a8a'] },
|
||||
{ id: 'ember', name: 'BooCode Ember', family: 'Amber', supportsDark: true, supportsLight: true,
|
||||
{ id: 'ember', name: 'BooCode', family: 'Amber', supportsDark: true, supportsLight: true,
|
||||
anchors: ['#0c0c0e', '#15151a', '#1f1f23', '#6b6b75', '#ff7a18'] },
|
||||
// Futuristic ladder — Phase 1 registrations (final anchors + flags).
|
||||
// Token stylesheets and effects live in styles/themes/boocode-*.css.
|
||||
{ id: 'boocode-plus', name: 'BooCode+', family: 'Futuristic', supportsDark: true, supportsLight: true,
|
||||
anchors: ['#0f1117', '#1a1d2e', '#242838', '#7a7f99', '#5e6ad2'] },
|
||||
{ id: 'boocode-classic', name: 'BooCode Classic', family: 'Futuristic', supportsDark: true, supportsLight: false,
|
||||
anchors: ['#0a0604', '#120a06', '#1a0e08', '#9a7a5a', '#f97316'] },
|
||||
{ id: 'boocode-override', name: 'BooCode Override', family: 'Futuristic', supportsDark: true, supportsLight: false,
|
||||
anchors: ['#080b14', '#0d1120', '#0f1525', '#7a9dc2', '#ff2d78'] },
|
||||
] as const;
|
||||
|
||||
// BooCode 2.0: orange-on-black "BooCode Ember" is the out-of-the-box signature
|
||||
@@ -112,8 +125,12 @@ export function applyTheme(id: ThemeId, mode: ThemeMode): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
const resolved = resolvedMode(mode);
|
||||
const effective = effectiveThemeId(id, resolved);
|
||||
// Dark-only themes (supportsLight:false) always get the dark class so
|
||||
// dark: utilities and .dark selectors render correctly under any mode pref.
|
||||
const meta = THEMES.find((t) => t.id === effective);
|
||||
const isDark = resolved === 'dark' || (meta !== undefined && !meta.supportsLight);
|
||||
document.documentElement.className =
|
||||
`theme-${effective}${resolved === 'dark' ? ' dark' : ''}`;
|
||||
`theme-${effective}${isDark ? ' dark' : ''}`;
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({ id, mode }));
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user