# Presentator: Specification

AI-generated, human-reviewed. Zero dependencies. Every line audited.
Copyright (c) 2026 devsimsek. MIT License.

## Architecture

```
deck.json  --+                              +--> layouts.js   (12 layout renderers)
              +--> build.js --> index.html --+--> theme.css    (styles, 17 themes)
deck.md    --+                              +--> runtime.js   (nav, canvas, cursor)
                                            +--> animate.js   (WAAPI micro-library)
                                            +--> md-parser.js (markdown -> JSON)
```

Everything is inlined into a single `index.html`. The build uses only Node.js `fs` and `path`. The browser runtime uses only the Web Animations API (native). KaTeX, Mermaid, and Google Fonts are loaded from CDN only when used.

## CLI

```bash
presentator <deck.json|deck.md> [output.html]
presentator --help
presentator --spec          # outputs this SPEC.md
presentator --install-skill # copies agent skill to ~/.agents/skills/
```

## Deck Config (top-level JSON)

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `title` | string | `"Presentation"` | HTML `<title>` |
| `accentColor` | string | `"#002FA7"` | Hex accent colour |
| `background` | string | `"particles"` | Per-slide default: `particles`, `waves`, `both`, `grid`, `dots`, `lines`, `diagonal`, `none` |
| `cursor` | string | `"none"` | Cursor effect: `glow`, `trail`, `both`, `none` |
| `transition` | string | `"slide"` | Between-slide: `slide`, `fade`, `none` |
| `autoAdvance` | number | `0` | Auto-advance ms. 0 = disabled |
| `themeToggle` | boolean | `true` | Show ◐ button |
| `fontSans` | string | `"Inter"` | Sans-serif font |
| `fontMono` | string | `"Space Mono"` | Monospace font |
| `waveOpacity` | number | `0.7` | Canvas wave opacity |
| `notesFile` | string | — | Path to JSON notes file |
| `shortcuts` | object | — | Key remapping (see below) |
| `branding` | object | — | `{ logo, footer }` applied to all slides |
| `confidentiality` | string | — | Badge shown on all slides |
| `math` | boolean | auto | Force KaTeX CDN |
| `mermaid` | boolean | auto | Force Mermaid CDN |
| `slides` | Slide[] | **required** | Slide definitions |

### Shortcuts object

```json
{
  "shortcuts": {
    "next": "ArrowRight,PageDown,Space",
    "prev": "ArrowLeft,PageUp",
    "present": "f",
    "presenter": "p",
    "lowPower": "b",
    "overview": "Escape",
    "theme": "t",
    "home": "Home",
    "end": "End"
  }
}
```

Keys are comma-separated. Single letters are case-insensitive, no modifiers.

### Branding object

```json
{
  "branding": {
    "logo": "img/company.png",
    "footer": "Company Name © 2026"
  }
}
```

Logo renders top-right (max 3.2vh height). Footer renders centered at bottom.

### Confidentiality

```json
{ "confidentiality": "INTERNAL USE ONLY" }
```

Shows a bordered badge top-left on every slide. Good practice: `PUBLIC`, `INTERNAL`, `CONFIDENTIAL`, `RESTRICTED`, `SECRET`.

## Slide Spec (per-slide JSON)

### Common fields (all layouts)

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `id` | string | no | — | Slide identifier |
| `layout` | string | **yes** | — | One of 12 layout types |
| `theme` | string | no | `"light"` | One of 17 theme presets |
| `eyebrow` | string | no | — | Top-bar eyebrow text |
| `kicker` | string | no | — | Mono-spaced label |
| `titleMain` | string | no | — | Heading (main part) |
| `titleItalic` | string | no | — | Heading (italic suffix) |
| `subtitle` | string | no | — | Deck-line text |
| `animate` | string | no | auto | Animation recipe override |
| `notes` | string | no | — | Speaker notes |
| `navTitle` | string | no | — | Short label for dot nav |
| `image` | string | no | — | Image src (data URI or `img/` path) |
| `imageAlt` | string | no | — | Image alt text |
| `mermaid` | string | no | — | Mermaid diagram code (frontmatter only) |
| `blocks` | Block[] | no | — | Rich content blocks |
| `background` | string | no | `"none"` | Per-slide background: `particles`, `waves`, `both`, `grid`, `dots`, `lines`, `diagonal`, `none` |

