diff --git a/docs/guides/claude-design-hyperframes.md b/docs/guides/claude-design-hyperframes.md
index f009be5dd7..e4fe9cc96a 100644
--- a/docs/guides/claude-design-hyperframes.md
+++ b/docs/guides/claude-design-hyperframes.md
@@ -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 |
@@ -415,7 +415,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`
diff --git a/docs/guides/claude-design-send-to-hyperframes.md b/docs/guides/claude-design-send-to-hyperframes.md
index 51d1133e98..ee1e6dad69 100644
--- a/docs/guides/claude-design-send-to-hyperframes.md
+++ b/docs/guides/claude-design-send-to-hyperframes.md
@@ -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 |
@@ -427,6 +427,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
diff --git a/skills/figma/SKILL.md b/skills/figma/SKILL.md
index 9189125076..5403989f10 100644
--- a/skills/figma/SKILL.md
+++ b/skills/figma/SKILL.md
@@ -129,4 +129,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.
diff --git a/skills/hyperframes-animation/SKILL.md b/skills/hyperframes-animation/SKILL.md
index 00aaaeed5c..a3ce0a80fb 100644
--- a/skills/hyperframes-animation/SKILL.md
+++ b/skills/hyperframes-animation/SKILL.md
@@ -59,7 +59,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:
diff --git a/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md b/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md
index 98256c8560..504b9256a1 100644
--- a/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md
+++ b/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md
@@ -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
diff --git a/skills/hyperframes-animation/adapters/gsap.md b/skills/hyperframes-animation/adapters/gsap.md
index d47b3d0df6..74a71c57e6 100644
--- a/skills/hyperframes-animation/adapters/gsap.md
+++ b/skills/hyperframes-animation/adapters/gsap.md
@@ -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
diff --git a/skills/hyperframes-animation/rules-index.md b/skills/hyperframes-animation/rules-index.md
index 8568d22791..48327c8840 100644
--- a/skills/hyperframes-animation/rules-index.md
+++ b/skills/hyperframes-animation/rules-index.md
@@ -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;
diff --git a/skills/hyperframes-core/SKILL.md b/skills/hyperframes-core/SKILL.md
index f3dc06d769..b680819561 100644
--- a/skills/hyperframes-core/SKILL.md
+++ b/skills/hyperframes-core/SKILL.md
@@ -57,7 +57,7 @@ Each composition registers **exactly one** `gsap.timeline({ paused: true })` at
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`
- Animate only the visual-property allowlist; never tween `display` or raw `visibility`. GSAP `autoAlpha` and zero-duration timeline boundary sets are the only visibility exceptions, and only on non-clip elements or wrappers inside a clip. The framework alone controls `.clip` visibility. Do not `gsap.set` later-scene clips at page load. → `determinism-rules.md`
- No ` ` in body text; transformed elements must be block-level + sized; pulsing absolute decoratives need peak clearance. → `determinism-rules.md`
- `