Add workout videos, editable HyperFrames projects and music tools

- diamond-pushup (with and without music) and bench-dips videos
- Editable project sources for both videos
- make_music.py: original copyright-free music generator

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXVsD1DgdzZLiPuwtKzMSY
This commit is contained in:
Claude committed 2026-10-21 23:40:56 +00:00
1 parent daac216bb9
commit 8b1eeff1ed
33 files changed
+1051

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
# Workout videos
Made with [HyperFrames](https://github.com/heygen-com/hyperframes) (HTML rendered to video).
| File | What it is |
|---|---|
| `diamond-pushup-music.mp4` | Diamond push-up, title and coaching tips, with music (10.1 s) |
| `diamond-pushup.mp4` | Same video without music |
| `bench-dips.mp4` | Bench dips, animated athlete, rep counter, tips and music (14.2 s) |
## Editable sources
- `projects/diamond-pushup/` and `projects/bench-dips/`: each is a HyperFrames project. Edit it, then from inside the folder run `npx hyperframes check` and `npx hyperframes render --quality looks --output out.mp4` (needs Node 22+ and FFmpeg).
- `music/make_music.py`: the program that composes the original, copyright-free music
(`python make_music.py out.wav SECONDS BPM rise|anthem`, needs `numpy` and `scipy`).
`music/norm.sh` sets its loudness and writes the MP3.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+153
View File
@@ -0,0 +1,153 @@
"""Original gym-motivation track, synthesised from scratch (no samples, no downloads).
usage: make_music.py OUT.wav DURATION_S BPM ROOT_MIDI_A_MINOR_STYLE_PROG_NAME"""
import sys, numpy as np
from scipy import signal
SR = 44100
out_path, dur, bpm, prog_name = sys.argv[1], float(sys.argv[2]), float(sys.argv[3]), sys.argv[4]
rng = np.random.default_rng(7)
beat = 60.0 / bpm
N = int((dur + 1.5) * SR) # render a little extra, trim at the end
t_all = np.arange(N) / SR
def midi(m): return 440.0 * 2 ** ((m - 69) / 12)
def lp(x, f, order=2): b, a = signal.butter(order, f / (SR / 2), "low"); return signal.lfilter(b, a, x)
def hp(x, f, order=2): b, a = signal.butter(order, f / (SR / 2), "high"); return signal.lfilter(b, a, x)
def bp(x, lo, hi): b, a = signal.butter(2, [lo / (SR / 2), hi / (SR / 2)], "band"); return signal.lfilter(b, a, x)
def put(buf, sig, at_s, gain=1.0, pan=0.0):
i = int(at_s * SR)
if i >= N: return
j = min(N, i + len(sig)); seg = sig[: j - i] * gain
buf[0, i:j] += seg * (1 - max(0, pan)); buf[1, i:j] += seg * (1 + min(0, pan))
def saw(f, n, detune=0.0, ph=0.0):
tt = np.arange(n) / SR
return 2 * ((tt * f * (1 + detune) + ph) % 1.0) - 1
# ---- progressions: (root midi for bass, chord tones midi) per bar --------------------------
PROGS = {
# Am F C G : minor start, lifts to major - classic "push through" feel
"rise": [(45, [57, 60, 64]), (41, [53, 57, 60]), (48, [55, 60, 64]), (43, [55, 59, 62])],
# Em C G D : bright anthemic
"anthem": [(40, [55, 59, 64]), (36, [55, 60, 64]), (43, [55, 59, 62]), (38, [54, 57, 62])],
}
prog = PROGS[prog_name]
n_beats = int(np.ceil((dur + 1.0) / beat))
final_beat = int((dur - 0.6) // beat) # the big final hit lands about 0.6-1 s before the end
final_beat -= final_beat % 4 # on a bar line
dry = np.zeros((2, N)) # drums / bass
mus = np.zeros((2, N)) # pad + pluck (gets reverb)
kick_times = []
# ---- drums ---------------------------------------------------------------------------------
def kick():
n = int(0.45 * SR); tt = np.arange(n) / SR
f = 45 + 110 * np.exp(-tt / 0.035)
ph = 2 * np.pi * np.cumsum(f) / SR
return (np.sin(ph) * np.exp(-tt / 0.17) + 0.25 * np.exp(-tt / 0.004) * rng.standard_normal(n)) * 1.0
def clap():
s = np.zeros(int(0.3 * SR))
for k, d in enumerate([0, 0.011, 0.022, 0.034]):
n = int(0.18 * SR); tt = np.arange(n) / SR
burst = bp(rng.standard_normal(n), 900, 3800) * np.exp(-tt / (0.012 if k < 3 else 0.07))
s[int(d * SR): int(d * SR) + n] += burst[: len(s) - int(d * SR)]
return s * 0.9
def hat(decay):
n = int(decay * 5 * SR); tt = np.arange(n) / SR
return hp(rng.standard_normal(n), 7000) * np.exp(-tt / decay) * 0.5
def crash():
n = int(1.4 * SR); tt = np.arange(n) / SR
return hp(rng.standard_normal(n), 5000) * np.exp(-tt / 0.3) * 0.3
K, C, HH, HHO = kick(), clap(), hat(0.025), hat(0.09)
for b in range(n_beats):
tb = b * beat; bar_i, bt = divmod(b, 4)
if b > final_beat: break
if b < final_beat:
put(dry, K, tb, 1.0); kick_times.append(tb)
if bar_i >= 1 and bt in (1, 3): put(dry, C, tb, 0.8)
if bar_i >= 1:
put(dry, HH, tb + beat * 0.5, 0.7)
if bt == 3: put(dry, HHO, tb + beat * 0.5, 0.6)
if bar_i >= 2: [put(dry, HH, tb + beat * x, 0.35) for x in (0.25, 0.75)]
else:
put(dry, K, tb, 1.1); put(dry, C, tb, 1.0); put(dry, crash(), tb, 0.9); kick_times.append(tb)
# ---- bass: rolling offbeat 8ths, silent on the final hit bar until the downbeat -------------
for b in range(final_beat):
root = prog[(b // 4) % len(prog)][0]
for h in (0.5, 0.0):
if h == 0.0 and b % 4 != 0: continue
n = int(beat * 0.45 * SR); tt = np.arange(n) / SR
f = midi(root + 12)
tone = np.sin(2 * np.pi * f * tt) + 0.35 * lp(saw(f, n), 600)
put(dry, tone * np.exp(-tt / 0.25) * np.minimum(1, tt / 0.004), b * beat + h * beat, 0.55)
rootF = prog[(final_beat // 4) % len(prog)][0] + 12
nF = int(1.8 * SR); ttF = np.arange(nF) / SR
put(dry, np.sin(2 * np.pi * midi(rootF) * ttF) * np.exp(-ttF / 0.9), final_beat * beat, 0.7)
# ---- pad: detuned saws on the chord, ducked by the kick -----------------------------------------
duck = np.ones(N)
for kt in kick_times:
i = int(kt * SR); m = min(N, i + int(0.35 * SR)); tt = np.arange(m - i) / SR
duck[i:m] = np.minimum(duck[i:m], 1 - 0.75 * np.exp(-tt / 0.11))
for bar in range(int(np.ceil(n_beats / 4))):
start = bar * 4 * beat
if start >= dur + 0.5: break
last = bar * 4 >= final_beat
chord = prog[bar % len(prog)][1]
length = 1.9 if last else 4 * beat
n = int((length + 0.3) * SR); tt = np.arange(n) / SR
pad = np.zeros(n)
for m in chord:
for dt in (-0.004, 0.0, 0.004):
pad += saw(midi(m), n, dt, rng.random())
pad = lp(pad / 9, 1900)
env = np.minimum(1, tt / 0.12) * (np.exp(-np.maximum(0, tt - length) / 0.12))
put(mus, pad * env * 0.55, start, 1.0, 0.0)
mus *= duck[None, :]
# ---- pluck arpeggio on 8ths (bright, hooky), with a dotted-eighth echo -------------------------
pluck_bus = np.zeros((2, N))
pat = [0, 1, 2, 1, 2, 1, 2, 3] # indices into chord tones (3 = octave of the first)
for b8 in range(int(final_beat * 2)):
bar = (b8 // 8) % len(prog); chord = prog[bar][1]
if b8 // 8 < 1 and b8 % 2 == 1: continue # sparser in the first bar
idx = pat[b8 % 8]; m = (chord[idx] if idx < 3 else chord[0] + 12) + 12
n = int(0.28 * SR); tt = np.arange(n) / SR
f = midi(m)
tone = lp(saw(f, n) + saw(f, n, 0.006), 3200)
put(pluck_bus, tone * np.exp(-tt / 0.1) * np.minimum(1, tt / 0.002), b8 * beat * 0.5, 0.30, pan=(-0.35 if b8 % 2 else 0.35))
for k, g in ((1, 0.42), (2, 0.2)):
d = int(0.75 * beat * SR * k)
mus[:, d:] += pluck_bus[:, : N - d][:, :] * g
mus += pluck_bus
# ---- small room reverb on the musical bus -------------------------------------------------------
def reverb(x, wet=0.22):
y = np.zeros_like(x)
for ch in range(2):
acc = np.zeros(N)
for d, g in ((0.0297, 0.80), (0.0371, 0.78), (0.0411, 0.76), (0.0437, 0.74)):
D = int(d * SR * (1 + 0.03 * ch)); c = np.zeros(N + D); c[:N] = x[ch]
c = signal.lfilter([1], [1] + [0] * (D - 1) + [-g], c)[:N]; acc += c
y[ch] = acc / 4
return x * (1 - wet) + lp(y, 5000) * wet * 2.0
mus = reverb(mus)
# ---- riser into the final hit -----------------------------------------------------------------
rn = int(2 * beat * SR); rt = np.arange(rn) / SR
riser = hp(rng.standard_normal(rn), 2500) * (rt / rt[-1]) ** 2 * 0.35
put(dry, riser, final_beat * beat - 2 * beat, 0.7)
# ---- mix, soft-limit, trim, fade ----------------------------------------------------------------
mix = dry + mus * 0.9
mix = np.tanh(mix * 0.85) / np.tanh(0.85)
mix = mix[:, : int(dur * SR)]
fade = int(0.5 * SR); mix[:, -fade:] *= np.linspace(1, 0, fade)
mix *= 0.89 / np.max(np.abs(mix))
import wave
pcm = (mix.T * 32767).astype("<i2")
with wave.open(out_path, "wb") as w:
w.setnchannels(2); w.setsampwidth(2); w.setframerate(SR); w.writeframes(pcm.tobytes())
print("wrote", out_path, f"{dur:.2f}s", f"bpm={bpm}", prog_name, "final hit at", round(final_beat * beat, 2), "s")
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
# usage: norm.sh in.wav out.mp3 target_LUFS -> gain-matches to the target, limits peaks to -1.5 dBFS, encodes mp3
in=$1; out=$2; tgt=${3:--15}
I=$(ffmpeg -hide_banner -i "$in" -af ebur128 -f null - 2>&1 | awk '/I:/{v=$2} END{print v}')
G=$(python3 -c "print(round($tgt - ($I),2))")
ffmpeg -v error -y -i "$in" -af "volume=${G}dB,alimiter=limit=0.84:level=disabled" -b:a 192k "$out"
echo "$in: was ${I} LUFS, gain ${G} dB"
ffmpeg -hide_banner -i "$out" -af ebur128=peak=true -f null - 2>&1 | grep -E "^\s+(I|Peak):"
+104
View File
@@ -0,0 +1,104 @@
# HyperFrames Composition Project
## Skills — read these first
**Invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that generic web docs don't cover, so compositions written without them break.
**Doing anything with HyperFrames?** Start at `/hyperframes`. Make any video by asking for it; the `/hyperframes` skill carries the contract and the references (each kind of film, story, motion, look, sound, review, captions, overlays). Read it first, especially when there's no project context to orient you.
- `/hyperframes` — read first for any request to make, edit, animate or render a video, motion graphic, captions or overlays. Holds the render contract and the work loop.
- `/hyperframes-cli` — the CLI by job: scaffold, check, look at frames, edit the timeline, preview, render, publish, project history.
- `/hyperframes-registry` — ready-made blocks and components installed as editable source. Search it before hand-building any named visual.
- `/media-use` — get or make any media (music, sound effects, images, voiceover, avatar and generated video, transcripts, cutouts, colour grades) and mix placed audio.
- `/figma` — bring a Figma design, frame, logo or brand into a video.
- `/slideshow` — a presentation or pitch deck. Output is a navigable deck, not a rendered MP4.
- `/remotion-to-hyperframes` — bring existing video code or footage into HyperFrames (Remotion first; also Motion Canvas, Lottie, Three.js, canvas, or renders from other tools) and verify it frame by frame against the original.
**Changing how real footage or images look or reveal?** Load `/media-use` and read its `references/media-treatments.md` before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” It governs how footage is treated, never whether media may be used. Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays.
> **Tailwind v4 projects** (`hyperframes init --tailwind`): see `hyperframes-cli/references/init-and-scaffold.md` (Tailwind).
> **Using a HyperFrames plugin?** Load skills from that installed bundle and follow
> its `hyperframes/references/plugin-installation.md` execution rules. Update via
> the plugin manager, not the standalone commands below.
>
> **Standalone skill missing or stale?** Run `npx hyperframes skills update <name>` to install/refresh
> the specific skill you need, or bare `npx hyperframes skills update` to refresh the core set plus
> everything already installed — neither pulls the full set. Restart the agent session so
> newly installed skills load.
## Commands
```bash
npm run dev # human-operated foreground preview (blocks until stopped)
npx hyperframes preview --background # agent-safe persistent Studio preview
npx hyperframes preview --status # verify the persistent preview is listening
npx hyperframes preview --stop # stop it when review is finished
npm run check # lint + runtime + layout + motion + contrast (one command)
npm run render # render to MP4
npm run publish # publish and get a shareable link
npx hyperframes lint --verbose # include info-level findings
npx hyperframes lint --json # machine-readable output for CI
npx hyperframes docs <topic> # reference docs in terminal
```
> **Agents must use `npx hyperframes preview --background` for Studio handoff.** Do not rely
> on a shell/tool `run_in_background` wrapper around `npm run dev`: that foreground process
> remains owned by the invoking session and can disappear while the browser stays open,
> leaving refreshes at `ERR_CONNECTION_TIMED_OUT`. Verify with `preview --status`, keep it
> alive through review, and stop it explicitly with `preview --stop` afterward.
> **Back from the desktop app.** Once this project was opened in the HyperFrames desktop app, run `npx hyperframes catch-up` before your next change here: it lists what the person asked Framey in the app and which files changed since.
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
## Documentation
**For quick reference**, use the local CLI docs command (no network required):
```bash
npx hyperframes docs <topic>
```
Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
**For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
```
https://hyperframes.heygen.com/llms.txt
```
## Project Structure
- `index.html` — main composition (root timeline)
- `compositions/` — sub-compositions referenced via `data-composition-src`
- `meta.json` — project metadata (id, name)
- `transcript.json` — whisper word-level transcript (if generated)
## Linting — ALWAYS RUN AFTER CHANGES
After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
```bash
npm run check
```
Fix all errors before presenting the result. Warnings should be reviewed before rendering.
Then look at what you changed: `npx hyperframes snapshot --at <seconds>` (or `--range START,END`, `--composition <id>`) at the moments you edited, and read the frames before you reply. A passing check does not show how it looks.
## Key Rules
1. Every timed element needs `data-start` and a duration. `data-start` is what marks it as timed; `data-track-index` is an optional Studio display lane the render never reads
2. Give timed visual elements `class="clip"`. The framework keys visibility off `data-start`, not the class, but the shared `.clip` CSS is what gives a scene its full-frame box, and `lint` warns without it
3. Register one paused root timeline per composition on `window.__timelines`:
```js
window.__timelines = window.__timelines || {};
window.__timelines["composition-id"] = gsap.timeline({ paused: true });
```
Scene timelines manually added to this root must not be paused. A paused
child does not advance when the root is seeked. The runtime activates
registered composition siblings, not arbitrary nested scene timelines.
4. A video with sound keeps it on the `<video>` (`data-has-audio="true"`, no `muted`). Use a separate `<audio>` for music, voiceover, replacement audio, J/L cuts, or audio detached in Studio. Silent footage and b-roll: `muted`.
5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
+104
View File
@@ -0,0 +1,104 @@
# HyperFrames Composition Project
## Skills — read these first
**Invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that generic web docs don't cover, so compositions written without them break.
**Doing anything with HyperFrames?** Start at `/hyperframes`. Make any video by asking for it; the `/hyperframes` skill carries the contract and the references (each kind of film, story, motion, look, sound, review, captions, overlays). Read it first, especially when there's no project context to orient you.
- `/hyperframes` — read first for any request to make, edit, animate or render a video, motion graphic, captions or overlays. Holds the render contract and the work loop.
- `/hyperframes-cli` — the CLI by job: scaffold, check, look at frames, edit the timeline, preview, render, publish, project history.
- `/hyperframes-registry` — ready-made blocks and components installed as editable source. Search it before hand-building any named visual.
- `/media-use` — get or make any media (music, sound effects, images, voiceover, avatar and generated video, transcripts, cutouts, colour grades) and mix placed audio.
- `/figma` — bring a Figma design, frame, logo or brand into a video.
- `/slideshow` — a presentation or pitch deck. Output is a navigable deck, not a rendered MP4.
- `/remotion-to-hyperframes` — bring existing video code or footage into HyperFrames (Remotion first; also Motion Canvas, Lottie, Three.js, canvas, or renders from other tools) and verify it frame by frame against the original.
**Changing how real footage or images look or reveal?** Load `/media-use` and read its `references/media-treatments.md` before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” It governs how footage is treated, never whether media may be used. Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays.
> **Tailwind v4 projects** (`hyperframes init --tailwind`): see `hyperframes-cli/references/init-and-scaffold.md` (Tailwind).
> **Using a HyperFrames plugin?** Load skills from that installed bundle and follow
> its `hyperframes/references/plugin-installation.md` execution rules. Update via
> the plugin manager, not the standalone commands below.
>
> **Standalone skill missing or stale?** Run `npx hyperframes skills update <name>` to install/refresh
> the specific skill you need, or bare `npx hyperframes skills update` to refresh the core set plus
> everything already installed — neither pulls the full set. Restart the agent session so
> newly installed skills load.
## Commands
```bash
npm run dev # human-operated foreground preview (blocks until stopped)
npx hyperframes preview --background # agent-safe persistent Studio preview
npx hyperframes preview --status # verify the persistent preview is listening
npx hyperframes preview --stop # stop it when review is finished
npm run check # lint + runtime + layout + motion + contrast (one command)
npm run render # render to MP4
npm run publish # publish and get a shareable link
npx hyperframes lint --verbose # include info-level findings
npx hyperframes lint --json # machine-readable output for CI
npx hyperframes docs <topic> # reference docs in terminal
```
> **Agents must use `npx hyperframes preview --background` for Studio handoff.** Do not rely
> on a shell/tool `run_in_background` wrapper around `npm run dev`: that foreground process
> remains owned by the invoking session and can disappear while the browser stays open,
> leaving refreshes at `ERR_CONNECTION_TIMED_OUT`. Verify with `preview --status`, keep it
> alive through review, and stop it explicitly with `preview --stop` afterward.
> **Back from the desktop app.** Once this project was opened in the HyperFrames desktop app, run `npx hyperframes catch-up` before your next change here: it lists what the person asked Framey in the app and which files changed since.
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
## Documentation
**For quick reference**, use the local CLI docs command (no network required):
```bash
npx hyperframes docs <topic>
```
Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
**For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
```
https://hyperframes.heygen.com/llms.txt
```
## Project Structure
- `index.html` — main composition (root timeline)
- `compositions/` — sub-compositions referenced via `data-composition-src`
- `meta.json` — project metadata (id, name)
- `transcript.json` — whisper word-level transcript (if generated)
## Linting — ALWAYS RUN AFTER CHANGES
After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
```bash
npm run check
```
Fix all errors before presenting the result. Warnings should be reviewed before rendering.
Then look at what you changed: `npx hyperframes snapshot --at <seconds>` (or `--range START,END`, `--composition <id>`) at the moments you edited, and read the frames before you reply. A passing check does not show how it looks.
## Key Rules
1. Every timed element needs `data-start` and a duration. `data-start` is what marks it as timed; `data-track-index` is an optional Studio display lane the render never reads
2. Give timed visual elements `class="clip"`. The framework keys visibility off `data-start`, not the class, but the shared `.clip` CSS is what gives a scene its full-frame box, and `lint` warns without it
3. Register one paused root timeline per composition on `window.__timelines`:
```js
window.__timelines = window.__timelines || {};
window.__timelines["composition-id"] = gsap.timeline({ paused: true });
```
Scene timelines manually added to this root must not be paused. A paused
child does not advance when the root is seeked. The runtime activates
registered composition siblings, not arbitrary nested scene timelines.
4. A video with sound keeps it on the `<video>` (`data-has-audio="true"`, no `muted`). Use a separate `<audio>` for music, voiceover, replacement audio, J/L cuts, or audio detached in Studio. Silent footage and b-roll: `muted`.
5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
+11
View File
@@ -0,0 +1,11 @@
/*!
* GSAP 3.14.2
* https://gsap.com
*
* @license Copyright 2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license.
* @author: Jack Doyle, jack@greensock.com
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function r(t){return"string"==typeof t}function s(t){return"function"==typeof t}function t(t){return"number"==typeof t}function u(t){return void 0===t}function v(t){return"object"==typeof t}function w(t){return!1!==t}function x(){return"undefined"!=typeof window}function y(t){return s(t)||r(t)}function R(t){return(i=bt(t,ht))&&Fe}function S(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function T(t,e){return!e&&console.warn(t)}function U(t,e){return t&&(ht[t]=e)&&i&&(i[t]=e)||ht}function V(){return 0}function ga(t){var e,r,i=t[0];if(v(i)||s(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=yt.length;r--&&!yt[r].targetTest(i););e=yt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Xt(t[r],e)))||t.splice(r,1);return t}function ha(t){return t._gsap||ga(Pt(t))[0]._gsap}function ia(t,e,r){return(r=t[e])&&s(r)?t[e]():u(r)&&t.getAttribute&&t.getAttribute(e)||r}function ja(t,e){return(t=t.split(",")).forEach(e)||t}function ka(t){return Math.round(1e5*t)/1e5||0}function la(t){return Math.round(1e7*t)/1e7||0}function ma(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function na(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++i<r;);return i<r}function oa(){var t,e,r=pt.length,i=pt.slice(0);for(_t={},t=pt.length=0;t<r;t++)(e=i[t])&&e._lazy&&(e.render(e._lazy[0],e._lazy[1],!0)._lazy=0)}function pa(t){return!!(t._initted||t._startAt||t.add)}function qa(t,e,r,i){pt.length&&!I&&oa(),t.render(e,r,i||!!(I&&e<0&&pa(t))),pt.length&&!I&&oa()}function ra(t){var e=parseFloat(t);return(e||0===e)&&(t+"").match(ot).length<2?e:r(t)?t.trim():t}function sa(t){return t}function ta(t,e){for(var r in e)r in t||(t[r]=e[r]);return t}function wa(t,e){for(var r in e)"__proto__"!==r&&"constructor"!==r&&"prototype"!==r&&(t[r]=v(e[r])?wa(t[r]||(t[r]={}),e[r]):e[r]);return t}function xa(t,e){var r,i={};for(r in t)r in e||(i[r]=t[r]);return i}function ya(t){var e=t.parent||L,r=t.keyframes?function _setKeyframeDefaults(i){return function(t,e){for(var r in e)r in t||"duration"===r&&i||"ease"===r||(t[r]=e[r])}}($(t.keyframes)):ta;if(w(t.inherit))for(;e;)r(t,e.vars.defaults),e=e.parent||e._dp;return t}function Aa(t,e,r,i,n){void 0===r&&(r="_first"),void 0===i&&(i="_last");var a,s=t[i];if(n)for(a=e[n];s&&s[n]>a;)s=s._prev;return s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[i]=e,e._prev=s,e.parent=e._dp=t,e}function Ba(t,e,r,i){void 0===r&&(r="_first"),void 0===i&&(i="_last");var n=e._prev,a=e._next;n?n._next=a:t[r]===e&&(t[r]=a),a?a._prev=n:t[i]===e&&(t[i]=n),e._next=e._prev=e.parent=null}function Ca(t,e){t.parent&&(!e||t.parent.autoRemoveChildren)&&t.parent.remove&&t.parent.remove(t),t._act=0}function Da(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var r=t;r;)r._dirty=1,r=r.parent;return t}function Fa(t,e,r,i){return t._startAt&&(I?t._startAt.revert(ft):t.vars.immediateRender&&!t.vars.autoRevert||t._startAt.render(e,!0,i))}function Ha(t){return t._repeat?wt(t._tTime,t=t.duration()+t._rDelay)*t:0}function Ja(t,e){return(t-e._start)*e._ts+(0<=e._ts?0:e._dirty?e.totalDuration():e._tDur)}function Ka(t){return t._end=la(t._start+(t._tDur/Math.abs(t._ts||t._rts||q)||0))}function La(t,e){var r=t._dp;return r&&r.smoothChildTiming&&t._ts&&(t._start=la(r._time-(0<t._ts?e/t._ts:((t._dirty?t.totalDuration():t._tDur)-e)/-t._ts)),Ka(t),r._dirty||Da(r,t)),t}function Ma(t,e){var r;if((e._time||!e._dur&&e._initted||e._start<t._time&&(e._dur||!e.add))&&(r=Ja(t.rawTime(),e),(!e._dur||Mt(0,e.totalDuration(),r)-e._tTime>q)&&e.render(r,!0)),Da(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur<t.duration())for(r=t;r._dp;)0<=r.rawTime()&&r.totalTime(r._tTime),r=r._dp;t._zTime=-q}}function Na(e,r,i,n){return r.parent&&Ca(r),r._start=la((t(i)?i:i||e!==L?Ot(e,i,r):e._time)+r._delay),r._end=la(r._start+(r.totalDuration()/Math.abs(r.timeScale())||0)),Aa(e,r,"_first","_last",e._sort?"_start":0),xt(r)||(e._recent=r),n||Ma(e,r),e._ts<0&&La(e,e._tTime),e}function Oa(t,e){return(ht.ScrollTrigger||S("scrollTrigger",e))&&ht.ScrollTrigger.create(e,t)}function Pa(t,e,r,i,n){return Qt(t,e,n),t._initted?!r&&t._pt&&!I&&(t._dur&&!1!==t.vars.lazy||!t._dur&&t.vars.lazy)&&f!==It.frame?(pt.push(t),t._lazy=[n,i],1):void 0:1}function Ua(t,e,r,i){var n=t._repeat,a=la(e)||0,s=t._tTime/t._tDur;return s&&!i&&(t._time*=a/t._dur),t._dur=a,t._tDur=n?n<0?1e10:la(a*(n+1)+t._rDelay*n):a,0<s&&!i&&La(t,t._tTime=t._tDur*s),t.parent&&Ka(t),r||Da(t.parent,t),t}function Va(t){return t instanceof Zt?Da(t)Line truncated
Binary file not shown.
@@ -0,0 +1,22 @@
<template>
<style>
#root { position: absolute; inset: 0; font-family: "Oswald", sans-serif; color: #eaf2ff; }
.rep { position: absolute; right: 192px; top: 108px; text-align: right; font-weight: 700; font-size: 64px; line-height: 1; letter-spacing: 0.04em; white-space: nowrap; }
.rep .lbl { font-family: "Inter", sans-serif; font-weight: 600; font-size: 28px; letter-spacing: 0.22em; margin-right: 18px; vertical-align: middle; }
.rep .n { color: var(--accent, #ff7a45); font-size: 96px; vertical-align: middle; }
.rep .of { opacity: 0.7; font-size: 48px; vertical-align: middle; }
</style>
<div id="root" data-composition-id="counter" data-width="1920" data-height="1080">
<div id="rep1" class="clip rep" data-start="2.4" data-duration="3.2" data-track-index="0"><span class="lbl">REP</span><span class="n">1</span> <span class="of">/ 3</span></div>
<div id="rep2" class="clip rep" data-start="5.6" data-duration="3.2" data-track-index="0"><span class="lbl">REP</span><span class="n">2</span> <span class="of">/ 3</span></div>
<div id="rep3" class="clip rep" data-start="8.8" data-duration="3.2" data-track-index="0"><span class="lbl">REP</span><span class="n">3</span> <span class="of">/ 3</span></div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
[["rep1", 2.4], ["rep2", 5.6], ["rep3", 8.8]].forEach(([id, s]) => {
tl.fromTo("#" + id, { opacity: 0, filter: "blur(6px)" }, { opacity: 1, filter: "blur(0px)", duration: 0.2, ease: "power4.out" }, s);
tl.to("#" + id, { opacity: 0, filter: "blur(6px)", duration: 0.12, ease: "power3.in" }, s + 3.2 - 0.17);
});
window.__timelines["counter"] = tl;
</script>
</template>
@@ -0,0 +1,126 @@
<template>
<style>
#root { position: absolute; inset: 0; background: #050b1a; }
#stage-svg { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; }
</style>
<div id="root" data-composition-id="stage" data-width="1920" data-height="1080">
<svg id="stage-svg" viewBox="0 0 1920 1080" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="st-smoke"><stop offset="0" stop-color="#4a7bd6" stop-opacity="0.34"/><stop offset="1" stop-color="#4a7bd6" stop-opacity="0"/></radialGradient>
<linearGradient id="st-floor" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#0d2147"/><stop offset="1" stop-color="#050b1a"/></linearGradient>
<radialGradient id="st-pool"><stop offset="0" stop-color="#3b82f6" stop-opacity="0.35"/><stop offset="1" stop-color="#3b82f6" stop-opacity="0"/></radialGradient>
<filter id="st-glow" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="9"/></filter>
</defs>
<rect width="1920" height="1080" fill="#050b1a"/>
<g id="st-smokes" data-layout-ignore></g>
<polygon points="0,780 1920,780 1920,1080 0,1080" fill="url(#st-floor)"/>
<g id="st-grid" data-layout-ignore stroke="#3b82f6" stroke-opacity="0.16" fill="none"></g>
<line x1="0" y1="780" x2="1920" y2="780" stroke="#60a5fa" stroke-opacity="0.5" stroke-width="2"/>
<g id="st-world" transform="translate(960 780) scale(1.2) translate(-960 -780)">
<ellipse cx="1000" cy="800" rx="720" ry="46" fill="url(#st-pool)"/>
<g id="st-bench">
<line x1="600" y1="625" x2="600" y2="780" stroke="#16306b" stroke-width="26" stroke-linecap="round"/>
<line x1="920" y1="625" x2="920" y2="780" stroke="#16306b" stroke-width="26" stroke-linecap="round"/>
<rect x="530" y="591" width="460" height="36" rx="12" fill="#1c3f8a"/>
<rect x="534" y="593" width="452" height="8" rx="4" fill="#7fb2ff" fill-opacity="0.55"/>
</g>
<g id="st-body"></g>
</g>
</svg>
</div>
<script>
const NS = "http://www.w3.org/2000/svg";
const svg = document.getElementById("stage-svg");
const mk = (tag, attrs, parent) => { const e = document.createElementNS(NS, tag); for (const k in attrs) e.setAttribute(k, attrs[k]); (parent || svg).appendChild(e); return e; };
// static atmosphere: floor grid + drifting smoke blobs (positions are a function of time below)
const grid = document.getElementById("st-grid");
for (let i = -12; i <= 12; i++) mk("line", { x1: 960 + i * 150, y1: 780, x2: 960 + i * 520, y2: 1080 }, grid);
for (let k = 1; k <= 7; k++) { const y = 780 + 300 * Math.pow(k / 7, 1.7); mk("line", { x1: 0, y1: y, x2: 1920, y2: y }, grid); }
const smokes = [[300, 300, 520, 0], [900, 220, 600, 1.7], [1500, 330, 560, 3.1], [1750, 560, 480, 4.6]].map(([x, y, r, ph]) =>
({ el: mk("circle", { cx: x, cy: y, r, fill: "url(#st-smoke)" }, document.getElementById("st-smokes")), x, y, ph }));
// body parts: each limb = dark outline + mid fill + thin highlight, so it reads as a glassy tube
const body = document.getElementById("st-body");
function limb(w, mid) {
const o = mk("line", { stroke: "#0b1d44", "stroke-width": w, "stroke-linecap": "round" }, body);
const m = mk("line", { stroke: mid || "#2a55b8", "stroke-width": w * 0.78, "stroke-linecap": "round" }, body);
const h = mk("line", { stroke: "#8fbcff", "stroke-opacity": 0.55, "stroke-width": w * 0.16, "stroke-linecap": "round" }, body);
return (a, b) => {
for (const e of [o, m]) { e.setAttribute("x1", a.x); e.setAttribute("y1", a.y); e.setAttribute("x2", b.x); e.setAttribute("y2", b.y); }
const dx = -w * 0.16, dy = -w * 0.2;
h.setAttribute("x1", a.x + dx); h.setAttribute("y1", a.y + dy); h.setAttribute("x2", b.x + dx); h.setAttribute("y2", b.y + dy);
};
}
const glowLine = (w) => mk("line", { stroke: "#ff7a45", "stroke-width": w, "stroke-linecap": "round" }, body);
const setShin = limb(54), setThigh = limb(76), setShorts = limb(102, "#10172f"), setTorso = limb(112, "#27499f"),
setUpper = limb(52), setFore = limb(42);
const shoe = mk("rect", { width: 92, height: 24, rx: 11, fill: "#0b1226", stroke: "#8fbcff", "stroke-opacity": 0.6, "stroke-width": 2 }, body);
const head = mk("circle", { r: 44, fill: "#2f5bbf", stroke: "#8fbcff", "stroke-opacity": 0.7, "stroke-width": 2 }, body);
const neck = limb(30, "#2a55b8");
const hand = mk("ellipse", { rx: 34, ry: 12, fill: "#7fa9f0", stroke: "#0b1d44", "stroke-width": 2 }, body);
// orange muscle glow: triceps on the upper arm, plus shoulder and chest
const tri = glowLine(26), triGlow = glowLine(40); triGlow.setAttribute("filter", "url(#st-glow)");
const delt = mk("circle", { r: 30, fill: "#ff7a45" }, body); delt.setAttribute("filter", "url(#st-glow)");
const chest = mk("circle", { r: 34, fill: "#ff7a45" }, body); chest.setAttribute("filter", "url(#st-glow)");
const deltCore = mk("circle", { r: 16, fill: "#ffb089" }, body);
// geometry (px). Real proportions: arm about 60 cm at 4.2 px/cm, bench 45 cm high.
const GY = 780, HAND = { x: 940, y: 581 };
const Lu = 126, Lf = 118, T = 210, Lleg = 378, lean = 6 * Math.PI / 180, DROP = 67;
const reach = 0.985 * (Lu + Lf);
const shTop = { x: HAND.x + 48, y: HAND.y - Math.sqrt(reach * reach - 48 * 48) };
const hip0 = { x: shTop.x + T * Math.sin(lean), y: shTop.y + T * Math.cos(lean) };
const ankle = { y: GY - 26 };
ankle.x = hip0.x + Math.sqrt(Lleg * Lleg - (ankle.y - hip0.y) ** 2); // heels stay planted
// reps: set-up hold to 2.4 s, then 3 reps of 3.2 s (1.7 s down, 1.5 s up), then a hold
const REP0 = 2.4, REP = 3.2, DOWN = 1.7, UP = 1.5;
const ease = (x) => 0.5 - 0.5 * Math.cos(Math.PI * Math.min(1, Math.max(0, x)));
function phase(t) {
for (let k = 0; k < 3; k++) {
const u = t - (REP0 + REP * k);
if (u >= 0 && u < DOWN) return { p: ease(u / DOWN), push: 0 };
if (u >= DOWN && u < REP) { const x = (u - DOWN) / UP; return { p: 1 - ease(x), push: Math.sin(Math.PI * x) }; }
}
return { p: 0, push: 0 };
}
function draw(t) {
const { p, push } = phase(t);
const hipY = hip0.y + DROP * p;
const hip = { x: ankle.x - Math.sqrt(Lleg * Lleg - (ankle.y - hipY) ** 2), y: hipY };
const sh = { x: hip.x - T * Math.sin(lean), y: hip.y - T * Math.cos(lean) };
// two-bone arm IK: hand is planted on the bench, elbow bends back (toward the bench)
const dx = HAND.x - sh.x, dy = HAND.y - sh.y, d = Math.hypot(dx, dy), dd = Math.min(d, (Lu + Lf) * 0.999);
const a = (Lu * Lu - Lf * Lf + dd * dd) / (2 * dd), h = Math.sqrt(Math.max(0, Lu * Lu - a * a));
const ux = dx / d, uy = dy / d;
const e1 = { x: sh.x + ux * a - uy * h, y: sh.y + uy * a + ux * h }, e2 = { x: sh.x + ux * a + uy * h, y: sh.y + uy * a - ux * h };
const el = e1.x < e2.x ? e1 : e2;
const knee = { x: hip.x + (ankle.x - hip.x) * 0.52, y: hip.y + (ankle.y - hip.y) * 0.52 - 7 };
setShin(knee, ankle); setThigh(hip, knee);
setShorts(hip, { x: hip.x + (knee.x - hip.x) * 0.45, y: hip.y + (knee.y - hip.y) * 0.45 });
shoe.setAttribute("x", ankle.x - 26); shoe.setAttribute("y", ankle.y + 4);
setTorso(hip, sh);
const hd = { x: sh.x + 18, y: sh.y - 74 };
head.setAttribute("cx", hd.x); head.setAttribute("cy", hd.y); neck(sh, { x: hd.x - 4, y: hd.y + 18 });
setUpper(sh, el); setFore(el, HAND);
hand.setAttribute("cx", HAND.x - 4); hand.setAttribute("cy", HAND.y + 4);
// glow follows the work: it swells while pressing up and stays warm at the bottom
const g = 0.22 + 0.78 * push + 0.25 * p * (1 - push);
const tA = { x: sh.x + (el.x - sh.x) * 0.3, y: sh.y + (el.y - sh.y) * 0.3 }, tB = { x: sh.x + (el.x - sh.x) * 0.88, y: sh.y + (el.y - sh.y) * 0.88 };
for (const e of [tri, triGlow]) { e.setAttribute("x1", tA.x); e.setAttribute("y1", tA.y); e.setAttribute("x2", tB.x); e.setAttribute("y2", tB.y); }
tri.setAttribute("stroke-opacity", Math.min(1, 0.25 + g * 0.8)); triGlow.setAttribute("stroke-opacity", Math.min(1, g));
delt.setAttribute("cx", sh.x + 4); delt.setAttribute("cy", sh.y + 8); delt.setAttribute("fill-opacity", 0.15 + 0.7 * g);
deltCore.setAttribute("cx", sh.x + 4); deltCore.setAttribute("cy", sh.y + 8); deltCore.setAttribute("fill-opacity", 0.1 + 0.6 * g);
chest.setAttribute("cx", sh.x + 30); chest.setAttribute("cy", sh.y + 62); chest.setAttribute("fill-opacity", 0.1 + 0.55 * g);
for (const s of smokes) { s.el.setAttribute("cx", s.x + 60 * Math.sin(t * 0.35 + s.ph)); s.el.setAttribute("cy", s.y + 24 * Math.sin(t * 0.27 + s.ph * 1.3)); }
}
draw(0);
const tl = gsap.timeline({ paused: true });
const clock = { t: 0 };
tl.to(clock, { t: 14.2, duration: 14.2, ease: "none", onUpdate: () => draw(clock.t) }, 0);
window.__timelines["stage"] = tl;
</script>
</template>
@@ -0,0 +1,42 @@
<template>
<style>
#root { position: absolute; inset: 0; font-family: "Inter", sans-serif; color: #eaf2ff; }
.tip { position: absolute; left: 192px; bottom: 108px; width: 1100px; }
.tip .num { display: inline-block; font-weight: 700; font-size: 28px; letter-spacing: 0.16em; color: var(--accent, #ff7a45); margin-right: 20px; }
.tip h2 { font-family: "Oswald", sans-serif; font-weight: 700; font-size: 76px; line-height: 1.02; letter-spacing: 0.01em; }
.tip p { margin-top: 10px; font-weight: 500; font-size: 32px; line-height: 1.25; }
</style>
<div id="root" data-composition-id="tips" data-width="1920" data-height="1080">
<div id="tip1" class="clip tip" data-start="0.3" data-duration="2.0" data-track-index="0">
<h2>SET UP</h2><p>Hands on the bench edge</p>
</div>
<div id="tip2" class="clip tip" data-start="2.4" data-duration="1.9" data-track-index="0">
<h2>LOWER SLOWLY</h2><p>Elbows back to 90°</p>
</div>
<div id="tip3" class="clip tip" data-start="4.4" data-duration="1.7" data-track-index="0">
<h2>PUSH UP</h2><p>Press through your palms</p>
</div>
<div id="tip4" class="clip tip" data-start="6.2" data-duration="2.5" data-track-index="0">
<h2>BACK CLOSE TO THE BENCH</h2><p>Hips stay near the edge</p>
</div>
<div id="tip5" class="clip tip" data-start="8.9" data-duration="3.0" data-track-index="0">
<h2>SHOULDERS DOWN</h2><p>Keep them away from your ears</p>
</div>
<div id="tip6" class="clip tip" data-start="12.1" data-duration="2.1" data-track-index="0">
<h2>NICE WORK</h2><p>3 reps done. Rest, then go again</p>
</div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
// Each tip blurs in, holds, and leaves faster than it arrived. Animate the children, never the .clip.
[["tip1", 0.3, 2.0], ["tip2", 2.4, 1.9], ["tip3", 4.4, 1.7], ["tip4", 6.2, 2.5], ["tip5", 8.9, 3.0], ["tip6", 12.1, 2.1]].forEach(([id, s, d]) => {
const kids = "#" + id + " > *";
tl.fromTo(kids, { opacity: 0, filter: "blur(8px)" },
{ opacity: 1, filter: "blur(0px)", duration: 0.22, ease: "power4.out", stagger: 0.06 }, s);
if (id !== "tip6") {
tl.to(kids, { opacity: 0, filter: "blur(8px)", duration: 0.14, ease: "power3.in" }, s + d - 0.2);
}
});
window.__timelines["tips"] = tl;
</script>
</template>
@@ -0,0 +1,33 @@
<template>
<style>
#root { position: absolute; inset: 0; font-family: "Inter", sans-serif; color: #eaf2ff; }
#title-block { position: absolute; left: 192px; top: 108px; }
#title-text { font-family: "Oswald", sans-serif; font-weight: 700; font-size: 96px; line-height: 1; letter-spacing: 0.01em; }
#title-text .w { display: inline-block; margin-right: 24px; }
#title-text .hot { color: var(--accent, #ff7a45); }
#title-muscles { margin-top: 18px; font-weight: 600; font-size: 30px; letter-spacing: 0.22em; }
#title-rule { width: 96px; height: 4px; margin-top: 22px; background: var(--accent, #ff7a45); transform-origin: left center; }
</style>
<div
id="root"
data-composition-id="title"
data-width="1920"
data-height="1080"
data-composition-variables='[{"id":"exercise","type":"string","label":"Exercise (first word)","default":"BENCH"},{"id":"exercise2","type":"string","label":"Exercise (second word)","default":"DIPS"},{"id":"muscles","type":"string","label":"Muscles worked","default":"TRICEPS · SHOULDERS · CHEST"},{"id":"accent","type":"color","label":"Accent colour","default":"#ff7a45"}]'
>
<div id="title-block">
<h1 id="title-text"><span class="w" data-var-text="exercise">BENCH</span><span class="w hot" data-var-text="exercise2">DIPS</span></h1>
<div id="title-muscles" data-var-text="muscles">TRICEPS · SHOULDERS · CHEST</div>
<div id="title-rule"></div>
</div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
tl.fromTo("#title-text .w", { opacity: 0, filter: "blur(8px)" },
{ opacity: 1, filter: "blur(0px)", duration: 0.2, ease: "power4.out", stagger: 0.09 }, 0.3);
tl.fromTo("#title-muscles", { opacity: 0, filter: "blur(6px)" },
{ opacity: 1, filter: "blur(0px)", duration: 0.3, ease: "power3.out" }, 0.65);
tl.fromTo("#title-rule", { scaleX: 0 }, { scaleX: 1, duration: 0.5, ease: "expo.out" }, 0.7);
window.__timelines["title"] = tl;
</script>
</template>
@@ -0,0 +1,15 @@
<template>
<style>
#root { position: absolute; inset: 0; pointer-events: none; }
#wash-top { position: absolute; left: 0; top: 0; width: 100%; height: 420px; background: linear-gradient(to bottom, rgba(5,11,26,0.78), rgba(5,11,26,0)); }
#wash-bottom { position: absolute; left: 0; bottom: 0; width: 100%; height: 440px; background: linear-gradient(to top, rgba(5,11,26,0.82), rgba(5,11,26,0)); }
</style>
<div id="root" data-composition-id="wash" data-width="1920" data-height="1080">
<div id="wash-top" data-layout-ignore></div>
<div id="wash-bottom" data-layout-ignore></div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
window.__timelines["wash"] = tl;
</script>
</template>
@@ -0,0 +1,12 @@
{
"$schema": "https://hyperframes.heygen.com/schema/hyperframes.json",
"registry": "https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry",
"paths": {
"blocks": "compositions",
"components": "compositions/components",
"assets": "assets"
},
"media": {
"autoProxy": true
}
}
+29
View File
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<script src="assets/gsap.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: #050b1a; }
#root { position: relative; width: 1920px; height: 1080px; background: #050b1a; }
.scene { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; }
</style>
</head>
<body>
<div id="root" data-composition-id="main" data-start="0" data-duration="14.2" data-width="1920" data-height="1080">
<div id="stage" class="scene" style="z-index: 1" data-composition-id="stage" data-composition-src="compositions/stage.html" data-start="0" data-duration="14.2" data-track-index="0" data-track-kind="graphics" data-width="1920" data-height="1080"></div>
<div id="wash" class="scene" style="z-index: 2" data-composition-id="wash" data-composition-src="compositions/wash.html" data-start="0" data-duration="14.2" data-track-index="1" data-track-kind="graphics" data-width="1920" data-height="1080"></div>
<div id="title" class="scene" style="z-index: 5" data-composition-id="title" data-composition-src="compositions/title.html" data-start="0" data-duration="14.2" data-track-index="2" data-track-kind="graphics" data-width="1920" data-height="1080"></div>
<div id="counter" class="scene" style="z-index: 5" data-composition-id="counter" data-composition-src="compositions/counter.html" data-start="0" data-duration="14.2" data-track-index="3" data-track-kind="graphics" data-width="1920" data-height="1080"></div>
<div id="tips" class="scene" style="z-index: 5" data-composition-id="tips" data-composition-src="compositions/tips.html" data-start="0" data-duration="14.2" data-track-index="4" data-track-kind="graphics" data-width="1920" data-height="1080"></div>
<audio id="music" src="assets/music.mp3" data-start="0" data-duration="14.2" data-track-index="5" data-volume="1" data-fade-in="0.15" data-fade-out="0.5"></audio>
</div>
<script>
const tl = gsap.timeline({ paused: true });
window.__timelines["main"] = tl;
tl.seek(0);
</script>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
{
"id": "app",
"name": "app",
"createdAt": "2026-10-21T23:35:06.896Z"
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "app",
"private": true,
"type": "module",
"scripts": {
"dev": "npx --yes hyperframes@0.8.185 preview",
"check": "npx --yes hyperframes@0.8.185 check",
"render": "npx --yes hyperframes@0.8.185 render",
"publish": "npx --yes hyperframes@0.8.185 publish"
}
}
+104
View File
@@ -0,0 +1,104 @@
# HyperFrames Composition Project
## Skills — read these first
**Invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that generic web docs don't cover, so compositions written without them break.
**Doing anything with HyperFrames?** Start at `/hyperframes`. Make any video by asking for it; the `/hyperframes` skill carries the contract and the references (each kind of film, story, motion, look, sound, review, captions, overlays). Read it first, especially when there's no project context to orient you.
- `/hyperframes` — read first for any request to make, edit, animate or render a video, motion graphic, captions or overlays. Holds the render contract and the work loop.
- `/hyperframes-cli` — the CLI by job: scaffold, check, look at frames, edit the timeline, preview, render, publish, project history.
- `/hyperframes-registry` — ready-made blocks and components installed as editable source. Search it before hand-building any named visual.
- `/media-use` — get or make any media (music, sound effects, images, voiceover, avatar and generated video, transcripts, cutouts, colour grades) and mix placed audio.
- `/figma` — bring a Figma design, frame, logo or brand into a video.
- `/slideshow` — a presentation or pitch deck. Output is a navigable deck, not a rendered MP4.
- `/remotion-to-hyperframes` — bring existing video code or footage into HyperFrames (Remotion first; also Motion Canvas, Lottie, Three.js, canvas, or renders from other tools) and verify it frame by frame against the original.
**Changing how real footage or images look or reveal?** Load `/media-use` and read its `references/media-treatments.md` before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” It governs how footage is treated, never whether media may be used. Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays.
> **Tailwind v4 projects** (`hyperframes init --tailwind`): see `hyperframes-cli/references/init-and-scaffold.md` (Tailwind).
> **Using a HyperFrames plugin?** Load skills from that installed bundle and follow
> its `hyperframes/references/plugin-installation.md` execution rules. Update via
> the plugin manager, not the standalone commands below.
>
> **Standalone skill missing or stale?** Run `npx hyperframes skills update <name>` to install/refresh
> the specific skill you need, or bare `npx hyperframes skills update` to refresh the core set plus
> everything already installed — neither pulls the full set. Restart the agent session so
> newly installed skills load.
## Commands
```bash
npm run dev # human-operated foreground preview (blocks until stopped)
npx hyperframes preview --background # agent-safe persistent Studio preview
npx hyperframes preview --status # verify the persistent preview is listening
npx hyperframes preview --stop # stop it when review is finished
npm run check # lint + runtime + layout + motion + contrast (one command)
npm run render # render to MP4
npm run publish # publish and get a shareable link
npx hyperframes lint --verbose # include info-level findings
npx hyperframes lint --json # machine-readable output for CI
npx hyperframes docs <topic> # reference docs in terminal
```
> **Agents must use `npx hyperframes preview --background` for Studio handoff.** Do not rely
> on a shell/tool `run_in_background` wrapper around `npm run dev`: that foreground process
> remains owned by the invoking session and can disappear while the browser stays open,
> leaving refreshes at `ERR_CONNECTION_TIMED_OUT`. Verify with `preview --status`, keep it
> alive through review, and stop it explicitly with `preview --stop` afterward.
> **Back from the desktop app.** Once this project was opened in the HyperFrames desktop app, run `npx hyperframes catch-up` before your next change here: it lists what the person asked Framey in the app and which files changed since.
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
## Documentation
**For quick reference**, use the local CLI docs command (no network required):
```bash
npx hyperframes docs <topic>
```
Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
**For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
```
https://hyperframes.heygen.com/llms.txt
```
## Project Structure
- `index.html` — main composition (root timeline)
- `compositions/` — sub-compositions referenced via `data-composition-src`
- `meta.json` — project metadata (id, name)
- `transcript.json` — whisper word-level transcript (if generated)
## Linting — ALWAYS RUN AFTER CHANGES
After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
```bash
npm run check
```
Fix all errors before presenting the result. Warnings should be reviewed before rendering.
Then look at what you changed: `npx hyperframes snapshot --at <seconds>` (or `--range START,END`, `--composition <id>`) at the moments you edited, and read the frames before you reply. A passing check does not show how it looks.
## Key Rules
1. Every timed element needs `data-start` and a duration. `data-start` is what marks it as timed; `data-track-index` is an optional Studio display lane the render never reads
2. Give timed visual elements `class="clip"`. The framework keys visibility off `data-start`, not the class, but the shared `.clip` CSS is what gives a scene its full-frame box, and `lint` warns without it
3. Register one paused root timeline per composition on `window.__timelines`:
```js
window.__timelines = window.__timelines || {};
window.__timelines["composition-id"] = gsap.timeline({ paused: true });
```
Scene timelines manually added to this root must not be paused. A paused
child does not advance when the root is seeked. The runtime activates
registered composition siblings, not arbitrary nested scene timelines.
4. A video with sound keeps it on the `<video>` (`data-has-audio="true"`, no `muted`). Use a separate `<audio>` for music, voiceover, replacement audio, J/L cuts, or audio detached in Studio. Silent footage and b-roll: `muted`.
5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
+104
View File
@@ -0,0 +1,104 @@
# HyperFrames Composition Project
## Skills — read these first
**Invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that generic web docs don't cover, so compositions written without them break.
**Doing anything with HyperFrames?** Start at `/hyperframes`. Make any video by asking for it; the `/hyperframes` skill carries the contract and the references (each kind of film, story, motion, look, sound, review, captions, overlays). Read it first, especially when there's no project context to orient you.
- `/hyperframes` — read first for any request to make, edit, animate or render a video, motion graphic, captions or overlays. Holds the render contract and the work loop.
- `/hyperframes-cli` — the CLI by job: scaffold, check, look at frames, edit the timeline, preview, render, publish, project history.
- `/hyperframes-registry` — ready-made blocks and components installed as editable source. Search it before hand-building any named visual.
- `/media-use` — get or make any media (music, sound effects, images, voiceover, avatar and generated video, transcripts, cutouts, colour grades) and mix placed audio.
- `/figma` — bring a Figma design, frame, logo or brand into a video.
- `/slideshow` — a presentation or pitch deck. Output is a navigable deck, not a rendered MP4.
- `/remotion-to-hyperframes` — bring existing video code or footage into HyperFrames (Remotion first; also Motion Canvas, Lottie, Three.js, canvas, or renders from other tools) and verify it frame by frame against the original.
**Changing how real footage or images look or reveal?** Load `/media-use` and read its `references/media-treatments.md` before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” It governs how footage is treated, never whether media may be used. Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays.
> **Tailwind v4 projects** (`hyperframes init --tailwind`): see `hyperframes-cli/references/init-and-scaffold.md` (Tailwind).
> **Using a HyperFrames plugin?** Load skills from that installed bundle and follow
> its `hyperframes/references/plugin-installation.md` execution rules. Update via
> the plugin manager, not the standalone commands below.
>
> **Standalone skill missing or stale?** Run `npx hyperframes skills update <name>` to install/refresh
> the specific skill you need, or bare `npx hyperframes skills update` to refresh the core set plus
> everything already installed — neither pulls the full set. Restart the agent session so
> newly installed skills load.
## Commands
```bash
npm run dev # human-operated foreground preview (blocks until stopped)
npx hyperframes preview --background # agent-safe persistent Studio preview
npx hyperframes preview --status # verify the persistent preview is listening
npx hyperframes preview --stop # stop it when review is finished
npm run check # lint + runtime + layout + motion + contrast (one command)
npm run render # render to MP4
npm run publish # publish and get a shareable link
npx hyperframes lint --verbose # include info-level findings
npx hyperframes lint --json # machine-readable output for CI
npx hyperframes docs <topic> # reference docs in terminal
```
> **Agents must use `npx hyperframes preview --background` for Studio handoff.** Do not rely
> on a shell/tool `run_in_background` wrapper around `npm run dev`: that foreground process
> remains owned by the invoking session and can disappear while the browser stays open,
> leaving refreshes at `ERR_CONNECTION_TIMED_OUT`. Verify with `preview --status`, keep it
> alive through review, and stop it explicitly with `preview --stop` afterward.
> **Back from the desktop app.** Once this project was opened in the HyperFrames desktop app, run `npx hyperframes catch-up` before your next change here: it lists what the person asked Framey in the app and which files changed since.
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
## Documentation
**For quick reference**, use the local CLI docs command (no network required):
```bash
npx hyperframes docs <topic>
```
Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
**For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
```
https://hyperframes.heygen.com/llms.txt
```
## Project Structure
- `index.html` — main composition (root timeline)
- `compositions/` — sub-compositions referenced via `data-composition-src`
- `meta.json` — project metadata (id, name)
- `transcript.json` — whisper word-level transcript (if generated)
## Linting — ALWAYS RUN AFTER CHANGES
After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
```bash
npm run check
```
Fix all errors before presenting the result. Warnings should be reviewed before rendering.
Then look at what you changed: `npx hyperframes snapshot --at <seconds>` (or `--range START,END`, `--composition <id>`) at the moments you edited, and read the frames before you reply. A passing check does not show how it looks.
## Key Rules
1. Every timed element needs `data-start` and a duration. `data-start` is what marks it as timed; `data-track-index` is an optional Studio display lane the render never reads
2. Give timed visual elements `class="clip"`. The framework keys visibility off `data-start`, not the class, but the shared `.clip` CSS is what gives a scene its full-frame box, and `lint` warns without it
3. Register one paused root timeline per composition on `window.__timelines`:
```js
window.__timelines = window.__timelines || {};
window.__timelines["composition-id"] = gsap.timeline({ paused: true });
```
Scene timelines manually added to this root must not be paused. A paused
child does not advance when the root is seeked. The runtime activates
registered composition siblings, not arbitrary nested scene timelines.
4. A video with sound keeps it on the `<video>` (`data-has-audio="true"`, no `muted`). Use a separate `<audio>` for music, voiceover, replacement audio, J/L cuts, or audio detached in Studio. Silent footage and b-roll: `muted`.
5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
+11
View File
@@ -0,0 +1,11 @@
/*!
* GSAP 3.14.2
* https://gsap.com
*
* @license Copyright 2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license.
* @author: Jack Doyle, jack@greensock.com
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function r(t){return"string"==typeof t}function s(t){return"function"==typeof t}function t(t){return"number"==typeof t}function u(t){return void 0===t}function v(t){return"object"==typeof t}function w(t){return!1!==t}function x(){return"undefined"!=typeof window}function y(t){return s(t)||r(t)}function R(t){return(i=bt(t,ht))&&Fe}function S(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function T(t,e){return!e&&console.warn(t)}function U(t,e){return t&&(ht[t]=e)&&i&&(i[t]=e)||ht}function V(){return 0}function ga(t){var e,r,i=t[0];if(v(i)||s(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=yt.length;r--&&!yt[r].targetTest(i););e=yt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Xt(t[r],e)))||t.splice(r,1);return t}function ha(t){return t._gsap||ga(Pt(t))[0]._gsap}function ia(t,e,r){return(r=t[e])&&s(r)?t[e]():u(r)&&t.getAttribute&&t.getAttribute(e)||r}function ja(t,e){return(t=t.split(",")).forEach(e)||t}function ka(t){return Math.round(1e5*t)/1e5||0}function la(t){return Math.round(1e7*t)/1e7||0}function ma(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function na(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++i<r;);return i<r}function oa(){var t,e,r=pt.length,i=pt.slice(0);for(_t={},t=pt.length=0;t<r;t++)(e=i[t])&&e._lazy&&(e.render(e._lazy[0],e._lazy[1],!0)._lazy=0)}function pa(t){return!!(t._initted||t._startAt||t.add)}function qa(t,e,r,i){pt.length&&!I&&oa(),t.render(e,r,i||!!(I&&e<0&&pa(t))),pt.length&&!I&&oa()}function ra(t){var e=parseFloat(t);return(e||0===e)&&(t+"").match(ot).length<2?e:r(t)?t.trim():t}function sa(t){return t}function ta(t,e){for(var r in e)r in t||(t[r]=e[r]);return t}function wa(t,e){for(var r in e)"__proto__"!==r&&"constructor"!==r&&"prototype"!==r&&(t[r]=v(e[r])?wa(t[r]||(t[r]={}),e[r]):e[r]);return t}function xa(t,e){var r,i={};for(r in t)r in e||(i[r]=t[r]);return i}function ya(t){var e=t.parent||L,r=t.keyframes?function _setKeyframeDefaults(i){return function(t,e){for(var r in e)r in t||"duration"===r&&i||"ease"===r||(t[r]=e[r])}}($(t.keyframes)):ta;if(w(t.inherit))for(;e;)r(t,e.vars.defaults),e=e.parent||e._dp;return t}function Aa(t,e,r,i,n){void 0===r&&(r="_first"),void 0===i&&(i="_last");var a,s=t[i];if(n)for(a=e[n];s&&s[n]>a;)s=s._prev;return s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[i]=e,e._prev=s,e.parent=e._dp=t,e}function Ba(t,e,r,i){void 0===r&&(r="_first"),void 0===i&&(i="_last");var n=e._prev,a=e._next;n?n._next=a:t[r]===e&&(t[r]=a),a?a._prev=n:t[i]===e&&(t[i]=n),e._next=e._prev=e.parent=null}function Ca(t,e){t.parent&&(!e||t.parent.autoRemoveChildren)&&t.parent.remove&&t.parent.remove(t),t._act=0}function Da(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var r=t;r;)r._dirty=1,r=r.parent;return t}function Fa(t,e,r,i){return t._startAt&&(I?t._startAt.revert(ft):t.vars.immediateRender&&!t.vars.autoRevert||t._startAt.render(e,!0,i))}function Ha(t){return t._repeat?wt(t._tTime,t=t.duration()+t._rDelay)*t:0}function Ja(t,e){return(t-e._start)*e._ts+(0<=e._ts?0:e._dirty?e.totalDuration():e._tDur)}function Ka(t){return t._end=la(t._start+(t._tDur/Math.abs(t._ts||t._rts||q)||0))}function La(t,e){var r=t._dp;return r&&r.smoothChildTiming&&t._ts&&(t._start=la(r._time-(0<t._ts?e/t._ts:((t._dirty?t.totalDuration():t._tDur)-e)/-t._ts)),Ka(t),r._dirty||Da(r,t)),t}function Ma(t,e){var r;if((e._time||!e._dur&&e._initted||e._start<t._time&&(e._dur||!e.add))&&(r=Ja(t.rawTime(),e),(!e._dur||Mt(0,e.totalDuration(),r)-e._tTime>q)&&e.render(r,!0)),Da(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur<t.duration())for(r=t;r._dp;)0<=r.rawTime()&&r.totalTime(r._tTime),r=r._dp;t._zTime=-q}}function Na(e,r,i,n){return r.parent&&Ca(r),r._start=la((t(i)?i:i||e!==L?Ot(e,i,r):e._time)+r._delay),r._end=la(r._start+(r.totalDuration()/Math.abs(r.timeScale())||0)),Aa(e,r,"_first","_last",e._sort?"_start":0),xt(r)||(e._recent=r),n||Ma(e,r),e._ts<0&&La(e,e._tTime),e}function Oa(t,e){return(ht.ScrollTrigger||S("scrollTrigger",e))&&ht.ScrollTrigger.create(e,t)}function Pa(t,e,r,i,n){return Qt(t,e,n),t._initted?!r&&t._pt&&!I&&(t._dur&&!1!==t.vars.lazy||!t._dur&&t.vars.lazy)&&f!==It.frame?(pt.push(t),t._lazy=[n,i],1):void 0:1}function Ua(t,e,r,i){var n=t._repeat,a=la(e)||0,s=t._tTime/t._tDur;return s&&!i&&(t._time*=a/t._dur),t._dur=a,t._tDur=n?n<0?1e10:la(a*(n+1)+t._rDelay*n):a,0<s&&!i&&La(t,t._tTime=t._tDur*s),t.parent&&Ka(t),r||Da(t.parent,t),t}function Va(t){return t instanceof Zt?Da(t)Line truncated
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,36 @@
<template>
<style>
#root { position: absolute; inset: 0; font-family: "Inter", sans-serif; color: #eaf2ff; }
.tip { position: absolute; left: 192px; bottom: 108px; width: 1100px; }
.tip .num { display: inline-block; font-weight: 700; font-size: 28px; letter-spacing: 0.16em; color: var(--accent, #ff7a45); margin-right: 20px; }
.tip h2 { font-family: "Oswald", sans-serif; font-weight: 700; font-size: 76px; line-height: 1.02; letter-spacing: 0.01em; }
.tip p { margin-top: 10px; font-weight: 500; font-size: 32px; line-height: 1.25; }
</style>
<div id="root" data-composition-id="tips" data-width="1920" data-height="1080">
<div id="tip1" class="clip tip" data-start="0.4" data-duration="2.0" data-track-index="0">
<h2>LOWER SLOWLY</h2><p><span class="num">01</span>Elbows close to your body</p>
</div>
<div id="tip2" class="clip tip" data-start="2.6" data-duration="2.0" data-track-index="0">
<h2>PUSH UP</h2><p><span class="num">02</span>Squeeze your chest and triceps</p>
</div>
<div id="tip3" class="clip tip" data-start="4.8" data-duration="2.4" data-track-index="0">
<h2>BODY STRAIGHT</h2><p><span class="num">03</span>Core tight, hips level</p>
</div>
<div id="tip4" class="clip tip" data-start="7.4" data-duration="2.7" data-track-index="0">
<h2>REPEAT WITH CONTROL</h2><p><span class="num">04</span>Breathe out as you push up</p>
</div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
// Each tip blurs in, holds, and leaves faster than it arrived. Animate the children, never the .clip.
[["tip1", 0.4, 2.0], ["tip2", 2.6, 2.0], ["tip3", 4.8, 2.4], ["tip4", 7.4, 2.7]].forEach(([id, s, d]) => {
const kids = "#" + id + " > *";
tl.fromTo(kids, { opacity: 0, filter: "blur(8px)" },
{ opacity: 1, filter: "blur(0px)", duration: 0.22, ease: "power4.out", stagger: 0.06 }, s);
if (id !== "tip4") {
tl.to(kids, { opacity: 0, filter: "blur(8px)", duration: 0.14, ease: "power3.in" }, s + d - 0.2);
}
});
window.__timelines["tips"] = tl;
</script>
</template>
@@ -0,0 +1,33 @@
<template>
<style>
#root { position: absolute; inset: 0; font-family: "Inter", sans-serif; color: #eaf2ff; }
#title-block { position: absolute; left: 192px; top: 108px; }
#title-text { font-family: "Oswald", sans-serif; font-weight: 700; font-size: 96px; line-height: 1; letter-spacing: 0.01em; }
#title-text .w { display: inline-block; margin-right: 24px; }
#title-text .hot { color: var(--accent, #ff7a45); }
#title-muscles { margin-top: 18px; font-weight: 600; font-size: 30px; letter-spacing: 0.22em; }
#title-rule { width: 96px; height: 4px; margin-top: 22px; background: var(--accent, #ff7a45); transform-origin: left center; }
</style>
<div
id="root"
data-composition-id="title"
data-width="1920"
data-height="1080"
data-composition-variables='[{"id":"exercise","type":"string","label":"Exercise (first word)","default":"DIAMOND"},{"id":"exercise2","type":"string","label":"Exercise (second word)","default":"PUSH-UP"},{"id":"muscles","type":"string","label":"Muscles worked","default":"CHEST · TRICEPS · SHOULDERS"},{"id":"accent","type":"color","label":"Accent colour","default":"#ff7a45"}]'
>
<div id="title-block">
<h1 id="title-text"><span class="w" data-var-text="exercise">DIAMOND</span><span class="w hot" data-var-text="exercise2">PUSH-UP</span></h1>
<div id="title-muscles" data-var-text="muscles">CHEST · TRICEPS · SHOULDERS</div>
<div id="title-rule"></div>
</div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
tl.fromTo("#title-text .w", { opacity: 0, filter: "blur(8px)" },
{ opacity: 1, filter: "blur(0px)", duration: 0.2, ease: "power4.out", stagger: 0.09 }, 0.3);
tl.fromTo("#title-muscles", { opacity: 0, filter: "blur(6px)" },
{ opacity: 1, filter: "blur(0px)", duration: 0.3, ease: "power3.out" }, 0.65);
tl.fromTo("#title-rule", { scaleX: 0 }, { scaleX: 1, duration: 0.5, ease: "expo.out" }, 0.7);
window.__timelines["title"] = tl;
</script>
</template>
@@ -0,0 +1,15 @@
<template>
<style>
#root { position: absolute; inset: 0; pointer-events: none; }
#wash-top { position: absolute; left: 0; top: 0; width: 100%; height: 420px; background: linear-gradient(to bottom, rgba(5,11,26,0.78), rgba(5,11,26,0)); }
#wash-bottom { position: absolute; left: 0; bottom: 0; width: 100%; height: 440px; background: linear-gradient(to top, rgba(5,11,26,0.82), rgba(5,11,26,0)); }
</style>
<div id="root" data-composition-id="wash" data-width="1920" data-height="1080">
<div id="wash-top" data-layout-ignore></div>
<div id="wash-bottom" data-layout-ignore></div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
window.__timelines["wash"] = tl;
</script>
</template>
@@ -0,0 +1,12 @@
{
"$schema": "https://hyperframes.heygen.com/schema/hyperframes.json",
"registry": "https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry",
"paths": {
"blocks": "compositions",
"components": "compositions/components",
"assets": "assets"
},
"media": {
"autoProxy": true
}
}
+30
View File
@@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<script src="assets/gsap.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: #050b1a; }
#root { position: relative; width: 1920px; height: 1080px; background: #050b1a; }
#pushup-video { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; object-fit: contain; z-index: 1; }
.scene { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; }
</style>
</head>
<body>
<div id="root" data-composition-id="main" data-start="0" data-duration="10.1" data-width="1920" data-height="1080">
<video id="pushup-video" class="clip" src="assets/pushup.mp4" data-start="0" data-duration="10.1" data-track-index="0" data-has-audio="true" data-volume="0.35" playsinline></video>
<audio id="music" src="assets/music.mp3" data-start="0" data-duration="10.1" data-track-index="4" data-volume="1" data-fade-in="0.15" data-fade-out="0.5"></audio>
<div id="wash" class="scene" style="z-index: 2" data-composition-id="wash" data-composition-src="compositions/wash.html" data-start="0" data-duration="10.1" data-track-index="1" data-track-kind="graphics" data-width="1920" data-height="1080"></div>
<div id="title" class="scene" style="z-index: 5" data-composition-id="title" data-composition-src="compositions/title.html" data-start="0" data-duration="10.1" data-track-index="2" data-track-kind="graphics" data-width="1920" data-height="1080"></div>
<div id="tips" class="scene" style="z-index: 5" data-composition-id="tips" data-composition-src="compositions/tips.html" data-start="0" data-duration="10.1" data-track-index="3" data-track-kind="graphics" data-width="1920" data-height="1080"></div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
window.__timelines["main"] = tl;
tl.seek(0);
</script>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
{
"id": "app",
"name": "app",
"createdAt": "2026-10-21T23:22:10.590Z"
}
@@ -0,0 +1,11 @@
{
"name": "app",
"private": true,
"type": "module",
"scripts": {
"dev": "npx --yes hyperframes@0.8.185 preview",
"check": "npx --yes hyperframes@0.8.185 check",
"render": "npx --yes hyperframes@0.8.185 render",
"publish": "npx --yes hyperframes@0.8.185 publish"
}
}