### Layout-specific fields

#### title (animation: hero)
No additional required fields.

#### split (animation: split-statement)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `left` | Half | **yes** | Left panel |
| `right` | Half | **yes** | Right panel |

**Half object:** `type` (`"accent"` or omitted), `items` (Card[]), `kicker`, `lines` (string[]), `body`, `eyebrow`, `pageLabel`.

#### metric-grid (animation: grid-reveal)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `metrics` | Metric[] | **yes** | 1-4 KPI cards (`{ value, label }`) |
| `insight` | string | no | Key-insight callout |

#### eda-grid (animation: grid-reveal)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `image` | string | no | Figure image |
| `sidebar` | Card[] | no | Sidebar callouts |
| `columns` | string | no | CSS grid columns (default: `"7fr 5fr"`) |

#### card-row (animation: grid-reveal)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cards` | Card[] | no | 1-4 insight cards |

#### quote (animation: fade-in)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `quoteText` | string | no | The quote |
| `attribution` | string | no | Who said it |
| `role` | string | no | Their role |

#### code (animation: slide-up)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `code` | string | no | Code content |
| `codeLang` | string | no | Language for syntax class |
| `filename` | string | no | Shown in chrome |
| `caption` | string | no | Below the code block |

**Important:** The `code` layout renders `spec.code` directly in `<pre class="code-block">`. `bm()` automatically filters `code` blocks from `spec.blocks` when `spec.code` is present — do NOT duplicate.

#### image-full (animation: scale-in)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `fit` | string | no | CSS object-fit: `cover`, `contain`, `fill` |
| `bg` | string | no | Background colour |
| `overlay` | string | no | CSS background for overlay |

#### columns (animation: stagger)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | Card[] | no | Card items |
| `columns` | number | no | Column count (default: items.length) |
| `footer` | string | no | Footer text |

#### agenda (animation: grid-reveal)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | Card[] | no | Agenda items |

#### timeline (animation: stagger)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` / `milestones` | Card[] | no | Timeline items (`date`, `title`, `body`) |

**Card object:** `tag`, `date`, `icon`, `image`, `title`, `body`.

#### video (animation: scale-in)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `video` | string | no | YouTube/Vimeo embed URL |
| `overlay` | string | no | CSS overlay |
| `autoplay` | boolean | no | Auto-play on slide enter (muted) |
| `mute` | boolean | no | Mute video (default: true for autoplay) |

## Blocks system

Any slide can have a `blocks` array:

| Type | Description | Key field |
|------|-------------|-----------|
| `text` | Markdown-formatted text (bold, italic, code, links) | `value` |
| `math` | LaTeX display or inline math | `value`, `display` |
| `diagram` | Mermaid diagram | `value` |
| `code` | Syntax-highlighted code block | `value`, `lang` |
| `video` | Embedded iframe | `value` (URL) |
| `nn` | Neural network visualization | `layers` (array of node counts) |

**Important:** `bm()` automatically filters `diagram` blocks when `spec.mermaid` is set, and `code` blocks when `spec.code` is set. Do NOT manually duplicate between `spec.mermaid`/`spec.code` and blocks.

## Backgrounds (per slide)

| Value | Type | Description |
|-------|------|-------------|
| `particles` | Canvas | Floating dot network with connecting lines |
| `waves` | Canvas | Flowing sine wave curves |
| `both` | Canvas | Particles + waves combined |
| `grid` | Canvas | Oscillating dot grid |
| `dots` | CSS | Static dot pattern |
| `lines` | CSS | Static grid lines |
| `diagonal` | CSS | Static diagonal stripes |
| `none` | — | No background |

Canvas backgrounds use `mix-blend-mode: multiply` on light slides and `difference` on dark slides. Opacity controlled by `waveOpacity` config. Disabled in low-power mode and fullscreen present mode.

The `bm()` function (`src/layouts.js`) renders blocks into each layout's body. It filters diagram blocks when `spec.mermaid` is set and code blocks when `spec.code` is set to prevent duplication.

## Themes (17 presets)

