mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 01:56:04 +00:00
This reverts commit 3b53bfd2f7.
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
# kinetic-type-swap
|
||||
|
||||
A held sentence keeps its prefix and suffix fixed while one masked word slot rolls vertically through comma-separated options. The slot pre-sizes to the widest option, so the sentence never reflows during a swap. The final word locks with a soft pulse and holds until the frame cuts.
|
||||
|
||||
4s authored, elastic HOLD, exit `none` by default.
|
||||
|
||||
## Variables
|
||||
|
||||
| id | type | default | notes |
|
||||
| --------- | ------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `prefix` | string | `Ship` | Fixed text before the rolling slot. |
|
||||
| `options` | string | `faster,smarter,together` | Comma-separated words, shown in order; the last one is the sentence's final state. |
|
||||
| `suffix` | string | `` (empty) | Fixed text after the rolling slot. |
|
||||
| `cues` | string | `` (empty) | Comma-separated seconds (from mount start) for each swap, e.g. `0.8,1.6`. Sorted ascending; swaps beyond the list extrapolate at the list's own gap. Empty keeps the authored even spread. |
|
||||
| `accent` | enum | `green` | Slot color: green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`. |
|
||||
| `exit` | enum | `none` | `none` holds until the cut; `fade` is the opacity release (the pre-Wave-J default ending); `up` adds a small rise to the fade. |
|
||||
|
||||
## Mount
|
||||
|
||||
```html
|
||||
<div
|
||||
class="clip"
|
||||
data-composition-id="kinetic-type-swap"
|
||||
data-composition-src="./kinetic-type-swap.html"
|
||||
data-variable-values='{"prefix":"Render","options":"anything,everywhere,tonight","cues":"1.0,2.2"}'
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
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 `kinetic-type-swap` key.
|
||||
|
||||
## Notes
|
||||
|
||||
- Cue N fires the swap from option N to option N+1. Swaps clamp so the roll and settle finish before any exit; the roll shortens automatically when cues sit close together.
|
||||
- Deterministic and seek-safe; the hold pulse uses a finite odd repeat, never an open loop.
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
kinetic-type-swap demo. This standalone composition mounts the primitive
|
||||
through data-composition-src. Non-default prefix, options, suffix, and
|
||||
accent values prove the variable path and the widest-option slot contract.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Kinetic Type Swap Demo</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #0b0c0e;
|
||||
}
|
||||
|
||||
#root {
|
||||
--bg: #0b0c0e;
|
||||
--fg: #f8fafc;
|
||||
--muted: #94a3b8;
|
||||
--surface: #15181e;
|
||||
--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: 2.4cqmin;
|
||||
--space-1: 1.2cqmin;
|
||||
--space-2: 2.4cqmin;
|
||||
--space-3: 4cqmin;
|
||||
--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% 44%,
|
||||
color-mix(in srgb, var(--surface) 72%, var(--accent-2)) 0%,
|
||||
var(--bg) 70%
|
||||
);
|
||||
}
|
||||
|
||||
.mount-card {
|
||||
width: 82%;
|
||||
height: 42%;
|
||||
left: 9%;
|
||||
top: 29%;
|
||||
container-type: size;
|
||||
overflow: hidden;
|
||||
border: 0.12cqmin solid color-mix(in srgb, var(--border) 74%, transparent);
|
||||
border-radius: calc(var(--radius) * 1.3);
|
||||
background: color-mix(in srgb, var(--surface) 76%, transparent);
|
||||
box-shadow: 0 4cqh 10cqh color-mix(in srgb, var(--bg) 60%, transparent);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="kinetic-type-swap-demo"
|
||||
data-start="0"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
data-duration="4"
|
||||
data-fps="30"
|
||||
>
|
||||
<div
|
||||
id="kinetic-type-swap-mount"
|
||||
class="mount-card clip"
|
||||
data-composition-id="kinetic-type-swap"
|
||||
data-composition-src="./kinetic-type-swap.html"
|
||||
data-variable-values='{"prefix":"Make","options":"motion,meaning,momentum","suffix":"visible","accent":"violet"}'
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
data-width="1574"
|
||||
data-height="454"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["kinetic-type-swap-demo"] = gsap.timeline({ paused: true });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,373 +0,0 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
kinetic-type-swap: HyperFrames video primitive (text effects / swap)
|
||||
|
||||
A held sentence keeps its prefix and suffix fixed while one masked word
|
||||
slot rolls through comma-separated options. Every option remains in the
|
||||
same CSS grid cell, so the slot takes the widest option's intrinsic width
|
||||
before motion starts and the sentence never reflows during a swap.
|
||||
|
||||
Variables:
|
||||
- prefix (string, default "Ship"): fixed text before the rolling slot.
|
||||
- options (string, default "faster,smarter,together"): comma-separated
|
||||
words shown in order.
|
||||
- suffix (string, default ""): fixed text after the rolling slot.
|
||||
- cues (string, default ""): comma-separated seconds (from mount start)
|
||||
for each word swap. Empty keeps the authored even spread.
|
||||
- accent (green | blue | violet, default "green"): rolling word color.
|
||||
- exit (none | fade | up, default "none"): optional departure.
|
||||
|
||||
Envelope, fixed IN and OUT with elastic HOLD only:
|
||||
IN_BASE = 2.25s, fade in followed by every vertical word swap (cues can
|
||||
stretch the swap phase past IN_BASE when they land later)
|
||||
HOLD = max(0, D - IN - OUT), final word stays locked with a soft pulse
|
||||
OUT_BASE = 0.45s departure when exit is fade or up, 0s when exit is
|
||||
none (the default: the sentence holds until the cut)
|
||||
If D is shorter than IN_BASE + OUT_BASE, IN and OUT compress together.
|
||||
The timeline is never time-scaled.
|
||||
|
||||
Determinism: options are parsed once before timeline construction. GSAP
|
||||
owns every visible change, uses finite repeats, and registers one paused
|
||||
timeline under the literal kinetic-type-swap key.
|
||||
|
||||
Mount contract: the runtime clones only this template. #root fills the host
|
||||
box, establishes the container query basis, and has no data-width or
|
||||
data-height. Only word transforms change position. The held sentence never
|
||||
moves, and the grid-owned slot width never changes.
|
||||
-->
|
||||
<html
|
||||
lang="en"
|
||||
data-composition-id="kinetic-type-swap"
|
||||
data-composition-duration="4"
|
||||
data-composition-variables='[
|
||||
{ "id": "prefix", "type": "string", "role": "content", "label": "Prefix", "description": "Fixed sentence text before the rolling word slot.", "default": "Ship" },
|
||||
{ "id": "options", "type": "string", "role": "content", "label": "Options", "description": "Comma-separated words shown in the rolling slot.", "default": "faster,smarter,together" },
|
||||
{ "id": "suffix", "type": "string", "role": "content", "label": "Suffix", "description": "Fixed sentence text after the rolling word slot.", "default": "" },
|
||||
{ "id": "cues", "type": "string", "role": "timing", "label": "Cues", "description": "Comma-separated seconds (from mount start) for each word swap. Empty keeps the authored even spread.", "default": "" },
|
||||
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Color used by the rolling word slot.", "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 sentence 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>Kinetic Type Swap</title>
|
||||
</head>
|
||||
<body>
|
||||
<template>
|
||||
<div id="root" data-composition-id="kinetic-type-swap" 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;
|
||||
}
|
||||
|
||||
.kts-clip {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
background: var(--bg, transparent);
|
||||
}
|
||||
|
||||
.kts-stage {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 94cqw;
|
||||
height: 70cqh;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.kts-sentence {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
/* Word gap must scale with the display type, never a host layout
|
||||
spacing token: --space-* is chrome-scale and jams the sentence
|
||||
("Makemomentumvisible") when the type is auto-fitted large. */
|
||||
gap: 0.26em;
|
||||
max-width: 94cqw;
|
||||
color: var(--fg, #f8fafc);
|
||||
font-size: var(--kts-font-size);
|
||||
font-weight: 760;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.045em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kts-prefix,
|
||||
.kts-suffix {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.kts-prefix:empty,
|
||||
.kts-suffix:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Every word occupies one grid cell and contributes its intrinsic
|
||||
width. Transforms do not affect layout, so this masked slot stays
|
||||
as wide as the widest option for the entire composition. */
|
||||
.kts-slot {
|
||||
display: inline-grid;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
color: var(--kts-accent);
|
||||
text-shadow: 0 0 2.8cqh color-mix(in srgb, var(--kts-accent) 28%, transparent);
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.kts-word {
|
||||
grid-area: 1 / 1;
|
||||
width: max-content;
|
||||
justify-self: center;
|
||||
color: var(--kts-accent);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
id="kinetic-type-swap-clip"
|
||||
class="kts-clip clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
>
|
||||
<div class="kts-stage" role="img">
|
||||
<div class="kts-sentence">
|
||||
<span class="kts-prefix"></span>
|
||||
<span class="kts-slot" aria-hidden="true"></span>
|
||||
<span class="kts-suffix"></span>
|
||||
</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(".kts-stage");
|
||||
var prefixElement = root.querySelector(".kts-prefix");
|
||||
var slotElement = root.querySelector(".kts-slot");
|
||||
var suffixElement = root.querySelector(".kts-suffix");
|
||||
var vars =
|
||||
window.__hyperframes && window.__hyperframes.getVariables
|
||||
? window.__hyperframes.getVariables()
|
||||
: {};
|
||||
|
||||
var defaultOptions = ["faster", "smarter", "together"];
|
||||
var prefix = vars.prefix == null ? "Ship" : String(vars.prefix).trim();
|
||||
var suffix = vars.suffix == null ? "" : String(vars.suffix).trim();
|
||||
var rawOptions = vars.options == null ? defaultOptions.join(",") : String(vars.options);
|
||||
var options = rawOptions
|
||||
.split(",")
|
||||
.map(function (option) {
|
||||
return option.trim();
|
||||
})
|
||||
.filter(function (option) {
|
||||
return option.length > 0;
|
||||
});
|
||||
if (options.length === 0) options = defaultOptions.slice();
|
||||
|
||||
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;
|
||||
}, [])
|
||||
.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
root.style.setProperty("--kts-accent", accentColors[accent]);
|
||||
prefixElement.textContent = prefix;
|
||||
suffixElement.textContent = suffix;
|
||||
|
||||
var words = options.map(function (option, index) {
|
||||
var word = document.createElement("span");
|
||||
word.className = "kts-word";
|
||||
word.id = "kinetic-type-swap-word-" + index;
|
||||
word.textContent = option;
|
||||
slotElement.appendChild(word);
|
||||
return word;
|
||||
});
|
||||
|
||||
var finalSentence = [prefix, options[options.length - 1], suffix]
|
||||
.filter(function (part) {
|
||||
return part.length > 0;
|
||||
})
|
||||
.join(" ");
|
||||
stage.setAttribute("aria-label", finalSentence);
|
||||
|
||||
var widestOptionLength = options.reduce(function (widest, option) {
|
||||
return Math.max(widest, Array.from(option).length);
|
||||
}, 1);
|
||||
var sentenceLength =
|
||||
Array.from(prefix).length + widestOptionLength + Array.from(suffix).length + 2;
|
||||
var fittedCqw = Math.min(12, 122 / Math.max(1, sentenceLength));
|
||||
root.style.setProperty(
|
||||
"--kts-font-size",
|
||||
"min(" + fittedCqw.toFixed(3) + "cqw, 22cqh)",
|
||||
);
|
||||
|
||||
var IN_BASE = 2.25;
|
||||
var OUT_BASE = exit === "none" ? 0 : 0.45;
|
||||
var ARRIVAL_BASE = 0.3;
|
||||
var SWAP_START_BASE = 0.48;
|
||||
var ROLL_BASE = 0.46;
|
||||
var SETTLE_TAIL_BASE = 0.18;
|
||||
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 IN = IN_BASE * scale;
|
||||
var OUT = OUT_BASE * scale;
|
||||
var ARRIVAL = ARRIVAL_BASE * scale;
|
||||
var SWAP_START = SWAP_START_BASE * scale;
|
||||
var ROLL = ROLL_BASE * scale;
|
||||
var SETTLE_TAIL = SETTLE_TAIL_BASE * scale;
|
||||
|
||||
// Swap schedule: cues own the swap moments when provided; the
|
||||
// authored even spread otherwise. Swaps beyond the cue list
|
||||
// extrapolate at the cue list's own gap, and every swap clamps
|
||||
// so its roll and settle finish before any exit.
|
||||
var swapCount = words.length - 1;
|
||||
var swapTimes = [];
|
||||
var rollDuration = ROLL;
|
||||
if (swapCount > 0) {
|
||||
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)) : ROLL;
|
||||
var latestSwap = Math.max(0, duration - OUT - ROLL - SETTLE_TAIL);
|
||||
for (var cueIndex = 0; cueIndex < swapCount; cueIndex += 1) {
|
||||
var cueAt =
|
||||
cueIndex < cues.length
|
||||
? cues[cueIndex]
|
||||
: lastCue + (cueIndex - (cues.length - 1)) * cueGap;
|
||||
swapTimes.push(Math.min(latestSwap, Math.max(0, cueAt)));
|
||||
}
|
||||
var minGap = Infinity;
|
||||
for (var gapIndex = 1; gapIndex < swapTimes.length; gapIndex += 1) {
|
||||
minGap = Math.min(minGap, swapTimes[gapIndex] - swapTimes[gapIndex - 1]);
|
||||
}
|
||||
if (Number.isFinite(minGap)) {
|
||||
rollDuration = Math.min(ROLL, Math.max(0.04, minGap * 0.72));
|
||||
}
|
||||
} else {
|
||||
var lastSwapStart = Math.max(SWAP_START, IN - SETTLE_TAIL - ROLL);
|
||||
var swapStep = swapCount === 1 ? 0 : (lastSwapStart - SWAP_START) / (swapCount - 1);
|
||||
rollDuration =
|
||||
swapCount <= 2 ? ROLL : Math.min(ROLL, Math.max(0.04, swapStep * 0.72));
|
||||
for (var index = 1; index <= swapCount; index += 1) {
|
||||
swapTimes.push(
|
||||
swapCount === 1 ? lastSwapStart : SWAP_START + (index - 1) * swapStep,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
var swapsEnd =
|
||||
swapTimes.length > 0
|
||||
? swapTimes[swapTimes.length - 1] + rollDuration + SETTLE_TAIL
|
||||
: IN;
|
||||
var HOLD_START = Math.min(Math.max(0, duration - OUT), Math.max(IN, swapsEnd));
|
||||
var HOLD = Math.max(0, duration - (HOLD_START + OUT));
|
||||
var OUT_START = HOLD_START + HOLD;
|
||||
|
||||
gsap.set(stage, { opacity: 0 });
|
||||
gsap.set(words, { yPercent: 112, opacity: 0 });
|
||||
gsap.set(words[0], { yPercent: 0, opacity: 1 });
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
tl.to(stage, { opacity: 1, duration: ARRIVAL, ease: "power2.out" }, 0);
|
||||
|
||||
// Masked exchange: the outgoing word whips fully past the slot
|
||||
// mask (power4.in) before the incoming word crosses center. The
|
||||
// two phases overlap only for ~10% of the roll, while both words
|
||||
// are opposite-edge slivers, so every frame shows at most one
|
||||
// legible word at full ink and never a double exposure.
|
||||
swapTimes.forEach(function (swapAt, swapIndex) {
|
||||
var wordIndex = swapIndex + 1;
|
||||
var incomingEase = wordIndex === words.length - 1 ? "back.out(1.7)" : "power4.out";
|
||||
var outDuration = rollDuration * 0.55;
|
||||
var inDuration = rollDuration * 0.55;
|
||||
var inStart = swapAt + rollDuration - inDuration;
|
||||
|
||||
tl.to(
|
||||
words[wordIndex - 1],
|
||||
{ yPercent: -112, duration: outDuration, ease: "power4.in" },
|
||||
swapAt,
|
||||
);
|
||||
tl.set(words[wordIndex - 1], { opacity: 0 }, swapAt + outDuration);
|
||||
tl.set(words[wordIndex], { opacity: 1 }, inStart);
|
||||
tl.to(
|
||||
words[wordIndex],
|
||||
{ yPercent: 0, duration: inDuration, ease: incomingEase },
|
||||
inStart,
|
||||
);
|
||||
});
|
||||
|
||||
var finalWord = words[words.length - 1];
|
||||
var beat = parseFloat(getComputedStyle(root).getPropertyValue("--dur-beat")) || 0.5;
|
||||
var pulseHalf = Math.min(beat, HOLD / 2);
|
||||
if (pulseHalf > 0) {
|
||||
var maxRepeat = Math.floor(HOLD / pulseHalf) - 1;
|
||||
var pulseRepeat = maxRepeat % 2 === 1 ? maxRepeat : maxRepeat - 1;
|
||||
if (pulseRepeat >= 1) {
|
||||
tl.to(
|
||||
finalWord,
|
||||
{
|
||||
opacity: 0.9,
|
||||
duration: pulseHalf,
|
||||
ease: "sine.inOut",
|
||||
yoyo: true,
|
||||
repeat: pulseRepeat,
|
||||
},
|
||||
HOLD_START,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// OUT: optional departure; exit none holds until the frame cuts.
|
||||
if (exit === "fade") {
|
||||
tl.to(stage, { opacity: 0, duration: OUT, ease: "power2.in" }, OUT_START);
|
||||
} else if (exit === "up") {
|
||||
tl.to(stage, { opacity: 0, y: "-3cqh", duration: OUT, ease: "power2.in" }, OUT_START);
|
||||
}
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["kinetic-type-swap"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "kinetic-type-swap",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Kinetic Type Swap",
|
||||
"description": "A held sentence keeps its fixed text in place while one widest-word masked slot rolls through alternatives and settles on the final option.",
|
||||
"tags": ["motion-primitive", "text-effects", "kinetic-type", "word-swap", "reveal"],
|
||||
"jobs": ["reveal"],
|
||||
"family": "text-effects",
|
||||
"profile": "swap",
|
||||
"evidence": "Wave I I2 true-spec contract: one widest-option masked word slot rolls while the surrounding sentence remains fixed",
|
||||
"syncPoints": [{ "id": "final-lock", "phase": "in", "offset": 2.07 }],
|
||||
"files": [
|
||||
{
|
||||
"path": "kinetic-type-swap.html",
|
||||
"target": "compositions/components/kinetic-type-swap.html",
|
||||
"type": "hyperframes:snippet"
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{
|
||||
"id": "prefix",
|
||||
"type": "string",
|
||||
"role": "content",
|
||||
"label": "Prefix",
|
||||
"description": "Fixed sentence text before the rolling word slot.",
|
||||
"default": "Ship"
|
||||
},
|
||||
{
|
||||
"id": "options",
|
||||
"type": "string",
|
||||
"role": "content",
|
||||
"label": "Options",
|
||||
"description": "Comma-separated words shown in the rolling slot.",
|
||||
"default": "faster,smarter,together"
|
||||
},
|
||||
{
|
||||
"id": "suffix",
|
||||
"type": "string",
|
||||
"role": "content",
|
||||
"label": "Suffix",
|
||||
"description": "Fixed sentence text after the rolling word slot.",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"id": "cues",
|
||||
"type": "string",
|
||||
"role": "timing",
|
||||
"label": "Cues",
|
||||
"description": "Comma-separated seconds (from mount start) for each word swap. Empty keeps the authored even spread.",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"id": "accent",
|
||||
"type": "enum",
|
||||
"role": "style",
|
||||
"label": "Accent",
|
||||
"description": "Color used by the rolling word slot.",
|
||||
"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 sentence 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