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