| Theme | Background | Text | Accent | Font |
|-------|-----------|------|--------|------|
| `light` | `#faf9f7` | `#111214` | `#002FA7` | Inter 200 |
| `dark` | `#111214` | `#faf9f7` | `#002FA7` | Inter 200 |
| `accent` | Accent | White | Accent | Inter 200 |
| `slate` | `#1e222a` | `#f2f2f0` | `#002FA7` | Inter 200 |
| `warm` | `#fdfaf5` | `#1a1614` | `#c44536` | Inter 300 |
| `cool` | `#f8fafc` | `#0f172a` | `#2563eb` | Inter 200 |
| `mono` | `#fff` | `#000` | `#000` | Inter 700 bold |
| `nature` | `#f9fbf8` | `#111c12` | `#166534` | Inter 200 |
| `sunset` | `#fffaf5` | `#1a120b` | `#e85d04` | Inter 200 |
| `ocean` | `#f5fafa` | `#0a1628` | `#0a9396` | Inter 200 |
| `forest` | `#f8faf8` | `#0d1f0d` | `#2d6a4f` | Inter 200 |
| `blush` | `#fffafa` | `#1a0a0f` | `#c9184a` | Inter 200 |
| `midnight` | `#090b1a` | `#e2e8f0` | `#6366f1` | Inter 200 |
| `sepia` | `#fdf6e8` | `#2d1b0a` | `#92400e` | Georgia serif 400 |
| `neon` | `#0a0a0a` | `#e0e0e0` | `#22d3ee` | Inter + Courier New mono |
| `amber` | `#fffbeb` | `#1c1606` | `#d97706` | Inter 300 |
| `frost` | `#f0f9ff` | `#082f49` | `#0284c7` | Inter 100 |

### Theme CSS variables

Each theme can override:
- `--accent`, `--accent-bright`, `--accent-rgb` — accent colors
- `--paper`, `--ink` — background and text
- `--text-primary`, `--text-secondary`, `--text-helper` — text opacity hierarchy
- `--border-subtle` — border color
- `--sans`, `--mono` — font families
- `--fw-heading`, `--fw-heading-italic`, `--fw-subheading`, `--fw-card-title` — font weights
- `--canvas-opacity` — wave canvas opacity (unused: now inline)

Dark themes (`dark`, `accent`, `slate`, `midnight`, `neon`) set `--text-primary: var(--paper)` and invert text-secondary/helper to light values.

## Animations (8 recipes)

| Recipe | Default layouts | Effect |
|--------|----------------|--------|
| `hero` | `title` | Chrome fades, kicker drops, title rises, subtitle follows |
| `grid-reveal` | `metric-grid`, `eda-grid`, `card-row`, `agenda` | Cards pop sequentially, figures fade |
| `split-statement` | `split` | Panels slide from edges, items cascade |
| `fade-in` | `quote` | Clean fade for all elements |
| `slide-up` | `code` | Chrome fades, content slides from below |
| `scale-in` | `image-full`, `video` | Content scales from 0.92 with fade |
| `stagger` | `columns`, `timeline` | Cards stagger with 80ms delays |
| `none` | (any) | Instant, no animation |

Overridable per-slide via `animate` field. Low-power mode (`B` key) disables all.

### Element animations (animate.js)

`animate(el).fadeIn().slideUp().pop().scaleIn().pulse().spin().glow().stagger().clear()`

- `pulse(opts)` — scale oscillation, configurable scale/opacity/duration/iterations
- `spin(opts)` — continuous rotation, configurable degrees/duration/iterations
- `glow(opts)` — box-shadow pulse, configurable radius/color/duration/iterations
- `stagger(opts)` — alias for fadeIn with automatic `each` delay

All use Web Animations API. No CSS dependencies.

## Markdown input format

### Deck frontmatter
```markdown
---
title: My Deck
accentColor: "#002FA7"
background: both
cursor: both
---
```

### Slide structure
```markdown
---
layout: title
theme: accent
id: cover
animate: hero
notes: Speaker notes here
---
# Main Heading *Italic suffix*

Subtitle text. With $inline$ math.
```

### Markdown parsing rules

1. **Headings** (`#` or `##`) become `titleMain`/`titleItalic`. Split on `*italic*` text.
2. **Tables** become `metrics` for `metric-grid`. Must have `|` delimiters.
3. **Lists** (`- **Title** Body`) become cards (card-row), items (columns/agenda), milestones (timeline), or left items (split).
4. **Fenced code blocks** — ` ```mermaid ` sets `spec.mermaid` + diagram block. Other languages set `spec.code` + code block.
5. **Nested YAML** — indented lists under keys like `sidebar:` are parsed into arrays of objects.
6. **Blank lines** between frontmatter and `---` delimiter are handled.
7. **`animate`**, `background`, `theme`, and all slide-level fields pass through from frontmatter.

### Nested YAML frontmatter

```markdown
---
layout: eda-grid
sidebar:
  - tag: "Category"
    title: "Item Title"
    body: "Item description."
