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.stacked-pane-titlebar {
576 background-color: var(--surface-variant);
577 border-bottom: 0.0625em solid var(--border);
578 padding: 0.25em 0.5em;
579 min-height: 1.5em;
580}
581
582/* Title bar button wrapper - clickable area */
583button.stacked-pane-title-button {
584 background-color: var(--surface-variant);
585 background-image: none;
586 border: none;
587 border-bottom: 0.0625em solid var(--border);
588 border-radius: 0;
589 padding: 0;
590 margin: 0;
591 transition: background-color 150ms ease-in-out;
592}
593
594button.stacked-pane-title-button:hover {
595 background-color: shade(var(--surface-variant), 1.15);
596}
597
598button.stacked-pane-title-button:focus {
599 outline: none;
600 box-shadow: none;
601}
602
603/* Title bar content box */
604.stacked-pane-titlebar.active {
605 background-color: shade(var(--surface-variant), 1.2);
606 border-left: 0.1875em solid var(--accent);
607}
608
609/* Favicon image in title bar */
610.stacked-pane-titlebar image {
611 min-width: 1em;
612 min-height: 1em;
613 margin-right: 0.375em;
614}
615
616/* Title text in title bar */
617.stacked-pane-titlebar label {
618 color: var(--text);
619 font-size: 0.75em;
620 font-weight: 400;
621}
622
623button.stacked-pane-title-button:hover .stacked-pane-titlebar label {
624 color: var(--accent);
625}
626`
627}
628
629// generateProgressBarCSS creates progress bar styles for page loading indication.
630func generateProgressBarCSS(p Palette) string {
631 return `/* ===== Progress Bar Styling ===== */
632
633/* Native GtkProgressBar with osd class */
634progressbar.osd {
635 min-height: 4px;
636}
637
638progressbar.osd trough {
639 min-height: 4px;
640 min-width: 2px;
641 margin: 0;
642 padding: 0;
643 background-color: alpha(var(--bg), 0.3);
644}
645
646progressbar.osd progress {
647 min-height: 4px;
648 min-width: 2px;
649 margin: 0;
650 padding: 0;
651 background-color: var(--accent);
652}
653`
654}
655
656// generateSessionManagerCSS creates session manager modal styles.
657// Uses em units for scalable UI, matches omnibox styling patterns.
658func generateSessionManagerCSS(p Palette) string {
659 _ = p
660 return `/* ===== Session Manager Styling ===== */
661
662/* Session manager outer container - for positioning in overlay */
663.session-manager-outer {
664 /* Positioning is handled via SetHalign/SetValign in Go */
665}
666
667/* Session manager main container - the visible popup */
668.session-manager-container {
669 background-color: var(--surface-variant);
670 border: 0.0625em solid var(--border);
671 border-radius: 0.1875em;
672 padding: 0;
673 min-width: 28em;
674 /* Note: max-width not supported in GTK4 CSS, rely on container constraints */
675}
676
677/* Header with title */
678.session-manager-header {
679 background-color: shade(var(--surface-variant), 1.1);
680 border-bottom: 0.0625em solid var(--border);
681 padding: 0.5em 0.75em;
682}
683
684.session-manager-title {
685 font-size: 0.9375em;
686 font-weight: 600;
687 color: var(--text);
688}
689
690/* Scrolled window for session list */
691.session-manager-scrolled {
692 background-color: shade(var(--surface-variant), 0.95);
693}
694
695/* List box */
696.session-manager-list {
697 background-color: transparent;
698}
699
700/* Session rows */
701.session-manager-row {
702 padding: 0.5em 0.75em;
703 margin: 0;
704 border-radius: 0;
705 border-left: 0.1875em solid transparent;
706 border-bottom: 0.0625em solid alpha(var(--border), 0.5);
707 transition: background-color 100ms ease-in-out, border-left 100ms ease-in-out;
708 min-height: 2.75em;
709}
710
711.session-manager-row:last-child {
712 border-bottom: none;
713}
714
715.session-manager-row:hover {
716 background-color: alpha(var(--accent), 0.12);
717 border-left: 0.1875em solid var(--accent);
718}
719
720.session-manager-row:selected,
721row:selected .session-manager-row {
722 background-color: alpha(var(--accent), 0.2);
723 border-left: 0.1875em solid var(--accent);
724}
725
726/* Status indicator dot */
727.session-status {
728 font-size: 0.875em;
729 min-width: 1em;
730 margin-right: 0.5em;
731}
732
733/* Current session - accent color dot */
734.session-current .session-status {
735 color: var(--accent);
736}
737
738/* Active session (other instance) - muted dot */
739.session-active .session-status {
740 color: var(--muted);
741}
742
743/* Exited session - dimmed appearance with subtle stop icon */
744.session-exited {
745 opacity: 0.75;
746}
747
748.session-exited .session-status {
749 color: var(--error);
750 opacity: 0.5;
751}
752
753/* Session ID labels */
754.session-id-short {
755 font-size: 0.875em;
756 font-weight: 600;
757 color: var(--accent);
758 font-family: var(--font-mono);
759}
760
761.session-id-prefix {
762 font-size: 0.875em;
763 font-weight: 400;
764 color: var(--muted);
765 font-family: var(--font-mono);
766}
767
768.session-id-badge {
769 font-size: 0.75em;
770 font-weight: 500;
771 color: var(--accent);
772 margin-left: 0.5em;
773}
774
775/* Tab/pane count label */
776.session-count {
777 font-size: 0.75em;
778 color: var(--muted);
779}
780
781/* Relative time label */
782.session-time {
783 font-size: 0.75em;
784 color: var(--muted);
785 /* Note: margin-left: auto not supported in GTK4 CSS, use SetHalign(End) in code */
786}
787
788/* Section divider for EXITED sessions */
789.session-divider {
790 font-size: 0.6875em;
791 font-weight: 600;
792 color: var(--muted);
793 text-transform: uppercase;
794 letter-spacing: 0.05em;
795 padding: 0.5em 0.75em 0.25em 0.75em;
796 background-color: alpha(var(--border), 0.3);
797 border-bottom: 0.0625em solid var(--border);
798}
799
800/* Footer with keyboard shortcuts */
801.session-manager-footer {
802 background-color: shade(var(--surface-variant), 0.9);
803 border-top: 0.0625em solid var(--border);
804 padding: 0.375em 0.75em;
805 font-size: 0.6875em;
806 color: var(--muted);
807 font-family: var(--font-mono);
808}
809
810/* Session mode border */
811.session-mode-active {
812 background-color: transparent;
813 box-shadow: inset 0 0 0 0.25em var(--session-mode-color);
814 border-radius: 0;
815}
816`
817}
818
819// generateToasterCSS creates toast notification styles.
820// Uses em units for scalable UI.
821func generateToasterCSS(p Palette) string {
822 return `/* ===== Toaster Styling ===== */
823
824/* Toast notification container */
825.toast {
826 background-color: var(--surface-variant);
827 border-radius: 0.25em;
828 padding: 0.375em 0.625em;
829 margin: 0.5em;
830 font-size: 0.875em;
831 font-weight: 600;
832 box-shadow: 0 2px 8px alpha(black, 0.3);
833 min-width: 2.5em;
834}
835
836/* Toast level: info (default, accent color) */
837.toast-info {
838 background-color: alpha(var(--accent), 0.9);
839 color: var(--bg);
840}
841
842/* Toast level: success */
843.toast-success {
844 background-color: alpha(var(--success), 0.9);
845 color: var(--bg);
846}
847
848/* Toast level: warning */
849.toast-warning {
850 background-color: alpha(var(--warning), 0.9);
851 color: var(--bg);
852}
853
854/* Toast level: error */
855.toast-error {
856 background-color: alpha(var(--destructive), 0.9);
857 color: var(--bg);
858}
859
860/* Toast with custom styling (mode indicator toasters) */
861.toast-custom {
862 color: #ffffff;
863}
864
865/* Mode-specific toast colors */
866.toast-pane-mode {
867 background-color: var(--pane-mode-color);
868 color: #ffffff;
869}
870
871.toast-tab-mode {
872 background-color: var(--tab-mode-color);
873 color: #ffffff;
874}
875
876.toast-session-mode {
877 background-color: var(--session-mode-color);
878 color: #ffffff;
879}
880
881.toast-resize-mode {
882 background-color: var(--resize-mode-color);
883 color: #ffffff;
884}
885`
886}
887
888// generateLinkStatusCSS creates link status overlay styles.
889// Uses CSS transitions for fade-in/fade-out effect.
890func generateLinkStatusCSS(p Palette) string {
891 return `/* ===== Link Status Overlay Styling ===== */
892
893/* Link status container - bottom left positioning */
894.link-status {
895 background-color: alpha(var(--surface-variant), 0.95);
896 border-radius: 0.25em 0.25em 0 0;
897 padding: 0.25em 0.5em;
898 margin: 0;
899 font-size: 0.75em;
900 /* Note: max-width not supported in GTK4 CSS, use label SetMaxWidthChars instead */
901 box-shadow: 0 -1px 4px alpha(black, 0.15);
902
903 /* Fade transition - hidden by default */
904 opacity: 0;
905 transition: opacity 150ms ease-in-out;
906}
907
908/* Visible state - triggered by adding .visible class */
909.link-status.visible {
910 opacity: 1;
911}
912
913/* Link status label text */
914.link-status label {
915 color: var(--muted);
916}
917`
918}