The Timeline Lives in the Markup

HyperFrames' composition contract answers a specific question: how can an ordinary HTML document describe a finite sequence of frames without hiding its timing in a separate editor? To inspect a HyperFrames project, start with the root, the timed elements inside it, and the distinction between timing, paint order, and playback. Once those responsibilities are separated, the markup becomes a readable model of what the renderer is being asked to reconstruct.

A composition root is a bounded canvas

A HyperFrames composition is an HTML document with a root element that identifies the composition and establishes its authored frame. The root normally carries a data-composition-id, data-start="0", data-width, and data-height. It usually also carries data-duration, the total length of the output in seconds. The dimensions are not decorative CSS preferences. They define the canvas that the renderer captures, while the duration defines the time window it must produce.

That last point is easy to underestimate. If the root says that the composition lasts five seconds, the renderer has a five-second output window even if a source video elsewhere is longer. A composition can omit the root duration when HyperFrames can infer a finite length from supported media or an animation runtime, but an explicit duration is the clearest boundary for a complete work. It also makes a project easier to inspect: the reader can tell where the composition begins and ends without waiting for scripts to run.

Inside the root, a timed visual element becomes a clip by declaring when it starts and how long its slot lasts. A stable id gives the element an identity for timing, editing, and animation. data-start places it on the composition timeline, and data-duration gives the visible slot its length for ordinary DOM, image, and nested-composition clips. Authored text, images, and other visible DOM elements normally receive class="clip" as well. The shared convention gives them a full-frame box and lets the runtime handle their timed visibility. Video visibility follows the media runtime's rules, and audio has no visual lifecycle, so the same convention does not mean every element is managed in exactly the same way.

A small example makes the separation concrete. Suppose a title should appear at one second and remain for four seconds. Its markup states that window; a style rule can make it large and centered; an animation script can fade its opacity from zero to one. The markup does not need to call a timer to hide the title at five seconds. HyperFrames already has the timing facts needed to determine whether the title belongs in the frame being requested.

The data-track-index attribute is useful, but it is often given too much authority. A track is a row that Studio can display in its timeline. It does not decide which element is in front, and it does not prevent two elements from overlapping. Paint order belongs to CSS, especially z-index. Two clips can share a track and overlap in time; putting them on separate tracks can make that intended crossfade easier to read in Studio, but it does not change the compositor's front-to-back rules.

This distinction matters in practice. Imagine an intro video that runs from zero to four seconds and a result card that begins half a second before the intro ends. The overlap is a timing relationship. Whether the card appears above the video is a paint relationship. If a reader treats the track row as a layer, they may move the card to a different row and wonder why it still sits behind the video. HyperFrames keeps the two questions separate because a renderer and an editor need different answers: the renderer needs the active elements and their CSS, while Studio needs an intelligible lane arrangement.

Relative timing lets the source express relationships without duplicating arithmetic. A numeric data-start is an absolute time in seconds. A reference such as data-start="intro" means that the element starts when the clip with the ID intro ends. Adding or subtracting seconds creates a gap or overlap: intro + 0.5 waits half a second, while intro - 0.5 begins half a second early. These references are resolved only within the same composition. The referenced clip needs a known duration, and a chain of references cannot loop back to itself.

<video
  id="intro"
  data-start="0"
  data-duration="4"
  data-track-index="0"
  src="./assets/intro.mp4"
  muted
  playsinline
></video>

<section
  id="result"
  class="clip"
  data-start="intro - 0.5"
  data-duration="3"
  data-track-index="1"
>
  The result
</section>

In this example, the markup says that both elements can be active during the half-second overlap. It does not say that the result card is visually on top; CSS must do that. It also does not tell the video element to start playing. The runtime uses the declared timing to decide which source moment belongs in each rendered frame.

flowchart LR A["Composition root"] --> B["Clip start and duration"] B --> C["Relative start reference"] B --> D["Timed visibility"] C --> D E["Studio track"] --> F["Editor lane only"] G["CSS z-index"] --> H["Paint order"] I["Media runtime"] --> D J["Nested host window"] --> K["Child local timeline"] K --> D

The diagram's asymmetry is deliberate. The root and clip attributes determine the temporal question. Relative references help calculate a start. Studio tracks organize what a person sees while editing. CSS decides the visual stack. The media runtime applies its own playback rules. These inputs meet in the captured frame, but they do not become one undifferentiated timeline language.

Local time, nested scenes, and runtime-owned media

A composition can contain another composition in two ways. An author can write the child inline, or a host can point to a separate file. The separate-file form is useful when a scene should be reused. The child file carries its live markup in a <template>, along with the styles and scripts needed by that scene. When HyperFrames mounts it, the host becomes the parent timeline's timed element and the child registers its own timeline.

