# reveal.js -- Administrator & Developer Guide

reveal.js v6.0.1 is a JavaScript presentation framework that transforms semantic HTML into fully interactive slide decks with transitions, fragments, speaker notes, code highlighting, math rendering, and more. This guide covers every architectural detail, configuration option, and operational concern an administrator or developer needs to deploy, extend, customize, and maintain reveal.js presentations at any scale.

For installation walkthroughs, everyday usage, and feature tutorials, see the **User Guide**.

---

## 1. Architecture Overview

### 1.1 Design Philosophy

reveal.js uses a closure-based factory pattern rather than ES6 classes. The main entry point (`js/reveal.js`) exports a factory function that returns a `Reveal` object. Each presentation instance is a self-contained closure with private state and a public API surface. This enables multiple independent presentations on a single page without shared mutable state.

The framework has **zero runtime dependencies**. Every dependency in `package.json` is a `devDependency` used only during build time. The distributed bundle is fully self-contained.

### 1.2 Entry Point and Initialization Flow

The initialization sequence proceeds through these stages:

1. **Factory invocation** -- `Reveal()` or `new Reveal()` creates a presentation instance
2. **Config merge** -- `defaultConfig` from `js/config.ts` is merged with constructor options
3. **DOM validation** -- locates the `.reveal` root element (throws `"Unable to find presentation root (<div class="reveal">)"` if missing)
4. **Controller instantiation** -- all 20 internal controllers are created
5. **Plugin registration** -- plugins from `config.plugins` array are registered
6. **Plugin initialization** -- all plugins' `init()` methods are called and awaited
7. **Layout** -- initial layout calculation and slide positioning
8. **`ready` event** -- fires only after all plugins have initialized

> [!IMPORTANT]
> The `ready` event is the only safe point to begin interacting with the API programmatically. Code that runs before `ready` may encounter uninitialized plugins or incomplete DOM state.

### 1.3 Controller Inventory

Every controller is instantiated during initialization and manages a specific aspect of the presentation. Controllers communicate through the central `Reveal` instance and its event system.

| Controller | Source File | Responsibility |
|---|---|---|
| SlideContent | `js/controllers/slidecontent.js` | Lazy-loading of slide content, media lifecycle (play/pause/stop), iframe management, preloading based on `viewDistance` |
| SlideNumber | `js/controllers/slidenumber.js` | Slide number rendering in configurable formats (`h.v`, `h/v`, `c`, `c/t`) |
| JumpToSlide | `js/controllers/jumptoslide.js` | Jump-to-slide overlay triggered by `G` key, accepts slide number or `h.v` format |
| Backgrounds | `js/controllers/backgrounds.js` | Creates and synchronizes `.slide-background` elements for images, videos, iframes, gradients, and solid colors |
| AutoAnimate | `js/controllers/autoanimate.js` | FLIP-based animation between consecutive `data-auto-animate` slides, element matching by `data-id` or DOM heuristics |
| ScrollView | `js/controllers/scrollview.js` | Scroll-based view mode as alternative to slide-by-slide navigation |
| PrintView | `js/controllers/printview.js` | PDF-optimized layout for print/export workflows |
| Fragments | `js/controllers/fragments.js` | Fragment stepping, ordering, grouping, and state management |
| Overview | `js/controllers/overview.js` | Bird's-eye view of all slides (activated by `ESC` or `O` key) |
| Keyboard | `js/controllers/keyboard.js` | Keyboard shortcut registration, dispatch, and custom binding management |
| Location | `js/controllers/location.js` | URL hash reading/writing, history API integration, deep linking |
| Controls | `js/controllers/controls.js` | Navigation arrow rendering, tutorial animation, directional availability |
| Progress | `js/controllers/progress.js` | Progress bar rendering and click-to-navigate |
| Pointer | `js/controllers/pointer.js` | Cursor visibility management, auto-hide after `hideCursorTime` |
| Plugins | `js/controllers/plugins.js` | Plugin registration, initialization sequencing, lifecycle management |
| Overlay | `js/controllers/overlay.js` | Link preview overlays for iframes, images, and videos |
| Touch | `js/controllers/touch.js` | Touch and swipe gesture recognition for mobile navigation |
| Focus | `js/controllers/focus.js` | Focus state management and accessibility |
| Notes | `js/controllers/notes.js` | Internal notes state tracking for `<aside class="notes">` and `data-notes` |
| Playback | `js/components/playback.js` | Canvas-based play/pause UI for auto-slide timer |

### 1.4 Module Dependency Graph

```
reveal.js (factory)
  +-- config.ts (defaults + RevealConfig interface)
  +-- controllers/ (20 controllers)
  |     +-- plugins.js --> plugin instances
  |     +-- slidecontent.js --> utils/loader.ts
  |     +-- autoanimate.js --> utils/util.ts
  |     +-- location.js --> utils/util.ts (getQueryHash)
  |     +-- backgrounds.js --> utils/color.ts
  +-- utils/
  |     +-- color.ts (colorToRgb, colorBrightness)
  |     +-- constants.ts (selectors, blacklists, regex)
  |     +-- device.ts (isMobile, isChrome, isAndroid)
  |     +-- loader.ts (dynamic script/link injection)
  |     +-- util.ts (DOM helpers, math, serialization)
  +-- components/
        +-- playback.js (canvas UI)
```

### 1.5 Utility Modules

| Module | Exports | Purpose |
|---|---|---|
| `js/utils/color.ts` | `colorToRgb()`, `colorBrightness()` | Parse CSS color strings and compute brightness for light/dark background detection |
| `js/utils/constants.ts` | `SLIDES_SELECTOR`, `POST_MESSAGE_METHOD_BLACKLIST`, `FRAGMENT_STYLE_REGEX`, slide number format constants | Centralized selectors and security constants |
| `js/utils/device.ts` | `isMobile`, `isChrome`, `isAndroid` | User-agent based device detection for touch/rendering behavior |
| `js/utils/loader.ts` | `loadScript()` | Dynamic `<script>` injection with callback |
| `js/utils/util.ts` | `extend()`, `queryAll()`, `toggleClass()`, `deserialize()`, `distanceBetween()`, `transformElement()`, `matches()`, `closest()`, `enterFullscreen()`, `createSingletonNode()`, `createStyleSheet()`, `getQueryHash()`, `getRemainingHeight()`, `getMimeTypeFromFile()`, `encodeRFC3986URI()` | DOM manipulation, math, serialization, fullscreen API |

---

## 2. System Requirements

### 2.1 Build Environment

| Requirement | Minimum Version |
|---|---|
| Node.js | >= 20.19.0 (enforced in `package.json` `engines` field) |
| npm | Bundled with Node.js 20+ |

### 2.2 Build Toolchain (devDependencies)

All dependencies are development-only. The built output has zero runtime dependencies.

