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