From a75ad6f5da7f82d73f04add01f9e1fd9c9efa1bb Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Tue, 18 Aug 2026 20:30:08 +0000 Subject: [PATCH] Overlay scrollbars via OverlayScrollbars overflow: overlay is dead in current Chromium, so an appearing native scrollbar shifted the layout again. OverlayScrollbars replaces it with floating auto-hiding scrollbars that never reserve space (native window scroll APIs unaffected on a body target). Themed via --os-* variables in pagerite.css; the html scrollbar-color styling stays as no-JS fallback. (package-lock.json is gitignored.) --- AGENTS.md | 4 ++++ frontend/package.json | 1 + frontend/src/assets/pagerite.css | 33 ++++++++++++-------------------- frontend/src/pagerite.js | 12 ++++++++++++ 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cd31b97..438aaa2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -119,6 +119,10 @@ not for the public pages. See `docs/design-principles.md` for the design. - `frontend/src/` — the Vue editor and public-page entries. - `main.js` — Vue editor app entry, mounts PageEditor/SiteEditor. - `pagerite.js` — public page entry; runs fetch-navigation, scroll-reveal, + OverlayScrollbars on `document.body` (floating, auto-hiding scrollbars + that never reserve layout space or shift the page when appearing; + native scroll APIs like `window.scrollTo` keep working; themed via the + `--os-*` variables in pagerite.css), brand shrink-to-fit (the themed size is the maximum; JS reduces the font-size so a long brand or narrow viewport still fits one line), code copy buttons, and the auth check. It first probes `GET /auth/api/settings` diff --git a/frontend/package.json b/frontend/package.json index 6a169e6..925cbc6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "@codemirror/view": "^6.43.8", "@lezer/highlight": "^1.2.3", "codemirror": "^6.0.2", + "overlayscrollbars": "^2.16.0", "paskia": "file:../../paskia/paskia-js", "transliteration": "^2.6.1", "vue": "^3.5.26", diff --git a/frontend/src/assets/pagerite.css b/frontend/src/assets/pagerite.css index 19159a4..187ebb5 100644 --- a/frontend/src/assets/pagerite.css +++ b/frontend/src/assets/pagerite.css @@ -67,31 +67,22 @@ html { scroll-behavior: smooth; - /* Overlay scrollbars where supported (Chromium/WebKit, deprecated but - working): a scrollbar that appears covers a strip of the layout instead - of shifting it, and no space is reserved while absent — so 100vw always - equals the body width and .wide breakout margins stay exact. Ignored by - Firefox (unknown value), which keeps its thin classic scrollbar. */ - overflow-y: overlay; - /* Lightweight scrollbar styling: thin, theme-muted thumb on a transparent - track where supported (Firefox: the two properties; WebKit: the rules - below). Full restyling deliberately not attempted. */ + /* Native-scrollbar fallback styling (JS off or before pagerite.js runs): + thin, theme-muted thumb on a transparent track. With JS the scrollbars + are replaced by OverlayScrollbars (see pagerite.js) — floating, + auto-hidden scrollbars styled by the --os-* variables below, so they + never reserve layout space or shift the page when appearing. */ scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--muted) 45%, transparent) transparent; } -::-webkit-scrollbar { - width: 0.5rem; - height: 0.5rem; -} - -::-webkit-scrollbar-thumb { - background: color-mix(in srgb, var(--muted) 45%, transparent); - border-radius: 0.25rem; -} - -::-webkit-scrollbar-track { - background: transparent; +.os-scrollbar { + --os-size: 0.5rem; + --os-thumb-bg: color-mix(in srgb, var(--muted) 45%, transparent); + --os-thumb-hover-bg: color-mix(in srgb, var(--muted) 65%, transparent); + --os-thumb-active-bg: var(--muted); + --os-track-bg: transparent; + --os-thumb-border-radius: 0.25rem; } body { diff --git a/frontend/src/pagerite.js b/frontend/src/pagerite.js index e06ba11..2fc1587 100644 --- a/frontend/src/pagerite.js +++ b/frontend/src/pagerite.js @@ -5,8 +5,20 @@ // Also: scroll-reveal effects and code copy buttons. These need no // support from the article itself and are re-applied after each swap. import { showAuthIframe } from 'paskia' +import { OverlayScrollbars } from "overlayscrollbars"; +import "overlayscrollbars/overlayscrollbars.css"; (() => { + // Overlay scrollbars: the native kind reserves a strip of layout (or + // shifts the layout when it appears; overflow: overlay is dead), so we + // replace it with floating ones that cover content instead. The body + // remains the native viewport scroller — window scroll events, scrollY + // and scroll restoration are unaffected; only the scrollbar UI is + // custom. Theme variables are in pagerite.css. + OverlayScrollbars(document.body, { + scrollbars: { autoHide: "scroll" }, + }); + if (import.meta.env.DEV) { // In dev the base stylesheet is injected by Vite from JS (linking the // raw module would pull in its HMR wrapper). Theme and banner-design