@miraiclip/assistant 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2674,7 +2674,7 @@ For comparison, a recent slide-like film made by the assistant (centred cards, a
|
|
|
2674
2674
|
"reference/rules/accent-punctuation.md": '# Accent punctuation\n\n**What:** A small accent shape (a square, a dot) moves with the words like punctuation, jumping to the end of each word as it lands.\n**When:** Kinetic lines of short words ("Plan. Build. Ship."); it carries the accent colour and gives the eye a guide.\n**Timing:** Each jump 0.25\u20130.3 s (expo.out), timed with the word it follows.\n**Mistake:** More than one accent object per line, or an accent that wanders without following anything.\n\n```html\n<div class="world"><p class="line"><span class="txt">Plan. Build. Ship.</span><i class="sq"></i></p></div>\n```\n\n```css\n.line{position:absolute;left:14cqw;top:42cqh;font-size:11cqmin;font-weight:900;white-space:nowrap}.sq{position:absolute;left:0;top:.62em;width:.22em;height:.22em;background:var(--p)}\n```\n\n```js\nconst words = S.split(".line .txt", "words");\ntl.label("say", 0.2).set(".sq", { x: 0, scale: 0 }, 0);\nwords.forEach((w, i) => {\n const at = "say+=" + i * 0.45;\n tl.from(w, { y: S.h(5), opacity: 0, blur: 8, duration: 0.4, ease: "expo.out" }, at)\n .to(".sq", { x: w.offsetLeft + w.offsetWidth + S.min(1), scale: 1, rotate: 90 * (i + 1), duration: 0.3, ease: "expo.out" }, at);\n});\ntl.camera({ x: -S.w(3), zoom: 1.05, duration: 3, ease: "sine.inOut" }, 0);\n```\n',
|
|
2675
2675
|
"reference/rules/anticipation.md": '# Anticipation\n\n**What:** A small move the opposite way just before the main move: a card pulls back and dips before it launches.\n**When:** Before any big, fast move (a launch, a throw, a whip). It tells the eye where to look a fraction before things happen.\n**Timing:** Wind-up 0.12\u20130.2 s (power2.out), then the main move 0.4\u20130.6 s (expo.out). The wind-up is 2\u20135% of the main move\'s distance.\n**Mistake:** A wind-up as big or as slow as the move itself: it reads as hesitation, not energy.\n\n```html\n<div class="world"><div class="card">Ship it</div></div>\n```\n\n```css\n.card{position:absolute;left:30cqw;top:42cqh;padding:3cqmin 5cqmin;border-radius:2cqmin;background:var(--p);color:var(--bg);font-size:6cqmin;font-weight:700}\n```\n\n```js\ntl.label("wind", 0.4)\n .to(".card", { x: -S.w(2), scale: 0.94, duration: 0.18, ease: "power2.out" }, "wind")\n .to(".card", { x: S.w(30), scale: 1, duration: 0.55, ease: "expo.out" }, ">")\n .camera({ x: -S.w(4), duration: 1.6, ease: "sine.inOut" }, "wind");\n```\n',
|
|
2676
2676
|
"reference/rules/blur-through.md": '# Blur-through\n\n**What:** A hidden cut: the whole beat blurs out and the next one blurs in, overlapping by a few frames, so no layout ever dissolves into another.\n**When:** A hard change of idea inside an act when nothing can carry over (and at most once or twice per act).\n**Timing:** Out 0.25\u20130.3 s (power2.in, blur to 16\u201324 px, slight scale up). In 0.4 s (expo.out) starting halfway through the out.\n**Mistake:** A slow cross-dissolve between two full layouts: both are half-visible and the frame turns to mush.\n\n```html\n<div class="world"><div class="center"><p class="a">Too many tools.</p></div><div class="center"><p class="b">One platform.</p></div></div>\n```\n\n```css\n.center{position:absolute;inset:0;display:grid;place-items:center}.a,.b{font-size:10cqmin;font-weight:800}.b{color:var(--p)}\n```\n\n```js\ntl.set(".b", { opacity: 0 }, 0)\n .label("a", 0)\n .from(".a", { opacity: 0, blur: 12, duration: 0.5, ease: "expo.out" }, "a")\n .label("cut", 1.6)\n .to(".a", { opacity: 0, blur: 22, scale: 1.08, duration: 0.28, ease: "power2.in" }, "cut")\n .fromTo(".b", { opacity: 0, blur: 22, scale: 0.94 }, { opacity: 1, blur: 0, scale: 1, duration: 0.4, ease: "expo.out" }, "cut+=0.14")\n .camera({ zoom: 1.08, duration: 3.4, ease: "sine.inOut" }, "a");\n```\n',
|
|
2677
|
-
"reference/rules/carry-over.md": '# Carry-over\n\n**What:** Something from the last beat becomes part of the next: a title becomes the label of the window that appears around it.\n**When:** Every change of beat inside an act. It is the main thing that makes a film one continuous move instead of slides.\n**Timing:** The hand-off move 0.5\u20130.8 s (expo.inOut); the new container arrives during it, never after.\n**Mistake:** Fading the old element out and a new copy in: the viewer must see the same object travel.\n\n```html\n<div class="world"><div class="win"><div class="bar"><i></i><i></i><i></i></div><div class="body"><p>Hello, Sam.</p><p class="q">What are we shipping today?</p></div></div><div class="center"><p class="name">Orbit Studio</p></div></div>\n```\n\n```css\n.center{position:absolute;inset:0;display:grid;place-items:center}.name{font-size:10cqmin;font-weight:800;white-space:nowrap}.win{position:absolute;left:14cqw;top:14cqh;width:72cqw;height:72cqh;border-radius:2cqmin;background:var(--card);border:.3cqmin solid var(--line);overflow:hidden}.bar{height:8cqh;display:flex;gap:1cqmin;align-items:center;padding-left:2cqmin;border-bottom:.3cqmin solid var(--line)}.bar i{width:1.4cqmin;height:1.4cqmin;border-radius:50%;background:var(--line)}.body{padding:6cqmin;font-size:5cqmin;font-weight:600}.q{color:var(--mut)}\n```\n\n```js\ntl.label("name", 0)\n .from(".name", { opacity: 0, blur: 12, scale: 1.2, duration: 0.6, ease: "expo.out" }, "name")\n .label("hand", 1.3)\n // The name travels into the window\'s title bar; the window grows around it at the same time.\n .to(".name", { x: -S.w(22), y: -S.h(32), scale: 0.32, duration: 0.7, ease: "expo.inOut" }, "hand")\n .from(".win", { scale: 0.7, opacity: 0, blur: 10, duration: 0.7, ease: "expo.inOut" }, "hand")\n .from(".body p", { y: S.h(3), opacity: 0, duration: 0.5, stagger: 0.15, ease: "expo.out" }, "hand+=0.6")\n .camera({ zoom: 1.1, tiltY: -8, duration: 3.4, ease: "sine.inOut" }, "name");\n```\n\n## With `tl.hop`, `tl.gather` and `tl.morph` (version 2)\n\nThe hand-off can be a verb: `tl.hop` carries an element to its next slot on an arc and lands with a squash, `tl.gather` flies many elements into one container, and `tl.morph` turns one box into another. Each one records the carry, so the shot check counts it.\n\n```html v2\n<div class="world"><div class="dot"></div><div class="slot"><p class="label">Ready</p></div></div>\n```\n\n```css v2\n.dot{position:absolute;left:12cqw;top:60cqh;width:7cqmin;height:7cqmin;border-radius:50%;background:var(--p)}.slot{position:absolute;left:62cqw;top:56cqh;width:26cqw;height:16cqh;border-radius:2cqmin;background:var(--card);border:.3cqmin solid var(--line);display:grid;place-items:center}.label{margin:0;font-size:4.4cqmin;font-weight:700}\n```\n\n```js v2\ntl.label("dot", 0)\n .from(".dot", { scale: 0, duration: 0.4, ease: "back.out" }, "dot")\n .from(".slot", { opacity: 0, duration: 0.4, ease: "expo.out" }, "dot+=0.2")\n .hop(".dot", { to: ".slot", height: S.h(14), duration: 0.7 }, 0.9)\n .impact(".slot", 1.55, { squash: 0.06 })\n .from(".label", { y: S.h(2), opacity: 0, duration: 0.4, ease: "expo.out" }, 1.6)\n .camera({ zoom: 1.08, duration: 3, ease: "sine.inOut" }, "dot");\n```\n',
|
|
2677
|
+
"reference/rules/carry-over.md": '# Carry-over\n\n**What:** Something from the last beat becomes part of the next: a title becomes the label of the window that appears around it.\n**When:** Every change of beat inside an act. It is the main thing that makes a film one continuous move instead of slides.\n**Timing:** The hand-off move 0.5\u20130.8 s (expo.inOut); the new container arrives during it, never after.\n**Mistake:** Fading the old element out and a new copy in: the viewer must see the same object travel.\n\n```html\n<div class="world"><div class="win"><div class="bar"><i></i><i></i><i></i></div><div class="body"><p>Hello, Sam.</p><p class="q">What are we shipping today?</p></div></div><div class="center"><p class="name">Orbit Studio</p></div></div>\n```\n\n```css\n.center{position:absolute;inset:0;display:grid;place-items:center}.name{font-size:10cqmin;font-weight:800;white-space:nowrap}.win{position:absolute;left:14cqw;top:14cqh;width:72cqw;height:72cqh;border-radius:2cqmin;background:var(--card);border:.3cqmin solid var(--line);overflow:hidden}.bar{height:8cqh;display:flex;gap:1cqmin;align-items:center;padding-left:2cqmin;border-bottom:.3cqmin solid var(--line)}.bar i{width:1.4cqmin;height:1.4cqmin;border-radius:50%;background:var(--line)}.body{padding:6cqmin;font-size:5cqmin;font-weight:600}.q{color:var(--mut)}\n```\n\n```js\ntl.label("name", 0)\n .from(".name", { opacity: 0, blur: 12, scale: 1.2, duration: 0.6, ease: "expo.out" }, "name")\n .label("hand", 1.3)\n // The name travels into the window\'s title bar; the window grows around it at the same time.\n .to(".name", { x: -S.w(22), y: -S.h(32), scale: 0.32, duration: 0.7, ease: "expo.inOut" }, "hand")\n .from(".win", { scale: 0.7, opacity: 0, blur: 10, duration: 0.7, ease: "expo.inOut" }, "hand")\n .from(".body p", { y: S.h(3), opacity: 0, duration: 0.5, stagger: 0.15, ease: "expo.out" }, "hand+=0.6")\n .camera({ zoom: 1.1, tiltY: -8, duration: 3.4, ease: "sine.inOut" }, "name");\n```\n\n## With `tl.hop`, `tl.gather` and `tl.morph` (version 2)\n\nThe hand-off can be a verb: `tl.hop` carries an element to its next slot on an arc and lands with a squash, `tl.gather` flies many elements into one container, and `tl.morph` turns one box into another. Each one records the carry, so the shot check counts it. A character can hop slot to slot: call `tl.hop` again on the same element and it leaves from where it landed. Add `shadow: true` for a contact shadow that thins out in the air, and mark layers with `data-dof` and call `tl.focus` when the shot should rack focus.\n\n```html v2\n<div class="world"><div class="dot"></div><div class="slot"><p class="label">Ready</p></div></div>\n```\n\n```css v2\n.dot{position:absolute;left:12cqw;top:60cqh;width:7cqmin;height:7cqmin;border-radius:50%;background:var(--p)}.slot{position:absolute;left:62cqw;top:56cqh;width:26cqw;height:16cqh;border-radius:2cqmin;background:var(--card);border:.3cqmin solid var(--line);display:grid;place-items:center}.label{margin:0;font-size:4.4cqmin;font-weight:700}\n```\n\n```js v2\ntl.label("dot", 0)\n .from(".dot", { scale: 0, duration: 0.4, ease: "back.out" }, "dot")\n .from(".slot", { opacity: 0, duration: 0.4, ease: "expo.out" }, "dot+=0.2")\n .hop(".dot", { to: ".slot", height: S.h(14), duration: 0.7 }, 0.9)\n .impact(".slot", 1.55, { squash: 0.06 })\n .from(".label", { y: S.h(2), opacity: 0, duration: 0.4, ease: "expo.out" }, 1.6)\n .camera({ zoom: 1.08, duration: 3, ease: "sine.inOut" }, "dot");\n```\n',
|
|
2678
2678
|
"reference/rules/collide-and-merge.md": '# Collide and merge\n\n**What:** Several elements converge on one point and become one new element.\n**When:** Many-into-one ideas: tools into a platform, steps into a result, voices into one message.\n**Timing:** Gather 0.6\u20130.8 s (expo.in, accelerating into the hit). The merged element pops 0.1 s before they arrive (back.out). A short camera kick (zoom \u22125%) on the hit.\n**Mistake:** The parts arriving one by one and fading: the hit must be one moment.\n\n```html\n<div class="world"><i class="part"></i><i class="part"></i><i class="part"></i><i class="part"></i><i class="part"></i><div class="core">One</div></div>\n```\n\n```css\n.part{position:absolute;left:50%;top:50%;width:9cqmin;height:9cqmin;margin:-4.5cqmin 0 0 -4.5cqmin;border-radius:50%;background:var(--s);box-shadow:0 0 4cqmin var(--s)}.part:nth-child(2n){background:var(--p);box-shadow:0 0 4cqmin var(--p)}.core{position:absolute;left:50%;top:50%;width:24cqmin;height:24cqmin;margin:-12cqmin 0 0 -12cqmin;border-radius:50%;background:var(--p);color:var(--bg);display:grid;place-items:center;font-size:6cqmin;font-weight:800}\n```\n\n```js\nconst spots = [[-32, -20], [30, -24], [-36, 18], [34, 16], [0, 30]];\ntl.set(".core", { scale: 0, opacity: 0 }, 0)\n .set(".part", { x: (i) => S.w(spots[i][0]), y: (i) => S.h(spots[i][1]) }, 0)\n .label("drift", 0)\n .from(".part", { scale: 0, opacity: 0, duration: 0.6, stagger: 0.08, ease: "back.out" }, "drift")\n .to(".part", { y: (i) => S.h(spots[i][1] - 4), duration: 1.6, ease: "sine.inOut" }, "drift+=0.4")\n .label("hit", 2)\n .to(".part", { x: 0, y: 0, scale: 0.4, duration: 0.7, stagger: 0.03, ease: "expo.in" }, "hit")\n .to(".core", { scale: 1, opacity: 1, duration: 0.5, ease: "back.out" }, "hit+=0.6")\n .to(".part", { opacity: 0, duration: 0.15 }, "hit+=0.75")\n .camera({ zoom: 0.95, duration: 0.25, ease: "power2.out" }, "hit+=0.7")\n .camera({ zoom: 1.04, duration: 1.6, ease: "sine.inOut" }, ">");\n```\n',
|
|
2679
2679
|
"reference/rules/count-up.md": '# Count-up\n\n**What:** A number rolls up to its value on digit reels (each digit is a column of 0\u20139 that slides), like an odometer.\n**When:** Stats and counters: "500 templates", "3\xD7 more calls". Rolling digits read as growth; a static number reads as a slide.\n**Timing:** 1.2\u20131.6 s (expo.out), reels staggered 0.1 s from the last digit. Hold the result 0.6\u20131 s with a slow push.\n**Mistake:** Inventing numbers: only count to a real figure from the brief or the user. The value is a `{{param}}`-friendly attribute.\n\n```html\n<div class="world"><div class="center"><div class="stat"><p><span class="num" data-to="500"></span><span class="plus">+</span></p><p class="cap">templates</p></div></div></div>\n```\n\n```css\n.center{position:absolute;inset:0;display:grid;place-items:center}.stat{text-align:center}.stat p:first-child{font-size:22cqmin;font-weight:900;line-height:1;letter-spacing:-.03em}.slot{display:inline-block;height:1em;overflow:hidden;vertical-align:top}.reel{display:flex;flex-direction:column}.reel span{height:1em;line-height:1}.plus{color:var(--p)}.cap{font-size:5cqmin;color:var(--mut);margin-top:1.6cqmin}\n```\n\n```js\n// Build one reel per digit of data-to: 0\u20139 twice, so each reel spins at least once.\nconst host = document.querySelector(".num");\nconst reels = [], stops = [];\nfor (const ch of host.getAttribute("data-to")) {\n const slot = document.createElement("span");\n slot.className = "slot";\n if (/[0-9]/.test(ch)) {\n const reel = document.createElement("span");\n reel.className = "reel";\n for (let k = 0; k < 20; k++) {\n const d = document.createElement("span");\n d.textContent = String(k % 10);\n reel.appendChild(d);\n }\n slot.appendChild(reel);\n reels.push(reel);\n stops.push(10 + Number(ch));\n } else slot.textContent = ch;\n host.appendChild(slot);\n}\ntl.label("count", 0.3)\n .from(".stat", { opacity: 0, blur: 10, scale: 0.9, duration: 0.5, ease: "expo.out" }, 0)\n .to(reels, { y: (i) => -stops[i] * 5 + "%", duration: 1.4, stagger: { each: 0.1, from: "last" }, ease: "expo.out" }, "count")\n .from(".plus", { scale: 0, duration: 0.4, ease: "back.out" }, ">-=0.3")\n .camera({ zoom: 1.1, duration: 3.2, ease: "sine.inOut" }, 0);\n```\n\nEach reel holds 20 digits, so stopping on digit d means moving it up by (10 + d) \xD7 5% of its height.\n',
|
|
2680
2680
|
"reference/rules/depth-stack.md": '# Depth stack\n\n**What:** Flat cards fan out in depth (z) while the camera tilts, so a stack of things becomes a space.\n**When:** Layers of a product, versions, a pile of documents, options to choose from.\n**Timing:** Fan-out 0.8\u20131 s (expo.inOut), stagger 0.05 s. Camera tilt 8\u201320\xB0 over the whole beat, then untilt to resolve.\n**Mistake:** Forgetting `transform-style: preserve-3d` on `.world` and the wrapper: the cards flatten into one plane.\n\n```html\n<div class="world"><div class="center"><div class="stack"><div class="card">Draft</div><div class="card">Review</div><div class="card">Approved</div><div class="card">Live</div></div></div></div>\n```\n\n```css\n.center{position:absolute;inset:0;display:grid;place-items:center}.world,.center,.stack{transform-style:preserve-3d}.stack{display:grid}.card{grid-area:1/1;width:44cqmin;height:28cqmin;border-radius:2cqmin;background:var(--card);border:.3cqmin solid var(--line);display:grid;place-items:center;font-size:5cqmin;font-weight:700}.card:last-child{background:var(--p);color:var(--bg)}\n```\n\n```js\nconst cards = document.querySelectorAll(".card");\ntl.label("stack", 0)\n .from(cards, { y: S.h(6), opacity: 0, duration: 0.5, stagger: 0.06, ease: "expo.out" }, "stack")\n .label("fan", 0.9)\n .to(cards, { z: (i) => S.min((i - 3) * 16), x: (i) => S.w((i - 1.5) * 6), y: (i) => -S.h((i - 1.5) * 5), duration: 0.9, stagger: 0.05, ease: "expo.inOut" }, "fan")\n .camera({ tiltX: 18, tiltY: -24, zoom: 0.95, duration: 1.4, ease: "power3.inOut" }, "fan")\n .camera({ tiltX: 0, tiltY: 0, zoom: 1.05, duration: 1.2, ease: "power3.inOut" }, "fan+=1.9");\n```\n',
|
|
@@ -2719,7 +2719,7 @@ For comparison, a recent slide-like film made by the assistant (centred cards, a
|
|
|
2719
2719
|
{
|
|
2720
2720
|
"name": "sequence-core",
|
|
2721
2721
|
"description": "The contract for compose_sequence acts \u2014 the stage kit API (tl, S, camera), positions, units, params, the hand-off between acts, and the rules every sequence must pass. Load it before writing your first act; load motion for the techniques.",
|
|
2722
|
-
"instructions": '# Sequence core\n\nA motion-graphics film is a few **acts**. Each act is one `compose_sequence` call: one continuous animation on **one timeline**, 8\u201325 s long, with the beats marked as labels. Elements stay on screen and change from beat to beat, and the camera keeps moving. This file is the contract for what you write. The `motion` skill has the techniques.\n\n## What you write\n\n`compose_sequence` takes four things, and wraps them in the film\'s stage (colours, fonts, the root):\n\n- **`markup`**: the act\'s elements.\n - Everything the camera sees goes inside `<div class="world">\u2026</div>`.\n - Put a `<div class="hud">\u2026</div>` beside it, not inside, for anything that must stay put while the camera moves (a caption bar, a small logo, a progress rail).\n- **`css`**: the look of those elements.\n- **`script`**: the body of `stage.build((tl, S) => { \u2026 })`. Write the body only, not the call.\n- **`durationSeconds`**: the act\'s length. The timeline must end within it. End it on a slow drift (a camera move, a breathing accent), so the act is still moving at its last frame.\n\n`params` (optional): `{{name}}` in the markup becomes an editable field in the editor, filled from `params`. Use it for the words people will want to change. Give each param a meaningful name (`headline`, `step2`, `cta`), not `a`, `b`, `c`. Values are inserted as text.\n\nThe stage gives you these CSS variables, from `plan_film`:\n\n| Variable | What it is |\n|---|---|\n| `--bg` | Background |\n| `--ink` | Text |\n| `--mut` | Muted text |\n| `--p` | The accent |\n| `--s` | The second accent |\n| `--card` | A card surface |\n| `--line` | Hairlines |\n| `--onp` | Text on the accent |\n\nIt also gives you two fonts: the body font on the root, and the display font on `.k-display`. Any other font family you name in the CSS is added to the project for you. Static helpers `k-fill` (fill the frame) and `k-cover` (a cover-fit picture) work. The animated `k-*` classes (`k-rise`, `k-pop`, `k-cam`, \u2026) belong to `design_scene` and are refused here: in a sequence every move is on the timeline.\n\n## The timeline: `tl`\n\nAll times are in **seconds**. Every method returns `tl`, so calls chain.\n\n| Call | Does |\n|---|---|\n| `tl.to(targets, vars, position)` | Animate from the current state to `vars` |\n| `tl.from(targets, vars, position)` | Animate from `vars` to the current (CSS) state: the usual entrance |\n| `tl.fromTo(targets, fromVars, toVars, position)` | Both ends explicit |\n| `tl.set(targets, vars, position)` | Change instantly at that time (a word swap, a caret jump, a hard cut) |\n| `tl.label(name, position)` | Mark a beat. Define a label before you use it in a position |\n| `tl.camera(vars, position)` | Move the camera (the `.world` layer). See below |\n\n**Targets**: a CSS selector (`".card"`), an element, or a list of elements (`S.split(\u2026)` returns one).\n\n**Vars**:\n\n- **Transform:** `x`, `y` (always through `S.w` / `S.h` / `S.min`), `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY` (degrees).\n- **Look:** `opacity`, `blur` (px, mapped to `filter: blur()`), `color`, `backgroundColor`, `borderRadius`, `width`, `height`, `clipPath` (strings like `"inset(0 100% 0 0)"`), CSS variables (`"--n": 10`).\n- **Keyframes:** an array (`x: [0, S.w(10), 0]`).\n- **Timing:** `duration` (default 0.6), `delay`, `ease`, and `stagger` (seconds between targets, or `{ each, from: "first" | "center" | "last" }`).\n\nAny value can be a function `(i, el) => value`.\n\n**Eases**:\n\n| Ease | Use it for |\n|---|---|\n| `"expo.out"` | Arrivals |\n| `"expo.in"` | Exits |\n| `"power3.inOut"` / `"expo.inOut"` | Moving between places |\n| `"back.out"` | Pops |\n| `"elastic.out"` | Wobbly landings |\n| `"sine.inOut"` | Drift |\n| `"linear"` | Constant rates |\n| `"steps(n)"` | Ticking, typing |\n\n`power1`\u2013`power4`, `circ`, `bounce` and anime.js names (`"out(3)"`) also work.\n\n**Positions**:\n\n| Position | Means |\n|---|---|\n| `2.4` | At that second |\n| `"hook"` | At the label |\n| `"hook+=0.3"` / `"hook-=0.2"` | Offset from the label |\n| `"<"` | With the previous step |\n| `">"` | After the previous step |\n| `"<+=0.2"` | Shortly after the previous step starts |\n| `"+=0.5"` | 0.5 s after everything so far |\n| *(none)* | At the end of everything so far |\n\n## The camera\n\n`tl.camera({ x, y, zoom, rotate, tiltX, tiltY, duration, ease }, position)` moves `.world`:\n\n- `zoom` is scale;\n- `tiltX` / `tiltY` rotate it in depth, with perspective already set;\n- the default is a 1.2 s `power2.inOut`-style ease.\n\nZoom pivots on `.world`\'s transform origin (the centre). To push into a corner, first `tl.set(".world", { transformOrigin: "70% 30%" }, t)`. A camera move with `duration: 0.01` is an instant reset, which hides a cut.\n\n## Continuous shot (version 2)\n\nPut `data-stage-version="2"` on the stage root to turn on the operated camera and the carry verbs. Without it everything above behaves as before. With it, `tl.camera` keys a camera track that follow, handheld and shake add to (zoom is interpolated in log space, so 1 to 100 reads 10 halfway), and these calls exist. Times are seconds, sizes are stage pixels (use `S.*`), and every call returns `tl`.\n\n**Carry one subject from beat to beat**\n\n| Call | Does |\n|---|---|\n| `tl.morph(from, to, { duration, ease, content, radius }, position)` | The `from` element\'s box, radius and colour become `to`\'s, and `to`\'s content rises in over the last 40 %. The two hand over on the last frame with no swap. `from` must be `position: absolute` or `relative` |\n| `tl.iris(target, { from, duration, ease, edge }, position)` | A circular clip opens from the centre of `from` until `target` covers the stage |\n| `tl.zoomThrough(target, { fit: "cover" \\| "contain", into, duration, ease }, position)` | Pushes the camera into `target` until it fills the frame. With `into` (an element outside `.world`), that scene takes over on the last frame and the camera resets |\n| `tl.hop(target, { to, height, duration, squash }, position)` | An arc to another element\'s slot, landing with a squash |\n| `tl.gather(targets, { into, arc, stagger, spin, duration }, position)` | Many elements fly on fanned arcs into one box |\n| `tl.impact(target, position, { squash, duration })` | A squash and spring-back, and a hit |\n| `tl.press(target, time)` | A pressed-button reaction, and a click hit |\n| `tl.pointer({ path, clicks, look })` | A cursor that passes through every `{ t, x, y }` knot on a smooth path and dips on each click time. `S.pointerPath(log)` builds the path from a recorded click log |\n\nElements these verbs drive keep their own `transform` and `opacity` for the verb\'s duration: don\'t animate the same properties with `tl.to`. Ease `"spring(zeta, omega)"` (defaults 0.55, 14) is a damped spring that has settled by the end of the tween.\n\n**Operate the camera**\n\n| Call | Does |\n|---|---|\n| `tl.camera({ anchor, at: [x%, y%], zoom, duration, ease }, position)` | Keys the zoom in screen space: the anchor element stays at that screen position, at any zoom. Use it for every zoom above 50\xD7 |\n| `tl.follow(target, { lead, from, to }, position)` | The camera keeps `target` near the centre, leading it by `lead` seconds (default 0.1) |\n| `tl.whip(target, { duration }, position)` | A fast expo snap that lands with `target` centred |\n| `tl.handheld({ amount }, from, to)` | A slow float, exactly zero outside its range and inside declared rests |\n| `tl.shake(position, { amp, decay, seed })` | A deterministic hit shake in screen space (the same size at any zoom), recorded as a hit |\n\n`data-depth="d"` on a child of `.world` gives parallax from the one camera (a layer moves 1 / (1 + d) as far as depth 0). `data-lod="minPx,maxPx"` fades an element in by its size on screen and drops it when it is off-frame.\n\n**Say what the shot does**\n\n| Call | Does |\n|---|---|\n| `tl.hit(name, position, { pan, kind })` | Marks a sound moment (`kind`: `impact`, `click`, `whoosh`, `land`, `shake`). Shakes, presses, impacts, pointer clicks and carry landings add their own |\n| `tl.rest(from, to, { allow })` | Declares a dead-still stretch. Only elements in `allow` may move, and the camera layers are zero. The build fails, naming what moved, if anything else does |\n| `tl.cut(position)` | Marks a deliberate hard cut |\n\nThe stage root reports `data-stage-carries`, `data-stage-events`, `data-stage-rests` and `data-stage-cuts` as JSON, for the checks and for placing sound.\n\n**Check the shot**\n\nMark every beat with `tl.label(name, time)`: labels are the beats the check looks at. Put `data-keep-in-frame="t0,t1"` on a subject that must stay on screen (add `,whole` to also forbid it being cut off by the frame edge). Then run `check_sequence` (when the tool is offered). For each label it compares the frame 0.2 s before and after and reports the beat as **carried** (something survives and moves, scales, or the camera moves with it), **anchored** (something large stays put) or **bare** (nothing survives), with a hint naming what left and what arrived. It also warns about subjects that leave the frame, beats spaced too evenly, and hard cuts outside a burst of cuts. A bare beat is fixed by carrying an element across it (`tl.morph`, `tl.hop`, `tl.zoomThrough`, `tl.gather`). Beats at `tl.cut` times, bursts of three or more beats within 1.5 s, and the last 1.2 s are not scored. Warnings never block an export.\n\n## The stage helpers: `S`\n\n| Helper | Gives |\n|---|---|\n| `S.w(p)`, `S.h(p)`, `S.min(p)` | `p` percent of the stage\'s width, height or shorter side, in pixels. Use these for every x, y and size in the script |\n| `S.width`, `S.height`, `S.portrait` | The stage size, and whether it is portrait |\n| `S.split(target, "words" \\| "chars")` | Wraps each word or character in a span (`.word` / `.char`). Returns them in reading order. Styled children (`<em>`) stay, with their words split inside |\n| `S.rand(seed)` | A repeatable random function: `const r = S.rand(7); r()` gives 0\u20131, the same numbers on every render |\n| `S.rect(el)`, `S.center(el)` | An element\'s resting box `{ x, y, w, h }` and centre `{ x, y }` in stage pixels (version 2) |\n| `S.grid({ depth, spacing, dot })` | A faint dot lattice behind the scene that keeps its dot size at any zoom, so camera moves read on an empty ground (version 2) |\n| `S.pointerPath(log)` | Turns a recorded click log `[{ t, x, y, type }]` into `{ path, clicks }` for `tl.pointer` (version 2) |\n| `S.beats`, `S.beat(n)` | The music\'s beat grid in seconds, when the film has one (otherwise `S.beat(n)` is n \xD7 0.5 s) |\n| `S.downbeats`, `S.downbeat(n)` | The first beat of each bar in seconds, from `data-downbeats` (otherwise `S.downbeat(n)` is n \xD7 2 s). Land a carry on `S.downbeat(n)` to put it on the music |\n\n## Rules (compose_sequence checks them)\n\nEach rule protects one of two things: every frame must be the same however often and in whatever order it is rendered (preview, scrubbing, export), and the act must fit any frame size.\n\n| Rule | Instead |\n|---|---|\n| `random`: no `Math.random()` | `S.rand(seed)` |\n| `clock`: no `Date`, `performance.now()` | Timeline positions |\n| `async-loop`: no `requestAnimationFrame`, `setTimeout`, `setInterval` | Put every change on the timeline. Frames are sought, never played |\n| `network`: no `fetch`, `XMLHttpRequest`, `import()` | Pictures are `asset:ID` references |\n| `infinite`: no `repeat: -1`, `loop: true` | Repeat a finite number of times within the act |\n| `owned-hooks`: no `__timelines`, `__miraiclipSeek`\u2026 | `stage.build` only |\n| `gsap`: GSAP isn\'t available | The `tl` API above |\n| `units`: no `vw`, `vh`, `vmin` | `cqw`, `cqh`, `cqmin` in CSS; `S.w/h/min` in the script |\n| `fixed`: no `position: fixed` | `position: absolute` inside the stage |\n| `external-script`: no `<script src>` | Everything inline |\n| `build`: `stage.build` exactly once | One timeline per act |\n| `version`: continuous-shot verbs need `data-stage-version="2"` | Add it to the stage root |\n| `carry-target`: a verb names an element the markup doesn\'t have | Add the element or fix the selector |\n| `zoom-target`: `zoomThrough` target outside `.world` | Move it inside the world |\n| `duration`, `libs` | compose_sequence sets these for you |\n\nWarnings (fix them):\n\n- `pixels`: a raw number over 40 for x, y, left, top, width or height won\'t fit another frame size. Use `S.*`.\n- `zoom-anchor`: a zoom above 50\xD7 without an `anchor` drifts off its subject. Key it in screen space.\n- `verb-transform`: a CSS `transform` on an element that `morph`, `hop`, `gather`, `impact` or `press` drives is replaced. Put it on a wrapper.\n- `engine`: calling anime.js directly bypasses the kit\'s deterministic seeking. Use `tl`.\n- `big-blur`: a large layer (30% of the frame or more) with `filter: blur()` can render with tile-shaped artifacts while the camera tilts. Make soft glows and colour blooms with a `radial-gradient(circle, <colour>, transparent 65%)` instead. Blur on small elements (an entrance focus-in, motion blur on a word) is fine.\n\nThe tool also runs the act once before placing it. A script error, or a timeline longer than the act, comes back as an error with the reason, and nothing is placed.\n\n## Things that trip people up\n\n- **The timeline owns `transform`** on every element it animates: a CSS `transform` on that element is replaced. Centre with a grid or flex wrapper, `inset` and margins, or put the CSS transform on a wrapper and animate the child.\n- **`from` animates to the CSS state.** Write the CSS for where the element should *end up*, then `tl.from` its entrance.\n- **Elements that appear later** start hidden with `tl.set(el, { opacity: 0 }, 0)`, or with a `from` at their beat. CSS `opacity: 0` with a later `to` also works. Nothing should flash at frame 0 by accident.\n- **Colours** tween to real values (`"#ffcf5a"`, `"rgb(\u2026)"`), not `var(--p)`. Read the plan\'s hex values.\n- **Measuring** (`el.offsetWidth`, `getBoundingClientRect()`) is fine: the script runs after fonts load.\n- **Keep the DOM fixed.** Create elements in the markup, or with `S.split`, before the timeline. Don\'t add or remove elements during it: use `opacity` and `tl.set`.\n\n## Hand-off between acts\n\nActs join with a 0.3 s dissolve by default. Design the dissolve away:\n\n- The next act\'s first frame starts where the last act\'s final frame ended: the same element, in the same place, at the same size.\n- Then it moves on. The orb that became the logo at the end of act 1 is the logo at the start of act 2.\n- Use `join: "cut"` when both sides hide the cut themselves (a blur-through or a whip on each side).\n- Use `join: "dip"` only at a real chapter break.\n\n## The loop\n\n1. Write the act\'s beats (motion skill, `reference/timing.md`).\n2. Compose it.\n3. Look at the filmstrip compose_sequence returns, and read its problems:\n - layout at each beat;\n - a timeline that ends early;\n - too few beats;\n - a still camera.\n4. Fix with `replace: "<act id>"`, which keeps the act\'s place and length.\n5. Then compose the next act.\n\n`preview_film` shows the whole film when every act is in.'
|
|
2722
|
+
"instructions": '# Sequence core\n\nA motion-graphics film is a few **acts**. Each act is one `compose_sequence` call: one continuous animation on **one timeline**, 8\u201325 s long, with the beats marked as labels. Elements stay on screen and change from beat to beat, and the camera keeps moving. This file is the contract for what you write. The `motion` skill has the techniques.\n\n## What you write\n\n`compose_sequence` takes four things, and wraps them in the film\'s stage (colours, fonts, the root):\n\n- **`markup`**: the act\'s elements.\n - Everything the camera sees goes inside `<div class="world">\u2026</div>`.\n - Put a `<div class="hud">\u2026</div>` beside it, not inside, for anything that must stay put while the camera moves (a caption bar, a small logo, a progress rail).\n- **`css`**: the look of those elements.\n- **`script`**: the body of `stage.build((tl, S) => { \u2026 })`. Write the body only, not the call.\n- **`durationSeconds`**: the act\'s length. The timeline must end within it. End it on a slow drift (a camera move, a breathing accent), so the act is still moving at its last frame.\n\n`params` (optional): `{{name}}` in the markup becomes an editable field in the editor, filled from `params`. Use it for the words people will want to change. Give each param a meaningful name (`headline`, `step2`, `cta`), not `a`, `b`, `c`. Values are inserted as text.\n\nThe stage gives you these CSS variables, from `plan_film`:\n\n| Variable | What it is |\n|---|---|\n| `--bg` | Background |\n| `--ink` | Text |\n| `--mut` | Muted text |\n| `--p` | The accent |\n| `--s` | The second accent |\n| `--card` | A card surface |\n| `--line` | Hairlines |\n| `--onp` | Text on the accent |\n\nIt also gives you two fonts: the body font on the root, and the display font on `.k-display`. Any other font family you name in the CSS is added to the project for you. Static helpers `k-fill` (fill the frame) and `k-cover` (a cover-fit picture) work. The animated `k-*` classes (`k-rise`, `k-pop`, `k-cam`, \u2026) belong to `design_scene` and are refused here: in a sequence every move is on the timeline.\n\n## The timeline: `tl`\n\nAll times are in **seconds**. Every method returns `tl`, so calls chain.\n\n| Call | Does |\n|---|---|\n| `tl.to(targets, vars, position)` | Animate from the current state to `vars` |\n| `tl.from(targets, vars, position)` | Animate from `vars` to the current (CSS) state: the usual entrance |\n| `tl.fromTo(targets, fromVars, toVars, position)` | Both ends explicit |\n| `tl.set(targets, vars, position)` | Change instantly at that time (a word swap, a caret jump, a hard cut) |\n| `tl.label(name, position)` | Mark a beat. Define a label before you use it in a position |\n| `tl.camera(vars, position)` | Move the camera (the `.world` layer). See below |\n\n**Targets**: a CSS selector (`".card"`), an element, or a list of elements (`S.split(\u2026)` returns one).\n\n**Vars**:\n\n- **Transform:** `x`, `y` (always through `S.w` / `S.h` / `S.min`), `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY` (degrees).\n- **Look:** `opacity`, `blur` (px, mapped to `filter: blur()`), `color`, `backgroundColor`, `borderRadius`, `width`, `height`, `clipPath` (strings like `"inset(0 100% 0 0)"`), CSS variables (`"--n": 10`).\n- **Keyframes:** an array (`x: [0, S.w(10), 0]`).\n- **Timing:** `duration` (default 0.6), `delay`, `ease`, and `stagger` (seconds between targets, or `{ each, from: "first" | "center" | "last" }`).\n\nAny value can be a function `(i, el) => value`.\n\n**Eases**:\n\n| Ease | Use it for |\n|---|---|\n| `"expo.out"` | Arrivals |\n| `"expo.in"` | Exits |\n| `"power3.inOut"` / `"expo.inOut"` | Moving between places |\n| `"back.out"` | Pops |\n| `"elastic.out"` | Wobbly landings |\n| `"sine.inOut"` | Drift |\n| `"linear"` | Constant rates |\n| `"steps(n)"` | Ticking, typing |\n\n`power1`\u2013`power4`, `circ`, `bounce` and anime.js names (`"out(3)"`) also work.\n\n**Positions**:\n\n| Position | Means |\n|---|---|\n| `2.4` | At that second |\n| `"hook"` | At the label |\n| `"hook+=0.3"` / `"hook-=0.2"` | Offset from the label |\n| `"<"` | With the previous step |\n| `">"` | After the previous step |\n| `"<+=0.2"` | Shortly after the previous step starts |\n| `"+=0.5"` | 0.5 s after everything so far |\n| *(none)* | At the end of everything so far |\n\n## The camera\n\n`tl.camera({ x, y, zoom, rotate, tiltX, tiltY, duration, ease }, position)` moves `.world`:\n\n- `zoom` is scale;\n- `tiltX` / `tiltY` rotate it in depth, with perspective already set;\n- the default is a 1.2 s `power2.inOut`-style ease.\n\nZoom pivots on `.world`\'s transform origin (the centre). To push into a corner, first `tl.set(".world", { transformOrigin: "70% 30%" }, t)`. A camera move with `duration: 0.01` is an instant reset, which hides a cut.\n\n## Continuous shot (version 2)\n\nPut `data-stage-version="2"` on the stage root to turn on the operated camera and the carry verbs. Without it everything above behaves as before. With it, `tl.camera` keys a camera track that follow, handheld and shake add to (zoom is interpolated in log space, so 1 to 100 reads 10 halfway), and these calls exist. Times are seconds, sizes are stage pixels (use `S.*`), and every call returns `tl`.\n\n**Carry one subject from beat to beat**\n\n| Call | Does |\n|---|---|\n| `tl.morph(from, to, { duration, ease, content, radius }, position)` | The `from` element\'s box, radius and colour become `to`\'s, and `to`\'s content rises in over the last 40 %. The two hand over on the last frame with no swap. `from` must be `position: absolute` or `relative` |\n| `tl.iris(target, { from, duration, ease, edge }, position)` | A circular clip opens from the centre of `from` until `target` covers the stage |\n| `tl.zoomThrough(target, { fit: "cover" \\| "contain", into, duration, ease }, position)` | Pushes the camera into `target` until it fills the frame. With `into` (an element outside `.world`), that scene takes over on the last frame and the camera resets |\n| `tl.hop(target, { to, height, duration, squash, shadow }, position)` | An arc to another element\'s slot, landing with a squash. Hop the same element again and it starts where it landed (a hop may not start before the last one ends). `shadow: true` (or its options) adds a contact shadow |\n| `tl.shadow(target, { width, height, opacity, spread, offset, lift, color })` | A soft contact shadow on the ground under the element: it follows hops along the ground, grows faint and wide as the element lifts, and fades with the element\'s opacity. `lift` sets a fixed hover height. The element\'s parent must be positioned |\n| `tl.frame(fn)` | Calls `fn(t)` with the time in seconds on every seek, after the timeline. Paint a `<canvas>` here when a scene is too dense for DOM elements (thousands of tiles): draw the whole frame from `t` alone, with no state from earlier frames |\n| `tl.focus(target \\| depth \\| null, { blur, max, duration, ease }, position)` | Depth of field. Elements marked `data-dof="<depth>"` blur by `blur` px for each depth unit away from the focus depth (capped at `max`). Call it again to rack the focus; `null` puts everything back in focus. Put `data-dof` on leaf elements, never on a `preserve-3d` container, because a filter flattens it |\n| `tl.gather(targets, { into, arc, stagger, spin, duration }, position)` | Many elements fly on fanned arcs into one box |\n| `tl.impact(target, position, { squash, duration })` | A squash and spring-back, and a hit |\n| `tl.press(target, time)` | A pressed-button reaction, and a click hit |\n| `tl.pointer({ path, clicks, look })` | A cursor that passes through every `{ t, x, y }` knot on a smooth path and dips on each click time. `S.pointerPath(log)` builds the path from a recorded click log |\n\nElements these verbs drive keep their own `transform` and `opacity` for the verb\'s duration: don\'t animate the same properties with `tl.to`. Ease `"spring(zeta, omega)"` (defaults 0.55, 14) is a damped spring that has settled by the end of the tween.\n\n**Operate the camera**\n\n| Call | Does |\n|---|---|\n| `tl.camera({ anchor, at: [x%, y%], zoom, duration, ease }, position)` | Keys the zoom in screen space: the anchor element stays at that screen position, at any zoom. Use it for every zoom above 50\xD7 |\n| `tl.follow(target, { lead, from, to }, position)` | The camera keeps `target` near the centre, leading it by `lead` seconds (default 0.1) |\n| `tl.whip(target, { duration }, position)` | A fast expo snap that lands with `target` centred |\n| `tl.handheld({ amount }, from, to)` | A slow float, exactly zero outside its range and inside declared rests |\n| `tl.shake(position, { amp, decay, seed })` | A deterministic hit shake in screen space (the same size at any zoom), recorded as a hit |\n\n`data-depth="d"` on a child of `.world` gives parallax from the one camera (a layer moves 1 / (1 + d) as far as depth 0). `data-lod="minPx,maxPx"` fades an element in by its size on screen and drops it when it is off-frame.\n\n**Say what the shot does**\n\n| Call | Does |\n|---|---|\n| `tl.hit(name, position, { pan, kind })` | Marks a sound moment (`kind`: `impact`, `click`, `whoosh`, `land`, `shake`). Shakes, presses, impacts, pointer clicks and carry landings add their own |\n| `tl.rest(from, to, { allow })` | Declares a dead-still stretch. Only elements in `allow` may move, and the camera layers are zero. The build fails, naming what moved, if anything else does |\n| `tl.cut(position)` | Marks a deliberate hard cut |\n\nThe stage root reports `data-stage-carries`, `data-stage-events`, `data-stage-rests` and `data-stage-cuts` as JSON, for the checks and for placing sound.\n\n**Check the shot**\n\nMark every beat with `tl.label(name, time)`: labels are the beats the check looks at. Put `data-keep-in-frame="t0,t1"` on a subject that must stay on screen (add `,whole` to also forbid it being cut off by the frame edge). Then run `check_sequence` (when the tool is offered). For each label it compares the frame 0.2 s before and after and reports the beat as **carried** (something survives and moves, scales, or the camera moves with it), **anchored** (something large stays put) or **bare** (nothing survives), with a hint naming what left and what arrived. It also warns about subjects that leave the frame, beats spaced too evenly, and hard cuts outside a burst of cuts. A bare beat is fixed by carrying an element across it (`tl.morph`, `tl.hop`, `tl.zoomThrough`, `tl.gather`). Beats at `tl.cut` times, bursts of three or more beats within 1.5 s, and the last 1.2 s are not scored. Warnings never block an export.\n\n## The stage helpers: `S`\n\n| Helper | Gives |\n|---|---|\n| `S.w(p)`, `S.h(p)`, `S.min(p)` | `p` percent of the stage\'s width, height or shorter side, in pixels. Use these for every x, y and size in the script |\n| `S.width`, `S.height`, `S.portrait` | The stage size, and whether it is portrait |\n| `S.split(target, "words" \\| "chars")` | Wraps each word or character in a span (`.word` / `.char`). Returns them in reading order. Styled children (`<em>`) stay, with their words split inside |\n| `S.rand(seed)` | A repeatable random function: `const r = S.rand(7); r()` gives 0\u20131, the same numbers on every render |\n| `S.rect(el)`, `S.center(el)` | An element\'s resting box `{ x, y, w, h }` and centre `{ x, y }` in stage pixels (version 2) |\n| `S.grid({ depth, spacing, dot })` | A faint dot lattice behind the scene that keeps its dot size at any zoom, so camera moves read on an empty ground (version 2) |\n| `S.pointerPath(log)` | Turns a recorded click log `[{ t, x, y, type }]` into `{ path, clicks }` for `tl.pointer` (version 2) |\n| `S.beats`, `S.beat(n)` | The music\'s beat grid in seconds, when the film has one (otherwise `S.beat(n)` is n \xD7 0.5 s) |\n| `S.downbeats`, `S.downbeat(n)` | The first beat of each bar in seconds, from `data-downbeats` (otherwise `S.downbeat(n)` is n \xD7 2 s). Land a carry on `S.downbeat(n)` to put it on the music |\n\n## Rules (compose_sequence checks them)\n\nEach rule protects one of two things: every frame must be the same however often and in whatever order it is rendered (preview, scrubbing, export), and the act must fit any frame size.\n\n| Rule | Instead |\n|---|---|\n| `random`: no `Math.random()` | `S.rand(seed)` |\n| `clock`: no `Date`, `performance.now()` | Timeline positions |\n| `async-loop`: no `requestAnimationFrame`, `setTimeout`, `setInterval` | Put every change on the timeline. Frames are sought, never played |\n| `network`: no `fetch`, `XMLHttpRequest`, `import()` | Pictures are `asset:ID` references |\n| `infinite`: no `repeat: -1`, `loop: true` | Repeat a finite number of times within the act |\n| `owned-hooks`: no `__timelines`, `__miraiclipSeek`\u2026 | `stage.build` only |\n| `gsap`: GSAP isn\'t available | The `tl` API above |\n| `units`: no `vw`, `vh`, `vmin` | `cqw`, `cqh`, `cqmin` in CSS; `S.w/h/min` in the script |\n| `fixed`: no `position: fixed` | `position: absolute` inside the stage |\n| `external-script`: no `<script src>` | Everything inline |\n| `build`: `stage.build` exactly once | One timeline per act |\n| `version`: continuous-shot verbs need `data-stage-version="2"` | Add it to the stage root |\n| `carry-target`: a verb names an element the markup doesn\'t have | Add the element or fix the selector |\n| `zoom-target`: `zoomThrough` target outside `.world` | Move it inside the world |\n| `duration`, `libs` | compose_sequence sets these for you |\n\nWarnings (fix them):\n\n- `pixels`: a raw number over 40 for x, y, left, top, width or height won\'t fit another frame size. Use `S.*`.\n- `zoom-anchor`: a zoom above 50\xD7 without an `anchor` drifts off its subject. Key it in screen space.\n- `verb-transform`: a CSS `transform` on an element that `morph`, `hop`, `gather`, `impact` or `press` drives is replaced. Put it on a wrapper.\n- `engine`: calling anime.js directly bypasses the kit\'s deterministic seeking. Use `tl`.\n- `big-blur`: a large layer (30% of the frame or more) with `filter: blur()` can render with tile-shaped artifacts while the camera tilts. Make soft glows and colour blooms with a `radial-gradient(circle, <colour>, transparent 65%)` instead. Blur on small elements (an entrance focus-in, motion blur on a word) is fine.\n\nThe tool also runs the act once before placing it. A script error, or a timeline longer than the act, comes back as an error with the reason, and nothing is placed.\n\n## Things that trip people up\n\n- **The timeline owns `transform`** on every element it animates: a CSS `transform` on that element is replaced. Centre with a grid or flex wrapper, `inset` and margins, or put the CSS transform on a wrapper and animate the child.\n- **`from` animates to the CSS state.** Write the CSS for where the element should *end up*, then `tl.from` its entrance.\n- **Elements that appear later** start hidden with `tl.set(el, { opacity: 0 }, 0)`, or with a `from` at their beat. CSS `opacity: 0` with a later `to` also works. Nothing should flash at frame 0 by accident.\n- **Colours** tween to real values (`"#ffcf5a"`, `"rgb(\u2026)"`), not `var(--p)`. Read the plan\'s hex values.\n- **Measuring** (`el.offsetWidth`, `getBoundingClientRect()`) is fine: the script runs after fonts load.\n- **Keep the DOM fixed.** Create elements in the markup, or with `S.split`, before the timeline. Don\'t add or remove elements during it: use `opacity` and `tl.set`.\n\n## Hand-off between acts\n\nActs join with a 0.3 s dissolve by default. Design the dissolve away:\n\n- The next act\'s first frame starts where the last act\'s final frame ended: the same element, in the same place, at the same size.\n- Then it moves on. The orb that became the logo at the end of act 1 is the logo at the start of act 2.\n- Use `join: "cut"` when both sides hide the cut themselves (a blur-through or a whip on each side).\n- Use `join: "dip"` only at a real chapter break.\n\n## The loop\n\n1. Write the act\'s beats (motion skill, `reference/timing.md`).\n2. Compose it.\n3. Look at the filmstrip compose_sequence returns, and read its problems:\n - layout at each beat;\n - a timeline that ends early;\n - too few beats;\n - a still camera.\n4. Fix with `replace: "<act id>"`, which keeps the act\'s place and length.\n5. Then compose the next act.\n\n`preview_film` shows the whole film when every act is in.'
|
|
2723
2723
|
},
|
|
2724
2724
|
{
|
|
2725
2725
|
"name": "talking-head",
|