@hyperframes/core 0.1.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.
- package/README.md +125 -0
- package/dist/adapters/gsap.d.ts +14 -0
- package/dist/adapters/gsap.d.ts.map +1 -0
- package/dist/adapters/gsap.js +25 -0
- package/dist/adapters/gsap.js.map +1 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/types.d.ts +15 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/compiler/htmlBundler.d.ts +16 -0
- package/dist/compiler/htmlBundler.d.ts.map +1 -0
- package/dist/compiler/htmlBundler.js +448 -0
- package/dist/compiler/htmlBundler.js.map +1 -0
- package/dist/compiler/htmlCompiler.d.ts +18 -0
- package/dist/compiler/htmlCompiler.d.ts.map +1 -0
- package/dist/compiler/htmlCompiler.js +65 -0
- package/dist/compiler/htmlCompiler.js.map +1 -0
- package/dist/compiler/index.d.ts +5 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +9 -0
- package/dist/compiler/index.js.map +1 -0
- package/dist/compiler/staticGuard.d.ts +8 -0
- package/dist/compiler/staticGuard.d.ts.map +1 -0
- package/dist/compiler/staticGuard.js +26 -0
- package/dist/compiler/staticGuard.js.map +1 -0
- package/dist/compiler/timingCompiler.d.ts +72 -0
- package/dist/compiler/timingCompiler.d.ts.map +1 -0
- package/dist/compiler/timingCompiler.js +191 -0
- package/dist/compiler/timingCompiler.js.map +1 -0
- package/dist/core.types.d.ts +314 -0
- package/dist/core.types.d.ts.map +1 -0
- package/dist/core.types.js +52 -0
- package/dist/core.types.js.map +1 -0
- package/dist/generators/hyperframes.d.ts +21 -0
- package/dist/generators/hyperframes.d.ts.map +1 -0
- package/dist/generators/hyperframes.js +572 -0
- package/dist/generators/hyperframes.js.map +1 -0
- package/dist/hyperframe.manifest.json +22 -0
- package/dist/hyperframe.runtime.iife.js +13 -0
- package/dist/hyperframe.runtime.mjs +13 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/inline-scripts/hyperframe.d.ts +13 -0
- package/dist/inline-scripts/hyperframe.d.ts.map +1 -0
- package/dist/inline-scripts/hyperframe.js +15 -0
- package/dist/inline-scripts/hyperframe.js.map +1 -0
- package/dist/inline-scripts/hyperframesRuntime.engine.d.ts +7 -0
- package/dist/inline-scripts/hyperframesRuntime.engine.d.ts.map +1 -0
- package/dist/inline-scripts/hyperframesRuntime.engine.js +31 -0
- package/dist/inline-scripts/hyperframesRuntime.engine.js.map +1 -0
- package/dist/inline-scripts/parityContract.d.ts +5 -0
- package/dist/inline-scripts/parityContract.d.ts.map +1 -0
- package/dist/inline-scripts/parityContract.js +43 -0
- package/dist/inline-scripts/parityContract.js.map +1 -0
- package/dist/inline-scripts/pickerApi.d.ts +32 -0
- package/dist/inline-scripts/pickerApi.d.ts.map +1 -0
- package/dist/inline-scripts/pickerApi.js +2 -0
- package/dist/inline-scripts/pickerApi.js.map +1 -0
- package/dist/inline-scripts/runtimeContract.d.ts +14 -0
- package/dist/inline-scripts/runtimeContract.d.ts.map +1 -0
- package/dist/inline-scripts/runtimeContract.js +21 -0
- package/dist/inline-scripts/runtimeContract.js.map +1 -0
- package/dist/lint/hyperframeLinter.d.ts +3 -0
- package/dist/lint/hyperframeLinter.d.ts.map +1 -0
- package/dist/lint/hyperframeLinter.js +621 -0
- package/dist/lint/hyperframeLinter.js.map +1 -0
- package/dist/lint/index.d.ts +3 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint/index.js +2 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/lint/types.d.ts +21 -0
- package/dist/lint/types.d.ts.map +1 -0
- package/dist/lint/types.js +2 -0
- package/dist/lint/types.js.map +1 -0
- package/dist/parsers/gsapParser.d.ts +50 -0
- package/dist/parsers/gsapParser.d.ts.map +1 -0
- package/dist/parsers/gsapParser.js +411 -0
- package/dist/parsers/gsapParser.js.map +1 -0
- package/dist/parsers/htmlParser.d.ts +29 -0
- package/dist/parsers/htmlParser.d.ts.map +1 -0
- package/dist/parsers/htmlParser.js +726 -0
- package/dist/parsers/htmlParser.js.map +1 -0
- package/dist/templates/base.d.ts +4 -0
- package/dist/templates/base.d.ts.map +1 -0
- package/dist/templates/base.js +20 -0
- package/dist/templates/base.js.map +1 -0
- package/dist/templates/constants.d.ts +7 -0
- package/dist/templates/constants.d.ts.map +1 -0
- package/dist/templates/constants.js +8 -0
- package/dist/templates/constants.js.map +1 -0
- package/docs/common-mistakes.md +73 -0
- package/docs/core.md +532 -0
- package/docs/core_notes.md +61 -0
- package/docs/quickstart-template.html +180 -0
- package/docs/versions/changelog.md +5 -0
- package/docs/versions/v0.1/core.md +326 -0
- package/package.json +83 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<!--
|
|
6
|
+
REQUIRED: viewport must match your composition dimensions.
|
|
7
|
+
Landscape: width=1920, height=1080
|
|
8
|
+
Portrait: width=1080, height=1920
|
|
9
|
+
-->
|
|
10
|
+
<meta name="viewport" content="width=1920, height=1080" />
|
|
11
|
+
|
|
12
|
+
<!--
|
|
13
|
+
REQUIRED: data-composition-id identifies this composition.
|
|
14
|
+
Must match the key you register in window.__timelines below.
|
|
15
|
+
Optional: data-width/data-height help the producer determine resolution.
|
|
16
|
+
-->
|
|
17
|
+
<meta data-composition-id="my-video" data-width="1920" data-height="1080" />
|
|
18
|
+
|
|
19
|
+
<title>My Video</title>
|
|
20
|
+
|
|
21
|
+
<!-- REQUIRED: GSAP for timeline animations -->
|
|
22
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
* {
|
|
26
|
+
margin: 0;
|
|
27
|
+
padding: 0;
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
}
|
|
30
|
+
html,
|
|
31
|
+
body {
|
|
32
|
+
width: 1920px;
|
|
33
|
+
height: 1080px;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
background: #000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#stage {
|
|
39
|
+
position: relative;
|
|
40
|
+
width: 1920px;
|
|
41
|
+
height: 1080px;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
REQUIRED: All timed clips need visibility: hidden.
|
|
47
|
+
The framework toggles visibility based on data-start/data-duration.
|
|
48
|
+
Never toggle visibility yourself in scripts.
|
|
49
|
+
*/
|
|
50
|
+
.clip {
|
|
51
|
+
position: absolute;
|
|
52
|
+
top: 0;
|
|
53
|
+
left: 0;
|
|
54
|
+
width: 100%;
|
|
55
|
+
height: 100%;
|
|
56
|
+
visibility: hidden;
|
|
57
|
+
object-fit: cover;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<div id="stage">
|
|
63
|
+
<!--
|
|
64
|
+
════════════════════════════════════════════════════
|
|
65
|
+
VIDEO CLIPS
|
|
66
|
+
════════════════════════════════════════════════════
|
|
67
|
+
|
|
68
|
+
REQUIRED attributes:
|
|
69
|
+
data-start — when the clip appears (seconds)
|
|
70
|
+
data-duration — how long the clip stays on screen (seconds)
|
|
71
|
+
(optional for video/audio — defaults to source duration)
|
|
72
|
+
data-track-index — z-stacking order + timeline track grouping
|
|
73
|
+
(higher = in front, clips on same track cannot overlap)
|
|
74
|
+
|
|
75
|
+
REQUIRED on <video>:
|
|
76
|
+
muted — framework manages audio via separate <audio> element
|
|
77
|
+
playsinline — prevents fullscreen on mobile
|
|
78
|
+
|
|
79
|
+
REQUIRED:
|
|
80
|
+
class="clip" — sets visibility: hidden so framework can manage lifecycle
|
|
81
|
+
|
|
82
|
+
DO NOT:
|
|
83
|
+
- Call video.play(), video.pause(), or set video.currentTime in scripts
|
|
84
|
+
- Toggle visibility in scripts
|
|
85
|
+
- Use the video element for audio (use a separate <audio> element)
|
|
86
|
+
-->
|
|
87
|
+
<video
|
|
88
|
+
id="el-video"
|
|
89
|
+
class="clip"
|
|
90
|
+
data-start="0"
|
|
91
|
+
data-duration="10"
|
|
92
|
+
data-track-index="0"
|
|
93
|
+
src="my-video.mp4"
|
|
94
|
+
muted
|
|
95
|
+
playsinline
|
|
96
|
+
></video>
|
|
97
|
+
|
|
98
|
+
<!--
|
|
99
|
+
════════════════════════════════════════════════════
|
|
100
|
+
IMAGE CLIPS
|
|
101
|
+
════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
data-duration is REQUIRED for images (they have no intrinsic duration).
|
|
104
|
+
-->
|
|
105
|
+
<img id="el-image" class="clip" data-start="10" data-duration="5" data-track-index="0" src="my-image.png" />
|
|
106
|
+
|
|
107
|
+
<!--
|
|
108
|
+
════════════════════════════════════════════════════
|
|
109
|
+
AUDIO
|
|
110
|
+
════════════════════════════════════════════════════
|
|
111
|
+
|
|
112
|
+
Audio is invisible — no class="clip" needed (no visual to hide).
|
|
113
|
+
Use a separate <audio> element even if the source is the same .mp4 file.
|
|
114
|
+
The framework syncs audio playback to the timeline position.
|
|
115
|
+
|
|
116
|
+
Optional: data-volume="0.5" (0-1, default 1)
|
|
117
|
+
-->
|
|
118
|
+
<audio
|
|
119
|
+
id="el-audio"
|
|
120
|
+
data-start="0"
|
|
121
|
+
data-duration="15"
|
|
122
|
+
data-track-index="1"
|
|
123
|
+
data-volume="0.8"
|
|
124
|
+
src="my-video.mp4"
|
|
125
|
+
></audio>
|
|
126
|
+
|
|
127
|
+
<!--
|
|
128
|
+
════════════════════════════════════════════════════
|
|
129
|
+
TEXT / HTML OVERLAYS
|
|
130
|
+
════════════════════════════════════════════════════
|
|
131
|
+
|
|
132
|
+
Any div with data-start becomes a timed clip.
|
|
133
|
+
Content inside can be freely styled with CSS.
|
|
134
|
+
Animate visual properties (opacity, transform) with GSAP — fine.
|
|
135
|
+
Do NOT animate visibility — the framework owns that.
|
|
136
|
+
-->
|
|
137
|
+
<div
|
|
138
|
+
id="el-title"
|
|
139
|
+
class="clip"
|
|
140
|
+
data-start="1"
|
|
141
|
+
data-duration="4"
|
|
142
|
+
data-track-index="2"
|
|
143
|
+
style="display: flex; align-items: center; justify-content: center; z-index: 10"
|
|
144
|
+
>
|
|
145
|
+
<h1 style="font-family: sans-serif; font-size: 72px; color: white; opacity: 0">Hello World</h1>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<script>
|
|
150
|
+
/*
|
|
151
|
+
════════════════════════════════════════════════════
|
|
152
|
+
GSAP TIMELINE
|
|
153
|
+
════════════════════════════════════════════════════
|
|
154
|
+
|
|
155
|
+
REQUIRED: Create a paused GSAP timeline.
|
|
156
|
+
REQUIRED: Register it in window.__timelines with the SAME ID
|
|
157
|
+
as data-composition-id on the <meta> tag.
|
|
158
|
+
|
|
159
|
+
Without this registration, NOTHING works — no playback,
|
|
160
|
+
no seeking, no rendering. This is the #1 mistake.
|
|
161
|
+
*/
|
|
162
|
+
var tl = gsap.timeline({ paused: true });
|
|
163
|
+
|
|
164
|
+
// Animate visual properties only — opacity, transform, color, etc.
|
|
165
|
+
// The framework handles clip mounting/unmounting and media playback.
|
|
166
|
+
tl.to("#el-video", { opacity: 1, duration: 0.5 }, 0);
|
|
167
|
+
tl.to("#el-video", { opacity: 0, duration: 0.5 }, 9.5);
|
|
168
|
+
|
|
169
|
+
tl.to("#el-image", { opacity: 1, duration: 0.5 }, 10);
|
|
170
|
+
tl.to("#el-image", { opacity: 0, duration: 0.5 }, 14.5);
|
|
171
|
+
|
|
172
|
+
tl.to("#el-title h1", { opacity: 1, y: 0, duration: 0.8, ease: "power2.out" }, 1);
|
|
173
|
+
tl.to("#el-title h1", { opacity: 0, duration: 0.5 }, 4);
|
|
174
|
+
|
|
175
|
+
// REQUIRED: Register the timeline. The key MUST match data-composition-id.
|
|
176
|
+
window.__timelines = window.__timelines || {};
|
|
177
|
+
window.__timelines["my-video"] = tl;
|
|
178
|
+
</script>
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# HyperFrames Core Changelog
|
|
2
|
+
|
|
3
|
+
## v0.1
|
|
4
|
+
|
|
5
|
+
Initial schema specification. Defines the foundational HTML-as-video format: compositions, clip types (video, image, audio, nested compositions), data attributes for timing and tracks, relative timing with offsets, the two-layer primitives + scripts model, and the GSAP timeline contract.
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# HyperFrames Schema
|
|
2
|
+
|
|
3
|
+
Reference for generating and editing HyperFrames HTML compositions. This is your source of truth for how to author compositions.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
HyperFrames uses HTML as the source of truth for describing a video:
|
|
8
|
+
|
|
9
|
+
- **HTML clips** = video, image, audio, composition
|
|
10
|
+
- **Data attributes** = timing, metadata, styling
|
|
11
|
+
- **CSS** = positioning and appearance
|
|
12
|
+
- **GSAP timeline** = animations and playback sync
|
|
13
|
+
|
|
14
|
+
### Framework-Managed Behavior
|
|
15
|
+
|
|
16
|
+
The framework reads data attributes and automatically manages:
|
|
17
|
+
|
|
18
|
+
- **Primitive clip timeline entries** — the framework reads `data-start`, `data-duration`, and `data-track-index` from primitive clips and adds them to the composition's GSAP timeline. You do not manually add primitive clips to the timeline in scripts.
|
|
19
|
+
- **Media playback** (play, pause, seek) for `<video>` and `<audio>`
|
|
20
|
+
- **Clip lifecycle** — clips are **mounted** (made visible on screen) and **unmounted** (removed from screen) based on `data-start` and `data-duration`
|
|
21
|
+
- **Timeline synchronization** (keeping media in sync with the GSAP master timeline)
|
|
22
|
+
- **Media loading** — the framework waits for all media elements to load before resolving timing and starting playback
|
|
23
|
+
|
|
24
|
+
Mounting and unmounting controls **presence**, not appearance. A clip that is mounted is on screen; a clip that is unmounted is not. Transitions (fade in, slide in, etc.) are separate — they are animated in scripts and happen _after_ a clip is mounted or _before_ it is unmounted.
|
|
25
|
+
|
|
26
|
+
The framework does **not** handle transitions, effects, or visual animation — those are driven by GSAP in JavaScript.
|
|
27
|
+
|
|
28
|
+
Do not manually call `video.play()`, `video.pause()`, set `audio.currentTime`, or mount/unmount clips in scripts. The framework owns media playback and clip lifecycle. Animating visual properties like `opacity` or `transform` for transitions is fine — that's what scripts are for.
|
|
29
|
+
|
|
30
|
+
## Viewport
|
|
31
|
+
|
|
32
|
+
The root composition must declare its intended render dimensions using `data-width` and `data-height` attributes. Common sizes:
|
|
33
|
+
|
|
34
|
+
- **Landscape**: `data-width="1920" data-height="1080"`
|
|
35
|
+
- **Portrait**: `data-width="1080" data-height="1920"`
|
|
36
|
+
|
|
37
|
+
e.g.,
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<div id="main" data-composition-id="my-video" data-start="0" data-width="1920" data-height="1080">
|
|
41
|
+
<!-- clips -->
|
|
42
|
+
</div>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Every composition's container is full-screen within the viewport by default. The framework applies full-screen sizing to composition containers automatically.
|
|
46
|
+
|
|
47
|
+
To position or size individual clips (e.g., picture-in-picture, overlay placement), use standard CSS on the element.
|
|
48
|
+
|
|
49
|
+
## Compositions
|
|
50
|
+
|
|
51
|
+
A composition is the fundamental grouping unit in HyperFrames. Every clip — video, image, audio — must live inside a composition. The `index.html` file is itself a composition (the top-level one), and it can contain nested compositions within it. Any composition can be imported into another composition as a sub-composition — there is no special "root" type.
|
|
52
|
+
|
|
53
|
+
A composition carries the same core attributes as any other clip (`id`, `data-start`, `data-track-index`), so it can be placed and timed on a timeline just like a video or image. A composition's length is determined by its GSAP timeline — there is no `data-duration` on compositions. This means compositions can be nested: a composition clip inside another composition behaves like a self-contained video within the parent timeline.
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<div id="comp-1" data-composition-id="my-video" data-start="0" data-width="1920" data-height="1080">
|
|
57
|
+
<!-- Clips live inside the composition -->
|
|
58
|
+
<video id="el-1" data-start="0" data-duration="10" data-track-index="0" src="..."></video>
|
|
59
|
+
<video id="el-2" data-start="el-1" data-duration="8" data-track-index="0" src="..."></video>
|
|
60
|
+
<img id="el-3" data-start="5" data-duration="4" data-track-index="1" src="..." />
|
|
61
|
+
<audio id="el-4" data-start="0" data-duration="30" data-track-index="2" src="..." />
|
|
62
|
+
|
|
63
|
+
<!-- Nested composition (e.g. a motion graphic or title sequence) -->
|
|
64
|
+
<div id="el-5" data-composition-id="intro-anim" data-start="0" data-track-index="3">
|
|
65
|
+
<!-- its own clips, timeline, and script -->
|
|
66
|
+
<script src="intro-anim.js"></script>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> Note: `data-width` and `data-height` are only required on the **root** composition. Nested compositions inherit the viewport dimensions.
|
|
72
|
+
|
|
73
|
+
## Clip Types
|
|
74
|
+
|
|
75
|
+
A clip is any discrete block on the timeline. We represent clips as HTML elements and apply data-attributes to describe them.
|
|
76
|
+
|
|
77
|
+
- `<video>` — Video clips, B-roll, A-roll
|
|
78
|
+
- `<img>` — Static images, overlays
|
|
79
|
+
- `<audio>` — Music, sound effects
|
|
80
|
+
- `<div data-composition-id="...">` — Nested compositions (animations, grouped sequences)
|
|
81
|
+
|
|
82
|
+
## HTML Attributes
|
|
83
|
+
|
|
84
|
+
### All Clips
|
|
85
|
+
|
|
86
|
+
- `id` — Unique identifier (e.g., "el-1")
|
|
87
|
+
- `data-start` — Start time in seconds, or a clip `id` reference. See [Relative Timing](#relative-timing).
|
|
88
|
+
- `data-duration` — Duration in seconds. Required for `<img>` clips. Optional for `<video>` and `<audio>` (defaults to the source media's full duration). Not used on compositions.
|
|
89
|
+
- `data-track-index` — Timeline track number. Tracks serve two purposes: they determine visual layering (higher tracks render in front) and they group clips into rows on the timeline. Clips on the same track **cannot overlap in time**.
|
|
90
|
+
|
|
91
|
+
### Media Clips (video, audio)
|
|
92
|
+
|
|
93
|
+
- `data-media-start` — (optional) Playback begins at this time in the source file, in seconds. Defaults to `0`.
|
|
94
|
+
|
|
95
|
+
### Composition Clips
|
|
96
|
+
|
|
97
|
+
- `data-composition-id` — Unique composition ID
|
|
98
|
+
|
|
99
|
+
> Compositions do **not** use `data-duration`. Their duration is determined by their GSAP timeline.
|
|
100
|
+
|
|
101
|
+
## Relative Timing
|
|
102
|
+
|
|
103
|
+
Instead of calculating absolute start times, a clip can reference another clip's `id` in its `data-start` attribute. This means "start when that clip ends." The referenced clip must be in the same composition and must have a known duration (either an explicit `data-duration` or an inferred duration from the source media).
|
|
104
|
+
|
|
105
|
+
### Basic Sequential Clips
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<video id="intro" data-start="0" data-duration="10" data-track-index="0" src="..."></video>
|
|
109
|
+
<video id="main" data-start="intro" data-duration="20" data-track-index="0" src="..."></video>
|
|
110
|
+
<video id="outro" data-start="main" data-duration="5" data-track-index="0" src="..."></video>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`main` resolves to second 10, `outro` resolves to second 30. If `intro`'s duration changes to 15, `main` and `outro` shift automatically.
|
|
114
|
+
|
|
115
|
+
### Offsets (gaps and overlaps)
|
|
116
|
+
|
|
117
|
+
Add `+ N` or `- N` after the ID to offset from the end of the referenced clip:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<!-- intro ends at 10. "intro + 2" = 10 + 2 = starts at second 12 (2s gap) -->
|
|
121
|
+
<video id="scene-a" data-start="intro + 2" data-duration="20" data-track-index="0" src="..."></video>
|
|
122
|
+
|
|
123
|
+
<!-- intro ends at 10. "intro - 0.5" = 10 - 0.5 = starts at second 9.5 (0.5s overlap for crossfade) -->
|
|
124
|
+
<!-- Different track because clips on the same track cannot overlap -->
|
|
125
|
+
<video id="scene-b" data-start="intro - 0.5" data-duration="20" data-track-index="1" src="..."></video>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Rules
|
|
129
|
+
|
|
130
|
+
- **Same composition only** — references resolve within the clip's parent composition
|
|
131
|
+
- **No circular references** — A cannot start after B if B starts after A
|
|
132
|
+
- **Referenced clip must have a known duration** — the system needs a known end time to resolve the reference (either explicit `data-duration` or inferred from source media)
|
|
133
|
+
- **Parsing** — if the value is a valid number, it is absolute seconds; otherwise it is parsed as `<id>`, `<id> + <number>`, or `<id> - <number>`
|
|
134
|
+
|
|
135
|
+
## Video Clips
|
|
136
|
+
|
|
137
|
+
Full-screen or positioned video clips. Videos sync their playback to the timeline position.
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<video
|
|
141
|
+
id="el-1"
|
|
142
|
+
data-start="0"
|
|
143
|
+
data-duration="15"
|
|
144
|
+
data-track-index="0"
|
|
145
|
+
src="./assets/video.mp4"
|
|
146
|
+
></video>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `data-media-start` — Playback begins at this time in the source video file (seconds). Default: `0`.
|
|
150
|
+
- `data-duration` — (optional) How long the clip occupies on the timeline, in seconds. Playback runs from `data-media-start` for up to `data-duration` seconds. If the source media runs out before `data-duration` elapses, playback naturally stops (the clip remains mounted showing the last frame). If omitted, defaults to the remaining duration of the source file from `data-media-start`.
|
|
151
|
+
|
|
152
|
+
## Image Clips
|
|
153
|
+
|
|
154
|
+
Static images that appear for a duration.
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<img
|
|
158
|
+
id="el-2"
|
|
159
|
+
data-start="5"
|
|
160
|
+
data-duration="4"
|
|
161
|
+
data-track-index="1"
|
|
162
|
+
src="./assets/video.mp4"
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Audio Clips
|
|
167
|
+
|
|
168
|
+
Background music or sound effects. Audio clips are invisible.
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
<audio
|
|
172
|
+
id="el-4"
|
|
173
|
+
data-start="0"
|
|
174
|
+
data-duration="30"
|
|
175
|
+
data-track-index="2"
|
|
176
|
+
src="./assets/music.mp3"
|
|
177
|
+
></audio>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- `data-media-start` — Playback begins at this time in the source audio file (seconds). Default: `0`.
|
|
181
|
+
- `data-duration` — (optional) How long the clip occupies on the timeline, in seconds. Playback runs from `data-media-start` for up to `data-duration` seconds. If the source media runs out before `data-duration` elapses, playback naturally stops (the clip remains mounted but silent). If omitted, defaults to the remaining duration of the source file from `data-media-start`.
|
|
182
|
+
|
|
183
|
+
## Two Layers: Primitives and Scripts
|
|
184
|
+
|
|
185
|
+
Every composition — master or sub — has the same two layers:
|
|
186
|
+
|
|
187
|
+
- **HTML** — primitive clips (`video`, `img`, `audio`, nested `div[data-composition-id]`). This is the declarative structure: what plays, when, and on which track.
|
|
188
|
+
- **Script** — effects, transitions, dynamic DOM, canvas, SVG — creative animation and visuals via GSAP. Scripts do **not** control media playback or clip visibility; the framework handles those via data attributes.
|
|
189
|
+
|
|
190
|
+
Both layers are available to every composition. The schema defines the primitives and the timeline contract; scripts handle visual creativity on top of that.
|
|
191
|
+
|
|
192
|
+
> **Warning:** Never use scripts to play/pause/seek media elements or to show/hide clips based on timing. The framework does this automatically from `data-start`, `data-duration`, and `data-media-start`. Scripts that duplicate this behavior will conflict with the framework.
|
|
193
|
+
|
|
194
|
+
### Script Isolation
|
|
195
|
+
|
|
196
|
+
Each composition's script is scoped to that composition. For sub-compositions, external JS files are the natural way to keep them self-contained and reusable:
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<div id="el-5" data-composition-id="intro-anim" data-start="0" data-track-index="2">
|
|
200
|
+
<script src="intro-anim.js"></script>
|
|
201
|
+
</div>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Inline scripts are fine when the script belongs to that composition. The JS file itself is the documentation for what a composition does — the schema doesn't need to describe its internals.
|
|
205
|
+
|
|
206
|
+
The only required file is `index.html`. Every composition must have at least a script to create and register its GSAP timeline.
|
|
207
|
+
|
|
208
|
+
### Top-Level Composition
|
|
209
|
+
|
|
210
|
+
The top-level composition is the `index.html` entry point. It acts as the conductor — sequencing clips and placing sub-composition timelines into an overall master timeline. It can technically do anything in its script, but its primary purpose is high-level orchestration. Any composition can serve as a top-level composition or be nested into another — there is no structural difference.
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<div id="comp-1" data-composition-id="my-video" data-start="0" data-width="1920" data-height="1080">
|
|
214
|
+
<video id="el-1" data-start="0" data-duration="10" data-track-index="0" src="..."></video>
|
|
215
|
+
<video id="el-2" data-start="el-1" data-duration="8" data-track-index="0" src="..."></video>
|
|
216
|
+
<img id="el-3" data-start="5" data-duration="4" data-track-index="1" src="..." />
|
|
217
|
+
<audio id="el-4" data-start="0" data-duration="30" data-track-index="2" src="..." />
|
|
218
|
+
|
|
219
|
+
<div id="el-5" data-composition-id="intro-anim" data-start="0" data-track-index="3">
|
|
220
|
+
<script src="intro-anim.js"></script>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<script>
|
|
224
|
+
// Just register the timeline - framework auto-nests sub-compositions
|
|
225
|
+
const tl = gsap.timeline({ paused: true });
|
|
226
|
+
window.__timelines["my-video"] = tl;
|
|
227
|
+
</script>
|
|
228
|
+
</div>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Sub-Compositions
|
|
232
|
+
|
|
233
|
+
Sub-compositions are a spectrum. One might simply group a few primitive clips. Another might be a fully custom program with its own HTML, CSS, and JavaScript — creating, animating, and destroying DOM however it sees fit. There are no categories or constraints on what a sub-composition does internally. All compositions default to full-screen within the viewport. The only rule: it must be driven by a GSAP timeline and export it.
|
|
234
|
+
|
|
235
|
+
## Wrapping Dynamic Content in Compositions
|
|
236
|
+
|
|
237
|
+
**Critical Rule: All visual content must live inside a composition with data attributes to appear in the timeline.**
|
|
238
|
+
|
|
239
|
+
When you have dynamic or script-animated content (captions, emojis, overlays, text animations), wrap them in a composition element with `data-start`, `data-duration` (or let the timeline determine duration), and `data-track-index`. The children inside can be freely created and animated via JavaScript—they don't need individual data attributes.
|
|
240
|
+
|
|
241
|
+
### Wrong: Dynamic content outside a composition
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<!-- BAD: captions-container is not a composition - won't appear in timeline -->
|
|
245
|
+
<div id="ui-layer">
|
|
246
|
+
<div id="captions-container">
|
|
247
|
+
<!-- Dynamically created caption groups via JS -->
|
|
248
|
+
</div>
|
|
249
|
+
<div class="emoji" id="emoji-1">🤩</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<script>
|
|
253
|
+
// These animations work visually but elements don't appear in timeline
|
|
254
|
+
tl.to(".caption-group", { opacity: 1 }, 0.5);
|
|
255
|
+
tl.to("#emoji-1", { scale: 1.2 }, 2);
|
|
256
|
+
</script>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Correct: Dynamic content wrapped in compositions
|
|
260
|
+
|
|
261
|
+
```html
|
|
262
|
+
<!-- GOOD: Each logical group is a composition that appears in timeline -->
|
|
263
|
+
<div id="captions-comp" data-composition-id="captions" data-start="0" data-track-index="5">
|
|
264
|
+
<!-- Children created/animated by script - no data attributes needed -->
|
|
265
|
+
<div id="captions-container"></div>
|
|
266
|
+
<script>
|
|
267
|
+
const captionTL = gsap.timeline({ paused: true });
|
|
268
|
+
// Dynamically create and animate caption groups...
|
|
269
|
+
window.__timelines["captions"] = captionTL;
|
|
270
|
+
</script>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<div id="emojis-comp" data-composition-id="emojis" data-start="0" data-track-index="6">
|
|
274
|
+
<div class="emoji" id="emoji-1">🤩</div>
|
|
275
|
+
<div class="emoji" id="emoji-2">🏔️</div>
|
|
276
|
+
<script>
|
|
277
|
+
const emojiTL = gsap.timeline({ paused: true });
|
|
278
|
+
emojiTL.to("#emoji-1", { opacity: 1, scale: 1.2 }, 2);
|
|
279
|
+
emojiTL.to("#emoji-2", { opacity: 1, scale: 1.2 }, 4);
|
|
280
|
+
window.__timelines["emojis"] = emojiTL;
|
|
281
|
+
</script>
|
|
282
|
+
</div>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### When to create separate compositions
|
|
286
|
+
|
|
287
|
+
- **Captions**: One composition for all captions, script manages word groups
|
|
288
|
+
- **Emojis/Stickers**: One composition for the emoji layer
|
|
289
|
+
- **Hooks/Titles**: One composition per distinct title sequence
|
|
290
|
+
- **Overlays**: Group related overlays into compositions by purpose
|
|
291
|
+
|
|
292
|
+
The composition appears in the timeline with its start time and duration (determined by its GSAP timeline). Everything inside is managed by the script but inherits the composition's timeline position.
|
|
293
|
+
|
|
294
|
+
## Timeline Contract
|
|
295
|
+
|
|
296
|
+
The framework initializes `window.__timelines = {}` before any scripts run. Every composition **must** have a script that creates a GSAP timeline and registers it:
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
const tl = gsap.timeline({ paused: true });
|
|
300
|
+
// ... add tweens, nested timelines, etc.
|
|
301
|
+
window.__timelines["<data-composition-id>"] = tl;
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Rules
|
|
305
|
+
|
|
306
|
+
- **Every composition needs a script** — at minimum, to create and register its timeline. A composition without a script has no timeline and cannot participate in the hierarchy.
|
|
307
|
+
- **All timelines start paused** — create timelines with `{ paused: true }`. The top-level timeline is controlled externally by the frontend player or renderer.
|
|
308
|
+
- **Framework auto-nests sub-timelines** — you do **not** need to manually add sub-composition timelines to the master timeline. The framework automatically nests any timeline registered in `window.__timelines` into its parent based on the composition's `data-start` attribute.
|
|
309
|
+
- **Duration comes from the timeline** — a composition's duration is `tl.duration()`. The timeline is the sole source of truth; there is no `data-duration` on compositions.
|
|
310
|
+
|
|
311
|
+
### What NOT to do
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
// UNNECESSARY - the framework does this automatically
|
|
315
|
+
if (window.__timelines["captions"]) {
|
|
316
|
+
masterTL.add(window.__timelines["captions"], 0);
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Just register your timeline in `window.__timelines` and the framework handles the rest.
|
|
321
|
+
|
|
322
|
+
## Output Checklist
|
|
323
|
+
|
|
324
|
+
- [ ] Root composition has `data-width` and `data-height` attributes
|
|
325
|
+
- [ ] `window.__timelines` given all compositions' timelines
|
|
326
|
+
- [ ] Complex/dynamic animations are handled by scripts in compositions
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hyperframes/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./src/index.ts",
|
|
10
|
+
"types": "./src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"./lint": {
|
|
13
|
+
"import": "./src/lint/index.ts",
|
|
14
|
+
"types": "./src/lint/index.ts"
|
|
15
|
+
},
|
|
16
|
+
"./compiler": {
|
|
17
|
+
"import": "./src/compiler/index.ts",
|
|
18
|
+
"types": "./src/compiler/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"./runtime": "./dist/hyperframe.runtime.iife.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"docs",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./lint": {
|
|
37
|
+
"import": "./dist/lint/index.js",
|
|
38
|
+
"types": "./dist/lint/index.d.ts"
|
|
39
|
+
},
|
|
40
|
+
"./compiler": {
|
|
41
|
+
"import": "./dist/compiler/index.js",
|
|
42
|
+
"types": "./dist/compiler/index.d.ts"
|
|
43
|
+
},
|
|
44
|
+
"./runtime": "./dist/hyperframe.runtime.iife.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc && pnpm build:hyperframes-runtime",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"test:coverage": "vitest run --coverage",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"lint:runtime-preview-guards": "tsx scripts/lint-runtime-preview-guards.ts",
|
|
54
|
+
"build:hyperframes-runtime": "tsx scripts/build-hyperframes-runtime-artifact.ts",
|
|
55
|
+
"build:hyperframes-runtime:modular": "SANDBOX_RUNTIME_VARIANT=modular tsx scripts/build-hyperframes-runtime-artifact.ts",
|
|
56
|
+
"build:hyperframe-runtime": "tsx scripts/build-hyperframes-runtime-artifact.ts",
|
|
57
|
+
|
|
58
|
+
"test:hyperframe-runtime-contract": "tsx scripts/test-hyperframe-runtime-contract.ts",
|
|
59
|
+
"test:hyperframe-runtime-behavior": "tsx scripts/test-hyperframe-runtime-behavior.ts",
|
|
60
|
+
"test:hyperframe-runtime-seek": "tsx scripts/test-hyperframe-runtime-seek.ts",
|
|
61
|
+
"test:hyperframe-runtime-duration-guards": "tsx scripts/test-hyperframe-runtime-duration-guards.ts",
|
|
62
|
+
"test:hyperframe-runtime-parity": "tsx scripts/test-hyperframe-runtime-parity.ts",
|
|
63
|
+
"test:hyperframe-runtime-security": "tsx scripts/test-hyperframe-runtime-security.ts",
|
|
64
|
+
"test:hyperframe-linter": "tsx scripts/test-hyperframe-linter.ts",
|
|
65
|
+
"test:hyperframe-runtime-ci": "pnpm build:hyperframes-runtime && pnpm test:hyperframe-runtime-contract && pnpm test:hyperframe-runtime-behavior && pnpm test:hyperframe-runtime-seek && pnpm test:hyperframe-runtime-duration-guards && pnpm test:hyperframe-runtime-parity && pnpm test:hyperframe-runtime-security",
|
|
66
|
+
"check:hyperframe-html": "tsx scripts/check-hyperframe-static.ts",
|
|
67
|
+
"debug:timeline": "tsx scripts/debug-timeline.ts",
|
|
68
|
+
"prepublishOnly": "pnpm build"
|
|
69
|
+
},
|
|
70
|
+
"optionalDependencies": {
|
|
71
|
+
"cheerio": "^1.2.0",
|
|
72
|
+
"esbuild": "^0.25.12"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@types/jsdom": "^28.0.0",
|
|
76
|
+
"@types/node": "^24.10.13",
|
|
77
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
78
|
+
"jsdom": "^29.0.0",
|
|
79
|
+
"tsx": "^4.21.0",
|
|
80
|
+
"typescript": "^5.0.0",
|
|
81
|
+
"vitest": "^3.2.4"
|
|
82
|
+
}
|
|
83
|
+
}
|