Spaces:
Running
Running
| import { createContext, useContext } from 'react' | |
| export type ThemeMode = 'light' | 'dark' | 'system' | |
| export interface BrandSwatch { | |
| id: string | |
| label: string | |
| /** Hex string of the 500 shade — a preview chip color. */ | |
| preview: string | |
| /** 10 RGB triplets (space-separated) for shades 50 → 900. */ | |
| scale: [string, string, string, string, string, string, string, string, string, string] | |
| } | |
| export const BRAND_SWATCHES: BrandSwatch[] = [ | |
| { | |
| id: 'sage', | |
| label: 'Sage green', | |
| preview: '#4f9862', | |
| scale: [ | |
| '243 250 244', | |
| '228 243 231', | |
| '200 230 207', | |
| '160 209 172', | |
| '115 181 131', | |
| '79 152 98', | |
| '61 124 78', | |
| '49 99 64', | |
| '40 79 52', | |
| '32 64 43', | |
| ], | |
| }, | |
| { | |
| id: 'emerald', | |
| label: 'Emerald', | |
| preview: '#10b981', | |
| scale: [ | |
| '236 253 245', | |
| '209 250 229', | |
| '167 243 208', | |
| '110 231 183', | |
| '52 211 153', | |
| '16 185 129', | |
| '5 150 105', | |
| '4 120 87', | |
| '6 95 70', | |
| '6 78 59', | |
| ], | |
| }, | |
| { | |
| id: 'blue', | |
| label: 'Ocean blue', | |
| preview: '#3b82f6', | |
| scale: [ | |
| '239 246 255', | |
| '219 234 254', | |
| '191 219 254', | |
| '147 197 253', | |
| '96 165 250', | |
| '59 130 246', | |
| '37 99 235', | |
| '29 78 216', | |
| '30 64 175', | |
| '30 58 138', | |
| ], | |
| }, | |
| { | |
| id: 'indigo', | |
| label: 'Deep indigo', | |
| preview: '#6366f1', | |
| scale: [ | |
| '238 242 255', | |
| '224 231 255', | |
| '199 210 254', | |
| '165 180 252', | |
| '129 140 248', | |
| '99 102 241', | |
| '79 70 229', | |
| '67 56 202', | |
| '55 48 163', | |
| '49 46 129', | |
| ], | |
| }, | |
| { | |
| id: 'violet', | |
| label: 'Royal violet', | |
| preview: '#8b5cf6', | |
| scale: [ | |
| '245 243 255', | |
| '237 233 254', | |
| '221 214 254', | |
| '196 181 253', | |
| '167 139 250', | |
| '139 92 246', | |
| '124 58 237', | |
| '109 40 217', | |
| '91 33 182', | |
| '76 29 149', | |
| ], | |
| }, | |
| { | |
| id: 'rose', | |
| label: 'Rose', | |
| preview: '#f43f5e', | |
| scale: [ | |
| '255 241 242', | |
| '255 228 230', | |
| '254 205 211', | |
| '253 164 175', | |
| '251 113 133', | |
| '244 63 94', | |
| '225 29 72', | |
| '190 18 60', | |
| '159 18 57', | |
| '136 19 55', | |
| ], | |
| }, | |
| { | |
| id: 'amber', | |
| label: 'Amber', | |
| preview: '#f59e0b', | |
| scale: [ | |
| '255 251 235', | |
| '254 243 199', | |
| '253 230 138', | |
| '252 211 77', | |
| '251 191 36', | |
| '245 158 11', | |
| '217 119 6', | |
| '180 83 9', | |
| '146 64 14', | |
| '120 53 15', | |
| ], | |
| }, | |
| { | |
| id: 'slate', | |
| label: 'Neutral slate', | |
| preview: '#64748b', | |
| scale: [ | |
| '248 250 252', | |
| '241 245 249', | |
| '226 232 240', | |
| '203 213 225', | |
| '148 163 184', | |
| '100 116 139', | |
| '71 85 105', | |
| '51 65 85', | |
| '30 41 59', | |
| '15 23 42', | |
| ], | |
| }, | |
| ] | |
| export interface AppSettings { | |
| theme: ThemeMode | |
| brandId: string | |
| defaultOutputFormat: 'html' | 'images' | 'pptx' | 'video' | |
| backendUrl: string | |
| concurrentPipelineRuns: boolean | |
| autoThumbnailBuilder: boolean | |
| /** | |
| * Per-user override of the wizard's Class options. Empty array uses | |
| * the curated default list (Nepali curriculum classes 1–12). | |
| */ | |
| customClassOptions: string[] | |
| /** | |
| * Per-user override of the wizard's Subject options. Empty array uses | |
| * the curated default list. | |
| */ | |
| customSubjectOptions: string[] | |
| /** Sidebar collapsed-to-icon-rail mode (desktop only). */ | |
| sidebarCollapsed: boolean | |
| } | |
| export const DEFAULT_CLASS_OPTIONS: readonly string[] = [ | |
| 'Class 1', | |
| 'Class 2', | |
| 'Class 3', | |
| 'Class 4', | |
| 'Class 5', | |
| 'Class 6', | |
| 'Class 7', | |
| 'Class 8', | |
| 'Class 9', | |
| 'Class 10', | |
| 'Class 11', | |
| 'Class 12', | |
| ] | |
| export const DEFAULT_SUBJECT_OPTIONS: readonly string[] = [ | |
| 'Nepali', | |
| 'English', | |
| 'Mathematics', | |
| 'Science', | |
| 'Social Studies', | |
| 'Health', | |
| 'Computer', | |
| 'Account', | |
| 'Economics', | |
| ] | |
| export const DEFAULT_SETTINGS: AppSettings = { | |
| theme: 'system', | |
| brandId: 'indigo', | |
| defaultOutputFormat: 'images', | |
| backendUrl: '', | |
| concurrentPipelineRuns: false, | |
| autoThumbnailBuilder: false, | |
| customClassOptions: [], | |
| customSubjectOptions: [], | |
| sidebarCollapsed: false, | |
| } | |
| export const SETTINGS_STORAGE_KEY = 'textbro:settings:v1' | |
| export interface SettingsContextValue { | |
| settings: AppSettings | |
| update: (patch: Partial<AppSettings>) => void | |
| reset: () => void | |
| } | |
| export const SettingsContext = createContext<SettingsContextValue | null>(null) | |
| export function useSettings(): SettingsContextValue { | |
| const ctx = useContext(SettingsContext) | |
| if (!ctx) throw new Error('useSettings must be used inside <SettingsProvider>') | |
| return ctx | |
| } | |
| export function findSwatch(id: string): BrandSwatch { | |
| return BRAND_SWATCHES.find((s) => s.id === id) ?? BRAND_SWATCHES[0] | |
| } | |
| /** | |
| * Applies theme (class on <html>) and brand palette (CSS vars on :root) to | |
| * the document. Called on mount and whenever settings change. | |
| */ | |
| export function applyTheme(settings: AppSettings) { | |
| const root = document.documentElement | |
| // Theme class | |
| const resolved = | |
| settings.theme === 'system' | |
| ? window.matchMedia('(prefers-color-scheme: dark)').matches | |
| ? 'dark' | |
| : 'light' | |
| : settings.theme | |
| root.classList.toggle('dark', resolved === 'dark') | |
| // Brand palette | |
| const swatch = findSwatch(settings.brandId) | |
| const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900] as const | |
| shades.forEach((shade, i) => { | |
| root.style.setProperty(`--brand-${shade}`, swatch.scale[i]) | |
| }) | |
| } | |