mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 16:42:27 +00:00
This reverts commit 3b53bfd2f7.
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
# stop-motion-cadence
|
||||
|
||||
The stepped-time law as a demo primitive (Wave M10 experiment). ONE driver quantizes timeline time FIRST, `ts = floor(t * fps) / fps`, and `ts` feeds ALL motion: four token paper-cut shapes throw-and-land in sequence at the quantized cadence, each squashing on its landing hit and recovering over the next steps, with a 2-frame boil jitter on edges (seeded per boil window, stable within it) and sparkle accents living exactly 2 or 3 steps on each hit, frame-alternating big / small-rotated like a drawn twinkle. Reference: the MotionMarkus stop-motion sheets.
|
||||
|
||||
4s authored, elastic HOLD (the boil keeps breathing through the hold; `boil: off` holds dead still), exit `none` by default.
|
||||
|
||||
## Variables
|
||||
|
||||
| id | type | default | notes |
|
||||
| -------- | ---- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `fps` | enum | `10` | Stepped-time cadence: `8`, `10`, or `12` frames per second. |
|
||||
| `boil` | enum | `on` | 2-frame edge jitter hashed from `floor(step / 2)`. `off` gives a dead-still settle. |
|
||||
| `accent` | enum | `green` | Lead shape color: green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`; the other shapes take the remaining two tokens in rotation. |
|
||||
| `exit` | enum | `none` | `none` holds until the cut; `fade` departs opacity-only; `up` rises out. |
|
||||
|
||||
## Mount
|
||||
|
||||
```html
|
||||
<div
|
||||
class="clip"
|
||||
data-composition-id="stop-motion-cadence"
|
||||
data-composition-src="./stop-motion-cadence.html"
|
||||
data-variable-values='{"fps":"8","accent":"blue"}'
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
></div>
|
||||
```
|
||||
|
||||
Elastic root: no `data-width`/`data-height`; it fills whatever box the host clip gives it. Timeline registers under the literal `stop-motion-cadence` key.
|
||||
|
||||
## Notes
|
||||
|
||||
- The law: quantize before evaluating. Every hit is an integer step index (`landStep = round(t_land * fps)`), squash is a per-step envelope over `step - landStep`, boil seeds hash `floor(step / 2)`, sparkles are visible iff `0 <= step - landStep < life`. Because all state derives from the quantized driver, eventful seeks land identical frames forward, reverse, or shuffled.
|
||||
- Squash uses `transform-origin: 50% 100%` so shapes compress into the floor contact; contact shadows grow and darken as each shape nears the ground.
|
||||
- Deterministic: the jitter hash is a sin-fold over integer indices; no Math.random, no wall clock, no incremental state. Stage size is measured once at mount; per-update writes are transforms and opacity only.
|
||||
- If the mount duration is shorter than the 3s choreography, the throw times compress together, but the fps grid itself never scales; the cadence is absolute.
|
||||
@@ -1,86 +0,0 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
stop-motion-cadence demo. FULL-BLEED light mount: the primitive fills
|
||||
the whole 1920x1080 frame on a light token block. Non-default fps and
|
||||
accent values prove the variable path.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Stop Motion Cadence Demo</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: var(--bg, #eef0f3);
|
||||
}
|
||||
|
||||
#root {
|
||||
--bg: #eef0f3;
|
||||
--fg: #14161b;
|
||||
--muted: #5b6472;
|
||||
--surface: #ffffff;
|
||||
--border: #d3d8e0;
|
||||
--brand: #3f3f46;
|
||||
--accent: #3f3f46;
|
||||
--accent-2: #3f3f46;
|
||||
--radius: 18px;
|
||||
--font-display: "Inter", system-ui, sans-serif;
|
||||
--font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: radial-gradient(ellipse at 50% 42%, #f7f8fa 0%, var(--bg, #eef0f3) 72%);
|
||||
}
|
||||
|
||||
.mount-bleed {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
container-type: size;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="stop-motion-cadence-demo"
|
||||
data-start="0"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
data-duration="4"
|
||||
data-fps="30"
|
||||
>
|
||||
<div
|
||||
id="stop-motion-cadence-mount"
|
||||
class="mount-bleed clip"
|
||||
data-composition-id="stop-motion-cadence"
|
||||
data-composition-src="./stop-motion-cadence.html"
|
||||
data-variable-values='{"fps":"8","accent":"blue"}'
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["stop-motion-cadence-demo"] = gsap.timeline({ paused: true });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,81 +0,0 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "stop-motion-cadence",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Stop Motion Cadence",
|
||||
"description": "The stepped-time law as a demo primitive: one driver quantizes timeline time to floor(t * fps) / fps and feeds ALL motion; four token paper-cut shapes throw-and-land with squash on hits, 2-frame boil jitter seeded per step, and sparkle accents living exactly 2 to 3 frames.",
|
||||
"tags": [
|
||||
"motion-primitive",
|
||||
"time-law",
|
||||
"stop-motion",
|
||||
"stepped",
|
||||
"boil",
|
||||
"deterministic",
|
||||
"experiment"
|
||||
],
|
||||
"jobs": ["demonstrate"],
|
||||
"family": "demonstrate",
|
||||
"profile": "stepped-time",
|
||||
"evidence": "Wave M10 experiment: MotionMarkus stop-motion sheets (2059406490981863865/sheet-01)",
|
||||
"syncPoints": [{ "id": "first-hit", "phase": "in", "offset": 1.0 }],
|
||||
"files": [
|
||||
{
|
||||
"path": "stop-motion-cadence.html",
|
||||
"target": "compositions/components/stop-motion-cadence.html",
|
||||
"type": "hyperframes:snippet"
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{
|
||||
"id": "fps",
|
||||
"type": "enum",
|
||||
"role": "timing",
|
||||
"label": "Cadence",
|
||||
"description": "Stepped-time cadence in frames per second.",
|
||||
"default": "10",
|
||||
"options": [
|
||||
{ "value": "8", "label": "8 fps" },
|
||||
{ "value": "10", "label": "10 fps" },
|
||||
{ "value": "12", "label": "12 fps" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "boil",
|
||||
"type": "enum",
|
||||
"role": "style",
|
||||
"label": "Boil",
|
||||
"description": "2-frame edge jitter, seeded per step. Off holds dead still.",
|
||||
"default": "on",
|
||||
"options": [
|
||||
{ "value": "on", "label": "On" },
|
||||
{ "value": "off", "label": "Off" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "accent",
|
||||
"type": "enum",
|
||||
"role": "style",
|
||||
"label": "Accent",
|
||||
"description": "Lead shape color: green rides --brand, blue rides --accent, violet rides --accent-2.",
|
||||
"default": "green",
|
||||
"options": [
|
||||
{ "value": "green", "label": "Green" },
|
||||
{ "value": "blue", "label": "Blue" },
|
||||
{ "value": "violet", "label": "Violet" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "exit",
|
||||
"type": "enum",
|
||||
"role": "timing",
|
||||
"label": "Exit",
|
||||
"description": "Optional departure. Default none: the settled collage holds until the frame cuts.",
|
||||
"default": "none",
|
||||
"options": [
|
||||
{ "value": "none", "label": "None" },
|
||||
{ "value": "fade", "label": "Fade" },
|
||||
{ "value": "up", "label": "Up" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,467 +0,0 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
stop-motion-cadence: HyperFrames video primitive (time law, Wave M10 experiment)
|
||||
|
||||
The stepped-time law as a demo primitive. ONE driver: every frame the
|
||||
timeline time is quantized FIRST,
|
||||
|
||||
ts = floor(t * fps) / fps
|
||||
|
||||
and ts feeds ALL motion. Four token paper-cut shapes throw-and-land in
|
||||
sequence at the quantized cadence, each squashing on its landing hit
|
||||
(transform-origin at the ground contact) and recovering over the next
|
||||
steps. A 2-frame boil rides every shape's edges: jitter offsets are
|
||||
hashed from the boil frame index floor(step / 2), so they are stable
|
||||
within a 2-frame window and pure functions of time. Sparkle accents
|
||||
live EXACTLY 2 or 3 steps on each landing hit, frame-alternating big /
|
||||
small-rotated like a drawn twinkle (per the MotionMarkus sheets).
|
||||
|
||||
Because every hit is defined by an integer step index derived from the
|
||||
quantized driver, seeks land identical frames in any order: forward,
|
||||
reverse, or shuffled evaluation of the same t produces the same step,
|
||||
the same boil seed, the same sparkle state.
|
||||
|
||||
Variables (declared in data-composition-variables below):
|
||||
- fps ("8" | "10" | "12", default "10"): the step cadence.
|
||||
- boil ("on" | "off", default "on"): the 2-frame edge jitter. Off
|
||||
gives a dead-still settle.
|
||||
- accent ("green" | "blue" | "violet", default "green"): the lead
|
||||
shape's color; green rides --brand, blue rides --accent, violet
|
||||
rides --accent-2 (the other shapes step down in value, not hue).
|
||||
- exit ("none" | "fade" | "up", default "none").
|
||||
|
||||
Envelope, fixed IN and OUT with elastic HOLD only (never timeScale):
|
||||
IN_BASE = 3.0s (all four shapes landed and recovered)
|
||||
HOLD = max(0, D - IN - OUT); the boil keeps breathing through the
|
||||
hold (that is the register), boil off holds dead still
|
||||
OUT_BASE = 0.45s only when exit != none
|
||||
If D < IN_BASE + OUT_BASE the choreography compresses together; the
|
||||
fps grid itself never scales (the law quantizes absolute time).
|
||||
|
||||
Determinism: no Math.random, no wall clock, no incremental state; the
|
||||
jitter hash is sin-fold on integer indices. Stage size is measured once
|
||||
at mount; per-update writes are transforms and opacity only, all
|
||||
recomputed from tl.time().
|
||||
|
||||
Mount contract: template-wrapped; #root fills the host box (no
|
||||
data-width/height, container-type: size, cqmin units), styled via
|
||||
#root only; one paused timeline under the LITERAL
|
||||
"stop-motion-cadence" key.
|
||||
-->
|
||||
<html
|
||||
lang="en"
|
||||
data-composition-id="stop-motion-cadence"
|
||||
data-composition-duration="4"
|
||||
data-composition-variables='[
|
||||
{ "id": "fps", "type": "enum", "role": "timing", "label": "Cadence", "description": "Stepped-time cadence in frames per second.", "default": "10", "options": [{ "value": "8", "label": "8 fps" }, { "value": "10", "label": "10 fps" }, { "value": "12", "label": "12 fps" }] },
|
||||
{ "id": "boil", "type": "enum", "role": "style", "label": "Boil", "description": "2-frame edge jitter, seeded per step. Off holds dead still.", "default": "on", "options": [{ "value": "on", "label": "On" }, { "value": "off", "label": "Off" }] },
|
||||
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Lead shape color: green rides --brand, blue rides --accent, violet rides --accent-2.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] },
|
||||
{ "id": "exit", "type": "enum", "role": "timing", "label": "Exit", "description": "Optional departure. Default none: the settled collage holds until the frame cuts.", "default": "none", "options": [{ "value": "none", "label": "None" }, { "value": "fade", "label": "Fade" }, { "value": "up", "label": "Up" }] }
|
||||
]'
|
||||
>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Stop Motion Cadence</title>
|
||||
</head>
|
||||
<body>
|
||||
<template>
|
||||
<div id="root" data-composition-id="stop-motion-cadence" data-duration="4" data-fps="30">
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#root {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
container-type: size;
|
||||
isolation: isolate;
|
||||
color: var(--fg, #f8fafc);
|
||||
font-family: var(--font-display, "Inter", system-ui, sans-serif);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.smc-clip {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
background: var(--bg, transparent);
|
||||
}
|
||||
|
||||
.smc-stage {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
/* Paper-cut floor: a hairline plus a soft paper band. */
|
||||
.smc-floor {
|
||||
position: absolute;
|
||||
left: 14cqw;
|
||||
right: 14cqw;
|
||||
top: 72cqh;
|
||||
height: 0.35cqmin;
|
||||
border-radius: 0.2cqmin;
|
||||
background: color-mix(in srgb, var(--fg, #f8fafc) 22%, transparent);
|
||||
}
|
||||
|
||||
.smc-shape {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
filter: drop-shadow(
|
||||
0.5cqmin 0.7cqmin 0 color-mix(in srgb, var(--bg, #000000) 35%, transparent)
|
||||
);
|
||||
}
|
||||
|
||||
.smc-shadow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 12cqmin;
|
||||
height: 2cqmin;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--fg, #f8fafc) 14%, transparent);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.smc-circle {
|
||||
width: 13cqmin;
|
||||
height: 13cqmin;
|
||||
border-radius: 50%;
|
||||
background: var(--smc-c1);
|
||||
}
|
||||
|
||||
.smc-square {
|
||||
width: 15cqmin;
|
||||
height: 15cqmin;
|
||||
border-radius: 1.6cqmin;
|
||||
background: var(--smc-c2);
|
||||
}
|
||||
|
||||
.smc-tri {
|
||||
width: 16cqmin;
|
||||
height: 14cqmin;
|
||||
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
|
||||
background: var(--smc-c3);
|
||||
}
|
||||
|
||||
.smc-star {
|
||||
width: 9cqmin;
|
||||
height: 9cqmin;
|
||||
clip-path: polygon(
|
||||
50% 0%,
|
||||
63% 34%,
|
||||
98% 35%,
|
||||
70% 57%,
|
||||
81% 91%,
|
||||
50% 70%,
|
||||
19% 91%,
|
||||
30% 57%,
|
||||
2% 35%,
|
||||
37% 34%
|
||||
);
|
||||
background: color-mix(in srgb, var(--fg, #f8fafc) 82%, var(--smc-c1));
|
||||
}
|
||||
|
||||
.smc-sparkle {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 5.5cqmin;
|
||||
height: 5.5cqmin;
|
||||
clip-path: polygon(
|
||||
50% 0%,
|
||||
60% 40%,
|
||||
100% 50%,
|
||||
60% 60%,
|
||||
50% 100%,
|
||||
40% 60%,
|
||||
0% 50%,
|
||||
40% 40%
|
||||
);
|
||||
background: var(--fg, #f8fafc);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
id="stop-motion-cadence-clip"
|
||||
class="smc-clip clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
>
|
||||
<div class="smc-stage">
|
||||
<div class="smc-floor"></div>
|
||||
<div class="smc-shadow" data-for="0"></div>
|
||||
<div class="smc-shadow" data-for="1"></div>
|
||||
<div class="smc-shadow" data-for="2"></div>
|
||||
<div class="smc-shadow" data-for="3"></div>
|
||||
<div class="smc-shape smc-circle"></div>
|
||||
<div class="smc-shape smc-square"></div>
|
||||
<div class="smc-shape smc-tri"></div>
|
||||
<div class="smc-shape smc-star"></div>
|
||||
<div class="smc-sparkle"></div>
|
||||
<div class="smc-sparkle"></div>
|
||||
<div class="smc-sparkle"></div>
|
||||
<div class="smc-sparkle"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var root = document.getElementById("root");
|
||||
var stage = root.querySelector(".smc-stage");
|
||||
var shapes = root.querySelectorAll(".smc-shape");
|
||||
var shadows = root.querySelectorAll(".smc-shadow");
|
||||
var sparkles = root.querySelectorAll(".smc-sparkle");
|
||||
var vars =
|
||||
window.__hyperframes && window.__hyperframes.getVariables
|
||||
? window.__hyperframes.getVariables()
|
||||
: {};
|
||||
|
||||
var fps =
|
||||
vars.fps === "8" || vars.fps === 8
|
||||
? 8
|
||||
: vars.fps === "12" || vars.fps === 12
|
||||
? 12
|
||||
: 10;
|
||||
var boil = vars.boil !== "off";
|
||||
var accentColors = {
|
||||
green: "var(--brand, #52525b)",
|
||||
blue: "var(--accent, #52525b)",
|
||||
violet: "var(--accent-2, #52525b)",
|
||||
};
|
||||
var accent = Object.prototype.hasOwnProperty.call(accentColors, vars.accent)
|
||||
? vars.accent
|
||||
: "green";
|
||||
var exit = vars.exit === "fade" || vars.exit === "up" ? vars.exit : "none";
|
||||
|
||||
// The lead shape is the one accented element; the other two paper
|
||||
// cutouts separate by value step, not by a second and third hue.
|
||||
root.style.setProperty("--smc-c1", accentColors[accent]);
|
||||
root.style.setProperty(
|
||||
"--smc-c2",
|
||||
"color-mix(in srgb, var(--fg, #f8fafc) 72%, transparent)",
|
||||
);
|
||||
root.style.setProperty(
|
||||
"--smc-c3",
|
||||
"color-mix(in srgb, var(--fg, #f8fafc) 45%, transparent)",
|
||||
);
|
||||
|
||||
// One-time stage measurement; all positions in px from here.
|
||||
var W = Math.max(1, stage.clientWidth);
|
||||
var H = Math.max(1, stage.clientHeight);
|
||||
var M = Math.min(W, H);
|
||||
var groundY = 0.72 * H;
|
||||
|
||||
// Envelope: fixed IN/OUT, elastic HOLD; fps grid never scales.
|
||||
var IN_BASE = 3.0;
|
||||
var OUT_BASE = exit === "none" ? 0 : 0.45;
|
||||
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "4"));
|
||||
var totalBase = Math.max(0.001, IN_BASE + OUT_BASE);
|
||||
var scale = duration < totalBase ? duration / totalBase : 1;
|
||||
var OUT = OUT_BASE * scale;
|
||||
var HOLD = Math.max(0, duration - (IN_BASE * scale + OUT));
|
||||
var OUT_START = IN_BASE * scale + HOLD;
|
||||
|
||||
// Choreography (seconds, scaled; converted to integer steps).
|
||||
// Each shape: launch, land, from (offstage), to (on the floor),
|
||||
// arc height, spin, squash amount, sparkle life in steps.
|
||||
function shapeSize(el) {
|
||||
return { w: el.offsetWidth, h: el.offsetHeight };
|
||||
}
|
||||
var throws = [
|
||||
{
|
||||
t0: 0.15,
|
||||
t1: 1.0,
|
||||
from: { x: -0.12 * W, yB: 0.28 * H },
|
||||
to: 0.3 * W,
|
||||
arc: 0.3 * H,
|
||||
rot0: -170,
|
||||
rot1: 8,
|
||||
squash: 0.3,
|
||||
life: 2,
|
||||
},
|
||||
{
|
||||
t0: 0.6,
|
||||
t1: 1.5,
|
||||
from: { x: 1.12 * W, yB: 0.2 * H },
|
||||
to: 0.56 * W,
|
||||
arc: 0.34 * H,
|
||||
rot0: 150,
|
||||
rot1: -6,
|
||||
squash: 0.34,
|
||||
life: 3,
|
||||
},
|
||||
{
|
||||
t0: 1.25,
|
||||
t1: 2.0,
|
||||
from: { x: 0.78 * W, yB: -0.12 * H },
|
||||
to: 0.72 * W,
|
||||
arc: 0.06 * H,
|
||||
rot0: -120,
|
||||
rot1: 5,
|
||||
squash: 0.26,
|
||||
life: 2,
|
||||
},
|
||||
{
|
||||
t0: 1.8,
|
||||
t1: 2.55,
|
||||
from: { x: -0.08 * W, yB: 0.1 * H },
|
||||
to: 0.43 * W,
|
||||
arc: 0.4 * H,
|
||||
rot0: 200,
|
||||
rot1: -12,
|
||||
squash: 0.22,
|
||||
life: 3,
|
||||
},
|
||||
];
|
||||
for (var s = 0; s < throws.length; s += 1) {
|
||||
var th = throws[s];
|
||||
th.t0 *= scale;
|
||||
th.t1 *= scale;
|
||||
th.landStep = Math.round(th.t1 * fps);
|
||||
th.size = shapeSize(shapes[s]);
|
||||
gsap.set(shapes[s], { transformOrigin: "50% 100%" });
|
||||
}
|
||||
|
||||
// Deterministic per-index hash (sin fold; no Math.random).
|
||||
function rnd(n) {
|
||||
var x = Math.sin(n * 127.1 + 311.7) * 43758.5453;
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
function clamp01(v) {
|
||||
return v < 0 ? 0 : v > 1 ? 1 : v;
|
||||
}
|
||||
function lerp(a, b, p) {
|
||||
return a + (b - a) * p;
|
||||
}
|
||||
|
||||
// Squash schedule by steps-since-landing: hit, deep, rebound
|
||||
// stretch, settle. Positive widens (scaleX 1+a, scaleY 1-a).
|
||||
var SQUASH_ENV = [1, 0.55, -0.3, 0.1, 0];
|
||||
function squashAt(stepsSince, amount) {
|
||||
if (stepsSince < 0) return 0;
|
||||
if (stepsSince >= SQUASH_ENV.length) return 0;
|
||||
return SQUASH_ENV[stepsSince] * amount;
|
||||
}
|
||||
|
||||
// THE LAW: quantize first, then evaluate everything from ts.
|
||||
// The 1e-6 epsilon absorbs GSAP seek round-trip float error so
|
||||
// a seek landing exactly on a step boundary quantizes the same
|
||||
// way from either direction.
|
||||
function applyTime(timeSeconds) {
|
||||
var step = Math.floor(timeSeconds * fps + 1e-6);
|
||||
var ts = step / fps;
|
||||
var boilIdx = Math.floor(step / 2);
|
||||
|
||||
for (var i = 0; i < throws.length; i += 1) {
|
||||
var th = throws[i];
|
||||
var el = shapes[i];
|
||||
var sw = th.size.w;
|
||||
var sh = th.size.h;
|
||||
var landed = step >= th.landStep;
|
||||
var u = clamp01((ts - th.t0) / Math.max(0.001, th.t1 - th.t0));
|
||||
if (landed) u = 1;
|
||||
var visible = ts >= th.t0;
|
||||
|
||||
// Ballistic arc: linear x, parabolic lift over the line
|
||||
// from launch height to the ground.
|
||||
var xC = lerp(th.from.x, th.to, u);
|
||||
var yB = lerp(th.from.yB, groundY, u) - th.arc * 4 * u * (1 - u);
|
||||
var rot = lerp(th.rot0, th.rot1, 1 - (1 - u) * (1 - u));
|
||||
|
||||
// Squash on the hit, keyed to integer steps since landing.
|
||||
var a = squashAt(step - th.landStep, th.squash);
|
||||
var sxq = 1 + a;
|
||||
var syq = 1 - a * 0.85;
|
||||
|
||||
// 2-frame boil, seeded per boil window, per shape.
|
||||
var bx = 0;
|
||||
var by = 0;
|
||||
var br = 0;
|
||||
if (boil && visible) {
|
||||
bx = (rnd(boilIdx * 13 + i * 101) - 0.5) * 0.008 * M;
|
||||
by = (rnd(boilIdx * 29 + i * 211) - 0.5) * 0.008 * M;
|
||||
br = (rnd(boilIdx * 47 + i * 331) - 0.5) * 1.6;
|
||||
}
|
||||
|
||||
gsap.set(el, {
|
||||
x: xC - sw / 2 + bx,
|
||||
y: yB - sh + by,
|
||||
rotation: rot + br,
|
||||
scaleX: sxq,
|
||||
scaleY: syq,
|
||||
opacity: visible ? 1 : 0,
|
||||
});
|
||||
|
||||
// Contact shadow: grows and darkens as the shape nears
|
||||
// the floor, pinned to the landing spot.
|
||||
var height = clamp01((groundY - yB) / (0.45 * H));
|
||||
var shadowScale = lerp(1.05, 0.55, height);
|
||||
gsap.set(shadows[i], {
|
||||
x: xC - 0.06 * M,
|
||||
y: groundY - 0.01 * M,
|
||||
scaleX: shadowScale * (landed ? sxq : 1),
|
||||
scaleY: shadowScale * 0.9,
|
||||
opacity: visible ? lerp(0.85, 0.25, height) : 0,
|
||||
});
|
||||
|
||||
// Sparkle: alive for EXACTLY life steps from the hit,
|
||||
// frame-alternating big / small rotated 45deg.
|
||||
var d = step - th.landStep;
|
||||
var sp = sparkles[i];
|
||||
if (d >= 0 && d < th.life) {
|
||||
var side = i % 2 === 0 ? -1 : 1;
|
||||
gsap.set(sp, {
|
||||
x: xC + side * (sw * 0.62) - 0.0275 * M,
|
||||
y: groundY - sh * 0.9,
|
||||
scale: d % 2 === 0 ? 1 : 0.62,
|
||||
rotation: d % 2 === 0 ? 0 : 45,
|
||||
opacity: 1,
|
||||
});
|
||||
} else {
|
||||
gsap.set(sp, { opacity: 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gsap.set(stage, { opacity: 1, y: "0cqh" });
|
||||
|
||||
var tl = gsap.timeline({
|
||||
paused: true,
|
||||
onUpdate: function () {
|
||||
applyTime(tl.time());
|
||||
},
|
||||
});
|
||||
|
||||
// Anchor tween: inert, spans [0, D] so onUpdate fires for
|
||||
// every eventful seek and holds never clamp short.
|
||||
tl.to({ p: 0 }, { p: 1, duration: duration, ease: "none" }, 0);
|
||||
|
||||
// OUT: optional departure; exit none holds until the cut.
|
||||
if (exit === "up") {
|
||||
tl.to(stage, { y: "-4cqh", duration: OUT, ease: "power2.in" }, OUT_START);
|
||||
tl.to(stage, { opacity: 0, duration: OUT, ease: "power2.in" }, OUT_START);
|
||||
} else if (exit === "fade") {
|
||||
tl.to(stage, { opacity: 0, duration: OUT, ease: "power2.in" }, OUT_START);
|
||||
}
|
||||
|
||||
tl.seek(0);
|
||||
applyTime(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["stop-motion-cadence"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user