psi-slides – a guided tour

A lecture medium that builds four views from one Markdown file

One Markdown file becomes four HTML files: a document to print, the same document with your speaker notes, a projection for the audience, and a view of your own at the lectern. A masthead cover leaves a field between the title along the top and the credits along the bottom, and this paragraph is what goes in it.

Dominik Herrmann

Tutorial lecture built with psi-slides itself

Use the tool to learn the tool

Welcome

One source, four views the deck, the hand-out and the notes stop drifting apart

The deck, the hand-out and your notes say the same thing, and they disagree the moment you edit one. A lecture normally needs all three, and keeping them in step is work you do instead of preparing the lecture.

Instead, you write one Markdown file that consists of columns and chunks.

One command turns that file into four HTML files, and the four differ only in what they show you. print.html is a reading copy with a cover and a table of contents. print-notes.html is that same document with your speaker notes folded in under each chunk. audience.html is the projection, the presentation you show to the audience; whenever the tour says what a slide shows, it means this file. speaker.html is the speaker view, the screen you keep at the lectern, carrying the notes, a strip of the slides around you and a timer. Nothing in the source is written for one of them and not the others.

One file in, four files out

postMessagelive sync print-notes.htmldocument + notes print.htmlthe document audience.htmlthe projector speaker.htmlthe cockpit source.mdone file build.js images

Each of the four files carries everything it needs inside itself – the pictures, the typefaces, the styling, the code. Each one opens by double-clicking, with no web server and nothing fetched from the network, so you can send any of them to a colleague as a single attachment.

Chunks and columns the two terms the rest of the tour uses

A chunk is one ## heading and everything written under it. In the projection it gets a screen of its own; in the printed document it is a section of the page. It is the nearest thing here to what another tool calls a slide.

A column is a run of chunks on one theme, opened by a # Heading. It is the part of a lecture that Shift and an arrow moves you through in one press, and it is what the contents list on T shows – chunks never appear there.

Every lecture consists of one or more columns, and each column holds one or more chunks. Everything after this slide is what you may write inside a chunk.

What you are reading is one chunk P, S and ? reach the rest of the lecture

Whichever of the four files you have open, what you are reading is one chunk – one ## heading in the source, with everything written under it. audience.html and speaker.html give a chunk the whole screen and move you from one to the next with the keyboard; print.html and print-notes.html run the same chunks on down the page, so a reader scrolls instead of pressing anything.

In the projection, three keys reach the rest:

  • P opens print.html in a new tab – the whole lecture as a document.
  • S opens speaker.html as a second window, the speaker view. Once both are open, they mirror each other as you move.
  • ? shows the full keyboard and mouse reference. Everything below is in there too.

The one file that produced all four is lectures/tutorial/source.md. Open it in a text editor beside this window and read the two together.

Moving around

The audience sets the pace forward moves by a piece, by a chunk, or by a column

A lecture has one order, but the pace belongs to the audience, so forward is not one fixed step. It uncovers the next piece of the chunk you are on; when that chunk has nothing left, it moves to the next chunk; and Shift with an arrow moves a whole column at a time.

A dense chunk can therefore arrive in parts, and a chunk the audience has already understood is a single press. The rest of this part lists those keys, and what a click opens.

Forward and back Space and the arrows, with Shift for a whole column

Two keys move you through the whole lecture, forward and back, and holding Shift jumps a whole column.

  • Forward is Space, , , Enter or PageDown. It uncovers the next piece of the chunk, then moves on to the next chunk.
  • Back is , , PageUp or Backspace. It puts the last piece away, and leaves the chunk only once that chunk is back where it started.
  • Shift- and Shift- move a whole column, from any slide and not only the first of one. Shift- goes to the top of the column you are in first, so returning to the start of a part and leaving it are the same key. Press forward now:

You just uncovered a segment: in the source, a line containing nothing but --- cuts a chunk into segments, as long as it is outside a block of code. The first segment is on screen when you arrive; forward uncovers the next, back puts it away.

A faint at the foot of the slide says the next forward press will leave the column. It is the one thing about where you are that the slide cannot show you by itself. There is nothing to click.

The cockpit – the speaker view, which is discussed later on – shows you what comes next. With it open, look at this slide there: the segment the next forward press will reveal is already drawn in place, hatched and inside a dashed frame, so you can read ahead without the audience seeing it. Only the immediately next one; the segments behind it stay hidden.

Segments let you pace a dense slide during a talk instead of putting all of it up at once, and this third one is here so you can see them chain. In print.html and print-notes.html they run together as one flowing body, so nothing is lost on paper.

Expansions 19, or a click on the chevron, opens one

Some chunks have extra detail behind a chevron button: click one, or press 19 for the n-th. This chunk has two of them – try both.

print.html and print-notes.html print every expansion as an indented aside where it stood in the source, so the reading copy loses nothing.

Zoom into a figure or code block click it, drag to pan, Esc to close

Click any figure, block of code or formula inside the chunk you are on. It opens in a card in the middle of the screen, with the slide dimmed behind it.

# Click this block to zoom it. Useful when a line
# matters more than the slide.
def anonymity_set(observations, senders):
    return {s for s in senders if plausible(s, observations)}

Inside an opened card: drag to pan, wheel or + - to zoom, 0 to reset, Esc or a click to close. With a speaker window open, the projection follows which card you opened, how far you zoomed and where you panned, so what you are inspecting is what the audience sees.

Selecting text hold Alt

In the projection, dragging pans instead of selecting, and holding Altoption on a Mac – turns selection back on. Hold the key and the slide becomes selectable and the cursor changes; let go and dragging pans again. The selection survives the key release so you can reach Cmd-C, and Esc clears it.

Finding content

A talk rarely runs in the order you planned so every slide has to be one move away

A question from the audience can send you forty slides back, and the back arrow is too slow for that.

Three panels reach any chunk directly. Which one you want depends on what you still remember about the slide, and none of them passes through the chunks in between.

  • Roughly where it sat – the overview board, O.
  • Which part of the lecture – the contents list, T.
  • A word that was on it – search, /.

Open the overview board O zooms out so you can see every slide at once

Press O now – the letter O, not the digit zero, which resets the zoom instead. The view zooms out to show every chunk at once, laid out in its columns, with an outline round the one you were on.

  • Drag to pan the board, wheel to zoom it.
  • Click a slide to go there – one click both picks it and leaves the board.
  • Arrow keys move the outline without landing, and the board follows, because the slide you want is often off screen.
  • O again or Enter lands on the outlined slide; Esc leaves without moving.

The board shows the shape of the lecture, which is usually enough to find the part you want. With a speaker window open, both windows enter, pan, zoom and leave together.

Open the contents list T lists the lecture's columns

T shows a list of every named column. Click an entry to jump there; T again closes the panel.

