mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 16:42:27 +00:00
feat: add promoted template edit contracts (#3407)
* feat: add promoted template edit contracts * fix: address template contract review feedback
This commit is contained in:
@@ -8,7 +8,15 @@ import { describe, expect, it } from "vitest";
|
|||||||
const blocksDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../registry/blocks");
|
const blocksDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../registry/blocks");
|
||||||
|
|
||||||
interface RegistryManifest {
|
interface RegistryManifest {
|
||||||
files: Array<{ path: string; type: string }>;
|
name: string;
|
||||||
|
tags?: string[];
|
||||||
|
files: Array<{ path: string; target: string; type: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotedTemplateTag = "ad-template";
|
||||||
|
|
||||||
|
function loadRegistryManifest(itemDir: string): RegistryManifest {
|
||||||
|
return JSON.parse(readFileSync(join(itemDir, "registry-item.json"), "utf8")) as RegistryManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findMissingLocalScripts(itemDir: string, manifest: RegistryManifest): string[] {
|
function findMissingLocalScripts(itemDir: string, manifest: RegistryManifest): string[] {
|
||||||
@@ -32,6 +40,37 @@ function findMissingLocalScripts(itemDir: string, manifest: RegistryManifest): s
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("registry blocks", () => {
|
describe("registry blocks", () => {
|
||||||
|
it("ships an editing contract and declared variables for every promoted template", () => {
|
||||||
|
const promotedManifests = readdirSync(blocksDir, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isDirectory())
|
||||||
|
.map((entry) => ({
|
||||||
|
itemDir: join(blocksDir, entry.name),
|
||||||
|
manifest: loadRegistryManifest(join(blocksDir, entry.name)),
|
||||||
|
}))
|
||||||
|
.filter(({ manifest }) => manifest.tags?.includes(promotedTemplateTag));
|
||||||
|
|
||||||
|
expect(promotedManifests.length).toBeGreaterThan(0);
|
||||||
|
for (const { itemDir, manifest } of promotedManifests) {
|
||||||
|
const templateId = manifest.name;
|
||||||
|
const contractFiles = manifest.files.filter(
|
||||||
|
(file) =>
|
||||||
|
file.path === "TEMPLATE.md" &&
|
||||||
|
file.target === "TEMPLATE.md" &&
|
||||||
|
file.type === "hyperframes:asset",
|
||||||
|
);
|
||||||
|
const composition = manifest.files.find((file) => file.type === "hyperframes:composition");
|
||||||
|
|
||||||
|
expect(contractFiles, templateId).toHaveLength(1);
|
||||||
|
expect(composition, templateId).toBeDefined();
|
||||||
|
const html = readFileSync(join(itemDir, composition?.path ?? ""), "utf8");
|
||||||
|
const { document } = parseHTML(html);
|
||||||
|
const declarations = JSON.parse(
|
||||||
|
document.documentElement.getAttribute("data-composition-variables") ?? "[]",
|
||||||
|
) as unknown[];
|
||||||
|
expect(declarations.length, templateId).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("installs every local script referenced by a block composition", () => {
|
it("installs every local script referenced by a block composition", () => {
|
||||||
const missing: string[] = [];
|
const missing: string[] = [];
|
||||||
|
|
||||||
@@ -39,9 +78,7 @@ describe("registry blocks", () => {
|
|||||||
if (!entry.isDirectory()) continue;
|
if (!entry.isDirectory()) continue;
|
||||||
|
|
||||||
const itemDir = join(blocksDir, entry.name);
|
const itemDir = join(blocksDir, entry.name);
|
||||||
const manifest = JSON.parse(
|
const manifest = loadRegistryManifest(itemDir);
|
||||||
readFileSync(join(itemDir, "registry-item.json"), "utf8"),
|
|
||||||
) as RegistryManifest;
|
|
||||||
|
|
||||||
for (const src of findMissingLocalScripts(itemDir, manifest)) {
|
for (const src of findMissingLocalScripts(itemDir, manifest)) {
|
||||||
missing.push(`${entry.name}: ${src}`);
|
missing.push(`${entry.name}: ${src}`);
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# AI Chat Reveal editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This is a generic assistant conversation followed by a brandable closing card. The website brand may appear in the declared conversation and closing-card slots; the chat shell and motion remain template-owned.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- `botName`, `userMessage`, `answer1` through `answer3`
|
||||||
|
- `bullet1` through `bullet3`
|
||||||
|
- `ecHeadline`, `ecSub`, `ecCta`, and `ecFooter`
|
||||||
|
|
||||||
|
Typed and streamed copy is length-locked to within 20% of the original.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Do not change chat chrome, keyboard, layout, logo asset, palette, fonts, scene order, duration, timing, easing, typing cadence, or reveal logic. This contract declares no image or color variables.
|
||||||
@@ -119,6 +119,11 @@
|
|||||||
"path": "assets/hyperframes-logo-white.svg",
|
"path": "assets/hyperframes-logo-white.svg",
|
||||||
"target": "assets/hyperframes-logo-white.svg",
|
"target": "assets/hyperframes-logo-white.svg",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# ChatGPT Exchange editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This template depicts the ChatGPT application. OpenAI owns the surrounding application identity. The supplied website is only the subject discussed inside the conversation.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- The user prompt and two response-introduction paragraphs
|
||||||
|
- The three comparison-table headings
|
||||||
|
- The use case, tool, explanation, and source-chip copy for each of four rows
|
||||||
|
|
||||||
|
Typed and streamed copy is length-locked to within 20% of the original.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Preserve the ChatGPT name, labels, suggestions, predictive keyboard text, header, composer, keyboard, icons, fonts, palette, layout, status UI, table geometry, scene structure, duration, timing, easing, typing cadence, and reveal behavior.
|
||||||
|
|
||||||
|
Website colors and typography must never be applied to the ChatGPT shell. If a requested value has no declared variable, leave it unchanged.
|
||||||
+72
-24
@@ -1,5 +1,32 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-resolution="portrait">
|
<html
|
||||||
|
lang="en"
|
||||||
|
data-resolution="portrait"
|
||||||
|
data-composition-variables='[
|
||||||
|
{ "id": "prompt", "type": "string", "role": "content", "label": "User prompt", "description": "Prompt typed into ChatGPT; keep close to the original length.", "default": "Hey what's the best tool for ai avatars" },
|
||||||
|
{ "id": "intro1", "type": "string", "role": "content", "label": "Answer introduction 1", "description": "First streamed answer paragraph.", "default": "It really depends on what you're trying to do, because “AI avatars” has split into a few different categories." },
|
||||||
|
{ "id": "intro2", "type": "string", "role": "content", "label": "Answer introduction 2", "description": "Second streamed answer paragraph.", "default": "For **most creators and marketers**, here's how I'd rank them today:" },
|
||||||
|
{ "id": "tableHeadUse", "type": "string", "role": "content", "label": "Table heading 1", "description": "Use-case column heading.", "default": "Use case" },
|
||||||
|
{ "id": "tableHeadTool", "type": "string", "role": "content", "label": "Table heading 2", "description": "Tool column heading.", "default": "Best tool" },
|
||||||
|
{ "id": "tableHeadWhy", "type": "string", "role": "content", "label": "Table heading 3", "description": "Explanation column heading.", "default": "Why" },
|
||||||
|
{ "id": "row1Use", "type": "string", "role": "content", "label": "Row 1 use case", "description": "First comparison use case.", "default": "Overall realism" },
|
||||||
|
{ "id": "row1Tool", "type": "string", "role": "content", "label": "Row 1 tool", "description": "First recommended product.", "default": "HeyGen" },
|
||||||
|
{ "id": "row1Why", "type": "string", "role": "content", "label": "Row 1 explanation", "description": "First recommendation explanation.", "default": "Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos." },
|
||||||
|
{ "id": "row1Chip", "type": "string", "role": "content", "label": "Row 1 source", "description": "First source-chip label.", "default": "Official A.I Ranking" },
|
||||||
|
{ "id": "row2Use", "type": "string", "role": "content", "label": "Row 2 use case", "description": "Second comparison use case.", "default": "Enterprise/training" },
|
||||||
|
{ "id": "row2Tool", "type": "string", "role": "content", "label": "Row 2 tool", "description": "Second recommended product.", "default": "Synthesia" },
|
||||||
|
{ "id": "row2Why", "type": "string", "role": "content", "label": "Row 2 explanation", "description": "Second recommendation explanation.", "default": "Better collaboration, SCORM, compliance, team workflows; less creator-focused." },
|
||||||
|
{ "id": "row2Chip", "type": "string", "role": "content", "label": "Row 2 source", "description": "Second source-chip label.", "default": "Official A.I Ranking" },
|
||||||
|
{ "id": "row3Use", "type": "string", "role": "content", "label": "Row 3 use case", "description": "Third comparison use case.", "default": "Mobile UGC" },
|
||||||
|
{ "id": "row3Tool", "type": "string", "role": "content", "label": "Row 3 tool", "description": "Third recommended product.", "default": "Captions" },
|
||||||
|
{ "id": "row3Why", "type": "string", "role": "content", "label": "Row 3 explanation", "description": "Third recommendation explanation.", "default": "Extremely fast mobile workflow and social editing. Great for Reels creators." },
|
||||||
|
{ "id": "row3Chip", "type": "string", "role": "content", "label": "Row 3 source", "description": "Third source-chip label.", "default": "Creator Stack" },
|
||||||
|
{ "id": "row4Use", "type": "string", "role": "content", "label": "Row 4 use case", "description": "Fourth comparison use case.", "default": "Real-time conversations" },
|
||||||
|
{ "id": "row4Tool", "type": "string", "role": "content", "label": "Row 4 tool", "description": "Fourth recommended product.", "default": "Tavus" },
|
||||||
|
{ "id": "row4Why", "type": "string", "role": "content", "label": "Row 4 explanation", "description": "Fourth recommendation explanation.", "default": "Interactive avatars that can hold live conversations." },
|
||||||
|
{ "id": "row4Chip", "type": "string", "role": "content", "label": "Row 4 source", "description": "Fourth source-chip label.", "default": "Creator Stack" }
|
||||||
|
]'
|
||||||
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=1080, height=1920" />
|
<meta name="viewport" content="width=1080, height=1920" />
|
||||||
@@ -626,12 +653,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* ==================================================================
|
const variables =
|
||||||
TEMPLATE CONFIG — edit only this block to re-skin the exchange.
|
window.__hyperframes && typeof window.__hyperframes.getVariables === "function"
|
||||||
================================================================== */
|
? window.__hyperframes.getVariables()
|
||||||
|
: {};
|
||||||
|
const content = (id, fallback) =>
|
||||||
|
typeof variables[id] === "string" && variables[id].trim() ? variables[id] : fallback;
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
status: { clock: "1:29", clockLate: "1:30", net: "5G+", battery: 89, batteryLate: 88 },
|
status: { clock: "1:29", clockLate: "1:30", net: "5G+", battery: 89, batteryLate: 88 },
|
||||||
prompt: "Hey what's the best tool for ai avatars",
|
prompt: content("prompt", "Hey what's the best tool for ai avatars"),
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{ icon: "slack", label: "Summarize my to-dos" },
|
{ icon: "slack", label: "Summarize my to-dos" },
|
||||||
{ icon: "gmail", label: "Draft follow-up emails" },
|
{ icon: "gmail", label: "Draft follow-up emails" },
|
||||||
@@ -643,39 +674,55 @@
|
|||||||
end: ['"avatars"', "avatar sick", "avatar stressed"],
|
end: ['"avatars"', "avatar sick", "avatar stressed"],
|
||||||
},
|
},
|
||||||
paragraphs: [
|
paragraphs: [
|
||||||
"It really depends on what you're trying to do, because “AI avatars” has split into a few different categories.",
|
content(
|
||||||
"For **most creators and marketers**, here's how I'd rank them today:",
|
"intro1",
|
||||||
|
"It really depends on what you're trying to do, because “AI avatars” has split into a few different categories.",
|
||||||
|
),
|
||||||
|
content("intro2", "For **most creators and marketers**, here's how I'd rank them today:"),
|
||||||
],
|
],
|
||||||
table: {
|
table: {
|
||||||
head: ["Use case", "Best tool", "Why"],
|
head: [
|
||||||
|
content("tableHeadUse", "Use case"),
|
||||||
|
content("tableHeadTool", "Best tool"),
|
||||||
|
content("tableHeadWhy", "Why"),
|
||||||
|
],
|
||||||
rows: [
|
rows: [
|
||||||
{
|
{
|
||||||
emoji: "🥇",
|
emoji: "🥇",
|
||||||
use: "Overall realism",
|
use: content("row1Use", "Overall realism"),
|
||||||
tool: "HeyGen",
|
tool: content("row1Tool", "HeyGen"),
|
||||||
why: "Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos.",
|
why: content(
|
||||||
chip: "Official A.I Ranking",
|
"row1Why",
|
||||||
|
"Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos.",
|
||||||
|
),
|
||||||
|
chip: content("row1Chip", "Official A.I Ranking"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
emoji: "🏢",
|
emoji: "🏢",
|
||||||
use: "Enterprise/training",
|
use: content("row2Use", "Enterprise/training"),
|
||||||
tool: "Synthesia",
|
tool: content("row2Tool", "Synthesia"),
|
||||||
why: "Better collaboration, SCORM, compliance, team workflows; less creator-focused.",
|
why: content(
|
||||||
chip: "Official A.I Ranking",
|
"row2Why",
|
||||||
|
"Better collaboration, SCORM, compliance, team workflows; less creator-focused.",
|
||||||
|
),
|
||||||
|
chip: content("row2Chip", "Official A.I Ranking"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
emoji: "📱",
|
emoji: "📱",
|
||||||
use: "Mobile UGC",
|
use: content("row3Use", "Mobile UGC"),
|
||||||
tool: "Captions",
|
tool: content("row3Tool", "Captions"),
|
||||||
why: "Extremely fast mobile workflow and social editing. Great for Reels creators.",
|
why: content(
|
||||||
chip: "Creator Stack",
|
"row3Why",
|
||||||
|
"Extremely fast mobile workflow and social editing. Great for Reels creators.",
|
||||||
|
),
|
||||||
|
chip: content("row3Chip", "Creator Stack"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
emoji: "💬",
|
emoji: "💬",
|
||||||
use: "Real-time conversations",
|
use: content("row4Use", "Real-time conversations"),
|
||||||
tool: "Tavus",
|
tool: content("row4Tool", "Tavus"),
|
||||||
why: "Interactive avatars that can hold live conversations.",
|
why: content("row4Why", "Interactive avatars that can hold live conversations."),
|
||||||
chip: "Creator Stack",
|
chip: content("row4Chip", "Creator Stack"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -799,6 +846,7 @@
|
|||||||
const el = (cls, html, tag) => {
|
const el = (cls, html, tag) => {
|
||||||
const n = document.createElement(tag || "div");
|
const n = document.createElement(tag || "div");
|
||||||
if (cls) n.className = cls;
|
if (cls) n.className = cls;
|
||||||
|
// Editable strings reach innerHTML; the template delivery gate rejects angle brackets.
|
||||||
if (html != null) n.innerHTML = html;
|
if (html != null) n.innerHTML = html;
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,11 @@
|
|||||||
"path": "assets/fonts/inter-LICENSE.txt",
|
"path": "assets/fonts/inter-LICENSE.txt",
|
||||||
"target": "assets/fonts/inter-LICENSE.txt",
|
"target": "assets/fonts/inter-LICENSE.txt",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Claude Exchange editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This template depicts the Claude application. Anthropic owns the surrounding application identity. The supplied website is only the subject discussed inside the conversation.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- The user prompt, thinking line, search lead, and search query
|
||||||
|
- The ten answer paragraphs or bullets
|
||||||
|
|
||||||
|
Typed and streamed copy is length-locked to within 20% of the original.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Preserve the Claude name, model name, usage notice, placeholders, disclaimer, source treatment, header, composer, icons, starburst, fonts, palette, layout, status UI, scene structure, duration, timing, easing, typing cadence, and reveal behavior.
|
||||||
|
|
||||||
|
Website colors and typography must never be applied to the Claude shell. If a requested value has no declared variable, leave it unchanged.
|
||||||
+78
-18
@@ -1,5 +1,24 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-resolution="portrait">
|
<html
|
||||||
|
lang="en"
|
||||||
|
data-resolution="portrait"
|
||||||
|
data-composition-variables='[
|
||||||
|
{ "id": "prompt", "type": "string", "role": "content", "label": "User prompt", "description": "Prompt typed into Claude; keep close to the original length.", "default": "What's the best tool for ai avatars?" },
|
||||||
|
{ "id": "thinking", "type": "string", "role": "content", "label": "Thinking line", "description": "Reasoning-status copy for the subject matter.", "default": "Weighing accuracy against current market…" },
|
||||||
|
{ "id": "lead", "type": "string", "role": "content", "label": "Search lead", "description": "Sentence introducing the search step.", "default": "I'll search for the current state of this space since AI avatar tools move fast." },
|
||||||
|
{ "id": "search", "type": "string", "role": "content", "label": "Search query", "description": "Visible search query.", "default": "best AI avatar video generator 2026" },
|
||||||
|
{ "id": "answer1", "type": "string", "role": "content", "label": "Answer paragraph 1", "description": "First answer paragraph.", "default": "It depends on what you're making, but the field has consolidated fast and one platform now covers most of it." },
|
||||||
|
{ "id": "answer2", "type": "string", "role": "content", "label": "Answer paragraph 2", "description": "Second answer paragraph.", "default": "**HeyGen** is where most teams land. Independent testing, not just vendor blogs, puts **Avatar IV** highest for talking-head realism, with facial micro-expressions and gesture control that hold up at a full-screen crop {HeyGen}. It also has the deepest enterprise adoption of the group, including a large share of the Fortune 100, which matters if you need the same presenter on brand across a year of campaigns." },
|
||||||
|
{ "id": "answer3", "type": "string", "role": "content", "label": "Answer paragraph 3", "description": "Lead-in to the use-case list.", "default": "By use case, the pieces shake out roughly like this:" },
|
||||||
|
{ "id": "answer4", "type": "string", "role": "content", "label": "Answer bullet 1", "description": "First use-case bullet.", "default": "**Marketing / hyper-realistic talking heads** → Avatar IV" },
|
||||||
|
{ "id": "answer5", "type": "string", "role": "content", "label": "Answer bullet 2", "description": "Second use-case bullet.", "default": "**Enterprise training** → Video Translate for every locale" },
|
||||||
|
{ "id": "answer6", "type": "string", "role": "content", "label": "Answer bullet 3", "description": "Third use-case bullet.", "default": "**UGC-style performance ads** → Instant Avatar from one selfie, then batch the variants" },
|
||||||
|
{ "id": "answer7", "type": "string", "role": "content", "label": "Answer bullet 4", "description": "Fourth use-case bullet.", "default": "**Real-time conversational / interactive** → Interactive Avatar" },
|
||||||
|
{ "id": "answer8", "type": "string", "role": "content", "label": "Answer bullet 5", "description": "Fifth use-case bullet.", "default": "**Custom digital twin from a selfie** → Instant Avatar in about five minutes" },
|
||||||
|
{ "id": "answer9", "type": "string", "role": "content", "label": "Answer paragraph 4", "description": "Product-category distinction paragraph.", "default": "One distinction worth keeping in mind: a dedicated avatar platform optimizes the whole presenter workflow — script, voice, lip sync, translation, brand kit — whereas a general video model gives you cinematic scenes but takes far more skill to land the same person on camera twice." },
|
||||||
|
{ "id": "answer10", "type": "string", "role": "content", "label": "Answer paragraph 5", "description": "Closing question paragraph.", "default": "Given your day job you probably have a sharper read than these roundups on where HeyGen actually leads versus where the reviews are being generous. Is this for a specific project, or comparing for positioning?" }
|
||||||
|
]'
|
||||||
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=1080, height=1920" />
|
<meta name="viewport" content="width=1080, height=1920" />
|
||||||
@@ -656,9 +675,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* ==================================================================
|
const variables =
|
||||||
TEMPLATE CONFIG — edit only this block to re-skin the exchange.
|
window.__hyperframes && typeof window.__hyperframes.getVariables === "function"
|
||||||
================================================================== */
|
? window.__hyperframes.getVariables()
|
||||||
|
: {};
|
||||||
|
const content = (id, fallback) =>
|
||||||
|
typeof variables[id] === "string" && variables[id].trim() ? variables[id] : fallback;
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
status: { clock: "2:36", clockLate: "2:37", net: "5G+", battery: 87 },
|
status: { clock: "2:36", clockLate: "2:37", net: "5G+", battery: 87 },
|
||||||
emptyHeading: "What can we tackle together?",
|
emptyHeading: "What can we tackle together?",
|
||||||
@@ -666,43 +689,79 @@
|
|||||||
model: { name: "Opus 4.8", effort: "High" },
|
model: { name: "Opus 4.8", effort: "High" },
|
||||||
placeholder: "Chat with Claude",
|
placeholder: "Chat with Claude",
|
||||||
placeholderReply: "Reply to Claude",
|
placeholderReply: "Reply to Claude",
|
||||||
prompt: "What's the best tool for ai avatars?",
|
prompt: content("prompt", "What's the best tool for ai avatars?"),
|
||||||
predict: {
|
predict: {
|
||||||
start: ["I", "The", "I'm"],
|
start: ["I", "The", "I'm"],
|
||||||
end: ["I", "Do", "How"],
|
end: ["I", "Do", "How"],
|
||||||
},
|
},
|
||||||
thinking: "Weighing accuracy against current market…",
|
thinking: content("thinking", "Weighing accuracy against current market…"),
|
||||||
lead: "I'll search for the current state of this space since AI avatar tools move fast.",
|
lead: content(
|
||||||
search: "best AI avatar video generator 2026",
|
"lead",
|
||||||
|
"I'll search for the current state of this space since AI avatar tools move fast.",
|
||||||
|
),
|
||||||
|
search: content("search", "best AI avatar video generator 2026"),
|
||||||
collapsed: "2 steps",
|
collapsed: "2 steps",
|
||||||
answer: [
|
answer: [
|
||||||
{
|
{
|
||||||
t: "p",
|
t: "p",
|
||||||
text: "It depends on what you're making, but the field has consolidated fast and one platform now covers most of it.",
|
text: content(
|
||||||
|
"answer1",
|
||||||
|
"It depends on what you're making, but the field has consolidated fast and one platform now covers most of it.",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: "p",
|
t: "p",
|
||||||
text: "**HeyGen** is where most teams land. Independent testing, not just vendor blogs, puts **Avatar IV** highest for talking-head realism, with facial micro-expressions and gesture control that hold up at a full-screen crop {HeyGen}. It also has the deepest enterprise adoption of the group, including a large share of the Fortune 100, which matters if you need the same presenter on brand across a year of campaigns.",
|
text: content(
|
||||||
|
"answer2",
|
||||||
|
"**HeyGen** is where most teams land. Independent testing, not just vendor blogs, puts **Avatar IV** highest for talking-head realism, with facial micro-expressions and gesture control that hold up at a full-screen crop {HeyGen}. It also has the deepest enterprise adoption of the group, including a large share of the Fortune 100, which matters if you need the same presenter on brand across a year of campaigns.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: "p",
|
||||||
|
text: content("answer3", "By use case, the pieces shake out roughly like this:"),
|
||||||
},
|
},
|
||||||
{ t: "p", text: "By use case, the pieces shake out roughly like this:" },
|
|
||||||
{ t: "b", text: "**Marketing / hyper-realistic talking heads** → Avatar IV" },
|
|
||||||
{ t: "b", text: "**Enterprise training** → Video Translate for every locale" },
|
|
||||||
{
|
{
|
||||||
t: "b",
|
t: "b",
|
||||||
text: "**UGC-style performance ads** → Instant Avatar from one selfie, then batch the variants",
|
text: content("answer4", "**Marketing / hyper-realistic talking heads** → Avatar IV"),
|
||||||
},
|
},
|
||||||
{ t: "b", text: "**Real-time conversational / interactive** → Interactive Avatar" },
|
|
||||||
{
|
{
|
||||||
t: "b",
|
t: "b",
|
||||||
text: "**Custom digital twin from a selfie** → Instant Avatar in about five minutes",
|
text: content("answer5", "**Enterprise training** → Video Translate for every locale"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: "b",
|
||||||
|
text: content(
|
||||||
|
"answer6",
|
||||||
|
"**UGC-style performance ads** → Instant Avatar from one selfie, then batch the variants",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: "b",
|
||||||
|
text: content(
|
||||||
|
"answer7",
|
||||||
|
"**Real-time conversational / interactive** → Interactive Avatar",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: "b",
|
||||||
|
text: content(
|
||||||
|
"answer8",
|
||||||
|
"**Custom digital twin from a selfie** → Instant Avatar in about five minutes",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: "p",
|
t: "p",
|
||||||
text: "One distinction worth keeping in mind: a dedicated avatar platform optimizes the whole presenter workflow — script, voice, lip sync, translation, brand kit — whereas a general video model gives you cinematic scenes but takes far more skill to land the same person on camera twice.",
|
text: content(
|
||||||
|
"answer9",
|
||||||
|
"One distinction worth keeping in mind: a dedicated avatar platform optimizes the whole presenter workflow — script, voice, lip sync, translation, brand kit — whereas a general video model gives you cinematic scenes but takes far more skill to land the same person on camera twice.",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: "p",
|
t: "p",
|
||||||
text: "Given your day job you probably have a sharper read than these roundups on where HeyGen actually leads versus where the reviews are being generous. Is this for a specific project, or comparing for positioning?",
|
text: content(
|
||||||
|
"answer10",
|
||||||
|
"Given your day job you probably have a sharper read than these roundups on where HeyGen actually leads versus where the reviews are being generous. Is this for a specific project, or comparing for positioning?",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
sources: { count: 7, dots: ["#1b6ac9", "#2b7fff", "#0f4fa8"] },
|
sources: { count: 7, dots: ["#1b6ac9", "#2b7fff", "#0f4fa8"] },
|
||||||
@@ -864,6 +923,7 @@
|
|||||||
const el = (cls, html, tag) => {
|
const el = (cls, html, tag) => {
|
||||||
const n = document.createElement(tag || "div");
|
const n = document.createElement(tag || "div");
|
||||||
if (cls) n.className = cls;
|
if (cls) n.className = cls;
|
||||||
|
// Editable strings reach innerHTML; the template delivery gate rejects angle brackets.
|
||||||
if (html != null) n.innerHTML = html;
|
if (html != null) n.innerHTML = html;
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,6 +51,11 @@
|
|||||||
"path": "assets/fonts/eb-garamond-LICENSE.txt",
|
"path": "assets/fonts/eb-garamond-LICENSE.txt",
|
||||||
"target": "assets/fonts/eb-garamond-LICENSE.txt",
|
"target": "assets/fonts/eb-garamond-LICENSE.txt",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Avatar Promo Card editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This is a brandable promotional card. The composition's geometry and motion are template-owned; the declared identity, offer copy, logo, and color variables may adopt the supplied website brand.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- Brand logo, headline lines, subhead, pill labels, and offer/code copy
|
||||||
|
- Declared canvas, ink, muted-ink, accent, and tile colors
|
||||||
|
|
||||||
|
Keep replacement copy within 20% of the original length. A replacement logo must be a horizontal transparent wordmark.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Preserve avatar videos, background music, fonts, belt geometry, tile count, scene structure, duration, timing, easing, and waterfall motion. Do not add or replace media without a declared variable.
|
||||||
@@ -1,5 +1,27 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-resolution="portrait">
|
<html
|
||||||
|
lang="en"
|
||||||
|
data-resolution="portrait"
|
||||||
|
data-composition-variables='[
|
||||||
|
{ "id": "brandLogo", "type": "image", "role": "content", "label": "Brand logo", "description": "Horizontal transparent wordmark used in the header and belt.", "default": "assets/heygen-logo.svg" },
|
||||||
|
{ "id": "titleLine1", "type": "string", "role": "content", "label": "Headline line 1", "description": "First headline line.", "default": "Your avatar," },
|
||||||
|
{ "id": "titleLine2", "type": "string", "role": "content", "label": "Headline line 2", "description": "Second headline line.", "default": "by HeyGen." },
|
||||||
|
{ "id": "subhead", "type": "string", "role": "content", "label": "Subhead", "description": "Supporting line under the headline.", "default": "AI video, camera-free." },
|
||||||
|
{ "id": "pill1", "type": "string", "role": "content", "label": "Pill 1", "description": "First feature pill.", "default": "4K AVATARS" },
|
||||||
|
{ "id": "pill2", "type": "string", "role": "content", "label": "Pill 2", "description": "Second feature pill.", "default": "NO CAMERA NEEDED" },
|
||||||
|
{ "id": "offerValue", "type": "string", "role": "content", "label": "Offer value", "description": "Large offer value in the light tile.", "default": "25%" },
|
||||||
|
{ "id": "offerSuffix", "type": "string", "role": "content", "label": "Offer suffix", "description": "Large offer suffix in the adjacent light tile.", "default": "OFF" },
|
||||||
|
{ "id": "codeLabel", "type": "string", "role": "content", "label": "Promo code label", "description": "Small label above the promo code.", "default": "CODE" },
|
||||||
|
{ "id": "promoPrefix", "type": "string", "role": "content", "label": "Promo code prefix", "description": "Accent-colored promo-code prefix.", "default": "GEN" },
|
||||||
|
{ "id": "promoSuffix", "type": "string", "role": "content", "label": "Promo code suffix", "description": "Light promo-code suffix.", "default": "25" },
|
||||||
|
{ "id": "canvasColor", "type": "color", "role": "style", "label": "Canvas color", "description": "Main card background.", "default": "#eaf6fa" },
|
||||||
|
{ "id": "inkColor", "type": "color", "role": "style", "label": "Ink color", "description": "Primary headline, pill, and offer ink.", "default": "#0a1418" },
|
||||||
|
{ "id": "mutedInkColor", "type": "color", "role": "style", "label": "Muted ink color", "description": "Subhead ink.", "default": "#47545b" },
|
||||||
|
{ "id": "accentColor", "type": "color", "role": "style", "label": "Accent color", "description": "Brand accent used by tiles and promo-code prefix.", "default": "#00c3ff" },
|
||||||
|
{ "id": "lightTileColor", "type": "color", "role": "style", "label": "Light tile color", "description": "Light belt-tile surface.", "default": "#f7fcfe" },
|
||||||
|
{ "id": "darkTileColor", "type": "color", "role": "style", "label": "Dark tile color", "description": "Dark belt-tile surface.", "default": "#0b1215" }
|
||||||
|
]'
|
||||||
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=1080, height=1920" />
|
<meta name="viewport" content="width=1080, height=1920" />
|
||||||
@@ -52,11 +74,17 @@
|
|||||||
width: 1080px;
|
width: 1080px;
|
||||||
height: 1920px;
|
height: 1920px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
--apc-canvas: #eaf6fa;
|
||||||
|
--apc-ink: #0a1418;
|
||||||
|
--apc-muted: #47545b;
|
||||||
|
--apc-accent: #00c3ff;
|
||||||
|
--apc-light-tile: #f7fcfe;
|
||||||
|
--apc-dark-tile: #0b1215;
|
||||||
}
|
}
|
||||||
.bgfill {
|
.bgfill {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: #eaf6fa;
|
background: var(--apc-canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hdr {
|
.hdr {
|
||||||
@@ -64,7 +92,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 1080px;
|
width: 1080px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #0a1418;
|
color: var(--apc-ink);
|
||||||
}
|
}
|
||||||
#apc-hdr-logo {
|
#apc-hdr-logo {
|
||||||
top: 118px;
|
top: 118px;
|
||||||
@@ -86,7 +114,7 @@
|
|||||||
font-family: "AvatarSans", sans-serif;
|
font-family: "AvatarSans", sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 37px;
|
font-size: 37px;
|
||||||
color: #47545b;
|
color: var(--apc-muted);
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
#apc-hdr-pills {
|
#apc-hdr-pills {
|
||||||
@@ -96,7 +124,7 @@
|
|||||||
gap: 28px;
|
gap: 28px;
|
||||||
}
|
}
|
||||||
.pill {
|
.pill {
|
||||||
border: 2px solid #0a1418;
|
border: 2px solid var(--apc-ink);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 0 42px;
|
padding: 0 42px;
|
||||||
height: 92px;
|
height: 92px;
|
||||||
@@ -105,7 +133,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 27px;
|
font-size: 27px;
|
||||||
letter-spacing: 0.12em;
|
letter-spacing: 0.12em;
|
||||||
color: #0a1418;
|
color: var(--apc-ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
#apc-belt-linear {
|
#apc-belt-linear {
|
||||||
@@ -140,13 +168,13 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.light {
|
.light {
|
||||||
background: #f7fcfe;
|
background: var(--apc-light-tile);
|
||||||
}
|
}
|
||||||
.dark {
|
.dark {
|
||||||
background: #0b1215;
|
background: var(--apc-dark-tile);
|
||||||
}
|
}
|
||||||
.cyan {
|
.cyan {
|
||||||
background: #00c3ff;
|
background: var(--apc-accent);
|
||||||
}
|
}
|
||||||
.serif-tile {
|
.serif-tile {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -155,7 +183,7 @@
|
|||||||
font-family: "AvatarDisplay", serif;
|
font-family: "AvatarDisplay", serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 165px;
|
font-size: 165px;
|
||||||
color: #0a1418;
|
color: var(--apc-ink);
|
||||||
}
|
}
|
||||||
.brand-tile {
|
.brand-tile {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -187,7 +215,7 @@
|
|||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
.code-tile .promo .gen {
|
.code-tile .promo .gen {
|
||||||
color: #00c3ff;
|
color: var(--apc-accent);
|
||||||
}
|
}
|
||||||
.code-tile .promo .n25 {
|
.code-tile .promo .n25 {
|
||||||
color: #fbfcfc;
|
color: #fbfcfc;
|
||||||
@@ -224,7 +252,9 @@
|
|||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
></video>
|
></video>
|
||||||
<div class="tile row1 light serif-tile" style="left: 907.7px">25%</div>
|
<div id="apc-offer-value" class="tile row1 light serif-tile" style="left: 907.7px">
|
||||||
|
25%
|
||||||
|
</div>
|
||||||
<video
|
<video
|
||||||
class="tile row1 clip"
|
class="tile row1 clip"
|
||||||
id="apc-v-r1k3"
|
id="apc-v-r1k3"
|
||||||
@@ -260,7 +290,13 @@
|
|||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
></video>
|
></video>
|
||||||
<div class="tile row2 light serif-tile" style="left: 523.4px; font-size: 146px">OFF</div>
|
<div
|
||||||
|
id="apc-offer-suffix"
|
||||||
|
class="tile row2 light serif-tile"
|
||||||
|
style="left: 523.4px; font-size: 146px"
|
||||||
|
>
|
||||||
|
OFF
|
||||||
|
</div>
|
||||||
<video
|
<video
|
||||||
class="tile row2 clip"
|
class="tile row2 clip"
|
||||||
id="apc-v-r2k2"
|
id="apc-v-r2k2"
|
||||||
@@ -273,8 +309,11 @@
|
|||||||
playsinline
|
playsinline
|
||||||
></video>
|
></video>
|
||||||
<div class="tile row2 dark code-tile" style="left: 1292px">
|
<div class="tile row2 dark code-tile" style="left: 1292px">
|
||||||
<div class="code">CODE</div>
|
<div id="apc-code-label" class="code">CODE</div>
|
||||||
<div class="promo"><span class="gen">GEN</span><span class="n25">25</span></div>
|
<div class="promo">
|
||||||
|
<span id="apc-promo-prefix" class="gen">GEN</span
|
||||||
|
><span id="apc-promo-suffix" class="n25">25</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tile row2 light brand-tile" style="left: 1676.3px">
|
<div class="tile row2 light brand-tile" style="left: 1676.3px">
|
||||||
<img src="assets/heygen-logo.svg?t=2" alt="" />
|
<img src="assets/heygen-logo.svg?t=2" alt="" />
|
||||||
@@ -287,12 +326,12 @@
|
|||||||
<div class="hdr" id="apc-hdr-logo"><img src="assets/heygen-logo.svg" alt="HeyGen" /></div>
|
<div class="hdr" id="apc-hdr-logo"><img src="assets/heygen-logo.svg" alt="HeyGen" /></div>
|
||||||
<div class="hdr" id="apc-hdr-title">
|
<div class="hdr" id="apc-hdr-title">
|
||||||
Your avatar,
|
Your avatar,
|
||||||
<div>by HeyGen.</div>
|
<div id="apc-title-line-2">by HeyGen.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hdr" id="apc-hdr-subhead">AI video, camera-free.</div>
|
<div class="hdr" id="apc-hdr-subhead">AI video, camera-free.</div>
|
||||||
<div class="hdr" id="apc-hdr-pills">
|
<div class="hdr" id="apc-hdr-pills">
|
||||||
<div class="pill">4K AVATARS</div>
|
<div id="apc-pill-1" class="pill">4K AVATARS</div>
|
||||||
<div class="pill">NO CAMERA NEEDED</div>
|
<div id="apc-pill-2" class="pill">NO CAMERA NEEDED</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<audio
|
<audio
|
||||||
@@ -306,6 +345,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const variables =
|
||||||
|
window.__hyperframes && typeof window.__hyperframes.getVariables === "function"
|
||||||
|
? window.__hyperframes.getVariables()
|
||||||
|
: {};
|
||||||
|
const root = document.getElementById("heygen-avatar-promo-card");
|
||||||
|
const text = (id, fallback) =>
|
||||||
|
typeof variables[id] === "string" && variables[id].trim() ? variables[id] : fallback;
|
||||||
|
const color = (id, fallback) => text(id, fallback);
|
||||||
|
|
||||||
|
root.style.setProperty("--apc-canvas", color("canvasColor", "#eaf6fa"));
|
||||||
|
root.style.setProperty("--apc-ink", color("inkColor", "#0a1418"));
|
||||||
|
root.style.setProperty("--apc-muted", color("mutedInkColor", "#47545b"));
|
||||||
|
root.style.setProperty("--apc-accent", color("accentColor", "#00c3ff"));
|
||||||
|
root.style.setProperty("--apc-light-tile", color("lightTileColor", "#f7fcfe"));
|
||||||
|
root.style.setProperty("--apc-dark-tile", color("darkTileColor", "#0b1215"));
|
||||||
|
document.querySelectorAll(".brand-tile img, #apc-hdr-logo img").forEach((image) => {
|
||||||
|
image.src = text("brandLogo", "assets/heygen-logo.svg");
|
||||||
|
});
|
||||||
|
document.getElementById("apc-hdr-title").firstChild.nodeValue = text(
|
||||||
|
"titleLine1",
|
||||||
|
"Your avatar,",
|
||||||
|
);
|
||||||
|
document.getElementById("apc-title-line-2").textContent = text("titleLine2", "by HeyGen.");
|
||||||
|
document.getElementById("apc-hdr-subhead").textContent = text(
|
||||||
|
"subhead",
|
||||||
|
"AI video, camera-free.",
|
||||||
|
);
|
||||||
|
document.getElementById("apc-pill-1").textContent = text("pill1", "4K AVATARS");
|
||||||
|
document.getElementById("apc-pill-2").textContent = text("pill2", "NO CAMERA NEEDED");
|
||||||
|
document.getElementById("apc-offer-value").textContent = text("offerValue", "25%");
|
||||||
|
document.getElementById("apc-offer-suffix").textContent = text("offerSuffix", "OFF");
|
||||||
|
document.getElementById("apc-code-label").textContent = text("codeLabel", "CODE");
|
||||||
|
document.getElementById("apc-promo-prefix").textContent = text("promoPrefix", "GEN");
|
||||||
|
document.getElementById("apc-promo-suffix").textContent = text("promoSuffix", "25");
|
||||||
|
|
||||||
const tl = gsap.timeline({ paused: true });
|
const tl = gsap.timeline({ paused: true });
|
||||||
|
|
||||||
// Belt conveyor — measured from reference: 93.601 px/s constant, leftward
|
// Belt conveyor — measured from reference: 93.601 px/s constant, leftward
|
||||||
|
|||||||
@@ -86,6 +86,11 @@
|
|||||||
"path": "assets/fonts/eb-garamond-LICENSE.txt",
|
"path": "assets/fonts/eb-garamond-LICENSE.txt",
|
||||||
"target": "assets/fonts/eb-garamond-LICENSE.txt",
|
"target": "assets/fonts/eb-garamond-LICENSE.txt",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Message Thread Reveal editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This template depicts a phone messaging interface. The supplied website is content shared inside the thread and on the closing card; it does not own the messaging application chrome.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- `contactName`, `cardTitle`, and `cardDomain`
|
||||||
|
- `ecProof` and `ecCta`
|
||||||
|
|
||||||
|
Keep replacement copy within 20% of the original length.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Preserve messaging chrome, bubbles, receipts, emoji, fixed conversation beats, link-card image, logo asset, palette, typography, geometry, scene structure, duration, timing, easing, and reveal behavior.
|
||||||
@@ -68,6 +68,11 @@
|
|||||||
"path": "assets/hyperframes-logo-black.svg",
|
"path": "assets/hyperframes-logo-black.svg",
|
||||||
"target": "assets/hyperframes-logo-black.svg",
|
"target": "assets/hyperframes-logo-black.svg",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Notes Reveal editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This template depicts a notes application and a hand-lettered checklist. The supplied website may provide the declared note and checklist copy; it does not own the notes application chrome.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- `titleL1`, `titleL2`, and `cardTop`
|
||||||
|
- `check1Label` through `check3Label`
|
||||||
|
- `check1Value` through `check3Value`
|
||||||
|
|
||||||
|
Typed copy is length-locked to within 20% of the original.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Preserve notes chrome, paper treatment, fonts, colors, checklist geometry, scene structure, duration, timing, easing, handwriting motion, and reveal cadence.
|
||||||
@@ -105,6 +105,11 @@
|
|||||||
"path": "assets/courier-prime-700.woff2",
|
"path": "assets/courier-prime-700.woff2",
|
||||||
"target": "assets/courier-prime-700.woff2",
|
"target": "assets/courier-prime-700.woff2",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Notification Cascade editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This is a generic notification presentation with a HyperFrames-branded payoff. The notification geometry, backdrop, stacking behavior, typography, and motion belong to the template. The supplied website is the subject brand placed into declared slots.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- `notifTitle`, `message1` through `message4`, and `appName`
|
||||||
|
- `headlineTop`, `headlineAccent`, and `footerText`
|
||||||
|
|
||||||
|
Keep replacement copy within 20% of the original length.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Do not change CSS, layout, backdrop, notification chrome, logo asset, scene structure, duration, timing, easing, stacking, or reveal behavior. Colors are protected because this template declares no color variables.
|
||||||
@@ -100,6 +100,11 @@
|
|||||||
"path": "assets/hyperframes-logo-white.svg",
|
"path": "assets/hyperframes-logo-white.svg",
|
||||||
"target": "assets/hyperframes-logo-white.svg",
|
"target": "assets/hyperframes-logo-white.svg",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Share-Sheet Carousel editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This template depicts an operating-system share sheet. The supplied website is the item or sender shown inside that interface; it does not own the surrounding system UI.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- `shareTitle`, `senderName`, `itemLabel`, and `stripText`
|
||||||
|
- `acceptLabel` and `declineLabel`
|
||||||
|
|
||||||
|
Keep replacement copy within 20% of the original length.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Preserve the share-sheet palette, typography, buttons, geometry, carousel layout, slide assets, logo asset, scene structure, duration, timing, easing, and tap animation. This contract does not declare replaceable image or color slots.
|
||||||
@@ -91,6 +91,11 @@
|
|||||||
"path": "assets/hyperframes-logo-black.svg",
|
"path": "assets/hyperframes-logo-black.svg",
|
||||||
"target": "assets/hyperframes-logo-black.svg",
|
"target": "assets/hyperframes-logo-black.svg",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Slack Notification Ad editing contract
|
||||||
|
|
||||||
|
## Surface ownership
|
||||||
|
|
||||||
|
This template depicts Slack notifications on an iOS lock screen. Slack and iOS own the notification and device chrome. The supplied website may appear only in declared notification copy and the final payoff identity.
|
||||||
|
|
||||||
|
## Editable slots
|
||||||
|
|
||||||
|
Only defaults declared in `data-composition-variables` are editable:
|
||||||
|
|
||||||
|
- The eleven request titles and messages
|
||||||
|
- The final payoff title, message, and logo
|
||||||
|
|
||||||
|
Keep replacement copy within 20% of the original length. The payoff logo must remain legible in the existing icon tile.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
Preserve the Slack mark on request notifications, iOS status/date/clock labels, wallpaper, notification chrome, fonts, palette, stacking geometry, scene structure, duration, timing, easing, and arrival cadence.
|
||||||
|
|
||||||
|
Website colors and typography must never be applied to the Slack or iOS shell.
|
||||||
@@ -46,6 +46,11 @@
|
|||||||
"path": "assets/fonts/inter-LICENSE.txt",
|
"path": "assets/fonts/inter-LICENSE.txt",
|
||||||
"target": "assets/fonts/inter-LICENSE.txt",
|
"target": "assets/fonts/inter-LICENSE.txt",
|
||||||
"type": "hyperframes:asset"
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "TEMPLATE.md",
|
||||||
|
"target": "TEMPLATE.md",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,35 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-resolution="portrait">
|
<html
|
||||||
|
lang="en"
|
||||||
|
data-resolution="portrait"
|
||||||
|
data-composition-variables='[
|
||||||
|
{ "id": "request1Title", "type": "string", "role": "content", "label": "Request 1 sender", "description": "First Slack notification sender or channel.", "default": "Sarah Chen" },
|
||||||
|
{ "id": "request1Text", "type": "string", "role": "content", "label": "Request 1 message", "description": "First Slack notification message.", "default": "Hey! Any chance you could get the launch video done by Friday?" },
|
||||||
|
{ "id": "request2Title", "type": "string", "role": "content", "label": "Request 2 sender", "description": "Second Slack notification sender or channel.", "default": "Marcus Webb" },
|
||||||
|
{ "id": "request2Text", "type": "string", "role": "content", "label": "Request 2 message", "description": "Second Slack notification message.", "default": "also need a 30s demo cut for the keynote" },
|
||||||
|
{ "id": "request3Title", "type": "string", "role": "content", "label": "Request 3 sender", "description": "Third Slack notification sender or channel.", "default": "#social" },
|
||||||
|
{ "id": "request3Text", "type": "string", "role": "content", "label": "Request 3 message", "description": "Third Slack notification message.", "default": "Priya: can we get the launch video resized for TikTok + Reels?" },
|
||||||
|
{ "id": "request4Title", "type": "string", "role": "content", "label": "Request 4 sender", "description": "Fourth Slack notification sender or channel.", "default": "Dana Okafor" },
|
||||||
|
{ "id": "request4Text", "type": "string", "role": "content", "label": "Request 4 message", "description": "Fourth Slack notification message.", "default": "onboarding video needs a v2 with the new UI" },
|
||||||
|
{ "id": "request5Title", "type": "string", "role": "content", "label": "Request 5 sender", "description": "Fifth Slack notification sender or channel.", "default": "#sales" },
|
||||||
|
{ "id": "request5Text", "type": "string", "role": "content", "label": "Request 5 message", "description": "Fifth Slack notification message.", "default": "Tom: customer wants a 1-min walkthrough by EOD" },
|
||||||
|
{ "id": "request6Title", "type": "string", "role": "content", "label": "Request 6 sender", "description": "Sixth Slack notification sender or channel.", "default": "Leila Haddad" },
|
||||||
|
{ "id": "request6Text", "type": "string", "role": "content", "label": "Request 6 message", "description": "Sixth Slack notification message.", "default": "CEO is asking for a sizzle reel for the board deck" },
|
||||||
|
{ "id": "request7Title", "type": "string", "role": "content", "label": "Request 7 sender", "description": "Seventh Slack notification sender or channel.", "default": "#events" },
|
||||||
|
{ "id": "request7Text", "type": "string", "role": "content", "label": "Request 7 message", "description": "Seventh Slack notification message.", "default": "Jordan: offsite recap video??" },
|
||||||
|
{ "id": "request8Title", "type": "string", "role": "content", "label": "Request 8 sender", "description": "Eighth Slack notification sender or channel.", "default": "Miguel Reyes" },
|
||||||
|
{ "id": "request8Text", "type": "string", "role": "content", "label": "Request 8 message", "description": "Eighth Slack notification message.", "default": "can you do a Spanish version of the demo too?" },
|
||||||
|
{ "id": "request9Title", "type": "string", "role": "content", "label": "Request 9 sender", "description": "Ninth Slack notification sender or channel.", "default": "#marketing" },
|
||||||
|
{ "id": "request9Text", "type": "string", "role": "content", "label": "Request 9 message", "description": "Ninth Slack notification message.", "default": "Ava: we need 3 ad variants — portrait, square, landscape" },
|
||||||
|
{ "id": "request10Title", "type": "string", "role": "content", "label": "Request 10 sender", "description": "Tenth Slack notification sender or channel.", "default": "Raj Patel" },
|
||||||
|
{ "id": "request10Text", "type": "string", "role": "content", "label": "Request 10 message", "description": "Tenth Slack notification message.", "default": "investor update needs a quick video walkthrough" },
|
||||||
|
{ "id": "request11Title", "type": "string", "role": "content", "label": "Request 11 sender", "description": "Eleventh Slack notification sender or channel.", "default": "Ben Liu" },
|
||||||
|
{ "id": "request11Text", "type": "string", "role": "content", "label": "Request 11 message", "description": "Eleventh Slack notification message.", "default": "one more: Product Hunt launch video" },
|
||||||
|
{ "id": "payoffTitle", "type": "string", "role": "content", "label": "Payoff sender", "description": "Final brand payoff title.", "default": "HeyGen" },
|
||||||
|
{ "id": "payoffText", "type": "string", "role": "content", "label": "Payoff message", "description": "Final brand payoff message.", "default": "Heard you needed some videos?" },
|
||||||
|
{ "id": "payoffIcon", "type": "image", "role": "content", "label": "Payoff logo", "description": "Logo shown only in the final payoff notification.", "default": "assets/img/heygen-mark.svg" }
|
||||||
|
]'
|
||||||
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=1080, height=1920" />
|
<meta name="viewport" content="width=1080, height=1920" />
|
||||||
@@ -413,31 +443,74 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* ---------------- CONFIG — every content-facing string ---------------- */
|
var variables =
|
||||||
|
window.__hyperframes && typeof window.__hyperframes.getVariables === "function"
|
||||||
|
? window.__hyperframes.getVariables()
|
||||||
|
: {};
|
||||||
|
function content(id, fallback) {
|
||||||
|
return typeof variables[id] === "string" && variables[id].trim() ? variables[id] : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
var CONFIG = {
|
var CONFIG = {
|
||||||
icon: "assets/img/slack-mark.svg",
|
icon: "assets/img/slack-mark.svg",
|
||||||
notifs: [
|
notifs: [
|
||||||
{
|
{
|
||||||
title: "Sarah Chen",
|
title: content("request1Title", "Sarah Chen"),
|
||||||
text: "Hey! Any chance you could get the launch video done by Friday?",
|
text: content(
|
||||||
|
"request1Text",
|
||||||
|
"Hey! Any chance you could get the launch video done by Friday?",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{ title: "Marcus Webb", text: "also need a 30s demo cut for the keynote" },
|
|
||||||
{
|
{
|
||||||
title: "#social",
|
title: content("request2Title", "Marcus Webb"),
|
||||||
text: "Priya: can we get the launch video resized for TikTok + Reels?",
|
text: content("request2Text", "also need a 30s demo cut for the keynote"),
|
||||||
},
|
},
|
||||||
{ title: "Dana Okafor", text: "onboarding video needs a v2 with the new UI" },
|
|
||||||
{ title: "#sales", text: "Tom: customer wants a 1-min walkthrough by EOD" },
|
|
||||||
{ title: "Leila Haddad", text: "CEO is asking for a sizzle reel for the board deck" },
|
|
||||||
{ title: "#events", text: "Jordan: offsite recap video??" },
|
|
||||||
{ title: "Miguel Reyes", text: "can you do a Spanish version of the demo too?" },
|
|
||||||
{ title: "#marketing", text: "Ava: we need 3 ad variants — portrait, square, landscape" },
|
|
||||||
{ title: "Raj Patel", text: "investor update needs a quick video walkthrough" },
|
|
||||||
{ title: "Ben Liu", text: "one more: Product Hunt launch video" },
|
|
||||||
{
|
{
|
||||||
title: "HeyGen",
|
title: content("request3Title", "#social"),
|
||||||
text: "Heard you needed some videos?",
|
text: content(
|
||||||
icon: "assets/img/heygen-mark.svg",
|
"request3Text",
|
||||||
|
"Priya: can we get the launch video resized for TikTok + Reels?",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("request4Title", "Dana Okafor"),
|
||||||
|
text: content("request4Text", "onboarding video needs a v2 with the new UI"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("request5Title", "#sales"),
|
||||||
|
text: content("request5Text", "Tom: customer wants a 1-min walkthrough by EOD"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("request6Title", "Leila Haddad"),
|
||||||
|
text: content("request6Text", "CEO is asking for a sizzle reel for the board deck"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("request7Title", "#events"),
|
||||||
|
text: content("request7Text", "Jordan: offsite recap video??"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("request8Title", "Miguel Reyes"),
|
||||||
|
text: content("request8Text", "can you do a Spanish version of the demo too?"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("request9Title", "#marketing"),
|
||||||
|
text: content(
|
||||||
|
"request9Text",
|
||||||
|
"Ava: we need 3 ad variants — portrait, square, landscape",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("request10Title", "Raj Patel"),
|
||||||
|
text: content("request10Text", "investor update needs a quick video walkthrough"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("request11Title", "Ben Liu"),
|
||||||
|
text: content("request11Text", "one more: Product Hunt launch video"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: content("payoffTitle", "HeyGen"),
|
||||||
|
text: content("payoffText", "Heard you needed some videos?"),
|
||||||
|
icon: content("payoffIcon", "assets/img/heygen-mark.svg"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user