---
```

Parsed into `spec.sidebar = [{ tag: "...", title: "...", body: "..." }]`. Indentation must be consistent (2+ spaces).

## Mermaid diagrams

### Mermaid code blocks in markdown body
```markdown
```mermaid
graph TD
    A --> B
```
```
Sets `spec.mermaid` AND adds a `diagram` block. `bm()` automatically filters diagram blocks when `spec.mermaid` is set — no duplication.

### Mermaid in frontmatter or JSON
```json
{ "mermaid": "graph TD\n    A --> B" }
```
Rendered by layout-specific `renderMermaid()` calls (title, eda-grid).

### Mermaid rendering
- Uses `mermaid.initialize({ theme: 'default', securityLevel: 'loose' })`
- `%%{init}%%` directives are supported. On failure, `data-processed` is removed so the diagram retries on next `playSlide()`.
- Only injected when deck contains mermaid content (or `"mermaid": true` in config).

## KaTeX math

- Inline: `$E=mc^2$` anywhere in text
- Display blocks: `$$x^2 + y^2 = z^2$$` or `$$$...$$$` blocks
- Block type: `{ "type": "math", "value": "...", "display": true }`
- Auto-rendered via `renderMathInElement()` with delimiters `$$` and `$`
- Only injected when deck contains math (or `"math": true` in config)
- `throwOnError: false` — broken expressions render as raw text

## Video embeds

### Video layout
```json
{ "layout": "video", "video": "https://www.youtube.com/embed/...", "overlay": "rgba(0,0,0,0.35)", "autoplay": true }
```

### Video blocks
```json
{ "type": "video", "value": "https://..." }
```

YouTube embed URLs auto-add `enablejsapi=1` for postMessage control. Play button overlay appears over the video. Click overlay or press Space on video slides to play. Autoplay auto-adds `mute=1` (browsers require mute for autoplay).

## Neural network visualization

```json
{ "type": "nn", "layers": [4, 8, 8, 6, 3] }
```

Renders a column layout of SVG-connected dots. Active layer pulses blue with glow. Each cycle takes ~1.5 seconds (25 frames × 5 layers). Uses requestAnimationFrame with CSS transitions.

## Keyboard shortcuts

| Key | Action |
|-----|--------|
| `→` / `Space` / `PageDown` | Next slide (or next build step) |
| `←` / `PageUp` | Previous slide |
| `F` | Fullscreen present mode |
| `P` | Pop-out presenter window |
| `T` | Toggle dark/light theme |
| `B` | Low-power mode (disable animations + canvas) |
| `Esc` | Exit present / close presenter / grid overview |
| `?` | Keyboard shortcuts overlay |
| `Home` / `End` | Jump to first/last |
| Click (slide) | Next slide (skips nav, buttons, links, inputs) |
| Click (video overlay) | Play video |

All keys remappable via `shortcuts` config.

## Presenter window

Press `P`. Opens a separate window with:
- Current slide title and number
- Speaker notes (from `notes` field or external `notesFile`)
- Next slide preview
- Elapsed timer with reset button
- Prev/Next navigation buttons
- Keyboard shortcuts: arrows, Space, Home, End. Esc closes.
- Automatic timer reset button
- Polls main deck every 300ms via `window.opener`

## Build step (click-to-reveal)

Tag elements with `data-step="1"`, `data-step="2"`, etc. Elements are hidden initially and revealed one at a time on click/arrow. Progress advances within the slide before moving to the next.

## Canvas wave background (per slide)

Added by `slideWrap()` when `spec.background` is `particles`, `waves`, `both`, or `grid`. A `<canvas class="slide-wave">` is placed before `.canvas-card`. The runtime's `initSlideWave()` creates particles and `drawSlideWave()` renders waves/particles/grid via requestAnimationFrame. The loop restarts on each slide change. Disabled in low-power mode and fullscreen present mode.

## Cursor effects

Configurable via `cursor` deck config:
- `glow` — 120px radial gradient follows mouse
- `trail` — fading accent-colored dots behind cursor
- `both` — glow + trail
- `none` — standard system cursor

Custom cursor: white 8px dot + 28px ring with `mix-blend-mode: difference` for universal visibility. Clicking spawns expanding accent rings. System cursor hidden via `body.cursor-active * { cursor: none }`.

## Print / PDF export

`@media print` styles:
- Backgrounds, nav, counter, buttons, overlays hidden
- Slides stack vertically, each `page-break-after: always`
- Dark/accent slides preserve colors (`print-color-adjust: exact`)
- Animations disabled

## Adding a new layout

1. `src/layouts.js`: Add function returning `slideWrap(spec, inner)`. Tag elements with `data-anim="key"`. Call `bm(spec)` for blocks.
2. `src/deck-runtime.js`: Add `RECIPES` entry calling `fade()`, `fadeX()`, `pop()`, `scaleIn()`.
3. Register in `LAYOUTS` map and `DEFAULT_ANIMATE`.
4. `schema.json`: Add to layout enum and `allOf` conditions.
5. `SPEC.md`: Document the layout.

## Adding a new theme

1. `src/theme.css`: Add `.slide.mytheme{...}` with `--accent`, `--ink`, `--paper`, `--text-*` variables.
2. `schema.json`: Add to theme enum.
3. `src/deck-runtime.js`: Add to `dark-bg` check array if dark theme. Add to `origTheme` array for theme toggle.
4. `SPEC.md`: Document in themes table.

## File structure

```
build.js              # CLI builder (presentator)
animate.js            # WAAPI animation micro-library
skill-install.js      # Install agent skill to ~/.agents/skills/
schema.json           # JSON Schema draft-07
SPEC.md               # This document
deck.example.json     # 13-slide JSON demo
deck.example.md       # 8-slide Markdown demo
deck.anim-demo.json   # 14-slide animation showcase
package.json          # Zero deps, MIT license
.agents/skills/       # Agent skill definition
src/
  layouts.js          # 12 layout renderers + bm()
  theme.css           # 17 themes, canvas, cursor, print
  deck-runtime.js     # Nav, canvas, cursor, NN, presenter
  animate.js          # pulse/spin/glow/stagger helpers
  md-parser.js        # Markdown + nested YAML parser
