File size: 5,461 Bytes
5f3e9f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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])
  })
}