mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 16:42:27 +00:00
* 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>
646 lines
26 KiB
Plaintext
646 lines
26 KiB
Plaintext
---
|
|
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)
|