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/* Ghost text autocomplete overlay - positioned to align with entry text */
414.omnibox-ghost {
415 font-size: 1em;
416 color: var(--muted);
417 margin-top: -0.0625em; /* Nudge up 1px to align with entry text baseline */
418 background-color: transparent;
419 /* Allow clicks to pass through to entry below */
420 pointer-events: none;
421}
422`
423}
424
425// generateFindBarCSS creates find bar styles.
426// Uses em units for scalable UI.
427func generateFindBarCSS(p Palette) string {
428 _ = p
429 return `/* ===== Find Bar Styling ===== */
430.find-bar-outer {
431 margin: 0.5em;
432}
433
434.find-bar-container {
435 background-color: var(--surface-variant);
436 border: 0.0625em solid var(--border);
437 border-radius: 0.25em;
438 box-shadow: 0 0.25em 0.75em alpha(black, 0.2);
439 padding: 0.5em;
440 min-width: 20em;
441}
442
443.find-bar-input-row {
444 margin-bottom: 0.25em;
445}
446
447.find-bar-entry {
448 background-color: var(--bg);
449 color: var(--text);
450 border: 0.0625em solid var(--border);
451 border-radius: 0.1875em;
452 padding: 0.4em 0.5em;
453 caret-color: var(--accent);
454}
455
456entry.find-bar-entry:focus,
457entry.find-bar-entry:focus-within,
458entry.find-bar-entry:focus-visible {
459 border-color: var(--accent);
460 outline-style: none;
461 outline-width: 0px;
462 outline-color: transparent;
463}
464
465.find-bar-entry.not-found {
466 background-color: alpha(#ef4444, 0.15);
467 border-color: #ef4444;
468}
469
470.find-bar-count {
471 font-size: 0.8em;
472 color: var(--muted);
473 min-width: 3.5em;
474}
475
476.find-bar-count.find-bar-count-has {
477 color: var(--accent);
478}
479
480.find-bar-nav,
481.find-bar-close,
482.find-bar-toggle {
483 background-image: none;
484 background-color: alpha(var(--bg), 0.6);
485 border: 0.0625em solid var(--border);
486 border-radius: 0.1875em;
487 padding: 0.25em 0.5em;
488 transition: background-color 120ms ease-in-out, border-color 120ms ease-in-out;
489}
490
491.find-bar-nav:hover,
492.find-bar-close:hover,
493.find-bar-toggle:hover {
494 background-color: alpha(var(--accent), 0.15);
495}
496
497.find-bar-toggle:checked {
498 background-color: alpha(var(--accent), 0.2);
499 border-color: var(--accent);
500 color: var(--accent);
501}
502`
503}
504
505// generatePaneCSS creates pane border styles.
506// Uses em units for scalable UI.
507func generatePaneCSS(p Palette) string {
508 return `/* ===== Pane Styling ===== */
509
510/* Pane overlay container - theme background prevents white flash */
511.pane-overlay {
512 background-color: var(--bg);
513}
514
515/* WebView widget - theme background prevents white flash during page load */
516.webview-themed {
517 background-color: var(--bg);
518}
519
520/* Loading skeleton - shown until WebView paints */
521.loading-skeleton {
522 background-color: var(--bg);
523}
524
525.loading-skeleton-spinner {
526 -gtk-icon-size: 32px;
527 min-width: 32px;
528 min-height: 32px;
529 color: var(--muted);
530}
531
532.loading-skeleton-logo {
533 opacity: 0.08;
534 min-width: 512px;
535 min-height: 512px;
536}
537
538/* Pane border - default transparent */
539.pane-border {
540 border: 0.0625em solid transparent;
541}
542
543/* Active pane - accent border */
544.pane-active {
545 border: 0.0625em solid var(--accent);
546}
547
548/* Hide active border when only one pane exists */
549.single-pane .pane-active {
550 border-color: transparent;
551}
552
553/* Pane mode active - thick inset border (for overlay) */
554.pane-mode-active {
555 background-color: transparent;
556 box-shadow: inset 0 0 0 0.25em var(--pane-mode-color);
557 border-radius: 0;
558}
559
560/* Tab mode active - thick inset border (for overlay) */
561.tab-mode-active {
562 background-color: transparent;
563 box-shadow: inset 0 0 0 0.25em var(--tab-mode-color);
564 border-radius: 0;
565}
566
567/* Resize mode active - thick inset border */
568.resize-mode-active {
569 background-color: transparent;
570 /* Prefer inset shadow, but also set outline for widgets that don't paint shadows */
571 box-shadow: inset 0 0 0 0.25em var(--resize-mode-color);
572 outline: 0.25em solid var(--resize-mode-color);
573 outline-offset: -0.25em;
574 border-radius: 0;
575}
576`
577}
578
579// generateStackedPaneCSS creates stacked pane (Zellij-style tabs within panes) styles.
580// Uses em units for scalable UI.
581func generateStackedPaneCSS(p Palette) string {
582 return `/* ===== Stacked Pane Styling ===== */
583
584/* Title bar for inactive panes in a stack */
585/* The title bar is now a Box with GestureClick, not wrapped in a button */
586.stacked-pane-titlebar {
587 background-color: var(--surface-variant);
588 border-bottom: 0.0625em solid var(--border);
589 padding: 0.25em 0.5em;
590 min-height: 1.5em;
591}
592
593/* Clickable title bar - hover styling */
594.stacked-pane-titlebar.stacked-pane-title-clickable {
595 transition: background-color 150ms ease-in-out;
596}
597
598.stacked-pane-titlebar.stacked-pane-title-clickable:hover {
599 background-color: shade(var(--surface-variant), 1.15);
600}
601
602/* Title bar content box */
603.stacked-pane-titlebar.active {
604 background-color: shade(var(--surface-variant), 1.2);
605 border-left: 0.1875em solid var(--accent);
606}
607
608/* Favicon image in title bar */
609.stacked-pane-titlebar image {
610 min-width: 1em;
611 min-height: 1em;
612 margin-right: 0.375em;
613}
614
615/* Title text in title bar */
616.stacked-pane-titlebar label {
617 color: var(--text);
618 font-size: 0.75em;
619 font-weight: 400;
620}
621
622/* Hover effect on title bar label */
623.stacked-pane-titlebar.stacked-pane-title-clickable:hover label {
624 color: var(--accent);
625}
626
627/* Close button in stacked pane title bar */
628.stacked-pane-close-button {
629 background: transparent;
630 border: none;
631 border-radius: 4px;
632 padding: 4px;
633 margin: 0 2px;
634 min-width: 20px;
635 min-height: 20px;
636 opacity: 0.4;
637 transition: opacity 100ms ease-in-out, background-color 100ms ease-in-out;
638}
639
640.stacked-pane-close-button > image {
641 margin: 0;
642 padding: 0;
643}
644
645.stacked-pane-close-button:hover {
646 opacity: 1.0;
647 background: alpha(var(--destructive), 0.2);
648}
649
650.stacked-pane-close-button:hover > image {
651 color: var(--destructive);
652}
653
654/* Show close button more prominently when hovering title bar */
655.stacked-pane-titlebar.stacked-pane-title-clickable:hover .stacked-pane-close-button {
656 opacity: 0.7;
657}
658
659.stacked-pane-close-button:focus {
660 outline: none;
661 box-shadow: none;
662}
663`
664}
665
666// generateProgressBarCSS creates progress bar styles for page loading indication.
667func generateProgressBarCSS(p Palette) string {
668 return `/* ===== Progress Bar Styling ===== */
669
670/* Native GtkProgressBar with osd class */
671progressbar.osd {
672 min-height: 4px;
673}
674
675progressbar.osd trough {
676 min-height: 4px;
677 min-width: 2px;
678 margin: 0;
679 padding: 0;
680 background-color: alpha(var(--bg), 0.3);
681}
682
683progressbar.osd progress {
684 min-height: 4px;
685 min-width: 2px;
686 margin: 0;
687 padding: 0;
688 background-color: var(--accent);
689}
690`
691}
692
693// generateSessionManagerCSS creates session manager modal styles.
694// Uses em units for scalable UI, matches omnibox styling patterns.
695func generateSessionManagerCSS(p Palette) string {
696 _ = p
697 return `/* ===== Session Manager Styling ===== */
698
699/* Session manager outer container - for positioning in overlay */
700.session-manager-outer {
701 /* Positioning is handled via SetHalign/SetValign in Go */
702}
703
704/* Session manager main container - the visible popup */
705.session-manager-container {
706 background-color: var(--surface-variant);
707 border: 0.0625em solid var(--border);
708 border-radius: 0.1875em;
709 padding: 0;
710 min-width: 28em;
711 /* Note: max-width not supported in GTK4 CSS, rely on container constraints */
712}
713
714/* Header with title */
715.session-manager-header {
716 background-color: shade(var(--surface-variant), 1.1);
717 border-bottom: 0.0625em solid var(--border);
718 padding: 0.5em 0.75em;
719}
720
721.session-manager-title {
722 font-size: 0.9375em;
723 font-weight: 600;
724 color: var(--text);
725}
726
727/* Scrolled window for session list */
728.session-manager-scrolled {
729 background-color: shade(var(--surface-variant), 0.95);
730}
731
732/* List box */
733.session-manager-list {
734 background-color: transparent;
735}
736
737/* Session rows */
738.session-manager-row {
739 padding: 0.5em 0.75em;
740 margin: 0;
741 border-radius: 0;
742 border-left: 0.1875em solid transparent;
743 border-bottom: 0.0625em solid alpha(var(--border), 0.5);
744 transition: background-color 100ms ease-in-out, border-left 100ms ease-in-out;
745 min-height: 2.75em;
746}
747
748.session-manager-row:last-child {
749 border-bottom: none;
750}
751
752.session-manager-row:hover {
753 background-color: alpha(var(--accent), 0.12);
754 border-left: 0.1875em solid var(--accent);
755}
756
757.session-manager-row:selected,
758row:selected .session-manager-row {
759 background-color: alpha(var(--accent), 0.2);
760 border-left: 0.1875em solid var(--accent);
761}
762
763/* Status indicator dot */
764.session-status {
765 font-size: 0.875em;
766 min-width: 1em;
767 margin-right: 0.5em;
768}
769
770/* Current session - accent color dot */
771.session-current .session-status {
772 color: var(--accent);
773}
774
775/* Active session (other instance) - muted dot */
776.session-active .session-status {
777 color: var(--muted);
778}
779
780/* Exited session - dimmed appearance with subtle stop icon */
781.session-exited {
782 opacity: 0.75;
783}
784
785.session-exited .session-status {
786 color: var(--error);
787 opacity: 0.5;
788}
789
790/* Session ID labels */
791.session-id-short {
792 font-size: 0.875em;
793 font-weight: 600;
794 color: var(--accent);
795 font-family: var(--font-mono);
796}
797
798.session-id-prefix {
799 font-size: 0.875em;
800 font-weight: 400;
801 color: var(--muted);
802 font-family: var(--font-mono);
803}
804
805.session-id-badge {
806 font-size: 0.75em;
807 font-weight: 500;
808 color: var(--accent);
809 margin-left: 0.5em;
810}
811
812/* Tab/pane count label */
813.session-count {
814 font-size: 0.75em;
815 color: var(--muted);
816}
817
818/* Relative time label */
819.session-time {
820 font-size: 0.75em;
821 color: var(--muted);
822 /* Note: margin-left: auto not supported in GTK4 CSS, use SetHalign(End) in code */
823}
824
825/* Section divider for EXITED sessions */
826.session-divider {
827 font-size: 0.6875em;
828 font-weight: 600;
829 color: var(--muted);
830 text-transform: uppercase;
831 letter-spacing: 0.05em;
832 padding: 0.5em 0.75em 0.25em 0.75em;
833 background-color: alpha(var(--border), 0.3);
834 border-bottom: 0.0625em solid var(--border);
835}
836
837/* Footer with keyboard shortcuts */
838.session-manager-footer {
839 background-color: shade(var(--surface-variant), 0.9);
840 border-top: 0.0625em solid var(--border);
841 padding: 0.375em 0.75em;
842 font-size: 0.6875em;
843 color: var(--muted);
844 font-family: var(--font-mono);
845}
846
847/* Session mode border */
848.session-mode-active {
849 background-color: transparent;
850 box-shadow: inset 0 0 0 0.25em var(--session-mode-color);
851 border-radius: 0;
852}
853`
854}
855
856// generateToasterCSS creates toast notification styles.
857// Uses em units for scalable UI.
858func generateToasterCSS(p Palette) string {
859 return `/* ===== Toaster Styling ===== */
860
861/* Toast notification container */
862.toast {
863 background-color: var(--surface-variant);
864 border-radius: 0.25em;
865 padding: 0.375em 0.625em;
866 margin: 0.5em;
867 font-size: 0.875em;
868 font-weight: 600;
869 box-shadow: 0 2px 8px alpha(black, 0.3);
870 min-width: 2.5em;
871}
872
873/* Toast level: info (default, accent color) */
874.toast-info {
875 background-color: alpha(var(--accent), 0.9);
876 color: var(--bg);
877}
878
879/* Toast level: success */
880.toast-success {
881 background-color: alpha(var(--success), 0.9);
882 color: var(--bg);
883}
884
885/* Toast level: warning */
886.toast-warning {
887 background-color: alpha(var(--warning), 0.9);
888 color: var(--bg);
889}
890
891/* Toast level: error */
892.toast-error {
893 background-color: alpha(var(--destructive), 0.9);
894 color: var(--bg);
895}
896
897/* Toast with custom styling (mode indicator toasters) */
898.toast-custom {
899 color: #ffffff;
900}
901
902/* Mode-specific toast colors */
903.toast-pane-mode {
904 background-color: var(--pane-mode-color);
905 color: #ffffff;
906}
907
908.toast-tab-mode {
909 background-color: var(--tab-mode-color);
910 color: #ffffff;
911}
912
913.toast-session-mode {
914 background-color: var(--session-mode-color);
915 color: #ffffff;
916}
917
918.toast-resize-mode {
919 background-color: var(--resize-mode-color);
920 color: #ffffff;
921}
922`
923}
924
925// generateLinkStatusCSS creates link status overlay styles.
926// Uses CSS transitions for fade-in/fade-out effect.
927func generateLinkStatusCSS(p Palette) string {
928 return `/* ===== Link Status Overlay Styling ===== */
929
930/* Link status container - bottom left positioning */
931.link-status {
932 background-color: alpha(var(--surface-variant), 0.95);
933 border-radius: 0.25em 0.25em 0 0;
934 padding: 0.25em 0.5em;
935 margin: 0;
936 font-size: 0.75em;
937 /* Note: max-width not supported in GTK4 CSS, use label SetMaxWidthChars instead */
938 box-shadow: 0 -1px 4px alpha(black, 0.15);
939
940 /* Fade transition - hidden by default */
941 opacity: 0;
942 transition: opacity 150ms ease-in-out;
943}
944
945/* Visible state - triggered by adding .visible class */
946.link-status.visible {
947 opacity: 1;
948}
949
950/* Link status label text */
951.link-status label {
952 color: var(--muted);
953}
954`
955}