mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
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.
265 lines
10 KiB
HTML
Vendored
265 lines
10 KiB
HTML
Vendored
<!doctype html>
|
|
<!--
|
|
cta-close: HyperFrames video primitive (call to action / close)
|
|
|
|
The action-only close. A display-scale action line lands per word, sized by
|
|
a character-aware fit so it fills 70 to 85 percent of the frame width with
|
|
real air above and below. One generous button capsule pops beneath it with
|
|
a single restrained overshoot, then the finished lockup holds dead still
|
|
until the frame cuts. Monotone: ink on ground, accent only on the capsule.
|
|
|
|
Variables:
|
|
action_line: two to four word closing action
|
|
button_label: capsule label
|
|
accent: green, blue, or violet capsule color
|
|
exit: none, fade, or up (default none; this primitive closes films)
|
|
|
|
Envelope:
|
|
IN_BASE = 1.32s, per-word landing followed by the capsule pop
|
|
HOLD = the rest of D, completely still
|
|
OUT = none by default; 0.45s lockup departure when exit is fade or up
|
|
If D is shorter than the fixed phases, they compress to fit. 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 cta-close key.
|
|
-->
|
|
<html
|
|
lang="en"
|
|
data-composition-id="cta-close"
|
|
data-composition-duration="3.5"
|
|
data-composition-variables='[
|
|
{ "id": "action_line", "type": "string", "role": "content", "label": "Action line", "description": "Two to four word action that closes the film.", "default": "Make it happen" },
|
|
{ "id": "button_label", "type": "string", "role": "content", "label": "Button label", "description": "Text displayed inside the single CTA capsule.", "default": "Start now" },
|
|
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Contract color used by the CTA capsule.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] },
|
|
{ "id": "exit", "type": "enum", "role": "timing", "label": "Exit", "description": "Optional departure. Default none: the lockup holds until the frame cuts (this primitive closes films).", "default": "none", "options": [{ "value": "none", "label": "None" }, { "value": "fade", "label": "Fade" }, { "value": "up", "label": "Up" }] }
|
|
]'
|
|
>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>CTA Close</title>
|
|
</head>
|
|
<body>
|
|
<template>
|
|
<div id="root" data-composition-id="cta-close" data-duration="3.5" 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;
|
|
}
|
|
|
|
.cc-clip {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: grid;
|
|
place-items: center;
|
|
overflow: hidden;
|
|
background: var(--bg, #0b0c0e);
|
|
}
|
|
|
|
.cc-lockup {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 7cqh;
|
|
max-width: 96cqw;
|
|
text-align: center;
|
|
}
|
|
|
|
.cc-action {
|
|
margin: 0;
|
|
color: var(--fg, #f8fafc);
|
|
font-family: var(--font-display, Inter, system-ui, sans-serif);
|
|
font-size: var(--cc-action-size, min(10.4cqw, 30cqh));
|
|
font-weight: 600;
|
|
line-height: 1.02;
|
|
letter-spacing: -0.035em;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.cc-w {
|
|
display: inline-block;
|
|
will-change: transform, opacity;
|
|
}
|
|
|
|
.cc-button {
|
|
margin: 0;
|
|
padding: 2.9cqh 5cqw;
|
|
overflow: hidden;
|
|
border: 0;
|
|
border-radius: 999px;
|
|
background: var(--cc-accent);
|
|
color: var(--bg, #0b0c0e);
|
|
font-family: var(--font-body, Inter, system-ui, sans-serif);
|
|
font-size: var(--cc-button-size, min(3.4cqw, 6cqh));
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
letter-spacing: -0.01em;
|
|
white-space: nowrap;
|
|
transform-origin: 50% 50%;
|
|
will-change: transform, opacity;
|
|
}
|
|
</style>
|
|
|
|
<div
|
|
id="cta-close-clip"
|
|
class="cc-clip clip"
|
|
data-start="0"
|
|
data-duration="3.5"
|
|
data-track-index="0"
|
|
>
|
|
<div class="cc-lockup">
|
|
<h2 class="cc-action"></h2>
|
|
<button class="cc-button" type="button" tabindex="-1"></button>
|
|
</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(".cc-lockup");
|
|
var actionLine = root.querySelector(".cc-action");
|
|
var button = root.querySelector(".cc-button");
|
|
var vars =
|
|
window.__hyperframes && window.__hyperframes.getVariables
|
|
? window.__hyperframes.getVariables()
|
|
: {};
|
|
|
|
var actionText =
|
|
vars.action_line == null
|
|
? "Make it happen"
|
|
: String(vars.action_line).trim().replace(/\s+/g, " ");
|
|
var buttonText =
|
|
vars.button_label == null
|
|
? "Start now"
|
|
: String(vars.button_label).trim().replace(/\s+/g, " ");
|
|
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";
|
|
|
|
// The line lands per word: one span per word, natural spaces kept
|
|
// between them so the display tracking never crushes word gaps.
|
|
actionLine.setAttribute("aria-label", actionText);
|
|
var words = actionText ? actionText.split(" ") : [];
|
|
words.forEach(function (word, index) {
|
|
if (index > 0) actionLine.appendChild(document.createTextNode(" "));
|
|
var span = document.createElement("span");
|
|
span.className = "cc-w";
|
|
span.setAttribute("aria-hidden", "true");
|
|
span.textContent = word;
|
|
actionLine.appendChild(span);
|
|
});
|
|
var wordEls = actionLine.querySelectorAll(".cc-w");
|
|
button.textContent = buttonText;
|
|
root.style.setProperty("--cc-accent", accentColors[accent]);
|
|
|
|
// Character-count fitting is deterministic before the template is
|
|
// laid out. The 0.47em advance estimate lands common display faces
|
|
// at 70 to 85 percent of the frame width; short lines cap on
|
|
// height instead so two-word closes stay monumental without
|
|
// touching the edges.
|
|
var actionCharacters = Math.max(1, Array.from(actionText).length);
|
|
var actionSize = Math.min(24, 76 / (actionCharacters * 0.47));
|
|
var buttonCharacters = Math.max(1, Array.from(buttonText).length);
|
|
var buttonSize = Math.min(3.5, 50 / (buttonCharacters * 0.5));
|
|
root.style.setProperty(
|
|
"--cc-action-size",
|
|
"min(" + actionSize.toFixed(3) + "cqw, 30cqh)",
|
|
);
|
|
root.style.setProperty(
|
|
"--cc-button-size",
|
|
"min(" + buttonSize.toFixed(3) + "cqw, 6cqh)",
|
|
);
|
|
|
|
var IN_BASE = 1.32;
|
|
var OUT_BASE = exit === "none" ? 0 : 0.45;
|
|
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "3.5"));
|
|
var envelopeScale = Math.min(1, duration / (IN_BASE + OUT_BASE));
|
|
var WORDS_AT = 0.06 * envelopeScale;
|
|
var WORD_DURATION = 0.62 * envelopeScale;
|
|
var WORD_STAGGER = 0.1 * envelopeScale;
|
|
var BUTTON_AT = 0.72 * envelopeScale;
|
|
var BUTTON_DURATION = 0.6 * envelopeScale;
|
|
var BUTTON_FADE = 0.22 * envelopeScale;
|
|
var OUT = OUT_BASE * envelopeScale;
|
|
var OUT_START = duration - OUT;
|
|
|
|
var tl = gsap.timeline({ paused: true });
|
|
|
|
// IN, phase one: the action line lands per word at display scale.
|
|
tl.fromTo(
|
|
wordEls,
|
|
{ opacity: 0, y: "8cqh" },
|
|
{
|
|
opacity: 1,
|
|
y: 0,
|
|
duration: WORD_DURATION,
|
|
ease: "power3.out",
|
|
stagger: WORD_STAGGER,
|
|
},
|
|
WORDS_AT,
|
|
);
|
|
|
|
// IN, phase two: the capsule pops beneath with ONE restrained
|
|
// overshoot (back.out), a real button under a monumental line.
|
|
tl.fromTo(
|
|
button,
|
|
{ scale: 0.85, y: "3cqh" },
|
|
{ scale: 1, y: 0, duration: BUTTON_DURATION, ease: "back.out(1.5)" },
|
|
BUTTON_AT,
|
|
);
|
|
tl.fromTo(
|
|
button,
|
|
{ opacity: 0 },
|
|
{ opacity: 1, duration: BUTTON_FADE, ease: "power2.out" },
|
|
BUTTON_AT,
|
|
);
|
|
|
|
// HOLD: dead still. The empty set pins the timeline to the full
|
|
// mount duration so seeks inside the hold land on the held pose.
|
|
tl.set({}, {}, duration);
|
|
|
|
// OUT: optional departure; exit none holds the still lockup
|
|
// until the frame cuts (this primitive closes films).
|
|
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["cta-close"] = tl;
|
|
})();
|
|
</script>
|
|
</div>
|
|
</template>
|
|
</body>
|
|
</html>
|