openvibers/openvibe.shared builtin-themes.js

builtin-themes.js

main 53.6 KB · 1435 lines · javascript

1'use strict';
2
3const RAW_BUILTIN_THEMES = [
4 {
5 name: 'Vibe',
6 slug: 'vibe',
7 mode: 'dark',
8 description: 'OpenVibe\'s default: deep blue night with an electric-blue glow.',
9 tags: ['default', 'cool', 'blue'],
10 variables: {
11 '--bg-primary': '#0a0f1c',
12 '--bg-secondary': '#101828',
13 '--bg-tertiary': '#172238',
14 '--bg-card': '#131c2e',
15 '--bg-hover': '#1c2a44',
16 '--bg-input': '#0d1424',
17 '--text-primary': '#e6edf7',
18 '--text-secondary': '#96a7c2',
19 '--text-muted': '#7386a3',
20 '--accent': '#3b82f6',
21 '--accent-light': '#60a5fa',
22 '--accent-dark': '#1d4ed8',
23 '--border': '#1f2d47',
24 '--border-light': '#2c3d5c',
25 '--live-red': '#ef4444',
26 '--success': '#22c55e',
27 '--warning': '#f59e0b',
28 '--danger': '#ef4444',
29 '--info': '#38bdf8',
30 '--shadow': '0 2px 12px rgba(0,0,0,0.45)',
31 '--shadow-lg': '0 8px 32px rgba(0,0,0,0.6)',
32 },
33 },
34 {
35 name: 'Violet Hour',
36 slug: 'violet-hour',
37 mode: 'dark',
38 description: 'Charcoal and violet: the colours OpenVibe started with.',
39 tags: ['classic', 'purple', 'warm'],
40 variables: {
41 '--bg-primary': '#0d0d0f',
42 '--bg-secondary': '#16161a',
43 '--bg-tertiary': '#1e1e24',
44 '--bg-card': '#1a1a20',
45 '--bg-hover': '#242430',
46 '--bg-input': '#12121a',
47 '--text-primary': '#e8e6e3',
48 '--text-secondary': '#9a9a9a',
49 '--text-muted': '#838282',
50 '--accent': '#8b5cf6',
51 '--accent-light': '#a78bfa',
52 '--accent-dark': '#6d28d9',
53 '--border': '#2a2a32',
54 '--border-light': '#3a3a44',
55 '--live-red': '#e74c3c',
56 '--success': '#2ecc71',
57 '--warning': '#f39c12',
58 '--danger': '#e74c3c',
59 '--info': '#3498db',
60 '--shadow': '0 2px 12px rgba(0,0,0,0.4)',
61 '--shadow-lg': '0 8px 32px rgba(0,0,0,0.6)',
62 },
63 },
64 {
65 name: 'Midnight',
66 slug: 'midnight',
67 mode: 'dark',
68 description: 'Blue-black with steel-indigo light.',
69 tags: ['cool', 'minimal', 'blue'],
70 variables: {
71 '--bg-primary': '#0a0e17',
72 '--bg-secondary': '#111827',
73 '--bg-tertiary': '#1a2235',
74 '--bg-card': '#151d2e',
75 '--bg-hover': '#1e2a42',
76 '--bg-input': '#0d1220',
77 '--text-primary': '#e2e8f0',
78 '--text-secondary': '#94a3b8',
79 '--text-muted': '#78879b',
80 '--accent': '#6366f1',
81 '--accent-light': '#818cf8',
82 '--accent-dark': '#4f46e5',
83 '--border': '#1e293b',
84 '--border-light': '#334155',
85 },
86 },
87 {
88 name: 'Understory',
89 slug: 'forest',
90 mode: 'dark',
91 description: 'Deep greens under the canopy.',
92 tags: ['nature', 'green', 'earthy'],
93 variables: {
94 '--bg-primary': '#0a120a',
95 '--bg-secondary': '#121e12',
96 '--bg-tertiary': '#1a2a1a',
97 '--bg-card': '#152015',
98 '--bg-hover': '#1e3420',
99 '--bg-input': '#0d160d',
100 '--text-primary': '#d4e8d4',
101 '--text-secondary': '#8aaa8a',
102 '--text-muted': '#708e70',
103 '--accent': '#4ade80',
104 '--accent-light': '#86efac',
105 '--accent-dark': '#22c55e',
106 '--border': '#1a3a1a',
107 '--border-light': '#2a4a2a',
108 },
109 },
110 {
111 name: 'Neon Tokyo',
112 slug: 'neon-tokyo',
113 mode: 'dark',
114 description: 'Hot pink neon on wet chrome.',
115 tags: ['cyberpunk', 'neon', 'vibrant'],
116 variables: {
117 '--bg-primary': '#0a0a0f',
118 '--bg-secondary': '#12101a',
119 '--bg-tertiary': '#1a1625',
120 '--bg-card': '#15111f',
121 '--bg-hover': '#221a32',
122 '--bg-input': '#0d0b14',
123 '--text-primary': '#f0e6ff',
124 '--text-secondary': '#b4a0d0',
125 '--text-muted': '#87799c',
126 '--accent': '#ff006e',
127 '--accent-light': '#ff4d94',
128 '--accent-dark': '#c9005a',
129 '--border': '#2a1a3a',
130 '--border-light': '#3a2a4a',
131 },
132 },
133 {
134 name: 'Dracula',
135 slug: 'dracula',
136 mode: 'dark',
137 description: 'The Dracula scheme, as its authors drew it.',
138 tags: ['popular', 'purple', 'classic'],
139 variables: {
140 '--bg-primary': '#282a36',
141 '--bg-secondary': '#1e1f29',
142 '--bg-tertiary': '#343746',
143 '--bg-card': '#2c2e3c',
144 '--bg-hover': '#3a3d50',
145 '--bg-input': '#21222c',
146 '--text-primary': '#f8f8f2',
147 '--text-secondary': '#bfbfb2',
148 '--text-muted': '#8995b8',
149 '--accent': '#bd93f9',
150 '--accent-light': '#d4b8ff',
151 '--accent-dark': '#9b6ddb',
152 '--border': '#44475a',
153 '--border-light': '#565970',
154 '--success': '#50fa7b',
155 '--danger': '#ff5555',
156 '--warning': '#f1fa8c',
157 '--info': '#8be9fd',
158 '--live-red': '#ff5555',
159 },
160 },
161 {
162 name: 'Monokai',
163 slug: 'monokai',
164 mode: 'dark',
165 description: 'The Monokai editor palette.',
166 tags: ['developer', 'classic', 'warm'],
167 variables: {
168 '--bg-primary': '#272822',
169 '--bg-secondary': '#1e1f1c',
170 '--bg-tertiary': '#3e3d32',
171 '--bg-card': '#2d2e27',
172 '--bg-hover': '#49483e',
173 '--bg-input': '#22231e',
174 '--text-primary': '#f8f8f2',
175 '--text-secondary': '#cfcfc2',
176 '--text-muted': '#979484',
177 '--accent': '#a6e22e',
178 '--accent-light': '#c4ff50',
179 '--accent-dark': '#7dac1c',
180 '--border': '#49483e',
181 '--border-light': '#5b5a50',
182 '--danger': '#f92672',
183 '--warning': '#e6db74',
184 '--info': '#66d9ef',
185 },
186 },
187 {
188 name: 'Trench',
189 slug: 'ocean-deep',
190 mode: 'dark',
191 description: 'Pressure-dark water with a teal glow.',
192 tags: ['ocean', 'teal', 'calm'],
193 variables: {
194 '--bg-primary': '#0a1215',
195 '--bg-secondary': '#0f1a1f',
196 '--bg-tertiary': '#162228',
197 '--bg-card': '#121c22',
198 '--bg-hover': '#1a2a32',
199 '--bg-input': '#0c1418',
200 '--text-primary': '#d0e8ef',
201 '--text-secondary': '#7aa8ba',
202 '--text-muted': '#658896',
203 '--accent': '#00bcd4',
204 '--accent-light': '#4dd0e1',
205 '--accent-dark': '#0097a7',
206 '--border': '#1a3040',
207 '--border-light': '#2a4050',
208 },
209 },
210 {
211 name: 'Dusk',
212 slug: 'sunset',
213 mode: 'dark',
214 description: 'Orange fading to rose over dark ground.',
215 tags: ['warm', 'gradient', 'vibrant'],
216 variables: {
217 '--bg-primary': '#120a0a',
218 '--bg-secondary': '#1a1010',
219 '--bg-tertiary': '#241616',
220 '--bg-card': '#1e1212',
221 '--bg-hover': '#2e1a1a',
222 '--bg-input': '#140c0c',
223 '--text-primary': '#ffe8e0',
224 '--text-secondary': '#c49080',
225 '--text-muted': '#977970',
226 '--accent': '#ff6b35',
227 '--accent-light': '#ff8f5e',
228 '--accent-dark': '#d04a1a',
229 '--border': '#3a2020',
230 '--border-light': '#4a3030',
231 },
232 },
233 {
234 name: 'Arctic',
235 slug: 'arctic',
236 mode: 'dark',
237 description: 'Frost surfaces, cyan light, crisp white text.',
238 tags: ['cool', 'ice', 'clean'],
239 variables: {
240 '--bg-primary': '#0b1620',
241 '--bg-secondary': '#0f1e2b',
242 '--bg-tertiary': '#163040',
243 '--bg-card': '#122534',
244 '--bg-hover': '#1a3a4c',
245 '--bg-input': '#0c1a26',
246 '--text-primary': '#eaf6ff',
247 '--text-secondary': '#9ec3d8',
248 '--text-muted': '#7ea3ba',
249 '--accent': '#22d3ee',
250 '--accent-light': '#a5f3fc',
251 '--accent-dark': '#0891b2',
252 '--border': '#1e3f52',
253 '--border-light': '#2a5268',
254 '--info': '#7dd3fc',
255 '--success': '#34d399',
256 '--shadow': '0 2px 12px rgba(2,12,20,0.5)',
257 '--shadow-lg': '0 8px 32px rgba(2,12,20,0.65)',
258 },
259 },
260 {
261 name: 'Ember',
262 slug: 'ember',
263 mode: 'dark',
264 description: 'Smouldering red on warm charcoal.',
265 tags: ['red', 'warm', 'intense'],
266 variables: {
267 '--bg-primary': '#120808',
268 '--bg-secondary': '#1a0e0e',
269 '--bg-tertiary': '#241414',
270 '--bg-card': '#1e1010',
271 '--bg-hover': '#2e1818',
272 '--bg-input': '#140a0a',
273 '--text-primary': '#f0dada',
274 '--text-secondary': '#b0808a',
275 '--text-muted': '#95787c',
276 '--accent': '#ef4444',
277 '--accent-light': '#f87171',
278 '--accent-dark': '#b91c1c',
279 '--border': '#3a1a20',
280 '--border-light': '#4a2a30',
281 },
282 },
283 {
284 name: 'Vapor',
285 slug: 'vapor',
286 mode: 'dark',
287 description: 'Magenta and teal, straight off a VHS tape.',
288 tags: ['retro', 'aesthetic', 'neon'],
289 variables: {
290 '--bg-primary': '#0e0a14',
291 '--bg-secondary': '#14101c',
292 '--bg-tertiary': '#1c1628',
293 '--bg-card': '#181220',
294 '--bg-hover': '#241a34',
295 '--bg-input': '#100c16',
296 '--text-primary': '#f0e0ff',
297 '--text-secondary': '#c090e0',
298 '--text-muted': '#8f73b7',
299 '--accent': '#e040fb',
300 '--accent-light': '#ea80fc',
301 '--accent-dark': '#aa00d4',
302 '--border': '#2a1a40',
303 '--border-light': '#3a2a50',
304 '--info': '#00e5ff',
305 '--success': '#69f0ae',
306 },
307 },
308 {
309 name: 'Slate',
310 slug: 'slate',
311 mode: 'dark',
312 description: 'Neutral grey with a cool violet accent.',
313 tags: ['neutral', 'minimal', 'professional'],
314 variables: {
315 '--bg-primary': '#0f1118',
316 '--bg-secondary': '#161820',
317 '--bg-tertiary': '#1e2028',
318 '--bg-card': '#1a1c24',
319 '--bg-hover': '#24262e',
320 '--bg-input': '#12141c',
321 '--text-primary': '#e4e6ea',
322 '--text-secondary': '#a0a4b0',
323 '--text-muted': '#828690',
324 '--accent': '#a78bfa',
325 '--accent-light': '#c4b5fd',
326 '--accent-dark': '#7c3aed',
327 '--border': '#2a2c38',
328 '--border-light': '#3a3c48',
329 },
330 },
331 {
332 name: 'Matrix',
333 slug: 'matrix',
334 mode: 'dark',
335 description: 'Green phosphor on black.',
336 tags: ['hacker', 'retro', 'terminal'],
337 variables: {
338 '--bg-primary': '#000000',
339 '--bg-secondary': '#050a05',
340 '--bg-tertiary': '#0a140a',
341 '--bg-card': '#071007',
342 '--bg-hover': '#0e1e0e',
343 '--bg-input': '#030803',
344 '--text-primary': '#00ff41',
345 '--text-secondary': '#00b030',
346 '--text-muted': '#008f25',
347 '--accent': '#00ff41',
348 '--accent-light': '#66ff8a',
349 '--accent-dark': '#00aa2a',
350 '--border': '#0a2a0a',
351 '--border-light': '#144014',
352 },
353 },
354 {
355 name: 'Nord',
356 slug: 'nord',
357 mode: 'dark',
358 description: 'The Nord palette: muted polar blues.',
359 tags: ['popular', 'blue', 'soft'],
360 variables: {
361 '--bg-primary': '#2e3440',
362 '--bg-secondary': '#272c36',
363 '--bg-tertiary': '#3b4252',
364 '--bg-card': '#333a48',
365 '--bg-hover': '#434c5e',
366 '--bg-input': '#2a303c',
367 '--text-primary': '#eceff4',
368 '--text-secondary': '#d8dee9',
369 '--text-muted': '#9ba5b8',
370 '--accent': '#88c0d0',
371 '--accent-light': '#8fbcbb',
372 '--accent-dark': '#5e81ac',
373 '--border': '#3b4252',
374 '--border-light': '#4c566a',
375 '--success': '#a3be8c',
376 '--danger': '#bf616a',
377 '--warning': '#ebcb8b',
378 '--info': '#81a1c1',
379 },
380 },
381 {
382 name: 'Gruvbox',
383 slug: 'gruvbox-dark',
384 mode: 'dark',
385 description: 'Warm retro contrast.',
386 tags: ['retro', 'warm', 'developer'],
387 variables: {
388 '--bg-primary': '#1d2021',
389 '--bg-secondary': '#282828',
390 '--bg-tertiary': '#3c3836',
391 '--bg-card': '#32302f',
392 '--bg-hover': '#504945',
393 '--bg-input': '#242424',
394 '--text-primary': '#ebdbb2',
395 '--text-secondary': '#d5c4a1',
396 '--text-muted': '#a69682',
397 '--accent': '#fabd2f',
398 '--accent-light': '#fce566',
399 '--accent-dark': '#d79921',
400 '--border': '#3c3836',
401 '--border-light': '#504945',
402 '--success': '#b8bb26',
403 '--danger': '#fb4934',
404 '--warning': '#fe8019',
405 '--info': '#83a598',
406 },
407 },
408 {
409 name: 'Abyss',
410 slug: 'abyss',
411 mode: 'dark',
412 description: 'Nearly black; the interface is the light.',
413 tags: ['minimal', 'ultra-dark', 'oled'],
414 variables: {
415 '--bg-primary': '#000000',
416 '--bg-secondary': '#080808',
417 '--bg-tertiary': '#111111',
418 '--bg-card': '#0a0a0a',
419 '--bg-hover': '#181818',
420 '--bg-input': '#050505',
421 '--text-primary': '#cccccc',
422 '--text-secondary': '#888888',
423 '--text-muted': '#7a7a7a',
424 '--accent': '#ffffff',
425 '--accent-light': '#ffffff',
426 '--accent-dark': '#aaaaaa',
427 '--border': '#1a1a1a',
428 '--border-light': '#252525',
429 },
430 },
431 {
432 name: 'Copper',
433 slug: 'copper',
434 mode: 'dark',
435 description: 'Bronze on dark walnut.',
436 tags: ['metallic', 'warm', 'premium'],
437 variables: {
438 '--bg-primary': '#110d09',
439 '--bg-secondary': '#1a140e',
440 '--bg-tertiary': '#231c14',
441 '--bg-card': '#1e1710',
442 '--bg-hover': '#2c2218',
443 '--bg-input': '#13100b',
444 '--text-primary': '#e8ddd0',
445 '--text-secondary': '#b49a80',
446 '--text-muted': '#8e7f6a',
447 '--accent': '#cd7f32',
448 '--accent-light': '#daa060',
449 '--accent-dark': '#a06020',
450 '--border': '#2a2016',
451 '--border-light': '#3a3020',
452 },
453 },
454 {
455 name: 'Sakura',
456 slug: 'sakura-night',
457 mode: 'dark',
458 description: 'Cherry blossom on indigo.',
459 tags: ['japanese', 'pink', 'soft'],
460 variables: {
461 '--bg-primary': '#0e0a14',
462 '--bg-secondary': '#150f1c',
463 '--bg-tertiary': '#1c1526',
464 '--bg-card': '#181020',
465 '--bg-hover': '#221830',
466 '--bg-input': '#100c16',
467 '--text-primary': '#f0e0f0',
468 '--text-secondary': '#c0a0c0',
469 '--text-muted': '#927592',
470 '--accent': '#f472b6',
471 '--accent-light': '#f9a8d4',
472 '--accent-dark': '#db2777',
473 '--border': '#2a1a30',
474 '--border-light': '#3a2a40',
475 },
476 },
477 {
478 name: 'Amber',
479 slug: 'hacker',
480 mode: 'dark',
481 description: 'Amber phosphor on black.',
482 tags: ['retro', 'terminal', 'nostalgia'],
483 variables: {
484 '--bg-primary': '#000000',
485 '--bg-secondary': '#0a0800',
486 '--bg-tertiary': '#141000',
487 '--bg-card': '#0e0c00',
488 '--bg-hover': '#1c1800',
489 '--bg-input': '#060400',
490 '--text-primary': '#ffb000',
491 '--text-secondary': '#cc8d00',
492 '--text-muted': '#a37100',
493 '--accent': '#ffb000',
494 '--accent-light': '#ffd050',
495 '--accent-dark': '#cc8d00',
496 '--border': '#1a1400',
497 '--border-light': '#2a2200',
498 },
499 },
500 {
501 name: 'Daylight',
502 slug: 'daylight',
503 mode: 'light',
504 description: 'Bright white with warm accents.',
505 tags: ['clean', 'bright', 'default-light'],
506 variables: {
507 '--bg-primary': '#ffffff',
508 '--bg-secondary': '#f7f7f8',
509 '--bg-tertiary': '#eeeef0',
510 '--bg-card': '#ffffff',
511 '--bg-hover': '#f0f0f2',
512 '--bg-input': '#f5f5f7',
513 '--text-primary': '#1a1a2e',
514 '--text-secondary': '#4a4a6a',
515 '--text-muted': '#747489',
516 '--accent': '#8756f3',
517 '--accent-light': '#a78bfa',
518 '--accent-dark': '#6d28d9',
519 '--border': '#e0e0e6',
520 '--border-light': '#d0d0d8',
521 '--shadow': '0 2px 12px rgba(0,0,0,0.08)',
522 '--shadow-lg': '0 8px 32px rgba(0,0,0,0.12)',
523 },
524 },
525 {
526 name: 'Paper',
527 slug: 'paper',
528 mode: 'light',
529 description: 'Aged paper and ink.',
530 tags: ['warm', 'soft', 'minimal'],
531 variables: {
532 '--bg-primary': '#faf8f5',
533 '--bg-secondary': '#f0ece6',
534 '--bg-tertiary': '#e8e2d8',
535 '--bg-card': '#faf8f5',
536 '--bg-hover': '#f0ece6',
537 '--bg-input': '#f5f2ed',
538 '--text-primary': '#2c2416',
539 '--text-secondary': '#5a4e3e',
540 '--text-muted': '#7b7061',
541 '--accent': '#8b6914',
542 '--accent-light': '#b08a30',
543 '--accent-dark': '#6a5010',
544 '--border': '#ddd6c8',
545 '--border-light': '#ccc4b0',
546 '--shadow': '0 2px 12px rgba(60,40,0,0.06)',
547 '--shadow-lg': '0 8px 32px rgba(60,40,0,0.10)',
548 },
549 },
550 {
551 name: 'Vibe Light',
552 slug: 'cloud',
553 mode: 'light',
554 description: 'OpenVibe\'s default in daylight: airy blue-white with the same blue.',
555 tags: ['blue', 'airy', 'fresh'],
556 variables: {
557 '--bg-primary': '#f0f5ff',
558 '--bg-secondary': '#e8eef8',
559 '--bg-tertiary': '#dde5f0',
560 '--bg-card': '#f5f8ff',
561 '--bg-hover': '#e4ecf8',
562 '--bg-input': '#edf2fa',
563 '--text-primary': '#1a2040',
564 '--text-secondary': '#4a5070',
565 '--text-muted': '#6b718c',
566 '--accent': '#2867ed',
567 '--accent-light': '#60a5fa',
568 '--accent-dark': '#2563eb',
569 '--border': '#d0d8ea',
570 '--border-light': '#bcc8e0',
571 '--shadow': '0 2px 12px rgba(0,20,60,0.06)',
572 '--shadow-lg': '0 8px 32px rgba(0,20,60,0.10)',
573 },
574 },
575 {
576 name: 'Meadow',
577 slug: 'meadow',
578 mode: 'light',
579 description: 'Spring greens in daylight.',
580 tags: ['nature', 'green', 'fresh'],
581 variables: {
582 '--bg-primary': '#f2f8f0',
583 '--bg-secondary': '#e8f0e4',
584 '--bg-tertiary': '#dce8d6',
585 '--bg-card': '#f5faf2',
586 '--bg-hover': '#e4f0de',
587 '--bg-input': '#edf5ea',
588 '--text-primary': '#1a2e16',
589 '--text-secondary': '#3a5a32',
590 '--text-muted': '#5d7a54',
591 '--accent': '#15823e',
592 '--accent-light': '#22c55e',
593 '--accent-dark': '#15803d',
594 '--border': '#c8dcc0',
595 '--border-light': '#b0ccaa',
596 '--shadow': '0 2px 12px rgba(0,40,0,0.06)',
597 '--shadow-lg': '0 8px 32px rgba(0,40,0,0.10)',
598 },
599 },
600 {
601 name: 'Peach',
602 slug: 'peach',
603 mode: 'light',
604 description: 'Peach and coral, soft and warm.',
605 tags: ['warm', 'pink', 'soft'],
606 variables: {
607 '--bg-primary': '#fff5f0',
608 '--bg-secondary': '#fbeae2',
609 '--bg-tertiary': '#f5ddd2',
610 '--bg-card': '#fff7f2',
611 '--bg-hover': '#fce8dc',
612 '--bg-input': '#fdf0ea',
613 '--text-primary': '#2e1a14',
614 '--text-secondary': '#6a4438',
615 '--text-muted': '#8f6a5c',
616 '--accent': '#c04808',
617 '--accent-light': '#fb923c',
618 '--accent-dark': '#ea580c',
619 '--border': '#f0d0c0',
620 '--border-light': '#e4bfab',
621 '--shadow': '0 2px 12px rgba(60,20,0,0.06)',
622 '--shadow-lg': '0 8px 32px rgba(60,20,0,0.10)',
623 },
624 },
625 {
626 name: 'Lavender',
627 slug: 'lavender',
628 mode: 'light',
629 description: 'Calm pale violet.',
630 tags: ['purple', 'calm', 'elegant'],
631 variables: {
632 '--bg-primary': '#f8f5ff',
633 '--bg-secondary': '#f0eaf8',
634 '--bg-tertiary': '#e6ddf0',
635 '--bg-card': '#faf7ff',
636 '--bg-hover': '#eee6f8',
637 '--bg-input': '#f4f0fa',
638 '--text-primary': '#1e1430',
639 '--text-secondary': '#4a3a60',
640 '--text-muted': '#796a92',
641 '--accent': '#844cf2',
642 '--accent-light': '#a78bfa',
643 '--accent-dark': '#7c3aed',
644 '--border': '#ddd0ee',
645 '--border-light': '#ccc0dd',
646 '--shadow': '0 2px 12px rgba(40,0,60,0.06)',
647 '--shadow-lg': '0 8px 32px rgba(40,0,60,0.10)',
648 },
649 },
650 {
651 name: 'Gruvbox Light',
652 slug: 'gruvbox-light',
653 mode: 'light',
654 description: 'Cream with bold retro accents.',
655 tags: ['retro', 'warm', 'developer'],
656 variables: {
657 '--bg-primary': '#fbf1c7',
658 '--bg-secondary': '#f2e5bc',
659 '--bg-tertiary': '#ebdbb2',
660 '--bg-card': '#fbf1c7',
661 '--bg-hover': '#f2e5bc',
662 '--bg-input': '#f5ecc4',
663 '--text-primary': '#3c3836',
664 '--text-secondary': '#504945',
665 '--text-muted': '#766b60',
666 '--accent': '#be4707',
667 '--accent-light': '#fe8019',
668 '--accent-dark': '#af3a03',
669 '--border': '#d5c4a1',
670 '--border-light': '#bdae93',
671 '--success': '#98971a',
672 '--danger': '#cc241d',
673 '--warning': '#d79921',
674 '--info': '#458588',
675 '--shadow': '0 2px 12px rgba(40,30,0,0.08)',
676 '--shadow-lg': '0 8px 32px rgba(40,30,0,0.12)',
677 },
678 },
679 {
680 name: 'Snow',
681 slug: 'snow',
682 mode: 'light',
683 description: 'Pure white with icy blue.',
684 tags: ['minimal', 'clean', 'blue'],
685 variables: {
686 '--bg-primary': '#ffffff',
687 '--bg-secondary': '#f8fafc',
688 '--bg-tertiary': '#f1f5f9',
689 '--bg-card': '#ffffff',
690 '--bg-hover': '#f1f5f9',
691 '--bg-input': '#f8fafc',
692 '--text-primary': '#0f172a',
693 '--text-secondary': '#334155',
694 '--text-muted': '#69768b',
695 '--accent': '#027cbb',
696 '--accent-light': '#38bdf8',
697 '--accent-dark': '#0284c7',
698 '--border': '#e2e8f0',
699 '--border-light': '#cbd5e1',
700 '--shadow': '0 2px 12px rgba(0,0,0,0.05)',
701 '--shadow-lg': '0 8px 32px rgba(0,0,0,0.08)',
702 },
703 },
704 {
705 name: 'Sand',
706 slug: 'sand',
707 mode: 'light',
708 description: 'Desert tan and warm stone.',
709 tags: ['warm', 'earthy', 'desert'],
710 variables: {
711 '--bg-primary': '#f8f4ef',
712 '--bg-secondary': '#f0e8de',
713 '--bg-tertiary': '#e8ddd0',
714 '--bg-card': '#faf6f0',
715 '--bg-hover': '#f0e8de',
716 '--bg-input': '#f5f0e8',
717 '--text-primary': '#2e2418',
718 '--text-secondary': '#5a4e3e',
719 '--text-muted': '#7a6e5f',
720 '--accent': '#8c6707',
721 '--accent-light': '#daa520',
722 '--accent-dark': '#8b6508',
723 '--border': '#ddd0be',
724 '--border-light': '#ccc0aa',
725 '--shadow': '0 2px 12px rgba(60,40,0,0.06)',
726 '--shadow-lg': '0 8px 32px rgba(60,40,0,0.10)',
727 },
728 },
729 {
730 name: 'Rose',
731 slug: 'rose',
732 mode: 'light',
733 description: 'Blush pink, light and delicate.',
734 tags: ['pink', 'romantic', 'soft'],
735 variables: {
736 '--bg-primary': '#fff5f7',
737 '--bg-secondary': '#fce7ec',
738 '--bg-tertiary': '#f5d5de',
739 '--bg-card': '#fff7f9',
740 '--bg-hover': '#fce0e8',
741 '--bg-input': '#fdeef2',
742 '--text-primary': '#2e1420',
743 '--text-secondary': '#6a3850',
744 '--text-muted': '#96617e',
745 '--accent': '#de1c47',
746 '--accent-light': '#f43f5e',
747 '--accent-dark': '#be123c',
748 '--border': '#f0c8d4',
749 '--border-light': '#e4b0c0',
750 '--shadow': '0 2px 12px rgba(60,0,20,0.06)',
751 '--shadow-lg': '0 8px 32px rgba(60,0,20,0.10)',
752 },
753 },
754 {
755 name: 'Solarized Dark',
756 slug: 'solarized-dark',
757 mode: 'dark',
758 description: 'Ethan Schoonover\'s precision palette, dark.',
759 tags: ['popular', 'developer', 'balanced'],
760 variables: {
761 '--bg-primary': '#002b36',
762 '--bg-secondary': '#073642',
763 '--bg-tertiary': '#0a3f4e',
764 '--bg-card': '#053542',
765 '--bg-hover': '#0d4a58',
766 '--bg-input': '#01303d',
767 '--text-primary': '#fdf6e3',
768 '--text-secondary': '#93a1a1',
769 '--text-muted': '#909c9a',
770 '--accent': '#b58900',
771 '--accent-light': '#d4a017',
772 '--accent-dark': '#8a6a00',
773 '--border': '#0a4050',
774 '--border-light': '#105060',
775 '--success': '#859900',
776 '--danger': '#dc322f',
777 '--warning': '#cb4b16',
778 '--info': '#268bd2',
779 },
780 },
781 {
782 name: 'Solarized Light',
783 slug: 'solarized-light',
784 mode: 'light',
785 description: 'Ethan Schoonover\'s precision palette, light.',
786 tags: ['popular', 'developer', 'balanced'],
787 variables: {
788 '--bg-primary': '#fdf6e3',
789 '--bg-secondary': '#eee8d5',
790 '--bg-tertiary': '#e4ddc6',
791 '--bg-card': '#fdf6e3',
792 '--bg-hover': '#eee8d5',
793 '--bg-input': '#f5eedb',
794 '--text-primary': '#002b36',
795 '--text-secondary': '#586e75',
796 '--text-muted': '#5b7478',
797 '--accent': '#8d6c00',
798 '--accent-light': '#d4a017',
799 '--accent-dark': '#8a6a00',
800 '--border': '#d6ceb5',
801 '--border-light': '#c8c0a5',
802 '--success': '#859900',
803 '--danger': '#dc322f',
804 '--warning': '#cb4b16',
805 '--info': '#268bd2',
806 '--shadow': '0 2px 12px rgba(0,30,40,0.08)',
807 '--shadow-lg': '0 8px 32px rgba(0,30,40,0.12)',
808 },
809 },
810 {
811 name: 'Catppuccin Mocha',
812 slug: 'catppuccin-mocha',
813 mode: 'dark',
814 description: 'Soothing pastels on mocha.',
815 tags: ['popular', 'pastel', 'soft'],
816 variables: {
817 '--bg-primary': '#1e1e2e',
818 '--bg-secondary': '#181825',
819 '--bg-tertiary': '#313244',
820 '--bg-card': '#1e1e2e',
821 '--bg-hover': '#313244',
822 '--bg-input': '#1a1a2c',
823 '--text-primary': '#cdd6f4',
824 '--text-secondary': '#bac2de',
825 '--text-muted': '#81869e',
826 '--accent': '#cba6f7',
827 '--accent-light': '#dcc0ff',
828 '--accent-dark': '#b07de0',
829 '--border': '#313244',
830 '--border-light': '#45475a',
831 '--success': '#a6e3a1',
832 '--danger': '#f38ba8',
833 '--warning': '#fab387',
834 '--info': '#89b4fa',
835 '--live-red': '#f38ba8',
836 },
837 },
838 {
839 name: 'Catppuccin Latte',
840 slug: 'catppuccin-latte',
841 mode: 'light',
842 description: 'Soothing pastels on latte.',
843 tags: ['popular', 'pastel', 'soft'],
844 variables: {
845 '--bg-primary': '#eff1f5',
846 '--bg-secondary': '#e6e9ef',
847 '--bg-tertiary': '#dce0e8',
848 '--bg-card': '#eff1f5',
849 '--bg-hover': '#dce0e8',
850 '--bg-input': '#e8ebf0',
851 '--text-primary': '#4c4f69',
852 '--text-secondary': '#5c5f77',
853 '--text-muted': '#696c83',
854 '--accent': '#8839ef',
855 '--accent-light': '#a05cff',
856 '--accent-dark': '#7028d4',
857 '--border': '#ccd0da',
858 '--border-light': '#bcc0cc',
859 '--success': '#40a02b',
860 '--danger': '#d20f39',
861 '--warning': '#fe640b',
862 '--info': '#1e66f5',
863 '--live-red': '#d20f39',
864 '--shadow': '0 2px 12px rgba(0,0,0,0.06)',
865 '--shadow-lg': '0 8px 32px rgba(0,0,0,0.10)',
866 },
867 },
868 {
869 name: 'High Contrast',
870 slug: 'high-contrast',
871 mode: 'dark',
872 description: 'Bold yellow on black for maximum legibility.',
873 tags: ['accessibility', 'high-contrast', 'bold'],
874 variables: {
875 '--bg-primary': '#000000',
876 '--bg-secondary': '#0a0a0a',
877 '--bg-tertiary': '#1a1a1a',
878 '--bg-card': '#0f0f0f',
879 '--bg-hover': '#222222',
880 '--bg-input': '#0a0a0a',
881 '--text-primary': '#ffffff',
882 '--text-secondary': '#f0f0f0',
883 '--text-muted': '#aaaaaa',
884 '--accent': '#ffd700',
885 '--accent-light': '#ffed4a',
886 '--accent-dark': '#ccac00',
887 '--border': '#444444',
888 '--border-light': '#666666',
889 '--live-red': '#ff0000',
890 '--success': '#00ff00',
891 '--warning': '#ffff00',
892 '--danger': '#ff0000',
893 '--info': '#00ffff',
894 },
895 },
896 // ── Seasonal themes ───────────────────────────────────────────────────────────────────────────
897 // A theme with `season` is also the network's default between `from` and `to` (MM-DD, the visitor's own
898 // calendar, both days included) for everyone on the default theme who has not turned seasonal themes off
899 // (theme-loader.js; display preference `seasonal`). Anyone can also pick one like any other theme, any time.
900 {
901 name: 'Midnight Pumpkin',
902 slug: 'halloween',
903 mode: 'dark',
904 description: 'Halloween: pumpkin orange on OpenVibe\'s midnight navy. On by itself through October (Hayride in light).',
905 tags: ['seasonal', 'halloween', 'orange'],
906 season: { id: 'halloween', name: 'Halloween', from: '10-01', to: '10-31' },
907 variables: {
908 '--bg-primary': '#0a0f1c',
909 '--bg-secondary': '#0f1626',
910 '--bg-tertiary': '#151e33',
911 '--bg-card': '#111a2b',
912 '--bg-hover': '#1a2540',
913 '--bg-input': '#0c1322',
914 '--text-primary': '#eef1f6',
915 '--text-secondary': '#a9b4c8',
916 '--text-muted': '#8c99b0',
917 '--accent': '#ff7a1a',
918 '--accent-light': '#ffa14f',
919 '--accent-dark': '#d65d00',
920 '--border': '#1c2840',
921 '--border-light': '#2a3858',
922 '--live-red': '#ef4444',
923 '--success': '#4ade80',
924 '--warning': '#fbbf24',
925 '--danger': '#f87171',
926 '--info': '#60a5fa',
927 '--shadow': '0 2px 12px rgba(0,4,14,0.5)',
928 '--shadow-lg': '0 8px 32px rgba(0,4,14,0.65)',
929 },
930 },
931 {
932 name: 'Frost',
933 slug: 'frost',
934 mode: 'dark',
935 description: 'Winter: frosted teal over a cold midnight sky. On by itself through December.',
936 tags: ['seasonal', 'winter', 'teal', 'cool'],
937 season: { id: 'winter', name: 'Winter', from: '12-01', to: '12-31' },
938 variables: {
939 '--bg-primary': '#08111b',
940 '--bg-secondary': '#0d1926',
941 '--bg-tertiary': '#132334',
942 '--bg-card': '#0f1c2b',
943 '--bg-hover': '#18293d',
944 '--bg-input': '#0a1520',
945 '--text-primary': '#eaf4fb',
946 '--text-secondary': '#a3b9cc',
947 '--text-muted': '#7f97ad',
948 '--accent': '#2dd4bf',
949 '--accent-light': '#7ee8da',
950 '--accent-dark': '#0f9e8c',
951 '--border': '#1b2d41',
952 '--border-light': '#294057',
953 '--live-red': '#ef4444',
954 '--success': '#4ade80',
955 '--warning': '#fbbf24',
956 '--danger': '#f87171',
957 '--info': '#93c5fd',
958 '--shadow': '0 2px 12px rgba(0,6,14,0.5)',
959 '--shadow-lg': '0 8px 32px rgba(0,6,14,0.65)',
960 },
961 },
962 {
963 name: 'Dawn',
964 slug: 'dawn',
965 mode: 'light',
966 description: 'Midnight\'s indigo in the first light.',
967 tags: ['cool', 'minimal', 'indigo'],
968 variables: {
969 '--bg-primary': '#f5f6fc',
970 '--bg-secondary': '#eceefa',
971 '--bg-tertiary': '#e1e4f5',
972 '--bg-card': '#fafbff',
973 '--bg-hover': '#e6e9f8',
974 '--bg-input': '#f0f2fb',
975 '--text-primary': '#161a33',
976 '--text-secondary': '#474d6b',
977 '--text-muted': '#646a87',
978 '--accent': '#4f46e5',
979 '--accent-light': '#6366f1',
980 '--accent-dark': '#3730a3',
981 '--border': '#d6daef',
982 '--border-light': '#c3c8e6',
983 '--shadow': '0 2px 12px rgba(10,14,40,0.07)',
984 '--shadow-lg': '0 8px 32px rgba(10,14,40,0.12)',
985 },
986 },
987 {
988 name: 'Tokyo Day',
989 slug: 'tokyo-day',
990 mode: 'light',
991 description: 'Neon Tokyo by day: hot pink on soft white.',
992 tags: ['vibrant', 'pink', 'city'],
993 variables: {
994 '--bg-primary': '#fff6fa',
995 '--bg-secondary': '#ffedf4',
996 '--bg-tertiary': '#fde1ec',
997 '--bg-card': '#fffafc',
998 '--bg-hover': '#fde7f0',
999 '--bg-input': '#fff0f6',
1000 '--text-primary': '#24101a',
1001 '--text-secondary': '#5c3a4a',
1002 '--text-muted': '#7a5566',
1003 '--accent': '#d1005a',
1004 '--accent-light': '#ff2d7f',
1005 '--accent-dark': '#9c0043',
1006 '--border': '#f3d3e0',
1007 '--border-light': '#ebbfd1',
1008 '--shadow': '0 2px 12px rgba(40,0,16,0.07)',
1009 '--shadow-lg': '0 8px 32px rgba(40,0,16,0.12)',
1010 },
1011 },
1012 {
1013 name: 'Alucard',
1014 slug: 'alucard',
1015 mode: 'light',
1016 description: 'Dracula\'s daylight twin: purple ink on warm parchment.',
1017 tags: ['popular', 'purple', 'warm'],
1018 variables: {
1019 '--bg-primary': '#fffbeb',
1020 '--bg-secondary': '#f6f0d8',
1021 '--bg-tertiary': '#ece4c6',
1022 '--bg-card': '#fffdf3',
1023 '--bg-hover': '#f2ead0',
1024 '--bg-input': '#fbf6e2',
1025 '--text-primary': '#1f1f1f',
1026 '--text-secondary': '#4f4b38',
1027 '--text-muted': '#6c664b',
1028 '--accent': '#644ac9',
1029 '--accent-light': '#7f63e0',
1030 '--accent-dark': '#4a33a3',
1031 '--border': '#e3dab8',
1032 '--border-light': '#d6cba3',
1033 '--shadow': '0 2px 12px rgba(40,30,0,0.07)',
1034 '--shadow-lg': '0 8px 32px rgba(40,30,0,0.12)',
1035 },
1036 },
1037 {
1038 name: 'Monokai Light',
1039 slug: 'monokai-light',
1040 mode: 'light',
1041 description: 'Monokai\'s greens on a calm off-white page.',
1042 tags: ['developer', 'green', 'classic'],
1043 variables: {
1044 '--bg-primary': '#fafaf6',
1045 '--bg-secondary': '#f1f1ea',
1046 '--bg-tertiary': '#e7e7de',
1047 '--bg-card': '#fdfdfa',
1048 '--bg-hover': '#ecece3',
1049 '--bg-input': '#f6f6f0',
1050 '--text-primary': '#272822',
1051 '--text-secondary': '#4e4f44',
1052 '--text-muted': '#6e6f62',
1053 '--accent': '#5c8a00',
1054 '--accent-light': '#7aa61a',
1055 '--accent-dark': '#436600',
1056 '--border': '#dcdcd0',
1057 '--border-light': '#cdcdbf',
1058 '--shadow': '0 2px 12px rgba(20,20,10,0.07)',
1059 '--shadow-lg': '0 8px 32px rgba(20,20,10,0.12)',
1060 },
1061 },
1062 {
1063 name: 'Shallows',
1064 slug: 'shallows',
1065 mode: 'light',
1066 description: 'Trench in the sun: clear teal water over white sand.',
1067 tags: ['ocean', 'teal', 'calm'],
1068 variables: {
1069 '--bg-primary': '#f0fafb',
1070 '--bg-secondary': '#e4f4f6',
1071 '--bg-tertiary': '#d5ecef',
1072 '--bg-card': '#f7fcfd',
1073 '--bg-hover': '#dcf0f2',
1074 '--bg-input': '#ebf7f8',
1075 '--text-primary': '#0c2226',
1076 '--text-secondary': '#3a5458',
1077 '--text-muted': '#567277',
1078 '--accent': '#00788a',
1079 '--accent-light': '#0097a7',
1080 '--accent-dark': '#005662',
1081 '--border': '#c9e3e7',
1082 '--border-light': '#b4d6db',
1083 '--shadow': '0 2px 12px rgba(0,30,36,0.07)',
1084 '--shadow-lg': '0 8px 32px rgba(0,30,36,0.12)',
1085 },
1086 },
1087 {
1088 name: 'Ash',
1089 slug: 'ash',
1090 mode: 'light',
1091 description: 'Ember cooled to ash: warm greys with a red glow.',
1092 tags: ['red', 'warm', 'soft'],
1093 variables: {
1094 '--bg-primary': '#faf7f6',
1095 '--bg-secondary': '#f3eeec',
1096 '--bg-tertiary': '#eae2df',
1097 '--bg-card': '#fdfbfa',
1098 '--bg-hover': '#efe7e4',
1099 '--bg-input': '#f7f3f1',
1100 '--text-primary': '#231716',
1101 '--text-secondary': '#54403d',
1102 '--text-muted': '#725b57',
1103 '--accent': '#c62828',
1104 '--accent-light': '#e53935',
1105 '--accent-dark': '#8e1b1b',
1106 '--border': '#e2d6d2',
1107 '--border-light': '#d4c4bf',
1108 '--shadow': '0 2px 12px rgba(40,10,8,0.07)',
1109 '--shadow-lg': '0 8px 32px rgba(40,10,8,0.12)',
1110 },
1111 },
1112 {
1113 name: 'Vapor Day',
1114 slug: 'vapor-day',
1115 mode: 'light',
1116 description: 'Vapor in pastel daylight: magenta on lilac.',
1117 tags: ['retro', 'aesthetic', 'pink'],
1118 variables: {
1119 '--bg-primary': '#fcf6ff',
1120 '--bg-secondary': '#f6ecfc',
1121 '--bg-tertiary': '#eedff8',
1122 '--bg-card': '#fefaff',
1123 '--bg-hover': '#f2e5fa',
1124 '--bg-input': '#f9f1fe',
1125 '--text-primary': '#22122c',
1126 '--text-secondary': '#533f5f',
1127 '--text-muted': '#715c7e',
1128 '--accent': '#a21caf',
1129 '--accent-light': '#c026d3',
1130 '--accent-dark': '#701a75',
1131 '--border': '#e6d3f1',
1132 '--border-light': '#dbc0ea',
1133 '--shadow': '0 2px 12px rgba(30,0,40,0.07)',
1134 '--shadow-lg': '0 8px 32px rgba(30,0,40,0.12)',
1135 },
1136 },
1137 {
1138 name: 'Greenbar',
1139 slug: 'greenbar',
1140 mode: 'light',
1141 description: 'Matrix on paper: green-bar printout green on white.',
1142 tags: ['retro', 'terminal', 'green'],
1143 variables: {
1144 '--bg-primary': '#f4faf3',
1145 '--bg-secondary': '#e9f4e7',
1146 '--bg-tertiary': '#dcecd9',
1147 '--bg-card': '#f9fdf8',
1148 '--bg-hover': '#e2f0df',
1149 '--bg-input': '#eff7ed',
1150 '--text-primary': '#0d1f10',
1151 '--text-secondary': '#3b5240',
1152 '--text-muted': '#57705c',
1153 '--accent': '#137a2c',
1154 '--accent-light': '#1f9d3a',
1155 '--accent-dark': '#0b5a1f',
1156 '--border': '#cfe3cb',
1157 '--border-light': '#bcd7b7',
1158 '--shadow': '0 2px 12px rgba(0,30,6,0.07)',
1159 '--shadow-lg': '0 8px 32px rgba(0,30,6,0.12)',
1160 },
1161 },
1162 {
1163 name: 'Nord Light',
1164 slug: 'nord-light',
1165 mode: 'light',
1166 description: 'Nord\'s Snow Storm: frost blues on arctic white.',
1167 tags: ['popular', 'blue', 'soft'],
1168 variables: {
1169 '--bg-primary': '#eceff4',
1170 '--bg-secondary': '#e5e9f0',
1171 '--bg-tertiary': '#d8dee9',
1172 '--bg-card': '#f2f4f8',
1173 '--bg-hover': '#dfe4ec',
1174 '--bg-input': '#e8ecf2',
1175 '--text-primary': '#2e3440',
1176 '--text-secondary': '#434c5e',
1177 '--text-muted': '#4c566a',
1178 '--accent': '#5e81ac',
1179 '--accent-light': '#81a1c1',
1180 '--accent-dark': '#4c6a91',
1181 '--border': '#d0d7e2',
1182 '--border-light': '#c2cad8',
1183 '--shadow': '0 2px 12px rgba(20,26,40,0.07)',
1184 '--shadow-lg': '0 8px 32px rgba(20,26,40,0.12)',
1185 },
1186 },
1187 {
1188 name: 'Bright',
1189 slug: 'bright',
1190 mode: 'light',
1191 description: 'Abyss turned inside out: pure white, ink black, nothing else.',
1192 tags: ['minimal', 'monochrome', 'clean'],
1193 variables: {
1194 '--bg-primary': '#ffffff',
1195 '--bg-secondary': '#f6f6f6',
1196 '--bg-tertiary': '#ececec',
1197 '--bg-card': '#ffffff',
1198 '--bg-hover': '#f0f0f0',
1199 '--bg-input': '#fafafa',
1200 '--text-primary': '#0a0a0a',
1201 '--text-secondary': '#3d3d3d',
1202 '--text-muted': '#5c5c5c',
1203 '--accent': '#111111',
1204 '--accent-light': '#333333',
1205 '--accent-dark': '#000000',
1206 '--border': '#e2e2e2',
1207 '--border-light': '#d0d0d0',
1208 '--shadow': '0 2px 12px rgba(0,0,0,0.07)',
1209 '--shadow-lg': '0 8px 32px rgba(0,0,0,0.12)',
1210 },
1211 },
1212 {
1213 name: 'High Contrast Light',
1214 slug: 'high-contrast-light',
1215 mode: 'light',
1216 description: 'Black on white with strong edges and deep blue links.',
1217 tags: ['accessibility', 'high-contrast', 'bold'],
1218 variables: {
1219 '--bg-primary': '#ffffff',
1220 '--bg-secondary': '#ffffff',
1221 '--bg-tertiary': '#f2f2f2',
1222 '--bg-card': '#ffffff',
1223 '--bg-hover': '#e8e8e8',
1224 '--bg-input': '#ffffff',
1225 '--text-primary': '#000000',
1226 '--text-secondary': '#1a1a1a',
1227 '--text-muted': '#2b2b2b',
1228 '--accent': '#0000c8',
1229 '--accent-light': '#1a1aff',
1230 '--accent-dark': '#00008b',
1231 '--border': '#000000',
1232 '--border-light': '#333333',
1233 '--shadow': '0 2px 12px rgba(0,0,0,0.07)',
1234 '--shadow-lg': '0 8px 32px rgba(0,0,0,0.12)',
1235 },
1236 },
1237 {
1238 name: 'Hayride',
1239 slug: 'hayride',
1240 mode: 'light',
1241 description: 'Halloween in daylight: pumpkin orange on warm hay. Midnight Pumpkin\'s light half.',
1242 tags: ['seasonal', 'halloween', 'orange'],
1243 variables: {
1244 '--bg-primary': '#fbf6ee',
1245 '--bg-secondary': '#f6eee2',
1246 '--bg-tertiary': '#efe3d1',
1247 '--bg-card': '#ffffff',
1248 '--bg-hover': '#f3e9da',
1249 '--bg-input': '#fffaf2',
1250 '--text-primary': '#23180f',
1251 '--text-secondary': '#5a4838',
1252 '--text-muted': '#6b5a4a',
1253 '--accent': '#c2410c',
1254 '--accent-light': '#ea580c',
1255 '--accent-dark': '#9a3412',
1256 '--border': '#eadfcd',
1257 '--border-light': '#e0cfb6',
1258 '--shadow': '0 2px 12px rgba(40,24,8,0.07)',
1259 '--shadow-lg': '0 8px 32px rgba(40,24,8,0.12)',
1260 },
1261 },
1262 {
1263 name: 'Snowfall',
1264 slug: 'snowfall',
1265 mode: 'light',
1266 description: 'Winter in daylight: frosted teal on fresh snow. Frost\'s light half.',
1267 tags: ['seasonal', 'winter', 'teal'],
1268 variables: {
1269 '--bg-primary': '#f2f8fb',
1270 '--bg-secondary': '#e8f2f7',
1271 '--bg-tertiary': '#dbeaf1',
1272 '--bg-card': '#f8fbfd',
1273 '--bg-hover': '#e1eef4',
1274 '--bg-input': '#edf5f9',
1275 '--text-primary': '#0e1d26',
1276 '--text-secondary': '#3b5260',
1277 '--text-muted': '#57707e',
1278 '--accent': '#0f766e',
1279 '--accent-light': '#14b8a6',
1280 '--accent-dark': '#115e59',
1281 '--border': '#cfe2ea',
1282 '--border-light': '#bcd5df',
1283 '--shadow': '0 2px 12px rgba(0,20,30,0.07)',
1284 '--shadow-lg': '0 8px 32px rgba(0,20,30,0.12)',
1285 },
1286 },
1287];
1288
1289// ── Derived tokens ──────────────────────────────────────────────────────────────
1290// Every theme also carries tokens a site cannot guess from the palette alone:
1291// --on-accent text/icon colour that reads on top of --accent (dark ink for light accents
1292// such as cyan or lime, white for deep ones) — chosen by WCAG contrast
1293// --accent-rgb "r,g,b" of the accent, for rgba(var(--accent-rgb), .3) in older stylesheets
1294// --accent-glow the accent at 35% for glows and focus rings
1295// --color-scheme dark|light, so native controls and scrollbars follow the theme
1296// --accent-strong, --on-accent-strong
1297// a filled button's background and its label at 4.5:1 or better (WCAG AA for
1298// normal text): the accent, darkened under white or lightened under dark ink just
1299// enough (the Sign In button; white on #3b82f6 was 3.67:1)
1300// A theme may set any of them explicitly; otherwise they are computed here so all 37 themes,
1301// and community themes that only define the base palette, stay consistent on every site.
1302function hexToRgb(hex) {
1303 const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(hex || '').trim());
1304 if (!m) return null;
1305 let h = m[1]; if (h.length === 3) h = h.split('').map((c) => c + c).join('');
1306 return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
1307}
1308function luminance(rgb) {
1309 const f = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
1310 return 0.2126 * f(rgb[0]) + 0.7152 * f(rgb[1]) + 0.0722 * f(rgb[2]);
1311}
1312function contrast(a, b) { const [x, y] = [luminance(a), luminance(b)]; return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); }
1313const toHex = (rgb) => '#' + rgb.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('');
1314/**
1315 * The accent moved toward black (under white) or white (under dark ink) until the pair reads at 4.5:1.
1316 * The label keeps --on-accent's choice (white wherever it reads at 3:1), so a button keeps its look and
1317 * only its fill deepens.
1318 */
1319function strongPair(accent) {
1320 const WHITE = [255, 255, 255], INK = [11, 13, 16];
1321 const ink = contrast(WHITE, accent) >= 3 ? WHITE : INK;
1322 const toward = ink === WHITE ? [0, 0, 0] : [255, 255, 255];
1323 let bg = accent;
1324 for (let t = 0; t <= 1 && contrast(ink, bg) < 4.5; t += 0.02) bg = accent.map((c, i) => c + (toward[i] - c) * t);
1325 return [toHex(bg), toHex(ink)];
1326}
1327/**
1328 * Text tokens read at 4.5:1 (WCAG AA for normal text) on every surface a theme defines. Sites write body text,
1329 * captions and links in --text-secondary, --text-muted, --accent and --accent-light on --bg-primary, --bg-secondary,
1330 * --bg-tertiary, --bg-card, --bg-input and --bg-hover; in the light themes --accent-light is a pale tint (snow's
1331 * #38bdf8 on white is 2.1:1) and --text-muted sat at 4.1 to 4.4 on the secondary surfaces, so axe failed 50 to 155
1332 * nodes on every site's home in every light theme (2026-10-22). A token below the floor moves toward black (light
1333 * themes) or white (dark ones) just far enough; a token that already reads is left exactly as the theme set it.
1334 */
1335// In the dark themes the accents already read on their surfaces and are the theme's brand colour (the default Vibe
1336// blue is #3b82f6), so only the two text tokens are held to the floor there.
1337const TEXT_TOKENS = { light: ['--text-secondary', '--text-muted', '--accent', '--accent-light'], dark: ['--text-secondary', '--text-muted'] };
1338const SURFACES = ['--bg-primary', '--bg-secondary', '--bg-tertiary', '--bg-card', '--bg-input', '--bg-hover'];
1339// The status colours stay exactly as each theme set them: they are backgrounds too (a live badge under white text, an
1340// amber strip under dark text), and darkening them would spoil those. Each gets a `-text` twin held to the text floor
1341// on every surface, for status words, counts and badges written in the colour (axe in snow, 2026-10-22: --success on
1342// white read at 2.3:1). CSS writes var(--success-text, var(--success)).
1343const STATUS_TOKENS = ['--success', '--warning', '--danger', '--error', '--info', '--live-red'];
1344// A theme that sets no status colours carries the default theme's (theme-loader merges DEFAULT_VARS first), so its twin
1345// is derived from those, on its own surfaces: snow inherits Vibe's #22c55e and needs a darker green for text.
1346const _vibeRaw = (RAW_BUILTIN_THEMES.find((t) => t.slug === 'vibe') || RAW_BUILTIN_THEMES[0]).variables || {};
1347const STATUS_DEFAULTS = Object.fromEntries(STATUS_TOKENS.filter((k) => _vibeRaw[k]).map((k) => [k, _vibeRaw[k]]));
1348function readableText(color, surfaces, mode, min = 4.5) {
1349 const c0 = hexToRgb(color);
1350 if (!c0 || !surfaces.length) return color;
1351 const worst = (c) => Math.min(...surfaces.map((b) => contrast(c, b)));
1352 if (worst(c0) >= min) return color;
1353 const toward = mode === 'light' ? [0, 0, 0] : [255, 255, 255];
1354 let c = c0;
1355 // Measured on the rounded colour the theme will carry, so the hex itself meets the floor.
1356 for (let t = 0.02; t <= 1 && worst(c) < min; t += 0.02) c = hexToRgb(toHex(c0.map((v, i) => v + (toward[i] - v) * t)));
1357 return toHex(c);
1358}
1359
1360function deriveTokens(vars, mode) {
1361 const out = { ...vars };
1362 const surfaces = SURFACES.map((k) => hexToRgb(vars[k])).filter(Boolean);
1363 for (const k of TEXT_TOKENS[mode === 'light' ? 'light' : 'dark']) if (out[k]) out[k] = readableText(out[k], surfaces, mode);
1364 for (const k of STATUS_TOKENS) {
1365 const c = out[k] || STATUS_DEFAULTS[k];
1366 if (c && !out[`${k}-text`]) out[`${k}-text`] = readableText(c, surfaces, mode);
1367 }
1368 const accent = hexToRgb(out['--accent']);
1369 if (accent) {
1370 if (!out['--accent-strong'] || !out['--on-accent-strong']) {
1371 const [bg, ink] = strongPair(accent);
1372 if (!out['--accent-strong']) out['--accent-strong'] = bg;
1373 if (!out['--on-accent-strong']) out['--on-accent-strong'] = ink;
1374 }
1375 // White wherever it reads (>= 3:1, the large-text floor a button label meets); dark ink only
1376 // on accents too light for it (cyan, lime, amber).
1377 if (!out['--on-accent']) out['--on-accent'] = contrast([255, 255, 255], accent) >= 3 ? '#ffffff' : '#0b0d10';
1378 if (!out['--accent-rgb']) out['--accent-rgb'] = accent.join(',');
1379 if (!out['--accent-glow']) out['--accent-glow'] = `rgba(${accent.join(',')},0.35)`;
1380 }
1381 if (!out['--color-scheme']) out['--color-scheme'] = mode === 'light' ? 'light' : 'dark';
1382 return out;
1383}
1384
1385/**
1386 * Every theme has a light and a dark half (the owner, 2026-10-25: "each theme should have a light counterpart instead of
1387 * … a mixed bag"): `pair` names the other one. The account menu's Light / Dark switches to it, and Auto follows the
1388 * device (theme-loader.js). The seasons follow too: Midnight Pumpkin's light half is Hayride, Frost's is Snowfall.
1389 */
1390const PAIRS = [
1391 ['vibe', 'cloud'], ['violet-hour', 'daylight'], ['midnight', 'dawn'], ['forest', 'meadow'], ['neon-tokyo', 'tokyo-day'],
1392 ['dracula', 'alucard'], ['monokai', 'monokai-light'], ['ocean-deep', 'shallows'], ['sunset', 'peach'], ['arctic', 'snow'],
1393 ['ember', 'ash'], ['vapor', 'vapor-day'], ['slate', 'lavender'], ['matrix', 'greenbar'], ['nord', 'nord-light'],
1394 ['gruvbox-dark', 'gruvbox-light'], ['abyss', 'bright'], ['copper', 'sand'], ['sakura-night', 'rose'], ['hacker', 'paper'],
1395 ['solarized-dark', 'solarized-light'], ['catppuccin-mocha', 'catppuccin-latte'], ['high-contrast', 'high-contrast-light'],
1396 ['halloween', 'hayride'], ['frost', 'snowfall'],
1397];
1398const PAIR_OF = Object.fromEntries(PAIRS.flatMap(([dark, light]) => [[dark, light], [light, dark]]));
1399
1400const BUILTIN_THEMES = RAW_BUILTIN_THEMES.map((theme) => ({
1401 id: theme.slug,
1402 pair: PAIR_OF[theme.slug] || null,
1403 ...theme,
1404 tags: Array.isArray(theme.tags) ? [...theme.tags] : [],
1405 variables: deriveTokens(theme.variables || {}, theme.mode),
1406}));
1407
1408const DEFAULT_THEME = BUILTIN_THEMES.find((theme) => theme.id === 'vibe') || BUILTIN_THEMES[0];
1409// The seasons, in calendar order: { id, name, theme, light, from: 'MM-DD', to: 'MM-DD' }: `theme` is the dark half and
1410// `light` its light half (theme-loader.js carries a copy).
1411const SEASONS = BUILTIN_THEMES.filter((theme) => theme.season).map((theme) => ({ id: theme.season.id, name: theme.season.name, theme: theme.id, light: theme.pair, from: theme.season.from, to: theme.season.to }))
1412 .sort((a, b) => a.from.localeCompare(b.from));
1413/** The season `date` falls in (the visitor's local calendar), or null. A season may run over New Year (from > to). */
1414function seasonOn(date = new Date()) {
1415 const md = `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
1416 return SEASONS.find((s) => (s.from <= s.to ? md >= s.from && md <= s.to : md >= s.from || md <= s.to)) || null;
1417}
1418const DEFAULT_VARS = Object.freeze({ ...(DEFAULT_THEME?.variables || {}) });
1419
1420module.exports = {
1421 BUILTIN_THEMES,
1422 PAIRS,
1423 SEASONS,
1424 seasonOn,
1425 DEFAULT_THEME,
1426 DEFAULT_VARS,
1427 deriveTokens,
1428 readableText,
1429 TEXT_TOKENS,
1430 STATUS_TOKENS,
1431 SURFACES,
1432 hexToRgb,
1433 contrast,
1434};
1435