ClojureScript presentation engine

Plato

A deck is an ordinary Clojure value, so the same source renders as a live presentation, as a standalone HTML file, and as an assertion in a test suite.

Slides become programs without becoming a JavaScript project.

The model

A deck is a value

(deck/deck
 {:title  "My talk"
  :slides [(deck/slide :intro
                       [:h1 "Hello"]
                       {:background-image "hero.jpg"
                        :notes            "Speaker-only"})

           (deck/stack :details
                       [(deck/slide :one [:p "First"])
                        (deck/slide :two [:p "Second"])])]})

Nothing is registered, mounted or configured. deck/deck validates the tree and throws rather than rendering something wrong.

The content model

Seventeen kinds, one multimethod

Media

Images, GIFs, video with poster and sources, audio, iframes.

Prose

Bullets, ordered lists, tables, quotations, callouts, kickers.

Code

Language-tagged blocks with stepped line highlighting.

Layout

Columns, card grids, groups and Reveal fragments.

Open/closed

Adding a content kind

(defmethod content/render :timeline [{:keys [events]}]
  (into [:ol.acme-timeline]
        (map (fn [e] [:li (:label e)]))
        events))

(defn timeline [events]
  {:plato/type :timeline :events events})
That is the whole extension point
One defmethod. No edit to the deck model, the browser shell, or the HTML exporter.
Authoring

Write it however you think

Markdown
---
title: My talk
---

# Hello

Revenue and the *three bets*.

Note: ninety seconds.
Org
#+TITLE: My talk

* Hello

Revenue and the /three bets/.

:NOTES:
Ninety seconds.
:END:
The same lever, again

So is the set of formats

(ns talk.latex
  (:require [plato.source :as source]))

(defn ->deck [text]
  ...)

(source/register-extensions! :latex ["tex"])

(defmethod source/->deck :latex [_ text]
  (->deck text))

Markdown, Org and EDN ship this way. plato.cli does not know their names.

Desargues scene graph500Pure data → live SVG
PlatoDesargues layout → browser
Render targets

Reagent here, a string on the JVM

One definition of a slide's attributes serves both
BrowserStatic export
RuntimeReagent + Reveal.jsJVM or a native binary
ScenesLive, scrub-able SVGFinal frame, same SVG code
Slide attributesplato.deck/section-attrsplato.deck/section-attrs
OutputA running pageOne self-contained .html
Determinism

The same deck, the same bytes

  • Attribute order is the serializer's, not a map's iteration order.
  • Accents fold through plato's own NFD table, not the host's.
  • So the JVM and the native binary emit byte-identical pages.
Theming

One source, N projections

{:meta  {:prefix "plato" :reveal-theme "night"}
 :color {:bg "#0b0e13" :accent "#f0ac5f"}
 :scene {:palette [:teal :gold] :background :bg}

 :rules
 [[:.reveal {:background [:token :bg]}
   [:.plato-kicker {:color [:token :accent]}]]]}
Generated, and checked for drift

What one token file becomes

CSS

A :root custom property per token, then the theme's own rules.

Clojure

The tokens as data — the scene palette is derived from it.

JSON

A language-neutral manifest, value and variable per token.

A theme may :extends another and state only what differs. Scene colours and CSS colours cannot drift, because they are the same token.

The CLI

Notes in, presentation out

# any registered format, one command
plato build talk.md  -o dist/talk.html --assets public
plato build talk.org -o dist/talk.html
plato build deck.edn -o dist/talk.html

# tokens in, stylesheet out
plato theme theme/acme.tokens.edn -o css/acme.css

On the JVM, or as a self-contained native binary built with ClojureWasm — no JVM at run time.

Evidence

Three suites

JVM
  • Parsers, deck model, HTML
  • Theming, CLI, convergence
Browser
  • Playwright over this page
  • Did Reveal take the config?
Fit
  • Every slide measured in its box
  • An overflow fails the build
Plato

Author once. Present anywhere.

A deck is an ordinary Clojure value.
the whole idea

github.com/BuddhiLW/plato · the Acme demo