@cardstack/choreo 0.0.0 → 0.1.0-unstable.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +55 -6
  4. package/addon-main.cjs +4 -0
  5. package/declarations/anchors.d.ts +26 -0
  6. package/declarations/anchors.d.ts.map +1 -0
  7. package/declarations/arming.d.ts +33 -0
  8. package/declarations/arming.d.ts.map +1 -0
  9. package/declarations/beacon.d.ts +9 -0
  10. package/declarations/beacon.d.ts.map +1 -0
  11. package/declarations/beacons.d.ts +23 -0
  12. package/declarations/beacons.d.ts.map +1 -0
  13. package/declarations/changeset.d.ts +46 -0
  14. package/declarations/changeset.d.ts.map +1 -0
  15. package/declarations/choreo.d.ts +261 -0
  16. package/declarations/choreo.d.ts.map +1 -0
  17. package/declarations/compile.d.ts +45 -0
  18. package/declarations/compile.d.ts.map +1 -0
  19. package/declarations/deliver.d.ts +44 -0
  20. package/declarations/deliver.d.ts.map +1 -0
  21. package/declarations/easings.d.ts +20 -0
  22. package/declarations/easings.d.ts.map +1 -0
  23. package/declarations/far.d.ts +33 -0
  24. package/declarations/far.d.ts.map +1 -0
  25. package/declarations/film/clip.d.ts +51 -0
  26. package/declarations/film/clip.d.ts.map +1 -0
  27. package/declarations/film/clips.d.ts +155 -0
  28. package/declarations/film/clips.d.ts.map +1 -0
  29. package/declarations/film/film.d.ts +1208 -0
  30. package/declarations/film/film.d.ts.map +1 -0
  31. package/declarations/film/graph/adjust.d.ts +112 -0
  32. package/declarations/film/graph/adjust.d.ts.map +1 -0
  33. package/declarations/film/graph/compile.d.ts +140 -0
  34. package/declarations/film/graph/compile.d.ts.map +1 -0
  35. package/declarations/film/graph/host.d.ts +67 -0
  36. package/declarations/film/graph/host.d.ts.map +1 -0
  37. package/declarations/film/graph/nodes.d.ts +287 -0
  38. package/declarations/film/graph/nodes.d.ts.map +1 -0
  39. package/declarations/film/index.d.ts +24 -0
  40. package/declarations/film/index.d.ts.map +1 -0
  41. package/declarations/film/joins.d.ts +143 -0
  42. package/declarations/film/joins.d.ts.map +1 -0
  43. package/declarations/film/math.d.ts +15 -0
  44. package/declarations/film/math.d.ts.map +1 -0
  45. package/declarations/film/overlays.d.ts +54 -0
  46. package/declarations/film/overlays.d.ts.map +1 -0
  47. package/declarations/film/picture.d.ts +104 -0
  48. package/declarations/film/picture.d.ts.map +1 -0
  49. package/declarations/film/plate.d.ts +37 -0
  50. package/declarations/film/plate.d.ts.map +1 -0
  51. package/declarations/film/player.d.ts +120 -0
  52. package/declarations/film/player.d.ts.map +1 -0
  53. package/declarations/film/rail.d.ts +40 -0
  54. package/declarations/film/rail.d.ts.map +1 -0
  55. package/declarations/film/schedule.d.ts +93 -0
  56. package/declarations/film/schedule.d.ts.map +1 -0
  57. package/declarations/film/seam.d.ts +38 -0
  58. package/declarations/film/seam.d.ts.map +1 -0
  59. package/declarations/film/titles.d.ts +37 -0
  60. package/declarations/film/titles.d.ts.map +1 -0
  61. package/declarations/film/types.d.ts +544 -0
  62. package/declarations/film/types.d.ts.map +1 -0
  63. package/declarations/film.d.ts +3 -0
  64. package/declarations/film.d.ts.map +1 -0
  65. package/declarations/gesture.d.ts +33 -0
  66. package/declarations/gesture.d.ts.map +1 -0
  67. package/declarations/index.d.ts +26 -0
  68. package/declarations/index.d.ts.map +1 -0
  69. package/declarations/measure.d.ts +29 -0
  70. package/declarations/measure.d.ts.map +1 -0
  71. package/declarations/path.d.ts +73 -0
  72. package/declarations/path.d.ts.map +1 -0
  73. package/declarations/registry.d.ts +46 -0
  74. package/declarations/registry.d.ts.map +1 -0
  75. package/declarations/run.d.ts +406 -0
  76. package/declarations/run.d.ts.map +1 -0
  77. package/declarations/space.d.ts +31 -0
  78. package/declarations/space.d.ts.map +1 -0
  79. package/declarations/steps.d.ts +479 -0
  80. package/declarations/steps.d.ts.map +1 -0
  81. package/declarations/test-support/index.d.ts +30 -0
  82. package/declarations/test-support/index.d.ts.map +1 -0
  83. package/declarations/types.d.ts +761 -0
  84. package/declarations/types.d.ts.map +1 -0
  85. package/dist/anchors.js +34 -0
  86. package/dist/anchors.js.map +1 -0
  87. package/dist/arming.js +120 -0
  88. package/dist/arming.js.map +1 -0
  89. package/dist/beacon.js +25 -0
  90. package/dist/beacon.js.map +1 -0
  91. package/dist/beacons.js +77 -0
  92. package/dist/beacons.js.map +1 -0
  93. package/dist/changeset.js +129 -0
  94. package/dist/changeset.js.map +1 -0
  95. package/dist/choreo.js +1026 -0
  96. package/dist/choreo.js.map +1 -0
  97. package/dist/compile.js +1403 -0
  98. package/dist/compile.js.map +1 -0
  99. package/dist/deliver.js +326 -0
  100. package/dist/deliver.js.map +1 -0
  101. package/dist/easings.js +39 -0
  102. package/dist/easings.js.map +1 -0
  103. package/dist/far.js +147 -0
  104. package/dist/far.js.map +1 -0
  105. package/dist/film/clip.js +100 -0
  106. package/dist/film/clip.js.map +1 -0
  107. package/dist/film/clips.js +147 -0
  108. package/dist/film/clips.js.map +1 -0
  109. package/dist/film/film.js +4424 -0
  110. package/dist/film/film.js.map +1 -0
  111. package/dist/film/graph/adjust.js +160 -0
  112. package/dist/film/graph/adjust.js.map +1 -0
  113. package/dist/film/graph/compile.js +225 -0
  114. package/dist/film/graph/compile.js.map +1 -0
  115. package/dist/film/graph/host.js +77 -0
  116. package/dist/film/graph/host.js.map +1 -0
  117. package/dist/film/graph/nodes.js +500 -0
  118. package/dist/film/graph/nodes.js.map +1 -0
  119. package/dist/film/index.js +18 -0
  120. package/dist/film/index.js.map +1 -0
  121. package/dist/film/joins.js +260 -0
  122. package/dist/film/joins.js.map +1 -0
  123. package/dist/film/math.js +49 -0
  124. package/dist/film/math.js.map +1 -0
  125. package/dist/film/overlays.js +66 -0
  126. package/dist/film/overlays.js.map +1 -0
  127. package/dist/film/picture.js +58 -0
  128. package/dist/film/picture.js.map +1 -0
  129. package/dist/film/plate.js +36 -0
  130. package/dist/film/plate.js.map +1 -0
  131. package/dist/film/player.js +79 -0
  132. package/dist/film/player.js.map +1 -0
  133. package/dist/film/rail.js +38 -0
  134. package/dist/film/rail.js.map +1 -0
  135. package/dist/film/schedule.js +238 -0
  136. package/dist/film/schedule.js.map +1 -0
  137. package/dist/film/seam.js +64 -0
  138. package/dist/film/seam.js.map +1 -0
  139. package/dist/film/titles.js +45 -0
  140. package/dist/film/titles.js.map +1 -0
  141. package/dist/film/types.js +2 -0
  142. package/dist/film/types.js.map +1 -0
  143. package/dist/film.js +18 -0
  144. package/dist/film.js.map +1 -0
  145. package/dist/gesture.js +111 -0
  146. package/dist/gesture.js.map +1 -0
  147. package/dist/index.js +9 -0
  148. package/dist/index.js.map +1 -0
  149. package/dist/measure.js +99 -0
  150. package/dist/measure.js.map +1 -0
  151. package/dist/path.js +272 -0
  152. package/dist/path.js.map +1 -0
  153. package/dist/registry.js +55 -0
  154. package/dist/registry.js.map +1 -0
  155. package/dist/run.js +2423 -0
  156. package/dist/run.js.map +1 -0
  157. package/dist/space.js +57 -0
  158. package/dist/space.js.map +1 -0
  159. package/dist/steps.js +831 -0
  160. package/dist/steps.js.map +1 -0
  161. package/dist/test-support/index.js +150 -0
  162. package/dist/test-support/index.js.map +1 -0
  163. package/dist/types.js +2 -0
  164. package/dist/types.js.map +1 -0
  165. package/package.json +202 -6
