mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
* feat(registry): add the notification-cascade block and banner-push-in component A vertical ad-template block: phone notifications land one after another on a 2.000s beat and stack up the screen, each banner pushing the pile down as it arrives, before the whole stack lifts away into a closing card carrying the HyperFrames lockup. Every decisive number - the 214px pitch, the 22px entry rise, the expo.out restack - is measured from reference footage rather than invented. The backdrop plate is a generated photograph, shipped with the item. The banner-push-in component carries the reusable arrival move on its own: one banner fades in and rises into place while everything already on screen slides up one pitch in lockstep. Both pass the full browser gate (runtime, layout, motion, WCAG AA contrast) installed into a scratch project from a served registry. * feat(registry): add the share-sheet-carousel block and sheet-spring-up component A share sheet springs up from the bottom of the phone and cycles photographic previews inside its card on a fast cut rhythm - fourteen cuts over 7.2s - while the blurred ground swaps behind it on the same beats, ending on an accept tap. A brand strip with the HyperFrames lockup rides the bottom of the preview, as the reference's did. The four stills are generated photographs, shipped with the item. The sheet-spring-up component carries the arrival on its own: a real spring sampled frame by frame from reference footage - up from below, 192px overshoot, 0.72s settle - shipped as the sampled table rather than an ease that approximates it. Both pass the full browser gate (WCAG AA) installed from a served registry. * feat(registry): add the typing-indicator and streaming-text components Two chat primitives extracted from reference footage of real conversations. typing-indicator is the three-dot bubble that says someone is writing: it pops in at the thread's edge on the measured entry (scale 0.6 to 1, alpha 0.4 to 1, 0.10s power2.out), holds, and vanishes when the message lands. An optional bounded dot wave is included for compositions that want the dots to breathe. streaming-text is not a typewriter: words arrive in the uneven bursts of a streamed AI answer, on a token rhythm measured word-by-word from the reference, each word landing grey and inking to full colour over 0.27s. The GAPS table ships in the integration note and cycles for any word count. Both demos pass the browser gate. * feat(registry): add the ai-chat-reveal block A question is typed on a rising keyboard - measured keystroke rhythm, steady caret - sent, and answered by an AI that streams its reply word by word: thinking dot on the measured alpha ramp, tokens arriving in uneven bursts, a bulleted list, then a cut to a branded closing card. The typing and streaming schedules are generated at load from the measured gap tables, so every piece of copy is a variable - question, three answer paragraphs, three bullets, and the whole closing card - while the rhythm stays the reference's. Kept at the native 750x1624 capture size so the measured keyboard geometry stays pixel-true. Passes the full browser gate (145/145 WCAG AA) installed from a served registry. * feat(registry): add the message-thread-reveal block A full text conversation plays out on a phone: bubbles popping in on their measured entries, typing indicators holding the beat, a shared link card, an emoji burst, delivered receipts, the composer typing five messages keystroke by keystroke - and the whole thread scrolling up beat by beat on the measured y-choreography until a warm closing card lands. Every message swap keeps the measured keystroke tables intact: the new copy is length-matched character for character to the reference's typed lines, so all 97 typed frames land on their original beats. The footage plate is replaced by a CSS room-and-phone-body, the link card carries a generated still, and the closing card is rebuilt around the HyperFrames lockup with its stars, proof line, and feature icons preserved. Relates to the thread-message-stack work in #3046: that PR ships a caller-payload message-stack component; this block is the complete ad-template film around the same subject. Kept as its own commit so either can move independently. Passes the full browser gate (65/65 WCAG AA) installed from a served registry. * feat(registry): add the notes-reveal block and line-reveal-list component A personal note is typed character by character in a notes app - the caret riding every letter on the measured keystroke frames, the page scrolling up on the measured settle curves as each line lands - then cuts to a paper card with a marker headline, a circled word, a ticked checklist, and the video card. New copy is length-matched line by line to the measured reveal table, so all 176 keystrokes land on their original frames. The two hand-drawn faces (Permanent Marker, Courier Prime - both OFL) ship with the item; the reference's proprietary UI face is replaced by the system stack. The line-reveal-list component carries the paragraph rhythm on its own: lines land on the measured writing cadence that slows as the thought gets heavier - 2.20s, 2.03s, 2.97s, 3.10s, 3.33s, 3.63s - and the page makes room with the measured scroll-settle table instead of a linear glide. Both pass the browser gate (366/366 WCAG AA for the block) installed from a served registry. * docs(catalog): publish the ten new ad-template items Regenerated pages, payloads, navigation and the flat index for the five blocks and five components added in the preceding commits. Every existing page is untouched: the diff is ten new item pages, their payload assets, and the two regenerated indexes. * chore(registry): rebuild the on-device search index for the new items * style(registry): run oxfmt over the new item sources The repo-wide format gate covers registry HTML; the staged-file hook did not reach these. Payloads and pages regenerated to match. * feat(registry): restage the five component demos as device specimens The first demos dropped each primitive onto a bare canvas, which stripped the context the moves were measured in - a dark bubble on a black frame, a sheet floating in empty gradient. Each demo now plays inside a phone on a studio ground, in the move's native habitat - lock screen, chat thread, share sheet, assistant answer, notes page - with a specimen caption naming the component and its measured numbers. Interiors are authored at 1080x1920 and scaled into the screen, so they reuse the blocks' gate-proven geometry. Timelines are unchanged. All five pass the browser gate. * refactor(registry): shelve the five ad-template films as showcases The video-primitive tag pins an item to the Motion Primitives shelf and overrides every other grouping rule - right for an atomic move, wrong for a 25-second film. The five ad-template blocks move to the Showcases shelf (Scenes & demos), where complete scenes live; the primitives shelf keeps only the moves. * refactor(registry): retire banner-push-in and line-reveal-list Reviewed against the shelf they would join and cut as duplicates. banner-push-in was the thinnest cut against the existing notification family - notification-stack already owns the arrival-plus-lockstep-shift move, mirrored. line-reveal-list was pinched from both sides: the existing line-by-line-slide owns generic staggered lines, and the incoming notes-typing scene carries the measured writing cadence and scroll-settle at scene level. Their measured tables live on inside the films they were extracted from. * feat(registry): add four scene primitives - the moments, mountable social-proof-card: the app-store close as one scene - wordmark, five stars, proof line, three feature icons, CTA capsule - in a quiet stagger. Composes with, not against, star-rating-fill / testimonial-proof-card / cta-lockup, which stay the atomic owners of their moves. marker-checklist-card: a hand-lettered paper card - marker headline, an ellipse that draws itself around one word, checklist rows whose checks draw on. The paper-and-marker sibling of mk-specs-list. chat-thread: a conversation as a scene - measured bubble pop-ins, expo lockstep shifts, typing dots holding the beat, a media placeholder. Message content is one variable: heights are measured at mount and the choreography is generated to fit. Relates to #3046's caller-payload stack; kept independent. notes-typing: the notes-app confession - lines typed on the measured human keystroke rhythm, the caret riding every landed character. The scene counterpart to typed-prompt's bare prompt line. All four follow notification-stack's mountable-scene pattern: container units, theme-contract tokens, one paused timeline registered under the literal id. All demos pass the browser gate. * docs(catalog): regenerate for the reshelved films and scene primitives Ten pages become twelve: the five films move to the Showcases shelf, the two retired components' pages are gone, and the four scene primitives get pages under Motion Scenes. Every untouched page is byte-identical. * feat(registry): bring chat-thread to film fidelity, with a per-message type switch The scene now carries everything the reference conversation carries: status bar, back badge, avatar and name pill, bubbles with real iMessage tails, a Delivered receipt under the last sent bubble, large emoji rows, a media placeholder, and a link card with title, domain and chevron. Every message picks its type by prefix, the way marker-highlight picks a style: recv:/sent: text bubbles, emoji: a large row, img a media placeholder, card:Title~domain a link card. Heights are still measured at mount and the choreography generated, so any mix of types fits. * feat(registry): add chat-message - the atom, with its type as an enum ONE message arrives on the measured entry, growing from its sender's corner. The type is a proper enum - Received, Sent, Emoji, Image, Link card - switched on the catalog page the way marker-highlight switches its style, with the bubble tail and the Delivered receipt as their own switches. chat-thread remains the conversation-as-scene built from arrivals like this one; the pipe grammar belongs at that level, the enum belongs here. Demo cycles all five types, one at a time, with a type chip naming each. * fix(registry): neutral composer placeholder, gsap 3.14.2 pin, exact font licences Review fixes for #3217: - Rendered composer placeholder "iMessage" -> "Message" in chat-thread, its demo, and message-thread-reveal (technique comments stay as provenance) - notes-reveal now loads gsap@3.14.2 like every other item - Licence wording names each face: Courier Prime OFL 1.1, Permanent Marker Apache-2.0 (verified against embedded name-table records) Mirrored catalog payloads and pages regenerated for the four touched items.
996 lines
33 KiB
HTML
Vendored
996 lines
33 KiB
HTML
Vendored
<!doctype html>
|
|
<html
|
|
lang="en"
|
|
data-composition-variables='[
|
|
{ "id": "botName", "type": "string", "role": "content", "label": "Assistant name", "description": "Title in the chat header.", "default": "Assistant" },
|
|
{ "id": "userMessage", "type": "string", "role": "content", "label": "User message", "description": "The question typed on the keyboard and sent. Around 40 characters keeps the measured pacing.", "default": "How do I turn my HTML into real video?" },
|
|
{ "id": "answer1", "type": "string", "role": "content", "label": "Answer paragraph 1", "description": "First streamed paragraph.", "default": "You do not need an editor. HyperFrames renders the HTML you already write into deterministic, pixel-perfect video." },
|
|
{ "id": "answer2", "type": "string", "role": "content", "label": "Answer paragraph 2", "description": "Short second beat.", "default": "It is markup, not magic." },
|
|
{ "id": "answer3", "type": "string", "role": "content", "label": "Answer paragraph 3", "description": "Lead-in to the list.", "default": "What you get out of the box:" },
|
|
{ "id": "bullet1", "type": "string", "role": "content", "label": "Bullet 1", "description": "First list item.", "default": "A catalog of motion primitives you install and own" },
|
|
{ "id": "bullet2", "type": "string", "role": "content", "label": "Bullet 2", "description": "Second list item.", "default": "GSAP timelines that seek to any frame" },
|
|
{ "id": "bullet3", "type": "string", "role": "content", "label": "Bullet 3", "description": "Third list item.", "default": "4K renders from a single CLI command" },
|
|
{ "id": "ecHeadline", "type": "string", "role": "content", "label": "End-card headline", "description": "Closing card headline. A | breaks the line.", "default": "It's not magic.|It's HTML." },
|
|
{ "id": "ecSub", "type": "string", "role": "content", "label": "End-card subline", "description": "Supporting sentence on the closing card.", "default": "Open-source video rendering — write HTML, ship pixel-perfect video." },
|
|
{ "id": "ecCta", "type": "string", "role": "content", "label": "End-card CTA", "description": "Label on the closing card button.", "default": "Try HyperFrames" },
|
|
{ "id": "ecFooter", "type": "string", "role": "content", "label": "End-card footer", "description": "Letter-spaced line at the very bottom.", "default": "HYPERFRAMES.HEYGEN.COM" }
|
|
]'
|
|
>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=750, height=1624" />
|
|
<title>AI Chat Reveal</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
html,
|
|
body {
|
|
width: 750px;
|
|
height: 1624px;
|
|
overflow: hidden;
|
|
background: #000;
|
|
}
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
|
}
|
|
/* 750x1624 is the reference device capture, kept native so the measured
|
|
keyboard geometry stays pixel-true; scale the block when embedding */
|
|
#acr-root {
|
|
position: relative;
|
|
width: 750px;
|
|
height: 1624px;
|
|
overflow: hidden;
|
|
}
|
|
.clip {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
#acr-chat-bg {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: #fdfdfd;
|
|
}
|
|
|
|
/* status bar */
|
|
#acr-statusbar {
|
|
position: absolute;
|
|
top: 14px;
|
|
left: 0;
|
|
right: 0;
|
|
height: 44px;
|
|
}
|
|
#acr-clock {
|
|
position: absolute;
|
|
left: 39px;
|
|
top: 9px;
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
letter-spacing: 2.4px;
|
|
font-variant-numeric: tabular-nums;
|
|
color: #0d0d0d;
|
|
}
|
|
#acr-sicons {
|
|
position: absolute;
|
|
right: 26px;
|
|
top: 8px;
|
|
display: flex;
|
|
gap: 9px;
|
|
align-items: center;
|
|
}
|
|
|
|
/* header */
|
|
#acr-header {
|
|
position: absolute;
|
|
top: 64px;
|
|
left: 0;
|
|
right: 0;
|
|
height: 52px;
|
|
}
|
|
#acr-hleft {
|
|
position: absolute;
|
|
left: 28px;
|
|
top: 12px;
|
|
}
|
|
#acr-htitle {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 48px;
|
|
text-align: center;
|
|
top: 13px;
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
color: #0d0d0d;
|
|
}
|
|
.acr-hicons {
|
|
position: absolute;
|
|
right: 28px;
|
|
top: 14px;
|
|
display: flex;
|
|
gap: 26px;
|
|
align-items: center;
|
|
}
|
|
#acr-hicons-b {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
|
|
/* user bubble */
|
|
#acr-bubble {
|
|
position: absolute;
|
|
top: 129px;
|
|
right: 32px;
|
|
max-width: 544px;
|
|
background: #f3f3f3;
|
|
border-radius: 34px;
|
|
padding: 20px 30px;
|
|
font-size: 27px;
|
|
line-height: 38.6px;
|
|
color: #111;
|
|
letter-spacing: 1.25px;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
|
|
/* answer */
|
|
#acr-answer {
|
|
position: absolute;
|
|
top: 267px;
|
|
left: 33px;
|
|
width: 662px;
|
|
font-size: 27px;
|
|
line-height: 38.6px;
|
|
color: #141414;
|
|
letter-spacing: 1.25px;
|
|
}
|
|
#acr-answer .acr-ap {
|
|
margin: 0 0 15.5px 0;
|
|
}
|
|
#acr-answer .acr-ali {
|
|
display: flex;
|
|
margin: 0 0 6.5px 0;
|
|
}
|
|
#acr-answer .acr-mark {
|
|
width: 30px;
|
|
flex: 0 0 30px;
|
|
text-align: center;
|
|
font-size: 18px;
|
|
line-height: 38.6px;
|
|
}
|
|
#acr-answer .acr-alitext {
|
|
flex: 1;
|
|
}
|
|
.acr-w {
|
|
opacity: 0;
|
|
}
|
|
#acr-dot {
|
|
position: absolute;
|
|
left: 32px;
|
|
top: 279px;
|
|
width: 15px;
|
|
height: 15px;
|
|
border-radius: 50%;
|
|
background: #646464;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
|
|
/* composer (rest dock; rides keyboard via transform) */
|
|
#acr-composer {
|
|
position: absolute;
|
|
left: 30px;
|
|
right: 30px;
|
|
bottom: 31px;
|
|
}
|
|
#acr-pill {
|
|
position: relative;
|
|
width: 100%;
|
|
min-height: 81px;
|
|
background: #fff;
|
|
border: 2px solid #ececec;
|
|
border-radius: 41px;
|
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.04);
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 13px 160px 13px 78px;
|
|
}
|
|
#acr-plusi {
|
|
position: absolute;
|
|
left: 30px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
}
|
|
#acr-ph {
|
|
position: absolute;
|
|
left: 78px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: #767676;
|
|
font-size: 28px;
|
|
letter-spacing: 1.25px;
|
|
}
|
|
#acr-ctextwrap {
|
|
width: 100%;
|
|
min-height: 38px;
|
|
}
|
|
#acr-ctext {
|
|
font-size: 29px;
|
|
line-height: 38px;
|
|
color: #0d0d0d;
|
|
letter-spacing: 1.25px;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
#acr-caret {
|
|
display: inline-block;
|
|
width: 3px;
|
|
height: 30px;
|
|
margin-left: 2px;
|
|
border-radius: 1.5px;
|
|
background: #0d0d0d;
|
|
vertical-align: -4px;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
#acr-cmic {
|
|
position: absolute;
|
|
right: 80px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
}
|
|
.acr-cbtn {
|
|
position: absolute;
|
|
right: 14px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 50%;
|
|
display: grid;
|
|
place-items: center;
|
|
}
|
|
#acr-btn-send-grey {
|
|
background: #ececec;
|
|
}
|
|
#acr-btn-send-black {
|
|
background: #0d0d0d;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
#acr-btn-stop {
|
|
background: #0d0d0d;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
#acr-btn-stop .acr-sq {
|
|
width: 19px;
|
|
height: 19px;
|
|
border-radius: 5px;
|
|
background: #fff;
|
|
}
|
|
|
|
/* keyboard: geometry and #d2d5e0 ground measured from the reference */
|
|
#acr-keyboard {
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: 0;
|
|
width: 750px;
|
|
height: 482px;
|
|
background: #d2d5e0;
|
|
}
|
|
#acr-suggest {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 74px;
|
|
display: flex;
|
|
}
|
|
#acr-suggest .acr-sug {
|
|
flex: 1;
|
|
display: grid;
|
|
place-items: center;
|
|
font-size: 29px;
|
|
color: #0d0d0d;
|
|
position: relative;
|
|
}
|
|
#acr-suggest .acr-sug + .acr-sug::before {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0;
|
|
top: 16px;
|
|
bottom: 16px;
|
|
width: 1.5px;
|
|
background: #bcbabf;
|
|
}
|
|
.acr-key {
|
|
position: absolute;
|
|
height: 74px;
|
|
background: #fdfdfd;
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 0 #8b8a8f;
|
|
display: grid;
|
|
place-items: center;
|
|
font-size: 32px;
|
|
color: #000;
|
|
}
|
|
.acr-gkey {
|
|
background: #aeacaf;
|
|
}
|
|
.acr-num,
|
|
.acr-spc {
|
|
font-size: 27px;
|
|
}
|
|
.acr-kbstrip {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 0;
|
|
right: 0;
|
|
height: 52px;
|
|
}
|
|
.acr-kglobe {
|
|
position: absolute;
|
|
left: 48px;
|
|
top: 21px;
|
|
}
|
|
.acr-kmic {
|
|
position: absolute;
|
|
right: 52px;
|
|
top: 23px;
|
|
}
|
|
|
|
/* end card: the HyperFrames closing card, on the family's dark ground */
|
|
#acr-endcard-inner {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(180deg, #14110e 0%, #221b13 100%);
|
|
opacity: 0;
|
|
}
|
|
#acr-eclogo {
|
|
position: absolute;
|
|
left: 255px;
|
|
top: 112px;
|
|
width: 240px;
|
|
height: 72px;
|
|
}
|
|
#acr-ecstar {
|
|
position: absolute;
|
|
left: 610px;
|
|
top: 230px;
|
|
}
|
|
#acr-ecvideo {
|
|
position: absolute;
|
|
left: 222px;
|
|
top: 336px;
|
|
width: 283px;
|
|
height: 454px;
|
|
border-radius: 36px;
|
|
background: linear-gradient(165deg, #0f3a2c 0%, #0a9c6b 78%, #3ce6ac 100%);
|
|
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
|
|
}
|
|
#acr-ecvideo .acr-play {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 108px;
|
|
height: 108px;
|
|
border-radius: 50%;
|
|
background: rgba(253, 253, 253, 0.94);
|
|
display: grid;
|
|
place-items: center;
|
|
}
|
|
#acr-echead {
|
|
position: absolute;
|
|
top: 946px;
|
|
left: 0;
|
|
right: 0;
|
|
text-align: center;
|
|
font-weight: 700;
|
|
font-size: 57px;
|
|
line-height: 58px;
|
|
color: #f7ece5;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
#acr-ecsub {
|
|
position: absolute;
|
|
top: 1084px;
|
|
left: 65px;
|
|
right: 65px;
|
|
text-align: center;
|
|
font-weight: 500;
|
|
font-size: 31px;
|
|
line-height: 40px;
|
|
color: #cfc4b8;
|
|
}
|
|
#acr-eccta {
|
|
position: absolute;
|
|
left: 160px;
|
|
top: 1406px;
|
|
width: 429px;
|
|
height: 87px;
|
|
background: #3ce6ac;
|
|
border-radius: 44px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 22px;
|
|
}
|
|
#acr-eccta .acr-lbl {
|
|
color: #0d0d0d;
|
|
font-weight: 700;
|
|
font-size: 33px;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
#acr-ecfoot {
|
|
position: absolute;
|
|
top: 1536px;
|
|
left: 0;
|
|
right: 0;
|
|
text-align: center;
|
|
font-weight: 700;
|
|
font-size: 25px;
|
|
letter-spacing: 4px;
|
|
color: #3ce6ac;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div
|
|
id="acr-root"
|
|
data-composition-id="ai-chat-reveal"
|
|
data-start="0"
|
|
data-width="750"
|
|
data-height="1624"
|
|
data-duration="19.3333"
|
|
>
|
|
<section id="acr-chat" class="clip" data-start="0" data-duration="16.9" data-track-index="1">
|
|
<div id="acr-chat-bg"></div>
|
|
|
|
<div id="acr-statusbar">
|
|
<div id="acr-clock">11:42</div>
|
|
<div id="acr-sicons">
|
|
<svg width="30" height="20" viewBox="0 0 20 12" fill="#0d0d0d">
|
|
<rect x="0" y="7" width="3" height="4" rx="1" />
|
|
<rect x="5" y="5" width="3" height="6" rx="1" />
|
|
<rect x="10" y="2.6" width="3" height="8.4" rx="1" />
|
|
<rect x="15" y="0.5" width="3" height="10.5" rx="1" /></svg
|
|
><svg
|
|
width="30"
|
|
height="21"
|
|
viewBox="0 0 22 15"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="2.2"
|
|
stroke-linecap="round"
|
|
>
|
|
<path d="M2 5 C7 -0.5 15 -0.5 20 5" fill="none" />
|
|
<path d="M5.5 8.6 C9 5 13 5 16.5 8.6" />
|
|
<path d="M9.2 12 C10.4 11 11.6 11 12.8 12" /></svg
|
|
><svg width="46" height="22" viewBox="0 0 30 14" fill="none">
|
|
<rect
|
|
x="0.8"
|
|
y="0.8"
|
|
width="25"
|
|
height="12.4"
|
|
rx="3.6"
|
|
stroke="#0d0d0d"
|
|
stroke-opacity="0.32"
|
|
stroke-width="1.2"
|
|
/>
|
|
<rect x="2.4" y="2.4" width="22.4" height="9.2" rx="2.4" fill="#0d0d0d" />
|
|
<path d="M28 4.6 v4.8 a2.4 2.4 0 0 0 0 -4.8" fill="#0d0d0d" fill-opacity="0.45" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="acr-header">
|
|
<div id="acr-hleft">
|
|
<svg
|
|
width="29"
|
|
height="29"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="1.9"
|
|
stroke-linecap="round"
|
|
>
|
|
<path d="M4 8 H20 M4 15 H12" />
|
|
</svg>
|
|
</div>
|
|
<div id="acr-htitle">Assistant</div>
|
|
<div class="acr-hicons" id="acr-hicons-a">
|
|
<svg
|
|
width="28"
|
|
height="28"
|
|
viewBox="0 0 26 24"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="1.7"
|
|
stroke-linecap="round"
|
|
>
|
|
<circle cx="10" cy="8" r="4" />
|
|
<path d="M3 21 C3 16 6.5 14 10 14 C13.5 14 17 16 17 21" />
|
|
<path d="M20 6 V12 M17 9 H23" /></svg
|
|
><svg
|
|
width="26"
|
|
height="26"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="1.7"
|
|
stroke-linecap="round"
|
|
stroke-dasharray="2.4 3.4"
|
|
>
|
|
<circle cx="12" cy="12" r="9" />
|
|
</svg>
|
|
</div>
|
|
<div class="acr-hicons" id="acr-hicons-b">
|
|
<svg
|
|
width="23"
|
|
height="23"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="1.7"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<path
|
|
d="M4 20 L5 15.5 L16.5 4 C17.6 2.9 19.4 2.9 20.5 4 C21.6 5.1 21.6 6.9 20.5 8 L9 19.5 L4 20 Z"
|
|
/></svg
|
|
><svg width="34" height="34" viewBox="0 0 24 24" fill="#0d0d0d">
|
|
<circle cx="5" cy="12" r="1.7" />
|
|
<circle cx="12" cy="12" r="1.7" />
|
|
<circle cx="19" cy="12" r="1.7" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="acr-bubble">How do I turn my HTML into real video?</div>
|
|
|
|
<div id="acr-dot"></div>
|
|
<div id="acr-answer" data-layout-allow-occlusion></div>
|
|
|
|
<div id="acr-composer">
|
|
<div id="acr-pill">
|
|
<div id="acr-plusi">
|
|
<svg
|
|
width="22"
|
|
height="22"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="1.9"
|
|
stroke-linecap="round"
|
|
>
|
|
<path d="M12 5 V19 M5 12 H19" />
|
|
</svg>
|
|
</div>
|
|
<div id="acr-ph" data-layout-allow-overlap data-layout-allow-occlusion>
|
|
Ask anything
|
|
</div>
|
|
<div id="acr-ctextwrap"><span id="acr-ctext"></span><span id="acr-caret"></span></div>
|
|
<div id="acr-cmic">
|
|
<svg
|
|
width="15"
|
|
height="22"
|
|
viewBox="0 0 24 34"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
>
|
|
<rect x="8" y="2" width="8" height="16" rx="4" fill="#0d0d0d" stroke="none" />
|
|
<path d="M4 14 a8 8 0 0 0 16 0 M12 22 V28 M7 31 H17" stroke-width="2.2" />
|
|
</svg>
|
|
</div>
|
|
<div class="acr-cbtn" id="acr-btn-send-grey">
|
|
<svg
|
|
width="26"
|
|
height="26"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="#b6b6b6"
|
|
stroke-width="2.6"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<path d="M12 19 V6 M6.5 11 L12 5.5 L17.5 11" />
|
|
</svg>
|
|
</div>
|
|
<div class="acr-cbtn" id="acr-btn-send-black">
|
|
<svg
|
|
width="26"
|
|
height="26"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="#fff"
|
|
stroke-width="2.6"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<path d="M12 19 V6 M6.5 11 L12 5.5 L17.5 11" />
|
|
</svg>
|
|
</div>
|
|
<div class="acr-cbtn" id="acr-btn-stop"><div class="acr-sq"></div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="acr-keyboard">
|
|
<div id="acr-suggest">
|
|
<div class="acr-sug">How</div>
|
|
<div class="acr-sug">Can</div>
|
|
<div class="acr-sug">My</div>
|
|
</div>
|
|
<div id="acr-keys"></div>
|
|
<div class="acr-kbstrip">
|
|
<span class="acr-kglobe"
|
|
><svg
|
|
width="30"
|
|
height="30"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="#000"
|
|
stroke-width="2.6"
|
|
>
|
|
<circle cx="12" cy="12" r="9.6" />
|
|
<path
|
|
d="M2.4 12 H21.6 M12 2.4 C7.8 6.8 7.8 17.2 12 21.6 C16.2 17.2 16.2 6.8 12 2.4 M4 7 H20 M4 17 H20"
|
|
stroke-width="2.2"
|
|
/></svg></span
|
|
><span class="acr-kmic"
|
|
><svg
|
|
width="16"
|
|
height="25"
|
|
viewBox="0 0 24 34"
|
|
preserveAspectRatio="none"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="3.9"
|
|
stroke-linecap="round"
|
|
>
|
|
<rect
|
|
x="5.5"
|
|
y="1"
|
|
width="13"
|
|
height="17.5"
|
|
rx="6.5"
|
|
fill="#0d0d0d"
|
|
stroke="none"
|
|
/>
|
|
<path d="M2.5 14 a9.5 9.5 0 0 0 19 0 M12 23.5 V28.5 M5.5 32 H18.5" /></svg
|
|
></span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section
|
|
id="acr-endcard"
|
|
class="clip"
|
|
data-start="15.8"
|
|
data-duration="3.5333"
|
|
data-track-index="2"
|
|
>
|
|
<div id="acr-endcard-inner">
|
|
<img id="acr-eclogo" src="assets/hyperframes-logo-white.svg" alt="HyperFrames" />
|
|
<svg id="acr-ecstar" width="80" height="80" viewBox="0 0 80 80">
|
|
<g stroke="#2f6b57" stroke-width="8.5" stroke-linecap="round">
|
|
<line x1="47" y1="40" x2="77" y2="40" />
|
|
<line x1="46.5" y1="42.7" x2="74.2" y2="54.2" />
|
|
<line x1="44.9" y1="44.9" x2="66.2" y2="66.2" />
|
|
<line x1="42.7" y1="46.5" x2="54.2" y2="74.2" />
|
|
<line x1="40" y1="47" x2="40" y2="77" />
|
|
<line x1="37.3" y1="46.5" x2="25.8" y2="74.2" />
|
|
<line x1="35.1" y1="44.9" x2="13.8" y2="66.2" />
|
|
<line x1="33.5" y1="42.7" x2="5.8" y2="54.2" />
|
|
<line x1="33" y1="40" x2="3" y2="40" />
|
|
<line x1="33.5" y1="37.3" x2="5.8" y2="25.8" />
|
|
<line x1="35.1" y1="35.1" x2="13.8" y2="13.8" />
|
|
<line x1="37.3" y1="33.5" x2="25.8" y2="5.8" />
|
|
<line x1="40" y1="33" x2="40" y2="3" />
|
|
<line x1="42.7" y1="33.5" x2="54.2" y2="5.8" />
|
|
<line x1="44.9" y1="35.1" x2="66.2" y2="13.8" />
|
|
<line x1="46.5" y1="37.3" x2="74.2" y2="25.8" />
|
|
</g>
|
|
</svg>
|
|
<div id="acr-ecvideo">
|
|
<div class="acr-play">
|
|
<svg width="40" height="46" viewBox="0 0 44 48" fill="none">
|
|
<path
|
|
d="M40.5 20.6 C43.2 22.1 43.2 25.9 40.5 27.4 L6.2 46.6 C3.5 48.1 0.2 46.2 0.2 43.2 L0.2 4.8 C0.2 1.8 3.5 -0.1 6.2 1.4 Z"
|
|
fill="#0d0d0d"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="acr-echead">It's not magic.<br />It's HTML.</div>
|
|
<div id="acr-ecsub">
|
|
Open-source video rendering — write HTML, ship pixel-perfect video.
|
|
</div>
|
|
<div id="acr-eccta">
|
|
<span class="acr-lbl">Try HyperFrames</span
|
|
><svg
|
|
width="30"
|
|
height="34"
|
|
viewBox="0 0 16 24"
|
|
fill="none"
|
|
stroke="#0d0d0d"
|
|
stroke-width="2.6"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<path d="M4 3 L13 12 L4 21" />
|
|
</svg>
|
|
</div>
|
|
<div id="acr-ecfoot">HYPERFRAMES.HEYGEN.COM</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
"use strict";
|
|
|
|
/* -------- variables -------- */
|
|
var vars =
|
|
window.__hyperframes && typeof window.__hyperframes.getVariables === "function"
|
|
? window.__hyperframes.getVariables()
|
|
: {};
|
|
|
|
function text(value, fallback, max) {
|
|
if (typeof value !== "string") return fallback;
|
|
var t = value.trim();
|
|
if (!t) return fallback;
|
|
return t.length > max ? t.slice(0, max) : t;
|
|
}
|
|
|
|
var botName = text(vars.botName, "Assistant", 24);
|
|
var userMessage = text(vars.userMessage, "How do I turn my HTML into real video?", 60);
|
|
var answer1 = text(
|
|
vars.answer1,
|
|
"You do not need an editor. HyperFrames renders the HTML you already write into deterministic, pixel-perfect video.",
|
|
220,
|
|
);
|
|
var answer2 = text(vars.answer2, "It is markup, not magic.", 120);
|
|
var answer3 = text(vars.answer3, "What you get out of the box:", 120);
|
|
var bullets = [
|
|
text(vars.bullet1, "A catalog of motion primitives you install and own", 90),
|
|
text(vars.bullet2, "GSAP timelines that seek to any frame", 90),
|
|
text(vars.bullet3, "4K renders from a single CLI command", 90),
|
|
];
|
|
|
|
document.getElementById("acr-htitle").textContent = botName;
|
|
document.getElementById("acr-bubble").textContent = userMessage;
|
|
document.getElementById("acr-echead").innerHTML = "";
|
|
text(vars.ecHeadline, "It's not magic.|It's HTML.", 60)
|
|
.split("|")
|
|
.forEach(function (line, i) {
|
|
var head = document.getElementById("acr-echead");
|
|
if (i > 0) head.appendChild(document.createElement("br"));
|
|
head.appendChild(document.createTextNode(line));
|
|
});
|
|
document.getElementById("acr-ecsub").textContent = text(
|
|
vars.ecSub,
|
|
"Open-source video rendering — write HTML, ship pixel-perfect video.",
|
|
140,
|
|
);
|
|
document.querySelector("#acr-eccta .acr-lbl").textContent = text(
|
|
vars.ecCta,
|
|
"Try HyperFrames",
|
|
30,
|
|
);
|
|
document.getElementById("acr-ecfoot").textContent = text(
|
|
vars.ecFooter,
|
|
"HYPERFRAMES.HEYGEN.COM",
|
|
40,
|
|
);
|
|
|
|
/* -------- build the answer DOM: paragraphs of word spans, then bullets -------- */
|
|
var answerEl = document.getElementById("acr-answer");
|
|
var wordEls = [];
|
|
function addPara(str) {
|
|
var p = document.createElement("p");
|
|
p.className = "acr-ap";
|
|
str.split(/\s+/).forEach(function (word, i) {
|
|
if (i > 0) p.appendChild(document.createTextNode(" "));
|
|
var s = document.createElement("span");
|
|
s.className = "acr-w";
|
|
s.textContent = word;
|
|
p.appendChild(s);
|
|
wordEls.push(s);
|
|
});
|
|
answerEl.appendChild(p);
|
|
}
|
|
addPara(answer1);
|
|
addPara(answer2);
|
|
addPara(answer3);
|
|
bullets.forEach(function (b) {
|
|
var row = document.createElement("div");
|
|
row.className = "acr-ali";
|
|
var mark = document.createElement("span");
|
|
mark.className = "acr-mark acr-w";
|
|
mark.textContent = "•";
|
|
row.appendChild(mark);
|
|
wordEls.push(mark);
|
|
var body = document.createElement("div");
|
|
body.className = "acr-alitext";
|
|
b.split(/\s+/).forEach(function (word, i) {
|
|
if (i > 0) body.appendChild(document.createTextNode(" "));
|
|
var s = document.createElement("span");
|
|
s.className = "acr-w";
|
|
s.textContent = word;
|
|
body.appendChild(s);
|
|
wordEls.push(s);
|
|
});
|
|
row.appendChild(body);
|
|
answerEl.appendChild(row);
|
|
});
|
|
|
|
/* -------- build the keyboard: measured key geometry -------- */
|
|
var keysEl = document.getElementById("acr-keys");
|
|
var ROWS = [
|
|
{ top: 83, left: 13, letters: "qwertyuiop" },
|
|
{ top: 173, left: 48, letters: "asdfghjkl" },
|
|
{ top: 263, left: 123, letters: "zxcvbnm" },
|
|
];
|
|
ROWS.forEach(function (row) {
|
|
row.letters.split("").forEach(function (ch, i) {
|
|
var k = document.createElement("div");
|
|
k.className = "acr-key";
|
|
k.style.left = row.left + i * 73.7 + "px";
|
|
k.style.top = row.top + "px";
|
|
k.style.width = "62px";
|
|
k.textContent = ch;
|
|
keysEl.appendChild(k);
|
|
});
|
|
});
|
|
[
|
|
{
|
|
left: 13,
|
|
top: 263,
|
|
width: 97,
|
|
cls: " acr-gkey",
|
|
html: '<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="1.6" stroke-linejoin="round"><path d="M12 4 L4.5 12 H8.5 V17 H15.5 V12 H19.5 Z"/></svg>',
|
|
},
|
|
{
|
|
left: 640,
|
|
top: 263,
|
|
width: 98,
|
|
cls: " acr-gkey",
|
|
html: '<svg width="31" height="24" viewBox="0 0 28 20" fill="none" stroke="#000" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"><path d="M9 2 H26 V18 H9 L1.5 10 Z"/><path d="M13 6.5 L20 13.5 M20 6.5 L13 13.5"/></svg>',
|
|
},
|
|
{ left: 13, top: 353, width: 97, cls: " acr-gkey acr-num", html: "123" },
|
|
{
|
|
left: 118,
|
|
top: 353,
|
|
width: 86,
|
|
cls: "",
|
|
html: '<svg width="27" height="27" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="1.4" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><circle cx="9" cy="10" r="0.6" fill="#000"/><circle cx="15" cy="10" r="0.6" fill="#000"/><path d="M8 14.5 C9.5 16.5 14.5 16.5 16 14.5"/></svg>',
|
|
},
|
|
{ left: 217, top: 353, width: 389, cls: " acr-spc", html: "space" },
|
|
{
|
|
left: 614,
|
|
top: 353,
|
|
width: 124,
|
|
cls: " acr-gkey",
|
|
html: '<svg width="27" height="27" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M19 5 V11 H6"/><path d="M9.5 7.5 L6 11 L9.5 14.5"/></svg>',
|
|
},
|
|
].forEach(function (spec) {
|
|
var k = document.createElement("div");
|
|
k.className = "acr-key" + spec.cls;
|
|
k.style.left = spec.left + "px";
|
|
k.style.top = spec.top + "px";
|
|
k.style.width = spec.width + "px";
|
|
k.innerHTML = spec.html;
|
|
keysEl.appendChild(k);
|
|
});
|
|
|
|
/* -------- timeline -------- */
|
|
window.__timelines = window.__timelines || {};
|
|
var tl = gsap.timeline({ paused: true });
|
|
|
|
/* keystroke and token rhythms, measured from the reference and cycled */
|
|
var KEY_GAPS = [
|
|
0.1, 0.033, 0.2, 0.133, 0.2, 0.033, 0.067, 0.033, 0.1, 0.067, 0.033, 0.067, 0.067, 0.067,
|
|
0.133, 0.067, 0.033, 0.067, 0.1, 0.1, 0.067, 0.133, 0.1, 0.033, 0.1, 0.033, 0.167, 0.033,
|
|
0.1, 0.1, 0.067, 0.133, 0.033, 0.067, 0.1, 0.033, 0.1, 0.067,
|
|
];
|
|
var WORD_GAPS = [
|
|
0.167, 0.1, 0.0, 0.367, 0.0, 0.167, 0.133, 0.1, 0.1, 0.133, 0.167, 0.133, 0.0, 0.233,
|
|
0.167, 0.1, 0.0, 0.133, 0.133, 0.1, 0.167, 0.133, 0.1, 0.133, 0.167, 0.1, 0.133, 0.1,
|
|
0.133,
|
|
];
|
|
|
|
/* keyboard rises, composer rides it (verbatim anchors) */
|
|
tl.fromTo("#acr-keyboard", { y: 482 }, { y: 0, duration: 0.2667, ease: "power2.out" }, 0.2);
|
|
tl.fromTo(
|
|
"#acr-composer",
|
|
{ y: 0 },
|
|
{ y: -497, duration: 0.2667, ease: "power2.out" },
|
|
0.2,
|
|
);
|
|
|
|
/* typing: two characters land on the first beat, then the measured gaps */
|
|
tl.set("#acr-ctext", { textContent: "", autoAlpha: 0 }, 0.001);
|
|
var TYPE_START = 0.8637;
|
|
tl.set("#acr-ctext", { autoAlpha: 1 }, TYPE_START);
|
|
tl.set("#acr-ph", { autoAlpha: 0 }, TYPE_START);
|
|
tl.set("#acr-btn-send-grey", { autoAlpha: 0 }, TYPE_START);
|
|
tl.set("#acr-btn-send-black", { autoAlpha: 1 }, TYPE_START);
|
|
var t = TYPE_START;
|
|
for (var n = 2; n <= userMessage.length; n += 1) {
|
|
tl.set("#acr-ctext", { textContent: userMessage.slice(0, n) }, t);
|
|
t += KEY_GAPS[(n - 2) % KEY_GAPS.length];
|
|
}
|
|
var typeEnd = t;
|
|
/* caret blinks on a steady half-second while typing */
|
|
tl.set("#acr-caret", { autoAlpha: 0 }, 0.001);
|
|
for (var c = TYPE_START, on = true; c < typeEnd; c += 0.5333, on = !on) {
|
|
tl.set("#acr-caret", { autoAlpha: on ? 1 : 0 }, c);
|
|
}
|
|
|
|
/* send: bubble pops, keyboard drops, stream begins */
|
|
var SEND = typeEnd + 0.1667;
|
|
tl.set("#acr-caret", { autoAlpha: 0 }, SEND);
|
|
tl.set("#acr-ctext", { textContent: "" }, SEND);
|
|
tl.set("#acr-ph", { autoAlpha: 1 }, SEND);
|
|
tl.set("#acr-btn-send-black", { autoAlpha: 0 }, SEND);
|
|
tl.set("#acr-btn-stop", { autoAlpha: 1 }, SEND);
|
|
tl.fromTo(
|
|
"#acr-bubble",
|
|
{ autoAlpha: 0 },
|
|
{ autoAlpha: 1, duration: 0.1667, ease: "power1.out" },
|
|
SEND - 0.03,
|
|
);
|
|
tl.set("#acr-hicons-a", { autoAlpha: 0 }, SEND);
|
|
tl.set("#acr-hicons-b", { autoAlpha: 1 }, SEND);
|
|
tl.to("#acr-keyboard", { y: 482, duration: 0.2667, ease: "power1.inOut" }, SEND - 0.03);
|
|
tl.to("#acr-composer", { y: 0, duration: 0.2667, ease: "power1.inOut" }, SEND - 0.03);
|
|
|
|
/* thinking dot: the measured alpha ramp, then it yields to the words */
|
|
var DOT_RAMP = [
|
|
0.488, 0.53, 0.53, 0.56, 0.608, 0.687, 0.729, 0.771, 0.813, 0.867, 0.904, 0.988, 1,
|
|
];
|
|
var dotStart = SEND + 0.4333;
|
|
tl.set("#acr-dot", { autoAlpha: 0 }, 0.001);
|
|
DOT_RAMP.forEach(function (a, i) {
|
|
tl.set("#acr-dot", { autoAlpha: a }, dotStart + i * 0.0333);
|
|
});
|
|
tl.set("#acr-dot", { autoAlpha: 0 }, dotStart + 0.5667);
|
|
|
|
/* the stream: measured token rhythm, fresh grey inking to full colour */
|
|
var w = dotStart + 0.6333;
|
|
wordEls.forEach(function (el, i) {
|
|
if (i > 0) w += WORD_GAPS[i % WORD_GAPS.length];
|
|
tl.set(el, { opacity: 1 }, w);
|
|
tl.fromTo(
|
|
el,
|
|
{ color: "#767676" },
|
|
{ color: "#141414", duration: 0.2667, ease: "none" },
|
|
w,
|
|
);
|
|
});
|
|
|
|
/* stream ends: stop button returns to send */
|
|
tl.set("#acr-btn-stop", { autoAlpha: 0 }, w + 0.4667);
|
|
tl.set("#acr-btn-send-black", { autoAlpha: 1 }, w + 0.4667);
|
|
|
|
/* end card */
|
|
tl.set("#acr-endcard-inner", { opacity: 0 }, 0.001);
|
|
tl.fromTo(
|
|
"#acr-endcard-inner",
|
|
{ opacity: 0 },
|
|
{ opacity: 1, duration: 0.3167, ease: "none" },
|
|
15.8333,
|
|
);
|
|
|
|
window.__timelines["ai-chat-reveal"] = tl;
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|