A column with no {#id} does not appear – the unnamed opening column that holds the title slide stays out of the list. The {#id} is also what a cross-reference points at: a [text](#some-id) link anywhere in the body finds it.

What goes on the slide

The audience and the reader need different amounts of text written once, cut two ways

A slide readable from the back of a hall holds a handful of lines, but a student revising for the exam – or you, teaching the course again next year – wants the explanation there was no room for. That second thing is what a lecture script is, and writing it separately means writing everything twice, in two copies that disagree by the second edit.

So there is one text, and every chunk is both versions of it at once. You write the argument in full; the projection shows a cut of it and the printed document shows all of it.

One chunk, two versions C switches between them

This chunk has more text in it than the slide is showing you: press C and the rest appears, press it again and it goes. Nothing was added – it has been in the source all along, and print.html has shown it from the start.

The projection opens in the short version, because that is the one the audience reads. The long one is for rehearsing, for looking something up mid-talk, and for whoever reads the lecture afterwards.

The cut shortens prose only: a list, a figure, a code block or a formula goes up whole in both versions.

Which of those sentences survive is decided per chunk, and you choose how. Either psi-slides works it out from your prose, or you mark the slide yourself. The next three chunks show both.

Option 1 – the default the slide is worked out from your prose: first sentences, plus the bold phrases

Unless you say otherwise, the slide is the first sentence of every paragraph plus any **bold** phrases from the rest. This chunk is written that way – press C twice and watch what appears and disappears.

It asks two things of you. Every paragraph has to open with a sentence that stands on its own, because that sentence is the slide. After it, a bold phrase becomes a bullet of its own, so it has to read as one. Everything unbolded is for print.html and print-notes.html.

The two bullets above are that rule applied: neither is a list in the source – each is a **bold** phrase inside a sentence the projection is holding back.

Bold selects, it does not stress. A bullet is set like the sentence above it, and one word inside a bold phrase is stressed with *em*, like this one. On paper the phrase is bold and the stressed word bold in the accent colour; style: {bold: …} and style: {print-bold: …} change either look for a whole lecture, and the chunk on the style: block lists the six.

That suits a chunk that argues, where every paragraph has a point to open with. It is the wrong fit when the chunk wants continuous explanation instead, and the next chunk shows the alternative.

Option 2 – explicitly set by you you mark which block is the screen

  • ::: slide marks the block that is the screen. Everything else in the chunk is what you say.
  • ::: script does the reverse: the chunk is the screen, and only the marked block is what you say.
  • Neither marked block is ever shortened, however long it runs.

You are reading the projector version of this chunk: the bullets above sit inside a ::: slide block and this paragraph does not. Press C and this paragraph appears; press C again and it goes away.

Use ::: slide when the slide wants tight bullets while the argument wants prose. A chunk with neither block behaves exactly as Option 1 does, and the next chunk shows the other half of Option 2.

Option 2, the other way round ::: script marks the narration instead

Press C twice on this chunk and watch one paragraph come and go while nothing else on the slide moves. That paragraph sits inside a ::: script block, which is the reverse of the last chunk: everything outside the block is the screen, and the block alone is what you say.

Use it when the screen half is the big half: the three made-up findings below are already the whole slide, and pressing C does not touch them. Wrapping them in a ::: slide block would mean marking nearly the whole chunk in order to exclude one paragraph, so marking that paragraph is the shorter way to say the same thing.

  • One request in seven is answered differently once the crawler is instrumented.
  • The gap is widest on the sites that serve the most third-party script.
  • It closes again if the crawler waits between requests.

This is the paragraph that comes and goes. It is what you would say out loud about those three lines, and the projection never gets it – under Option 1 its first sentence would be up there with them.

A ::: slide block wins wherever a chunk has one; failing that, a ::: script block puts everything outside itself on the screen; failing both, Option 1 applies. Three rules, checked in that order, on each chunk separately, so one lecture normally uses all three.

The chunk vocabulary

Chunk types

Only the {#id} is required, and a chunk written with no type counts as free. The type, the sub-heading and the width are all optional.

  • The line is ## type: Heading | Sub-heading {.width #id}.
  • The ten types: title, closing, outline, principle, definition, example, question, figure, exercise, free.
  • {.width} is narrow, standard, wide or full, and defaults to standard.
  • {#id} anchors links, the contents list and your reading position – rename one and those need fixing too.

What the type is for a word budget, a label in the document, a line over the heading

The type changes almost nothing on the slide, and it never sets the width – that is the {.width} class – but it does three things.

  • It caps how many words the chunk may carry, from 80 for a principle to 350 for an exercise; node lint.js reports one that runs over.
  • It labels the chunk in the printed views, in small capitals over the heading. The projection prints only EXERCISE.
  • It adds a small mark. This chunk is typed definition, hence the hairline above its heading; a principle gets a short rule there.

title, closing and outline each draw a whole slide instead.

Picking the wrong type is not an error; it shows on the overview board, where a principle typed as an example stops standing out.

Try the vocabulary three edits, with --watch running

Open lectures/tutorial/source.md with --watch running and change three things. Every save rebuilds the lecture and reloads every open tab, so keep the projection, the lectern view and your text editor visible at once.

  1. Change this chunk's type from exercise to principle. The label above the heading changes, and lint.js starts complaining: the budget has dropped from 350 words to 80.
  2. Wrap the list in a ::: slide block, then press C here. Everything else leaves the screen.
  3. Add a > note: line under the heading, then look at the notes pane in the speaker view and at print-notes.html.

Speaker cockpit

The audience and the speaker need different screens one file, two windows

What helps you through a talk – the notes, the clock, the slide that comes next – is exactly what the audience must not be shown. Putting any of it on the projection spoils the slide, and leaving it out means presenting from memory.

So the lecture opens twice, out of the same file. One window is the projection and the other is your lectern screen, and the two keep each other in step with no server between them.

Speaker view the second window, the one S opens

The speaker view is your lectern screen, in four bands. Press S here if you have not already – it opens speaker.html as a second window, and from then on the two windows talk to each other directly.

From the top edge downwards:

  • A row of dots, one per chunk in the column, each of them clickable.
  • A copy of the projection, laid out identically and at the same zoom.
  • A notes pane under it, which you can type into, and which folds away when the chunk has no notes.
  • A strip of slide thumbnails you can scroll and click.

The two windows stay in sync: they always show the same slide, at the same point in it. Which chunk you are on, how much of it is uncovered, your annotations, the theme, the font, the zoom, which expansion is open, the overview board, the opened figure and the laser pointer are all synchronised. V freezes the projection so you can read ahead without the audience following; unfreezing brings the audience to wherever you got to.

Arranging the speaker view resizing the panes, and where the thumbnails sit

  • Shift-V moves the thumbnail strip between the bottom edge and the right edge.
  • Drag the hairline bar above the notes to resize the notes pane. The slide above rescales to fit.
  • Drag the bar along the edge of the thumbnail strip to resize that too, in either position.
  • Double-click either bar to go back to the automatic size.
  • The and + in the corner of the notes scale the notes text, separately from the pane height.
  • ? opens the full reference. The footer has buttons for all of these.

The notes pane sizes itself: up to three lines of text, one line once you have emptied it, and folded away entirely on a chunk that has no notes. Once you drag it, the height stays where you put it and is remembered across lectures and reloads. The slide above gives up exactly the space the notes take, so the copy of the projection keeps the projector's proportions instead of stretching.

Put the thumbnails down the right-hand side if the screen has width to spare: they get larger and their text becomes readable, so you can read ahead in the strip instead of only reading your position off it. The strip's height and its width are remembered separately.

Two kinds of note one the audience sees, one only you see

An annotation is public, speaker notes are private.

N in either window writes an annotation on the chunk you are on. While you type, the note is the slide: it fills the frame, a single word stands large and centred, several lines stand as a block, and an address in it gets a QR code the room can scan. Whatever you write appears in the other window as you type it, and Esc leaves it beside the text as a margin note. Use it for the things a talk produces: the word that turned out to be missing, a question from the audience, a correction, a link.

Annotations are kept in the browser, one set per lecture. Shift-E in the speaker view copies all of them to your clipboard as > annot: Markdown; paste that under the matching chunk heading in source.md, run node build.js <source.md> --integrate-annotations, and the text becomes permanent – already in the typing box next time, and printed under the chunk in print.html and print-notes.html.

Shift-N in the speaker view opens the private notes pane below the slide. This one is yours alone and never reaches the projection. It arrives filled from the > note: … lines in the source; anything you change during the talk overrides that text and is kept in the browser, per chunk.

If the pane is folded away because this chunk has no notes, the + note button in the corner of the slide does the same as Shift-N.

print-notes.html is the third place the same text appears: the document with every > note: folded in under its chunk. That is the file to hand out when you want what was on the slide plus what the lecturer said.

Your notes as cue cards K, and what to write so it reads from the corner of an eye

A talk with a written-out script and minimal slides needs the script where you can glance at it. K in the speaker view rearranges the window: your notes for this chunk as cards down a rail, the projection small in the corner, the clock in the header. Space says the next card; when the cards of this beat are said, it clicks the projector, and the clicks stand in the same column as diamonds, so you read one list from top to bottom. Backspace takes one press back, whatever it was. Enter skips to the next slide.

A paragraph in a > note: is a card, and its bold phrases are the bullets. Bold the words you want to see, not the words you want to stress. A paragraph with no bold shows whole, in smaller type. A #### Title above a paragraph titles the card, and @12:30 on it is when you meant to reach it: the drift stands beside the clock.

Where the note stands is when it is said. A note before the first --- belongs to the beat the slide opens on, a note after it to the beat that --- opens. Open the speaker view on this chunk, press K, and walk it with Space: the cards under each diamond are the notes written under the matching --- in the source. A chunk whose notes all sit at the end shows them all on the first beat, so a lecture written before this feature does not move.

Changing how the lecture reads C F A and zoom

Single keys change how the lecture reads, and each one applies to both windows at once.

  • C switches between what the audience sees and the full text.
  • F cycles the font: serif, then sans, then monospace, for legibility across a room.
  • A cycles the theme: four light ones with different accent colours, a neutral dark one, and two green-and-amber terminal ones.
  • + - 0 set the text size; # cycles auto-fit through its three modes, which is worth trying right here – this chunk is longer than the screen.
  • B blanks the projection.
  • D puts a live demo on the projection: a window or a screen of this machine, chosen in a picker, until D again. The very first capture on a Mac fails while macOS asks for screen-recording rights – allow it and press D again, so do that once before the talk.
  • L cycles the slide numbers: stacked, in a row, or off.

Shift with C, F, A or L goes backwards. # has three modes and no Shift, because it is a shifted key on some keyboards and an unshifted one on others. Font, theme and slide numbers are remembered for every lecture you open, so the preference follows you; zoom and the C setting are not remembered beyond the talk you are giving.

The same controls without a keyboard the toolbar on a phone or tablet

On a phone or a tablet with no keyboard, both windows show a small toolbar along the bottom edge. Forward, back, overview and zoom sit on it; C, F, A, #, the search and text selection are behind its button. Attach a keyboard and the toolbar goes away again, because the keys are back.

What the keys remember themes, the two zooms, auto-fit and blanking

Dark mode follows your machine unless something says otherwise. If you have never pressed A and the lecture pins no theme, a machine set to dark opens the lecture dark. Press A once and your choice is remembered from then on, everywhere. An author who writes theme: in the frontmatter overrides both, by the same rule as the other opening settings.

The two C modes keep separate zoom levels. The short version holds whatever size you set with + and -; the full text picks its own so the whole chunk fits the screen, and switching back restores yours exactly.

# cycles auto-fit through three modes, and the middle one, shrink, leaves your zoom where you set it and only makes a slide that is too big fit. So the audience reads one size all hour, except on the slides that would otherwise run off the bottom. Full sizes every slide to the screen, growing a short chunk as readily as shrinking a long one, which suits a lecture whose chunks vary a lot. Off is neither.

While the audience sees black, the speaker window keeps everything. The slide, the notes and the thumbnails stay where they were, so you can move on or read ahead with nothing showing. A small BLANK · hit B to toggle marker sits at the bottom of the speaker window, or at the bottom of the projection when there is no speaker window. D has the same shape: pressed in the speaker window, it opens the browser's picker there, and the window or screen you choose fills the projection while a DEMO · hit D to end it marker sits where the blank marker would. That is what a live demo on an extended desktop needs – the demo stays on the laptop, where the pointer is, and nothing has to be mirrored.

Authoring layouts

Two decisions make a layout a width class on the heading, and ::: blocks in the body

A layout is two independent decisions: how wide the chunk is, and how its body is arranged inside that width. The heading picks one of four widths – {.narrow}, {.standard}, {.wide}, {.full} – and ::: blocks in the body do the rest.

A .wide chunk with a ::: side body is the usual shape for a figure with commentary beside it. The width is the decision about the slide, and the blocks work inside it.

Text across two columns ::: cols 2 and ::: cols 3

::: cols 2 (or cols 3) flows the body across that many columns, the way a newspaper page does. Use it when several short paragraphs read better side by side than stacked – a list of features, a brief comparison, two or three parallel definitions.

Left column. The browser balances the columns for you: it fills from the top and breaks wherever the text allows. Do not put one long paragraph here, or one column fills and the other sits empty. Several short blocks work best.

Right column. This block is the third paragraph in the source, which is why it landed on the right – the text runs down the first column and then wraps into the second. In print.html and print-notes.html the columns become one ordinary sequence of paragraphs.

Columns fold to one while the slide is short – press C here and the two above stack. Shortened, each paragraph is down to its opening sentence, and a browser will not split a paragraph across columns, so two single sentences of different lengths do not balance. The full-text mode brings them back, and so do print.html and print-notes.html.

Revealed segments – the --- lines that uncover a chunk a piece at a time – work inside ::: cols, but text uncovered piecemeal while it also flows across columns is hard to follow: pick one or the other.

Two panes you fill yourself ::: side and ::: flip

::: side makes two panes side by side, and ::: flip marks where one ends and the other begins. Unlike cols, you decide what goes where: everything before ::: flip is the left pane, everything after it the right. Use it for a figure with its commentary, or for a before-and-after pair.

Left pane. Write ::: side, then the left content, then ::: flip, then the right content, then ::: to close. The two panes are equal halves unless you say otherwise, so neither side takes over the slide.

Right pane. A figure usually goes here with the text on the left. On the projection, click either pane to open it large; print.html and print-notes.html stack the two panes one above the other, so neither is lost.

Code in a pane needs short lines. A code block never wraps, so at the default zoom a pane holds about 36 characters against the 78 a block across the slide holds – and that 78 is the same whatever width the chunk is, because a code block of its own breaks out of the text column and spans the slide. A longer line is not cut off; the build shrinks that one slide until it fits, and the slide then reads noticeably smaller than the ones either side of it. Break the line, or put the code across the full width and keep the panes for prose.

Marginalia ::: marginalia puts an aside in the slide margin

::: marginalia sets an aside out to the right of the chunk, past the edge of the text column and into the slide's margin.

A marginalia is the one aside you can click: the frame slides right until all of it is on screen. A figure or a block of code opens in a card in the middle of the screen; a marginalia gets no card, because it is part of the slide's layout rather than something laid over it. Esc, or a click on the slide, moves the frame back. Try it on the block out to the right, the part of it the edge of the screen has cut off.

The body stays in the middle column and only the marginalia moves outward. Keep them short: a marginalia shares the chunk's height and cannot grow taller than it. One can also go inside a ::: side pane, when a tangent belongs to one half in particular – it still goes to the slide's right margin.

Footnotes ::: footnote is a small note under the chunk

::: footnote puts a small grey note under the chunk, labelled and always visible – down in the flow of the text rather than out at the side. No button, no separate panel, nothing to click.

A marginalia goes out into the margin and can be brought to the centre with a click; a footnote stays under the chunk and is read where it stands. Use ::: footnote when the extra material is short and you want it on the page every time, and for ::: expand <label>, the chevron button from earlier, when it should stay behind a button until somebody asks.

Images ![Caption](fig-id) resolves against assets/

Write ![Caption](fig-id) and the build looks in assets/ for fig-id.svg, .png, .jpg, .jpeg, .gif or .webp, taking the first it finds. No folder, no extension. Writing the path out in full still works when you need it.

Whatever you write in the square brackets becomes the caption under the picture. The one beside this paragraph is ![An abstract dusk skyline](dusk), and the small grey line under it is that text – which is also the image's alt text, so a screen reader reads the same words. Leave the brackets empty and the picture stands on its own. On a figure: chunk whose heading already says what the picture is, a caption stacks two labels, so the checker warns and suggests leaving the alt text out.

A drawing saved as SVG is written into the page as artwork, not as a picture file, so it takes its colours from the theme and changes with the A key. Photographs, and pictures like this skyline that carry their own colours, are embedded exactly as they are.

An abstract dusk skyline

As long as your pictures are small, the build puts them inside the HTML, so the whole lecture stays one file. The chevron has the limits, and what happens to a picture over them.

Video ![](clip-id), the same shorthand an image uses

Drop clip.mp4 into assets/ and write ![](clip) – the same shorthand an image uses. The build looks for video files after image files, so an id that has both a still and a clip behind it gives you the still.

That player is a real clip embedded in this HTML file, a pan across the overview board. Press play, then check the address bar – nothing was fetched. The file is 78 KB and it costs 104 KB here, because a data: URI is base64 and base64 is a third larger than the bytes it carries – which is the price of the whole file being one file.

More on videos the size limit, clips on a server, and what a click does

Play, pause and seeking are shared between the windows. Operate the clip at the lectern and the projection follows. Freeze the projection first and it does not, so you can check a clip before showing it.

A clip goes inside the HTML like any other asset, up to its own limit of 12 MB. A clip is an order of magnitude heavier than a diagram, and the 2 MB picture limit would reject every real one.

Over that limit the clip is stored beside the file instead. The build copies it into a videos/ folder next to the output, plays it from there, tells you on the terminal, and suggests an ffmpeg line that would make it small enough to go inside. One named folder to copy along with the HTML, instead of a path that only works on the machine that built it.

A clip can also live on a web server: ![](https://host/clip.mp4) works and stays an ordinary player, so play, pause and seeking are still synchronised between the two windows.

There is no fullscreen setting: the player has its own button, and how large the clip sits on the slide is the chunk's width, as with a still picture. Clicking a clip does not open it in a card either, because that would conflict with the play button.

Hosted players ::: embed for YouTube and Vimeo

A hosted player is written as ::: embed, and a bare link never becomes one. This is the only thing you can write that makes a lecture fetch from somebody else's server while you are teaching, so you say so in the source:

::: embed https://www.youtube.com/watch?v=aqz-KE-bpKQ
Big Buck Bunny, Blender Foundation
:::

The line under it becomes the caption. A youtu.be/… or a bare vimeo.com/123 works too; anything else has to be a full https:// address, and the build refuses what it does not recognise.

The address is always printed under the player, with a QR code on Shift-click, so people can reach the video even when the player will not run. YouTube is asked for through youtube-nocookie.com, and Vimeo is asked not to track.

A lecture with a hosted player no longer contains everything it needs: the machine showing it – often the lecture hall's own PC – contacts that company while you teach. A clip in assets/, or an .mp4 address on a server you control, keeps the two windows in step and needs no other server. The build tells you which of the two you have chosen.

More on hosted players what the directive does that an embed code would not

  • Nothing loads until you get there. The player points at the video only while its chunk is on screen.
  • Play and pause are shared between the windows, as for a local clip. Freeze the projection and it stays put.
  • Nothing starts by itself. Arriving at the slide gives you a loaded player waiting on its button.
  • A player that cannot run is replaced by a card that says why. A page opened from disk has no web address and YouTube will not play; Vimeo does.

To teach with a YouTube video, serve the lecture:

node build.js <source.md> --serve         # prints the URLs
node build.js <source.md> --watch --serve # and live reload

Math $inline$ and $$display$$

Formulas are typeset when the lecture is built, so the finished file needs nothing at the moment you show it. Maths inside a sentence goes between single dollars – the anonymity set SS has size S|S| – and a formula on its own line goes between double ones:

d=H(S)log2Sd = \frac{H(S)}{\log_2 |S|}

A formula on its own line behaves like a figure: it stays on screen when the prose around it is shortened away, and clicking it opens it large for the audience.

This chunk carries {.blocks-left}, which is why the formula starts where this sentence starts. A code block, a figure and a display formula are centred by default, and style: {blocks: left} says otherwise for a whole deck. Centred is right when the block is the slide; on a slide that is an argument with a formula inside it, three blocks on three axes is what you get instead. Maths inside a sentence follows that sentence – on screen in an opening line, gone with everything else.

A lone dollar sign is safe. The delimiters are read as Markdown, not searched for in your text, so $PATH inside code, a price of $5 and $10 in prose, and a $$ inside a code block are all left alone. Write \$ if you want to be explicit.

Only the mathematical typefaces your formulas use are embedded in the file. The build prints what that came to: for this lecture, about 120 KB in each printed view and 166 KB in the projection, against the 254 KB a complete set of KaTeX faces would take.

The maths follows the F key. Switch the body font to sans or monospace and the formulas change with it instead of staying serif while the text around them changes. Only the letters change: operators, relations and brackets keep their own shapes, and a character the sans face does not have falls back to the mathematical one. That is where the projection's extra 46 KB goes – the printed document has no F key and carries no faces for it.

Writing chunks that work

How to write a chunk that works one paragraph per point, and the first sentence of each is the slide

A chunk that works is written one paragraph per point, because the projection shows the first sentence of every paragraph and nothing after it. It is not one topic sentence per chunk. It is one per paragraph, in the order you wrote them, plus whatever you set in bold.

There are two ways to write such a chunk, and both end in the same place:

  • Prose first. Write the argument as paragraphs, then sharpen the opening sentence of each until it states that paragraph's point on its own.
  • Outline first. Write the opening sentences as an outline of the argument, then write the paragraph under each one.

Whichever way, each opening has to be a claim that survives having its paragraph taken away. Everything after it is the supporting text, which appears in print.html and print-notes.html and never on the projection.

Every thought the argument depends on, and every explanation it needs, starts a paragraph of its own. Two of them sharing a paragraph means the second one never reaches the audience, however well it is written.

What breaks a shortened chunk bold as a label, bold on one word, a weak opening, substance after a colon

Most chunks that read badly on the projector fail in one of four ways.

  • Bold used as a label. **Consequence:** shortens to a bullet reading “Consequence” and nothing else. Put the claim inside the sentence.
  • Bold on one word. A lone **not** becomes a cryptic bullet. Bold a phrase that stands alone, or bold nothing.
  • An opening that only connects. “That was deliberate.” carries no claim. Say the thing itself in the first sentence.
  • The substance after a colon. If it sits after a colon at the end of the opening sentence, the cue dangles. Rewrite as one sentence.

All four read fine inside a paragraph and fall apart the moment the paragraph is taken away, so they show up when you walk the lecture once in the short view before you teach it.

When several parallel items pile up inside one paragraph, write a real Markdown list instead of scattering bold through the prose. A list stays readable when it is shortened; a paragraph with bold scattered through it almost never does.

The squint test walk your own lecture end to end in the short view

Open your own lecture in the projection, press C until it is short, walk it end to end without opening the source, and stop at every chunk you could not talk from using only what is on the screen.

For each chunk you stopped at, ask three questions in this order:

  1. Is the opening sentence a claim, or a warm-up?
  2. Would each bold phrase read as a sensible bullet on its own?
  3. Is there a list hiding inside a paragraph?

If all three answers are fine and it still reads badly, mark the slide by hand. Option 1 holds up while a chunk is an argument of one to three paragraphs; once it wants continuous prose, a ::: slide block is the shorter route.

Next steps

Start from a talk you have already given the text already exists, so the work left is cutting it into chunks

The first lecture takes the most effort, because you are still learning the vocabulary, so start with a talk you have already given. Its text already exists. Most of the remaining work is deciding where one chunk ends and the next begins, and the vocabulary you have just read is all you need for that.

The steps are the same for every lecture – write the prose, sharpen the opening sentences, run the checker, then walk the lecture once in the short view before you teach it.

Read more three finished lectures to open

psi-slides comes with three finished lectures. Open them, and take whatever you need out of their sources.

1. A 36-chunk teaching lecture: lectures/python-intro/audience.html. Open its speaker window with S and watch the layout vocabulary you have just learned in real use, running through segments, expansions and opened figures.

2. Every construction that puts something other than a column of text on a slide, one per slide: lectures/decoration/audience.html. The covers, the six kinds of divider and the three kinds of divider content, cards and rows, a backdrop whose window opens on a keypress.

3. Every ::: draw statement drawn rather than described: lectures/diagrams/audience.html. Real lecture figures are among them.

Writing your own --new, --watch, lint.js

These are the commands you need while writing a lecture:

  • node build.js --new <slug> makes a lecture folder with working frontmatter and two chunks. It builds the moment it lands on disk.
  • node build.js <source.md> --watch rebuilds and reloads every open tab on every save.
  • node lint.js lectures/ checks what can be checked without building: unknown types, unclosed ::: blocks, repeated ids, word budgets, too many segments, columns with only one chunk, captions that repeat the heading. --strict turns the warnings into failures.

A source file can switch one check off with <!-- linter: ignore reveal-overuse, density --> anywhere in the body. It has to be ordinary text to count: inside a code block or between backticks, as in the sentence you are reading, it is an example and not an instruction. This lecture carries a real one at the top, for density, and says there why.

Deciding how a lecture opens seven frontmatter keys, and lang: beside them

A lecture can set its own starting look instead of inheriting whatever the reader last chose.

---
title: Anonymous Communication
font: mono              # serif | sans | mono
theme: terminal-green   # light-{red,teal,blue,orange}
                        # dark | terminal-{amber,green}
collapse: none          # topic-bold | none     – the C key
auto-fit: shrink        # true | false | shrink – the # key
slide-numbers: off      # vertical | horizontal | off
print-slide-numbers: vertical
                        # the same three. Left out, it follows
                        # whatever slide-numbers says
editor: speaker         # both | speaker | none – the diagram editor
---

The language, and which setting wins lang: and the rule for every key above

lang: de                # the language the lecture is written in:
                        # en, de, de-DE, fr and so on, and en
                        # when you leave it out

lang: picks the hyphenation dictionary, and by default only the two printed views use it: a long German compound breaks at the end of a line there instead of leaving a hole, while the projection and the lectern view do not hyphenate. style: {hyphenate: all} puts it into the projection too, which a German lecture at .narrow usually wants, and none takes it out everywhere. It is not one of the six above in the other sense either – the six are opening settings that override whatever the reader last chose, and the language is a property of the lecture.

A key you write beats whatever the reader last chose, and a key you leave out leaves that choice alone. So a lecture that sets nothing behaves as before – font, theme and slide numbers follow the reader from lecture to lecture.

slide-numbers applies to print.html and print-notes.html too, and print-slide-numbers: overrides it there when the printed document wants different numbering from the projection. A value the tool does not know stops the build and lists the ones it does.

Beyond 1.0.0: figures

Everything from here on is newer than the 1.0.0 download, so build these two parts from a clone of the repository, and expect them to change before they are tagged into a release.

A figure written as text is a figure you can still change you say what sits beside what, and the placing is worked out

A drawing made in a drawing tool is finished the moment you export it: it does not follow the theme, it cannot arrive a piece at a time, and a fact that changes means opening the tool again. Written as lines in the lecture source, a figure is versioned with the prose, re-coloured with the page and revealed one beat at a time.

What you give up is placing anything by eye. You name the boxes and say which one sits beside which; where they actually land is not your decision.

Diagrams the figure below is five lines of source

A ::: draw block is a figure written as text. The build draws it into the page: you name the boxes and say where each one goes, and the arrows between them are routed for you.

encrypted recoded Sender Mix Receiver

That drawing is these five lines and nothing else:

box src "Sender"
box mix "Mix"       right of src gap 2.1
box dst "Receiver"  right of mix gap 2.1
edge src -> mix "encrypted"
edge mix -> dst "recoded"

The first element sits at the origin, so a simple figure needs no coordinates. Everything after it is placed against a neighbour – right of, left of, above, below – and gap says how far. There is no automatic layout: an element goes where its neighbour and its gap put it.

A figure that arrives in pieces a step block advances on the same key as a reveal

Write step blocks and the figure changes with each forward press. One step is one press of the same key that uncovers a segment, so steps and segments arrive in the order you wrote them and the speaker view reads ahead exactly as it does for text. Press forward twice here.

encrypted recoded Sender Mix Receiver Logfile this is wherethe anonymity ends

What a step block says the source of the figure on the previous slide

The figure on the previous slide is the three boxes from the slide before it, with a logfile added and four lines at the end.

box  src  "Sender"
box  mix  "Mix"        right of src gap 1.05
box  dst  "Receiver"   right of mix gap 1.05
box  log  "Logfile"    below mix gap 0.9  {.dashed}
edge src -> mix "encrypted"
edge mix -> dst "recoded"
edge leak mix -> log {.dashed}
text why "this is where\nthe anonymity ends"  right of log gap 1.4 -- leak {.hand}
step leak
  show log
step blame
  emph leak, log

step leak shows the logfile, and step blame emphasises the leak and the box it runs to. The words a step knows are show, hide, move … to, move … by, the three attention verbs emph, dim and ghost, plus style and label.

Anything attached to something invisible is invisible too, which is why step leak names only the logfile. An arrow is only as visible as the two things it joins, a container or a brace only as visible as its members, and a text with a line drawn to something only as visible as what it points at. So showing the boxes shows the arrows between them, and most of a figure needs no show of its own.

Words that arrive with a step --- from N pins a beat by number

A beat is taken in the order it was written, and that is wrong for one shape: a figure that steps in one pane and the words about it in the other. Written plainly the figure's steps come first and the words queue behind them, because document order is the only order there is. --- from N pins a beat to an advance by number instead, so the two halves move together.

request cache origin

The request reaches the cache first, and most of the time that is the end of it.

A hit is answered from the cache, and the dashed line is never walked.

A miss walks it, and the origin pays for the request instead.

A pinned beat rides one the slide already has rather than adding one, so this chunk takes two presses, not four. from 0 is refused – that is the beat the slide opens on, so write the words above the marker – and so is a from inside an ::: overlay from N or a ::: dock from N, which numbers its own markers already.

Every line has the same six slots kind name label placement options tail

Every line in a ::: draw block has the same six slots, in this order, and most lines fill three or four of them:

box   mix   "Mix"   right of src gap 0.6   w 1.2    {.tone-2 @crypto}
kind  name  label   placement              options  tail

The name is how later lines refer to an element and is never drawn; the label is what the audience reads, and "" is a legal empty one. A name is letters, digits, _ and -, and a line starting with # is a comment.

Inside the tail, the first character says what a word is: . starts a class and @ starts a tag.

  • .tone-2 is a class, which says how the element looks. {!tone-2}, with an exclamation mark, takes one off again.
  • @crypto is a tag, which says what set the element belongs to.

A tag goes wherever a name goes, so show @crypto in a step selects every element that carries it. An element joins a set on its own line, which makes adding one a one-line edit.

Where an element goes a grid square, a neighbour, or another element's coordinate

Placement is a grid square, or a relation to a neighbour.

  • at 2,1 puts an element in a grid square.
  • right of mix gap 0.6 places it against a neighbour, as do left of, above and below.
  • between a,b is the point on the line joining two elements.
  • offset dx,dy is a nudge any of the three accepts on the end.

A coordinate can be another element's fractions, edges and pictures

A coordinate can be another element's, plus or minus a littleat mix.cx,src.cy+0.4. Anywhere an X,Y pair goes, that form goes.

An anchor can carry a fraction: mix.right:0.3 slides the attachment point along that edge, so two arrows between the same pair of boxes run side by side rather than on top of each other. gap 0 flush left at the end of a placement makes two boxes touch.

An edge is one of the things a coordinate can name. text n "only after the handshake" above w1 gap 0.2 sets a phrase against the wire it describes rather than against a box at one end of it, so the label follows its line instead of drifting off it the next time a box changes height. Name the edge first, in the slot before the arrow's first end: edge w1 mix -> log. An edge has no name until you write one, and most edges never need one.

A picture can be an element too. image alice avatar-alice w 0.4 finds the file exactly as ![](fig-id) does, and an SVG drawing takes the theme's colours there in the same way.

Classes thirteen groups, and one question each

Only one member of a group is in force. The names come from a fixed list, and {.tone-1} on a box therefore replaces a default box {.tone-4} rather than piling on top of it, which is what makes the groups worth knowing.

paper tone-1 tone-2 tone-3 tone-4 clear fill round sharp hex chevron wedge cross diamond outline dashed dotted thick bare ghost dim stroke,and presence sans bold mono serif hand small large family,and size a label that is too long short shrink fit type meetsits box topleft centred bottomright turn where the words sit

Lining things up flush, align and spread

Three words put elements level with each other, and they are not interchangeable.

  • flush finishes a placement and takes one word: below src gap 0 flush left keeps the new box's left edge level with src.
  • align is a statement on a line of its own. align y middle a, b, c gives b and c the vertical centre of a; the first name is the one the others follow.
  • spread x a, b, c, d shares a set out evenly. First and last stay put; everything between gets the same distance from its neighbours.

The two statements are both used in the sheet on the last slide: an align x right gives the five row labels the right edge of the first, and a spread x puts the five middle words of the family row between sans and bold.

Charts bars, grid and plot

Three statements draw data, and each turns into ordinary boxes, texts and edges first. bars becomes one box per column plus a baseline, grid one box per cell, and plot a frame of gridlines, ticks and two axis titles.

the three to rewrite budget words per chunk one dot per chunk,tinted where a figure lives

Everything a chart draws is an ordinary element with a generated namewc-0, wc-1, … for the columns and ch-1-0, ch-4-2, … for the cells – which is why the brace and the tinted cells above needed no vocabulary of their own.

More on bars a second series, and columns laid flat

A second set of numbers is one more bars line: bars after "…" series of wc {.tone-1} joins the first chart's frame and borrows its ticks, its baseline and its scale. key "2024" on either line names the run, and the chart draws the legend itself.

Columns laid flat horizontal, and when to use it

horizontal lays the columns flat, which is what a chart wants as soon as its categories have names rather than numbers – lengths from one shared left edge are easier to rank, and “DNS cache poisoning” cannot be written under an upright column at all.

writing the prose drawing the figures fixing one wording fighting the tooling minutes, in the hour before a lecture

Plots plot draws a frame and a scale, and nothing else

A plot draws the frame and the scale; you write the curves over it.

0 10 20 30 40 50 60 0 10 20 30 40 minutes into the talk chunks covered even pace the first thirdalways runs long

What a plot gives you a frame, a scale, and ordinary edges over it

A chart is sized with aspect, not with w and h – those two are counted in grid squares, and a grid square is not square, so they do not describe the shape a reader sees.

A plot takes two ranges and one tick interval – after which pace@26 names a value in the plot's own units anywhere a coordinate can go, and the curves over it are ordinary edges.

A figure that moves hide, dim, move and label, inside a step

A figure with steps is an argument in stages – the setting, the intruder, the cut wire, and what it costs. Press forward three times.

the intended channel M M M′ Alice Bob EveEve rewrites M no cipher is broken here –she just stands in the middle

Every element after the first is placed against another one, so nothing comes apart when the middle box moves in. move eve to between alice,bob states a position, move alice by -0.5,0 shifts an element by an amount, and the whole figure is laid out again at every step – so Alice and Bob step aside, the container re-fits around them, and the arrows are drawn wherever their ends have gone. hide takes the direct wire away, dim is the opposite of emph, and label swaps in wording that was typeset when the lecture was built.

Two tags do all the revealing: @attack and @cut. show @attack brings Eve in and the handwritten caption with her, because both lines carry that tag; show @cut brings the three arrows through her a step later. The pair running to Bob leaves Eve's right edge at :0.2 and :0.8, a fraction along a side being how two arrows between the same two boxes run parallel instead of on top of each other, and side top and side bottom put one label above its line and the other below.

A figure that advances on a timer ::: draw autoplay N

A figure written with autoplay advances its own steps on a timer once the slide is on screen – one delay, in milliseconds, for every step. A cover figure that moves while the audience files in is the case it was asked for, but it works on any chunk.

::: draw 150x56 autoplay 1200
box crawler "Crawler" {.tone-1}
box det "Detector" right of crawler gap 1.6
edge crawler -> det "request"

step probe
  emph det
:::

The timer presses the same key you would press, so the speaker view follows and freezing the projection stops it. It runs on the projection only, and the first key, click or scroll on that slide stops it – once you have touched the figure you have taken over. It also refuses to start on a slide that is already half uncovered.

What the timer promises the bounds, and who takes over

The delay has to be between 200 ms and 60 s; outside that the build refuses the number rather than quietly moving it.

cycle repeats the run of stepsautoplay 1200 cycle – which is usually what a cover figure wants while the audience is arriving. It rewinds the same way it advanced, so the speaker view follows the rewind too. The last step is held for one delay like every other, and there is no second number for how long to hold the finished picture.

The figure below is running now, written exactly as the block on the slide before with cycle added. Press any key and it stops where it stands – that is the take-over, and it is why you can read the rest of this slide without it moving underneath you.

request Crawler Detector

Beyond 1.0.0: slide decoration

Newer than the 1.0.0 download as well, and out of the same clone.

A slide can be more than a column of text and what makes it one is not written inside the text

Write a picture into a chunk and you get a picture in the text column – not one that fills the slide, and not three things standing side by side.

Three kinds of construction sit beside the body rather than in it: a picture behind the slide, blocks in place of the paragraphs, and the slides a lecture opens and closes with. The rest of this part takes them one at a time, in that order, and all of them are shown one per slide in the decoration lecture.

A picture that fills the frame ::: backdrop and ::: overlay

::: backdrop puts a picture behind the whole slide, edge to edge, and ::: overlay puts a block of text on top of it. One line each, on any chunk – a cover is not a special case. A backdrop names its picture the same three ways an image does: a bare asset id, a path, an https address.

## figure: {#skyline .full}

::: backdrop city-at-night {.invert .blur}

::: overlay {.bottom-left .ink .wide}
### Every endpoint is a sensor
A crawler that looks like a browser gets measured back.
:::

The words in the braces five questions for a backdrop, three for an overlay

A backdrop's braces answer five questions, at most one word each. How the picture fills the frame – it covers the slide, or it fits inside it whole. Which part of it survives the crop. What is laid over it. Whether it is sharp or blurred. And whether it sits under the type or in front of it. Two words answering the same question is an error, and the message names both.

The default is veil: the theme's own paper at 80%, so ordinary dark text stays legible on a photograph in all seven themes. invert darkens the picture and turns the text light instead – the next slide is one.

An overlay answers three: where on a 3×3 grid, what it sits on (paper, ink, accent, clear or glass) and how wide. Every one is a card with padding and rounded corners, because text laid straight onto a photograph is unreadable at the back of a room.

The decoration lecture has a slide for each of the two lists, and a backdrop whose window opens on a keypress.

A picture behind the words what the two blocks on the last slide produce

The backdrop is the slide's ground
and this block is an overlay, placed on a 3×3 grid.

A row of cards ::: cards N

::: cards 3 puts three separate boxes across the slide, and an item sits in one of them whole and never spans two. A ::: cols 3 block does the other thing: one run of text the browser shares across three columns, so a paragraph can spill from the foot of one into the head of the next.

  • cards
    • N containers side by side
    • a three-item comparison reads as three things
  • rows
    • the same container turned ninety degrees
    • a term, with its body beside it
  • cols
    • one text flow balanced across N tracks
    • a paragraph can spill from one into the next

That row is one Markdown list between ::: cards 3 and :::, and each card has a second level folded away under it – press C and it appears. The folded level is in print.html and print-notes.html either way, so one row serves the audience and the hand-out.

One rule decides what becomes a card: write a single list and each of its items is a card; write anything else and each block is a card. The count runs from 1 to 6 – one card is a callout you want to stand apart, and past six what you have is a table.

The two ways to open a card a lead-in, or a heading

How you open a card decides what the bold does, and the two below are written the two ways:

  • A lead-in is written on the same line as its text, so the bold runs into the sentence and the card reads as one paragraph
  • A heading
    is written before a line break, so the bold sits on its own line with the text under it

Use cols for an argument that runs long, and cards for a comparison the audience should be able to count.

What the words in the braces do the seven that set a card row's look

Seven words in the tail set the look of a card row, written bare between braces, at most one per question – a second word answering a question already answered stops the build.

  • ground – what the card is made of: a tint, a hairline, the page, or nothing.
  • anchor – where the text sits when the card is taller than its content.
  • corner, detail and scrim – the radius, the small print, and what is laid over a picture.
  • size and align decide themselves: the longest item picks the size, and the alignment follows it.

One ground for a whole row accent, paper, clear

ground is answered once for a whole row, so three grounds means three rows, each written ::: cards 1 {…} with its own word. panel is the default, a tinted fill; outline is a hairline and no fill; photo makes the card's first picture its background, and scrim says what is laid over it.

  • accent – the theme's own colour, with the text in the page colour on top
  • paper – the page colour, so the card stands out from whatever is behind it
  • clear – no box at all, so the gap is what separates one card from the next

A tail that answers twice ::: cards 3 {.outline .middle}

  • outline
    a hairline and no fill, which is less prominent on a slide that already carries a figure
  • middle
    this text is centred against the tallest card. In the row above it sits at the top
  • never both
    a fill inside a hairline reads as a form field rather than as a card

That row is ::: cards 3 {.outline .middle}, so its tail answers two questions at once: ground, and anchor – where the text sits when the card is taller than its content, which it always is, a grid row being as tall as its longest card.

A term and what it means ::: rows

::: rows is a card turned ninety degrees: a term in a card on the left, its explanation beside it, several of them stacked.

  • SeparatismEngineers do the technical work; managers take the decisions
  • TechnocracyEngineers should take them, because they understand them
  • DeferenceEngineers name the options and say what each one costs

What a row block does differently no count, and three defaults of its own

That row is ::: rows {.accent} around one list, and every term gets the same column width, so the explanations line up down the slide however long the terms are. The explanation is optional – a term written on its own is a labelled row with nothing beside it, which is what an agenda or a list of names wants.

It takes no count, a row block having one column by definition, and it takes every word a card row takes. Three defaults differ: the text is centred against its term rather than against its first line; align says how the term sits in its card, and the explanation always ranges left; and the automatic size stops at medium, a term being a label in a column rather than a headline across the slide.

Use rows when a term needs a sentence, and cards when a comparison needs counting.

A figure beside the prose ::: side 2:1

::: side takes an optional ratio, so the two panes need not be equal halves. This slide is ::: side 2:1: two parts of prose to one part figure, which is the shape a diagram with its commentary usually wants. Any two numbers work, ::: side on its own is equal halves, and ::: flip starts the second pane.

That drawing is a ::: draw block inside the second pane. In print.html and print-notes.html the two panes stack one after the other and the ratio is ignored, because a page has only one column to give them.

A short pane sits at the top of its half unless you say otherwise, and {.middle} centres it against the taller one. Here the figure is the short pane, so {middle} is what puts it level with the middle of this column instead of at the top. {top} is the default and often right – a caption over a figure should be aligned from the top. The word belongs to the block and not to either pane, because the taller pane is what makes the row tall, so centring can only move the shorter one.

A figure above or below the text needs nothing – put the block first or last in the chunk body. ::: cols is the one place a figure does not belong: a figure breaks the run of text the columns share, so the columns stop working, without an error. A ::: draw written there is refused, and the message points you at ::: side.

request Crawler Detector

Setting the typography for a whole lecture the style: block

The style: block holds the settings you make once for a whole lecture rather than chunk by chunk.

style:
  headings: left        # auto | left | center | off
  rules: off            # on | off  – the hairline over a principle
  labels: off           # on | off  – the type word over a chunk
  link-codes: off       # on | off  – the mark after an external link
  blocks: left          # center | left – where a code block,
                        # a figure and a formula sit
  wrap: none            # balance | none – even line lengths,
                        # in headings and in prose alike
  print-body: sans      # serif | sans – the printed document's face
  bold: accent-bold     # plain | bold | italic | accent | accent-bold
                        # | accent-italic – a **bold** phrase, live
  print-bold: italic    # the same six – a **bold** phrase on paper
  heading-scale: 1.15   # 0.6 … 1.8
  body-scale: 0.95      # 0.6 … 1.8

headings: auto is the default, and it means the type decides: a question is centred, a figure's caption sits over its artwork. left overrides all of that, for one line of alignment down the whole lecture. off takes every heading off the projection while keeping it in print.html, print-notes.html, the contents list and the search.

Five keys the block's names do not explain wrap, blocks, print-body, the bold pair and the scales

wrap applies to headings and prose both, which its name does not say: balance evens the line lengths of a heading and protects the last line of a paragraph, and none turns both off. blocks and wrap are the two keys a single chunk can answer for itself, with {.blocks-left} and {.wrap-none} in its attribute tail.

print-body is the one setting here that only the printed pages see. The projection and the lectern let a reader pick the face with F; a document has no reader to press it, so sans is how you ask for one set in the sans. Code stays in the monospace, and so does everything the document already draws in the sans – the type word, a caption, the contents list.

bold and print-bold set how a **bold** phrase looks, and plain is a legal answer because bold selects before it decorates. Live the default is plain, so a promoted bullet is set like the sentence above it; on paper it is bold, in the ink. accent-bold in both is how the tool drew every bold before the keys existed. A word stressed with *em* inside the phrase is bold and in the accent whatever the key says – except under accent-bold, where it stays italic.

The two scales multiply the tool's own sizes rather than replacing them, and they are bounded. Outside 0.6 to 1.8 the shortened view, the limit on how wide a line of code may be and the automatic zoom stop agreeing with each other.

Turning the generated labels off style: {labels: off}

The type word above a chunk is drawn in two places, and one setting takes it out of both.

  • print.html and print-notes.html set a small line of capitals over every typed chunk. Every type has one except free and figure, so that is where most of them live.
  • The projection prints only the word over an exercise. The rest were taken out: a label naming the kind of slide is only as right as the type was.
style:
  labels: off

rules is the neighbouring key and switches the lines – the bar above a principle, the hairline above a definition. labels switches the words.

A figure's heading, set in capitals, is your own text and needs no key. It is the chunk's heading, drawn that way because the type is figure, so ## figure: {.wide #id} with no heading text leaves it off the slide. The cost is that the chunk then has no text for search to find and no heading in print.html. (The contents list is unaffected – T lists the lecture's columns, never its chunks.)

Which typefaces are embedded in the file nine come with the tool

Three families are embedded in any one file, and you pick which three. Nine come with the tool, so naming one of those needs no font file at all.

fonts:
  serif: Bitter                    # or Literata, the default; also Source
                                   # Serif 4, Noto Serif, Roboto Serif
  sans: Inter Tight                # or IBM Plex Sans, the default
  mono: Noto Sans Mono Condensed   # or JetBrains Mono, the default

Only the three a lecture actually asks for are read, so choosing an alternative costs that lecture and no other. A name that is neither one of the nine nor a file in fonts/ stops the build, and the message lists the names available for that role.

Among the serifs, the question is what a projector does to a typeface. Bitter has the lowest stroke contrast and the smallest file, which is why it stays legible in a lit room; Roboto Serif has the strongest bold but sets 8% wider, so it re-wraps a deck written against another face.

The condensed monospace is 17% narrower – 0.50 em against 0.60 em per character, measured in a browser – so a listing that ran off the slide now fits. It is Noto Sans Mono with its width axis pinned rather than a different typeface, so it costs 54 KB. Slashed zero, and I, l and 1 are three visibly different shapes.

ligatures: decides whether letter pairs are drawn joined, and answers separately for prose and for code. text is the default: fi and fl joined up in prose, nothing joined in code. none takes them out of prose as well. all puts the code ones back, so JetBrains Mono draws -> as a single arrow again. The code ones are off by default because in the figure language -> and -- are two different arrows, and every listing on a slide is source somebody may retype.

Embedding your own typefaces fonts/ beside source.md, plus a frontmatter block

A family that is not inside the file is a family the lecture-hall machine may not have. Safari does not tell a page which fonts a machine has, so a lecture that merely names one takes whatever that browser decides instead. The three a lecture carries are embedded in every output it writes, cost about 280 KB per file, and fonts: none leaves them out; the bundled three are under the SIL Open Font License, which permits exactly this.

Each of the three roles is answered on its own, so you can replace one and leave the others alone. Put your files in a fonts/ folder beside source.md:

fonts:
  serif: Vollkorn        # yours – the files are in fonts/
  mono: JetBrains Mono   # one of the five that ship, so no file
                         # sans: not written, so it stays the default

A file's name says which weight and style it is: Vollkorn-Regular.woff2, then -Bold, -Italic, -BoldItalic, -600, -600italic – or one file, Vollkorn[wght].woff2, carrying every weight. A family that is neither one of the five nor a file in fonts/ stops the build.

Putting a font inside the file redistributes it, so check the licence first. The SIL Open Font License and Apache-2.0 – between them nearly all of Google Fonts – allow that; most commercial desktop licences do not, and want a separate web licence. The build prints a reminder and checks nothing.

How the title slide is composed cover: plus a subtitle: line

cover: in the frontmatter picks how the title slide is composed, and subtitle: gives it the line that says what the talk is about. Without those two, a cover is one weight of one colour with the subject set beside the venue, and it reads as a text file rather than as the opening of a talk.

title: How Caches Forget
subtitle: Eviction, Staleness and the Cost of Being Wrong
presenter: Jana Wieland
info: |
  Distributed Systems · Lecture 7 · Room WE5/00.019
  uni.example/ds
cover: split            # see the two rows below
cover-image: skyline    # only the four picture covers take one;
                        # on the six text-only ones it is an error

info: takes as many lines as you give it – the course and the room, the address students should write down, or, at a conference, its name and dates. Without subtitle: the one line saying what the talk is about has nowhere to go but info either, where it is set exactly like the rest.

The ten cover compositions six of text alone, four that take a picture

They are ordered by how prominent the opening slide is. Six are text and nothing else:

  • classic the lower-left third. The default, so a lecture that names no cover is unchanged
  • masthead the title along the top edge, the credits along the bottom, your own text in the field between
  • stack the title block centred on both axes, for a calm opening
  • display the title set to fill the slide. The scale is the whole design
  • panel the type on a full field of the theme's accent colour
  • quote the title chunk's body set as the claim, the lecture's name under it

The four covers that take a picture and what each does with it

Four take a picture:

  • split type on the left, the picture running off the right edge
  • hero the picture is the slide, type reversed out of a dark gradient
  • beside the title chunk's own body, a drawing say, set to the right
  • above that same body on top, the title centred in the band below it

What a cover reads besides its name the body, a backdrop, and three more keys

beside, above and quote take their content from the chunk body, so a ::: draw can be the cover – a diagram is not a file, and cover-image can never name one.

The six text-only compositions each take a ::: backdrop, which is how a photograph reaches a cover with no cover-image of its own.

Where a new part starts section:

A column with a # Heading opens with a divider slide, and section: picks how that slide is drawn.

  • plain the heading on its own. The default
  • tinted the accent colour over the whole slide. The most visible of the six
  • rule the heading between two rules. The one that survives a mono print
  • card the heading set on a panel
  • number a large counter above the heading
  • outline every part listed, the one you are entering marked. A running agenda

A divider is never the title slide and section-mark: puts a word over it

They stay less prominent than the cover, so that a divider is not mistaken for the title slide: it says a new part starts here, and it is part of the thing you are already in.

section: tinted         # plain | tinted | rule
                        # card | number | outline
section-mark: Teil      # any short word, or nothing

section-mark: puts a word of your own – Teil, Kapitel – over the heading. By default there is none.

The last slide ## closing:

## closing: draws a last slide in the same composition as the cover, so the lecture ends on the shape it opened with. A lecture that starts on a designed slide and ends on the last bullet of the last argument stops rather than finishes.

## closing: Questions? | office hours Thursday, 14–16 {#end}

Next week: certificates, and who you are actually trusting.

The heading is the first line, the sub-heading after the | is the second, and the body is whatever should stay on screen while the audience asks questions. Your name and the info block are not drawn.

A closing slide never uses cover-image by itself – ending on the opening picture unasked would be the repetition a closing slide is meant to avoid. closing-image: cover in the frontmatter asks for it, and the deck closes on the picture it opened with; any other value names a different one, in the same three forms cover-image takes. A ::: backdrop on the chunk is the other way and a different thing – a full-bleed ground behind the words, which works on all ten compositions and wins over both.

That is the tour

now write your own source.md

Everything in this tour comes out of one Markdown file and one command, node build.js source.md. The four views are already sitting beside it.

overview · drag pans · wheel zooms · selects · click or O/Enter lands · / search · Esc leaves