```

## Known patterns (for agents)

### Avoid double-rendering
- `bm()` filters diagram blocks when `spec.mermaid` is set. Same for code blocks when `spec.code` is set.
- Layouts that render `spec.mermaid` directly (eda-grid, title) should NOT also render mermaid blocks.
- Subtitle text extracted from blocks is removed from the source block to prevent duplication.

### Mermaid rendering
- `mermaid.run()` marks nodes `data-processed="true"` before rendering. On failure, remove the attribute so retry works.
- Always add `.catch()` handler that cleans up `data-processed`.
- `%%{init}%%` directives work but may fail on first render due to async config loading. The retry mechanism handles this.

### Canvas wave lifecycle
- `initSlideWave()` cancels old RAF, creates particles, and restarts the loop.
- The loop dies on slides without waves. Re-init on every `go()` call.
- `drawSlideWave()` only runs when `waveCtx` and `waveEl` are valid.
- Particles are re-spawned on every slide change (not carried over).

### Theme toggle
- Stores original theme in `data-orig-theme` on first toggle.
- Only swaps `light` ↔ `dark`. Custom themes (warm, midnight, etc.) are preserved.
- Overview rebuilds on toggle to reflect changes.
- The `dark-bg` body class must include all dark theme names.

### Markdown parser edge cases
- Tables can appear anywhere in text blocks, not just at the start.
- Headings are stripped from text blocks BEFORE `escMd()` conversion.
- Nested YAML frontmatter requires consistent indentation.
- Blank lines between frontmatter and closing `---` are handled.
- List items with bold titles become card/item objects.
- Timeline items get `date` from list title and `title` from bold text in body.

### Versioning
- `package.json` version is the single source of truth.
- CLI reads version dynamically: `const pkg = require('./package.json')`.
- Landing page version badge must be updated manually on version bump.
- All dist files must be rebuilt after version changes.

## Complete Examples

### Minimal JSON deck
```json
{
  "title": "Hello World",
  "slides": [
    { "layout": "title", "theme": "accent", "titleMain": "Hello", "titleItalic": "World" }
  ]
}
```

### Full JSON deck (all config fields)
```json
{
  "title": "My Presentation",
  "accentColor": "#002FA7",
  "background": "both",
  "cursor": "both",
  "transition": "slide",
  "autoAdvance": 0,
  "themeToggle": true,
  "fontSans": "Inter",
  "fontMono": "Space Mono",
  "waveOpacity": 0.7,
  "notesFile": "notes.json",
  "branding": { "logo": "img/logo.png", "footer": "Company © 2026" },
  "confidentiality": "INTERNAL USE ONLY",
  "shortcuts": { "next": "ArrowRight,Space" },
  "slides": [
    {
      "id": "cover",
      "layout": "title",
      "theme": "accent",
      "eyebrow": "QUARTERLY REVIEW",
      "kicker": "Q4 2024",
      "titleMain": "Results",
      "titleItalic": "& Outlook",
      "subtitle": "A comprehensive review of Q4 performance and roadmap.",
      "animate": "hero",
      "notes": "Opening slide — set the tone.",
      "navTitle": "Cover",
      "background": "both"
    },
    {
      "id": "agenda",
      "layout": "agenda",
      "theme": "light",
      "kicker": "WHAT WE COVER",
      "titleMain": "Agenda",
      "items": [
        { "title": "Performance", "body": "Key metrics and growth" },
        { "title": "Product", "body": "Features shipped in Q4" },
        { "title": "Roadmap", "body": "Q1 priorities and goals" }
      ]
    },
    {
      "id": "metrics",
      "layout": "metric-grid",
      "theme": "light",
      "eyebrow": "BY THE NUMBERS",
      "kicker": "KEY METRICS",
      "titleMain": "Q4",
      "titleItalic": "Performance",
      "metrics": [
        { "value": "2.4M", "label": "Active Users" },
        { "value": "99.9%", "label": "Uptime" },
        { "value": "47%", "label": "Revenue Growth" },
        { "value": "12ms", "label": "p99 Latency" }
      ],
      "insight": "Revenue growth accelerated by 12% QoQ, driven by enterprise adoption.",
      "image": "img/chart.png",
      "imageAlt": "Revenue growth chart"
    },
    {
      "id": "eda",
      "layout": "eda-grid",
      "theme": "light",
      "eyebrow": "DEEP DIVE",
      "kicker": "USER SEGMENTS",
      "titleMain": "Enterprise",
      "titleItalic": "Adoption",
      "image": "img/segments.png",
      "imageAlt": "User segment breakdown",
      "sidebar": [
        { "tag": "ENTERPRISE", "title": "47% Growth", "body": "12 new Fortune 500 logos." },
        { "tag": "SMB", "title": "23% Growth", "body": "Self-serve onboarding driving adoption." }
      ],
      "mermaid": "graph LR\n  A[Acquisition] --> B[Onboarding]\n  B --> C[Activation]\n  C --> D[Retention]"
    },
    {
      "id": "quote-slide",
      "layout": "quote",
      "theme": "dark",
      "quoteText": "The best teams ship fast, learn faster, and never stop improving.",
      "attribution": "Engineering Lead",
      "role": "Q4 Retrospective",
      "subtitle": "We shipped 14 features, fixed 127 bugs, and reduced build time by 60%.",
      "animate": "fade-in",
      "background": "particles"
    },
    {
      "id": "code-example",
      "layout": "code",
      "theme": "light",
      "eyebrow": "TECHNICAL",
      "kicker": "API EXAMPLE",
      "titleMain": "Integration",
      "titleItalic": "Code",
      "code": "const api = new PresentatorAPI({\n  endpoint: 'https://api.example.com',\n  version: 'v2'\n});\n\nawait api.deploy('production');",
      "codeLang": "javascript",
      "filename": "deploy.js",
      "caption": "One-line deploy with the Presentator API v2."
    },
    {
      "id": "video-demo",
      "layout": "video",
      "video": "https://www.youtube.com/embed/mIPaso8xGh8",
      "overlay": "rgba(0,0,0,0.35)",
      "autoplay": false,
      "titleMain": "See It",
      "titleItalic": "In Action"
    },
    {
      "id": "timeline-example",
      "layout": "timeline",
      "theme": "light",
      "eyebrow": "ROADMAP",
      "kicker": "Q1 2025",
      "titleMain": "Product",
      "titleItalic": "Timeline",
      "items": [
        { "date": "Jan", "title": "Beta Launch", "body": "Invite-only beta with 50 customers." },
        { "date": "Feb", "title": "GA Release", "body": "Public launch with full feature set." },
        { "date": "Mar", "title": "Enterprise Tier", "body": "SSO, audit logs, SLA guarantees." },
        { "date": "Apr", "title": "International", "body": "EU data residency, 8 language support." }
      ]
    },
    {
      "id": "columns-example",
      "layout": "columns",
      "theme": "cool",
      "eyebrow": "COMPETITIVE",
      "kicker": "LANDSCAPE",
      "titleMain": "Why",
      "titleItalic": "Us",
      "columns": 3,
      "items": [
        { "icon": "🚀", "title": "Speed", "body": "12ms p99 latency. 10x faster than competitors." },
        { "icon": "🔒", "title": "Security", "body": "SOC2 Type II, GDPR, CCPA compliant." },
        { "icon": "💰", "title": "Pricing", "body": "Transparent. No per-seat fees. Predictable." }
      ],
      "footer": "Trusted by 500+ companies worldwide."
    },
    {
      "id": "neural-demo",
      "layout": "title",
      "theme": "dark",
      "eyebrow": "VISUALIZATION",
      "kicker": "MACHINE LEARNING",
      "titleMain": "Model",
      "titleItalic": "Architecture",
      "subtitle": "Forward propagation across 5 layers. Active layer glows blue.",
      "blocks": [
        { "type": "nn", "layers": [4, 8, 8, 4, 2] },
        { "type": "text", "value": "Input → Hidden 1 → Hidden 2 → Hidden 3 → Output" }
      ],
      "background": "particles",
      "animate": "fade-in"
    },
    {
      "id": "closing",
      "layout": "split",
      "theme": "dark",
      "left": {
        "items": [
          { "title": "Record Growth", "body": "47% revenue increase, 2.4M active users." },
          { "title": "Team Expansion", "body": "Hired 24 engineers across 3 offices." },
          { "title": "Zero Downtime", "body": "99.9% uptime. Zero P0 incidents in Q4." }
        ]
      },
      "right": {
        "type": "accent",
        "kicker": "Q4 2024 → Q1 2025",
        "lines": ["Build.", "Ship.", "Repeat."],
        "body": "Thank you. Questions?"
      },
      "notes": "Closing slide — thank the team and open for Q&A."
    }
  ]
}
```

### Minimal Markdown deck
```markdown
---
title: Hello World
---
---
layout: title
theme: accent
---
# Hello *World*
```

### Full Markdown deck (all slide types)
```markdown
---
title: Hardware Architecture
accentColor: "#005A8B"
background: both
cursor: trail
branding:
  logo: img/logo.png
  footer: Company © 2026
