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 widget sizing that uses relative units; text scaling is handled via GtkSettings.
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 GtkFont string
25}
26
27// DefaultFontConfig returns safe font fallbacks.
28func DefaultFontConfig() FontConfig {
29 return FontConfig{
30 SansFont: "sans-serif",
31 MonospaceFont: "monospace",
32 GtkFont: DefaultGTKFont(),
33 }
34}
35
36// FontCSSVars generates CSS custom properties for fonts.
37func FontCSSVars(fonts FontConfig) string {
38 var sb strings.Builder
39 // Quote the configured family; include generic fallback.
40 fmt.Fprintf(&sb, " --font-sans: %q, sans-serif;\n", fonts.SansFont)
41 fmt.Fprintf(&sb, " --font-mono: %q, monospace;\n", fonts.MonospaceFont)
42 fmt.Fprintf(&sb, " --font-gtk: %q, sans-serif;\n", fonts.GtkFont)
43 return sb.String()
44}
45
46func generateFontCSS() string {
47 return `/* Font styling */
48window, tooltip, popover {
49 font-family: var(--font-gtk);
50}
51`
52}
53
54// GenerateCSSWithScaleAndFonts creates GTK4 CSS using the provided palette, UI scale factor and fonts.
55// Scale affects widget sizing that uses relative units; text scaling is handled via GtkSettings.
56// Uses default mode colors.
57func GenerateCSSWithScaleAndFonts(p Palette, scale float64, fonts FontConfig) string {
58 return GenerateCSSFull(p, scale, fonts, DefaultModeColors())
59}
60
61// GenerateCSSFull creates GTK4 CSS using all provided configuration.
62// Text scaling is handled via GtkSettings; CSS here stays scale-independent.
63func GenerateCSSFull(p Palette, _ float64, fonts FontConfig, modeColors ModeColors) string {
64 defaults := DefaultFontConfig()
65 fonts = FontConfig{
66 SansFont: Coalesce(fonts.SansFont, defaults.SansFont),
67 MonospaceFont: Coalesce(fonts.MonospaceFont, defaults.MonospaceFont),
68 GtkFont: Coalesce(fonts.GtkFont, defaults.GtkFont),
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 // Tab bar styling
86 sb.WriteString(generateTabBarCSS(p))
87 sb.WriteString("\n")
88
89 // Omnibox styling
90 sb.WriteString(generateOmniboxCSS(p))
91 sb.WriteString("\n")
92
93 // Find bar styling
94 sb.WriteString(generateFindBarCSS(p))
95 sb.WriteString("\n")
96
97 // Pane styling
98 sb.WriteString(generatePaneCSS(p))
99 sb.WriteString("\n")
100
101 // Stacked pane styling
102 sb.WriteString(generateStackedPaneCSS(p))
103 sb.WriteString("\n")
104
105 // Progress bar styling
106 sb.WriteString(generateProgressBarCSS(p))
107 sb.WriteString("\n")
108
109 // Touchpad history navigation indicator styling
110 sb.WriteString(generateTouchpadNavigationIndicatorCSS(p))
111 sb.WriteString("\n")
112
113 // Toaster styling
114 sb.WriteString(generateToasterCSS(p))
115 sb.WriteString("\n")
116
117 // Link status overlay styling
118 sb.WriteString(generateLinkStatusCSS(p))
119 sb.WriteString("\n")
120
121 // Session manager styling
122 sb.WriteString(generateSessionManagerCSS(p))
123 sb.WriteString("\n")
124
125 // Context menu styling
126 sb.WriteString(generateContextMenuCSS())
127 sb.WriteString("\n")
128
129 // Tab picker styling
130 sb.WriteString(generateTabPickerCSS(p))
131 sb.WriteString("\n")
132
133 // Permission popup styling
134 sb.WriteString(generatePermissionPopupCSS(p))
135 sb.WriteString("\n")
136
137 // Floating pane styling
138 sb.WriteString(generateFloatingPaneCSS(p))
139 sb.WriteString("\n")
140
141 // WebRTC permission activity indicator styling
142 sb.WriteString(generateWebRTCPermissionIndicatorCSS(p))
143 sb.WriteString("\n")
144
145 // Accent picker styling
146 sb.WriteString(generateAccentPickerCSS(p))
147 sb.WriteString("\n")
148
149 // History sidebar styling
150 sb.WriteString(generateHistorySidebarCSS(p))
151
152 return sb.String()
153}
154
155// generateTabBarCSS creates tab bar styles.
156// Uses em units for scalable UI.
157func generateTabBarCSS(p Palette) string {
158 return `/* Tab bar styling */
159/* Content area inset for overlaid tab bars — uses the same 2em token as
160 .tab-bar min-height and remains static to avoid OSR vertical reallocations. */
161.content-area-tabbar-inset-top {
162 padding-top: 2em;
163}
164
165.content-area-tabbar-inset-bottom {
166 padding-bottom: 2em;
167}
168
169.tab-bar {
170 background-color: var(--surface);
171 border-top: 0.125em solid var(--border);
172 padding: 0;
173 min-height: 2em;
174}
175
176/* Tab button styling */
177button.tab-button {
178 background-color: var(--surface-variant);
179 background-image: none;
180 border: none;
181 border-right: 0.0625em solid var(--border);
182 border-radius: 0;
183 padding: 0.25em 0.5em;
184 transition: background-color 200ms ease-in-out;
185}
186
187button.tab-button:hover {
188 background-color: shade(var(--surface-variant), 1.2);
189}
190
191button.tab-button.tab-button-active {
192 background-color: shade(var(--surface-variant), 1.2);
193 border-bottom: 0.125em solid var(--accent);
194 font-weight: 600;
195}
196
197/* Tab title text */
198.tab-title {
199 font-size: 0.75em;
200 color: var(--text);
201 font-weight: 500;
202}
203`
204}
205
206// generateOmniboxCSS creates omnibox styles.
207// Uses em units for scalable UI.
208func generateOmniboxCSS(p Palette) string {
209 return `/* ===== Omnibox Styling ===== */
210
211window.standalone-omnibox-window {
212 background-color: transparent;
213}
214
215/* Omnibox outer container - for positioning in overlay */
216.omnibox-outer {
217 /* Positioning is handled via SetHalign/SetValign/SetMarginTop in Go */
218}
219
220/* Omnibox main container - the visible popup */
221.omnibox-container {
222 background-color: var(--surface);
223 background-image: none;
224 border: 0.0625em solid alpha(var(--border), 0.85);
225 border-radius: 0.1875em;
226 box-shadow: 0 0.875em 2.5em alpha(black, 0.24), 0 0.0625em 0 alpha(white, 0.05) inset;
227 padding: 0;
228}
229
230/* Header with History/Favorites toggle */
231.omnibox-header {
232 background-color: var(--surface);
233 background-image: none;
234 border-bottom: 0.0625em solid alpha(var(--border), 0.75);
235 padding: 0.375em 0.75em;
236}
237
238.omnibox-header-btn {
239 background-color: alpha(var(--surface), 0.12);
240 background-image: none;
241 border: 0.0625em solid transparent;
242 border-radius: 0.125em;
243 padding: 0.25em 0.75em;
244 margin-right: 0.5em;
245 font-size: 0.8125em;
246 font-weight: 500;
247 color: var(--muted);
248 transition: all 100ms ease-in-out;
249}
250
251.omnibox-header-btn:hover {
252 background-color: alpha(var(--accent), 0.14);
253 border-color: alpha(var(--accent), 0.18);
254 color: var(--text);
255}
256
257.omnibox-header-btn.omnibox-header-active {
258 background-color: alpha(var(--accent), 0.18);
259 border-color: alpha(var(--accent), 0.28);
260 color: var(--accent);
261 font-weight: 600;
262}
263
264/* Search entry field */
265entry.omnibox-entry,
266entry.omnibox-entry > text {
267 background-color: var(--surface-variant);
268 background-image: none;
269 color: var(--text);
270 caret-color: var(--accent);
271}
272
273entry.omnibox-entry {
274 border: 0.0625em solid alpha(var(--border), 0.82);
275 border-radius: 0.125em;
276 padding: 0.625em 0.75em;
277 margin: 0.5em 0.75em;
278 font-size: 1em;
279 box-shadow: 0 0.0625em 0 alpha(white, 0.04) inset;
280}
281
282entry.omnibox-entry:focus,
283entry.omnibox-entry:focus-within,
284entry.omnibox-entry:focus-visible,
285entry.omnibox-entry:focus > text,
286entry.omnibox-entry:focus-within > text,
287entry.omnibox-entry:focus-visible > text {
288 border-color: var(--accent);
289 background-color: shade(var(--surface-variant), 1.08);
290 background-image: none;
291 outline-style: none;
292 outline-width: 0px;
293 outline-color: transparent;
294}
295
296/* Override any internal focus styling */
297entry.omnibox-entry > text:focus,
298entry.omnibox-entry > text:focus-visible {
299 outline-style: none;
300 outline-color: transparent;
301}
302
303/* Scrolled window for suggestions */
304.omnibox-scrolled {
305 background-color: var(--surface);
306 border-top: 0.0625em solid alpha(var(--border), 0.65);
307}
308
309/* List box */
310.omnibox-listbox {
311 background-color: transparent;
312}
313
314/* Suggestion rows */
315.omnibox-row {
316 padding: 0.5em 0.75em;
317 margin: 0;
318 border-radius: 0;
319 border-left: 0.1875em solid transparent;
320 border-bottom: 0.0625em solid alpha(var(--border), 0.35);
321 background-color: var(--surface);
322 background-image: none;
323 transition: background-color 100ms ease-in-out, border-left 100ms ease-in-out;
324 min-height: 2.75em;
325}
326
327.omnibox-row:last-child {
328 border-bottom: none;
329}
330
331.omnibox-row:hover {
332 background-color: alpha(var(--accent), 0.12);
333 border-left: 0.1875em solid var(--accent);
334}
335
336.omnibox-row:selected {
337 background-color: alpha(var(--accent), 0.2);
338 border-left: 0.1875em solid var(--accent);
339}
340
341/* Favorite indicator in history mode - keep the row neutral and use the left selection stripe */
342.omnibox-row.omnibox-row-favorite {
343 background-color: transparent;
344 border-left: 0.1875em solid var(--warning);
345}
346
347.omnibox-row.omnibox-row-favorite:hover {
348 background-color: alpha(var(--accent), 0.12);
349 border-left: 0.1875em solid var(--warning);
350}
351
352.omnibox-row.omnibox-row-favorite:selected {
353 background-color: alpha(var(--accent), 0.2);
354 border-left: 0.1875em solid var(--warning);
355}
356
357/* Favicon in omnibox rows */
358.omnibox-favicon {
359 min-width: 1em;
360 min-height: 1em;
361 margin-right: 0.5em;
362}
363
364/* Suggestion title/URL */
365.omnibox-suggestion-title {
366 font-size: 0.875em;
367 color: var(--text);
368 font-weight: 500;
369}
370
371/* Also style labels inside omnibox rows directly */
372.omnibox-row label {
373 color: var(--text);
374}
375
376.omnibox-row:selected .omnibox-suggestion-title {
377 color: var(--text);
378 font-weight: 500;
379}
380
381/* URL text below title - use .omnibox-row prefix for specificity over .omnibox-row label */
382.omnibox-row .omnibox-suggestion-url {
383 font-size: 0.75em;
384 color: alpha(var(--muted), 0.72);
385 font-weight: 400;
386}
387
388.omnibox-favorite-star {
389 color: mix(var(--warning), var(--muted), 0.45);
390 opacity: 0.75;
391 margin-left: 0.625em;
392 margin-right: 0.125em;
393}
394
395.omnibox-row:selected .omnibox-favorite-star {
396 color: mix(var(--warning), var(--text), 0.35);
397 opacity: 0.88;
398}
399
400.omnibox-row:selected .omnibox-suggestion-url {
401 color: alpha(var(--muted), 0.75);
402}
403
404/* Keyboard shortcut badge */
405.omnibox-shortcut-badge {
406 background-color: alpha(var(--muted), 0.3);
407 color: var(--muted);
408 border-radius: 0.25em;
409 padding: 0.125em 0.375em;
410 font-size: 0.625em;
411 font-weight: 500;
412 font-family: var(--font-mono);
413 margin-left: 0.5em;
414}
415
416.omnibox-row:hover .omnibox-shortcut-badge {
417 background-color: alpha(var(--accent), 0.2);
418 color: var(--accent);
419}
420
421.omnibox-row:selected .omnibox-shortcut-badge {
422 background-color: alpha(var(--accent), 0.3);
423 color: var(--accent);
424}
425
426/* Bang indicator badge in omnibox header */
427.omnibox-bang-badge {
428 background-color: alpha(var(--accent), 0.18);
429 color: var(--accent);
430 border-radius: 0.25em;
431 padding: 0.125em 0.5em;
432 font-size: 0.6875em;
433 font-weight: 600;
434}
435
436/* Header badge used for omnibox state */
437.omnibox-header-badge {
438 background-color: alpha(var(--surface-variant), 0.65);
439 color: var(--muted);
440 border: 0.0625em solid alpha(var(--border), 0.85);
441 border-radius: 0.1875em;
442 padding: 0.0625em 0.375em;
443 font-size: 0.6875em;
444 font-weight: 500;
445 transition: background-color 100ms ease-in-out, border-color 100ms ease-in-out, color 100ms ease-in-out;
446}
447
448.omnibox-header-badge:hover {
449 background-color: shade(var(--surface-variant), 1.08);
450 border-color: var(--border);
451 color: var(--text);
452}
453
454/* Zoom indicator in omnibox header */
455.omnibox-zoom-indicator {
456 background-color: alpha(var(--accent), 0.2);
457 color: var(--accent);
458 border-radius: 0.25em;
459 padding: 0.125em 0.5em;
460 font-size: 0.6875em;
461 font-weight: 600;
462 /* Note: margin-left: auto not supported in GTK4 CSS, use SetHalign(End) in code */
463}
464
465entry.omnibox-entry.omnibox-entry-bang-active,
466entry.omnibox-entry.omnibox-entry-bang-active:focus,
467entry.omnibox-entry.omnibox-entry-bang-active:focus-within,
468entry.omnibox-entry.omnibox-entry-bang-active:focus-visible,
469entry.omnibox-entry.omnibox-entry-bang-active > text,
470entry.omnibox-entry.omnibox-entry-bang-active:focus > text,
471entry.omnibox-entry.omnibox-entry-bang-active:focus-within > text,
472entry.omnibox-entry.omnibox-entry-bang-active:focus-visible > text {
473 border-color: var(--accent);
474 background-color: shade(var(--surface-variant), 1.08);
475 background-image: none;
476}
477
478.omnibox-row.omnibox-row-bang .omnibox-suggestion-title {
479 color: var(--accent);
480 font-weight: 600;
481}
482
483/* Ghost text autocomplete overlay - positioned to align with entry text */
484.omnibox-ghost {
485 font-size: 1em;
486 color: var(--muted);
487 margin-top: -0.0625em; /* Nudge up 1px to align with entry text baseline */
488 background-color: transparent;
489}
490`
491}
492
493// generateFindBarCSS creates find bar styles.
494// Uses em units for scalable UI.
495func generateFindBarCSS(p Palette) string {
496 _ = p
497 return `/* ===== Find Bar Styling ===== */
498.find-bar-outer {
499 margin: 0.5em;
500}
501
502.find-bar-container {
503 background-color: var(--surface-variant);
504 border: 0.0625em solid var(--border);
505 border-radius: 0.25em;
506 box-shadow: 0 0.25em 0.75em alpha(black, 0.2);
507 padding: 0.5em;
508 min-width: 20em;
509}
510
511.find-bar-input-row {
512 margin-bottom: 0.25em;
513}
514
515.find-bar-entry {
516 background-color: var(--bg);
517 color: var(--text);
518 border: 0.0625em solid var(--border);
519 border-radius: 0.1875em;
520 padding: 0.4em 0.5em;
521 caret-color: var(--accent);
522}
523
524entry.find-bar-entry:focus,
525entry.find-bar-entry:focus-within,
526entry.find-bar-entry:focus-visible {
527 border-color: var(--accent);
528 outline-style: none;
529 outline-width: 0px;
530 outline-color: transparent;
531}
532
533.find-bar-entry.not-found {
534 background-color: alpha(#ef4444, 0.15);
535 border-color: #ef4444;
536}
537
538.find-bar-count {
539 font-size: 0.8em;
540 color: var(--muted);
541 min-width: 3.5em;
542}
543
544.find-bar-count.find-bar-count-has {
545 color: var(--accent);
546}
547
548.find-bar-nav,
549.find-bar-close,
550.find-bar-toggle {
551 background-image: none;
552 background-color: alpha(var(--bg), 0.6);
553 border: 0.0625em solid var(--border);
554 border-radius: 0.1875em;
555 padding: 0.25em 0.5em;
556 transition: background-color 120ms ease-in-out, border-color 120ms ease-in-out;
557}
558
559.find-bar-nav:hover,
560.find-bar-close:hover,
561.find-bar-toggle:hover {
562 background-color: alpha(var(--accent), 0.15);
563}
564
565.find-bar-toggle:checked {
566 background-color: alpha(var(--accent), 0.2);
567 border-color: var(--accent);
568 color: var(--accent);
569}
570`
571}
572
573// generatePaneCSS creates pane border styles.
574// Uses em units for scalable UI.
575func generatePaneCSS(p Palette) string {
576 return `/* ===== Pane Styling ===== */
577
578/* Pane overlay container - theme background prevents white flash */
579.pane-overlay {
580 background-color: var(--bg);
581}
582
583/* WebView widget - theme background prevents white flash during page load */
584.webview-themed {
585 background-color: var(--bg);
586}
587
588/* Loading skeleton - shown until WebView paints */
589.loading-skeleton {
590 background-color: var(--bg);
591}
592
593.loading-skeleton-spinner {
594 -gtk-icon-size: 32px;
595 min-width: 32px;
596 min-height: 32px;
597 color: var(--muted);
598 opacity: 0.4;
599}
600
601.loading-skeleton-logo {
602 opacity: 0.08;
603 min-width: 256px;
604 min-height: 256px;
605}
606
607.loading-skeleton-version {
608 opacity: 0.25;
609 font-size: 13px;
610 color: var(--muted);
611}
612
613/* Pane border - default transparent */
614.pane-border {
615 border: 0.0625em solid transparent;
616}
617
618/* Active pane - accent border */
619.pane-active {
620 border: 0.0625em solid var(--accent);
621}
622
623/* Hide active border when only one pane exists */
624.single-pane .pane-active {
625 border-color: transparent;
626}
627
628/* Pane mode active - thick inset border (for overlay) */
629.pane-mode-active {
630 background-color: transparent;
631 box-shadow: inset 0 0 0 0.25em var(--pane-mode-color);
632 border-radius: 0;
633}
634
635/* Tab mode active - thick inset border (for overlay) */
636.tab-mode-active {
637 background-color: transparent;
638 box-shadow: inset 0 0 0 0.25em var(--tab-mode-color);
639 border-radius: 0;
640}
641
642/* Resize mode active - thick inset border */
643.resize-mode-active {
644 background-color: transparent;
645 /* Prefer inset shadow, but also set outline for widgets that don't paint shadows */
646 box-shadow: inset 0 0 0 0.25em var(--resize-mode-color);
647 outline: 0.25em solid var(--resize-mode-color);
648 outline-offset: -0.25em;
649 border-radius: 0;
650}
651`
652}
653
654// generateStackedPaneCSS creates stacked pane (Zellij-style tabs within panes) styles.
655// Uses em units for scalable UI.
656func generateStackedPaneCSS(p Palette) string {
657 return `/* ===== Stacked Pane Styling ===== */
658
659/* Title bar for inactive panes in a stack */
660/* The title bar is now a Box with GestureClick, not wrapped in a button */
661.stacked-pane-titlebar {
662 background-color: var(--surface-variant);
663 border-bottom: 0.0625em solid var(--border);
664 padding: 0.25em 0.5em;
665 min-height: 1.5em;
666}
667
668/* Clickable title bar - hover styling */
669.stacked-pane-titlebar.stacked-pane-title-clickable {
670 transition: background-color 150ms ease-in-out;
671}
672
673.stacked-pane-titlebar.stacked-pane-title-clickable:hover {
674 background-color: shade(var(--surface-variant), 1.15);
675}
676
677/* Title bar content box */
678.stacked-pane-titlebar.active {
679 background-color: shade(var(--surface-variant), 1.2);
680 border-left: 0.1875em solid var(--accent);
681}
682
683/* Favicon image in title bar */
684.stacked-pane-titlebar image {
685 min-width: 1em;
686 min-height: 1em;
687 margin-right: 0.375em;
688}
689
690/* Title text in title bar */
691.stacked-pane-titlebar label {
692 color: var(--text);
693 font-size: 0.75em;
694 font-weight: 400;
695}
696
697/* Hover effect on title bar label */
698.stacked-pane-titlebar.stacked-pane-title-clickable:hover label {
699 color: var(--accent);
700}
701
702/* Close button in stacked pane title bar */
703.stacked-pane-close-button {
704 background: transparent;
705 border: none;
706 border-radius: 4px;
707 padding: 4px;
708 margin: 0 2px;
709 min-width: 20px;
710 min-height: 20px;
711 opacity: 0.4;
712 transition: opacity 100ms ease-in-out, background-color 100ms ease-in-out;
713}
714
715.stacked-pane-close-button > image {
716 margin: 0;
717 padding: 0;
718}
719
720.stacked-pane-close-button:hover {
721 opacity: 1.0;
722 background: alpha(var(--destructive), 0.2);
723}
724
725.stacked-pane-close-button:hover > image {
726 color: var(--destructive);
727}
728
729/* Show close button more prominently when hovering title bar */
730.stacked-pane-titlebar.stacked-pane-title-clickable:hover .stacked-pane-close-button {
731 opacity: 0.7;
732}
733
734.stacked-pane-close-button:focus {
735 outline: none;
736 box-shadow: none;
737}
738`
739}
740
741// generateProgressBarCSS creates progress bar styles for page loading indication.
742func generateProgressBarCSS(p Palette) string {
743 return `/* ===== Progress Bar Styling ===== */
744
745/* Native GtkProgressBar with osd class */
746progressbar.osd {
747 min-height: 4px;
748}
749
750progressbar.osd trough {
751 min-height: 4px;
752 min-width: 2px;
753 margin: 0;
754 padding: 0;
755 background-color: alpha(var(--bg), 0.3);
756}
757
758progressbar.osd progress {
759 min-height: 4px;
760 min-width: 2px;
761 margin: 0;
762 padding: 0;
763 background-color: var(--accent);
764}
765`
766}
767
768func generateTouchpadNavigationIndicatorCSS(p Palette) string {
769 _ = p
770 return `/* ===== Touchpad Navigation Indicator Styling ===== */
771
772.touchpad-navigation-indicator {
773 background-color: alpha(var(--surface), 0.90);
774 color: var(--text);
775 border: 0.0625em solid alpha(var(--border), 0.72);
776 border-radius: 1.125em;
777 padding: 0.75em 0.875em 0.8125em;
778 opacity: 1;
779 filter: blur(0px);
780 backdrop-filter: blur(0.75em);
781 box-shadow: 0 1em 2.25em alpha(black, 0.30),
782 0 0.0625em 0 alpha(white, 0.06) inset;
783 transition: opacity 220ms ease-out,
784 filter 220ms ease-out,
785 backdrop-filter 220ms ease-out,
786 background-color 140ms ease-out,
787 border-color 140ms ease-out,
788 box-shadow 220ms ease-out;
789}
790
791.touchpad-navigation-indicator.back {
792 border-left: 0.1875em solid alpha(var(--accent), 0.82);
793}
794
795.touchpad-navigation-indicator.forward {
796 border-right: 0.1875em solid alpha(var(--accent), 0.82);
797}
798
799.touchpad-navigation-indicator.threshold-reached {
800 border-color: alpha(var(--accent), 0.95);
801 background-color: alpha(var(--accent), 0.18);
802 box-shadow: 0 1.125em 2.5em alpha(black, 0.34),
803 0 0 0 0.0625em alpha(var(--accent), 0.24) inset;
804}
805
806.touchpad-navigation-indicator.hiding {
807 opacity: 0;
808 filter: blur(0.0625em);
809 box-shadow: 0 0.375em 0.875em alpha(black, 0.10),
810 0 0 0 0.0625em alpha(var(--accent), 0.04) inset;
811}
812
813.touchpad-navigation-header {
814 min-width: 9.75em;
815}
816
817.touchpad-navigation-icon {
818 background-color: alpha(var(--accent), 0.16);
819 color: var(--accent);
820 border-radius: 999px;
821 font-size: 0.95em;
822 font-weight: 700;
823 padding: 0.125em 0.375em;
824}
825
826.touchpad-navigation-indicator.threshold-reached .touchpad-navigation-icon {
827 background-color: var(--accent);
828 color: var(--bg);
829}
830
831.touchpad-navigation-label {
832 color: var(--text);
833 font-weight: 700;
834 font-size: 0.92em;
835 letter-spacing: 0.01em;
836}
837
838progressbar.touchpad-navigation-progress {
839 min-height: 0.3125em;
840}
841
842progressbar.touchpad-navigation-progress trough {
843 min-height: 0.3125em;
844 min-width: 9.75em;
845 border-radius: 999px;
846 background-color: alpha(var(--muted), 0.24);
847 box-shadow: 0 0.0625em 0 alpha(white, 0.06) inset;
848}
849
850progressbar.touchpad-navigation-progress progress {
851 min-height: 0.3125em;
852 border-radius: 999px;
853 background-color: var(--accent);
854 box-shadow: 0 0 0.35em alpha(var(--accent), 0.35);
855}
856`
857}
858
859// generateSessionManagerCSS creates session manager modal styles.
860// Uses em units for scalable UI, matches omnibox styling patterns.
861func generateSessionManagerCSS(p Palette) string {
862 _ = p
863 return `/* ===== Session Manager Styling ===== */
864
865/* Session manager outer container - for positioning in overlay */
866.session-manager-outer {
867 /* Positioning is handled via SetHalign/SetValign in Go */
868}
869
870/* Session manager main container - the visible popup */
871.session-manager-container {
872 background-color: var(--surface-variant);
873 border: 0.0625em solid var(--border);
874 border-radius: 0.1875em;
875 padding: 0;
876 min-width: 28em;
877 /* Note: max-width not supported in GTK4 CSS, rely on container constraints */
878}
879
880/* Header with title */
881.session-manager-header {
882 background-color: shade(var(--surface-variant), 1.1);
883 border-bottom: 0.0625em solid var(--border);
884 padding: 0.5em 0.75em;
885}
886
887.session-manager-title {
888 font-size: 0.9375em;
889 font-weight: 600;
890 color: var(--text);
891}
892
893/* Scrolled window for session list */
894.session-manager-scrolled {
895 background-color: shade(var(--surface-variant), 0.95);
896}
897
898/* List box */
899.session-manager-list {
900 background-color: transparent;
901}
902
903/* Session rows */
904.session-manager-row {
905 padding: 0.5em 0.75em;
906 margin: 0;
907 border-radius: 0;
908 border-left: 0.1875em solid transparent;
909 border-bottom: 0.0625em solid alpha(var(--border), 0.5);
910 transition: background-color 100ms ease-in-out, border-left 100ms ease-in-out;
911 min-height: 2.75em;
912}
913
914.session-manager-row:last-child {
915 border-bottom: none;
916}
917
918.session-manager-row:hover {
919 background-color: alpha(var(--accent), 0.12);
920 border-left: 0.1875em solid var(--accent);
921}
922
923.session-manager-row:selected,
924row:selected .session-manager-row {
925 background-color: alpha(var(--accent), 0.2);
926 border-left: 0.1875em solid var(--accent);
927}
928
929/* Status indicator dot */
930.session-status {
931 font-size: 0.875em;
932 min-width: 1em;
933 margin-right: 0.5em;
934}
935
936/* Current session - accent color dot */
937.session-current .session-status {
938 color: var(--accent);
939}
940
941/* Active session (other instance) - muted dot */
942.session-active .session-status {
943 color: var(--muted);
944}
945
946/* Exited session - dimmed appearance with subtle stop icon */
947.session-exited {
948 opacity: 0.75;
949}
950
951.session-exited .session-status {
952 color: var(--error);
953 opacity: 0.5;
954}
955
956/* Session ID labels */
957.session-id-short {
958 font-size: 0.875em;
959 font-weight: 600;
960 color: var(--accent);
961 font-family: var(--font-mono);
962}
963
964.session-id-prefix {
965 font-size: 0.875em;
966 font-weight: 400;
967 color: var(--muted);
968 font-family: var(--font-mono);
969}
970
971.session-id-badge {
972 font-size: 0.75em;
973 font-weight: 500;
974 color: var(--accent);
975 margin-left: 0.5em;
976}
977
978/* Tab/pane count label */
979.session-count {
980 font-size: 0.75em;
981 color: var(--muted);
982}
983
984/* Relative time label */
985.session-time {
986 font-size: 0.75em;
987 color: var(--muted);
988 /* Note: margin-left: auto not supported in GTK4 CSS, use SetHalign(End) in code */
989}
990
991/* Section divider for EXITED sessions */
992.session-divider {
993 font-size: 0.6875em;
994 font-weight: 600;
995 color: var(--muted);
996 text-transform: uppercase;
997 letter-spacing: 0.05em;
998 padding: 0.5em 0.75em 0.25em 0.75em;
999 background-color: alpha(var(--border), 0.3);
1000 border-bottom: 0.0625em solid var(--border);
1001}
1002
1003/* Footer with keyboard shortcuts */
1004.session-manager-footer {
1005 background-color: shade(var(--surface-variant), 0.9);
1006 border-top: 0.0625em solid var(--border);
1007 padding: 0.375em 0.75em;
1008 font-size: 0.6875em;
1009 color: var(--muted);
1010 font-family: var(--font-mono);
1011}
1012
1013/* Session mode border */
1014.session-mode-active {
1015 background-color: transparent;
1016 box-shadow: inset 0 0 0 0.25em var(--session-mode-color);
1017 border-radius: 0;
1018}
1019`
1020}
1021
1022// generateContextMenuCSS creates shared context menu styles.
1023func generateContextMenuCSS() string {
1024 return `/* ===== Context Menu Styling ===== */
1025
1026popover.context-menu-popover {
1027 background-color: transparent;
1028 background-image: none;
1029}
1030
1031popover.context-menu-popover > contents {
1032 background-color: var(--surface);
1033 background-image: none;
1034 border: 0.0625em solid alpha(var(--border), 0.85);
1035 border-radius: 0.25em;
1036 box-shadow: 0 0.75em 1.75em alpha(var(--bg), 0.28), 0 0.0625em 0 alpha(var(--border), 0.18) inset;
1037 padding: 0;
1038}
1039
1040.context-menu {
1041 padding: 0.125em 0;
1042 min-width: 10.5em;
1043}
1044
1045.context-menu button.context-menu-item {
1046 background-color: transparent;
1047 background-image: none;
1048 border: none;
1049 border-radius: 0;
1050 color: var(--text);
1051 padding: 0.25em 0.75em;
1052 margin: 0;
1053 min-height: 1.75em;
1054 transition: background-color 120ms ease-in-out, color 120ms ease-in-out;
1055}
1056
1057.context-menu button.context-menu-item:hover,
1058.context-menu button.context-menu-item:focus,
1059.context-menu button.context-menu-item:active {
1060 background-color: alpha(var(--surface-variant), 0.9);
1061 color: var(--text);
1062}
1063
1064.context-menu button.context-menu-item:active {
1065 background-color: alpha(var(--accent), 0.16);
1066}
1067
1068.context-menu button.context-menu-item:disabled {
1069 color: var(--muted);
1070 opacity: 0.7;
1071}
1072
1073.context-menu-separator {
1074 background-color: alpha(var(--border), 0.55);
1075 margin: 0.25em 0.5em;
1076 min-height: 0.0625em;
1077}
1078`
1079}
1080
1081// generateToasterCSS creates toast notification styles.
1082// Uses em units for scalable UI.
1083func generateToasterCSS(p Palette) string {
1084 return `/* ===== Toaster Styling ===== */
1085
1086/* Toast notification container */
1087.toast {
1088 background-color: var(--surface-variant);
1089 border-radius: 0.25em;
1090 padding: 0.375em 0.625em;
1091 margin: 0.5em;
1092 font-size: 0.875em;
1093 font-weight: 600;
1094 box-shadow: 0 2px 8px alpha(black, 0.3);
1095 min-width: 2.5em;
1096}
1097
1098/* Toast level: info (default, accent color) */
1099.toast-info {
1100 background-color: alpha(var(--accent), 0.9);
1101 color: var(--bg);
1102}
1103
1104/* Toast level: success */
1105.toast-success {
1106 background-color: alpha(var(--success), 0.9);
1107 color: var(--bg);
1108}
1109
1110/* Toast level: warning */
1111.toast-warning {
1112 background-color: alpha(var(--warning), 0.9);
1113 color: var(--bg);
1114}
1115
1116/* Toast level: error */
1117.toast-error {
1118 background-color: alpha(var(--destructive), 0.9);
1119 color: var(--bg);
1120}
1121
1122/* Toast with custom styling (mode indicator toasters) */
1123.toast-custom {
1124 color: #ffffff;
1125}
1126
1127/* Mode-specific toast colors */
1128.toast-pane-mode {
1129 background-color: var(--pane-mode-color);
1130 color: #ffffff;
1131}
1132
1133.toast-tab-mode {
1134 background-color: var(--tab-mode-color);
1135 color: #ffffff;
1136}
1137
1138.toast-session-mode {
1139 background-color: var(--session-mode-color);
1140 color: #ffffff;
1141}
1142
1143.toast-resize-mode {
1144 background-color: var(--resize-mode-color);
1145 color: #ffffff;
1146}
1147`
1148}
1149
1150// generateLinkStatusCSS creates link status overlay styles.
1151// Uses CSS transitions for fade-in/fade-out effect.
1152func generateLinkStatusCSS(p Palette) string {
1153 return `/* ===== Link Status Overlay Styling ===== */
1154
1155/* Link status container - bottom left positioning */
1156.link-status {
1157 background-color: alpha(var(--surface-variant), 0.95);
1158 border-radius: 0.25em 0.25em 0 0;
1159 padding: 0.25em 0.5em;
1160 margin: 0;
1161 font-size: 0.75em;
1162 /* Note: max-width not supported in GTK4 CSS, use label SetMaxWidthChars instead */
1163 box-shadow: 0 -1px 4px alpha(black, 0.15);
1164
1165 /* Fade transition - hidden by default */
1166 opacity: 0;
1167 transition: opacity 150ms ease-in-out;
1168}
1169
1170/* Visible state - triggered by adding .visible class */
1171.link-status.visible {
1172 opacity: 1;
1173}
1174
1175/* Link status label text */
1176.link-status label {
1177 color: var(--muted);
1178}
1179`
1180}
1181
1182func generateFloatingPaneCSS(p Palette) string {
1183 _ = p
1184 return `/* ===== Floating Pane Styling ===== */
1185.floating-pane-outer {
1186 /* Positioning handled in Go via overlay alignment and margins. */
1187}
1188
1189.floating-pane-container {
1190 background-color: var(--surface-variant);
1191 border: 0.0625em solid var(--border);
1192 border-radius: 0.25em;
1193 box-shadow: 0 0.5em 1.5em alpha(black, 0.25);
1194 opacity: 0;
1195 transition: opacity 110ms ease-out;
1196}
1197
1198.floating-pane-container.floating-pane-visible {
1199 opacity: 1;
1200}
1201
1202.floating-pane-container.pane-active {
1203 border-color: var(--accent);
1204}
1205
1206.floating-pane-omnibox {
1207 background-color: shade(var(--surface-variant), 1.08);
1208 border-bottom: 0.0625em solid var(--border);
1209 padding: 0.375em 0.5em;
1210}
1211
1212.floating-pane-content {
1213 background-color: var(--bg);
1214 min-height: 12em;
1215}
1216`
1217}