1// Package theme provides GTK CSS styling for UI components.
2package theme
3
4import (
5 "fmt"
6 "strings"
7)
8
9// GenerateCSS creates GTK4 CSS using the provided palette with default scale.
10func GenerateCSS(p Palette) string {
11 return GenerateCSSWithScale(p, 1.0)
12}
13
14// GenerateCSSWithScale creates GTK4 CSS using the provided palette and UI scale factor.
15// Scale affects font sizes and widget padding/margins proportionally.
16func GenerateCSSWithScale(p Palette, scale float64) string {
17 return GenerateCSSWithScaleAndFonts(p, scale, DefaultFontConfig())
18}
19
20// FontConfig holds font settings for CSS generation.
21type FontConfig struct {
22 SansFont string
23 MonospaceFont string
24}
25
26// DefaultFontConfig returns safe font fallbacks.
27func DefaultFontConfig() FontConfig {
28 return FontConfig{
29 SansFont: "sans-serif",
30 MonospaceFont: "monospace",
31 }
32}
33
34// FontCSSVars generates CSS custom properties for fonts.
35func FontCSSVars(fonts FontConfig) string {
36 var sb strings.Builder
37 // Quote the configured family; include generic fallback.
38 sb.WriteString(fmt.Sprintf(" --font-sans: %q, sans-serif;\n", fonts.SansFont))
39 sb.WriteString(fmt.Sprintf(" --font-mono: %q, monospace;\n", fonts.MonospaceFont))
40 return sb.String()
41}
42
43func generateFontCSS() string {
44 return `/* Font styling */
45window, tooltip, popover {
46 font-family: var(--font-sans);
47}
48`
49}
50
51// GenerateCSSWithScaleAndFonts creates GTK4 CSS using the provided palette, UI scale factor and fonts.
52// Scale affects font sizes and widget padding/margins proportionally.
53// Uses default mode colors.
54func GenerateCSSWithScaleAndFonts(p Palette, scale float64, fonts FontConfig) string {
55 return GenerateCSSFull(p, scale, fonts, DefaultModeColors())
56}
57
58// GenerateCSSFull creates GTK4 CSS using all provided configuration.
59// Scale affects font sizes and widget padding/margins proportionally.
60func GenerateCSSFull(p Palette, scale float64, fonts FontConfig, modeColors ModeColors) string {
61 if scale <= 0 {
62 scale = 1.0
63 }
64
65 defaults := DefaultFontConfig()
66 fonts = FontConfig{
67 SansFont: Coalesce(fonts.SansFont, defaults.SansFont),
68 MonospaceFont: Coalesce(fonts.MonospaceFont, defaults.MonospaceFont),
69 }
70
71 var sb strings.Builder
72
73 // CSS custom properties (variables) - GTK4 uses :root selector
74 sb.WriteString("/* Theme variables */\n")
75 sb.WriteString(":root {\n")
76 sb.WriteString(p.ToCSSVars())
77 sb.WriteString(FontCSSVars(fonts))
78 sb.WriteString(modeColors.ToCSSVars())
79 sb.WriteString("}\n\n")
80
81 // Global font styling
82 sb.WriteString(generateFontCSS())
83 sb.WriteString("\n")
84
85 // Global UI scaling via font-size on all widgets
86 if scale != 1.0 {
87 sb.WriteString(generateScalingCSS(scale))
88 sb.WriteString("\n")
89 }
90
91 // Tab bar styling
92 sb.WriteString(generateTabBarCSS(p))
93 sb.WriteString("\n")
94
95 // Omnibox styling
96 sb.WriteString(generateOmniboxCSS(p))
97 sb.WriteString("\n")
98
99 // Find bar styling
100 sb.WriteString(generateFindBarCSS(p))
101 sb.WriteString("\n")
102
103 // Pane styling
104 sb.WriteString(generatePaneCSS(p))
105 sb.WriteString("\n")
106
107 // Stacked pane styling
108 sb.WriteString(generateStackedPaneCSS(p))
109 sb.WriteString("\n")
110
111 // Progress bar styling
112 sb.WriteString(generateProgressBarCSS(p))
113 sb.WriteString("\n")
114
115 // Toaster styling
116 sb.WriteString(generateToasterCSS(p))
117 sb.WriteString("\n")
118
119 // Link status overlay styling
120 sb.WriteString(generateLinkStatusCSS(p))
121 sb.WriteString("\n")
122
123 // Session manager styling
124 sb.WriteString(generateSessionManagerCSS(p))
125 sb.WriteString("\n")
126
127 // Tab picker styling
128 sb.WriteString(generateTabPickerCSS(p))
129 sb.WriteString("\n")
130
131 // Accent picker styling
132 sb.WriteString(generateAccentPickerCSS(p))
133
134 return sb.String()
135}
136
137// generateScalingCSS creates CSS rules that scale the UI.
138// GTK4 CSS doesn't support transform:scale() well, so we scale font-size
139// and use relative units (em) for padding/margins in other rules.
140func generateScalingCSS(scale float64) string {
141 // Use 16px base so em conversions are correct:
142 // 1px = 0.0625em (1/16), 4px = 0.25em, etc.
143 basePx := int(16 * scale)
144 return fmt.Sprintf(`/* UI Scaling (%.0f%%) */
145window {
146 font-size: %dpx;
147}
148`, scale*100, basePx)
149}
150
151// generateTabBarCSS creates tab bar styles.
152// Uses em units for scalable UI.
153func generateTabBarCSS(p Palette) string {
154 return `/* Tab bar styling */
155.tab-bar {
156 background-color: var(--surface);
157 border-top: 0.125em solid var(--border);
158 padding: 0;
159 min-height: 2em;
160}
161
162/* Tab button styling */
163button.tab-button {
164 background-color: var(--surface-variant);
165 background-image: none;
166 border: none;
167 border-right: 0.0625em solid var(--border);
168 border-radius: 0;
169 padding: 0.25em 0.5em;
170 transition: background-color 200ms ease-in-out;
171}
172
173button.tab-button:hover {
174 background-color: shade(var(--surface-variant), 1.2);
175}
176
177button.tab-button.tab-button-active {
178 background-color: shade(var(--surface-variant), 1.2);
179 border-bottom: 0.125em solid var(--accent);
180 font-weight: 600;
181}
182
183/* Tab title text */
184.tab-title {
185 font-size: 0.75em;
186 color: var(--text);
187 font-weight: 500;
188}
189`
190}
191
192// generateOmniboxCSS creates omnibox styles.
193// Uses em units for scalable UI.
194func generateOmniboxCSS(p Palette) string {
195 return `/* ===== Omnibox Styling ===== */
196
197/* Omnibox outer container - for positioning in overlay */
198.omnibox-outer {
199 /* Positioning is handled via SetHalign/SetValign/SetMarginTop in Go */
200}
201
202/* Omnibox main container - the visible popup */
203.omnibox-container {
204 background-color: var(--surface-variant);
205 border: 0.0625em solid var(--border);
206 border-radius: 0.1875em;
207 padding: 0;
208}
209
210/* Header with History/Favorites toggle */
211.omnibox-header {
212 background-color: shade(var(--surface-variant), 1.1);
213 border-bottom: 0.0625em solid var(--border);
214 padding: 0.375em 0.75em;
215}
216
217.omnibox-header-btn {
218 background-color: transparent;
219 background-image: none;
220 border: none;
221 border-radius: 0.125em;
222 padding: 0.25em 0.75em;
223 margin-right: 0.5em;
224 font-size: 0.8125em;
225 font-weight: 500;
226 color: var(--muted);
227 transition: all 100ms ease-in-out;
228}
229
230.omnibox-header-btn:hover {
231 background-color: alpha(var(--accent), 0.15);
232 color: var(--text);
233}
234
235.omnibox-header-btn.omnibox-header-active {
236 background-color: alpha(var(--accent), 0.2);
237 color: var(--accent);
238 font-weight: 600;
239}
240
241/* Search entry field */
242.omnibox-entry {
243 background-color: var(--bg);
244 color: var(--text);
245 border: 0.0625em solid var(--border);
246 border-radius: 0.125em;
247 padding: 0.625em 0.75em;
248 margin: 0.5em 0.75em;
249 font-size: 1em;
250 caret-color: var(--accent);
251}
252
253entry.omnibox-entry:focus,
254entry.omnibox-entry:focus-within,
255entry.omnibox-entry:focus-visible {
256 border-color: var(--accent);
257 background-color: shade(var(--bg), 1.05);
258 outline-style: none;
259 outline-width: 0px;
260 outline-color: transparent;
261}
262
263/* Override any internal focus styling */
264entry.omnibox-entry > *:focus,
265entry.omnibox-entry > *:focus-visible {
266 outline-style: none;
267 outline-color: transparent;
268}
269
270/* Scrolled window for suggestions */
271.omnibox-scrolled {
272 background-color: shade(var(--surface-variant), 0.95);
273 border-top: 0.0625em solid var(--border);
274}
275
276/* List box */
277.omnibox-listbox {
278 background-color: transparent;
279}
280
281/* Suggestion rows */
282.omnibox-row {
283 padding: 0.5em 0.75em;
284 margin: 0;
285 border-radius: 0;
286 border-left: 0.1875em solid transparent;
287 border-bottom: 0.0625em solid alpha(var(--border), 0.5);
288 transition: background-color 100ms ease-in-out, border-left 100ms ease-in-out;
289 min-height: 2.75em;
290}
291
292.omnibox-row:last-child {
293 border-bottom: none;
294}
295
296.omnibox-row:hover {
297 background-color: alpha(var(--accent), 0.12);
298 border-left: 0.1875em solid var(--accent);
299}
300
301.omnibox-row:selected {
302 background-color: alpha(var(--accent), 0.2);
303 border-left: 0.1875em solid var(--accent);
304}
305
306/* Favorite indicator in history mode - yellowish accent */
307.omnibox-row.omnibox-row-favorite {
308 background-color: alpha(var(--warning), 0.08);
309 border-left: 0.1875em solid var(--warning);
310}
311
312.omnibox-row.omnibox-row-favorite:hover {
313 background-color: alpha(var(--warning), 0.15);
314 border-left: 0.1875em solid var(--warning);
315}
316
317.omnibox-row.omnibox-row-favorite:selected {
318 background-color: alpha(var(--warning), 0.2);
319 border-left: 0.1875em solid var(--warning);
320}
321
322/* Favicon in omnibox rows */
323.omnibox-favicon {
324 min-width: 1em;
325 min-height: 1em;
326 margin-right: 0.5em;
327}
328
329/* Suggestion title/URL */
330.omnibox-suggestion-title {
331 font-size: 0.875em;
332 color: var(--text);
333 font-weight: 400;
334}
335
336/* Also style labels inside omnibox rows directly */
337.omnibox-row label {
338 color: var(--text);
339}
340
341.omnibox-row:selected .omnibox-suggestion-title {
342 color: var(--text);
343 font-weight: 500;
344}
345
346/* URL text below title - use .omnibox-row prefix for specificity over .omnibox-row label */
347.omnibox-row .omnibox-suggestion-url {
348 font-size: 0.75em;
349 color: alpha(var(--muted), 0.65);
350 font-weight: 400;
351}
352
353.omnibox-row:selected .omnibox-suggestion-url {
354 color: alpha(var(--muted), 0.75);
355}
356
357/* Keyboard shortcut badge */
358.omnibox-shortcut-badge {
359 background-color: alpha(var(--muted), 0.3);
360 color: var(--muted);
361 border-radius: 0.25em;
362 padding: 0.125em 0.375em;
363 font-size: 0.625em;
364 font-weight: 500;
365 font-family: var(--font-mono);
366 margin-left: 0.5em;
367}
368
369.omnibox-row:hover .omnibox-shortcut-badge {
370 background-color: alpha(var(--accent), 0.2);
371 color: var(--accent);
372}
373
374.omnibox-row:selected .omnibox-shortcut-badge {
375 background-color: alpha(var(--accent), 0.3);
376 color: var(--accent);
377}
378
379/* Bang indicator badge in omnibox header */
380.omnibox-bang-badge {
381 background-color: alpha(var(--accent), 0.18);
382 color: var(--accent);
383 border-radius: 0.25em;
384 padding: 0.125em 0.5em;
385 font-size: 0.6875em;
386 font-weight: 600;
387}
388
389/* Zoom indicator in omnibox header */
390.omnibox-zoom-indicator {
391 background-color: alpha(var(--accent), 0.2);
392 color: var(--accent);
393 border-radius: 0.25em;
394 padding: 0.125em 0.5em;
395 font-size: 0.6875em;
396 font-weight: 600;
397 /* Note: margin-left: auto not supported in GTK4 CSS, use SetHalign(End) in code */
398}
399
400entry.omnibox-entry.omnibox-entry-bang-active,
401entry.omnibox-entry.omnibox-entry-bang-active:focus,
402entry.omnibox-entry.omnibox-entry-bang-active:focus-within,
403entry.omnibox-entry.omnibox-entry-bang-active:focus-visible {
404 border-color: var(--accent);
405 background-color: shade(var(--bg), 1.05);
406}
407
408.omnibox-row.omnibox-row-bang .omnibox-suggestion-title {
409 color: var(--accent);
410 font-weight: 600;
411}
412`
413}
414
415// generateFindBarCSS creates find bar styles.
416// Uses em units for scalable UI.
417func generateFindBarCSS(p Palette) string {
418 _ = p
419 return `/* ===== Find Bar Styling ===== */
420.find-bar-outer {
421 margin: 0.5em;
422}
423
424.find-bar-container {
425 background-color: var(--surface-variant);
426 border: 0.0625em solid var(--border);
427 border-radius: 0.25em;
428 box-shadow: 0 0.25em 0.75em alpha(black, 0.2);
429 padding: 0.5em;
430 min-width: 20em;
431}
432
433.find-bar-input-row {
434 margin-bottom: 0.25em;
435}
436
437.find-bar-entry {
438 background-color: var(--bg);
439 color: var(--text);
440 border: 0.0625em solid var(--border);
441 border-radius: 0.1875em;
442 padding: 0.4em 0.5em;
443 caret-color: var(--accent);
444}
445
446entry.find-bar-entry:focus,
447entry.find-bar-entry:focus-within,
448entry.find-bar-entry:focus-visible {
449 border-color: var(--accent);
450 outline-style: none;
451 outline-width: 0px;
452 outline-color: transparent;
453}
454
455.find-bar-entry.not-found {
456 background-color: alpha(#ef4444, 0.15);
457 border-color: #ef4444;
458}
459
460.find-bar-count {
461 font-size: 0.8em;
462 color: var(--muted);
463 min-width: 3.5em;
464}
465
466.find-bar-count.find-bar-count-has {
467 color: var(--accent);
468}
469
470.find-bar-nav,
471.find-bar-close,
472.find-bar-toggle {
473 background-image: none;
474 background-color: alpha(var(--bg), 0.6);
475 border: 0.0625em solid var(--border);
476 border-radius: 0.1875em;
477 padding: 0.25em 0.5em;
478 transition: background-color 120ms ease-in-out, border-color 120ms ease-in-out;
479}
480
481.find-bar-nav:hover,
482.find-bar-close:hover,
483.find-bar-toggle:hover {
484 background-color: alpha(var(--accent), 0.15);
485}
486
487.find-bar-toggle:checked {
488 background-color: alpha(var(--accent), 0.2);
489 border-color: var(--accent);
490 color: var(--accent);
491}
492`
493}
494
495// generatePaneCSS creates pane border styles.
496// Uses em units for scalable UI.
497func generatePaneCSS(p Palette) string {
498 return `/* ===== Pane Styling ===== */
499
500/* Pane overlay container - theme background prevents white flash */
501.pane-overlay {
502 background-color: var(--bg);
503}
504
505/* WebView widget - theme background prevents white flash during page load */
506.webview-themed {
507 background-color: var(--bg);
508}
509
510/* Loading skeleton - shown until WebView paints */
511.loading-skeleton {
512 background-color: var(--bg);
513}
514
515.loading-skeleton-spinner {
516 -gtk-icon-size: 32px;
517 min-width: 32px;
518 min-height: 32px;
519 color: var(--muted);
520}
521
522.loading-skeleton-logo {
523 opacity: 0.08;
524 min-width: 512px;
525 min-height: 512px;
526}
527
528/* Pane border - default transparent */
529.pane-border {
530 border: 0.0625em solid transparent;
531}
532
533/* Active pane - accent border */
534.pane-active {
535 border: 0.0625em solid var(--accent);
536}
537
538/* Hide active border when only one pane exists */
539.single-pane .pane-active {
540 border-color: transparent;
541}
542
543/* Pane mode active - thick inset border (for overlay) */
544.pane-mode-active {
545 background-color: transparent;
546 box-shadow: inset 0 0 0 0.25em var(--pane-mode-color);
547 border-radius: 0;
548}
549
550/* Tab mode active - thick inset border (for overlay) */
551.tab-mode-active {
552 background-color: transparent;
553 box-shadow: inset 0 0 0 0.25em var(--tab-mode-color);
554 border-radius: 0;
555}
556
557/* Resize mode active - thick inset border */
558.resize-mode-active {
559 background-color: transparent;
560 /* Prefer inset shadow, but also set outline for widgets that don't paint shadows */
561 box-shadow: inset 0 0 0 0.25em var(--resize-mode-color);
562 outline: 0.25em solid var(--resize-mode-color);
563 outline-offset: -0.25em;
564 border-radius: 0;
565}
566`
567}
568
569// generateStackedPaneCSS creates stacked pane (Zellij-style tabs within panes) styles.
570// Uses em units for scalable UI.
571func generateStackedPaneCSS(p Palette) string {
572 return `/* ===== Stacked Pane Styling ===== */
573
574/* Title bar for inactive panes in a stack */
575/* The title bar is now a Box with GestureClick, not wrapped in a button */
576.stacked-pane-titlebar {
577 background-color: var(--surface-variant);
578 border-bottom: 0.0625em solid var(--border);
579 padding: 0.25em 0.5em;
580 min-height: 1.5em;
581}
582
583/* Clickable title bar - hover styling */
584.stacked-pane-titlebar.stacked-pane-title-clickable {
585 transition: background-color 150ms ease-in-out;
586}
587
588.stacked-pane-titlebar.stacked-pane-title-clickable:hover {
589 background-color: shade(var(--surface-variant), 1.15);
590}
591
592/* Title bar content box */
593.stacked-pane-titlebar.active {
594 background-color: shade(var(--surface-variant), 1.2);
595 border-left: 0.1875em solid var(--accent);
596}
597
598/* Favicon image in title bar */
599.stacked-pane-titlebar image {
600 min-width: 1em;
601 min-height: 1em;
602 margin-right: 0.375em;
603}
604
605/* Title text in title bar */
606.stacked-pane-titlebar label {
607 color: var(--text);
608 font-size: 0.75em;
609 font-weight: 400;
610}
611
612/* Hover effect on title bar label */
613.stacked-pane-titlebar.stacked-pane-title-clickable:hover label {
614 color: var(--accent);
615}
616
617/* Close button in stacked pane title bar */
618.stacked-pane-close-button {
619 background: transparent;
620 border: none;
621 border-radius: 4px;
622 padding: 4px;
623 margin: 0 2px;
624 min-width: 20px;
625 min-height: 20px;
626 opacity: 0.4;
627 transition: opacity 100ms ease-in-out, background-color 100ms ease-in-out;
628}
629
630.stacked-pane-close-button > image {
631 margin: 0;
632 padding: 0;
633}
634
635.stacked-pane-close-button:hover {
636 opacity: 1.0;
637 background: alpha(var(--destructive), 0.2);
638}
639
640.stacked-pane-close-button:hover > image {
641 color: var(--destructive);
642}
643
644/* Show close button more prominently when hovering title bar */
645.stacked-pane-titlebar.stacked-pane-title-clickable:hover .stacked-pane-close-button {
646 opacity: 0.7;
647}
648
649.stacked-pane-close-button:focus {
650 outline: none;
651 box-shadow: none;
652}
653`
654}
655
656// generateProgressBarCSS creates progress bar styles for page loading indication.
657func generateProgressBarCSS(p Palette) string {
658 return `/* ===== Progress Bar Styling ===== */
659
660/* Native GtkProgressBar with osd class */
661progressbar.osd {
662 min-height: 4px;
663}
664
665progressbar.osd trough {
666 min-height: 4px;
667 min-width: 2px;
668 margin: 0;
669 padding: 0;
670 background-color: alpha(var(--bg), 0.3);
671}
672
673progressbar.osd progress {
674 min-height: 4px;
675 min-width: 2px;
676 margin: 0;
677 padding: 0;
678 background-color: var(--accent);
679}
680`
681}
682
683// generateSessionManagerCSS creates session manager modal styles.
684// Uses em units for scalable UI, matches omnibox styling patterns.
685func generateSessionManagerCSS(p Palette) string {
686 _ = p
687 return `/* ===== Session Manager Styling ===== */
688
689/* Session manager outer container - for positioning in overlay */
690.session-manager-outer {
691 /* Positioning is handled via SetHalign/SetValign in Go */
692}
693
694/* Session manager main container - the visible popup */
695.session-manager-container {
696 background-color: var(--surface-variant);
697 border: 0.0625em solid var(--border);
698 border-radius: 0.1875em;
699 padding: 0;
700 min-width: 28em;
701 /* Note: max-width not supported in GTK4 CSS, rely on container constraints */
702}
703
704/* Header with title */
705.session-manager-header {
706 background-color: shade(var(--surface-variant), 1.1);
707 border-bottom: 0.0625em solid var(--border);
708 padding: 0.5em 0.75em;
709}
710
711.session-manager-title {
712 font-size: 0.9375em;
713 font-weight: 600;
714 color: var(--text);
715}
716
717/* Scrolled window for session list */
718.session-manager-scrolled {
719 background-color: shade(var(--surface-variant), 0.95);
720}
721
722/* List box */
723.session-manager-list {
724 background-color: transparent;
725}
726
727/* Session rows */
728.session-manager-row {
729 padding: 0.5em 0.75em;
730 margin: 0;
731 border-radius: 0;
732 border-left: 0.1875em solid transparent;
733 border-bottom: 0.0625em solid alpha(var(--border), 0.5);
734 transition: background-color 100ms ease-in-out, border-left 100ms ease-in-out;
735 min-height: 2.75em;
736}
737
738.session-manager-row:last-child {
739 border-bottom: none;
740}
741
742.session-manager-row:hover {
743 background-color: alpha(var(--accent), 0.12);
744 border-left: 0.1875em solid var(--accent);
745}
746
747.session-manager-row:selected,
748row:selected .session-manager-row {
749 background-color: alpha(var(--accent), 0.2);
750 border-left: 0.1875em solid var(--accent);
751}
752
753/* Status indicator dot */
754.session-status {
755 font-size: 0.875em;
756 min-width: 1em;
757 margin-right: 0.5em;
758}
759
760/* Current session - accent color dot */
761.session-current .session-status {
762 color: var(--accent);
763}
764
765/* Active session (other instance) - muted dot */
766.session-active .session-status {
767 color: var(--muted);
768}
769
770/* Exited session - dimmed appearance with subtle stop icon */
771.session-exited {
772 opacity: 0.75;
773}
774
775.session-exited .session-status {
776 color: var(--error);
777 opacity: 0.5;
778}
779
780/* Session ID labels */
781.session-id-short {
782 font-size: 0.875em;
783 font-weight: 600;
784 color: var(--accent);
785 font-family: var(--font-mono);
786}
787
788.session-id-prefix {
789 font-size: 0.875em;
790 font-weight: 400;
791 color: var(--muted);
792 font-family: var(--font-mono);
793}
794
795.session-id-badge {
796 font-size: 0.75em;
797 font-weight: 500;
798 color: var(--accent);
799 margin-left: 0.5em;
800}
801
802/* Tab/pane count label */
803.session-count {
804 font-size: 0.75em;
805 color: var(--muted);
806}
807
808/* Relative time label */
809.session-time {
810 font-size: 0.75em;
811 color: var(--muted);
812 /* Note: margin-left: auto not supported in GTK4 CSS, use SetHalign(End) in code */
813}
814
815/* Section divider for EXITED sessions */
816.session-divider {
817 font-size: 0.6875em;
818 font-weight: 600;
819 color: var(--muted);
820 text-transform: uppercase;
821 letter-spacing: 0.05em;
822 padding: 0.5em 0.75em 0.25em 0.75em;
823 background-color: alpha(var(--border), 0.3);
824 border-bottom: 0.0625em solid var(--border);
825}
826
827/* Footer with keyboard shortcuts */
828.session-manager-footer {
829 background-color: shade(var(--surface-variant), 0.9);
830 border-top: 0.0625em solid var(--border);
831 padding: 0.375em 0.75em;
832 font-size: 0.6875em;
833 color: var(--muted);
834 font-family: var(--font-mono);
835}
836
837/* Session mode border */
838.session-mode-active {
839 background-color: transparent;
840 box-shadow: inset 0 0 0 0.25em var(--session-mode-color);
841 border-radius: 0;
842}
843`
844}
845
846// generateToasterCSS creates toast notification styles.
847// Uses em units for scalable UI.
848func generateToasterCSS(p Palette) string {
849 return `/* ===== Toaster Styling ===== */
850
851/* Toast notification container */
852.toast {
853 background-color: var(--surface-variant);
854 border-radius: 0.25em;
855 padding: 0.375em 0.625em;
856 margin: 0.5em;
857 font-size: 0.875em;
858 font-weight: 600;
859 box-shadow: 0 2px 8px alpha(black, 0.3);
860 min-width: 2.5em;
861}
862
863/* Toast level: info (default, accent color) */
864.toast-info {
865 background-color: alpha(var(--accent), 0.9);
866 color: var(--bg);
867}
868
869/* Toast level: success */
870.toast-success {
871 background-color: alpha(var(--success), 0.9);
872 color: var(--bg);
873}
874
875/* Toast level: warning */
876.toast-warning {
877 background-color: alpha(var(--warning), 0.9);
878 color: var(--bg);
879}
880
881/* Toast level: error */
882.toast-error {
883 background-color: alpha(var(--destructive), 0.9);
884 color: var(--bg);
885}
886
887/* Toast with custom styling (mode indicator toasters) */
888.toast-custom {
889 color: #ffffff;
890}
891
892/* Mode-specific toast colors */
893.toast-pane-mode {
894 background-color: var(--pane-mode-color);
895 color: #ffffff;
896}
897
898.toast-tab-mode {
899 background-color: var(--tab-mode-color);
900 color: #ffffff;
901}
902
903.toast-session-mode {
904 background-color: var(--session-mode-color);
905 color: #ffffff;
906}
907
908.toast-resize-mode {
909 background-color: var(--resize-mode-color);
910 color: #ffffff;
911}
912`
913}
914
915// generateLinkStatusCSS creates link status overlay styles.
916// Uses CSS transitions for fade-in/fade-out effect.
917func generateLinkStatusCSS(p Palette) string {
918 return `/* ===== Link Status Overlay Styling ===== */
919
920/* Link status container - bottom left positioning */
921.link-status {
922 background-color: alpha(var(--surface-variant), 0.95);
923 border-radius: 0.25em 0.25em 0 0;
924 padding: 0.25em 0.5em;
925 margin: 0;
926 font-size: 0.75em;
927 /* Note: max-width not supported in GTK4 CSS, use label SetMaxWidthChars instead */
928 box-shadow: 0 -1px 4px alpha(black, 0.15);
929
930 /* Fade transition - hidden by default */
931 opacity: 0;
932 transition: opacity 150ms ease-in-out;
933}
934
935/* Visible state - triggered by adding .visible class */
936.link-status.visible {
937 opacity: 1;
938}
939
940/* Link status label text */
941.link-status label {
942 color: var(--muted);
943}
944`
945}