confidentiality: INTERNAL USE ONLY
---

---
layout: title
theme: accent
id: cover
eyebrow: DEVOPS TEAM
kicker: Q3 2024
animate: hero
notes: Opening slide — introduce the architecture.
navTitle: Cover
---
# Hardware Architecture. *Scalable Infrastructure*

Good afternoon. We're presenting our distributed architecture for large-scale
geospatial data processing.

---
layout: agenda
theme: light
kicker: AGENDA
---
## What We Cover

- **Introduction** Project goals and requirements
- **Core Services** 17 selected services across 7 categories
- **Why These Services** Selection rationale and trade-offs
- **Alternatives** What we evaluated and why we didn't choose them
- **Deployment** Distributed nodes approach with phased rollout

---
layout: split
theme: light
right_type: accent
right_kicker: KEY REQUIREMENTS
right_lines: [Scalability., Reliability., Performance.]
kicker: INTRODUCTION
---
## Introduction & Objectives

- **Primary Objective** Design scalable infrastructure for 3D CityGML processing
- **High Availability** Eliminate single points of failure with redundancy
- **Resource Efficiency** Right-size hardware per tier
- **ML-Ready** Native GPU support for training and inference

Our project provides a curated selection of self-hosted, open-source services.

---
layout: eda-grid
theme: light
eyebrow: CORE SERVICES
kicker: ARCHITECTURE OVERVIEW
navTitle: Service Overview
sidebar:
  - tag: "Auth & Security"
    title: "Authentik · OpenBao"
    body: "Identity and secrets management. Self-hosted, open-source."
  - tag: "Storage & DBs"
    title: "MinIO · PostGIS · 3DCityDB"
    body: "S3-compatible store, spatial DB, CityGML-optimized 3D DB."
  - tag: "Compute"
    title: "Spark · JupyterHub · Trino"
    body: "Distributed ETL, multi-user notebooks, federated SQL."
  - tag: "DevOps & Monitoring"
    title: "Jenkins · Terraform · Grafana"
    body: "CI/CD automation, IaC, unified dashboards."
