A presentation engine that checks its own layout
Slides as a plain data array, about forty lines of JavaScript, and a probe that measures every slide for overflow so I do not have to trust my own eyes.
The problem
I needed to build a technical presentation with real diagrams, and I wanted it to be a file I could version, diff and open anywhere — not a binary in someone else's format. The obvious answer is one of the HTML slide frameworks, and the obvious answer was too much: a build step and a dependency tree to render fourteen pages of mostly text and boxes.
The actual problem, though, was not authoring. It was that I could not tell whether a slide was broken. Content overflowing its slide is invisible when you build at one window size and present at another. I found out the hard way, mid-review, that a diagram had been quietly sitting on top of a slide number for several days.
The approach
Slides are a JavaScript array. Each entry is an object with a CSS class, a
footer label, and a string of HTML. The whole engine is a map over
that array into <section> elements, plus keyboard and click
navigation, plus one scaling function. There is no build step, no bundler, and
nothing to install. Editing a slide is editing one array element.
Fitting is one line of arithmetic: take the smaller of the width and height ratios between the window and a fixed 1280×800 stage, and set it as a CSS custom property that a transform reads. The deck is laid out once at fixed pixel dimensions and then scaled, which means layout is completely deterministic and identical on every screen.
The part I would keep in any future version is the QA probe. On load, it walks every slide and measures whether any element extends past the slide's usable area, then writes a machine-readable verdict into a hidden element. A headless browser loads the deck, greps that one line, and either reports clean or names the offending element. Layout correctness became a check I can run in two seconds instead of a thing I squint at.
Two bugs in the probe worth writing down
The probe was wrong twice before it was right, and both mistakes are the kind that generalise.
-
Scaled versus unscaled pixels.
getBoundingClientRect()returns on-screen pixels, which are post-transform, while the layout constants are unscaled CSS pixels. Mixing the two made the probe report phantom failures at one window size and hide real ones at another. The fix is to neutralise the scale to 1 for the duration of the measurement and restore it afterwards. A measuring instrument that changes its answer depending on your window size is not measuring anything. - Measure against the real obstacle. The first version compared against a guessed padding value with a fudge factor added. It passed while a diagram sat on top of the slide number. The fix was to measure against the footer rule's actual position. If you find yourself adding a magic constant to make a check pass, the check is testing the wrong thing.
Tradeoffs
- The deck requires JavaScript.
- No JS, no slides. For a presentation that is acceptable, but it means a deck can never be the only copy of an argument — anything that matters has to exist as prose or as a PDF too.
- Slide content is HTML strings inside a JS array.
- No syntax highlighting inside the string, no template checking, and an unclosed tag is found at runtime. In exchange the whole authoring model is one array with no build step. At fourteen slides that is clearly the right trade; at a hundred it would not be.
- A fixed 1280×800 stage is not responsive.
- It scales, which is not the same thing. It is fine on any laptop or projector and genuinely poor on a phone. It is a presentation tool, and I decided not to pretend otherwise.
- Per-slide CSS classes collide across decks.
- Numbered per-slide selectors are convenient and do not survive two decks sharing a stylesheet. Shared primitives belong in the common sheet and per-slide overrides need a namespace. I found this out by having two decks.
Outcome
Fourteen slides, one self-contained file, and a layout check that runs in about two seconds. The deck I built with it did its job. More usefully, the probe changed how I work: once a visual property is machine-checkable, you stop negotiating with yourself about whether it is probably fine.
What I would do differently
Split the engine from the content from the start. My first version inlined everything into one file — including base64-encoded fonts, which made the file 198 KB and meant a second deck would have been a copy-paste of the whole thing. The right shape, which this site now uses, is a shared stylesheet and engine plus a small data file per deck.
I would also gate the probe behind a query parameter rather than running it on every load. As built, every viewer pays for a full layout pass over every slide to produce a diagnostic only I read.