cues.json owns it.
decktalk-runtime.jsimplements the contract. DeckTalk serves it at/__decktalk/decktalk-runtime.json the origin it opens the page at, and a page that would rather follow it by hand can.- A scene is markup: a
[data-scene]wrapper holding one<template data-slide>per slide. Attributes do the work, so a page needs no JavaScript of its own. - The runtime adds only the stage element, the slide transitions and the reveal effects. Every rule
of its own sits inside
:where(), so one rule of yours wins. - To write your first page, follow Your first deck.
The four moments
An element declares up to four moments, and each attribute’s value is a cue name rather than a number.
They play in that order: in, back, front, out. An exit declared at or before its own entrance never
plays, which is
PAGE_MOMENT_ORDER, and the order is read
off the cues the slide declares rather than guessed.
data-back and data-front are attention rather than reveal. Use them when one thing on the slide
has to recede while another is read, so the page never has to remove something and bring it back.
The cue id
Each moment names a cue, and the runtime qualifies that name with its slide into the cue idcues.json carries. Here it is 4.1:expand.
deck/index.html
cues.json owns the second it happens on. An author writing a slide never types a number of
seconds, and an author writing cues.json never types a class or an effect.
The subjects
Every attribute is written on one of four subjects, and the table below names the subjects each row admits.Every attribute
This is the whole contract.Value gives the closed word set where a row has one, and otherwise says
what kind of value the row takes. A row that publishes a default may be written bare to take it, so
data-words alone is data-words="highlight". Range is the range the row admits, in seconds, and
a value outside it or off its step earns PAGE_BAD_VALUE.
Span is the seconds of motion the row puts between its own cue and the next one, where 0 is a row
that never moves a pixel and from the page is a span read off the page or off a closed word set.
A
data- attribute that is not in this table is
PAGE_ATTR_UNKNOWN, reported before a single pixel is
drawn, so a misspelling is a finding rather than an attribute nothing reads.
decktalk schema page prints this same table as JSON, with each row’s on, kind, values,
default, range, code, span, affects and summary, and beside it the entrance, exit, word,
count, attention and slide word sets with their lengths, the query keys, the t0_signal value,
measurable_span_seconds and slowest_fps.
The table above and the schema are written from one registry, so the page cannot say something the
registry does not.
The five rows that earn no code
An attribute lives in the contract only when its value can change a judgement or be named by a finding. Five rows can do neither, because each of them carries text a person wrote or a preview length nothing records, and each publishes the sentence that says why.
The list is closed and a test counts it. An open field saying what a row affects would let every
label back into a contract that exists to hold the things a measurement depends on.
How long a moment may move
MEASURABLE_SPAN_SECONDS is 0.5 seconds, and it is the one ceiling above which a cue cannot be
measured. An effect still playing half a second after its cue covers the frames the next cue is read
from, so it marks its own cue unmeasurable. No span a page can declare may reach it.
data-in-seconds runs from 0.12 to 0.44 in steps of 0.04, which is three to eleven frames at 25
frames a second, the slowest rate a film is recorded at. data-stagger runs from 0.04 to 0.2 in the same steps,
which is one to five frames.
A staggered container’s total is exact arithmetic, and that is why its overrun is an error.
The last child starts one step per earlier child after the cue, and then plays its own entrance.
data-stagger="0.08" and the default rise entrance totals
0.08 * 5 + 0.32, which is 0.72 seconds. That is over the ceiling, so it earns
PAGE_STAGGER_OVERRUN. Four children at the same step
total 0.56 seconds and still overrun. Four children at 0.04 with a pop entrance total 0.32
seconds and are fine. A container that staggers no children at all earns
PAGE_STAGGER_EMPTY, and a data-spotlight container with no
child that declares data-in earns PAGE_SPOTLIGHT_EMPTY.
motion.scale multiplies every declared span and every duration in the deck. A scale above 1 slows
motion down, and each scaled span is clamped one frame under the ceiling rather than obeyed, so a
project cannot change a setting until its own cues stop being measurable. A motion still playing at the
frame the next cue is read from is
PAGE_MOTION_OVERRUN. A data-class change is the one
span the contract cannot bound in advance, because its length belongs to your own stylesheet, so the
runtime measures it off a laid-out element instead.
Reduced motion has two switches, and neither is a flag. motion.reduce asks Chromium to report
prefers-reduced-motion to the page, and motion.scale is published to the page as
--dt-motion-scale on :root. A class change that still animates under a reduced render is
PAGE_CLASS_NOT_REDUCED, against the stylesheet that
owes the reduction.
The frame reaches the page the same way. [video] size names the frame, wide, square, vertical
or a width and a height in pixels, and the page is laid out on a canvas of that shape whose short side
is 1080 CSS pixels. The runtime puts the canvas’s size on :root as --dt-width and --dt-height and
its shape as data-dt-shape, so a stylesheet that reads them lays one page out for any frame. One
project makes one video: a square cut is the project copied with its size changed.
Motion
A moment is a reveal of at most 0.46 seconds, measured as one step in the picture at its cue. A motion is the other noun. It may run for any length, it is declared on the element it moves, and its length is the seconds between the cue it departs on and the cue it arrives on. No length is ever written in the markup, so a line voiced again times the motion again, as it times every reveal.deck/index.html
data-motion names the @keyframes the element moves through, and data-motion-from and
data-motion-to are cue names local to the slide, which join the slide’s cues as a moment’s do.
data-motion-ease is one of firm, glide, settle and spring, and firm is the default
because it keeps a little speed into its last frame, so its arrival stops on a frame edge that
verify can measure. settle and spring ease to rest, so an arrival on either is allowed only on a
cue cues.json marks "verify": false, and anywhere else is
MOTION_EASE_UNMEASURABLE.
Three more things on a page may move for longer than a moment, and each is declared too.
Things that move shows each of them in a working deck.
Undeclared motion is refused
Verify reads a reveal as a rise in how much of the frame changed, and anything else moving at the same time rises with it, so an undeclared animation makes a cue read early or makes a control span dishonest. So anything that still moves 0.46 seconds after it starts, and is not inside a declared motion, loop or region, isPAGE_MOTION_UNDECLARED.
The rule is held in two places. The index page reads the computed style of every element of every
slide, so check names a long or endless CSS animation the markup forgot before a frame is recorded.
The recorder meets every animation as the stepped clock first sees it, so record names one a script
starts. A frame hook with no region is undeclared from the call that registers it.
A script can also change the picture in one step, with no animation at all, such as a setTimeout
that shows an element. Verify reads that as a reveal nobody cued, or a mask hides it. So the recorder
watches what the page changes between two frames, outside every cue, frame hook and animation the
runtime ran, and a change that sits inside no declared motion, loop or region and shows in the frame
is PAGE_CHANGE_UNDECLARED. Make the change in a cue’s
handler, draw it from a frame hook with a region, or declare it as a motion.
A <video> or an <audio> element is refused outright as
PAGE_REALTIME_MEDIA, because it plays at its own rate
and not on the stepped clock. Footage plays in a clip section, and a picture another program draws
plays in a render section.
On a still, which has no clock, a loop rests in its own style and a motion whose departure has fired
is drawn where it ends.
The lane report
While the recorder steps a section, the runtime samples every declared motion, loop and region on each frame and keeps one lane for each. The recording log carries the lanes asrecording.report.motions, and verify masks and measures from them rather than from any file kept
by hand.
A render section’s command writes its lanes in this same shape, so verify reads one shape whoever
drew the picture. Verify says how a cue is measured with the lanes masked
and how each landing is measured on its own lane.
Cue ownership
Ownership is declared and never inferred. A slide owns exactly the cues it names, which is the whole rule:- Every moment attribute written on an element inside its template, qualified with the slide’s id.
- Every moment named on the left of a
data-classpair, qualified the same way. - Every cue name in the slide’s
data-owns, which is for a cue that only a handler serves. - Every key of a slide’s own
onobject, for a slide a script declares. - Every cue a motion written in its markup departs or arrives on.
PAGE_SLIDE_DOUBLED.
In record mode, ownership decides when each slide mounts.
- A slide mounts at the earliest cue time it owns.
- The first slide to mount is on the stage before the recorder’s first step, so no frame of a recording is ever drawn on an empty stage.
- An element that arrives on a cue due at the section’s start arrives as a cut, as the first slide does, so the section’s first frame shows it. An entrance starts on its cue’s own frame and draws nothing there, and the section’s start has no frame before it to begin on.
- A slide that owns no cue in
?cues=never mounts, which isPAGE_SLIDE_UNUSED. - A cue that no slide of the playing scene owns, and that no handler serves, is
PAGE_NO_OWNER. - The last slide to mount stays until the recording ends.
cues.json and the page
disagree.
When a cue fires
When a cue fires, the runtime adds it towindow.__decktalk.fired and then does three things, in
this order.
- Every moment on the mounted slide that names the cue plays.
- The mounted slide’s own
onhandler for that cue name runs, if it has one. - Every handler that
DeckTalk.on(id, fn)registered for the cue id runs, in the order they were registered.
id, at, frozen and slideId,
so a handler finds its own elements with slide.querySelector and needs no global of its own. A
handler that throws is PAGE_HANDLER_THREW, or
PAGE_SLIDE_HANDLER_THREW for a slide’s own, and the
rest of the cue still runs. A cue that draws nothing and runs nothing is
PAGE_CUE_UNKNOWN, because a name that drew nothing is
almost always a name that drifted between the two files.
Browser preview and recording
A page holds scenes, a page section plays one scene, and the URL decides what the page is doing. It lists its scenes and slides with no query, previews a scene at?scene=N at the speed a person reads
it, plays a scene against its cue times at ?cues= in the mode every recording is made in, and
freezes one slide at ?freeze=ID for storyboard and check. window.__decktalk.mode holds the
word. Modes and the query keys give
each mode and each key, and a section’s params may name no key outside that list.
A preview and a recording differ in three places.
record, storyboard and check open every page at http://project.localhost/, and answer each
request under that origin from your project directory. A page may therefore fetch() a file beside
it and load an ES module, neither of which works from a file:// URL, and a relative URL resolves
exactly as the author wrote it. A request is answered only from the directory that holds each page
and from the files decktalk.toml names, such as a clip, a music file or an effect, so the script,
the cues, the build directory and the .env holding the speech key are beside the deck and are not
part of it. A declared file is reached only by the spelling decktalk.toml gives it, so a request
that spells it with other capitals is refused on every platform alike. A declared directory is a
place, so a file inside it is served however the page spells its name. Nothing is
downloaded and no port is opened. A page that loads an asset from another origin is
PAGE_CDN_ASSET, because the film would depend on a host the
project does not own.
To open a page in your own browser the same way, run decktalk serve and follow the URL it prints.
The handshake
deadline is record.page_deadline_seconds, 15 by default, the seconds the page has to answer each
call before the recorder stops it.
With t0=signal in the URL the page’s section clock moves only when the recorder steps it. A
browser preview has no t0=signal, so its clock starts when the page loads and plays in real time.
A page that starts a clock of its own at load fires every cue early, by the whole time between load
and the recorder’s first step, and every reveal in the film then comes before its word. A page that
animates something itself draws it from DeckTalk.frame,
which hands it the second each frame shows, and names the region it draws in, as
Motion says.
The recorder waits on readiness and two painted frames and on nothing else, so no fixed settle is
added on top. Frame 0 is the page at second 0, and
How it works explains why every frame after it is
the page at its own second.
The probe reads the whole page back in one call, and the recording log keeps what it answered.
One report() call lists its fields.
The catalog is what every static judgement is read from, rather than a scan of the markup, because
data-spotlight, data-class and data-owns all declare moments no text scan can see. A page that
publishes no catalog is a page whose runtime never registered a scene, and record counts that as a
page error of its own.
A seamless cut
seamless = true on a [[section]] says the section opens on the picture the section before it
ended on, so the cut between them should not show. It is a claim the page keeps rather than an
effect: the runtime carries nothing across a cut, and a scene always starts on its own first slide.
decktalk.toml refuses the key on the first section, which has no picture before it.
verify compares the last frame of the outgoing section with the first frame of the incoming one,
both taken outside any dip, and a changed share over verify.cut_change_max_percent is
CUT_POP. check makes the same comparison from two frozen states
before anything is recorded.
To make a scene open on the previous scene’s last picture, rebuild that picture as the new scene’s
first state.
- Copy the markup of the previous scene’s last slide into the new scene’s first slide.
- Take the moment attributes off the copies, so they are there from the first frame instead of arriving on a cue. The first slide of a played scene enters with no animation.
- Give the copies the same classes and the same styles, so they draw in the same place at the same size.
- Add the new elements with their own
data-in, so they arrive on their own words.
[transition] dips, because a dip fades through black and a seam that fades
cannot be seamless.
Next
- Write your first page: Your first deck
- Choose an effect and size a reveal: Design a slide
- Give every moment its phrase: Cues