---

```mermaid
graph LR
    subgraph Auth ["Identity"]
        Authentik --> OpenBao
    end
    subgraph Storage ["Storage"]
        MinIO
    end
    Auth --> Storage
    Storage --> Compute
```

---
layout: card-row
theme: light
eyebrow: WHY THESE SERVICES
kicker: AUTHENTICATION
navTitle: Auth & Security
---
## Why *Authentik & OpenBao*

- **Authentik** Centralized SSO supporting OIDC, OAuth2, SAML, LDAP. Self-hosted AGPL-3.0.
- **OpenBao** Secrets management fork of HashiCorp Vault. MPL-2.0. Dynamic secrets.
- **Together** Identity-first architecture. Zero vendor lock-in. Defense in depth.

---
layout: card-row
theme: light
eyebrow: WHY THESE SERVICES
kicker: STORAGE & DATABASES
---
## Why *MinIO, PostgreSQL & 3D City DB*

- **MinIO** Self-hosted S3-compatible object storage. Petabyte-scale. Erasure coding.
- **PostgreSQL + PostGIS** Mature spatial database with 3D geometry support. ACID-compliant.
- **3D City Database** Purpose-built for CityGML with native 3D queries and versioning.

---
layout: columns
theme: light
eyebrow: ALTERNATIVES
kicker: AUTH & SECURITY
navTitle: Alternatives
columns: 2
---
## Alternatives: *Auth & Security*

