mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
This reverts commit 3b53bfd2f7.
This commit is contained in:
-84
@@ -1,84 +0,0 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
type-match-cut demo. This standalone composition mounts type-match-cut.html
|
||||
inside a sized host clip. Non-default copy and accent prove both variables
|
||||
flow through the elastic mount.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Type Match Cut Demo</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #0b0c0e;
|
||||
}
|
||||
|
||||
#root {
|
||||
--bg: #0b0c0e;
|
||||
--fg: #f7f8fa;
|
||||
--font-display: Inter, system-ui, sans-serif;
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: radial-gradient(ellipse at 50% 44%, #1b1e24 0%, var(--bg) 72%);
|
||||
}
|
||||
|
||||
.mount-card {
|
||||
width: 80%;
|
||||
height: 72%;
|
||||
left: 10%;
|
||||
top: 14%;
|
||||
container-type: size;
|
||||
overflow: hidden;
|
||||
border: 2px solid rgba(247, 248, 250, 0.14);
|
||||
border-radius: 32px;
|
||||
background: #0b0c0e;
|
||||
box-shadow: 0 52px 130px rgba(0, 0, 0, 0.52);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="type-match-cut-demo"
|
||||
data-start="0"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
data-duration="4"
|
||||
data-fps="30"
|
||||
>
|
||||
<div
|
||||
id="type-match-cut-mount"
|
||||
class="mount-card clip"
|
||||
data-composition-id="type-match-cut"
|
||||
data-composition-src="./type-match-cut.html"
|
||||
data-variable-values='{"text":"Ideas become motion","accent":"violet"}'
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
data-width="1536"
|
||||
data-height="778"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
tl.seek(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["type-match-cut-demo"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "type-match-cut",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Type Match Cut",
|
||||
"description": "A headline splits vertically around an incoming panel, which grows from the negative space and takes the frame as the words exit through opposite edges.",
|
||||
"tags": ["motion-primitive", "transition", "typography", "match-cut", "continuity"],
|
||||
"jobs": ["bridge"],
|
||||
"family": "transitions",
|
||||
"profile": "match-cut",
|
||||
"evidence": "Wave H U30 type-match-cut specification, inspired by the verified MONOLOG type-to-image transition",
|
||||
"syncPoints": [{ "id": "panel-slot", "phase": "in", "offset": 0.58 }],
|
||||
"files": [
|
||||
{
|
||||
"path": "type-match-cut.html",
|
||||
"target": "compositions/components/type-match-cut.html",
|
||||
"type": "hyperframes:snippet"
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{
|
||||
"id": "text",
|
||||
"type": "string",
|
||||
"role": "content",
|
||||
"label": "Headline",
|
||||
"description": "Text split around the incoming panel.",
|
||||
"default": "Meet the new editor"
|
||||
},
|
||||
{
|
||||
"id": "accent",
|
||||
"type": "enum",
|
||||
"role": "style",
|
||||
"label": "Accent",
|
||||
"description": "Accent color used by the incoming editor scene.",
|
||||
"default": "green",
|
||||
"options": [
|
||||
{ "value": "green", "label": "Green" },
|
||||
{ "value": "blue", "label": "Blue" },
|
||||
{ "value": "violet", "label": "Violet" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,407 +0,0 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
type-match-cut: HyperFrames video primitive
|
||||
|
||||
A headline splits into upper and lower word groups. An incoming editor
|
||||
panel appears in the negative space between them, holds there as a visible
|
||||
geometric match, then grows to fill the frame while the words arc through
|
||||
the top and bottom edges.
|
||||
|
||||
Variables:
|
||||
text: headline copy
|
||||
accent: green, blue, or violet incoming-scene accent
|
||||
|
||||
Envelope, fixed IN and OUT with elastic HOLD:
|
||||
IN_BASE = 1.05s, words separate and expose the matched panel slot
|
||||
HOLD = max(0, D - IN - OUT), the slot breathes almost imperceptibly
|
||||
OUT_BASE = 1.15s, the panel takes the frame while both word groups exit
|
||||
If D is shorter than IN_BASE + OUT_BASE, IN and OUT 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, and has no data-width or
|
||||
data-height. One paused timeline registers under the literal type-match-cut
|
||||
key.
|
||||
-->
|
||||
<html
|
||||
lang="en"
|
||||
data-composition-id="type-match-cut"
|
||||
data-composition-duration="4"
|
||||
data-composition-variables='[
|
||||
{ "id": "text", "type": "string", "role": "content", "label": "Headline", "description": "Text split around the incoming panel.", "default": "Meet the new editor" },
|
||||
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Accent color used by the incoming editor scene.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] }
|
||||
]'
|
||||
>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Type Match Cut</title>
|
||||
</head>
|
||||
<body>
|
||||
<template>
|
||||
<div id="root" data-composition-id="type-match-cut" 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;
|
||||
background: var(--bg, #0b0c0e);
|
||||
color: var(--fg, #f7f8fa);
|
||||
font-family: var(--font-display, Inter, system-ui, sans-serif);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tmc-clip,
|
||||
.tmc-panel,
|
||||
.tmc-copy {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100cqw;
|
||||
height: 100cqh;
|
||||
}
|
||||
|
||||
.tmc-clip {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tmc-panel {
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(145deg, #f8fafc 0%, #e8edf5 100%);
|
||||
box-shadow: 0 4cqh 12cqh color-mix(in srgb, #000 45%, transparent);
|
||||
transform-origin: 50% 50%;
|
||||
will-change: transform, border-radius;
|
||||
}
|
||||
|
||||
.tmc-editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 22cqw 1fr;
|
||||
grid-template-rows: 11cqh 1fr;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.tmc-toolbar {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.1cqw;
|
||||
padding: 0 4cqw;
|
||||
border-bottom: 0.16cqmin solid #cbd5e1;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.tmc-mark {
|
||||
width: 2.4cqmin;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: var(--tmc-accent, #34d399);
|
||||
box-shadow: 0 0 2cqmin color-mix(in srgb, var(--tmc-accent, #34d399) 46%, transparent);
|
||||
}
|
||||
|
||||
.tmc-title {
|
||||
font-size: 2.4cqmin;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.tmc-status {
|
||||
margin-left: auto;
|
||||
padding: 1.1cqh 1.4cqw;
|
||||
border-radius: 999px;
|
||||
background: var(--tmc-accent, #34d399);
|
||||
color: #07110d;
|
||||
font-size: 1.7cqmin;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.tmc-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.4cqh;
|
||||
padding: 5cqh 3cqw;
|
||||
border-right: 0.16cqmin solid #cbd5e1;
|
||||
background: #eef2f7;
|
||||
}
|
||||
|
||||
.tmc-nav {
|
||||
height: 1.5cqh;
|
||||
border-radius: 999px;
|
||||
background: #cbd5e1;
|
||||
}
|
||||
|
||||
.tmc-nav:first-child {
|
||||
width: 76%;
|
||||
background: var(--tmc-accent, #34d399);
|
||||
}
|
||||
|
||||
.tmc-nav:nth-child(2) {
|
||||
width: 58%;
|
||||
}
|
||||
|
||||
.tmc-nav:nth-child(3) {
|
||||
width: 82%;
|
||||
}
|
||||
|
||||
.tmc-canvas {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 7cqh 8cqw;
|
||||
background: #dfe5ee;
|
||||
}
|
||||
|
||||
.tmc-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0.18cqmin solid #c2cad6;
|
||||
border-radius: 3cqmin;
|
||||
background: radial-gradient(
|
||||
circle at 50% 42%,
|
||||
color-mix(in srgb, var(--tmc-accent, #34d399) 32%, #1f2937) 0%,
|
||||
#111827 68%
|
||||
);
|
||||
box-shadow: 0 3cqh 8cqh rgba(15, 23, 42, 0.2);
|
||||
}
|
||||
|
||||
.tmc-play {
|
||||
width: 12cqmin;
|
||||
aspect-ratio: 1;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--tmc-accent, #34d399);
|
||||
color: #07110d;
|
||||
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
font-size: 4.2cqmin;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.tmc-copy {
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
text-shadow: 0 1.4cqh 4.8cqh rgba(0, 0, 0, 0.72);
|
||||
}
|
||||
|
||||
.tmc-half {
|
||||
position: absolute;
|
||||
top: calc(50% - 0.44em);
|
||||
display: block;
|
||||
width: 40cqw;
|
||||
color: var(--fg, #f7f8fa);
|
||||
font-size: var(--tmc-font-size, 10.5cqw);
|
||||
font-weight: 900;
|
||||
line-height: 0.88;
|
||||
letter-spacing: -0.045em;
|
||||
white-space: nowrap;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.tmc-top {
|
||||
right: 52cqw;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tmc-bottom {
|
||||
left: 52cqw;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
id="type-match-cut-clip"
|
||||
class="tmc-clip clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
>
|
||||
<section class="tmc-panel" aria-label="Incoming editor scene">
|
||||
<div class="tmc-editor">
|
||||
<div class="tmc-toolbar">
|
||||
<span class="tmc-mark" aria-hidden="true"></span>
|
||||
<span class="tmc-title">New editor</span>
|
||||
<span class="tmc-status">Ready</span>
|
||||
</div>
|
||||
<div class="tmc-sidebar" aria-hidden="true">
|
||||
<span class="tmc-nav"></span>
|
||||
<span class="tmc-nav"></span>
|
||||
<span class="tmc-nav"></span>
|
||||
</div>
|
||||
<div class="tmc-canvas">
|
||||
<div class="tmc-preview">
|
||||
<span class="tmc-play" aria-hidden="true">▶</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="tmc-copy" role="heading" aria-level="1">
|
||||
<span class="tmc-half tmc-top" aria-hidden="true"></span>
|
||||
<span class="tmc-half tmc-bottom" aria-hidden="true"></span>
|
||||
</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 panel = root.querySelector(".tmc-panel");
|
||||
var copy = root.querySelector(".tmc-copy");
|
||||
var topWords = root.querySelector(".tmc-top");
|
||||
var bottomWords = root.querySelector(".tmc-bottom");
|
||||
var vars =
|
||||
window.__hyperframes && window.__hyperframes.getVariables
|
||||
? window.__hyperframes.getVariables()
|
||||
: {};
|
||||
|
||||
var text = vars.text == null ? "Meet the new editor" : String(vars.text);
|
||||
var accentName =
|
||||
vars.accent === "blue" || vars.accent === "violet" ? vars.accent : "green";
|
||||
var accents = { green: "#34d399", blue: "#60a5fa", violet: "#a78bfa" };
|
||||
var words = text.trim() === "" ? [""] : text.trim().split(/\s+/);
|
||||
var splitAt = Math.ceil(words.length / 2);
|
||||
var topText = words.slice(0, splitAt).join(" ");
|
||||
var bottomText = words.slice(splitAt).join(" ");
|
||||
|
||||
root.style.setProperty("--tmc-accent", accents[accentName]);
|
||||
copy.setAttribute("aria-label", text);
|
||||
topWords.textContent = topText;
|
||||
bottomWords.textContent = bottomText;
|
||||
|
||||
// Deterministic fit from character count. Layout measurement is
|
||||
// zero while the mounted template is being constructed, so each
|
||||
// half is sized against its authored 40cqw box instead. One em per
|
||||
// character is the conservative ceiling, so even wide glyph runs
|
||||
// remain inside the box.
|
||||
var longestHalf = Math.max(1, topText.length, bottomText.length);
|
||||
var fittedSize = Math.min(10.5, 40 / longestHalf);
|
||||
root.style.setProperty("--tmc-font-size", fittedSize.toFixed(2) + "cqw");
|
||||
|
||||
var IN_BASE = 1.05;
|
||||
var OUT_BASE = 1.15;
|
||||
var SPLIT_AT_BASE = 0.14;
|
||||
var SPLIT_DURATION_BASE = 0.58;
|
||||
var PANEL_AT_BASE = 0.58;
|
||||
var PANEL_DURATION_BASE = 0.47;
|
||||
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "4"));
|
||||
var fixedTotal = IN_BASE + OUT_BASE;
|
||||
var phaseScale = duration < fixedTotal ? duration / fixedTotal : 1;
|
||||
var IN = IN_BASE * phaseScale;
|
||||
var OUT = OUT_BASE * phaseScale;
|
||||
var SPLIT_AT = SPLIT_AT_BASE * phaseScale;
|
||||
var SPLIT_DURATION = SPLIT_DURATION_BASE * phaseScale;
|
||||
var PANEL_AT = PANEL_AT_BASE * phaseScale;
|
||||
var PANEL_DURATION = PANEL_DURATION_BASE * phaseScale;
|
||||
var HOLD = Math.max(0, duration - IN - OUT);
|
||||
var HOLD_START = IN;
|
||||
var OUT_START = IN + HOLD;
|
||||
var ARC_FIRST = OUT * 0.42;
|
||||
var ARC_SECOND = OUT - ARC_FIRST;
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(panel, { scaleX: 0.02, scaleY: 0.02, borderRadius: "12cqmin" }, 0);
|
||||
tl.set(topWords, { x: 0, y: 0 }, 0);
|
||||
tl.set(bottomWords, { x: 0, y: 0 }, 0);
|
||||
|
||||
tl.to(
|
||||
topWords,
|
||||
{ y: "-20cqh", duration: SPLIT_DURATION, ease: "power3.out" },
|
||||
SPLIT_AT,
|
||||
);
|
||||
tl.to(
|
||||
bottomWords,
|
||||
{ y: "20cqh", duration: SPLIT_DURATION, ease: "power3.out" },
|
||||
SPLIT_AT,
|
||||
);
|
||||
tl.to(
|
||||
panel,
|
||||
{
|
||||
scaleX: 0.46,
|
||||
scaleY: 0.14,
|
||||
borderRadius: "6cqmin",
|
||||
duration: PANEL_DURATION,
|
||||
ease: "power3.out",
|
||||
},
|
||||
PANEL_AT,
|
||||
);
|
||||
|
||||
if (HOLD > 0) {
|
||||
tl.to(
|
||||
panel,
|
||||
{
|
||||
scaleX: 0.47,
|
||||
scaleY: 0.145,
|
||||
duration: HOLD / 2,
|
||||
ease: "sine.inOut",
|
||||
yoyo: true,
|
||||
repeat: 1,
|
||||
},
|
||||
HOLD_START,
|
||||
);
|
||||
}
|
||||
|
||||
tl.to(
|
||||
panel,
|
||||
{
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
borderRadius: 0,
|
||||
duration: OUT,
|
||||
ease: "power4.out",
|
||||
},
|
||||
OUT_START,
|
||||
);
|
||||
|
||||
// Two authored segments create an arc instead of a straight exit.
|
||||
tl.to(
|
||||
topWords,
|
||||
{ x: "3cqw", y: "-42cqh", duration: ARC_FIRST, ease: "power2.in" },
|
||||
OUT_START,
|
||||
);
|
||||
tl.to(
|
||||
topWords,
|
||||
{
|
||||
x: "-5cqw",
|
||||
y: "-125cqh",
|
||||
duration: ARC_SECOND,
|
||||
ease: "power2.in",
|
||||
},
|
||||
OUT_START + ARC_FIRST,
|
||||
);
|
||||
tl.to(
|
||||
bottomWords,
|
||||
{ x: "-3cqw", y: "42cqh", duration: ARC_FIRST, ease: "power2.in" },
|
||||
OUT_START,
|
||||
);
|
||||
tl.to(
|
||||
bottomWords,
|
||||
{
|
||||
x: "5cqw",
|
||||
y: "125cqh",
|
||||
duration: ARC_SECOND,
|
||||
ease: "power2.in",
|
||||
},
|
||||
OUT_START + ARC_FIRST,
|
||||
);
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["type-match-cut"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user