@@ -0,0 +1,1208 @@
1
+ import Component from '@glimmer/component';
2
+ import { type FilmVocabulary } from './graph/host';
3
+ import type { PictureSpec } from './picture';
4
+ import { type RailMark } from './rail';
5
+ import { TICK } from './schedule.ts';
6
+ import type { Beat, Chapter, FilmClock, FilmHandle, Join, Over, Picture } from './types.ts';
7
+ /**
8
+ * `<Film>` — a headless cutting room, in which a 3D scene takes the place
9
+ * of the video track.
10
+ *
11
+ * The model is an editor: one clock, a shot list dropped onto it, and
12
+ * components that read the clock — the lens, the joins, the type, the
13
+ * voice, the timeline. The film is a pure function of that clock, which
14
+ * is why a beat can be widened and the type, the voice, the transport
15
+ * and the readout all re-time together. The picture is whatever the film
16
+ * hands in through the `Picture` port; two reference films hand in a
17
+ * WebGL page in an iframe (notes/film-construct.md).
18
+ *
19
+ * THE CAMERA. The score authors the shot and the lens CHASES it: one
20
+ * critically-damped stage here, the page's own chase as the second, so
21
+ * two integrators in series bound the jerk. That is what gives the films
22
+ * their hand-held quality, and it is exactly what forfeits seeking — so
23
+ * a skip here is an edit, never a seek: the score is re-cut from a
24
+ * chapter's head and replays.
25
+ *
26
+ * WHAT THE POSE ARGS MEAN (an orbit rig):
27
+ * yaw → the orbit's azimuth, degrees
28
+ * pitch → elevation, degrees
29
+ * dolly → magnification: 1 fits the subject, bigger is tighter
30
+ * lookY → how far up the subject the lens aims, from the rig's mid-height
31
+ * fx, fz → where on the ground the lens aims
32
+ * ox → an off-centre frustum, fractions of the frame
33
+ */
34
+ /**
35
+ * ONE TICK IS TWO SECONDS, and every beat is a whole number of them.
36
+ * `@through` splits its clock evenly between waypoints, so a beat buys
37
+ * the screen time it wants by contributing that many waypoints to the
38
+ * path — and a beat that wants to HOLD contributes the same pose several
39
+ * times over, which a Catmull-Rom spline comes smoothly to rest on.
40
+ */
41
+ export { TICK };
42
+ export interface FilmSignature {
43
+ Args: {
44
+ /** which cut of the film this is: logged at boot, shown under `?debug` */
45
+ build?: string;
46
+ /** the film's own clock, when the picture keeps one (years) */
47
+ clock?: FilmClock;
48
+ /** mounted in a page's iframe: the door still opens it, but no transport and no plate */
49
+ embed?: boolean;
50
+ /** the seam a beat gets when it names none */
51
+ join?: Join;
52
+ /** the chapter menu's second line */
53
+ menuSub?: string;
54
+ /** the film's name, as the menu heads it */
55
+ menuTitle: string;
56
+ /** a short name for the film (a body class, the boot log) */
57
+ name: string;
58
+ /** the air of a shot has been applied: grade, sun, weather — a hook for what the construct does not know */
59
+ onAir?: (beat: Beat, picture: Picture, hard: boolean) => void;
60
+ /** a beat has landed — a hook for what the construct does not know */
61
+ onBeat?: (beat: Beat, picture: Picture) => void;
62
+ /**
63
+ * HOW DEEP THE FILM'S SEAMS GO, when a beat does not say. `picture`
64
+ * (the default) transitions the picture and makes the incoming type
65
+ * wait for the seam; `everything` puts the seam above the type, the
66
+ * insert and the clip, so the incoming setting is already under way
67
+ * when the sweep reveals it. See `Over`.
68
+ */
69
+ over?: Over;
70
+ /**
71
+ * THE RAIL — the film on one line, on the picture: chapters as dots
72
+ * on a rule, the head riding it, the year when the rule is a clock.
73
+ * It is the second film's transport and not every film's: a film
74
+ * whose own bar is the whole story of its progress says `false` and
75
+ * keeps the floating player alone.
76
+ */
77
+ rail?: boolean;
78
+ /**
79
+ * HOW THE FILM SEEKS — the fork everything hangs on.
80
+ *
81
+ * `cut` (the default) is the hand-held film: a cascaded spring chases
82
+ * the score, which is what gives the lens its sway and its arrival at
83
+ * pace, and exactly what forfeits random access — a spring's state is
84
+ * its history, so a skip re-cuts the score from a chapter's head.
85
+ *
86
+ * `exact` makes the whole film a pure function of one clock: no
87
+ * integrator in the pose path, the beat in force derived from the
88
+ * time, the seams driven by it, the type's run seeked to it. Scrub
89
+ * anywhere and the frame is correct; `renderAt(t)` on a fresh page is
90
+ * the same frame as playing there. It used to cost the hand — the
91
+ * spring was the only thing softening the spline, and an exact film
92
+ * could not have one. `@settle` is that hand written as a function of
93
+ * the clock, so it no longer does.
94
+ *
95
+ * What `exact` still costs is a read-back per cut: every seam holds a
96
+ * still of the outgoing frame, because the page's own dissolves run
97
+ * on their own clock.
98
+ */
99
+ seek?: 'cut' | 'exact';
100
+ /**
101
+ * THE OPERATOR'S SECOND HAND, in seconds.
102
+ *
103
+ * The camera path is a spline through every shot's pose, and a spline
104
+ * crosses its control points with a step in curvature — a tick, on
105
+ * every waypoint, for the whole running time. A spring chasing the
106
+ * pose hides it and cannot be seeked; this is an average of the path
107
+ * over a window this wide, which is the same cure written as a pure
108
+ * function of the clock (`settleThrough`). Zero turns it off.
109
+ *
110
+ * Keep it well under the gap between waypoints — a film that holds a
111
+ * pose for two seconds wants a fraction of a second here, not two —
112
+ * or the lens stops visiting the poses the score wrote.
113
+ */
114
+ settle?: number;
115
+ /** the level each line was mixed to (0..1) */
116
+ voGain?: Record<string, number>;
117
+ };
118
+ Blocks: {
119
+ /** under the stage, outside the picture: a wall plate, a cutting room */
120
+ default: [FilmContext];
121
+ /** the back matter, inside the end card */
122
+ end: [FilmHandle];
123
+ /** the front matter, on the door */
124
+ gate: [FilmHandle];
125
+ /** the picture: a component that registers what it is (`IframePicture`), and declares `f.picture.*` */
126
+ picture: [(spec: PictureSpec | null) => void];
127
+ };
128
+ }
129
+ /** what the blocks are given: the handle, and the vocabulary a film's score is written in */
130
+ export type FilmContext = FilmHandle & FilmVocabulary;
131
+ export declare class Film extends Component<FilmSignature> {
132
+ /**
133
+ * THE GRAPH, compiled. A film written as `f.Spine` / `f.Chapter` /
134
+ * `f.Shot` in the default block compiles to the same rows a table would
135
+ * hand in (`graph/compile.ts`); the table arguments, when given, win.
136
+ */
137
+ private graph;
138
+ private takeGraph;
139
+ /**
140
+ * THE PICTURE, as it registered itself from the `<:picture>` block: the
141
+ * page, its files, how it is seated, its rig, its moods. Nothing here
142
+ * is about the edit, which is why none of it is an argument any more.
143
+ */
144
+ private picture;
145
+ private takePicture;
146
+ private get assets();
147
+ private get standing();
148
+ private get frameTitle();
149
+ /**
150
+ * THE WHOLE SHOT LIST, and there is only one place it comes from: the
151
+ * score in the default block, compiled. The film used to take a
152
+ * `Beat[]` as an argument as well, which is how both films were fed
153
+ * until Phase 2 migrated them; the table path is gone now that nothing
154
+ * hands one in, and the graph is the only way to write a film.
155
+ */
156
+ private get all();
157
+ private get chapters();
158
+ private get voSecs();
159
+ private get voGain();
160
+ private get grades();
161
+ private get lookFx();
162
+ private get lutAmount();
163
+ private get defaultJoin();
164
+ /**
165
+ * WHERE THIS SEAM IS PLAYED. A seam the PICTURE declares by name is
166
+ * played in its glass with that name; the three the film knows how to
167
+ * mix are played there under the film's own law; everything else is an
168
+ * overlay in this document. The name is the only thing that decides,
169
+ * which is what makes the vocabulary open: a picture that declares
170
+ * `ridged-burn` gets `<f.Join @presentation="ridged-burn" />` for free,
171
+ * and the film learns nothing about it but its length.
172
+ */
173
+ private glassFor;
174
+ /** how deep a seam goes when nothing says: the film's own default */
175
+ private get defaultOver();
176
+ /**
177
+ * HOW DEEP THIS BEAT'S SEAM GOES. The row says, or the presentation
178
+ * says, or the film does. A seam that covers the furniture is always a
179
+ * seam with a still in the DOM: the picture's own glass is inside the
180
+ * canvas and cannot cover anything outside it.
181
+ */
182
+ private overOf;
183
+ /** every seam the film can play: its own twelve, and any the score brought */
184
+ private get presentations();
185
+ /** seconds a seam holds the picture; nothing, for one the film does not know */
186
+ private secsOf;
187
+ /** does the seam paint a still of the outgoing frame over the incoming one */
188
+ private holdsStill;
189
+ /** how much of a frosted city a film frame can carry */
190
+ private get cityGlass();
191
+ /** the rig's mid-height: `lookY` is world height minus this */
192
+ private get rigMid();
193
+ /** how much the passing cloud thickens the air */
194
+ private get cloudHaze();
195
+ /** the face type in the world is set in; a film that names none of it
196
+ * leaves the page's own */
197
+ private get worldFamily();
198
+ private get worldWeight();
199
+ private get worldTrack();
200
+ /** the editorial accent: the film's own, or the scene's palette by day */
201
+ private accentFor;
202
+ /** the film's handles, as the door and the card are given them */
203
+ get handle(): FilmContext;
204
+ private get exact();
205
+ /** the master clock: seconds into the whole film (exact mode) */
206
+ private t;
207
+ /** a seek happened since the last fold: it lands instantly, seam re-made */
208
+ private jumped;
209
+ /** the score region's context, read every frame for its current run */
210
+ private scoreCtx;
211
+ private scoreRun;
212
+ /** the element the seam overlays live in, so their animations can be seeked */
213
+ private joinsEl?;
214
+ /** where the seam on screen began, film seconds, and how long it plays */
215
+ private seamAt;
216
+ private seamLen;
217
+ /** the lead whose air has been applied ahead of its beat */
218
+ private airedFor;
219
+ /** where the sun was before this beat asked, for a deterministic walk */
220
+ private sunFrom;
221
+ /**
222
+ * THE CLIPS ON SCREEN, one row per lane, in lane order — which is also
223
+ * paint order. There used to be one of these and a beat that declared
224
+ * two clips kept the second one only.
225
+ */
226
+ private clipRows;
227
+ /** the picture read back, per lane, for a freeze */
228
+ private clipFreezes;
229
+ private clipNow;
230
+ private clipMedia;
231
+ private clipEl;
232
+ /**
233
+ * THE CLIP, EVERY FRAME. Resolve what should be on screen at this film
234
+ * time, land the DOM when that changes (a new window is a new element,
235
+ * so the entrance replays), and drive the media: a playing video is
236
+ * left to run and corrected when it drifts, a paused or scrubbed one
237
+ * is seeked, a held one stands at its out point, a frozen one is left
238
+ * alone. A freeze reads the picture back when its window opens — or,
239
+ * on a seek into it, after the page has been stood at that moment.
240
+ */
241
+ private clips;
242
+ /**
243
+ * ONE LANE'S MEDIA, every frame. A playing video is left to run and
244
+ * corrected when it drifts, a paused or scrubbed one is seeked, a held
245
+ * one stands at its out point, a frozen one is left alone.
246
+ *
247
+ * A CLIP'S SOUND NEVER STEPS. The element arrives at zero and is eased
248
+ * up to its level, and when its window closes it is eased back down
249
+ * before the exit fade takes the element away — a source that starts or
250
+ * stops at level is a click, whatever it is playing.
251
+ */
252
+ private driveClip;
253
+ /**
254
+ * THE PICTURE AT A MOMENT THAT HAS PASSED. A freeze seeked into needs
255
+ * the frame from its window's head: the score is stood there for one
256
+ * evaluation (the camera step reports the pose), the page is posed and
257
+ * clocked to that moment, and the fold's own time is restored after
258
+ * the read-back.
259
+ */
260
+ private freezeClipAt;
261
+ /**
262
+ * FILM SECONDS AT WHICH A BEAT'S OWN SHOT BEGINS. The cues fire one
263
+ * tick after the beat table says, because the pose-in-force seed
264
+ * occupies the spline's first slot — so the windows the exact clock
265
+ * derives carry the same offset, and the picture and the type agree
266
+ * with the cut film to the frame.
267
+ */
268
+ private beatStart;
269
+ /** the beat in force at a film time */
270
+ private indexAt;
271
+ /** the score's region, held so its run can be driven by the clock */
272
+ private grabScore;
273
+ private joinsWrap;
274
+ /** the score must stand even while paused: a paused exact film is a still */
275
+ get scoreOn(): boolean;
276
+ /**
277
+ * SEEK. In exact mode the clock is set and the next fold lands there;
278
+ * in cut mode the nearest shot's head is re-cut to, which is the only
279
+ * honest seek a chased lens has.
280
+ */
281
+ private seek;
282
+ /**
283
+ * A STILL AT A TIME, for a capture worker: pause, seek, run one frame
284
+ * of the loop against a zero step, and give the page two frames to
285
+ * draw it. The same code path as playing there — that is the claim.
286
+ */
287
+ /** a frame is being rendered rather than scrubbed to: seams play */
288
+ private rendering;
289
+ /**
290
+ * A RENDER OPENS THE DOOR ITSELF.
291
+ *
292
+ * `?from=0` puts the title card up and leaves the film unbooted — and
293
+ * an unbooted film HAS NO SCORE, because the whole `<Choreo>` sits
294
+ * behind `{{#if this.booted}}` (the rig has to be an insertion before
295
+ * the camera step has a subject). So `fold`'s `run.time = t` wrote to
296
+ * a null run, `onCamera3D` never fired, `goal` stayed wherever the
297
+ * load path's `snap` seated it, and exact mode copied that one pose
298
+ * forward on every frame. Measured on towers, a stepped render swept
299
+ * half a degree of yaw across 273 seconds where the compiled path
300
+ * sweeps 380 — a whole render of a frozen lens, and `film-render.mjs`
301
+ * hit it every time, because its default `--from` is 0.
302
+ *
303
+ * A render is a request for the film, not for the lobby. So it opens
304
+ * the door itself, MUTED — nothing was clicked, and no browser hands
305
+ * out audio for a gesture nobody made.
306
+ */
307
+ private renderAt;
308
+ /**
309
+ * THE DOOR, OPENED BY A SCRIPT. Two waits, and neither is optional.
310
+ *
311
+ * The handle publishes itself from the mount modifier, a frame before
312
+ * the iframe's load seats the port — so a renderer that polls for
313
+ * `__choreo[name]` can call in before there is a picture to begin
314
+ * against, and `begin` would only pocket the choice. And `begin`
315
+ * itself DEFERS the lap bump that buys the score region its second
316
+ * render pass: until that pass lands the run does not exist, and a
317
+ * seek against a missing run is exactly the frozen lens this avoids.
318
+ *
319
+ * Both waits are bounded. A render against a film that never arrives
320
+ * degrades to the frame it would have drawn before, rather than
321
+ * hanging the worker on a promise that will not settle.
322
+ */
323
+ private openTheDoor;
324
+ /**
325
+ * THE FOLD (exact mode): the beat in force is the one whose window
326
+ * holds the clock; entering it — forward by playing, or by a seek in
327
+ * either direction — applies it whole, because every beat asserts its
328
+ * complete state and never inherits. A seek that lands inside a seam
329
+ * re-makes the outgoing frame first, so the seam plays from the middle
330
+ * exactly as it would have played into it.
331
+ */
332
+ private fold;
333
+ /** a line, from the middle: what a seek owes the voice */
334
+ private speakAt;
335
+ /**
336
+ * THE SEAM'S GATE (`film/seam.ts`). A join's still is a JPEG the
337
+ * browser has not decoded yet on the frame it is handed over, and the
338
+ * lens moves on that frame — so every cut used to show three frames of
339
+ * the incoming shot before the outgoing one was on screen. The gate
340
+ * decodes first and holds the cut; a seam with no still runs at once.
341
+ */
342
+ private seam;
343
+ /**
344
+ * THE SEAM, EXACT. Every join that carries the outgoing frame carries it
345
+ * as a still in the DOM — the page's own dissolves run on the page's
346
+ * clock and cannot be stood at a time — and the whip, which is the
347
+ * chaser's, becomes a cut. The overlay is then driven by the fold.
348
+ */
349
+ private seamExact;
350
+ /** the seam itself: the still on screen, the lens on the incoming shot */
351
+ private seamPlay;
352
+ /** the last cue has run: hold the pose, settle the music, offer the card */
353
+ private end;
354
+ /** the beat on screen; the score names it, the front layer renders it */
355
+ private beatIndex;
356
+ private playing;
357
+ /** bumping this edits the score, which is how a finite film loops */
358
+ private lap;
359
+ private booted;
360
+ /** which seam is playing, and a key that replays it: bumped on every cut */
361
+ private joinKind;
362
+ private joinStamp;
363
+ /** the seam on screen covers the furniture, not only the picture */
364
+ private joinOver;
365
+ private overTimer;
366
+ /**
367
+ * THE SEAM IN THE GLASS. Its kind, how long it has and the colour it
368
+ * passes through — and `reveal`, how much of the incoming frame is NOT
369
+ * covered by it, which is the film's other half: the type, the stamp
370
+ * and the inserts wear it, so a caption is uncovered by the same
371
+ * gesture that uncovers the picture.
372
+ */
373
+ private glassKind;
374
+ /** the picture's own name for it, when the picture declared this seam */
375
+ private glassName;
376
+ private glassLen;
377
+ private glassColor;
378
+ private reveal;
379
+ /** the captured outgoing frame every freeze-based join plays with */
380
+ private freeze;
381
+ /** the 'dip' join's veil colour */
382
+ private dipColor;
383
+ /** where the iris closes to, as inline custom properties */
384
+ private irisAt;
385
+ /** dev beacon: the first uncaught error, worn on the sleeve */
386
+ private fault;
387
+ /**
388
+ * THE SEAM, DRIVEN. Half of it is the picture, which is handed two
389
+ * numbers and composites them in light; half is the furniture, which
390
+ * wears what is left over. Both come from one progress, and that
391
+ * progress is a function of the clock — so a seek through a seam needs
392
+ * nothing stood at a time, which is what the DOM overlay always did.
393
+ */
394
+ /** take a driven seam off: the picture back to live, the furniture whole */
395
+ private endSeam;
396
+ private driveSeam;
397
+ /**
398
+ * Run a seam's overlay: a new element per cut, from its own first
399
+ * frame. A seam played `everything` is lifted above the type, the
400
+ * insert and the clip for as long as it runs, and put back after — an
401
+ * empty layer left standing over the furniture would swallow the next
402
+ * shot's pointer events, and there is no event for a CSS animation the
403
+ * film did not start.
404
+ */
405
+ private play;
406
+ /**
407
+ * THE ENDING. A film that laps back to its own first frame has no
408
+ * ending, and the coda earns one — so when the last cue has run, the
409
+ * run simply stops: the final pose holds, the music settles down a
410
+ * step, and a card offers the way back in. Replay is a choice the
411
+ * viewer makes, never something the clock does to them.
412
+ */
413
+ private ended;
414
+ /**
415
+ * THE YEAR IS THE SUBJECT. A film whose whole argument is that one
416
+ * building took a hundred and forty-four years cannot leave the audience
417
+ * to infer the date from the state of the scaffolding: the year is on
418
+ * screen, and it is on a line from the first stone to the present, so
419
+ * 1954 is not a number but a POSITION — two thirds along a rule that
420
+ * still has a third to run.
421
+ */
422
+ private yearNow;
423
+ private yearSent;
424
+ /** whether the announced year is currently seated in the scene */
425
+ private stampOn;
426
+ /** the page has been told the picture is static — see the frame loop */
427
+ private idled;
428
+ private gradeSent;
429
+ private rakeSent;
430
+ private rakeDeg;
431
+ /** which beat's lightning cue has fired (reset on every beat change) */
432
+ private struck;
433
+ /** which of the beat's hours (Beat.hours) is in force; -1 between beats */
434
+ private hourStep;
435
+ /** the next grade goes to the glass without its ease (a still-join) */
436
+ private gradeSnap;
437
+ /** the last stock sent to the glass, by name; undefined before the first */
438
+ private lutSent;
439
+ /** the last hour a beat named, so the night can be known without naming it again */
440
+ private hourNow;
441
+ /** the scrub track's width in px, kept by a ResizeObserver */
442
+ private trackW;
443
+ private trackRO?;
444
+ /** the chapter fractions the bar is drawn with, computed once */
445
+ private segFr?;
446
+ private trackWrap;
447
+ /**
448
+ * THE KNOB IS THE FILL'S END. The bar is chapters with a gap between
449
+ * them, so the playhead's position is not linear in the playhead's
450
+ * value — a second formula for the knob drifts from the fill inside
451
+ * every segment. This is the one formula: the same fractions the fill
452
+ * uses, over the measured track, with the gaps added back.
453
+ */
454
+ private headPx;
455
+ /** which tower the lineup's metronome is on; -1 between lineups */
456
+ private cycleStep;
457
+ private film?;
458
+ /**
459
+ * THE FRAME'S DRESSING, WHERE THE PICTURE CAN TAKE IT.
460
+ *
461
+ * The wash and the cloud were CSS layers over the canvas, which meant a
462
+ * seam could not touch them: the picture dissolved and the paper behind
463
+ * the type snapped on the cut frame — two edits at once. A transition
464
+ * applies to what its own container renders, so they belong in the
465
+ * container, and the container is the picture's post pass (where the
466
+ * vignette, the grain, the split tone and the grade already live). A
467
+ * picture that offers `wash`/`cloud` gets them and the film stops
468
+ * drawing its own; one that does not keeps the CSS layers.
469
+ */
470
+ private washInGlass;
471
+ private cloudInGlass;
472
+ private dimInGlass;
473
+ /** the palette's paper, as `wear` last read it, for the wash */
474
+ private paperNow;
475
+ /** the last wash the picture was told about: mode and paper, told on change */
476
+ private washSent;
477
+ private frameEl?;
478
+ private lineEl?;
479
+ private dotEl?;
480
+ private tetherEl?;
481
+ private dim?;
482
+ /** the dim's own value, chased rather than cut so it never snaps on */
483
+ private dimNow;
484
+ private plateEl?;
485
+ private rowEls;
486
+ private raf;
487
+ private lastTick;
488
+ /** when the current beat was entered, for the clocks it owns */
489
+ private beatAt;
490
+ /** where the beat's sky word was planted, in world azimuth */
491
+ private skyAz;
492
+ /** the azimuth the mark word is planted on, behind the building */
493
+ private markAz;
494
+ /** a sideways nudge in world units, for a word too short to centre */
495
+ private markOff;
496
+ /** the height the word rises FROM — set when it actually arrives */
497
+ private markFrom;
498
+ private markSeated;
499
+ /** the last pose actually pushed — the final smoothing stage */
500
+ private sent?;
501
+ /** the light in force, and the light the beat asked for */
502
+ private sunNow;
503
+ private sunGoal;
504
+ private idle;
505
+ /** the fraction the hand is holding while scrubbing, else null */
506
+ private scrubAt;
507
+ /** the fraction under a resting pointer on the bar, else null */
508
+ private hoverAt;
509
+ /** the master fader, 0..1, remembered across visits */
510
+ private vol;
511
+ /** the centre-screen glyph that confirms a play or a pause */
512
+ private burst;
513
+ /** the viewer's pause: the run, the page and the beat clock all hold */
514
+ private paused;
515
+ private pausedAt;
516
+ /** the lap whose head beat the cut itself already applied */
517
+ private primedLap;
518
+ /** the bar's own element, for the slider's live value */
519
+ private scrubEl?;
520
+ private shownPct;
521
+ private clock;
522
+ private full;
523
+ private idleTimer;
524
+ private shownSec;
525
+ /** the whole film, in seconds, leads included */
526
+ private get totalSecs();
527
+ private secsBefore;
528
+ /** one segment per chapter, sized and placed in whole-film fractions */
529
+ get playbar(): {
530
+ head: number;
531
+ n: string;
532
+ style: string;
533
+ title: string;
534
+ }[];
535
+ private static mmss;
536
+ get duration(): string;
537
+ /**
538
+ * What the hand is pointing at — resting on the bar or dragging it:
539
+ * the chapter under it and the time, on two lines, the way every
540
+ * player's hover bubble does. A drag wins over a hover.
541
+ */
542
+ get tip(): {
543
+ ch: string;
544
+ t: string;
545
+ } | null;
546
+ /** rolling AND not held by the viewer: what the play button shows */
547
+ get live(): boolean;
548
+ /** the fader's setting, as it was left last time — one setting for
549
+ * every film, the way a viewer's volume is theirs and not the film's */
550
+ private static savedVol;
551
+ /** sound on, and the fader up: the speaker glyph's three states */
552
+ get loud(): 'high' | 'low' | 'off';
553
+ get volStyle(): string;
554
+ /**
555
+ * THE FADER. Dragging it up while muted unmutes, because that is what
556
+ * the hand means; the value is the page's master gain, under the mix
557
+ * and the mute, and it is remembered for the next visit.
558
+ */
559
+ private fade;
560
+ /** the glyph that flashes in the middle of the picture on play/pause */
561
+ private pop;
562
+ private burstDone;
563
+ /**
564
+ * A tap on the picture is play/pause and two taps are full screen —
565
+ * the grammar of every player since the first one. The controls, the
566
+ * door, the end card, the rail and the menu keep their own clicks.
567
+ */
568
+ private static onGlass;
569
+ private tap;
570
+ private tapTwice;
571
+ /** the bar's own element, for the slider's live value */
572
+ private slider;
573
+ private wake;
574
+ private scrubFrom;
575
+ /**
576
+ * The bubble and the ghost fill follow the pointer, not the playhead:
577
+ * the bubble's x in pixels (held off the edges so it never clips) and
578
+ * the hover fraction, both as properties, both without a re-render.
579
+ */
580
+ private point;
581
+ private scrubDown;
582
+ private scrubMove;
583
+ private scrubLeave;
584
+ /**
585
+ * THE PLAYHEAD SNAPS TO A SHOT, because this film cannot seek: the
586
+ * lens is an integrator and a run dropped into its own middle arrives
587
+ * with the wrong velocity (docs/choreo-splices.md). So the drag reads
588
+ * as time, the label names the shot under the hand, and the release
589
+ * RE-CUTS from that shot's head — which is the same edit the chapter
590
+ * buttons and the arrow keys make.
591
+ */
592
+ /**
593
+ * A HAND ON THE BAR NEVER LANDS ON THE CARD. The bar's right edge is
594
+ * the film's last second, and a click there — or a drag that ran off
595
+ * the end — seeks to the total, which is the ending itself: the card
596
+ * came up under a hand that wanted the last shot. The bar stops two
597
+ * seconds short; the ending is reached by playing to it.
598
+ */
599
+ private barSecs;
600
+ private scrubUp;
601
+ private screen;
602
+ private fullChange;
603
+ /**
604
+ * THE POINTER MOVES THE WORLD A LITTLE.
605
+ *
606
+ * A film that cannot be touched is a video, and the whole argument
607
+ * here is that this is a scene. So the cursor gets a few degrees of
608
+ * lean: the lens takes a fraction of it (real parallax — the building
609
+ * and the hills separate) and the type planes take more of it in the
610
+ * other direction, each layer by its own depth. It is small enough to
611
+ * be felt rather than played with, and damped, so it never fights the
612
+ * shot the score is composing.
613
+ */
614
+ private lean;
615
+ private leanTo;
616
+ private aim;
617
+ /** the haze the beat asked for — the weather drift breathes around it */
618
+ private hazeBase;
619
+ private goal;
620
+ private mid;
621
+ private midV;
622
+ private now;
623
+ private nowV;
624
+ /** the world-layer plane's own fade, so a chapter's type breathes in */
625
+ private skyOn;
626
+ /**
627
+ * THE SKY WORD'S OPACITY AS ACTUALLY SENT — not the ramp above.
628
+ *
629
+ * `skyOn` only says a word is WANTED; what the word is actually wearing
630
+ * is that ramp times the beat's own in and out, and the out takes it to
631
+ * nothing by 68% of the beat. When the next beat had no word the film
632
+ * used to hand the picture `skyOn` to fade from — a number still sitting
633
+ * near 1 — so a word that had been gone for seconds snapped back to 92%
634
+ * on the frame after the cut and faded out a second time, across the
635
+ * seam. Fade from what the word is wearing instead.
636
+ */
637
+ private skyA;
638
+ /** the time of day the furniture is currently dressed for */
639
+ private wearing;
640
+ /** the cast-shadow offset the type is currently wearing, px */
641
+ private shadow;
642
+ private pageEl?;
643
+ /**
644
+ * WHERE THE CUT STARTS — the arrow keys' one piece of state.
645
+ *
646
+ * A film this long is unwatchable without chapter skip, and seeking a run
647
+ * that carries an integrator is not honest (the chaser's pose depends on
648
+ * its history, which is the price Drift's doctrine names out loud). So
649
+ * skipping RE-CUTS instead of seeking: the beat list is sliced here, the
650
+ * path and the cues are both derived from that list, and the score the
651
+ * region sees is a different, shorter film. An edited timeline replays
652
+ * from its own head, which is exactly the behaviour wanted — and it is
653
+ * the same move Sylva's lap makes to loop.
654
+ *
655
+ * `?from=N` seeds it, so a shot can be linked to.
656
+ */
657
+ /**
658
+ * The deep link's shot, as asked (`?from=N`). Clamped against the
659
+ * rows when READ, never when set: with the score a graph the rows
660
+ * arrive a frame after construction, and clamping here against zero
661
+ * rows once made every deep link −1.
662
+ */
663
+ private fromRaw;
664
+ private get from();
665
+ private set from(value);
666
+ /** sound is off until asked for: nobody's first second should be音 */
667
+ private sound;
668
+ /**
669
+ * THE GATE. This film is narrated, and narration behind a mute button
670
+ * is a film shown with the projector lamp off — so the front door asks.
671
+ * One held frame, one choice, and the choice IS the user gesture that
672
+ * autoplay policy wants anyway: the browser unlocks audio on the same
673
+ * click that starts the picture. Museums have worked this way forever.
674
+ *
675
+ * `?from` skips it (an authoring link wants the shot, not the lobby)
676
+ * and so does embedding.
677
+ */
678
+ private gate;
679
+ /** the scene is loaded and seated behind the gate — the door can open */
680
+ private ready;
681
+ /**
682
+ * One pass BEHIND `booted`, on purpose. A region's first render
683
+ * collects no score, so anything standing in it on that pass — the
684
+ * first beat's type, a head photo — was never inserted and never
685
+ * animates. Content mounts on this flag instead, which flips a tick
686
+ * later: the same render that gives the score its first real pass
687
+ * hands the front layer a genuine insertion, and the opening beat's
688
+ * type is DELIVERED like every other beat's instead of being found
689
+ * already on the glass.
690
+ */
691
+ private rolling;
692
+ get beats(): Beat[];
693
+ /** the first beat of each chapter, in whole-film indices */
694
+ get chapterHeads(): number[];
695
+ /** where we are in the WHOLE film, not the current cut */
696
+ get absoluteIndex(): number;
697
+ get beat(): Beat;
698
+ /**
699
+ * CAPTIONS. `?subs` seeds them on; CC toggles them.
700
+ *
701
+ * They are the narration, printed. That makes them two things at once
702
+ * and both are wanted: an accessibility track for anyone who cannot or
703
+ * would rather not hear the voice, and — before any voice exists — the
704
+ * only reliable way to find out that a line is two seconds too long for
705
+ * the shot it is sitting on.
706
+ */
707
+ private subsOn;
708
+ get subs(): boolean;
709
+ private cc;
710
+ get grade(): string;
711
+ get chapter(): Chapter;
712
+ get embed(): boolean;
713
+ /** `?debug` puts the build stamp in the corner — see `@build` */
714
+ get debug(): boolean;
715
+ get build(): string;
716
+ get srcdoc(): string | undefined;
717
+ get src(): string;
718
+ get photoSrc(): string;
719
+ /** a photograph that failed to load is no photograph — drop the plate */
720
+ private lost;
721
+ private lostStamp;
722
+ get hasPhoto(): boolean;
723
+ private missing;
724
+ /** a fresh name per lap: an edited score replays, a restarted one fights */
725
+ get filmName(): string;
726
+ /**
727
+ * HOW MUCH HAND, in seconds — see `@settle`.
728
+ *
729
+ * The default is a QUARTER OF THE GAP BETWEEN WAYPOINTS, because that
730
+ * is the only number that means the same thing to two different films.
731
+ * A window is smoothing relative to the spacing of the kinks it
732
+ * smooths: a quarter takes the curvature step off every pose change
733
+ * and is far too little to stop the lens visiting the poses. Both
734
+ * films that ship here spend two seconds a waypoint, so both get half
735
+ * a second; a score that cuts four times as fast gets a quarter of
736
+ * that without being asked, and half a second imposed on it would have
737
+ * flattened its shots.
738
+ *
739
+ * Capped at half a second so a very slow film does not drift, and
740
+ * floored at nothing: `@settle={{0}}` gives the raw spline.
741
+ */
742
+ get settle(): number;
743
+ /**
744
+ * The pose the score OPENS on, declared to the region — without it the
745
+ * first spline segment travels in from the library's default rig, a
746
+ * two-second bounce every cold boot wore before its first real frame.
747
+ */
748
+ get openPose(): {
749
+ dolly: number;
750
+ look: {
751
+ x: number;
752
+ y: number;
753
+ z: number;
754
+ };
755
+ pitch: number;
756
+ x: number;
757
+ y: number;
758
+ yaw: number;
759
+ };
760
+ get filmSeconds(): number;
761
+ /**
762
+ * THE WHOLE FILM IS ONE CAMERA STEP.
763
+ *
764
+ * Every beat contributes `ticks` waypoints: a moving beat is sampled from
765
+ * its head pose to its tail, a holding beat repeats its pose, and the
766
+ * spline runs through the lot on one clock. So the shot list is a path
767
+ * rather than a playlist, the camera crosses each mark with velocity
768
+ * instead of arriving at it, and a hold is genuinely still without
769
+ * anything having stopped.
770
+ */
771
+ /**
772
+ * WHERE A SHOT ENDS — the beat's authored tail, floored so it always
773
+ * reads as a move, and clamped so it never travels past the pose the
774
+ * next shot begins on. Shared by the path (which lerps its waypoints
775
+ * across it) and by the LAUNCH: a cut hands the chaser this shot's own
776
+ * speed, so the lens is already travelling when the cut lands rather
777
+ * than easing up from a standstill.
778
+ */
779
+ private tailFor;
780
+ get path(): import("./schedule.ts").Waypoint[];
781
+ /**
782
+ * Each beat's entrance, as a delay into the one camera step — offset
783
+ * one tick, because the pose-in-force seed occupies the spline's first
784
+ * slot: waypoint k is crossed at (k+1) slots, not k. The old uniform
785
+ * skew was invisible (camera and cues equally late, a constant the
786
+ * chaser's own lag swallowed); a SPLICE made it audible — the snap
787
+ * fired two seconds before the goal stepped, and the chaser spent the
788
+ * gap dragged back toward the old shot. Cue 0 stays at zero: the boot
789
+ * and every re-cut apply their head beat by hand, and its immediate
790
+ * re-fire has always been the first cue's job.
791
+ */
792
+ get cues(): import("./schedule.ts").Cue[];
793
+ /**
794
+ * THE ATTACHMENTS: every window the score drives another region over.
795
+ * The plate for every beat, the insert for a beat with a photograph,
796
+ * the clip for a beat with one — each on the score's own clock, so a
797
+ * seek stands all of them where playing there would. The regions are
798
+ * keyed per beat and mounted for their windows; `remove` past a window
799
+ * leaves them to that; a clip may outlive its beat and holds.
800
+ */
801
+ get windows(): {
802
+ end: "hold" | "remove";
803
+ length: number;
804
+ region: string;
805
+ start: number;
806
+ }[];
807
+ /**
808
+ * THE TRANSPORT — a broadcast bar, not a thermometer. One segment per
809
+ * chapter, sized by the chapter's actual running time, filled by
810
+ * WHOLE-film progress (the old bar measured the current cut, so a
811
+ * skip made it lie), and clickable: the segments are the same re-cut
812
+ * the menu and the arrows perform.
813
+ */
814
+ /**
815
+ * ONE TIMELINE, NOT TWO.
816
+ *
817
+ * The corner carried a strip of chapter segments beside a rule of years:
818
+ * two timelines of the same film, at two different scales, in two
819
+ * different shapes, six inches apart. So the chapters are ON the years
820
+ * now — each one a dot at the year it opens, so the gap between THE SITE
821
+ * and SILENCE is fifty-four years wide and the gap between the last two
822
+ * is a decade, which is the fact the film is about. The dots are still
823
+ * the doors into the chapters.
824
+ */
825
+ get transport(): RailMark[];
826
+ /** the ends of the rule, when it is a clock */
827
+ get railLabels(): [string, string] | undefined;
828
+ /** the rail is on unless the film says otherwise */
829
+ get railOn(): boolean;
830
+ get railReadout(): string;
831
+ /**
832
+ * WHEN EACH CUE LANDS — paced against the voice, not the clock.
833
+ *
834
+ * The phrases used to arrive on four fixed delays, which meant a
835
+ * fourteen-second beat finished its type with five seconds of dead air
836
+ * and a short one crowded the read. So the cues spread themselves: the
837
+ * first lands early, the last lands as the measured line is finishing
838
+ * (`VO_SECS`), and a beat with no recording paces against two thirds of
839
+ * its own length, which is where a read for it would sit anyway.
840
+ */
841
+ /**
842
+ * THE TRANSITION FIRST, THEN THE TYPE. A setting that arrives while the
843
+ * seam is still on screen is read against two pictures at once. The
844
+ * block waits for the incoming join to finish and a breath more, then
845
+ * enters in its order: kicker, glyph, reading — and the lines after.
846
+ */
847
+ get typeAt(): number[];
848
+ get sayAt(): number[];
849
+ /**
850
+ * Thai and Khmer are not kanji: their ascenders, vowel marks and
851
+ * subscripts stand far outside a CJK-tuned glyph box, so at the kanji
852
+ * size they collide with the kicker above and the reading below. Tall
853
+ * scripts take a reduced setting with real leading.
854
+ */
855
+ /**
856
+ * HOW MANY CHARACTERS HAVE TO FIT. A vertical setting is as tall as
857
+ * its string, and 一国一城令 is five glyphs — at the plate's own size
858
+ * that column runs off the top of the frame and the film crops its
859
+ * own title. The count goes to CSS, which sizes the column to the
860
+ * height available rather than to a number somebody typed once while
861
+ * looking at a three-glyph word.
862
+ */
863
+ get glyphFit(): string;
864
+ get glyphTone(): string;
865
+ /** the lineup's current kanji; empty between lineups */
866
+ get stamp(): string;
867
+ /**
868
+ * THE POSTER IS AN EVENING (by default). The film proper opens in the
869
+ * morning, so the door stands in the last of the light with a long hard
870
+ * shadow across the empty half of the frame; pressing it turns the day
871
+ * over. The film may dress its door otherwise (`@poster`).
872
+ */
873
+ private dressPoster;
874
+ private announced;
875
+ private mount;
876
+ private trackEls;
877
+ private dimEl;
878
+ private plate;
879
+ private snap;
880
+ /**
881
+ * THE WHIP. A 'whip' join does not snap: the goal steps across the
882
+ * seam (the splice does that) and the chaser races it on a briefly
883
+ * stiff spring — a fast, smooth tween between the shots that is over
884
+ * in a third of a second and never reads as a glide. While it runs,
885
+ * the spring is ~3× its documentary stiffness; then the hand relaxes.
886
+ */
887
+ private whipUntil;
888
+ /**
889
+ * One critically-damped stage. The page's own chase is the second, which
890
+ * is the whole cascade argument getting made for free by the fact that the
891
+ * scene lives in another document.
892
+ */
893
+ private chase;
894
+ /**
895
+ * THE POSTER TURNS.
896
+ *
897
+ * A still frame behind a title is indistinguishable from a JPEG, and
898
+ * this whole film's argument is that it is not one. So while the door
899
+ * is up the lens makes a slow circuit of the opening pose — a few
900
+ * degrees either side, breathing in and out — which says "live 3D"
901
+ * before a word is read and costs one rAF. It hands over on the
902
+ * click: the film does not snap away from the poster, it flies from
903
+ * wherever the circuit had reached.
904
+ */
905
+ private poster;
906
+ private posterRaf?;
907
+ private posterAt;
908
+ /** where the poster's circuit had reached when the door was answered */
909
+ private posterPose?;
910
+ /**
911
+ * ONE SETTING AT A TIME, AND THIS TIME IT IS GUARANTEED.
912
+ *
913
+ * The hand-off is a leaver and an arriver sharing the corner for half
914
+ * a second, which is the design. But a leaver whose exit never
915
+ * completes just stays — and a block from the first beat was still
916
+ * standing at full strength eight beats later, with two settings of
917
+ * different chapters overprinting each other. Whatever strands it (a
918
+ * removal that waits on a cue the outgoing beat never had), the rule
919
+ * the film states out loud in its own comment is worth enforcing
920
+ * rather than trusting: any block that is not the current one and has
921
+ * outstayed the longest hand-off is taken off the glass.
922
+ */
923
+ private sweepBlocks;
924
+ private frame;
925
+ private tick;
926
+ /**
927
+ * TYPE THROWS ITS SHADOW WHERE THE BUILDING THROWS ITS OWN.
928
+ *
929
+ * The scene lights itself from a key whose position is a property of the
930
+ * hour, and the tower's cast shadow lies along the ground at whatever
931
+ * diagonal that produces. Type set over the frame with a shadow offset
932
+ * down and right — the default of every drop shadow ever shipped — is
933
+ * lit by a different sun than everything behind it, and the eye reads
934
+ * the mismatch long before it can name it. So the key's own screen
935
+ * position comes back from the scene and the offset is simply the
936
+ * direction away from it. At night, or with the sun behind the lens,
937
+ * there is no honest cast shadow and the type wears none.
938
+ */
939
+ private follow;
940
+ /** which of the frame's layers this picture draws itself */
941
+ private takeGlass;
942
+ /**
943
+ * THE WASH, TOLD ON A CHANGE. Two things decide it — the shot's own mode
944
+ * and the palette's paper — and neither changes often, so the picture is
945
+ * told when one does and eases the change itself over the same 700 ms
946
+ * the stylesheet used to.
947
+ */
948
+ private pushWash;
949
+ /** the cloud: into the glass where the picture takes it, onto a CSS plate where it does not */
950
+ private setCloud;
951
+ /**
952
+ * THE FURNITURE WEARS THE SCENE'S PALETTE.
953
+ *
954
+ * The page carries four times of day and swings its whole palette between
955
+ * them, so a scrim and a caption painted in one fixed ochre are correct in
956
+ * exactly one chapter and wrong in the other four — at noon the film's
957
+ * warm paper sits on a cool scene like a sticker. The scene already
958
+ * publishes its ink as CSS variables, so the film simply reads them and
959
+ * dresses itself the same, once per change rather than once per frame.
960
+ */
961
+ private wear;
962
+ /**
963
+ * PLANES, MOVING AT THEIR OWN RATES.
964
+ *
965
+ * The world layer parallaxes because it is genuinely out in the scene at
966
+ * different distances. The front layer has no depth to borrow, so it is
967
+ * given one: over the life of a beat each row drifts and scales on its
968
+ * own rate, the big glyph fastest and nearest, the small print slowest
969
+ * and furthest, so the type reads as a set of planes rather than a
970
+ * sheet. It is the oldest trick in motion graphics and it is here for
971
+ * the oldest reason — a still caption over a moving picture looks
972
+ * pasted on, and a caption that moves WITH the picture looks composited
973
+ * into it.
974
+ *
975
+ * It runs on the beat's own clock rather than Motion's, deliberately:
976
+ * the entrances belong to the score and this belongs to the shot, and
977
+ * putting the two on one timeline would mean the drift restarting every
978
+ * time a word did.
979
+ */
980
+ private parallax;
981
+ /**
982
+ * THE TRACES LIVE IN THE SCENE NOW. They began as SVG projected over
983
+ * the frame — approximately right and one frame behind the lens on
984
+ * every move. As tubes in the page's own scene they are exact: they
985
+ * lie ON the eave because they are geometry at the eave's own
986
+ * coordinates, the building occludes them honestly, and the draw-on
987
+ * is the tube's index buffer revealed in path order. Each line in a
988
+ * set starts a moment after the one before it — a hand annotating,
989
+ * not a diagram switching on.
990
+ */
991
+ /** how much of the follow is in force (eased), and its last aim and zoom */
992
+ private riseK;
993
+ private riseAim;
994
+ private riseZoom;
995
+ /**
996
+ * THE LENS FOLLOWS THE WORK. Chris's rule for a building that goes up
997
+ * in the shot: if a spire is rising, ZOOM OUT; as you push in, follow
998
+ * the top that is being built. So while the followed campaign is
999
+ * between its base and its top, the aim rides a little above the
1000
+ * middle of what stands and the zoom is capped to keep base and top
1001
+ * in the frame; when it finishes, the authored pose takes over again,
1002
+ * on a one-pole so the release is a move and not a jump.
1003
+ */
1004
+ private followRise;
1005
+ /** the beat whose lines are being timed, and the local time they became drawable */
1006
+ private traceBeat;
1007
+ private traceFrom;
1008
+ /** whether the beat's lines may be on the glass right now */
1009
+ private traceOk;
1010
+ /**
1011
+ * NO LINE ON A THING STILL GOING UP. A trace or a callout is drawn on a
1012
+ * finished part: the followed campaign must be done (or, with nothing
1013
+ * followed, the built height must have passed the line). The draw-on
1014
+ * is timed from the moment that became true, not from the head.
1015
+ */
1016
+ private traceReady;
1017
+ private traceShape;
1018
+ /** the mark's facing, damped behind the camera's own bearing */
1019
+ private markFace;
1020
+ /**
1021
+ * Place the stage mark: climb to its height with a settle, hold, and
1022
+ * face the camera a beat late.
1023
+ */
1024
+ private stageMark;
1025
+ /**
1026
+ * THE TRACKING LINE — the one piece of the front layer that has to know
1027
+ * where the camera is pointing this frame. The caption stays where the
1028
+ * layout put it and a line reaches from it to the actual point on the
1029
+ * building, redrawn every frame, so the label is attached to the thing
1030
+ * rather than merely near it.
1031
+ */
1032
+ private trackPoint;
1033
+ /**
1034
+ * THE READOUT AND THE MARKER. The numeral is tracked and written only
1035
+ * when the whole year changes, which is a handful of times a run; the
1036
+ * marker's position is a custom property written every frame, because a
1037
+ * dot that steps once a year along a rule is a dot that looks broken.
1038
+ */
1039
+ /**
1040
+ * WHEN THE NUMERAL IS ON. It rises as the voice reaches the year, holds
1041
+ * while the phrase finishes, and is gone well before the cut — a date
1042
+ * that outstays the sentence that said it becomes a watermark.
1043
+ */
1044
+ private stampAt;
1045
+ /** the film's clock readout, from page seconds — nothing without a clock */
1046
+ private showYear;
1047
+ /** everything a beat changes in the world, done once on entry */
1048
+ private applyBeat;
1049
+ /**
1050
+ * THE AIR OF A SHOT: grade, sun, haze, weather. Split out of the beat
1051
+ * so a chapter's sky can begin turning while the lens is still flying
1052
+ * into it — the `air` cue fires at the head of a lead, the beat's own
1053
+ * cue when it lands, and applying it twice is harmless because every
1054
+ * one of these is a set, not a step.
1055
+ */
1056
+ private applyAir;
1057
+ /**
1058
+ * THE HOUR A CUT INHERITS.
1059
+ *
1060
+ * Theme and weather are stated where they CHANGE, so a beat halfway
1061
+ * through the film usually says nothing about either — which is right
1062
+ * for a film played from the top and wrong for one dropped into. A
1063
+ * deep link (or a chapter skip) would open in whatever sky was last
1064
+ * set, which since the front door became an evening meant the
1065
+ * construction chapter got built at dusk. So a cut resolves its own
1066
+ * hour first: walk back to the last beat that named one, and take it.
1067
+ */
1068
+ private settleAir;
1069
+ private shot;
1070
+ private dispatch;
1071
+ /**
1072
+ * PAUSE MEANS EVERYTHING STOPS. The score's run holds where it is (the
1073
+ * lens included), the page's own clock holds (grass, weather, the
1074
+ * build, the day), the audio graph suspends, and the beat clock is
1075
+ * shifted forward by the length of the hold on resume so the hours,
1076
+ * the build and the type pick up exactly where they stood. Tearing the
1077
+ * Sequence down was the old pause, and it re-ran the chapter on play.
1078
+ * An exact film's runs are already standing — its clock is the only
1079
+ * thing that has to hold.
1080
+ */
1081
+ private toggle;
1082
+ /** any re-cut or restart clears a hold: the new run starts rolling */
1083
+ private unhold;
1084
+ /**
1085
+ * SKIP A CHAPTER — by re-cutting, never by seeking.
1086
+ *
1087
+ * `from` moves to a chapter head, which changes the beat list, which
1088
+ * changes the path, the cues and the sequence's name all at once. The
1089
+ * region sees a different score and plays it from its head. Seeking the
1090
+ * existing run would be the obvious alternative and it would be wrong:
1091
+ * the lens is an integrator whose pose depends on where it has been, so
1092
+ * a run dropped into the middle of itself arrives with the wrong
1093
+ * velocity — the same reason `notes/sylva-one-world.md` calls the chaser
1094
+ * seek-unsafe and means it.
1095
+ */
1096
+ private goChapter;
1097
+ private cutTo;
1098
+ /** an uncaught error anywhere becomes a visible line — a film that
1099
+ * dies silently mid-reel cannot be debugged from a chair */
1100
+ private trip;
1101
+ private prev;
1102
+ private next;
1103
+ private key;
1104
+ /**
1105
+ * THE NARRATION, one file per beat.
1106
+ *
1107
+ * `assets/vo/<id>.mp3`, played on the beat's entrance and stopped
1108
+ * when the beat changes. Per beat rather than one long track because
1109
+ * chapter skip RE-CUTS the film: a single track would have to be sought,
1110
+ * and a sought track against a re-cut score drifts inside a chapter.
1111
+ *
1112
+ * A missing file is silence, not an error. The film ships before the
1113
+ * voice does, and the score has to be right either way. While a line
1114
+ * plays the scene's own music ducks under it, which is the one piece of
1115
+ * mixing that cannot wait for a mix.
1116
+ */
1117
+ private voSrc;
1118
+ /** ask the page to fetch and decode the line after this one, now */
1119
+ private primeNext;
1120
+ /**
1121
+ * A BOUNDARY NEVER CLIPS THE VOICE — and every line has its own
1122
+ * throat. One shared element made politeness impossible: however
1123
+ * gently the old line was being faded, the new line's src swap
1124
+ * guillotined it mid-word. So an interrupted line keeps its OWN
1125
+ * element and fades there (~120ms, fast enough to read as a stop,
1126
+ * slow enough that no waveform is cut mid-cycle) while the new line
1127
+ * starts clean on a fresh one — the game-dialogue barge-in, which is
1128
+ * what a chapter skip actually is. ("Stop at the next word" is the
1129
+ * refinement this is built to take: an analyser watching for the
1130
+ * inter-word trough before the fade — see docs/choreo-splices.md.)
1131
+ */
1132
+ /**
1133
+ * A JOIN, EXHIBITED. Any seam as a pure overlay on whatever is playing —
1134
+ * no beat change, no snap: the transition itself, on the living
1135
+ * picture. What a cutting room's wall plate triggers.
1136
+ */
1137
+ private preview;
1138
+ /** the wrapper the live picture sits in, for rack-defocus */
1139
+ private liveEl?;
1140
+ private liveWrap;
1141
+ /**
1142
+ * THE LIGHT SWEEP: the sun itself flares across the seam — swings
1143
+ * high and past, then settles back onto whatever light the beat
1144
+ * actually asked for. Fire-and-forget; the restore hands the key
1145
+ * back to the beat's own sun (or the hour's).
1146
+ */
1147
+ private lightSweep;
1148
+ /** the scene brought its own score — six of them, one per tower */
1149
+ /** the line for this beat, at the level the session was mixed to */
1150
+ private speak;
1151
+ private hush;
1152
+ private hear;
1153
+ /**
1154
+ * FROM THE TOP means the front door, not the first beat.
1155
+ *
1156
+ * A film restarted straight into its opening shot skips the one piece
1157
+ * of the picture that says what it is — and the poster is a
1158
+ * composition in its own right, with the building turning in the last
1159
+ * of the light. So this clears everything the run has done and puts
1160
+ * the door back up, with the lens seated where it opens.
1161
+ */
1162
+ private restart;
1163
+ /** a choice made at the door before the scene finished loading */
1164
+ private wanted?;
1165
+ /** through the gate — on the click the audio policy was waiting for */
1166
+ private begin;
1167
+ /** the whole film's running time, said the way a poster says it */
1168
+ get runtime(): string;
1169
+ /**
1170
+ * BACK IN FROM THE END CARD — a dissolve, not a flight.
1171
+ *
1172
+ * The film ends three hundred degrees around the building from where
1173
+ * it opened, so tweening the lens home spins it like a globe. The
1174
+ * ending cross-fades into the opening frame instead: the last frame
1175
+ * is held as a still, the camera is PLACED at the opening pose behind
1176
+ * it, and the still fades away.
1177
+ */
1178
+ /**
1179
+ * BACK IN FROM THE END CARD — a hard reset, not a journey.
1180
+ *
1181
+ * The film ends three hundred degrees around the building, with the
1182
+ * tower dissolved, the sky at golden hour, the rain gone and a chapter
1183
+ * numeral on screen. Tweening any of that back to the opening is a
1184
+ * confusing ten seconds in which nothing is either the ending or the
1185
+ * beginning. So the ending is CLEARED: every overlay, the model, the
1186
+ * weather, the world type, the traces and the lens all go back to
1187
+ * their opening state in one frame, and the film starts.
1188
+ */
1189
+ /** the end card's WATCH AGAIN is the front door, not the first beat: the
1190
+ * choice of sound is made there, every time */
1191
+ private replay;
1192
+ /** the chapter menu. The film keeps running behind it, blurred: a
1193
+ * menu that freezes the picture makes the picture feel like a file, and
1194
+ * this one is meant to feel like a broadcast you are stepping around in */
1195
+ private menu;
1196
+ private toc;
1197
+ /** every chapter, with the beat it starts at and whether we are in it */
1198
+ get contents(): {
1199
+ here: boolean;
1200
+ head: number;
1201
+ n: string;
1202
+ secs: number;
1203
+ shots: number;
1204
+ title: string;
1205
+ }[];
1206
+ private pick;
1207
+ }
1208
+ //# sourceMappingURL=film.d.ts.map