The Lisa doesn't have square pixels, so the canvas is scaled to be 1.5x as high as it is wide. This generally looks fine on high-dpi displays, because there's technically twice as much space to render with (pixels are 2px wide by 3px high). However, things will look distorted on a lower resolution display (where pixels are 1px wide by 1.5px high). That's just a compromise I made when designing this.
The good news is, if you have a large enough low-dpi display, and you make the window big enough, the automatic integer scaling settings will kick in, and the pixels themselves will be displayed larger. This can be forced via the preferences app (under the display options). If you screw this up, then restart LisaGUI while holding the shift key to reset the scaling settings.
EDIT: Unrelated to this, there are a couple minor bugs with PWAs on iOS relating to the positioning of the canvas. These can be resolved by rotating your device to a different orientation and then rotating it back to the original position... but this is annoying.
EDIT 2: To close windows, just double click the icon in the titlebar! This "collapses the window back into an icon."
How do you handle dynamic window/font scaling regardless of browser size (you get it for free with html mostly).
It's integer scaling; it involves changing the width, height, and style attributes of the canvas dynamically. I have a whole class that handles this, and let me tell you it took a lot of effort to get working properly and involved juggling around quite a few parameters, including the DPI, the border width, the pixel aspect ratio, and more. I had to use a ResizeObserver object to detect changes in the size of the DOM's body element.
To get the canvas to be consistently smooth, I had to apply a lot of contrast using a CSS filter, and I set image-rendering to pixelated, IIRC.
It might be worthwhile to overshoot the target resolution significantly with the nearest-neighbor scaler, then downscale interpolatively (e.g. bilinear) to the viewport. This should help dealing with doubled rows/columns and moire patterns, while maintaining visually crisp pixels.
I do something similar when dealing with raw screenshots from DOS games, which are typically 320x200, but intended for 4:3 display, so pixels are 5:6. In this case, upscaling to 1600x1200 (the smallest possible integer upscale to achieve the correct aspect ratio) with nearest-neighbor, then downscaling with bilinear to the target res (e.g. 1440x1080, 960x720, etc.) works really well.
Not only do you upscale with nearest neighbor then downscale with bilinear, you also make sure that you are using the correct colorspace for downscaling.
The most common mistake is to reinterpret sRGB as if it is a linear color space, and assume that half of RGB 255,255,255 is RGB 128,128,128. But that's wrong. sRGB is a gamma-compressed color space that does not respond linearly.
First you treat all sRGB values as if they are between 0 and 1. To convert into linear RGB, take all values to the power of 2.2 (like squaring them). Then you do any math on color values in Linear RGB. For example, half of linear 1 is indeed 0.5. To convert back into sRGB, take all values to the power of 1/2.2 (like a square root).'
This means that 0.5 in linear RGB corresponds to 0.73 in sRGB. You can confirm this by placing a black and white checkerboard next to a square of RGB 186,186,186. They will match in apparent intensity (provided you have an sRGB display that doesn't have viewing angle problems).
Some graphics APIs can do the sRGB to Linear RGB conversion for you automatically, but you need to request the API to do that. This makes the pixel shader only see Linear RGB, but the pixels are coming from an sRGB texture, and are being written to an sRGB buffer as output.
The short answer is, you can't
If we're talking about pixel perfect rendering there are various issues in the Web APIs that conspire against you.
devicePixelRatio (dpr) - This is supposed to tell you how many CSS pixels = one device pixel. This particular demo ignores it. On my Windows PC my dpr is 1.25 and the site looks horrendous with every other pixel 4th pixel of a font being double wide (or something along those lines). Not dissing the demo, it's cool. It's uncommon for Web devs to be aware of these issues as like 99.99% are on a Mac and never set zoom to anything other then 100%
The idea was supposed to be, you could take the CSS size and multiply by devicePixelRatio to get the display size. Unfortunately that does not work for various reasons.
There's zoom. Firefox and Chrome change the devicePixelRatio in response to zooming. Safari does not. Even if Safari supported this it's not enough. You can maybe repo how bad the site looks on my Windows PC on a Mac by zooming in on any browser (pick zoom from the menus, not pinch to zoom - see below)
Next there is pixel snapping. The short version is, imagine your window is 3 pixels wide and you ask the browser to make 2 elements side by side each 50% wide. If you check the CSS width both elements will say they are 1.5 pixels wide (getContentBoundingRect().width). But the browser won't display a 1.5 pixel element. I will snap one element 2 pixels and the other 1 pixel. To find out which is which you're supposed to use ResizeObserver with devicePixelContentBoxSize, but again Safari doesn't support this. It's not just with splits, that just the easiest way to demo the issue. Get this wrong and your checkerboard background will have a stripe somewhere where the checkerboard is missing a row or column.
Next there is pinch to zoom. It's separate and reported no where in the Web API AFAICT. The browsers will re-render text/svg to a higher resolution when you pinch to zoom but they don't pass that info to the page so it can not re-render canvases in response.
So: tl;dr, you can't - partly because of Safari. Partly because the Web is missing functionality
I should clarify I'm indeed not using the api call to get device pixel ratio; I forgot I had ended up not needing it because all that code is stuff I wrote about a year ago, lol