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
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
|
After Width: | Height: | Size: 992 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 724 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 73 KiB |
@@ -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 (0–2s)
|
||||
// ════════════════════════════════════
|
||||
|
||||
// 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 (2–4s)
|
||||
// ════════════════════════════════════
|
||||
|
||||
// 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 (4–5.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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
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 · 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>
|
||||
|
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 · 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>
|
||||
|
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>
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||