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:
33 files changed
+1051
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user