mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 01:56:04 +00:00
feat(registry): bring back the video-primitive moves (#3169)
Restores the 208 catalog items reverted after their previews 404'd in production, this time on the payload mechanism rather than the .html files that caused the outage. The generator no longer writes a preview document to docs/public. That writer, and the machinery under it, existed only to produce files the docs host discards, so it is gone rather than bypassed. Items now embed the composition itself via a payload, which is what the previous change already does for the items that were already in the catalog. The variables explorer is parked, not restored: it drove its preview through the same unpublished .html path, so it would have shown an empty frame. Items that declare variables get the live player plus the static variables table, and reconnecting the explorer to payloads is a follow-up.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# per-word-rise
|
||||
|
||||
Words or characters rise into place in a controlled blur-to-sharp cascade, settle with a short decel tail on landing, and hold truly still until the frame cuts.
|
||||
|
||||
3.5s authored, elastic HOLD, exit `none` by default.
|
||||
|
||||
## Variables
|
||||
|
||||
| id | type | default | notes |
|
||||
| -------- | ------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `text` | string | `WORDS IN MOTION` | The displayed line. Font size auto-fits to character count. |
|
||||
| `split` | enum | `word` | `word` or `char`: the unit that rises. |
|
||||
| `cues` | string | `` (empty) | Comma-separated seconds (from mount start) for each unit's landing, e.g. `0.4,0.8,1.6`. Units beyond the list extrapolate at the list's own gap. Empty keeps the authored cascade. |
|
||||
| `accent` | enum | `green` | Text color: green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`. |
|
||||
| `exit` | enum | `none` | `none` holds until the cut; `fade` departs opacity-only; `up` is the staggered upward fade (the pre-Wave-J default ending). |
|
||||
|
||||
## Mount
|
||||
|
||||
```html
|
||||
<div
|
||||
class="clip"
|
||||
data-composition-id="per-word-rise"
|
||||
data-composition-src="./per-word-rise.html"
|
||||
data-variable-values='{"text":"SHIP IT TODAY","split":"word","cues":"0.5,1.0,1.5"}'
|
||||
data-start="0"
|
||||
data-duration="3.5"
|
||||
data-track-index="0"
|
||||
></div>
|
||||
```
|
||||
|
||||
Elastic root: no `data-width`/`data-height`; it fills whatever box the host clip gives it. Timeline registers under the literal `per-word-rise` key.
|
||||
|
||||
## Notes
|
||||
|
||||
- Cues are landing times: each unit's rise ends on its cue (start compresses toward 0 for early cues). Landings clamp ahead of any exit.
|
||||
- Deterministic and seek-safe; the hold is truly still (no drift), and every landing ends with a short overshoot-free settle tail instead of a dead stop.
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
per-word-rise demo. This standalone composition mounts per-word-rise.html
|
||||
through data-composition-src. Non-default text, split, and accent values
|
||||
prove the variable path while the primitive remains elastic to its host.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Per Word Rise Demo</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: var(--bg, #0b0c0e);
|
||||
}
|
||||
|
||||
#root {
|
||||
--bg: #0b0c0e;
|
||||
--fg: #f8fafc;
|
||||
--muted: #94a3b8;
|
||||
--surface: #14171c;
|
||||
--border: #343a46;
|
||||
--brand: #71f5a7;
|
||||
--accent: #61a8ff;
|
||||
--accent-2: #c5a3ff;
|
||||
--font-display: Inter, system-ui, sans-serif;
|
||||
--font-body: Inter, system-ui, sans-serif;
|
||||
--font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
--radius: 3cqmin;
|
||||
--space-1: 1.8cqw;
|
||||
--space-2: 2.8cqh;
|
||||
--space-3: 4cqh;
|
||||
--dur-beat: 0.5s;
|
||||
--ease-standard: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
--ease-emphasis: cubic-bezier(0.19, 1, 0.22, 1);
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: radial-gradient(
|
||||
ellipse at 50% 42%,
|
||||
color-mix(in srgb, var(--surface) 88%, var(--accent-2)) 0%,
|
||||
var(--bg) 72%
|
||||
);
|
||||
}
|
||||
|
||||
.mount-card {
|
||||
width: 76%;
|
||||
height: 50%;
|
||||
left: 12%;
|
||||
top: 25%;
|
||||
container-type: size;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--border) 72%, transparent);
|
||||
border-radius: calc(var(--radius) * 1.3);
|
||||
background: var(--bg);
|
||||
box-shadow: 0 4cqh 12cqh color-mix(in srgb, var(--bg) 68%, transparent);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="per-word-rise-demo"
|
||||
data-start="0"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
data-duration="3.5"
|
||||
data-fps="30"
|
||||
>
|
||||
<div
|
||||
id="per-word-rise-mount"
|
||||
class="mount-card clip"
|
||||
data-composition-id="per-word-rise"
|
||||
data-composition-src="./per-word-rise.html"
|
||||
data-variable-values='{"text":"EVERY FRAME MATTERS","split":"char","accent":"violet"}'
|
||||
data-start="0"
|
||||
data-duration="3.5"
|
||||
data-track-index="0"
|
||||
data-width="1459"
|
||||
data-height="540"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
tl.seek(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["per-word-rise-demo"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,316 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
per-word-rise: HyperFrames video primitive (text effects / reveal)
|
||||
|
||||
Words, or characters through the split variable, rise into place with a
|
||||
controlled cascade and a small blur-to-sharp settle. Each unit lands with
|
||||
a short overshoot-free settle tail (the final few pixels glide in over the
|
||||
closing frames instead of stopping dead), then the finished line holds
|
||||
truly still until the frame cuts (exit none) or departs with the optional
|
||||
exit variable.
|
||||
|
||||
Variables:
|
||||
- text (string, default "WORDS IN MOTION"): displayed text.
|
||||
- split (word | char, default "word"): animated unit size.
|
||||
- cues (string, default ""): comma-separated seconds (from mount start)
|
||||
for each unit's landing. Empty keeps the authored cascade rhythm.
|
||||
- accent (green | blue | violet, default "green"): text color.
|
||||
- exit (none | fade | up, default "none"): optional departure.
|
||||
|
||||
Envelope, fixed IN and OUT with elastic HOLD only:
|
||||
IN_BASE = 1.45s, the full staggered rise and sharp settle (cues can
|
||||
stretch the rise phase past IN_BASE when they land later)
|
||||
HOLD = max(0, D - IN - OUT), truly still: no drift
|
||||
OUT_BASE = 0.50s staggered departure when exit is fade or up, 0s when
|
||||
exit is none (the default: the line holds until the cut)
|
||||
If D is shorter than IN_BASE + OUT_BASE, IN and OUT compress together.
|
||||
The timeline is never time-scaled.
|
||||
|
||||
Mount contract: the runtime clones only this template. #root fills the host
|
||||
box, establishes the container query basis, has no data-width or data-height,
|
||||
and registers one paused timeline under the literal per-word-rise key.
|
||||
-->
|
||||
<html
|
||||
lang="en"
|
||||
data-composition-id="per-word-rise"
|
||||
data-composition-duration="3.5"
|
||||
data-composition-variables='[
|
||||
{ "id": "text", "type": "string", "role": "content", "label": "Text", "description": "Text whose words or characters rise into place.", "default": "WORDS IN MOTION" },
|
||||
{ "id": "split", "type": "enum", "role": "content", "label": "Split", "description": "Animate the text as words or individual characters.", "default": "word", "options": [{ "value": "word", "label": "Word" }, { "value": "char", "label": "Character" }] },
|
||||
{ "id": "cues", "type": "string", "role": "timing", "label": "Cues", "description": "Comma-separated seconds (from mount start) for each unit landing. Empty keeps the authored cascade rhythm.", "default": "" },
|
||||
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Contract accent token used for the text color.", "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 line 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>Per Word Rise</title>
|
||||
</head>
|
||||
<body>
|
||||
<template>
|
||||
<div id="root" data-composition-id="per-word-rise" data-duration="3.5" 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;
|
||||
}
|
||||
|
||||
.pwr-clip {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
background: var(--bg, #0b0c0e);
|
||||
}
|
||||
|
||||
.pwr-stage {
|
||||
display: flex;
|
||||
width: 88cqw;
|
||||
max-width: 88cqw;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
column-gap: var(--space-1, 1.8cqw);
|
||||
row-gap: var(--space-2, 2.8cqh);
|
||||
color: var(--pwr-accent);
|
||||
font-size: var(--pwr-font-size);
|
||||
font-weight: 780;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.035em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pwr-stage.pwr-char {
|
||||
column-gap: 0.045em;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.pwr-unit {
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
filter: blur(var(--pwr-blur, 0cqh));
|
||||
will-change: transform, filter, opacity;
|
||||
}
|
||||
|
||||
.pwr-space {
|
||||
width: 0.36em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
id="per-word-rise-clip"
|
||||
class="pwr-clip clip"
|
||||
data-start="0"
|
||||
data-duration="3.5"
|
||||
data-track-index="0"
|
||||
>
|
||||
<div class="pwr-stage" role="img"></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(".pwr-stage");
|
||||
var vars =
|
||||
window.__hyperframes && window.__hyperframes.getVariables
|
||||
? window.__hyperframes.getVariables()
|
||||
: {};
|
||||
|
||||
var text = vars.text == null ? "WORDS IN MOTION" : String(vars.text);
|
||||
var split = vars.split === "char" ? "char" : "word";
|
||||
var accentColors = {
|
||||
green: "var(--brand, #71f5a7)",
|
||||
blue: "var(--accent, #61a8ff)",
|
||||
violet: "var(--accent-2, #c5a3ff)",
|
||||
};
|
||||
var accent = Object.prototype.hasOwnProperty.call(accentColors, vars.accent)
|
||||
? vars.accent
|
||||
: "green";
|
||||
var exit = vars.exit === "fade" || vars.exit === "up" ? vars.exit : "none";
|
||||
var cuesValue = vars.cues == null ? "" : String(vars.cues);
|
||||
var cues = cuesValue.split(",").reduce(function (result, token) {
|
||||
var trimmed = token.trim();
|
||||
if (trimmed === "") return result;
|
||||
var seconds = Number(trimmed);
|
||||
if (!Number.isFinite(seconds) || seconds < 0) return result;
|
||||
result.push(seconds);
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
root.style.setProperty("--pwr-accent", accentColors[accent]);
|
||||
stage.classList.toggle("pwr-char", split === "char");
|
||||
stage.setAttribute("aria-label", text);
|
||||
|
||||
var characterCount = Math.max(1, Array.from(text).length);
|
||||
var fittedCqw = Math.min(9.5, 126 / characterCount);
|
||||
root.style.setProperty(
|
||||
"--pwr-font-size",
|
||||
"min(" + fittedCqw.toFixed(3) + "cqw, 15cqh)",
|
||||
);
|
||||
|
||||
var segments = split === "char" ? Array.from(text) : text.trim().split(/\s+/);
|
||||
if (segments.length === 0) segments = [""];
|
||||
|
||||
var units = segments.map(function (segment, index) {
|
||||
var unit = document.createElement("span");
|
||||
unit.id = "per-word-rise-unit-" + index;
|
||||
unit.className = "pwr-unit";
|
||||
if (split === "char" && /\s/.test(segment)) {
|
||||
unit.classList.add("pwr-space");
|
||||
unit.textContent = "\u00a0";
|
||||
} else {
|
||||
unit.textContent = segment;
|
||||
}
|
||||
unit.setAttribute("aria-hidden", "true");
|
||||
stage.appendChild(unit);
|
||||
return unit;
|
||||
});
|
||||
|
||||
var IN_BASE = 1.45;
|
||||
var OUT_BASE = exit === "none" ? 0 : 0.5;
|
||||
var RISE_BASE = 0.74;
|
||||
var STAGGER_BASE = 0.62;
|
||||
var EXIT_STAGGER_BASE = 0.14;
|
||||
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "3.5"));
|
||||
var totalBase = Math.max(0.001, IN_BASE + OUT_BASE);
|
||||
var scale = duration < totalBase ? duration / totalBase : 1;
|
||||
var IN = IN_BASE * scale;
|
||||
var OUT = OUT_BASE * scale;
|
||||
var RISE = RISE_BASE * scale;
|
||||
var STAGGER = units.length > 1 ? STAGGER_BASE * scale : 0;
|
||||
var EXIT_STAGGER = units.length > 1 ? EXIT_STAGGER_BASE * scale : 0;
|
||||
var EXIT_MOVE = Math.max(0.001, OUT - EXIT_STAGGER);
|
||||
|
||||
// Landing ease: overshoot-free decel with a settle tail. A plain
|
||||
// power3.out slams from fast to imperceptible in ~2 frames and the
|
||||
// words stop dead; this piecewise cubic keeps ~7% of the travel for
|
||||
// the last 22% of the duration and is C1-continuous at the seam,
|
||||
// so the word glides its final few pixels over the closing frames
|
||||
// (a settle) with monotonic velocity: no stall, no hop, no
|
||||
// overshoot. Pure function of progress, so it stays seek-safe.
|
||||
var LAND_SPLIT = 0.78; // progress where the settle tail begins
|
||||
var LAND_RESIDUAL = 0.07; // fraction of travel left for the tail
|
||||
var LAND_SEAM_SLOPE = ((1 - LAND_RESIDUAL) / LAND_SPLIT) * 0.5; // shared velocity at the seam
|
||||
function landEase(p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
if (p < LAND_SPLIT) {
|
||||
var x = p / LAND_SPLIT;
|
||||
var inv = 1 - x;
|
||||
// Half cubic-out, half linear: keeps end velocity for the seam.
|
||||
return (1 - LAND_RESIDUAL) * (0.5 * (1 - inv * inv * inv) + 0.5 * x);
|
||||
}
|
||||
var u = (p - LAND_SPLIT) / (1 - LAND_SPLIT);
|
||||
var b = LAND_SEAM_SLOPE * (1 - LAND_SPLIT);
|
||||
var c = 3 * LAND_RESIDUAL - 2 * b;
|
||||
var d = b - 2 * LAND_RESIDUAL;
|
||||
return 1 - LAND_RESIDUAL + b * u + c * u * u + d * u * u * u;
|
||||
}
|
||||
|
||||
// Cues own the word landings when provided; the authored cascade
|
||||
// otherwise. Landings beyond the cue list extrapolate at the cue
|
||||
// list's own gap, and every landing clamps ahead of any exit.
|
||||
var riseEnd = IN;
|
||||
var landings;
|
||||
if (cues.length > 0) {
|
||||
var lastCue = cues[cues.length - 1];
|
||||
var cueGap =
|
||||
cues.length >= 2
|
||||
? Math.max(0.05, (lastCue - cues[0]) / (cues.length - 1))
|
||||
: RISE * 0.4;
|
||||
var latestLanding = Math.max(RISE * 0.5, duration - OUT);
|
||||
landings = units.map(function (unit, index) {
|
||||
var landing =
|
||||
index < cues.length
|
||||
? cues[index]
|
||||
: lastCue + (index - (cues.length - 1)) * cueGap;
|
||||
return Math.min(latestLanding, Math.max(0.05, landing));
|
||||
});
|
||||
riseEnd = Math.min(duration - OUT, Math.max(IN, Math.max.apply(null, landings)));
|
||||
} else {
|
||||
// Authored cascade: same left-to-right wave the stagger drew,
|
||||
// expressed as explicit landings so every unit gets the settle.
|
||||
var step = units.length > 1 ? STAGGER / (units.length - 1) : 0;
|
||||
landings = units.map(function (unit, index) {
|
||||
return index * step + RISE;
|
||||
});
|
||||
}
|
||||
var HOLD_START = riseEnd;
|
||||
var HOLD = Math.max(0, duration - (HOLD_START + OUT));
|
||||
var OUT_START = HOLD_START + HOLD;
|
||||
|
||||
gsap.set(units, { opacity: 0, y: "8cqh", "--pwr-blur": "1.4cqh" });
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
units.forEach(function (unit, index) {
|
||||
var landing = landings[index];
|
||||
var startAt = Math.max(0, landing - RISE);
|
||||
tl.to(
|
||||
unit,
|
||||
{
|
||||
opacity: 1,
|
||||
y: "0cqh",
|
||||
"--pwr-blur": "0cqh",
|
||||
duration: Math.max(0.08, landing - startAt),
|
||||
ease: landEase,
|
||||
},
|
||||
startAt,
|
||||
);
|
||||
});
|
||||
|
||||
// TRUE hold: the finished line is dead still until the exit or
|
||||
// the cut. The old 0.55cqh drift was sub-perceptual at 1080p.
|
||||
|
||||
// OUT: optional departure; exit none holds until the frame cuts.
|
||||
if (exit === "up") {
|
||||
tl.to(
|
||||
units,
|
||||
{
|
||||
opacity: 0,
|
||||
y: "-4.5cqh",
|
||||
"--pwr-blur": "0.7cqh",
|
||||
duration: EXIT_MOVE,
|
||||
stagger: { amount: EXIT_STAGGER, from: "start" },
|
||||
ease: "power2.in",
|
||||
},
|
||||
OUT_START,
|
||||
);
|
||||
} else if (exit === "fade") {
|
||||
tl.to(
|
||||
units,
|
||||
{
|
||||
opacity: 0,
|
||||
duration: EXIT_MOVE,
|
||||
stagger: { amount: EXIT_STAGGER, from: "start" },
|
||||
ease: "power2.in",
|
||||
},
|
||||
OUT_START,
|
||||
);
|
||||
}
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["per-word-rise"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "per-word-rise",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Per Word Rise",
|
||||
"description": "Words or characters rise into place in a controlled blur-to-sharp cascade, settle softly on landing, and hold still until the cut (optional fade or up exit).",
|
||||
"tags": ["motion-primitive", "text-effects", "typography", "stagger", "reveal"],
|
||||
"jobs": ["reveal"],
|
||||
"family": "text-effects",
|
||||
"profile": "stagger",
|
||||
"evidence": "Wave I I1 approved contract build, merging the per-word and SplitTypeReveal candidates",
|
||||
"syncPoints": [{ "id": "rise-settle", "phase": "in", "offset": 1.45 }],
|
||||
"files": [
|
||||
{
|
||||
"path": "per-word-rise.html",
|
||||
"target": "compositions/components/per-word-rise.html",
|
||||
"type": "hyperframes:snippet"
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{
|
||||
"id": "text",
|
||||
"type": "string",
|
||||
"role": "content",
|
||||
"label": "Text",
|
||||
"description": "Text whose words or characters rise into place.",
|
||||
"default": "WORDS IN MOTION"
|
||||
},
|
||||
{
|
||||
"id": "split",
|
||||
"type": "enum",
|
||||
"role": "content",
|
||||
"label": "Split",
|
||||
"description": "Animate the text as words or individual characters.",
|
||||
"default": "word",
|
||||
"options": [
|
||||
{ "value": "word", "label": "Word" },
|
||||
{ "value": "char", "label": "Character" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cues",
|
||||
"type": "string",
|
||||
"role": "timing",
|
||||
"label": "Cues",
|
||||
"description": "Comma-separated seconds (from mount start) for each unit landing. Empty keeps the authored cascade rhythm.",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"id": "accent",
|
||||
"type": "enum",
|
||||
"role": "style",
|
||||
"label": "Accent",
|
||||
"description": "Contract accent token used for the text color.",
|
||||
"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 line holds until the frame cuts.",
|
||||
"default": "none",
|
||||
"options": [
|
||||
{ "value": "none", "label": "None" },
|
||||
{ "value": "fade", "label": "Fade" },
|
||||
{ "value": "up", "label": "Up" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user