From 6a1103bf5f02cc187db42f9227810efab6b0076e Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 25 Jul 2026 13:25:05 +0200 Subject: [PATCH] fix(zoom): keep the window filled when zoomed out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CSS `zoom` doesn't rescale viewport units, so the 100vh app root left an empty strip below the UI when zoomed under 100%. Counter-size the app root to (100/z)vw × (100/z)vh so that, after the zoom scales it back down, it exactly fills the window again. Zoom still applies to the whole document so portalled menus/modals scale with it. --- frontend/src/App.tsx | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cfc44f6..c89a1d2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -845,13 +845,29 @@ export default function App() { useEffect(() => { wkEsmRef.current = wkEsm; }, [wkEsm]); // Persistent Ctrl+wheel zoom. The native WebView2 Ctrl+wheel zoom isn't saved, - // so we run our own: CSS `zoom` on the root element, factor stored in - // localStorage and restored at startup. Ctrl+0 resets to 100%. + // so we run our own: CSS `zoom` on the app root, factor stored in localStorage + // and restored at startup. Ctrl+0 resets to 100%. + // + // `zoom` scales the element uniformly (grid included) but does NOT rescale + // viewport units, so a 100vh/100vw root would leave an empty strip when zoomed + // out. We counter that by sizing the root to (100/z)vw × (100/z)vh so that, + // after the zoom multiplies it back down, it exactly fills the window. + const appRootRef = useRef(null); useEffect(() => { const KEY = 'opslog.uiZoom'; let z = parseFloat(localStorage.getItem(KEY) || '1'); if (!Number.isFinite(z) || z <= 0) z = 1; - const apply = () => { (document.documentElement.style as any).zoom = String(z); }; + const apply = () => { + // Zoom the whole document (so portalled menus/modals/tooltips scale too)… + (document.documentElement.style as any).zoom = String(z); + // …and counter-size the app root: `zoom` doesn't rescale vh/vw, so a 100vh + // root leaves an empty strip when zoomed out. (100/z)vh zoomed by z = 100vh. + const el = appRootRef.current; + if (el) { + el.style.width = `${100 / z}vw`; + el.style.height = `${100 / z}vh`; + } + }; apply(); const onWheel = (e: WheelEvent) => { if (!e.ctrlKey && !e.metaKey) return; // plain wheel is left alone (scroll / freq nudge) @@ -4034,7 +4050,7 @@ export default function App() { }; return ( -
+
{/* ===== TOPBAR ===== */} {compact ? (