Rural Income gets its Play-UI — and a five-format export pipeline to go with it
The first Play-UI build for any reach-edu-hub deck: eleven static, presentation-ready slides for Rural Income Boosts through Apprenticeship Degrees, plus a script that turns them into a PDF, a PPTX, and a Google-Slides-ready deck on demand. Two real rendering bugs in the shell's /play surface got found and fixed along the way.
Rural Income's Play-UI, and the export pipeline behind it
Why Care?
A Scroll-UI deck is right for browsing on the site. It's the wrong shape for "open this in Google Slides and present it tomorrow." Rural Income — the first and most-audited of the seven strategy decks — is now the first to have both: eleven Play-UI slides, static and pinned to a real 16:9 canvas, and a script that turns them into whatever format the moment calls for.
What's New?
Rural Income's Play-UI is live —
/play/rural-income/v1/{01..11}, recreated from the Scroll-UI source per the "recreate, don't extract" discipline, not a mechanical port. Sized up for a wide canvas rather than a scroll column — a literal port of scroll-proportioned spacing reads as sparse on 16:9, so type and card scale are meaningfully larger here.Five export formats, one script —
scripts/export-rural-income.mjs(+ a native-shapes companion) produces:Retina PNGs, one per slide
An 11-page vector PDF
A self-contained HTML snapshot
A PPTX with one full-bleed image per slide (what actually opens cleanly in Google Slides today)
A PPTX with real, editable text boxes and shapes — same content, genuinely editable in Slides, at the cost of falling back to system fonts and flattening gradient headlines to a solid color
Two real shell bugs found and fixed, both root-caused rather than patched around:
The
/playroute's stage never scaled its content to fit — a fixed 1920×1080 slide would bleed off the page in any container that wasn't exactly that size. Fixed by wrapping every slide in the same "fixed design-space +transform: scale()via container-query units" technique the shell's ownSlideCanvasuses elsewhere, applied directly since this particular route didn't provide it./playsets no defaultdata-modeat all — a bare viewer would see black text on a transparent background until they manually toggled a mode. Fixed with explicit light-mode fallback values on every color/ font token this deck's styles reference, so "no mode set" now renders as light mode instead of unstyled.
How it Works
The scale-to-fit fix, concretely: .play-canvas is a responsive outer box
that fills whatever the shell's .ddd-frame__stage hands it and paints the
page background across the whole thing (so no dark shell default peeks
through in the gaps). .play-section is the fixed 1920×1080 design stage
inside it, scaled down — never up — via
transform: scale(min(100cqw/1920px, 100cqh/1080px)). Verified by shrinking
the browser viewport to 960×720 (a different aspect ratio entirely) and
confirming the slide scaled proportionally with no bleed, rather than just
trusting the CSS on paper.
The export script leans on the shell's existing /play/{deck}/{variant}/print
route for the PDF and HTML exports — it already lays out all eleven slides
with page-break-after CSS, so page.pdf() and page.content() just work
against it. The image-based PPTX reuses the same PNGs the images export
produces; the native-shapes PPTX is hand-mapped from the eleven source
files rather than scraped from the DOM, which was faster and more reliable
than building a generic converter for an 11-slide one-off.
What's Next
The other six strategy decks don't have Play-UI yet — same recipe applies
per deck, rated and scaffolded through /toc/{deck} first. exports/ is
gitignored (matching the calmstorm-decks convention); regenerate via
node scripts/run-export-rural-income.mjs against a running pnpm dev.
