diff --git a/web/SitePreview.tsx b/web/SitePreview.tsx index 24ad74f..a9cffef 100644 --- a/web/SitePreview.tsx +++ b/web/SitePreview.tsx @@ -102,7 +102,13 @@ export const SitePreview = ({ clientRef.current = client; const { device } = useViewMode(); - const { width } = DEVICES[device]; + /* + `null` means fluid — Desktop takes the panel's width. See DEVICES. + + Everything below reads `frame`: the real number once the panel has been measured. Until then it is + the panel's own width, which is what a fluid frame is anyway. + */ + const { width: deviceWidth } = DEVICES[device]; const [ready, setReady] = useState(false); const [failed, setFailed] = useState(null); @@ -123,6 +129,9 @@ export const SitePreview = ({ const box = useRef(null); const [available, setAvailable] = useState({ width: 0, height: 0 }); + // The width the frame is actually rendering at, for the caption and the centring maths. + const frameWidth = deviceWidth ?? Math.round(available.width); + useEffect(() => { const element = box.current; if (!element) return; @@ -250,8 +259,8 @@ export const SitePreview = ({ useEffect(() => { const element = scroller.current; if (!element) return; - element.scrollLeft = Math.max(0, (width - element.clientWidth) / 2); - }, [width, available.width, loaded]); + element.scrollLeft = Math.max(0, (frameWidth - element.clientWidth) / 2); + }, [frameWidth, available.width, loaded]); return (
@@ -273,7 +282,9 @@ export const SitePreview = ({ the platforms where scrollbars are always classic. */ className="mx-auto overflow-auto rounded-lg border border-border bg-white [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" - style={{ width: Math.min(width, available.width || width), height: shownHeight }} + // Fluid fills the panel; a fixed device is capped by it so a 1440 frame in a 900 panel + // scrolls rather than overflowing the workspace. + style={{ width: deviceWidth === null ? '100%' : Math.min(frameWidth, available.width || frameWidth), height: shownHeight }} > {message ? (

{message}

@@ -299,7 +310,7 @@ export const SitePreview = ({ title={`The site at /${path}`} className="border-0" // The device's real width, and the room's real height. No transform: see the note above. - style={{ width, height: shownHeight }} + style={{ width: deviceWidth === null ? '100%' : frameWidth, height: shownHeight }} /> )} @@ -308,7 +319,7 @@ export const SitePreview = ({ {/* Always, now that editing is the only mode. It says which server this is and at what width, which the device buttons show but the SERVER half of it — dev, not production — they do not. */}

- The development server, live at {width}px. Save to see a change here. + The development server, live at {frameWidth}px. Save to see a change here.

); diff --git a/web/ViewMode.tsx b/web/ViewMode.tsx index 512229b..a723d23 100644 --- a/web/ViewMode.tsx +++ b/web/ViewMode.tsx @@ -63,8 +63,21 @@ export type DeviceName = 'desktop' | 'tablet' | 'mobile'; * desktop 1440 — a common laptop, comfortably past the 1280 the content column caps at, so the * margins either side are visible rather than implied. */ -export const DEVICES: Record = { - desktop: { label: 'Desktop', width: 1440 }, +/** + * `width: null` means FLUID — the frame takes whatever the panel gives it. + * + * Desktop is fluid and the other two are not, which is the honest split. Tablet and mobile are claims + * about a real device: 834 and 390 are widths a page will actually be asked to render at, and pinning + * them is the only way the preview can answer "what does this look like on a phone". + * + * There is no such width for desktop. A desktop is whatever window somebody opened, so 1440 was a + * guess that then had to be scaled to fit the panel — a page rendered at one width and shown at + * another, with the measure guides landing wherever the scale put them. Fluid, the frame IS a desktop + * window: the site's media queries answer the panel's real width and the guides mark the measure in + * the pixels you are looking at. + */ +export const DEVICES: Record = { + desktop: { label: 'Desktop', width: null }, tablet: { label: 'Tablet', width: 834 }, mobile: { label: 'Mobile', width: 390 }, }; diff --git a/web/canvas.ts b/web/canvas.ts index c89e746..bf2ff5d 100644 --- a/web/canvas.ts +++ b/web/canvas.ts @@ -512,37 +512,6 @@ export function paintMeasure(document: Document): () => void { // lines against the edges would be noise claiming to be information. if (box.width >= (document.documentElement.clientWidth || 0) - 1) return; - /* - The margins THEMSELVES, not only the lines that bound them. - - Two hairlines say where the measure ends and leave the reader to notice that the space outside is - empty. Tinting it says what that space IS: room the page is not using, which is the question - somebody widening a gutter or judging a desktop layout is actually asking. On a narrow frame there - is nothing to tint and the block below returns before drawing any of it. - */ - for (const [side, from, width] of [ - ['left', 0, box.left], - ['right', box.right, (document.documentElement.clientWidth || 0) - box.right], - ] as const) { - if (width <= 0) continue; - const margin = document.createElement('div'); - margin.setAttribute(MARK, `measure-margin-${side}`); - style(margin, [ - 'position:fixed', - 'top:0', - 'bottom:0', - `left:${Math.round(from)}px`, - `width:${Math.round(width)}px`, - // Faint, because this is the part of the screen the page does NOT occupy: it has to read as - // absence. Anything stronger competes with the content for the eye. - `background:color-mix(in srgb, ${ACCENT} 7%, transparent)`, - 'pointer-events:none', - 'z-index:2147482999', - ]); - document.body.append(margin); - guides.push(margin); - } - for (const [side, x] of [ ['left', box.left], ['right', box.right],