Files
hyperframes/docs/catalog/components/streaming-text.mdx
miga-heygenandMiguel Ángel 7b5fdb1ad2 fix(docs): emit catalog source accordion titles as plain strings (#4006)
The generated catalog pages rendered the "Source" accordion title as a
JSX expression, `<Accordion title={`name.html`}>`. Mintlify's search
indexer stringifies expression attribute nodes rather than evaluating
them, so every one of the 399 catalog pages surfaced its Source section
in search results as "[object Object]" (visible on the Scroll Feed page,
among others).

The template literal carried no escaping benefit: every title is a
single-segment `name.html` path, and a double-quoted JSX string handles
it directly. Introduce `mdxStringAttribute`, which emits a plain
`name="value"` attribute and escapes `& " < { }` as HTML character
references (MDX decodes references inside quoted attribute values, and
unlike JSON.stringify it does not rely on backslash escapes, which JSX
strings do not honour). Use it for both the Source accordion title and
the preview iframe title, and regenerate the catalog pages.

Regenerated with `npx tsx scripts/generate-catalog-pages.ts`; the only
change across the 399 pages is the one Accordion title line each.

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-16 11:55:00 -04:00

586 lines
25 KiB
Plaintext

---
title: "Streaming Text"
description: "Words arrive the way an AI answer streams - in uneven bursts on a measured token rhythm, each word landing grey and inking to full colour - rather than the even tick of a typewriter"
---
import { InstallCommand } from "/snippets/install-command.jsx";
import { VariablesExplorer } from "/snippets/variables-explorer.jsx";
## Install
<InstallCommand command="npx hyperframes add streaming-text" item="streaming-text" />
That writes one file: `compositions/components/streaming-text.html`.
<VariablesExplorer
previewSrc="/public/catalog/components/streaming-text.json"
compositionId="streaming-text"
compositionSrc="compositions/components/streaming-text.html"
variables={[{"id":"size","type":"number","role":"style","label":"Size","description":"Type size of the streamed words.","default":27,"min":18,"max":64,"step":1,"unit":"px"},{"id":"tone","type":"enum","role":"style","label":"Tone","description":"Ink pair: dark words on a light ground, or light words on a dark ground.","default":"dark-on-light","options":[{"value":"dark-on-light","label":"Dark on light"},{"value":"light-on-dark","label":"Light on dark"}]}]}
>
```html streaming-text.html
<!--
Streaming Text - AI-answer token-flow primitive.
Paste the markup, CSS and script into a composition. Use the timeline
integration note at the bottom as the starting point for deterministic GSAP
animation.
This is not a typewriter. A typewriter is even; an AI answer arrives in
bursts - two words together, a pause, three more. The GAPS table in the
integration note is that rhythm, measured word-by-word from reference
footage of a real streamed answer, and each word lands grey and inks to
full colour over 0.27s, the way fresh tokens read before the eye settles.
Wrap each word in a span with class "w". The script only styles; the
timeline recipe reveals.
Variables. The script reads each one, falls back to the declared default on
anything missing or unrecognised, and writes the result as a custom property
the CSS below consumes.
- size (18 to 64, default 27, in px): type size.
- tone (dark-on-light | light-on-dark, default dark-on-light): the ink
pair. Fresh-token grey is chosen to sit between the two.
On every default the result matches the measured reference: 27px ink on a
light chat ground.
-->
<div
data-hf-ui-root
class="hf-ui-streaming-text"
aria-label="Streamed answer text"
data-composition-variables='[
{ "id": "size", "type": "number", "role": "style", "label": "Size", "description": "Type size of the streamed words.", "default": 27, "min": 18, "max": 64, "step": 1, "unit": "px" },
{ "id": "tone", "type": "enum", "role": "style", "label": "Tone", "description": "Ink pair: dark words on a light ground, or light words on a dark ground.", "default": "dark-on-light", "options": [{ "value": "dark-on-light", "label": "Dark on light" }, { "value": "light-on-dark", "label": "Light on dark" }] }
]'
>
<p class="hf-st-p">
<span class="w">You</span> <span class="w">don&#39;t</span> <span class="w">need</span>
<span class="w">a</span> <span class="w">video</span> <span class="w">editor.</span>
<span class="w">HyperFrames</span> <span class="w">renders</span> <span class="w">the</span>
<span class="w">HTML</span> <span class="w">you</span> <span class="w">already</span>
<span class="w">write</span> <span class="w">into</span> <span class="w">pixel-perfect,</span>
<span class="w">deterministic</span> <span class="w">video.</span>
</p>
</div>
<style>
/* hf-ui:tokens:start */
/* prettier-ignore */
[data-hf-ui-root] {
--_hf-ui-accent: var(--hf-ui-accent, #3ce6ac);
--_hf-ui-action: var(--hf-ui-action, #eeeeee);
--_hf-ui-action-contrast: var(--hf-ui-action-contrast, #111111);
--_hf-ui-border: var(--hf-ui-border, #2a2a2a);
--_hf-ui-border-strong: var(--hf-ui-border-strong, #383838);
--_hf-ui-canvas: var(--hf-ui-canvas, #0a0a0a);
--_hf-ui-control-border: var(--hf-ui-control-border, #686868);
--_hf-ui-control-border-hover: var(--hf-ui-control-border-hover, #777777);
--_hf-ui-control-height: var(--hf-ui-control-height, 40px);
--_hf-ui-danger: var(--hf-ui-danger, #ff7878);
--_hf-ui-data-1: var(--hf-ui-data-1, #3ce6ac);
--_hf-ui-data-2: var(--hf-ui-data-2, #63a5ff);
--_hf-ui-data-3: var(--hf-ui-data-3, #e9ab55);
--_hf-ui-data-4: var(--hf-ui-data-4, #a78bfa);
--_hf-ui-data-5: var(--hf-ui-data-5, #ff8a75);
--_hf-ui-duration-drawer-enter: var(--hf-ui-duration-drawer-enter, 360ms);
--_hf-ui-duration-drawer-exit: var(--hf-ui-duration-drawer-exit, 240ms);
--_hf-ui-duration-modal-enter: var(--hf-ui-duration-modal-enter, 280ms);
--_hf-ui-duration-modal-exit: var(--hf-ui-duration-modal-exit, 180ms);
--_hf-ui-duration-overlay-enter: var(--hf-ui-duration-overlay-enter, 210ms);
--_hf-ui-duration-overlay-exit: var(--hf-ui-duration-overlay-exit, 140ms);
--_hf-ui-duration-press: var(--hf-ui-duration-press, 120ms);
--_hf-ui-duration-state: var(--hf-ui-duration-state, 160ms);
--_hf-ui-ease-enter: var(--hf-ui-ease-enter, cubic-bezier(0.32, 0.72, 0, 1));
--_hf-ui-ease-exit: var(--hf-ui-ease-exit, cubic-bezier(0.23, 1, 0.32, 1));
--_hf-ui-ease-response: var(--hf-ui-ease-response, cubic-bezier(0.23, 1, 0.32, 1));
--_hf-ui-font-mono: var(--hf-ui-font-mono, ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace);
--_hf-ui-font-sans: var(--hf-ui-font-sans, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
--_hf-ui-hit-target: var(--hf-ui-hit-target, 44px);
--_hf-ui-radius-control: var(--hf-ui-radius-control, 5px);
--_hf-ui-radius-overlay: var(--hf-ui-radius-overlay, 10px);
--_hf-ui-radius-surface: var(--hf-ui-radius-surface, 7px);
--_hf-ui-shadow-contact: var(--hf-ui-shadow-contact, 0 8px 18px rgba(0, 0, 0, 0.38));
--_hf-ui-shadow-inset: var(--hf-ui-shadow-inset, inset 0 1px 0 rgba(255, 255, 255, 0.035));
--_hf-ui-shadow-modal: var(--hf-ui-shadow-modal, 0 18px 46px rgba(0, 0, 0, 0.52));
--_hf-ui-space-1: var(--hf-ui-space-1, 4px);
--_hf-ui-space-12: var(--hf-ui-space-12, 48px);
--_hf-ui-space-16: var(--hf-ui-space-16, 64px);
--_hf-ui-space-2: var(--hf-ui-space-2, 8px);
--_hf-ui-space-3: var(--hf-ui-space-3, 12px);
--_hf-ui-space-4: var(--hf-ui-space-4, 16px);
--_hf-ui-space-6: var(--hf-ui-space-6, 24px);
--_hf-ui-space-8: var(--hf-ui-space-8, 32px);
--_hf-ui-surface: var(--hf-ui-surface, #141414);
--_hf-ui-surface-inset: var(--hf-ui-surface-inset, #101010);
--_hf-ui-surface-raised: var(--hf-ui-surface-raised, #1a1a1a);
--_hf-ui-text: var(--hf-ui-text, #e5e5e5);
--_hf-ui-text-faint: var(--hf-ui-text-faint, #7e7e7e);
--_hf-ui-text-muted: var(--hf-ui-text-muted, #929292);
--_hf-ui-text-strong: var(--hf-ui-text-strong, #f3f3f3);
--_hf-ui-warning: var(--hf-ui-warning, #e9ab55);
--_hf-ui-weight-medium: var(--hf-ui-weight-medium, 500);
--_hf-ui-weight-regular: var(--hf-ui-weight-regular, 400);
--_hf-ui-weight-semibold: var(--hf-ui-weight-semibold, 600);
box-sizing: border-box;
color-scheme: dark;
font-family: var(--_hf-ui-font-sans);
}
/* prettier-ignore */
[data-hf-ui-root] *,
[data-hf-ui-root] *::before,
[data-hf-ui-root] *::after {
box-sizing: border-box;
}
/* prettier-ignore */
[data-hf-theme="light"] [data-hf-ui-root],
[data-hf-ui-root][data-hf-theme="light"] {
--_hf-ui-accent: var(--hf-ui-accent, #087a57);
--_hf-ui-action: var(--hf-ui-action, #171717);
--_hf-ui-action-contrast: var(--hf-ui-action-contrast, #ffffff);
--_hf-ui-border: var(--hf-ui-border, #e0dfdb);
--_hf-ui-border-strong: var(--hf-ui-border-strong, #cbc9c3);
--_hf-ui-canvas: var(--hf-ui-canvas, #f6f5f1);
--_hf-ui-control-border: var(--hf-ui-control-border, #888888);
--_hf-ui-control-border-hover: var(--hf-ui-control-border-hover, #6f6e68);
--_hf-ui-danger: var(--hf-ui-danger, #a61b1b);
--_hf-ui-data-1: var(--hf-ui-data-1, #087a57);
--_hf-ui-data-2: var(--hf-ui-data-2, #245ea8);
--_hf-ui-data-3: var(--hf-ui-data-3, #8a4b00);
--_hf-ui-data-4: var(--hf-ui-data-4, #6d3fc0);
--_hf-ui-data-5: var(--hf-ui-data-5, #a83d2d);
--_hf-ui-shadow-contact: var(--hf-ui-shadow-contact, 0 7px 16px rgba(26, 24, 20, 0.13));
--_hf-ui-shadow-inset: var(--hf-ui-shadow-inset, inset 0 1px 0 rgba(255, 255, 255, 0.78));
--_hf-ui-shadow-modal: var(--hf-ui-shadow-modal, 0 18px 46px rgba(26, 24, 20, 0.18));
--_hf-ui-surface: var(--hf-ui-surface, #ffffff);
--_hf-ui-surface-inset: var(--hf-ui-surface-inset, #f0efeb);
--_hf-ui-surface-raised: var(--hf-ui-surface-raised, #ffffff);
--_hf-ui-text: var(--hf-ui-text, #171717);
--_hf-ui-text-faint: var(--hf-ui-text-faint, #6f6e68);
--_hf-ui-text-muted: var(--hf-ui-text-muted, #5f5f5a);
--_hf-ui-text-strong: var(--hf-ui-text-strong, #0a0a0a);
--_hf-ui-warning: var(--hf-ui-warning, #8a4b00);
color-scheme: light;
}
/* prettier-ignore */
[data-hf-ui-root]:focus-visible {
outline: 2px solid var(--_hf-ui-accent);
outline-offset: 3px;
}
/* prettier-ignore */
[data-hf-rendering="true"][data-hf-ui-root],
[data-hf-rendering="true"] [data-hf-ui-root],
[data-hf-rendering="true"][data-hf-ui-root] *,
[data-hf-rendering="true"] [data-hf-ui-root] *,
[data-hf-rendering="true"][data-hf-ui-root]::before,
[data-hf-rendering="true"][data-hf-ui-root]::after,
[data-hf-rendering="true"] [data-hf-ui-root]::before,
[data-hf-rendering="true"] [data-hf-ui-root]::after,
[data-hf-rendering="true"][data-hf-ui-root] *::before,
[data-hf-rendering="true"][data-hf-ui-root] *::after,
[data-hf-rendering="true"] [data-hf-ui-root] *::before,
[data-hf-rendering="true"] [data-hf-ui-root] *::after {
animation-duration: 0s !important;
animation-delay: 0s !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
}
/* prettier-ignore */
@media (prefers-reduced-motion: reduce) {
[data-hf-ui-root],
[data-hf-ui-root] *,
[data-hf-ui-root] *::before,
[data-hf-ui-root] *::after {
animation-duration: 0s !important;
animation-delay: 0s !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
scroll-behavior: auto !important;
}
}
/* hf-ui:tokens:end */
.hf-ui-streaming-text {
font-size: var(--hf-st-size, 27px);
color: var(--hf-st-ink, #141414);
font-family: var(--_hf-ui-font-sans);
}
.hf-st-p {
margin: 0;
line-height: 1.43;
letter-spacing: 0.046em;
max-width: 24.5em;
}
.hf-ui-streaming-text .w {
opacity: 0;
}
</style>
<script>
(function () {
"use strict";
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
// Unrecognised overrides return to their declared defaults.
function pick(table, value, fallback) {
return Object.prototype.hasOwnProperty.call(table, value) ? value : fallback;
}
function clampNumber(value, min, max, fallback) {
var n = typeof value === "number" ? value : parseFloat(value);
if (!isFinite(n)) {
return fallback;
}
return Math.min(max, Math.max(min, n));
}
var tones = {
"dark-on-light": { ink: "#141414", fresh: "#767676" },
"light-on-dark": { ink: "#f3f3f3", fresh: "#5f5f5f" },
};
var size = clampNumber(vars.size, 18, 64, 27);
var tone = tones[pick(tones, vars.tone, "dark-on-light")];
var roots = document.querySelectorAll(".hf-ui-streaming-text");
for (var i = 0; i < roots.length; i += 1) {
roots[i].style.setProperty("--hf-st-size", size + "px");
roots[i].style.setProperty("--hf-st-ink", tone.ink);
roots[i].style.setProperty("--hf-st-fresh", tone.fresh);
}
})();
</script>
<!--
Timeline integration - GAPS is the measured token rhythm (seconds between
consecutive words, zeros are words that arrive together). Cycle it for any
word count; the ramp inks each word from fresh grey to full colour.
var GAPS = [
0.167, 0.100, 0.000, 0.367, 0.000, 0.167, 0.133, 0.100, 0.100, 0.133,
0.167, 0.133, 0.000, 0.233, 0.167, 0.100, 0.000, 0.133, 0.133, 0.100,
0.167, 0.133, 0.100, 0.133, 0.167, 0.100, 0.133, 0.100, 0.133,
];
var t = startTime;
gsap.utils.toArray('.hf-ui-streaming-text .w').forEach(function (w, i) {
if (i > 0) t += GAPS[i % GAPS.length];
tl.set(w, { opacity: 1 }, t);
tl.fromTo(w, { color: '#767676' }, { color: 'var(--hf-st-ink)', duration: 0.2667, ease: 'none' }, t);
});
-->
```
</VariablesExplorer>
## Paste it into your composition
Open `compositions/components/streaming-text.html` and copy what is inside into your own composition.
The file opens with a comment header that walks you through it.
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 |
| --- | --- | --- | --- |
| `size` | `27` | 18px to 64px, step 1px | Type size of the streamed words. |
| `tone` | `dark-on-light` | `dark-on-light`, `light-on-dark` | Ink pair: dark words on a light ground, or light words on a dark ground. |
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="streaming-text"
data-composition-src="compositions/components/streaming-text.html"
data-variable-values='{"size":27,"tone":"dark-on-light"}'
></div>
```
## Source
<Accordion title="streaming-text.html">
```html
<!--
Streaming Text - AI-answer token-flow primitive.
Paste the markup, CSS and script into a composition. Use the timeline
integration note at the bottom as the starting point for deterministic GSAP
animation.
This is not a typewriter. A typewriter is even; an AI answer arrives in
bursts - two words together, a pause, three more. The GAPS table in the
integration note is that rhythm, measured word-by-word from reference
footage of a real streamed answer, and each word lands grey and inks to
full colour over 0.27s, the way fresh tokens read before the eye settles.
Wrap each word in a span with class "w". The script only styles; the
timeline recipe reveals.
Variables. The script reads each one, falls back to the declared default on
anything missing or unrecognised, and writes the result as a custom property
the CSS below consumes.
- size (18 to 64, default 27, in px): type size.
- tone (dark-on-light | light-on-dark, default dark-on-light): the ink
pair. Fresh-token grey is chosen to sit between the two.
On every default the result matches the measured reference: 27px ink on a
light chat ground.
-->
<div
data-hf-ui-root
class="hf-ui-streaming-text"
aria-label="Streamed answer text"
data-composition-variables='[
{ "id": "size", "type": "number", "role": "style", "label": "Size", "description": "Type size of the streamed words.", "default": 27, "min": 18, "max": 64, "step": 1, "unit": "px" },
{ "id": "tone", "type": "enum", "role": "style", "label": "Tone", "description": "Ink pair: dark words on a light ground, or light words on a dark ground.", "default": "dark-on-light", "options": [{ "value": "dark-on-light", "label": "Dark on light" }, { "value": "light-on-dark", "label": "Light on dark" }] }
]'
>
<p class="hf-st-p">
<span class="w">You</span> <span class="w">don&#39;t</span> <span class="w">need</span>
<span class="w">a</span> <span class="w">video</span> <span class="w">editor.</span>
<span class="w">HyperFrames</span> <span class="w">renders</span> <span class="w">the</span>
<span class="w">HTML</span> <span class="w">you</span> <span class="w">already</span>
<span class="w">write</span> <span class="w">into</span> <span class="w">pixel-perfect,</span>
<span class="w">deterministic</span> <span class="w">video.</span>
</p>
</div>
<style>
/* hf-ui:tokens:start */
/* prettier-ignore */
[data-hf-ui-root] {
--_hf-ui-accent: var(--hf-ui-accent, #3ce6ac);
--_hf-ui-action: var(--hf-ui-action, #eeeeee);
--_hf-ui-action-contrast: var(--hf-ui-action-contrast, #111111);
--_hf-ui-border: var(--hf-ui-border, #2a2a2a);
--_hf-ui-border-strong: var(--hf-ui-border-strong, #383838);
--_hf-ui-canvas: var(--hf-ui-canvas, #0a0a0a);
--_hf-ui-control-border: var(--hf-ui-control-border, #686868);
--_hf-ui-control-border-hover: var(--hf-ui-control-border-hover, #777777);
--_hf-ui-control-height: var(--hf-ui-control-height, 40px);
--_hf-ui-danger: var(--hf-ui-danger, #ff7878);
--_hf-ui-data-1: var(--hf-ui-data-1, #3ce6ac);
--_hf-ui-data-2: var(--hf-ui-data-2, #63a5ff);
--_hf-ui-data-3: var(--hf-ui-data-3, #e9ab55);
--_hf-ui-data-4: var(--hf-ui-data-4, #a78bfa);
--_hf-ui-data-5: var(--hf-ui-data-5, #ff8a75);
--_hf-ui-duration-drawer-enter: var(--hf-ui-duration-drawer-enter, 360ms);
--_hf-ui-duration-drawer-exit: var(--hf-ui-duration-drawer-exit, 240ms);
--_hf-ui-duration-modal-enter: var(--hf-ui-duration-modal-enter, 280ms);
--_hf-ui-duration-modal-exit: var(--hf-ui-duration-modal-exit, 180ms);
--_hf-ui-duration-overlay-enter: var(--hf-ui-duration-overlay-enter, 210ms);
--_hf-ui-duration-overlay-exit: var(--hf-ui-duration-overlay-exit, 140ms);
--_hf-ui-duration-press: var(--hf-ui-duration-press, 120ms);
--_hf-ui-duration-state: var(--hf-ui-duration-state, 160ms);
--_hf-ui-ease-enter: var(--hf-ui-ease-enter, cubic-bezier(0.32, 0.72, 0, 1));
--_hf-ui-ease-exit: var(--hf-ui-ease-exit, cubic-bezier(0.23, 1, 0.32, 1));
--_hf-ui-ease-response: var(--hf-ui-ease-response, cubic-bezier(0.23, 1, 0.32, 1));
--_hf-ui-font-mono: var(--hf-ui-font-mono, ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace);
--_hf-ui-font-sans: var(--hf-ui-font-sans, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
--_hf-ui-hit-target: var(--hf-ui-hit-target, 44px);
--_hf-ui-radius-control: var(--hf-ui-radius-control, 5px);
--_hf-ui-radius-overlay: var(--hf-ui-radius-overlay, 10px);
--_hf-ui-radius-surface: var(--hf-ui-radius-surface, 7px);
--_hf-ui-shadow-contact: var(--hf-ui-shadow-contact, 0 8px 18px rgba(0, 0, 0, 0.38));
--_hf-ui-shadow-inset: var(--hf-ui-shadow-inset, inset 0 1px 0 rgba(255, 255, 255, 0.035));
--_hf-ui-shadow-modal: var(--hf-ui-shadow-modal, 0 18px 46px rgba(0, 0, 0, 0.52));
--_hf-ui-space-1: var(--hf-ui-space-1, 4px);
--_hf-ui-space-12: var(--hf-ui-space-12, 48px);
--_hf-ui-space-16: var(--hf-ui-space-16, 64px);
--_hf-ui-space-2: var(--hf-ui-space-2, 8px);
--_hf-ui-space-3: var(--hf-ui-space-3, 12px);
--_hf-ui-space-4: var(--hf-ui-space-4, 16px);
--_hf-ui-space-6: var(--hf-ui-space-6, 24px);
--_hf-ui-space-8: var(--hf-ui-space-8, 32px);
--_hf-ui-surface: var(--hf-ui-surface, #141414);
--_hf-ui-surface-inset: var(--hf-ui-surface-inset, #101010);
--_hf-ui-surface-raised: var(--hf-ui-surface-raised, #1a1a1a);
--_hf-ui-text: var(--hf-ui-text, #e5e5e5);
--_hf-ui-text-faint: var(--hf-ui-text-faint, #7e7e7e);
--_hf-ui-text-muted: var(--hf-ui-text-muted, #929292);
--_hf-ui-text-strong: var(--hf-ui-text-strong, #f3f3f3);
--_hf-ui-warning: var(--hf-ui-warning, #e9ab55);
--_hf-ui-weight-medium: var(--hf-ui-weight-medium, 500);
--_hf-ui-weight-regular: var(--hf-ui-weight-regular, 400);
--_hf-ui-weight-semibold: var(--hf-ui-weight-semibold, 600);
box-sizing: border-box;
color-scheme: dark;
font-family: var(--_hf-ui-font-sans);
}
/* prettier-ignore */
[data-hf-ui-root] *,
[data-hf-ui-root] *::before,
[data-hf-ui-root] *::after {
box-sizing: border-box;
}
/* prettier-ignore */
[data-hf-theme="light"] [data-hf-ui-root],
[data-hf-ui-root][data-hf-theme="light"] {
--_hf-ui-accent: var(--hf-ui-accent, #087a57);
--_hf-ui-action: var(--hf-ui-action, #171717);
--_hf-ui-action-contrast: var(--hf-ui-action-contrast, #ffffff);
--_hf-ui-border: var(--hf-ui-border, #e0dfdb);
--_hf-ui-border-strong: var(--hf-ui-border-strong, #cbc9c3);
--_hf-ui-canvas: var(--hf-ui-canvas, #f6f5f1);
--_hf-ui-control-border: var(--hf-ui-control-border, #888888);
--_hf-ui-control-border-hover: var(--hf-ui-control-border-hover, #6f6e68);
--_hf-ui-danger: var(--hf-ui-danger, #a61b1b);
--_hf-ui-data-1: var(--hf-ui-data-1, #087a57);
--_hf-ui-data-2: var(--hf-ui-data-2, #245ea8);
--_hf-ui-data-3: var(--hf-ui-data-3, #8a4b00);
--_hf-ui-data-4: var(--hf-ui-data-4, #6d3fc0);
--_hf-ui-data-5: var(--hf-ui-data-5, #a83d2d);
--_hf-ui-shadow-contact: var(--hf-ui-shadow-contact, 0 7px 16px rgba(26, 24, 20, 0.13));
--_hf-ui-shadow-inset: var(--hf-ui-shadow-inset, inset 0 1px 0 rgba(255, 255, 255, 0.78));
--_hf-ui-shadow-modal: var(--hf-ui-shadow-modal, 0 18px 46px rgba(26, 24, 20, 0.18));
--_hf-ui-surface: var(--hf-ui-surface, #ffffff);
--_hf-ui-surface-inset: var(--hf-ui-surface-inset, #f0efeb);
--_hf-ui-surface-raised: var(--hf-ui-surface-raised, #ffffff);
--_hf-ui-text: var(--hf-ui-text, #171717);
--_hf-ui-text-faint: var(--hf-ui-text-faint, #6f6e68);
--_hf-ui-text-muted: var(--hf-ui-text-muted, #5f5f5a);
--_hf-ui-text-strong: var(--hf-ui-text-strong, #0a0a0a);
--_hf-ui-warning: var(--hf-ui-warning, #8a4b00);
color-scheme: light;
}
/* prettier-ignore */
[data-hf-ui-root]:focus-visible {
outline: 2px solid var(--_hf-ui-accent);
outline-offset: 3px;
}
/* prettier-ignore */
[data-hf-rendering="true"][data-hf-ui-root],
[data-hf-rendering="true"] [data-hf-ui-root],
[data-hf-rendering="true"][data-hf-ui-root] *,
[data-hf-rendering="true"] [data-hf-ui-root] *,
[data-hf-rendering="true"][data-hf-ui-root]::before,
[data-hf-rendering="true"][data-hf-ui-root]::after,
[data-hf-rendering="true"] [data-hf-ui-root]::before,
[data-hf-rendering="true"] [data-hf-ui-root]::after,
[data-hf-rendering="true"][data-hf-ui-root] *::before,
[data-hf-rendering="true"][data-hf-ui-root] *::after,
[data-hf-rendering="true"] [data-hf-ui-root] *::before,
[data-hf-rendering="true"] [data-hf-ui-root] *::after {
animation-duration: 0s !important;
animation-delay: 0s !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
}
/* prettier-ignore */
@media (prefers-reduced-motion: reduce) {
[data-hf-ui-root],
[data-hf-ui-root] *,
[data-hf-ui-root] *::before,
[data-hf-ui-root] *::after {
animation-duration: 0s !important;
animation-delay: 0s !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
scroll-behavior: auto !important;
}
}
/* hf-ui:tokens:end */
.hf-ui-streaming-text {
font-size: var(--hf-st-size, 27px);
color: var(--hf-st-ink, #141414);
font-family: var(--_hf-ui-font-sans);
}
.hf-st-p {
margin: 0;
line-height: 1.43;
letter-spacing: 0.046em;
max-width: 24.5em;
}
.hf-ui-streaming-text .w {
opacity: 0;
}
</style>
<script>
(function () {
"use strict";
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
// Unrecognised overrides return to their declared defaults.
function pick(table, value, fallback) {
return Object.prototype.hasOwnProperty.call(table, value) ? value : fallback;
}
function clampNumber(value, min, max, fallback) {
var n = typeof value === "number" ? value : parseFloat(value);
if (!isFinite(n)) {
return fallback;
}
return Math.min(max, Math.max(min, n));
}
var tones = {
"dark-on-light": { ink: "#141414", fresh: "#767676" },
"light-on-dark": { ink: "#f3f3f3", fresh: "#5f5f5f" },
};
var size = clampNumber(vars.size, 18, 64, 27);
var tone = tones[pick(tones, vars.tone, "dark-on-light")];
var roots = document.querySelectorAll(".hf-ui-streaming-text");
for (var i = 0; i < roots.length; i += 1) {
roots[i].style.setProperty("--hf-st-size", size + "px");
roots[i].style.setProperty("--hf-st-ink", tone.ink);
roots[i].style.setProperty("--hf-st-fresh", tone.fresh);
}
})();
</script>
<!--
Timeline integration - GAPS is the measured token rhythm (seconds between
consecutive words, zeros are words that arrive together). Cycle it for any
word count; the ramp inks each word from fresh grey to full colour.
var GAPS = [
0.167, 0.100, 0.000, 0.367, 0.000, 0.167, 0.133, 0.100, 0.100, 0.133,
0.167, 0.133, 0.000, 0.233, 0.167, 0.100, 0.000, 0.133, 0.133, 0.100,
0.167, 0.133, 0.100, 0.133, 0.167, 0.100, 0.133, 0.100, 0.133,
];
var t = startTime;
gsap.utils.toArray('.hf-ui-streaming-text .w').forEach(function (w, i) {
if (i > 0) t += GAPS[i % GAPS.length];
tl.set(w, { opacity: 1 }, t);
tl.fromTo(w, { color: '#767676' }, { color: 'var(--hf-st-ink)', duration: 0.2667, ease: 'none' }, t);
});
-->
```
</Accordion>
{/* hf:generated-footer */}
Tagged `video-primitive` `text-effect` `ai` `chat`.
Created by Miao Yang.
## Related topics
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)