Revealing a slide step by step#
A slide that unfolds in front of the room instead of standing there finished.
Which tool for what#
Six building blocks cover nearly everything, and they mix on one slide.
| Tool | For what |
|---|---|
#pause | The slide unfolds from top to bottom, with nothing wrapped around anything. The commonest case. |
stagger[…] | A list point by point, bullet and text together. Also for several blocks in sequence. |
anim(…) | One piece on one step, with a motion of its own. The tool wherever #pause cannot reach: grid cells, tables, boxes. |
alternatives(…) | Several versions of the same thing in the same place, each replacing the one before. |
build(…) | A drawing that comes into being in stages – one CeTZ line, one lilaq data series, one label after another. |
scene(…) | A drawing that depends on a value. For everything that moves rather than being added. |
Beside them stand tiles for a grid that staggers itself, and morph for things that fly between two slides.
The step cursor#
Every slide carries a step cursor. at defaults to auto, the next free step, so consecutive reveals number themselves and most slides hold no number at all. Automatic reveals start no earlier than step 2, including the first items of stagger, cue, alternatives, tiles, build and scene. Step 1 contains static content. Use an explicit at: 1 or start: 1 to show an item on entry.
== Three things
#anim[first] // step 2
#anim[second] // step 3
#anim(at: 4)[late] // 4
#anim[after that] // 5The spellings of at:
| Written | Meaning |
|---|---|
auto | the next free step (the default) |
3 | from step three on, the same as "3-" |
(2, 5) | on step two and on step five |
"2-" | from step two on |
"1-2" | on steps one and two, not after that |
"2,4" | on step two and on step four |
"-2" | from the start until step two |
"3" | exactly on step three |
A bare number is an open end: what is there once stays to the end of the slide. A closed spelling such as "1-2" or "3" lets the element disappear again, and then exit applies. A range is written from its first step to its last: a backwards one such as "4-2" stops the compile, rather than never appearing in the browser and appearing on paper all the same.
A slide without a single number#
#pause needs no counting. It cuts the body where it stands, and everything after it arrives one step later:
== What we know
The two legs carry as much area as the hypotenuse.
#pause
$ a^2 + b^2 = c^2 $
#pause
And that is enough to compute the third side from two of them.#pause splits the body, so it works between blocks but not inside a grid cell or a table – there is nothing there to cut. anim goes anywhere content goes.A list point by point#
stagger takes a list and reveals it item by item, bullet and text together:
#stagger[
- What the room already knows
- What it is about to learn
- What it will be able to do afterwards
]stride: 2 leaves a step out between two items, stride: 0 puts all of them on one. start sets the first step, enter the motion, stagger the delay in milliseconds between neighbours, spacing the distance between items, dim lets each point step back once the next arrives.
stagger also takes several blocks instead of one list. Each block is then one step – three paragraphs or three pictures in turn, without three anim calls.dim: true turns the sequence into a walk: the point being discussed stands there, the ones before it stay legible but muted.
#stagger(dim: true)[
- What the room already knows
- What it is about to learn
- What it will be able to do afterwards
]Each point then holds its own step and rests in after: "dimmed" (see “The muted resting state”). Two consequences, both intended: the last point dims too once the slide has a step after it, and stride: 0 dims them all together.
What stands beside a piece#
stagger-layer hangs something on the step of one particular piece – the annotation beside a calculation, say. The stagger needs a name: name: says it, and a morph: written as a name says it too.
#stagger(morph: "rewrite",
$ x^2 + 6x + 2 = 0 $,
$ x^2 + 6x = -2 $,
$ (x + 3)^2 = 7 $,
)
#stagger-layer("rewrite", 2)[$| -2$]The layer stays from its piece to the end of the slide, as cue-layer and scene-layer do, and carries no morph name: what flies is the piece, the annotation merely appears beside it. The stagger has to stand before its layers, because a layer looks up which step its piece was given, and on the same slide: as with cue, a name belongs to one slide, and a layer naming a stagger from the slide before stops the compile.
spacing: applies to the list branch only. Where the pieces stand on their own, ordinary block spacing decides.Revealing in the order it is called out#
Some points have no order. What a graph shows, what stands out in an experiment – a class names those as they come, and a deck that reveals them in its order makes the teacher wait or reshuffle. cue turns that round: the digits 1 to 9 reveal whatever was just named.
#cue("readings", start: 2)[
- positive and negative values
- lowest and highest value
- falling and rising
]The group takes a name so that cue-layer can point at it. It owns as many steps as it has points, whatever the order, so the progress bar, info().step.total, the overflow check and the handout are untouched. The list keeps its reading order: a point not yet named holds its place, so nothing jumps when it arrives.
A name belongs to one slide. The same name on the next slide is a new group starting again at 1, so every exercise slide can say cue("marks", …) without numbering the names apart. Several cue calls on the same slide under the same name do form one group and count on.
What appears together with a point#
cue-layer hangs something on the same step – a drawing layer, a picture, a sentence beside it:
#cue("readings", start: 2)[
- positive and negative values
- lowest and highest value
- falling and rising
]
#cue-layer("readings", 1, [and what goes with it])The point and the layer share a step, so moving the step moves both, and you can hang as much on a point as you like. The group has to stand before its layers; otherwise the package says so.
cetz.draw.hide(rest, bounds: true), so that it still counts towards the bounds. All layers then lie exactly on top of each other and the graph holds still, in whatever order it grows. A layer carries only its own contribution, no grid and no base curve, or the layer set last paints over the first. Where the drawing is to grow in written order, use build instead.One piece on a step of its own#
anim wraps exactly what should appear and says when:
#side-by-side(
card(title: [Before])[The old way.],
anim(at: 2, enter: "fade-left", card(title: [After])[The new one.]),
)Entrance and exit#
enter and exit name the motion. Twelve of them exist:
| Written | What happens |
|---|---|
"fade" | opacity alone |
"fade-up" | from a little below, the default for an entrance |
"fade-down" | from a little above |
"fade-left", "fade-right" | from the side |
"scale" | grows into place |
"scale-down" | shrinks into place |
"blur" | out of the blur |
"rise" | from below and slightly smaller, the loudest of them |
"draw" | it draws itself – see “A path that draws itself” |
"none" | it is simply there |
"hold" | not an exit but a wait: the piece stays until the next one is there. As an enter it is the same as "none" |
duration is in milliseconds and auto takes the presentation’s. delay holds the start back, which lets two elements on the same step arrive one after the other.
A name the package does not know is an error at compile time, as it is for easing: a typo would otherwise render as "fade" in silence.
#anim(enter: "fdae-up")[A typo.] // error at compile timeThe curve#
Everything this package moves runs on one curve: slow off the mark, brisk through the middle, soft at the end. easing hands a different one to a single element.
#anim(result, enter: "rise", easing: "out-back")
#stagger(stride: 0, stagger: 60, easing: "out-quad")[
- first this
- then that
]It stands wherever duration stands: on anim, stagger, alternatives and build, and it covers the entrance, the departure and the dimming. Not the slide transition, and not the flight of a magic move, which has two ends.
| Name | Curve |
|---|---|
"standard" | this package’s own curve, written out – the same as saying nothing |
"linear" | even, with no run-up and no run-out |
"ease", "ease-in", "ease-out", "ease-in-out" | the four the Web Animations API knows by itself |
"in-quad", "out-quad", "in-out-quad" | gentle |
"in-cubic", "out-cubic", "in-out-cubic" | more pronounced |
"in-expo", "out-expo", "in-out-expo" | sharp – nearly everything happens at one end |
"in-back", "out-back", "in-out-back" | winds up and overshoots |
in means slow off the mark, out soft at the end; for an entrance out is nearly always right, because the eye watches the ending. A name that does not exist is an error at compile time, and the message lists the choices.
#anim(result, easing: "out-bounce") // an error at compile timeThe three back curves go past their mark. On a travel that is the swing back; on opacity the browser clips whatever reaches past 1, so "out-back" on a plain "fade" is merely a faster "fade". Use it with an effect that travels: "rise", "scale", "fade-up". Springs and bounces – elastic, bounce – do not exist here: they are not cubic Bézier curves, and the Web Animations API knows only those.
The muted resting state#
An element whose range ends plays exit and goes. after: "dimmed" is the other resting state: the point stays and is drawn muted – legible, but no longer the thing being talked about.
#anim(at: "2-3", after: "dimmed")[A passing remark.]
#anim(at: 4)[And on with the talk.]Nothing moves and nothing is recoloured: the element settles to 65 percent opacity and comes back up when you page back. after is "hidden", the default, or "dimmed".
after wants a range that ends and a step after it, or there is nothing to rest in and no step to be seen muted on; both are errors at compile time. at: "3" is that one step, at: "2-3" a range, and the second line above supplies the step after it. As a list, at: (2, 4) shows the element on 2, hides it on 3, brings it back on 4 and rests it dim from 5.
On paper after does nothing: a page shows every step at once, and a point that is only quiet because the talk moved past it has no past there.
ink colour above 4.5 to 1 on every bundled palette. What is already quiet becomes too quiet: muted text or a word in the accent colour falls below that. Dim a point, not a label. Over a card(fill: ...) of your own or over an image nothing is measured at all.A tracked element inside a dimmed one inherits the dimming only if it has exactly the same range – the same inheritance by which enter, delay and duration reach inwards. It may be less visible than its host, never more.
That leaves morph, video, embed and flipbook outside, because all four default to the open range at: "1-". Inside a dimmed element they keep full strength, so a formula in a dimmed line stands black in a grey sentence. Give it the same closed range by hand, or do not dim the line.
Several versions in the same place#
alternatives puts versions on top of one another. Each step shows exactly one, the next replaces it:
#alternatives(
$ (a + b)^2 $,
$ a^2 + 2 a b + b^2 $,
$ a^2 + 2 a b + b^2 = c^2 $,
)The box is as large as the largest version, so nothing around it jumps. align decides where the smaller ones sit inside it, start on which step the first appears, and inline: true puts the whole thing in a line of text. enter, duration and easing describe the change from one version to the next. A version that reveals something of its own stays until that is done, and the next one comes after it. That waiting needs start: auto: with start written out, every version but the last holds exactly its own step, and a chain inside one of them comes after its version has gone and is never seen.
morph: true is the other way, and for the example above it is the better one: the versions fly into one another instead of replacing one another. They stand in the same place, so the flight has no distance – what you see is the glyphs rearranging themselves where they stand, which is what a rewritten formula does. With morph there is no entrance, so enter: and easing: are refused; duration: becomes the time of the flight. The waiting for a chain holds on paper only: in the browser the next version comes on the step after the one before, even when that one reveals something of its own, and what it reveals is never seen there.
A drawing that grows#
A CeTZ canvas and a lilaq diagram are one piece, not many: Typst hands out the finished setting, and a line or a data series in it cannot be reached from outside. So there is no anim around a single line of a drawing – there is the drawing itself, as often as you want it. build calls it once per step and lays the versions exactly on top of one another: on stage the drawing stands as it looks after steps, and exactly one is on show.
Which piece joins when is said by the question every stage is handed. It is called ab – “from” – because it says what at: says elsewhere:
#build(from => cetz.canvas({
import cetz.draw: *
line((0,0), (4,0)) // there from the start
line((4,0), (4,3), stroke: from(2, black)) // from step 2
line((4,3), (0,0), stroke: from(3, 1.4pt + red))
content((2.2, 1.8), from(4, [$c$]))
if from(4) { circle((4,0), radius: 0.18) }
else { hide(circle((4,0), radius: 0.18), bounds: true) }
}), steps: 4)from(2, black) gives the colour back once the second piece is due, and otherwise the same colour with alpha 0. What carries no number stands there from the start. steps: 4 says how many stages there are; it is said, not guessed, because nobody can see from outside what the drawing function does with its question. from(4) with a single argument is the same question as a boolean, for everything that cannot be recoloured – in CeTZ that is where hide(…, bounds: true) belongs.
Air rather than omission, because a piece left out takes its room with it and the drawing jumps. from makes air out of a colour, out of a stroke (the brush goes, thickness and dashing stay, because the measure hangs on those), out of the colours in a dictionary, and out of content, which goes into hide. Not out of a gradient – there it says so instead.
A lilaq diagram#
A data series turns to air in two places: at its colour and at its label in the legend. The second is easy to forget – the entry would otherwise stand in the legend while its curve is missing:
#build(from => lq.diagram(
width: 7cm, height: 4.5cm,
legend: (position: top + left),
lq.plot(x, measured, color: from(1, red), label: from(1, [measured])),
lq.plot(x, model, color: from(2, blue), label: from(2, [model])),
), steps: 2)Because the series stays in the data as air, lilaq reckons its axes over both: the scale is settled from the start, and the first curve does not jump when the second arrives.
Stages that do not come one click after another#
steps: 4 puts the four stages on four consecutive steps. That holds as long as the drawing grows click by click. As soon as something else happens on the slide between two stages – a camera move, a verdict, a second diagram beside it – it holds no longer: the drawing has two pictures, but the second is not due until step 9.
at: names, per stage, the step it first stands on:
#build(from => cetz.canvas({
import cetz.draw: *
line((0,0), (4,0))
line((4,0), (4,3), stroke: from(2, black))
}), at: (1, 9))Two stages, the second from step 9 on; in between the first one stands. The list’s length is the number of stages, so steps and start have nothing left to say and are refused rather than quietly ignored.
from keeps counting stages, not steps. from(2, …) means: from the second picture on. Where that picture stands is said by at: alone. It could not be otherwise – under start: auto nobody knows while writing which step the drawing will land on.
What this saves is not typing but work. Without at: the same picture needs steps: 9, and stages 1 to 8 are pixel for pixel the same drawing and are all typeset regardless. Measured on a slide carrying three diagrams that are discussed one after another: ten sprites instead of 22, and the whole file 2.98 MB instead of 3.45 MB.
The arguments#
| Argument | Effect |
|---|---|
steps | number of stages, and hence of steps (default 2) |
start | first step; auto follows on from the cursor |
at | the step each stage first stands on, say at: (1, 9); every stage holds until the next one is due. steps and start then fall away |
enter | motion a stage arrives with (default "fade"); "draw" is an error here, see the next section |
duration | duration in milliseconds |
easing | the curve of the motion, see “The curve” |
On paper only the last stage is set, in a block of the same size. Under “reduce motion” nothing changes: the stages fade, they do not travel.
Every stage really is typeset. Four stages mean four layouts and four SVG trees in the file – for an elaborate drawing both grow as fast as they do for a flip book. A drawing in twenty stages is not a good idea.
Hence at: where the stages lie far apart. What counts is the number of pictures, not the number of steps between them.
A path that draws itself#
enter: "draw" lets a stroke come into being instead of fading in: the pen is set down and traces the path from start to end.
#anim(circuit, enter: "draw", duration: 900)
#stagger(enter: "draw", stride: 1, axes, curve, tangent)Behind it lies stroke-dasharray on the SVG path: one dash exactly as long as the path, slid in by stroke-dashoffset. duration applies as everywhere, but a drawing wants more time than a bullet point – 900 is a workable start, and the presentation’s default of 520 is tight for three long lines.
What can be traced and what cannot#
Text cannot. Typst sets glyphs as filled shapes with no outline, and an area has no length to travel along – the same for an arrow head, a solid dot, the face of a card. So draw does two things at once: the strokes draw themselves, everything else fades in, over the same time. A label arrives while the lines are being drawn and stands finished together with them.
An element on which nothing at all can be traced fades in completely, and the runtime says so in the browser’s console, once per element:
typstage: enter: "draw" on slide 4 (element 2) finds no stroked path to
trace. What is drawn is an outline, and text has none: Typst sets glyphs
as filled shapes. The element fades in instead. draw is for a drawing,
the fade is for text.It cannot be caught earlier: Typst hands out the SVG only on export, so only in the browser is there a path to count.
All at once, and how to get them one after another#
Every stroked path of an element sets off at the same time, and there is no knob for that: the order in the SVG is Typst’s painting order, not one the deck chose. Say the order instead, by giving each piece its own step:
#stagger(enter: "draw", stride: 1, axes, curve, tangent)Where a drawing has to stand#
Not on the first step of its slide. Entering a slide plays no entrances – the runtime only restores the state, or the transition and a dozen reveals would run against each other. A drawing on step one would simply be there. Give it a step in front:
#anim[First the sentence that announces the drawing.]
#anim(circuit, enter: "draw", duration: 900)That holds for every effect; with draw it merely stands out, because there the travel is the whole point.
Who delivers outlines#
Whatever gets a stroke in Typst becomes a path with an outline and can be traced; whatever gets a fill does not. A drawing package delivers exactly as much as it strokes, and a slide of text delivers nothing.
That decides between draw and build. A plain CeTZ drawing strokes a handful of paths – a few long lines an eye can follow, which is what draw was made for. A lilaq diagram strokes nearly everything, grid, ticks and markers included, and all of it sets off at once: a diagram wiping in, not a drawing coming into being. For a diagram, use build.
Dashed lines stay with the fade. The dash pattern lives in the very attribute the pen needs, so a dashed guide line fades in while its neighbours draw themselves.
In both directions, and what holds at the edges#
| Where | What happens |
|---|---|
| Paging back | The pen traces its way out: what drew itself undraws itself. |
| Jumping to a step | No drawing. A jump – address, overview, reload – restores the end state, which is the finished drawing. |
exit: "draw" | Allowed and symmetric: an element leaving its range takes its strokes back instead of fading away. |
| Speaker view | The preview of the next step shows the finished drawing, with no motion. |
| Paper | Nothing. enter never reaches the PDF. |
| Reduce motion | The pen holds still, the fade remains – opacity stays, travel goes, and for draw the drawing is the travel. What is left is the fade that ran underneath it anyway, over the same duration. The console message still comes. |
Together with a drawing that grows in stages#
Both at once does not work, and the package says so at compile time:
#build(painter, enter: "draw") // an error at compile timeEvery stage of a build drawing is the whole drawing, so a stage that drew itself would retrace every stroke over ink already down. For strokes that come into being one by one, hand them over as pieces of their own; for a diagram that grows in stages, leave it with its fade.
A drawing that moves#
build lets a drawing grow, piece by piece. scene is the other half: nothing is added, a value changes, and the picture hangs on it.
The deck writes a function from a value to a picture and names the values at which the talk stops. Typst renders every stop and the frames in between, and a step pulls the picture from one stop to the next.
#scene(
x => drawing-at(x),
stops: (-3, 0, 1.5, 3), // four stops, three steps
tween: 8, // frames between two stops
)stops are the values themselves, not 0.0 to 1.0. That is the difference to the flip book: there t is a fraction of a running time, here x is the quantity being talked about. Whoever wants the tangent at , at the vertex and at writes those three numbers down.
The scene takes stops.len() - 1 steps. The first stop is there as soon as the scene appears – like a morph, unlike an anim – and every further stop costs a keypress.
What belongs to a stop#
A sentence, a formula, a second drawing: scene-layer puts itself on the step of one particular stop. The scene needs a name to be found by.
#scene("derivative", x => tangent-at(f, x), stops: (-3, 0, 1.5, 3))
#scene-layer("derivative", 2)[At the vertex the slope is zero.]
#scene-layer("derivative", 4, enter: "scale")[$f'(x) = 1/2 x$]This is word for word cue-layer: the coupling falls out of the shared step. Move a stop and everything hanging on it moves along, and nowhere does a number stand twice. The scene has to stand before its layers.
Several values at once#
A stop may be a tuple, and then the drawing function takes that many arguments:
#scene(
(a, b) => box-of(width: a, height: b),
stops: ((1, 1), (1, 3), (2, 3)),
tween: 6,
)First the height grows, then the width. What does not work: two values moving independently. Everything travels from stop to stop together, and a tuple puts several values on the one way.
The arguments#
| Argument | Effect |
|---|---|
stops | The values at which the talk stops. At least two. A number, a length, an angle, a ratio – or a tuple of them. |
tween | Frames between two stops (default 8). With 0 the scene jumps. |
start | first step; auto takes the running one |
width, height | The box the scene stands in (default 100% and 190pt). |
duration | how long one pull from stop to stop takes, in milliseconds |
enter | motion the scene itself arrives with (default "fade") |
still | what stands on paper, if not the last stop |
steady | What measuring the frames is for: auto reports, false takes the scene out of the check, true insists on it. See below. |
duration is the duration of the journey, not of the fade the scene arrives with. Unlike build, scene does not stack its frames: they are drawings of different values and may legitimately come out different sizes. So a scene stands in a box of fixed size, every frame is clipped to it – and every frame is measured:
The box stands still, the ink inside it does not do so by itself. A CeTZ canvas grows with its content, so if the tangent at reaches further left than the one at , the axis cross sits elsewhere in the box, and paging moves the whole picture although only one point was meant to move. Every scene measures its frames and says so where the sizes differ:
error: assertion failed: typstage: 1 scene draws frames of different sizes. …
slide 4, from step 1: 28 frames in 19 different sizes, up to 28.35pt apart across and 53.86pt downThe way out lies in the drawing: give it a fixed extent and keep what moves inside. In CeTZ that is a rect with a transparent stroke:
#scene(x => cetz.canvas({
import cetz.draw: *
// Holds the canvas open, wherever the point stands.
rect((-4.4, -0.8), (4.4, 4.6), stroke: rgb(0, 0, 0, 0))
line((-4, 0), (4, 0))
circle((x, 0.25 * x * x), radius: 0.1)
}), stops: (-3, 0, 3), height: 160pt)That pins the width. Whatever still reaches beyond it – a tangent running off the edge – has to be cut off, or it pulls the canvas open again.
Where the frames are meant to differ, say so: steady: false takes the scene out of the check. drift on the presentation decides what happens with the findings.
steady: true is the opposite commitment: the scene has to stand still, and it stops on the spot rather than in a list at the end of the deck:
#scene(x => cetz.canvas({
import cetz.draw: *
line((0, 0), (x, 0.25 * x * x)) // pulls the canvas along
}), stops: (-3, 3), steady: true) // error at compile timeOn paper the last stop is set, as with alternatives; still puts something else in its place. The step cursor runs there too, so info().step.total names the same number in both outputs. Under “reduce motion” the frames in between fall away and the scene jumps.
What a scene costs#
Every frame really is a Typst layout and sits in the file as an SVG tree of its own, so compile time and raw file size grow with tween. Over the wire it matters far less: the trees are so alike that gzip takes some 98 percent away. On paper a scene costs nothing – one still image. Measuring the frames costs one more layout each, in the browser branch only; steady: false gives it back for one scene, drift: "none" for all.
Moving in on a detail#
Sometimes the next step is not a new sentence but the same one from close up: the one cell of the table, the one term of the equation. camera moves in on it and back out again. It aims at a pin and at nothing else – the package’s word for a named piece of a slide, whose rectangle the runtime measures anyway.
#pin(<sensor>, card(title: [Sensor])[Thermocouple, bridge, amplifier.])
#camera(<sensor>)
#anim[And out again, on the step after.]How you get out again#
Said, not guessed. at is a step selector as everywhere else, and the slide is seen through the camera for as long as it is active:
| Written | What happens |
|---|---|
at: auto | The next free step, and the one after takes it back out. The default. |
at: "3" | In on step three, out on four. |
at: "3-5" | The crop holds across three steps. |
at: 3 | In on step three and stay; the slide change takes it out. |
The way back out is a step and is counted as one: a slide carrying nothing but a pin and a camera has three steps – the whole slide, the crop, the whole slide. info().step.total and the handout count the same way.
at: auto is a closed range here, while for anim it is open: an entrance has no natural end, a camera move does. And never step one – a move there would mean nobody ever saw the slide whole.What travels along and what stays put#
What travels is the slide: background and the layer of revealed parts above it, with the same transform. The furniture does not – footer, page number, progress and running header sit as their own layer above the stage, hold still while the slide grows underneath them, and stay legible. The title travels; it stands in the body, and so does a footer built by hand into the body. What leaves the frame is cut at the edge of the stage, and drawn ink stays put.
How far it goes#
margin says how much of the slide stays around the detail, measured on the unzoomed slide (16 pt by default). The camera fits detail plus margin into the frame, and the tighter direction decides, so the whole of it is seen. The move takes duration milliseconds, 700 by default, and easing bends it.
#pin(<term>, $b^2$)
#camera(<term>, margin: 4pt, duration: 900, easing: "out-quad")
#anim[After that.]There is no upper limit. A pin the size of a comma is shown the size of a wall, and what Typst set stays sharp, because it stands there as vectors; a video, an image or an embedded document will not. A detail already as large as the slide gives nothing to travel to.
Two special cases#
Two pins of the same name on one slide. The camera frames the box around both.
Two moves overlapping on one step. The later one in the source wins.
On a jump, paging back, and on paper#
The crop is a function of the step and nothing else:
- Paging back runs the way in reverse and lands on the whole slide again.
- A jump – overview,
#3in the address, a click in the speaker view – sets the crop instead of travelling to it. - The speaker view shows the running slide with its camera, and the preview beside it carries the crop along: its question is “what stands there after the next keypress”.
- Under reduced motion the camera jumps to the crop.
When the name is not there#
A camera aiming at a pin that does not exist on its slide is an error at compile time:
#pin(<sensor>, card[…])
#camera(<senor>) // one letter shortThe question is asked at the end of the document, not on the spot: a move may stand before its target, and what stands on a slide is only settled once the slide is set. A pin on the slide before does not count.
One case stays open: a pin inside an anim not revealed on this step has a rectangle but nothing visible in it, and the camera moves in on an empty place. Which step shows what is decided in the browser.
Three stumbling blocks#
Only reveals count. The cursor counts anim, stagger, alternatives and #pause – everything that makes something appear. An applet, a video or a morph uses up no step and is there from the beginning. That matters in a two-column slide: the bullets beside an applet should start at one, not behind its motions.
#side-by-side(
embed(url: "…", width: 100%, height: 220pt), // no step
stagger[
- first bullet // step 1
- second bullet // step 2
],
)That holds as long as at keeps its default. Give a video, an embed, a flip book or a morph an at past step one and it appears, so it counts like an anim: the slide has at least as many steps as the at names, and whatever follows with auto comes after it.
#video("experiment.mp4", at: 2) // step 2
#anim[What we see] // step 3A step is not inherited inwards. Every tracked element carries its own step, and one sitting inside another still follows it:
#anim(at: 3)[From step three, #morph(<m>, $x^2$) but from step one.]With morph that is right: the target of a flight has to be standing when the slide is entered, or the flight from the previous slide arrives nowhere. With an anim inside an anim it is usually an oversight, noticed only while paging.
A morph stands from the first step. So a morph does not belong inside something that only appears later. Put it in a tile that arrives on step two and it hovers alone on step one, where its container will only later turn up.