| Package | Version | Role |
|---|---|---|
| TypeScript | ^6.0.2 | Type checking and declaration generation |
| Vite | ^8.0.3 | Build system and dev server |
| esbuild | ^0.28.0 | ES5 fallback transpilation |
| Sass | ^1.98.0 | SCSS theme compilation |
| vite-plugin-dts | ^4.5.4 | TypeScript declaration file generation |
| highlight.js | ^11.11.1 | Bundled into Highlight plugin |
| marked | ^17.0.5 | Bundled into Markdown plugin |
| marked-smartypants | ^1.1.11 | Smart quotes for Markdown plugin |
| fitty | ^2.4.2 | Text fitting utility |
| jszip | ^3.10.1 | ZIP archive creation for `npm run package` |
| glob | ^13.0.6 | File globbing for build scripts |
| QUnit | ^2.25.0 | Test framework |
| node-qunit-puppeteer | ^2.2.1 | Headless browser test runner |

### 2.3 Browser Support

reveal.js targets modern browsers with ES module support. The `npm run build:es5` command produces an ES5 fallback bundle via esbuild for legacy environments.

| Browser | Support |
|---|---|
| Chrome/Edge | Current and recent versions |
| Firefox | Current and recent versions |
| Safari | Current and recent versions |
| Mobile Safari/Chrome | Supported with touch gestures |
| IE 11 | ES5 fallback only (`reveal.es5.js`) |

> [!NOTE]
> Device detection in `js/utils/device.ts` applies browser-specific workarounds. The `isChrome` and `isAndroid` flags affect touch handling and rendering behavior.

### 2.4 React Wrapper Requirements

The `@revealjs/react` package (v0.2.1) in `react/` has these peer dependencies:

| Package | Version |
|---|---|
| react | >= 18 |
| react-dom | >= 18 |
| reveal.js | >= 5 |

---

## 3. Configuration Reference

### 3.1 Config Resolution Order

Configuration values are merged with increasing precedence:

1. **`defaultConfig`** -- hardcoded defaults in `js/config.ts`
2. **`Reveal.configure()` calls before `initialize()`** -- pre-init overrides
3. **Constructor options** -- `Reveal({ ... })` or `new Reveal({ ... })`
4. **`Reveal.initialize()` options** -- final programmatic overrides
5. **URL query parameters** -- parsed by `getQueryHash()` from the URL fragment

> [!TIP]
> URL query parameters provide an easy way to override config for testing or sharing. Append them after `#` in the URL: `presentation.html#/2?transition=none&controls=false`. Values are automatically deserialized (strings `"true"`/`"false"` become booleans, numeric strings become numbers).

### 3.2 Complete Config Options

Every option below is from the `defaultConfig` object in `js/config.ts` and the `RevealConfig` interface.

#### 3.2.1 Presentation Dimensions

| Option | Type | Default | Description |
|---|---|---|---|
| `width` | `number` | `960` | Presentation width in pixels. The framework scales content to fit the viewport while maintaining this aspect ratio. |
| `height` | `number` | `700` | Presentation height in pixels. |
| `margin` | `number` | `0.04` | Factor of empty space around the content (0.0 = no margin, 0.5 = half the viewport). |
| `minScale` | `number` | `0.2` | Minimum scale factor applied to the presentation. |
| `maxScale` | `number` | `2.0` | Maximum scale factor applied to the presentation. |

#### 3.2.2 Controls

| Option | Type | Default | Description |
|---|---|---|---|
| `controls` | `boolean` | `true` | Show navigation arrow controls. |
| `controlsTutorial` | `boolean` | `true` | Show a subtle bounce animation on controls the first time a presentation is viewed to hint at interactivity. |
| `controlsLayout` | `string` | `'bottom-right'` | Position of controls. Allowed values: `'bottom-right'`, `'edges'`. |
| `controlsBackArrows` | `string` | `'faded'` | Visibility of back-direction arrows. Allowed values: `'faded'`, `'hidden'`, `'visible'`. |

#### 3.2.3 Progress and Slide Numbers

| Option | Type | Default | Description |
|---|---|---|---|
| `progress` | `boolean` | `true` | Show a thin progress bar at the bottom. |
| `slideNumber` | `boolean \| string` | `false` | Display slide number. Set to `true` for default format, or a format string: `'h.v'` (horizontal.vertical), `'h/v'`, `'c'` (flattened count), `'c/t'` (count/total). |
| `showSlideNumber` | `string` | `'all'` | Where slide numbers appear. Allowed values: `'all'`, `'print'`, `'speaker'`. |

#### 3.2.4 Navigation and History

| Option | Type | Default | Description |
|---|---|---|---|
| `hash` | `boolean` | `false` | Write the current slide hash to the URL, enabling browser back/forward navigation. |
| `hashOneBasedIndex` | `boolean` | `false` | Use 1-based indices in URL hash instead of 0-based. |
| `respondToHashChanges` | `boolean` | `true` | Listen for `hashchange` events and navigate accordingly. |
| `jumpToSlide` | `boolean` | `true` | Enable the jump-to-slide overlay (triggered by `G` key). |
| `history` | `boolean` | `false` | Push each slide change to the browser history (creates back/forward entries). |
| `keyboard` | `boolean` | `true` | Enable keyboard navigation shortcuts. |
| `keyboardCondition` | `string \| null \| function` | `null` | Condition for keyboard activation. Set to `'focused'` to only enable when the presentation has focus. Can also be a function returning boolean. |
| `overview` | `boolean` | `true` | Enable the slide overview mode (`ESC`/`O` key). |
| `loop` | `boolean` | `false` | Loop the presentation (navigating past the last slide returns to the first). |
| `rtl` | `boolean` | `false` | Right-to-left navigation order. |
| `navigationMode` | `string` | `'default'` | Navigation mode. Allowed values: `'default'`, `'linear'`, `'grid'`. Linear flattens all slides. Grid enables full 2D arrow navigation. |
| `shuffle` | `boolean` | `false` | Randomize slide order on each presentation load. |

#### 3.2.5 Content Behavior

| Option | Type | Default | Description |
|---|---|---|---|
| `center` | `boolean` | `true` | Vertically center slide content. |
| `touch` | `boolean` | `true` | Enable touch/swipe navigation on touch devices. |
| `fragments` | `boolean` | `true` | Enable fragment stepping (sequential reveal of elements). |
| `fragmentInURL` | `boolean` | `true` | Include fragment index in the URL hash. |
| `help` | `boolean` | `true` | Enable the help overlay (`?` key). |
| `pause` | `boolean` | `true` | Enable pause mode (`.` or `B` key). |
| `showNotes` | `boolean` | `false` | Show speaker notes in the presentation view (visible to audience). |
| `showHiddenSlides` | `boolean` | `false` | Show slides marked with `data-visibility="hidden"`. |
| `sortFragmentsOnSync` | `boolean` | `true` | Automatically sort fragments by their `data-fragment-index` on sync. |

#### 3.2.6 Media and Embedding

| Option | Type | Default | Description |
|---|---|---|---|
| `autoPlayMedia` | `boolean \| null` | `null` | Auto-play `<video>` and `<audio>` on slide entry. `null` = auto-play only elements with `autoplay` attribute. `true` = always. `false` = never. |
| `preloadIframes` | `boolean \| null` | `null` | Pre-load `<iframe>` content. `null` = preload only elements with `data-preload`. `true` = always. `false` = never. |
| `previewLinks` | `boolean` | `false` | Open links in an iframe overlay instead of navigating away. |
| `viewDistance` | `number` | `3` | Number of slides away from current to pre-load content for. |
| `mobileViewDistance` | `number` | `2` | Same as `viewDistance` but for mobile devices (reduced for performance). |

