v0.26.0 ae3a77a

css.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close