Imported from toddward/red-hat-quick-deck (
SKILL.md). Install upstream withnpx skills add toddward/red-hat-quick-deck. Copyright stays with the author.
Red Hat Quick Deck
Generate stunning, self-contained HTML presentations that follow Red Hat brand standards and use narrative story arcs to make technical content compelling and memorable.
Before You Begin
- Read the brand reference:
references/redhat-brand.md— contains the full color palette, typography, and design rules. - Read the story arc guide:
references/story-arcs.md— contains narrative structures and slide design principles. - If the
redhat-brandskill is also installed, consult it for additional brand application guidance. - Ask the user which mode they prefer for the deck before generating. Use the ask_user_input tool:
- Question: "What visual mode should this deck use?"
- Options: "Dark mode (cinematic, dark backgrounds — best for screens & presenting)", "Light mode (clean, white backgrounds — best for print & email sharing)"
- Default to Dark mode if the user doesn't specify or says "either" or "don't care."
- Ask the user about PDF export intent — this determines what the printed PDF looks like and is baked into the deck's
@media printCSS, so it must be decided before generation:- Question: "If/when this deck is exported as a PDF, how will it primarily be used?"
- Options: "Digital sharing (email, Slack, viewing on screens — keep the on-screen palette in the PDF)", "Print (physical paper or ink-efficient PDF — force a white background with dark text)"
- Default to Digital sharing if the user doesn't specify.
- This only changes behavior for Dark and Expressive Dark decks. Light decks already print on white regardless of intent.
- For Dark decks + Digital sharing → omit the Print Palette Override block. The PDF preserves the dark cinematic palette.
- For Dark decks + Print → include the Print Palette Override block. The PDF remaps to white background with dark text and preserved Red Hat red accents.
What This Skill Produces
A single .html file that:
- Is completely self-contained (inline CSS, inline JS; Google Fonts, Red Hat Design Tokens, and optionally Red Hat Icons loaded via CDN)
- Can be opened in any browser, emailed, or hosted on any web server
- Has keyboard navigation (arrow keys, spacebar) and click navigation
- Has a slide counter / progress indicator
- Looks cinematic and professional on screen, projector, or shared link
- Follows a deliberate story arc that builds a persuasive narrative
- Includes a contextual notes panel (toggled with 'N' key) for additional references, links, and deeper context on each slide
- Supports embedded videos (YouTube, Vimeo, direct URLs) with auto-pause on slide change
- Supports linked media (memes, GIFs, images) with responsive display and optional captions
- Is responsive and works on mobile for async viewing
Design System
Red Hat Design Tokens
This skill integrates the official Red Hat Design Tokens (@rhds/tokens) for spacing, typography sizing,
borders, and shadows. The tokens CSS is loaded via jsDelivr CDN alongside the existing Google Fonts and
@rhds/icons dependencies. All token references include hardcoded fallbacks so decks degrade gracefully offline.
CDN URL:
https://cdn.jsdelivr.net/npm/@rhds/tokens@3.0.2/css/global.min.css
Important color note: The RHDS v3 tokens use accessibility-adjusted color values (e.g.,
--rh-color-brand-red-on-dark resolves to #FF442B, not #ee0000). This skill intentionally keeps
the established Red Hat brand palette hex values (#ee0000 for red-50, etc.) for visual consistency
with the classic brand aesthetic. Token names are annotated in comments for cross-reference.
What we use from @rhds/tokens:
- Spacing:
--rh-space-xs(4px) through--rh-space-7xl(128px) — consistent 4px-base scale - Typography sizing:
--rh-font-size-heading-*and--rh-font-size-body-text-* - Font families:
--rh-font-family-heading,--rh-font-family-body-text,--rh-font-family-code - Border radius:
--rh-border-radius-default(3px),--rh-border-radius-pill(64px) - Border width:
--rh-border-width-sm(1px),--rh-border-width-md(2px),--rh-border-width-lg(3px) - Box shadows:
--rh-box-shadow-sm/md/lg/xl— for cards, architecture boxes, elevated surfaces - Opacity:
--rh-opacity-*— for overlays, muted elements, and glow effects
Token reference: https://ux.redhat.com/tokens/ · Package: https://www.npmjs.com/package/@rhds/tokens
Color Palette Selection
Choose ONE color collection per deck from the Red Hat brand palette. The default and recommended collection is "Core Dark" which matches the reference screenshot aesthetic.
Each mode sets color-scheme on :root so that any light-dark() token values resolve correctly.
Core Dark (Default)
:root { color-scheme: dark; }
--bg-primary: #000000; /* black · --rh-color-surface-darkest */
--bg-secondary: #1f1f1f; /* gray-90 · --rh-color-surface-darker */
--bg-surface: #292929; /* gray-80 · --rh-color-surface-dark */
--text-primary: #ffffff; /* white · --rh-color-text-primary-on-dark */
--text-secondary: #c7c7c7; /* gray-30 · --rh-color-text-secondary-on-dark */
--text-muted: #a3a3a3; /* gray-40 */
--accent: #ee0000; /* red-50 — Red Hat Red */
--accent-dark: #a60000; /* red-60 */
--accent-light: #f56e6e; /* red-40 */
--tag-border: #383838; /* gray-70 · --rh-color-border-subtle-on-dark */
--icon-filter: brightness(0) invert(1); /* white icons on dark bg */
--icon-filter-accent: invert(12%) sepia(100%) saturate(10000%) hue-rotate(0deg) brightness(95%); /* red-50 icons */
Other available collections (use when the user requests a different feel):
Core Light (clean, professional — best for print/email/documentation)
:root { color-scheme: light; }
--bg-primary: #ffffff; /* white · --rh-color-surface-lightest */
--bg-secondary: #f2f2f2; /* gray-10 · --rh-color-surface-lighter */
--bg-surface: #e0e0e0; /* gray-20 · --rh-color-surface-light */
--text-primary: #151515; /* gray-95 · --rh-color-text-primary-on-light */
--text-secondary: #4d4d4d; /* gray-60 · --rh-color-text-secondary-on-light */
--text-muted: #707070; /* gray-50 */
--accent: #ee0000; /* red-50 — Red Hat Red */
--accent-dark: #a60000; /* red-60 */
--accent-light: #f56e6e; /* red-40 */
--tag-border: #c7c7c7; /* gray-30 · --rh-color-border-subtle-on-light */
--icon-filter: none; /* dark icons on light bg (SVGs are dark by default) */
--icon-filter-accent: invert(12%) sepia(100%) saturate(10000%) hue-rotate(0deg) brightness(95%); /* red-50 icons */
When using Core Light:
- The ambient glow effect uses a very subtle red tint:
rgba(238,0,0,0.04) - Body and slide backgrounds alternate between
--bg-primaryand--bg-secondaryfor rhythm - Use the standard (black wordmark) logo SVG
- Tag pill borders use
--tag-border(#c7c7c7) with--text-secondarytext - The
.accenttag variant uses--accentborder and text color (same as dark mode) - Architecture diagram boxes use
background: rgba(242,242,242,0.8)andborder: 1px solid #c7c7c7 - Quote marks use
--accentat low opacity - The progress indicator
.currentnumber is still red-50
Expressive Dark (for more colorful content — adds teal and purple accents)
:root { color-scheme: dark; }
--bg-primary: #1b0d33; /* purple-80 */
--bg-secondary: #000000; /* black */
--bg-surface: #21134d; /* purple-70 */
--text-primary: #ffffff; /* white · --rh-color-text-primary-on-dark */
--text-secondary: #d0c5f4; /* purple-20 */
--text-muted: #b6a6e9; /* purple-30 */
--accent: #ee0000; /* red-50 — Red Hat Red */
--accent-dark: #a60000; /* red-60 */
--accent-light: #f56e6e; /* red-40 */
--highlight-teal: #37a3a3; /* teal-50 */
--highlight-purple: #876fd4; /* purple-40 */
--tag-border: #3d2785; /* purple-60 */
--icon-filter: brightness(0) invert(1); /* white icons on dark bg */
--icon-filter-accent: invert(12%) sepia(100%) saturate(10000%) hue-rotate(0deg) brightness(95%); /* red-50 icons */
Typography
@import url('https://fonts.googleapis.com/css2?family=Red+Hat+Display:wght@400;500;700;900&family=Red+Hat+Text:wght@400;500;700&family=Red+Hat+Mono:wght@400;700&display=swap');
--font-display: var(--rh-font-family-heading, 'Red Hat Display', sans-serif); /* Headlines */
--font-body: var(--rh-font-family-body-text, 'Red Hat Text', sans-serif); /* Body text */
--font-mono: var(--rh-font-family-code, 'Red Hat Mono', monospace); /* Code, tags, technical */
Typography Sizing (from @rhds/tokens)
Use these token-based sizes for consistent typographic scale across decks:
| Token | Size | Usage |
|---|---|---|
--rh-font-size-heading-2xl |
3rem (48px) | Title slide headline |
--rh-font-size-heading-xl |
2.5rem (40px) | Section headlines, big impact text |
--rh-font-size-heading-lg |
2rem (32px) | Slide headlines |
--rh-font-size-heading-md |
1.5rem (24px) | Sub-headlines |
--rh-font-size-heading-sm |
1.25rem (20px) | Card titles, labels |
--rh-font-size-body-text-xl |
1.25rem (20px) | Lead paragraphs, subtitles |
--rh-font-size-body-text-lg |
1.125rem (18px) | Body text on slides |
--rh-font-size-body-text-md |
1rem (16px) | Standard body |
--rh-font-size-body-text-sm |
0.875rem (14px) | Captions, attributions |
--rh-font-size-body-text-xs |
0.75rem (12px) | Tags, breadcrumbs, fine print |
Example usage:
h1 { font-size: var(--rh-font-size-heading-2xl, 3rem); }
h2 { font-size: var(--rh-font-size-heading-lg, 2rem); }
.subtitle { font-size: var(--rh-font-size-body-text-xl, 1.25rem); }
.tag { font-size: var(--rh-font-size-body-text-xs, 0.75rem); }
Visual Effects
The reference screenshot uses a subtle ambient glow in the upper-right corner. Achieve this with a radial gradient overlay on the slide background:
.slide::before {
content: '';
position: absolute;
top: -20%;
right: -10%;
width: 60%;
height: 60%;
background: radial-gradient(ellipse, rgba(238,0,0,0.08) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
}
This creates the distinctive "red atmosphere" effect. Vary the position and opacity per slide for visual rhythm. On some slides, shift it to the left. On the title slide, make it more prominent.
Red Hat Logo
Every deck must include the official Red Hat logo. Since these are self-contained HTML files, the logo is embedded as an inline SVG. The skill provides two versions — use the correct one for the deck's mode.
The logo appears in two places:
- Breadcrumb area (top-left of title slide) — small, subtle, as part of the navigation breadcrumb
- Footer / closing slide — larger, standalone placement
Per brand guidelines:
- On dark backgrounds: hat is red, wordmark is white (reverse full-color)
- On light backgrounds: hat is red, wordmark is black (standard full-color)
- Always maintain minimum spacing around the logo
- Never distort, recolor the hat band, or add effects
Logo SVG — Reverse (for Dark Mode)
Use this on dark backgrounds. The hat is red (#e00), the wordmark is white (#fff).
This is the official RedHat-Logo-A-Reverse from static.redhat.com/libs/redhat/brand-assets/2/corp/logo--on-dark.svg.
<svg class="rh-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 613 145" role="img" aria-label="Red Hat">
<title>Red Hat</title>
<path d="M127.47,83.49c12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42l-7.45-32.36c-1.72-7.12-3.23-10.35-15.73-16.6C124.89,8.69,103.76.5,97.51.5,91.69.5,90,8,83.06,8c-6.68,0-11.64-5.6-17.89-5.6-6,0-9.91,4.09-12.93,12.5,0,0-8.41,23.72-9.49,27.16A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33C22.27,52,.5,55,.5,74.22c0,31.48,74.59,70.28,133.65,70.28,45.28,0,56.7-20.48,56.7-36.65,0-12.72-11-27.16-30.83-35.78" fill="#e00"/>
<path d="M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33l3.66-9.06A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45,12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42Z"/>
<path d="M579.74,92.8c0,11.89,7.15,17.67,20.19,17.67a52.11,52.11,0,0,0,11.89-1.68V95a24.84,24.84,0,0,1-7.68,1.16c-5.37,0-7.36-1.68-7.36-6.73V68.3h15.56V54.1H596.78v-18l-17,3.68V54.1H568.49V68.3h11.25Zm-53,.32c0-3.68,3.69-5.47,9.26-5.47a43.12,43.12,0,0,1,10.1,1.26v7.15a21.51,21.51,0,0,1-10.63,2.63c-5.46,0-8.73-2.1-8.73-5.57m5.2,17.56c6,0,10.84-1.26,15.36-4.31v3.37h16.82V74.08c0-13.56-9.14-21-24.39-21-8.52,0-16.94,2-26,6.1l6.1,12.52c6.52-2.74,12-4.42,16.83-4.42,7,0,10.62,2.73,10.62,8.31v2.73a49.53,49.53,0,0,0-12.62-1.58c-14.31,0-22.93,6-22.93,16.73,0,9.78,7.78,17.24,20.19,17.24m-92.44-.94h18.09V80.92h30.29v28.82H506V36.12H487.93V64.41H457.64V36.12H439.55ZM370.62,81.87c0-8,6.31-14.1,14.62-14.1A17.22,17.22,0,0,1,397,72.09V91.54A16.36,16.36,0,0,1,385.24,96c-8.2,0-14.62-6.1-14.62-14.09m26.61,27.87h16.83V32.44l-17,3.68V57.05a28.3,28.3,0,0,0-14.2-3.68c-16.19,0-28.92,12.51-28.92,28.5a28.25,28.25,0,0,0,28.4,28.6,25.12,25.12,0,0,0,14.93-4.83ZM320,67c5.36,0,9.88,3.47,11.67,8.83H308.47C310.15,70.3,314.36,67,320,67M291.33,82c0,16.2,13.25,28.82,30.28,28.82,9.36,0,16.2-2.53,23.25-8.42l-11.26-10c-2.63,2.74-6.52,4.21-11.14,4.21a14.39,14.39,0,0,1-13.68-8.83h39.65V83.55c0-17.67-11.88-30.39-28.08-30.39a28.57,28.57,0,0,0-29,28.81M262,51.58c6,0,9.36,3.78,9.36,8.31S268,68.2,262,68.2H244.11V51.58Zm-36,58.16h18.09V82.92h13.77l13.89,26.82H292l-16.2-29.45a22.27,22.27,0,0,0,13.88-20.72c0-13.25-10.41-23.45-26-23.45H226Z" fill="#fff"/>
</svg>
Logo SVG — Standard (for Light Mode)
Use this on light backgrounds. The hat is red (#e00), the wordmark is near-black (#151515). This is the official logo extracted from the Red Hat brand standards page header.
<svg class="rh-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 613 145" role="img" aria-label="Red Hat">
<title>Red Hat</title>
<path d="M127.47,83.49c12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42l-7.45-32.36c-1.72-7.12-3.23-10.35-15.73-16.6C124.89,8.69,103.76.5,97.51.5,91.69.5,90,8,83.06,8c-6.68,0-11.64-5.6-17.89-5.6-6,0-9.91,4.09-12.93,12.5,0,0-8.41,23.72-9.49,27.16A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33C22.27,52,.5,55,.5,74.22c0,31.48,74.59,70.28,133.65,70.28,45.28,0,56.7-20.48,56.7-36.65,0-12.72-11-27.16-30.83-35.78" fill="#e00"/>
<path d="M160,72.07c1.73,8.19,1.73,9.05,1.73,10.13,0,14-15.74,21.77-36.43,21.77C78.54,104,37.58,76.6,37.58,58.49a18.45,18.45,0,0,1,1.51-7.33l3.66-9.06A6.43,6.43,0,0,0,42.53,44c0,9.22,36.3,39.45,84.94,39.45,12.51,0,30.61-2.58,30.61-17.46a14,14,0,0,0-.31-3.42Z"/>
<path d="M579.74,92.8c0,11.89,7.15,17.67,20.19,17.67a52.11,52.11,0,0,0,11.89-1.68V95a24.84,24.84,0,0,1-7.68,1.16c-5.37,0-7.36-1.68-7.36-6.73V68.3h15.56V54.1H596.78v-18l-17,3.68V54.1H568.49V68.3h11.25Zm-53,.32c0-3.68,3.69-5.47,9.26-5.47a43.12,43.12,0,0,1,10.1,1.26v7.15a21.51,21.51,0,0,1-10.63,2.63c-5.46,0-8.73-2.1-8.73-5.57m5.2,17.56c6,0,10.84-1.26,15.36-4.31v3.37h16.82V74.08c0-13.56-9.14-21-24.39-21-8.52,0-16.94,2-26,6.1l6.1,12.52c6.52-2.74,12-4.42,16.83-4.42,7,0,10.62,2.73,10.62,8.31v2.73a49.53,49.53,0,0,0-12.62-1.58c-14.31,0-22.93,6-22.93,16.73,0,9.78,7.78,17.24,20.19,17.24m-92.44-.94h18.09V80.92h30.29v28.82H506V36.12H487.93V64.41H457.64V36.12H439.55ZM370.62,81.87c0-8,6.31-14.1,14.62-14.1A17.22,17.22,0,0,1,397,72.09V91.54A16.36,16.36,0,0,1,385.24,96c-8.2,0-14.62-6.1-14.62-14.09m26.61,27.87h16.83V32.44l-17,3.68V57.05a28.3,28.3,0,0,0-14.2-3.68c-16.19,0-28.92,12.51-28.92,28.5a28.25,28.25,0,0,0,28.4,28.6,25.12,25.12,0,0,0,14.93-4.83ZM320,67c5.36,0,9.88,3.47,11.67,8.83H308.47C310.15,70.3,314.36,67,320,67M291.33,82c0,16.2,13.25,28.82,30.28,28.82,9.36,0,16.2-2.53,23.25-8.42l-11.26-10c-2.63,2.74-6.52,4.21-11.14,4.21a14.39,14.39,0,0,1-13.68-8.83h39.65V83.55c0-17.67-11.88-30.39-28.08-30.39a28.57,28.57,0,0,0-29,28.81M262,51.58c6,0,9.36,3.78,9.36,8.31S268,68.2,262,68.2H244.11V51.58Zm-36,58.16h18.09V82.92h13.77l13.89,26.82H292l-16.2-29.45a22.27,22.27,0,0,0,13.88-20.72c0-13.25-10.41-23.45-26-23.45H226Z" fill="#151515"/>
</svg>
Logo CSS
.rh-logo {
height: 28px;
width: auto;
}
.rh-logo.small { height: 20px; }
.rh-logo.large { height: 40px; }
Logo Placement Rules
Title slide: Place the logo in the breadcrumb row, left-aligned, before any breadcrumb text:
<div class="breadcrumb">
<svg class="rh-logo small" ...>[logo SVG]</svg>
<span style="margin-left: 12px;">› NAPS STP</span>
</div>
Closing / CTA slide: Place the logo centered or left-aligned near the bottom of the slide,
above or beside the attribution, at .rh-logo.large size.
Every other slide: The logo should NOT appear on every content slide — this avoids clutter. Instead, include a minimal red accent bar or the Red Hat red (#ee0000) in the progress indicator to maintain brand presence throughout.
Red Hat Icons
Red Hat publishes an official icon library (@rhds/icons) with 1,135 SVGs across 4 sets. Use <img> tags
pointing to the jsDelivr CDN to add icons to slides. Icons are controlled via CSS filter variables
so they adapt to each color mode automatically.
CDN URL pattern:
https://cdn.jsdelivr.net/npm/@rhds/icons@2.1.0/{set}/{icon}.svg
Choosing Icons — Read references/rhds-icons.md
Before using any icon, always read references/rhds-icons.md — it is the single source of truth for:
- The full inventory of all 1,135 icons across 4 sets (
standard,ui,microns,social) - Common alias mappings — many intuitive names don't match the actual RHDS names (e.g.,
database→data,integration→interoperability,build→circuit,network→network-automation) - Semantic groupings by topic (Cloud, Security, AI, DevOps, etc.) to quickly find the right icon for a slide's subject matter
Do not guess icon names. If you use a name that doesn't exist in the inventory, the icon will silently fail to load.
Icon CSS
/* === ICON STYLES === */
.rh-icon {
height: 48px;
width: 48px;
filter: var(--icon-filter);
vertical-align: middle;
}
.rh-icon.small { height: 24px; width: 24px; }
.rh-icon.medium { height: 48px; width: 48px; }
.rh-icon.large { height: 64px; width: 64px; }
.rh-icon.xl { height: 96px; width: 96px; }
.rh-icon.accent { filter: var(--icon-filter-accent); }
Icon Usage in HTML
<!-- Basic icon -->
<img class="rh-icon" src="https://cdn.jsdelivr.net/npm/@rhds/icons@2.1.0/standard/cloud.svg" alt="Cloud">
<!-- Small accent-colored icon beside a headline -->
<h2><img class="rh-icon small accent" src="https://cdn.jsdelivr.net/npm/@rhds/icons@2.1.0/standard/shield.svg" alt=""> Security First</h2>
<!-- Large icon for a stat slide -->
<img class="rh-icon xl accent" src="https://cdn.jsdelivr.net/npm/@rhds/icons@2.1.0/standard/graph-line-up.svg" alt="">
<div class="big-number">3.2x</div>
Icon Usage Guidelines
- Use sparingly: 2-3 icons per slide maximum. Icons should clarify, not decorate.
- Best uses: Stat slide topic icons, feature list bullets, architecture diagram box labels, comparison column headers.
- Don't: Use icons as the sole content, mix too many sizes on one slide, use icons without supporting text.
- Sizing: Use
.small(24px) inline with text,.medium(48px) for feature lists,.large/.xlfor hero/stat accent. - Color: Icons inherit the mode's filter by default. Use
.accentclass sparingly for emphasis (same restraint as red-50 text).
Spacing Scale (from @rhds/tokens)
Use the official Red Hat spacing tokens for all padding, margins, and gaps. This ensures visual consistency with the broader Red Hat design system. All values are multiples of 4px.
| Token | Value | Common Usage |
|---|---|---|
--rh-space-xs |
4px | Tight inline gaps, icon-to-text spacing |
--rh-space-sm |
8px | Compact element spacing |
--rh-space-md |
16px | Standard element spacing, tag padding horizontal |
--rh-space-lg |
24px | Section gaps, card padding |
--rh-space-xl |
32px | Slide content gaps |
--rh-space-2xl |
48px | Major section spacing |
--rh-space-3xl |
64px | Slide side padding |
--rh-space-4xl |
80px | Slide top/bottom padding |
--rh-space-5xl |
96px | Large hero spacing |
--rh-space-6xl |
112px | Extra large spacing |
--rh-space-7xl |
128px | Maximum spacing |
Example usage:
.slide { padding: var(--rh-space-4xl, 80px) var(--rh-space-3xl, 64px); }
.content-body { gap: var(--rh-space-lg, 24px); }
.breadcrumb svg + span { margin-left: var(--rh-space-sm, 8px); }
Border & Shadow Tokens (from @rhds/tokens)
/* Border widths */
--rh-border-width-sm: 1px; /* Default borders, tag outlines */
--rh-border-width-md: 2px; /* Emphasized borders, accent lines */
--rh-border-width-lg: 3px; /* Heavy emphasis, decorative rules */
/* Border radii */
--rh-border-radius-sharp: 0px; /* No rounding — architecture boxes */
--rh-border-radius-default: 3px; /* Subtle rounding — cards, surfaces */
--rh-border-radius-pill: 64px; /* Full pill — tags, badges */
/* Box shadows — use for elevated surfaces and architecture diagram depth */
--rh-box-shadow-sm: 0 2px 4px 0 rgba(21, 21, 21, 0.2); /* Subtle lift */
--rh-box-shadow-md: 0 4px 6px 1px rgba(21, 21, 21, 0.25); /* Cards */
--rh-box-shadow-lg: 0 6px 8px 2px rgba(21, 21, 21, 0.3); /* Modals, panels */
--rh-box-shadow-xl: 0 8px 24px 3px rgba(21, 21, 21, 0.35); /* Hero elements */
Video Container Styling
/* === VIDEO CONTAINER === */
.video-container {
position: relative;
width: 100%;
max-width: 960px;
aspect-ratio: 16 / 9;
border-radius: var(--rh-border-radius-default, 3px);
overflow: hidden;
box-shadow: var(--rh-box-shadow-lg, 0 6px 8px 2px rgba(21, 21, 21, 0.3));
background: var(--bg-secondary);
align-self: center;
}
.video-container iframe,
.video-container video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
Media Container Styling (Memes, GIFs, Linked Images)
/* === MEDIA CONTAINER === */
.media-container {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
width: 100%;
max-height: 65vh;
padding: var(--rh-space-md, 16px) 0;
align-self: center;
}
.slide-media {
display: block;
max-width: 90%;
max-height: 100%;
object-fit: contain;
border-radius: var(--rh-border-radius-default, 3px);
box-shadow: var(--rh-box-shadow-lg, 0 6px 8px 2px rgba(21, 21, 21, 0.3));
}
.media-caption {
font-family: var(--rh-font-family-body-text, 'Red Hat Text', sans-serif);
font-size: var(--rh-font-size-body-text-lg, 1.125rem);
color: var(--text-secondary);
text-align: center;
margin-top: var(--rh-space-md, 16px);
max-width: 720px;
align-self: center;
}
Tag / Pill Styling
The reference uses outlined pills for categorization (e.g., "Local-First", "Air-Gap Ready"). Style them using design tokens for sizing, spacing, and borders:
.tag {
display: inline-block;
padding: var(--rh-space-xs, 4px) var(--rh-space-md, 16px);
border: var(--rh-border-width-sm, 1px) solid var(--tag-border);
border-radius: var(--rh-border-radius-pill, 64px);
font-family: var(--font-mono);
font-size: var(--rh-font-size-body-text-xs, 0.75rem);
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--text-secondary);
}
.tag.accent {
border-color: var(--accent);
color: var(--accent);
}
Slide Structure Template
HTML Skeleton
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[Deck Title]</title>
<!-- Red Hat Design Tokens (spacing, typography, borders, shadows) -->
<link href="https://cdn.jsdelivr.net/npm/@rhds/tokens@3.0.2/css/global.min.css" rel="stylesheet">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Red+Hat+Display:wght@400;500;700;900&family=Red+Hat+Text:wght@400;500;700&family=Red+Hat+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
/* === RESET & BASE === */
:root { color-scheme: dark; } /* Set to 'light' for Core Light mode */
/* === COLOR VARIABLES (dark or light, based on user choice) === */
/* === TYPOGRAPHY (uses --rh-font-family-* and --rh-font-size-* tokens with fallbacks) === */
/* === SPACING (uses --rh-space-* tokens with fallbacks) === */
/* === LOGO STYLES === */
/* === ICON STYLES === */
/* === SLIDE CONTAINER === */
/* === NAVIGATION === */
/* === SLIDE TYPES === */
/* === CONTEXTUAL NOTES PANEL === */
/* === ANIMATIONS === */
/* === PRINT / PDF EXPORT === */ /* @media print — see "Print / PDF Export Styles" */
/* === PRINT PALETTE OVERRIDE === */ /* dark-mode decks only — remaps to ink-efficient light */
</style>
</head>
<body>
<div class="deck">
<!-- SLIDE 1: TITLE (includes logo in breadcrumb) -->
<div class="slide">
<div class="breadcrumb">
<!-- ⚠ REQUIRED: inline the FULL official Red Hat logo SVG from the "Red Hat Logo" section above
(lines ~220–246). Use the Reverse variant (white wordmark) for dark decks, Standard (dark
wordmark) for light decks. DO NOT substitute a stylized approximation, silhouette-only
icon, text-based wordmark, or any other placeholder — use the canonical 613×145 paths. -->
<svg class="rh-logo small" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 613 145" role="img" aria-label="Red Hat">
<title>Red Hat</title>
<!-- [INLINE the 3 paths from the Reverse OR Standard logo block above, selected by deck mode] -->
</svg>
<span>› [Team / Group]</span>
</div>
<!-- ... rest of title slide ... -->
</div>
<!-- CONTENT SLIDES (no logo — brand presence via red accents) -->
<!-- VIDEO SLIDES use .video-container with iframe or video tag -->
<!-- MEDIA SLIDES use .media-container with img tag (memes, GIFs, linked images) -->
<!-- FINAL SLIDE: CTA (includes larger logo) -->
<div class="slide">
<!-- ... content ... -->
<div class="logo-footer">
<!-- ⚠ REQUIRED: same official SVG as above, but with class="rh-logo large" -->
<svg class="rh-logo large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 613 145" role="img" aria-label="Red Hat">
<title>Red Hat</title>
<!-- [INLINE the 3 paths from the Reverse OR Standard logo block above, selected by deck mode] -->
</svg>
</div>
</div>
</div>
<div class="controls">
<div class="nav-hint">← → or click to navigate · N for additional context</div>
<div class="progress"><span class="current">1</span> / <span class="total">10</span></div>
</div>
<script>
// Navigation logic
</script>
</body>
</html>
Required Slide Types
Every deck should include these slide types (adapt as needed):
1. Title Slide
- Breadcrumb / source label (e.g., "RED HAT › NAPS STP") in small monospace, top-left
- Working group or team label + date in red monospace, left-aligned
- Large bold headline in Red Hat Display Black weight
- Accent word(s) in the headline colored red-50
- Subtitle / description in lighter text below
- Tag pills for key concepts
- Author attribution at bottom: "Name · Role · Organization"
2. Content Slide
- Slide headline as an assertion (not a label)
- Body content: paragraphs, bullet points (use sparingly), or key-value pairs
- Optional: a small icon (
.rh-icon.small) beside the headline for topical accent - Optional: icons as feature list markers instead of bullet characters
- Optional: source attribution at bottom
3. Big Number / Stat Slide
- Optional: topic icon (
.rh-icon.xl.accent) centered above the big number - One large number (80-120px font size) in red-50 or white
- Brief context line below in gray-30
- Source attribution at bottom
4. Comparison / Before-After Slide
- Two-column layout
- Optional: icons representing each side as column headers (e.g.,
padlock-unlockedvspadlock-locked) - Clear visual distinction between old/new or with/without
- Use red-50 to highlight the preferred side
5. Architecture / Diagram Slide
- CSS-based box diagrams with flexbox/grid (no images required)
- Boxes with borders and labels — use icons (
.rh-icon.small) inside boxes alongside text labels for visual clarity - Arrows represented with CSS or Unicode characters (→, ↓)
- Red-50 highlight on the key innovation
6. Quote Slide
- Large pull quote in Red Hat Display, medium weight
- Attribution below
- Red-50 opening quotation mark as decorative element
7. Call-to-Action / Closing Slide
- Clear next steps
- Contact info or resources
- QR code placeholder if relevant (note in contextual notes)
8. Video Slide
- Use when the user provides a video URL or asks to embed a video
- Supports YouTube, Vimeo (via thumbnail + click-to-embed iframe), and direct video URLs (via
<video>tag) - Headline with optional accent word above the video
- 16:9 responsive container with brand-consistent framing (shadow, rounded corners)
- Optional caption below the video for context
- Source attribution at bottom
- Click the play button to start the video; controls are visible for pause/seek
⚠ IMPORTANT — When a user adds a video slide, you MUST inform them:
Inline video playback requires serving this deck via HTTP or HTTPS. YouTube and Vimeo embeds do not work when opening the HTML file directly from your filesystem (
file://protocol) — this is a browser security restriction that cannot be worked around.To enable inline video playback:
- Quick local server: Run
python3 -m http.serverin the deck's folder, then openhttp://localhost:8000/deck-name.html- Or use:
npx serve, VS Code Live Server, or any static file host- Or host it: Upload to any web server, GitHub Pages, S3, etc.
When opened as a local file, clicking a video will open it on YouTube in a new browser tab instead.
This message should be delivered every time a video slide is added to a deck. Do not skip it.
URL conversion rules:
- YouTube
https://www.youtube.com/watch?v=VIDEO_ID→https://www.youtube-nocookie.com/embed/VIDEO_ID?enablejsapi=1&mute=1 - YouTube
https://youtu.be/VIDEO_ID→https://www.youtube-nocookie.com/embed/VIDEO_ID?enablejsapi=1&mute=1 - Vimeo
https://vimeo.com/VIDEO_ID→https://player.vimeo.com/video/VIDEO_ID?muted=1 - Direct
.mp4,.webm,.oggURLs → use<video>tag withmutedattribute
YouTube/Vimeo implementation — thumbnail + open-in-new-tab pattern:
YouTube iframes have multiple failure modes: file:// protocol blocks them (Error 153), ad blockers
block YouTube's tracking requests (ERR_BLOCKED_BY_CLIENT), and CSP policies can prevent embedding.
To ensure decks work reliably everywhere, always use the thumbnail + new-tab pattern:
- Show the YouTube thumbnail as an image with a branded red play button overlay
- On click, open the video on youtube.com in a new browser tab
- The presenter's slide stays visible — they can return to it after watching
Required attribute for every .video-container: data-video-url="[full URL]"
In addition to data-video-id (used by the click-to-play handler), every video container must
carry data-video-url set to the canonical user-facing URL (e.g. https://www.youtube.com/watch?v=VIDEO_ID
or the direct .mp4 URL). The print CSS reads this attribute via content: attr(data-video-url)
to render the URL as a caption beneath the poster/thumbnail in PDF export — videos can't play in
a PDF, so readers need the link.
This approach has zero dependencies on iframes, works from any protocol, and is immune to ad blockers.
The thumbnail URL pattern: https://i.ytimg.com/vi/VIDEO_ID/maxresdefault.jpg
(falls back to hqdefault.jpg if maxres isn't available)
Required CSS for video slides:
.video-thumbnail {
position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;
}
.video-play-btn {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
width: 80px; height: 80px; background: rgba(238,0,0,0.9); border-radius: 50%;
display: flex; align-items: center; justify-content: center; z-index: 2;
transition: background 0.2s ease;
}
.video-play-btn::after {
content: ''; display: block; width: 0; height: 0;
border-style: solid; border-width: 14px 0 14px 24px;
border-color: transparent transparent transparent #fff; margin-left: 4px;
}
.video-container:hover .video-play-btn { background: rgba(238,0,0,1); }
Required JavaScript (inside the navigation IIFE, BEFORE the navigation click handler):
// Click-to-play for video thumbnails
// HTTP/HTTPS: embed iframe inline for seamless playback
// file://: open in new tab (YouTube blocks iframe embeds from file:// protocol)
document.addEventListener('click', (e) => {
const vc = e.target.closest('.video-container[data-video-id]');
if (vc) {
e.stopImmediatePropagation(); // prevent navigation handler from firing
const vid = vc.dataset.videoId;
if (window.location.protocol === 'file:') {
window.open('https://www.youtube.com/watch?v=' + vid, '_blank');
} else {
const iframe = document.createElement('iframe');
iframe.src = 'https://www.youtube-nocookie.com/embed/' + vid + '?autoplay=1';
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture');
iframe.setAttribute('allowfullscreen', '');
vc.innerHTML = '';
vc.appendChild(iframe);
}
return;
}
});
Why e.stopImmediatePropagation(): When the click handler replaces the video container's
innerHTML, the clicked element (play button) becomes detached from the DOM. Without stopping
propagation, the navigation click handler can't find .video-container via closest() on the
detached target and accidentally advances the slide.
Inline video playback note: Include in the first slide's contextual notes and the nav hint
that videos play inline when served via HTTP (python3 -m http.server). When opened as a local
file, videos open on YouTube in a new tab instead.
<!-- YouTube video (thumbnail + new-tab pattern) -->
<div class="slide" data-notes="[notes]">
<div class="slide-label animate-in">—— LABEL</div>
<h2 class="animate-in">Headline <span class="accent">with accent</span></h2>
<div class="video-container animate-in"
data-video-id="VIDEO_ID"
data-video-url="https://www.youtube.com/watch?v=VIDEO_ID">
<img class="video-thumbnail" src="https://i.ytimg.com/vi/VIDEO_ID/maxresdefault.jpg" alt="Video description">
<div class="video-play-btn"></div>
</div>
<div class="media-caption animate-in">Optional context about the video</div>
<div class="source animate-in">Source attribution</div>
</div>
<!-- Direct video file (works from file:// natively) -->
<div class="slide" data-notes="[notes]">
<div class="slide-label animate-in">—— LABEL</div>
<h2 class="animate-in">Headline <span class="accent">with accent</span></h2>
<div class="video-container animate-in" data-video-url="https://example.com/video.mp4">
<video controls muted preload="metadata">
<source src="https://example.com/video.mp4" type="video/mp4">
</video>
</div>
<div class="media-caption animate-in">Optional context</div>
<div class="source animate-in">Source attribution</div>
</div>
9. Media Slide (Memes, GIFs, Linked Images, Giphy)
- Use when the user provides an image/meme/GIF URL or asks to include visual media
- Supports any image format: JPG, PNG, GIF (including animated), WebP, SVG
- Supports Giphy GIFs (see URL conversion rules below)
- Responsive container that preserves the original aspect ratio
- Optional caption below for humor, context, or commentary — use a lighter tone for memes
- Source / credit line at bottom
- Works for memes, reaction GIFs, diagrams, screenshots, photos — any linked image
- Important: Use full-resolution image URLs, not thumbnails. For imgflip templates, use the
https://imgflip.com/s/meme/Template-Name.jpgpattern (full-size) instead ofhttps://i.imgflip.com/2/xxxxx.jpg(150x150 thumbnails)
Media URL conversion rules:
- Giphy page
https://giphy.com/gifs/SLUG-ID→https://media.giphy.com/media/ID/giphy.gif - Giphy short
https://gph.is/SHORTCODE→ resolve to full URL, then extract ID forhttps://media.giphy.com/media/ID/giphy.gif - Giphy direct links (
media.giphy.com,media0-4.giphy.com) → use as-is - Imgflip templates → use
https://imgflip.com/s/meme/Template-Name.jpg(full-size) - Direct image URLs (
.jpg,.png,.gif,.webp,.svg) → use as-is - Local files (via
@file, file path, or "use this image") → embed as base64 data URI (see below)
Local File / @file Image Support
When the user provides a local file path or references an image via @file, embed the image
directly into the HTML as a base64 data URI. This keeps the deck fully self-contained and
portable — the image travels with the HTML file.
Encoding workflow:
-
Determine the MIME type from the file extension:
.png→image/png.jpg/.jpeg→image/jpeg.gif→image/gif.webp→image/webp.svg→image/svg+xml
-
Base64-encode the file using a shell command:
base64 -i /path/to/image.png | tr -d '\n' -
Construct the data URI and use it as the
src:<img src="data:image/png;base64,iVBORw0KGgo..." alt="Description" class="slide-media">
Important notes:
- This works for any image the user can reference — screenshots, diagrams, downloaded memes, photos, exported charts, etc.
- Base64 increases file size by ~33%, so very large images (>5MB) may make the HTML file unwieldy. For large images, suggest the user resize or compress first.
- Animated GIFs can be embedded as base64 but will be large. For animated content, prefer Giphy links when possible.
- The user may provide images by:
- Using
@filesyntax:@screenshot.pngor@/Users/name/Desktop/diagram.png - Pasting a file path:
/tmp/my-meme.jpg - Saying "use this image" with a file reference in context
- Dragging an image into the conversation
- Using
<!-- Standard image/meme -->
<div class="slide" data-notes="[notes]">
<div class="slide-label animate-in">—— LABEL</div>
<h2 class="animate-in">Headline <span class="accent">with accent</span></h2>
<div class="media-container animate-in">
<img src="https://example.com/meme.gif" alt="Descriptive alt text" class="slide-media">
</div>
<div class="media-caption animate-in">Optional witty caption or context</div>
<div class="source animate-in">Source / credit</div>
</div>
<!-- Giphy GIF example -->
<div class="slide" data-notes="[notes]">
<div class="slide-label animate-in">—— LABEL</div>
<h2 class="animate-in">Headline <span class="accent">with accent</span></h2>
<div class="media-container animate-in">
<img src="https://media.giphy.com/media/GIPHY_ID/giphy.gif" alt="Descriptive alt text" class="slide-media">
</div>
<div class="media-caption animate-in">Caption</div>
<div class="source animate-in">via Giphy</div>
</div>
10. Thank You Slide (Required — always the final slide)
- Large "Thank You" headline in Red Hat Display, Black weight
- Accent word ("You") in red-50
- Author name, role, and organization centered below
- Red Hat logo (large) centered beneath attribution
- Optionally include contact email, social handle, or team URL in small muted text
- Keep it clean — no tags, no body text, generous whitespace
- The ambient glow effect should be prominent on this slide for a strong visual close
Building the Narrative
When the user gives you a topic, follow this process:
Step 1: Research (if web search available)
Search for current statistics, quotes, and developments related to the topic. Look for:
- Recent industry reports or surveys with quotable numbers
- Expert quotes that support the thesis
- Competitive landscape data
- Adoption metrics or trends
Step 2: Choose a Story Arc
The arc is a spine, not a fill-in-the-blank template — vary structure so decks don't
feel formulaic. Read references/story-arcs.md and select the best arc for the content:
- Problem → Tension → Resolution: Best for introducing a new tool, approach, or technology
- Myth-Busting: Best for challenging conventional thinking
- Journey: Best for case studies or retrospectives
Step 3: Outline the Deck
Write the slide headlines FIRST. The headlines alone should tell the complete story. Show the user
the outline before generating the full HTML if the topic is complex. Run each headline through the
anti-slop checks in references/anti-ai-slop.md before proceeding — assertions, active voice,
concrete, no hype, no em-dashes.
Step 4: Write Each Slide
For each slide:
- Write the headline as an assertion
- Write concise supporting content (fewer words = more impact)
- Apply
references/anti-ai-slop.mdas you write — no buzzwords, no binary-contrast clichés, no padded triads, no em-dashes; name the specific thing - Choose the appropriate slide type
- Add source attributions where data is cited
- Add contextual notes with references, links, deeper explanations, and related resources that let viewers dive deeper into the slide's topic
Step 5: Note Video & Media Opportunities
As you build the narrative, identify slides where a video or meme could strengthen the story — a demo video after an architecture slide, a reaction meme to break tension after a dense section, etc. Do not insert Video or Media slides during initial generation. Instead, note opportunities in the contextual notes like:
[VIDEO OPPORTUNITY] A short demo of [feature] here would reinforce the architecture on the previous slide.
[MEME OPPORTUNITY] A well-placed meme here could break the tension after the dense comparison slide.
After delivering the deck, the post-generation prompt (see "Post-Generation: Video & Media Placement") will guide the user to add these if they choose.
Step 6: AI Image Opportunities
As you build slides, identify moments where a custom AI-generated image would elevate the deck. For each opportunity, add a note in the contextual notes like:
[IMAGE OPPORTUNITY] Prompt for Nano Banana Pro 2:
"[detailed image generation prompt describing the desired visual,
style, composition, colors, and mood — incorporating Red Hat brand
colors where appropriate]"
Good candidates for AI images:
- Title slide hero visuals (abstract, on-brand)
- Concept illustrations (e.g., "air gap" as a visual metaphor)
- Background textures or atmospheric elements
- Infographic-style data visualizations
- Metaphorical illustrations for complex concepts
When writing prompts, specify:
- Dark background compatible (for Core Dark decks)
- Red Hat color palette (reds, dark grays, subtle teals/purples)
- No text in the image (text will be overlaid in HTML)
- Aspect ratio suited for the slide layout (usually 16:9 or specific region)
Anti-AI-Slop (Non-Negotiable)
Decks must not read as AI-written. The failure mode is not just buzzwords — it is
rhetorical sameness: padded triads, binary-contrast clichés ("not just X, it's Y"),
throat-clearing, hype adjectives, and metronomic rhythm. The full catalog, the voice
target, and the scoring rubric live in references/anti-ai-slop.md. Read it before
writing copy.
Mandatory pre-delivery gate. Before you emit the final HTML, run this gate and do not skip it:
- Quick Checks — pass every headline, eyebrow/tag, bullet, stat caption, and
contextual-notes paragraph against the banned phrases and structures in
references/anti-ai-slop.md. Rewrite every hit. - Score — rate the deck copy on the five rubric dimensions (Directness, Rhythm, Trust, Authenticity, Density). If the total is below 35/50, revise and re-score.
- Lint — run the deterministic backstop on the generated files:
node scripts/slop-lint.mjs <deck>.html <deck>.mdFix every[error]. Review each[warning]and fix unless it is a deliberate, defensible choice (e.g. one intentional tricolon on the closing slide). - Only then deliver. The linter catches lexical tells; the structural/rhetorical slop is yours to catch in steps 1–2. A clean linter run is necessary, not sufficient.
Navigation JavaScript
Include this navigation system in every deck:
(function() {
const slides = document.querySelectorAll('.slide');
const currentEl = document.querySelector('.current');
const totalEl = document.querySelector('.total');
const notesPanel = document.querySelector('.notes-panel');
let idx = 0;
let notesVisible = false;
totalEl.textContent = slides.length;
function pauseAllMedia() {
// Pause HTML5 video elements on inactive slides
document.querySelectorAll('.slide:not(.active) video').forEach(v => v.pause());
// Pause YouTube/Vimeo iframes on inactive slides
document.querySelectorAll('.slide:not(.active) iframe').forEach(f => {
f.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
f.contentWindow.postMessage('{"method":"pause"}', '*');
});
}
function playActiveMedia() {
// Autoplay HTML5 video on the active slide
document.querySelectorAll('.slide.active video').forEach(v => v.play());
// Autoplay YouTube/Vimeo iframes on the active slide
document.querySelectorAll('.slide.active iframe').forEach(f => {
f.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
f.contentWindow.postMessage('{"method":"play"}', '*');
});
}
function show(i) {
slides.forEach((s, j) => {
s.classList.toggle('active', j === i);
s.style.display = j === i ? 'flex' : 'none';
});
currentEl.textContent = i + 1;
pauseAllMedia();
playActiveMedia();
// Update contextual notes if visible
if (notesVisible && notesPanel) {
const note = slides[i].dataset.notes || '';
notesPanel.innerHTML = note;
}
}
function next() { if (idx < slides.length - 1) { idx++; show(idx); } }
function prev() { if (idx > 0) { idx--; show(idx); } }
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); next(); }
if (e.key === 'ArrowLeft') { prev(); }
if (e.key === 'n' || e.key === 'N') {
notesVisible = !notesVisible;
if (notesPanel) notesPanel.classList.toggle('visible', notesVisible);
show(idx);
}
});
document.addEventListener('click', (e) => {
if (e.target.closest('.controls') || e.target.closest('.notes-panel') || e.target.closest('.video-container') || e.target.closest('.media-container')) return;
const x = e.clientX / window.innerWidth;
x > 0.5 ? next() : prev();
});
// Touch support
let touchStartX = 0;
document.addEventListener('touchstart', (e) => { touchStartX = e.touches[0].clientX; });
document.addEventListener('touchend', (e) => {
const diff = e.changedTouches[0].clientX - touchStartX;
if (Math.abs(diff) > 50) { diff < 0 ? next() : prev(); }
});
// Print / PDF export — clear JS-set inline display so all slides render in print,
// auto-fit any slide whose content would overflow the 7.5in print page, and
// restore the presenter view when the dialog closes. The fit pass measures each
// slide against print geometry and scales only the slides that need it; slides
// that already fit print at exact 1:1 fidelity. See "Print / PDF Export Styles"
// for the matching @media print CSS.
window.addEventListener('beforeprint', () => {
slides.forEach(s => { s.style.display = ''; });
document.body.classList.add('printing');
slides.forEach(s => {
// Wrap children once (idempotent across repeat prints). The wrapper is a flex
// container that MIRRORS the slide's own flex layout (justify-content,
// align-items, text-align, gap) so vertical/horizontal centering and the
// .thank-you-slide centering rule survive the wrap. Without this mirror,
// the slide's `justify-content: center` is silently lost and content
// top-anchors — which collides with absolutely-positioned eyebrow/breadcrumb
// elements on the title slide and breaks the closer slide's centering.
let fit = s.querySelector(':scope > .slide-fit');
if (!fit) {
fit = document.createElement('div');
fit.className = 'slide-fit';
while (s.firstChild) fit.appendChild(s.firstChild);
s.appendChild(fit);
}
const cs = window.getComputedStyle(s);
fit.style.cssText =
'display:flex;width:100%;height:100%;' +
'flex-direction:' + (cs.flexDirection || 'column') + ';' +
'justify-content:' + (cs.justifyContent || 'center') + ';' +
'align-items:' + (cs.alignItems || 'stretch') + ';' +
'text-align:' + (cs.textAlign || 'left') + ';' +
'gap:' + (cs.gap || '0') + ';' +
'transform-origin:top left;transform:none;';
// Lock children against flex-shrink so scrollHeight reflects true content
// size — otherwise items silently shrink and clip their own content (e.g.
// a code-block losing its last line of YAML, with no auto-fit triggered).
for (let i = 0; i < fit.children.length; i++) {
fit.children[i].style.flexShrink = '0';
}
// Pin slide to print geometry inline so clientWidth/Height reflect the print
// page even before the browser fully applies @media print rules.
s._priorStyle = s.style.cssText;
s.style.cssText +=
';display:flex !important;width:13.33in !important;height:7.5in !important;' +
'padding:0.55in 0.85in !important;box-sizing:border-box !important;' +
'overflow:hidden !important;';
// Measure content vs available area; scale down only if it overflows.
const cw = fit.clientWidth, ch = fit.clientHeight;
const sw = fit.scrollWidth, sh = fit.scrollHeight;
if (sw > cw + 1 || sh > ch + 1) {
const k = Math.min(cw / sw, ch / sh);
fit.style.width = (cw / k) + 'px';
fit.style.height = (ch / k) + 'px';
fit.style.transform = 'scale(' + k + ')';
fit.dataset.fitScale = k.toFixed(3);
}
});
});
window.addEventListener('afterprint', () => {
document.body.classList.remove('printing');
// Unwrap auto-fit containers and restore inline styles so on-screen
// navigation/animation behavior is unchanged.
document.querySelectorAll('.slide > .slide-fit').forEach(fit => {
const s = fit.parentElement;
while (fit.firstChild) {
const child = fit.firstChild;
// Clear the flex-shrink we set during beforeprint so screen layout
// returns to default flex behavior.
if (child.style && child.style.flexShrink) child.style.flexShrink = '';
s.appendChild(child);
}
fit.remove();
if (s._priorStyle !== undefined) {
s.style.cssText = s._priorStyle;
delete s._priorStyle;
}
});
show(idx);
});
show(0);
})();
Animations
Use subtle entrance animations for slide content. Stagger child elements for a cinematic reveal:
.slide.active .animate-in {
animation: fadeSlideUp 0.6s ease-out forwards;
}
.slide.active .animate-in:nth-child(2) { animation-delay: 0.1s; }
.slide.active .animate-in:nth-child(3) { animation-delay: 0.2s; }
.slide.active .animate-in:nth-child(4) { animation-delay: 0.3s; }
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
Print / PDF Export Styles
Every generated deck must include an @media print block so users can produce a clean,
multi-page PDF via the browser's built-in Cmd/Ctrl+P → Save as PDF. Without these styles,
printing captures only the first slide — because the navigation JS sets inline
style="display: none" on every non-active slide, which overrides regular stylesheet rules.
The fidelity requirement (non-negotiable)
A printed PDF page must look like a shrunken on-screen slide — same headline weight, same bullet alignment, same card geometry, same rhythm. The print CSS is not a redesign — it only changes what's physically required for paper (ink palette, page breaks, hidden chrome, disabled animations) and keeps everything else identical.
Failure mode to avoid: bullets that line up at a 24px offset on screen but drift to a
different offset in the PDF because em-based positioning compounded with a different font
size computed from clamp() in print context. Fix: pin print typography and geometry to
absolute pt values, and give each printed page a 16:9 aspect ratio so proportions match
the screen slide.
Why this is required
- The
show()function in the navigation IIFE setss.style.display = 'none'on inactive slides. Print CSS must use!importantAND thebeforeprintlistener must clear the inline styles (both are needed — some browsers honor inlinedisplay:noneeven in print context). - On-screen layouts use
100vh/ viewport units that clip when rendered onto paper. - Entrance animations (
fadeSlideUp) are triggered by the.activeclass and would leave non-active slides faded/offset in print if not reset. - Navigation chrome (
.controls,.notes-panel,.nav-hint) is useless in a PDF. clamp()withvwresolves unpredictably in print context across browsers — always override to fixedptvalues for every typographic class used in the deck.- A 7.5in print page is a hard ceiling — content taller than that gets clipped silently
(the screenshot shows the title chopped at the top and the last layer chopped at the
bottom of the same slide, because the slide centers content vertically and overflows
in both directions). The navigation IIFE includes a
beforeprintauto-fit pass that wraps each slide's children in a.slide-fitdiv, measures it against the print page, and appliestransform: scale()only if the content overflows. Slides that already fit print at exact 1:1 fidelity. This is a safety net — it does not replace the authoring density budgets below.
Required @media print block (include in every deck)
/* Print preview hygiene — outside any @media block so it applies in screen context.
While the print dialog is open, the JS beforeprint handler clears each slide's
inline display:none and sets display:flex on every slide so the auto-fit
measurement pass works. In the visible viewport behind the dialog this paints
all 14 (or N) slides on top of each other (they share `position:absolute;
inset:0;`). The PDF render is unaffected — but the screen view looks like a
jumbled overlap. Hiding the deck while body.printing is set keeps the visible
viewport clean; the @media print rule below restores it for the actual page render. */
body.printing .deck { visibility: hidden; }
@media print {
/* Restore visibility for the actual print render. body.printing is still set
during the print pass, but here we're in print context (not screen), and the
!important re-show overrides the screen-only rule above. */
body.printing .deck,
body.printing .slide { visibility: visible !important; }
/* Custom 16:9 page — each printed page has the same aspect ratio as an on-screen slide,
so proportions are preserved. Chrome's "Save as PDF" respects this; users who pick a
physical page size in the dialog get scaled-to-fit output the browser handles. */
@page { size: 13.33in 7.5in; margin: 0; }
/* Preserve brand colors (red accents, logo) through print */
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
/* Reset JS-set inline display:none and viewport-fill sizing */
html, body {
height: auto !important;
width: auto !important;
overflow: visible !important;
background: #fff !important;
}
.deck {
height: auto !important; width: auto !important;
position: static !important; overflow: visible !important;
}
/* Each slide fills one 16:9 page. Dimensions and padding stay in physical units so
layout is deterministic across browsers — no viewport math in print context. */
.slide {
display: flex !important;
position: relative !important;
inset: auto !important;
width: 13.33in !important;
height: 7.5in !important;
padding: 0.55in 0.85in !important;
gap: 0.14in !important;
box-sizing: border-box !important;
break-after: page;
page-break-after: always;
break-inside: avoid;
page-break-inside: avoid;
overflow: hidden !important;
}
.slide:last-child { break-after: auto; page-break-after: auto; }
.slide::before, .slide::after { display: none !important; }
/* Pin typography to fixed pt values — never let clamp()/vw recompute in print.
These numbers are scaled to fit the 13.33×7.5in page at roughly the same visual
proportions as a 1920×1080 on-screen slide. Tune if your deck uses a different
screen type scale, but keep them ABSOLUTE (pt), never relative (vw/em for clamp). */
.headline-xl { font-size: 60pt !important; line-height: 1.05 !important; letter-spacing: -0.02em; }
.headline-lg { font-size: 42pt !important; line-height: 1.05 !important; letter-spacing: -0.02em; }
.headline-md { font-size: 28pt !important; line-height: 1.1 !important; }
.subtitle, .body-text { font-size: 14pt !important; line-height: 1.45 !important; }
.big-number { font-size: 150pt !important; line-height: 0.9 !important; letter-spacing: -0.03em; }
.quote { font-size: 30pt !important; line-height: 1.2 !important; }
.slide-label, .breadcrumb { font-size: 10pt !important; letter-spacing: 0.1em; }
.attribution, .source, .nav-hint,
.arch-layer .arch-tag, .compare-col .lead, .proof-card .proof-title,
.quote-attribution, .tag { font-size: 9pt !important; }
/* Bullet geometry: pin ALL dimensions in pt so marks align identically to screen. */
.bullet-list { gap: 10pt !important; max-width: none !important; }
.bullet-list li {
padding-left: 18pt !important;
font-size: 12pt !important;
line-height: 1.45 !important;
}
.bullet-list li::before {
content: '' !important;
position: absolute !important;
left: 0 !important;
top: 0.7em !important;
width: 10pt !important;
height: 2pt !important;
background: #ee0000 !important;
border-radius: 0 !important;
}
/* Cards/grids: preserve on-screen structure; just normalize padding to physical units. */
.compare-grid, .proof-grid { ga
*Truncated - read the full file at https://github.com/toddward/red-hat-quick-deck/blob/7c9c4fb9fde806ab45351cef2e3e09e22f0c97a6/SKILL.md.*