#### 3.2.7 Appearance

| Option | Type | Default | Description |
|---|---|---|---|
| `display` | `string` | `'block'` | CSS `display` value applied to each slide when shown. |
| `hideInactiveCursor` | `boolean` | `true` | Hide the cursor after a period of inactivity. |
| `hideCursorTime` | `number` | `5000` | Milliseconds before an inactive cursor is hidden. |
| `embedded` | `boolean` | `false` | Mark the presentation as embedded within a larger page (affects sizing/layout). |
| `disableLayout` | `boolean` | `false` | Disable all reveal.js layout/scaling. Useful for fully custom CSS layouts. |

#### 3.2.8 Auto-Animate

| Option | Type | Default | Description |
|---|---|---|---|
| `autoAnimate` | `boolean` | `true` | Enable auto-animate between consecutive slides with `data-auto-animate`. |
| `autoAnimateMatcher` | `function \| null` | `null` | Custom element matching function for auto-animate. Signature: `(fromEl, toEl) => boolean`. |
| `autoAnimateEasing` | `string` | `'ease'` | Default CSS easing for auto-animate transitions. |
| `autoAnimateDuration` | `number` | `1.0` | Default duration in seconds for auto-animate transitions. |
| `autoAnimateUnmatched` | `boolean` | `true` | Animate unmatched elements (fade in/out) during auto-animate. |
| `autoAnimateStyles` | `string[]` | `['opacity', 'color', 'background-color', 'padding', 'font-size', 'line-height', 'letter-spacing', 'border-width', 'border-color', 'border-radius', 'outline', 'outline-offset']` | CSS properties that are auto-animated when they differ between matched elements. |

#### 3.2.9 Auto-Slide

| Option | Type | Default | Description |
|---|---|---|---|
| `autoSlide` | `number` | `0` | Milliseconds between automatic slide transitions. `0` disables. |
| `autoSlideStoppable` | `boolean` | `true` | Allow the user to stop auto-sliding by interacting with the presentation. |
| `autoSlideMethod` | `function \| null` | `null` | Custom function called to advance slides during auto-slide. Receives the `Reveal` instance. |
| `defaultTiming` | `number \| null` | `null` | Default timing for slides without `data-autoslide`. If set, used instead of `autoSlide` per slide. |

#### 3.2.10 Transitions

| Option | Type | Default | Description |
|---|---|---|---|
| `transition` | `string` | `'slide'` | Default slide transition. Allowed values: `'none'`, `'fade'`, `'slide'`, `'convex'`, `'concave'`, `'zoom'`. |
| `transitionSpeed` | `string` | `'default'` | Transition speed. Allowed values: `'default'`, `'fast'`, `'slow'`. |
| `backgroundTransition` | `string` | `'fade'` | Background transition style. Same allowed values as `transition`. |

#### 3.2.11 Parallax Backgrounds

| Option | Type | Default | Description |
|---|---|---|---|
| `parallaxBackgroundImage` | `string` | `''` | URL of parallax background image. |
| `parallaxBackgroundSize` | `string` | `''` | CSS `background-size` for parallax image (e.g., `'2100px 900px'`). |
| `parallaxBackgroundRepeat` | `string` | `''` | CSS `background-repeat` for parallax image. |
| `parallaxBackgroundPosition` | `string` | `''` | CSS `background-position` for parallax image. |
| `parallaxBackgroundHorizontal` | `number \| null` | `null` | Pixels to shift parallax background per slide horizontally. Auto-calculated if `null`. |
| `parallaxBackgroundVertical` | `number \| null` | `null` | Pixels to shift parallax background per slide vertically. Auto-calculated if `null`. |

#### 3.2.12 Scroll View

| Option | Type | Default | Description |
|---|---|---|---|
| `view` | `string \| null` | `null` | View mode. Set to `'scroll'` for scroll-based presentation. `null` for default slide view. |
| `scrollLayout` | `string` | `'full'` | Scroll view layout. Allowed values: `'full'`, `'compact'`. |
| `scrollSnap` | `string` | `'mandatory'` | CSS scroll-snap behavior. Allowed values: `'mandatory'`, `'proximity'`. |
| `scrollProgress` | `string` | `'auto'` | Show scroll progress indicator. Allowed values: `'auto'`, `true`, `false`. |
| `scrollActivationWidth` | `number` | `435` | Viewport width threshold (in pixels) below which scroll view is automatically activated. |

#### 3.2.13 PDF Export

| Option | Type | Default | Description |
|---|---|---|---|
| `pdfMaxPagesPerSlide` | `number` | `Infinity` | Maximum number of PDF pages generated per slide (for very tall slides). `Infinity` means no limit. |
| `pdfSeparateFragments` | `boolean` | `true` | Give each fragment step its own page in the PDF. |
| `pdfPageHeightOffset` | `number` | `-1` | Offset applied to PDF page height to avoid page-break issues. `-1` uses auto-detection. |

#### 3.2.14 Plugins and PostMessage

| Option | Type | Default | Description |
|---|---|---|---|
| `plugins` | `array` | `[]` | Array of plugin instances or factory functions to register. |
| `dependencies` | `array` | `[]` | Legacy dependency loading (deprecated in favor of `plugins`). |
| `postMessage` | `boolean` | `true` | Enable the `postMessage` API for cross-window communication. |
| `postMessageEvents` | `boolean` | `false` | Dispatch all reveal.js events as `postMessage` messages to the parent window. |
| `focusBodyOnPageVisibilityChange` | `boolean` | `true` | Focus `document.body` when the page becomes visible (prevents focus loss). |
| `preventIframeAutoFocus` | `boolean` | `false` | Prevent iframe embeds from automatically stealing focus. |

### 3.3 Plugin-Specific Config Keys

Plugin configuration is nested under the main config object using the plugin name as the key.

#### 3.3.1 Highlight Plugin Config

```javascript
Reveal.initialize({
  highlight: {
    highlightOnLoad: true,    // boolean - Auto-highlight <pre><code> on load
    escapeHTML: true,          // boolean - Escape HTML entities in code blocks
    beforeHighlight: null      // function(code, block) - Callback before highlighting
  }
});
```

#### 3.3.2 Markdown Plugin Config

```javascript
Reveal.initialize({
  markdown: {
    separator: '\\r?\\n---\\r?\\n',       // string - Horizontal slide separator regex
    verticalSeparator: null,               // string|null - Vertical slide separator regex
    notesSeparator: '^\\s*notes?:',        // string - Notes separator regex
    animateLists: false,                   // boolean - Wrap <li> elements in fragments
    smartypants: false,                    // boolean - Enable typographic quote replacement
    // All marked library options are also accepted:
    gfm: true,                            // boolean - GitHub Flavored Markdown
    breaks: false,                         // boolean - Treat newlines as <br>
    renderer: null                         // object - Custom marked renderer
  }
});
```

#### 3.3.3 Math Plugin Config (KaTeX)