The parent and child therefore answer different timing questions. The host declares when the scene appears in the parent and how long its window remains visible. The child timeline describes motion inside that window. A child video that starts at two seconds is two seconds into the child's local composition; if the host begins at ten seconds on the parent, that content appears at twelve seconds on the root timeline. This local-time rule lets the same scene be reused without rewriting every internal start time.

The host can also choose which moment of the child is shown first through its playback-start offset. Trimming or splitting from the left can advance that offset so the child animation continues rather than jumping back to its beginning. For new nested composition hosts, the canonical attribute is data-playback-start; video and audio use data-media-start for their source offset. The names reflect two related but distinct jobs: a media element is being positioned inside a source file, while a nested host is mapping a parent window onto a child's timeline. Treating them as interchangeable can make a visual trim and an audio trim disagree.

A nested scene's host duration is also a boundary, not a suggestion. If the child timeline is shorter than the host window, the child can hold its final state. If the host window is shorter than the child timeline, the parent cuts the scene off. This lets a parent composition control editorial placement while the child remains responsible for its internal state. It is also why manually adding a child timeline to the parent's animation timeline is the wrong mental model: the framework already maps the parent playhead into the nested scene.

That separation makes reuse predictable. The same four-second child can be mounted once at the beginning of a video and again later, with each host choosing its own parent start and duration. Both instances still interpret the child's internal two-second title entrance as local child time. The parent decides when an instance becomes visible; the child decides what its title, shapes, or transition look like at each point inside that instance. If the parent tried to flatten both responsibilities into one hand-built timeline, a trim or playback-rate change could shift the child twice or leave the child at the wrong internal moment. The host window is the boundary that lets the two clocks be related without becoming indistinguishable.

The same rule explains why a short explicit root duration is consequential. A long source file or a long child animation does not automatically enlarge a root window that the author has already bounded. The parent must give the scene enough time, and the child must have a finite state that can be sampled inside that time. These are not separate administrative settings: together they determine which content can ever reach a rendered frame.

Media follows the same source-oriented principle. A <video> or <audio> element can declare a start, duration, source offset, playback rate, and volume. A video can declare that it contributes audio. If the intrinsic duration is known, media may be able to omit an explicit duration when the intended behavior is to play the remaining source. Those attributes give the runtime enough information to choose the correct source moment and gain while it renders the parent timeline.

It helps to distinguish a clip's position from its position inside the source file. A video may enter the composition at five seconds while beginning at a later point in its own media. A playback rate can make that source advance faster or slower, and a volume value changes its contribution without changing when the visual clip is active. For video and audio, the canonical source-offset attribute is data-media-start. A nested composition host uses data-playback-start to say which point in the child's timeline should appear first. The distinction is operational: different parts of the system read these attributes for different kinds of elements, so using the wrong name can make a picture trim and its audio disagree.

For example, if a four-second video starts at two seconds in the composition and its source offset is one second, the first active composition frame should show the source around one second, not the source's beginning. The runtime can make that choice consistently for every requested frame. If an author script instead sets currentTime after some other event has fired, the result depends on event order and the history of playback. Declarative media timing turns the source offset into a property of the clip, not a side effect that must already have happened.

Author scripts should not compete with that ownership. Calling play(), pause(), or assigning currentTime from composition code introduces a second controller. A script that hides a video or an audio element according to its own clock can also disagree with the framework about whether that element belongs in the requested frame. The resulting bug may look like a media-sync problem, but the deeper cause is that two systems are trying to define the same state.

This leaves scripts with an important but bounded role. They can create a visual transition, draw on a canvas, alter an SVG, or animate a wrapper around media. They describe how the visible state changes; they should not replace the composition contract's timing or the runtime's media lifecycle. The rule is less about restricting creativity than about assigning one owner to each decision. When the renderer seeks directly to a frame, it needs to know that the same source facts will be applied again rather than waiting for an earlier imperative event to have happened.

The resulting model is compact. The root establishes the canvas and output window. Clips establish temporal slots. Relative expressions describe relationships inside one composition. Tracks help humans organize those slots, while CSS establishes the visual stack. Nested hosts establish parent windows, and child compositions own local time. Media attributes describe source playback, while the runtime owns the act of playing and seeking. Scripts supply motion without taking control of those boundaries.

That is why the timeline can live in the markup without reducing a composition to a list of fields. The attributes make the temporal structure inspectable; HTML and CSS still provide the visual material; scripts still provide creative behavior. The remaining question is how HyperFrames makes all of those declared and scripted states arrive at the exact frame a renderer requests, rather than merely following the last moment of live playback.

References