> ## Documentation Index
> Fetch the complete documentation index at: https://hyperframes.heygen.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stop Motion Cadence

> 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.

export const InstallCommand = ({command}) => {
  const [copied, setCopied] = React.useState(false);
  const copy = async () => {
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(command);
      } else {
        const previous = document.activeElement;
        const scratch = document.createElement("textarea");
        scratch.value = command;
        scratch.setAttribute("readonly", "");
        scratch.style.position = "fixed";
        scratch.style.opacity = "0";
        document.body.appendChild(scratch);
        scratch.select();
        document.execCommand("copy");
        document.body.removeChild(scratch);
        previous?.focus?.();
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {}
  };
  return <div className="hf-install-command not-prose my-4 flex items-stretch overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900">
      <code className="flex-1 overflow-x-auto whitespace-nowrap border-r border-zinc-200 px-4 py-3 font-mono text-sm text-zinc-800 dark:border-zinc-800 dark:text-zinc-100">
        {command}
      </code>
      <button type="button" onClick={copy} data-copied={copied ? "true" : "false"} aria-label={`Copy ${command} to the clipboard`} className="hf-install-copy">
        <svg className="hf-install-copy-clipboard" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" />
          <path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" />
        </svg>
        <svg className="hf-install-copy-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M2.75 9.5L6.5 13.25L15.25 4.5" />
        </svg>
      </button>
      <span className="hf-install-copy-status" role="status" aria-live="polite">
        {copied ? "Copied" : ""}
      </span>
    </div>;
};

<iframe className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800" title="stop-motion-cadence preview" loading="lazy" srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@0.7/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/components/stop-motion-cadence.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/stop-motion-cadence.png");document.body.appendChild(p)});<\/script></body></html>`} />

## Install

<InstallCommand command="npx hyperframes add stop-motion-cadence" />

That writes one file: `compositions/components/stop-motion-cadence.html`.

## Paste it into your composition

Open `compositions/components/stop-motion-cadence.html` and copy what is inside into your own composition.

A component has no size or duration of its own. It takes both from the composition
you paste it into.

## Variables

Every one of these has a default, so the piece works untouched. Set the ones you
want to change on the element:

| Variable | Default | Accepts                   | What it does                                                                         |
| -------- | ------- | ------------------------- | ------------------------------------------------------------------------------------ |
| `fps`    | `10`    | `8`, `10`, `12`           | Stepped-time cadence in frames per second.                                           |
| `boil`   | `on`    | `on`, `off`               | 2-frame edge jitter, seeded per step. Off holds dead still.                          |
| `accent` | `green` | `green`, `blue`, `violet` | Lead shape color: green rides --brand, blue rides --accent, violet rides --accent-2. |
| `exit`   | `none`  | `none`, `fade`, `up`      | Optional departure. Default none: the settled collage holds until the frame cuts.    |

Set them with `data-variable-values` on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:

```html wrap theme={null}
<div
  data-composition-id="stop-motion-cadence"
  data-composition-src="compositions/components/stop-motion-cadence.html"
  data-variable-values='{"fps":"10","boil":"on","accent":"green","exit":"none"}'
></div>
```

## Source

<Accordion title={`stop-motion-cadence.html`}>
  ```html theme={null}
  <!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>
  ```
</Accordion>

Tagged `motion-primitive` `time-law` `stop-motion` `stepped` `boil` `deterministic` `experiment`.

## Related topics

* [Browse the complete Catalog](/catalog)
* [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
* [Build a richer composition](/go-further)


## Related topics

- [Motion that reads premium](/prompting/motion.md)
- [Finish and share a video](/guides/export-and-share.md)
- [Typed Prompt](/catalog/components/typed-prompt.md)
