[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"l1lLqzPMID":3},"# VersoSlides\n\nVersoSlides is a Verso genre that generates `reveal.js` slide\npresentations from Lean 4 documents. All third-party dependencies\n(`reveal.js`, KaTeX, marked) are vendored and embedded at compile\ntime, so the generated output works fully offline. The output includes\nfull support for Verso's elaborated Lean code blocks, including syntax\nhighlighting and hover-based documentation tooltips.\n\nThe [demo slides](./Demo.lean) can be seen at\n[this repository's GitHub pages](https://leanprover.github.io/verso-slides/).\n\n## Requirements\n\nVersoSlides requires the Lean 4 toolchain specified in\n`lean-toolchain` and pulls Verso as a Lake dependency.\n\n## Building and Running\n\nBuild the project with Lake, then run the executable to generate\nslides:\n\n```\nlake build\nlake exe demo-slides\n```\n\nThe output is written to `_slides/` by default, containing\n`index.html` and a `lib/` directory with the vendored `reveal.js`,\nKaTeX, and marked assets. To view the presentation, serve the output\ndirectory over HTTP:\n\n```\npython3 -m http.server -d _slides\n```\n\n## Writing a Presentation\n\nA presentation is a Verso document declared with the `Slides` genre.\nThe document title becomes the HTML page title. Each top-level heading\n(`#`) becomes a horizontal slide, and the content under it forms the\nslide body.\n\n```\nimport VersoSlides\nimport Verso.Doc.Concrete\n\nopen VersoSlides\n\n#doc (Slides) \"My Presentation\" =>\n\n# First Slide\n\nContent of the first slide.\n\n# Second Slide\n\nContent of the second slide.\n```\n\nThe document module must be imported in `Main.lean`, where the\n`slidesMain` function generates the output. Document-level\nconfiguration (theme, transition style, slide numbering, etc.) is set\non the `Config` passed to `slidesMain` — see\n[Document-Level Configuration](#document-level-configuration).\n\n### Vertical Slides\n\nWhen a top-level section has `vertical := some true` in its metadata\nblock, its subsections (`##`) become vertical sub-slides arranged in a\ncolumn beneath the parent. Without this setting, subsections are\nflattened into the parent slide without their own `\u003Csection>`\nwrappers.\n\n```\n# Vertical Group\n\n%%%\nvertical := some true\n%%%\n\nContent on the first vertical sub-slide.\n\n## Second Sub-Slide\n\nThis appears below the first when navigating down.\n```\n\n### Slide Metadata\n\nEach slide can carry per-slide metadata in a `%%%` block immediately\nafter its heading. All fields are optional and fall back to\ndocument-level defaults or `reveal.js` defaults when omitted. The\nmetadata is written as Lean structure field syntax.\n\n```\n# My Slide\n\n%%%\ntransition := some \"zoom\"\nbackgroundColor := some \"#2d1b69\"\nautoAnimate := some true\n%%%\n```\n\nThe available metadata fields are listed below. Each one maps directly\nto a `data-*` attribute on the slide's `\u003Csection>` element.\n\n| Field                   | Type            | `reveal.js` attribute                                               |\n| ----------------------- | --------------- | ------------------------------------------------------------------- |\n| `vertical`              | `Option Bool`   | Controls vertical sub-slide grouping (not rendered as an attribute) |\n| `transition`            | `Option String` | `data-transition`                                                   |\n| `transitionSpeed`       | `Option String` | `data-transition-speed`                                             |\n| `backgroundColor`       | `Option String` | `data-background-color`                                             |\n| `backgroundImage`       | `Option String` | `data-background-image`                                             |\n| `backgroundSize`        | `Option String` | `data-background-size`                                              |\n| `backgroundPosition`    | `Option String` | `data-background-position`                                          |\n| `backgroundRepeat`      | `Option String` | `data-background-repeat`                                            |\n| `backgroundOpacity`     | `Option Float`  | `data-background-opacity`                                           |\n| `backgroundVideo`       | `Option String` | `data-background-video`                                             |\n| `backgroundVideoLoop`   | `Option Bool`   | `data-background-video-loop`                                        |\n| `backgroundVideoMuted`  | `Option Bool`   | `data-background-video-muted`                                       |\n| `backgroundIframe`      | `Option String` | `data-background-iframe`                                            |\n| `backgroundGradient`    | `Option String` | `data-background-gradient`                                          |\n| `backgroundTransition`  | `Option String` | `data-background-transition`                                        |\n| `backgroundInteractive` | `Option Bool`   | `data-background-interactive`                                       |\n| `autoAnimate`           | `Option Bool`   | `data-auto-animate`                                                 |\n| `autoAnimateId`         | `Option String` | `data-auto-animate-id`                                              |\n| `autoAnimateEasing`     | `Option String` | `data-auto-animate-easing`                                          |\n| `autoAnimateDuration`   | `Option Float`  | `data-auto-animate-duration`                                        |\n| `autoAnimateUnmatched`  | `Option Bool`   | `data-auto-animate-unmatched`                                       |\n| `autoAnimateRestart`    | `Option Bool`   | `data-auto-animate-restart`                                         |\n| `timing`                | `Option Nat`    | `data-timing`                                                       |\n| `visibility`            | `Option String` | `data-visibility`                                                   |\n| `state`                 | `Option String` | `data-state`                                                        |\n| `autoSlide`             | `Option Nat`    | `data-autoslide`                                                    |\n\n## Directives\n\nDirectives are block-level constructs delimited by `:::` fences. They\napply `reveal.js` features to their content.\n\n### Speaker Notes\n\nThe `notes` directive wraps its content in an `\u003Caside class=\"notes\">`\nelement, which `reveal.js` displays in the speaker view (opened with\nthe S key).\n\n```\n:::notes\nRemember to explain this point carefully.\n:::\n```\n\n### Fragments\n\nThe `fragment` directive makes its content appear incrementally. Each\nchild block receives the fragment class individually, so a directive\nwrapping multiple paragraphs produces multiple independent animation\nsteps.\n\nAn optional positional argument sets the animation style. The style\nname is written in camelCase and converted to the corresponding\nreveal.js class (for example, `fadeUp` becomes `fade-up`). A named\n`index` argument controls the ordering of fragment steps.\n\n```\n:::fragment fadeUp (index := 2)\nThis paragraph fades up at step 2.\n:::\n```\n\nThe built-in animation styles include `fadeUp`, `fadeDown`,\n`fadeLeft`, `fadeRight`, `fadeIn`, `fadeOut`, `currentVisible`,\n`highlightRed`, `highlightGreen`, `highlightBlue`, and others defined\nby `reveal.js`.\n\n### Fit Text, Stretch, and Frame\n\nThree named directives apply common `reveal.js` utility classes to\ntheir content.\n\nThe `fitText` directive causes `reveal.js` to scale the text to fill\nthe slide width. The `stretch` directive causes an element to expand\nto fill the remaining vertical space on the slide. The `frame`\ndirective adds a default styled border around the content.\n\n```\n:::fitText\nLarge heading text\n:::\n```\n\n### Layout: `stack`, `hstack`, `vstack`\n\nThe `stack`, `hstack`, and `vstack` directives wrap their children in\na container `\u003Cdiv>` with the corresponding `reveal.js` layout class.\nUnlike the push-down directives described above, these create a\nwrapper element around all the content rather than applying attributes\nto each child individually.\n\nThe `stack` directive stacks elements on top of one another (useful\nfor layered animations). The `hstack` directive arranges elements\nhorizontally, and `vstack` arranges them vertically.\n\n```\n:::hstack\nLeft column content.\n\nRight column content.\n:::\n```\n\n### CSS Classes and IDs\n\nThe `class` directive pushes one or more CSS classes onto each child\nblock. Class names are given as positional string arguments and are\nmerged with any existing classes on the element.\n\n```\n:::class \"custom-highlight\" \"another-class\"\nThis paragraph receives both classes.\n:::\n```\n\nThe `id` directive similarly pushes an `id` attribute onto each child\nblock. It takes a single positional string argument.\n\n```\n:::id \"important-paragraph\"\nThis paragraph has the given ID.\n:::\n```\n\n### Generic Attributes\n\nThe `attr` directive pushes arbitrary HTML attributes onto each child\nblock. Attribute names and values are given as named arguments.\nBecause Verso's argument parser uses Lean identifier syntax, attribute\nnames that contain hyphens (such as `data-id`) must be escaped with\nguillemets.\n\n```\n:::attr («data-id» := \"box\") (style := \"color: red\")\nThis paragraph receives both attributes.\n:::\n```\n\nThis directive is particularly useful for auto-animate element\nmatching, where paired elements across adjacent slides need matching\n`data-id` attributes.\n\n### Tables\n\nThe `table` directive renders an HTML `\u003Ctable>` from a nested list of\nlists: each outer list item is a row, and each inner list item is a\ncell. All rows must have the same number of cells.\n\n```\n:::table +colHeaders +stripedRows +border\n*\n  * Header A\n  * Header B\n*\n  * Cell A1\n  * Cell B1\n*\n  * Cell A2\n  * Cell B2\n:::\n```\n\nCell contents are parsed as block content, so inline markup, Lean\ncode, images, and other directives are allowed inside cells.\n\nAll style options are off by default. Boolean flags are written as\n`+name` (or `-name` to explicitly disable); `cellGap` takes a named\nstring argument:\n\n- `+colHeaders` — the first row becomes a `\u003Cthead>` whose cells are\n  `\u003Cth scope=\"col\">`.\n- `+rowHeaders` — the first cell of each body row becomes\n  `\u003Cth scope=\"row\">`.\n- `+stripedRows` — alternating body rows receive a tinted background.\n- `+stripedCols` — alternating columns receive a tinted background.\n  Combined with `+stripedRows` it produces a checkerboard pattern.\n- `+rowSeps` — draws horizontal separators between data rows (and\n  between a header row and the first body row).\n- `+colSeps` — draws vertical separators between data columns (and\n  between a row-header column and the first data column).\n- `+headerSep` — draws a thicker separator after the header row and/or\n  header column.\n- `+border` — draws separator lines along the four outer edges.\n- `(cellGap := \"0.4em 0.6em\")` — overrides cell padding. The value is\n  passed through unchanged as a CSS `padding` shorthand (one, two, or\n  four lengths).\n\nColors derive from the current slide's text colour via `color-mix()`,\nso tables automatically adapt to both light and dark themes. The\nunderlying custom properties (`--slide-table-stripe-row-a`,\n`--slide-table-sep`, `--slide-table-cell-padding`, etc.) can be\noverridden in your own CSS for per-presentation restyling.\n\n### Nesting Directives\n\nDirectives can be nested by using a longer fence for the outer\ndirective. The inner directive uses the standard three-colon fence,\nand the outer directive uses a fence with more colons to avoid\nambiguity.\n\n```\n:::::fitText\n:::attr («data-id» := \"title\")\nScaled and tracked text.\n:::\n:::::\n```\n\n## Inline Roles\n\nRoles are inline constructs written as `{roleName args}[content]`.\nThey wrap their content in a `\u003Cspan>` element with the appropriate\nattributes.\n\n### Inline Fragments\n\nThe `fragment` role wraps inline content in a `\u003Cspan>` with the\nfragment class. Unlike the block directive, the role uses named\narguments for the style and index.\n\n```\nThis is {fragment (style := highlightRed)}[highlighted] text.\n```\n\n### Inline Classes, IDs, and Attributes\n\nThe `class`, `id`, and `attr` roles are the inline counterparts of the\ncorresponding block directives. They wrap their content in a `\u003Cspan>`\nwith the specified attributes.\n\n```\nThis word is {class \"custom\"}[styled] differently.\n\nThis has an {id \"target\"}[identified span].\n\nThis is {attr («data-id» := \"word\")}[tracked] across slides.\n```\n\n## Math\n\nInline and display math are written with Verso's built-in math syntax\nand rendered by KaTeX at page load:\n\n```\nEuler's identity: $`e^{i\\pi} + 1 = 0`.\n\n$$`\\int_0^\\infty e^{-x^2}\\,dx = \\frac{\\sqrt{\\pi}}{2}`\n```\n\n`$`…`$` is inline math and `$$`…`$$` is display math; both bodies are\nwrapped in backticks and parsed as LaTeX. KaTeX is vendored, so math\nrenders fully offline.\n\n### Prelude of Math Macros\n\nRecurring notation can be declared once at the document level by\nsetting `Config.mathPrelude` to a string of prelude commands in the\nmath renderer's syntax (KaTeX).\n\n```lean\nslidesMain\n  { mathPrelude :=\n      \"\\\\def\\\\RR{\\\\mathbb{R}}\\n\\\\newcommand{\\\\Hom}[2]{\\\\mathrm{Hom}(#1, #2)}\\n\" }\n  (%doc MyTalk)\n```\n\nAfter that, `$`…`\\RR`…`$` and `$`…`\\Hom{A}{B}`…`$` resolve on every\nslide. Parse errors in the prelude are logged to the browser console\nand do not abort rendering.\n\n## Lean Code Blocks\n\nFenced code blocks tagged with `lean` are elaborated by the Lean\ncompiler and rendered with full syntax highlighting and hover\ndocumentation. The code is type-checked at build time, so any errors\nare caught before the slides are generated.\n\n````\n```lean\ndef factorial : Nat → Nat\n  | 0 => 1\n  | n + 1 => (n + 1) * factorial n\n```\n````\n\nThe generated HTML includes the necessary CSS and JavaScript for\nVerso's highlighting system.\n\nInstead of hovers, information about code is revealed on click. This\nis to allow the presenter to point using the mouse without hover boxes\npopping up over content.\n\n### Code Box Sizing\n\nBy default, a code box fills the remaining vertical space on its\nslide. This uses `reveal.js`'s `r-stretch` mechanism. Code shorter\nthan the available space sits at the top of the box; code taller than\nthe available space scrolls within the box. Pass the `-stretch` flag\nto opt out, sizing the box to its content instead:\n\n````\n```lean -stretch\ndef answer : Nat := 42\n```\n````\n\n`reveal.js` can only stretch one element per slide. If a slide has\nmore than one code box, add `-stretch` to all but one of them;\notherwise the heights are computed incorrectly. (The standalone\n`:::stretch` directive, documented above, applies the same mechanism\nto arbitrary content such as images.)\n\nThe `stretch` flag is available on `lean`, `leanModule`, and\n`leanLibCode` code blocks.\n\n### Tips\n\nUse the `-show` flag to include Lean code that is not rendered. This\ncan set options, define variables, or create helpers:\n\n````\n```lean -show\nsection\nset_option pp.all true\nvariable {α : Type} [ToString α]\n```\n```lean\n#check List α\n```\n```lean -show\nend\n```\n````\n\n### Progressive Code Reveal\n\nInside elaborated Lean code blocks, special comments control how code\nis revealed incrementally using `reveal.js` fragments. There are three\nkinds of control comments: fragment breaks, click targets, and hidden\nregions.\n\n#### Fragment Breaks\n\nA line comment of the form `-- !fragment` splits the code block at\nthat point. Everything from the break to the next break (or the end of\nthe block) appears as a separate `reveal.js` fragment, so successive\npresses of the advance key reveal successive portions of the code.\n\nAn optional style name and numeric index may follow the keyword. The\nstyle name is any identifier recognized by `reveal.js` (for example,\n`fadeUp`), and the index controls the ordering of animation steps\nacross the slide.\n\n````\n```lean\ndef f := 1\n-- !fragment\ndef g := 2\n-- !fragment fadeUp 3\ndef h := 3\n```\n````\n\n#### Click Targets\n\nA line comment of the form `-- ^ !click` marks the token on the\npreceding line at the column of the caret (`^`) as a \"click target.\"\nWhen that fragment step is reached, information about the code at that\nposition is revealed. An optional numeric index controls the ordering.\n\n````\n```lean\ntheorem foo : True := by\n  intro n\n-- ^ !click\n  trivial\n```\n````\n\nIn this example, `intro n` is revealed normally, and then on the next\nadvance, the token at the caret column on the preceding line (here,\n`intro`) receives a click highlight, resulting in the proof state for\n`intro n` being revealed.\n\n#### Hidden Regions\n\nCode that should be elaborated but not displayed in the slides can be\nwrapped in a hide region. The block comment `/- !hide -/` opens a\nhidden region, and `/- !end hide -/` closes it. Everything between the\ntwo markers is type-checked as usual but omitted from the rendered\noutput. This is useful for auxiliary definitions or imports that the\naudience does not need to see.\n\n````\n```lean\n/- !hide -/\ndef helper := 42\n/- !end hide -/\ntheorem uses_helper : helper = 42 := rfl\n```\n````\n\nHide markers can appear inline (on the same line as code) or on their\nown lines. They can also be nested: an inner `/- !hide -/` inside an\nalready-hidden region increments a depth counter, and the\ncorresponding `/- !end hide -/` decrements it. Code is only shown once\nall hide regions have been closed.\n\n#### Inline Fragment Regions\n\nBlock comments of the form `/- !fragment -/` and `/- !end fragment -/`\ndelimit an inline fragment region within a line. Unlike line-level\nfragment breaks, which split the code block vertically, inline\nfragment regions wrap a horizontal span of code in a reveal.js\nfragment. An optional style name and index may appear between\n`!fragment` and the closing `-/`.\n\n````\n```lean\ndef f := /- !fragment -/42/- !end fragment -/\n```\n````\n\n### Library Code\n\nThe `leanLibCode` code block shows the current source of a\ndeclaration, or a line range, from a library the presentation depends\non. The block body contains the expected code from the library, and it\nis an error if it does not match.\n\nIf the library changes, the block stops matching and Lean offers a\nclickable quickfix that replaces the block with the current source. In\nline-range mode, when the body still appears in the library at a\ndifferent range (because lines were inserted or removed earlier in the\nfile), Lean additionally offers a quickfix that updates\n`(startLine := …) (endLine := …)` to where the body is now. Small\ncharacter-level edits within lines (renames, typos) are tolerated when\nlocating the new range.\n\n````\n```leanLibCode MyLib.Foo (decl := MyLib.Foo.bar)\ndef bar : Nat := 42\n```\n````\n\n````\n```leanLibCode MyLib.Foo (startLine := 10) (endLine := 30)\n-- lines 10..30 of MyLib.Foo\n```\n````\n\nArguments:\n\n- The first positional argument is the module name.\n- `package` — optional. Disambiguates when several packages may\n  contain a module with the same name, or when the module is only\n  resolvable with a package qualifier.\n- `decl` — optional declaration name; extracts the item whose\n  declarations include this name.\n- `startLine` / `endLine`: optional 1-based inclusive line range; must\n  be provided together and cannot combine with `decl`.\n- `panel`: a flag that determines whether to show the interactive info\n  panel under the slide (default `true`). Disable with `-panel`;\n  re-enable explicitly with `+panel`.\n- `stretch`: a flag that determines whether the code box fills the\n  remaining vertical space on the slide (default `true`). Disable with\n  `-stretch` to size the box to its content. See\n  [Code Box Sizing](#code-box-sizing).\n\nOmitting `decl` and the line range shows the entire module.\n\n#### Wiring up the Lake build\n\nFor this to work, the highlighted source code of the library must be\navailable for the slides to read. The highlighted code can be built\nusing the `highlighted` facet in Lake. To arrange for Lake to build\nthis automatically, the slides must declare a `needs` dependency so\nLake builds the facet before elaborating the slides.\n\nIn `lakefile.toml`:\n\n```toml\n[[lean_lib]]\nname = \"MySlides\"\nneeds = [\"@mypkg/+MyLib.Foo:highlighted\"]\n```\n\nOr a whole package at once:\n\n```toml\nneeds = [\"@mypkg:highlighted\"]\n```\n\nIn `lakefile.lean`:\n\n```lean\nlean_lib MySlides where\n  needs := #[`@mypkg/+MyLib.Foo:highlighted]\n```\n\nOr for an entire package:\n\n```lean\nlean_lib MySlides where\n  needs := #[`@mypkg:highlighted]\n```\n\nWith this declaration, `lake build` on the slides project produces the\nhighlighted JSON before Lean elaborates `leanLibCode` blocks. If the\nfacet isn't available, elaboration fails fast with a reminder to add\nthe `needs` entry. It won't silently trigger a large build while\nelaborating the slides.\n\nLean's own prelude and `Std` modules don't need the `needs`\nconfiguration.\n\n## Document-Level Configuration\n\nDocument-level `reveal.js` settings live on the `Config` value passed\nto `slidesMain` from your `Main.lean`. They correspond to `reveal.js`\n[`Reveal.initialize`](https://revealjs.com/config/) options and apply\nto the whole presentation. `%%%` blocks on individual slides only\ncarry per-slide attributes (the table above); doc-level config does\nnot appear in `%%%` blocks at all.\n\n```\nimport VersoSlides\nimport MyPresentation\n\nopen VersoSlides\n\ndef main : IO UInt32 :=\n  slidesMain\n    (config := { theme := \"white\", slideNumber := true,\n                 transition := \"fade\", autoSlide := 5000 })\n    (doc := %doc MyPresentation)\n```\n\nThe available `Config` fields that map to `Reveal.initialize` options\nare `theme`, `transition`, `width`, `height`, `margin`, `controls`,\n`progress`, `slideNumber`, `hash`, `center`, `navigationMode`,\n`autoSlide`, `autoSlideStoppable`, and `autoSlideMethod`. Each one\nsets the document-wide default; some have a matching per-slide\noverride on the slide's `\u003Csection>` (see the table above) — for\nexample, `Config.transition` is the global default but a slide can\noverride it with `transition := some \"zoom\"` in its `%%%` block, which\nbecomes `data-transition=\"zoom\"`.\n\n`Config` also has presentation-level knobs that don't go through\n`Reveal.initialize`:\n\n- `extraCss : Array CssFile` — overlay stylesheets, see\n  [Custom CSS](#custom-css).\n- `extraJs : Array String` — extra `\u003Cscript src=…>` tags appended to\n  the page.\n- `outputDir : System.FilePath` — where to write `index.html` and the\n  vendored assets. Defaults to `_slides`.\n\n### Auto-Advance\n\n`autoSlide`, `autoSlideStoppable`, and `autoSlideMethod` together\nexpose the\n[`reveal.js` auto-slide feature](https://revealjs.com/auto-slide/).\n\n- `autoSlide : Nat` — auto-advance interval in milliseconds. `0` (the\n  default) disables auto-advancing; any positive value advances slides\n  every N ms and adds a play/pause control. The matching per-slide\n  `autoSlide : Option Nat` field on `SlideMetadata` emits\n  `data-autoslide=\"N\"` and overrides the global default for that slide\n  only.\n- `autoSlideStoppable : Bool` — when `true` (the default),\n  auto-sliding pauses as soon as the audience interacts with the deck.\n  Set `false` for unattended kiosk-style playback. No per-slide\n  override.\n- `autoSlideMethod : AutoSlideMethod` — which navigation method\n  `reveal.js` calls when auto-sliding advances. `.next` (the default)\n  calls `Reveal.navigateNext()` (advance through fragments, then\n  horizontal/vertical slides in order); `.right` calls\n  `Reveal.right()`; `.down` calls `Reveal.down()`. For anything more\n  exotic, `.js \"\u003Cexpr>\"` emits the given JavaScript expression\n  verbatim — it must evaluate to a function, e.g.\n  `() => Reveal.left()`. No per-slide override.\n\nThe generated HTML loads the Notes, Highlight, and KaTeX Math plugins\nautomatically. All plugin assets are vendored and written to the\noutput directory, so no internet connection is required.\n\n## Themes\n\nThe built-in `reveal.js` themes are the ones listed in the\n[`reveal.js` themes documentation](https://revealjs.com/themes/), plus\nthe high-contrast `black-contrast` and `white-contrast` variants.\nSelect one by setting `theme := \"white\"` (or any of the other theme\nnames) on the `Config` you pass to `slidesMain`.\n\nAll fonts referenced by built-in themes are bundled (Source Sans Pro\nand League Gothic from upstream `reveal.js`, plus Lato, Ubuntu,\nMontserrat, Open Sans, News Cycle, and Quicksand from Google Fonts).\nThe `@import` URLs in the upstream theme CSS are rewritten to local\npaths while building the slides, so presentations render with the\nintended typeface even with no network access. To refresh the vendored\nfonts after a `reveal.js` bump, re-run `scripts/vendor-fonts.sh`.\n\nThe `theme` field of `Config` is a `Theme` sum type: either\n`.builtin name` (one of the bundled themes, selected by name) or\n`.custom theme` (a user-supplied `CustomTheme` that fully replaces the\nbundled theme). A bare string coerces to `.builtin` automatically, so\n`theme := \"black\"` continues to work, and a bare `CssFile` coerces to\na `CustomTheme` with no bundled assets, so simple cases stay short.\n\n### Writing a Custom Theme\n\nA `reveal.js` theme is just a stylesheet that sets the theme variables\nand base rules documented at\n[revealjs.com/themes/#creating-a-theme](https://revealjs.com/themes/#creating-a-theme).\nTo replace the bundled theme, wrap your stylesheet in a `CustomTheme`\nand pass it to `Config.theme`:\n\n```\ndef customRevealTheme : CssFile where\n  filename := \"theme/my-reveal-theme.css\"\n  contents := ⟨include_str \"my-reveal-theme.css\"⟩\n\ndef main : IO UInt32 :=\n  slidesMain\n    (config := { theme := .custom customRevealTheme })\n    (doc := %doc MyPresentation)\n```\n\nWhen `theme` is `.custom`, the bundled theme CSS is not written or\nlinked; the custom file stands on its own. The `filename` may contain\nsubdirectories (nested directories are created on demand) and is used\nverbatim as the `href` of the emitted `\u003Clink>` tag.\n\n#### Bundling Theme Assets (Images, Fonts, …)\n\nA `CustomTheme` can carry any number of companion files — typically\nanything the stylesheet references by URL — via its `assets` field:\n\n```\nstructure CustomTheme where\n  stylesheet : CssFile\n  assets     : Array ThemeAsset := #[]\n\nstructure ThemeAsset where\n  filename : String\n  contents : ByteArray\n```\n\nThe simplest way to populate `assets` is to drop every file the theme\nneeds into a single directory and pull the whole tree in at compile\ntime with Verso's `include_bin_dir`:\n\n```\nimport VersoUtil.BinFiles\n\nopen VersoSlides\nopen Verso.BinFiles\n\ndef customReveal : CustomTheme where\n  stylesheet := { filename := \"my-theme/theme.css\"\n                  contents := ⟨include_str \"my-theme-source.css\"⟩ }\n  -- `include_bin_dir \"my-theme-assets\"` returns an `Array (String ×\n  -- ByteArray)` whose strings all start with \"my-theme-assets/\" — we\n  -- feed it straight into `ThemeAsset.fromDir`, so the files land at\n  -- the matching paths under the output directory.\n  assets := ThemeAsset.fromDir (include_bin_dir \"my-theme-assets\")\n```\n\nBecause the stylesheet URL at runtime is `my-theme/theme.css` and the\nassets sit at `my-theme-assets/...`, the stylesheet should reference\nthem as `url(\"../my-theme-assets/foo.png\")` (standard CSS relative-URL\nresolution). For a single file, use `include_bin` directly:\n\n```\n{ filename := \"my-theme/logo.png\", contents := include_bin \"logo.png\" }\n```\n\n## Syntax Highlighting Theme\n\nNon-Lean code blocks are highlighted at presentation time by the\n`reveal.js` plugin that uses `highlight.js` plugin. The `highlight.js`\ntheme, which is a CSS file, is configurable via\n`Config.highlightTheme`.\n\nThe following themes are bundled:\n\n- `monokai` (dark) — default for the dark `reveal.js` themes\n- `github` (light) — default for the light `reveal.js` themes\n- `githubDark`\n- `atomOneLight`, `.atomOneDark`\n- `tomorrow`\n- `solarizedLight` — default for the `solarized` `reveal.js` theme\n- `solarizedDark`\n\nThe default is chosen automatically from the theme: dark `reveal.js`\nthemes use `monokai`, light themes use `github`, and `solarized` uses\n`solarizedLight`. This default can be overridden:\n\n```\ndef main : IO UInt32 :=\n  slidesMain\n    (config := { theme := \"white\", highlightTheme := .githubDark })\n    (doc := %doc MyPresentation)\n```\n\nSupply your own stylesheet by passing a `CssFile`:\n\n```\ndef myHighlight : HighlightTheme where\n  filename := \"lib/my-hl.css\"\n  contents := ⟨include_str \"my-hl.css\"⟩\n\ndef main : IO UInt32 :=\n  slidesMain\n    (config := { highlightTheme := myHighlight })\n    (doc := %doc MyPresentation)\n```\n\nA `CustomTheme` may also ship a `highlight.js` theme. An explicit\n`Config.highlightTheme` always wins over the highlighted theme\nspecified by the `reveal.js` theme.\n\n## Custom CSS\n\nTo layer additional CSS on top of a theme — tweaking colors, adding\nper-slide utility classes, or overriding individual rules — pass the\n`extraCss` field to `slidesMain` via the `config` argument. Each entry\nis a `CssFile` carrying a `filename` and the CSS source to write at\nthat filename. The file is written alongside `index.html` in the\noutput directory and loaded by a `\u003Clink rel=\"stylesheet\">` tag emitted\n_after_ the theme, so its rules override the theme's.\n\nUse `include_str` to embed the stylesheet at compile time so the\ncompiled executable stays self-contained:\n\n```\nimport VersoSlides\nimport MyPresentation\n\nopen VersoSlides\n\ndef myExtraCss : CssFile where\n  filename := \"custom.css\"\n  contents := ⟨include_str \"custom.css\"⟩\n\ndef main : IO UInt32 :=\n  slidesMain\n    (config := { extraCss := #[myExtraCss] })\n    (doc := %doc MyPresentation)\n```\n\nThe `filename` is interpreted relative to the output directory (it may\ncontain subdirectories, which are created on demand) and is also used\nverbatim as the `href` of the emitted link tag. Unlike a custom theme,\n`extraCss` is additive: the bundled `reveal.js` theme is still loaded,\nand each entry layers on top of it (and on top of each earlier\n`extraCss` entry) in declaration order.\n\n## Filename Collisions\n\n`slidesMain` compiles the custom theme's stylesheet, every bundled\nasset, and every `extraCss` entry into a single deduplicated write\nplan before touching the filesystem:\n\n- If two entries share a filename **and** have identical contents,\n  they are merged: the file is written once and linked once. This is\n  the expected case when two `include_bin_dir` bundles share a common\n  font or logo, or when the same stylesheet is wired in twice from\n  different paths.\n- If two entries share a filename with **different contents**\n  (including a text/binary mismatch, e.g. a stylesheet and a binary\n  asset both claiming `theme.css`), `slidesMain` raises an\n  `IO.userError` and writes nothing. The error names the offending\n  filename and both sources so the conflict is easy to fix.\n",1780241993945]