mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
docs: align infinite-repeat guidance with bounded repeat: -1 lint (#3795)
Since v0.7.71 (#2763), gsap_infinite_repeat demotes to a warning when the root declares a finite data-duration: deterministic seeking and export clip to that explicit window. Without one it remains a hard error (unbounded timeline duration breaks render planning). Update every doc site that still teaches the old absolute ban — the core determinism contract, the animation adapter notes, and the design-guide tables/checklists — to state the bounded allowance and keep the finite-count formula for loops that must end before the composition does. skills/hyperframes-animation/rules/ambient-glow-bloom.md is intentionally untouched: its shimmer must stop before the composition ends, which is exactly the case where a computed finite count is still required. Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
This commit is contained in:
co-authored by
Miguel Ángel
parent
ecfcbb0c61
commit
6f799aaa41
@@ -374,7 +374,7 @@ The skeleton handles most structural rules. These are the runtime rules the skel
|
||||
| `Math.random()` | Seeded PRNG (only if you need randomness) |
|
||||
| `Date.now()`, `performance.now()` | Hard-coded timing or `tl.time()` in `onUpdate` |
|
||||
| `setInterval`, `setTimeout` | Timeline tweens + `onUpdate` |
|
||||
| `repeat: -1` | `repeat: Math.max(0, Math.floor(duration / cycle) - 1)` |
|
||||
| `repeat: -1` without root `data-duration` | a finite root `data-duration` (export clips to it), or `repeat: Math.max(0, Math.floor(duration / cycle) - 1)` |
|
||||
| `stagger: { from: "random" }` | `from: "start"`, `"center"`, `"end"` |
|
||||
| Async timeline construction | Synchronous at page load |
|
||||
|
||||
@@ -416,7 +416,7 @@ Run before delivering. Check with actual code, not assumptions.
|
||||
- [ ] Shader transitions have boundary INSIDE the window: `time < boundary < time + duration`
|
||||
- [ ] No transition shorter than 0.3s
|
||||
- [ ] No exit tweens except on the final scene
|
||||
- [ ] No `Date.now()`, unseeded `Math.random()`, `repeat: -1`
|
||||
- [ ] No `Date.now()`, unseeded `Math.random()`, or `repeat: -1` without a finite root `data-duration`
|
||||
- [ ] No SVG filter data URLs as `background-image`
|
||||
- [ ] `window.__timelines["main"] = tl` matches `data-composition-id`
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ The cloud renderer seeks the timeline frame-by-frame. Non-deterministic or self-
|
||||
| `Date.now()`, `performance.now()` | hard-coded timing or `tl.time()` in `onUpdate` |
|
||||
| `setInterval`, `setTimeout` | timeline tweens + `onUpdate` |
|
||||
| `requestAnimationFrame` | GSAP tweens |
|
||||
| `repeat: -1` | `repeat: Math.max(0, Math.floor(duration / cycle) - 1)` |
|
||||
| `repeat: -1` without root `data-duration` | a finite root `data-duration` (export clips to it), or `repeat: Math.max(0, Math.floor(duration / cycle) - 1)` |
|
||||
| `stagger: { from: "random" }` | `from: "start"`, `"center"`, or `"end"` |
|
||||
| async timeline construction | build synchronously at page load |
|
||||
| `video.play()` / `audio.play()` | the framework owns playback |
|
||||
@@ -429,6 +429,6 @@ You don't author any of this. Produce a clean, on-brand, correctly-timed silent
|
||||
- [ ] No exit tweens except on the final scene
|
||||
|
||||
**Renders correctly:**
|
||||
- [ ] No `Date.now()`, unseeded `Math.random()`, `setInterval/Timeout`, `requestAnimationFrame`, `repeat: -1`
|
||||
- [ ] No `Date.now()`, unseeded `Math.random()`, `setInterval/Timeout`, `requestAnimationFrame`, or `repeat: -1` without a finite root `data-duration`
|
||||
- [ ] Timeline built synchronously; paused; framework owns playback
|
||||
- [ ] Colors and fonts exact; brand substance preserved: real headline/copy, distinctive figures/stats/data points, product names, and signature visuals carried over verbatim (form adapted, never genericized or invented); no placeholders; ≥2 animation patterns + mid-scene activity per scene
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"source": "heygen-com/hyperframes",
|
||||
"skills": {
|
||||
"embedded-captions": {
|
||||
"hash": "9ae9e4e06bf4b61e",
|
||||
"hash": "ebef42b483d02ab6",
|
||||
"files": 144
|
||||
},
|
||||
"faceless-explainer": {
|
||||
@@ -10,7 +10,7 @@
|
||||
"files": 27
|
||||
},
|
||||
"figma": {
|
||||
"hash": "1639b96642ac26c6",
|
||||
"hash": "ade0258f3cb1f378",
|
||||
"files": 2
|
||||
},
|
||||
"general-video": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"files": 29
|
||||
},
|
||||
"hyperframes-animation": {
|
||||
"hash": "2a567656b9291f5a",
|
||||
"hash": "8c183924c57c2801",
|
||||
"files": 122
|
||||
},
|
||||
"hyperframes-audio": {
|
||||
@@ -34,7 +34,7 @@
|
||||
"files": 11
|
||||
},
|
||||
"hyperframes-core": {
|
||||
"hash": "96756e9138ed43d9",
|
||||
"hash": "010a65915fc7b45e",
|
||||
"files": 11
|
||||
},
|
||||
"hyperframes-creative": {
|
||||
@@ -74,7 +74,7 @@
|
||||
"files": 34
|
||||
},
|
||||
"remotion-to-hyperframes": {
|
||||
"hash": "e13e3681d9b0dd75",
|
||||
"hash": "833bb7855cd99d4f",
|
||||
"files": 77
|
||||
},
|
||||
"slideshow": {
|
||||
@@ -82,7 +82,7 @@
|
||||
"files": 2
|
||||
},
|
||||
"talking-head-recut": {
|
||||
"hash": "865d98665f0011da",
|
||||
"hash": "c9b7ab6112feb4e3",
|
||||
"files": 28
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ HyperFrames-native, so anyone can reproduce it:
|
||||
|
||||
- **One paused GSAP timeline per composition**, registered to `window.__timelines[data-composition-id]`.
|
||||
- All timing in **seconds**; `data-start` / `data-duration` carry the scene window.
|
||||
- **Deterministic + seek-safe** only — no `Math.random()`, no `Date.now()`, no infinite repeats, no un-seekable CSS animations. Every state is reachable by seeking the timeline to a time `t`.
|
||||
- **Deterministic + seek-safe** only — no `Math.random()`, no `Date.now()`, no `repeat: -1` without a finite root `data-duration`, no un-seekable CSS animations. Every state is reachable by seeking the timeline to a time `t`.
|
||||
- GSAP transform aliases (`x`, `y`, `scale`, `rotation`); animate `opacity`, `filter`, `clipPath`, `textShadow`, `backgroundPosition`, `letterSpacing` — never layout props (width/top/left/margin).
|
||||
|
||||
For the composition contract see `hyperframes-core`; eases + the animated-property allowlist see `hyperframes-gsap`; caption grouping/positioning/exit guarantees see `hyperframes-captions`.
|
||||
@@ -218,7 +218,7 @@ The gallery used a `setInterval` loop; HyperFrames needs the same beats as **abs
|
||||
|
||||
- Timeline **paused**; registry key = `data-composition-id`.
|
||||
- **No CSS keyframe animation** on caption elements — all motion is GSAP tweens at absolute times (seek-safe).
|
||||
- No `Math.random` / `Date.now` / infinite repeats.
|
||||
- No `Math.random` / `Date.now` / `repeat: -1` without a finite root `data-duration`.
|
||||
- `display:inline-block` on every `.w` and the climax `span`.
|
||||
- **Hard-hide** each flow group at its end time; **CLIMAX_OUT ends at `opacity:0`** (or fully-clipped) so nothing lingers.
|
||||
- Gradient / `background-clip:text` / stroke fills go on `.climax span`, not the transformed `.climax`.
|
||||
|
||||
@@ -131,4 +131,4 @@ Storyboard files follow a grammar you can parse mechanically — don't eyeball,
|
||||
|
||||
## Determinism
|
||||
|
||||
Never leave a Figma URL in the composition — freeze first. Never emit `repeat: -1`. Timelines paused, finite, literal `window.__timelines` keys. All Figma I/O at import time; render sees local files only.
|
||||
Never leave a Figma URL in the composition — freeze first. Never emit `repeat: -1` without a finite root `data-duration`. Timelines paused, finite, literal `window.__timelines` keys. All Figma I/O at import time; render sees local files only.
|
||||
|
||||
@@ -63,7 +63,7 @@ Multiple runtimes can coexist in one composition. Each registers its instances o
|
||||
|
||||
## Critical Constraints
|
||||
|
||||
**Prerequisite: `hyperframes-core` → Non-Negotiable Rules** (single paused timeline, `data-duration` governs length, no `Math.random` / `Date.now` / `performance.now`, no `repeat: -1`, no page-load `gsap.set` on later-scene clips, no `display` or raw `visibility` tweens, and no timeline construction inside `async` / `setTimeout` / `Promise`). GSAP `autoAlpha` and zero-duration visibility sets at explicit timeline boundaries remain allowed by core. Use those exceptions only on non-clip elements or wrappers inside a clip; the framework owns `.clip` lifecycle. Don't restate the full contract here.
|
||||
**Prerequisite: `hyperframes-core` → Non-Negotiable Rules** (single paused timeline, `data-duration` governs length, no `Math.random` / `Date.now` / `performance.now`, no `repeat: -1` without a finite root `data-duration`, no page-load `gsap.set` on later-scene clips, no `display` or raw `visibility` tweens, and no timeline construction inside `async` / `setTimeout` / `Promise`). GSAP `autoAlpha` and zero-duration visibility sets at explicit timeline boundaries remain allowed by core. Use those exceptions only on non-clip elements or wrappers inside a clip; the framework owns `.clip` lifecycle. Don't restate the full contract here.
|
||||
|
||||
Animation-craft additions on top of core's contract:
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ tl.to(".a", { x: 100 }).to(".b", { y: 50 }).to(".c", { opacity: 0 });
|
||||
Timeline options:
|
||||
|
||||
- **paused: true** — required in HyperFrames. The framework drives the playhead.
|
||||
- **repeat**, **yoyo** — apply to the whole timeline. `repeat: -1` is forbidden; use finite counts.
|
||||
- **repeat**, **yoyo** — apply to the whole timeline. `repeat: -1` requires a finite root `data-duration` (export clips to it); otherwise use finite counts.
|
||||
- **defaults** — vars merged into every child tween. Use this instead of repeating `ease` and `duration` on every line.
|
||||
|
||||
## Position Parameter
|
||||
|
||||
@@ -96,7 +96,7 @@ HyperFrames is stricter than vanilla GSAP. Animate only:
|
||||
- Use both `svgOrigin` and `transformOrigin` on the same SVG element.
|
||||
- Chain animations with `delay` when a timeline can sequence them.
|
||||
- Create tweens before the DOM exists.
|
||||
- Use infinite `repeat: -1` in HyperFrames compositions — use finite repeat counts computed from the visible duration.
|
||||
- Use infinite `repeat: -1` without a finite root `data-duration` (with one, export clips to that window); when a loop must end before the composition does, use a finite repeat count computed from the visible duration.
|
||||
|
||||
## Credits And References
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Stated once here so individual rules don't repeat it. Every recipe in `rules/`:
|
||||
|
||||
- runs on ONE **paused** GSAP timeline registered on `window.__timelines` (never autoplay, never a second timeline);
|
||||
- is **seek-safe both directions**: `fromTo` with explicit from-states (t=0 correct under seek; `immediateRender: false` when re-owning a target), absolute values — never relative `+=` tweens; state readable as a pure function of timeline time, no mutable trackers;
|
||||
- is **deterministic**: no `Math.random()`, no `Date.now()` — index-derived pseudo-random and baked schedules only; finite repeats, never `repeat: -1`;
|
||||
- is **deterministic**: no `Math.random()`, no `Date.now()` — index-derived pseudo-random and baked schedules only; finite repeats (`repeat: -1` only under a finite root `data-duration`);
|
||||
- animates **transforms and paint-only properties** — `width`/`height`/`top`/`left` tweens are forbidden (use scale/translate proxies, masks, or `anchored-layout-expand`);
|
||||
- caps group staggers so an arrival reads as one beat (`items × stagger ≤ ~0.5s`);
|
||||
- puts **no CSS `transition`** on animated elements (they interpolate independently of seek and flicker) and hints compositors with `will-change: transform` where many tweens run at once;
|
||||
|
||||
@@ -73,7 +73,7 @@ A lint **error** also switches off the layout and contrast audits: `check` then
|
||||
|
||||
Surfaced here; full rationale in the linked reference. Do not violate:
|
||||
|
||||
- No render-time clocks / unseeded `Math.random` / network / input-state; no `repeat: -1` (use a finite count). → `determinism-rules.md`
|
||||
- No render-time clocks / unseeded `Math.random` / network / input-state; `repeat: -1` only under a finite root `data-duration` (export clips to it — otherwise use a finite count). → `determinism-rules.md`
|
||||
- Never tween `display`, `visibility`, or `autoAlpha` on a `.clip` element. The framework owns clip visibility, and `lint` rejects it (`gsap_animates_clip_element`). Animate a child instead. → `determinism-rules.md`
|
||||
- No `<br>` in body text; transformed elements must be block-level + sized; pulsing absolute decoratives need peak clearance. → `determinism-rules.md`
|
||||
- `<video>`/`<audio>` are found by a flat document query, so the framework seeks and decodes them at **any nesting depth** (including inside a sub-comp `<template>` or wrapper). One hard limit: `lint` errors if a `<video data-start>` sits inside another **plain** element that also has `data-start`, and the failure is real (wrong source frames, then the clip vanishes mid-slot), so put the timing on the wrapper or on the video, never both. Sub-composition hosts are exempt: media inside a sub-composition renders correctly. The other caveat is timelines, not placement: a sub-comp timeline can't animate host-root elements. → `variables-and-media.md`
|
||||
|
||||
@@ -25,7 +25,7 @@ Rendered frames must be reproducible from the requested time. Do **not** use any
|
||||
- Unseeded `Math.random()`. Use a seeded PRNG if random-looking placement is needed.
|
||||
- Render-time network fetches for required assets. Inline or pre-bundle them.
|
||||
- Hover, scroll, pointer, or focus state. The renderer has no input events.
|
||||
- Infinite loops such as `repeat: -1`. Compute a finite count: `repeat: Math.max(0, Math.floor(duration / cycleDuration) - 1)` — **`floor`, not `ceil`** (`ceil` overshoots `data-duration` and trips the `gsap_repeat_ceil_overshoot` lint; `max(0, …)` avoids a negative repeat = infinite).
|
||||
- Unbounded infinite loops. `repeat: -1` is allowed only when the root declares a finite `data-duration` — deterministic seeking and export clip to that explicit window (`gsap_infinite_repeat` demotes to a warning). Without a finite composition duration it stays a hard error: the timeline can report an unbounded length and render planning fails. When the loop itself must end **before** the composition does, compute a finite count: `repeat: Math.max(0, Math.floor(duration / cycleDuration) - 1)` — **`floor`, not `ceil`** (`ceil` overshoots `data-duration` and trips the `gsap_repeat_ceil_overshoot` lint; `max(0, …)` avoids a negative repeat = infinite).
|
||||
|
||||
Also avoid:
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ output but visually-identical video, so SSIM passes — just flag it.
|
||||
A bounded GSAP repeat can reproduce _visual_ repetition when its finite count is derived
|
||||
from the visible duration. If the looped child has cross-iteration state (a counter, a
|
||||
randomness seed), HF won't reproduce it identically per iteration. Bow out unless the child
|
||||
is fully deterministic per-iteration; never use `repeat: -1`.
|
||||
is fully deterministic per-iteration; use `repeat: -1` only when the root declares a finite `data-duration`.
|
||||
|
||||
### Remotion's `<Img>` with crossOrigin
|
||||
|
||||
|
||||
@@ -1154,7 +1154,7 @@ decides where the actual visible card sits.
|
||||
- Register one paused master timeline as `window.__timelines["talking-head-recut"]`.
|
||||
- Build timelines synchronously at page load; no `async`, `setTimeout`, Promises, or media `play()` calls.
|
||||
- Do not use `Math.random()` or `Date.now()` in render paths.
|
||||
- Do not use `repeat: -1`; calculate finite repeats from the video duration.
|
||||
- `repeat: -1` is allowed only under the finite root `data-duration`; for loops that must end earlier, calculate finite repeats from the video duration.
|
||||
- Prefer GSAP transforms and opacity (`x`, `y`, `scale`, `rotation`, `opacity`) over layout properties (`top`, `left`, `width`, `height`) for motion.
|
||||
- Animate wrappers such as `#video-wrap`, not the video element dimensions directly.
|
||||
- Avoid animating the same property on the same element from multiple timelines at the same time.
|
||||
|
||||
Reference in New Issue
Block a user