← Corpus / reach-edu-hub / exploration
Exporting the Strategy Decks to Google Slides
The seven strategy decks live as code — Astro Scroll-UI sections, not slides in any format Google Slides understands. Getting them into Slides for a stakeholder who wants to open, present, or lightly edit them in the tool she already knows is a real pipeline question, not a five-minute export. This names the actual path: what format has to exist first, what's proven prior art from calmstorm-decks, and what reach-edu-hub is missing today to make it clean.
- Path
- explorations/Exporting-Strategy-Decks-to-Google-Slides.md
- Authors
- Michael Staton
- Augmented with
- Claude Code on Claude Sonnet 5
- Tags
- Deck-Export · Google-Slides · PPTX · Playwright · Screenshot-Pipeline · Play-UI · Scroll-UI · Static-Site-Constraints · Known-Limitations
Exporting the Strategy Decks to Google Slides
The ask, in one line
Get the seven strategy decks (/scroll/{deck}/{variant}) into Google Slides,
so a stakeholder can open, present, and maybe lightly edit them in a tool she
already knows — without asking “do we need to make them SVGs or PPTs first?”
being an open question every time.
The honest reframing
Short answer to “SVG or PPT first”: neither, directly — PNG, then PPTX. SVG doesn’t help here (confirmed below); PPTX is the right assembly format, but the thing that has to exist first isn’t a file format, it’s a canvas. Here’s why that matters more than it sounds like it should.
- Google Slides doesn’t import code, HTML, or a live URL. The only way
content gets into Slides is: (a)
.pptx/.key/.odpfile import, or (b) the Slides API creating shapes/images programmatically. Either way, something has to render the Astro deck into pixels or shapes before Slides ever sees it. - The clean way to get pixels is a headless browser, not a converter. This is already solved and documented for this exact family of sites — see [[High-Resolution-High-Fidelity-Deck-Exports-from-Code-to-Images-&-PDFs]] in calmstorm-decks. No PDF/HTML-to-slide library “gets it perfect” for a design-heavy deck (custom fonts, gradients, full-bleed images); the only renderer with full parity to what a human sees is a real Chromium, captured with Playwright.
- Reach’s strategy decks don’t have a slide canvas yet — only a scroll
canvas. This is the piece that’s specific to reach-edu-hub and wasn’t
true for calmstorm at the time of that doc. dididecks-ai’s own naming
convention (
../../CLAUDE.md) draws a hard line between two coordinated implementations of every deck:- Scroll-UI — what exists today for all seven strategies. Responsive sections in one long page, laid out for reading, not for a fixed 16:9 stage.
- Play-UI — standalone components at
src/components/slides/{variant}/{slot}-{slug}.astro, rendered insideSlideCanvasat a rigid 1920×1080, no responsive CSS, no JS. None of the seven strategy decks have these yet —src/components/slides/doesn’t exist in reach-edu-hub at all (confirmed by directory listing). Onlybasics/,citations/,markdown/,ui/.
- Calmstorm already hit this exact wall and named the fix. Calmstorm’s
export pipeline (Playwright +
pdf-lib, shipped and working) captures scroll sections directly, and its own retro names three geometric glitches that fall out of that: sections aren’t normalized to 16:9, neighbor-section bleed leaks into the capture, and sub-pixelscrollIntoViewIfNeededmisalignment. That doc’s own recommendation for “the next deck site” is to bake a strict 16:9 canvas into the section primitive from day one — which is precisely what Play-UI +SlideCanvasalready is, architecturally, in@dididecks/shell. reach-edu-hub already has the shell. It just hasn’t built the Play-UI files that would let this export skip calmstorm’s glitches entirely. - The site is gated now, too. As of today, the whole hub sits behind
src/middleware.ts(passcode or@reach.eduemail). Whatever captures these decks has to either run againstlocalhostwith the gate disabled, or authenticate — the same problem calmstorm’s exporter solved for its own gate, adapted for ours.
What “high fidelity” actually requires
Unchanged from calmstorm’s findings — every point below applies to reach-edu-hub verbatim, so no need to re-derive it:
- Wait for
document.fonts.readybefore capturing (custom fonts are the silent killer). waitForLoadState("networkidle"), then a scroll-tour to flush any lazy-loaded images, then one more idle wait.deviceScaleFactor: 2for crisp Retina-equivalent raster.- Viewport matched to the design — 1920×1080 for Play-UI slides specifically
(this is exact, not approximate, once Play-UI exists — the whole point of
SlideCanvas). colorSchemeandreducedMotionset deliberately, not left to headless Chromium’s defaults.- Gate bypass. New for reach-edu-hub: either run the capture against a
build with
RUH_GATE_PASSCODE/RUH_ALLOWED_EMAIL_DOMAINunset (gate effectively open in that environment) or have the capture script inject theruh_gate=grantedcookie viacontext.addCookies([...])before navigating — the same shape as calmstorm’saddInitScriptlocalStorage trick, adapted to a real HttpOnly cookie instead.
Options on the menu
Ordered lowest-ceremony to most-pipeline, same framing as the calmstorm precedent.
A. Manual, one deck at a time, today
Open each Scroll-UI deck in a real browser, screenshot each section by hand (DevTools “Capture node screenshot,” or just a careful window screenshot), and paste the resulting PNGs into a blank Google Slides deck — one image per new slide, sized to fill.
Pros: zero code, zero new dependencies, ships this week if the stakeholder meeting is close. Doesn’t require Play-UI to exist at all.
Cons: fully manual, every deck, every revision. Section boundaries are whatever the responsive layout happens to render at your window size — no guaranteed 16:9, so images will look inconsistently sized in Slides. Not a pattern; a one-off favor.
Verdict: the honest escape hatch if this needs to exist before Play-UI work can happen. Not a foundation to build on.
B. Automate today’s shape: Playwright over Scroll-UI sections → pptxgenjs
Reuse calmstorm’s export-decks.ts pattern almost as-is — Playwright
navigates each /scroll/{deck}/{variant} route, captures each
section.deck-section element as a PNG — but instead of stitching into a
PDF with pdf-lib, assemble a .pptx with
PptxGenJS: one slide per PNG, sized
full-bleed. Upload the .pptx to Google Drive, open with Google Slides.
Pros: no Play-UI prerequisite — works against what exists today. Reuses a proven capture pattern almost line-for-line (chrome-hiding, fidelity waits, gate-cookie injection). PPTX-with-full-bleed-images is a well-trodden, high-fidelity import path into Slides (confirmed below).
Cons: inherits calmstorm’s three named glitches exactly, because the root cause — sections authored for scroll, not for a fixed slide canvas — is identical here. Every slide in the resulting Slides deck will be a slightly different aspect ratio, because each section’s rendered height varies with its content. That looks noticeably off in a presentation tool where slides are expected to be uniform.
Verdict: works, ships faster than C, but produces a visibly rougher deck. Reasonable only as a stopgap while Play-UI work is in flight.
C. Build Play-UI first, then export — the clean path (recommended)
- Run the shell’s existing decompose-first workflow
(
/api/slide-decompose,DecomposeFirstPlaceholder) to generate the Play-UI counterpart for each of the ~70 slots across the seven strategy decks. This is real design work per CLAUDE.md’s own framing — “recreate, don’t extract” — not a mechanical port; Claude is explicitly worse at the rigid, no-JS, fixed-aspect constraint than at responsive Scroll-UI, so budget real iteration here, not a batch script. - Once Play-UI files exist, capture each one via the shell’s own
/play/[deckSlug]/[variantSlug]/[slot]route (or the print-mode surface at/play/[deckSlug]/[variantSlug]/print/, which already lays every slot out as onepage-break-after: alwayspage at the design’s native 1920×1080). A Playwright script does exactly what calmstorm’s/dev/shot/{slot}/{variant}capture does:page.goto()→.slide-canvaselement screenshot atdeviceScaleFactor: 2— no glitches, becauseSlideCanvasenforces the fixed canvas by construction. No neighbor bleed (each slide is its own route, not a scrolled-into-view section sharing a DOM with its siblings). No sub-pixel misalignment (noscrollIntoViewIfNeededinvolved at all). - Assemble the resulting PNGs into a
.pptxwith PptxGenJS — one 16:9 (13.333in × 7.5in) slide per PNG, ordered by the deck’s slot registry (src/data/slides.ts), full-bleed. - Upload to Google Drive → Open with → Google Slides, or File → Save as Google Slides to fully convert rather than just preview. Share with the stakeholder.
Pros: the only path that produces a uniform, presentation-grade deck
— every slide the same aspect ratio, no bleed, no clipping. Reuses the
@dididecks/shell architecture exactly as it was designed to be reused.
Once Play-UI exists, the export script itself is genuinely small (the
calmstorm doc’s own words: “the export script becomes a five-line affair”
once the canvas is baked in from day one — which Play-UI already is).
Cons: real upfront cost — Play-UI doesn’t exist for any of the seven decks yet, and per the CLAUDE.md framing this is the harder surface for an LLM to author well. This is not a “run a script tonight” deliverable; it’s “invest in Play-UI once, per deck, then exporting is cheap forever after.”
Verdict: the right foundation if this needs to happen more than once — and it will, since decks get revised and new strategies get added. Treat Play-UI construction as the actual project; the Slides export at the end of it is nearly free.
D. Same PNGs, pushed via the Slides API instead of a manual pptx upload
Identical capture step to Option C, but instead of assembling a local
.pptx, use the Slides API (presentations.batchUpdate with
createSlide + createImage requests) to push each PNG directly into a
specific, already-shared Google Slides file.
Pros: no manual “upload the pptx, then open it” step — useful if this needs to re-sync on a schedule (e.g., regenerate on every deck revision) and land in the same Slides file/URL the stakeholder already has open or bookmarked, rather than a new file each time.
Cons: meaningfully more setup — a Google Cloud project, OAuth
credentials or a service account, Drive + Slides API scopes, and someone
who owns token refresh/expiry. CreateImageRequest only accepts
PNG/JPEG/GIF, under 50MB, ≤25 megapixels — confirmed current
(Google’s own docs) —
so the PNG capture step doesn’t change, only the delivery mechanism.
Verdict: worth it only if “regenerate and re-share the same link” becomes a recurring need. Don’t build this before Option C proves the capture pipeline works; it’s a delivery-mechanism upgrade on top, not a different pipeline.
E. True editable-text recreation (native Slides shapes, not flattened images)
Instead of one image per slide, walk each Play-UI slide’s rendered DOM
(Playwright can read computed styles, bounding boxes, and text content per
element) and emit matching native objects — PptxGenJS addText/addShape
calls, or Slides API createShape/insertText requests — positioned to
match. The result is a Slides deck where the stakeholder can actually click
into a text box and retype a word, not just view a picture of one.
Pros: the only option that yields real in-Slides editability.
Cons: substantial new engineering with no existing prior art in this
codebase to build from — effectively a DOM-to-native-Slides layout
compiler, one mapping per distinct slide template (title slides, stat-card
grids, funder-pipeline tables, etc. all lay out differently). And even done
well, the fidelity ceiling is lower than the flattened-image path,
because Slides’ native shape/text model is a materially reduced subset of
what the site’s CSS can do: no custom @font-face unless the font is
separately uploaded to Slides’ font picker, no gradient text or
backdrop-filter, no CSS grid — several of the deck’s signature visual
details (gradient headlines, glass-morphism cards) have no native Slides
equivalent at all and would need to degrade to something plainer.
Verdict: only worth it if “edit text inside Slides itself” is a real, named requirement — not a nice-to-have. If the actual need is “present this deck and maybe tweak a word,” the honest answer is: edit the Astro source and re-export, the same discipline the site already lives by everywhere else. Revisit this only if that answer turns out to be insufficient in practice.
On SVG specifically — why it’s not the missing piece
Checked directly rather than assumed: the Slides API’s CreateImageRequest
supports PNG, JPEG, and GIF only — SVG is not accepted, and inserting one
via any workaround (e.g. browser extensions that “expand” supported
formats) rasterizes it to a bitmap anyway, which is exactly what a direct
PNG capture already gives you with one less step
(source,
corroborated across multiple current how-to write-ups on SVG-in-Slides
workarounds). The only place SVG could theoretically matter is as an
intermediate representation for Option E’s vector-shape recreation — but
Playwright’s direct DOM/style introspection is strictly richer than
round-tripping through an SVG export, so there’s no reason to introduce
SVG as a step anywhere in this pipeline.
Recommendation
Short-term (if a stakeholder meeting is close and can’t wait): Option A. It’s a manual favor, not a pattern, and everyone should know that going in.
The actual investment worth making: Option C. Build Play-UI for the
seven strategy decks (the real work — budget it as such, not as a script),
capture through the shell’s existing /play surface, assemble via
PptxGenJS, hand off as a .pptx that opens natively in Google Slides. This
is the same conclusion calmstorm’s own retro reached for “the next deck
site” — reach-edu-hub already has the architecture that recommendation was
pointing at ( @dididecks/shell + SlideCanvas ); it’s just not populated
for these seven decks yet.
Not recommended yet: Option D (API push) and Option E (editable recreation) — both are real upgrades on top of Option C, not substitutes for it, and neither has a concrete need behind it today.
Questions to resolve before building
- Timeline pressure. Is there a specific meeting driving this, or is “get it into Slides” a standing capability we want? Decides A-as-bridge vs. going straight to C.
- Editability requirement. Does the stakeholder need to type inside Slides, or just open/present/maybe reorder? Decides whether Option E is ever worth revisiting.
- Which decks, and how often. All seven, or a subset for this specific stakeholder? One-time export, or does this need to re-run every time a deck’s content is audited/updated (as just happened with the citation pass across all seven)?
- Where the file lives. A Drive folder we hand-manage per export (Option C), or a single persistent Slides URL that gets refreshed in place (Option D)?
- Who owns the gate bypass long-term. The capture script needs a way
past
src/middleware.ts— an env-var escape hatch for capture runs, or cookie injection against a real deployment. Whoever builds the exporter should also decide which.