diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 86eff25..ffbb9cc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -845,31 +845,28 @@ 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 app root, factor stored in localStorage - // and restored at startup. Ctrl+0 resets to 100%. + // so we run our own, 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. + // We use `transform: scale` (NOT CSS `zoom`): `zoom` re-lays-out and rounds each + // element to the pixel grid, which opens ~1px seams between map tiles. `transform` + // scales the whole subtree as one composited layer, so tiles stay seamless. The + // app root is counter-sized to (100/z)vw × (100/z)vh and scaled from its top-left + // by z, so it reflows to fill that larger box and then scales back to exactly the + // window (more content visible when zoomed out, like a real browser zoom). 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 = () => { - // Zoom the whole document (so portalled menus/modals/tooltips scale too)… - (document.documentElement.style as any).zoom = String(z); - // Flag the zoom state so the Leaflet tile-seam fix (style.css) only kicks in - // when actually zoomed (fractional scaling opens 1px gaps between tiles). - document.documentElement.setAttribute('data-uizoom', 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`; - } + if (!el) return; + el.style.transformOrigin = '0 0'; + el.style.transform = z === 1 ? '' : `scale(${z})`; + el.style.width = `${100 / z}vw`; + el.style.height = `${100 / z}vh`; + document.documentElement.setAttribute('data-uizoom', String(z)); }; apply(); const onWheel = (e: WheelEvent) => { diff --git a/frontend/src/style.css b/frontend/src/style.css index e0dd8f0..5b3d2b9 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -652,13 +652,3 @@ border: 2px solid var(--background); } ::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); } - -/* When the UI is CSS-zoomed (our persistent Ctrl+wheel zoom), map tiles are - scaled by a fractional factor, which opens ~1px seams between adjacent tiles - (visible as a faint grid over the map). Enlarging each 256px tile by 1px makes - neighbours overlap just enough to hide the seams. Scoped to the zoomed state so - it never touches the crisp 100% view. */ -html[data-uizoom]:not([data-uizoom="1"]) .leaflet-tile { - width: 257px !important; - height: 257px !important; -}