init.go raw
1 package branding
2
3 import (
4 "bytes"
5 "embed"
6 "encoding/json"
7 "fmt"
8 "image"
9 "image/color"
10 "image/png"
11 "io/fs"
12 "math"
13 "os"
14 "path/filepath"
15 )
16
17 // BrandingStyle represents the type of branding kit to generate
18 type BrandingStyle string
19
20 const (
21 StyleORLY BrandingStyle = "orly" // ORLY-branded assets
22 StyleGeneric BrandingStyle = "generic" // Generic/white-label assets
23 )
24
25 // InitBrandingKit creates a branding directory with assets and configuration
26 func InitBrandingKit(dir string, embeddedFS embed.FS, style BrandingStyle) error {
27 // Create directory structure
28 dirs := []string{
29 dir,
30 filepath.Join(dir, "assets"),
31 filepath.Join(dir, "css"),
32 }
33
34 for _, d := range dirs {
35 if err := os.MkdirAll(d, 0755); err != nil {
36 return fmt.Errorf("failed to create directory %s: %w", d, err)
37 }
38 }
39
40 // Write branding.json based on style
41 var config Config
42 var cssTemplate, varsTemplate string
43
44 switch style {
45 case StyleGeneric:
46 config = GenericConfig()
47 cssTemplate = GenericCSSTemplate
48 varsTemplate = GenericCSSVariablesTemplate
49 default:
50 config = DefaultConfig()
51 cssTemplate = CSSTemplate
52 varsTemplate = CSSVariablesTemplate
53 }
54
55 configData, err := json.MarshalIndent(config, "", " ")
56 if err != nil {
57 return fmt.Errorf("failed to marshal config: %w", err)
58 }
59 configPath := filepath.Join(dir, "branding.json")
60 if err := os.WriteFile(configPath, configData, 0644); err != nil {
61 return fmt.Errorf("failed to write branding.json: %w", err)
62 }
63
64 // Generate or extract assets based on style
65 if style == StyleGeneric {
66 // Generate generic placeholder images
67 if err := generateGenericAssets(dir); err != nil {
68 return fmt.Errorf("failed to generate generic assets: %w", err)
69 }
70 } else {
71 // Extract ORLY embedded assets
72 assetMappings := map[string]string{
73 "web/dist/orly.png": filepath.Join(dir, "assets", "logo.png"),
74 "web/dist/favicon.png": filepath.Join(dir, "assets", "favicon.png"),
75 "web/dist/icon-192.png": filepath.Join(dir, "assets", "icon-192.png"),
76 "web/dist/icon-512.png": filepath.Join(dir, "assets", "icon-512.png"),
77 }
78
79 for src, dst := range assetMappings {
80 data, err := fs.ReadFile(embeddedFS, src)
81 if err != nil {
82 altSrc := "web/" + filepath.Base(src)
83 data, err = fs.ReadFile(embeddedFS, altSrc)
84 if err != nil {
85 fmt.Printf("Warning: could not extract %s: %v\n", src, err)
86 continue
87 }
88 }
89 if err := os.WriteFile(dst, data, 0644); err != nil {
90 return fmt.Errorf("failed to write %s: %w", dst, err)
91 }
92 }
93 }
94
95 // Write CSS template
96 cssPath := filepath.Join(dir, "css", "custom.css")
97 if err := os.WriteFile(cssPath, []byte(cssTemplate), 0644); err != nil {
98 return fmt.Errorf("failed to write custom.css: %w", err)
99 }
100
101 // Write variables-only CSS template
102 varsPath := filepath.Join(dir, "css", "variables.css")
103 if err := os.WriteFile(varsPath, []byte(varsTemplate), 0644); err != nil {
104 return fmt.Errorf("failed to write variables.css: %w", err)
105 }
106
107 return nil
108 }
109
110 // generateGenericAssets creates simple geometric placeholder images
111 func generateGenericAssets(dir string) error {
112 // Color scheme: neutral blue-gray
113 primaryColor := color.RGBA{R: 64, G: 128, B: 192, A: 255} // #4080C0 - professional blue
114 transparent := color.RGBA{R: 0, G: 0, B: 0, A: 0} // Transparent background
115
116 // Generate each size
117 sizes := map[string]int{
118 "logo.png": 256,
119 "favicon.png": 64,
120 "icon-192.png": 192,
121 "icon-512.png": 512,
122 }
123
124 for filename, size := range sizes {
125 img := generateRoundedSquare(size, primaryColor, transparent)
126 path := filepath.Join(dir, "assets", filename)
127 if err := savePNG(path, img); err != nil {
128 return fmt.Errorf("failed to save %s: %w", filename, err)
129 }
130 }
131
132 return nil
133 }
134
135 // generateRoundedSquare creates a simple rounded square icon
136 func generateRoundedSquare(size int, primary, bg color.RGBA) image.Image {
137 img := image.NewRGBA(image.Rect(0, 0, size, size))
138
139 // Fill background
140 for y := 0; y < size; y++ {
141 for x := 0; x < size; x++ {
142 img.Set(x, y, bg)
143 }
144 }
145
146 // Draw a rounded square in the center
147 margin := size / 8
148 cornerRadius := size / 6
149 squareSize := size - (margin * 2)
150
151 for y := margin; y < margin+squareSize; y++ {
152 for x := margin; x < margin+squareSize; x++ {
153 // Check if point is inside rounded rectangle
154 if isInsideRoundedRect(x-margin, y-margin, squareSize, squareSize, cornerRadius) {
155 img.Set(x, y, primary)
156 }
157 }
158 }
159
160 // Draw a simple inner circle (relay symbol)
161 centerX := size / 2
162 centerY := size / 2
163 innerRadius := size / 5
164 ringWidth := size / 20
165
166 for y := 0; y < size; y++ {
167 for x := 0; x < size; x++ {
168 dx := float64(x - centerX)
169 dy := float64(y - centerY)
170 dist := math.Sqrt(dx*dx + dy*dy)
171
172 // Ring (circle outline)
173 if dist >= float64(innerRadius-ringWidth) && dist <= float64(innerRadius) {
174 img.Set(x, y, bg)
175 }
176 }
177 }
178
179 return img
180 }
181
182 // isInsideRoundedRect checks if a point is inside a rounded rectangle
183 func isInsideRoundedRect(x, y, w, h, r int) bool {
184 // Check corners
185 if x < r && y < r {
186 // Top-left corner
187 return isInsideCircle(x, y, r, r, r)
188 }
189 if x >= w-r && y < r {
190 // Top-right corner
191 return isInsideCircle(x, y, w-r-1, r, r)
192 }
193 if x < r && y >= h-r {
194 // Bottom-left corner
195 return isInsideCircle(x, y, r, h-r-1, r)
196 }
197 if x >= w-r && y >= h-r {
198 // Bottom-right corner
199 return isInsideCircle(x, y, w-r-1, h-r-1, r)
200 }
201
202 // Inside main rectangle
203 return x >= 0 && x < w && y >= 0 && y < h
204 }
205
206 // isInsideCircle checks if a point is inside a circle
207 func isInsideCircle(x, y, cx, cy, r int) bool {
208 dx := x - cx
209 dy := y - cy
210 return dx*dx+dy*dy <= r*r
211 }
212
213 // savePNG saves an image as a PNG file
214 func savePNG(path string, img image.Image) error {
215 var buf bytes.Buffer
216 if err := png.Encode(&buf, img); err != nil {
217 return err
218 }
219 return os.WriteFile(path, buf.Bytes(), 0644)
220 }
221
222 // GenericConfig returns a generic/white-label configuration
223 func GenericConfig() Config {
224 return Config{
225 Version: 1,
226 App: AppConfig{
227 Name: "Relay",
228 ShortName: "Relay",
229 Title: "Relay Dashboard",
230 Description: "Nostr relay service",
231 },
232 NIP11: NIP11Config{
233 Name: "Relay",
234 Description: "A Nostr relay",
235 Icon: "",
236 },
237 Manifest: ManifestConfig{
238 ThemeColor: "#4080C0",
239 BackgroundColor: "#F0F4F8",
240 },
241 Assets: AssetsConfig{
242 Logo: "assets/logo.png",
243 Favicon: "assets/favicon.png",
244 Icon192: "assets/icon-192.png",
245 Icon512: "assets/icon-512.png",
246 },
247 CSS: CSSConfig{
248 CustomCSS: "css/custom.css",
249 VariablesCSS: "css/variables.css",
250 },
251 }
252 }
253
254 // CSSTemplate is the full CSS template with all variables and documentation
255 const CSSTemplate = `/*
256 * Custom Branding CSS for ORLY Relay
257 * ==================================
258 *
259 * This file is loaded AFTER the default styles, so any rules here
260 * will override the defaults. You can customize:
261 *
262 * 1. CSS Variables (colors, spacing, etc.)
263 * 2. Component styles (buttons, cards, headers, etc.)
264 * 3. Add completely custom styles
265 *
266 * Restart the relay to apply changes.
267 *
268 * For variable-only overrides, edit variables.css instead.
269 */
270
271 /* =============================================================================
272 LIGHT THEME VARIABLES
273 ============================================================================= */
274
275 :root {
276 /* Background colors */
277 --bg-color: #ddd; /* Main page background */
278 --header-bg: #eee; /* Header background */
279 --sidebar-bg: #eee; /* Sidebar background */
280 --card-bg: #f8f9fa; /* Card/container background */
281 --panel-bg: #f8f9fa; /* Panel background */
282
283 /* Border colors */
284 --border-color: #dee2e6; /* Default border color */
285
286 /* Text colors */
287 --text-color: #444444; /* Primary text color */
288 --text-muted: #6c757d; /* Secondary/muted text */
289
290 /* Input/form colors */
291 --input-border: #ccc; /* Input border color */
292 --input-bg: #ffffff; /* Input background */
293 --input-text-color: #495057; /* Input text color */
294
295 /* Button colors */
296 --button-bg: #ddd; /* Default button background */
297 --button-hover-bg: #eee; /* Button hover background */
298 --button-text: #444444; /* Button text color */
299 --button-hover-border: #adb5bd; /* Button hover border */
300
301 /* Theme/accent colors */
302 --primary: #00bcd4; /* Primary accent (cyan) */
303 --primary-bg: rgba(0, 188, 212, 0.1); /* Primary background tint */
304 --secondary: #6c757d; /* Secondary color */
305
306 /* Status colors */
307 --success: #28a745; /* Success/positive */
308 --success-bg: #d4edda; /* Success background */
309 --success-text: #155724; /* Success text */
310 --info: #17a2b8; /* Info/neutral */
311 --warning: #ff3e00; /* Warning (Svelte orange) */
312 --warning-bg: #fff3cd; /* Warning background */
313 --danger: #dc3545; /* Danger/error */
314 --danger-bg: #f8d7da; /* Danger background */
315 --danger-text: #721c24; /* Danger text */
316 --error-bg: #f8d7da; /* Error background */
317 --error-text: #721c24; /* Error text */
318
319 /* Code block colors */
320 --code-bg: #f8f9fa; /* Code block background */
321 --code-text: #495057; /* Code text color */
322
323 /* Tab colors */
324 --tab-inactive-bg: #bbb; /* Inactive tab background */
325
326 /* Link/accent colors */
327 --accent-color: #007bff; /* Link color */
328 --accent-hover-color: #0056b3; /* Link hover color */
329 }
330
331 /* =============================================================================
332 DARK THEME VARIABLES
333 ============================================================================= */
334
335 body.dark-theme {
336 /* Background colors */
337 --bg-color: #263238; /* Main page background */
338 --header-bg: #1e272c; /* Header background */
339 --sidebar-bg: #1e272c; /* Sidebar background */
340 --card-bg: #37474f; /* Card/container background */
341 --panel-bg: #37474f; /* Panel background */
342
343 /* Border colors */
344 --border-color: #404040; /* Default border color */
345
346 /* Text colors */
347 --text-color: #ffffff; /* Primary text color */
348 --text-muted: #adb5bd; /* Secondary/muted text */
349
350 /* Input/form colors */
351 --input-border: #555; /* Input border color */
352 --input-bg: #37474f; /* Input background */
353 --input-text-color: #ffffff; /* Input text color */
354
355 /* Button colors */
356 --button-bg: #263238; /* Default button background */
357 --button-hover-bg: #1e272c; /* Button hover background */
358 --button-text: #ffffff; /* Button text color */
359 --button-hover-border: #6c757d; /* Button hover border */
360
361 /* Theme/accent colors */
362 --primary: #00bcd4; /* Primary accent (cyan) */
363 --primary-bg: rgba(0, 188, 212, 0.2); /* Primary background tint */
364 --secondary: #6c757d; /* Secondary color */
365
366 /* Status colors */
367 --success: #28a745; /* Success/positive */
368 --success-bg: #1e4620; /* Success background (dark) */
369 --success-text: #d4edda; /* Success text (light) */
370 --info: #17a2b8; /* Info/neutral */
371 --warning: #ff3e00; /* Warning (Svelte orange) */
372 --warning-bg: #4d1f00; /* Warning background (dark) */
373 --danger: #dc3545; /* Danger/error */
374 --danger-bg: #4d1319; /* Danger background (dark) */
375 --danger-text: #f8d7da; /* Danger text (light) */
376 --error-bg: #4d1319; /* Error background */
377 --error-text: #f8d7da; /* Error text */
378
379 /* Code block colors */
380 --code-bg: #1e272c; /* Code block background */
381 --code-text: #ffffff; /* Code text color */
382
383 /* Tab colors */
384 --tab-inactive-bg: #1a1a1a; /* Inactive tab background */
385
386 /* Link/accent colors */
387 --accent-color: #007bff; /* Link color */
388 --accent-hover-color: #0056b3; /* Link hover color */
389 }
390
391 /* =============================================================================
392 CUSTOM STYLES
393 Add your custom CSS rules below. These will override any default styles.
394 ============================================================================= */
395
396 /* Example: Custom header styling
397 .header {
398 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
399 }
400 */
401
402 /* Example: Custom button styling
403 .btn {
404 border-radius: 8px;
405 text-transform: uppercase;
406 letter-spacing: 0.5px;
407 }
408 */
409
410 /* Example: Custom card styling
411 .card {
412 border-radius: 12px;
413 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
414 }
415 */
416 `
417
418 // CSSVariablesTemplate contains only CSS variable definitions
419 const CSSVariablesTemplate = `/*
420 * CSS Variables Override for ORLY Relay
421 * ======================================
422 *
423 * This file contains only CSS variable definitions.
424 * Edit values here to customize colors without touching component styles.
425 *
426 * For full CSS customization (including component styles),
427 * edit custom.css instead.
428 */
429
430 /* Light theme variables */
431 :root {
432 --bg-color: #ddd;
433 --header-bg: #eee;
434 --sidebar-bg: #eee;
435 --card-bg: #f8f9fa;
436 --panel-bg: #f8f9fa;
437 --border-color: #dee2e6;
438 --text-color: #444444;
439 --text-muted: #6c757d;
440 --input-border: #ccc;
441 --input-bg: #ffffff;
442 --input-text-color: #495057;
443 --button-bg: #ddd;
444 --button-hover-bg: #eee;
445 --button-text: #444444;
446 --button-hover-border: #adb5bd;
447 --primary: #00bcd4;
448 --primary-bg: rgba(0, 188, 212, 0.1);
449 --secondary: #6c757d;
450 --success: #28a745;
451 --success-bg: #d4edda;
452 --success-text: #155724;
453 --info: #17a2b8;
454 --warning: #ff3e00;
455 --warning-bg: #fff3cd;
456 --danger: #dc3545;
457 --danger-bg: #f8d7da;
458 --danger-text: #721c24;
459 --error-bg: #f8d7da;
460 --error-text: #721c24;
461 --code-bg: #f8f9fa;
462 --code-text: #495057;
463 --tab-inactive-bg: #bbb;
464 --accent-color: #007bff;
465 --accent-hover-color: #0056b3;
466 }
467
468 /* Dark theme variables */
469 body.dark-theme {
470 --bg-color: #263238;
471 --header-bg: #1e272c;
472 --sidebar-bg: #1e272c;
473 --card-bg: #37474f;
474 --panel-bg: #37474f;
475 --border-color: #404040;
476 --text-color: #ffffff;
477 --text-muted: #adb5bd;
478 --input-border: #555;
479 --input-bg: #37474f;
480 --input-text-color: #ffffff;
481 --button-bg: #263238;
482 --button-hover-bg: #1e272c;
483 --button-text: #ffffff;
484 --button-hover-border: #6c757d;
485 --primary: #00bcd4;
486 --primary-bg: rgba(0, 188, 212, 0.2);
487 --secondary: #6c757d;
488 --success: #28a745;
489 --success-bg: #1e4620;
490 --success-text: #d4edda;
491 --info: #17a2b8;
492 --warning: #ff3e00;
493 --warning-bg: #4d1f00;
494 --danger: #dc3545;
495 --danger-bg: #4d1319;
496 --danger-text: #f8d7da;
497 --error-bg: #4d1319;
498 --error-text: #f8d7da;
499 --code-bg: #1e272c;
500 --code-text: #ffffff;
501 --tab-inactive-bg: #1a1a1a;
502 --accent-color: #007bff;
503 --accent-hover-color: #0056b3;
504 }
505 `
506
507 // GenericCSSTemplate is the CSS template for generic/white-label branding
508 const GenericCSSTemplate = `/*
509 * Custom Branding CSS - White Label Template
510 * ==========================================
511 *
512 * This file is loaded AFTER the default styles, so any rules here
513 * will override the defaults. You can customize:
514 *
515 * 1. CSS Variables (colors, spacing, etc.)
516 * 2. Component styles (buttons, cards, headers, etc.)
517 * 3. Add completely custom styles
518 *
519 * Restart the relay to apply changes.
520 *
521 * For variable-only overrides, edit variables.css instead.
522 */
523
524 /* =============================================================================
525 LIGHT THEME VARIABLES - Professional Blue-Gray
526 ============================================================================= */
527
528 html, body {
529 /* Background colors */
530 --bg-color: #F0F4F8; /* Light gray-blue background */
531 --header-bg: #FFFFFF; /* Clean white header */
532 --sidebar-bg: #FFFFFF; /* Clean white sidebar */
533 --card-bg: #FFFFFF; /* White cards */
534 --panel-bg: #FFFFFF; /* White panels */
535
536 /* Border colors */
537 --border-color: #E2E8F0; /* Subtle gray border */
538
539 /* Text colors */
540 --text-color: #334155; /* Dark slate text */
541 --text-muted: #64748B; /* Muted slate */
542
543 /* Input/form colors */
544 --input-border: #CBD5E1; /* Light slate border */
545 --input-bg: #FFFFFF; /* White input */
546 --input-text-color: #334155; /* Dark slate text */
547
548 /* Button colors */
549 --button-bg: #F1F5F9; /* Light slate button */
550 --button-hover-bg: #E2E8F0; /* Slightly darker on hover */
551 --button-text: #334155; /* Dark slate text */
552 --button-hover-border: #94A3B8; /* Medium slate border */
553
554 /* Theme/accent colors - Professional Blue */
555 --primary: #4080C0; /* Professional blue */
556 --primary-bg: rgba(64, 128, 192, 0.1); /* Light blue tint */
557 --secondary: #64748B; /* Slate gray */
558
559 /* Status colors */
560 --success: #22C55E; /* Green */
561 --success-bg: #DCFCE7; /* Light green */
562 --success-text: #166534; /* Dark green */
563 --info: #3B82F6; /* Blue */
564 --warning: #F59E0B; /* Amber */
565 --warning-bg: #FEF3C7; /* Light amber */
566 --danger: #EF4444; /* Red */
567 --danger-bg: #FEE2E2; /* Light red */
568 --danger-text: #991B1B; /* Dark red */
569 --error-bg: #FEE2E2; /* Light red */
570 --error-text: #991B1B; /* Dark red */
571
572 /* Code block colors */
573 --code-bg: #F8FAFC; /* Very light slate */
574 --code-text: #334155; /* Dark slate */
575
576 /* Tab colors */
577 --tab-inactive-bg: #E2E8F0; /* Light slate */
578
579 /* Link/accent colors */
580 --accent-color: #4080C0; /* Professional blue */
581 --accent-hover-color: #2563EB; /* Darker blue */
582 }
583
584 /* =============================================================================
585 DARK THEME VARIABLES - Professional Dark
586 ============================================================================= */
587
588 body.dark-theme {
589 /* Background colors */
590 --bg-color: #0F172A; /* Dark navy */
591 --header-bg: #1E293B; /* Slate gray */
592 --sidebar-bg: #1E293B; /* Slate gray */
593 --card-bg: #1E293B; /* Slate gray */
594 --panel-bg: #1E293B; /* Slate gray */
595
596 /* Border colors */
597 --border-color: #334155; /* Medium slate */
598
599 /* Text colors */
600 --text-color: #F8FAFC; /* Almost white */
601 --text-muted: #94A3B8; /* Muted slate */
602
603 /* Input/form colors */
604 --input-border: #475569; /* Slate border */
605 --input-bg: #1E293B; /* Slate background */
606 --input-text-color: #F8FAFC; /* Light text */
607
608 /* Button colors */
609 --button-bg: #334155; /* Slate button */
610 --button-hover-bg: #475569; /* Lighter on hover */
611 --button-text: #F8FAFC; /* Light text */
612 --button-hover-border: #64748B; /* Medium slate */
613
614 /* Theme/accent colors */
615 --primary: #60A5FA; /* Lighter blue for dark mode */
616 --primary-bg: rgba(96, 165, 250, 0.2); /* Blue tint */
617 --secondary: #94A3B8; /* Muted slate */
618
619 /* Status colors */
620 --success: #4ADE80; /* Bright green */
621 --success-bg: #166534; /* Dark green */
622 --success-text: #DCFCE7; /* Light green */
623 --info: #60A5FA; /* Light blue */
624 --warning: #FBBF24; /* Bright amber */
625 --warning-bg: #78350F; /* Dark amber */
626 --danger: #F87171; /* Light red */
627 --danger-bg: #7F1D1D; /* Dark red */
628 --danger-text: #FEE2E2; /* Light red */
629 --error-bg: #7F1D1D; /* Dark red */
630 --error-text: #FEE2E2; /* Light red */
631
632 /* Code block colors */
633 --code-bg: #0F172A; /* Dark navy */
634 --code-text: #F8FAFC; /* Light text */
635
636 /* Tab colors */
637 --tab-inactive-bg: #1E293B; /* Slate */
638
639 /* Link/accent colors */
640 --accent-color: #60A5FA; /* Light blue */
641 --accent-hover-color: #93C5FD; /* Lighter blue */
642 }
643
644 /* =============================================================================
645 PRIMARY BUTTON TEXT COLOR FIX
646 Ensures buttons with primary background have white text for contrast
647 ============================================================================= */
648
649 /* Target all common button patterns that use primary background */
650 button[class*="-btn"],
651 button[class*="submit"],
652 button[class*="action"],
653 button[class*="save"],
654 button[class*="add"],
655 button[class*="create"],
656 button[class*="connect"],
657 button[class*="refresh"],
658 button[class*="retry"],
659 button[class*="send"],
660 button[class*="apply"],
661 button[class*="execute"],
662 button[class*="run"],
663 .primary-action,
664 .action-button,
665 .permission-badge,
666 [class*="badge"] {
667 color: #FFFFFF !important;
668 }
669
670 /* More specific override for any button that visually appears to have primary bg */
671 /* This uses a broad selector with low impact on non-primary buttons */
672 html:not(.dark-theme) button:not([disabled]) {
673 /* Default to inherit, primary buttons will be caught above */
674 }
675
676 /* =============================================================================
677 CUSTOM STYLES
678 Add your custom CSS rules below. These will override any default styles.
679 ============================================================================= */
680
681 /* Example: Custom header styling
682 .header {
683 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
684 }
685 */
686
687 /* Example: Custom button styling
688 .btn {
689 border-radius: 6px;
690 font-weight: 500;
691 }
692 */
693
694 /* Example: Custom card styling
695 .card {
696 border-radius: 8px;
697 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
698 }
699 */
700 `
701
702 // GenericCSSVariablesTemplate contains CSS variables for generic/white-label branding
703 const GenericCSSVariablesTemplate = `/*
704 * CSS Variables Override - White Label Template
705 * ==============================================
706 *
707 * This file contains only CSS variable definitions.
708 * Edit values here to customize colors without touching component styles.
709 *
710 * For full CSS customization (including component styles),
711 * edit custom.css instead.
712 */
713
714 /* Light theme variables - Professional Blue-Gray */
715 /* Applied to both html and body for maximum compatibility */
716 html, body {
717 --bg-color: #F0F4F8;
718 --header-bg: #FFFFFF;
719 --sidebar-bg: #FFFFFF;
720 --card-bg: #FFFFFF;
721 --panel-bg: #FFFFFF;
722 --border-color: #E2E8F0;
723 --text-color: #334155;
724 --text-muted: #64748B;
725 --input-border: #CBD5E1;
726 --input-bg: #FFFFFF;
727 --input-text-color: #334155;
728 --button-bg: #F1F5F9;
729 --button-hover-bg: #E2E8F0;
730 --button-text: #334155;
731 --button-hover-border: #94A3B8;
732 --primary: #4080C0;
733 --primary-bg: rgba(64, 128, 192, 0.1);
734 --secondary: #64748B;
735 --success: #22C55E;
736 --success-bg: #DCFCE7;
737 --success-text: #166534;
738 --info: #3B82F6;
739 --warning: #F59E0B;
740 --warning-bg: #FEF3C7;
741 --danger: #EF4444;
742 --danger-bg: #FEE2E2;
743 --danger-text: #991B1B;
744 --error-bg: #FEE2E2;
745 --error-text: #991B1B;
746 --code-bg: #F8FAFC;
747 --code-text: #334155;
748 --tab-inactive-bg: #E2E8F0;
749 --accent-color: #4080C0;
750 --accent-hover-color: #2563EB;
751 }
752
753 /* Dark theme variables - Professional Dark */
754 body.dark-theme {
755 --bg-color: #0F172A;
756 --header-bg: #1E293B;
757 --sidebar-bg: #1E293B;
758 --card-bg: #1E293B;
759 --panel-bg: #1E293B;
760 --border-color: #334155;
761 --text-color: #F8FAFC;
762 --text-muted: #94A3B8;
763 --input-border: #475569;
764 --input-bg: #1E293B;
765 --input-text-color: #F8FAFC;
766 --button-bg: #334155;
767 --button-hover-bg: #475569;
768 --button-text: #F8FAFC;
769 --button-hover-border: #64748B;
770 --primary: #60A5FA;
771 --primary-bg: rgba(96, 165, 250, 0.2);
772 --secondary: #94A3B8;
773 --success: #4ADE80;
774 --success-bg: #166534;
775 --success-text: #DCFCE7;
776 --info: #60A5FA;
777 --warning: #FBBF24;
778 --warning-bg: #78350F;
779 --danger: #F87171;
780 --danger-bg: #7F1D1D;
781 --danger-text: #FEE2E2;
782 --error-bg: #7F1D1D;
783 --error-text: #FEE2E2;
784 --code-bg: #0F172A;
785 --code-text: #F8FAFC;
786 --tab-inactive-bg: #1E293B;
787 --accent-color: #60A5FA;
788 --accent-hover-color: #93C5FD;
789 }
790 `
791