Add bodyweight squat video and editable project

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:46:55 +00:00
1 parent 8b1eeff1ed
commit b3600ec8f3
16 files changed
+506 -1

No files matched your search

+2 -1
View File
@@ -7,9 +7,10 @@ Made with [HyperFrames](https://github.com/heygen-com/hyperframes) (HTML rendere
| `diamond-pushup-music.mp4` | Diamond push-up, title and coaching tips, with music (10.1 s) | | `diamond-pushup-music.mp4` | Diamond push-up, title and coaching tips, with music (10.1 s) |
| `diamond-pushup.mp4` | Same video without music | | `diamond-pushup.mp4` | Same video without music |
| `bench-dips.mp4` | Bench dips, animated athlete, rep counter, tips and music (14.2 s) | | `bench-dips.mp4` | Bench dips, animated athlete, rep counter, tips and music (14.2 s) |
| `bodyweight-squat.mp4` | Bodyweight squat, animated athlete, 4-rep counter, tips and music (16.0 s) |
## Editable sources ## 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). - `projects/diamond-pushup/`, `projects/bench-dips/` and `projects/bodyweight-squat/`: 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 - `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`). (`python make_music.py out.wav SECONDS BPM rise|anthem`, needs `numpy` and `scipy`).
`music/norm.sh` sets its loudness and writes the MP3. `music/norm.sh` sets its loudness and writes the MP3.
Binary file not shown.
Binary file not shown.
+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,23 @@
<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="2.8" data-track-index="0"><span class="lbl">REP</span><span class="n">1</span> <span class="of">/ 4</span></div>
<div id="rep2" class="clip rep" data-start="5.2" data-duration="2.8" data-track-index="0"><span class="lbl">REP</span><span class="n">2</span> <span class="of">/ 4</span></div>
<div id="rep3" class="clip rep" data-start="8.0" data-duration="2.8" data-track-index="0"><span class="lbl">REP</span><span class="n">3</span> <span class="of">/ 4</span></div>
<div id="rep4" class="clip rep" data-start="10.8" data-duration="2.8" data-track-index="0"><span class="lbl">REP</span><span class="n">4</span> <span class="of">/ 4</span></div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
[["rep1", 2.4], ["rep2", 5.2], ["rep3", 8.0], ["rep4", 10.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 + 2.8 - 0.17);
});
window.__timelines["counter"] = tl;
</script>
</template>
@@ -0,0 +1,112 @@
<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(0.85) translate(-960 -780)">
<ellipse cx="1250" cy="800" rx="520" ry="42" fill="url(#st-pool)"/>
<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; };
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 }));
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 glowDot = (r) => { const c = mk("circle", { r, fill: "#ff7a45" }, body); c.setAttribute("filter", "url(#st-glow)"); return c; };
const setShin = limb(58), setThigh = limb(84), setShorts = limb(108, "#10172f"), setTorso = limb(112, "#27499f"),
setUpper = limb(46), setFore = limb(38), neck = limb(30);
const shoe = mk("rect", { width: 104, height: 26, rx: 12, 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 hand = mk("circle", { r: 20, fill: "#7fa9f0", stroke: "#0b1d44", "stroke-width": 2 }, body);
const quad = glowLine(34), quadGlow = glowLine(48); quadGlow.setAttribute("filter", "url(#st-glow)");
const glute = glowDot(46), core = glowDot(34);
// geometry (px at scale 1): feet planted, joints worked out from the heels up
const AX = 1240, ANK = { x: AX, y: 754 };
const Ls = 185, Lt = 193, T = 218, Lu = 126, Lf = 118;
const A1 = 30 * Math.PI / 180, A2 = 90 * Math.PI / 180, A3 = 40 * Math.PI / 180; // shin lean, thigh drop, torso lean at the bottom
// 4 reps of 2.8 s (1.5 s down, 1.3 s up) after a 2.4 s set-up
const REP0 = 2.4, REP = 2.8, DOWN = 1.5, UP = 1.3, REPS = 4;
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 < REPS; 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 a1 = A1 * p, a2 = A2 * p, a3 = A3 * Math.pow(p, 0.85);
const knee = { x: ANK.x + Ls * Math.sin(a1), y: ANK.y - Ls * Math.cos(a1) };
const hip = { x: knee.x - Lt * Math.sin(a2), y: knee.y - Lt * Math.cos(a2) };
const sh = { x: hip.x + T * Math.sin(a3), y: hip.y - T * Math.cos(a3) };
// arms reach straight forward for balance; elbow bends slightly downward
const tgt = { x: sh.x + 0.97 * (Lu + Lf), y: sh.y + 10 };
const dx = tgt.x - sh.x, dy = tgt.y - sh.y, d = Math.hypot(dx, dy);
const a = (Lu * Lu - Lf * Lf + d * d) / (2 * d), h = Math.sqrt(Math.max(0, Lu * Lu - a * a)), 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.y > e2.y ? e1 : e2;
setShin(ANK, knee); setThigh(knee, hip);
setShorts(hip, { x: hip.x + (knee.x - hip.x) * 0.5, y: hip.y + (knee.y - hip.y) * 0.5 });
shoe.setAttribute("x", ANK.x - 36); shoe.setAttribute("y", ANK.y + 2);
setTorso(hip, sh);
const hd = { x: sh.x + Math.sin(a3) * 74 + 16, y: sh.y - Math.cos(a3) * 74 };
head.setAttribute("cx", hd.x); head.setAttribute("cy", hd.y); neck(sh, { x: hd.x - 6, y: hd.y + 20 });
setUpper(sh, el); setFore(el, tgt);
hand.setAttribute("cx", tgt.x + 6); hand.setAttribute("cy", tgt.y);
// glow follows the work: strongest while driving up, warm at the bottom
const g = 0.22 + 0.78 * push + 0.3 * p * (1 - push);
const qa = { x: knee.x + (hip.x - knee.x) * 0.15, y: knee.y + (hip.y - knee.y) * 0.15 }, qb = { x: knee.x + (hip.x - knee.x) * 0.8, y: knee.y + (hip.y - knee.y) * 0.8 };
// sit the glow on the front of the thigh (rotate the hip-to-knee direction by -90 degrees)
const tx = knee.x - hip.x, ty = knee.y - hip.y, tn = Math.hypot(tx, ty), fx = (ty / tn) * 13, fy = (-tx / tn) * 13;
for (const e of [quad, quadGlow]) { e.setAttribute("x1", qa.x + fx); e.setAttribute("y1", qa.y + fy); e.setAttribute("x2", qb.x + fx); e.setAttribute("y2", qb.y + fy); }
quad.setAttribute("stroke-opacity", Math.min(1, 0.25 + g * 0.8)); quadGlow.setAttribute("stroke-opacity", Math.min(1, g));
glute.setAttribute("cx", hip.x - 34); glute.setAttribute("cy", hip.y + 6); glute.setAttribute("fill-opacity", 0.12 + 0.7 * g);
core.setAttribute("cx", (hip.x + sh.x) / 2 + 26); core.setAttribute("cy", (hip.y + sh.y) / 2); core.setAttribute("fill-opacity", 0.08 + 0.45 * 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: 16.0, duration: 16.0, ease: "none", onUpdate: () => draw(clock.t) }, 0);
window.__timelines["stage"] = tl;
</script>
</template>
@@ -0,0 +1,45 @@
<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>Feet shoulder-width apart</p>
</div>
<div id="tip2" class="clip tip" data-start="2.4" data-duration="2.0" data-track-index="0">
<h2>SIT BACK AND DOWN</h2><p>Knees follow your toes</p>
</div>
<div id="tip3" class="clip tip" data-start="4.5" data-duration="1.7" data-track-index="0">
<h2>DRIVE UP</h2><p>Press through heels</p>
</div>
<div id="tip4" class="clip tip" data-start="6.3" data-duration="2.6" data-track-index="0">
<h2>CHEST UP</h2><p>Keep your back straight</p>
</div>
<div id="tip5" class="clip tip" data-start="9.0" data-duration="2.9" data-track-index="0">
<h2>CORE TIGHT</h2><p>Brace your core</p>
</div>
<div id="tip6" class="clip tip" data-start="12.0" data-duration="1.6" data-track-index="0">
<h2>FINISH STRONG</h2><p>Last rep</p>
</div>
<div id="tip7" class="clip tip" data-start="13.7" data-duration="2.3" data-track-index="0">
<h2>GREAT SET</h2><p>4 reps done. 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, 2.0], ["tip3", 4.5, 1.7], ["tip4", 6.3, 2.6], ["tip5", 9.0, 2.9], ["tip6", 12.0, 1.6], ["tip7", 13.7, 2.3]].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 !== "tip7") {
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":"BODYWEIGHT"},{"id":"exercise2","type":"string","label":"Exercise (second word)","default":"SQUAT"},{"id":"muscles","type":"string","label":"Muscles worked","default":"QUADS · GLUTES · CORE"},{"id":"accent","type":"color","label":"Accent colour","default":"#ff7a45"}]'
>
<div id="title-block">
<h1 id="title-text"><span class="w" data-var-text="exercise">BODYWEIGHT</span><span class="w hot" data-var-text="exercise2">SQUAT</span></h1>
<div id="title-muscles" data-var-text="muscles">QUADS · GLUTES · CORE</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
}
}
@@ -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="16.0" 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="16.0" 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="16.0" 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="16.0" 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="16.0" 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="16.0" 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="16.0" 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>
@@ -0,0 +1,5 @@
{
"id": "squat",
"name": "squat",
"createdAt": "2026-10-21T23:35:06.896Z"
}
@@ -0,0 +1,11 @@
{
"name": "squat",
"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"
}
}