- **Authentik vs Keycloak** Keycloak is equally capable but more complex. Authentik has better K8s integration.
- **OpenBao vs Vault** HashiCorp moved to BSL. OpenBao is the community fork, same API, MPL-2.0.

---
layout: eda-grid
theme: light
eyebrow: SUGGESTED DEPLOYMENT
kicker: DISTRIBUTED NODES
navTitle: Deployment
sidebar:
  - tag: "Tier 1"
    title: "Control Plane"
    body: "3 Nodes · 4-8 Cores · Kubernetes, Authentik, Grafana"
  - tag: "Tier 2"
    title: "Storage & DB"
    body: "3-4 Nodes · 8-16 Cores · MinIO, PostGIS, 3DCityDB"
  - tag: "Tier 3"
    title: "Compute"
    body: "2-4 Nodes · 16-32 Cores · Spark, JupyterHub, Trino"
  - tag: "Tier 4"
    title: "GPU Accelerator"
    body: "Xeon Platinum 8470Q · NVIDIA L40S · ML Training"
---

```mermaid
graph TD
    CP["Control Plane"] --> SN["Storage & DB"]
    CP --> CN["Compute"]
    SN <--> CN
    GN["GPU Node"] --> CN
```

---
layout: split
theme: dark
right_type: accent
right_kicker: DEPLOYMENT RATIONALE
right_lines: [Scale., Survive., Perform.]
kicker: WHY DISTRIBUTED
---
## Why Distributed Nodes?

- **Scalability** Add nodes independently as load grows
- **High Availability** No single point of failure
- **Performance** Dedicated hardware per tier

---
layout: timeline
theme: light
eyebrow: DEPLOYMENT PLAN
kicker: ROLLOUT PHASES
navTitle: Timeline
---
## Deployment *Steps*

- **Phase 1** **Procure Hardware** Acquire 3 control plane + 1-2 storage nodes
- **Phase 2** **Networking** Configure 10Gbps NICs with VLAN isolation
- **Phase 3** **Control Plane** Install K8s, Authentik, Grafana, OpenBao
- **Phase 4** **Storage Layer** MinIO distributed mode, PostGIS replication
- **Phase 5** **Compute Layer** Spark, JupyterHub, Trino, Airflow
- **Phase 6** **GPU & Validate** L40S node, integration tests, go live

---
layout: title
theme: accent
id: qa
kicker: THANK YOU
---
## Q&A. *Discussion*

Thank you for your attention. Any questions?
```

