Files
hyperframes/registry/components/logo-brand-close/logo-brand-close.html
T
Miguel Ángel 9734578e60 feat(registry): bring back the video-primitive moves (#3169)
Restores the 208 catalog items reverted after their previews 404'd in
production, this time on the payload mechanism rather than the .html files
that caused the outage.

The generator no longer writes a preview document to docs/public. That writer,
and the machinery under it, existed only to produce files the docs host
discards, so it is gone rather than bypassed. Items now embed the composition
itself via a payload, which is what the previous change already does for the
items that were already in the catalog.

The variables explorer is parked, not restored: it drove its preview through
the same unpublished .html path, so it would have shown an empty frame. Items
that declare variables get the live player plus the static variables table, and
reconnecting the explorer to payloads is a follow-up.
2026-08-10 18:46:03 -04:00

368 lines
14 KiB
HTML
Vendored
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<!--
logo-brand-close: HyperFrames video primitive (brand close)
The identity close that ends films. The wordmark lands at display scale,
a chars-aware fit sizing it to roughly three quarters of the frame width.
Letters cascade left to right (per-letter fade plus rise with a long
expo tail) while the whole wordmark settles from a slightly larger scale,
the accent brand period arrives last with a decisive pop, a readable
tagline settles beneath, a wide-tracked mono URL breathes in below, then
the finished lockup holds dead still to the end. Distinct from cta-close:
identity, not action. No button, no cursor.
Variables:
- wordmark (string): the brand name that cascades into place.
- tagline (string): optional line beneath the wordmark; empty hides it.
- url (string): optional mono URL line; empty hides it.
- accent (green | blue | violet): color of the brand period.
- exit (none | fade | up, default none): films end on the hold.
Envelope, fixed IN with elastic HOLD only (OUT exists only when exit is
not none):
IN_BASE = 2.6s, letter cascade + scale settle, period pop, tagline
settle, URL tracking settle
HOLD = max(0, D - IN - OUT), completely still
OUT_BASE = 0.5s only when exit is fade or up, otherwise 0
If D is shorter than IN_BASE + OUT_BASE, both compress together.
The timeline is never time-scaled.
Mount contract: the runtime clones only this template. #root fills the host
box, establishes the container query basis, has no data-width or data-height,
and registers one paused timeline under the literal logo-brand-close key.
-->
<html
lang="en"
data-composition-id="logo-brand-close"
data-composition-duration="4"
data-composition-variables='[
{ "id": "wordmark", "type": "string", "role": "content", "label": "Wordmark", "description": "Brand name whose letters cascade into the centered lockup.", "default": "HYPERFRAMES" },
{ "id": "tagline", "type": "string", "role": "content", "label": "Tagline", "description": "Optional tagline that settles beneath the wordmark. Empty hides it.", "default": "Write HTML. Render video." },
{ "id": "url", "type": "string", "role": "content", "label": "URL", "description": "Optional mono URL line beneath the tagline. Empty hides it.", "default": "hyperframes.heygen.com" },
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Contract color used by the brand period at the end of the wordmark.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] },
{ "id": "exit", "type": "enum", "role": "style", "label": "Exit", "description": "Optional departure. Films end on the still hold, so the default is none.", "default": "none", "options": [{ "value": "none", "label": "None" }, { "value": "fade", "label": "Fade" }, { "value": "up", "label": "Up" }] }
]'
>
<head>
<meta charset="UTF-8" />
<title>Logo Brand Close</title>
</head>
<body>
<template>
<div id="root" data-composition-id="logo-brand-close" data-duration="4" data-fps="30">
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
#root {
position: absolute;
inset: 0;
overflow: hidden;
container-type: size;
isolation: isolate;
color: var(--fg, #f8fafc);
font-family: var(--font-body, Inter, system-ui, sans-serif);
pointer-events: none;
}
.lbc-clip {
position: absolute;
inset: 0;
display: grid;
place-items: center;
overflow: hidden;
background: var(--bg, #0b0c0e);
}
.lbc-lockup {
display: flex;
flex-direction: column;
align-items: center;
width: 100cqw;
text-align: center;
will-change: transform, opacity;
}
/* Display scale: the JS chars-aware fit targets ~76cqw of set
width; 40cqh caps it in short, wide mounts. */
.lbc-wordmark {
display: flex;
align-items: baseline;
justify-content: center;
margin: 0;
color: var(--fg, #f8fafc);
font-family: var(--font-display, Inter, system-ui, sans-serif);
font-size: var(--lbc-wordmark-size, min(10.5cqw, 40cqh));
font-weight: 600;
line-height: 1.04;
letter-spacing: -0.035em;
white-space: nowrap;
will-change: transform;
}
.lbc-letter {
display: inline-block;
flex: 0 0 auto;
will-change: transform, opacity;
}
.lbc-space {
width: 0.34em;
}
.lbc-period {
color: var(--lbc-accent);
}
.lbc-tagline {
max-width: 88cqw;
margin: var(--space-2, 3cqmin) 0 0;
color: var(--fg, #f8fafc);
font-size: min(3.4cqw, 5.4cqmin);
font-weight: 500;
line-height: 1.3;
letter-spacing: 0.01em;
will-change: transform, opacity;
}
.lbc-url {
margin: var(--space-3, 4.6cqmin) 0 0;
color: var(--muted, #94a3b8);
font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
font-size: min(2.4cqw, 3.2cqmin);
font-weight: 500;
line-height: 1;
letter-spacing: 0.28em;
will-change: opacity;
}
</style>
<div
id="logo-brand-close-clip"
class="lbc-clip clip"
data-start="0"
data-duration="4"
data-track-index="0"
>
<div class="lbc-lockup">
<h2 class="lbc-wordmark"></h2>
<p class="lbc-tagline"></p>
<p class="lbc-url"></p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
"use strict";
var root = document.getElementById("root");
var lockup = root.querySelector(".lbc-lockup");
var wordmarkElement = root.querySelector(".lbc-wordmark");
var taglineElement = root.querySelector(".lbc-tagline");
var urlElement = root.querySelector(".lbc-url");
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
var wordmark =
vars.wordmark == null
? "HYPERFRAMES"
: String(vars.wordmark).trim().replace(/\s+/g, " ");
if (wordmark.length === 0) wordmark = "HYPERFRAMES";
var tagline =
vars.tagline == null
? "Write HTML. Render video."
: String(vars.tagline).trim().replace(/\s+/g, " ");
var url = vars.url == null ? "hyperframes.heygen.com" : String(vars.url).trim();
var accentColors = {
green: "var(--brand, #71f5a7)",
blue: "var(--accent, #61a8ff)",
violet: "var(--accent-2, #c5a3ff)",
};
var accent = Object.prototype.hasOwnProperty.call(accentColors, vars.accent)
? vars.accent
: "green";
var exit = vars.exit === "fade" || vars.exit === "up" ? vars.exit : "none";
root.style.setProperty("--lbc-accent", accentColors[accent]);
wordmarkElement.setAttribute("aria-label", wordmark);
wordmarkElement.setAttribute("role", "img");
// The brand period: an accent dot ends the wordmark. If the
// wordmark already ends with "." that period takes the accent
// instead of appending a second one.
var characters = Array.from(wordmark);
if (characters[characters.length - 1] !== ".") characters.push(".");
var letters = characters.map(function (character, index) {
var letter = document.createElement("span");
letter.id = "logo-brand-close-letter-" + index;
letter.className = "lbc-letter";
if (/\s/.test(character)) {
letter.classList.add("lbc-space");
letter.textContent = " ";
} else {
letter.textContent = character;
}
if (index === characters.length - 1) letter.classList.add("lbc-period");
letter.setAttribute("aria-hidden", "true");
wordmarkElement.appendChild(letter);
return letter;
});
var periodLetter = letters[letters.length - 1];
var cascadeLetters = letters.slice(0, -1);
// Deterministic chars-aware fit: probe the set width at a fixed
// 100px font (letter-spacing rides in em, so the em ratio is a
// pure font metric), then size the wordmark so it lands at
// ~76cqw. The 40cqh term caps it in short, wide mounts. If the
// probe cannot measure (detached render), fall back to a
// per-glyph advance estimate.
wordmarkElement.style.fontSize = "100px";
var probedWidth = wordmarkElement.getBoundingClientRect().width;
wordmarkElement.style.fontSize = "";
var setWidthEm;
if (probedWidth > 0) {
setWidthEm = probedWidth / 100;
} else {
setWidthEm = characters.reduce(function (total, character) {
if (/\s/.test(character)) return total + 0.34;
if (/[.,:;!'|]/.test(character)) return total + 0.3;
if (/[Iijlftr]/.test(character)) return total + 0.42;
if (/[MWmw@]/.test(character)) return total + 0.92;
return total + 0.64;
}, 0);
setWidthEm -= characters.length * 0.035;
}
setWidthEm = Math.max(0.3, setWidthEm);
var wordmarkSize = 76 / setWidthEm;
root.style.setProperty(
"--lbc-wordmark-size",
"min(" + wordmarkSize.toFixed(3) + "cqw, 40cqh)",
);
if (tagline.length === 0) taglineElement.remove();
else taglineElement.textContent = tagline;
if (url.length === 0) urlElement.remove();
else urlElement.textContent = url;
var IN_BASE = 2.6;
var OUT_BASE = exit === "none" ? 0 : 0.5;
var CASCADE_RISE_BASE = 1.15;
var CASCADE_STAGGER_BASE = 0.7;
var SETTLE_BASE = 2.4;
var PERIOD_AT_BASE = 0.95;
var PERIOD_DURATION_BASE = 0.9;
var TAGLINE_AT_BASE = 1.35;
var TAGLINE_DURATION_BASE = 0.9;
var URL_AT_BASE = 1.7;
var URL_DURATION_BASE = 0.85;
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "4"));
var totalBase = IN_BASE + OUT_BASE;
var scale = duration < totalBase ? duration / totalBase : 1;
var IN = IN_BASE * scale;
var OUT = OUT_BASE * scale;
var STAGGER = cascadeLetters.length > 1 ? CASCADE_STAGGER_BASE * scale : 0;
var HOLD = Math.max(0, duration - (IN + OUT));
var OUT_START = IN + HOLD;
var tl = gsap.timeline({ paused: true });
// IN: per-letter fade plus rise with a long expo tail, while the
// whole wordmark settles down from a slightly larger scale.
if (cascadeLetters.length > 0) {
tl.fromTo(
cascadeLetters,
{ opacity: 0, y: "0.62em" },
{
opacity: 1,
y: "0em",
duration: CASCADE_RISE_BASE * scale,
stagger: { amount: STAGGER, from: "start" },
ease: "expo.out",
},
0,
);
}
tl.fromTo(
wordmarkElement,
{ scale: 1.04, transformOrigin: "50% 100%" },
{ scale: 1, duration: SETTLE_BASE * scale, ease: "expo.out" },
0,
);
// The brand period lands last: a decisive accent pop.
tl.fromTo(
periodLetter,
{ opacity: 0, scale: 0.2, y: "0.08em", transformOrigin: "50% 65%" },
{
opacity: 1,
scale: 1,
y: "0em",
duration: PERIOD_DURATION_BASE * scale,
ease: "back.out(1.8)",
},
PERIOD_AT_BASE * scale,
);
if (taglineElement.isConnected) {
tl.fromTo(
taglineElement,
{ opacity: 0, y: "0.9em" },
{
opacity: 1,
y: "0em",
duration: TAGLINE_DURATION_BASE * scale,
ease: "power3.out",
},
TAGLINE_AT_BASE * scale,
);
}
if (urlElement.isConnected) {
tl.fromTo(
urlElement,
// scaleX, not letterSpacing: tracking is a layout property, so
// the browser snaps it to integer device pixels and the
// ease-out tail stutters under seek-by-frame capture. The
// widening reads the same and stays on the compositor.
{ opacity: 0, scaleX: 1.06 },
{
opacity: 1,
scaleX: 1,
duration: URL_DURATION_BASE * scale,
ease: "power2.out",
},
URL_AT_BASE * scale,
);
}
// HOLD: dead still. No drift, no breath; the confidence is the
// stillness, and by default the film ends here.
if (exit === "fade") {
tl.to(lockup, { opacity: 0, duration: OUT, ease: "power2.in" }, OUT_START);
} else if (exit === "up") {
tl.to(
lockup,
{ opacity: 0, y: "-5cqh", duration: OUT, ease: "power2.in" },
OUT_START,
);
}
tl.seek(0);
window.__timelines = window.__timelines || {};
window.__timelines["logo-brand-close"] = tl;
})();
</script>
</div>
</template>
</body>
</html>