detached 991a91a

feat(cef): auto-hiding scrollbar with theme color support

Inject custom scrollbar CSS and JS on all pages via the content injector:
- Scrollbar invisible by default (transparent thumb)
- Appears with --primary theme color on scroll/wheel activity
- Widens from 6px to 10px on hover during scroll
- Fades out after 1s of inactivity via .dumber-scrolling class toggle
- Track transparent normally, 10% grey on hover
- Rounded corners on thumb
commit
991a91af8d668faf04760715659d7a42feab3c78
author
brice · · 2026-03-21 22:01 UTC
committer
brice · · 2026-03-28 04:38 UTC
parent
179d874
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +54 -0
M internal/infrastructure/cef/content_injector.go +54 -0
@@ -17,6 +17,56 @@ var _ port.ContentInjector = (*contentInjector)(nil)
1717 // and message bridge scripts. Must match the engine's InternalSchemePath prefix.
1818 const internalSchemePrefix = "dumb://"
1919
20+// scrollbarCSS styles the scrollbar with auto-hide behavior: invisible by
21+// default, fades in on scroll, widens on hover, fades out after 1s idle.
22+// Uses --primary theme color for the thumb.
23+const scrollbarCSS = `
24+::-webkit-scrollbar {
25+ width: 6px;
26+ height: 6px;
27+}
28+::-webkit-scrollbar-track {
29+ background: transparent;
30+}
31+::-webkit-scrollbar-thumb {
32+ background: transparent;
33+ border-radius: 3px;
34+ transition: background 0.3s ease;
35+}
36+.dumber-scrolling ::-webkit-scrollbar-thumb {
37+ background: var(--primary, rgba(128, 128, 128, 0.4));
38+}
39+.dumber-scrolling ::-webkit-scrollbar:hover {
40+ width: 10px;
41+ height: 10px;
42+}
43+.dumber-scrolling ::-webkit-scrollbar-track:hover {
44+ background: rgba(128, 128, 128, 0.1);
45+}
46+.dumber-scrolling ::-webkit-scrollbar-thumb:hover {
47+ background: var(--primary, rgba(128, 128, 128, 0.6));
48+}
49+::-webkit-scrollbar-corner {
50+ background: transparent;
51+}
52+`
53+
54+// scrollbarAutoHideJS adds/removes the .dumber-scrolling class on <html>
55+// on scroll activity, with a 1s fade-out timeout after scrolling stops.
56+const scrollbarAutoHideJS = `(function(){
57+ var t, el = document.documentElement;
58+ function show() {
59+ el.classList.add('dumber-scrolling');
60+ clearTimeout(t);
61+ t = setTimeout(function(){ el.classList.remove('dumber-scrolling'); }, 1000);
62+ }
63+ window.addEventListener('scroll', show, {passive:true,capture:true});
64+ window.addEventListener('wheel', show, {passive:true});
65+ window.addEventListener('mouseenter', function(e){
66+ if(el.scrollHeight > el.clientHeight) show();
67+ });
68+})();`
69+
2070 // contentInjector implements port.ContentInjector for the CEF engine.
2171 // It stores CSS strings and injects them into webviews via ExecuteJavaScript.
2272 // Thread-safe: InjectThemeCSS may be called from the UI thread while OnLoadEnd
@@ -128,6 +178,10 @@ func (ci *contentInjector) onLoadEnd(wv *WebView) {
128178 if findCSS != "" {
129179 ci.injectCSS(wv, "dumber-find-highlight", findCSS)
130180 }
181+
182+ // All pages get custom scrollbar styling with auto-hide.
183+ ci.injectCSS(wv, "dumber-scrollbar", scrollbarCSS)
184+ wv.RunJavaScript(context.Background(), scrollbarAutoHideJS)
131185 }
132186
133187 // injectCSS injects a CSS string as a <style> element via JavaScript.
@@ -17,6 +17,56 @@ var _ port.ContentInjector = (*contentInjector)(nil)
17 // and message bridge scripts. Must match the engine's InternalSchemePath prefix.17 // and message bridge scripts. Must match the engine's InternalSchemePath prefix.
18 const internalSchemePrefix = "dumb://"18 const internalSchemePrefix = "dumb://"
19 19
20+// scrollbarCSS styles the scrollbar with auto-hide behavior: invisible by
21+// default, fades in on scroll, widens on hover, fades out after 1s idle.
22+// Uses --primary theme color for the thumb.
23+const scrollbarCSS = `
24+::-webkit-scrollbar {
25+ width: 6px;
26+ height: 6px;
27+}
28+::-webkit-scrollbar-track {
29+ background: transparent;
30+}
31+::-webkit-scrollbar-thumb {
32+ background: transparent;
33+ border-radius: 3px;
34+ transition: background 0.3s ease;
35+}
36+.dumber-scrolling ::-webkit-scrollbar-thumb {
37+ background: var(--primary, rgba(128, 128, 128, 0.4));
38+}
39+.dumber-scrolling ::-webkit-scrollbar:hover {
40+ width: 10px;
41+ height: 10px;
42+}
43+.dumber-scrolling ::-webkit-scrollbar-track:hover {
44+ background: rgba(128, 128, 128, 0.1);
45+}
46+.dumber-scrolling ::-webkit-scrollbar-thumb:hover {
47+ background: var(--primary, rgba(128, 128, 128, 0.6));
48+}
49+::-webkit-scrollbar-corner {
50+ background: transparent;
51+}
52+`
53+
54+// scrollbarAutoHideJS adds/removes the .dumber-scrolling class on <html>
55+// on scroll activity, with a 1s fade-out timeout after scrolling stops.
56+const scrollbarAutoHideJS = `(function(){
57+ var t, el = document.documentElement;
58+ function show() {
59+ el.classList.add('dumber-scrolling');
60+ clearTimeout(t);
61+ t = setTimeout(function(){ el.classList.remove('dumber-scrolling'); }, 1000);
62+ }
63+ window.addEventListener('scroll', show, {passive:true,capture:true});
64+ window.addEventListener('wheel', show, {passive:true});
65+ window.addEventListener('mouseenter', function(e){
66+ if(el.scrollHeight > el.clientHeight) show();
67+ });
68+})();`
69+
20 // contentInjector implements port.ContentInjector for the CEF engine.70 // contentInjector implements port.ContentInjector for the CEF engine.
21 // It stores CSS strings and injects them into webviews via ExecuteJavaScript.71 // It stores CSS strings and injects them into webviews via ExecuteJavaScript.
22 // Thread-safe: InjectThemeCSS may be called from the UI thread while OnLoadEnd72 // Thread-safe: InjectThemeCSS may be called from the UI thread while OnLoadEnd
@@ -128,6 +178,10 @@ func (ci *contentInjector) onLoadEnd(wv *WebView) {
128 if findCSS != "" {178 if findCSS != "" {
129 ci.injectCSS(wv, "dumber-find-highlight", findCSS)179 ci.injectCSS(wv, "dumber-find-highlight", findCSS)
130 }180 }
181+
182+ // All pages get custom scrollbar styling with auto-hide.
183+ ci.injectCSS(wv, "dumber-scrollbar", scrollbarCSS)
184+ wv.RunJavaScript(context.Background(), scrollbarAutoHideJS)
131 }185 }
132 186
133 // injectCSS injects a CSS string as a <style> element via JavaScript.187 // injectCSS injects a CSS string as a <style> element via JavaScript.

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