```javascript
Reveal.initialize({
  katex: {
    local: null,              // string|null - Path to local KaTeX installation
    version: 'latest',        // string - KaTeX version from CDN
    delimiters: [             // array - Delimiter pairs for math detection
      { left: '$$', right: '$$', display: true },
      { left: '$', right: '$', display: false },
      { left: '\\(', right: '\\)', display: false },
      { left: '\\[', right: '\\]', display: true }
    ],
    ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
    extensions: []            // array - KaTeX extensions (e.g., ['mhchem'])
  }
});
```

CDN base: `cdn.jsdelivr.net/npm/katex`

#### 3.3.4 Math Plugin Config (MathJax2)

```javascript
Reveal.initialize({
  mathjax2: {
    mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js',
    config: 'TeX-AMS_HTML-full',    // string - MathJax configuration preset
    tex2jax: {
      inlineMath: [['$', '$'], ['\\(', '\\)']],
      skipTags: ['script', 'noscript', 'style', 'textarea', 'pre']
    }
  }
});
```

MathJax2 retypesets on every `slidechanged` event.

#### 3.3.5 Math Plugin Config (MathJax3)

```javascript
Reveal.initialize({
  mathjax3: {
    mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js',
    tex: {
      inlineMath: [['$', '$'], ['\\(', '\\)']]
    },
    options: {
      skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre']
    },
    startup: {
      ready: null              // function - Custom startup handler
    }
  }
});
```

Sets `window.MathJax` configuration object before loading the library.

#### 3.3.6 Math Plugin Config (MathJax4)

```javascript
Reveal.initialize({
  mathjax4: {
    mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js',
    tex: {
      inlineMath: [['$', '$'], ['\\(', '\\)']]
    },
    options: {
      skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre']
    },
    startup: {
      ready: null
    },
    output: 'chtml'            // string - Output format
  }
});
```

Uses async startup and waits for `MathJax.startup.promise` to resolve.

---

## 4. Deployment & Scaling

### 4.1 Build Commands

| Command | Purpose | Output |
|---|---|---|
| `npm run dev` / `npm start` | Start Vite dev server with hot reload | Serves from source |
| `npm run build` | Full production build (TypeScript + Vite core + styles + all 6 plugins) | `dist/` |
| `npm run build:core` | Build core library only | `dist/reveal.js`, `dist/reveal.mjs`, `dist/reveal.d.ts` |
| `npm run build:es5` | ES5 fallback bundle via esbuild | `dist/reveal.es5.js` |
| `npm run build:styles` | Compile SCSS themes and base styles | `dist/reveal.css`, `dist/reset.css`, `dist/theme/*.css` |
| `npm run package` | Create distributable ZIP archive | ZIP file via jszip |
| `npm test` | Run QUnit tests in headless Puppeteer | Console output |
| `npm run react:build` | Build React wrapper | `react/dist/` |
| `npm run react:demo` | Run React wrapper demo | Dev server |
| `npm run react:test` | Run React wrapper tests (Vitest + Testing Library) | Console output |

### 4.2 Build Architecture

Each plugin has its own `vite.config.ts` that produces both ES module (`.mjs`) and UMD (`.js`) formats. TypeScript declarations are generated via `vite-plugin-dts` with a custom path rewriting plugin at `plugin/vite-plugin-dts.ts`.

The build pipeline:

```
TypeScript compile (tsc)
  +-- Vite build: core (js/index.ts -> dist/reveal.{js,mjs})
  +-- Vite build: highlight plugin (-> dist/plugin/highlight.{js,mjs})
  +-- Vite build: markdown plugin (-> dist/plugin/markdown.{js,mjs})
  +-- Vite build: math plugin (-> dist/plugin/math.{js,mjs})
  +-- Vite build: notes plugin (-> dist/plugin/notes.{js,mjs})
  +-- Vite build: search plugin (-> dist/plugin/search.{js,mjs})
  +-- Vite build: zoom plugin (-> dist/plugin/zoom.{js,mjs})
  +-- Sass compile: themes (css/theme/*.scss -> dist/theme/*.css)
  +-- Sass compile: base (css/reveal.scss -> dist/reveal.css)
  +-- Copy: reset.css -> dist/reset.css
```

### 4.3 Package Exports Map

The `package.json` `exports` field defines the public module entry points:

| Specifier | CJS | ESM | Types |
|---|---|---|---|
| `.` | `dist/reveal.js` | `dist/reveal.mjs` | `dist/reveal.d.ts` |
| `./reveal.css` | `dist/reveal.css` | -- | -- |
| `./reset.css` | `dist/reset.css` | -- | -- |
| `./theme/*` | `dist/theme/*` | -- | -- |
| `./plugin/highlight` | `dist/plugin/highlight.js` | `dist/plugin/highlight.mjs` | `dist/plugin/highlight.d.ts` |
| `./plugin/markdown` | `dist/plugin/markdown.js` | `dist/plugin/markdown.mjs` | `dist/plugin/markdown.d.ts` |
| `./plugin/math` | `dist/plugin/math.js` | `dist/plugin/math.mjs` | `dist/plugin/math.d.ts` |
| `./plugin/notes` | `dist/plugin/notes.js` | `dist/plugin/notes.mjs` | `dist/plugin/notes.d.ts` |
| `./plugin/search` | `dist/plugin/search.js` | `dist/plugin/search.mjs` | `dist/plugin/search.d.ts` |
| `./plugin/zoom` | `dist/plugin/zoom.js` | `dist/plugin/zoom.mjs` | `dist/plugin/zoom.d.ts` |

### 4.4 Deployment Patterns

#### Static File Hosting

The simplest deployment is serving the `dist/` directory (plus your HTML files) from any static host (Nginx, Apache, S3, Netlify, Vercel, GitHub Pages).

```
your-presentation/
  index.html
  dist/
    reveal.js
    reveal.mjs
    reveal.css
    reset.css
    theme/
    plugin/
```

#### CDN Usage

