feat(registry): seed blocks batch — social overlays, data viz, showcases (#269)

## What

Add 11 blocks from the Hyperframe Template Structure catalog, bringing the registry to 25 total items.

### Social overlays
| Block | Dimensions | Duration | Description |
|-------|-----------|----------|-------------|
| `instagram-follow` | 1080×1920 | 4.5s | Instagram follow overlay with profile card |
| `tiktok-follow` | 1080×1920 | 4.5s | TikTok follow overlay with profile card |
| `yt-lower-third` | 1920×1080 | 4.5s | YouTube subscribe lower third |
| `x-post` | 1920×1080 | 5s | X/Twitter post card with engagement |
| `reddit-post` | 1920×1080 | 5s | Reddit post card with upvotes |
| `spotify-card` | 1080×1920 | 5s | Spotify now-playing card |
| `macos-notification` | 1920×1080 | 5s | macOS notification banner |

### Data & visualization
| Block | Duration | Description |
|-------|----------|-------------|
| `ascii-dashboard` | 10s | Retro terminal-style data viz |
| `ascii-lightning` | 9s | ASCII art lightning bolt animation |

### Showcases
| Block | Duration | Description |
|-------|----------|-------------|
| `app-showcase` | 5.5s | Floating smartphone screens |
| `ui-3d-reveal` | 13s | Perspective 3D UI reveal |

## Why

Phase D content accumulation. The registry pipeline (PRs 6-10) is in place — this PR exercises it at scale.

## How

- Extracted from zip files in the Hyperframe Template Structure Notion doc
- Social overlays: single-file standalone HTML, copied directly
- Multi-file blocks (ascii-*, app-showcase, ui-3d-reveal): converted `<template>` sub-compositions to standalone HTML with proper `<!doctype>` wrappers
- All previews (PNG + MP4) rendered locally via `generate-catalog-previews.ts`
- Catalog MDX pages regenerated via `generate-catalog-pages.ts`
- `docs.json` updated with new catalog entries

## Test plan

- [x] All 11 blocks render to PNG + MP4 without errors
- [x] Catalog pages generated for all 17 items (14 blocks + 3 components)
- [x] `registry-item.json` files have correct dimensions, duration, tags
- [x] `oxfmt --check` passes on all files
This commit is contained in:
James Russo
2026-04-14 16:29:25 -07:00
committed by GitHub
parent 4bde66f532
commit d37d738be9
61 changed files with 8007 additions and 1 deletions
+44
View File
@@ -0,0 +1,44 @@
---
title: "App Showcase"
description: "Fitness app product showcase with three floating smartphone screens"
---
# App Showcase
Fitness app product showcase with three floating smartphone screens
`showcase` `app` `3d`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/app-showcase.mp4" poster="/images/catalog/blocks/app-showcase.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add app-showcase
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1920×1080 |
| Duration | 5.5s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `app-showcase.html` | `compositions/app-showcase.html` | hyperframes:composition |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="app-showcase" data-composition-src="compositions/app-showcase.html" data-start="0" data-duration="5.5" data-track-index="1" data-width="1920" data-height="1080"></div>
```
+44
View File
@@ -0,0 +1,44 @@
---
title: "ASCII Dashboard"
description: "Retro terminal-style data visualization with charts and metrics"
---
# ASCII Dashboard
Retro terminal-style data visualization with charts and metrics
`data` `ascii` `dashboard`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/ascii-dashboard.mp4" poster="/images/catalog/blocks/ascii-dashboard.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add ascii-dashboard
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1920×1080 |
| Duration | 10s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `ascii-dashboard.html` | `compositions/ascii-dashboard.html` | hyperframes:composition |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="ascii-dashboard" data-composition-src="compositions/ascii-dashboard.html" data-start="0" data-duration="10" data-track-index="1" data-width="1920" data-height="1080"></div>
```
+44
View File
@@ -0,0 +1,44 @@
---
title: "ASCII Lightning"
description: "Pure ASCII art lightning bolt animation on dark background"
---
# ASCII Lightning
Pure ASCII art lightning bolt animation on dark background
`title` `ascii` `animation`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/ascii-lightning.mp4" poster="/images/catalog/blocks/ascii-lightning.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add ascii-lightning
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1920×1080 |
| Duration | 9s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `ascii-lightning.html` | `compositions/ascii-lightning.html` | hyperframes:composition |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="ascii-lightning" data-composition-src="compositions/ascii-lightning.html" data-start="0" data-duration="9" data-track-index="1" data-width="1920" data-height="1080"></div>
```
+45
View File
@@ -0,0 +1,45 @@
---
title: "Instagram Follow"
description: "Animated Instagram follow overlay with profile card and follow button"
---
# Instagram Follow
Animated Instagram follow overlay with profile card and follow button
`social` `overlay` `instagram`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/instagram-follow.mp4" poster="/images/catalog/blocks/instagram-follow.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add instagram-follow
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1080×1920 |
| Duration | 4.5s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `instagram-follow.html` | `compositions/instagram-follow.html` | hyperframes:composition |
| `assets/avatar.jpg` | `assets/avatar.jpg` | hyperframes:asset |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="instagram-follow" data-composition-src="compositions/instagram-follow.html" data-start="0" data-duration="4.5" data-track-index="1" data-width="1080" data-height="1920"></div>
```
@@ -0,0 +1,44 @@
---
title: "macOS Notification"
description: "Animated macOS-style notification banner with app icon and message"
---
# macOS Notification
Animated macOS-style notification banner with app icon and message
`social` `overlay` `notification`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/macos-notification.mp4" poster="/images/catalog/blocks/macos-notification.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add macos-notification
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1920×1080 |
| Duration | 5s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `macos-notification.html` | `compositions/macos-notification.html` | hyperframes:composition |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="macos-notification" data-composition-src="compositions/macos-notification.html" data-start="0" data-duration="5" data-track-index="1" data-width="1920" data-height="1080"></div>
```
+44
View File
@@ -0,0 +1,44 @@
---
title: "Reddit Post Card"
description: "Animated Reddit post card overlay with upvotes and comments"
---
# Reddit Post Card
Animated Reddit post card overlay with upvotes and comments
`social` `overlay` `reddit`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/reddit-post.mp4" poster="/images/catalog/blocks/reddit-post.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add reddit-post
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1920×1080 |
| Duration | 5s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `reddit-post.html` | `compositions/reddit-post.html` | hyperframes:composition |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="reddit-post" data-composition-src="compositions/reddit-post.html" data-start="0" data-duration="5" data-track-index="1" data-width="1920" data-height="1080"></div>
```
+44
View File
@@ -0,0 +1,44 @@
---
title: "Spotify Now Playing"
description: "Animated Spotify now-playing card with album art and progress bar"
---
# Spotify Now Playing
Animated Spotify now-playing card with album art and progress bar
`social` `overlay` `spotify`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/spotify-card.mp4" poster="/images/catalog/blocks/spotify-card.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add spotify-card
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1080×1920 |
| Duration | 5s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `spotify-card.html` | `compositions/spotify-card.html` | hyperframes:composition |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="spotify-card" data-composition-src="compositions/spotify-card.html" data-start="0" data-duration="5" data-track-index="1" data-width="1080" data-height="1920"></div>
```
+45
View File
@@ -0,0 +1,45 @@
---
title: "TikTok Follow"
description: "Animated TikTok follow overlay with profile card and follow button"
---
# TikTok Follow
Animated TikTok follow overlay with profile card and follow button
`social` `overlay` `tiktok`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/tiktok-follow.mp4" poster="/images/catalog/blocks/tiktok-follow.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add tiktok-follow
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1080×1920 |
| Duration | 4.5s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `tiktok-follow.html` | `compositions/tiktok-follow.html` | hyperframes:composition |
| `assets/avatar.jpg` | `assets/avatar.jpg` | hyperframes:asset |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="tiktok-follow" data-composition-src="compositions/tiktok-follow.html" data-start="0" data-duration="4.5" data-track-index="1" data-width="1080" data-height="1920"></div>
```
+44
View File
@@ -0,0 +1,44 @@
---
title: "3D UI Reveal"
description: "Perspective 3D reveal animation for UI elements"
---
# 3D UI Reveal
Perspective 3D reveal animation for UI elements
`showcase` `3d` `reveal`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/ui-3d-reveal.mp4" poster="/images/catalog/blocks/ui-3d-reveal.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add ui-3d-reveal
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1920×1080 |
| Duration | 13s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `ui-3d-reveal.html` | `compositions/ui-3d-reveal.html` | hyperframes:composition |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="ui-3d-reveal" data-composition-src="compositions/ui-3d-reveal.html" data-start="0" data-duration="13" data-track-index="1" data-width="1920" data-height="1080"></div>
```
+44
View File
@@ -0,0 +1,44 @@
---
title: "X Post Card"
description: "Animated X/Twitter post card overlay with engagement metrics"
---
# X Post Card
Animated X/Twitter post card overlay with engagement metrics
`social` `overlay` `twitter`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/x-post.mp4" poster="/images/catalog/blocks/x-post.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add x-post
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1920×1080 |
| Duration | 5s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `x-post.html` | `compositions/x-post.html` | hyperframes:composition |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="x-post" data-composition-src="compositions/x-post.html" data-start="0" data-duration="5" data-track-index="1" data-width="1920" data-height="1080"></div>
```
+45
View File
@@ -0,0 +1,45 @@
---
title: "YouTube Lower Third"
description: "Animated YouTube subscribe lower third with avatar and channel info"
---
# YouTube Lower Third
Animated YouTube subscribe lower third with avatar and channel info
`social` `overlay` `youtube`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="/images/catalog/blocks/yt-lower-third.mp4" poster="/images/catalog/blocks/yt-lower-third.png" muted loop playsInline autoPlay />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add yt-lower-third
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Block |
| Dimensions | 1920×1080 |
| Duration | 4.5s |
## Files
| File | Target | Type |
| --- | --- | --- |
| `yt-lower-third.html` | `compositions/yt-lower-third.html` | hyperframes:composition |
| `assets/avatar.jpg` | `assets/avatar.jpg` | hyperframes:asset |
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="yt-lower-third" data-composition-src="compositions/yt-lower-third.html" data-start="0" data-duration="4.5" data-track-index="1" data-width="1920" data-height="1080"></div>
```
+12 -1
View File
@@ -99,9 +99,20 @@
{
"group": "Blocks",
"pages": [
"catalog/blocks/app-showcase",
"catalog/blocks/ascii-dashboard",
"catalog/blocks/ascii-lightning",
"catalog/blocks/data-chart",
"catalog/blocks/flowchart",
"catalog/blocks/logo-outro"
"catalog/blocks/instagram-follow",
"catalog/blocks/logo-outro",
"catalog/blocks/macos-notification",
"catalog/blocks/reddit-post",
"catalog/blocks/spotify-card",
"catalog/blocks/tiktok-follow",
"catalog/blocks/ui-3d-reveal",
"catalog/blocks/x-post",
"catalog/blocks/yt-lower-third"
]
},
{
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 992 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

+143
View File
@@ -1,4 +1,43 @@
[
{
"name": "app-showcase",
"type": "block",
"title": "App Showcase",
"description": "Fitness app product showcase with three floating smartphone screens",
"tags": [
"showcase",
"app",
"3d"
],
"href": "/catalog/blocks/app-showcase",
"preview": "/images/catalog/blocks/app-showcase.png"
},
{
"name": "ascii-dashboard",
"type": "block",
"title": "ASCII Dashboard",
"description": "Retro terminal-style data visualization with charts and metrics",
"tags": [
"data",
"ascii",
"dashboard"
],
"href": "/catalog/blocks/ascii-dashboard",
"preview": "/images/catalog/blocks/ascii-dashboard.png"
},
{
"name": "ascii-lightning",
"type": "block",
"title": "ASCII Lightning",
"description": "Pure ASCII art lightning bolt animation on dark background",
"tags": [
"title",
"ascii",
"animation"
],
"href": "/catalog/blocks/ascii-lightning",
"preview": "/images/catalog/blocks/ascii-lightning.png"
},
{
"name": "data-chart",
"type": "block",
@@ -53,6 +92,19 @@
"href": "/catalog/components/grid-pixelate-wipe",
"preview": "/images/catalog/components/grid-pixelate-wipe.png"
},
{
"name": "instagram-follow",
"type": "block",
"title": "Instagram Follow",
"description": "Animated Instagram follow overlay with profile card and follow button",
"tags": [
"social",
"overlay",
"instagram"
],
"href": "/catalog/blocks/instagram-follow",
"preview": "/images/catalog/blocks/instagram-follow.png"
},
{
"name": "logo-outro",
"type": "block",
@@ -66,6 +118,32 @@
"href": "/catalog/blocks/logo-outro",
"preview": "/images/catalog/blocks/logo-outro.png"
},
{
"name": "macos-notification",
"type": "block",
"title": "macOS Notification",
"description": "Animated macOS-style notification banner with app icon and message",
"tags": [
"social",
"overlay",
"notification"
],
"href": "/catalog/blocks/macos-notification",
"preview": "/images/catalog/blocks/macos-notification.png"
},
{
"name": "reddit-post",
"type": "block",
"title": "Reddit Post Card",
"description": "Animated Reddit post card overlay with upvotes and comments",
"tags": [
"social",
"overlay",
"reddit"
],
"href": "/catalog/blocks/reddit-post",
"preview": "/images/catalog/blocks/reddit-post.png"
},
{
"name": "shimmer-sweep",
"type": "component",
@@ -79,5 +157,70 @@
],
"href": "/catalog/components/shimmer-sweep",
"preview": "/images/catalog/components/shimmer-sweep.png"
},
{
"name": "spotify-card",
"type": "block",
"title": "Spotify Now Playing",
"description": "Animated Spotify now-playing card with album art and progress bar",
"tags": [
"social",
"overlay",
"spotify"
],
"href": "/catalog/blocks/spotify-card",
"preview": "/images/catalog/blocks/spotify-card.png"
},
{
"name": "tiktok-follow",
"type": "block",
"title": "TikTok Follow",
"description": "Animated TikTok follow overlay with profile card and follow button",
"tags": [
"social",
"overlay",
"tiktok"
],
"href": "/catalog/blocks/tiktok-follow",
"preview": "/images/catalog/blocks/tiktok-follow.png"
},
{
"name": "ui-3d-reveal",
"type": "block",
"title": "3D UI Reveal",
"description": "Perspective 3D reveal animation for UI elements",
"tags": [
"showcase",
"3d",
"reveal"
],
"href": "/catalog/blocks/ui-3d-reveal",
"preview": "/images/catalog/blocks/ui-3d-reveal.png"
},
{
"name": "x-post",
"type": "block",
"title": "X Post Card",
"description": "Animated X/Twitter post card overlay with engagement metrics",
"tags": [
"social",
"overlay",
"twitter"
],
"href": "/catalog/blocks/x-post",
"preview": "/images/catalog/blocks/x-post.png"
},
{
"name": "yt-lower-third",
"type": "block",
"title": "YouTube Lower Third",
"description": "Animated YouTube subscribe lower third with avatar and channel info",
"tags": [
"social",
"overlay",
"youtube"
],
"href": "/catalog/blocks/yt-lower-third",
"preview": "/images/catalog/blocks/yt-lower-third.png"
}
]
@@ -0,0 +1,946 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>app-showcase</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background-color: #0a0a0f;
}
</style>
</head>
<body>
<div
data-composition-id="scene-3-phones"
data-width="1920"
data-height="1080"
data-duration="5.5"
>
<!-- Background -->
<div class="s3-bg" id="s3Bg"></div>
<div class="s3-bg-glow" id="s3BgGlow"></div>
<!-- Stage wrapper for ambient scale -->
<div class="s3-stage" id="s3Stage">
<!-- ═══════════════════════════════════════
LEFT PHONE — Lifestyle / Motivational
═══════════════════════════════════════ -->
<div class="s3-phone s3-phone-left" id="s3PhoneLeft">
<div class="s3-bezel">
<div class="s3-notch"></div>
<div class="s3-screen s3-screen-left">
<!-- Abstract exercise shapes -->
<div class="left-shapes">
<svg viewBox="0 0 304 400" xmlns="http://www.w3.org/2000/svg" fill="none">
<!-- Abstract running figure — geometric -->
<circle cx="152" cy="80" r="28" fill="#271f15" opacity="0.15" />
<line
x1="152"
y1="108"
x2="152"
y2="200"
stroke="#271f15"
stroke-width="6"
stroke-linecap="round"
opacity="0.15"
/>
<line
x1="152"
y1="145"
x2="115"
y2="175"
stroke="#271f15"
stroke-width="6"
stroke-linecap="round"
opacity="0.15"
/>
<line
x1="152"
y1="145"
x2="195"
y2="165"
stroke="#271f15"
stroke-width="6"
stroke-linecap="round"
opacity="0.15"
/>
<line
x1="152"
y1="200"
x2="120"
y2="260"
stroke="#271f15"
stroke-width="6"
stroke-linecap="round"
opacity="0.15"
/>
<line
x1="152"
y1="200"
x2="190"
y2="255"
stroke="#271f15"
stroke-width="6"
stroke-linecap="round"
opacity="0.15"
/>
<!-- Decorative arcs -->
<path
d="M60 300 Q152 240 244 300"
stroke="#e4fa72"
stroke-width="3"
fill="none"
opacity="0.5"
/>
<path
d="M40 330 Q152 260 264 330"
stroke="#e4fa72"
stroke-width="2"
fill="none"
opacity="0.3"
/>
</svg>
</div>
<div class="left-bottom">
<div class="left-tagline" id="s3Tagline">Unleash Full Potential</div>
<div class="left-cta">START NOW</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
CENTER PHONE — Dashboard (Hero)
═══════════════════════════════════════ -->
<div class="s3-phone s3-phone-center" id="s3PhoneCenter">
<div class="s3-bezel">
<div class="s3-notch"></div>
<div class="s3-screen s3-screen-center">
<!-- Header -->
<div class="center-header">
<div class="center-avatar">JM</div>
<div class="center-user-info">
<div class="center-name">James Medrano</div>
<div class="center-subtitle">Premium Member</div>
</div>
<div class="center-bell">
<svg viewBox="0 0 20 20" width="20" height="20" fill="none">
<path
d="M10 2a5 5 0 00-5 5v3l-1.5 2.5h13L15 10V7a5 5 0 00-5-5z"
stroke="#7c857c"
stroke-width="1.4"
stroke-linejoin="round"
/>
<path
d="M8 16.5a2 2 0 004 0"
stroke="#7c857c"
stroke-width="1.4"
stroke-linecap="round"
/>
<circle cx="14" cy="5" r="3" fill="#e4fa72" />
</svg>
</div>
</div>
<!-- Weekly Goal -->
<div class="center-goal">
<div class="goal-label">Weekly Goal</div>
<div class="goal-ring-wrap">
<svg class="goal-ring" viewBox="0 0 140 140">
<circle
class="ring-track"
cx="70"
cy="70"
r="52"
fill="none"
stroke="#333"
stroke-width="10"
/>
<circle
class="ring-fill"
id="s3Ring"
cx="70"
cy="70"
r="52"
fill="none"
stroke="#e4fa72"
stroke-width="10"
stroke-linecap="round"
transform="rotate(-90 70 70)"
/>
</svg>
<div class="goal-count">
<span class="goal-num">2</span>
<span class="goal-sep">/</span>
<span class="goal-den">3</span>
</div>
</div>
<div class="goal-days">
<span class="day done">M</span>
<span class="day done">T</span>
<span class="day">W</span>
<span class="day">T</span>
<span class="day">F</span>
<span class="day">S</span>
<span class="day">S</span>
</div>
</div>
<!-- Your Progress -->
<div class="center-progress">
<div class="progress-header">
<span class="progress-title">Your Progress</span>
<span class="progress-link">See all</span>
</div>
<div class="progress-bars">
<div class="bar-row">
<div class="bar-icon">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none">
<path
d="M8 2l1.5 3H13l-2.5 2 1 3L8 8.5 4.5 10l1-3L3 5h3.5z"
fill="#e4fa72"
/>
</svg>
</div>
<div class="bar-info">
<span class="bar-name">Running</span>
<span class="bar-val">5.2 km</span>
</div>
<div class="bar-track"><div class="bar-value bar-v1" id="s3Bar1"></div></div>
</div>
<div class="bar-row">
<div class="bar-icon">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none">
<circle cx="8" cy="8" r="5" stroke="#e4fa72" stroke-width="2" />
</svg>
</div>
<div class="bar-info">
<span class="bar-name">Cycling</span>
<span class="bar-val">12.8 km</span>
</div>
<div class="bar-track"><div class="bar-value bar-v2" id="s3Bar2"></div></div>
</div>
<div class="bar-row">
<div class="bar-icon">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none">
<rect
x="3"
y="6"
width="10"
height="6"
rx="1"
stroke="#e4fa72"
stroke-width="1.5"
/>
<path d="M6 6V4a2 2 0 014 0v2" stroke="#e4fa72" stroke-width="1.5" />
</svg>
</div>
<div class="bar-info">
<span class="bar-name">Strength</span>
<span class="bar-val">45 min</span>
</div>
<div class="bar-track"><div class="bar-value bar-v3" id="s3Bar3"></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════
RIGHT PHONE — Calories Data
═══════════════════════════════════════ -->
<div class="s3-phone s3-phone-right" id="s3PhoneRight">
<div class="s3-bezel">
<div class="s3-notch"></div>
<div class="s3-screen s3-screen-right">
<div class="right-header">
<div class="right-title">Burned Calories</div>
<div class="right-value">740 <span class="right-unit">kcal</span></div>
</div>
<div class="right-graph-wrap">
<svg class="right-graph" viewBox="0 0 260 130" xmlns="http://www.w3.org/2000/svg">
<!-- Grid lines -->
<line
x1="10"
y1="25"
x2="250"
y2="25"
stroke="#d9e0bc"
stroke-width="0.5"
opacity="0.5"
/>
<line
x1="10"
y1="55"
x2="250"
y2="55"
stroke="#d9e0bc"
stroke-width="0.5"
opacity="0.5"
/>
<line
x1="10"
y1="85"
x2="250"
y2="85"
stroke="#d9e0bc"
stroke-width="0.5"
opacity="0.5"
/>
<line
x1="10"
y1="115"
x2="250"
y2="115"
stroke="#d9e0bc"
stroke-width="0.5"
opacity="0.5"
/>
<!-- Area fill -->
<path
class="graph-area"
id="s3GraphArea"
d="M10,105 L50,80 90,90 130,45 170,60 210,25 250,35 250,115 10,115 Z"
fill="#e4fa72"
opacity="0.15"
/>
<!-- Line -->
<polyline
class="graph-line"
id="s3GraphLine"
points="10,105 50,80 90,90 130,45 170,60 210,25 250,35"
fill="none"
stroke="#e4fa72"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<!-- Dots -->
<circle class="graph-dot" cx="10" cy="105" r="3.5" fill="#e4fa72" />
<circle class="graph-dot" cx="50" cy="80" r="3.5" fill="#e4fa72" />
<circle class="graph-dot" cx="90" cy="90" r="3.5" fill="#e4fa72" />
<circle class="graph-dot" cx="130" cy="45" r="3.5" fill="#e4fa72" />
<circle class="graph-dot" cx="170" cy="60" r="3.5" fill="#e4fa72" />
<circle class="graph-dot" cx="210" cy="25" r="3.5" fill="#e4fa72" />
<circle class="graph-dot" cx="250" cy="35" r="3.5" fill="#e4fa72" />
</svg>
<div class="right-labels">
<span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span
><span>Sat</span><span>Sun</span>
</div>
</div>
<div class="right-stats">
<div class="stat-pill">
<span class="stat-dot" style="background: #e4fa72"></span>
<span class="stat-label">Active</span>
<span class="stat-num">540</span>
</div>
<div class="stat-pill">
<span class="stat-dot" style="background: #7c857c"></span>
<span class="stat-label">Rest</span>
<span class="stat-num">200</span>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
@import url("https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap");
[data-composition-id="scene-3-phones"] {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
font-family: "DM Sans", sans-serif;
color: #271f15;
}
/* ── Background ── */
[data-composition-id="scene-3-phones"] .s3-bg {
position: absolute;
inset: 0;
background: linear-gradient(160deg, #f1f2ec 0%, #e8ecda 40%, #f1f2ec 100%);
}
[data-composition-id="scene-3-phones"] .s3-bg-glow {
position: absolute;
width: 900px;
height: 900px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: radial-gradient(circle, rgba(228, 250, 114, 0.18) 0%, transparent 70%);
border-radius: 50%;
}
/* ── Stage ── */
[data-composition-id="scene-3-phones"] .s3-stage {
position: absolute;
inset: 0;
transform-origin: center center;
}
/* ── Phones ── */
[data-composition-id="scene-3-phones"] .s3-phone {
position: absolute;
width: 320px;
height: 650px;
top: 215px;
left: 800px;
}
[data-composition-id="scene-3-phones"] .s3-phone-center {
z-index: 30;
}
[data-composition-id="scene-3-phones"] .s3-phone-left {
z-index: 20;
}
[data-composition-id="scene-3-phones"] .s3-phone-right {
z-index: 20;
}
/* ── Bezel ── */
[data-composition-id="scene-3-phones"] .s3-bezel {
width: 100%;
height: 100%;
border-radius: 42px;
background: #271f15;
padding: 8px;
box-shadow:
0 24px 64px rgba(39, 31, 21, 0.35),
0 4px 16px rgba(39, 31, 21, 0.15);
position: relative;
overflow: hidden;
}
[data-composition-id="scene-3-phones"] .s3-notch {
position: absolute;
top: 8px;
left: 50%;
transform: translateX(-50%);
width: 100px;
height: 26px;
background: #271f15;
border-radius: 0 0 18px 18px;
z-index: 5;
}
/* ── Screens ── */
[data-composition-id="scene-3-phones"] .s3-screen {
width: 100%;
height: 100%;
border-radius: 36px;
overflow: hidden;
position: relative;
}
/* Left screen */
[data-composition-id="scene-3-phones"] .s3-screen-left {
background: linear-gradient(160deg, #d9e0bc 0%, #c8d4a0 50%, #b8c78a 100%);
display: flex;
flex-direction: column;
justify-content: flex-end;
}
[data-composition-id="scene-3-phones"] .left-shapes {
position: absolute;
top: 40px;
left: 0;
width: 100%;
height: 400px;
display: flex;
align-items: center;
justify-content: center;
}
[data-composition-id="scene-3-phones"] .left-shapes svg {
width: 200px;
height: 280px;
}
[data-composition-id="scene-3-phones"] .left-bottom {
padding: 0 28px 40px;
position: relative;
z-index: 2;
}
[data-composition-id="scene-3-phones"] .left-tagline {
font-size: 28px;
font-weight: 700;
color: #271f15;
line-height: 1.15;
margin-bottom: 16px;
letter-spacing: -0.02em;
}
[data-composition-id="scene-3-phones"] .left-cta {
display: inline-block;
background: #271f15;
color: #e4fa72;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.08em;
padding: 10px 28px;
border-radius: 24px;
}
/* Center screen */
[data-composition-id="scene-3-phones"] .s3-screen-center {
background: #1a1a1a;
padding: 42px 22px 22px;
display: flex;
flex-direction: column;
gap: 18px;
}
[data-composition-id="scene-3-phones"] .center-header {
display: flex;
align-items: center;
gap: 10px;
}
[data-composition-id="scene-3-phones"] .center-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #e4fa72, #d9e0bc);
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
font-weight: 700;
color: #271f15;
flex-shrink: 0;
}
[data-composition-id="scene-3-phones"] .center-user-info {
flex: 1;
}
[data-composition-id="scene-3-phones"] .center-name {
font-size: 18px;
font-weight: 700;
color: #f1f2ec;
line-height: 1.2;
}
[data-composition-id="scene-3-phones"] .center-subtitle {
font-size: 12px;
font-weight: 400;
color: #7c857c;
margin-top: 1px;
}
[data-composition-id="scene-3-phones"] .center-bell {
flex-shrink: 0;
}
/* Weekly Goal */
[data-composition-id="scene-3-phones"] .center-goal {
background: #242424;
border-radius: 20px;
padding: 18px 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
[data-composition-id="scene-3-phones"] .goal-label {
font-size: 15px;
font-weight: 500;
color: #7c857c;
align-self: flex-start;
}
[data-composition-id="scene-3-phones"] .goal-ring-wrap {
position: relative;
width: 130px;
height: 130px;
}
[data-composition-id="scene-3-phones"] .goal-ring {
width: 100%;
height: 100%;
}
[data-composition-id="scene-3-phones"] .ring-track {
opacity: 0.3;
}
[data-composition-id="scene-3-phones"] .goal-count {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
}
[data-composition-id="scene-3-phones"] .goal-num {
font-size: 36px;
font-weight: 700;
color: #e4fa72;
}
[data-composition-id="scene-3-phones"] .goal-sep {
font-size: 24px;
font-weight: 400;
color: #7c857c;
margin: 0 2px;
}
[data-composition-id="scene-3-phones"] .goal-den {
font-size: 24px;
font-weight: 400;
color: #7c857c;
}
[data-composition-id="scene-3-phones"] .goal-days {
display: flex;
gap: 8px;
}
[data-composition-id="scene-3-phones"] .day {
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 500;
color: #7c857c;
background: #1a1a1a;
}
[data-composition-id="scene-3-phones"] .day.done {
background: #e4fa72;
color: #271f15;
font-weight: 700;
}
/* Progress section */
[data-composition-id="scene-3-phones"] .center-progress {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
[data-composition-id="scene-3-phones"] .progress-header {
display: flex;
justify-content: space-between;
align-items: center;
}
[data-composition-id="scene-3-phones"] .progress-title {
font-size: 15px;
font-weight: 500;
color: #f1f2ec;
}
[data-composition-id="scene-3-phones"] .progress-link {
font-size: 12px;
font-weight: 400;
color: #e4fa72;
}
[data-composition-id="scene-3-phones"] .progress-bars {
display: flex;
flex-direction: column;
gap: 12px;
}
[data-composition-id="scene-3-phones"] .bar-row {
display: flex;
align-items: center;
gap: 10px;
}
[data-composition-id="scene-3-phones"] .bar-icon {
width: 28px;
height: 28px;
border-radius: 8px;
background: #2a2a2a;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
[data-composition-id="scene-3-phones"] .bar-info {
width: 70px;
flex-shrink: 0;
}
[data-composition-id="scene-3-phones"] .bar-name {
display: block;
font-size: 12px;
font-weight: 500;
color: #f1f2ec;
line-height: 1.2;
}
[data-composition-id="scene-3-phones"] .bar-val {
display: block;
font-size: 10px;
font-weight: 400;
color: #7c857c;
}
[data-composition-id="scene-3-phones"] .bar-track {
flex: 1;
height: 8px;
background: #2a2a2a;
border-radius: 4px;
overflow: hidden;
}
[data-composition-id="scene-3-phones"] .bar-value {
height: 100%;
border-radius: 4px;
background: #e4fa72;
width: 0%;
}
/* Right screen */
[data-composition-id="scene-3-phones"] .s3-screen-right {
background: #f1f2ec;
padding: 42px 20px 24px;
display: flex;
flex-direction: column;
gap: 14px;
}
[data-composition-id="scene-3-phones"] .right-header {
display: flex;
flex-direction: column;
gap: 4px;
}
[data-composition-id="scene-3-phones"] .right-title {
font-size: 14px;
font-weight: 500;
color: #7c857c;
}
[data-composition-id="scene-3-phones"] .right-value {
font-size: 40px;
font-weight: 700;
color: #271f15;
line-height: 1;
letter-spacing: -0.03em;
}
[data-composition-id="scene-3-phones"] .right-unit {
font-size: 20px;
font-weight: 400;
color: #7c857c;
}
[data-composition-id="scene-3-phones"] .right-graph-wrap {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
[data-composition-id="scene-3-phones"] .right-graph {
width: 100%;
flex: 1;
}
[data-composition-id="scene-3-phones"] .right-labels {
display: flex;
justify-content: space-between;
padding: 0 6px;
font-size: 10px;
font-weight: 400;
color: #7c857c;
}
[data-composition-id="scene-3-phones"] .right-stats {
display: flex;
gap: 12px;
}
[data-composition-id="scene-3-phones"] .stat-pill {
display: flex;
align-items: center;
gap: 6px;
background: #e8ecda;
border-radius: 20px;
padding: 8px 14px;
}
[data-composition-id="scene-3-phones"] .stat-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
[data-composition-id="scene-3-phones"] .stat-label {
font-size: 12px;
font-weight: 400;
color: #7c857c;
}
[data-composition-id="scene-3-phones"] .stat-num {
font-size: 13px;
font-weight: 700;
color: #271f15;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
var S = '[data-composition-id="scene-3-phones"] ';
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// ── SVG setup ──
var graphLine = document.querySelector(S + "#s3GraphLine");
var graphLen = graphLine.getTotalLength();
gsap.set(graphLine, { strokeDasharray: graphLen, strokeDashoffset: graphLen });
var graphArea = document.querySelector(S + "#s3GraphArea");
gsap.set(graphArea, { opacity: 0 });
var graphDots = document.querySelectorAll(S + ".graph-dot");
gsap.set(graphDots, { opacity: 0, scale: 0 });
var CIRC = 2 * 3.14159265 * 52;
gsap.set(S + "#s3Ring", { strokeDasharray: CIRC, strokeDashoffset: CIRC });
// ── Initial states ──
gsap.set(S + ".s3-bg", { opacity: 0 });
gsap.set(S + ".s3-bg-glow", { opacity: 0 });
// Center phone: starts scaled down + hidden
gsap.set(S + "#s3PhoneCenter", { opacity: 0, scale: 0.85 });
// Side phones: start stacked behind center (same position), hidden
gsap.set(S + "#s3PhoneLeft", { opacity: 0, x: 0, rotation: 0 });
gsap.set(S + "#s3PhoneRight", { opacity: 0, x: 0, rotation: 0 });
gsap.set(S + ".bar-value", { width: "0%" });
gsap.set(S + "#s3Tagline", { opacity: 0, y: 20 });
gsap.set(S + ".left-cta", { opacity: 0, y: 12 });
// ════════════════════════════════════
// BUILD PHASE (02s)
// ════════════════════════════════════
// Background
tl.to(S + ".s3-bg", { opacity: 1, duration: 0.8, ease: "power1.out" }, 0);
tl.to(S + ".s3-bg-glow", { opacity: 1, duration: 1.2, ease: "power1.out" }, 0.2);
// Center phone — scales up first
tl.to(
S + "#s3PhoneCenter",
{
scale: 1,
opacity: 1,
duration: 0.7,
ease: "back.out(1.4)",
},
0.2,
);
// Side phones: opacity matches center (0.7s, back.out), fan starts at 0.1
tl.to(S + "#s3PhoneLeft", { opacity: 1, duration: 0.7, ease: "back.out(1.4)" }, 0.3);
tl.to(
S + "#s3PhoneLeft",
{
x: -340,
rotation: -12,
duration: 1.2,
ease: "expo.out",
},
0.3,
);
tl.to(S + "#s3PhoneRight", { opacity: 1, duration: 0.7, ease: "back.out(1.4)" }, 0.4);
tl.to(
S + "#s3PhoneRight",
{
x: 340,
rotation: 12,
duration: 1.2,
ease: "expo.out",
},
0.4,
);
// ════════════════════════════════════
// BREATHE PHASE (24s)
// ════════════════════════════════════
// Progress ring fills to 2/3
tl.to(
S + "#s3Ring",
{
strokeDashoffset: CIRC * (1 - 2 / 3),
duration: 1.2,
ease: "circ.out",
},
1.8,
);
// Activity bars stagger in
tl.to(S + "#s3Bar1", { width: "75%", duration: 0.8, ease: "power3.out" }, 2.1);
tl.to(S + "#s3Bar2", { width: "50%", duration: 0.8, ease: "power3.out" }, 2.3);
tl.to(S + "#s3Bar3", { width: "90%", duration: 0.8, ease: "power3.out" }, 2.5);
// Graph line draws on
tl.to(
graphLine,
{
strokeDashoffset: 0,
duration: 1.4,
ease: "sine.inOut",
},
2.0,
);
// Graph area fades in
tl.to(graphArea, { opacity: 0.15, duration: 0.8, ease: "power1.out" }, 2.6);
// Graph dots pop in
tl.to(
graphDots,
{
opacity: 1,
scale: 1,
duration: 0.3,
ease: "back.out(2)",
stagger: { each: 0.06, ease: "power2.in" },
},
3.0,
);
// Tagline and CTA on left phone
tl.to(
S + "#s3Tagline",
{
opacity: 1,
y: 0,
duration: 0.6,
ease: "power2.out",
},
2.2,
);
tl.to(
S + ".left-cta",
{
opacity: 1,
y: 0,
duration: 0.5,
ease: "power2.out",
},
2.5,
);
// Ambient slow scale push
tl.to(
S + "#s3Stage",
{
scale: 1.02,
duration: 3.5,
ease: "sine.inOut",
},
2.0,
);
// ════════════════════════════════════
// RESOLVE PHASE (45.5s)
// ════════════════════════════════════
// Subtle glow pulse on lime accent elements
tl.to(S + ".ring-fill", { opacity: 0.65, duration: 0.6, ease: "sine.inOut" }, 4.0);
tl.to(S + ".ring-fill", { opacity: 1, duration: 0.6, ease: "sine.inOut" }, 4.6);
tl.to(S + ".bar-value", { opacity: 0.7, duration: 0.6, ease: "sine.inOut" }, 4.1);
tl.to(S + ".bar-value", { opacity: 1, duration: 0.6, ease: "sine.inOut" }, 4.7);
tl.to(graphLine, { opacity: 0.65, duration: 0.6, ease: "sine.inOut" }, 4.05);
tl.to(graphLine, { opacity: 1, duration: 0.6, ease: "sine.inOut" }, 4.65);
window.__timelines["scene-3-phones"] = tl;
})();
</script>
</div>
</body>
</html>
@@ -0,0 +1,20 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "app-showcase",
"type": "hyperframes:block",
"title": "App Showcase",
"description": "Fitness app product showcase with three floating smartphone screens",
"tags": ["showcase", "app", "3d"],
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 5.5,
"files": [
{
"path": "app-showcase.html",
"target": "compositions/app-showcase.html",
"type": "hyperframes:composition"
}
]
}
@@ -0,0 +1,502 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>ascii-dashboard</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 {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background-color: #0a0a0f;
}
</style>
</head>
<body>
<div
data-composition-id="ascii-dashboard"
data-width="1920"
data-height="1080"
data-duration="10"
>
<canvas id="ascii-canvas" width="1920" height="1080"></canvas>
<style>
[data-composition-id="ascii-dashboard"] {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #020405;
}
#ascii-canvas {
position: absolute;
inset: 0;
width: 1920px;
height: 1080px;
}
</style>
<script src="../three.min.js"></script>
<script>
(function initAsciiDashboard() {
var root = document.querySelector('[data-composition-id="ascii-dashboard"]');
if (!root || typeof THREE === "undefined") {
setTimeout(initAsciiDashboard, 50);
return;
}
var canvas = root.querySelector("#ascii-canvas");
if (!canvas) {
setTimeout(initAsciiDashboard, 50);
return;
}
var W = 1920;
var H = 1080;
var ctx = canvas.getContext("2d");
var CELL_W = 10;
var CELL_H = 16;
var COLS = Math.floor(W / CELL_W);
var ROWS = Math.floor(H / CELL_H);
var GLYPHS = " .,:;=+*#%@";
var GLYPH_MAX = GLYPHS.length - 1;
var BAYER = [
[0.03125, 0.53125, 0.15625, 0.65625],
[0.78125, 0.28125, 0.90625, 0.40625],
[0.21875, 0.71875, 0.09375, 0.59375],
[0.96875, 0.46875, 0.84375, 0.34375],
];
function clamp01(v) {
return v < 0 ? 0 : v > 1 ? 1 : v;
}
function mix(a, b, t) {
return a + (b - a) * t;
}
function easeInOut(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
function easeOut(t) {
return 1 - Math.pow(1 - t, 3);
}
function smoothStep(edge0, edge1, x) {
var t = clamp01((x - edge0) / (edge1 - edge0));
return t * t * (3 - 2 * t);
}
function hash(n) {
var x = Math.sin(n * 127.1 + 311.7) * 43758.5453123;
return x - Math.floor(x);
}
var renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: false,
preserveDrawingBuffer: true,
powerPreference: "high-performance",
});
renderer.setPixelRatio(1);
renderer.setSize(COLS, ROWS, false);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.65;
var target = new THREE.WebGLRenderTarget(COLS, ROWS, {
depthBuffer: true,
stencilBuffer: false,
});
var pixels = new Uint8Array(COLS * ROWS * 4);
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x030507);
scene.fog = new THREE.FogExp2(0x020405, 0.085);
var camera = new THREE.PerspectiveCamera(34, COLS / ROWS, 0.1, 100);
camera.position.set(0, 4.5, 15);
var chartGroup = new THREE.Group();
scene.add(chartGroup);
var ambient = new THREE.AmbientLight(0x406080, 1.8);
scene.add(ambient);
var key = new THREE.DirectionalLight(0x9fe7ff, 2.4);
key.position.set(4, 7, 8);
scene.add(key);
var rim = new THREE.DirectionalLight(0xff7b4d, 1.7);
rim.position.set(-7, 4, -5);
scene.add(rim);
var magentaPoint = new THREE.PointLight(0xff4fd8, 18, 28, 2);
magentaPoint.position.set(-6, 4, 7);
scene.add(magentaPoint);
var cyanPoint = new THREE.PointLight(0x29e7ff, 20, 28, 2);
cyanPoint.position.set(7, 2, 6);
scene.add(cyanPoint);
var floor = new THREE.Mesh(
new THREE.PlaneGeometry(18, 12),
new THREE.MeshStandardMaterial({
color: 0x041018,
metalness: 0.2,
roughness: 0.9,
}),
);
floor.rotation.x = -Math.PI / 2;
floor.position.y = -0.02;
floor.position.z = 0.35;
chartGroup.add(floor);
var gridMaterial = new THREE.MeshBasicMaterial({
color: 0x0f9fb5,
transparent: true,
opacity: 0.22,
});
var gridLines = [];
function addGridLine(x, y, z, sx, sy, sz) {
var mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), gridMaterial.clone());
mesh.position.set(x, y, z);
mesh.scale.set(sx, sy, sz);
mesh.material.opacity = 0;
chartGroup.add(mesh);
gridLines.push(mesh);
}
for (var gx = -6; gx <= 6; gx += 2) addGridLine(gx, 0.01, 0.5, 0.03, 0.03, 9.6);
for (var gz = -3; gz <= 5; gz += 2) addGridLine(0, 0.01, gz, 13.6, 0.03, 0.03);
var axisMaterial = new THREE.MeshStandardMaterial({
color: 0xb8f9ff,
emissive: 0x0f9fb5,
emissiveIntensity: 0.35,
roughness: 0.35,
metalness: 0.15,
});
var xAxis = new THREE.Mesh(new THREE.BoxGeometry(13, 0.08, 0.08), axisMaterial);
xAxis.position.set(0, 0.04, 3.7);
chartGroup.add(xAxis);
var yAxis = new THREE.Mesh(new THREE.BoxGeometry(0.08, 5.8, 0.08), axisMaterial);
yAxis.position.set(-6.3, 2.9, 3.7);
chartGroup.add(yAxis);
var zAxis = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.08, 8.2), axisMaterial);
zAxis.position.set(-6.3, 0.04, -0.1);
chartGroup.add(zAxis);
var colors = [0xff5f7a, 0xffa63d, 0x29e7ff, 0x89ff65, 0xbb6dff];
var barHeights = [2.2, 3.4, 4.1, 5.4, 4.6];
var xPositions = [-4.8, -2.3, 0.2, 2.7, 5.2];
var zPositions = [-1.8, -0.9, 0, 0.8, 1.6];
var dataNodes = [];
var barGeometry = new THREE.BoxGeometry(1, 1, 1);
var sphereGeometry = new THREE.IcosahedronGeometry(0.7, 4);
var ringGeometry = new THREE.TorusGeometry(0.92, 0.09, 18, 72);
function makeGlowMaterial(hex) {
return new THREE.MeshStandardMaterial({
color: hex,
emissive: hex,
emissiveIntensity: 1.1,
roughness: 0.28,
metalness: 0.22,
transparent: true,
});
}
for (var i = 0; i < barHeights.length; i++) {
var group = new THREE.Group();
chartGroup.add(group);
var bar = new THREE.Mesh(barGeometry, makeGlowMaterial(colors[i]));
bar.scale.set(0.88, 0.001, 0.88);
bar.position.set(0, 0.0005, 0);
group.add(bar);
var sphere = new THREE.Mesh(sphereGeometry, makeGlowMaterial(colors[i]));
group.add(sphere);
var ring = new THREE.Mesh(
ringGeometry,
new THREE.MeshBasicMaterial({
color: colors[i],
transparent: true,
opacity: 0.7,
}),
);
ring.rotation.x = Math.PI / 2;
group.add(ring);
dataNodes.push({
group: group,
bar: bar,
sphere: sphere,
ring: ring,
height: barHeights[i],
color: new THREE.Color(colors[i]),
targetX: xPositions[i],
targetZ: zPositions[i],
});
}
var lineMaterial = new THREE.MeshStandardMaterial({
color: 0xffefb7,
emissive: 0xffb84d,
emissiveIntensity: 0.65,
roughness: 0.25,
metalness: 0.08,
transparent: true,
opacity: 0,
});
var pointMaterial = new THREE.MeshStandardMaterial({
color: 0xfff3cf,
emissive: 0xffd77a,
emissiveIntensity: 0.8,
roughness: 0.2,
metalness: 0.1,
transparent: true,
opacity: 0,
});
var lineSegments = [];
var pointMarkers = [];
for (var li = 0; li < dataNodes.length - 1; li++) {
var seg = new THREE.Mesh(
new THREE.CylinderGeometry(0.07, 0.07, 1, 12),
lineMaterial.clone(),
);
chartGroup.add(seg);
lineSegments.push(seg);
}
for (var pi = 0; pi < dataNodes.length; pi++) {
var marker = new THREE.Mesh(
new THREE.SphereGeometry(0.16, 14, 14),
pointMaterial.clone(),
);
chartGroup.add(marker);
pointMarkers.push(marker);
}
function setSegment(mesh, start, end, opacity) {
var dir = new THREE.Vector3().subVectors(end, start);
var length = dir.length();
mesh.position.copy(start).add(end).multiplyScalar(0.5);
mesh.scale.set(1, length, 1);
mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.normalize());
mesh.material.opacity = opacity;
}
function updateSceneAtTime(t) {
var morph = easeInOut(smoothStep(1.3, 4.7, t));
var lineReveal = smoothStep(4.6, 6.2, t);
var rotateReveal = easeInOut(smoothStep(5.2, 8.6, t));
var settle = smoothStep(8.2, 10.0, t);
var scenePulse = 0.5 + 0.5 * Math.sin(t * 2.2);
chartGroup.position.set(0, -1.6, 0);
chartGroup.rotation.x = mix(-0.12, -0.72, rotateReveal);
chartGroup.rotation.y = mix(-0.38, 0.68, rotateReveal);
chartGroup.rotation.z = mix(0.08, -0.06, rotateReveal * 0.8);
floor.material.opacity = 1;
floor.material.emissive = new THREE.Color(0x000000);
camera.position.set(
mix(0, 1.2, rotateReveal) + Math.sin(t * 0.33) * 0.18,
mix(4.5, 5.9, rotateReveal) + Math.cos(t * 0.27) * 0.12,
mix(15, 13.8, rotateReveal),
);
camera.lookAt(0.4, 1.95 + morph * 0.55, 0.7);
key.intensity = mix(1.9, 2.8, rotateReveal);
rim.intensity = mix(1.2, 2.0, rotateReveal);
magentaPoint.intensity = 14 + scenePulse * 5;
cyanPoint.intensity = 16 + (1 - scenePulse) * 5;
for (var gi = 0; gi < gridLines.length; gi++) {
var gridFade = smoothStep(2.6 + gi * 0.03, 5.4 + gi * 0.03, t);
gridLines[gi].material.opacity = 0.08 + 0.18 * gridFade;
}
xAxis.material.opacity = smoothStep(2.0, 3.8, t);
yAxis.material.opacity = smoothStep(2.1, 3.9, t);
zAxis.material.opacity = smoothStep(2.3, 4.1, t);
var topPoints = [];
for (var i = 0; i < dataNodes.length; i++) {
var node = dataNodes[i];
var lift = 1.3 + i * 0.25;
var intro = clamp01(t / 1.8);
var orbit = Math.sin(t * 1.2 + i * 1.35) * 0.8;
var spiralX = Math.cos(t * 1.4 + i * 1.7) * (3.8 - i * 0.25);
var spiralZ = Math.sin(t * 1.05 + i * 1.9) * 2.6;
var floatY = 1.6 + Math.sin(t * 1.8 + i) * 0.7 + lift;
var targetX = node.targetX;
var targetZ = node.targetZ;
var targetHeight = node.height;
var barHeight = 0.001 + targetHeight * morph;
node.group.position.set(
mix(spiralX, targetX, morph),
0,
mix(spiralZ, targetZ, morph),
);
node.bar.scale.set(0.88, barHeight, 0.88);
node.bar.position.set(0, barHeight * 0.5, 0);
var capY = mix(floatY, targetHeight + 0.24, morph);
var stretchY = mix(1.0 + intro * 0.35, 0.52 + targetHeight * 0.12, morph);
var squashXZ = mix(1.0, 0.74, morph);
node.sphere.position.set(0, capY, 0);
node.sphere.scale.set(squashXZ, stretchY, squashXZ);
node.sphere.rotation.x = t * 0.7 + i * 0.3;
node.sphere.rotation.y = t * 1.1 + i * 0.45;
node.ring.position.set(0, mix(floatY - 0.12, targetHeight + 0.16, morph), 0);
node.ring.rotation.z = t * 0.8 + i;
node.ring.rotation.x = Math.PI / 2 + t * 0.35;
node.ring.scale.set(
mix(1.2, 0.68, morph),
mix(1.2, 0.68, morph),
mix(1.2, 0.68, morph),
);
node.ring.material.opacity = mix(0.8, 0.12, morph) * smoothStep(0.0, 0.7, t);
node.bar.material.opacity = smoothStep(1.0 + i * 0.15, 2.8 + i * 0.15, t);
node.bar.material.emissiveIntensity =
mix(0.65, 1.15, rotateReveal) + Math.sin(t * 2.5 + i) * 0.04;
node.sphere.material.opacity = mix(1.0, 0.55, morph);
node.sphere.material.emissiveIntensity = 0.9 + scenePulse * 0.35;
topPoints.push(new THREE.Vector3(targetX, targetHeight + 0.24, targetZ));
}
for (var si = 0; si < lineSegments.length; si++) {
var start = topPoints[si];
var end = topPoints[si + 1];
var segProgress = clamp01((lineReveal - si * 0.16) / 0.6);
var visibleEnd = start.clone().lerp(end, segProgress);
setSegment(lineSegments[si], start, visibleEnd, segProgress * 0.95);
}
for (var mi = 0; mi < pointMarkers.length; mi++) {
var markerProgress = clamp01((lineReveal - mi * 0.12) / 0.5);
pointMarkers[mi].position.copy(topPoints[mi]);
pointMarkers[mi].material.opacity = markerProgress;
pointMarkers[mi].scale.setScalar(
0.8 + markerProgress * 0.9 + Math.sin(t * 3.5 + mi) * 0.06,
);
}
if (settle > 0) {
chartGroup.rotation.y += Math.sin(t * 0.9) * 0.05 * (1 - settle * 0.5);
chartGroup.rotation.x += Math.cos(t * 0.8) * 0.025 * (1 - settle * 0.65);
}
}
function renderAscii(t) {
updateSceneAtTime(t);
renderer.setRenderTarget(target);
renderer.render(scene, camera);
renderer.readRenderTargetPixels(target, 0, 0, COLS, ROWS, pixels);
renderer.setRenderTarget(null);
ctx.fillStyle = "#030506";
ctx.fillRect(0, 0, W, H);
ctx.font = CELL_H - 1 + "px Menlo, Consolas, monospace";
ctx.textBaseline = "top";
var lastFill = "";
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var py = ROWS - 1 - row;
var idx = (py * COLS + col) * 4;
var r = pixels[idx];
var g = pixels[idx + 1];
var b = pixels[idx + 2];
var luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
var glow = Math.max(r, Math.max(g, b)) / 255;
var dither = BAYER[row & 3][col & 3];
var wave =
Math.sin(col * 0.055 + t * 1.6) * 0.035 + Math.cos(row * 0.21 + t * 1.1) * 0.02;
var bright = clamp01(0.06 + luminance * 1.85 + glow * 0.28 + wave);
var scaled = bright * GLYPH_MAX;
var base = Math.floor(scaled);
var frac = scaled - base;
var level = base + (frac > dither ? 1 : 0);
if (level < 1 && bright > 0.035) level = 1;
if (level > GLYPH_MAX) level = GLYPH_MAX;
var scan = 0.9 + 0.1 * Math.sin(row * 0.65 + t * 1.25);
var vx = col / COLS - 0.5;
var vy = row / ROWS - 0.5;
var vignette = 1 - (vx * vx + vy * vy) * 0.45;
if (vignette < 0.52) vignette = 0.52;
var tint = 0.95 + hash(col * 13 + row * 17) * 0.1;
var colorBoost = 1.22 + glow * 0.18;
var finalR = Math.min(255, Math.round(r * scan * vignette * tint * colorBoost));
var finalG = Math.min(255, Math.round(g * scan * vignette * colorBoost));
var finalB = Math.min(
255,
Math.round(b * scan * vignette * (1.04 + glow * 0.1) * colorBoost),
);
var fill = "rgb(" + finalR + "," + finalG + "," + finalB + ")";
if (fill !== lastFill) {
ctx.fillStyle = fill;
lastFill = fill;
}
ctx.fillText(GLYPHS[level], col * CELL_W, row * CELL_H);
}
}
}
renderAscii(0);
window.__timelines = window.__timelines || {};
window.__timelines["ascii-dashboard"] = {
_time: 0,
pause: function () {},
seek: function (time) {
this._time = Math.max(0, Math.min(10, Number(time) || 0));
renderAscii(this._time);
},
duration: function () {
return 10;
},
};
})();
</script>
</div>
</body>
</html>
@@ -0,0 +1,20 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "ascii-dashboard",
"type": "hyperframes:block",
"title": "ASCII Dashboard",
"description": "Retro terminal-style data visualization with charts and metrics",
"tags": ["data", "ascii", "dashboard"],
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 10,
"files": [
{
"path": "ascii-dashboard.html",
"target": "compositions/ascii-dashboard.html",
"type": "hyperframes:composition"
}
]
}
@@ -0,0 +1,652 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>ascii-lightning</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background-color: #0a0a0f;
}
</style>
</head>
<body>
<div
data-composition-id="ascii-lightning"
data-width="1920"
data-height="1080"
data-duration="9"
>
<canvas id="al-canvas" width="1920" height="1080"></canvas>
<style>
[data-composition-id="ascii-lightning"] {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000;
}
#al-canvas {
position: absolute;
inset: 0;
width: 1920px;
height: 1080px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function initAsciiLightning() {
var root = document.querySelector('[data-composition-id="ascii-lightning"]');
if (!root) {
setTimeout(initAsciiLightning, 50);
return;
}
var canvas = root.querySelector("#al-canvas");
if (!canvas) {
setTimeout(initAsciiLightning, 50);
return;
}
var W = 1920,
H = 1080;
var ctx = canvas.getContext("2d");
var CELL_W = 10,
CELL_H = 16;
var COLS = Math.floor(W / CELL_W);
var ROWS = Math.floor(H / CELL_H);
var GLYPHS = " .,:;+*#@W";
var GLYPH_MAX = GLYPHS.length - 1;
var BAYER = [
[0.03125, 0.53125, 0.15625, 0.65625],
[0.78125, 0.28125, 0.90625, 0.40625],
[0.21875, 0.71875, 0.09375, 0.59375],
[0.96875, 0.46875, 0.84375, 0.34375],
];
// ═══════════════════════════════════════════════════════
// DETERMINISTIC HASH & NOISE
// ═══════════════════════════════════════════════════════
function hash(x, y) {
var n = x * 374761393 + y * 668265263;
n = (n ^ (n >> 13)) * 1274126177;
return ((n ^ (n >> 16)) & 0x7fffffff) / 0x7fffffff;
}
function vnoise(x, y) {
var ix = Math.floor(x),
iy = Math.floor(y);
var fx = x - ix,
fy = y - iy;
fx = fx * fx * (3 - 2 * fx);
fy = fy * fy * (3 - 2 * fy);
var a = hash(ix, iy),
b = hash(ix + 1, iy);
var c = hash(ix, iy + 1),
d = hash(ix + 1, iy + 1);
return a + (b - a) * fx + (c - a) * fy + (a - b - c + d) * fx * fy;
}
function fbm(x, y) {
return (
vnoise(x, y) * 0.5 +
vnoise(x * 2.1 + 5.3, y * 2.1 + 3.7) * 0.3 +
vnoise(x * 4.3 + 11.7, y * 4.3 + 7.1) * 0.2
);
}
function clamp01(v) {
return v < 0 ? 0 : v > 1 ? 1 : v;
}
// ═══════════════════════════════════════════════════════
// BITMAP FONT 5x5 at 2x scale
// ═══════════════════════════════════════════════════════
var BFONT = {
A: [0b01110, 0b10001, 0b11111, 0b10001, 0b10001],
B: [0b11110, 0b10001, 0b11110, 0b10001, 0b11110],
C: [0b01111, 0b10000, 0b10000, 0b10000, 0b01111],
D: [0b11110, 0b10001, 0b10001, 0b10001, 0b11110],
E: [0b11111, 0b10000, 0b11110, 0b10000, 0b11111],
F: [0b11111, 0b10000, 0b11110, 0b10000, 0b10000],
G: [0b01111, 0b10000, 0b10011, 0b10001, 0b01111],
H: [0b10001, 0b10001, 0b11111, 0b10001, 0b10001],
I: [0b11111, 0b00100, 0b00100, 0b00100, 0b11111],
J: [0b00111, 0b00010, 0b00010, 0b10010, 0b01100],
K: [0b10001, 0b10010, 0b11100, 0b10010, 0b10001],
L: [0b10000, 0b10000, 0b10000, 0b10000, 0b11111],
M: [0b10001, 0b11011, 0b10101, 0b10001, 0b10001],
N: [0b10001, 0b11001, 0b10101, 0b10011, 0b10001],
O: [0b01110, 0b10001, 0b10001, 0b10001, 0b01110],
P: [0b11110, 0b10001, 0b11110, 0b10000, 0b10000],
R: [0b11110, 0b10001, 0b11110, 0b10010, 0b10001],
S: [0b01111, 0b10000, 0b01110, 0b00001, 0b11110],
T: [0b11111, 0b00100, 0b00100, 0b00100, 0b00100],
U: [0b10001, 0b10001, 0b10001, 0b10001, 0b01110],
V: [0b10001, 0b10001, 0b10001, 0b01010, 0b00100],
W: [0b10001, 0b10001, 0b10101, 0b11011, 0b10001],
X: [0b10001, 0b01010, 0b00100, 0b01010, 0b10001],
Y: [0b10001, 0b01010, 0b00100, 0b00100, 0b00100],
Z: [0b11111, 0b00010, 0b00100, 0b01000, 0b11111],
" ": [0b00000, 0b00000, 0b00000, 0b00000, 0b00000],
};
var SCALE = 2;
var CHAR_W = 5 * SCALE,
CHAR_H = 5 * SCALE;
var CHAR_STRIDE = CHAR_W + SCALE;
function textW(text) {
return text.length * CHAR_STRIDE - SCALE;
}
function textHit(text, sx, sy, c, r) {
if (r < sy || r >= sy + CHAR_H || c < sx) return 0;
var lc2 = c - sx;
if (lc2 >= textW(text)) return 0;
var ci = Math.floor(lc2 / CHAR_STRIDE);
var ic = lc2 - ci * CHAR_STRIDE;
if (ci >= text.length || ic >= CHAR_W) return 0;
var bx = Math.floor(ic / SCALE);
var by = Math.floor((r - sy) / SCALE);
var g = BFONT[text[ci]];
if (!g) return 0;
return (g[by] >> (4 - bx)) & 1;
}
// ═══════════════════════════════════════════════════════
// TEXT LAYOUT & MASK
// ═══════════════════════════════════════════════════════
var TEXT = "HYPERFRAMES";
var textX = Math.floor((COLS - textW(TEXT)) / 2);
var textY = Math.floor((ROWS - CHAR_H) / 2);
var textMask = new Uint8Array(ROWS * COLS);
var tw = textW(TEXT);
for (var r = textY; r < textY + CHAR_H && r < ROWS; r++) {
for (var c = textX; c < textX + tw && c < COLS; c++) {
if (textHit(TEXT, textX, textY, c, r)) {
textMask[r * COLS + c] = 1;
}
}
}
// ═══════════════════════════════════════════════════════
// PROCEDURAL LIGHTNING — bolts strike TOP of text, energy crawls along letters
// ═══════════════════════════════════════════════════════
var textTopY = textY;
function generateBolt(startX, targetX, seed, branchCount) {
// Bolt zigzags from top of screen down to the TOP of the text
var segments = [];
var x = startX,
y = 0;
var step = 0;
while (y < textTopY) {
var stepY = 3 + Math.floor(hash(seed + step, 100) * 4);
var progress = Math.min(1.0, y / textTopY);
var attract = (targetX - x) * progress * 0.35;
var jitter = (hash(seed + step, 200) - 0.5) * 14 * (1.0 - progress * 0.8);
var stepX = attract + jitter;
var newX = Math.max(3, Math.min(COLS - 3, Math.floor(x + stepX)));
var newY = Math.min(textTopY, y + stepY);
segments.push({ x1: x, y1: y, x2: newX, y2: newY });
x = newX;
y = newY;
step++;
}
// Final segment connects directly to the target on the text top edge
if (segments.length > 0) {
var last = segments[segments.length - 1];
if (last.y2 < textTopY) {
segments.push({ x1: last.x2, y1: last.y2, x2: targetX, y2: textTopY });
}
}
// Strike point: where bolt meets the text
var strikeCol = segments.length > 0 ? segments[segments.length - 1].x2 : targetX;
var strikeRow = textTopY;
// Branches — only above the text
var branches = [];
var bc = branchCount || Math.floor(hash(seed, 300) * 3) + 2;
for (var bi = 0; bi < bc; bi++) {
var segIdx = Math.floor(hash(seed + bi * 13, 400) * segments.length);
var seg = segments[Math.min(segIdx, segments.length - 1)];
var bx = seg.x2,
by = seg.y2;
var bsegs = [];
var bseed = seed + 1000 + bi * 77;
var bstep = 0;
var blen = 3 + Math.floor(hash(bseed, 500) * 4);
var dir = hash(bseed, 600) > 0.5 ? 1 : -1;
for (var bs = 0; bs < blen && by < textTopY; bs++) {
var bStepY = 2 + Math.floor(hash(bseed + bstep, 700) * 3);
var bStepX = dir * (2 + hash(bseed + bstep, 800) * 6);
var nbx = Math.max(2, Math.min(COLS - 2, Math.floor(bx + bStepX)));
var nby = Math.min(textTopY, by + bStepY);
bsegs.push({ x1: bx, y1: by, x2: nbx, y2: nby });
bx = nbx;
by = nby;
bstep++;
}
branches.push({ segments: bsegs, brightness: 0.3 + hash(bseed, 900) * 0.35 });
}
return {
segments: segments,
branches: branches,
strikeCol: strikeCol,
strikeRow: strikeRow,
};
}
// ═══════════════════════════════════════════════════════
// BFS CRAWL MAPS — electricity crawls through text pixels from strike point
// ═══════════════════════════════════════════════════════
function buildCrawlMap(startCol, startRow) {
// BFS through textMask from the strike point
// Can jump gaps up to 3 cells (bridges between letters)
var dist = new Float32Array(ROWS * COLS);
for (var i = 0; i < dist.length; i++) dist[i] = -1;
// Find nearest text pixel to the strike point (wide search)
var bestD = 9999,
bestC = startCol,
bestR = startRow;
for (var dr = -8; dr <= 8; dr++) {
for (var dc = -8; dc <= 8; dc++) {
var nr = startRow + dr,
nc = startCol + dc;
if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS && textMask[nr * COLS + nc]) {
var d2 = dr * dr + dc * dc;
if (d2 < bestD) {
bestD = d2;
bestC = nc;
bestR = nr;
}
}
}
}
if (bestD === 9999) return dist;
// BFS with gap-jumping: check neighbors up to 3 cells away
var queue = [bestR * COLS + bestC];
dist[bestR * COLS + bestC] = 0;
var head = 0;
var GAP = 3;
while (head < queue.length) {
var cur = queue[head++];
var curDist = dist[cur];
var cr2 = Math.floor(cur / COLS),
cc = cur % COLS;
for (var dr2 = -GAP; dr2 <= GAP; dr2++) {
for (var dc2 = -GAP; dc2 <= GAP; dc2++) {
if (dr2 === 0 && dc2 === 0) continue;
var nr3 = cr2 + dr2,
nc3 = cc + dc2;
if (nr3 < 0 || nr3 >= ROWS || nc3 < 0 || nc3 >= COLS) continue;
var ni = nr3 * COLS + nc3;
if (dist[ni] >= 0) continue;
if (!textMask[ni]) continue;
var stepDist = Math.sqrt(dr2 * dr2 + dc2 * dc2);
var newDist = curDist + stepDist;
dist[ni] = newDist;
queue.push(ni);
}
}
}
return dist;
}
var letterCenterX = function (idx) {
return textX + idx * CHAR_STRIDE + Math.floor(CHAR_W / 2);
};
// 5 quick strikes → 1 mega strike that holds for 3s
var BOLTS = [
{
strikeTime: 0.6,
decayRate: 7,
bolt: generateBolt(15, letterCenterX(0), 1001, 3),
mega: false,
crawlSpeed: 60,
},
{
strikeTime: 1.3,
decayRate: 7,
bolt: generateBolt(175, letterCenterX(10), 2002, 3),
mega: false,
crawlSpeed: 60,
},
{
strikeTime: 2.0,
decayRate: 7,
bolt: generateBolt(120, letterCenterX(6), 3003, 3),
mega: false,
crawlSpeed: 60,
},
{
strikeTime: 2.7,
decayRate: 7,
bolt: generateBolt(50, letterCenterX(2), 4004, 3),
mega: false,
crawlSpeed: 60,
},
{
strikeTime: 3.4,
decayRate: 7,
bolt: generateBolt(150, letterCenterX(8), 5005, 3),
mega: false,
crawlSpeed: 60,
},
// MEGA: strikes center, fast crawl fills all text, holds bright for 3s
{
strikeTime: 4.2,
decayRate: 0,
bolt: generateBolt(96, letterCenterX(5), 7777, 10),
mega: true,
crawlSpeed: 250,
holdTime: 3.0,
},
];
// Pre-compute crawl maps and bounding boxes for each bolt
for (var bi = 0; bi < BOLTS.length; bi++) {
var b = BOLTS[bi].bolt;
BOLTS[bi].crawlMap = buildCrawlMap(b.strikeCol, b.strikeRow);
var minC = COLS,
maxC = 0;
for (var si = 0; si < b.segments.length; si++) {
var s = b.segments[si];
if (s.x1 < minC) minC = s.x1;
if (s.x2 < minC) minC = s.x2;
if (s.x1 > maxC) maxC = s.x1;
if (s.x2 > maxC) maxC = s.x2;
}
for (var bri = 0; bri < b.branches.length; bri++) {
var br = b.branches[bri];
for (var bsi = 0; bsi < br.segments.length; bsi++) {
var bs2 = br.segments[bsi];
if (bs2.x1 < minC) minC = bs2.x1;
if (bs2.x2 < minC) minC = bs2.x2;
if (bs2.x1 > maxC) maxC = bs2.x1;
if (bs2.x2 > maxC) maxC = bs2.x2;
}
}
BOLTS[bi].minCol = minC - 35;
BOLTS[bi].maxCol = maxC + 35;
}
// ═══════════════════════════════════════════════════════
// ILLUMINATION BUFFER & BOLT BRIGHTNESS
// ═══════════════════════════════════════════════════════
var illumBuf = new Float32Array(ROWS * COLS);
function boltBrightness(bolt, t) {
var dt = t - bolt.strikeTime;
if (dt < 0) return 0;
if (bolt.mega) {
// Mega bolt: bright flash, then gentle glow during hold
if (dt > (bolt.holdTime || 0) + 1.0) return 0;
var attack = clamp01(dt / 0.02);
var decay = Math.exp(-dt * 4.0);
var flicker = 0;
if (dt > 0.04 && dt < 0.08) flicker = 0.5 * Math.exp(-Math.pow(dt - 0.06, 2) * 8000);
if (dt > 0.1 && dt < 0.16) flicker = 0.3 * Math.exp(-Math.pow(dt - 0.13, 2) * 5000);
// Keep a subtle glow during the hold period
var holdGlow = dt < (bolt.holdTime || 0) ? 0.15 : 0;
return attack * Math.max(decay + flicker, holdGlow);
}
if (dt > 1.5) return 0;
var attack = clamp01(dt / 0.02);
var decay = Math.exp(-dt * bolt.decayRate);
var flicker = 0;
if (dt > 0.04 && dt < 0.08) flicker = 0.5 * Math.exp(-Math.pow(dt - 0.06, 2) * 8000);
if (dt > 0.1 && dt < 0.16) flicker = 0.3 * Math.exp(-Math.pow(dt - 0.13, 2) * 5000);
return attack * (decay + flicker);
}
// Paint a single segment's glow into illumBuf
function paintSegGlow(x1, y1, x2, y2, strength, radius) {
var mnR = Math.max(0, Math.floor(Math.min(y1, y2) - radius));
var mxR = Math.min(ROWS - 1, Math.ceil(Math.max(y1, y2) + radius));
var mnC = Math.max(0, Math.floor(Math.min(x1, x2) - radius));
var mxC = Math.min(COLS - 1, Math.ceil(Math.max(x1, x2) + radius));
var dx = x2 - x1,
dy = y2 - y1;
var lenSq = dx * dx + dy * dy;
for (var r = mnR; r <= mxR; r++) {
for (var c = mnC; c <= mxC; c++) {
var dist;
if (lenSq < 0.001) {
var ex = c - x1,
ey = r - y1;
dist = Math.sqrt(ex * ex + ey * ey);
} else {
var t2 = ((c - x1) * dx + (r - y1) * dy) / lenSq;
if (t2 < 0) t2 = 0;
if (t2 > 1) t2 = 1;
var px = x1 + t2 * dx,
py = y1 + t2 * dy;
var ex2 = c - px,
ey2 = r - py;
dist = Math.sqrt(ex2 * ex2 + ey2 * ey2);
}
if (dist < radius) {
var val = strength * (1.0 - dist / radius);
var idx = r * COLS + c;
if (val > illumBuf[idx]) illumBuf[idx] = val;
}
}
}
}
// Paint a radial impact bloom at the strike point
function paintImpact(cx, cy, strength, radius) {
var mnR = Math.max(0, Math.floor(cy - radius));
var mxR = Math.min(ROWS - 1, Math.ceil(cy + radius));
var mnC = Math.max(0, Math.floor(cx - radius));
var mxC = Math.min(COLS - 1, Math.ceil(cx + radius));
for (var r = mnR; r <= mxR; r++) {
for (var c = mnC; c <= mxC; c++) {
var dx = c - cx,
dy = r - cy;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < radius) {
var falloff = 1.0 - dist / radius;
var val = strength * falloff * falloff;
var idx = r * COLS + c;
if (val > illumBuf[idx]) illumBuf[idx] = val;
}
}
}
}
function paintBolt(boltData, bb, mega) {
var radius = mega ? 45 : 20;
var segs = boltData.segments;
for (var i = 0; i < segs.length; i++) {
paintSegGlow(segs[i].x1, segs[i].y1, segs[i].x2, segs[i].y2, bb, radius);
}
var brs = boltData.branches;
for (var j = 0; j < brs.length; j++) {
var br = brs[j];
var brSegs = br.segments;
var brStr = bb * br.brightness;
var brRad = radius * 0.6;
for (var k = 0; k < brSegs.length; k++) {
paintSegGlow(brSegs[k].x1, brSegs[k].y1, brSegs[k].x2, brSegs[k].y2, brStr, brRad);
}
}
// Impact bloom at the strike point — extra bright glow where bolt meets text
var impactRadius = mega ? 55 : 28;
paintImpact(boltData.strikeCol, boltData.strikeRow, bb * 1.2, impactRadius);
}
// ═══════════════════════════════════════════════════════
// RENDER
// ═══════════════════════════════════════════════════════
function renderAtTime(t) {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, W, H);
ctx.font = CELL_H - 2 + "px monospace";
ctx.textBaseline = "top";
// Fade out in the last 1.5s of the composition
var TOTAL_DUR = 9.0;
var globalFade = clamp01((TOTAL_DUR - t) / 1.5);
// Clear and fill illumination buffer (for bolt glow in the sky)
for (var i = 0; i < illumBuf.length; i++) illumBuf[i] = 0;
// Also build per-cell crawl illumination for text
var crawlBuf = new Float32Array(ROWS * COLS);
for (var bi = 0; bi < BOLTS.length; bi++) {
var bb = boltBrightness(BOLTS[bi], t);
// For mega bolt, keep painting even after the flash fades (for the hold)
var dt = t - BOLTS[bi].strikeTime;
if (bb > 0.001) {
paintBolt(BOLTS[bi].bolt, bb, BOLTS[bi].mega);
}
// Crawl effect: electricity spreads through text pixels from strike point
if (dt > 0) {
var crawlMap = BOLTS[bi].crawlMap;
var crawlRadius = dt * BOLTS[bi].crawlSpeed;
var holdTime = BOLTS[bi].holdTime || 0;
var crawlDecay;
if (BOLTS[bi].mega) {
// Mega bolt: stays at full brightness during hold, then fades
if (dt < holdTime) {
crawlDecay = 1.0;
} else {
crawlDecay = Math.exp(-(dt - holdTime) * 3.0);
}
} else {
crawlDecay = Math.exp(-dt * 5.0);
}
if (crawlDecay < 0.005) continue;
for (var ci = 0; ci < crawlMap.length; ci++) {
if (crawlMap[ci] < 0) continue;
if (crawlMap[ci] > crawlRadius) continue;
var waveDist = crawlRadius - crawlMap[ci];
var waveBright = crawlDecay;
// Extra bright at the wavefront (leading edge of crawl)
if (waveDist < 8 && dt < 1.0) {
waveBright *= 1.0 + (1.0 - waveDist / 8) * 2.0;
}
if (waveBright > crawlBuf[ci]) crawlBuf[ci] = waveBright;
}
}
}
var lastFill = "";
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var ambN = fbm(col * 0.03 + t * 0.08, row * 0.04 + t * 0.06);
var ambBright = 0.02 + ambN * 0.04;
if (t < 0.6) {
var flickerN = hash(col * 7 + Math.floor(t * 15), row * 11 + Math.floor(t * 15));
if (flickerN > 0.997) {
ambBright += 0.15 * (1.0 - t / 0.6);
}
}
var localIllum = illumBuf[row * COLS + col];
var crawlIllum = crawlBuf[row * COLS + col];
var isText = textMask[row * COLS + col];
var brightness, cr, cg, cb;
if (isText && crawlIllum > 0.01) {
// Text pixel illuminated by crawling electricity
var ci2 = clamp01(crawlIllum);
brightness = ci2 * globalFade;
// Bright crawl front is white-green, trailing is green
var whiteness = clamp01(crawlIllum - 0.8) * 3.0;
cr = Math.round((10 + whiteness * 220) * brightness);
cg = Math.round((60 + ci2 * 195) * brightness);
cb = Math.round((5 + whiteness * 180) * brightness);
// Also blend in any direct bolt proximity
if (localIllum > 0.1) {
var boost = localIllum * 0.4;
cr = Math.min(255, cr + Math.round(boost * 200));
cg = Math.min(255, cg + Math.round(boost * 100));
cb = Math.min(255, cb + Math.round(boost * 180));
brightness = Math.min(1.0, brightness + boost);
}
} else if (localIllum > 0.03) {
// Non-text cell near a bolt — bolt glow
brightness = localIllum * globalFade;
var coreAmt = clamp01((localIllum - 0.4) * 2.5);
cr = Math.round(coreAmt * 230 * brightness);
cg = Math.round((80 + localIllum * 175) * brightness);
cb = Math.round(coreAmt * 190 * brightness);
} else {
brightness = ambBright * globalFade;
cr = 0;
cg = Math.round((10 + ambN * 20) * brightness);
cb = 0;
}
var dither = BAYER[row & 3][col & 3];
var sl = brightness * GLYPH_MAX;
var bl2 = Math.floor(sl);
var fr = sl - bl2;
var rl = bl2 + (fr > dither ? 1 : 0);
if (rl > GLYPH_MAX) rl = GLYPH_MAX;
if (rl < 1 && brightness > 0.01) rl = 1;
if (brightness < 0.01) rl = 0;
var ch = GLYPHS[rl];
if (rl === 0) continue;
var scanline = 0.92 + 0.08 * Math.sin(row * 0.6 + t * 0.8);
var vx = col / COLS - 0.5,
vy = row / ROWS - 0.5;
var vig = 1.0 - (vx * vx + vy * vy) * 0.35;
if (vig < 0.55) vig = 0.55;
var m = scanline * vig;
var fr2 = Math.round(cr * m);
var fg = Math.round(cg * m);
var fb2 = Math.round(cb * m);
if (fr2 > 255) fr2 = 255;
if (fg > 255) fg = 255;
if (fb2 > 255) fb2 = 255;
var fill = "rgb(" + fr2 + "," + fg + "," + fb2 + ")";
if (fill !== lastFill) {
ctx.fillStyle = fill;
lastFill = fill;
}
ctx.fillText(ch, col * CELL_W, row * CELL_H);
}
}
}
renderAtTime(0);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var proxy = { time: 0 };
tl.to(
proxy,
{
time: 9,
duration: 9,
ease: "none",
onUpdate: function () {
renderAtTime(proxy.time);
},
},
0,
);
window.__timelines["ascii-lightning"] = tl;
})();
</script>
</div>
</body>
</html>
@@ -0,0 +1,20 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "ascii-lightning",
"type": "hyperframes:block",
"title": "ASCII Lightning",
"description": "Pure ASCII art lightning bolt animation on dark background",
"tags": ["title", "ascii", "animation"],
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 9,
"files": [
{
"path": "ascii-lightning.html",
"target": "compositions/ascii-lightning.html",
"type": "hyperframes:composition"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -0,0 +1,288 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=block"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
background: transparent;
overflow: hidden;
}
body {
width: 1080px;
height: 1920px;
font-family: "DM Sans", sans-serif;
}
.lower-third {
position: absolute;
bottom: 160px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 30px;
background: #1a1a1a;
border-radius: 75px;
padding: 25px 40px 25px 25px;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.4);
}
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
border: 3px solid #333;
}
.profile-info {
display: flex;
flex-direction: column;
gap: 2px;
margin-right: 20px;
}
.name-row {
display: flex;
align-items: center;
gap: 10px;
}
.display-name {
font-size: 42px;
font-weight: 700;
color: #ffffff;
line-height: 1.3;
letter-spacing: -0.01em;
}
.verified-badge {
width: 34px;
height: 34px;
flex-shrink: 0;
}
.verified-badge svg {
width: 34px;
height: 34px;
display: block;
}
.handle {
font-size: 28px;
font-weight: 400;
color: #a0a0a0;
line-height: 1.3;
}
.follower-count {
font-size: 25px;
font-weight: 400;
color: #737373;
line-height: 1.3;
}
.follow-btn {
position: relative;
width: 250px;
height: 80px;
border-radius: 40px;
background: #0095f6;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.btn-text {
position: absolute;
font-family: "DM Sans", sans-serif;
font-size: 30px;
font-weight: 700;
color: #ffffff;
letter-spacing: 0.02em;
white-space: nowrap;
}
.btn-text-follow {
opacity: 1;
}
.btn-text-following {
opacity: 0;
display: flex;
align-items: center;
gap: 8px;
}
.chevron-icon {
display: inline-block;
width: 18px;
height: 18px;
}
.chevron-icon svg {
width: 18px;
height: 18px;
display: block;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="instagram-follow-overlay"
data-width="1080"
data-height="1920"
data-start="0"
data-duration="4.5"
>
<div
id="card"
class="clip lower-third"
data-start="0"
data-duration="4.5"
data-track-index="0"
>
<img class="avatar" src="assets/avatar.jpg" alt="HeyGen" crossorigin="anonymous" />
<div class="profile-info">
<div class="name-row">
<div class="display-name">HeyGen</div>
<span class="verified-badge"
><svg viewBox="0 0 40 40" fill="none">
<circle cx="20" cy="20" r="20" fill="#0095F6" />
<path d="M17.5 27.5L10 20l2.5-2.5 5 5 10-10L30 15 17.5 27.5z" fill="#fff" /></svg
></span>
</div>
<div class="handle">@heygen_official</div>
<div class="follower-count">47.5K followers</div>
</div>
<div id="follow-btn" class="follow-btn">
<span id="btn-follow" class="btn-text btn-text-follow">Follow</span>
<span id="btn-following" class="btn-text btn-text-following">
<span>Following</span>
<span class="chevron-icon"
><svg
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9"></polyline></svg
></span>
</span>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var card = document.getElementById("card");
var btn = document.getElementById("follow-btn");
var textFollow = document.getElementById("btn-follow");
var textFollowing = document.getElementById("btn-following");
// Start off-screen
gsap.set(card, { y: 300, opacity: 0 });
// Slide in from bottom
tl.to(
card,
{
y: 0,
opacity: 1,
duration: 0.5,
ease: "power3.out",
},
0.1,
);
// Hold, then button press-in
tl.to(
btn,
{
scale: 0.92,
duration: 0.15,
ease: "power2.out",
},
1.0,
);
// Button release with spring bounce
tl.to(
btn,
{
scale: 1,
duration: 0.4,
ease: "elastic.out(1, 0.4)",
},
1.15,
);
// Button background: IG blue → dark gray
tl.to(
btn,
{
backgroundColor: "#2f2f2f",
duration: 0.12,
ease: "none",
},
1.15,
);
// Fade out "Follow", fade in "Following"
tl.to(
textFollow,
{
opacity: 0,
duration: 0.08,
ease: "none",
},
1.15,
);
tl.to(
textFollowing,
{
opacity: 1,
duration: 0.08,
ease: "none",
},
1.18,
);
// Slide out to bottom
tl.to(
card,
{
y: 300,
opacity: 0,
duration: 0.25,
ease: "power3.in",
},
3.8,
);
window.__timelines["instagram-follow-overlay"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,25 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "instagram-follow",
"type": "hyperframes:block",
"title": "Instagram Follow",
"description": "Animated Instagram follow overlay with profile card and follow button",
"tags": ["social", "overlay", "instagram"],
"dimensions": {
"width": 1080,
"height": 1920
},
"duration": 4.5,
"files": [
{
"path": "instagram-follow.html",
"target": "compositions/instagram-follow.html",
"type": "hyperframes:composition"
},
{
"path": "assets/avatar.jpg",
"target": "assets/avatar.jpg",
"type": "hyperframes:asset"
}
]
}
@@ -0,0 +1,201 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=block"
rel="stylesheet"
/>
<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 {
background: transparent;
overflow: hidden;
}
body {
width: 1920px;
height: 1080px;
font-family: "Inter", sans-serif;
}
.notification-card {
position: absolute;
top: 24px;
right: 24px;
width: 380px;
background: rgba(30, 30, 30, 0.82);
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
border-radius: 16px;
padding: 16px;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.4),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.1);
display: flex;
gap: 12px;
align-items: flex-start;
}
.app-icon {
width: 36px;
height: 36px;
border-radius: 8px;
flex-shrink: 0;
overflow: hidden;
}
.app-icon svg {
width: 36px;
height: 36px;
display: block;
}
.notification-content {
flex: 1;
min-width: 0;
}
.notification-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2px;
}
.app-name {
font-size: 13px;
font-weight: 600;
color: rgba(255, 255, 255, 0.5);
text-transform: uppercase;
letter-spacing: 0.02em;
}
.time-label {
font-size: 13px;
font-weight: 400;
color: rgba(255, 255, 255, 0.35);
}
.notification-title {
font-size: 15px;
font-weight: 600;
color: #ffffff;
line-height: 1.3;
margin-bottom: 2px;
}
.notification-body {
font-size: 13px;
font-weight: 400;
color: rgba(255, 255, 255, 0.6);
line-height: 1.35;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="macos-notification"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="5"
>
<div
id="notification"
class="clip notification-card"
data-start="0"
data-duration="5"
data-track-index="0"
>
<div class="app-icon">
<svg viewBox="0 0 36 36" fill="none">
<defs>
<linearGradient
id="icon-grad"
x1="0"
y1="0"
x2="36"
y2="36"
gradientUnits="userSpaceOnUse"
>
<stop offset="0%" stop-color="#6366f1" />
<stop offset="100%" stop-color="#8b5cf6" />
</linearGradient>
</defs>
<rect width="36" height="36" rx="8" fill="url(#icon-grad)" />
<text
x="18"
y="23"
text-anchor="middle"
font-family="Inter, sans-serif"
font-size="14"
font-weight="700"
fill="#fff"
>
HF
</text>
</svg>
</div>
<div class="notification-content">
<div class="notification-header">
<span class="app-name">HyperFrames</span>
<span class="time-label">now</span>
</div>
<div class="notification-title">Prompt to replace this title</div>
<div class="notification-body">
Prompt to change this body text, the app icon, and the app name to match your content.
</div>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var notification = document.getElementById("notification");
// Start off-screen to the right
gsap.set(notification, { x: 420, opacity: 0 });
// Slide in from right
tl.to(
notification,
{
x: 0,
opacity: 1,
duration: 0.5,
ease: "power3.out",
},
0.2,
);
// Slide out to right
tl.to(
notification,
{
x: 420,
opacity: 0,
duration: 0.3,
ease: "power3.in",
},
4.2,
);
window.__timelines["macos-notification"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,20 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "macos-notification",
"type": "hyperframes:block",
"title": "macOS Notification",
"description": "Animated macOS-style notification banner with app icon and message",
"tags": ["social", "overlay", "notification"],
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 5,
"files": [
{
"path": "macos-notification.html",
"target": "compositions/macos-notification.html",
"type": "hyperframes:composition"
}
]
}
@@ -0,0 +1,346 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=block"
rel="stylesheet"
/>
<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 {
background: transparent;
overflow: hidden;
}
body {
width: 1920px;
height: 1080px;
font-family: "DM Sans", sans-serif;
}
.reddit-card {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 860px;
background: #1a1a2e;
border-radius: 16px;
padding: 32px;
box-shadow: 0 12px 60px rgba(0, 0, 0, 0.6);
}
/* Header */
.post-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 24px;
}
.subreddit-icon {
width: 48px;
height: 48px;
flex-shrink: 0;
}
.subreddit-icon svg {
width: 48px;
height: 48px;
display: block;
}
.post-meta {
display: flex;
flex-direction: column;
gap: 2px;
}
.subreddit-name {
font-size: 28px;
font-weight: 700;
color: #d7dadc;
line-height: 1.3;
}
.post-author {
font-size: 24px;
font-weight: 400;
color: #818384;
line-height: 1.3;
}
/* Content */
.post-title {
font-size: 38px;
font-weight: 700;
color: #d7dadc;
line-height: 1.35;
margin-bottom: 16px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.post-body {
font-size: 28px;
font-weight: 400;
color: #818384;
line-height: 1.45;
margin-bottom: 28px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Action bar */
.action-bar {
display: flex;
align-items: center;
gap: 28px;
padding-top: 20px;
border-top: 1px solid #343536;
}
.vote-group {
display: flex;
align-items: center;
gap: 10px;
background: #272729;
border-radius: 24px;
padding: 8px 16px;
}
.vote-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.vote-btn svg {
width: 28px;
height: 28px;
display: block;
}
.vote-count {
font-size: 26px;
font-weight: 700;
color: #d7dadc;
min-width: 50px;
text-align: center;
}
.action-btn {
display: flex;
align-items: center;
gap: 8px;
background: #272729;
border-radius: 24px;
padding: 10px 20px;
}
.action-btn svg {
width: 26px;
height: 26px;
display: block;
}
.action-btn span {
font-size: 24px;
font-weight: 400;
color: #818384;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="reddit-post-overlay"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="5"
>
<div id="card" class="clip reddit-card" data-start="0" data-duration="5" data-track-index="0">
<!-- Header -->
<div class="post-header">
<div class="subreddit-icon">
<svg viewBox="0 0 48 48" fill="none">
<circle cx="24" cy="24" r="24" fill="#4A90D9" />
<text
x="24"
y="30"
text-anchor="middle"
font-family="DM Sans, sans-serif"
font-size="22"
font-weight="700"
fill="#fff"
>
r/
</text>
</svg>
</div>
<div class="post-meta">
<span class="subreddit-name">r/hyperframes</span>
<span class="post-author">u/placeholder_user &middot; 3h</span>
</div>
</div>
<!-- Content -->
<div class="post-title">Prompt to change this title to whatever you want</div>
<div class="post-body">
Prompt to change this body text, the subreddit, username, and vote count to match your
content.
</div>
<!-- Action bar -->
<div class="action-bar">
<div class="vote-group">
<div id="upvote-btn" class="vote-btn">
<svg viewBox="0 0 24 24" fill="none">
<path id="upvote-arrow" d="M12 4L3 14h5v6h8v-6h5L12 4z" fill="#818384" />
</svg>
</div>
<span id="vote-count" class="vote-count">4.2k</span>
<div class="vote-btn">
<svg viewBox="0 0 24 24" fill="none">
<path d="M12 20l9-10h-5V4H8v6H3l9 10z" fill="#818384" />
</svg>
</div>
</div>
<div class="action-btn">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="#818384"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" />
</svg>
<span>328</span>
</div>
<div class="action-btn">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="#818384"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4 12v8a2 2 0 002 2h12a2 2 0 002-2v-8" />
<polyline points="16 6 12 2 8 6" />
<line x1="12" y1="2" x2="12" y2="15" />
</svg>
<span>Share</span>
</div>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var card = document.getElementById("card");
var upvoteBtn = document.getElementById("upvote-btn");
var upvoteArrow = document.getElementById("upvote-arrow");
var voteCount = document.getElementById("vote-count");
// Start off-screen below
gsap.set(card, { y: 400, opacity: 0 });
// Slide in from bottom
tl.to(
card,
{
y: 0,
opacity: 1,
duration: 0.6,
ease: "power3.out",
},
0.1,
);
// Upvote press-in
tl.to(
upvoteBtn,
{
scale: 0.85,
duration: 0.12,
ease: "power2.out",
},
1.5,
);
// Upvote release with spring bounce
tl.to(
upvoteBtn,
{
scale: 1,
duration: 0.4,
ease: "elastic.out(1, 0.4)",
},
1.62,
);
// Arrow turns orange
tl.to(
upvoteArrow,
{
attr: { fill: "#ff4500" },
duration: 0.12,
ease: "none",
},
1.62,
);
// Vote count turns orange + increments
tl.to(
voteCount,
{
color: "#ff4500",
duration: 0.12,
ease: "none",
onStart: function () {
voteCount.textContent = "4.3k";
},
},
1.62,
);
// Slide out to bottom
tl.to(
card,
{
y: 400,
opacity: 0,
duration: 0.3,
ease: "power3.in",
},
4.2,
);
window.__timelines["reddit-post-overlay"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,20 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "reddit-post",
"type": "hyperframes:block",
"title": "Reddit Post Card",
"description": "Animated Reddit post card overlay with upvotes and comments",
"tags": ["social", "overlay", "reddit"],
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 5,
"files": [
{
"path": "reddit-post.html",
"target": "compositions/reddit-post.html",
"type": "hyperframes:composition"
}
]
}
@@ -0,0 +1,20 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "spotify-card",
"type": "hyperframes:block",
"title": "Spotify Now Playing",
"description": "Animated Spotify now-playing card with album art and progress bar",
"tags": ["social", "overlay", "spotify"],
"dimensions": {
"width": 1080,
"height": 1920
},
"duration": 5,
"files": [
{
"path": "spotify-card.html",
"target": "compositions/spotify-card.html",
"type": "hyperframes:composition"
}
]
}
@@ -0,0 +1,299 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=block"
rel="stylesheet"
/>
<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 {
background: transparent;
overflow: hidden;
}
body {
width: 1080px;
height: 1920px;
font-family: "Inter", sans-serif;
}
.card {
position: absolute;
top: 50%;
left: 50%;
margin-top: -495px;
margin-left: -390px;
width: 780px;
background: linear-gradient(
165deg,
rgba(140, 155, 170, 0.42) 0%,
rgba(100, 115, 130, 0.35) 30%,
rgba(75, 88, 105, 0.38) 60%,
rgba(60, 72, 88, 0.44) 100%
);
border-radius: 28px;
padding: 36px;
border: 1px solid rgba(255, 255, 255, 0.22);
box-shadow:
0 24px 48px rgba(0, 0, 0, 0.3),
0 2px 6px rgba(0, 0, 0, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.32),
inset 0 -1px 0 rgba(0, 0, 0, 0.12);
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
overflow: hidden;
}
/* Specular highlight — light refraction across top */
.card::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 55%;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.18) 0%,
rgba(255, 255, 255, 0.07) 35%,
transparent 100%
);
border-radius: 28px 28px 0 0;
pointer-events: none;
}
/* Edge shine — bottom rim catch light */
.card::after {
content: "";
position: absolute;
bottom: 0;
left: 20px;
right: 20px;
height: 1px;
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.08) 30%,
rgba(255, 255, 255, 0.12) 50%,
rgba(255, 255, 255, 0.08) 70%,
transparent 100%
);
pointer-events: none;
}
/* Album Art */
.album-art {
width: 708px;
height: 708px;
border-radius: 16px;
overflow: hidden;
position: relative;
z-index: 1;
background: linear-gradient(145deg, #1db954 0%, #17a34a 30%, #0f7234 60%, #191414 100%);
margin-bottom: 36px;
}
.album-art-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0.2;
}
/* Track Info */
.track-info {
position: relative;
z-index: 1;
margin-bottom: 28px;
padding: 0 8px;
}
.track-name {
font-size: 52px;
font-weight: 700;
color: #ffffff;
letter-spacing: -0.02em;
line-height: 1.15;
margin-bottom: 8px;
}
.artist-name {
font-size: 34px;
font-weight: 400;
color: rgba(255, 255, 255, 0.7);
line-height: 1.3;
}
/* Spotify Branding */
.spotify-brand {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 12px;
padding: 0 8px;
}
.spotify-logo {
display: flex;
align-items: center;
justify-content: center;
}
.spotify-brand-text {
font-size: 26px;
font-weight: 600;
color: rgba(255, 255, 255, 0.55);
letter-spacing: 0.01em;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="spotify-now-playing"
data-width="1080"
data-height="1920"
data-start="0"
data-duration="5"
>
<div id="card" class="clip card" data-start="0" data-duration="5" data-track-index="0">
<!-- Album Art -->
<div id="album-art" class="album-art">
<svg class="album-art-icon" width="200" height="200" viewBox="0 0 24 24" fill="none">
<path
d="M9 18V5l12-2v13"
stroke="rgba(255,255,255,0.7)"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<circle cx="6" cy="18" r="3" stroke="rgba(255,255,255,0.7)" stroke-width="1" />
<circle cx="18" cy="16" r="3" stroke="rgba(255,255,255,0.7)" stroke-width="1" />
</svg>
</div>
<!-- Track Info -->
<div class="track-info">
<div id="track-name" class="track-name">HyperFrames</div>
<div id="artist-name" class="artist-name">HeyGen</div>
</div>
<!-- Spotify Branding -->
<div id="spotify-brand" class="spotify-brand">
<div class="spotify-logo">
<svg width="32" height="32" viewBox="0 0 16 16" fill="rgba(255,255,255,0.55)">
<path
d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0m3.669 11.538a.5.5 0 0 1-.686.165c-1.879-1.147-4.243-1.407-7.028-.77a.499.499 0 0 1-.222-.973c3.048-.696 5.662-.397 7.77.892a.5.5 0 0 1 .166.686m.979-2.178a.624.624 0 0 1-.858.205c-2.15-1.321-5.428-1.704-7.972-.932a.625.625 0 0 1-.362-1.194c2.905-.881 6.517-.454 8.986 1.063a.624.624 0 0 1 .206.858m.084-2.268C10.154 5.56 5.9 5.419 3.438 6.166a.748.748 0 1 1-.434-1.432c2.825-.857 7.523-.692 10.492 1.07a.747.747 0 1 1-.764 1.288"
/>
</svg>
</div>
<span class="spotify-brand-text">Spotify</span>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var card = document.getElementById("card");
var albumArt = document.getElementById("album-art");
var trackName = document.getElementById("track-name");
var artistName = document.getElementById("artist-name");
var spotifyBrand = document.getElementById("spotify-brand");
// Start state
gsap.set(card, { y: 60, opacity: 0, scale: 0.94 });
gsap.set(trackName, { y: 20, opacity: 0 });
gsap.set(artistName, { y: 16, opacity: 0 });
gsap.set(spotifyBrand, { y: 12, opacity: 0 });
// ── Card entrance ────────────────────────────────────────
tl.to(
card,
{
y: 0,
opacity: 1,
scale: 1,
duration: 0.7,
ease: "power3.out",
},
0.1,
);
// ── Text stagger in ─────────────────────────────────────
tl.to(
trackName,
{
y: 0,
opacity: 1,
duration: 0.5,
ease: "power2.out",
},
0.55,
);
tl.to(
artistName,
{
y: 0,
opacity: 1,
duration: 0.45,
ease: "power2.out",
},
0.7,
);
tl.to(
spotifyBrand,
{
y: 0,
opacity: 1,
duration: 0.4,
ease: "power2.out",
},
0.85,
);
// ── Subtle album art breathe ────────────────────────────
tl.to(
albumArt,
{
scale: 1.015,
duration: 1.5,
ease: "power1.inOut",
yoyo: true,
repeat: 1,
},
1.2,
);
// ── Card exit ────────────────────────────────────────────
tl.to(
card,
{
y: -40,
opacity: 0,
scale: 0.96,
duration: 0.5,
ease: "power2.in",
},
4.2,
);
window.__timelines["spotify-now-playing"] = tl;
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -0,0 +1,25 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "tiktok-follow",
"type": "hyperframes:block",
"title": "TikTok Follow",
"description": "Animated TikTok follow overlay with profile card and follow button",
"tags": ["social", "overlay", "tiktok"],
"dimensions": {
"width": 1080,
"height": 1920
},
"duration": 4.5,
"files": [
{
"path": "tiktok-follow.html",
"target": "compositions/tiktok-follow.html",
"type": "hyperframes:composition"
},
{
"path": "assets/avatar.jpg",
"target": "assets/avatar.jpg",
"type": "hyperframes:asset"
}
]
}
@@ -0,0 +1,263 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=block"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
background: transparent;
overflow: hidden;
}
body {
width: 1080px;
height: 1920px;
font-family: "DM Sans", sans-serif;
}
.lower-third {
position: absolute;
bottom: 160px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 30px;
background: #1a1a1a;
border-radius: 75px;
padding: 25px 40px 25px 25px;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.4);
}
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
border: 3px solid #333;
}
.profile-info {
display: flex;
flex-direction: column;
gap: 2px;
margin-right: 20px;
}
.display-name {
font-size: 42px;
font-weight: 700;
color: #ffffff;
line-height: 1.3;
letter-spacing: -0.01em;
}
.handle {
font-size: 28px;
font-weight: 400;
color: #a0a0a0;
line-height: 1.3;
}
.follower-count {
font-size: 25px;
font-weight: 400;
color: #737373;
line-height: 1.3;
}
.follow-btn {
position: relative;
width: 250px;
height: 80px;
border-radius: 40px;
background: #fe2c55;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.btn-text {
position: absolute;
font-family: "DM Sans", sans-serif;
font-size: 30px;
font-weight: 700;
color: #ffffff;
letter-spacing: 0.02em;
white-space: nowrap;
}
.btn-text-follow {
opacity: 1;
}
.btn-text-following {
opacity: 0;
display: flex;
align-items: center;
gap: 8px;
}
.check-icon {
display: inline-block;
width: 22px;
height: 22px;
}
.check-icon svg {
width: 22px;
height: 22px;
display: block;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="tiktok-follow-overlay"
data-width="1080"
data-height="1920"
data-start="0"
data-duration="4.5"
>
<div
id="card"
class="clip lower-third"
data-start="0"
data-duration="4.5"
data-track-index="0"
>
<img class="avatar" src="assets/avatar.jpg" alt="HeyGen" crossorigin="anonymous" />
<div class="profile-info">
<div class="display-name">HeyGen</div>
<div class="handle">@heygen.com</div>
<div class="follower-count">1,999 followers</div>
</div>
<div id="follow-btn" class="follow-btn">
<span id="btn-follow" class="btn-text btn-text-follow">Follow</span>
<span id="btn-following" class="btn-text btn-text-following">
<span>Following</span>
<span class="check-icon"
><svg
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline></svg
></span>
</span>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var card = document.getElementById("card");
var btn = document.getElementById("follow-btn");
var textFollow = document.getElementById("btn-follow");
var textFollowing = document.getElementById("btn-following");
// Start off-screen
gsap.set(card, { y: 300, opacity: 0 });
// Slide in from bottom
tl.to(
card,
{
y: 0,
opacity: 1,
duration: 0.5,
ease: "power3.out",
},
0.1,
);
// Hold, then button press-in
tl.to(
btn,
{
scale: 0.92,
duration: 0.15,
ease: "power2.out",
},
1.0,
);
// Button release with spring bounce
tl.to(
btn,
{
scale: 1,
duration: 0.4,
ease: "elastic.out(1, 0.4)",
},
1.15,
);
// Button background: rose-red → dark gray
tl.to(
btn,
{
backgroundColor: "#2f2f2f",
duration: 0.12,
ease: "none",
},
1.15,
);
// Fade out "Follow", fade in "Following"
tl.to(
textFollow,
{
opacity: 0,
duration: 0.08,
ease: "none",
},
1.15,
);
tl.to(
textFollowing,
{
opacity: 1,
duration: 0.08,
ease: "none",
},
1.18,
);
// Slide out to bottom
tl.to(
card,
{
y: 300,
opacity: 0,
duration: 0.25,
ease: "power3.in",
},
3.8,
);
window.__timelines["tiktok-follow-overlay"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,20 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "ui-3d-reveal",
"type": "hyperframes:block",
"title": "3D UI Reveal",
"description": "Perspective 3D reveal animation for UI elements",
"tags": ["showcase", "3d", "reveal"],
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 13,
"files": [
{
"path": "ui-3d-reveal.html",
"target": "compositions/ui-3d-reveal.html",
"type": "hyperframes:composition"
}
]
}
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "x-post",
"type": "hyperframes:block",
"title": "X Post Card",
"description": "Animated X/Twitter post card overlay with engagement metrics",
"tags": ["social", "overlay", "twitter"],
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 5,
"files": [
{
"path": "x-post.html",
"target": "compositions/x-post.html",
"type": "hyperframes:composition"
}
]
}
+442
View File
@@ -0,0 +1,442 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=block"
rel="stylesheet"
/>
<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 {
background: transparent;
overflow: hidden;
}
body {
width: 1920px;
height: 1080px;
font-family: "Inter", sans-serif;
}
.x-card {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 880px;
background: #15202b;
border-radius: 20px;
padding: 36px 36px 28px;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
border: 1px solid #38444d;
overflow: hidden;
}
/* Bottom glow */
.x-card::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 80px;
background: linear-gradient(to top, rgba(29, 155, 240, 0.08), transparent);
pointer-events: none;
}
/* Header row */
.post-header {
display: flex;
align-items: flex-start;
gap: 14px;
margin-bottom: 18px;
position: relative;
}
.avatar {
width: 52px;
height: 52px;
flex-shrink: 0;
border-radius: 50%;
overflow: hidden;
}
.avatar svg {
width: 52px;
height: 52px;
display: block;
}
.user-info {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
}
.name-row {
display: flex;
align-items: center;
gap: 6px;
}
.display-name {
font-size: 26px;
font-weight: 700;
color: #e7e9ea;
line-height: 1.3;
}
.verified-badge svg {
width: 22px;
height: 22px;
display: block;
}
.handle {
font-size: 22px;
font-weight: 400;
color: #8b98a5;
line-height: 1.3;
}
/* X logo top-right */
.x-logo {
position: absolute;
top: 2px;
right: 0;
width: 32px;
height: 32px;
}
.x-logo svg {
width: 32px;
height: 32px;
display: block;
}
/* Tweet body */
.tweet-body {
font-size: 30px;
font-weight: 400;
color: #e7e9ea;
line-height: 1.45;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
.hashtag {
color: #1d9bf0;
}
/* Timestamp */
.timestamp {
font-size: 22px;
color: #8b98a5;
padding-bottom: 20px;
border-bottom: 1px solid #38444d;
margin-bottom: 16px;
}
/* Metrics bar — matches real X spacing */
.metrics-bar {
display: flex;
align-items: center;
padding: 0;
position: relative;
z-index: 1;
}
.metrics-left {
display: flex;
align-items: center;
flex: 1;
justify-content: space-between;
padding-right: 60px;
}
.metrics-right {
display: flex;
align-items: center;
gap: 20px;
}
.metric {
display: flex;
align-items: center;
gap: 8px;
}
.metric-icon {
width: 35px;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
}
.metric-icon svg {
width: 35px;
height: 35px;
display: block;
}
.metric-count {
font-size: 20px;
font-weight: 400;
color: #8b98a5;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="x-post-overlay"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="5"
>
<div id="card" class="clip x-card" data-start="0" data-duration="5" data-track-index="0">
<!-- Header: prompt to replace display name, handle, and avatar -->
<div class="post-header">
<div class="avatar">
<svg viewBox="0 0 52 52" fill="none">
<circle cx="26" cy="26" r="26" fill="#536471" />
<circle cx="26" cy="21" r="10" fill="#8b98a5" />
<ellipse cx="26" cy="42" rx="16" ry="12" fill="#8b98a5" />
</svg>
</div>
<div class="user-info">
<div class="name-row">
<span class="display-name">Hyperframes</span>
<span class="verified-badge">
<svg viewBox="0 0 22 22" fill="none">
<circle cx="11" cy="11" r="11" fill="#1d9bf0" />
<path
d="M9.5 14.25L6.25 11l1.06-1.06 2.19 2.19 4.69-4.69L15.25 8.5 9.5 14.25z"
fill="#fff"
/>
</svg>
</span>
</div>
<span class="handle">@hyperframes</span>
</div>
<div class="x-logo">
<svg viewBox="0 0 24 24" fill="#e7e9ea">
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</div>
</div>
<!-- Tweet body: prompt to replace with your content -->
<div class="tweet-body">
Prompt to replace this text with your tweet. Change the display name, handle, timestamp,
and engagement counts to match your content. <span class="hashtag">#YourHashtag</span>
</div>
<!-- Timestamp: prompt to replace -->
<div class="timestamp">1:10 PM &middot; Apr 7, 2026</div>
<!-- Metrics bar -->
<div class="metrics-bar">
<div class="metrics-left">
<!-- Reply -->
<div class="metric">
<div class="metric-icon">
<svg viewBox="0 0 24 24" fill="#8b98a5">
<path
d="M1.751 10c0-4.42 3.584-8 8.005-8h4.366c4.49 0 8.129 3.64 8.129 8.13 0 2.96-1.607 5.68-4.196 7.11l-8.054 4.46v-3.69h-.067c-4.49.1-8.183-3.51-8.183-8.01zm8.005-6c-3.317 0-6.005 2.69-6.005 6 0 3.37 2.77 6.08 6.138 6.01l.351-.01h1.761v2.3l5.087-2.81c1.951-1.08 3.163-3.13 3.163-5.36 0-3.39-2.744-6.13-6.129-6.13H9.756z"
/>
</svg>
</div>
<span class="metric-count">34</span>
</div>
<!-- Repost -->
<div class="metric">
<div class="metric-icon">
<svg viewBox="0 0 24 24" fill="#8b98a5">
<path
d="M4.5 3.88l4.432 4.14-1.364 1.46L5.5 7.55V16c0 1.1.896 2 2 2H13v2H7.5c-2.209 0-4-1.79-4-4V7.55L1.432 9.48.068 8.02 4.5 3.88zM16.5 6H11V4h5.5c2.209 0 4 1.79 4 4v8.45l2.068-1.93 1.364 1.46-4.432 4.14-4.432-4.14 1.364-1.46 2.068 1.93V8c0-1.1-.896-2-2-2z"
/>
</svg>
</div>
<span class="metric-count">2.3K</span>
</div>
<!-- Like -->
<div id="like-btn" class="metric">
<div class="metric-icon">
<svg viewBox="0 0 24 24">
<!-- Outline heart (visible initially) -->
<path
id="heart-outline"
d="M16.697 5.5c-1.222-.06-2.679.51-3.89 2.16l-.805 1.09-.806-1.09C9.984 6.01 8.526 5.44 7.304 5.5c-1.243.07-2.349.78-2.91 1.91-.552 1.12-.633 2.78.479 4.82 1.074 1.97 3.257 4.27 7.129 6.61 3.87-2.34 6.052-4.64 7.126-6.61 1.111-2.04 1.03-3.7.477-4.82-.561-1.13-1.666-1.84-2.908-1.91zm4.187 7.69c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z"
fill="#8b98a5"
/>
<!-- Filled heart (hidden initially, shown on like) -->
<path
id="heart-filled"
d="M20.884 13.19c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z"
fill="#f91880"
opacity="0"
/>
</svg>
</div>
<span id="like-count" class="metric-count">10.9K</span>
</div>
<!-- Views -->
<div class="metric">
<div class="metric-icon">
<svg viewBox="0 0 24 24" fill="#8b98a5">
<path
d="M8.75 21V3h2v18h-2zM18 21V8.5h2V21h-2zM4 21l.004-10h2L6 21H4zm9.248 0v-7h2v7h-2z"
/>
</svg>
</div>
<span class="metric-count">150K</span>
</div>
</div>
<div class="metrics-right">
<!-- Bookmark -->
<div class="metric">
<div class="metric-icon">
<svg viewBox="0 0 24 24" fill="#8b98a5">
<path
d="M4 4.5C4 3.12 5.119 2 6.5 2h11C18.881 2 20 3.12 20 4.5v18.44l-8-5.71-8 5.71V4.5zM6.5 4c-.276 0-.5.22-.5.5v14.56l6-4.29 6 4.29V4.5c0-.28-.224-.5-.5-.5h-11z"
/>
</svg>
</div>
</div>
<!-- Share -->
<div class="metric">
<div class="metric-icon">
<svg viewBox="0 0 24 24" fill="#8b98a5">
<path
d="M12 2.59l5.7 5.7-1.41 1.42L13 6.41V16h-2V6.41l-3.3 3.3-1.41-1.42L12 2.59zM21 15l-.02 3.51c0 1.38-1.12 2.49-2.5 2.49H5.5C4.11 21 3 19.88 3 18.5V15h2v3.5c0 .28.22.5.5.5h12.98c.28 0 .5-.22.5-.5L19 15h2z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var card = document.getElementById("card");
var likeBtn = document.getElementById("like-btn");
var heartOutline = document.getElementById("heart-outline");
var heartFilled = document.getElementById("heart-filled");
var likeCount = document.getElementById("like-count");
// Start off-screen below
gsap.set(card, { y: 400, opacity: 0 });
// Slide in from bottom
tl.to(
card,
{
y: 0,
opacity: 1,
duration: 0.6,
ease: "power3.out",
},
0.1,
);
// Like press-in
tl.to(
likeBtn,
{
scale: 0.85,
duration: 0.12,
ease: "power2.out",
},
1.5,
);
// Like release with spring bounce
tl.to(
likeBtn,
{
scale: 1,
duration: 0.4,
ease: "elastic.out(1, 0.4)",
},
1.62,
);
// Hide outline heart
tl.to(
heartOutline,
{
opacity: 0,
duration: 0.01,
ease: "none",
},
1.62,
);
// Show filled heart
tl.to(
heartFilled,
{
opacity: 1,
duration: 0.12,
ease: "none",
},
1.62,
);
// Like count turns pink + increments
tl.to(
likeCount,
{
color: "#f91880",
duration: 0.12,
ease: "none",
onStart: function () {
likeCount.textContent = "11.0K";
},
},
1.62,
);
// Slide out to bottom
tl.to(
card,
{
y: 400,
opacity: 0,
duration: 0.3,
ease: "power3.in",
},
4.2,
);
window.__timelines["x-post-overlay"] = tl;
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -0,0 +1,25 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "yt-lower-third",
"type": "hyperframes:block",
"title": "YouTube Lower Third",
"description": "Animated YouTube subscribe lower third with avatar and channel info",
"tags": ["social", "overlay", "youtube"],
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 4.5,
"files": [
{
"path": "yt-lower-third.html",
"target": "compositions/yt-lower-third.html",
"type": "hyperframes:composition"
},
{
"path": "assets/avatar.jpg",
"target": "assets/avatar.jpg",
"type": "hyperframes:asset"
}
]
}
@@ -0,0 +1,242 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=block"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
background: transparent;
overflow: hidden;
}
body {
width: 1920px;
height: 1080px;
font-family: "DM Sans", sans-serif;
}
.lower-third {
position: absolute;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 28px;
background: #ffffff;
border-radius: 60px;
padding: 20px 32px 20px 20px;
box-shadow: 0 6px 32px rgba(0, 0, 0, 0.14);
}
.avatar {
width: 96px;
height: 96px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.channel-info {
display: flex;
flex-direction: column;
gap: 2px;
margin-right: 20px;
}
.channel-name {
font-size: 34px;
font-weight: 700;
color: #0f0f0f;
line-height: 1.3;
letter-spacing: -0.01em;
}
.subscriber-count {
font-size: 22px;
font-weight: 400;
color: #606060;
line-height: 1.3;
}
.subscribe-btn {
position: relative;
width: 220px;
height: 68px;
border-radius: 34px;
background: #0f0f0f;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.btn-text {
position: absolute;
font-family: "DM Sans", sans-serif;
font-size: 24px;
font-weight: 700;
color: #ffffff;
letter-spacing: 0.02em;
white-space: nowrap;
}
.btn-text-subscribe {
opacity: 1;
}
.btn-text-subscribed {
opacity: 0;
display: flex;
align-items: center;
gap: 8px;
}
.check-icon {
display: inline-block;
width: 24px;
height: 24px;
}
.check-icon svg {
width: 24px;
height: 24px;
display: block;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="yt-lower-third"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="4.5"
>
<div
id="card"
class="clip lower-third"
data-start="0"
data-duration="4.5"
data-track-index="0"
>
<img class="avatar" src="assets/avatar.jpg" alt="HeyGen" crossorigin="anonymous" />
<div class="channel-info">
<div class="channel-name">HeyGen</div>
<div class="subscriber-count">82.2K subscribers</div>
</div>
<div id="subscribe-btn" class="subscribe-btn">
<span id="btn-subscribe" class="btn-text btn-text-subscribe">Subscribe</span>
<span id="btn-subscribed" class="btn-text btn-text-subscribed">
<span class="check-icon"
><svg
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline></svg
></span>
Subscribed
</span>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var card = document.getElementById("card");
var btn = document.getElementById("subscribe-btn");
var textSub = document.getElementById("btn-subscribe");
var textSubd = document.getElementById("btn-subscribed");
// Slide in from bottom
gsap.set(card, { y: 300, opacity: 0 });
tl.to(
card,
{
y: 0,
opacity: 1,
duration: 0.5,
ease: "power3.out",
},
0.1,
);
// Hold, then button press-in
tl.to(
btn,
{
scale: 0.92,
duration: 0.15,
ease: "power2.out",
},
1.0,
);
// Button release with spring bounce + text swap
tl.to(
btn,
{
scale: 1,
duration: 0.4,
ease: "elastic.out(1, 0.4)",
},
1.15,
);
// Fade out "Subscribe", fade in "Subscribed"
tl.to(
textSub,
{
opacity: 0,
duration: 0.08,
ease: "none",
},
1.15,
);
tl.to(
textSubd,
{
opacity: 1,
duration: 0.08,
ease: "none",
},
1.18,
);
// Slide out to bottom (inverse of entrance)
tl.to(
card,
{
y: 300,
opacity: 0,
duration: 0.25,
ease: "power3.in",
},
3.8,
);
window.__timelines["yt-lower-third"] = tl;
</script>
</body>
</html>
+44
View File
@@ -58,6 +58,50 @@
{
"name": "grid-pixelate-wipe",
"type": "hyperframes:component"
},
{
"name": "instagram-follow",
"type": "hyperframes:block"
},
{
"name": "tiktok-follow",
"type": "hyperframes:block"
},
{
"name": "yt-lower-third",
"type": "hyperframes:block"
},
{
"name": "x-post",
"type": "hyperframes:block"
},
{
"name": "reddit-post",
"type": "hyperframes:block"
},
{
"name": "spotify-card",
"type": "hyperframes:block"
},
{
"name": "macos-notification",
"type": "hyperframes:block"
},
{
"name": "ascii-dashboard",
"type": "hyperframes:block"
},
{
"name": "ascii-lightning",
"type": "hyperframes:block"
},
{
"name": "app-showcase",
"type": "hyperframes:block"
},
{
"name": "ui-3d-reveal",
"type": "hyperframes:block"
}
]
}