diff --git a/README.md b/README.md index ca1465a..d4575cf 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,10 @@ exactly 36 screen pixels, as even as at 2x, and only sub-cell pixels alternate. than halves because coarse steps overshoot — with halves this jumped 2x to 2.5x, too small to too large with nothing in between. +The scale is then CAPPED at 2x. Fitting the panel is not the same as looking right, and past 2x this stops +reading as a window on a desktop. That is a default, not a verdict — the panel header has a zoom control, +and `MAX_SCALE` is one constant. + Two things the scale has to know about that are easy to miss. `devicePixelRatio` is not the whole story: the panel header has its own CSS `zoom`, so the effective ratio is `devicePixelRatio × zoom`, read as `getBoundingClientRect().width / offsetWidth`. And the stage is fitted into the box MINUS a margin, because diff --git a/web/MinesweeperPanel.tsx b/web/MinesweeperPanel.tsx index fac1962..f64c77a 100644 --- a/web/MinesweeperPanel.tsx +++ b/web/MinesweeperPanel.tsx @@ -27,6 +27,17 @@ const BUTTON_MASK: Record = { 0: 1, 1: 2, 2: 4 }; /** Breathing room around the stage, in device pixels. Small — it is a frame, not a margin. */ const MARGIN_DEVICE_PX = 24; +/** + * The ceiling on how large the game is drawn, whatever room the panel has. + * + * A taste decision, not a limit of the method: fitting the panel is not the same as looking right, and + * past 2x this stops reading as a window on a desktop and starts reading as a wall. Raise it and the + * quarter steps below take over again. + * + * The panel header has a zoom control, so this is a default rather than a verdict. + */ +const MAX_SCALE = 2; + // Plugin panels have no localisation mechanism yet — `officerdev`'s `useTranslation`/`panelCatalogs` // (merged by `I18nBridge`) is platform-internal and not exported from the barrel, and // `scripts/i18n/scan-hardcoded.ts` explicitly does not scan `plugins/`. So these stay plain English @@ -124,7 +135,8 @@ export const MinesweeperPanel = () => { const fit = Math.min(usableW / frame.width, usableH / frame.height); // Below 1 there is nothing to floor to, so take a unit fraction: the rows that survive are then // evenly spaced instead of dropped in clumps. - const scale = fit < 1 ? 1 / Math.ceil(1 / fit) : fit < 2 ? 1 : Math.floor(fit * 4) / 4; + const snapped = fit < 1 ? 1 / Math.ceil(1 / fit) : fit < 2 ? 1 : Math.floor(fit * 4) / 4; + const scale = Math.min(snapped, MAX_SCALE); return { width: (frame.width * scale) / box.dpr, height: (frame.height * scale) / box.dpr }; }, [frame, box]);