> [!WARNING]
> As of v6.0.1, CDN availability is a known issue (see GitHub issue #3894). Verify the version is available on your CDN of choice before relying on it for production.

#### npm Package

```javascript
import Reveal from 'reveal.js';
import 'reveal.js/reveal.css';
import 'reveal.js/theme/black.css';
import Highlight from 'reveal.js/plugin/highlight';
import Markdown from 'reveal.js/plugin/markdown';
```

#### Self-Hosted with Vite Dev Server

For development, `npm run dev` starts Vite with hot module replacement. The `index.html` at the project root serves as the development entry point.

### 4.5 Multiple Instances

reveal.js supports multiple independent presentation instances on a single page. Each `Reveal()` call creates a new instance targeting a different `.reveal` root element:

```javascript
let deck1 = new Reveal(document.querySelector('#deck1'), { embedded: true });
let deck2 = new Reveal(document.querySelector('#deck2'), { embedded: true });

await deck1.initialize();
await deck2.initialize();
```

Set `embedded: true` to prevent instances from capturing global keyboard events.

> [!CAUTION]
> When running multiple instances, each must target a unique DOM element. Initializing two instances on the same `.reveal` root leads to undefined behavior. The `embedded: true` flag is essential to prevent keyboard/focus conflicts.

---

## 5. Operations

### 5.1 Theme System

reveal.js ships 14 themes, all authored as SCSS in `css/theme/`:

| Theme | File | Character |
|---|---|---|
| beige | `beige.scss` | Warm, parchment-like |
| black | `black.scss` | Dark, high contrast (default) |
| black-contrast | `black-contrast.scss` | WCAG-enhanced dark |
| blood | `blood.scss` | Dark red dramatic |
| dracula | `dracula.scss` | Dracula color scheme |
| league | `league.scss` | Dark gray textured |
| moon | `moon.scss` | Dark blue/gray |
| night | `night.scss` | Pure dark |
| serif | `serif.scss` | Light, serif typography |
| simple | `simple.scss` | Minimal light |
| sky | `sky.scss` | Light blue |
| solarized | `solarized.scss` | Solarized light palette |
| white | `white.scss` | Clean white |
| white-contrast | `white-contrast.scss` | WCAG-enhanced light |

#### Theme Architecture

The theme template system uses three files in `css/theme/template/`:

- **`settings.scss`** -- Defines all SCSS variables (fonts, colors, spacing, heading sizes)
- **`mixins.scss`** -- Shared SCSS mixins
- **`theme.scss`** -- Base theme styles that consume the variables

Each theme file imports the template, overrides variables, and optionally adds custom rules. To create a custom theme:

```scss
// my-theme.scss
@use 'template/settings' with (
  $backgroundColor: #191919,
  $mainColor: #fff,
  $headingColor: #fff,
  $linkColor: #42affa
);
@use 'template/theme';
```

### 5.2 PDF Export

PDF export works through the PrintView controller. Access the print-optimized layout by appending `?print-pdf` to the URL, then use the browser's print dialog (Ctrl/Cmd+P).

Key config options:

- `pdfSeparateFragments: true` -- each fragment step gets its own page
- `pdfMaxPagesPerSlide: Infinity` -- limit pages for very tall slides
- `pdfPageHeightOffset: -1` -- auto-detect page height offset

The `pdf-ready` event fires when the print layout is fully rendered and ready for capture.

> [!NOTE]
> GitHub issue #3860 reports that PDF export can generate very large files. Issue #3888 documents compatibility problems with Decktape (a popular third-party PDF export tool). For reliable PDF export, use the browser's native print dialog with Chrome.

### 5.3 Keyboard Shortcuts

Default key bindings registered by the Keyboard controller:

| Key | Action |
|---|---|
| Right / Down / Space / N / Page Down | Next slide |
| Left / Up / Shift+Space / P / Page Up | Previous slide |
| Home | First slide |
| End | Last slide |
| F | Toggle fullscreen |
| ESC / O | Toggle overview |
| S | Open speaker notes window |
| G | Jump to slide overlay |
| ? | Toggle help overlay |
| . / B | Toggle pause (blackout) |
| V | Toggle black screen |

### 5.4 Events Reference

All events are dispatched via the reveal.js event system and can be listened to with `Reveal.on()`:

| Event | Payload | Fires When |
|---|---|---|
| `ready` | `{ indexh, indexv, currentSlide }` | Initialization complete, all plugins loaded |
| `slidechanged` | `{ indexh, indexv, previousSlide, currentSlide, origin }` | Slide navigation occurs |
| `slidesync` | `{ indexh, indexv, currentSlide }` | Slide content synchronized |
| `fragmentshown` | `{ fragment, fragments }` | Fragment becomes visible |
| `fragmenthidden` | `{ fragment, fragments }` | Fragment becomes hidden |
| `overviewshown` | `{}` | Overview mode activated |
| `overviewhidden` | `{}` | Overview mode deactivated |
| `paused` | `{}` | Presentation paused |
| `resumed` | `{}` | Presentation resumed |
| `autoanimate` | `{ fromSlide, toSlide }` | Auto-animate transition occurs |
| `pdf-ready` | `{}` | Print/PDF layout fully rendered |
| `previewiframe` | `{ url }` | Iframe preview overlay shown |
| `previewimage` | `{ url }` | Image preview overlay shown |
| `previewvideo` | `{ url }` | Video preview overlay shown |
| `closeoverlay` | `{}` | Preview overlay closed |

---

## 6. Performance

### 6.1 Lazy Loading

The SlideContent controller implements lazy loading based on `viewDistance` (default: 3 slides) and `mobileViewDistance` (default: 2). Content outside the view distance is not loaded until the user navigates near it.

This affects:
- `<img>` with `data-src` -- source set on approach, cleared on departure
- `<video>` and `<audio>` with `data-src` -- source set on approach
- `<iframe>` with `data-src` -- source set on approach, cleared on departure
- Background images, videos, and iframes follow the same pattern

### 6.2 Layout Performance

- `layout()` recalculates all slide positions and scaling. Called automatically on window resize.
- `sync()` re-processes all slides (content, backgrounds, fragments). Expensive; use `syncSlide()` for single-slide updates.
- `disableLayout: true` skips all reveal.js layout calculations, deferring entirely to CSS.
- Background brightness detection (`colorBrightness()` in `utils/color.ts`) adds `has-light-background` or `has-dark-background` classes, enabling automatic text color adaptation.

### 6.3 Mobile Optimization

- `mobileViewDistance` reduces preloading on mobile to conserve bandwidth and memory
- Touch controller uses passive event listeners where supported
- Device detection (`utils/device.ts`) adjusts behavior for mobile browsers
- `scrollActivationWidth: 435` auto-activates scroll view on narrow viewports

> [!TIP]
> For bandwidth-constrained deployments, reduce `viewDistance` and `mobileViewDistance` to 1. This significantly reduces upfront resource loading at the cost of brief loading delays during navigation.

---

## 7. Security

### 7.1 PostMessage API Security

The `postMessage` API enables cross-window communication (used by the speaker notes window and embedded presentations). Security measures:

**Method Blacklist** (`POST_MESSAGE_METHOD_BLACKLIST` in `js/utils/constants.ts`):

The following methods are blocked from `postMessage` invocation to prevent unauthorized plugin injection, event hijacking, and preview abuse:

- `registerPlugin`
- `registerKeyboardShortcut`
- `addKeyBinding`
- `addEventListener`
- `showPreview`
- `previewIframe`

Any `postMessage` attempting to call these methods is silently ignored.

### 7.2 Speaker Notes Window Security

The Notes plugin implements same-origin verification on `postMessage` events. Only messages from the same origin (`window.location.origin`) are accepted. This XSS safeguard prevents malicious cross-origin pages from injecting commands into the speaker view.

The notes window uses a heartbeat mechanism for orphaned window reconnection -- if the parent window closes and reopens, the existing notes window can re-establish the connection.

### 7.3 Query Parameter Sanitization

The `getQueryHash()` function in `js/utils/util.ts` explicitly deletes the `dependencies` key from parsed URL query parameters. This prevents script injection via crafted URLs that could load arbitrary remote scripts through the legacy dependency system.

### 7.4 Markdown External File Loading

External markdown files loaded via `<section data-markdown="file.md">` use XHR requests, which are subject to the browser's same-origin policy. Cross-origin markdown files require appropriate CORS headers on the serving host.

> [!WARNING]
> When `postMessage: true` (the default), any page that can reference your presentation's window can send navigation commands. Set `postMessage: false` for presentations containing sensitive content that should not be externally controllable.

### 7.5 Content Security Policy Considerations

reveal.js dynamically creates `<style>` elements (via `createStyleSheet()` in `utils/util.ts`) and injects inline styles for layout. A strict CSP policy requires:

- `style-src 'unsafe-inline'` -- for dynamic style injection
- `script-src` must allow the reveal.js bundle and any plugin scripts
- `frame-src` as needed for iframe backgrounds and preview overlays
- Math plugins load scripts from `cdn.jsdelivr.net` by default; either allow this origin or use the `local` config option

---

## 8. Developer Internals

### 8.1 Plugin Architecture

#### Plugin Interface

```typescript
interface RevealPlugin {
  id: string;                              // Unique identifier
  init?(reveal: RevealApi): void | Promise<void>;  // Called during initialization
  destroy?(): void;                        // Called on Reveal.destroy()
}
```

Plugins can also be registered as factory functions that return a `RevealPlugin` object:

```javascript
// Factory pattern
const MyPlugin = () => ({
  id: 'my-plugin',
  init: (reveal) => {
    // Access full API
    reveal.on('slidechanged', handleSlideChange);
    reveal.getConfig(); // read config
  },
  destroy: () => {
    // Cleanup
  }
});

Reveal.initialize({
  plugins: [MyPlugin]
});
```

#### Plugin Lifecycle

1. **Registration** -- plugins are registered from `config.plugins` array or via `Reveal.registerPlugin(plugin)`
2. **Initialization** -- `Plugins` controller calls each plugin's `init()` method, passing the `RevealApi` instance
3. **Async support** -- if `init()` returns a `Promise`, the framework waits for all plugin promises to resolve before firing `ready`
4. **Access** -- `Reveal.hasPlugin(id)`, `Reveal.getPlugin(id)`, `Reveal.getPlugins()` for runtime inspection
5. **Destruction** -- `Reveal.destroy()` calls each plugin's `destroy()` method

#### Built-in Plugin Details

**Highlight** (id: `'highlight'`)
- Wraps highlight.js for syntax highlighting
- Processes all `<pre><code>` blocks in the presentation
- `data-line-numbers` attribute format: `"1,2|3,5-10"` where `|` separates animation steps, `,` separates individual lines, `-` denotes ranges
- `data-trim` normalizes leading/trailing whitespace
- `data-noescape` prevents HTML entity escaping
- `data-fragment-index` controls fragment ordering when line highlighting steps

**Markdown** (id: `'markdown'`)
- Wraps `marked` library with `marked-smartypants` extension
- Two authoring modes:
  - Inline: `<section data-markdown>` with `<script type="text/template">` or `<textarea data-template>`
  - External: `<section data-markdown="file.md">` loads via XHR
- Attribute injection via HTML comments:
  - Element: `<!-- .element: class="fragment" data-fragment-index="1" -->`
  - Slide: `<!-- .slide: data-background="#ff0000" -->`
- Default separators: horizontal `\r?\n---\r?\n`, vertical `null`, notes `^\s*notes?:`

**Math** (wrapper, id depends on sub-plugin)
- Contains 4 sub-plugins: `KaTeX` (id: `'katex'`), `MathJax2` (id: `'mathjax2'`), `MathJax3` (id: `'mathjax3'`), `MathJax4` (id: `'mathjax4'`)
- MathJax2 is the default if `Math` is registered without specifying a sub-plugin
- Each sub-plugin loads its library from CDN by default but supports local paths

**Notes** (id: `'notes'`)
- Opens speaker view in a popup window (triggered by `S` key, keyCode 83)
- Communication via `postMessage` with same-origin check
- Notes sources (in priority order): fragment-level notes override slide-level
  - `<aside class="notes">` elements
  - `data-notes` attribute on `<section>`
- Heartbeat for orphaned window reconnection

**Search** (id: `'search'`)
- Registers `Ctrl+Shift+F` keyboard shortcut
- Uses Hilitor algorithm for DOM-based text highlighting
- Highlight colors cycle: `#ff6`, `#a0ffff`, `#9f9`, `#f99`, `#f6f`
- Methods: `open()`, `close()`, `toggle()`
- Input field appears top-right, Enter advances to next match

**Zoom** (id: `'zoom'`)
- Alt+Click to zoom (Ctrl+Click on Linux)
- Config: `zoomKey` (override modifier key), `zoomLevel` (default: 2)
- Escape to zoom out
- CSS transform-based with mouse-tracking pan support
- Methods: `zoom.to()`, `zoom.out()`, `zoom.magnify()`, `zoom.reset()`, `zoom.zoomLevel()`

### 8.2 Public API Reference

The complete public API as defined in `reveal.d.ts`:

#### Initialization and Lifecycle

| Method | Signature | Description |
|---|---|---|
| `initialize` | `(options?: RevealOptions) => Promise<RevealApi>` | Initialize the presentation. Returns a promise that resolves when ready. |
| `configure` | `(options: RevealOptions) => void` | Update configuration at runtime. Triggers re-layout. |
| `destroy` | `() => void` | Destroy the instance, remove event listeners, clean up DOM. |
| `sync` | `() => void` | Re-process all slides (content, backgrounds, fragments, notes). |
| `syncSlide` | `(slide?: HTMLElement) => void` | Re-process a single slide. |
| `syncFragments` | `(slide?: HTMLElement) => void` | Re-process fragments on a slide. |

#### Navigation

| Method | Signature | Description |
|---|---|---|
| `slide` | `(h: number, v?: number, f?: number) => void` | Navigate to specific slide by indices (horizontal, vertical, fragment). |
| `left` | `() => void` | Navigate left. |
| `right` | `() => void` | Navigate right. |
| `up` | `() => void` | Navigate up. |
| `down` | `() => void` | Navigate down. |
| `prev` | `() => void` | Navigate to previous slide. |
| `next` | `() => void` | Navigate to next slide. |
| `prevFragment` | `() => boolean` | Step to previous fragment. Returns false if no previous fragment. |
| `nextFragment` | `() => boolean` | Step to next fragment. Returns false if no next fragment. |
| `navigateFragment` | `(index: number) => void` | Navigate to a specific fragment index. |
| `shuffle` | `() => void` | Randomize slide order. |

#### State

| Method | Signature | Description |
|---|---|---|
| `getState` | `() => RevealState` | Get current presentation state (indices, paused, overview). |
| `setState` | `(state: RevealState) => void` | Restore a previously captured state. |
| `getProgress` | `() => number` | Get progress through presentation (0.0 to 1.0). |
| `getIndices` | `(slide?: HTMLElement) => { h: number, v: number, f: number }` | Get indices of current or specified slide. |
| `getScale` | `() => number` | Get current scale factor. |
| `getConfig` | `() => RevealOptions` | Get active configuration. |

#### Slide Access

| Method | Signature | Description |
|---|---|---|
| `getSlides` | `() => HTMLElement[]` | Get all slide elements as a flat array. |
| `getSlide` | `(x: number, y?: number) => HTMLElement` | Get slide element at given indices. |
| `getCurrentSlide` | `() => HTMLElement` | Get current slide element. |
| `getPreviousSlide` | `() => HTMLElement` | Get previous slide element. |
| `getTotalSlides` | `() => number` | Get total number of slides. |
| `getSlidePastCount` | `() => number` | Get number of slides before current. |
| `getSlideNotes` | `(slide?: HTMLElement) => string` | Get notes for current or specified slide. |
| `getSlideBackground` | `(x: number, y?: number) => HTMLElement` | Get background element for slide at given indices. |

#### Queries

| Method | Signature | Description |
|---|---|---|
| `isReady` | `() => boolean` | True after initialization completes. |
| `isPaused` | `() => boolean` | True if presentation is paused. |
| `isOverview` | `() => boolean` | True if overview mode is active. |
| `isFirstSlide` | `() => boolean` | True if on the first slide. |
| `isLastSlide` | `() => boolean` | True if on the last slide. |
| `availableRoutes` | `() => { left: boolean, right: boolean, up: boolean, down: boolean }` | Check which navigation directions are available. |
| `availableFragments` | `() => { prev: boolean, next: boolean }` | Check if previous/next fragments exist. |

#### Toggles

| Method | Signature | Description |
|---|---|---|
| `toggleHelp` | `(override?: boolean) => void` | Toggle or set help overlay visibility. |
| `toggleOverview` | `(override?: boolean) => void` | Toggle or set overview mode. |
| `togglePause` | `(override?: boolean) => void` | Toggle or set pause state. |
| `toggleAutoSlide` | `(override?: boolean) => void` | Toggle or set auto-slide. |
| `layout` | `() => void` | Force a layout recalculation. |

#### Plugins

| Method | Signature | Description |
|---|---|---|
| `registerPlugin` | `(plugin: RevealPlugin) => void` | Register a plugin at runtime. |
| `hasPlugin` | `(id: string) => boolean` | Check if a plugin is registered. |
| `getPlugin` | `(id: string) => RevealPlugin` | Get a registered plugin by ID. |
| `getPlugins` | `() => { [id: string]: RevealPlugin }` | Get all registered plugins. |

#### Keyboard

| Method | Signature | Description |
|---|---|---|
| `addKeyBinding` | `(binding: object, callback: function) => void` | Register a custom key binding. |
| `removeKeyBinding` | `(keyCode: number) => void` | Remove a key binding. |
| `triggerKey` | `(keyCode: number) => void` | Programmatically trigger a key press. |
| `registerKeyboardShortcut` | `(key: string, description: string) => void` | Register a shortcut for the help overlay. |

#### Events

| Method | Signature | Description |
|---|---|---|
| `on` | `(type: string, listener: function, options?: object) => void` | Add event listener. |
| `off` | `(type: string, listener: function, options?: object) => void` | Remove event listener. |
| `dispatchEvent` | `(event: Event) => void` | Dispatch a custom event. |

#### Preview

| Method | Signature | Description |
|---|---|---|
| `showPreview` | `(url: string) => void` | Show a URL in the preview overlay. |
| `hidePreview` | `() => void` | Hide the preview overlay. |

### 8.3 Data Attributes Reference

#### Slide-Level Attributes

| Attribute | Values | Description |
|---|---|---|
| `data-transition` | `none`, `fade`, `slide`, `convex`, `concave`, `zoom` | Per-slide transition override. Supports directional: `slide-in fade-out`. |
| `data-transition-speed` | `default`, `fast`, `slow` | Per-slide transition speed. |
| `data-autoslide` | integer (ms) | Per-slide auto-advance timing. |
| `data-notes` | string | Speaker notes for the slide. |
| `data-visibility` | `hidden`, `uncounted` | Hide slide from navigation (`hidden`) or exclude from count (`uncounted`). |
| `data-state` | string | Adds a class to the `<body>` when the slide is active. |
| `data-preload` | (presence) | Force preloading of slide content regardless of `viewDistance`. |

#### Background Attributes

| Attribute | Values | Description |
|---|---|---|
| `data-background` | color/URL/gradient | Shorthand for background (auto-detects type). |
| `data-background-image` | URL | Background image URL. |
| `data-background-video` | URL | Background video URL. |
| `data-background-iframe` | URL | Background iframe URL. |
| `data-background-color` | CSS color | Background solid color. |
| `data-background-gradient` | CSS gradient | Background gradient (e.g., `linear-gradient(...)`). |
| `data-background-size` | CSS size | Background image size (e.g., `cover`, `contain`). |
| `data-background-position` | CSS position | Background image position. |
| `data-background-repeat` | CSS repeat | Background image repeat mode. |
| `data-background-opacity` | 0.0-1.0 | Background opacity. |
| `data-background-transition` | transition name | Per-slide background transition. |
| `data-background-video-loop` | (presence) | Loop background video. |
| `data-background-video-muted` | (presence) | Mute background video. |
| `data-background-interactive` | (presence) | Allow interaction with background iframe. |

#### Auto-Animate Attributes

| Attribute | Values | Description |
|---|---|---|
| `data-auto-animate` | (presence) | Enable auto-animate for this slide. |
| `data-auto-animate-id` | string | Group auto-animate slides by ID (allows non-consecutive matching). |
| `data-auto-animate-restart` | (presence) | Start a new auto-animate sequence (no transition from previous). |
| `data-auto-animate-easing` | CSS easing | Per-slide easing override. |
| `data-auto-animate-duration` | number (seconds) | Per-slide duration override. |
| `data-auto-animate-delay` | number (seconds) | Per-element delay. |
| `data-auto-animate-unmatched` | `fade`, `none` | Behavior for unmatched elements. |
| `data-id` | string | Explicit element matching ID for auto-animate. |

#### Fragment Animations

Applied as CSS classes on elements with `class="fragment"`:

| Animation Class | Effect |
|---|---|
| `fade-out` | Fades element out |
| `fade-up` | Slides up while fading in |
| `fade-down` | Slides down while fading in |
| `fade-left` | Slides left while fading in |
| `fade-right` | Slides right while fading in |
| `fade-in-then-out` | Fades in, then fades out on next step |
| `fade-in-then-semi-out` | Fades in, then semi-transparent on next step |
| `grow` | Scales up from smaller |
| `shrink` | Scales down from larger |
| `strike` | Applies strikethrough |
| `highlight-red` | Highlights text red |
| `highlight-blue` | Highlights text blue |
| `highlight-green` | Highlights text green |
| `highlight-current-red` | Highlights red only while current |
| `highlight-current-blue` | Highlights blue only while current |
| `highlight-current-green` | Highlights green only while current |

Custom animation classes are also supported -- any string value works and is applied as a CSS class.

> [!TIP]
> Fragment order is controlled by `data-fragment-index`. Multiple elements can share the same index to animate simultaneously. When `sortFragmentsOnSync` is `true` (default), fragments are automatically sorted by their index.

### 8.4 Error Messages

| Error | Source | Cause |
|---|---|---|
| `"Unable to find presentation root (<div class="reveal">)"` | `js/index.ts:44` | `Reveal.initialize()` called without a `.reveal` element in the DOM (or the specified root element). |
| `"Please specify a valid media type to preview"` | `js/controllers/overlay.js:189` | `showPreview()` called with a URL whose MIME type cannot be determined as image, video, or iframe. |

### 8.5 Test Infrastructure

| Framework | Scope | Location |
|---|---|---|
| QUnit + Puppeteer | Core library integration tests | `test/` directory |
| Vitest + Testing Library + jsdom | React wrapper unit tests | `react/` directory |
| TypeScript type tests | Type declaration correctness | `test/types/plugin-imports.ts` |

Test HTML pages in `test/` cover: auto-animate, dependencies, grid navigation, iframes, markdown, mathjax4, multiple instances, PDF, plugins, scroll, and state.

Run tests:
```bash
npm test            # Core: QUnit in headless Puppeteer
npm run react:test  # React wrapper: Vitest
```

---

## 9. Integration Points

### 9.1 React Wrapper (@revealjs/react v0.2.1)

The React wrapper lives in the `react/` subdirectory and provides a declarative component API for building presentations in React.

#### Components

**`Deck`** -- The root component that wraps a reveal.js instance.

| Prop | Type | Description |
|---|---|---|
| `config` | `RevealOptions` | reveal.js configuration object |
| `plugins` | `RevealPlugin[]` | Array of plugins to register |
| `onReady` | `(event) => void` | Fires when presentation is initialized |
| `onSlideChange` | `(event) => void` | Fires on slide navigation |
| `onFragmentShown` | `(event) => void` | Fires when a fragment becomes visible |
| `onFragmentHidden` | `(event) => void` | Fires when a fragment is hidden |
| `deckRef` | `React.Ref` | Ref to the underlying Reveal instance |

**`Slide`** -- Maps to a `<section>` element with shorthand props for all `data-*` attributes.

| Prop | Type | Maps To |
|---|---|---|
| `background` | `string` | `data-background` |
| `autoAnimate` | `boolean` | `data-auto-animate` |
| `transition` | `string` | `data-transition` |
| `notes` | `string` | `data-notes` |
| `visibility` | `string` | `data-visibility` |
| `state` | `string` | `data-state` |

**`Stack`** -- Wraps vertical slides in a parent `<section>`.

**`Markdown`** -- Renders markdown content as slides.

| Prop | Type | Description |
|---|---|---|
| `children` | `string` | Inline markdown content |
| `src` | `string` | External markdown file URL |
| `separator` | `string` | Horizontal slide separator |
| `verticalSeparator` | `string` | Vertical slide separator |
| `notesSeparator` | `string` | Notes separator |
| `options` | `object` | Passed to `marked` library |

**`Fragment`** -- Wraps elements in fragment animations.

| Prop | Type | Description |
|---|---|---|
| `animation` | `string` | Fragment animation class (e.g., `'fade-up'`) |
| `index` | `number` | Fragment ordering index |
| `as` | `string \| Component` | Render as a different element type |
| `asChild` | `boolean` | Polymorphic rendering (passes props to child) |

**`Code`** -- Renders syntax-highlighted code blocks.

| Prop | Type | Description |
|---|---|---|
| `language` | `string` | highlight.js language identifier |
| `lineNumbers` | `string` | Line number / step-through spec |
| `startFrom` | `number` | Starting line number |
| `trim` | `boolean` | Enable whitespace trimming |
| `noEscape` | `boolean` | Disable HTML escaping |

#### Hooks and Context

- **`useReveal()`** -- Hook that returns the `RevealApi` instance from `RevealContext`. Must be called within a `Deck` component tree.
- **`RevealContext`** -- React context providing the `RevealApi` instance.

> [!NOTE]
> GitHub issue #3883 documents that React Markdown fragment directives are broken with Vite 8. Verify fragment behavior when using the Markdown component with the current build toolchain.

### 9.2 PostMessage API

When `postMessage: true`, reveal.js accepts commands from parent/child windows:

```javascript
// From parent window controlling an embedded presentation
iframe.contentWindow.postMessage(JSON.stringify({
  method: 'slide',
  args: [2, 0]  // Navigate to slide 2
}), '*');
```

When `postMessageEvents: true`, all reveal.js events are forwarded as `postMessage` to the parent window, enabling external monitoring and synchronization.

### 9.3 Multiplexing and Synchronization

State synchronization between presenter and audience can be built using:

1. **`getState()` / `setState()`** -- serialize and restore complete presentation state
2. **`postMessageEvents: true`** -- forward all events to a parent frame or relay server
3. **`slidechanged` event** -- includes `indexh`, `indexv` for remote navigation commands

### 9.4 Embedding

Set `embedded: true` for presentations embedded within larger pages:

- Prevents capturing global keyboard events
- Adjusts sizing behavior to fit within a container rather than the viewport
- Multiple instances can coexist with independent navigation

### 9.5 Known Issues and Compatibility

Active GitHub issues relevant to administrators and developers:

| Issue | Summary |
|---|---|
| #3888 | Decktape PDF export error |
| #3883 | React Markdown fragment directives broken with Vite 8 |
| #3821 | npm install error |
| #3895 | v6 Dracula theme background color incorrect |
| #3839 | Whitespace normalization corruption |
| #3894 | v6 not available on CDN |
| #3749 | Incorrect TypeScript typing for keyboard config |
| #3860 | PDF generates very large files |
| #3884 | Fade-out / fade-in-then-out causes blank slides |
| #3878 | Vertical background offset incorrect |
| #3869 | `.background` CSS not scoped to reveal.js (leaks to page) |

> [!CAUTION]
> Issue #3869 reports that `.background` CSS selectors in reveal.js themes are not scoped and can leak styles to the host page. When embedding reveal.js in a larger application, use CSS containment or shadow DOM to prevent style conflicts.

### 9.6 Troubleshooting

#### Presentation Root Not Found

**Error:** `"Unable to find presentation root (<div class="reveal">)"`

**Cause:** `Reveal.initialize()` runs before the `.reveal` div exists in the DOM, or the element is missing entirely.

**Solution:** Ensure the HTML contains `<div class="reveal"><div class="slides">...</div></div>` and that initialization runs after DOM ready (e.g., in a `DOMContentLoaded` handler or at the end of `<body>`).

**Prevention:** Always verify DOM structure before calling `initialize()`.

#### Plugins Not Loading

**Error:** `ready` event never fires; presentation appears stuck.

**Cause:** A plugin's `init()` method returned a Promise that never resolves or threw an error.

**Solution:** Check browser console for plugin initialization errors. Test with `plugins: []` to confirm the issue is plugin-related. Add each plugin back one at a time to identify the failing one.

**Prevention:** Always handle errors in async plugin `init()` methods. Test plugin initialization in isolation.

#### PDF Export Issues

**Error:** Blank pages, missing content, or excessively large file size in PDF export.

**Cause:** Fragment-based pagination (`pdfSeparateFragments: true`) generates a page per fragment step. Complex backgrounds and media increase file size.

**Solution:** Set `pdfSeparateFragments: false` to reduce page count. Use `pdfMaxPagesPerSlide` to cap pages per slide. Remove or simplify heavy backgrounds before export.

**Prevention:** Test PDF export early in presentation development. Use `?print-pdf` in the URL to preview print layout.

#### npm Install Failures

**Error:** Various errors during `npm install` (see issue #3821).

**Cause:** Node.js version below the required minimum (20.19.0) or platform-specific dependency issues.

**Solution:** Verify Node.js version with `node --version`. Ensure it meets the `>= 20.19.0` requirement in `package.json engines`.

**Prevention:** Use `nvm` or `fnm` to manage Node.js versions and pin to a compatible version.

---

_Generated May 2026 from hakimel/reveal.js at 76dc900. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
