mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-02 12:08:50 +00:00
feat: add Stronkter catalog blocks (#570)
## Problem The Catalog did not include the four prompt-matched Stronkter one-shot HyperFrames projects, and registry metadata only supported a plain author string, so there was no structured way to show creator attribution or the original generation prompt on generated catalog pages. ## What this fixes - Adds four Catalog blocks matching the provided prompts, in order: - `north-korea-locked-down` - `apple-money-count` - `nyc-paris-flight` - `goonvpn-youtube-spot` - Attributes each block to [Stronkter](https://x.com/Stronkter). - Stores and renders the original source prompt for each generated catalog page. - Adds a local realistic map plate for the North Korea block so rendering does not depend on live map tile requests. - Extends registry item metadata/schema with `authorUrl` and `sourcePrompt`. - Updates catalog page generation to read items from `registry/registry.json`, keeping generated docs aligned to the public registry manifest. - Ignores normal browser media preload `net::ERR_ABORTED` request failures for media assets during `hyperframes validate`, while preserving failures for real missing assets. ## Root cause The imported projects are Catalog-ready compositions, but the registry/docs pipeline did not have first-class source-prompt or linked-author fields to expose creator credit on generated MDX pages. The audio-backed compositions also surfaced a validation edge case: Chrome can report aborted media preload requests as `net::ERR_ABORTED` even when the audio file exists and playback is valid. ## Verification ### Local - `bun run --filter @hyperframes/cli test src/commands/validate.test.ts` - `bun run --filter @hyperframes/core test src/registry/types.test.ts` - `bun run sync-schemas:check` - `bunx oxlint packages/cli/src/commands/validate.ts packages/cli/src/commands/validate.test.ts packages/core/src/registry/types.ts packages/core/src/registry/types.test.ts scripts/generate-catalog-pages.ts` - `bunx oxfmt --check ...` on changed source, registry, docs, and composition files - `git diff --check` - `bun packages/cli/src/cli.ts lint` and `validate` against temp installed projects for all four blocks - Lefthook pre-commit: lint/format/typecheck on the initial commit, plus format on the amend - Lefthook commit-msg: commitlint ### Browser - Exercised all four blocks through HyperFrames preview routes with `agent-browser`. - Captured playback screenshots and WebM recordings for: - `north-korea-locked-down` - `apple-money-count` - `nyc-paris-flight` - `goonvpn-youtube-spot` ## Notes - The zip also contained unrelated project directories, but this PR intentionally includes only the four prompt-matched Catalog blocks requested here. - The imported one-shot compositions may trigger the existing large-composition lint warning, but there are no lint errors and runtime validation passes.
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
title: "Apple Money Count"
|
||||||
|
description: "Apple-style finance counter that counts from $0 to $10,000, flashes green, and bursts money icons with sound."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Apple Money Count
|
||||||
|
|
||||||
|
Apple-style finance counter that counts from $0 to $10,000, flashes green, and bursts money icons with sound.
|
||||||
|
|
||||||
|
`showcase` `finance` `kinetic` `youtube` `sfx`
|
||||||
|
|
||||||
|
Created by [Stronkter](https://x.com/Stronkter).
|
||||||
|
|
||||||
|
## Source Prompt
|
||||||
|
|
||||||
|
```text
|
||||||
|
📷HyperFrames by HeyGen Make me a five-second video of, on a white background, of a Apple-style bold font counting from $0 to $10,000. Once it counts to $10,000, it changes to a green color and the screen also flashes green for a second, and then money icons come out of the $10,000 amount all over the screen and then disappear.
|
||||||
|
```
|
||||||
|
|
||||||
|
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/apple-money-count.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/apple-money-count.png" autoPlay muted loop playsInline />
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
<CodeGroup>
|
||||||
|
|
||||||
|
```bash Terminal
|
||||||
|
npx hyperframes add apple-money-count
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Type | Block |
|
||||||
|
| Dimensions | 1920×1080 |
|
||||||
|
| Duration | 5s |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Target | Type |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `apple-money-count.html` | `compositions/apple-money-count.html` | hyperframes:composition |
|
||||||
|
| `assets/sfx-production.wav` | `assets/sfx-production.wav` | hyperframes:asset |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
After installing, add the block to your host composition:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div data-composition-id="apple-money-count" data-composition-src="compositions/apple-money-count.html" data-start="0" data-duration="5" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||||
|
```
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
title: "GoonVPN YouTube Spot"
|
||||||
|
description: "Snappy Apple-style YouTube insert showing a phone finding and installing a fictional VPN app with sound effects."
|
||||||
|
---
|
||||||
|
|
||||||
|
# GoonVPN YouTube Spot
|
||||||
|
|
||||||
|
Snappy Apple-style YouTube insert showing a phone finding and installing a fictional VPN app with sound effects.
|
||||||
|
|
||||||
|
`app` `showcase` `youtube` `sfx`
|
||||||
|
|
||||||
|
Created by [Stronkter](https://x.com/Stronkter).
|
||||||
|
|
||||||
|
## Source Prompt
|
||||||
|
|
||||||
|
```text
|
||||||
|
📷HyperFrames by HeyGen make me a 7s video apple style bold font and styling a phone scrolling in a appstore clicking on a vpn app called goonVPN and installing it the app installs the phone snaps down and fades to a white backround make it snappy and pop its for a youtube video also add soundfx and make it 60fps and 1920x1080
|
||||||
|
```
|
||||||
|
|
||||||
|
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/goonvpn-youtube-spot.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/goonvpn-youtube-spot.png" autoPlay muted loop playsInline />
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
<CodeGroup>
|
||||||
|
|
||||||
|
```bash Terminal
|
||||||
|
npx hyperframes add goonvpn-youtube-spot
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Type | Block |
|
||||||
|
| Dimensions | 1920×1080 |
|
||||||
|
| Duration | 7s |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Target | Type |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `goonvpn-youtube-spot.html` | `compositions/goonvpn-youtube-spot.html` | hyperframes:composition |
|
||||||
|
| `assets/goonvpn-sfx.wav` | `assets/goonvpn-sfx.wav` | hyperframes:asset |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
After installing, add the block to your host composition:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div data-composition-id="goonvpn-youtube-spot" data-composition-src="compositions/goonvpn-youtube-spot.html" data-start="0" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||||
|
```
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
title: "North Korea Locked Down"
|
||||||
|
description: "Realistic map zoom into North Korea with a red scribble circle, locked-down pop-up label, and reddish editorial wash."
|
||||||
|
---
|
||||||
|
|
||||||
|
# North Korea Locked Down
|
||||||
|
|
||||||
|
Realistic map zoom into North Korea with a red scribble circle, locked-down pop-up label, and reddish editorial wash.
|
||||||
|
|
||||||
|
`showcase` `map` `annotation` `youtube` `kinetic`
|
||||||
|
|
||||||
|
Created by [Stronkter](https://x.com/Stronkter).
|
||||||
|
|
||||||
|
## Source Prompt
|
||||||
|
|
||||||
|
```text
|
||||||
|
use 📷HyperFrames by HeyGen and Image Gen if you need it for assets or like png images of assets without backround to make a youtube style camera moving in out and other things that are in youtube videos, to make a video of a map zooms in on north korea and a scribble style circle circles the country and a text pops up above it saying locked down when the text apears the video turns a bit redish make the video 7 seconds long id like the map to look realistic and accurate to real lfe
|
||||||
|
```
|
||||||
|
|
||||||
|
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/north-korea-locked-down.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/north-korea-locked-down.png" autoPlay muted loop playsInline />
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
<CodeGroup>
|
||||||
|
|
||||||
|
```bash Terminal
|
||||||
|
npx hyperframes add north-korea-locked-down
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Type | Block |
|
||||||
|
| Dimensions | 1920×1080 |
|
||||||
|
| Duration | 7s |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Target | Type |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `north-korea-locked-down.html` | `compositions/north-korea-locked-down.html` | hyperframes:composition |
|
||||||
|
| `assets/korea-map.png` | `assets/korea-map.png` | hyperframes:asset |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
After installing, add the block to your host composition:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div data-composition-id="north-korea-locked-down" data-composition-src="compositions/north-korea-locked-down.html" data-start="0" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||||
|
```
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: "NYC Paris Flight"
|
||||||
|
description: "Apple-style realistic map animation with a plane flying from New York to Paris, marker circle, landing pop, and sound effects."
|
||||||
|
---
|
||||||
|
|
||||||
|
# NYC Paris Flight
|
||||||
|
|
||||||
|
Apple-style realistic map animation with a plane flying from New York to Paris, marker circle, landing pop, and sound effects.
|
||||||
|
|
||||||
|
`showcase` `travel` `map` `youtube` `sfx`
|
||||||
|
|
||||||
|
Created by [Stronkter](https://x.com/Stronkter).
|
||||||
|
|
||||||
|
## Source Prompt
|
||||||
|
|
||||||
|
```text
|
||||||
|
📷HyperFrames by HeyGen Make a six-second Apple-style font bold video of a plane going from New York to Paris. A map animation, and then it shows the plane going from New York to Paris. Make the fonts Apple-style bold and make the map actual realistic, actually realistic. Before it lands in Paris, do a doodle circle in red around Paris, and then it lands in the doodle circle, and the video basically cuts to a white screen at the end. Also add sound effects for everything as well. Every nice motion, every the plane for pop-ups, bubble, pop-up effect, everything you want.
|
||||||
|
```
|
||||||
|
|
||||||
|
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/nyc-paris-flight.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/nyc-paris-flight.png" autoPlay muted loop playsInline />
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
<CodeGroup>
|
||||||
|
|
||||||
|
```bash Terminal
|
||||||
|
npx hyperframes add nyc-paris-flight
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Type | Block |
|
||||||
|
| Dimensions | 1920×1080 |
|
||||||
|
| Duration | 6s |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Target | Type |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `nyc-paris-flight.html` | `compositions/nyc-paris-flight.html` | hyperframes:composition |
|
||||||
|
| `assets/map-nyc-paris.png` | `assets/map-nyc-paris.png` | hyperframes:asset |
|
||||||
|
| `assets/sfx-mix.wav` | `assets/sfx-mix.wav` | hyperframes:asset |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
After installing, add the block to your host composition:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div data-composition-id="nyc-paris-flight" data-composition-src="compositions/nyc-paris-flight.html" data-start="0" data-duration="6" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||||
|
```
|
||||||
@@ -141,6 +141,10 @@
|
|||||||
"group": "Showcases",
|
"group": "Showcases",
|
||||||
"pages": [
|
"pages": [
|
||||||
"catalog/blocks/app-showcase",
|
"catalog/blocks/app-showcase",
|
||||||
|
"catalog/blocks/apple-money-count",
|
||||||
|
"catalog/blocks/goonvpn-youtube-spot",
|
||||||
|
"catalog/blocks/north-korea-locked-down",
|
||||||
|
"catalog/blocks/nyc-paris-flight",
|
||||||
"catalog/blocks/ui-3d-reveal"
|
"catalog/blocks/ui-3d-reveal"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,21 @@
|
|||||||
"href": "/catalog/blocks/app-showcase",
|
"href": "/catalog/blocks/app-showcase",
|
||||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/app-showcase.png"
|
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/app-showcase.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "apple-money-count",
|
||||||
|
"type": "block",
|
||||||
|
"title": "Apple Money Count",
|
||||||
|
"description": "Apple-style finance counter that counts from $0 to $10,000, flashes green, and bursts money icons with sound.",
|
||||||
|
"tags": [
|
||||||
|
"showcase",
|
||||||
|
"finance",
|
||||||
|
"kinetic",
|
||||||
|
"youtube",
|
||||||
|
"sfx"
|
||||||
|
],
|
||||||
|
"href": "/catalog/blocks/apple-money-count",
|
||||||
|
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/apple-money-count.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "chromatic-radial-split",
|
"name": "chromatic-radial-split",
|
||||||
"type": "block",
|
"type": "block",
|
||||||
@@ -110,6 +125,20 @@
|
|||||||
"href": "/catalog/blocks/glitch",
|
"href": "/catalog/blocks/glitch",
|
||||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/glitch.png"
|
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/glitch.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "goonvpn-youtube-spot",
|
||||||
|
"type": "block",
|
||||||
|
"title": "GoonVPN YouTube Spot",
|
||||||
|
"description": "Snappy Apple-style YouTube insert showing a phone finding and installing a fictional VPN app with sound effects.",
|
||||||
|
"tags": [
|
||||||
|
"app",
|
||||||
|
"showcase",
|
||||||
|
"youtube",
|
||||||
|
"sfx"
|
||||||
|
],
|
||||||
|
"href": "/catalog/blocks/goonvpn-youtube-spot",
|
||||||
|
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/goonvpn-youtube-spot.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "grain-overlay",
|
"name": "grain-overlay",
|
||||||
"type": "component",
|
"type": "component",
|
||||||
@@ -201,6 +230,36 @@
|
|||||||
"href": "/catalog/blocks/macos-notification",
|
"href": "/catalog/blocks/macos-notification",
|
||||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/macos-notification.png"
|
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/macos-notification.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "north-korea-locked-down",
|
||||||
|
"type": "block",
|
||||||
|
"title": "North Korea Locked Down",
|
||||||
|
"description": "Realistic map zoom into North Korea with a red scribble circle, locked-down pop-up label, and reddish editorial wash.",
|
||||||
|
"tags": [
|
||||||
|
"showcase",
|
||||||
|
"map",
|
||||||
|
"annotation",
|
||||||
|
"youtube",
|
||||||
|
"kinetic"
|
||||||
|
],
|
||||||
|
"href": "/catalog/blocks/north-korea-locked-down",
|
||||||
|
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/north-korea-locked-down.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nyc-paris-flight",
|
||||||
|
"type": "block",
|
||||||
|
"title": "NYC Paris Flight",
|
||||||
|
"description": "Apple-style realistic map animation with a plane flying from New York to Paris, marker circle, landing pop, and sound effects.",
|
||||||
|
"tags": [
|
||||||
|
"showcase",
|
||||||
|
"travel",
|
||||||
|
"map",
|
||||||
|
"youtube",
|
||||||
|
"sfx"
|
||||||
|
],
|
||||||
|
"href": "/catalog/blocks/nyc-paris-flight",
|
||||||
|
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/nyc-paris-flight.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reddit-post",
|
"name": "reddit-post",
|
||||||
"type": "block",
|
"type": "block",
|
||||||
|
|||||||
@@ -35,6 +35,14 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
|
"authorUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"sourcePrompt": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { shouldIgnoreRequestFailure } from "./validate.js";
|
||||||
|
|
||||||
|
describe("shouldIgnoreRequestFailure", () => {
|
||||||
|
it("ignores aborted media preload requests", () => {
|
||||||
|
expect(
|
||||||
|
shouldIgnoreRequestFailure("http://127.0.0.1:3000/assets/sfx.wav", "net::ERR_ABORTED"),
|
||||||
|
).toBe(true);
|
||||||
|
expect(shouldIgnoreRequestFailure("http://127.0.0.1:3000/video.mp4", "net::ERR_ABORTED")).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps non-media and non-aborted failures reportable", () => {
|
||||||
|
expect(
|
||||||
|
shouldIgnoreRequestFailure("http://127.0.0.1:3000/assets/map.png", "net::ERR_ABORTED"),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
shouldIgnoreRequestFailure("http://127.0.0.1:3000/assets/sfx.wav", "net::ERR_FAILED"),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -29,6 +29,16 @@ interface ContrastEntry {
|
|||||||
|
|
||||||
const CONTRAST_SAMPLES = 5;
|
const CONTRAST_SAMPLES = 5;
|
||||||
const SEEK_SETTLE_MS = 150;
|
const SEEK_SETTLE_MS = 150;
|
||||||
|
const MEDIA_EXTENSIONS = /\.(aac|flac|m4a|mov|mp3|mp4|oga|ogg|wav|webm)$/i;
|
||||||
|
|
||||||
|
export function shouldIgnoreRequestFailure(url: string, errorText: string | undefined): boolean {
|
||||||
|
if (errorText !== "net::ERR_ABORTED") return false;
|
||||||
|
try {
|
||||||
|
return MEDIA_EXTENSIONS.test(new URL(url).pathname);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getCompositionDuration(page: import("puppeteer-core").Page): Promise<number> {
|
async function getCompositionDuration(page: import("puppeteer-core").Page): Promise<number> {
|
||||||
return page.evaluate(() => {
|
return page.evaluate(() => {
|
||||||
@@ -183,10 +193,12 @@ async function validateInBrowser(
|
|||||||
page.on("requestfailed", (req) => {
|
page.on("requestfailed", (req) => {
|
||||||
const url = req.url();
|
const url = req.url();
|
||||||
if (url.includes("favicon") || url.startsWith("data:")) return;
|
if (url.includes("favicon") || url.startsWith("data:")) return;
|
||||||
|
const failureText = req.failure()?.errorText;
|
||||||
|
if (shouldIgnoreRequestFailure(url, failureText)) return;
|
||||||
const path = decodeURIComponent(new URL(url).pathname).replace(/^\//, "");
|
const path = decodeURIComponent(new URL(url).pathname).replace(/^\//, "");
|
||||||
errors.push({
|
errors.push({
|
||||||
level: "error",
|
level: "error",
|
||||||
text: `Failed to load ${path}: ${req.failure()?.errorText ?? "net::ERR_FAILED"}`,
|
text: `Failed to load ${path}: ${failureText ?? "net::ERR_FAILED"}`,
|
||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,6 +35,14 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
|
"authorUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"sourcePrompt": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
|
|||||||
@@ -212,13 +212,15 @@ describe("registry types", () => {
|
|||||||
expect(true).toBe(true);
|
expect(true).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("optional metadata fields are accepted (author, license, deprecated, minCliVersion)", () => {
|
it("optional metadata fields are accepted", () => {
|
||||||
const item: ComponentItem = {
|
const item: ComponentItem = {
|
||||||
name: "shader-wipe",
|
name: "shader-wipe",
|
||||||
type: "hyperframes:component",
|
type: "hyperframes:component",
|
||||||
title: "Shader Wipe",
|
title: "Shader Wipe",
|
||||||
description: "d",
|
description: "d",
|
||||||
author: "heygen",
|
author: "heygen",
|
||||||
|
authorUrl: "https://example.com/heygen",
|
||||||
|
sourcePrompt: "Create a shader wipe.",
|
||||||
license: "Apache-2.0",
|
license: "Apache-2.0",
|
||||||
minCliVersion: "0.4.0",
|
minCliVersion: "0.4.0",
|
||||||
deprecated: "Use `shader-wipe-v2` instead.",
|
deprecated: "Use `shader-wipe-v2` instead.",
|
||||||
@@ -231,6 +233,8 @@ describe("registry types", () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
expect(item.author).toBe("heygen");
|
expect(item.author).toBe("heygen");
|
||||||
|
expect(item.authorUrl).toBe("https://example.com/heygen");
|
||||||
|
expect(item.sourcePrompt).toBe("Create a shader wipe.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ interface RegistryItemBase {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
/** Item author / maintainer. */
|
/** Item author / maintainer. */
|
||||||
author?: string;
|
author?: string;
|
||||||
|
/** URL for the author / creator credit. */
|
||||||
|
authorUrl?: string;
|
||||||
|
/** Original prompt used to create or inspire the item. */
|
||||||
|
sourcePrompt?: string;
|
||||||
/** SPDX license identifier. */
|
/** SPDX license identifier. */
|
||||||
license?: string;
|
license?: string;
|
||||||
/** Minimum `hyperframes` CLI version required to install this item (semver). */
|
/** Minimum `hyperframes` CLI version required to install this item (semver). */
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=1920, height=1080" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fdfefe;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
position: relative;
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fdfefe;
|
||||||
|
color: #111315;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-flash {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: #30d158;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 120px 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-wrap {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 1080px;
|
||||||
|
min-height: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
position: relative;
|
||||||
|
color: #111315;
|
||||||
|
font-size: 190px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 0.9;
|
||||||
|
letter-spacing: 0;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-shadow:
|
||||||
|
0 3px 0 rgba(255, 255, 255, 0.58),
|
||||||
|
0 18px 36px rgba(17, 19, 21, 0.14),
|
||||||
|
0 42px 92px rgba(17, 19, 21, 0.1);
|
||||||
|
will-change: transform, color, opacity, text-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.money-burst {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 8;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.money-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
filter: drop-shadow(0 20px 24px rgba(7, 84, 31, 0.2));
|
||||||
|
will-change: transform, opacity, filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.money-icon.bill {
|
||||||
|
width: 96px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #30d158;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 3px rgba(7, 84, 31, 0.2),
|
||||||
|
inset 0 -10px 18px rgba(7, 84, 31, 0.1),
|
||||||
|
0 16px 26px rgba(7, 84, 31, 0.2),
|
||||||
|
0 34px 64px rgba(7, 84, 31, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.money-icon.bill::before {
|
||||||
|
position: absolute;
|
||||||
|
inset: 9px 16px;
|
||||||
|
border: 2px solid rgba(7, 84, 31, 0.28);
|
||||||
|
border-radius: 999px;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.money-icon.coin {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle at 34% 30%, #fff7a6 0%, #ffd54f 40%, #d9a514 100%);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 4px rgba(111, 76, 0, 0.12),
|
||||||
|
inset 0 -8px 14px rgba(111, 76, 0, 0.1),
|
||||||
|
0 16px 30px rgba(111, 76, 0, 0.18),
|
||||||
|
0 34px 58px rgba(111, 76, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.money-icon .mark {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
color: #07541f;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.money-icon.coin .mark {
|
||||||
|
color: #6f4c00;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div
|
||||||
|
id="root"
|
||||||
|
data-composition-id="apple-money-count"
|
||||||
|
data-root="true"
|
||||||
|
data-start="0"
|
||||||
|
data-duration="5"
|
||||||
|
data-width="1920"
|
||||||
|
data-height="1080"
|
||||||
|
>
|
||||||
|
<div id="greenFlash" class="green-flash"></div>
|
||||||
|
<div class="stage-content">
|
||||||
|
<div class="amount-wrap">
|
||||||
|
<div id="amount" class="amount">$0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="moneyBurst" class="money-burst" data-layout-allow-overflow></div>
|
||||||
|
<audio
|
||||||
|
id="sfxTrack"
|
||||||
|
data-start="0"
|
||||||
|
data-duration="5"
|
||||||
|
data-track-index="9"
|
||||||
|
src="assets/sfx-production.wav"
|
||||||
|
data-volume="0.92"
|
||||||
|
></audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.__timelines = window.__timelines || {};
|
||||||
|
|
||||||
|
const amount = document.querySelector("#amount");
|
||||||
|
const moneyBurst = document.querySelector("#moneyBurst");
|
||||||
|
const countState = { value: 0 };
|
||||||
|
|
||||||
|
function formatMoney(value) {
|
||||||
|
const bounded = Math.max(0, Math.min(10000, value));
|
||||||
|
const rounded = Math.round(bounded);
|
||||||
|
return "$" + rounded.toLocaleString("en-US");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAmount(value) {
|
||||||
|
amount.textContent = formatMoney(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moneyIcons = [];
|
||||||
|
const moneySpecs = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 62; i += 1) {
|
||||||
|
const icon = document.createElement("div");
|
||||||
|
const isCoin = i % 3 === 0;
|
||||||
|
icon.className = "money-icon " + (isCoin ? "coin" : "bill");
|
||||||
|
icon.innerHTML = '<span class="mark">$</span>';
|
||||||
|
moneyBurst.appendChild(icon);
|
||||||
|
moneyIcons.push(icon);
|
||||||
|
|
||||||
|
const angle = i * 2.399963229728653;
|
||||||
|
const ring = i % 5;
|
||||||
|
const radiusX = 260 + ring * 145 + (i % 7) * 12;
|
||||||
|
const radiusY = 160 + ring * 78 + (i % 6) * 12;
|
||||||
|
const offsetX = (i % 4) * 24 - 36;
|
||||||
|
const offsetY = (i % 5) * 18 - 36;
|
||||||
|
const x = Math.max(-865, Math.min(865, Math.cos(angle) * radiusX + offsetX));
|
||||||
|
const y = Math.max(-465, Math.min(465, Math.sin(angle * 1.13) * radiusY + offsetY));
|
||||||
|
|
||||||
|
moneySpecs.push({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
delay: (i % 8) * 0.025,
|
||||||
|
duration: 0.74 + (i % 5) * 0.045,
|
||||||
|
fadeDelay: (i % 5) * 0.05,
|
||||||
|
rotation: ((i * 43) % 160) - 80,
|
||||||
|
scale: isCoin ? 0.72 + (i % 4) * 0.08 : 0.68 + (i % 5) * 0.07,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAmount(0);
|
||||||
|
|
||||||
|
const tl = gsap.timeline({
|
||||||
|
paused: true,
|
||||||
|
defaults: { ease: "power3.out" },
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.set(moneyIcons, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
xPercent: -50,
|
||||||
|
yPercent: -50,
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.18,
|
||||||
|
transformOrigin: "50% 50%",
|
||||||
|
});
|
||||||
|
|
||||||
|
tl.from(
|
||||||
|
"#amount",
|
||||||
|
{
|
||||||
|
y: 26,
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.985,
|
||||||
|
duration: 0.45,
|
||||||
|
ease: "power2.out",
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
tl.to(
|
||||||
|
countState,
|
||||||
|
{
|
||||||
|
value: 10000,
|
||||||
|
duration: 3.16,
|
||||||
|
ease: "none",
|
||||||
|
onUpdate: () => renderAmount(countState.value),
|
||||||
|
onComplete: () => renderAmount(10000),
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
tl.to(
|
||||||
|
"#amount",
|
||||||
|
{
|
||||||
|
color: "#30d158",
|
||||||
|
textShadow:
|
||||||
|
"0 3px 0 rgba(255,255,255,0.52), 0 18px 40px rgba(48,209,88,0.3), 0 46px 96px rgba(7,84,31,0.2)",
|
||||||
|
duration: 0.18,
|
||||||
|
},
|
||||||
|
3.16,
|
||||||
|
);
|
||||||
|
tl.to("#amount", { scale: 1.06, duration: 0.16, ease: "back.out(2.2)" }, 3.16);
|
||||||
|
tl.to("#amount", { scale: 1, duration: 0.23, ease: "power2.out" }, 3.33);
|
||||||
|
|
||||||
|
tl.to("#greenFlash", { opacity: 0.34, duration: 0.08, ease: "none" }, 3.16);
|
||||||
|
tl.to("#greenFlash", { opacity: 0.22, duration: 0.76, ease: "none" }, 3.24);
|
||||||
|
tl.to("#greenFlash", { opacity: 0, duration: 0.16, ease: "power2.out" }, 4);
|
||||||
|
|
||||||
|
moneyIcons.forEach((icon, i) => {
|
||||||
|
const spec = moneySpecs[i];
|
||||||
|
tl.to(
|
||||||
|
icon,
|
||||||
|
{
|
||||||
|
x: spec.x,
|
||||||
|
y: spec.y,
|
||||||
|
rotation: spec.rotation,
|
||||||
|
scale: spec.scale,
|
||||||
|
opacity: 1,
|
||||||
|
duration: spec.duration,
|
||||||
|
ease: "power4.out",
|
||||||
|
},
|
||||||
|
3.28 + spec.delay,
|
||||||
|
);
|
||||||
|
tl.to(
|
||||||
|
icon,
|
||||||
|
{
|
||||||
|
y: spec.y + (spec.y < 0 ? -58 : 58),
|
||||||
|
scale: spec.scale * 0.54,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.38,
|
||||||
|
ease: "power2.in",
|
||||||
|
},
|
||||||
|
4.18 + spec.fadeDelay,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tl.to("#amount", { opacity: 0, scale: 0.985, duration: 0.28, ease: "power2.in" }, 4.36);
|
||||||
|
|
||||||
|
window.__timelines["apple-money-count"] = tl;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||||
|
"name": "apple-money-count",
|
||||||
|
"type": "hyperframes:block",
|
||||||
|
"title": "Apple Money Count",
|
||||||
|
"description": "Apple-style finance counter that counts from $0 to $10,000, flashes green, and bursts money icons with sound.",
|
||||||
|
"tags": ["showcase", "finance", "kinetic", "youtube", "sfx"],
|
||||||
|
"author": "Stronkter",
|
||||||
|
"authorUrl": "https://x.com/Stronkter",
|
||||||
|
"sourcePrompt": "\ud83d\udcf7HyperFrames by HeyGen Make me a five-second video of, on a white background, of a Apple-style bold font counting from $0 to $10,000. Once it counts to $10,000, it changes to a green color and the screen also flashes green for a second, and then money icons come out of the $10,000 amount all over the screen and then disappear.",
|
||||||
|
"dimensions": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
"duration": 5,
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "apple-money-count.html",
|
||||||
|
"target": "compositions/apple-money-count.html",
|
||||||
|
"type": "hyperframes:composition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/sfx-production.wav",
|
||||||
|
"target": "assets/sfx-production.wav",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,987 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=1920, height=1080" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #ffffff;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "SF Pro Display", Inter, "Helvetica Neue", Arial,
|
||||||
|
sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
position: relative;
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #111111;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 20% 16%, rgba(0, 122, 255, 0.13), transparent 24%),
|
||||||
|
radial-gradient(circle at 82% 22%, rgba(52, 199, 89, 0.14), transparent 22%),
|
||||||
|
linear-gradient(180deg, #ffffff 0%, #f5f5f7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 70px 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
position: absolute;
|
||||||
|
left: 118px;
|
||||||
|
top: 104px;
|
||||||
|
width: 580px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline span {
|
||||||
|
display: block;
|
||||||
|
font-size: 92px;
|
||||||
|
line-height: 0.92;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline .blue {
|
||||||
|
color: #007aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline .sub {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 410px;
|
||||||
|
color: #6e6e73;
|
||||||
|
font-size: 27px;
|
||||||
|
line-height: 1.18;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ambient-word {
|
||||||
|
position: absolute;
|
||||||
|
right: 78px;
|
||||||
|
bottom: 64px;
|
||||||
|
color: rgba(17, 17, 17, 0.05);
|
||||||
|
font-size: 208px;
|
||||||
|
line-height: 0.84;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ambient-line {
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
width: 360px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(0, 122, 255, 0.2);
|
||||||
|
transform-origin: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-a {
|
||||||
|
left: 198px;
|
||||||
|
bottom: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-b {
|
||||||
|
right: 208px;
|
||||||
|
top: 196px;
|
||||||
|
width: 248px;
|
||||||
|
background: rgba(52, 199, 89, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.snap-shadow {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 132px;
|
||||||
|
width: 560px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba(17, 17, 17, 0.14);
|
||||||
|
filter: blur(24px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone {
|
||||||
|
position: relative;
|
||||||
|
width: 462px;
|
||||||
|
height: 952px;
|
||||||
|
border-radius: 72px;
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(145deg, #1f2025 0%, #07070a 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 46px 120px rgba(17, 17, 17, 0.22),
|
||||||
|
inset 0 0 0 2px rgba(255, 255, 255, 0.12);
|
||||||
|
z-index: 4;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 8px;
|
||||||
|
border-radius: 64px;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.07);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 56px;
|
||||||
|
background: #f5f5f7;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.island {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 15px;
|
||||||
|
width: 138px;
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 999px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: #07070a;
|
||||||
|
z-index: 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-view,
|
||||||
|
.detail-view,
|
||||||
|
.white-wipe {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-view {
|
||||||
|
padding: 70px 24px 26px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-title {
|
||||||
|
font-size: 38px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 62% 38%, rgba(255, 255, 255, 0.9) 0 10%, transparent 11%),
|
||||||
|
linear-gradient(135deg, #007aff, #34c759);
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 122, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-pill {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
height: 45px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-radius: 15px;
|
||||||
|
background: #e9e9ed;
|
||||||
|
color: #6e6e73;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 3px solid #8e8e93;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
right: -8px;
|
||||||
|
bottom: -5px;
|
||||||
|
width: 9px;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #8e8e93;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-window {
|
||||||
|
height: 720px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 72px 1fr 78px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 13px;
|
||||||
|
min-height: 104px;
|
||||||
|
padding: 13px 12px;
|
||||||
|
border-radius: 23px;
|
||||||
|
background: rgba(255, 255, 255, 0.88);
|
||||||
|
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-icon {
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
border-radius: 18px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.38);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-cloud {
|
||||||
|
background: linear-gradient(135deg, #70d7ff, #007aff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-photo {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 32% 35%, #ffffff 0 10%, transparent 11%),
|
||||||
|
linear-gradient(135deg, #ff9f0a, #ff375f);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-music {
|
||||||
|
background: linear-gradient(135deg, #ff2d55, #5856d6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-bank {
|
||||||
|
background: linear-gradient(135deg, #111111, #8e8e93);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-map {
|
||||||
|
background: linear-gradient(135deg, #34c759, #ffd60a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-goon {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 72% 16%, rgba(255, 255, 255, 0.62) 0 12%, transparent 13%),
|
||||||
|
linear-gradient(145deg, #101828 0%, #007aff 64%, #34c759 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-goon::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 21px;
|
||||||
|
top: 16px;
|
||||||
|
width: 30px;
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 16px 16px 18px 18px;
|
||||||
|
background: rgba(255, 255, 255, 0.94);
|
||||||
|
clip-path: polygon(50% 0, 88% 13%, 88% 46%, 50% 100%, 12% 46%, 12% 13%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-goon::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 31px;
|
||||||
|
top: 29px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #007aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-copy {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-title {
|
||||||
|
color: #111111;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.24;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-sub {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #6e6e73;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.28;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stars {
|
||||||
|
margin-top: 6px;
|
||||||
|
color: #8e8e93;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.25;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-button,
|
||||||
|
.install-button,
|
||||||
|
.open-button {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-button,
|
||||||
|
.open-button {
|
||||||
|
width: 72px;
|
||||||
|
color: #007aff;
|
||||||
|
background: #ececf1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#goon-row {
|
||||||
|
position: relative;
|
||||||
|
transform-origin: center;
|
||||||
|
outline: 0 solid rgba(0, 122, 255, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-dot {
|
||||||
|
position: absolute;
|
||||||
|
right: 47px;
|
||||||
|
top: 52px;
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid rgba(0, 122, 255, 0.8);
|
||||||
|
background: rgba(0, 122, 255, 0.18);
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-view {
|
||||||
|
padding: 78px 24px 26px;
|
||||||
|
background: #f5f5f7;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-card {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 36px;
|
||||||
|
padding: 28px 22px 24px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 26px 70px rgba(17, 17, 17, 0.12);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-hero {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 108px 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-icon {
|
||||||
|
width: 108px;
|
||||||
|
height: 108px;
|
||||||
|
border-radius: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
font-size: 31px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-sub {
|
||||||
|
margin-top: 7px;
|
||||||
|
color: #6e6e73;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-button {
|
||||||
|
position: relative;
|
||||||
|
width: 94px;
|
||||||
|
margin-top: 15px;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #007aff;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-label,
|
||||||
|
.open-label,
|
||||||
|
.ring {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-label,
|
||||||
|
.open-label {
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open-label,
|
||||||
|
.ring {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring {
|
||||||
|
inset: 4px auto auto 34px;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring .track {
|
||||||
|
fill: none;
|
||||||
|
stroke: rgba(255, 255, 255, 0.36);
|
||||||
|
stroke-width: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring .progress {
|
||||||
|
fill: none;
|
||||||
|
stroke: #ffffff;
|
||||||
|
stroke-width: 4;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-dasharray: 69;
|
||||||
|
stroke-dashoffset: 69;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
margin: 26px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
min-height: 72px;
|
||||||
|
border-radius: 22px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
background: #f5f5f7;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
color: #8e8e93;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
margin-top: 7px;
|
||||||
|
color: #111111;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-panel {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
height: 260px;
|
||||||
|
border-radius: 34px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 40%, rgba(0, 122, 255, 0.12), transparent 48%), #f5f5f7;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-halo {
|
||||||
|
position: absolute;
|
||||||
|
width: 196px;
|
||||||
|
height: 196px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 24px solid rgba(0, 122, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-big {
|
||||||
|
width: 190px;
|
||||||
|
height: 190px;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-track,
|
||||||
|
.big-progress {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-track {
|
||||||
|
stroke: rgba(0, 122, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-progress {
|
||||||
|
stroke: #007aff;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-dasharray: 440;
|
||||||
|
stroke-dashoffset: 440;
|
||||||
|
}
|
||||||
|
|
||||||
|
.percent {
|
||||||
|
position: absolute;
|
||||||
|
color: #111111;
|
||||||
|
font-size: 48px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shield-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
color: #34c759;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 900;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #34c759;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-lines {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 20;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-lines i {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
width: 4px;
|
||||||
|
height: 210px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(17, 17, 17, 0.18);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-lines i:nth-child(1) {
|
||||||
|
top: 70px;
|
||||||
|
margin-left: -310px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-lines i:nth-child(2) {
|
||||||
|
top: 130px;
|
||||||
|
margin-left: 304px;
|
||||||
|
height: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-lines i:nth-child(3) {
|
||||||
|
top: 260px;
|
||||||
|
margin-left: -232px;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-lines i:nth-child(4) {
|
||||||
|
top: 220px;
|
||||||
|
margin-left: 228px;
|
||||||
|
height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-wipe {
|
||||||
|
background: #ffffff;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 40;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div
|
||||||
|
id="root"
|
||||||
|
data-composition-id="goonvpn-youtube-spot"
|
||||||
|
data-start="0"
|
||||||
|
data-duration="7"
|
||||||
|
data-width="1920"
|
||||||
|
data-height="1080"
|
||||||
|
>
|
||||||
|
<audio
|
||||||
|
id="sfx"
|
||||||
|
data-start="0"
|
||||||
|
data-duration="7"
|
||||||
|
data-track-index="1"
|
||||||
|
src="assets/goonvpn-sfx.wav"
|
||||||
|
data-volume="0.95"
|
||||||
|
></audio>
|
||||||
|
|
||||||
|
<div class="stage">
|
||||||
|
<div class="headline" id="stageHeadline">
|
||||||
|
<span>SECURE</span>
|
||||||
|
<span class="blue">INSTANTLY</span>
|
||||||
|
<div class="sub">One tap, installed, ready.</div>
|
||||||
|
</div>
|
||||||
|
<div class="ambient-word">VPN</div>
|
||||||
|
<div class="ambient-line line-a"></div>
|
||||||
|
<div class="ambient-line line-b"></div>
|
||||||
|
<div class="snap-shadow"></div>
|
||||||
|
|
||||||
|
<div class="phone">
|
||||||
|
<div class="screen">
|
||||||
|
<div class="island"></div>
|
||||||
|
|
||||||
|
<section class="store-view">
|
||||||
|
<div class="store-top">
|
||||||
|
<div class="store-title">Search</div>
|
||||||
|
<div class="avatar"></div>
|
||||||
|
</div>
|
||||||
|
<div class="search-pill">
|
||||||
|
<div class="search-icon"></div>
|
||||||
|
<div>vpn apps</div>
|
||||||
|
</div>
|
||||||
|
<div class="screen-window" data-layout-allow-overflow>
|
||||||
|
<div class="scroll-content">
|
||||||
|
<div class="store-row">
|
||||||
|
<div class="app-icon icon-cloud"></div>
|
||||||
|
<div class="row-copy">
|
||||||
|
<div class="row-title">CloudPocket</div>
|
||||||
|
<div class="row-sub">Files and backups</div>
|
||||||
|
<div class="stars">4.6 ★★★★★</div>
|
||||||
|
</div>
|
||||||
|
<div class="get-button">GET</div>
|
||||||
|
</div>
|
||||||
|
<div class="store-row">
|
||||||
|
<div class="app-icon icon-photo"></div>
|
||||||
|
<div class="row-copy">
|
||||||
|
<div class="row-title">LensLab</div>
|
||||||
|
<div class="row-sub">Photo editor</div>
|
||||||
|
<div class="stars">4.8 ★★★★★</div>
|
||||||
|
</div>
|
||||||
|
<div class="get-button">GET</div>
|
||||||
|
</div>
|
||||||
|
<div class="store-row">
|
||||||
|
<div class="app-icon icon-music"></div>
|
||||||
|
<div class="row-copy">
|
||||||
|
<div class="row-title">BeatShelf</div>
|
||||||
|
<div class="row-sub">Music discovery</div>
|
||||||
|
<div class="stars">4.4 ★★★★☆</div>
|
||||||
|
</div>
|
||||||
|
<div class="get-button">GET</div>
|
||||||
|
</div>
|
||||||
|
<div class="store-row">
|
||||||
|
<div class="app-icon icon-bank"></div>
|
||||||
|
<div class="row-copy">
|
||||||
|
<div class="row-title">VaultPay</div>
|
||||||
|
<div class="row-sub">Budget tracker</div>
|
||||||
|
<div class="stars">4.7 ★★★★★</div>
|
||||||
|
</div>
|
||||||
|
<div class="get-button">GET</div>
|
||||||
|
</div>
|
||||||
|
<div class="store-row">
|
||||||
|
<div class="app-icon icon-map"></div>
|
||||||
|
<div class="row-copy">
|
||||||
|
<div class="row-title">TrailPing</div>
|
||||||
|
<div class="row-sub">Maps for trips</div>
|
||||||
|
<div class="stars">4.5 ★★★★☆</div>
|
||||||
|
</div>
|
||||||
|
<div class="get-button">GET</div>
|
||||||
|
</div>
|
||||||
|
<div class="store-row" id="goon-row">
|
||||||
|
<div class="app-icon icon-goon"></div>
|
||||||
|
<div class="row-copy">
|
||||||
|
<div class="row-title">goonVPN</div>
|
||||||
|
<div class="row-sub">Fast private browsing</div>
|
||||||
|
<div class="stars">4.9 ★★★★★</div>
|
||||||
|
</div>
|
||||||
|
<div class="get-button">GET</div>
|
||||||
|
<div class="tap-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="store-row">
|
||||||
|
<div class="app-icon icon-cloud"></div>
|
||||||
|
<div class="row-copy">
|
||||||
|
<div class="row-title">QuickTunnel</div>
|
||||||
|
<div class="row-sub">Network tools</div>
|
||||||
|
<div class="stars">4.2 ★★★★☆</div>
|
||||||
|
</div>
|
||||||
|
<div class="get-button">GET</div>
|
||||||
|
</div>
|
||||||
|
<div class="store-row">
|
||||||
|
<div class="app-icon icon-photo"></div>
|
||||||
|
<div class="row-copy">
|
||||||
|
<div class="row-title">PrivateFrame</div>
|
||||||
|
<div class="row-sub">Photo lockers</div>
|
||||||
|
<div class="stars">4.3 ★★★★☆</div>
|
||||||
|
</div>
|
||||||
|
<div class="get-button">GET</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="detail-view">
|
||||||
|
<div class="detail-card">
|
||||||
|
<div class="detail-hero">
|
||||||
|
<div class="app-icon icon-goon detail-icon"></div>
|
||||||
|
<div>
|
||||||
|
<div class="detail-title">goonVPN</div>
|
||||||
|
<div class="detail-sub">Private, fast VPN for everyday browsing.</div>
|
||||||
|
<div class="install-button">
|
||||||
|
<span class="install-label">GET</span>
|
||||||
|
<svg class="ring" viewBox="0 0 28 28" aria-hidden="true">
|
||||||
|
<circle class="track" cx="14" cy="14" r="11"></circle>
|
||||||
|
<circle class="progress" cx="14" cy="14" r="11"></circle>
|
||||||
|
</svg>
|
||||||
|
<span class="open-label"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-label">RATING</div>
|
||||||
|
<div class="stat-value">4.9</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-label">SPEED</div>
|
||||||
|
<div class="stat-value">Fast</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-label">AGE</div>
|
||||||
|
<div class="stat-value">4+</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-panel">
|
||||||
|
<div class="progress-halo"></div>
|
||||||
|
<svg class="progress-big" viewBox="0 0 160 160" aria-hidden="true">
|
||||||
|
<circle class="big-track" cx="80" cy="80" r="70"></circle>
|
||||||
|
<circle id="progressArc" class="big-progress" cx="80" cy="80" r="70"></circle>
|
||||||
|
</svg>
|
||||||
|
<div class="percent" id="progressText">0%</div>
|
||||||
|
</div>
|
||||||
|
<div class="shield-status">
|
||||||
|
<div class="check">✓</div>
|
||||||
|
<div>Installed</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="speed-lines">
|
||||||
|
<i></i>
|
||||||
|
<i></i>
|
||||||
|
<i></i>
|
||||||
|
<i></i>
|
||||||
|
</div>
|
||||||
|
<div class="white-wipe"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.__timelines = window.__timelines || {};
|
||||||
|
|
||||||
|
gsap.defaults({ force3D: true });
|
||||||
|
|
||||||
|
const tl = gsap.timeline({
|
||||||
|
paused: true,
|
||||||
|
defaults: { duration: 0.45, ease: "power3.out" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const breatheRepeats = Math.ceil(7 / 2.4) - 1;
|
||||||
|
|
||||||
|
tl.from(
|
||||||
|
".headline span",
|
||||||
|
{ y: 46, opacity: 0, stagger: 0.055, duration: 0.38, ease: "back.out(1.35)" },
|
||||||
|
0.05,
|
||||||
|
)
|
||||||
|
.from(".headline .sub", { y: 20, opacity: 0, duration: 0.28 }, 0.2)
|
||||||
|
.from(".ambient-word", { scale: 0.92, opacity: 0, duration: 0.5 }, 0.08)
|
||||||
|
.from(".ambient-line", { scaleX: 0, opacity: 0, stagger: 0.08, duration: 0.42 }, 0.16)
|
||||||
|
.to(
|
||||||
|
".ambient-line",
|
||||||
|
{
|
||||||
|
scaleX: 1.08,
|
||||||
|
opacity: 0.55,
|
||||||
|
duration: 2.4,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: breatheRepeats,
|
||||||
|
ease: "sine.inOut",
|
||||||
|
},
|
||||||
|
0.55,
|
||||||
|
)
|
||||||
|
.from(
|
||||||
|
".phone",
|
||||||
|
{
|
||||||
|
y: 760,
|
||||||
|
scale: 0.82,
|
||||||
|
rotation: -2.5,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.47,
|
||||||
|
ease: "back.out(1.6)",
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.from(".store-title", { y: 26, opacity: 0, duration: 0.28 }, 0.28)
|
||||||
|
.from(".avatar", { scale: 0.4, opacity: 0, duration: 0.24, ease: "back.out(1.8)" }, 0.34)
|
||||||
|
.from(".search-pill", { y: 18, opacity: 0, duration: 0.26 }, 0.4)
|
||||||
|
.from(
|
||||||
|
".store-row",
|
||||||
|
{ y: 44, opacity: 0, stagger: 0.035, duration: 0.36, ease: "power3.out" },
|
||||||
|
0.48,
|
||||||
|
);
|
||||||
|
|
||||||
|
tl.to(".scroll-content", { y: -456, duration: 1.2, ease: "power4.out" }, 0.62)
|
||||||
|
.to(
|
||||||
|
".screen-window",
|
||||||
|
{ scale: 1.012, duration: 0.18, yoyo: true, repeat: 1, ease: "power2.out" },
|
||||||
|
0.92,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
".screen-window",
|
||||||
|
{ scale: 1.014, duration: 0.14, yoyo: true, repeat: 1, ease: "power2.out" },
|
||||||
|
1.25,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
"#goon-row",
|
||||||
|
{
|
||||||
|
scale: 1.035,
|
||||||
|
outlineWidth: 3,
|
||||||
|
outlineColor: "rgba(0,122,255,0.34)",
|
||||||
|
duration: 0.2,
|
||||||
|
ease: "back.out(1.7)",
|
||||||
|
},
|
||||||
|
1.72,
|
||||||
|
)
|
||||||
|
.to("#goon-row", { scale: 1, duration: 0.22 }, 1.93)
|
||||||
|
.fromTo(
|
||||||
|
".tap-dot",
|
||||||
|
{ scale: 0.25, opacity: 0.95 },
|
||||||
|
{ scale: 2.15, opacity: 0, duration: 0.42, ease: "power2.out" },
|
||||||
|
2.02,
|
||||||
|
)
|
||||||
|
.to("#goon-row .get-button", { scale: 0.82, duration: 0.1, ease: "power3.in" }, 2.04)
|
||||||
|
.to("#goon-row .get-button", { scale: 1, duration: 0.14, ease: "back.out(2)" }, 2.14);
|
||||||
|
|
||||||
|
tl.fromTo(
|
||||||
|
".detail-view",
|
||||||
|
{ y: 620, scale: 0.92, opacity: 0 },
|
||||||
|
{ y: 0, scale: 1, opacity: 1, duration: 0.46, ease: "power4.out" },
|
||||||
|
2.32,
|
||||||
|
)
|
||||||
|
.from(".detail-card", { y: 56, scale: 0.96, duration: 0.42, ease: "back.out(1.2)" }, 2.34)
|
||||||
|
.from(
|
||||||
|
".detail-icon",
|
||||||
|
{ scale: 0.55, rotation: -8, opacity: 0, duration: 0.32, ease: "back.out(1.8)" },
|
||||||
|
2.47,
|
||||||
|
)
|
||||||
|
.from(".detail-title", { y: 20, opacity: 0, duration: 0.28 }, 2.54)
|
||||||
|
.from(".detail-sub", { y: 14, opacity: 0, duration: 0.24 }, 2.6)
|
||||||
|
.from(
|
||||||
|
".install-button",
|
||||||
|
{ scale: 0.72, opacity: 0, duration: 0.24, ease: "back.out(2)" },
|
||||||
|
2.66,
|
||||||
|
)
|
||||||
|
.from(".stat", { y: 28, opacity: 0, stagger: 0.05, duration: 0.3 }, 2.78)
|
||||||
|
.from(".progress-panel", { y: 42, scale: 0.96, opacity: 0, duration: 0.38 }, 2.92);
|
||||||
|
|
||||||
|
tl.to(".install-label", { y: -18, opacity: 0, duration: 0.16 }, 2.86)
|
||||||
|
.set(".install-label", { textContent: "" }, 3.02)
|
||||||
|
.to(".ring", { opacity: 1, duration: 0.16 }, 2.96)
|
||||||
|
.fromTo(
|
||||||
|
".ring .progress",
|
||||||
|
{ strokeDashoffset: 69 },
|
||||||
|
{ strokeDashoffset: 0, duration: 1.2, ease: "power2.inOut" },
|
||||||
|
3.04,
|
||||||
|
)
|
||||||
|
.fromTo(
|
||||||
|
"#progressArc",
|
||||||
|
{ strokeDashoffset: 440 },
|
||||||
|
{ strokeDashoffset: 0, duration: 1.42, ease: "power2.inOut" },
|
||||||
|
3.02,
|
||||||
|
)
|
||||||
|
.set("#progressText", { textContent: "18%" }, 3.18)
|
||||||
|
.to(".progress-panel", { scale: 1.025, duration: 0.12, yoyo: true, repeat: 1 }, 3.18)
|
||||||
|
.set("#progressText", { textContent: "52%" }, 3.56)
|
||||||
|
.to(".progress-panel", { scale: 1.03, duration: 0.12, yoyo: true, repeat: 1 }, 3.56)
|
||||||
|
.set("#progressText", { textContent: "87%" }, 3.9)
|
||||||
|
.to(".progress-panel", { scale: 1.035, duration: 0.12, yoyo: true, repeat: 1 }, 3.9)
|
||||||
|
.set("#progressText", { textContent: "100%" }, 4.28)
|
||||||
|
.to(
|
||||||
|
".progress-panel",
|
||||||
|
{ scale: 1.04, duration: 0.12, yoyo: true, repeat: 1, ease: "back.out(1.7)" },
|
||||||
|
4.28,
|
||||||
|
)
|
||||||
|
.to(".ring", { scale: 0.35, opacity: 0, duration: 0.16 }, 4.44)
|
||||||
|
.to(".install-button", { backgroundColor: "#34c759", width: 94, duration: 0.18 }, 4.5)
|
||||||
|
.set(".open-label", { textContent: "OPEN" }, 4.52)
|
||||||
|
.to(".open-label", { opacity: 1, duration: 0.16 }, 4.56)
|
||||||
|
.fromTo(
|
||||||
|
".shield-status",
|
||||||
|
{ y: 22, scale: 0.9, opacity: 0 },
|
||||||
|
{ y: 0, scale: 1, opacity: 1, duration: 0.34, ease: "back.out(1.8)" },
|
||||||
|
4.62,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
".phone",
|
||||||
|
{ scale: 1.025, duration: 0.18, yoyo: true, repeat: 1, ease: "back.out(1.5)" },
|
||||||
|
4.7,
|
||||||
|
);
|
||||||
|
|
||||||
|
tl.to(".headline", { y: -18, opacity: 0.35, duration: 0.22 }, 5.18)
|
||||||
|
.to(".ambient-word", { opacity: 0.018, duration: 0.22 }, 5.18)
|
||||||
|
.to(".snap-shadow", { opacity: 1, scaleX: 0.8, duration: 0.16 }, 5.35)
|
||||||
|
.fromTo(
|
||||||
|
".speed-lines",
|
||||||
|
{ y: -220, opacity: 0 },
|
||||||
|
{ y: 420, opacity: 1, duration: 0.26, ease: "power4.in" },
|
||||||
|
5.42,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
".phone",
|
||||||
|
{ y: 1050, scale: 0.78, rotation: -3.5, duration: 0.48, ease: "power4.in" },
|
||||||
|
5.44,
|
||||||
|
)
|
||||||
|
.to(".snap-shadow", { opacity: 0, scaleX: 1.6, duration: 0.28 }, 5.62)
|
||||||
|
.to(".speed-lines", { opacity: 0, duration: 0.2 }, 5.74)
|
||||||
|
.to(".white-wipe", { opacity: 1, duration: 0.42, ease: "power2.out" }, 5.86);
|
||||||
|
|
||||||
|
window.__timelines["goonvpn-youtube-spot"] = tl;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||||
|
"name": "goonvpn-youtube-spot",
|
||||||
|
"type": "hyperframes:block",
|
||||||
|
"title": "GoonVPN YouTube Spot",
|
||||||
|
"description": "Snappy Apple-style YouTube insert showing a phone finding and installing a fictional VPN app with sound effects.",
|
||||||
|
"tags": ["app", "showcase", "youtube", "sfx"],
|
||||||
|
"author": "Stronkter",
|
||||||
|
"authorUrl": "https://x.com/Stronkter",
|
||||||
|
"sourcePrompt": "\ud83d\udcf7HyperFrames by HeyGen make me a 7s video apple style bold font and styling a phone scrolling in a appstore clicking on a vpn app called goonVPN and installing it the app installs the phone snaps down and fades to a white backround make it snappy and pop its for a youtube video also add soundfx and make it 60fps and 1920x1080",
|
||||||
|
"dimensions": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
"duration": 7,
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "goonvpn-youtube-spot.html",
|
||||||
|
"target": "compositions/goonvpn-youtube-spot.html",
|
||||||
|
"type": "hyperframes:composition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/goonvpn-sfx.wav",
|
||||||
|
"target": "assets/goonvpn-sfx.wav",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 480 KiB |
@@ -0,0 +1,305 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=1920, height=1080" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #eef3f4;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
position: relative;
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #eef3f4;
|
||||||
|
color: #151515;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
transform-origin: 50% 51%;
|
||||||
|
will-change: transform, filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-depth {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 52% 50%, rgba(255, 255, 255, 0.08), transparent 28%),
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(10, 20, 24, 0.12));
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.annotation {
|
||||||
|
position: absolute;
|
||||||
|
left: 652px;
|
||||||
|
top: 235px;
|
||||||
|
z-index: 4;
|
||||||
|
width: 560px;
|
||||||
|
height: 392px;
|
||||||
|
overflow: visible;
|
||||||
|
transform-origin: center;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scribble {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 560px;
|
||||||
|
height: 392px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scribble path {
|
||||||
|
fill: none;
|
||||||
|
stroke: #e21d2f;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
filter: drop-shadow(0 8px 12px rgba(226, 29, 47, 0.24));
|
||||||
|
}
|
||||||
|
|
||||||
|
#circle-a {
|
||||||
|
stroke-width: 12;
|
||||||
|
stroke-dasharray: 980;
|
||||||
|
stroke-dashoffset: 980;
|
||||||
|
}
|
||||||
|
|
||||||
|
#circle-b {
|
||||||
|
stroke-width: 6;
|
||||||
|
opacity: 0.82;
|
||||||
|
stroke-dasharray: 910;
|
||||||
|
stroke-dashoffset: 910;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: -146px;
|
||||||
|
transform: translateX(-50%) scale(0.82);
|
||||||
|
transform-origin: 50% 100%;
|
||||||
|
min-width: 370px;
|
||||||
|
padding: 18px 26px 20px;
|
||||||
|
border-radius: 18px;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #111111;
|
||||||
|
box-shadow:
|
||||||
|
0 22px 54px rgba(25, 4, 7, 0.24),
|
||||||
|
inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||||
|
font-size: 60px;
|
||||||
|
line-height: 0.9;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: -16px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: #111111;
|
||||||
|
transform: translateX(-50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-wash {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background:
|
||||||
|
radial-gradient(
|
||||||
|
circle at 50% 48%,
|
||||||
|
rgba(255, 59, 48, 0.3),
|
||||||
|
rgba(255, 59, 48, 0.06) 42%,
|
||||||
|
rgba(79, 0, 8, 0.22) 100%
|
||||||
|
),
|
||||||
|
linear-gradient(90deg, rgba(99, 0, 12, 0.12), rgba(255, 59, 48, 0.2));
|
||||||
|
opacity: 0;
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scanline {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 3;
|
||||||
|
opacity: 0;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(65, 0, 0, 0.12) 0,
|
||||||
|
rgba(65, 0, 0, 0.12) 1px,
|
||||||
|
transparent 1px,
|
||||||
|
transparent 7px
|
||||||
|
);
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 6;
|
||||||
|
width: 84px;
|
||||||
|
height: 84px;
|
||||||
|
border-color: rgba(17, 17, 17, 0.62);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner.tl {
|
||||||
|
left: 86px;
|
||||||
|
top: 78px;
|
||||||
|
border-top: 5px solid;
|
||||||
|
border-left: 5px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner.tr {
|
||||||
|
right: 86px;
|
||||||
|
top: 78px;
|
||||||
|
border-top: 5px solid;
|
||||||
|
border-right: 5px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner.bl {
|
||||||
|
left: 86px;
|
||||||
|
bottom: 78px;
|
||||||
|
border-bottom: 5px solid;
|
||||||
|
border-left: 5px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner.br {
|
||||||
|
right: 86px;
|
||||||
|
bottom: 78px;
|
||||||
|
border-bottom: 5px solid;
|
||||||
|
border-right: 5px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribution {
|
||||||
|
position: absolute;
|
||||||
|
right: 28px;
|
||||||
|
bottom: 22px;
|
||||||
|
z-index: 7;
|
||||||
|
color: rgba(17, 17, 17, 0.44);
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 650;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div
|
||||||
|
id="root"
|
||||||
|
data-composition-id="north-korea-locked-down"
|
||||||
|
data-root="true"
|
||||||
|
data-start="0"
|
||||||
|
data-duration="7"
|
||||||
|
data-width="1920"
|
||||||
|
data-height="1080"
|
||||||
|
>
|
||||||
|
<div id="camera" class="camera">
|
||||||
|
<img class="map" src="assets/korea-map.png" alt="" />
|
||||||
|
<div class="paper-depth"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="annotation" class="annotation">
|
||||||
|
<div id="label" class="label">LOCKED<br />DOWN</div>
|
||||||
|
<svg class="scribble" viewBox="0 0 560 392" aria-hidden="true" data-layout-ignore>
|
||||||
|
<path
|
||||||
|
id="circle-a"
|
||||||
|
d="M 74 211 C 42 119 118 42 254 36 C 410 30 524 111 508 222 C 494 320 350 377 208 346 C 94 322 37 278 74 211"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id="circle-b"
|
||||||
|
d="M 92 231 C 33 150 105 55 243 44 C 408 31 529 126 501 244 C 480 334 335 370 197 342 C 83 319 47 269 92 231"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="redWash" class="red-wash"></div>
|
||||||
|
<div id="scanline" class="scanline"></div>
|
||||||
|
<div class="corner tl"></div>
|
||||||
|
<div class="corner tr"></div>
|
||||||
|
<div class="corner bl"></div>
|
||||||
|
<div class="corner br"></div>
|
||||||
|
<div class="attribution">© OpenStreetMap contributors © CARTO</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.__timelines = window.__timelines || {};
|
||||||
|
|
||||||
|
const tl = gsap.timeline({
|
||||||
|
paused: true,
|
||||||
|
defaults: { overwrite: "auto" },
|
||||||
|
});
|
||||||
|
|
||||||
|
tl.fromTo(
|
||||||
|
"#camera",
|
||||||
|
{ scale: 0.92, x: -70, y: 18, filter: "saturate(0.92) contrast(0.98)" },
|
||||||
|
{
|
||||||
|
scale: 2.12,
|
||||||
|
x: -56,
|
||||||
|
y: -22,
|
||||||
|
filter: "saturate(1.08) contrast(1.04)",
|
||||||
|
duration: 3.18,
|
||||||
|
ease: "expo.inOut",
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
tl.to("#camera", { scale: 2.2, x: -44, y: -34, duration: 0.42, ease: "back.out(1.5)" }, 3.18);
|
||||||
|
tl.from(
|
||||||
|
".corner",
|
||||||
|
{ opacity: 0, scale: 0.72, duration: 0.28, stagger: 0.035, ease: "power3.out" },
|
||||||
|
0.42,
|
||||||
|
);
|
||||||
|
tl.to(".corner", { opacity: 0.72, duration: 0.18, ease: "power2.out" }, 1.2);
|
||||||
|
tl.fromTo(
|
||||||
|
"#annotation",
|
||||||
|
{ opacity: 0, scale: 0.92, x: 4, y: -4 },
|
||||||
|
{ opacity: 1, scale: 1, x: 0, y: 0, duration: 0.16, ease: "power2.out" },
|
||||||
|
3.12,
|
||||||
|
);
|
||||||
|
tl.to("#circle-a", { strokeDashoffset: 0, duration: 0.62, ease: "power2.out" }, 3.24);
|
||||||
|
tl.to("#circle-b", { strokeDashoffset: 0, duration: 0.44, ease: "power3.out" }, 3.46);
|
||||||
|
tl.to("#label", { opacity: 1, scale: 1, duration: 0.28, ease: "back.out(2.1)" }, 3.78);
|
||||||
|
tl.to("#redWash", { opacity: 1, duration: 0.24, ease: "power2.out" }, 3.82);
|
||||||
|
tl.to("#scanline", { opacity: 0.44, duration: 0.18, ease: "none" }, 3.86);
|
||||||
|
tl.to(
|
||||||
|
"#annotation",
|
||||||
|
{ scale: 1.045, duration: 0.14, yoyo: true, repeat: 1, ease: "sine.inOut" },
|
||||||
|
4.1,
|
||||||
|
);
|
||||||
|
tl.to("#camera", { scale: 2.06, x: -82, y: -8, duration: 2.35, ease: "sine.inOut" }, 4.2);
|
||||||
|
tl.to("#redWash", { opacity: 0.82, duration: 2.35, ease: "sine.inOut" }, 4.2);
|
||||||
|
tl.to(".corner", { opacity: 0.38, duration: 0.72, ease: "sine.inOut" }, 5.82);
|
||||||
|
tl.to("#label", { y: -6, duration: 0.4, yoyo: true, repeat: 1, ease: "sine.inOut" }, 5.94);
|
||||||
|
|
||||||
|
window.__timelines["north-korea-locked-down"] = tl;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||||
|
"name": "north-korea-locked-down",
|
||||||
|
"type": "hyperframes:block",
|
||||||
|
"title": "North Korea Locked Down",
|
||||||
|
"description": "Realistic map zoom into North Korea with a red scribble circle, locked-down pop-up label, and reddish editorial wash.",
|
||||||
|
"tags": ["showcase", "map", "annotation", "youtube", "kinetic"],
|
||||||
|
"author": "Stronkter",
|
||||||
|
"authorUrl": "https://x.com/Stronkter",
|
||||||
|
"sourcePrompt": "use \ud83d\udcf7HyperFrames by HeyGen and Image Gen if you need it for assets or like png images of assets without backround to make a youtube style camera moving in out and other things that are in youtube videos, to make a video of a map zooms in on north korea and a scribble style circle circles the country and a text pops up above it saying locked down when the text apears the video turns a bit redish make the video 7 seconds long id like the map to look realistic and accurate to real lfe",
|
||||||
|
"dimensions": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
"duration": 7,
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "north-korea-locked-down.html",
|
||||||
|
"target": "compositions/north-korea-locked-down.html",
|
||||||
|
"type": "hyperframes:composition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/korea-map.png",
|
||||||
|
"target": "assets/korea-map.png",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 441 KiB |
Binary file not shown.
@@ -0,0 +1,525 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=1920, height=1080" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f7;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
position: relative;
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f7;
|
||||||
|
color: #1d1d1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-plane {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
transform-origin: 52% 48%;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-image {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soft-ocean {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 24%, rgba(0, 113, 227, 0.18), rgba(0, 113, 227, 0) 34%),
|
||||||
|
linear-gradient(180deg, rgba(245, 245, 247, 0.05), rgba(245, 245, 247, 0.3));
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-svg,
|
||||||
|
.doodle-svg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 1920px;
|
||||||
|
height: 1080px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-glow {
|
||||||
|
fill: none;
|
||||||
|
stroke: rgba(0, 113, 227, 0.23);
|
||||||
|
stroke-width: 24;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-dasharray: 2000;
|
||||||
|
stroke-dashoffset: 2000;
|
||||||
|
filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-path {
|
||||||
|
fill: none;
|
||||||
|
stroke: #0071e3;
|
||||||
|
stroke-width: 9;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-dasharray: 2000;
|
||||||
|
stroke-dashoffset: 2000;
|
||||||
|
filter: drop-shadow(0 10px 20px rgba(0, 113, 227, 0.22));
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-thread {
|
||||||
|
fill: none;
|
||||||
|
stroke: rgba(255, 255, 255, 0.86);
|
||||||
|
stroke-width: 3;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-dasharray: 2000;
|
||||||
|
stroke-dashoffset: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-tick {
|
||||||
|
position: absolute;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin: -7px 0 0 -7px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 3px solid #0071e3;
|
||||||
|
box-shadow: 0 8px 26px rgba(0, 113, 227, 0.32);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pin {
|
||||||
|
position: absolute;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 7px solid #0071e3;
|
||||||
|
box-shadow:
|
||||||
|
0 15px 34px rgba(0, 0, 0, 0.18),
|
||||||
|
0 0 0 10px rgba(0, 113, 227, 0.12);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transform-origin: center;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pin::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: -20px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 2px solid rgba(0, 113, 227, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pin-label {
|
||||||
|
position: absolute;
|
||||||
|
top: 32px;
|
||||||
|
min-width: 142px;
|
||||||
|
padding: 12px 16px 13px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.84);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.88);
|
||||||
|
color: #1d1d1f;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0;
|
||||||
|
box-shadow: 0 18px 38px rgba(29, 29, 31, 0.16);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pin-label span {
|
||||||
|
display: block;
|
||||||
|
margin-top: 7px;
|
||||||
|
color: #6e6e73;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nyc-pin {
|
||||||
|
left: 308.41px;
|
||||||
|
top: 584.16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nyc-pin .pin-label {
|
||||||
|
left: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#paris-pin {
|
||||||
|
left: 1611.59px;
|
||||||
|
top: 387.84px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#paris-pin .pin-label {
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plane {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 112px;
|
||||||
|
height: 112px;
|
||||||
|
offset-path: path("M 308.41 584.158 C 650 520 1120 245 1611.59 387.842");
|
||||||
|
offset-distance: 0%;
|
||||||
|
offset-rotate: auto;
|
||||||
|
offset-anchor: 50% 50%;
|
||||||
|
transform-origin: center;
|
||||||
|
filter: drop-shadow(0 22px 22px rgba(0, 0, 0, 0.24));
|
||||||
|
opacity: 1;
|
||||||
|
will-change: offset-distance, transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plane-core {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
transform-origin: center;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plane svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doodle-line {
|
||||||
|
fill: none;
|
||||||
|
stroke: #ff3b30;
|
||||||
|
stroke-width: 13;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-dasharray: 900;
|
||||||
|
stroke-dashoffset: 900;
|
||||||
|
filter: drop-shadow(0 10px 14px rgba(255, 59, 48, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
.doodle-line.secondary {
|
||||||
|
stroke-width: 6;
|
||||||
|
opacity: 0.82;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paris-pulse {
|
||||||
|
position: absolute;
|
||||||
|
left: 1611.59px;
|
||||||
|
top: 387.84px;
|
||||||
|
width: 126px;
|
||||||
|
height: 126px;
|
||||||
|
margin: -63px 0 0 -63px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 4px solid rgba(255, 59, 48, 0.34);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-badge {
|
||||||
|
position: absolute;
|
||||||
|
left: 1500px;
|
||||||
|
top: 502px;
|
||||||
|
padding: 15px 20px 16px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: #d70015;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0;
|
||||||
|
box-shadow: 0 20px 44px rgba(29, 29, 31, 0.14);
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.78);
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-stack {
|
||||||
|
position: absolute;
|
||||||
|
left: 82px;
|
||||||
|
top: 70px;
|
||||||
|
z-index: 6;
|
||||||
|
max-width: 780px;
|
||||||
|
padding: 30px 34px 33px;
|
||||||
|
border-radius: 30px;
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.86);
|
||||||
|
box-shadow: 0 28px 70px rgba(29, 29, 31, 0.12);
|
||||||
|
backdrop-filter: blur(22px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
color: #6e6e73;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
margin-top: 11px;
|
||||||
|
color: #1d1d1f;
|
||||||
|
font-size: 84px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 0.92;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline span {
|
||||||
|
color: #0071e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
margin-top: 18px;
|
||||||
|
color: #6e6e73;
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-meta i {
|
||||||
|
display: block;
|
||||||
|
width: 54px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #0071e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribution {
|
||||||
|
position: absolute;
|
||||||
|
right: 28px;
|
||||||
|
bottom: 24px;
|
||||||
|
z-index: 5;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.68);
|
||||||
|
color: rgba(29, 29, 31, 0.76);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-screen {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 30;
|
||||||
|
background: #ffffff;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div
|
||||||
|
id="root"
|
||||||
|
data-composition-id="nyc-paris-flight"
|
||||||
|
data-start="0"
|
||||||
|
data-duration="6"
|
||||||
|
data-width="1920"
|
||||||
|
data-height="1080"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="flight-scene"
|
||||||
|
class="clip stage"
|
||||||
|
data-start="0"
|
||||||
|
data-duration="6"
|
||||||
|
data-track-index="0"
|
||||||
|
>
|
||||||
|
<div id="map-plane" class="map-plane" data-layout-allow-overflow>
|
||||||
|
<img class="map-image" src="assets/map-nyc-paris.png" alt="" />
|
||||||
|
<div class="soft-ocean" data-layout-ignore></div>
|
||||||
|
|
||||||
|
<svg class="route-svg" viewBox="0 0 1920 1080" aria-hidden="true" data-layout-ignore>
|
||||||
|
<path
|
||||||
|
id="route-glow"
|
||||||
|
class="route-glow"
|
||||||
|
d="M 308.41 584.158 C 650 520 1120 245 1611.59 387.842"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id="route-path"
|
||||||
|
class="route-path"
|
||||||
|
d="M 308.41 584.158 C 650 520 1120 245 1611.59 387.842"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id="route-thread"
|
||||||
|
class="route-thread"
|
||||||
|
d="M 308.41 584.158 C 650 520 1120 245 1611.59 387.842"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div class="route-tick" style="left: 481.8px; top: 539.7px"></div>
|
||||||
|
<div class="route-tick" style="left: 647.6px; top: 486.5px"></div>
|
||||||
|
<div class="route-tick" style="left: 824.8px; top: 430.6px"></div>
|
||||||
|
<div class="route-tick" style="left: 1011.5px; top: 382.4px"></div>
|
||||||
|
<div class="route-tick" style="left: 1206.1px; top: 352.3px"></div>
|
||||||
|
<div class="route-tick" style="left: 1406.7px; top: 350.7px"></div>
|
||||||
|
|
||||||
|
<div id="nyc-pin" class="pin">
|
||||||
|
<div class="pin-label">New York<span>JFK / NYC</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="paris-pin" class="pin">
|
||||||
|
<div class="pin-label">Paris<span>CDG / FR</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="plane" class="plane" aria-hidden="true">
|
||||||
|
<div id="plane-core" class="plane-core">
|
||||||
|
<svg viewBox="-64 -64 128 128">
|
||||||
|
<path
|
||||||
|
d="M 58 0 C 52 -10 37 -15 15 -16 L -18 -56 C -23 -62 -34 -58 -32 -49 L -21 -15 L -48 -12 C -56 -11 -60 -6 -60 0 C -60 6 -56 11 -48 12 L -21 15 L -32 49 C -34 58 -23 62 -18 56 L 15 16 C 37 15 52 10 58 0 Z"
|
||||||
|
fill="#ffffff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M 54 0 C 46 -6 33 -9 14 -10 L -19 -50 C -21 -53 -25 -52 -25 -47 L -15 -10 L -49 -6 C -53 -5 -55 -2 -55 0 C -55 2 -53 5 -49 6 L -15 10 L -25 47 C -25 52 -21 53 -19 50 L 14 10 C 33 9 46 6 54 0 Z"
|
||||||
|
fill="#0071e3"
|
||||||
|
/>
|
||||||
|
<circle cx="18" cy="0" r="6" fill="#f5f5f7" opacity="0.95" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<svg class="doodle-svg" viewBox="0 0 1920 1080" aria-hidden="true" data-layout-ignore>
|
||||||
|
<path
|
||||||
|
id="doodle-a"
|
||||||
|
class="doodle-line"
|
||||||
|
d="M 1510 382 C 1505 323 1560 287 1627 300 C 1700 315 1740 358 1720 416 C 1705 468 1624 486 1556 462 C 1518 449 1494 417 1510 382"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id="doodle-b"
|
||||||
|
class="doodle-line secondary"
|
||||||
|
d="M 1518 396 C 1488 356 1528 307 1605 296 C 1688 284 1748 338 1730 397 C 1713 453 1641 480 1570 463 C 1526 453 1504 427 1518 396"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div id="paris-pulse" class="paris-pulse"></div>
|
||||||
|
<div id="landing-badge" class="landing-badge">ARRIVED</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="title-stack">
|
||||||
|
<div class="eyebrow">Six-second transatlantic hop</div>
|
||||||
|
<div class="headline">NEW YORK <span>TO</span> PARIS</div>
|
||||||
|
<div class="route-meta">
|
||||||
|
<i></i>
|
||||||
|
<p>5,837 km compressed</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="attribution">© OpenStreetMap contributors © CARTO</div>
|
||||||
|
<div id="white-screen" class="white-screen"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio
|
||||||
|
id="sfx"
|
||||||
|
data-start="0"
|
||||||
|
data-duration="6"
|
||||||
|
data-track-index="1"
|
||||||
|
src="assets/sfx-mix.wav"
|
||||||
|
data-volume="0.95"
|
||||||
|
></audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.__timelines = window.__timelines || {};
|
||||||
|
|
||||||
|
const tl = gsap.timeline({
|
||||||
|
paused: true,
|
||||||
|
defaults: { overwrite: "auto" },
|
||||||
|
});
|
||||||
|
|
||||||
|
tl.from(
|
||||||
|
"#map-plane",
|
||||||
|
{ scale: 1.1, x: -38, y: 20, opacity: 0.94, duration: 1.05, ease: "power2.out" },
|
||||||
|
0.16,
|
||||||
|
);
|
||||||
|
tl.from(
|
||||||
|
".title-stack",
|
||||||
|
{ y: 28, scale: 0.97, opacity: 0, duration: 0.52, ease: "expo.out" },
|
||||||
|
0.24,
|
||||||
|
);
|
||||||
|
tl.from(".eyebrow", { x: -22, opacity: 0, duration: 0.36, ease: "power3.out" }, 0.32);
|
||||||
|
tl.from(".headline", { y: 34, opacity: 0, duration: 0.5, ease: "back.out(1.45)" }, 0.4);
|
||||||
|
tl.from(".route-meta", { x: -26, opacity: 0, duration: 0.38, ease: "circ.out" }, 0.58);
|
||||||
|
|
||||||
|
tl.from("#nyc-pin", { scale: 0.12, opacity: 0, duration: 0.34, ease: "back.out(2.8)" }, 0.7);
|
||||||
|
tl.from(
|
||||||
|
"#paris-pin",
|
||||||
|
{ scale: 0.12, opacity: 0, duration: 0.34, ease: "elastic.out(1, 0.5)" },
|
||||||
|
0.86,
|
||||||
|
);
|
||||||
|
|
||||||
|
tl.from("#plane", { scale: 0.48, opacity: 0, duration: 0.3, ease: "back.out(2.2)" }, 1.08);
|
||||||
|
tl.to("#route-glow", { strokeDashoffset: 0, duration: 3.1, ease: "power1.inOut" }, 1.16);
|
||||||
|
tl.to("#route-path", { strokeDashoffset: 0, duration: 3.05, ease: "power2.inOut" }, 1.2);
|
||||||
|
tl.to("#route-thread", { strokeDashoffset: 0, duration: 2.92, ease: "sine.inOut" }, 1.26);
|
||||||
|
tl.to("#plane", { offsetDistance: "100%", duration: 3.25, ease: "sine.inOut" }, 1.17);
|
||||||
|
tl.to(
|
||||||
|
"#plane-core",
|
||||||
|
{ scale: 1.06, duration: 0.2, repeat: 12, yoyo: true, ease: "sine.inOut" },
|
||||||
|
1.22,
|
||||||
|
);
|
||||||
|
tl.to("#map-plane", { scale: 1.025, duration: 3.2, ease: "sine.inOut" }, 1.28);
|
||||||
|
|
||||||
|
gsap.utils.toArray(".route-tick").forEach((tick, index) => {
|
||||||
|
tl.fromTo(
|
||||||
|
tick,
|
||||||
|
{ scale: 0.2, opacity: 0 },
|
||||||
|
{ scale: 1, opacity: 1, duration: 0.16, ease: "back.out(3)" },
|
||||||
|
1.34 + index * 0.44,
|
||||||
|
);
|
||||||
|
tl.to(
|
||||||
|
tick,
|
||||||
|
{ scale: 0.6, opacity: 0.18, duration: 0.34, ease: "power2.out" },
|
||||||
|
1.48 + index * 0.44,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tl.from(
|
||||||
|
"#paris-pulse",
|
||||||
|
{ scale: 0.52, opacity: 0, duration: 0.26, ease: "back.out(2.4)" },
|
||||||
|
4.06,
|
||||||
|
);
|
||||||
|
tl.to("#doodle-a", { strokeDashoffset: 0, duration: 0.54, ease: "power2.out" }, 4.18);
|
||||||
|
tl.to("#doodle-b", { strokeDashoffset: 0, duration: 0.36, ease: "power3.out" }, 4.36);
|
||||||
|
tl.to("#paris-pulse", { scale: 1.5, opacity: 0, duration: 0.58, ease: "power3.out" }, 4.64);
|
||||||
|
|
||||||
|
tl.to("#plane-core", { scale: 1.23, duration: 0.16, ease: "back.out(3)" }, 4.96);
|
||||||
|
tl.to("#plane-core", { scale: 0.9, duration: 0.12, ease: "power2.in" }, 5.12);
|
||||||
|
tl.to("#plane-core", { scale: 1, duration: 0.22, ease: "elastic.out(1, 0.45)" }, 5.24);
|
||||||
|
tl.to(
|
||||||
|
"#landing-badge",
|
||||||
|
{ scale: 1, opacity: 1, duration: 0.28, ease: "back.out(2.4)" },
|
||||||
|
5.02,
|
||||||
|
);
|
||||||
|
tl.to(
|
||||||
|
"#landing-badge",
|
||||||
|
{ scale: 1.06, duration: 0.16, yoyo: true, repeat: 1, ease: "sine.inOut" },
|
||||||
|
5.3,
|
||||||
|
);
|
||||||
|
|
||||||
|
tl.set("#white-screen", { opacity: 1 }, 5.5);
|
||||||
|
|
||||||
|
window.__timelines["nyc-paris-flight"] = tl;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||||
|
"name": "nyc-paris-flight",
|
||||||
|
"type": "hyperframes:block",
|
||||||
|
"title": "NYC Paris Flight",
|
||||||
|
"description": "Apple-style realistic map animation with a plane flying from New York to Paris, marker circle, landing pop, and sound effects.",
|
||||||
|
"tags": ["showcase", "travel", "map", "youtube", "sfx"],
|
||||||
|
"author": "Stronkter",
|
||||||
|
"authorUrl": "https://x.com/Stronkter",
|
||||||
|
"sourcePrompt": "\ud83d\udcf7HyperFrames by HeyGen Make a six-second Apple-style font bold video of a plane going from New York to Paris. A map animation, and then it shows the plane going from New York to Paris. Make the fonts Apple-style bold and make the map actual realistic, actually realistic. Before it lands in Paris, do a doodle circle in red around Paris, and then it lands in the doodle circle, and the video basically cuts to a white screen at the end. Also add sound effects for everything as well. Every nice motion, every the plane for pop-ups, bubble, pop-up effect, everything you want.",
|
||||||
|
"dimensions": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
"duration": 6,
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "nyc-paris-flight.html",
|
||||||
|
"target": "compositions/nyc-paris-flight.html",
|
||||||
|
"type": "hyperframes:composition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/map-nyc-paris.png",
|
||||||
|
"target": "assets/map-nyc-paris.png",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/sfx-mix.wav",
|
||||||
|
"target": "assets/sfx-mix.wav",
|
||||||
|
"type": "hyperframes:asset"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -91,6 +91,22 @@
|
|||||||
"name": "app-showcase",
|
"name": "app-showcase",
|
||||||
"type": "hyperframes:block"
|
"type": "hyperframes:block"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "north-korea-locked-down",
|
||||||
|
"type": "hyperframes:block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apple-money-count",
|
||||||
|
"type": "hyperframes:block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "goonvpn-youtube-spot",
|
||||||
|
"type": "hyperframes:block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nyc-paris-flight",
|
||||||
|
"type": "hyperframes:block"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ui-3d-reveal",
|
"name": "ui-3d-reveal",
|
||||||
"type": "hyperframes:block"
|
"type": "hyperframes:block"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* npx tsx scripts/generate-catalog-pages.ts
|
* npx tsx scripts/generate-catalog-pages.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||||
import { join, resolve, dirname } from "node:path";
|
import { join, resolve, dirname } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
// Import from source — bun workspace linking doesn't resolve for scripts outside packages/.
|
// Import from source — bun workspace linking doesn't resolve for scripts outside packages/.
|
||||||
@@ -27,11 +27,17 @@ const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|||||||
const repoRoot = resolve(scriptDir, "..");
|
const repoRoot = resolve(scriptDir, "..");
|
||||||
const registryDir = resolve(repoRoot, "registry");
|
const registryDir = resolve(repoRoot, "registry");
|
||||||
const docsDir = resolve(repoRoot, "docs");
|
const docsDir = resolve(repoRoot, "docs");
|
||||||
|
const catalogImageBase = "https://static.heygen.ai/hyperframes-oss/docs/images/catalog";
|
||||||
|
|
||||||
// ── Types ──────────────────────────────────────────────────────────────────
|
// ── Types ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
type ItemKind = "block" | "component";
|
type ItemKind = "block" | "component";
|
||||||
|
|
||||||
|
interface SourceMetadata {
|
||||||
|
authorUrl?: string;
|
||||||
|
sourcePrompt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface CatalogEntry {
|
interface CatalogEntry {
|
||||||
name: string;
|
name: string;
|
||||||
type: ItemKind;
|
type: ItemKind;
|
||||||
@@ -48,28 +54,34 @@ interface CatalogEntry {
|
|||||||
|
|
||||||
function discoverItems(): { kind: ItemKind; manifest: RegistryItem }[] {
|
function discoverItems(): { kind: ItemKind; manifest: RegistryItem }[] {
|
||||||
const items: { kind: ItemKind; manifest: RegistryItem }[] = [];
|
const items: { kind: ItemKind; manifest: RegistryItem }[] = [];
|
||||||
|
const registryManifest = JSON.parse(
|
||||||
|
readFileSync(join(registryDir, "registry.json"), "utf-8"),
|
||||||
|
) as { items?: { name: string; type: string }[] };
|
||||||
|
|
||||||
const dirs: { kind: ItemKind; dir: string }[] = [
|
for (const item of registryManifest.items ?? []) {
|
||||||
{ kind: "block", dir: join(registryDir, "blocks") },
|
const kind =
|
||||||
{ kind: "component", dir: join(registryDir, "components") },
|
item.type === "hyperframes:block"
|
||||||
];
|
? "block"
|
||||||
|
: item.type === "hyperframes:component"
|
||||||
|
? "component"
|
||||||
|
: null;
|
||||||
|
|
||||||
for (const { kind, dir } of dirs) {
|
if (!kind) continue;
|
||||||
if (!existsSync(dir)) continue;
|
|
||||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
||||||
if (!entry.isDirectory()) continue;
|
|
||||||
const manifestPath = join(dir, entry.name, "registry-item.json");
|
|
||||||
if (!existsSync(manifestPath)) continue;
|
|
||||||
|
|
||||||
let manifest: RegistryItem;
|
const manifestPath = join(registryDir, typeDir(kind), item.name, "registry-item.json");
|
||||||
try {
|
if (!existsSync(manifestPath)) {
|
||||||
manifest = JSON.parse(readFileSync(manifestPath, "utf-8")) as RegistryItem;
|
console.warn(` ⚠ Skipping ${item.name}: missing ${manifestPath}`);
|
||||||
} catch (err) {
|
continue;
|
||||||
console.warn(` ⚠ Skipping ${manifestPath}: ${(err as Error).message}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
items.push({ kind, manifest });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let manifest: RegistryItem;
|
||||||
|
try {
|
||||||
|
manifest = JSON.parse(readFileSync(manifestPath, "utf-8")) as RegistryItem;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(` ⚠ Skipping ${manifestPath}: ${(err as Error).message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
items.push({ kind, manifest });
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
|
return items.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
|
||||||
@@ -89,6 +101,7 @@ function generateItemMdx(kind: ItemKind, manifest: RegistryItem): string {
|
|||||||
const tags = manifest.tags ?? [];
|
const tags = manifest.tags ?? [];
|
||||||
const tagBadges = tags.map((t) => `\`${t}\``).join(" ");
|
const tagBadges = tags.map((t) => `\`${t}\``).join(" ");
|
||||||
const installCmd = `npx hyperframes add ${manifest.name}`;
|
const installCmd = `npx hyperframes add ${manifest.name}`;
|
||||||
|
const source = manifest as RegistryItem & SourceMetadata;
|
||||||
|
|
||||||
const lines: string[] = [
|
const lines: string[] = [
|
||||||
"---",
|
"---",
|
||||||
@@ -106,8 +119,17 @@ function generateItemMdx(kind: ItemKind, manifest: RegistryItem): string {
|
|||||||
lines.push(tagBadges, "");
|
lines.push(tagBadges, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manifest.author) {
|
||||||
|
const author = source.authorUrl ? `[${manifest.author}](${source.authorUrl})` : manifest.author;
|
||||||
|
lines.push(`Created by ${author}.`, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.sourcePrompt) {
|
||||||
|
lines.push("## Source Prompt", "", "```text", source.sourcePrompt, "```", "");
|
||||||
|
}
|
||||||
|
|
||||||
// Preview video with poster — muted loop, no autoPlay (matches examples page).
|
// Preview video with poster — muted loop, no autoPlay (matches examples page).
|
||||||
const previewPath = `/images/catalog/${typeDir(kind)}/${manifest.name}`;
|
const previewPath = `${catalogImageBase}/${typeDir(kind)}/${manifest.name}`;
|
||||||
lines.push(
|
lines.push(
|
||||||
`<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="${previewPath}.mp4" poster="${previewPath}.png" autoPlay muted loop playsInline />`,
|
`<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="${previewPath}.mp4" poster="${previewPath}.png" autoPlay muted loop playsInline />`,
|
||||||
"",
|
"",
|
||||||
@@ -227,7 +249,7 @@ function main(): void {
|
|||||||
description: manifest.description,
|
description: manifest.description,
|
||||||
tags: manifest.tags ?? [],
|
tags: manifest.tags ?? [],
|
||||||
href: `/catalog/${dir}/${manifest.name}`,
|
href: `/catalog/${dir}/${manifest.name}`,
|
||||||
preview: `/images/catalog/${dir}/${manifest.name}.png`,
|
preview: `${catalogImageBase}/${dir}/${manifest.name}.png`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user