mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
feat(registry): add kinetic center build component (#3269)
* feat(registry): add kinetic center build component * chore: regenerate search vectors for kinetic-center-build Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(registry): load all kinetic center build font weights * chore(catalog): regenerate local search vectors --------- Co-authored-by: miga-heygen <miguel.sierra_miga@heygen.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
miga-heygen
Miguel Ángel
parent
aab3bfcd10
commit
f498be94c2
@@ -0,0 +1,645 @@
|
||||
---
|
||||
title: "Kinetic Center Build"
|
||||
description: "Words enter from the right and push the growing phrase left until the final line locks centered."
|
||||
---
|
||||
|
||||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||||
import { VariablesExplorer } from "/snippets/variables-explorer.jsx";
|
||||
|
||||
<VariablesExplorer
|
||||
previewSrc="/public/catalog/components/kinetic-center-build.json"
|
||||
compositionId="kinetic-center-build"
|
||||
compositionSrc="compositions/components/kinetic-center-build.html"
|
||||
variables={[{"id":"text","type":"string","role":"content","label":"Text","description":"Short phrase that builds one word at a time.","default":"Words push left."},{"id":"font_size","type":"number","role":"style","label":"Font size","description":"Maximum text size in pixels. Longer phrases shrink to stay inside the frame.","default":72,"min":36,"max":160,"step":2,"unit":"px"},{"id":"entry_offset","type":"number","role":"motion","label":"Entry offset","description":"Horizontal distance each new word travels before it lands.","default":88,"min":20,"max":160,"step":4,"unit":"px"},{"id":"color","type":"color","role":"style","label":"Color","description":"Text color for the full phrase.","default":"#171717"},{"id":"font_weight","type":"enum","role":"style","label":"Font weight","description":"Weight used by every word in the phrase.","default":"600","options":[{"value":"500","label":"Medium"},{"value":"600","label":"Semibold"},{"value":"700","label":"Bold"},{"value":"800","label":"Extra bold"}]}]}
|
||||
>
|
||||
|
||||
```html kinetic-center-build.html
|
||||
<!doctype html>
|
||||
<!--
|
||||
Kinetic Center Build
|
||||
|
||||
Words enter from the right while the existing phrase shifts left, keeping
|
||||
every intermediate phrase centered until the final line locks in place.
|
||||
|
||||
Native HyperFrames port prepared through Hyfrme from:
|
||||
https://github.com/Remocn/remocn/blob/ea730a20b4ab09430ee7292aebc847c002375151/registry/remocn/kinetic-center-build/index.tsx
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Remocn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-->
|
||||
<html
|
||||
lang="en"
|
||||
data-composition-id="kinetic-center-build"
|
||||
data-composition-duration="2"
|
||||
data-composition-variables='[
|
||||
{ "id": "text", "type": "string", "role": "content", "label": "Text", "description": "Short phrase that builds one word at a time.", "default": "Words push left." },
|
||||
{ "id": "font_size", "type": "number", "role": "style", "label": "Font size", "description": "Maximum text size in pixels. Longer phrases shrink to stay inside the frame.", "default": 72, "min": 36, "max": 160, "step": 2, "unit": "px" },
|
||||
{ "id": "entry_offset", "type": "number", "role": "motion", "label": "Entry offset", "description": "Horizontal distance each new word travels before it lands.", "default": 88, "min": 20, "max": 160, "step": 4, "unit": "px" },
|
||||
{ "id": "color", "type": "color", "role": "style", "label": "Color", "description": "Text color for the full phrase.", "default": "#171717" },
|
||||
{ "id": "font_weight", "type": "enum", "role": "style", "label": "Font weight", "description": "Weight used by every word in the phrase.", "default": "600", "options": [{ "value": "500", "label": "Medium" }, { "value": "600", "label": "Semibold" }, { "value": "700", "label": "Bold" }, { "value": "800", "label": "Extra bold" }] }
|
||||
]'
|
||||
>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Kinetic Center Build</title>
|
||||
</head>
|
||||
<body>
|
||||
<template>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Geist:wght@500;600;700;800&display=block"
|
||||
/>
|
||||
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#root {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
container-type: size;
|
||||
isolation: isolate;
|
||||
color: var(--color, #171717);
|
||||
font-family: var(--font-display, Geist, system-ui, sans-serif);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.kcb-clip,
|
||||
.kcb-stage {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.kcb-stage {
|
||||
letter-spacing: -0.03em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kcb-word {
|
||||
--kcb-blur: 0px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
filter: blur(var(--kcb-blur));
|
||||
backface-visibility: hidden;
|
||||
will-change: transform, filter, opacity;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="root" data-composition-id="kinetic-center-build" data-duration="2" data-fps="30">
|
||||
<div
|
||||
id="kcb-clip"
|
||||
class="kcb-clip clip"
|
||||
data-start="0"
|
||||
data-duration="2"
|
||||
data-track-index="0"
|
||||
>
|
||||
<div id="kcb-stage" class="kcb-stage" role="img"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/CustomEase.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var root = document.getElementById("root");
|
||||
var stage = document.getElementById("kcb-stage");
|
||||
var vars =
|
||||
window.__hyperframes && window.__hyperframes.getVariables
|
||||
? window.__hyperframes.getVariables()
|
||||
: {};
|
||||
|
||||
function numberBetween(value, fallback, min, max) {
|
||||
var parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return fallback;
|
||||
return Math.min(max, Math.max(min, parsed));
|
||||
}
|
||||
|
||||
var text = typeof vars.text === "string" ? vars.text.trim() : "";
|
||||
if (!text) text = "Words push left.";
|
||||
|
||||
var words = text.split(/\s+/);
|
||||
var requestedFontSize = numberBetween(vars.font_size, 72, 36, 160);
|
||||
var entryOffset = numberBetween(vars.entry_offset, 88, 20, 160);
|
||||
var fontWeight =
|
||||
vars.font_weight === "500" || vars.font_weight === "700" || vars.font_weight === "800"
|
||||
? vars.font_weight
|
||||
: "600";
|
||||
|
||||
stage.style.fontWeight = fontWeight;
|
||||
stage.setAttribute("aria-label", text);
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
var context = canvas.getContext("2d");
|
||||
var measureFont = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
||||
var gapRatio = 10 / 72;
|
||||
|
||||
function measure(fontSize) {
|
||||
if (!context) {
|
||||
return words.map(function (word) {
|
||||
return word.length * fontSize * 0.55;
|
||||
});
|
||||
}
|
||||
context.font = fontWeight + " " + fontSize + "px " + measureFont;
|
||||
return words.map(function (word) {
|
||||
return context.measureText(word).width;
|
||||
});
|
||||
}
|
||||
|
||||
var fontSize = requestedFontSize;
|
||||
var widths = measure(fontSize);
|
||||
var gap = fontSize * gapRatio;
|
||||
var fullWidth = widths.reduce(
|
||||
function (total, width) {
|
||||
return total + width;
|
||||
},
|
||||
gap * Math.max(0, words.length - 1),
|
||||
);
|
||||
var maxWidth = Math.max(1, root.clientWidth * 0.9);
|
||||
|
||||
if (fullWidth > maxWidth) {
|
||||
fontSize = Math.max(24, fontSize * (maxWidth / fullWidth));
|
||||
widths = measure(fontSize);
|
||||
gap = fontSize * gapRatio;
|
||||
}
|
||||
|
||||
stage.style.fontSize = fontSize + "px";
|
||||
|
||||
var positionsAt = [];
|
||||
for (var count = 1; count <= words.length; count += 1) {
|
||||
var total = gap * (count - 1);
|
||||
for (var widthIndex = 0; widthIndex < count; widthIndex += 1) {
|
||||
total += widths[widthIndex];
|
||||
}
|
||||
|
||||
var cursor = -total / 2;
|
||||
var positions = [];
|
||||
for (var positionIndex = 0; positionIndex < count; positionIndex += 1) {
|
||||
positions.push(cursor + widths[positionIndex] / 2);
|
||||
cursor += widths[positionIndex] + gap;
|
||||
}
|
||||
positionsAt.push(positions);
|
||||
}
|
||||
|
||||
var elements = words.map(function (word, index) {
|
||||
var element = document.createElement("span");
|
||||
element.id = "kcb-word-" + index;
|
||||
element.className = "kcb-word";
|
||||
element.textContent = word;
|
||||
element.setAttribute("aria-hidden", "true");
|
||||
stage.appendChild(element);
|
||||
return element;
|
||||
});
|
||||
|
||||
gsap.registerPlugin(CustomEase);
|
||||
var entryEase = CustomEase.create("kcb-entry-ease", "M0,0 C0.2,0.8 0.2,1 1,1");
|
||||
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "2"));
|
||||
var firstDuration = 10 / 30;
|
||||
var pushDuration = 13 / 30;
|
||||
var authoredBuildDuration =
|
||||
firstDuration + Math.max(0, words.length - 1) * pushDuration;
|
||||
var availableBuildDuration = Math.max(0.25, duration - 0.5);
|
||||
var timingScale = Math.min(1, availableBuildDuration / authoredBuildDuration);
|
||||
firstDuration *= timingScale;
|
||||
pushDuration *= timingScale;
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
elements.forEach(function (element, index) {
|
||||
var targetX = positionsAt[index][index];
|
||||
var start = index === 0 ? 0 : firstDuration + (index - 1) * pushDuration;
|
||||
var moveDuration = index === 0 ? firstDuration : pushDuration;
|
||||
var fromX = index === 0 ? targetX : targetX + entryOffset;
|
||||
var fromY = index === 0 ? 6 : 0;
|
||||
|
||||
tl.fromTo(
|
||||
element,
|
||||
{
|
||||
x: fromX,
|
||||
y: fromY,
|
||||
xPercent: -50,
|
||||
yPercent: -50,
|
||||
scale: 0.992,
|
||||
opacity: 0,
|
||||
"--kcb-blur": "3.5px",
|
||||
},
|
||||
{
|
||||
x: targetX,
|
||||
y: 0,
|
||||
xPercent: -50,
|
||||
yPercent: -50,
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
"--kcb-blur": "0px",
|
||||
duration: moveDuration,
|
||||
ease: entryEase,
|
||||
immediateRender: index === 0,
|
||||
},
|
||||
start,
|
||||
);
|
||||
|
||||
if (index === 0) return;
|
||||
|
||||
for (var priorIndex = 0; priorIndex < index; priorIndex += 1) {
|
||||
var priorElement = elements[priorIndex];
|
||||
var nextX = positionsAt[index][priorIndex];
|
||||
var blurPeak = start + moveDuration / 2;
|
||||
|
||||
tl.to(priorElement, { x: nextX, duration: moveDuration, ease: entryEase }, start);
|
||||
tl.to(
|
||||
priorElement,
|
||||
{ "--kcb-blur": "0.8px", duration: moveDuration / 2, ease: "none" },
|
||||
start,
|
||||
);
|
||||
tl.to(
|
||||
priorElement,
|
||||
{ "--kcb-blur": "0px", duration: moveDuration / 2, ease: "none" },
|
||||
blurPeak,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["kinetic-center-build"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</VariablesExplorer>
|
||||
|
||||
## Install
|
||||
|
||||
<InstallCommand command="npx hyperframes add kinetic-center-build" item="kinetic-center-build" />
|
||||
|
||||
That writes one file: `compositions/components/kinetic-center-build.html`.
|
||||
|
||||
## Paste it into your composition
|
||||
|
||||
Open `compositions/components/kinetic-center-build.html` and copy what is inside into your own composition.
|
||||
|
||||
A component has no size or duration of its own. It takes both from the composition
|
||||
you paste it into.
|
||||
|
||||
## Variables
|
||||
|
||||
Every one of these has a default, so the piece works untouched. Set the ones you
|
||||
want to change on the element:
|
||||
|
||||
| Variable | Default | Accepts | What it does |
|
||||
| -------------- | ------------------ | -------------------------- | ---------------------------------------------------------------------------- |
|
||||
| `text` | `Words push left.` | string | Short phrase that builds one word at a time. |
|
||||
| `font_size` | `72` | 36px to 160px, step 2px | Maximum text size in pixels. Longer phrases shrink to stay inside the frame. |
|
||||
| `entry_offset` | `88` | 20px to 160px, step 4px | Horizontal distance each new word travels before it lands. |
|
||||
| `color` | `#171717` | color | Text color for the full phrase. |
|
||||
| `font_weight` | `600` | `500`, `600`, `700`, `800` | Weight used by every word in the phrase. |
|
||||
|
||||
Set them with `data-variable-values` on the element that mounts it. These are the
|
||||
defaults, so this behaves exactly like the preview above until you change one:
|
||||
|
||||
```html wrap
|
||||
<div
|
||||
data-composition-id="kinetic-center-build"
|
||||
data-composition-src="compositions/components/kinetic-center-build.html"
|
||||
data-variable-values='{"text":"Words push left.","font_size":72,"entry_offset":88,"color":"#171717","font_weight":"600"}'
|
||||
></div>
|
||||
```
|
||||
|
||||
## Source
|
||||
|
||||
<Accordion title={`kinetic-center-build.html`}>
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<!--
|
||||
Kinetic Center Build
|
||||
|
||||
Words enter from the right while the existing phrase shifts left, keeping
|
||||
every intermediate phrase centered until the final line locks in place.
|
||||
|
||||
Native HyperFrames port prepared through Hyfrme from:
|
||||
https://github.com/Remocn/remocn/blob/ea730a20b4ab09430ee7292aebc847c002375151/registry/remocn/kinetic-center-build/index.tsx
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Remocn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-->
|
||||
<html
|
||||
lang="en"
|
||||
data-composition-id="kinetic-center-build"
|
||||
data-composition-duration="2"
|
||||
data-composition-variables='[
|
||||
{ "id": "text", "type": "string", "role": "content", "label": "Text", "description": "Short phrase that builds one word at a time.", "default": "Words push left." },
|
||||
{ "id": "font_size", "type": "number", "role": "style", "label": "Font size", "description": "Maximum text size in pixels. Longer phrases shrink to stay inside the frame.", "default": 72, "min": 36, "max": 160, "step": 2, "unit": "px" },
|
||||
{ "id": "entry_offset", "type": "number", "role": "motion", "label": "Entry offset", "description": "Horizontal distance each new word travels before it lands.", "default": 88, "min": 20, "max": 160, "step": 4, "unit": "px" },
|
||||
{ "id": "color", "type": "color", "role": "style", "label": "Color", "description": "Text color for the full phrase.", "default": "#171717" },
|
||||
{ "id": "font_weight", "type": "enum", "role": "style", "label": "Font weight", "description": "Weight used by every word in the phrase.", "default": "600", "options": [{ "value": "500", "label": "Medium" }, { "value": "600", "label": "Semibold" }, { "value": "700", "label": "Bold" }, { "value": "800", "label": "Extra bold" }] }
|
||||
]'
|
||||
>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Kinetic Center Build</title>
|
||||
</head>
|
||||
<body>
|
||||
<template>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Geist:wght@500;600;700;800&display=block"
|
||||
/>
|
||||
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#root {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
container-type: size;
|
||||
isolation: isolate;
|
||||
color: var(--color, #171717);
|
||||
font-family: var(--font-display, Geist, system-ui, sans-serif);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.kcb-clip,
|
||||
.kcb-stage {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.kcb-stage {
|
||||
letter-spacing: -0.03em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kcb-word {
|
||||
--kcb-blur: 0px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
filter: blur(var(--kcb-blur));
|
||||
backface-visibility: hidden;
|
||||
will-change: transform, filter, opacity;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="root" data-composition-id="kinetic-center-build" data-duration="2" data-fps="30">
|
||||
<div
|
||||
id="kcb-clip"
|
||||
class="kcb-clip clip"
|
||||
data-start="0"
|
||||
data-duration="2"
|
||||
data-track-index="0"
|
||||
>
|
||||
<div id="kcb-stage" class="kcb-stage" role="img"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/CustomEase.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var root = document.getElementById("root");
|
||||
var stage = document.getElementById("kcb-stage");
|
||||
var vars =
|
||||
window.__hyperframes && window.__hyperframes.getVariables
|
||||
? window.__hyperframes.getVariables()
|
||||
: {};
|
||||
|
||||
function numberBetween(value, fallback, min, max) {
|
||||
var parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return fallback;
|
||||
return Math.min(max, Math.max(min, parsed));
|
||||
}
|
||||
|
||||
var text = typeof vars.text === "string" ? vars.text.trim() : "";
|
||||
if (!text) text = "Words push left.";
|
||||
|
||||
var words = text.split(/\s+/);
|
||||
var requestedFontSize = numberBetween(vars.font_size, 72, 36, 160);
|
||||
var entryOffset = numberBetween(vars.entry_offset, 88, 20, 160);
|
||||
var fontWeight =
|
||||
vars.font_weight === "500" || vars.font_weight === "700" || vars.font_weight === "800"
|
||||
? vars.font_weight
|
||||
: "600";
|
||||
|
||||
stage.style.fontWeight = fontWeight;
|
||||
stage.setAttribute("aria-label", text);
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
var context = canvas.getContext("2d");
|
||||
var measureFont = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
||||
var gapRatio = 10 / 72;
|
||||
|
||||
function measure(fontSize) {
|
||||
if (!context) {
|
||||
return words.map(function (word) {
|
||||
return word.length * fontSize * 0.55;
|
||||
});
|
||||
}
|
||||
context.font = fontWeight + " " + fontSize + "px " + measureFont;
|
||||
return words.map(function (word) {
|
||||
return context.measureText(word).width;
|
||||
});
|
||||
}
|
||||
|
||||
var fontSize = requestedFontSize;
|
||||
var widths = measure(fontSize);
|
||||
var gap = fontSize * gapRatio;
|
||||
var fullWidth = widths.reduce(
|
||||
function (total, width) {
|
||||
return total + width;
|
||||
},
|
||||
gap * Math.max(0, words.length - 1),
|
||||
);
|
||||
var maxWidth = Math.max(1, root.clientWidth * 0.9);
|
||||
|
||||
if (fullWidth > maxWidth) {
|
||||
fontSize = Math.max(24, fontSize * (maxWidth / fullWidth));
|
||||
widths = measure(fontSize);
|
||||
gap = fontSize * gapRatio;
|
||||
}
|
||||
|
||||
stage.style.fontSize = fontSize + "px";
|
||||
|
||||
var positionsAt = [];
|
||||
for (var count = 1; count <= words.length; count += 1) {
|
||||
var total = gap * (count - 1);
|
||||
for (var widthIndex = 0; widthIndex < count; widthIndex += 1) {
|
||||
total += widths[widthIndex];
|
||||
}
|
||||
|
||||
var cursor = -total / 2;
|
||||
var positions = [];
|
||||
for (var positionIndex = 0; positionIndex < count; positionIndex += 1) {
|
||||
positions.push(cursor + widths[positionIndex] / 2);
|
||||
cursor += widths[positionIndex] + gap;
|
||||
}
|
||||
positionsAt.push(positions);
|
||||
}
|
||||
|
||||
var elements = words.map(function (word, index) {
|
||||
var element = document.createElement("span");
|
||||
element.id = "kcb-word-" + index;
|
||||
element.className = "kcb-word";
|
||||
element.textContent = word;
|
||||
element.setAttribute("aria-hidden", "true");
|
||||
stage.appendChild(element);
|
||||
return element;
|
||||
});
|
||||
|
||||
gsap.registerPlugin(CustomEase);
|
||||
var entryEase = CustomEase.create("kcb-entry-ease", "M0,0 C0.2,0.8 0.2,1 1,1");
|
||||
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "2"));
|
||||
var firstDuration = 10 / 30;
|
||||
var pushDuration = 13 / 30;
|
||||
var authoredBuildDuration =
|
||||
firstDuration + Math.max(0, words.length - 1) * pushDuration;
|
||||
var availableBuildDuration = Math.max(0.25, duration - 0.5);
|
||||
var timingScale = Math.min(1, availableBuildDuration / authoredBuildDuration);
|
||||
firstDuration *= timingScale;
|
||||
pushDuration *= timingScale;
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
elements.forEach(function (element, index) {
|
||||
var targetX = positionsAt[index][index];
|
||||
var start = index === 0 ? 0 : firstDuration + (index - 1) * pushDuration;
|
||||
var moveDuration = index === 0 ? firstDuration : pushDuration;
|
||||
var fromX = index === 0 ? targetX : targetX + entryOffset;
|
||||
var fromY = index === 0 ? 6 : 0;
|
||||
|
||||
tl.fromTo(
|
||||
element,
|
||||
{
|
||||
x: fromX,
|
||||
y: fromY,
|
||||
xPercent: -50,
|
||||
yPercent: -50,
|
||||
scale: 0.992,
|
||||
opacity: 0,
|
||||
"--kcb-blur": "3.5px",
|
||||
},
|
||||
{
|
||||
x: targetX,
|
||||
y: 0,
|
||||
xPercent: -50,
|
||||
yPercent: -50,
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
"--kcb-blur": "0px",
|
||||
duration: moveDuration,
|
||||
ease: entryEase,
|
||||
immediateRender: index === 0,
|
||||
},
|
||||
start,
|
||||
);
|
||||
|
||||
if (index === 0) return;
|
||||
|
||||
for (var priorIndex = 0; priorIndex < index; priorIndex += 1) {
|
||||
var priorElement = elements[priorIndex];
|
||||
var nextX = positionsAt[index][priorIndex];
|
||||
var blurPeak = start + moveDuration / 2;
|
||||
|
||||
tl.to(priorElement, { x: nextX, duration: moveDuration, ease: entryEase }, start);
|
||||
tl.to(
|
||||
priorElement,
|
||||
{ "--kcb-blur": "0.8px", duration: moveDuration / 2, ease: "none" },
|
||||
start,
|
||||
);
|
||||
tl.to(
|
||||
priorElement,
|
||||
{ "--kcb-blur": "0px", duration: moveDuration / 2, ease: "none" },
|
||||
blurPeak,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["kinetic-center-build"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
{/* hf:generated-footer */}
|
||||
|
||||
Tagged `motion-primitive` `text-effects` `kinetic-type` `reveal` `remocn-port`.
|
||||
|
||||
Created by [Remocn](https://github.com/Remocn/remocn).
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Browse the complete Catalog](/catalog)
|
||||
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
|
||||
- [Build a richer composition](/go-further)
|
||||
@@ -297,6 +297,7 @@
|
||||
"catalog/blocks/hw-title",
|
||||
"catalog/blocks/hw-write-title",
|
||||
"catalog/components/inline-highlight",
|
||||
"catalog/components/kinetic-center-build",
|
||||
"catalog/components/kinetic-type-swap",
|
||||
"catalog/components/line-by-line-slide",
|
||||
"catalog/components/line-swap",
|
||||
|
||||
@@ -2241,6 +2241,21 @@
|
||||
"href": "/catalog/components/keyframe-scrub-stack",
|
||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/keyframe-scrub-stack.png"
|
||||
},
|
||||
{
|
||||
"name": "kinetic-center-build",
|
||||
"type": "component",
|
||||
"title": "Kinetic Center Build",
|
||||
"description": "Words enter from the right and push the growing phrase left until the final line locks centered.",
|
||||
"tags": [
|
||||
"motion-primitive",
|
||||
"text-effects",
|
||||
"kinetic-type",
|
||||
"reveal",
|
||||
"remocn-port"
|
||||
],
|
||||
"href": "/catalog/components/kinetic-center-build",
|
||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/kinetic-center-build.png"
|
||||
},
|
||||
{
|
||||
"name": "kinetic-type-swap",
|
||||
"type": "component",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -2,7 +2,7 @@
|
||||
"model": "bge-small-en-v1.5",
|
||||
"modelRevision": "ea104dacec62c0de699686887e3f920caeb4f3e3",
|
||||
"dimensions": 384,
|
||||
"revision": "2611d04335aacf5a70649019f948e5ba2f474c38bc45cd59e179d71294cd8cb8",
|
||||
"revision": "fa440b4e9cf8dd760a65776d39a118a57e02fb52c1b0efd9c1d2e9ec04b11930",
|
||||
"names": [
|
||||
"ai-chat-reveal",
|
||||
"animated-bar-chart",
|
||||
@@ -156,6 +156,7 @@
|
||||
"ios26-liquid-glass",
|
||||
"iris-reveal",
|
||||
"keyframe-scrub-stack",
|
||||
"kinetic-center-build",
|
||||
"kinetic-type-swap",
|
||||
"light-leak",
|
||||
"light-sweep-pass",
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Kinetic Center Build demo. The 1280x720 frame and default values match the
|
||||
pinned Hyfrme parity fixture so reviewers can compare the motion directly.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1280, height=720" />
|
||||
<title>Kinetic Center Build Demo</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Geist:wght@600&display=block"
|
||||
/>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 1280px;
|
||||
height: 720px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
--font-display: Geist, system-ui, sans-serif;
|
||||
position: relative;
|
||||
width: 1280px;
|
||||
height: 720px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.kcb-demo-background,
|
||||
.kcb-demo-mount {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 1280px;
|
||||
height: 720px;
|
||||
}
|
||||
|
||||
.kcb-demo-background {
|
||||
background: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="kinetic-center-build-demo"
|
||||
data-start="0"
|
||||
data-width="1280"
|
||||
data-height="720"
|
||||
data-duration="2"
|
||||
data-fps="30"
|
||||
>
|
||||
<div
|
||||
id="kcb-demo-background"
|
||||
class="kcb-demo-background clip"
|
||||
data-start="0"
|
||||
data-duration="2"
|
||||
data-track-index="0"
|
||||
></div>
|
||||
<div
|
||||
id="kcb-demo-mount"
|
||||
class="kcb-demo-mount clip"
|
||||
data-composition-id="kinetic-center-build"
|
||||
data-composition-src="./kinetic-center-build.html"
|
||||
data-start="0"
|
||||
data-duration="2"
|
||||
data-track-index="1"
|
||||
data-width="1280"
|
||||
data-height="720"
|
||||
></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-center-build-demo"] = gsap.timeline({ paused: true });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,284 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Kinetic Center Build
|
||||
|
||||
Words enter from the right while the existing phrase shifts left, keeping
|
||||
every intermediate phrase centered until the final line locks in place.
|
||||
|
||||
Native HyperFrames port prepared through Hyfrme from:
|
||||
https://github.com/Remocn/remocn/blob/ea730a20b4ab09430ee7292aebc847c002375151/registry/remocn/kinetic-center-build/index.tsx
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Remocn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-->
|
||||
<html
|
||||
lang="en"
|
||||
data-composition-id="kinetic-center-build"
|
||||
data-composition-duration="2"
|
||||
data-composition-variables='[
|
||||
{ "id": "text", "type": "string", "role": "content", "label": "Text", "description": "Short phrase that builds one word at a time.", "default": "Words push left." },
|
||||
{ "id": "font_size", "type": "number", "role": "style", "label": "Font size", "description": "Maximum text size in pixels. Longer phrases shrink to stay inside the frame.", "default": 72, "min": 36, "max": 160, "step": 2, "unit": "px" },
|
||||
{ "id": "entry_offset", "type": "number", "role": "motion", "label": "Entry offset", "description": "Horizontal distance each new word travels before it lands.", "default": 88, "min": 20, "max": 160, "step": 4, "unit": "px" },
|
||||
{ "id": "color", "type": "color", "role": "style", "label": "Color", "description": "Text color for the full phrase.", "default": "#171717" },
|
||||
{ "id": "font_weight", "type": "enum", "role": "style", "label": "Font weight", "description": "Weight used by every word in the phrase.", "default": "600", "options": [{ "value": "500", "label": "Medium" }, { "value": "600", "label": "Semibold" }, { "value": "700", "label": "Bold" }, { "value": "800", "label": "Extra bold" }] }
|
||||
]'
|
||||
>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Kinetic Center Build</title>
|
||||
</head>
|
||||
<body>
|
||||
<template>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Geist:wght@500;600;700;800&display=block"
|
||||
/>
|
||||
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#root {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
container-type: size;
|
||||
isolation: isolate;
|
||||
color: var(--color, #171717);
|
||||
font-family: var(--font-display, Geist, system-ui, sans-serif);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.kcb-clip,
|
||||
.kcb-stage {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.kcb-stage {
|
||||
letter-spacing: -0.03em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kcb-word {
|
||||
--kcb-blur: 0px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
filter: blur(var(--kcb-blur));
|
||||
backface-visibility: hidden;
|
||||
will-change: transform, filter, opacity;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="root" data-composition-id="kinetic-center-build" data-duration="2" data-fps="30">
|
||||
<div
|
||||
id="kcb-clip"
|
||||
class="kcb-clip clip"
|
||||
data-start="0"
|
||||
data-duration="2"
|
||||
data-track-index="0"
|
||||
>
|
||||
<div id="kcb-stage" class="kcb-stage" role="img"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/CustomEase.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var root = document.getElementById("root");
|
||||
var stage = document.getElementById("kcb-stage");
|
||||
var vars =
|
||||
window.__hyperframes && window.__hyperframes.getVariables
|
||||
? window.__hyperframes.getVariables()
|
||||
: {};
|
||||
|
||||
function numberBetween(value, fallback, min, max) {
|
||||
var parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return fallback;
|
||||
return Math.min(max, Math.max(min, parsed));
|
||||
}
|
||||
|
||||
var text = typeof vars.text === "string" ? vars.text.trim() : "";
|
||||
if (!text) text = "Words push left.";
|
||||
|
||||
var words = text.split(/\s+/);
|
||||
var requestedFontSize = numberBetween(vars.font_size, 72, 36, 160);
|
||||
var entryOffset = numberBetween(vars.entry_offset, 88, 20, 160);
|
||||
var fontWeight =
|
||||
vars.font_weight === "500" || vars.font_weight === "700" || vars.font_weight === "800"
|
||||
? vars.font_weight
|
||||
: "600";
|
||||
|
||||
stage.style.fontWeight = fontWeight;
|
||||
stage.setAttribute("aria-label", text);
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
var context = canvas.getContext("2d");
|
||||
var measureFont = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
||||
var gapRatio = 10 / 72;
|
||||
|
||||
function measure(fontSize) {
|
||||
if (!context) {
|
||||
return words.map(function (word) {
|
||||
return word.length * fontSize * 0.55;
|
||||
});
|
||||
}
|
||||
context.font = fontWeight + " " + fontSize + "px " + measureFont;
|
||||
return words.map(function (word) {
|
||||
return context.measureText(word).width;
|
||||
});
|
||||
}
|
||||
|
||||
var fontSize = requestedFontSize;
|
||||
var widths = measure(fontSize);
|
||||
var gap = fontSize * gapRatio;
|
||||
var fullWidth = widths.reduce(
|
||||
function (total, width) {
|
||||
return total + width;
|
||||
},
|
||||
gap * Math.max(0, words.length - 1),
|
||||
);
|
||||
var maxWidth = Math.max(1, root.clientWidth * 0.9);
|
||||
|
||||
if (fullWidth > maxWidth) {
|
||||
fontSize = Math.max(24, fontSize * (maxWidth / fullWidth));
|
||||
widths = measure(fontSize);
|
||||
gap = fontSize * gapRatio;
|
||||
}
|
||||
|
||||
stage.style.fontSize = fontSize + "px";
|
||||
|
||||
var positionsAt = [];
|
||||
for (var count = 1; count <= words.length; count += 1) {
|
||||
var total = gap * (count - 1);
|
||||
for (var widthIndex = 0; widthIndex < count; widthIndex += 1) {
|
||||
total += widths[widthIndex];
|
||||
}
|
||||
|
||||
var cursor = -total / 2;
|
||||
var positions = [];
|
||||
for (var positionIndex = 0; positionIndex < count; positionIndex += 1) {
|
||||
positions.push(cursor + widths[positionIndex] / 2);
|
||||
cursor += widths[positionIndex] + gap;
|
||||
}
|
||||
positionsAt.push(positions);
|
||||
}
|
||||
|
||||
var elements = words.map(function (word, index) {
|
||||
var element = document.createElement("span");
|
||||
element.id = "kcb-word-" + index;
|
||||
element.className = "kcb-word";
|
||||
element.textContent = word;
|
||||
element.setAttribute("aria-hidden", "true");
|
||||
stage.appendChild(element);
|
||||
return element;
|
||||
});
|
||||
|
||||
gsap.registerPlugin(CustomEase);
|
||||
var entryEase = CustomEase.create("kcb-entry-ease", "M0,0 C0.2,0.8 0.2,1 1,1");
|
||||
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "2"));
|
||||
var firstDuration = 10 / 30;
|
||||
var pushDuration = 13 / 30;
|
||||
var authoredBuildDuration =
|
||||
firstDuration + Math.max(0, words.length - 1) * pushDuration;
|
||||
var availableBuildDuration = Math.max(0.25, duration - 0.5);
|
||||
var timingScale = Math.min(1, availableBuildDuration / authoredBuildDuration);
|
||||
firstDuration *= timingScale;
|
||||
pushDuration *= timingScale;
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
elements.forEach(function (element, index) {
|
||||
var targetX = positionsAt[index][index];
|
||||
var start = index === 0 ? 0 : firstDuration + (index - 1) * pushDuration;
|
||||
var moveDuration = index === 0 ? firstDuration : pushDuration;
|
||||
var fromX = index === 0 ? targetX : targetX + entryOffset;
|
||||
var fromY = index === 0 ? 6 : 0;
|
||||
|
||||
tl.fromTo(
|
||||
element,
|
||||
{
|
||||
x: fromX,
|
||||
y: fromY,
|
||||
xPercent: -50,
|
||||
yPercent: -50,
|
||||
scale: 0.992,
|
||||
opacity: 0,
|
||||
"--kcb-blur": "3.5px",
|
||||
},
|
||||
{
|
||||
x: targetX,
|
||||
y: 0,
|
||||
xPercent: -50,
|
||||
yPercent: -50,
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
"--kcb-blur": "0px",
|
||||
duration: moveDuration,
|
||||
ease: entryEase,
|
||||
immediateRender: index === 0,
|
||||
},
|
||||
start,
|
||||
);
|
||||
|
||||
if (index === 0) return;
|
||||
|
||||
for (var priorIndex = 0; priorIndex < index; priorIndex += 1) {
|
||||
var priorElement = elements[priorIndex];
|
||||
var nextX = positionsAt[index][priorIndex];
|
||||
var blurPeak = start + moveDuration / 2;
|
||||
|
||||
tl.to(priorElement, { x: nextX, duration: moveDuration, ease: entryEase }, start);
|
||||
tl.to(
|
||||
priorElement,
|
||||
{ "--kcb-blur": "0.8px", duration: moveDuration / 2, ease: "none" },
|
||||
start,
|
||||
);
|
||||
tl.to(
|
||||
priorElement,
|
||||
{ "--kcb-blur": "0px", duration: moveDuration / 2, ease: "none" },
|
||||
blurPeak,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["kinetic-center-build"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "kinetic-center-build",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Kinetic Center Build",
|
||||
"description": "Words enter from the right and push the growing phrase left until the final line locks centered.",
|
||||
"tags": ["motion-primitive", "text-effects", "kinetic-type", "reveal", "remocn-port"],
|
||||
"jobs": ["reveal"],
|
||||
"family": "text-effects",
|
||||
"profile": "build",
|
||||
"author": "Remocn",
|
||||
"authorUrl": "https://github.com/Remocn/remocn",
|
||||
"license": "MIT",
|
||||
"syncPoints": [
|
||||
{
|
||||
"id": "phrase-locked",
|
||||
"phase": "in",
|
||||
"offset": 1.2,
|
||||
"description": "Calibrated to the default three-word text; custom word counts change the lock time."
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "kinetic-center-build.html",
|
||||
"target": "compositions/components/kinetic-center-build.html",
|
||||
"type": "hyperframes:snippet"
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{
|
||||
"id": "text",
|
||||
"type": "string",
|
||||
"role": "content",
|
||||
"label": "Text",
|
||||
"description": "Short phrase that builds one word at a time.",
|
||||
"default": "Words push left."
|
||||
},
|
||||
{
|
||||
"id": "font_size",
|
||||
"type": "number",
|
||||
"role": "style",
|
||||
"label": "Font size",
|
||||
"description": "Maximum text size in pixels. Longer phrases shrink to stay inside the frame.",
|
||||
"default": 72,
|
||||
"min": 36,
|
||||
"max": 160,
|
||||
"step": 2,
|
||||
"unit": "px"
|
||||
},
|
||||
{
|
||||
"id": "entry_offset",
|
||||
"type": "number",
|
||||
"role": "motion",
|
||||
"label": "Entry offset",
|
||||
"description": "Horizontal distance each new word travels before it lands.",
|
||||
"default": 88,
|
||||
"min": 20,
|
||||
"max": 160,
|
||||
"step": 4,
|
||||
"unit": "px"
|
||||
},
|
||||
{
|
||||
"id": "color",
|
||||
"type": "color",
|
||||
"role": "style",
|
||||
"label": "Color",
|
||||
"description": "Text color for the full phrase.",
|
||||
"default": "#171717"
|
||||
},
|
||||
{
|
||||
"id": "font_weight",
|
||||
"type": "enum",
|
||||
"role": "style",
|
||||
"label": "Font weight",
|
||||
"description": "Weight used by every word in the phrase.",
|
||||
"default": "600",
|
||||
"options": [
|
||||
{ "value": "500", "label": "Medium" },
|
||||
{ "value": "600", "label": "Semibold" },
|
||||
{ "value": "700", "label": "Bold" },
|
||||
{ "value": "800", "label": "Extra bold" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -995,6 +995,10 @@
|
||||
"name": "keyframe-scrub-stack",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "kinetic-center-build",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "kinetic-type-swap",
|
||||
"type": "hyperframes:component"
|
||||
@@ -1533,6 +1537,6 @@
|
||||
}
|
||||
],
|
||||
"catalogArtifact": {
|
||||
"revision": "2611d04335aacf5a70649019f948e5ba2f474c38bc45cd59e179d71294cd8cb8"
|
||||
"revision": "fa440b4e9cf8dd760a65776d39a118a57e02fb52c1b0efd9c1d2e9ec04b11930"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user