[§] /docs/how-to/win95-chrome-and-verify
How to build Win95 chrome, and prove it all moves
Real bevels and pixel fonts for the retro act, then the checks that prove every piece moves, stops and fits.
TL;DR
- Bevels are four inset shadows, stored as
--w-bevel-outand--w-bevel-in. - Load pixel fonts before any canvas draws text.
- Verify with frames 1.5 s apart, a reduced-motion still, and 390px with no overflow. Then look at the pictures.
Goal: Windows 95 chrome that looks right to people who used it, and proof that each piece works.
You need
- The Win95 tokens in
app/global.css(--w-*) and the fonts inpublic/fonts/. - Playwright, and
scripts/validate-exp.ts.
bevel-out (raised button) bevel-in (pressed, sunken field) ┌──────────────┐ white ┌──────────────┐ black │ OK │ │ OK │ └──────────────┘ black └──────────────┘ white + grey inner edges + grey inner edges, swapped
Win95 chrome
1. Use the bevel tokens
.w95-button {
background: var(--w-face);
box-shadow: var(--w-bevel-out);
font-family: var(--font-w95);
font-size: 16px;
-webkit-font-smoothing: none;
}
.w95-button:active { box-shadow: var(--w-bevel-in); padding: 1px 0 0 1px; }Four hard inset shadows, no blur, no radius. The 1px nudge on press is what makes it feel clicked.
2. Keep pixel fonts on their grid
The VGA font is 8x16. Use 16, 32, 48 or 64px only, with -webkit-font-smoothing: none. Any other size
blurs the pixels.
3. Load fonts before canvas text
A canvas draws with whatever font is ready. If the pixel font has not loaded, you get a fallback, and it never updates.
await document.fonts.load('16px "Web IBM VGA 8x16"');
await document.fonts.load('16px "MS Sans Serif Pixel"');
draw();Prove it
4. Capture two frames 1.5 s apart
A single screenshot proves nothing about motion. Two frames must differ.


5. Check the reduced-motion still
Run again with reducedMotion: 'reduce'. The two frames must now match, and the still must look
composed, not half-way through a move. For canvases, that still is the poster.
6. Check 390px
At 390px wide, document.documentElement.scrollWidth - innerWidth must be 0.
7. Run the validator
npx tsx scripts/validate-exp.ts pixel-blast # one experience at /lab/pixel-blast
npx tsx scripts/validate-exp.ts /docs/how-to # or any routeIt prints moves, reducedStill, mobileOverflowPx and console errors, and saves
desktop-a/b, reduced-a/b and mobile-a/b screenshots to .validate/<id>/. Where the repo has
scripts/posters.ts, run it to refresh the reduced-motion posters.
8. Open the screenshots and look
A black canvas with one flickering pixel counts as "moving". Open every screenshot. Check the thing you meant is on screen, readable, and not cut off at 390px.
The GL budget
- At most two live WebGL canvases on screen at once. Browsers cap contexts, and old ones get lost.
dpr={[1, 1.75]}on every canvas.- Pause off screen and when the tab is hidden.
- Load 3D with
next/dynamicandssr: false, with the ASCII loader while it loads.
Why it works
The bevel is four lines of light, so it stays sharp at any zoom. The checks are cheap and mechanical, so you run them every time, and they catch the failures that look fine in one screenshot.
See it live
- Win95 boot
- Win95 desktop
- The full story of a Win95 site: BR95 case study