deck3d · git:20260918.d661546 · 2026-09-18 · sha256 54aa53f8928eb5f5

deck3d git:20260918.d661546A

Immutable. This exact content is served forever at /api/v1/blob/54aa53f8928eb5f5.

---
name: deck3d
description: 'Build a self-contained 3D presentation from a Markdown outline — parse, tune via `overrides`, render to one offline deck.html. Use on "make a 3D deck from this outline", "build a presentation from these notes", "deck3d this markdown", "turn this talk into a 3D deck".'
---

# deck3d

Turn a Markdown outline into one self-contained, offline `deck.html`: extruded 3D
titles, harvested mermaid diagrams built as 3D objects, animated backgrounds, and
an optional glTF prop. The converter code is deterministic; **you tune the result
by writing `overrides` in `deck.json`**, never by editing the HTML.

## When to use

- The user has a Markdown outline (headings + bullets + ```mermaid blocks) and
  wants a presentation they can open offline in a browser.
- You need to tune placement, legibility, mode, effects or props of an existing
  `deck.json`.

## Markdown grammar

- `---` front-matter: deck defaults (`mode`, `palette`, `material`, `quality`, `transition`, …).
- `# Title` starts a slide. Zero headings ⇒ one slide id `slide`.
- The first paragraph after the heading (before bullets) is the subtitle.
- `- bullet` lines are body bullets.
- A fenced ```mermaid `flowchart` / `sequenceDiagram` block becomes the slide's
  diagram (harvested in headless chromium). Any other diagram type ⇒ warn + no diagram.
- `<!-- deck3d: {...} -->` inline overrides win over `deck.json` and warn on clobber.
- Pin a slide id with `# Final {#outro}` so renames never orphan tuning.

## The tune loop

1. `deck3d parse talk.md` → `talk.json` (derived IR + empty `overrides`).
2. `deck3d validate talk.json` → schema + derived-edit + orphan/prop warnings.
3. `deck3d build talk.md -o talk.html` → `talk.json` + `talk.html`; runs `check`.
4. Read the `check` findings. **Fix only the suggested key** (e.g.
   `overrides.slides["arch"].diagram.scale`), then re-run step 3.
5. `deck3d snapshot talk.html --slide 5 -o s5.png` to eyeball one slide.
6. Repeat until `check` is clean (or only acceptable contrast warnings remain).

Tune by writing `deck.json`'s `overrides` only:

```
overrides: {
  deck:    { ...defaults knobs... },
  effects: [{ id, params? }],
  props:   [{ source, id, licence, author, sha256, slide, role, size?, count?, restyle?, anim? }],
  slides:  { "<slideId>": { mode?, palette?, material?, scene?, quality?,
             diagram: { scale?, offset? }, camera: { distance? }, labels: { size? },
             check: { ignore? }, effects? } },
  nodes:   { "<slideId>/<nodeId>": { shape?, label?, position?, size?, material? } },
  edges:   { "<slideId>/<edgeId>": { kind?, material? } }
}
```

Objects deep-merge; **arrays replace** (an `effects`/`props` list is the whole list).
Full field list: [`reference/ir-fields.md`](reference/ir-fields.md).

## Styling with effects

`deck3d fx list [--kind k] [--tag t] [--json]` prints the catalogue;
[`reference/effects.md`](reference/effects.md) has params and licences.

- `parse` assigns deterministic defaults (title→`swarm`, flowchart→`tokens`,
  sequence→`rings`, security→`glyph-rain`, data→`data-columns`).
- Replace them with `overrides.slides["<id>"].effects = [{ id, params? }]`.
- Conflicts fail `render`; mode-incompatible effects are skipped with a warning;
  the summed `cost` warns over the quality budget (`low` 6 / `medium` 12 / `high` 20).

## Props (content illustrations)

`deck3d props search <keywords>` → candidate table (vendored first, then Poly Pizza).
**You** pick by relevance then style — code never picks. Then:

1. Prefer one pack per deck and `restyle: "palette"`.
2. `deck3d props fetch <source> <id>` → prints the `overrides.props[]` entry
   (with the `sha256`) and caches it in `.deck3d/props/`.
3. Add the entry to `overrides.props` (roles: `hero`, `illustration`,
   `ambient`, `node:<id>`), then `build`/`snapshot`.

Licences: CC0 needs no credits; CC-BY (and any non-CC0, non-`generated`) produces a
last `credits` slide at render. Never fetch a model whose licence you cannot name.

## Forbidden edits

- **Never edit `deck.html`** — it is regenerated; tune `deck.json` `overrides`.
- **Never edit derived fields** (`slides[]` and below) — `validate` warns
  `edited outside overrides`; the next `parse` loses the change.
- If you need a knob that does not exist, propose a schema field — do not hack the HTML.

## Pitfalls

- `parse` of a deck **with mermaid needs chromium**; missing ⇒
  `npx playwright install chromium`. `render`/`validate` never need a browser.
- Every three.js `typeface.json` has corrupt `ő ű Ő Ű` glyphs; deck3d uses a real
  Poppins TTF through opentype.js — do not swap in a typeface.json.
- Extruded small text blooms and is unreadable; diagram labels are flat canvas
  planes with a background-colour outline. Tune `labels.size`, not the material.
- The mermaid engine is pinned exactly; a bump is a deliberate change and the
  harvest fixtures will fail if the rendered id scheme moves.
- Headless/background tabs stall `requestAnimationFrame`; the loop falls back to
  `setTimeout` so snapshots capture a finished frame.

## Contributing an effect

Development-time procedure on the package (distinct from the deck tune loop):

1. Search a source; check its licence against the allow-list: **MIT, Zlib,
   BSD-2/3-Clause, CC0-1.0, Apache-2.0, OFL-1.1**. LYGIA (Prosperity) and
   Shadertoy content (default CC BY-NC-SA) are **inspiration only** — never port
   them verbatim.
2. Port to the module interface: `create(ctx, params) → { object?, pass?, material?, tick?, dispose() }`.
3. Write the card `src/fx/<id>.meta.json` (kind, tags, cost, modes, params schema,
   conflicts, `source` URL, `licence`).
4. `deck3d fx preview <id>` → check the PNG is not black.
5. `npm run gen:effects` → regenerate `reference/effects.md`.
6. `npm test` → the corpus test validates every card and the catalogue hash.