Files
hyperframes/docs/catalog/components/notes-typing.mdx
Ular Kimsanov a06fdfe26e docs(catalog): put each item page's title, description and Docs text in the server HTML (#6711)
* docs(catalog): put each item page's title, description and Docs text in the server HTML

Mintlify renders snippet components in the browser only, so everything inside
CatalogDetail (title, standfirst, the Docs tab) was missing from the HTML search
engines read: 448 catalog pages served Googlebot no body text. The generator now
writes the H1 and standfirst as page markup above the component and the Docs tab
as page markup after it, shown with that tab (docs/custom.css). The component no
longer draws its own title. Pages patched structurally only; registry drift on 14
pages is left for a regeneration of its own.

* docs(catalog): keep the title's top spacing the component gave it

* fix(docs): catalog titles and descriptions keep markdown markers literal

MDX reads text inside the static <h1> and <p> as inline markdown, so @_blue_sweater_
rendered as italic blue_sweater. mdxText now writes backslash, backtick, * _ [ ] ~ as
character references too, with a unit test; the four affected pages are re-escaped.
2026-10-30 15:48:25 +00:00

435 lines
16 KiB
Plaintext

---
title: "Notes Typing"
description: "The notes-app confession as a mountable scene: a bold title, then body lines typed character by character on the measured human keystroke rhythm, the caret riding every landed character and jumping down at each paragraph. For a bare prompt line use typed-prompt; for an even mechanical reveal use typewriter"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
<div className="hf-ve-static-head not-prose">
<h1>Notes Typing</h1>
<p>The notes-app confession as a mountable scene: a bold title, then body lines typed character by character on the measured human keystroke rhythm, the caret riding every landed character and jumping down at each paragraph. For a bare prompt line use typed-prompt; for an even mechanical reveal use typewriter</p>
</div>
<CatalogDetail
previewSrc="/public/catalog/components/notes-typing.json"
compositionId="notes-typing"
compositionSrc="compositions/components/notes-typing.html"
title="Notes Typing"
variables={[{"id":"title","type":"string","role":"content","label":"Title","description":"Bold note title. A | breaks the line.","default":"Things nobody told me|about video."},{"id":"lines","type":"string","role":"content","label":"Lines","description":"Pipe-separated body lines, typed one after another.","default":"my videos sucked|my tools cost $$$|and re-edits never end."},{"id":"speed","type":"number","role":"timing","label":"Speed","description":"Typing speed multiplier. 1 is the measured human rhythm.","default":1,"min":0.5,"max":2,"step":0.1}]}
meta={{"category":"Motion Scenes","badge":"Stable","codeLines":347}}
attribution={{"author":"Miao Yang","path":"registry/components/notes-typing","tags":["motion-primitive","notes","typing","mock-ui","vertical"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/components/notes-typing/notes-typing.html"
>
<CatalogSlot slot="code">
```html notes-typing.html
<!doctype html>
<!--
notes-typing: HyperFrames motion primitive (scene)
The notes-app confession, as a mountable scene: a bold title, then body
lines typed character by character on the measured human keystroke rhythm
- uneven, bursty, never a metronome - with the caret riding the right edge
of every landed character and jumping down at each paragraph.
This is the notes-app SCENE. For a bare prompt line typing itself, use
typed-prompt; for an even mechanical reveal, use typewriter.
#root only, container-type: size, one paused timeline registered under the
literal "notes-typing" key. Deterministic: the schedule is generated from a
measured gap table; caret positions are measured at mount.
-->
<html
lang="en"
data-composition-id="notes-typing"
data-composition-duration="10"
data-composition-variables='[{"id": "title", "type": "string", "role": "content", "label": "Title", "description": "Bold note title. A | breaks the line.", "default": "Things nobody told me|about video."}, {"id": "lines", "type": "string", "role": "content", "label": "Lines", "description": "Pipe-separated body lines, typed one after another.", "default": "my videos sucked|my tools cost $$$|and re-edits never end."}, {"id": "speed", "type": "number", "role": "timing", "label": "Speed", "description": "Typing speed multiplier. 1 is the measured human rhythm.", "default": 1, "min": 0.5, "max": 2, "step": 0.1}]'
>
<head>
<meta charset="UTF-8" />
<title>Notes Typing</title>
</head>
<body>
<template>
<div id="root" data-composition-id="notes-typing" data-duration="10" data-fps="30">
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
#root {
position: absolute;
inset: 0;
overflow: hidden;
container-type: size;
isolation: isolate;
background: #fefefe;
font-family: var(
--font-body,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif
);
pointer-events: none;
}
.nty-nav {
position: absolute;
left: 4cqw;
top: 3.5cqh;
width: 10cqmin;
height: 10cqmin;
border-radius: 50%;
background: #fff;
box-shadow: 0 0.4cqmin 1.4cqmin rgba(0, 0, 0, 0.07);
}
.nty-nav svg {
position: absolute;
left: 32%;
top: 30%;
width: 36%;
height: 40%;
}
.nty-check {
position: absolute;
right: 4cqw;
top: 3.5cqh;
width: 10cqmin;
height: 10cqmin;
border-radius: 50%;
background: #fec909;
}
.nty-check svg {
position: absolute;
left: 26%;
top: 30%;
width: 50%;
height: 42%;
}
.nty-body {
position: absolute;
left: 6cqw;
right: 6cqw;
top: 16cqh;
}
.nty-title {
font-size: 8.4cqmin;
font-weight: 700;
letter-spacing: -0.005em;
color: #191919;
line-height: 1.15;
margin: 0 0 3.4cqh;
}
.nty-line {
font-size: 4.9cqmin;
color: #191919;
line-height: 1.5;
margin: 0 0 2.6cqh;
white-space: pre-wrap;
min-height: 1.5em;
}
.nty-ch {
opacity: 0;
}
#nty-caret {
position: absolute;
width: 0.42cqmin;
height: 5.2cqmin;
background: #ffca00;
border-radius: 0.2cqmin;
left: 0;
top: 0;
will-change: transform;
}
.nty-fmt {
position: absolute;
left: 4cqw;
right: 4cqw;
bottom: 4cqh;
height: 8.5cqh;
background: #fdfdfd;
border-radius: 100cqmin;
box-shadow: 0 0.4cqmin 1.6cqmin rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
justify-content: space-around;
}
.nty-fmt .aa {
font-size: 4.6cqmin;
font-weight: 700;
color: #111;
}
.nty-fmt .aa small {
font-size: 0.65em;
font-weight: 400;
}
.nty-fmt svg {
width: 5.6cqmin;
height: 5.6cqmin;
}
</style>
<div class="nty-nav">
<svg viewBox="0 0 40 40">
<path
d="M26 4 L10 20 L26 36"
fill="none"
stroke="#111"
stroke-width="4.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
<div class="nty-check">
<svg viewBox="0 0 56 46">
<path
d="M6 24 L21 38 L50 6"
fill="none"
stroke="#fff"
stroke-width="7"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
<div class="nty-body" id="nty-body">
<div class="nty-title" id="nty-title" data-layout-allow-overlap>
Things nobody told me<br />about video.
</div>
<div id="nty-lines"></div>
<div id="nty-caret"></div>
</div>
<div class="nty-fmt">
<span class="aa">A<small>a</small></span>
<svg viewBox="0 0 64 54">
<circle cx="10" cy="12" r="9" fill="none" stroke="#111" stroke-width="4" />
<path
d="M6 12 l3 3 6 -7"
fill="none"
stroke="#111"
stroke-width="3"
stroke-linecap="round"
/>
<line
x1="28"
y1="12"
x2="60"
y2="12"
stroke="#111"
stroke-width="4"
stroke-linecap="round"
/>
<circle cx="10" cy="42" r="9" fill="none" stroke="#111" stroke-width="4" />
<line
x1="28"
y1="42"
x2="52"
y2="42"
stroke="#111"
stroke-width="4"
stroke-linecap="round"
/>
</svg>
<svg viewBox="0 0 62 62">
<rect
x="3"
y="3"
width="56"
height="56"
rx="8"
fill="none"
stroke="#111"
stroke-width="4"
/>
<line x1="3" y1="22" x2="59" y2="22" stroke="#111" stroke-width="4" />
<line x1="22" y1="3" x2="22" y2="59" stroke="#111" stroke-width="4" />
</svg>
<svg viewBox="0 0 46 70">
<path
d="M33 14 v34 a11 11 0 0 1 -22 0 V16 a8 8 0 0 1 16 0 v30 a4 4 0 0 1 -8 0 V20"
fill="none"
stroke="#111"
stroke-width="4"
stroke-linecap="round"
/>
</svg>
<svg viewBox="0 0 62 62">
<circle cx="31" cy="31" r="27" fill="none" stroke="#111" stroke-width="4" />
<path d="M22 40 L36 18 a5 5 0 0 1 8 6 L30 46 l-10 4 z" fill="#111" />
</svg>
</div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
"use strict";
var compositionId = "notes-typing";
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
function text(v, fb, mx) {
if (typeof v !== "string") return fb;
var t = v.trim();
if (!t) return fb;
return t.length > mx ? t.slice(0, mx) : t;
}
function clampNumber(v, min, max, fb) {
var n = typeof v === "number" ? v : parseFloat(v);
if (!isFinite(n)) return fb;
return Math.min(max, Math.max(min, n));
}
var titleEl = document.getElementById("nty-title");
titleEl.innerHTML = "";
text(vars.title, "Things nobody told me|about video.", 60)
.split("|")
.forEach(function (line, i) {
if (i > 0) titleEl.appendChild(document.createElement("br"));
titleEl.appendChild(document.createTextNode(line));
});
var lines = text(
vars.lines,
"my videos sucked|my tools cost $$$|and re-edits never end.",
240,
)
.split("|")
.map(function (l) {
return l.trim();
})
.filter(Boolean)
.slice(0, 6);
if (lines.length === 0) lines = ["my videos sucked"];
var speed = clampNumber(vars.speed, 0.5, 2, 1);
var linesEl = document.getElementById("nty-lines");
var chars = [];
lines.forEach(function (line, li) {
var p = document.createElement("div");
p.className = "nty-line";
p.setAttribute("data-layout-allow-overlap", "");
p.setAttribute("data-layout-allow-occlusion", "");
line.split("").forEach(function (ch) {
var s = document.createElement("span");
s.className = "nty-ch";
s.textContent = ch;
p.appendChild(s);
chars.push({ el: s, line: li });
});
linesEl.appendChild(p);
});
/* the measured human keystroke rhythm, cycled */
var KEY_GAPS = [
0.1, 0.033, 0.2, 0.133, 0.2, 0.033, 0.067, 0.033, 0.1, 0.067, 0.033, 0.067, 0.067,
0.067, 0.133, 0.067, 0.033, 0.067, 0.1, 0.1, 0.067, 0.133, 0.1, 0.033, 0.1, 0.033,
0.167, 0.033, 0.1, 0.1,
];
var LINE_PAUSE = 0.85; /* the measured breath before a new thought */
var caret = document.getElementById("nty-caret");
var body = document.getElementById("nty-body");
var tl = gsap.timeline({ paused: true });
function build() {
tl.clear();
var bodyRect = body.getBoundingClientRect();
var scale = bodyRect.width / body.offsetWidth || 1;
gsap.set(caret, { autoAlpha: 0 });
var t = 0.5;
var lastLine = -1;
chars.forEach(function (c, i) {
if (c.line !== lastLine) {
if (lastLine >= 0) t += LINE_PAUSE / speed;
lastLine = c.line;
}
var r = c.el.getBoundingClientRect();
var x = (r.right - bodyRect.left) / scale;
var y = (r.top - bodyRect.top) / scale;
tl.set(c.el, { opacity: 1 }, t);
tl.set(caret, { x: x + 2, y: y, autoAlpha: 1 }, t);
t += KEY_GAPS[i % KEY_GAPS.length] / speed;
});
/* settle blink, then rest */
for (var b = 0; b < 4; b += 1) {
tl.set(caret, { autoAlpha: b % 2 === 0 ? 0 : 1 }, t + 0.4 + b * 0.5333);
}
tl.set({}, {}, 10);
tl.seek(0);
}
build();
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(function () {
build();
tl.pause(0);
});
}
window.__timelines = window.__timelines || {};
window.__timelines[compositionId] = tl;
})();
</script>
</div>
</template>
</body>
</html>
```
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add notes-typing" item="notes-typing" />
That writes one file: `compositions/components/notes-typing.html`.
</CatalogSlot>
</CatalogDetail>
<div data-static-slot="docs" className="prose prose-gray dark:prose-invert">
## Paste it into your composition
Open `compositions/components/notes-typing.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 |
| --- | --- | --- | --- |
| `title` | `Things nobody told me|about video.` | string | Bold note title. A | breaks the line. |
| `lines` | `my videos sucked|my tools cost $$$|and re-edits never end.` | string | Pipe-separated body lines, typed one after another. |
| `speed` | `1` | 0.5 to 2, step 0.1 | Typing speed multiplier. 1 is the measured human rhythm. |
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="notes-typing"
data-composition-src="compositions/components/notes-typing.html"
data-variable-values='{"title":"Things nobody told me|about video.","lines":"my videos sucked|my tools cost $$$|and re-edits never end.","speed":1}'
></div>
```
{/* hf:generated-footer */}
Tagged `motion-primitive` `notes` `typing` `mock-ui` `vertical`.
Created by Miao Yang.
## Related topics
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in the browser editor](/studio/assets-and-blocks)
- [Put blocks together into one video](/concepts/compositions)
</div>