html-deck
HTML Slide Deck to PDF Workflow
Build polished, presentation-ready HTML slide decks and convert them to razor-sharp PDFs using headless Chrome.
When to use
- Creating a new slide deck from program documentation, notes, or outlines.
- Converting rough content into a professional presentation.
- Generating a PDF version of an HTML deck with exact page dimensions.
- Building decks that include app screenshots or product visuals.
Progress checklist
Deck Build Progress:
- [ ] Phase 1: Slide skeleton (HTML + CSS foundation)
- [ ] Phase 2: Content population (slides from source material)
- [ ] Phase 3: Visual polish (spacing, typography, layout fixes)
- [ ] Phase 4: Print-safe CSS
- [ ] Phase 5: PDF conversion (use bundled script; do not reimplement)
- [ ] Phase 6: Verification (screenshot comparison)
Phase 1 — Slide skeleton
Use the Domo theme — MANDATORY FIRST STEP
- Read references/domo-theme.md — it contains a complete, copy-paste-ready HTML skeleton that produces slides matching the Domo template exactly.
- Read references/assets/logo-data-uri.txt — it contains the actual Domo logo as a base64 PNG data URI. Replace every
LOGO_DATA_URIplaceholder in the skeleton with this value. - The logo PNG is also at references/assets/domologo.png.
Do not skip this step. Copy the complete skeleton from the theme file, then build slides into it. Every content slide MUST have:
- Header:
<div class="slide-header">with<h1>,.subtitle, and.header-line(thin gray separator — required) - Content:
<div class="slide-content has-bg">— always usehas-bgfor the gray#DDE5EDbackground (this is the Domo look; content is vertically centered automatically) - Footer:
<div class="slide-footer">with.confidential(left),.footer-line(center), and.domo-badgewith<img src="LOGO_DATA_URI">(bottom-right — required)
Dimensions
Standard 16:9 widescreen: --slide-w: 1024px; --slide-h: 576px;
Content area positioning
The has-bg class handles positioning automatically (position:absolute; top:90px; bottom:52px) with vertical centering (display:flex; justify-content:center). Do not override these with inline styles.
Phase 2 — Content population
Slide type patterns
Table slide — Use a styled <table> with labeled column headers. Keep cell padding tight (6px 10px) on dense slides.
Flow/pipeline slide — Horizontal flex row of boxes with arrow separators (▶). Use colored left borders or top borders to differentiate phases.
Card grid — Flex row of equal-width cards. Use flex:1 on each card, consistent padding, and subtle background colors (#F6F8FA).
Architecture diagram — Nested flex containers. Label each layer (e.g. "INTELLIGENCE LAYER", "STORAGE", "COMPUTE") with colored card groups.
Screenshot showcase — One hero image centered, or two side-by-side with max-width:48%. Apply border-radius:8px for polish. No box-shadow (removed by print CSS anyway).
Info boxes — Paired boxes below main content. Use display:flex; gap:32px with flex:1 on each.
Typography scale
| Element | Size | Weight |
|---|---|---|
| Slide title (h1) | 28px | 800 |
| Subtitle | 11px | 700 |
| Section label | 10px | 700, uppercase, letter-spacing 2px |
| Body text | 13–14px | 400 |
| Table text | 11–13px | 400 |
| Fine print / captions | 9–10px | 400 |
Color conventions
Use CSS variables for consistency. Define brand colors, accent colors, and text shades in :root.
Phase 3 — Visual polish
Spacing rules
-
Content must never touch the footer. The
bottom:52pxon.slide-contentcreates a 52px exclusion zone. If content still overlaps, reducemargin-top,padding,gap,font-size, orline-heighton the offending elements. -
Header–content gap. The
topvalue on.slide-contentcontrols clearance below the header. Typical:88–100px. Increase if subtitle text collides with content. -
Dense slides. When a slide has both a table and info boxes, use compact values:
- Table cell padding:
4px 10px - Info box padding:
5px 12px - Gaps between cards:
3–5px - Font size: step down 1px from defaults
- Table cell padding:
-
justify-content:centercaution. Vertical centering can push tall content pastbottom. Remove it on content-heavy slides and let content flow from top.
Image handling
- URL-encode spaces in
srcpaths:Sample%20Assets/file.png. - Avoid special Unicode characters in filenames. Copy to clean filenames if needed.
- Use
max-width:100%; max-height:100%withobject-fit:containto auto-size within the content area.
Phase 4 — Print-safe CSS
PDF rendering through Chromium's print pipeline treats box-shadow differently than screen rendering. Shadows appear as heavy dark blocks in PDF output.
Required rule (already in the skeleton above):
@media print {
*, *::before, *::after {
box-shadow: none !important;
}
}
This strips all shadows for print only. The HTML browser view retains its shadows.
Additional print considerations:
page-break-after: alwayson.slide— one slide per page.page-break-inside: avoid— prevent mid-slide splits.background: whiteon body — no gray viewer background.
Phase 5 — PDF converter
Do not rewrite the converter from scratch. This skill ships a maintained script: references/convert-to-pdf.js. Copy it into the user’s project or run it by path from the skill directory after install.
npm install puppeteer-core
node convert-to-pdf.js path/to/deck.html path/to/deck.pdf
# optional second arg omitted => writes next to the HTML with .pdf extension
The script uses puppeteer-core with the system Chrome binary (not bundled Chromium), matching the approach below.
Key settings (implemented in the script; change only if you have a deliberate reason):
deviceScaleFactor: 2— retina-crisp text.printBackground: true— preserves colored backgrounds.margin: { top: 0, ... }— no extra whitespace around slides.width/heightmust match--slide-w/--slide-hexactly.- Wait for
document.fonts.readybefore capturing — Google Fonts must finish loading.
package.json setup
{
"scripts": { "pdf": "node convert-to-pdf.js" },
"dependencies": { "puppeteer-core": "latest" }
}
Use puppeteer-core (not puppeteer): the full puppeteer package downloads its own Chromium on install (~hundreds of MB) and uses that browser; puppeteer-core is the automation library only and expects you to pass executablePath to an existing Chrome/Chromium (this script finds system Chrome). Smaller install, one browser to update, fewer CI/cache issues.
Phase 6 — Verification
Screenshot each HTML slide and each PDF page, then compare visually.
Quick verification script
// Screenshot slide N from the HTML
const box = await page.evaluate((idx) => {
const el = document.querySelectorAll(".slide")[idx];
const r = el.getBoundingClientRect();
return { x: r.x, y: r.y, width: r.width, height: r.height };
}, slideIndex);
await page.screenshot({ path: `html_slide_${n}.png`, clip: box });
What to check
- Page count matches slide count.
- No content clipped by footer on any slide.
- Fonts loaded (not fallback serif/sans-serif).
- All images render (no broken-image icons or alt text).
- No dark hue/shadow blocks around cards or images.
- Background colors and gradients preserved.
- Page edges are razor-sharp (no extra margin or whitespace).
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
| Content overlaps footer | bottom not set or justify-content:center on tall content |
Set bottom:52px on .slide-content; remove vertical centering on dense slides |
| Dark blocks around elements in PDF | box-shadow renders differently in print |
Add @media print { * { box-shadow: none !important; } } |
| Images don't load | Spaces or Unicode in file paths | URL-encode spaces (%20); copy files to clean filenames |
| Fonts fallback to serif | networkidle0 not waited or fonts blocked |
Wait for document.fonts.ready; ensure Google Fonts URL is correct |
| Chrome not found | Puppeteer can't locate browser | Use puppeteer-core + executablePath to system Chrome |
| Extra whitespace around pages | PDF margin not zeroed | Set margin: { top: 0, right: 0, bottom: 0, left: 0 } |
| Blurry text in PDF | Low device scale factor | Set deviceScaleFactor: 2 in viewport |
Checklist
- Slide dimensions match
--slide-wand--slide-hCSS variables - Every
.slide-contenthasbottom:52pxto clear the footer -
@media printblock strips allbox-shadowvalues -
page-break-after: alwaysset on.slidefor print - Converter uses
puppeteer-corewith system Chrome path -
printBackground: trueand zero margins in PDF options -
deviceScaleFactor: 2for retina quality - Font loading awaited before PDF capture
- Image paths URL-encoded (no raw spaces)
- Visual verification done on cover, dense slides, and screenshot slides
More from stahura/domo-ai-vibe-rules
domo-js
Use ryuu.js (domo.js) APIs for env, events, navigation, and data calls.
57appdb
Toolkit-first AppDB document CRUD, query operators, and collection wiring.
56migrate-lovable
Convert SSR-heavy Lovable/v0 apps into client-only Domo apps.
56data-api
High-level entry skill for Domo data access. Routes detailed query work to dataset-query.
55jsapi-filters
Apply runtime filters to embedded Domo dashboards/cards from the host page via the JS API (MessagePort). Covers dynamic filtering, drill events, filter change listeners, iframe resize, pfilter URL params, overrideFilters, and App Studio appData. Use for any client-side Domo embed interaction. Not for server-side programmatic filters (use programmatic-filters).
55ai-service-layer
Toolkit-first AIClient patterns for generation, text-to-sql, and response parsing.
55