feat(catalog): add the seven 3D-motion pieces as installable registry blocks (#4056)

* feat(catalog): add the seven 3D-motion pieces as installable registry blocks

Each piece ships as a registry block with a CDN poster and preview, a live
catalog detail page and its source. Scripts travel inside the catalog payload
(shared vendor libraries fetched once, item scripts inline, the one HDR as a
data URI) because the docs host only serves fonts, images and JSON.

NO_ESSAY_COMMENTS_OK: the long comment blocks are in vendored libraries and
the ported frost source under registry/blocks, not hand-written here.
HF_MAX_NONLFS_KB=6000: four hosted texture PNGs under docs/public/catalog/items
are runtime assets the docs host must serve as files.
fallow skipped locally: it audits against origin/main and this branch is
stacked on an older main.

* chore(catalog): regenerate gallery data and nav after rebasing onto main

* fix(catalog): read the install manifest through the bounded project reader

* fix(catalog): a script the inliner missed fails the payload instead of shipping a dead URL

* refactor(catalog): the leftover-script check reuses the reference finder

* fix(catalog): frost and glass textures load from the CDN and the iteration presets are gone

* chore(catalog): regenerate the frost and glass payloads and nav

* fix(catalog): frost and glass textures are hosted through the registry url field, not hardcoded

* chore(catalog): regenerate the frost and glass payloads against the hosted texture URLs

* fix(catalog): the hosted-reference rewrite refuses a bare name it cannot safely replace

* ci(catalog): verify changed catalog payloads in headless Chrome and check the docs catalog

* feat(catalog): every item page follows the shared layout with a Tune panel and tabs

* fix(catalog): let the texture-mask item host its masks so its tile plays live

* chore(catalog): regenerate item pages, gallery data and the texture-mask payload

* chore(catalog): regenerate item pages after merging main

* fix(catalog): declared-only variables get the Tune panel, Code and Docs tabs capped

* feat(catalog): give the blend-difference caption a demo scene so its tile plays live

* chore(catalog): live payload for the blend-difference caption

* perf(catalog): heavy 3d pieces compile and draw before ready, not on the first visible frame

* feat(catalog): the avatar promo card hosts its videos, audio and fonts on the CDN

* feat(catalog): the avatar promo card tile plays live from its CDN-hosted media

* chore(catalog): the avatar promo card plays live, no allowlist left

* chore(catalog): shorten two comment blocks

* chore(catalog): trim first-party comment blocks to the comment-check limits

* chore(catalog): shorten two frost source comments

* fix(catalog): the Docs tab renders as prose again, and the detail page honours reduced motion

Frame-mode pages carry no prose wrapper, and the detail component sat inside
not-prose, so the Docs slot's headings, paragraphs, links and lists had no
styling at all. The slots now carry their own prose classes and the chrome
around them keeps not-prose.

The recorded clip also gets the reduced-motion guard the docs-snippet check
requires, and the generator reads stability from the source metadata type so
the scripts typecheck passes.

* fix(catalog): docs-tab paragraphs render as blocks

Mintlify emits paragraphs as inline spans, so two paragraphs in the Docs slot ran together.

* fix(ci): drop the duplicate docs_catalog job output

git's line-based auto-merge silently duplicated the outputs key when
main and this branch each added it on non-adjacent lines during the
rebase; oxfmt's yaml parser rejects the repeated map key.

* fix(catalog): close four CodeQL alerts in the frost source

* chore(catalog): allowlist the avatar-promo-card CDN asset deletions, format sync-docs-catalog

check-no-main-deletions.mjs was rejecting 11 local mp4/font files this branch
already deleted in favor of CDN hosting; scripts/sync-docs-catalog.mjs had one
line over the 100-col print width from an earlier edit in this branch.

* fix(catalog): detail tabs use docs prose spacing, Install first; previews skip no-WebGPU runners

* fix(catalog): the docs tab scrolls with the page instead of a capped inner box

* refactor(catalog): split the payload asset and verify scripts into single-purpose helpers

* refactor(catalog): split the preview generator into single-purpose helpers

* ci(catalog): fail when the committed catalog differs from generator output

The docs host serves the committed catalog and nothing regenerates it, so a stale
copy shipped silently. The catalog payloads job now generates into a temp dir and
compares it with the committed tree.

* chore(catalog): regenerate docs/public/catalog with the refactored scripts

One full regeneration from an empty output tree with the current scripts. The tree grows
from 58.4 MB to 65.1 MB. Two hosted assets are about 2 MB each (an avatar png and a wav);
the same bytes are already committed under assets/, so HF_MAX_NONLFS_KB is raised for
this commit only.

* fix(catalog): run the catalog script tests under the runner they are written for

The rebase put three node:test files in the vitest half of test:scripts, where they report
"No test suite found". They now sit in the node --test list.

* fix(catalog): a WebGPU item without an adapter shows its recorded clip and says why

* ci(catalog): the drift check runs from a committed fetch mirror with no network

Generation now serves every font and CDN script fetch from scripts/catalog-fetch-mirror and fails
when a fetch is not in it, even if the caller swallowed the error. CATALOG_FETCH_MIRROR=record
refreshes the mirror from the live network. The docs_catalog filter also watches packages/core,
packages/producer and the mirror, and the 13 re-hashed font files are listed in ALLOWED_DELETIONS.

* chore(catalog): clean up the temp font cache and sort the mirror index without a locale

* fix(catalog): hide Tune when the clip stands in, time out the adapter request, share one check

* fix(catalog): the adapter probe survives a sync throw and a hang; test the clip guard

* fix(catalog): keep the adapter probe inside the component, Mintlify evaluates only exports

* fix(catalog): run the detail and page generator tests in CI; no Tune flash while probing WebGPU

* docs(catalog): credit vendored libraries and make each SKILL.md list what the block installs

CREDITS.md names every vendored dependency with its upstream and licence, and flags the two leaf
textures whose origin is still to be confirmed. Each SKILL.md Files table now comes from the block
manifest and its install and render paths point at compositions/<name>/. A test checks that every
path a SKILL.md names is a manifest file that exists.

* fix(catalog): a declared WebGPU item passes verify by its manifest tag, not by console wording

verify-catalog-payloads forgave a missing adapter only by matching the markup and the message, which
library-reported errors and widgets never satisfy. The webgpu tag in the item manifest is now the
owner: a tagged item ignores its page and console errors, an untagged one gets no forgiveness, and a
marker payload with no html is listed instead of crashing the run.

* docs(catalog): correct the credits per block and guard the mount and render paths

CREDITS.md states the three.js version and vendored-or-CDN status per block, adds d3-delaunay,
Clipper on the CDN and the Google Fonts loads, and lists every texture with no known origin.
The SKILL.md test now also checks the mount and render paths against the manifest composition.

* test(catalog): read each block manifest once in the SKILL.md file test

The two checks shared a copied manifest read, which fallow flagged as a clone with a high score.

* fix(catalog): commit the hosted item files a gitignore rule kept out, and check git not the disk

The compositions/ and *-ad/ ignore rules matched folder names under docs/public/catalog/items,
so 33 hosted fonts, textures and marks were generated but never committed and would 404 on
the docs host. The rules now have a negation for that tree and the files are added.

The drift check read the checkout, where git-ignored files still exist, so it passed locally
and failed only in CI. It now unpacks HEAD with git archive and compares that.

* fix(catalog): a tile keeps its poster until the composition has painted

* fix(catalog): a tagged WebGPU item forgives only the adapter-absent error, and test the wiring

The manifest webgpu tag now decides whether an item may lack an adapter, and the error wording
decides which lines are that absence. Any other page or console error on the item, and every
failed request, still fails. The per-item decision is one exported function the tests drive,
marker payloads are parsed by a tested helper, and the SKILL.md variable counts are checked
against their tables (cuboid-carousel said forty-nine for forty).

* fix(catalog): the pinned group's posters load eagerly at high priority

* fix(catalog): type the tile reveal test so the scripts typecheck passes

* fix(catalog): stop the adapter signature matching an unrelated failed adapter request

failed to request .*adapter also matched errors about other adapters. It now matches only a
request for the WebGPU adapter, so a tagged item cannot hide an unrelated failure behind it.

* test(catalog): a tagged item still fails on an error about some other adapter

* feat(catalog): the install command sits above the preview, as on the live catalog

* fix(catalog): find the install slot through Mintlify's child wrapper

* fix(docs): catalog copy buttons keep their size when the label swaps to Copied

* refactor(docs): name the shown copy label once in the catalog copy button

* fix(docs): primary catalog button keeps a readable label on hover and after copy

* fix(docs): inset the catalog tab strip and tab content to match the boxed panels
This commit is contained in:
Miguel Ángel
2026-09-19 16:53:29 -04:00
committed by GitHub
parent ef64323174
commit f24ff83834
954 changed files with 154319 additions and 100801 deletions
+2 -2
View File
@@ -967,8 +967,8 @@
// file-level fingerprint invalidation re-flags the inherited findings even at
// unchanged line numbers.
"packages/core/src/runtime/picker.ts",
// generate-catalog-payloads.ts: hostsOwnDirectory, parseArgs and main are
// untouched. buildPayload's canvas-draw-element branch now writes a marker
// generate-catalog-payloads.ts: hostsOwnDirectory, parseArgs and generate (the
// former main body, moved unchanged) are untouched. buildPayload's canvas-draw-element branch now writes a marker
// via a small writePayload helper instead of dropping the payload, but at
// the same branch count — its flagged 14-branch CRAP score predates this PR.
"scripts/generate-catalog-payloads.ts",
+29 -1
View File
@@ -61,7 +61,11 @@ jobs:
docs_catalog:
- "docs/**"
- "registry/**"
- "scripts/*docs-catalog*"
- "scripts/*catalog*"
- "scripts/catalog-fetch-mirror/**"
- "scripts/docs-catalog-shared.mjs"
- "packages/core/**"
- "packages/producer/**"
- "package.json"
- ".github/workflows/ci.yml"
code:
@@ -168,6 +172,30 @@ jobs:
- run: bun run lint
- run: bun run check:docs-catalog
# Loads every catalog payload a PR changes in headless Chrome. A static scan of the
# markup cannot see an asset a script fetches, and the docs host serves only fonts,
# images and JSON, so a payload that runs locally can 404 there.
catalog-payloads:
name: Catalog payloads
needs: changes
if: needs.changes.outputs.docs_catalog == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
- run: bash scripts/ci/install-workspace-dependencies.sh
- run: bun run --filter '@hyperframes/{parsers,lint,studio-server}' build
- run: bun run --cwd packages/core build
- run: bun run --cwd packages/core build:hyperframes-runtime
- run: bun run verify:catalog-payloads --changed origin/main
- run: bun run check:catalog-drift
# `fallow audit` runs dead-code + complexity + duplication analysis scoped to
# the changed files. The default `--gate new-only` means existing legacy
# findings don't fail the build — only NEW issues introduced by the PR do.
+4
View File
@@ -188,3 +188,7 @@ tmp/
# Local Chrome-for-Testing downloads (large binaries, never commit)
chrome/
# Hosted catalog item files are generated output the docs host serves; the rules above match their folder names
!docs/public/catalog/items/**/compositions/
!docs/public/catalog/items/*-ad/
+2
View File
@@ -13,6 +13,8 @@
"packages/producer/tests/**/src/vendor/**",
"docs/public/catalog/",
"registry/blocks/**/lib/*.iife.js",
"registry/blocks/{glass-shard-title,code-slice-hero,cuboid-carousel,frost-sequence-camera-orbit,orbit-card}/**/*.{js,mjs}",
"registry/blocks/frost-sequence-camera-orbit/source/**",
"chrome/"
],
"overrides": [
+17
View File
@@ -12,6 +12,23 @@ packages/**/__goldens__/
# vendored third-party bundles inside skills — never reformat
skills/**/assets/vendor/
skills/**/*.min.js
# vendored/bundled third-party JS inside registry blocks — never reformat.
# Without this the format hook silently re-pretty-prints a minified bundle,
# roughly doubling its size. Listed by exact file: these blocks also ship
# hand-authored source (glass-main.mjs, surface.js, orbit-scene.js, etc) that must stay formattable.
registry/blocks/**/lib/*.iife.js
registry/blocks/frost-sequence-camera-orbit/assets/frost.js
registry/blocks/frost-sequence-camera-orbit/assets/motion.js
registry/blocks/frost-sequence-camera-orbit/assets/gsap-3.14.2.min.js
registry/blocks/glass-shard-title/assets/glass-main.js
registry/blocks/code-slice-hero/assets/gsap-3.14.2.min.js
registry/blocks/cuboid-carousel/assets/three.core.min.js
registry/blocks/cuboid-carousel/assets/three.module.min.js
registry/blocks/cuboid-carousel/assets/gsap-3.14.2.min.js
registry/blocks/cuboid-carousel/assets/addons/**/*.js
registry/blocks/orbit-card/assets/three.core.min.js
registry/blocks/orbit-card/assets/three.module.min.js
registry/blocks/orbit-card/assets/gsap-3.14.2.min.js
# reference snippets with intentional pseudo-markup (literal "..." attributes)
skills/talking-head-recut/references/frames/polaroid.html
# repo-native skill examples with intentional pseudo-markup placeholders
+31
View File
@@ -28,3 +28,34 @@ broader Node.js ecosystem.
- **[mediabunny](https://github.com/Vanilagy/mediabunny)** — media toolkit used
in the studio for fast metadata extraction from file headers. Licensed under
the [Mozilla Public License 2.0 (MPL-2.0)](https://mozilla.org/MPL/2.0/).
- The seven 3D-motion catalog pieces (`canopy-part-title`, `glass-shard-title`,
`code-slice-hero`, `frost-sequence-camera-orbit`, `cuboid-carousel`,
`orbit-card`, `wireframe-portal-title`) are contributed with their author's
permission under this repository's license. What they vendor or load, by upstream:
- **[three.js](https://threejs.org)**, MIT. Vendored as r185 in `frost-sequence-camera-orbit`,
`cuboid-carousel` and `orbit-card` (`Three-LICENSE.txt` beside the copy); bundled as r181 inside
`glass-shard-title`'s `glass-main.js`; loaded from the jsDelivr CDN by `canopy-part-title`
(0.170.0) and `wireframe-portal-title` (0.181.2).
- **[GSAP](https://gsap.com)** 3.14.2, under the [GSAP Standard License](https://gsap.com/standard-license)
(not an OSI open-source licence). Vendored in `code-slice-hero`, `cuboid-carousel`,
`frost-sequence-camera-orbit` and `orbit-card` with `GSAP-NOTICE.txt`; loaded from the CDN by
`glass-shard-title`, `canopy-part-title` and `wireframe-portal-title`.
- **[three-mesh-bvh](https://github.com/gkjohnson/three-mesh-bvh)** 0.9.14 and
**[opentype.js](https://github.com/opentypejs/opentype.js)** 2.x (range in the block's
`source/package.json`), both MIT, bundled into the frost block's `frost.js`.
- **[Clipper](https://sourceforge.net/projects/jsclipper/)** 6.4.2 (JavaScript port of Angus
Johnson's Clipper), Boost Software License 1.0: build-time source of `frost-sequence-camera-orbit`
and loaded from the CDN as `clipper-lib` by `wireframe-portal-title`.
- **[d3-delaunay](https://github.com/d3/d3-delaunay)** 6.0.4, ISC, loaded from the CDN by
`glass-shard-title`.
- **[Geist](https://github.com/vercel/geist-font)**, **[Archivo](https://github.com/Omnibus-Type/Archivo)**
and **[Cormorant Garamond](https://github.com/CatharsisFonts/Cormorant)**, SIL Open Font License 1.1,
vendored with their licence text; `canopy-part-title` (Gelasio) and `cuboid-carousel` (Inter) load
their fonts from Google Fonts at run time.
- **[Ferndale Studio 01](https://polyhaven.com/a/ferndale_studio_01)** HDR from Poly Haven, CC0
(`glass-shard-title`); it needs no licence text.
- Origin to be confirmed with the author: `canopy-part-title/assets/leaf-surface-color.webp` and
`leaf-surface-normal.webp`, `glass-shard-title/assets/matcap-1.png`,
`frost-sequence-camera-orbit/assets/shards-atlas.png`,
`frost-sequence-camera-orbit/assets/textures/ice-inclusions-generated.png` and
`frost-sequence-camera-orbit/assets/textures/bluenoise64.png`.
File diff suppressed because it is too large Load Diff
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "App Showcase"
description: "Fitness app product showcase with three floating smartphone screens"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/app-showcase.json"
compositionId="app-showcase"
compositionSrc="compositions/app-showcase.html"
title="App Showcase"
description="Fitness app product showcase with three floating smartphone screens"
variables={[]}
meta={{"duration":5.5,"width":1920,"height":1080,"category":"Showcases","badge":"Stable","codeLines":947}}
attribution={{"path":"registry/blocks/app-showcase","tags":["showcase","app","3d"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/app-showcase/app-showcase.html"
>
<InstallCommand command="npx hyperframes add app-showcase" item="app-showcase" />
<CatalogSlot slot="code">
That writes one file: `compositions/app-showcase.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="app-showcase preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/app-showcase.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/app-showcase.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="app-showcase.html">
```html
```html app-showcase.html
<!doctype html>
<html lang="en">
<head>
@@ -972,16 +972,37 @@ That writes one file: `compositions/app-showcase.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add app-showcase" item="app-showcase" />
```html
<div data-composition-id="app-showcase" data-composition-src="compositions/app-showcase.html" data-start="0" data-duration="5.5" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/app-showcase.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 5.5 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="app-showcase"
data-composition-src="compositions/app-showcase.html"
data-start="0"
data-duration="5.5"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `showcase` `app` `3d`.
@@ -991,3 +1012,7 @@ Tagged `showcase` `app` `3d`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Apple Money Count"
description: "Apple-style finance counter that counts from $0 to $10,000, flashes green, and bursts money icons with sound."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/apple-money-count.json"
compositionId="apple-money-count"
compositionSrc="compositions/apple-money-count.html"
title="Apple Money Count"
description="Apple-style finance counter that counts from $0 to $10,000, flashes green, and bursts money icons with sound."
variables={[]}
meta={{"duration":5,"width":1920,"height":1080,"category":"Showcases","badge":"Stable","codeLines":320}}
attribution={{"author":"Stronkter","authorUrl":"https://x.com/Stronkter","path":"registry/blocks/apple-money-count","tags":["showcase","finance","kinetic","youtube","sfx"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/apple-money-count/apple-money-count.html"
>
<InstallCommand command="npx hyperframes add apple-money-count" item="apple-money-count" />
<CatalogSlot slot="code">
That writes `compositions/apple-money-count.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="apple-money-count preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/apple-money-count.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/apple-money-count.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="apple-money-count.html">
```html
```html apple-money-count.html
<!doctype html>
<html lang="en">
<head>
@@ -345,16 +345,37 @@ That writes `compositions/apple-money-count.html`, plus 1 supporting file under
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add apple-money-count" item="apple-money-count" />
```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>
That writes `compositions/apple-money-count.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 5 seconds at 1920×1080. Paste this into your composition:
```html index.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>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `showcase` `finance` `kinetic` `youtube` `sfx`.
@@ -374,3 +395,7 @@ Created by [Stronkter](https://x.com/Stronkter).
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+26 -602
View File
@@ -1,24 +1,27 @@
---
title: "Bar Chart Race"
description: "Ranked bars grow and overtake each other as a time series advances, with the axis rescaling as values climb and the accent handing over to whoever leads"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
import { VariablesExplorer } from "/snippets/variables-explorer.jsx";
## Install
<InstallCommand command="npx hyperframes add bar-chart-race" item="bar-chart-race" />
That writes one file: `compositions/bar-chart-race.html`.
<VariablesExplorer
<CatalogDetail
previewSrc="/public/catalog/blocks/bar-chart-race.json"
compositionId="bar-chart-race"
compositionSrc="compositions/bar-chart-race.html"
title="Bar Chart Race"
description="Ranked bars grow and overtake each other as a time series advances, with the axis rescaling as values climb and the accent handing over to whoever leads"
variables={[{"id":"title","type":"string","label":"Title","default":"Streaming Subscribers by Service"},{"id":"subtitle","type":"string","label":"Subtitle","default":"Ranked by reported subscribers"},{"id":"periods","type":"string","label":"Period labels (comma separated)","default":"2019, 2020, 2021, 2022, 2023, 2024"},{"id":"series","type":"string","label":"Series (one per line: Name: v1, v2, ...)","default":"Northwind: 42, 58, 71, 96, 118, 131\nCobalt: 30, 46, 68, 92, 126, 168\nFerry: 55, 62, 66, 70, 74, 79\nMarlow: 18, 33, 52, 61, 88, 104\nAster: 25, 28, 44, 58, 63, 72\nPell: 12, 20, 39, 47, 55, 90\nQuill: 8, 11, 15, 24, 40, 66\nDunmore: 35, 37, 38, 40, 42, 44"},{"id":"barCount","type":"number","label":"Bars shown","default":6,"min":3,"max":12,"step":1},{"id":"periodDuration","type":"number","label":"Seconds per period","default":2,"min":0.4,"max":6,"step":0.1},{"id":"valuePrefix","type":"string","label":"Value prefix","default":"$"},{"id":"valueSuffix","type":"string","label":"Value suffix","default":"M"},{"id":"valueDecimals","type":"number","label":"Value decimals","default":0,"min":0,"max":3,"step":1},{"id":"accent","type":"color","label":"Leader accent","default":"#c8452d"}]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Data","badge":"Stable","codeLines":584}}
attribution={{"path":"registry/blocks/bar-chart-race","tags":["data","chart","statistics","ranking"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/bar-chart-race/bar-chart-race.html"
>
<CatalogSlot slot="code">
```html bar-chart-race.html
<!doctype html>
<html lang="en">
@@ -606,7 +609,17 @@ That writes one file: `compositions/bar-chart-race.html`.
</html>
```
</VariablesExplorer>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add bar-chart-race" item="bar-chart-race" />
That writes one file: `compositions/bar-chart-race.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
@@ -685,599 +698,6 @@ defaults, so this behaves exactly like the preview above until you change one:
></div>
```
## Source
<Accordion title="bar-chart-race.html">
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Bar Chart Race</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
overflow: hidden;
background-color: #f5f3ef;
}
</style>
</head>
<body>
<div
id="bcr-root"
data-composition-id="bar-chart-race"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="12"
data-composition-variables='[
{"id":"title","type":"string","label":"Title","default":"Streaming Subscribers by Service"},
{"id":"subtitle","type":"string","label":"Subtitle","default":"Ranked by reported subscribers"},
{"id":"periods","type":"string","label":"Period labels (comma separated)","default":"2019, 2020, 2021, 2022, 2023, 2024"},
{"id":"series","type":"string","label":"Series (one per line: Name: v1, v2, ...)","default":"Northwind: 42, 58, 71, 96, 118, 131\nCobalt: 30, 46, 68, 92, 126, 168\nFerry: 55, 62, 66, 70, 74, 79\nMarlow: 18, 33, 52, 61, 88, 104\nAster: 25, 28, 44, 58, 63, 72\nPell: 12, 20, 39, 47, 55, 90\nQuill: 8, 11, 15, 24, 40, 66\nDunmore: 35, 37, 38, 40, 42, 44"},
{"id":"barCount","type":"number","label":"Bars shown","default":6,"min":3,"max":12,"step":1},
{"id":"periodDuration","type":"number","label":"Seconds per period","default":2,"min":0.4,"max":6,"step":0.1},
{"id":"valuePrefix","type":"string","label":"Value prefix","default":"$"},
{"id":"valueSuffix","type":"string","label":"Value suffix","default":"M"},
{"id":"valueDecimals","type":"number","label":"Value decimals","default":0,"min":0,"max":3,"step":1},
{"id":"accent","type":"color","label":"Leader accent","default":"#c8452d"}
]'
>
<div id="bcr-stage" class="clip" data-start="0" data-duration="12" data-track-index="0">
<div id="bcr-bg"></div>
<div id="bcr-head">
<div id="bcr-head-left">
<h1 id="bcr-title">Streaming Subscribers by Service</h1>
<p id="bcr-subtitle">Ranked by reported subscribers</p>
</div>
<div id="bcr-head-right">
<span id="bcr-period-caption">Period</span>
<span id="bcr-period">2019</span>
</div>
</div>
<!-- Rows live here, one per series, positioned by rank. -->
<div id="bcr-plot"></div>
<!-- Axis is painted above the rows: the tick labels sliding left is the
visible evidence that the scale is rescaling as values climb. -->
<div id="bcr-axis"></div>
<p id="bcr-source">Placeholder data</p>
</div>
<style>
#bcr-root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
font-family: Inter, sans-serif;
color: #1f1d1b;
}
#bcr-stage {
position: absolute;
inset: 0;
}
/* Scene fill goes on a full-bleed child, never the composition root. */
#bcr-bg {
position: absolute;
inset: 0;
background-color: #f5f3ef;
}
#bcr-head {
position: absolute;
top: 58px;
left: 96px;
width: 1728px;
display: flex;
align-items: flex-start;
justify-content: space-between;
}
#bcr-title {
font-size: 46px;
font-weight: 700;
letter-spacing: -0.015em;
line-height: 1.1;
}
#bcr-subtitle {
margin-top: 10px;
font-size: 19px;
font-weight: 400;
color: #6b6560;
}
#bcr-head-right {
display: flex;
flex-direction: column;
align-items: flex-end;
}
#bcr-period-caption {
font-size: 15px;
font-weight: 600;
letter-spacing: 0.18em;
text-transform: uppercase;
color: #6b6560;
}
#bcr-period {
margin-top: 4px;
font-size: 72px;
font-weight: 700;
line-height: 1;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
}
#bcr-plot {
position: absolute;
left: 0;
top: 236px;
width: 1920px;
height: 760px;
overflow: hidden;
}
.bcr-row {
position: absolute;
left: 0;
top: 0;
width: 1920px;
}
/* Rows are transparent so both bars of an overtake stay visible sliding
past each other; only the two TEXT blocks carry an opaque backing, so
the front row's label covers the one it crosses instead of tangling
with it. */
.bcr-name {
position: absolute;
left: 96px;
width: 272px;
top: 50%;
transform: translateY(-50%);
text-align: right;
font-size: 26px;
font-weight: 600;
line-height: 1.7;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background-color: #f5f3ef;
}
.bcr-bar {
position: absolute;
left: 400px;
top: 50%;
transform: translateY(-50%);
border-radius: 3px;
background-color: #1f1d1b;
}
.bcr-value {
position: absolute;
left: 404px;
top: 50%;
padding: 0 12px;
font-size: 26px;
font-weight: 600;
line-height: 1.7;
font-variant-numeric: tabular-nums;
white-space: nowrap;
background-color: #f5f3ef;
}
#bcr-axis {
position: absolute;
inset: 0;
z-index: 5000;
}
.bcr-tick-line {
position: absolute;
top: 232px;
left: 0;
width: 1px;
height: 768px;
background-color: rgba(31, 29, 27, 0.11);
}
.bcr-tick-line.bcr-tick-zero {
background-color: rgba(31, 29, 27, 0.5);
}
.bcr-tick-label {
position: absolute;
top: 198px;
left: 0;
font-size: 17px;
font-weight: 500;
font-variant-numeric: tabular-nums;
color: #6b6560;
white-space: nowrap;
}
#bcr-source {
position: absolute;
left: 96px;
top: 1014px;
font-size: 16px;
color: #6b6560;
}
</style>
<script>
(function () {
"use strict";
// ---- variables -------------------------------------------------
// The declaration attribute is the single source of truth: when the
// runtime is absent (raw file open) we parse the same attribute.
function readVars() {
// Read the declaration off THIS block's own root, never
// document.documentElement: mounted as a sub-composition the block's
// <html> is discarded and documentElement is the HOST's, so a
// documentElement read returns the wrong element (or null) and the
// block renders with no series at all. Runtime / host / --variables
// values are layered on top as overrides, so the block does not
// depend on the runtime being injected before this script runs.
var out = {};
var root = document.getElementById("bcr-root");
try {
JSON.parse(root.getAttribute("data-composition-variables")).forEach(function (d) {
out[d.id] = d.default;
});
} catch (err) {
// Declaration unreadable; the per-variable literal fallbacks below
// still produce a renderable composition.
}
var hf = window.__hyperframes;
var overrides =
hf && typeof hf.getVariables === "function"
? hf.getVariables()
: window.__hfVariables;
if (overrides && typeof overrides === "object") {
for (var key in overrides) {
if (overrides[key] !== undefined) out[key] = overrides[key];
}
}
return out;
}
function num(value, fallback, lo, hi) {
var n = typeof value === "number" ? value : Number.parseFloat(value);
if (!Number.isFinite(n)) n = fallback;
return Math.min(hi, Math.max(lo, n));
}
function str(value, fallback) {
return typeof value === "string" && value.length > 0 ? value : fallback;
}
var V = readVars();
var ACCENT = str(V.accent, "#c8452d");
var BAR_COLOR = "#1f1d1b";
var BAR_COUNT = Math.round(num(V.barCount, 6, 1, 24));
var PERIOD_DURATION = num(V.periodDuration, 2, 0.1, 30);
var DECIMALS = Math.round(num(V.valueDecimals, 0, 0, 6));
var PREFIX = typeof V.valuePrefix === "string" ? V.valuePrefix : "";
var SUFFIX = typeof V.valueSuffix === "string" ? V.valueSuffix : "";
// ---- data format ------------------------------------------------
// Wide table, one series per line: "Name: v1, v2, v3".
// `;` is accepted as a line separator so the whole table survives a
// single-line text input. Short rows hold their last value.
function parsePeriods(text) {
return String(text)
.split(",")
.map(function (s) {
return s.trim();
})
.filter(function (s) {
return s.length > 0;
});
}
function parseSeries(text, periodCount) {
var out = [];
var lines = String(text).split(/[\n;]+/);
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (!line) continue;
var split = line.indexOf(":");
if (split < 0) continue;
var label = line.slice(0, split).trim();
if (!label) continue;
var values = line
.slice(split + 1)
.split(",")
.map(function (s) {
return Number.parseFloat(s.trim());
})
.filter(function (n) {
return Number.isFinite(n);
});
if (values.length === 0) continue;
while (values.length < periodCount) values.push(values[values.length - 1]);
out.push({ label: label, values: values.slice(0, periodCount) });
}
return out;
}
var PERIODS = parsePeriods(str(V.periods, "2019, 2020, 2021, 2022, 2023, 2024"));
if (PERIODS.length === 0) PERIODS = ["1"];
var SERIES = parseSeries(str(V.series, ""), PERIODS.length);
var T = PERIODS.length;
var N = SERIES.length;
// ---- closed-form state ------------------------------------------
// k = 10 interpolated keyframes per period (Bostock's number). Rank is
// computed at each keyframe from the interpolated value and baked once;
// a bar's on-screen row is then solved FROM rank, so two bars can never
// cross without swapping. Everything below is a pure function of t.
var K = 10;
var KF_DURATION = PERIOD_DURATION / K;
var KF_COUNT = T > 1 ? (T - 1) * K + 1 : 1;
var RACE_SECONDS = T > 1 ? (T - 1) * PERIOD_DURATION : 0;
function clamp(v, lo, hi) {
return v < lo ? lo : v > hi ? hi : v;
}
function valueAt(t, series) {
if (T < 2) return series.values[0];
var u = t / PERIOD_DURATION;
var i = clamp(Math.floor(u), 0, T - 2);
var f = clamp(u - i, 0, 1);
return series.values[i] + (series.values[i + 1] - series.values[i]) * f;
}
// ranks[m][j] = integer rank of series j at keyframe m (0 = leader).
var RANKS = [];
for (var m = 0; m < KF_COUNT; m++) {
var tm = m * KF_DURATION;
var order = SERIES.map(function (s, j) {
return { j: j, v: valueAt(tm, s) };
});
// Descending by value; index breaks ties so the sort is total and
// therefore identical on every evaluation.
order.sort(function (a, b) {
return b.v - a.v || a.j - b.j;
});
var row = new Array(N);
for (var r = 0; r < order.length; r++) row[order[r].j] = r;
RANKS.push(row);
}
function smoothstep(x) {
return x * x * (3 - 2 * x);
}
// Continuous row position solved from the baked ranks. A rank change
// between two adjacent keyframes IS the swap, and it plays out over one
// keyframe interval (PERIOD_DURATION / 10) instead of snapping.
function rankPosAt(t, j) {
if (KF_COUNT < 2) return RANKS[0][j];
var m = t / KF_DURATION;
var m0 = clamp(Math.floor(m), 0, KF_COUNT - 2);
var e = smoothstep(clamp(m - m0, 0, 1));
var a = RANKS[m0][j];
var b = RANKS[m0 + 1][j];
return a + (b - a) * e;
}
// ---- formatting --------------------------------------------------
function formatValue(v) {
return (
PREFIX +
v.toLocaleString("en-US", {
minimumFractionDigits: DECIMALS,
maximumFractionDigits: DECIMALS,
}) +
SUFFIX
);
}
function hexToRgb(hex) {
var h = String(hex).trim().replace("#", "");
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
var n = Number.parseInt(h, 16);
if (!Number.isFinite(n)) return [31, 29, 27];
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
}
// Accent is the only colour in the piece and it carries exactly one
// meaning: this bar currently leads. It is binary, not blended — a
// half-accent bar would assert "half leading", which is not a fact.
// It hands over at the instant the two values cross, which is also the
// instant the two bars are the same length, so the handover lands on
// the overtake instead of drifting near it. Flat fill, never a
// gradient: a gradient inside a bar makes the longest bar the palest
// exactly where the eye is comparing lengths.
function hexToCss(hex) {
var rgb = hexToRgb(hex);
return "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
}
var BAR_FILL = hexToCss(BAR_COLOR);
var ACCENT_FILL = hexToCss(ACCENT);
function niceStep(x) {
var e = Math.pow(10, Math.floor(Math.log10(x)));
var f = x / e;
return e * (f <= 1 ? 1 : f <= 2 ? 2 : f <= 5 ? 5 : 10);
}
// ---- geometry ----------------------------------------------------
var TRACK_X = 400;
var TRACK_W = 1180;
var PLOT_H = 760;
var PITCH = PLOT_H / BAR_COUNT;
var BAR_H = Math.max(12, PITCH * 0.62);
var TICK_POOL = 12;
// ---- DOM ---------------------------------------------------------
var plot = document.getElementById("bcr-plot");
var axis = document.getElementById("bcr-axis");
var periodEl = document.getElementById("bcr-period");
document.getElementById("bcr-title").textContent = str(
V.title,
"Streaming Subscribers by Service",
);
document.getElementById("bcr-subtitle").textContent = str(
V.subtitle,
"Ranked by reported subscribers",
);
var rows = SERIES.map(function (s) {
var row = document.createElement("div");
row.className = "bcr-row";
row.style.height = PITCH + "px";
// Declared, not silenced: two rows mid-overtake genuinely occupy the
// same band for ~200ms. Readability is handled by the opaque chip on
// each label, so the front one is always legible.
var name = document.createElement("div");
name.className = "bcr-name";
name.setAttribute("data-layout-allow-overlap", "");
name.textContent = s.label;
var bar = document.createElement("div");
bar.className = "bcr-bar";
bar.style.height = BAR_H + "px";
var value = document.createElement("div");
value.className = "bcr-value";
value.setAttribute("data-layout-allow-overlap", "");
row.appendChild(name);
row.appendChild(bar);
row.appendChild(value);
plot.appendChild(row);
return { row: row, bar: bar, value: value };
});
var ticks = [];
for (var k = 0; k < TICK_POOL; k++) {
var line = document.createElement("div");
line.className = "bcr-tick-line" + (k === 0 ? " bcr-tick-zero" : "");
var label = document.createElement("div");
label.className = "bcr-tick-label";
axis.appendChild(line);
axis.appendChild(label);
ticks.push({ line: line, label: label });
}
// ---- render ------------------------------------------------------
function render(t) {
if (N === 0) return;
var values = SERIES.map(function (s) {
return valueAt(t, s);
});
// Paint order follows the CURRENT value, not the row position: the
// bar that has just edged ahead comes forward, so at the instant two
// bars are superimposed the frame shows the one that is overtaking
// (its bar and its label together) rather than the one it passed.
var byValue = values
.map(function (v, i) {
return i;
})
.sort(function (a, b) {
return values[b] - values[a] || a - b;
});
var zRank = new Array(values.length);
for (var q = 0; q < byValue.length; q++) zRank[byValue[q]] = q;
var leader = byValue[0];
var maxValue = values[leader];
// Headroom keeps the leader off the frame edge; the domain is a
// continuous function of t, so the axis glides instead of stepping.
var scaleMax = Math.max(maxValue * 1.06, 1e-6);
var step = niceStep(scaleMax / 4);
for (var i = 0; i < TICK_POOL; i++) {
var tv = i * step;
var visible = tv <= scaleMax;
var tx = TRACK_X + (tv / scaleMax) * TRACK_W;
ticks[i].line.style.transform = "translateX(" + tx.toFixed(2) + "px)";
ticks[i].line.style.opacity = visible ? "1" : "0";
ticks[i].label.style.transform =
"translateX(" + tx.toFixed(2) + "px) translateX(-50%)";
ticks[i].label.style.opacity = visible ? "1" : "0";
ticks[i].label.textContent = visible ? formatValue(tv) : "";
}
for (var j = 0; j < rows.length; j++) {
var rankPos = rankPosAt(t, j);
var barW = (values[j] / scaleMax) * TRACK_W;
var el = rows[j];
el.row.style.transform = "translateY(" + (rankPos * PITCH).toFixed(2) + "px)";
// Fades out only as it slides past the last visible slot.
el.row.style.opacity = clamp(BAR_COUNT - rankPos, 0, 1).toFixed(3);
el.row.style.zIndex = String(1000 - zRank[j]);
el.bar.style.width = barW.toFixed(2) + "px";
el.bar.style.backgroundColor = j === leader ? ACCENT_FILL : BAR_FILL;
el.value.style.transform = "translate(" + barW.toFixed(2) + "px, -50%)";
el.value.textContent = formatValue(values[j]);
}
var pIndex = clamp(Math.floor(t / PERIOD_DURATION), 0, T - 1);
periodEl.textContent = PERIODS[pIndex];
}
// ---- timeline ----------------------------------------------------
// A property SETTER drives the per-frame work: tl.eventCallback("onUpdate")
// does not fire on tl.seek(), a tweened accessor does.
var driver = { _t: 0 };
Object.defineProperty(driver, "t", {
get: function () {
return this._t;
},
set: function (value) {
this._t = value;
render(value);
},
});
var tl = gsap.timeline({ paused: true });
if (RACE_SECONDS > 0) {
tl.to(driver, { t: RACE_SECONDS, duration: RACE_SECONDS, ease: "none" }, 0);
}
render(0);
window.__timelines = window.__timelines || {};
window.__timelines["bar-chart-race"] = tl;
})();
</script>
</div>
</body>
</html>
```
</Accordion>
{/* hf:generated-footer */}
Tagged `data` `chart` `statistics` `ranking`.
@@ -1287,3 +707,7 @@ Tagged `data` `chart` `statistics` `ranking`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+57 -43
View File
@@ -1,54 +1,28 @@
---
title: "Beat Freeze Cut"
description: "A beat-driven speed ramp, freeze-frame hit, and hard-cut sequence for music-led promos and montages."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/beat-freeze-cut.json"
compositionId="beat-freeze-cut"
compositionSrc="compositions/beat-freeze-cut.html"
title="Beat Freeze Cut"
description="A beat-driven speed ramp, freeze-frame hit, and hard-cut sequence for music-led promos and montages."
variables={[]}
meta={{"duration":6,"width":1920,"height":1080,"category":"CSS Transitions","badge":"Stable","codeLines":879}}
attribution={{"path":"registry/blocks/beat-freeze-cut","tags":["transition","music","beat-driven","freeze-frame"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/beat-freeze-cut/beat-freeze-cut.html"
>
<InstallCommand command="npx hyperframes add beat-freeze-cut" item="beat-freeze-cut" />
<CatalogSlot slot="code">
That writes one file: `compositions/beat-freeze-cut.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="beat-freeze-cut preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/beat-freeze-cut.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/beat-freeze-cut.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="beat-freeze-cut"
data-composition-src="compositions/beat-freeze-cut.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Change the colors
Set these CSS variables on the block:
- `--bg-color` — Background. Defaults to `#0B0D10`.
- `--accent-color` — Accent. Defaults to `#00E5C7`.
## Source
<Accordion title="beat-freeze-cut.html">
```html
```html beat-freeze-cut.html
<!doctype html>
<html lang="en">
<head>
@@ -930,7 +904,43 @@ Set these CSS variables on the block:
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add beat-freeze-cut" item="beat-freeze-cut" />
That writes one file: `compositions/beat-freeze-cut.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="beat-freeze-cut"
data-composition-src="compositions/beat-freeze-cut.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Change the colors
Set these CSS variables on the block:
- `--bg-color` — Background. Defaults to `#0B0D10`.
- `--accent-color` — Accent. Defaults to `#00E5C7`.
{/* hf:generated-footer */}
@@ -941,3 +951,7 @@ Tagged `transition` `music` `beat-driven` `freeze-frame`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Blue Sweater Intro Video"
description: "Warm AI creator intro sequence that resolves into an X follow card for @_blue_sweater_."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/blue-sweater-intro-video.json"
compositionId="blue-sweater-intro-video"
compositionSrc="compositions/blue-sweater-intro-video.html"
title="Blue Sweater Intro Video"
description="Warm AI creator intro sequence that resolves into an X follow card for @_blue_sweater_."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Showcases","badge":"Stable","codeLines":1523}}
attribution={{"author":"Joe Sai","authorUrl":"https://x.com/_blue_sweater_","path":"registry/blocks/blue-sweater-intro-video","tags":["showcase","ai","creator","sfx"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/blue-sweater-intro-video/blue-sweater-intro-video.html"
>
<InstallCommand command="npx hyperframes add blue-sweater-intro-video" item="blue-sweater-intro-video" />
<CatalogSlot slot="code">
That writes `compositions/blue-sweater-intro-video.html`, plus 2 supporting files under `assets/` and `assets/sfx/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="blue-sweater-intro-video preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/blue-sweater-intro-video.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/blue-sweater-intro-video.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="blue-sweater-intro-video.html">
```html
```html blue-sweater-intro-video.html
<!doctype html>
<html lang="en">
<head>
@@ -1548,16 +1548,37 @@ That writes `compositions/blue-sweater-intro-video.html`, plus 2 supporting file
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add blue-sweater-intro-video" item="blue-sweater-intro-video" />
```html
<div data-composition-id="blue-sweater-intro-video" data-composition-src="compositions/blue-sweater-intro-video.html" data-start="0" data-duration="12" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes `compositions/blue-sweater-intro-video.html`, plus 2 supporting files under `assets/` and `assets/sfx/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="blue-sweater-intro-video"
data-composition-src="compositions/blue-sweater-intro-video.html"
data-start="0"
data-duration="12"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `showcase` `ai` `creator` `sfx`.
@@ -1569,3 +1590,7 @@ Created by [Joe Sai](https://x.com/_blue_sweater_).
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -36
View File
@@ -1,47 +1,28 @@
---
title: "Camcorder HUD"
description: "Responsive REC, battery, editable date placeholder, and timeline-driven counter overlay for creator-camera and camcorder treatments"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/camcorder-hud.json"
compositionId="camcorder-hud"
compositionSrc="compositions/camcorder-hud.html"
title="Camcorder HUD"
description="Responsive REC, battery, editable date placeholder, and timeline-driven counter overlay for creator-camera and camcorder treatments"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Camera & 3D","badge":"Stable","codeLines":163}}
attribution={{"path":"registry/blocks/camcorder-hud","tags":["camcorder","camera","recording","hud","overlay","creator","media-treatment-overlay"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/camcorder-hud/camcorder-hud.html"
>
<InstallCommand command="npx hyperframes add camcorder-hud" item="camcorder-hud" />
<CatalogSlot slot="code">
That writes one file: `compositions/camcorder-hud.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="camcorder-hud preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/camcorder-hud.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/camcorder-hud.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="camcorder-hud"
data-composition-src="compositions/camcorder-hud.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="camcorder-hud.html">
```html
```html camcorder-hud.html
<!doctype html>
<html lang="en">
<head>
@@ -207,7 +188,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add camcorder-hud" item="camcorder-hud" />
That writes one file: `compositions/camcorder-hud.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="camcorder-hud"
data-composition-src="compositions/camcorder-hud.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -218,3 +228,7 @@ Tagged `camcorder` `camera` `recording` `hud` `overlay` `creator` `media-treatme
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+26 -372
View File
@@ -1,24 +1,27 @@
---
title: "Dolly Zoom (Vertigo Shot)"
description: "Camera tracks toward or away from the subject while the field of view changes the opposite way, so the subject holds its frame size while the background collapses behind it or rushes in. Focal length is solved from dolly distance every frame against d * tan(FOV/2) = const, not keyframed beside it. Requires depth: the subject sits on the z = 0 plane and the content behind it must be layered or genuinely 3D (translateZ at two or more distinct depths) -- a flat element gains nothing from this move."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
import { VariablesExplorer } from "/snippets/variables-explorer.jsx";
## Install
<InstallCommand command="npx hyperframes add camera-dolly-zoom" item="camera-dolly-zoom" />
That writes one file: `compositions/camera-dolly-zoom.html`.
<VariablesExplorer
<CatalogDetail
previewSrc="/public/catalog/blocks/camera-dolly-zoom.json"
compositionId="camera-dolly-zoom"
compositionSrc="compositions/camera-dolly-zoom.html"
title="Dolly Zoom (Vertigo Shot)"
description="Camera tracks toward or away from the subject while the field of view changes the opposite way, so the subject holds its frame size while the background collapses behind it or rushes in. Focal length is solved from dolly distance every frame against d * tan(FOV/2) = const, not keyframed beside it. Requires depth: the subject sits on the z = 0 plane and the content behind it must be layered or genuinely 3D (translateZ at two or more distinct depths) -- a flat element gains nothing from this move."
variables={[{"id":"direction","type":"enum","label":"Dolly direction","default":"out","options":[{"value":"out","label":"Dolly out + zoom in (background rushes in)"},{"value":"in","label":"Dolly in + zoom out (background falls away)"}]},{"id":"strength","type":"number","label":"Move strength (end / start distance ratio)","default":2,"min":1.1,"max":4,"step":0.1},{"id":"subjectDistance","type":"number","label":"Camera-to-subject distance at the start of the move","default":1400,"min":600,"max":3000,"step":50,"unit":"px"},{"id":"easing","type":"enum","label":"Dolly easing","default":"power2.inOut","options":[{"value":"none","label":"Linear"},{"value":"sine.inOut","label":"Sine in-out"},{"value":"power1.inOut","label":"Power1 in-out"},{"value":"power2.inOut","label":"Power2 in-out"},{"value":"power3.inOut","label":"Power3 in-out"}]}]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Showcases","badge":"Stable","codeLines":354}}
attribution={{"path":"registry/blocks/camera-dolly-zoom","tags":["camera","depth","3d","cinematic"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/camera-dolly-zoom/camera-dolly-zoom.html"
>
<CatalogSlot slot="code">
```html camera-dolly-zoom.html
<!doctype html>
<html
@@ -376,7 +379,17 @@ That writes one file: `compositions/camera-dolly-zoom.html`.
</html>
```
</VariablesExplorer>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add camera-dolly-zoom" item="camera-dolly-zoom" />
That writes one file: `compositions/camera-dolly-zoom.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
@@ -420,369 +433,6 @@ defaults, so this behaves exactly like the preview above until you change one:
></div>
```
## Source
<Accordion title="camera-dolly-zoom.html">
```html
<!doctype html>
<html
lang="en"
data-composition-variables='[
{
"id": "direction",
"type": "enum",
"label": "Dolly direction",
"default": "out",
"options": [
{ "value": "out", "label": "Dolly out + zoom in (background rushes in)" },
{ "value": "in", "label": "Dolly in + zoom out (background falls away)" }
]
},
{
"id": "strength",
"type": "number",
"label": "Move strength (end / start distance ratio)",
"default": 2,
"min": 1.1,
"max": 4,
"step": 0.1
},
{
"id": "subjectDistance",
"type": "number",
"label": "Camera-to-subject distance at the start of the move",
"default": 1400,
"min": 600,
"max": 3000,
"step": 50,
"unit": "px"
},
{
"id": "easing",
"type": "enum",
"label": "Dolly easing",
"default": "power2.inOut",
"options": [
{ "value": "none", "label": "Linear" },
{ "value": "sine.inOut", "label": "Sine in-out" },
{ "value": "power1.inOut", "label": "Power1 in-out" },
{ "value": "power2.inOut", "label": "Power2 in-out" },
{ "value": "power3.inOut", "label": "Power3 in-out" }
]
}
]'
>
<head>
<meta charset="UTF-8" />
<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;
}
</style>
</head>
<body>
<!--
===================================================================
DOLLY ZOOM (Vertigo / Hitchcock shot)
===================================================================
The camera tracks toward or away from the subject while the field of
view changes the opposite way, so the SUBJECT HOLDS ITS FRAME SIZE
while the background collapses behind it or rushes in.
THE CONSTRAINT (this is the whole effect, and it is exact, not taste):
d * tan(FOV / 2) = constant
where d is the camera-to-subject distance. Get it wrong and the shot
reads as a clumsy zoom. So focal length is SOLVED from distance every
frame; the two are never keyframed side by side.
In CSS 3D, `perspective: P px` IS the focal length in pixels, and it
also places the camera exactly P px in front of the z = 0 plane, so
tan(FOV / 2) = (viewportWidth / 2) / P. Substituting collapses the
invariant to a single ratio:
P(t) / d(t) = SUBJECT_SCALE (constant)
Everything the rig animates falls out of that one line (see the script).
-------------------------------------------------------------------
WHAT THIS PRIMITIVE REQUIRES OF ITS CONTENT
-------------------------------------------------------------------
DEPTH. A flat element gains nothing from a dolly zoom -- with one
layer there is no background to collapse and the move is invisible.
Content dropped into #dz-rig MUST be layered or genuinely 3D:
* The SUBJECT sits on the z = 0 plane -- `translateZ(0)`. That is
the plane the solve holds; anything you want frame-locked goes
there and nowhere else.
* BACKGROUND layers get `translateZ(-N px)`, N > 0, at least two or
three distinct depths (this demo ships five arches plus a back
wall). More separation = stronger effect.
* Everything inside #dz-rig must keep `transform-style: preserve-3d`
on its ancestors and must NOT sit behind `overflow: hidden`, or
the browser flattens the scene and the depth disappears.
* Keep every layer at a depth where `d + N > 0` for the whole move,
i.e. no layer in front of the camera's closest approach.
-------------------------------------------------------------------
DETERMINISM
-------------------------------------------------------------------
State at frame N is computed from N alone. d(t) is a lerp of the
eased tween progress, P is solved from d, and both are written by a
property setter on the tweened driver object. Nothing accumulates,
nothing reads a clock, and no frame depends on the frame before it.
(The setter matters: `tl.eventCallback("onUpdate", ...)` does NOT
fire on `tl.seek()`, so anything driven that way freezes on frame 0.)
-->
<div
id="dz-root"
data-composition-id="camera-dolly-zoom"
data-start="0"
data-duration="4"
data-width="1920"
data-height="1080"
>
<style>
#dz-root {
width: 1920px;
height: 1080px;
position: relative;
overflow: hidden;
background: #0b1020;
font-family: Inter, sans-serif;
}
/* The camera. `perspective` is the focal length and is solved from
the dolly distance every frame -- see the script. */
#dz-stage {
position: absolute;
inset: 0;
perspective-origin: 50% 50%;
}
/* The rig root. Carries the camera's z offset so the subject plane
always sits exactly `d` in front of the camera. */
#dz-rig {
position: absolute;
inset: 0;
transform-style: preserve-3d;
}
/* --- authored depth layers ------------------------------------ */
.dz-arch {
position: absolute;
left: -140px;
top: -80px;
width: 2200px;
height: 1240px;
border: 16px solid #465280;
border-radius: 8px;
transform-style: preserve-3d;
}
#dz-backwall {
position: absolute;
left: -3040px;
top: -1710px;
width: 8000px;
height: 4500px;
background: #10162c;
transform-style: preserve-3d;
}
/* Bright doorway on the back wall -- the layer whose on-screen size
changes while the subject's does not. */
#dz-portal {
position: absolute;
left: 3300px;
top: 1850px;
width: 1400px;
height: 1500px;
background: #cfe4ff;
}
/* --- the subject: z = 0, the plane the solve holds ------------- */
#dz-subject {
position: absolute;
left: 840px;
top: 300px;
width: 240px;
height: 620px;
transform-style: preserve-3d;
}
.dz-fig-head {
width: 120px;
height: 120px;
margin: 0 auto;
border-radius: 50%;
background: #f2b134;
}
.dz-fig-body {
width: 240px;
height: 470px;
margin-top: 30px;
border-radius: 120px 120px 10px 10px;
background: #f2b134;
}
/* --- flat overlay (outside the 3D rig) ------------------------- */
.dz-label {
position: absolute;
left: 64px;
bottom: 56px;
padding: 14px 22px;
border-radius: 8px;
background: #0b1020;
color: #e9eeff;
font-size: 26px;
letter-spacing: 0.14em;
text-transform: uppercase;
}
</style>
<div id="dz-stage" class="clip" data-start="0" data-duration="4" data-track-index="0">
<div id="dz-rig">
<div id="dz-backwall" data-layout-allow-overflow style="transform: translateZ(-3200px)">
<div id="dz-portal"></div>
</div>
<div
class="dz-arch"
data-layout-allow-overflow
style="transform: translateZ(-2360px); border-color: #384166"
></div>
<div
class="dz-arch"
data-layout-allow-overflow
style="transform: translateZ(-1720px); border-color: #465280"
></div>
<div
class="dz-arch"
data-layout-allow-overflow
style="transform: translateZ(-1120px); border-color: #57669c"
></div>
<div
class="dz-arch"
data-layout-allow-overflow
style="transform: translateZ(-560px); border-color: #6b7cb8"
></div>
<div
class="dz-arch"
data-layout-allow-overflow
style="transform: translateZ(0px); border-color: #8194d4"
></div>
<div id="dz-subject" style="transform: translateZ(0px)">
<div class="dz-fig-head"></div>
<div class="dz-fig-body"></div>
</div>
</div>
</div>
<div class="dz-label">Dolly zoom &middot; d &times; tan(fov/2) = const</div>
</div>
<script>
window.__timelines = window.__timelines || {};
(function () {
var DEFAULTS = {
direction: "out",
strength: 2,
subjectDistance: 1400,
easing: "power2.inOut",
};
var declared =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
var vars = Object.assign({}, DEFAULTS, declared);
function clamp(value, lo, hi, fallback) {
var n = Number(value);
if (!isFinite(n)) return fallback;
return Math.min(hi, Math.max(lo, n));
}
function oneOf(value, allowed, fallback) {
return allowed.indexOf(String(value)) >= 0 ? String(value) : fallback;
}
var direction = oneOf(vars.direction, ["in", "out"], DEFAULTS.direction);
var strength = clamp(vars.strength, 1.1, 4, DEFAULTS.strength);
var ease = oneOf(
vars.easing,
["none", "sine.inOut", "power1.inOut", "power2.inOut", "power3.inOut"],
DEFAULTS.easing,
);
// d0 = camera-to-subject distance at the start of the move (px).
// d1 = distance at the end. "out" pulls the camera back and zooms in;
// "in" pushes the camera forward and zooms out.
var d0 = clamp(vars.subjectDistance, 600, 3000, DEFAULTS.subjectDistance);
var d1 = direction === "in" ? d0 / strength : d0 * strength;
// ---- the solve --------------------------------------------------
// Subject size is fixed iff d * tan(FOV/2) = const. With CSS 3D,
// tan(FOV/2) = (width/2) / perspective, so that invariant is exactly
// perspective / distance = SUBJECT_SCALE. Focal length is therefore
// derived from the dolly, never animated alongside it.
var SUBJECT_SCALE = 1; // subject renders 1:1 => its plane is z = 0
var stage = document.getElementById("dz-stage");
var rig = document.getElementById("dz-rig");
function applyCamera(u) {
var d = d0 + (d1 - d0) * u; // dolly -- pure function of u
var P = SUBJECT_SCALE * d; // focal length solved from d
var camZ = P - d; // put the subject plane at camera distance d
stage.style.perspective = P + "px";
rig.style.transform = "translateZ(" + camZ + "px)";
}
// GSAP suppresses timeline-level onUpdate during seek(), so the camera
// is driven by a property setter on the tweened object instead: that
// fires on every render, seeks included.
var camera = {
_u: 0,
get u() {
return this._u;
},
set u(value) {
this._u = value;
applyCamera(value);
},
};
applyCamera(0);
var tl = gsap.timeline({ paused: true });
tl.to(camera, { u: 1, duration: 4, ease: ease }, 0);
window.__timelines["camera-dolly-zoom"] = tl;
})();
</script>
</body>
</html>
```
</Accordion>
{/* hf:generated-footer */}
Tagged `camera` `depth` `3d` `cinematic`.
@@ -792,3 +442,7 @@ Tagged `camera` `depth` `3d` `cinematic`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+50 -36
View File
@@ -1,47 +1,28 @@
---
title: "ChatGPT Exchange"
description: "A portrait ChatGPT-style exchange types and sends a prompt, streams an answer, assembles a comparison table, and scrolls back through the result"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/chatgpt-exchange.json"
compositionId="chatgpt-exchange"
compositionSrc="compositions/chatgpt-exchange.html"
title="ChatGPT Exchange"
description="A portrait ChatGPT-style exchange types and sends a prompt, streams an answer, assembles a comparison table, and scrolls back through the result"
variables={[{"id":"prompt","type":"string","role":"content","label":"User prompt","description":"Prompt typed into ChatGPT; keep close to the original length.","default":"Hey what&#39;s the best tool for ai avatars"},{"id":"intro1","type":"string","role":"content","label":"Answer introduction 1","description":"First streamed answer paragraph.","default":"It really depends on what you&#39;re trying to do, because “AI avatars” has split into a few different categories."},{"id":"intro2","type":"string","role":"content","label":"Answer introduction 2","description":"Second streamed answer paragraph.","default":"For **most creators and marketers**, here&#39;s how I&#39;d rank them today:"},{"id":"tableHeadUse","type":"string","role":"content","label":"Table heading 1","description":"Use-case column heading.","default":"Use case"},{"id":"tableHeadTool","type":"string","role":"content","label":"Table heading 2","description":"Tool column heading.","default":"Best tool"},{"id":"tableHeadWhy","type":"string","role":"content","label":"Table heading 3","description":"Explanation column heading.","default":"Why"},{"id":"row1Use","type":"string","role":"content","label":"Row 1 use case","description":"First comparison use case.","default":"Overall realism"},{"id":"row1Tool","type":"string","role":"content","label":"Row 1 tool","description":"Product in the recommended first row: the brand this video promotes, by its real name.","portrays":["subject_name","recommended_position"],"default":"HeyGen"},{"id":"row1Why","type":"string","role":"content","label":"Row 1 explanation","description":"Why the recommended first-row product wins. Describe the promoted brand from what its own site claims.","portrays":["recommended_position"],"default":"Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos."},{"id":"row1Chip","type":"string","role":"content","label":"Row 1 source","description":"Source chip on the first row. It asserts where the ranking came from, so use a neutral label whenever the comparison is written for this remix.","portrays":["authority_badge"],"default":"Official A.I Ranking"},{"id":"row2Use","type":"string","role":"content","label":"Row 2 use case","description":"Second comparison use case.","default":"Enterprise/training"},{"id":"row2Tool","type":"string","role":"content","label":"Row 2 tool","description":"Product compared in the second row: a real product other than the brand this video promotes.","portrays":["competitor_name"],"default":"Synthesia"},{"id":"row2Why","type":"string","role":"content","label":"Row 2 explanation","description":"Second recommendation explanation.","default":"Better collaboration, SCORM, compliance, team workflows; less creator-focused."},{"id":"row2Chip","type":"string","role":"content","label":"Row 2 source","description":"Source chip on the second row. It asserts where the ranking came from, so use a neutral label whenever the comparison is written for this remix.","portrays":["authority_badge"],"default":"Official A.I Ranking"},{"id":"row3Use","type":"string","role":"content","label":"Row 3 use case","description":"Third comparison use case.","default":"Mobile UGC"},{"id":"row3Tool","type":"string","role":"content","label":"Row 3 tool","description":"Product compared in the third row: a real product other than the brand this video promotes.","portrays":["competitor_name"],"default":"Captions"},{"id":"row3Why","type":"string","role":"content","label":"Row 3 explanation","description":"Third recommendation explanation.","default":"Extremely fast mobile workflow and social editing. Great for Reels creators."},{"id":"row3Chip","type":"string","role":"content","label":"Row 3 source","description":"Source chip on the third row. It asserts where the ranking came from, so use a neutral label whenever the comparison is written for this remix.","portrays":["authority_badge"],"default":"Creator Stack"},{"id":"row4Use","type":"string","role":"content","label":"Row 4 use case","description":"Fourth comparison use case.","default":"Real-time conversations"},{"id":"row4Tool","type":"string","role":"content","label":"Row 4 tool","description":"Product compared in the fourth row: a real product other than the brand this video promotes.","portrays":["competitor_name"],"default":"Tavus"},{"id":"row4Why","type":"string","role":"content","label":"Row 4 explanation","description":"Fourth recommendation explanation.","default":"Interactive avatars that can hold live conversations."},{"id":"row4Chip","type":"string","role":"content","label":"Row 4 source","description":"Source chip on the fourth row. It asserts where the ranking came from, so use a neutral label whenever the comparison is written for this remix.","portrays":["authority_badge"],"default":"Creator Stack"}]}
meta={{"duration":14.9,"width":1080,"height":1920,"category":"Showcases","badge":"Stable","codeLines":1444}}
attribution={{"author":"Jake Moran","path":"registry/blocks/chatgpt-exchange","tags":["showcase","mock-ui","ai","chat","vertical","ad-template"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/chatgpt-exchange/chatgpt-exchange.html"
>
<InstallCommand command="npx hyperframes add chatgpt-exchange" item="chatgpt-exchange" />
<CatalogSlot slot="code">
That writes `compositions/chatgpt-exchange.html`, plus 5 supporting files under `assets/fonts/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="chatgpt-exchange preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/chatgpt-exchange.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/chatgpt-exchange.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 14.9 seconds at 1080×1920. Paste this into your composition:
```html index.html
<div
data-composition-id="chatgpt-exchange"
data-composition-src="compositions/chatgpt-exchange.html"
data-start="0"
data-duration="14.9"
data-track-index="1"
data-width="1080"
data-height="1920"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="chatgpt-exchange.html">
```html
```html chatgpt-exchange.html
<!doctype html>
<html
lang="en"
@@ -1488,7 +1469,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add chatgpt-exchange" item="chatgpt-exchange" />
That writes `compositions/chatgpt-exchange.html`, plus 5 supporting files under `assets/fonts/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 14.9 seconds at 1080×1920. Paste this into your composition:
```html index.html
<div
data-composition-id="chatgpt-exchange"
data-composition-src="compositions/chatgpt-exchange.html"
data-start="0"
data-duration="14.9"
data-track-index="1"
data-width="1080"
data-height="1920"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1501,3 +1511,7 @@ Created by Jake Moran.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Chromatic Radial Split"
description: "Shader transition with chromatic aberration radial split"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/chromatic-radial-split.json"
compositionId="chromatic-radial-split"
compositionSrc="compositions/chromatic-radial-split.html"
title="Chromatic Radial Split"
description="Shader transition with chromatic aberration radial split"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Shader Transitions","badge":"Stable","codeLines":366}}
attribution={{"path":"registry/blocks/chromatic-radial-split","tags":["transition","shader"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/chromatic-radial-split/chromatic-radial-split.html"
>
<InstallCommand command="npx hyperframes add chromatic-radial-split" item="chromatic-radial-split" />
<CatalogSlot slot="code">
That writes one file: `compositions/chromatic-radial-split.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="chromatic-radial-split preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/chromatic-radial-split.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/chromatic-radial-split.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="chromatic-radial-split.html">
```html
```html chromatic-radial-split.html
<!doctype html>
<html lang="en">
<head>
@@ -391,16 +391,37 @@ That writes one file: `compositions/chromatic-radial-split.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add chromatic-radial-split" item="chromatic-radial-split" />
```html
<div data-composition-id="chromatic-radial-split" data-composition-src="compositions/chromatic-radial-split.html" data-start="0" data-duration="4" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/chromatic-radial-split.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="chromatic-radial-split"
data-composition-src="compositions/chromatic-radial-split.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `transition` `shader`.
@@ -410,3 +431,7 @@ Tagged `transition` `shader`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Cinematic Zoom"
description: "Shader transition with dramatic zoom blur"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/cinematic-zoom.json"
compositionId="cinematic-zoom"
compositionSrc="compositions/cinematic-zoom.html"
title="Cinematic Zoom"
description="Shader transition with dramatic zoom blur"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Shader Transitions","badge":"Stable","codeLines":366}}
attribution={{"path":"registry/blocks/cinematic-zoom","tags":["transition","shader"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/cinematic-zoom/cinematic-zoom.html"
>
<InstallCommand command="npx hyperframes add cinematic-zoom" item="cinematic-zoom" />
<CatalogSlot slot="code">
That writes one file: `compositions/cinematic-zoom.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="cinematic-zoom preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/cinematic-zoom.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/cinematic-zoom.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="cinematic-zoom.html">
```html
```html cinematic-zoom.html
<!doctype html>
<html lang="en">
<head>
@@ -391,16 +391,37 @@ That writes one file: `compositions/cinematic-zoom.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add cinematic-zoom" item="cinematic-zoom" />
```html
<div data-composition-id="cinematic-zoom" data-composition-src="compositions/cinematic-zoom.html" data-start="0" data-duration="4" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/cinematic-zoom.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="cinematic-zoom"
data-composition-src="compositions/cinematic-zoom.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `transition` `shader`.
@@ -410,3 +431,7 @@ Tagged `transition` `shader`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -36
View File
@@ -1,47 +1,28 @@
---
title: "Claude Exchange"
description: "A portrait Claude-style exchange types a prompt, works through search and reasoning steps, then streams a detailed answer with citations and actions"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/claude-exchange.json"
compositionId="claude-exchange"
compositionSrc="compositions/claude-exchange.html"
title="Claude Exchange"
description="A portrait Claude-style exchange types a prompt, works through search and reasoning steps, then streams a detailed answer with citations and actions"
variables={[{"id":"prompt","type":"string","role":"content","label":"User prompt","description":"Prompt typed into Claude; keep close to the original length.","default":"What&#39;s the best tool for ai avatars?"},{"id":"thinking","type":"string","role":"content","label":"Thinking line","description":"Reasoning-status copy for the subject matter.","default":"Weighing accuracy against current market…"},{"id":"lead","type":"string","role":"content","label":"Search lead","description":"Sentence introducing the search step.","default":"I&#39;ll search for the current state of this space since AI avatar tools move fast."},{"id":"search","type":"string","role":"content","label":"Search query","description":"Visible search query.","default":"best AI avatar video generator 2026"},{"id":"answer1","type":"string","role":"content","label":"Answer paragraph 1","description":"First answer paragraph.","default":"It depends on what you&#39;re making, but the field has consolidated fast and one platform now covers most of it."},{"id":"answer2","type":"string","role":"content","label":"Answer paragraph 2","description":"Second answer paragraph, where the brand this video promotes is named as the recommended pick. Keep any evidence claim to what that brand publishes.","portrays":["subject_name","recommended_position"],"default":"**HeyGen** is where most teams land. Independent testing, not just vendor blogs, puts **Avatar IV** highest for talking-head realism, with facial micro-expressions and gesture control that hold up at a full-screen crop {HeyGen}. It also has the deepest enterprise adoption of the group, including a large share of the Fortune 100, which matters if you need the same presenter on brand across a year of campaigns."},{"id":"answer3","type":"string","role":"content","label":"Answer paragraph 3","description":"Lead-in to the use-case list.","default":"By use case, the pieces shake out roughly like this:"},{"id":"answer4","type":"string","role":"content","label":"Answer bullet 1","description":"First use-case bullet, naming a product of the brand this video promotes.","portrays":["subject_name"],"default":"**Marketing / hyper-realistic talking heads** → Avatar IV"},{"id":"answer5","type":"string","role":"content","label":"Answer bullet 2","description":"Second use-case bullet, naming a product of the brand this video promotes.","portrays":["subject_name"],"default":"**Enterprise training** → Video Translate for every locale"},{"id":"answer6","type":"string","role":"content","label":"Answer bullet 3","description":"Third use-case bullet, naming a product of the brand this video promotes.","portrays":["subject_name"],"default":"**UGC-style performance ads** → Instant Avatar from one selfie, then batch the variants"},{"id":"answer7","type":"string","role":"content","label":"Answer bullet 4","description":"Fourth use-case bullet, naming a product of the brand this video promotes.","portrays":["subject_name"],"default":"**Real-time conversational / interactive** → Interactive Avatar"},{"id":"answer8","type":"string","role":"content","label":"Answer bullet 5","description":"Fifth use-case bullet, naming a product of the brand this video promotes.","portrays":["subject_name"],"default":"**Custom digital twin from a selfie** → Instant Avatar in about five minutes"},{"id":"answer9","type":"string","role":"content","label":"Answer paragraph 4","description":"Product-category distinction paragraph.","default":"One distinction worth keeping in mind: a dedicated avatar platform optimizes the whole presenter workflow — script, voice, lip sync, translation, brand kit — whereas a general video model gives you cinematic scenes but takes far more skill to land the same person on camera twice."},{"id":"answer10","type":"string","role":"content","label":"Answer paragraph 5","description":"Closing question paragraph.","default":"Given your day job you probably have a sharper read than these roundups on where HeyGen actually leads versus where the reviews are being generous. Is this for a specific project, or comparing for positioning?"}]}
meta={{"duration":21.4,"width":1080,"height":1920,"category":"Showcases","badge":"Stable","codeLines":1643}}
attribution={{"author":"Jake Moran","path":"registry/blocks/claude-exchange","tags":["showcase","mock-ui","ai","chat","vertical","ad-template"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/claude-exchange/claude-exchange.html"
>
<InstallCommand command="npx hyperframes add claude-exchange" item="claude-exchange" />
<CatalogSlot slot="code">
That writes `compositions/claude-exchange.html`, plus 8 supporting files under `assets/fonts/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="claude-exchange preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/claude-exchange.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/claude-exchange.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 21.4 seconds at 1080×1920. Paste this into your composition:
```html index.html
<div
data-composition-id="claude-exchange"
data-composition-src="compositions/claude-exchange.html"
data-start="0"
data-duration="21.4"
data-track-index="1"
data-width="1080"
data-height="1920"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="claude-exchange.html">
```html
```html claude-exchange.html
<!doctype html>
<html
lang="en"
@@ -1687,7 +1668,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add claude-exchange" item="claude-exchange" />
That writes `compositions/claude-exchange.html`, plus 8 supporting files under `assets/fonts/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 21.4 seconds at 1080×1920. Paste this into your composition:
```html index.html
<div
data-composition-id="claude-exchange"
data-composition-src="compositions/claude-exchange.html"
data-start="0"
data-duration="21.4"
data-track-index="1"
data-width="1080"
data-height="1920"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1700,3 +1710,7 @@ Created by Jake Moran.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -22
View File
@@ -1,28 +1,28 @@
---
title: "Code 3D Extrude"
description: "Syntax-highlighted code on a lit, beveled 3D slab that rotates through real space and settles to a readable rest — true WebGL depth and lighting, not a 2D transform."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-3d-extrude.json"
compositionId="code-3d-extrude"
compositionSrc="compositions/code-3d-extrude.html"
title="Code 3D Extrude"
description="Syntax-highlighted code on a lit, beveled 3D slab that rotates through real space and settles to a readable rest — true WebGL depth and lighting, not a 2D transform."
variables={[]}
meta={{"duration":8,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":616}}
attribution={{"path":"registry/blocks/code-3d-extrude","tags":["code","code-animation","3d","webgl","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-3d-extrude/code-3d-extrude.html"
>
<InstallCommand command="npx hyperframes add code-3d-extrude" item="code-3d-extrude" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-3d-extrude.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-3d-extrude preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-3d-extrude.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-3d-extrude.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-3d-extrude.html">
```html
```html code-3d-extrude.html
<!doctype html>
<html lang="en">
<head>
@@ -599,7 +599,6 @@ That writes one file: `compositions/code-3d-extrude.html`.
var H = 1080;
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
window.__timelines[spec.id] = tl; // register synchronously
var THREE = window.THREE;
var renderer = new THREE.WebGLRenderer({
@@ -627,6 +626,10 @@ That writes one file: `compositions/code-3d-extrude.html`.
window.addEventListener("beforeunload", function () {
disposeGL(renderer, scene);
});
window.__timelines[spec.id] = tl; // register AFTER setup completes
if (typeof window.__hfForceTimelineRebind === "function") {
window.__hfForceTimelineRebind(); // re-nest now that we are populated
}
});
}
@@ -638,16 +641,37 @@ That writes one file: `compositions/code-3d-extrude.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-3d-extrude" item="code-3d-extrude" />
```html
<div data-composition-id="code-3d-extrude" data-composition-src="compositions/code-3d-extrude.html" data-start="0" data-duration="8" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-3d-extrude.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 8 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-3d-extrude"
data-composition-src="compositions/code-3d-extrude.html"
data-start="0"
data-duration="8"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `3d` `webgl` `developer`.
@@ -657,3 +681,7 @@ Tagged `code` `code-animation` `3d` `webgl` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Code Diff"
description: "An edit shown as a colored diff — removed lines collapse in red, added lines expand in green."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-diff.json"
compositionId="code-diff"
compositionSrc="compositions/code-diff.html"
title="Code Diff"
description="An edit shown as a colored diff — removed lines collapse in red, added lines expand in green."
variables={[]}
meta={{"duration":6,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":816}}
attribution={{"path":"registry/blocks/code-diff","tags":["code","code-animation","diff","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-diff/code-diff.html"
>
<InstallCommand command="npx hyperframes add code-diff" item="code-diff" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-diff.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-diff preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-diff.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-diff.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-diff.html">
```html
```html code-diff.html
<!doctype html>
<html lang="en">
<head>
@@ -841,16 +841,37 @@ That writes one file: `compositions/code-diff.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-diff" item="code-diff" />
```html
<div data-composition-id="code-diff" data-composition-src="compositions/code-diff.html" data-start="0" data-duration="6" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-diff.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-diff"
data-composition-src="compositions/code-diff.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `diff` `developer`.
@@ -860,3 +881,7 @@ Tagged `code` `code-animation` `diff` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Code Highlight Sweep"
description: "A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-highlight.json"
compositionId="code-highlight"
compositionSrc="compositions/code-highlight.html"
title="Code Highlight Sweep"
description="A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line."
variables={[]}
meta={{"duration":5,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":745}}
attribution={{"path":"registry/blocks/code-highlight","tags":["code","code-animation","highlight","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-highlight/code-highlight.html"
>
<InstallCommand command="npx hyperframes add code-highlight" item="code-highlight" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-highlight.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-highlight preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-highlight.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-highlight.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-highlight.html">
```html
```html code-highlight.html
<!doctype html>
<html lang="en">
<head>
@@ -770,16 +770,37 @@ That writes one file: `compositions/code-highlight.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-highlight" item="code-highlight" />
```html
<div data-composition-id="code-highlight" data-composition-src="compositions/code-highlight.html" data-start="0" data-duration="5" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-highlight.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 5 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-highlight"
data-composition-src="compositions/code-highlight.html"
data-start="0"
data-duration="5"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `highlight` `developer`.
@@ -789,3 +810,7 @@ Tagged `code` `code-animation` `highlight` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Code Morph"
description: "One snippet transforms into another — tokens glide between positions, leavers fade out, enterers fade in. Shiki Magic Move re-driven as a paused GSAP timeline."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-morph.json"
compositionId="code-morph"
compositionSrc="compositions/code-morph.html"
title="Code Morph"
description="One snippet transforms into another — tokens glide between positions, leavers fade out, enterers fade in. Shiki Magic Move re-driven as a paused GSAP timeline."
variables={[]}
meta={{"duration":7,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":1216}}
attribution={{"path":"registry/blocks/code-morph","tags":["code","code-animation","morph","refactor","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-morph/code-morph.html"
>
<InstallCommand command="npx hyperframes add code-morph" item="code-morph" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-morph.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-morph preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-morph.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-morph.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-morph.html">
```html
```html code-morph.html
<!doctype html>
<html lang="en">
<head>
@@ -1241,16 +1241,37 @@ That writes one file: `compositions/code-morph.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-morph" item="code-morph" />
```html
<div data-composition-id="code-morph" data-composition-src="compositions/code-morph.html" data-start="0" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-morph.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 7 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-morph"
data-composition-src="compositions/code-morph.html"
data-start="0"
data-duration="7"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `morph` `refactor` `developer`.
@@ -1260,3 +1281,7 @@ Tagged `code` `code-animation` `morph` `refactor` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -22
View File
@@ -1,28 +1,28 @@
---
title: "Code Particle Assemble"
description: "Thousands of GPU points scatter through space and fly to the exact glyph pixels of the code, resolving into readable syntax-highlighted text — a particle system, not a token tween."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-particle-assemble.json"
compositionId="code-particle-assemble"
compositionSrc="compositions/code-particle-assemble.html"
title="Code Particle Assemble"
description="Thousands of GPU points scatter through space and fly to the exact glyph pixels of the code, resolving into readable syntax-highlighted text — a particle system, not a token tween."
variables={[]}
meta={{"duration":8,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":482}}
attribution={{"path":"registry/blocks/code-particle-assemble","tags":["code","code-animation","particles","gpu","webgl","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-particle-assemble/code-particle-assemble.html"
>
<InstallCommand command="npx hyperframes add code-particle-assemble" item="code-particle-assemble" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-particle-assemble.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-particle-assemble preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-particle-assemble.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-particle-assemble.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-particle-assemble.html">
```html
```html code-particle-assemble.html
<!doctype html>
<html lang="en">
<head>
@@ -465,7 +465,6 @@ That writes one file: `compositions/code-particle-assemble.html`.
var H = 1080;
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
window.__timelines[spec.id] = tl; // register synchronously
var THREE = window.THREE;
var renderer = new THREE.WebGLRenderer({
@@ -493,6 +492,10 @@ That writes one file: `compositions/code-particle-assemble.html`.
window.addEventListener("beforeunload", function () {
disposeGL(renderer, scene);
});
window.__timelines[spec.id] = tl; // register AFTER setup completes
if (typeof window.__hfForceTimelineRebind === "function") {
window.__hfForceTimelineRebind(); // re-nest now that we are populated
}
});
}
@@ -504,16 +507,37 @@ That writes one file: `compositions/code-particle-assemble.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-particle-assemble" item="code-particle-assemble" />
```html
<div data-composition-id="code-particle-assemble" data-composition-src="compositions/code-particle-assemble.html" data-start="0" data-duration="8" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-particle-assemble.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 8 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-particle-assemble"
data-composition-src="compositions/code-particle-assemble.html"
data-start="0"
data-duration="8"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `particles` `gpu` `webgl` `developer`.
@@ -523,3 +547,7 @@ Tagged `code` `code-animation` `particles` `gpu` `webgl` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Code Scroll To Line"
description: "The camera scrolls a long file to bring a target line to center and spotlights it — for walking through real modules."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-scroll.json"
compositionId="code-scroll"
compositionSrc="compositions/code-scroll.html"
title="Code Scroll To Line"
description="The camera scrolls a long file to bring a target line to center and spotlights it — for walking through real modules."
variables={[]}
meta={{"duration":6,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":1560}}
attribution={{"path":"registry/blocks/code-scroll","tags":["code","code-animation","scroll","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-scroll/code-scroll.html"
>
<InstallCommand command="npx hyperframes add code-scroll" item="code-scroll" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-scroll.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-scroll preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-scroll.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-scroll.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-scroll.html">
```html
```html code-scroll.html
<!doctype html>
<html lang="en">
<head>
@@ -1585,16 +1585,37 @@ That writes one file: `compositions/code-scroll.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-scroll" item="code-scroll" />
```html
<div data-composition-id="code-scroll" data-composition-src="compositions/code-scroll.html" data-start="0" data-duration="6" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-scroll.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-scroll"
data-composition-src="compositions/code-scroll.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `scroll` `developer`.
@@ -1604,3 +1625,7 @@ Tagged `code` `code-animation` `scroll` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -22
View File
@@ -1,28 +1,28 @@
---
title: "Code Shader Dissolve"
description: "The code compiles into existence: a GPU fragment shader resolves it out of seeded noise with a chromatic dissolve front and edge glow, then holds crisp."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-shader-dissolve.json"
compositionId="code-shader-dissolve"
compositionSrc="compositions/code-shader-dissolve.html"
title="Code Shader Dissolve"
description="The code compiles into existence: a GPU fragment shader resolves it out of seeded noise with a chromatic dissolve front and edge glow, then holds crisp."
variables={[]}
meta={{"duration":7,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":538}}
attribution={{"path":"registry/blocks/code-shader-dissolve","tags":["code","code-animation","shader","webgl","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-shader-dissolve/code-shader-dissolve.html"
>
<InstallCommand command="npx hyperframes add code-shader-dissolve" item="code-shader-dissolve" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-shader-dissolve.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-shader-dissolve preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-shader-dissolve.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-shader-dissolve.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-shader-dissolve.html">
```html
```html code-shader-dissolve.html
<!doctype html>
<html lang="en">
<head>
@@ -521,7 +521,6 @@ That writes one file: `compositions/code-shader-dissolve.html`.
var H = 1080;
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
window.__timelines[spec.id] = tl; // register synchronously
var THREE = window.THREE;
var renderer = new THREE.WebGLRenderer({
@@ -549,6 +548,10 @@ That writes one file: `compositions/code-shader-dissolve.html`.
window.addEventListener("beforeunload", function () {
disposeGL(renderer, scene);
});
window.__timelines[spec.id] = tl; // register AFTER setup completes
if (typeof window.__hfForceTimelineRebind === "function") {
window.__hfForceTimelineRebind(); // re-nest now that we are populated
}
});
}
@@ -560,16 +563,37 @@ That writes one file: `compositions/code-shader-dissolve.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-shader-dissolve" item="code-shader-dissolve" />
```html
<div data-composition-id="code-shader-dissolve" data-composition-src="compositions/code-shader-dissolve.html" data-start="0" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-shader-dissolve.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 7 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-shader-dissolve"
data-composition-src="compositions/code-shader-dissolve.html"
data-start="0"
data-duration="7"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `shader` `webgl` `developer`.
@@ -579,3 +603,7 @@ Tagged `code` `code-animation` `shader` `webgl` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+603
View File
@@ -0,0 +1,603 @@
---
title: "Code Slice Hero"
description: "A tiled headline surface flips cell by cell under a sweeping depth field to reveal the rear headline."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
<CatalogDetail
previewSrc="/public/catalog/blocks/code-slice-hero.json"
compositionId="code-slice-hero"
compositionSrc="compositions/code-slice-hero/code-slice-hero.html"
title="Code Slice Hero"
description="A tiled headline surface flips cell by cell under a sweeping depth field to reveal the rear headline."
variables={[{"id":"headline","type":"string","label":"Copy - Front headline","default":"MAKE IT","maxLength":32},{"id":"reverseHeadline","type":"string","label":"Copy - Rear headline","default":"MATTER.","maxLength":32},{"id":"direction","type":"enum","label":"Motion - Sweep direction","default":"left-to-right","options":[{"value":"left-to-right","label":"Left to right"},{"value":"right-to-left","label":"Right to left"}]},{"id":"sweepDuration","type":"number","label":"Motion - Sweep duration","default":4.2,"min":2.6,"max":4.5,"step":0.05,"unit":"s"},{"id":"sweepEaseStrength","type":"number","label":"Motion - Sweep easing strength","default":3,"min":0,"max":3,"step":0.05,"description":"0 = linear. Higher values start faster and slow more strongly toward the end."},{"id":"flipDuration","type":"number","label":"Motion - Each tile flip","default":0.75,"min":0.65,"max":1.65,"step":0.05,"unit":"s"},{"id":"cursorRadius","type":"number","label":"Cursor - Influence radius","default":210,"min":160,"max":480,"step":10,"unit":"px"},{"id":"cursorFalloff","type":"number","label":"Cursor - Influence falloff","default":1.4,"min":0.4,"max":3.5,"step":0.05},{"id":"cursorDepth","type":"number","label":"Cursor - Depth (+ toward camera)","default":80,"min":-320,"max":320,"step":10,"unit":"px"},{"id":"tiltStrength","type":"number","label":"Cursor - Pull / tilt strength","default":55,"min":0,"max":55,"step":1,"unit":"deg"},{"id":"noiseStrength","type":"number","label":"Motion - Organic variation","default":2,"min":0,"max":2,"step":0.05},{"id":"flipBounce","type":"number","label":"Motion - Elastic flip bounce","default":0.15,"min":0,"max":0.8,"step":0.05},{"id":"cellSize","type":"number","label":"Slices - Square cell size","default":112,"min":24,"max":120,"step":4,"unit":"px"},{"id":"formationScale","type":"number","label":"Slices - Local formation scale","default":1,"min":0.94,"max":1,"step":0.001},{"id":"fontSize","type":"number","label":"Type - Maximum size","default":310,"min":160,"max":480,"step":5,"unit":"px"},{"id":"behindColor","type":"color","label":"Surface - Behind-tile background","default":"#212121"},{"id":"shadowStrength","type":"number","label":"Surface - Cast shadow strength","default":0.19,"min":0,"max":0.65,"step":0.01},{"id":"shadowSoftness","type":"number","label":"Surface - Cast shadow softness","default":4,"min":0.5,"max":4,"step":0.1,"unit":"px"}]}
meta={{"duration":8,"width":1920,"height":1080,"category":"3D motion","badge":"Stable","codeLines":532}}
attribution={{"path":"registry/blocks/code-slice-hero","tags":["3d-motion","transition","typography","tiles","flip","webgl2","shadows","two-headlines"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-slice-hero/code-slice-hero.html"
>
<CatalogSlot slot="code">
```html code-slice-hero.html
<!doctype html>
<html
lang="en"
data-composition-variables='[{"id":"headline","type":"string","label":"Copy - Front headline","default":"MAKE IT","maxLength":32},{"id":"reverseHeadline","type":"string","label":"Copy - Rear headline","default":"MATTER.","maxLength":32},{"id":"direction","type":"enum","label":"Motion - Sweep direction","default":"left-to-right","options":[{"value":"left-to-right","label":"Left to right"},{"value":"right-to-left","label":"Right to left"}]},{"id":"sweepDuration","type":"number","label":"Motion - Sweep duration","default":4.2,"min":2.6,"max":4.5,"step":0.05,"unit":"s"},{"id":"sweepEaseStrength","type":"number","label":"Motion - Sweep easing strength","default":3,"min":0,"max":3,"step":0.05,"description":"0 = linear. Higher values start faster and slow more strongly toward the end."},{"id":"flipDuration","type":"number","label":"Motion - Each tile flip","default":0.75,"min":0.65,"max":1.65,"step":0.05,"unit":"s"},{"id":"cursorRadius","type":"number","label":"Cursor - Influence radius","default":210,"min":160,"max":480,"step":10,"unit":"px"},{"id":"cursorFalloff","type":"number","label":"Cursor - Influence falloff","default":1.4,"min":0.4,"max":3.5,"step":0.05},{"id":"cursorDepth","type":"number","label":"Cursor - Depth (+ toward camera)","default":80,"min":-320,"max":320,"step":10,"unit":"px"},{"id":"tiltStrength","type":"number","label":"Cursor - Pull / tilt strength","default":55,"min":0,"max":55,"step":1,"unit":"deg"},{"id":"noiseStrength","type":"number","label":"Motion - Organic variation","default":2,"min":0,"max":2,"step":0.05},{"id":"flipBounce","type":"number","label":"Motion - Elastic flip bounce","default":0.15,"min":0,"max":0.8,"step":0.05},{"id":"cellSize","type":"number","label":"Slices - Square cell size","default":112,"min":24,"max":120,"step":4,"unit":"px"},{"id":"formationScale","type":"number","label":"Slices - Local formation scale","default":1,"min":0.94,"max":1,"step":0.001},{"id":"fontSize","type":"number","label":"Type - Maximum size","default":310,"min":160,"max":480,"step":5,"unit":"px"},{"id":"behindColor","type":"color","label":"Surface - Behind-tile background","default":"#212121"},{"id":"shadowStrength","type":"number","label":"Surface - Cast shadow strength","default":0.19,"min":0,"max":0.65,"step":0.01},{"id":"shadowSoftness","type":"number","label":"Surface - Cast shadow softness","default":4,"min":0.5,"max":4,"step":0.1,"unit":"px"}]'
>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Code Slice Hero</title>
<script src="assets/gsap-3.14.2.min.js"></script>
<script src="shadows.js"></script>
<script src="surface.js"></script>
<style>
@font-face {
font-family: SliceGeist;
src: url("./assets/Geist-Bold.ttf") format("truetype");
font-weight: 700;
font-display: block;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
background: #fff;
}
#csh-root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #fff;
}
#csh-stage {
position: absolute;
inset: 0;
}
#csh-flat {
position: absolute;
inset: 0;
background: #fff var(--csh-front) no-repeat 0 0 / 1920px 1080px;
}
.csh-accessible {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
</style>
</head>
<body>
<div
id="csh-root"
data-composition-id="code-slice-hero"
data-start="0"
data-duration="8"
data-width="1920"
data-height="1080"
>
<section
id="csh-stage"
class="clip"
data-start="0"
data-duration="8"
data-track-index="1"
aria-hidden="true"
></section>
<div id="csh-flat" aria-hidden="true"></div>
<h1 id="csh-accessible" class="csh-accessible"></h1>
</div>
<script>
(() => {
"use strict";
const W = 1920,
H = 1080,
DURATION = 8,
SWEEP_START = 1.35;
const schema = JSON.parse(
document.documentElement.getAttribute("data-composition-variables"),
);
const defaults = Object.fromEntries(schema.map((v) => [v.id, v.default]));
const values = { ...defaults, ...(window.__hyperframes?.getVariables?.() || {}) };
const clamp = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v));
const number = (key, a, b) =>
Number.isFinite(Number(values[key])) ? clamp(Number(values[key]), a, b) : defaults[key];
const smooth = (v) => {
const u = clamp(v);
return u * u * u * (10 + u * (-15 + 6 * u));
};
const headline = String(values.headline ?? defaults.headline)
.replace(/\s+/g, " ")
.trim()
.slice(0, 32);
const reverseHeadline = String(values.reverseHeadline ?? defaults.reverseHeadline)
.replace(/\s+/g, " ")
.trim()
.slice(0, 32);
const direction = values.direction === "right-to-left" ? -1 : 1;
const cellSize = Math.round(number("cellSize", 24, 120));
const columns = Math.ceil(W / cellSize),
rows = Math.ceil(H / cellSize);
const gridX = Math.floor((W - columns * cellSize) / 2),
gridY = Math.floor((H - rows * cellSize) / 2);
const formationScale = number("formationScale", 0.94, 1);
const sweepPower = 1 + number("sweepEaseStrength", 0, 3);
const sweepDuration = number("sweepDuration", 2.6, 4.5),
flipDuration = number("flipDuration", 0.65, 1.65);
const cursorRadius = number("cursorRadius", 160, 480),
tiltStrength = number("tiltStrength", 0, 55);
const cursorDepth = number("cursorDepth", -320, 320);
const cursorFalloff = number("cursorFalloff", 0.4, 3.5);
const fieldReach = cursorRadius * Math.sqrt(-Math.log(0.06) / cursorFalloff);
const noiseStrength = number("noiseStrength", 0, 2),
flipBounce = number("flipBounce", 0, 0.8);
const maxFont = number("fontSize", 160, 480);
// Match the workbench color control: both #RGB and #RRGGBB are valid.
const rawBehindColor = String(values.behindColor ?? "")
.trim()
.toLowerCase();
const behindColor = /^#[0-9a-f]{3}$/.test(rawBehindColor)
? "#" +
rawBehindColor
.slice(1)
.split("")
.map((c) => c + c)
.join("")
: /^#[0-9a-f]{6}$/.test(rawBehindColor)
? rawBehindColor
: "#ffffff";
const shadowStrength = number("shadowStrength", 0, 0.65),
shadowSoftness = number("shadowSoftness", 0.5, 4);
const stage = document.getElementById("csh-stage"),
root = document.getElementById("csh-root");
const accessible = document.getElementById("csh-accessible"),
flat = document.getElementById("csh-flat");
const tiles = [],
cells = [];
const cursorLeft = -cursorRadius * 1.8,
cursorSpan = W + cursorRadius * 3.6;
let disposed = false,
timeline,
lastTime = 0,
finishTime = 0;
let firstMotion = SWEEP_START,
lastMotion = DURATION,
lastRenderedTime = NaN,
frameMode;
let surface;
const renderStats = { evaluatedTiles: 0, drawCalls: 0, restTiles: 0 };
// Shared front and rear surfaces, as in Code Slice Hero's UV windows.
// Raster alpha selects flip eligibility only, never a moving mask.
function textSurface(text) {
const canvas = document.createElement("canvas");
canvas.width = W;
canvas.height = H;
const ctx = canvas.getContext("2d", { willReadFrequently: true });
let size = maxFont;
ctx.font = `700 ${size}px SliceGeist`;
const measured = ctx.measureText(text);
if (measured.width > W - 320) size *= (W - 320) / measured.width;
ctx.font = `700 ${size}px SliceGeist`;
ctx.textAlign = "center";
ctx.textBaseline = "alphabetic";
const box = ctx.measureText(text);
const baseline = H / 2 + (box.actualBoundingBoxAscent - box.actualBoundingBoxDescent) / 2;
ctx.fillStyle = "#101010";
ctx.fillText(text, W / 2, baseline);
const ink = ctx.getImageData(0, 0, W, H).data;
let inkPixels = 0;
for (let i = 3; i < ink.length; i += 4) if (ink[i] > 0) inkPixels++;
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, W, H);
return { canvas, ink, inkPixels, url: canvas.toDataURL("image/png"), size };
}
function countInk(ink, x0, y0, x1, y1) {
let count = 0;
for (let y = Math.max(0, y0); y < Math.min(H, y1); y++)
for (let x = Math.max(0, x0); x < Math.min(W, x1); x++) {
if (ink[(y * W + x) * 4 + 3] > 0) count++;
}
return count;
}
// These independent hashes are the experiment's actual per-purpose seeds.
function hash(col, row, a, b) {
const v = Math.sin(col * a + row * b) * 43758.5453;
return v - Math.floor(v);
}
// Same Gaussian^falloff + smooth cutoff as the source hover (falloff 1.4).
// Distances are in composition pixels, so the field stays circular even
// when square cell size / grid density changes.
function fieldAt(x, y, cursorX, cursorY) {
const distanceSquared = (x - cursorX) ** 2 + (y - cursorY) ** 2;
const raw = Math.exp((-cursorFalloff * distanceSquared) / (cursorRadius * cursorRadius));
const u = clamp((raw - 0.06) / 0.94);
return u * u * (3 - 2 * u);
}
// One analytic ease-out and its inverse drive every passage/window time.
// Strength zero preserves linear travel exactly; strength one is quadratic.
const easeSweep = (u) =>
sweepPower === 1 ? clamp(u) : 1 - Math.pow(1 - clamp(u), sweepPower);
const inverseSweep = (u) =>
sweepPower === 1 ? clamp(u) : 1 - Math.pow(1 - clamp(u), 1 / sweepPower);
function cursorTimeAtX(x) {
const rank = (direction === 1 ? x - cursorLeft : W - x - cursorLeft) / cursorSpan;
return SWEEP_START + sweepDuration * inverseSweep(rank);
}
function cursorAt(time) {
const progress = easeSweep((time - SWEEP_START) / sweepDuration);
return {
x: cursorLeft + cursorSpan * (direction === 1 ? progress : 1 - progress),
y: H / 2,
z: cursorDepth,
};
}
function poseAt(tile, time) {
const t = clamp(time, 0, DURATION);
const progress = tile.active ? clamp((t - tile.flipStart) / tile.flipDuration) : 0;
const turn = smooth(progress / 0.64);
// Analytic damped oscillation: overshoot, return past 180, settle exactly.
// The attack/tail both have zero slope, so bounce=0 is a clean half-turn.
const ringT = clamp((progress - 0.64) / 0.36);
const ringEnvelope =
smooth(ringT / 0.12) * Math.exp(-3.3 * ringT) * (1 - smooth((ringT - 0.72) / 0.28));
const bounce = flipBounce * 36 * Math.sin(ringT * Math.PI * 3.3) * ringEnvelope;
const flight = Math.sin(Math.PI * progress) ** 0.7;
const cursor = cursorAt(t);
const radial = fieldAt(tile.cx, tile.cy, cursor.x, cursor.y);
const automatic = t >= SWEEP_START && t <= SWEEP_START + sweepDuration;
// A short approach replaces the long, separate anticipation phase.
const approach = smooth((t - tile.passTime + 0.2) / 0.16);
const pull =
(automatic ? radial * approach * (1 - smooth(progress / 0.32)) : 0) *
(tile.active ? 1 : 0.55);
const dx = cursor.x - tile.cx,
dy = cursor.y - tile.cy;
const variation = noiseStrength * Math.max(pull, flight * tile.peak);
// Continuous deterministic oscillations, not random-at-render-time jitter.
const n1 = Math.sin(t * 5.7 + tile.phase),
n2 = Math.sin(t * 10.3 + tile.phase * 1.71);
const jitter = variation * (n1 * 0.7 + n2 * 0.3);
const formation = Math.max(pull, flight * (0.55 + 0.45 * tile.peak));
// CSS positive Z points toward the camera. The prior 2D tilt signs
// depressed the cursor-facing edge despite lifting the tile center.
// A signed depth field now raises that edge for a positive cursor Z;
// moving the cursor through the plane smoothly reverses this torque.
const depthGain = cursor.z / 160;
const pullTiltX =
depthGain *
((dy / cursorRadius) * tiltStrength * 1.6 * pull +
tile.seedX * tiltStrength * 0.16 * pull);
// Ink yaw cannot reverse against its half-turn after cursor passage.
// Release its seeded torque over 80 ms; blank tiles retain signed pull.
const yawDx = tile.active ? direction * Math.max(0, -direction * dx) : -dx;
const yawHandoff = tile.active ? 1 - smooth((t - tile.passTime + 0.04) / 0.12) : 1;
const pullTiltY =
depthGain *
((yawDx / cursorRadius) * tiltStrength * 1.8 * pull +
tile.seedY * tiltStrength * 0.12 * pull) *
yawHandoff;
const pullZ = (tiltStrength / 30) * cursor.z * 0.45 * pull;
const rx = pullTiltX + tile.seedX * variation * 3 * flight + jitter;
const ry = direction * (180 * turn + bounce) + pullTiltY + jitter * 0.7;
const rz = tile.seedZ * variation * (2.3 * flight + 1.2 * pull) + jitter * 0.35;
return {
x: clamp(dx * 0.08 * pull, -tile.width * 0.18, tile.width * 0.18) + jitter * 0.2,
y: clamp(dy * 0.07 * pull, -tile.height * 0.11, tile.height * 0.11) + jitter * 0.16,
z: pullZ + 17 * flight * tile.peak,
cursorDepth: cursor.z,
pullTiltX,
pullTiltY,
pullZ,
rx,
ry,
rz,
turn,
bounce,
radial,
pull,
peak: tile.peak,
formation,
scale: 1 - (1 - formationScale) * formation,
shade: 1 - 0.12 * Math.abs(Math.sin((ry * Math.PI) / 180)) - 0.018 * formation,
};
}
function setRest(tile, rear) {
const rest = tile.active && rear ? 1 : 0;
if (tile.restState === rest) return;
tile.restState = rest;
surface.set(tile.index, {
x: 0,
y: 0,
z: 0,
rx: 0,
ry: rest * direction * 180,
rz: 0,
scale: 1,
formation: 0,
shade: 1,
});
}
function renderAt(time) {
if (disposed) return;
lastTime = clamp(time, 0, DURATION);
if (lastTime === lastRenderedTime) return;
lastRenderedTime = lastTime;
renderStats.evaluatedTiles = 0;
renderStats.drawCalls = 0;
renderStats.restTiles = 0;
const mode =
lastTime <= firstMotion ? "front" : lastTime >= lastMotion ? "rear" : "moving";
if (mode !== frameMode) {
frameMode = mode;
stage.style.visibility = mode === "moving" ? "visible" : "hidden";
flat.style.visibility = mode === "moving" ? "hidden" : "visible";
if (mode !== "moving")
flat.style.backgroundImage = `var(--csh-${mode === "front" ? "front" : "back"})`;
}
// One unsegmented atlas is shown only while EVERY tile is flat. This
// removes compositor hairlines in the workbench's scaled first frame.
// It is hidden for the whole active wave: no backing text or mask fade.
for (const tile of tiles) {
if (mode !== "moving" || lastTime < tile.motionStart || lastTime > tile.motionEnd) {
setRest(tile, mode === "rear" || lastTime > tile.flipStart + tile.flipDuration);
renderStats.restTiles++;
continue;
}
tile.restState = null;
surface.set(tile.index, poseAt(tile, lastTime));
renderStats.evaluatedTiles++;
}
if (mode === "moving") renderStats.drawCalls = surface.draw();
const copy =
lastTime < SWEEP_START + sweepDuration / 2 + flipDuration / 2
? headline
: reverseHeadline;
if (accessible.textContent !== copy) accessible.textContent = copy;
stage.dataset.time = lastTime.toFixed(4);
}
function build() {
if (disposed) return;
const front = textSurface(headline),
back = textSurface(reverseHeadline);
root.style.setProperty("--csh-front", `url("${front.url}")`);
root.style.setProperty("--csh-back", `url("${back.url}")`);
let coveredFront = 0,
coveredBack = 0;
firstMotion = Infinity;
lastMotion = 0;
for (let row = 0; row < rows; row++)
for (let col = 0; col < columns; col++) {
// One exhaustive integer pixel partition. No alpha threshold, corner
// sampling, skipped row, or per-letter box can punch holes in the type.
const x = gridX + col * cellSize,
x1 = x + cellSize;
const y = gridY + row * cellSize,
y1 = y + cellSize;
const frontInk = countInk(front.ink, x, y, x1, y1),
backInk = countInk(back.ink, x, y, x1, y1);
coveredFront += frontInk;
coveredBack += backInk;
const cell = {
col,
row,
x,
y,
width: x1 - x,
height: y1 - y,
frontInk,
backInk,
active: frontInk + backInk > 0,
};
cells.push(cell);
// Build the whole square surface. White cells participate in radial
// lift/tilt at a restrained strength, but progress is locked to zero
// above so they never enter the headline's half-turn or bounce.
const cx = x + cell.width / 2,
cy = y + cell.height / 2;
const peak = fieldAt(cx, cy, cx, H / 2);
const passTime = cursorTimeAtX(cx);
const columnSeconds = Math.abs(
cursorTimeAtX(cx + cellSize / 2) - cursorTimeAtX(cx - cellSize / 2),
);
// The turn starts at passage, with at most 12 ms of seeded stagger.
const flipStart =
passTime +
hash(col, row, 269.5, 183.3) *
Math.min(0.012, columnSeconds * 0.08) *
Math.min(noiseStrength, 1);
const duration = flipDuration * (1 + 0.18 * (1 - peak));
const tile = {
...cell,
index: tiles.length,
cx,
cy,
peak,
passTime,
flipStart,
flipDuration: duration,
phase: hash(col, row, 127.1, 311.7) * Math.PI * 2,
seedX: hash(col, row, 419.2, 371.9) * 2 - 1,
seedY: hash(col, row, 37.7, 631.5) * 2 - 1,
seedZ: hash(col, row, 97.3, 127.9) * 2 - 1,
};
// The Gaussian is exactly zero outside this circle. Precompute each
// cell's entry/exit times once, independent of playback history.
const dy = cy - H / 2;
let enter = Infinity,
exit = -Infinity;
if (Math.abs(dy) < fieldReach) {
const dx = Math.sqrt(fieldReach * fieldReach - dy * dy);
enter = Math.max(
SWEEP_START,
passTime - 0.2,
cursorTimeAtX(direction === 1 ? cx - dx : cx + dx),
);
exit = Math.min(
SWEEP_START + sweepDuration,
cursorTimeAtX(direction === 1 ? cx + dx : cx - dx),
);
}
tile.motionStart = Math.min(enter, tile.active ? flipStart : Infinity);
tile.motionEnd = Math.max(exit, tile.active ? flipStart + duration : -Infinity);
firstMotion = Math.min(firstMotion, tile.motionStart);
lastMotion = Math.max(lastMotion, tile.motionEnd);
if (tile.active) finishTime = Math.max(finishTime, flipStart + duration);
tiles.push(tile);
}
const inkTiles = tiles.filter((tile) => tile.active);
root.style.backgroundColor = behindColor;
surface = window.CodeSliceSurface(stage, front.canvas, back.canvas, tiles, {
behindColor,
shadowStrength,
shadowSoftness,
});
const state = { time: 0 };
timeline = gsap.timeline({ paused: true });
timeline.to(
state,
{
time: DURATION,
duration: DURATION,
ease: "none",
onUpdate: () => renderAt(state.time),
},
0,
);
timeline
.addLabel("seamless-front", 0)
.addLabel("cursor-sweep", SWEEP_START)
.addLabel("cursor-passed", SWEEP_START + sweepDuration)
.addLabel("seamless-rear", finishTime);
window.__timelines = window.__timelines || {};
window.__timelines["code-slice-hero"] = timeline;
window.__codeSliceHero = {
duration: DURATION,
cellSize,
columns,
rows,
activeCount: tiles.length,
flipCount: inkTiles.length,
backgroundCount: tiles.length - inkTiles.length,
copy: [headline, reverseHeadline],
fontSizes: [front.size, back.size],
finishTime,
coverage: {
front: { inkPixels: front.inkPixels, covered: coveredFront },
back: { inkPixels: back.inkPixels, covered: coveredBack },
},
cells,
cursorAt,
cursorTimeAtX,
sweepEaseStrength: sweepPower - 1,
renderStats,
shadowStats: surface.shadowStats,
behindColor,
renderer: "webgl2-instanced",
sample: (time) =>
tiles.map((tile) => ({
col: tile.col,
row: tile.row,
flips: tile.active,
passTime: tile.passTime,
flipStart: tile.flipStart,
flipDuration: tile.flipDuration,
...poseAt(tile, time),
})),
renderAt,
};
renderAt(0);
}
// The cursor is imaginary and belongs exclusively to the timeline.
// No mouse/pointer input or wall-clock accumulation can affect a frame.
function seek(event) {
if (!timeline || typeof event.detail?.time !== "number") return;
const time = clamp(event.detail.time, 0, DURATION);
timeline.totalTime(time, true);
renderAt(time);
}
window.addEventListener("hf-seek", seek);
window.__codeSliceInstance = {
dispose() {
disposed = true;
timeline?.kill();
surface?.dispose();
window.removeEventListener("hf-seek", seek);
stage.replaceChildren();
tiles.length = 0;
cells.length = 0;
root.style.removeProperty("--csh-front");
root.style.removeProperty("--csh-back");
},
};
window.__codeSliceReady = document.fonts.load("700 420px SliceGeist").then(build);
})();
</script>
</body>
</html>
```
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add code-slice-hero" item="code-slice-hero" />
That writes `compositions/code-slice-hero/code-slice-hero.html`, plus 7 supporting files under `compositions/code-slice-hero/assets/` and `compositions/code-slice-hero/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 8 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-slice-hero"
data-composition-src="compositions/code-slice-hero/code-slice-hero.html"
data-start="0"
data-duration="8"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `3d-motion` `transition` `typography` `tiles` `flip` `webgl2` `shadows` `two-headlines`.
## Related topics
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Basic"
description: "Apple Terminal Basic profile: white background, black text, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-basic.json"
compositionId="code-snippet-apple-terminal-basic"
compositionSrc="compositions/code-snippet-apple-terminal-basic.html"
title="Code Snippet - Apple Terminal Basic"
description="Apple Terminal Basic profile: white background, black text, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":249}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-basic","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-basic/code-snippet-apple-terminal-basic.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-basic" item="code-snippet-apple-terminal-basic" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-basic.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-basic preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-basic.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-basic.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-basic.html">
```html
```html code-snippet-apple-terminal-basic.html
<!doctype html>
<html lang="en">
<head>
@@ -274,13 +274,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-basic.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-basic" item="code-snippet-apple-terminal-basic" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-basic.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-basic"
data-composition-src="compositions/code-snippet-apple-terminal-basic.html"
@@ -292,14 +302,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Basic profile — white background, black text, solid cursor
- macOS window chrome with traffic light buttons (close, minimize, fullscreen) and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -310,3 +314,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Clear Dark"
description: "Apple Terminal Clear Dark profile: semi-transparent dark background, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-clear-dark.json"
compositionId="code-snippet-apple-terminal-clear-dark"
compositionSrc="compositions/code-snippet-apple-terminal-clear-dark.html"
title="Code Snippet - Apple Terminal Clear Dark"
description="Apple Terminal Clear Dark profile: semi-transparent dark background, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":244}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-clear-dark","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-clear-dark/code-snippet-apple-terminal-clear-dark.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-clear-dark" item="code-snippet-apple-terminal-clear-dark" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-clear-dark.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-clear-dark preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-clear-dark.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-clear-dark.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-clear-dark.html">
```html
```html code-snippet-apple-terminal-clear-dark.html
<!doctype html>
<html lang="en">
<head>
@@ -269,13 +269,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-clear-dark.html`
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-clear-dark" item="code-snippet-apple-terminal-clear-dark" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-clear-dark.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-clear-dark"
data-composition-src="compositions/code-snippet-apple-terminal-clear-dark.html"
@@ -287,14 +297,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Clear Dark profile — semi-transparent black background with white text
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -305,3 +309,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Clear Light"
description: "Apple Terminal Clear Light profile: semi-transparent light background, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-clear-light.json"
compositionId="code-snippet-apple-terminal-clear-light"
compositionSrc="compositions/code-snippet-apple-terminal-clear-light.html"
title="Code Snippet - Apple Terminal Clear Light"
description="Apple Terminal Clear Light profile: semi-transparent light background, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":244}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-clear-light","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-clear-light/code-snippet-apple-terminal-clear-light.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-clear-light" item="code-snippet-apple-terminal-clear-light" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-clear-light.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-clear-light preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-clear-light.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-clear-light.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-clear-light.html">
```html
```html code-snippet-apple-terminal-clear-light.html
<!doctype html>
<html lang="en">
<head>
@@ -269,13 +269,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-clear-light.html
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-clear-light" item="code-snippet-apple-terminal-clear-light" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-clear-light.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-clear-light"
data-composition-src="compositions/code-snippet-apple-terminal-clear-light.html"
@@ -287,14 +297,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Clear Light profile — semi-transparent white background with black text
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -305,3 +309,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Grass"
description: "Apple Terminal Grass profile: black background with green text, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-grass.json"
compositionId="code-snippet-apple-terminal-grass"
compositionSrc="compositions/code-snippet-apple-terminal-grass.html"
title="Code Snippet - Apple Terminal Grass"
description="Apple Terminal Grass profile: black background with green text, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":243}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-grass","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-grass/code-snippet-apple-terminal-grass.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-grass" item="code-snippet-apple-terminal-grass" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-grass.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-grass preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-grass.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-grass.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-grass.html">
```html
```html code-snippet-apple-terminal-grass.html
<!doctype html>
<html lang="en">
<head>
@@ -268,13 +268,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-grass.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-grass" item="code-snippet-apple-terminal-grass" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-grass.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-grass"
data-composition-src="compositions/code-snippet-apple-terminal-grass.html"
@@ -286,14 +296,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Grass profile — black background with #00C100 green text
- macOS window chrome with green-accented title bar and traffic light buttons
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -304,3 +308,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Homebrew"
description: "Apple Terminal Homebrew profile: black background with bright green text and lime cursor, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-homebrew.json"
compositionId="code-snippet-apple-terminal-homebrew"
compositionSrc="compositions/code-snippet-apple-terminal-homebrew.html"
title="Code Snippet - Apple Terminal Homebrew"
description="Apple Terminal Homebrew profile: black background with bright green text and lime cursor, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":248}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-homebrew","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-homebrew/code-snippet-apple-terminal-homebrew.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-homebrew" item="code-snippet-apple-terminal-homebrew" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-homebrew.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-homebrew preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-homebrew.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-homebrew.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-homebrew.html">
```html
```html code-snippet-apple-terminal-homebrew.html
<!doctype html>
<html lang="en">
<head>
@@ -273,13 +273,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-homebrew.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-homebrew" item="code-snippet-apple-terminal-homebrew" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-homebrew.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-homebrew"
data-composition-src="compositions/code-snippet-apple-terminal-homebrew.html"
@@ -291,14 +301,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Homebrew profile — black background with #00CF00 text and #48F800 cursor
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -309,3 +313,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Man Page"
description: "Apple Terminal Man Page profile: pale yellow background with black text, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-man-page.json"
compositionId="code-snippet-apple-terminal-man-page"
compositionSrc="compositions/code-snippet-apple-terminal-man-page.html"
title="Code Snippet - Apple Terminal Man Page"
description="Apple Terminal Man Page profile: pale yellow background with black text, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":248}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-man-page","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-man-page/code-snippet-apple-terminal-man-page.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-man-page" item="code-snippet-apple-terminal-man-page" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-man-page.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-man-page preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-man-page.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-man-page.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-man-page.html">
```html
```html code-snippet-apple-terminal-man-page.html
<!doctype html>
<html lang="en">
<head>
@@ -273,13 +273,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-man-page.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-man-page" item="code-snippet-apple-terminal-man-page" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-man-page.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-man-page"
data-composition-src="compositions/code-snippet-apple-terminal-man-page.html"
@@ -291,14 +301,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Man Page profile — pale yellow background with black text
- Shows a realistic man page output structure with sections and indented content
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -309,3 +313,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Novel"
description: "Apple Terminal Novel profile: warm parchment background with dark brown text, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-novel.json"
compositionId="code-snippet-apple-terminal-novel"
compositionSrc="compositions/code-snippet-apple-terminal-novel.html"
title="Code Snippet - Apple Terminal Novel"
description="Apple Terminal Novel profile: warm parchment background with dark brown text, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":244}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-novel","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-novel/code-snippet-apple-terminal-novel.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-novel" item="code-snippet-apple-terminal-novel" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-novel.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-novel preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-novel.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-novel.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-novel.html">
```html
```html code-snippet-apple-terminal-novel.html
<!doctype html>
<html lang="en">
<head>
@@ -269,13 +269,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-novel.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-novel" item="code-snippet-apple-terminal-novel" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-novel.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-novel"
data-composition-src="compositions/code-snippet-apple-terminal-novel.html"
@@ -287,14 +297,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Novel profile — warm parchment background with #3E2900 dark brown text
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -305,3 +309,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Ocean"
description: "Apple Terminal Ocean profile: deep blue background with white text, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-ocean.json"
compositionId="code-snippet-apple-terminal-ocean"
compositionSrc="compositions/code-snippet-apple-terminal-ocean.html"
title="Code Snippet - Apple Terminal Ocean"
description="Apple Terminal Ocean profile: deep blue background with white text, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":253}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-ocean","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-ocean/code-snippet-apple-terminal-ocean.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-ocean" item="code-snippet-apple-terminal-ocean" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-ocean.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-ocean preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-ocean.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-ocean.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-ocean.html">
```html
```html code-snippet-apple-terminal-ocean.html
<!doctype html>
<html lang="en">
<head>
@@ -278,13 +278,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-ocean.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-ocean" item="code-snippet-apple-terminal-ocean" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-ocean.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-ocean"
data-composition-src="compositions/code-snippet-apple-terminal-ocean.html"
@@ -296,14 +306,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Ocean profile — #224FBE blue background with white text
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -314,3 +318,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Pro"
description: "Apple Terminal Pro profile: black background with grey text and lime green cursor, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-pro.json"
compositionId="code-snippet-apple-terminal-pro"
compositionSrc="compositions/code-snippet-apple-terminal-pro.html"
title="Code Snippet - Apple Terminal Pro"
description="Apple Terminal Pro profile: black background with grey text and lime green cursor, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":247}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-pro","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-pro/code-snippet-apple-terminal-pro.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-pro" item="code-snippet-apple-terminal-pro" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-pro.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-pro preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-pro.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-pro.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-pro.html">
```html
```html code-snippet-apple-terminal-pro.html
<!doctype html>
<html lang="en">
<head>
@@ -272,13 +272,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-pro.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-pro" item="code-snippet-apple-terminal-pro" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-pro.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-pro"
data-composition-src="compositions/code-snippet-apple-terminal-pro.html"
@@ -290,14 +300,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Pro profile — black background, #BBBBBB grey text, #88FF67 lime cursor
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -308,3 +312,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Red Sands"
description: "Apple Terminal Red Sands profile: deep red background with sandy text, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-red-sands.json"
compositionId="code-snippet-apple-terminal-red-sands"
compositionSrc="compositions/code-snippet-apple-terminal-red-sands.html"
title="Code Snippet - Apple Terminal Red Sands"
description="Apple Terminal Red Sands profile: deep red background with sandy text, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":244}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-red-sands","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-red-sands/code-snippet-apple-terminal-red-sands.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-red-sands" item="code-snippet-apple-terminal-red-sands" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-red-sands.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-red-sands preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-red-sands.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-red-sands.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-red-sands.html">
```html
```html code-snippet-apple-terminal-red-sands.html
<!doctype html>
<html lang="en">
<head>
@@ -269,13 +269,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-red-sands.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-red-sands" item="code-snippet-apple-terminal-red-sands" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-red-sands.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-red-sands"
data-composition-src="compositions/code-snippet-apple-terminal-red-sands.html"
@@ -287,14 +297,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Red Sands profile — #7B140E red background with #FFE1B9 sandy text
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -305,3 +309,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Silver Aerogel"
description: "Apple Terminal Silver Aerogel profile: dark grey background with white text, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-silver-aerogel.json"
compositionId="code-snippet-apple-terminal-silver-aerogel"
compositionSrc="compositions/code-snippet-apple-terminal-silver-aerogel.html"
title="Code Snippet - Apple Terminal Silver Aerogel"
description="Apple Terminal Silver Aerogel profile: dark grey background with white text, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":250}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-silver-aerogel","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-silver-aerogel/code-snippet-apple-terminal-silver-aerogel.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-silver-aerogel" item="code-snippet-apple-terminal-silver-aerogel" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-silver-aerogel.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-silver-aerogel preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-silver-aerogel.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-silver-aerogel.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-silver-aerogel.html">
```html
```html code-snippet-apple-terminal-silver-aerogel.html
<!doctype html>
<html lang="en">
<head>
@@ -275,13 +275,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-silver-aerogel.h
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-silver-aerogel" item="code-snippet-apple-terminal-silver-aerogel" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-silver-aerogel.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-silver-aerogel"
data-composition-src="compositions/code-snippet-apple-terminal-silver-aerogel.html"
@@ -293,14 +303,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Silver Aerogel profile — #3D3D3D dark grey background with white text
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -311,3 +315,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Apple Terminal Solid Colors"
description: "Apple Terminal Solid Colors profile: deep purple background with white text, per-character typing animation of a shell session."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-apple-terminal-solid-colors.json"
compositionId="code-snippet-apple-terminal-solid-colors"
compositionSrc="compositions/code-snippet-apple-terminal-solid-colors.html"
title="Code Snippet - Apple Terminal Solid Colors"
description="Apple Terminal Solid Colors profile: deep purple background with white text, per-character typing animation of a shell session."
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":247}}
attribution={{"path":"registry/blocks/code-snippet-apple-terminal-solid-colors","tags":["code","developer","showcase","terminal","apple","apple-terminal"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-apple-terminal-solid-colors/code-snippet-apple-terminal-solid-colors.html"
>
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-solid-colors" item="code-snippet-apple-terminal-solid-colors" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-apple-terminal-solid-colors.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-apple-terminal-solid-colors preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-apple-terminal-solid-colors.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-solid-colors.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-apple-terminal-solid-colors.html">
```html
```html code-snippet-apple-terminal-solid-colors.html
<!doctype html>
<html lang="en">
<head>
@@ -272,13 +272,23 @@ That writes one file: `compositions/code-snippet-apple-terminal-solid-colors.htm
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-apple-terminal-solid-colors" item="code-snippet-apple-terminal-solid-colors" />
```html
That writes one file: `compositions/code-snippet-apple-terminal-solid-colors.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-apple-terminal-solid-colors"
data-composition-src="compositions/code-snippet-apple-terminal-solid-colors.html"
@@ -290,14 +300,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Faithful recreation of macOS Terminal.app Solid Colors profile — #4C0099 deep purple background with white text
- macOS window chrome with traffic light buttons and centered title bar
- Per-character GSAP typing animation — command types out character by character
- Output lines reveal sequentially after command executes
- Cursor blinks three times at the end then holds steady
- Self-contained HTML — no external assets, only GSAP CDN
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -308,3 +312,7 @@ Tagged `code` `developer` `showcase` `terminal` `apple` `apple-terminal`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+39 -29
View File
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Dark 2026"
description: "VS Code workbench with per-character typing animation in the Dark 2026 theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-dark-2026.json"
compositionId="code-snippet-dark-2026"
compositionSrc="compositions/code-snippet-dark-2026.html"
title="Code Snippet - Dark 2026"
description="VS Code workbench with per-character typing animation in the Dark 2026 theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1091}}
attribution={{"path":"registry/blocks/code-snippet-dark-2026","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-dark-2026/code-snippet-dark-2026.html"
>
<InstallCommand command="npx hyperframes add code-snippet-dark-2026" item="code-snippet-dark-2026" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-dark-2026.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-dark-2026 preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-dark-2026.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-dark-2026.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-dark-2026.html">
```html
```html code-snippet-dark-2026.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-dark-2026.html`, plus 1 supporting file u
<body>
<template id="vscode-dark-2026-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-dark-2026"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-dark-2026.html`, plus 1 supporting file u
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-dark-2026.html`, plus 1 supporting file u
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1115,13 +1116,23 @@ That writes `compositions/code-snippet-dark-2026.html`, plus 1 supporting file u
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-dark-2026" item="code-snippet-dark-2026" />
```html
That writes `compositions/code-snippet-dark-2026.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-dark-2026"
data-composition-src="compositions/code-snippet-dark-2026.html"
@@ -1133,13 +1144,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1150,3 +1156,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Dark Modern"
description: "VS Code workbench with per-character typing animation in the Dark Modern theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-dark-modern.json"
compositionId="code-snippet-dark-modern"
compositionSrc="compositions/code-snippet-dark-modern.html"
title="Code Snippet - Dark Modern"
description="VS Code workbench with per-character typing animation in the Dark Modern theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1087}}
attribution={{"path":"registry/blocks/code-snippet-dark-modern","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-dark-modern/code-snippet-dark-modern.html"
>
<InstallCommand command="npx hyperframes add code-snippet-dark-modern" item="code-snippet-dark-modern" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-dark-modern.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-dark-modern preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-dark-modern.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-dark-modern.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-dark-modern.html">
```html
```html code-snippet-dark-modern.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-dark-modern.html`, plus 1 supporting file
<body>
<template id="vscode-dark-modern-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-dark-modern"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-dark-modern.html`, plus 1 supporting file
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-dark-modern.html`, plus 1 supporting file
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1111,13 +1112,23 @@ That writes `compositions/code-snippet-dark-modern.html`, plus 1 supporting file
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-dark-modern" item="code-snippet-dark-modern" />
```html
That writes `compositions/code-snippet-dark-modern.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-dark-modern"
data-composition-src="compositions/code-snippet-dark-modern.html"
@@ -1129,13 +1140,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1146,3 +1152,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+39 -29
View File
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Dark+"
description: "VS Code workbench with per-character typing animation in the Dark+ theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-dark-plus.json"
compositionId="code-snippet-dark-plus"
compositionSrc="compositions/code-snippet-dark-plus.html"
title="Code Snippet - Dark+"
description="VS Code workbench with per-character typing animation in the Dark+ theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1056}}
attribution={{"path":"registry/blocks/code-snippet-dark-plus","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-dark-plus/code-snippet-dark-plus.html"
>
<InstallCommand command="npx hyperframes add code-snippet-dark-plus" item="code-snippet-dark-plus" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-dark-plus.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-dark-plus preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-dark-plus.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-dark-plus.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-dark-plus.html">
```html
```html code-snippet-dark-plus.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-dark-plus.html`, plus 1 supporting file u
<body>
<template id="vscode-dark-plus-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-dark-plus"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-dark-plus.html`, plus 1 supporting file u
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-dark-plus.html`, plus 1 supporting file u
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1080,13 +1081,23 @@ That writes `compositions/code-snippet-dark-plus.html`, plus 1 supporting file u
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-dark-plus" item="code-snippet-dark-plus" />
```html
That writes `compositions/code-snippet-dark-plus.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-dark-plus"
data-composition-src="compositions/code-snippet-dark-plus.html"
@@ -1098,13 +1109,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1115,3 +1121,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -22
View File
@@ -1,28 +1,28 @@
---
title: "Code Snippet Flight"
description: "Discrete code snippets fly in from the side and assemble into a stacked program, staggered. Block-level FLIP."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-flight.json"
compositionId="code-snippet-flight"
compositionSrc="compositions/code-snippet-flight.html"
title="Code Snippet Flight"
description="Discrete code snippets fly in from the side and assemble into a stacked program, staggered. Block-level FLIP."
variables={[]}
meta={{"duration":6,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":668}}
attribution={{"path":"registry/blocks/code-snippet-flight","tags":["code","code-animation","flight","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-flight/code-snippet-flight.html"
>
<InstallCommand command="npx hyperframes add code-snippet-flight" item="code-snippet-flight" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-snippet-flight.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-flight preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-flight.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-flight.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-flight.html">
```html
```html code-snippet-flight.html
<!doctype html>
<html lang="en">
<head>
@@ -673,12 +673,15 @@ That writes one file: `compositions/code-snippet-flight.html`.
var root = document.getElementById("root");
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
window.__timelines["code-snippet-flight"] = tl; // register synchronously
document.fonts.ready.then(function () {
tl.from("#editor", { opacity: 0, scale: 0.985, duration: 0.5, ease: "power2.out" }, 0);
buildEffect(tl, surface, spec);
var dur = parseFloat(root.dataset.duration) || tl.duration();
tl.to({}, { duration: dur }, 0); // pad to full composition length
window.__timelines["code-snippet-flight"] = tl; // register AFTER setup completes
if (typeof window.__hfForceTimelineRebind === "function") {
window.__hfForceTimelineRebind(); // re-nest now that we are populated
}
});
}
@@ -690,16 +693,37 @@ That writes one file: `compositions/code-snippet-flight.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-flight" item="code-snippet-flight" />
```html
<div data-composition-id="code-snippet-flight" data-composition-src="compositions/code-snippet-flight.html" data-start="0" data-duration="6" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-snippet-flight.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-flight"
data-composition-src="compositions/code-snippet-flight.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `flight` `developer`.
@@ -709,3 +733,7 @@ Tagged `code` `code-animation` `flight` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - High Contrast Light"
description: "VS Code workbench with per-character typing animation in the High Contrast Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-high-contrast-light.json"
compositionId="code-snippet-high-contrast-light"
compositionSrc="compositions/code-snippet-high-contrast-light.html"
title="Code Snippet - High Contrast Light"
description="VS Code workbench with per-character typing animation in the High Contrast Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1051}}
attribution={{"path":"registry/blocks/code-snippet-high-contrast-light","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-high-contrast-light/code-snippet-high-contrast-light.html"
>
<InstallCommand command="npx hyperframes add code-snippet-high-contrast-light" item="code-snippet-high-contrast-light" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-high-contrast-light.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-high-contrast-light preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-high-contrast-light.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-high-contrast-light.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-high-contrast-light.html">
```html
```html code-snippet-high-contrast-light.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-high-contrast-light.html`, plus 1 support
<body>
<template id="vscode-high-contrast-light-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-high-contrast-light"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-high-contrast-light.html`, plus 1 support
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-high-contrast-light.html`, plus 1 support
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1075,13 +1076,23 @@ That writes `compositions/code-snippet-high-contrast-light.html`, plus 1 support
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-high-contrast-light" item="code-snippet-high-contrast-light" />
```html
That writes `compositions/code-snippet-high-contrast-light.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-high-contrast-light"
data-composition-src="compositions/code-snippet-high-contrast-light.html"
@@ -1093,13 +1104,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1110,3 +1116,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - High Contrast"
description: "VS Code workbench with per-character typing animation in the High Contrast theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-high-contrast.json"
compositionId="code-snippet-high-contrast"
compositionSrc="compositions/code-snippet-high-contrast.html"
title="Code Snippet - High Contrast"
description="VS Code workbench with per-character typing animation in the High Contrast theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1054}}
attribution={{"path":"registry/blocks/code-snippet-high-contrast","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-high-contrast/code-snippet-high-contrast.html"
>
<InstallCommand command="npx hyperframes add code-snippet-high-contrast" item="code-snippet-high-contrast" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-high-contrast.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-high-contrast preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-high-contrast.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-high-contrast.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-high-contrast.html">
```html
```html code-snippet-high-contrast.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-high-contrast.html`, plus 1 supporting fi
<body>
<template id="vscode-high-contrast-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-high-contrast"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-high-contrast.html`, plus 1 supporting fi
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-high-contrast.html`, plus 1 supporting fi
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1078,13 +1079,23 @@ That writes `compositions/code-snippet-high-contrast.html`, plus 1 supporting fi
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-high-contrast" item="code-snippet-high-contrast" />
```html
That writes `compositions/code-snippet-high-contrast.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-high-contrast"
data-composition-src="compositions/code-snippet-high-contrast.html"
@@ -1096,13 +1107,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1113,3 +1119,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+39 -29
View File
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Light 2026"
description: "VS Code workbench with per-character typing animation in the Light 2026 theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-light-2026.json"
compositionId="code-snippet-light-2026"
compositionSrc="compositions/code-snippet-light-2026.html"
title="Code Snippet - Light 2026"
description="VS Code workbench with per-character typing animation in the Light 2026 theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1090}}
attribution={{"path":"registry/blocks/code-snippet-light-2026","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-light-2026/code-snippet-light-2026.html"
>
<InstallCommand command="npx hyperframes add code-snippet-light-2026" item="code-snippet-light-2026" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-light-2026.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-light-2026 preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-light-2026.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-light-2026.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-light-2026.html">
```html
```html code-snippet-light-2026.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-light-2026.html`, plus 1 supporting file
<body>
<template id="vscode-light-2026-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-light-2026"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-light-2026.html`, plus 1 supporting file
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-light-2026.html`, plus 1 supporting file
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1114,13 +1115,23 @@ That writes `compositions/code-snippet-light-2026.html`, plus 1 supporting file
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-light-2026" item="code-snippet-light-2026" />
```html
That writes `compositions/code-snippet-light-2026.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-light-2026"
data-composition-src="compositions/code-snippet-light-2026.html"
@@ -1132,13 +1143,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1149,3 +1155,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Light Modern"
description: "VS Code workbench with per-character typing animation in the Light Modern theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-light-modern.json"
compositionId="code-snippet-light-modern"
compositionSrc="compositions/code-snippet-light-modern.html"
title="Code Snippet - Light Modern"
description="VS Code workbench with per-character typing animation in the Light Modern theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1088}}
attribution={{"path":"registry/blocks/code-snippet-light-modern","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-light-modern/code-snippet-light-modern.html"
>
<InstallCommand command="npx hyperframes add code-snippet-light-modern" item="code-snippet-light-modern" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-light-modern.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-light-modern preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-light-modern.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-light-modern.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-light-modern.html">
```html
```html code-snippet-light-modern.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-light-modern.html`, plus 1 supporting fil
<body>
<template id="vscode-light-modern-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-light-modern"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-light-modern.html`, plus 1 supporting fil
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-light-modern.html`, plus 1 supporting fil
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1112,13 +1113,23 @@ That writes `compositions/code-snippet-light-modern.html`, plus 1 supporting fil
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-light-modern" item="code-snippet-light-modern" />
```html
That writes `compositions/code-snippet-light-modern.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-light-modern"
data-composition-src="compositions/code-snippet-light-modern.html"
@@ -1130,13 +1141,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1147,3 +1153,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+39 -29
View File
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Light+"
description: "VS Code workbench with per-character typing animation in the Light+ theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-light-plus.json"
compositionId="code-snippet-light-plus"
compositionSrc="compositions/code-snippet-light-plus.html"
title="Code Snippet - Light+"
description="VS Code workbench with per-character typing animation in the Light+ theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1056}}
attribution={{"path":"registry/blocks/code-snippet-light-plus","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-light-plus/code-snippet-light-plus.html"
>
<InstallCommand command="npx hyperframes add code-snippet-light-plus" item="code-snippet-light-plus" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-light-plus.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-light-plus preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-light-plus.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-light-plus.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-light-plus.html">
```html
```html code-snippet-light-plus.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-light-plus.html`, plus 1 supporting file
<body>
<template id="vscode-light-plus-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-light-plus"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-light-plus.html`, plus 1 supporting file
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-light-plus.html`, plus 1 supporting file
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1080,13 +1081,23 @@ That writes `compositions/code-snippet-light-plus.html`, plus 1 supporting file
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-light-plus" item="code-snippet-light-plus" />
```html
That writes `compositions/code-snippet-light-plus.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-light-plus"
data-composition-src="compositions/code-snippet-light-plus.html"
@@ -1098,13 +1109,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1115,3 +1121,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+39 -29
View File
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Monokai"
description: "VS Code workbench with per-character typing animation in the Monokai theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-monokai.json"
compositionId="code-snippet-monokai"
compositionSrc="compositions/code-snippet-monokai.html"
title="Code Snippet - Monokai"
description="VS Code workbench with per-character typing animation in the Monokai theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1068}}
attribution={{"path":"registry/blocks/code-snippet-monokai","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-monokai/code-snippet-monokai.html"
>
<InstallCommand command="npx hyperframes add code-snippet-monokai" item="code-snippet-monokai" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-monokai.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-monokai preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-monokai.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-monokai.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-monokai.html">
```html
```html code-snippet-monokai.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-monokai.html`, plus 1 supporting file und
<body>
<template id="vscode-monokai-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-monokai"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-monokai.html`, plus 1 supporting file und
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-monokai.html`, plus 1 supporting file und
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1092,13 +1093,23 @@ That writes `compositions/code-snippet-monokai.html`, plus 1 supporting file und
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-monokai" item="code-snippet-monokai" />
```html
That writes `compositions/code-snippet-monokai.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-monokai"
data-composition-src="compositions/code-snippet-monokai.html"
@@ -1110,13 +1121,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1127,3 +1133,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Solarized Light"
description: "VS Code workbench with per-character typing animation in the Solarized Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-solarized-light.json"
compositionId="code-snippet-solarized-light"
compositionSrc="compositions/code-snippet-solarized-light.html"
title="Code Snippet - Solarized Light"
description="VS Code workbench with per-character typing animation in the Solarized Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1070}}
attribution={{"path":"registry/blocks/code-snippet-solarized-light","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-solarized-light/code-snippet-solarized-light.html"
>
<InstallCommand command="npx hyperframes add code-snippet-solarized-light" item="code-snippet-solarized-light" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-solarized-light.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-solarized-light preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-solarized-light.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-solarized-light.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-solarized-light.html">
```html
```html code-snippet-solarized-light.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-solarized-light.html`, plus 1 supporting
<body>
<template id="vscode-solarized-light-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-solarized-light"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-solarized-light.html`, plus 1 supporting
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-solarized-light.html`, plus 1 supporting
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1094,13 +1095,23 @@ That writes `compositions/code-snippet-solarized-light.html`, plus 1 supporting
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-solarized-light" item="code-snippet-solarized-light" />
```html
That writes `compositions/code-snippet-solarized-light.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-solarized-light"
data-composition-src="compositions/code-snippet-solarized-light.html"
@@ -1112,13 +1123,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1129,3 +1135,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Visual Studio Dark"
description: "VS Code workbench with per-character typing animation in the Visual Studio Dark theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-visual-studio-dark.json"
compositionId="code-snippet-visual-studio-dark"
compositionSrc="compositions/code-snippet-visual-studio-dark.html"
title="Code Snippet - Visual Studio Dark"
description="VS Code workbench with per-character typing animation in the Visual Studio Dark theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1056}}
attribution={{"path":"registry/blocks/code-snippet-visual-studio-dark","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-visual-studio-dark/code-snippet-visual-studio-dark.html"
>
<InstallCommand command="npx hyperframes add code-snippet-visual-studio-dark" item="code-snippet-visual-studio-dark" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-visual-studio-dark.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-visual-studio-dark preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-visual-studio-dark.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-visual-studio-dark.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-visual-studio-dark.html">
```html
```html code-snippet-visual-studio-dark.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-visual-studio-dark.html`, plus 1 supporti
<body>
<template id="vscode-visual-studio-dark-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-visual-studio-dark"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-visual-studio-dark.html`, plus 1 supporti
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-visual-studio-dark.html`, plus 1 supporti
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1080,13 +1081,23 @@ That writes `compositions/code-snippet-visual-studio-dark.html`, plus 1 supporti
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-visual-studio-dark" item="code-snippet-visual-studio-dark" />
```html
That writes `compositions/code-snippet-visual-studio-dark.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-visual-studio-dark"
data-composition-src="compositions/code-snippet-visual-studio-dark.html"
@@ -1098,13 +1109,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1115,3 +1121,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,28 +1,28 @@
---
title: "Code Snippet - Visual Studio Light"
description: "VS Code workbench with per-character typing animation in the Visual Studio Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-snippet-visual-studio-light.json"
compositionId="code-snippet-visual-studio-light"
compositionSrc="compositions/code-snippet-visual-studio-light.html"
title="Code Snippet - Visual Studio Light"
description="VS Code workbench with per-character typing animation in the Visual Studio Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar."
variables={[]}
meta={{"duration":11,"width":1920,"height":1080,"category":"Code Snippets","badge":"Stable","codeLines":1056}}
attribution={{"path":"registry/blocks/code-snippet-visual-studio-light","tags":["code","developer","showcase","vscode"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-snippet-visual-studio-light/code-snippet-visual-studio-light.html"
>
<InstallCommand command="npx hyperframes add code-snippet-visual-studio-light" item="code-snippet-visual-studio-light" />
<CatalogSlot slot="code">
That writes `compositions/code-snippet-visual-studio-light.html`, plus 1 supporting file under `assets/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-snippet-visual-studio-light preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-snippet-visual-studio-light.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-visual-studio-light.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-snippet-visual-studio-light.html">
```html
```html code-snippet-visual-studio-light.html
<!doctype html>
<html lang="en">
<head>
@@ -33,6 +33,7 @@ That writes `compositions/code-snippet-visual-studio-light.html`, plus 1 support
<body>
<template id="vscode-visual-studio-light-template">
<div
id="root"
class="vscode-theme-scene"
data-composition-id="vscode-visual-studio-light"
data-width="1920"
@@ -160,7 +161,7 @@ That writes `compositions/code-snippet-visual-studio-light.html`, plus 1 support
sans-serif;
}
.vscode-theme-scene {
#root {
--activity-bg: #181818;
--activity-fg: #d7d7d7;
--activity-muted: #868686;
@@ -201,7 +202,7 @@ That writes `compositions/code-snippet-visual-studio-light.html`, plus 1 support
overflow: hidden;
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.32)),
url("../assets/background.jpeg") center / cover no-repeat;
url("assets/background.jpeg") center / cover no-repeat;
color: var(--editor-fg);
}
@@ -1080,13 +1081,23 @@ That writes `compositions/code-snippet-visual-studio-light.html`, plus 1 support
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-snippet-visual-studio-light" item="code-snippet-visual-studio-light" />
```html
That writes `compositions/code-snippet-visual-studio-light.html`, plus 1 supporting file under `assets/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 11 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-snippet-visual-studio-light"
data-composition-src="compositions/code-snippet-visual-studio-light.html"
@@ -1098,13 +1109,8 @@ After installing, add the block to your host composition:
></div>
```
## Features
- Full VS Code workbench recreation — activity bar, sidebar, explorer tree, editor tabs, breadcrumbs, terminal panel, status bar
- Per-character typing animation with cursor tracking and active line highlight
- Colors sourced from official `microsoft/vscode` built-in theme JSON
- 3D perspective tilt at the end of the typing sequence
- Terminal panel with mock test output
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -1115,3 +1121,7 @@ Tagged `code` `developer` `showcase` `vscode`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Code Typing"
description: "Token-streamed typing reveal with a caret that tracks the frontier — deterministic, no CSS animation."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/code-typing.json"
compositionId="code-typing"
compositionSrc="compositions/code-typing.html"
title="Code Typing"
description="Token-streamed typing reveal with a caret that tracks the frontier — deterministic, no CSS animation."
variables={[]}
meta={{"duration":5,"width":1920,"height":1080,"category":"Code Animations","badge":"Stable","codeLines":763}}
attribution={{"path":"registry/blocks/code-typing","tags":["code","code-animation","typing","developer"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/code-typing/code-typing.html"
>
<InstallCommand command="npx hyperframes add code-typing" item="code-typing" />
<CatalogSlot slot="code">
That writes one file: `compositions/code-typing.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="code-typing preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/code-typing.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-typing.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="code-typing.html">
```html
```html code-typing.html
<!doctype html>
<html lang="en">
<head>
@@ -788,16 +788,37 @@ That writes one file: `compositions/code-typing.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add code-typing" item="code-typing" />
```html
<div data-composition-id="code-typing" data-composition-src="compositions/code-typing.html" data-start="0" data-duration="5" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/code-typing.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 5 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="code-typing"
data-composition-src="compositions/code-typing.html"
data-start="0"
data-duration="5"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `code` `code-animation` `typing` `developer`.
@@ -807,3 +828,7 @@ Tagged `code` `code-animation` `typing` `developer`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+26 -449
View File
@@ -1,24 +1,27 @@
---
title: "Cosmic Orb"
description: "An iridescent soap-bubble planet: a slowly spinning sphere of nebula bands, dust lanes and twinkling stars wrapped in a refracting glass limb with chromatic aberration at the edge."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
import { VariablesExplorer } from "/snippets/variables-explorer.jsx";
## Install
<InstallCommand command="npx hyperframes add cosmic-orb" item="cosmic-orb" />
That writes one file: `compositions/cosmic-orb.html`.
<VariablesExplorer
<CatalogDetail
previewSrc="/public/catalog/blocks/cosmic-orb.json"
compositionId="cosmic-orb"
compositionSrc="compositions/cosmic-orb.html"
title="Cosmic Orb"
description="An iridescent soap-bubble planet: a slowly spinning sphere of nebula bands, dust lanes and twinkling stars wrapped in a refracting glass limb with chromatic aberration at the edge."
variables={[{"id":"hue","type":"number","label":"Nebula hue","default":268,"min":0,"max":360,"step":1,"unit":"deg"},{"id":"accent","type":"number","label":"Accent hue","default":196,"min":0,"max":360,"step":1,"unit":"deg"},{"id":"spin","type":"number","label":"Spin rate","default":0.035,"min":0,"max":0.5,"step":0.005,"unit":"turns/s"},{"id":"stars","type":"number","label":"Star density","default":1,"min":0,"max":2,"step":0.05},{"id":"glow","type":"number","label":"Glow intensity","default":1,"min":0,"max":2,"step":0.05},{"id":"size","type":"number","label":"Orb size","default":0.86,"min":0.2,"max":1.2,"step":0.01},{"id":"pulse","type":"number","label":"Beat pulse","default":0,"min":0,"max":1,"step":0.01},{"id":"pulseEnvelope","type":"string","label":"Beat pulse envelope (comma-separated 0-1 samples)","default":"","placeholder":"0,0.2,1,0.4,0"},{"id":"backdrop","type":"color","label":"Backdrop","default":"#04040a"}]}
meta={{"duration":10,"width":1920,"height":1080,"category":"Showcases","badge":"Experimental","codeLines":431}}
attribution={{"path":"registry/blocks/cosmic-orb","tags":["webgl","shader","background","showcase","3d"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/cosmic-orb/cosmic-orb.html"
>
<CatalogSlot slot="code">
```html cosmic-orb.html
<!doctype html>
<html lang="en">
@@ -453,7 +456,17 @@ That writes one file: `compositions/cosmic-orb.html`.
</html>
```
</VariablesExplorer>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add cosmic-orb" item="cosmic-orb" />
That writes one file: `compositions/cosmic-orb.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
@@ -516,446 +529,6 @@ defaults, so this behaves exactly like the preview above until you change one:
></div>
```
## Source
<Accordion title="cosmic-orb.html">
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Cosmic Orb</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
overflow: hidden;
}
#co-root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
}
#co-backdrop {
position: absolute;
inset: 0;
background: #04040a;
}
#co-canvas {
position: absolute;
top: 0;
left: 0;
width: 1920px;
height: 1080px;
}
</style>
</head>
<body>
<div
id="co-root"
data-composition-id="cosmic-orb"
data-root="true"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="10"
data-composition-variables='[
{"id":"hue","type":"number","label":"Nebula hue","default":268,"min":0,"max":360,"step":1,"unit":"deg"},
{"id":"accent","type":"number","label":"Accent hue","default":196,"min":0,"max":360,"step":1,"unit":"deg"},
{"id":"spin","type":"number","label":"Spin rate","default":0.035,"min":0,"max":0.5,"step":0.005,"unit":"turns/s"},
{"id":"stars","type":"number","label":"Star density","default":1,"min":0,"max":2,"step":0.05},
{"id":"glow","type":"number","label":"Glow intensity","default":1,"min":0,"max":2,"step":0.05},
{"id":"size","type":"number","label":"Orb size","default":0.86,"min":0.2,"max":1.2,"step":0.01},
{"id":"pulse","type":"number","label":"Beat pulse","default":0,"min":0,"max":1,"step":0.01},
{"id":"pulseEnvelope","type":"string","label":"Beat pulse envelope (comma-separated 0-1 samples)","default":"","placeholder":"0,0.2,1,0.4,0"},
{"id":"backdrop","type":"color","label":"Backdrop","default":"#04040a"}
]'
>
<div id="co-backdrop"></div>
<canvas id="co-canvas" width="1920" height="1080"></canvas>
<!-- Driver clip: gives HyperFrames a timed element to own on track 0. -->
<div
id="co-drv"
class="clip"
data-start="0"
data-duration="10"
data-track-index="0"
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
></div>
</div>
<script>
(function () {
var DUR = 10;
var W = 1920;
var H = 1080;
var ROOT = document.getElementById("co-root");
var CS = getComputedStyle(ROOT);
// `data-composition-variables` is the single owner of every declared
// default — parse it rather than repeating each default a second time
// in JS. `window.__hyperframes` is NOT guaranteed to exist yet when this
// inline script runs, so it can only ever be an override, never the
// source. Precedence: host CSS custom property, runtime variable bag,
// declared default.
var DECL = {};
JSON.parse(ROOT.getAttribute("data-composition-variables") || "[]").forEach(function (v) {
DECL[v.id] = v.default;
});
var HF = window.__hyperframes;
var V = (HF && HF.getVariables && HF.getVariables()) || {};
function raw(id) {
var css = CS.getPropertyValue("--" + id.toLowerCase()).trim();
if (css !== "") return css;
if (V[id] !== undefined && V[id] !== "") return V[id];
return DECL[id];
}
function num(id) {
var n = parseFloat(raw(id));
return isFinite(n) ? n : 0;
}
function str(id) {
var s = raw(id);
return typeof s === "string" ? s.trim() : "";
}
var HUE = num("hue");
var ACCENT = num("accent");
var SPIN = num("spin");
var STARS = num("stars");
var GLOW = num("glow");
var SIZE = num("size");
var PULSE = num("pulse");
var BACKDROP = str("backdrop");
document.getElementById("co-backdrop").style.background = BACKDROP;
// Beat hook. A precomputed envelope (comma-separated 0-1 samples spread
// evenly across the clip) is sampled closed-form at time t — no audio
// analysis, no accumulator. Empty envelope => the flat `pulse` value.
var ENV = str("pulseEnvelope")
.split(",")
.map(function (s) {
return parseFloat(s);
})
.filter(function (n) {
return isFinite(n);
});
function pulseAt(t) {
if (ENV.length === 0) return PULSE;
if (ENV.length === 1) return ENV[0];
var x = Math.min(1, Math.max(0, t / DUR)) * (ENV.length - 1);
var i = Math.floor(x);
var j = Math.min(i + 1, ENV.length - 1);
return ENV[i] + (ENV[j] - ENV[i]) * (x - i);
}
var canvas = document.getElementById("co-canvas");
var gl =
canvas.getContext("webgl", {
alpha: true,
antialias: false,
depth: false,
stencil: false,
preserveDrawingBuffer: true,
powerPreference: "high-performance",
}) ||
canvas.getContext("experimental-webgl", {
alpha: true,
preserveDrawingBuffer: true,
});
var VERT = [
"attribute vec2 aPos;",
"void main() { gl_Position = vec4(aPos, 0.0, 1.0); }",
].join("\n");
var FRAG = [
"precision highp float;",
"uniform vec2 uRes;",
"uniform float uTime;",
"uniform float uRadius;",
"uniform float uSpin;",
"uniform float uHue;",
"uniform float uAccent;",
"uniform float uStars;",
"uniform float uGlow;",
"uniform float uPulse;",
"const float TAU = 6.28318531;",
"",
"// One cheap hash for every random-looking thing in here.",
"float h1(float x) { return fract(sin(x * 127.1) * 43758.5453); }",
"float h2(vec2 p) { return h1(dot(p, vec2(1.0, 157.31))); }",
"",
"vec3 hue2rgb(float h) {",
" return clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);",
"}",
"",
"vec3 rotX(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(p.x, c * p.y - s * p.z, s * p.y + c * p.z); }",
"vec3 rotY(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z); }",
"vec3 rotZ(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(c * p.x - s * p.y, s * p.x + c * p.y, p.z); }",
"",
"vec3 safeDir(vec3 v) {",
" float l = dot(v, v);",
" if (l < 1e-6) return vec3(0.0, 0.0, -1.0);",
" return v * inversesqrt(l);",
"}",
"",
"// Fixed axial tilt, then spin about the tilted polar axis.",
"vec3 toBody(vec3 n, float spin) { return rotY(rotZ(rotX(n, -0.34), 0.42), -spin); }",
"vec2 lonlat(vec3 d) { return vec2(atan(d.x, d.z), asin(clamp(d.y, -1.0, 1.0))); }",
"",
"float dens(float base) { return clamp(1.0 - (1.0 - base) * uStars, 0.0, 1.0); }",
"",
"// One star size-class: cell grid, seeded jitter, soft round core.",
"float starField(vec2 q, float freq, float base, float rad) {",
" vec2 g = q * freq;",
" vec2 id = floor(g);",
" float h = h2(id);",
" if (h < dens(base)) return 0.0;",
" vec2 c = vec2(h2(id + vec2(3.7, 1.3)), h2(id + vec2(9.1, 5.5)));",
" float d = length(fract(g) - c);",
" float core = smoothstep(rad, 0.0, d);",
" // Twinkle: a per-star phase driven straight off uTime, no accumulator.",
" float tw = 0.62 + 0.38 * sin(uTime * 2.4 + h * 53.7);",
" return core * core * (0.45 + 0.55 * h1(h * 91.7)) * tw;",
"}",
"",
"// 4-point diffraction glare, only worth paying for on the brightest class.",
"float starGlare(vec2 q, float freq, float base) {",
" vec2 g = q * freq;",
" vec2 id = floor(g);",
" float h = h2(id);",
" if (h < dens(base)) return 0.0;",
" vec2 c = vec2(h2(id + vec2(3.7, 1.3)), h2(id + vec2(9.1, 5.5)));",
" vec2 dv = abs(fract(g) - c);",
" float hx = exp(-dv.x * 70.0) * exp(-dv.y * 9.0);",
" float hy = exp(-dv.y * 70.0) * exp(-dv.x * 9.0);",
" return max(hx, hy) * 0.45;",
"}",
"",
"// Three size-classes at ~6 / 11 / 19 cell frequency.",
"float starsAll(vec2 q, float gain) {",
" float s = starField(q, 6.0, 0.958, 0.20);",
" s += starGlare(q, 6.0, 0.958);",
" s += starField(q, 11.0, 0.920, 0.18) * 0.95;",
" s += starField(q, 19.0, 0.865, 0.17) * 0.75;",
" return s * gain;",
"}",
"",
"vec3 nebula(vec2 q) {",
" // Two sin-field turbulence layers.",
" float t1 = sin(q.x * 3.1 + sin(q.y * 2.7) * 1.9) * sin(q.y * 4.3 + sin(q.x * 1.7) * 2.3);",
" float t2 = sin(q.x * 7.9 + sin(q.y * 5.1) * 1.3) * sin(q.y * 9.7 + sin(q.x * 4.4) * 1.1);",
" float turb = t1 * 0.66 + t2 * 0.34;",
"",
" // Galaxy bands, warped by the turbulence.",
" float band = sin(q.y * 3.2 + turb * 1.7 + q.x * 0.55);",
" float bands = pow(max(0.0, band * 0.5 + 0.5), 3.2);",
"",
" // Dust lanes: broad darkened swathes, not a thin curve.",
" float lane = sin(q.y * 2.9 + q.x * 1.3 + t1 * 1.6);",
" float dust = 0.22 + 0.78 * smoothstep(-0.45, 0.75, lane);",
"",
" // Core bulge.",
" float dc = length(vec2((q.x - 0.55) * 0.5, (q.y + 0.12) * 1.1));",
" float core = exp(-dc * dc * 2.6);",
"",
" // Two pocket glows.",
" float dA = length(vec2((q.x + 1.95) * 0.62, (q.y - 0.52) * 1.45));",
" float dB = length(vec2((q.x - 2.35) * 0.55, (q.y + 0.78) * 1.30));",
" float pA = exp(-dA * dA * 4.0);",
" float pB = exp(-dB * dB * 5.0);",
"",
" vec3 cNeb = hue2rgb(uHue / 360.0);",
" vec3 cAcc = hue2rgb(uAccent / 360.0);",
" vec3 cWarm = hue2rgb(uHue / 360.0 + 0.07);",
"",
" vec3 col = mix(cNeb, cWarm, 0.5 + 0.5 * turb) * bands * dust * 0.17;",
" col += mix(cNeb, vec3(1.0), 0.50) * core * dust * 0.13;",
" col += mix(cAcc, vec3(1.0), 0.60) * pA * 0.30 * uGlow;",
" col += mix(cNeb, vec3(1.0), 0.50) * pB * 0.24 * uGlow;",
" return col;",
"}",
"",
"void main() {",
" vec2 p = (gl_FragCoord.xy - uRes * 0.5) / uRadius;",
" float r = length(p);",
" float aa = 1.4 / uRadius;",
" float spin = TAU * uSpin * uTime;",
"",
" vec3 cNeb = hue2rgb(uHue / 360.0);",
" vec3 cAcc = hue2rgb(uAccent / 360.0);",
"",
" float mask = smoothstep(1.0 + aa, 1.0 - aa, r);",
" float halo = exp(-max(0.0, r - 1.0) * 13.0) * (1.0 - mask);",
"",
" vec3 body = vec3(0.0);",
" if (mask > 0.001) {",
" float rc = min(r, 1.0);",
" // Analytic sphere: the normal falls straight out of z = sqrt(1 - r^2).",
" float z = sqrt(max(0.0, 1.0 - rc * rc));",
" vec3 N = vec3(p.x, p.y, z);",
" vec2 q = lonlat(toBody(safeDir(N), spin));",
"",
" float gain = 0.9 + 2.6 * uPulse;",
" body = nebula(q);",
" body += vec3(1.0) * starsAll(q, gain) * (0.95 + 0.15 * uGlow);",
"",
" // Limb darkening.",
" body *= mix(0.32, 1.0, pow(z, 0.5));",
"",
" // Refract-sampled back layer, split per channel: the chromatic",
" // aberration that reads as glass at the limb.",
" vec3 I = vec3(0.0, 0.0, -1.0);",
" vec3 dR = safeDir(refract(I, N, 1.0 / 1.09));",
" vec3 dG = safeDir(refract(I, N, 1.0 / 1.13));",
" vec3 dB = safeDir(refract(I, N, 1.0 / 1.17));",
" vec2 qR = lonlat(toBody(dR, spin));",
" vec2 qG = lonlat(toBody(dG, spin));",
" vec2 qB = lonlat(toBody(dB, spin));",
" vec3 back = vec3(starsAll(qR, gain), starsAll(qG, gain), starsAll(qB, gain));",
" float fres = pow(1.0 - z, 1.8);",
" body += back * fres * 2.2;",
" body += nebula(qG) * fres * 0.8;",
"",
" // Aurora sheen riding the glass shell.",
" float ang = atan(p.y, p.x);",
" float sheen = smoothstep(0.72, 0.99, r) * smoothstep(1.0, 0.90, r);",
" float w = 0.5 + 0.5 * sin(ang * 2.0 + spin * 1.6);",
" body += (mix(cNeb, cAcc, w) * 0.55 + vec3(0.16)) * sheen * 0.30 * uGlow;",
"",
" // Three speculars, fixed in screen space: the key light does not spin.",
" vec2 a1 = p - vec2(-0.34, 0.62);",
" vec2 a2 = p - vec2(0.58, 0.60);",
" vec2 a3 = p - vec2(0.24, -0.80);",
" float sp = exp(-dot(a1, a1) * 30.0) * 0.30;",
" sp += exp(-dot(a2, a2) * 150.0) * 0.80;",
" sp += exp(-dot(a3, a3) * 70.0) * 0.30;",
" body += vec3(1.0) * sp * (0.30 + 0.70 * pow(1.0 - z, 1.5)) * uGlow;",
"",
" // Fresnel void glow hugging the inside of the rim.",
" body += mix(cAcc, vec3(1.0), 0.4) * pow(1.0 - z, 6.0) * 0.30 * uGlow;",
" }",
"",
" vec3 haloCol = mix(cNeb, cAcc, 0.35);",
" vec3 outCol = body * mask + haloCol * halo * 0.22 * uGlow;",
" float outA = clamp(mask + halo * 0.16 * uGlow, 0.0, 1.0);",
" gl_FragColor = vec4(clamp(outCol, 0.0, 4.0), outA);",
"}",
].join("\n");
var uni = {};
var ready = false;
function compile(type, src) {
var sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
throw new Error("cosmic-orb shader: " + gl.getShaderInfoLog(sh));
}
return sh;
}
if (gl) {
var prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
throw new Error("cosmic-orb link: " + gl.getProgramInfoLog(prog));
}
gl.useProgram(prog);
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
var loc = gl.getAttribLocation(prog, "aPos");
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
[
"uRes",
"uTime",
"uRadius",
"uSpin",
"uHue",
"uAccent",
"uStars",
"uGlow",
"uPulse",
].forEach(function (n) {
uni[n] = gl.getUniformLocation(prog, n);
});
gl.viewport(0, 0, W, H);
gl.uniform2f(uni.uRes, W, H);
gl.uniform1f(uni.uRadius, (SIZE * H) / 2);
gl.uniform1f(uni.uSpin, SPIN);
gl.uniform1f(uni.uHue, HUE);
gl.uniform1f(uni.uAccent, ACCENT);
gl.uniform1f(uni.uStars, STARS);
gl.uniform1f(uni.uGlow, GLOW);
ready = true;
}
// Every frame is computed from t alone: spin is spin_rate * t, the
// pulse is sampled from the envelope at t. No accumulators, no clocks.
function draw(t) {
if (!ready) return;
gl.uniform1f(uni.uTime, t);
gl.uniform1f(uni.uPulse, pulseAt(t));
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.flush();
}
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// The canvas is repainted from a property SETTER, not from onUpdate:
// gsap's seek(t) suppresses events by default, so an onUpdate callback
// silently never fires on a scrub and the canvas freezes on frame 0.
// Tweened values are always written during render, suppressed or not,
// so this fires on every seek — and hands us the frame time directly.
var driver = { _t: 0 };
Object.defineProperty(driver, "t", {
get: function () {
return this._t;
},
set: function (v) {
this._t = v;
draw(v);
},
});
tl.to(driver, { t: DUR, duration: DUR, ease: "none" }, 0);
window.__timelines["cosmic-orb"] = tl;
draw(0);
})();
</script>
</body>
</html>
```
</Accordion>
{/* hf:generated-footer */}
Tagged `webgl` `shader` `background` `showcase` `3d`.
@@ -965,3 +538,7 @@ Tagged `webgl` `shader` `background` `showcase` `3d`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Cross Warp Morph"
description: "Shader transition with cross-warped morphing"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/cross-warp-morph.json"
compositionId="cross-warp-morph"
compositionSrc="compositions/cross-warp-morph.html"
title="Cross Warp Morph"
description="Shader transition with cross-warped morphing"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Shader Transitions","badge":"Stable","codeLines":377}}
attribution={{"path":"registry/blocks/cross-warp-morph","tags":["transition","shader"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/cross-warp-morph/cross-warp-morph.html"
>
<InstallCommand command="npx hyperframes add cross-warp-morph" item="cross-warp-morph" />
<CatalogSlot slot="code">
That writes one file: `compositions/cross-warp-morph.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="cross-warp-morph preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/cross-warp-morph.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/cross-warp-morph.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="cross-warp-morph.html">
```html
```html cross-warp-morph.html
<!doctype html>
<html lang="en">
<head>
@@ -402,16 +402,37 @@ That writes one file: `compositions/cross-warp-morph.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add cross-warp-morph" item="cross-warp-morph" />
```html
<div data-composition-id="cross-warp-morph" data-composition-src="compositions/cross-warp-morph.html" data-start="0" data-duration="4" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/cross-warp-morph.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="cross-warp-morph"
data-composition-src="compositions/cross-warp-morph.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `transition` `shader`.
@@ -421,3 +442,7 @@ Tagged `transition` `shader`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+944
View File
@@ -0,0 +1,944 @@
---
title: "Cuboid Carousel"
description: "A chain of bevelled cuboids rides a travelling wave as a content carousel."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
<CatalogDetail
previewSrc="/public/catalog/blocks/cuboid-carousel.json"
compositionId="cuboid-carousel"
compositionSrc="compositions/cuboid-carousel/cuboid-carousel.html"
title="Cuboid Carousel"
description="A chain of bevelled cuboids rides a travelling wave as a content carousel."
variables={[{"id":"cardsJson","type":"string","label":"Content - Cards JSON (optional, see CARDS in source)","default":""},{"id":"heroCard","type":"number","label":"Content - Hero card (1-based)","default":4,"min":1,"max":12,"step":1},{"id":"count","type":"number","label":"Content - Repeating card count","default":8,"min":1,"max":12,"step":1},{"id":"gap","type":"number","label":"Layout - Extra gap between cuboids","default":0.01,"min":0,"max":1.5,"step":0.005},{"id":"cuboidWidth","type":"number","label":"Cuboid - Width","default":1.7,"min":0.3,"max":5,"step":0.01},{"id":"cuboidHeight","type":"number","label":"Cuboid - Height","default":2.4,"min":0.3,"max":6,"step":0.01},{"id":"cuboidDepth","type":"number","label":"Cuboid - Thickness (depth)","default":0.14,"min":0.02,"max":2,"step":0.01},{"id":"cornerRadius","type":"number","label":"Cuboid - Corner radius","default":0.105,"min":0,"max":0.6,"step":0.005},{"id":"bevelSize","type":"number","label":"Cuboid - Bevel size (edge chamfer)","default":0.03,"min":0,"max":0.3,"step":0.001},{"id":"bevelThickness","type":"number","label":"Cuboid - Bevel thickness","default":0.04,"min":0,"max":0.3,"step":0.001},{"id":"smoothness","type":"number","label":"Cuboid - Smoothness (segments)","default":32,"min":1,"max":32,"step":1},{"id":"bodyColor","type":"color","label":"Cuboid - Body colour","default":"#242424"},{"id":"roughness","type":"number","label":"Cuboid - Roughness","default":0.6,"min":0,"max":1,"step":0.01},{"id":"metalness","type":"number","label":"Cuboid - Metalness","default":0.4,"min":0,"max":1,"step":0.01},{"id":"heroScale","type":"number","label":"Hero - Isolated scale","default":1,"min":1,"max":1.6,"step":0.01},{"id":"heroFillIntensity","type":"number","label":"Hero - Face light intensity","default":0.55,"min":0,"max":2,"step":0.05,"description":"A soft fill on the detached face keeps its text readable without changing the original chain lighting."},{"id":"heroExitX","type":"number","label":"Hero - Exit X rotation degrees","default":288,"min":-720,"max":720,"step":1},{"id":"heroExitY","type":"number","label":"Hero - Exit Y rotation degrees","default":-234,"min":-720,"max":720,"step":1},{"id":"heroExitZ","type":"number","label":"Hero - Exit Z rotation degrees","default":198,"min":-720,"max":720,"step":1},{"id":"ambientIntensity","type":"number","label":"Lights - Ambient intensity","default":0.35,"min":0,"max":4,"step":0.01},{"id":"ambientColor","type":"color","label":"Lights - Ambient colour","default":"#ffffff"},{"id":"topIntensity","type":"number","label":"Lights - Top rig intensity","default":3.1,"min":0,"max":12,"step":0.05},{"id":"topColor","type":"color","label":"Lights - Top rig colour","default":"#ffffff"},{"id":"bottomIntensity","type":"number","label":"Lights - Bottom rig intensity","default":2.15,"min":0,"max":12,"step":0.05},{"id":"bottomColor","type":"color","label":"Lights - Bottom rig colour","default":"#ffcaad"},{"id":"keyIntensity","type":"number","label":"Lights - Key rig intensity","default":5,"min":0,"max":12,"step":0.05},{"id":"keyColor","type":"color","label":"Lights - Key rig colour","default":"#ffe9d6"},{"id":"shadowRadius","type":"number","label":"Shadows - Softness at map size 2048","default":44.5,"min":0,"max":50,"step":0.5},{"id":"shadowMapSize","type":"number","label":"Shadows - Map size","default":2048,"min":512,"max":4096,"step":256},{"id":"backdrop","type":"color","label":"Background - Colour","default":"#0b0d13"},{"id":"cameraFov","type":"number","label":"Camera - Initial Field of view","default":11,"min":10,"max":90,"step":0.5},{"id":"cameraX","type":"number","label":"Camera - Initial X","default":-1.65,"min":-20,"max":20,"step":0.05},{"id":"cameraY","type":"number","label":"Camera - Initial Y","default":8.5,"min":-20,"max":20,"step":0.05},{"id":"cameraZ","type":"number","label":"Camera - Initial Z (distance)","default":-8.508708,"min":-20,"max":50,"step":0.1},{"id":"cameraSettleFrame","type":"number","label":"Camera - Settle frame (30 fps)","default":70,"min":20,"max":90,"step":1,"description":"Fast-start ease-out along the saved route. Approved value: 70."},{"id":"handheldStrength","type":"number","label":"Camera - Handheld hold strength","default":1,"min":0,"max":1,"step":0.05,"description":"Camera-only motion during frames 90–150; 0 disables it, 1 matches Blender."},{"id":"cameraTargetFov","type":"number","label":"Camera target - Field of view","default":34,"min":10,"max":90,"step":0.5},{"id":"cameraTargetX","type":"number","label":"Camera target - X","default":-7.25,"min":-20,"max":20,"step":0.05},{"id":"cameraTargetY","type":"number","label":"Camera target - Y","default":1.15,"min":-20,"max":20,"step":0.05},{"id":"cameraTargetZ","type":"number","label":"Camera target - Z","default":6.2,"min":2,"max":50,"step":0.1}]}
meta={{"duration":6.67,"width":1920,"height":1080,"category":"3D motion","badge":"Stable","codeLines":873}}
attribution={{"path":"registry/blocks/cuboid-carousel","tags":["3d-motion","carousel","cards","travelling-wave","pbr","shadows","room-environment"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/cuboid-carousel/cuboid-carousel.html"
>
<CatalogSlot slot="code">
```html cuboid-carousel.html
<!doctype html>
<html
lang="en"
data-composition-variables='[{"id":"cardsJson","type":"string","label":"Content - Cards JSON (optional, see CARDS in source)","default":""},{"id":"heroCard","type":"number","label":"Content - Hero card (1-based)","default":4,"min":1,"max":12,"step":1},{"id":"count","type":"number","label":"Content - Repeating card count","default":8,"min":1,"max":12,"step":1},{"id":"gap","type":"number","label":"Layout - Extra gap between cuboids","default":0.01,"min":0,"max":1.5,"step":0.005},{"id":"cuboidWidth","type":"number","label":"Cuboid - Width","default":1.7,"min":0.3,"max":5,"step":0.01},{"id":"cuboidHeight","type":"number","label":"Cuboid - Height","default":2.4,"min":0.3,"max":6,"step":0.01},{"id":"cuboidDepth","type":"number","label":"Cuboid - Thickness (depth)","default":0.14,"min":0.02,"max":2,"step":0.01},{"id":"cornerRadius","type":"number","label":"Cuboid - Corner radius","default":0.105,"min":0,"max":0.6,"step":0.005},{"id":"bevelSize","type":"number","label":"Cuboid - Bevel size (edge chamfer)","default":0.03,"min":0,"max":0.3,"step":0.001},{"id":"bevelThickness","type":"number","label":"Cuboid - Bevel thickness","default":0.04,"min":0,"max":0.3,"step":0.001},{"id":"smoothness","type":"number","label":"Cuboid - Smoothness (segments)","default":32,"min":1,"max":32,"step":1},{"id":"bodyColor","type":"color","label":"Cuboid - Body colour","default":"#242424"},{"id":"roughness","type":"number","label":"Cuboid - Roughness","default":0.6,"min":0,"max":1,"step":0.01},{"id":"metalness","type":"number","label":"Cuboid - Metalness","default":0.4,"min":0,"max":1,"step":0.01},{"id":"heroScale","type":"number","label":"Hero - Isolated scale","default":1,"min":1,"max":1.6,"step":0.01},{"id":"heroFillIntensity","type":"number","label":"Hero - Face light intensity","default":0.55,"min":0,"max":2,"step":0.05,"description":"A soft fill on the detached face keeps its text readable without changing the original chain lighting."},{"id":"heroExitX","type":"number","label":"Hero - Exit X rotation degrees","default":288,"min":-720,"max":720,"step":1},{"id":"heroExitY","type":"number","label":"Hero - Exit Y rotation degrees","default":-234,"min":-720,"max":720,"step":1},{"id":"heroExitZ","type":"number","label":"Hero - Exit Z rotation degrees","default":198,"min":-720,"max":720,"step":1},{"id":"ambientIntensity","type":"number","label":"Lights - Ambient intensity","default":0.35,"min":0,"max":4,"step":0.01},{"id":"ambientColor","type":"color","label":"Lights - Ambient colour","default":"#ffffff"},{"id":"topIntensity","type":"number","label":"Lights - Top rig intensity","default":3.1,"min":0,"max":12,"step":0.05},{"id":"topColor","type":"color","label":"Lights - Top rig colour","default":"#ffffff"},{"id":"bottomIntensity","type":"number","label":"Lights - Bottom rig intensity","default":2.15,"min":0,"max":12,"step":0.05},{"id":"bottomColor","type":"color","label":"Lights - Bottom rig colour","default":"#ffcaad"},{"id":"keyIntensity","type":"number","label":"Lights - Key rig intensity","default":5,"min":0,"max":12,"step":0.05},{"id":"keyColor","type":"color","label":"Lights - Key rig colour","default":"#ffe9d6"},{"id":"shadowRadius","type":"number","label":"Shadows - Softness at map size 2048","default":44.5,"min":0,"max":50,"step":0.5},{"id":"shadowMapSize","type":"number","label":"Shadows - Map size","default":2048,"min":512,"max":4096,"step":256},{"id":"backdrop","type":"color","label":"Background - Colour","default":"#0b0d13"},{"id":"cameraFov","type":"number","label":"Camera - Initial Field of view","default":11,"min":10,"max":90,"step":0.5},{"id":"cameraX","type":"number","label":"Camera - Initial X","default":-1.65,"min":-20,"max":20,"step":0.05},{"id":"cameraY","type":"number","label":"Camera - Initial Y","default":8.5,"min":-20,"max":20,"step":0.05},{"id":"cameraZ","type":"number","label":"Camera - Initial Z (distance)","default":-8.508708,"min":-20,"max":50,"step":0.1},{"id":"cameraSettleFrame","type":"number","label":"Camera - Settle frame (30 fps)","default":70,"min":20,"max":90,"step":1,"description":"Fast-start ease-out along the saved route. Approved value: 70."},{"id":"handheldStrength","type":"number","label":"Camera - Handheld hold strength","default":1,"min":0,"max":1,"step":0.05,"description":"Camera-only motion during frames 90–150; 0 disables it, 1 matches Blender."},{"id":"cameraTargetFov","type":"number","label":"Camera target - Field of view","default":34,"min":10,"max":90,"step":0.5},{"id":"cameraTargetX","type":"number","label":"Camera target - X","default":-7.25,"min":-20,"max":20,"step":0.05},{"id":"cameraTargetY","type":"number","label":"Camera target - Y","default":1.15,"min":-20,"max":20,"step":0.05},{"id":"cameraTargetZ","type":"number","label":"Camera target - Z","default":6.2,"min":2,"max":50,"step":0.1}]'
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>cuboid-carousel</title>
<script src="assets/gsap-3.14.2.min.js"></script>
<script type="importmap">
{
"imports": {
"three": "./assets/three.module.min.js",
"three/addons/": "./assets/addons/"
}
}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap"
rel="stylesheet"
/>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#cc-root {
position: relative;
width: 1920px;
height: 1080px;
background: #0b0d13;
overflow: hidden;
}
#cc-bg {
position: absolute;
inset: 0;
z-index: 0;
background: #0b0d13;
}
#cc-stage {
position: relative;
z-index: 1;
}
#cc-canvas {
display: block;
width: 1920px;
height: 1080px;
}
#cc-fontload {
position: absolute;
opacity: 0;
pointer-events: none;
font-family: "Inter", sans-serif;
}
</style>
</head>
<body>
<div
data-hf-id="hf-im3h"
id="cc-root"
data-composition-id="cuboid-carousel"
data-start="0"
data-duration="6.666666666666667"
data-width="1920"
data-height="1080"
>
<div data-hf-id="hf-y4po" id="cc-bg" aria-hidden="true"></div>
<section
data-hf-id="hf-xpi6"
id="cc-stage"
class="clip"
data-start="0"
data-duration="6.666666666666667"
data-track-index="1"
></section>
<div data-hf-id="hf-0e46" id="cc-fontload" aria-hidden="true">
<span data-hf-id="hf-kqtg" style="font-weight: 400">Aa</span
><span data-hf-id="hf-grhl" style="font-weight: 500">Aa</span>
</div>
</div>
<script>
/* Studio's variables parser fails on any composition with a <canvas> in
its MARKUP - created at runtime instead (identical for render). */
(function () {
var c = document.createElement("canvas");
c.id = "cc-canvas";
c.width = 1920;
c.height = 1080;
document.getElementById("cc-stage").appendChild(c);
})();
</script>
<script type="module">
/* Approved Blender choreography, 200 frames at 30 fps. Card geometry,
content textures and lighting remain the existing live block. Motion
uses saved native card curves and evaluated camera/handheld samples.
Frame 31: breakaway; 90–150: locked hero; 150–190: camera fly-through. */
import * as THREE from "three";
import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
import { mergeVertices } from "three/addons/utils/BufferGeometryUtils.js";
import { MOTION } from "./assets/cuboid-motion.js";
const DUR = MOTION.endFrame / MOTION.fps;
const V =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
// ---- variable readers (every one clamped to the DialKit range) ----
const num = (v, d, lo, hi) => {
const n = Number(v);
return isFinite(n) ? Math.min(hi, Math.max(lo, n)) : d;
};
const col = (v, d) =>
typeof v === "string" && /^#[0-9a-fA-F]{3,8}$/.test(v.trim()) ? v.trim() : d;
const bool = (v, d) =>
typeof v === "boolean" ? v : v === "true" ? true : v === "false" ? false : d;
const pick = (v, opts, d) => (opts.indexOf(String(v)) >= 0 ? String(v) : d);
// cuboid
const WIDTH = num(V.cuboidWidth, 1.7, 0.3, 5);
const HEIGHT = num(V.cuboidHeight, 2.4, 0.3, 6);
const DEPTH = num(V.cuboidDepth, 0.16, 0.02, 2);
const CORNER_R = num(V.cornerRadius, 0.105, 0, 0.6);
const BEVEL_SIZE = num(V.bevelSize, 0.03, 0, 0.3);
const BEVEL_THICK = num(V.bevelThickness, 0.04, 0, 0.3);
const SMOOTH = Math.round(num(V.smoothness, 32, 1, 32));
const ROUGH = num(V.roughness, 0.6, 0, 1);
const METAL = num(V.metalness, 0.4, 0, 1);
const BODY = col(V.bodyColor, "#242424");
// layout
const COUNT = Math.round(num(V.count, 8, 1, 12));
const GAP = num(V.gap, 0.01, 0, 1.5);
// rotation
const AXIS = "x";
// hero
const HERO_SCALE = num(V.heroScale, 1, 1, 1.6);
// lights
const AMB_I = num(V.ambientIntensity, 0.35, 0, 4);
const AMB_C = col(V.ambientColor, "#ffffff");
const TOP_I = num(V.topIntensity, 3.1, 0, 12);
const TOP_C = col(V.topColor, "#ffffff");
const BOT_I = num(V.bottomIntensity, 2.15, 0, 12);
const BOT_C = col(V.bottomColor, "#ffcaad");
const KEY_I = num(V.keyIntensity, 5, 0, 12);
const KEY_C = col(V.keyColor, "#ffe9d6");
// shadows
const SHADOW_RADIUS = num(V.shadowRadius, 44.5, 0, 50);
const MAP_SIZE = Math.max(
512,
Math.min(4096, Math.round(num(V.shadowMapSize, 2048, 512, 4096) / 256) * 256),
);
// flat page background
const BACKDROP = col(V.backdrop, "#0b0d13");
// camera
const FOV = num(V.cameraFov, 65, 10, 90);
const CAM_X = num(V.cameraX, -0.010523080825805664, -20, 20);
const CAM_Y = num(V.cameraY, 0.5195326805114746, -20, 20);
const CAM_Z = num(V.cameraZ, -8.508708000183105, -20, 50);
const TARGET_FOV = num(V.cameraTargetFov, 34, 10, 90);
const TARGET_X = num(V.cameraTargetX, -7.25, -20, 20);
const TARGET_Y = num(V.cameraTargetY, 1.15, -20, 20);
const TARGET_Z = num(V.cameraTargetZ, 6.2, 2, 50);
/* ---- derived spacing: the no-overlap floor (see header) ---- */
const sweepHalf =
AXIS === "x"
? WIDTH / 2
: AXIS === "y"
? Math.hypot(WIDTH, DEPTH) / 2
: Math.hypot(WIDTH, HEIGHT) / 2;
const zSweepHalf =
AXIS === "x"
? Math.hypot(HEIGHT, DEPTH) / 2
: AXIS === "y"
? Math.hypot(WIDTH, DEPTH) / 2
: DEPTH / 2;
const SPACING = Math.max(WIDTH, 2 * sweepHalf + 0.02) + GAP;
const HALF_EXTENT = sweepHalf * HERO_SCALE;
/* ---- viewport maths (ported from viewportHalfWidth) ---- */
const ASPECT = 1920 / 1080;
const vFov = (FOV * Math.PI) / 180;
const hFov = 2 * Math.atan(Math.tan(vFov / 2) * ASPECT);
const VIEW_HALF = CAM_Z * Math.tan(hFov / 2);
/* Off-screen test. A cuboid reaches back to z = -zSweepHalf*heroScale,
where the frustum is WIDER than at z = 0, so culling on VIEW_HALF
alone would clip a corner that is still in shot. Measure at the
deepest point the geometry can reach. */
const EDGE =
(CAM_Z + zSweepHalf * HERO_SCALE) * Math.tan(hFov / 2) + Math.abs(CAM_X) + HALF_EXTENT; // |x| > EDGE = off-screen
/* ================= CONTENT - edit here =================
Each: { color, category, title, pills: [1-2 strings] }. The same array
can be supplied at render/preview time as JSON via the `cardsJson`
variable; anything invalid falls back to this list. */
const CARDS = [
{
color: "#fe552e",
category: "Creative requests",
title: "Identify key resources",
pills: ["In progress", "Jul 17, 2026"],
},
{
color: "#d98a1f",
category: "Creative requests",
title: "Develop concept art",
pills: ["In review", "Jul 20, 2026"],
},
{
color: "#4f6ef0",
category: "Product Development",
title: "Create Design Prototypes",
pills: ["In progress", "Aug 02, 2026"],
},
{
color: "#1f9d57",
category: "Project Management",
title: "Draft Project Timeline",
pills: ["Planned", "Aug 09, 2026"],
},
{
color: "#c026d3",
category: "Research",
title: "Run user interviews",
pills: ["Planned", "Aug 15, 2026"],
},
{
color: "#0891b2",
category: "Product Development",
title: "Ship the beta",
pills: ["Blocked", "Aug 22, 2026"],
},
{
color: "#e11d48",
category: "Creative requests",
title: "Cut the launch film",
pills: ["Planned", "Sep 01, 2026"],
},
{
color: "#7c3aed",
category: "Research",
title: "Synthesise findings",
pills: ["Planned", "Sep 08, 2026"],
},
];
const FALLBACK_COLORS = [
"#fe552e",
"#d98a1f",
"#4f6ef0",
"#1f9d57",
"#c026d3",
"#0891b2",
"#e11d48",
"#7c3aed",
];
/* cardsJson override: strict parse, silent fallback on any problem. */
let ITEMS = CARDS;
try {
const raw = String(V.cardsJson || "").trim();
if (raw) {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed) && parsed.length >= 1 && parsed.length <= 24) {
const clean = parsed.map((e, i) => ({
color: typeof e.color === "string" ? e.color : FALLBACK_COLORS[i % 8],
category: typeof e.category === "string" ? e.category : "",
title: typeof e.title === "string" ? e.title : "",
pills: Array.isArray(e.pills) ? e.pills.slice(0, 2).map((x) => String(x)) : [],
}));
if (clean.every((e) => e.title)) ITEMS = clean;
}
}
} catch (e) {
/* keep CARDS */
}
const HERO = Math.max(0, Math.min(COUNT - 1, Math.round(num(V.heroCard, 4, 1, 12)) - 1));
const clamp01 = (x) => Math.max(0, Math.min(1, x));
const smooth01 = (x) => x * x * (3 - 2 * x);
const N = MOTION.tiles.length;
const CAMERA_SETTLE = num(V.cameraSettleFrame, 70, 20, 90);
const T_IN = CAMERA_SETTLE / 30,
T_BREAK = 31 / 30,
T_CENTER = 90 / 30;
const T_CLEAR = MOTION.tiles.find((tile) => !tile.hero).hideFrame / 30;
const T_OUT = 150 / 30,
T_EMPTY = 190 / 30;
const canvas = document.getElementById("cc-canvas");
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(1);
renderer.setSize(1920, 1080, false);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputColorSpace = THREE.SRGBColorSpace;
const scene = new THREE.Scene();
scene.background = new THREE.Color(BACKDROP);
document.getElementById("cc-bg").style.background = BACKDROP;
const camera = new THREE.PerspectiveCamera(FOV, ASPECT, 0.1, 200);
camera.position.set(CAM_X, CAM_Y, CAM_Z);
camera.lookAt(0, 0, 0);
// environment (stand-in for the `city` preset)
const pmrem = new THREE.PMREMGenerator(renderer);
const envRT = pmrem.fromScene(new RoomEnvironment(), 0.04);
const envMap = envRT.texture;
// ---- lights, exactly the experiment's rig ----
scene.add(new THREE.AmbientLight(new THREE.Color(AMB_C), AMB_I));
const top = new THREE.DirectionalLight(new THREE.Color(TOP_C), TOP_I);
top.position.set(20, 9, 3);
top.castShadow = true;
top.shadow.mapSize.set(MAP_SIZE, MAP_SIZE);
// "softness at the reference map size (2048)" - stable across mapSize
top.shadow.radius = SHADOW_RADIUS * (MAP_SIZE / 2048);
top.shadow.bias = -0.0003;
top.shadow.normalBias = 0.02;
{
const c = top.shadow.camera;
const halfW = Math.max(14, VIEW_HALF + SPACING * 3);
const halfH = Math.max(10, HEIGHT * 0.7 + 2);
c.left = -halfW;
c.right = halfW;
c.top = halfH;
c.bottom = -halfH;
c.near = 0.5;
c.far = 60;
c.updateProjectionMatrix();
}
scene.add(top);
const bottom = new THREE.DirectionalLight(new THREE.Color(BOT_C), BOT_I);
bottom.position.set(0, -8, 3);
scene.add(bottom);
const keyLight = new THREE.DirectionalLight(new THREE.Color(KEY_C), KEY_I);
keyLight.position.set(12, 9.2, 7);
scene.add(keyLight);
/* ---- rounded-rect shape + bevelled extrude, ported from
roundedRectShape() / buildCardGeometry() ---- */
function roundedRectShape(w, h, r) {
const s = new THREE.Shape();
const x = -w / 2,
y = -h / 2;
s.moveTo(x + r, y);
s.lineTo(x + w - r, y);
s.quadraticCurveTo(x + w, y, x + w, y + r);
s.lineTo(x + w, y + h - r);
s.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
s.lineTo(x + r, y + h);
s.quadraticCurveTo(x, y + h, x, y + h - r);
s.lineTo(x, y + r);
s.quadraticCurveTo(x, y, x + r, y);
s.closePath();
return s;
}
function buildCardGeometry() {
const outerR = Math.min(CORNER_R, Math.min(WIDTH, HEIGHT) / 2 - 0.001);
const r = Math.max(outerR, 0.002);
const bSize = Math.max(
0,
Math.min(BEVEL_SIZE, r * 0.98, WIDTH / 2 - 0.005, HEIGHT / 2 - 0.005),
);
const maxThick = Math.max(DEPTH * 0.45, 0.0005);
const bThick = Math.max(0, Math.min(BEVEL_THICK, maxThick));
const sizeOn = bSize > 0.0005,
thickOn = bThick > 0.0005;
const bevelOn = sizeOn || thickOn;
const innerW = sizeOn ? Math.max(WIDTH - 2 * bSize, 0.01) : WIDTH;
const innerH = sizeOn ? Math.max(HEIGHT - 2 * bSize, 0.01) : HEIGHT;
const innerR = sizeOn ? Math.max(r - bSize, 0.002) : r;
const extrudeDepth = thickOn ? Math.max(DEPTH - 2 * bThick, DEPTH * 0.15, 0.002) : DEPTH;
const shape = roundedRectShape(innerW, innerH, innerR);
// Cap UVs span the full face so the text texture lands square on it;
// side walls collapse to a single point, as in the source.
const uvGenerator = {
generateTopUV(_g, vertices, a, b, c) {
const p = (i) =>
new THREE.Vector2(
(vertices[i * 3] + WIDTH / 2) / WIDTH,
(vertices[i * 3 + 1] + HEIGHT / 2) / HEIGHT,
);
return [p(a), p(b), p(c)];
},
generateSideWallUV() {
return [
new THREE.Vector2(0, 0),
new THREE.Vector2(0, 0),
new THREE.Vector2(0, 0),
new THREE.Vector2(0, 0),
];
},
};
const geo = new THREE.ExtrudeGeometry(shape, {
depth: extrudeDepth,
bevelEnabled: bevelOn,
bevelThickness: bThick,
bevelSize: bSize,
bevelSegments: Math.max(16, Math.round(SMOOTH * 2)),
curveSegments: Math.max(16, Math.round(SMOOTH)),
steps: 1,
UVGenerator: uvGenerator,
});
geo.center();
if (bevelOn) {
const merged = mergeVertices(geo, 1e-4);
geo.dispose();
merged.computeVertexNormals();
merged.computeBoundingSphere();
return merged;
}
geo.computeVertexNormals();
geo.computeBoundingSphere();
return geo;
}
/* ---- face texture, ported from face-renderer.ts (540x756).
The source clears to transparent and lets the body colour show
through a masking shader; painting the body colour in first gives
the same result without the extra pass. ---- */
const FACE_W = 540,
FACE_H = 756;
const FONT_STACK = '"Inter", -apple-system, system-ui, sans-serif';
function lightTint(hex, amount = 0.55) {
return new THREE.Color(hex).lerp(new THREE.Color("#ffffff"), amount).getStyle();
}
function roundedRectPath(ctx, x, y, w, h, r) {
const radius = Math.min(r, w / 2, h / 2);
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arcTo(x + w, y, x + w, y + h, radius);
ctx.arcTo(x + w, y + h, x, y + h, radius);
ctx.arcTo(x, y + h, x, y, radius);
ctx.arcTo(x, y, x + w, y, radius);
ctx.closePath();
}
function wrapLines(ctx, text, maxWidth) {
const words = String(text).split(" ");
const lines = [];
let current = "";
for (const word of words) {
const candidate = current ? current + " " + word : word;
if (ctx.measureText(candidate).width > maxWidth && current) {
lines.push(current);
current = word;
} else current = candidate;
}
if (current) lines.push(current);
return lines;
}
function faceTexture(item) {
const c = document.createElement("canvas");
c.width = FACE_W;
c.height = FACE_H;
const ctx = c.getContext("2d");
ctx.fillStyle = BODY;
ctx.fillRect(0, 0, FACE_W, FACE_H);
const tint = lightTint(item.color);
const pad = 45;
const contentW = FACE_W - pad * 2;
let y = pad + 8;
ctx.textBaseline = "alphabetic";
ctx.textAlign = "left";
ctx.fillStyle = tint;
ctx.font = "400 25px " + FONT_STACK;
ctx.fillText(item.category, pad, y + 22);
y += 22 + 34;
ctx.fillStyle = "#ffffff";
ctx.font = "500 38px " + FONT_STACK;
for (const line of wrapLines(ctx, item.title, contentW)) {
ctx.fillText(line, pad, y + 34);
y += 47;
}
ctx.font = "400 19px " + FONT_STACK;
const pillH = 50;
const pillY = FACE_H - pad - pillH;
let pillX = pad;
for (const label of item.pills) {
const pillW = ctx.measureText(label).width + 44;
roundedRectPath(ctx, pillX, pillY, pillW, pillH, pillH / 2);
ctx.strokeStyle = tint;
ctx.lineWidth = 1.4;
ctx.stroke();
ctx.fillStyle = tint;
ctx.textBaseline = "middle";
ctx.fillText(label, pillX + 22, pillY + pillH / 2 + 1);
ctx.textBaseline = "alphabetic";
pillX += pillW + 15;
}
const tex = new THREE.CanvasTexture(c);
tex.flipY = false;
tex.colorSpace = THREE.SRGBColorSpace;
tex.anisotropy = 8;
tex.minFilter = THREE.LinearMipmapLinearFilter;
tex.magFilter = THREE.LinearFilter;
return tex;
}
// ---- build the chain ----
const geometry = buildCardGeometry();
const sideMat = new THREE.MeshStandardMaterial({
color: new THREE.Color(BODY),
roughness: ROUGH,
metalness: METAL,
envMap,
envMapIntensity: 0.3,
});
const EULER_ORDER = AXIS === "x" ? "XYZ" : AXIS === "y" ? "YXZ" : "ZXY";
let capMats = [];
let heroMat = null;
let cuboids = [];
function buildChain() {
if (heroMat) heroMat.dispose();
capMats.forEach((m) => {
if (m.map) m.map.dispose();
m.dispose();
});
capMats = ITEMS.map((item) => {
const capMat = new THREE.MeshStandardMaterial({
map: faceTexture(item),
roughness: ROUGH,
metalness: METAL,
envMap,
envMapIntensity: 0,
});
/* The source masks content to the front cap with a smoothstep on
position.z (uHalfDepth / uCapBand). Same idea: the back cap drops
the map and shows plain body colour, so text never reads mirrored. */
capMat.onBeforeCompile = (shader) => {
shader.uniforms.uBody = { value: new THREE.Color(BODY) };
shader.vertexShader = shader.vertexShader
.replace("#include <common>", "#include <common>\nvarying float vLocalZ;")
.replace("#include <begin_vertex>", "#include <begin_vertex>\nvLocalZ = position.z;");
shader.fragmentShader = shader.fragmentShader
.replace(
"#include <common>",
"#include <common>\nvarying float vLocalZ;\nuniform vec3 uBody;",
)
.replace(
"#include <map_fragment>",
"#include <map_fragment>\nif (vLocalZ > 0.0) diffuseColor.rgb = uBody;",
)
.replace(
"#include <emissivemap_fragment>",
"#include <emissivemap_fragment>\nif (vLocalZ > 0.0) totalEmissiveRadiance = vec3(0.0);",
);
};
return capMat;
});
// A separate material avoids brightening repeated cards with the same
// content. Emissive fill is masked off the unprinted back cap as well.
const heroSource = capMats[HERO % ITEMS.length];
heroMat = heroSource.clone();
heroMat.onBeforeCompile = heroSource.onBeforeCompile;
heroMat.emissive.set(0xffffff);
heroMat.emissiveMap = heroSource.map;
heroMat.emissiveIntensity = 0;
if (cuboids.length === 0) {
for (let j = 0; j < N; j++) {
// ExtrudeGeometry group 0 = caps, group 1 = side walls
const mesh = new THREE.Mesh(geometry, [capMats[0], sideMat]);
mesh.rotation.order = EULER_ORDER;
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
cuboids.push(mesh);
}
}
}
buildChain();
// Blender frame 0 maps to web time 0. Every pose is reconstructed on seek.
const basis = new THREE.Quaternion().setFromAxisAngle(
new THREE.Vector3(1, 0, 0),
-Math.PI / 2,
);
const inverseBasis = basis.clone().invert();
const sourceRoot = new THREE.Matrix4().set(...MOTION.rootMatrix);
const convertedRoot = new THREE.Matrix4()
.makeRotationFromQuaternion(basis)
.multiply(sourceRoot)
.multiply(new THREE.Matrix4().makeRotationFromQuaternion(inverseBasis));
const motionRoot = new THREE.Group();
motionRoot.name = "Blender approved Cuboid chain";
motionRoot.matrixAutoUpdate = false;
motionRoot.matrix.copy(convertedRoot);
scene.add(motionRoot);
for (const mesh of cuboids) motionRoot.add(mesh);
const heroSlot = MOTION.tiles.findIndex((tile) => tile.hero);
const sourceStart = new THREE.Vector3(...MOTION.cameraStart).applyQuaternion(basis);
const sourceEnd = new THREE.Vector3(...MOTION.cameraEnd).applyQuaternion(basis);
const startOffset = new THREE.Vector3(CAM_X, CAM_Y, CAM_Z).sub(sourceStart);
const endOffset = new THREE.Vector3(TARGET_X, TARGET_Y, TARGET_Z).sub(sourceEnd);
const hasCameraOffset = startOffset.lengthSq() + endOffset.lengthSq() > 1e-14;
const targetCamera = new THREE.Vector3(TARGET_X, TARGET_Y, TARGET_Z);
const samplePosition = new THREE.Vector3(),
basePosition = new THREE.Vector3();
const sampleQuaternion = new THREE.Quaternion(),
baseQuaternion = new THREE.Quaternion();
const qA = new THREE.Quaternion(),
qB = new THREE.Quaternion(),
qDelta = new THREE.Quaternion();
const poseEuler = new THREE.Euler(0, 0, 0, "ZYX");
const lookCamera = new THREE.PerspectiveCamera();
const zero = new THREE.Vector3();
const S = { t: 0 };
const HERO_FILL = num(V.heroFillIntensity, 0.55, 0, 2);
const HANDHELD = num(V.handheldStrength, 1, 0, 1);
const EXIT_ANGLES = [
num(V.heroExitX, 288, -720, 720),
num(V.heroExitY, -234, -720, 720),
num(V.heroExitZ, 198, -720, 720),
];
const sourceExitAngles = [288, -234, 198];
function channelAt(keys, frame) {
if (!keys || !keys.length) return 0;
if (frame <= keys[0][0]) return keys[0][1];
let lo = 0,
hi = keys.length - 1;
if (frame >= keys[hi][0]) return keys[hi][1];
while (hi - lo > 1) {
const mid = (lo + hi) >> 1;
if (keys[mid][0] <= frame) lo = mid;
else hi = mid;
}
const a = keys[lo],
b = keys[hi],
u = (frame - a[0]) / (b[0] - a[0]);
return a[1] + (b[1] - a[1]) * u;
}
function readCamera(frame, position, quaternion) {
const f = Math.max(0, Math.min(MOTION.endFrame, frame));
const i = Math.floor(f),
j = Math.min(MOTION.endFrame, i + 1),
u = f - i;
const a = MOTION.camera[i],
b = MOTION.camera[j];
position
.set(a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u, a[2] + (b[2] - a[2]) * u)
.applyQuaternion(basis);
qA.set(a[3], a[4], a[5], a[6]);
qB.set(b[3], b[4], b[5], b[6]);
// Camera optical axes are already -Z forward / +Y up: convert world basis only.
quaternion.slerpQuaternions(qA, qB, u).premultiply(basis).normalize();
return a[7] + (b[7] - a[7]) * u;
}
function cameraAt(frame) {
const cameraFrame =
frame < CAMERA_SETTLE ? (frame * 70) / CAMERA_SETTLE : Math.max(70, frame);
const sourceFov = readCamera(cameraFrame, samplePosition, sampleQuaternion);
const inHandheld = frame > 90 && frame < 150;
if (inHandheld) {
readCamera(90, basePosition, baseQuaternion);
if (HANDHELD !== 1) {
samplePosition.lerpVectors(basePosition, samplePosition, HANDHELD);
sampleQuaternion.slerpQuaternions(baseQuaternion, sampleQuaternion, HANDHELD);
}
} else {
basePosition.copy(samplePosition);
baseQuaternion.copy(sampleQuaternion);
}
const u = clamp01(
(MOTION.cameraControls["Start FOV degrees"] - sourceFov) /
(MOTION.cameraControls["Start FOV degrees"] - MOTION.cameraControls["End FOV degrees"]),
);
// A changed start must not recompute the approved settled/handheld pose.
if (hasCameraOffset && (u < 1 || endOffset.lengthSq() > 1e-14)) {
qDelta.copy(baseQuaternion).invert().multiply(sampleQuaternion);
const offset = startOffset.clone().lerp(endOffset, u);
lookCamera.position.copy(basePosition).add(offset);
lookCamera.lookAt(zero);
sampleQuaternion.copy(lookCamera.quaternion).multiply(qDelta);
samplePosition.add(offset);
}
camera.position.copy(samplePosition);
camera.quaternion.copy(sampleQuaternion);
camera.fov = FOV === 65 && TARGET_FOV === 34 ? sourceFov : FOV + (TARGET_FOV - FOV) * u;
camera.updateProjectionMatrix();
camera.updateMatrixWorld();
}
const heroInitialX = MOTION.tiles[heroSlot].channels.location[0][0][1];
const slotOffsets = MOTION.tiles.map(
(tile) => (tile.channels.location[0][0][1] - heroInitialX) / 1.73,
);
// One mesh owns the hero slot before and after separation. Its arrival
// must use the same transport as the constant-rate chain, not the old
// slower Blender arrival curve. Keep that transport while it first lifts
// clear, then rejoin the approved absolute path without a position jump.
const heroXKeys = MOTION.tiles[heroSlot].channels.location[0];
const chainXKeys = MOTION.tiles.find((tile) => !tile.hero).channels.location[0];
const HERO_SLOT_RELEASE = 36,
HERO_PATH_REJOIN = 70;
const heroSlotXAt = (frame) => heroInitialX + channelAt(chainXKeys, frame) - chainXKeys[0][1];
const heroSlotDelta =
heroSlotXAt(HERO_SLOT_RELEASE) - channelAt(heroXKeys, HERO_SLOT_RELEASE);
const heroSlotVelocityDelta =
(heroSlotXAt(HERO_SLOT_RELEASE) -
heroSlotXAt(HERO_SLOT_RELEASE - 0.25) -
channelAt(heroXKeys, HERO_SLOT_RELEASE) +
channelAt(heroXKeys, HERO_SLOT_RELEASE - 0.25)) /
0.25;
function heroSlotCorrection(frame) {
if (frame <= HERO_SLOT_RELEASE) return heroSlotXAt(frame) - channelAt(heroXKeys, frame);
if (frame >= HERO_PATH_REJOIN) return 0;
const duration = HERO_PATH_REJOIN - HERO_SLOT_RELEASE;
const u = (frame - HERO_SLOT_RELEASE) / duration,
u2 = u * u,
u3 = u2 * u;
return (
(2 * u3 - 3 * u2 + 1) * heroSlotDelta +
(u3 - 2 * u2 + u) * duration * heroSlotVelocityDelta
);
}
const heroExitBase = MOTION.tiles[heroSlot].channels.rotation_euler.map((keys) =>
channelAt(keys, 150),
);
const actualPose = { frame: 0, camera: [], hero: [], heroRotation: [], visibleTiles: 0 };
function renderAt(time) {
S.t = Math.max(0, Math.min(DUR, time));
const frame = S.t * MOTION.fps;
cameraAt(frame);
const heroProgress = smooth01(clamp01((frame - 31) / 59));
heroMat.emissiveIntensity = HERO_FILL * heroProgress;
let visibleTiles = 0;
for (let j = 0; j < cuboids.length; j++) {
const mesh = cuboids[j],
tile = MOTION.tiles[j],
c = tile.channels;
const p = c.location.map((keys) => channelAt(keys, frame));
if (tile.hero) p[0] += heroSlotCorrection(frame);
const r = c.rotation_euler.map((keys) => channelAt(keys, frame));
if (tile.hero && frame > 150) {
for (let axis = 0; axis < 3; axis++)
r[axis] =
heroExitBase[axis] +
((r[axis] - heroExitBase[axis]) * EXIT_ANGLES[axis]) / sourceExitAngles[axis];
}
mesh.position.set(p[0], p[2], -p[1]);
if (!tile.hero) mesh.position.x += slotOffsets[j] * (SPACING - 1.73);
// Blender XYZ is extrinsic X→Y→Z; Three's matching Euler order is ZYX.
poseEuler.set(r[0], r[1], r[2], "ZYX");
mesh.quaternion.setFromEuler(poseEuler).premultiply(basis).multiply(inverseBasis);
const scale = c.scale.map((keys) => channelAt(keys, frame));
mesh.scale.set(scale[0], scale[2], scale[1]);
if (tile.hero) mesh.scale.multiplyScalar(1 + (HERO_SCALE - 1) * heroProgress);
const item = ((((tile.itemIndex + HERO - 3) % COUNT) + COUNT) % COUNT) % ITEMS.length;
mesh.material[0] = tile.hero && frame >= 31 ? heroMat : capMats[item];
mesh.visible = frame < tile.hideFrame;
if (mesh.visible) visibleTiles++;
if (tile.hero) {
actualPose.hero = mesh.position.toArray();
actualPose.heroRotation = r;
}
}
actualPose.frame = frame;
actualPose.camera = camera.position.toArray();
actualPose.visibleTiles = visibleTiles;
renderer.toneMappingExposure = 1;
renderer.render(scene, camera);
}
window.__cuboidMotion = {
source: MOTION.sourceFile,
sha256: MOTION.sourceSha256,
fps: MOTION.fps,
endFrame: MOTION.endFrame,
phases: {
breakaway: 31,
directApproach: 40,
centered: 90,
exit: 150,
clear: 190,
end: 200,
},
get pose() {
return { ...actualPose };
},
};
/* ---- timeline: paused, seek-safe, no rAF ---- */
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.addLabel("arrival", 0);
tl.addLabel("alternate-camera", T_IN);
tl.addLabel("hero-breakaway", T_BREAK);
tl.addLabel("hero-centered", T_CENTER);
tl.addLabel("solo-hold", T_CLEAR);
tl.addLabel("hero-exit", T_OUT);
tl.addLabel("empty", T_EMPTY);
tl.to(S, { t: DUR, duration: DUR, ease: "none" }, 0);
tl.eventCallback("onUpdate", () => renderAt(S.t));
/* Studio's player seeks with GSAP events suppressed and dispatches an
hf-seek CustomEvent instead - re-sync and repaint so the stage follows. */
window.addEventListener("hf-seek", function (event) {
const d = event && event.detail;
if (d && typeof d.time === "number")
tl.totalTime(Math.min(d.time, tl.totalDuration()), true);
const fn = tl.eventCallback("onUpdate");
if (fn) fn();
});
window.__timelines["cuboid-carousel"] = tl;
renderAt(0);
/* Draw across the timeline and flush the GPU before ready, so shader links
and texture uploads land in the readiness gate, not the first visible frames. */
async function warmGpu() {
for (const f of [0.25, 0.5, 0.75]) {
await new Promise((resolve) => setTimeout(resolve, 0));
renderAt(DUR * f);
}
renderAt(S.t);
renderer.getContext().finish();
}
window.__hf = window.__hf || {};
window.__hf.buildReady = window.__hf.buildReady || {};
window.__hf.buildReady["cuboid-carousel"] = (
document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve()
)
.then(() => {
buildChain();
renderAt(S.t);
return warmGpu();
})
.catch(() => {});
</script>
<script>
/* Standalone playback: open this file with ?play (add &loop to repeat).
Inert without the param — Studio and the renderer never add it, so
editing and deterministic renders are unaffected. */
(function () {
var q = new URLSearchParams(location.search);
if (!q.has("play")) return;
var tick = setInterval(function () {
var map = window.__timelines || {};
var ids = Object.keys(map);
if (!ids.length) return;
clearInterval(tick);
for (var i = 0; i < ids.length; i++) {
var tl = map[ids[i]];
if (q.has("loop")) tl.repeat(-1);
tl.play(0);
}
}, 100);
})();
</script>
</body>
</html>
```
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add cuboid-carousel" item="cuboid-carousel" />
That writes `compositions/cuboid-carousel/cuboid-carousel.html`, plus 9 supporting files under `compositions/cuboid-carousel/assets/`, `compositions/cuboid-carousel/assets/addons/environments/`, `compositions/cuboid-carousel/assets/addons/utils/`, and `compositions/cuboid-carousel/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 6.67 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="cuboid-carousel"
data-composition-src="compositions/cuboid-carousel/cuboid-carousel.html"
data-start="0"
data-duration="6.67"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `3d-motion` `carousel` `cards` `travelling-wave` `pbr` `shadows` `room-environment`.
## Related topics
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+53 -28
View File
@@ -1,35 +1,28 @@
---
title: "Data Chart"
description: "Animated bar + line chart with staggered reveal, NYT-style typography, and value labels"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/data-chart.json"
compositionId="data-chart"
compositionSrc="compositions/data-chart.html"
title="Data Chart"
description="Animated bar + line chart with staggered reveal, NYT-style typography, and value labels"
variables={[]}
meta={{"duration":15,"width":1920,"height":1080,"category":"Data","badge":"Stable","codeLines":457}}
attribution={{"path":"registry/blocks/data-chart","tags":["data","chart","statistics"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/data-chart/data-chart.html"
>
<InstallCommand command="npx hyperframes add data-chart" item="data-chart" />
<CatalogSlot slot="code">
That writes one file: `compositions/data-chart.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="data-chart preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/data-chart.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/data-chart.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Change the colors
Set these CSS variables on the block:
- `--bg-color` — Background. Defaults to `#faf9f6`.
- `--text-color` — Text color. Defaults to `#333333`.
## Source
<Accordion title="data-chart.html">
```html
```html data-chart.html
<!doctype html>
<html lang="en">
<head>
@@ -489,16 +482,44 @@ Set these CSS variables on the block:
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add data-chart" item="data-chart" />
```html
<div data-composition-id="data-chart" data-composition-src="compositions/data-chart.html" data-start="0" data-duration="15" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/data-chart.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 15 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="data-chart"
data-composition-src="compositions/data-chart.html"
data-start="0"
data-duration="15"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Change the colors
Set these CSS variables on the block:
- `--bg-color` — Background. Defaults to `#faf9f6`.
- `--text-color` — Text color. Defaults to `#333333`.
{/* hf:generated-footer */}
Tagged `data` `chart` `statistics`.
@@ -508,3 +529,7 @@ Tagged `data` `chart` `statistics`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Domain Warp Dissolve"
description: "Shader transition with fractal noise domain warping"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/domain-warp-dissolve.json"
compositionId="domain-warp-dissolve"
compositionSrc="compositions/domain-warp-dissolve.html"
title="Domain Warp Dissolve"
description="Shader transition with fractal noise domain warping"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Shader Transitions","badge":"Stable","codeLines":380}}
attribution={{"path":"registry/blocks/domain-warp-dissolve","tags":["transition","shader"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/domain-warp-dissolve/domain-warp-dissolve.html"
>
<InstallCommand command="npx hyperframes add domain-warp-dissolve" item="domain-warp-dissolve" />
<CatalogSlot slot="code">
That writes one file: `compositions/domain-warp-dissolve.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="domain-warp-dissolve preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/domain-warp-dissolve.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/domain-warp-dissolve.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="domain-warp-dissolve.html">
```html
```html domain-warp-dissolve.html
<!doctype html>
<html lang="en">
<head>
@@ -405,16 +405,37 @@ That writes one file: `compositions/domain-warp-dissolve.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add domain-warp-dissolve" item="domain-warp-dissolve" />
```html
<div data-composition-id="domain-warp-dissolve" data-composition-src="compositions/domain-warp-dissolve.html" data-start="0" data-duration="4" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/domain-warp-dissolve.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="domain-warp-dissolve"
data-composition-src="compositions/domain-warp-dissolve.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `transition` `shader`.
@@ -424,3 +445,7 @@ Tagged `transition` `shader`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -36
View File
@@ -1,47 +1,28 @@
---
title: "Editorial Flash Overlay"
description: "Finite neutral-warm light layers for a seek-safe camera-flash cut or social reveal"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/editorial-flash-overlay.json"
compositionId="editorial-flash-overlay"
compositionSrc="compositions/editorial-flash-overlay.html"
title="Editorial Flash Overlay"
description="Finite neutral-warm light layers for a seek-safe camera-flash cut or social reveal"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Social Overlays","badge":"Stable","codeLines":129}}
attribution={{"path":"registry/blocks/editorial-flash-overlay","tags":["flash","editorial","social","light","transition","overlay","media-treatment-overlay"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/editorial-flash-overlay/editorial-flash-overlay.html"
>
<InstallCommand command="npx hyperframes add editorial-flash-overlay" item="editorial-flash-overlay" />
<CatalogSlot slot="code">
That writes one file: `compositions/editorial-flash-overlay.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="editorial-flash-overlay preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/editorial-flash-overlay.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/editorial-flash-overlay.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="editorial-flash-overlay"
data-composition-src="compositions/editorial-flash-overlay.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="editorial-flash-overlay.html">
```html
```html editorial-flash-overlay.html
<!doctype html>
<html lang="en">
<head>
@@ -173,7 +154,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add editorial-flash-overlay" item="editorial-flash-overlay" />
That writes one file: `compositions/editorial-flash-overlay.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="editorial-flash-overlay"
data-composition-src="compositions/editorial-flash-overlay.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -184,3 +194,7 @@ Tagged `flash` `editorial` `social` `light` `transition` `overlay` `media-treatm
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Flash Through White"
description: "Shader transition with white flash crossfade"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/flash-through-white.json"
compositionId="flash-through-white"
compositionSrc="compositions/flash-through-white.html"
title="Flash Through White"
description="Shader transition with white flash crossfade"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Shader Transitions","badge":"Stable","codeLines":366}}
attribution={{"path":"registry/blocks/flash-through-white","tags":["transition","shader"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/flash-through-white/flash-through-white.html"
>
<InstallCommand command="npx hyperframes add flash-through-white" item="flash-through-white" />
<CatalogSlot slot="code">
That writes one file: `compositions/flash-through-white.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="flash-through-white preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/flash-through-white.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/flash-through-white.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="flash-through-white.html">
```html
```html flash-through-white.html
<!doctype html>
<html lang="en">
<head>
@@ -391,16 +391,37 @@ That writes one file: `compositions/flash-through-white.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add flash-through-white" item="flash-through-white" />
```html
<div data-composition-id="flash-through-white" data-composition-src="compositions/flash-through-white.html" data-start="0" data-duration="4" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/flash-through-white.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="flash-through-white"
data-composition-src="compositions/flash-through-white.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `transition` `shader`.
@@ -410,3 +431,7 @@ Tagged `transition` `shader`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Flowchart Vertical"
description: "Portrait animated decision tree with SVG connectors, sticky-note nodes, cursor interaction, and typing correction"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/flowchart-vertical.json"
compositionId="flowchart-vertical"
compositionSrc="compositions/flowchart-vertical.html"
title="Flowchart Vertical"
description="Portrait animated decision tree with SVG connectors, sticky-note nodes, cursor interaction, and typing correction"
variables={[]}
meta={{"duration":12,"width":1440,"height":2560,"category":"Blocks","badge":"Stable","codeLines":459}}
attribution={{"path":"registry/blocks/flowchart-vertical","tags":["diagram","flowchart","interactive","portrait"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/flowchart-vertical/flowchart-vertical.html"
>
<InstallCommand command="npx hyperframes add flowchart-vertical" item="flowchart-vertical" />
<CatalogSlot slot="code">
That writes one file: `compositions/flowchart-vertical.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="flowchart-vertical preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/flowchart-vertical.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/flowchart-vertical.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="flowchart-vertical.html">
```html
```html flowchart-vertical.html
<!doctype html>
<html lang="en">
<head>
@@ -484,16 +484,37 @@ That writes one file: `compositions/flowchart-vertical.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add flowchart-vertical" item="flowchart-vertical" />
```html
<div data-composition-id="flowchart-vertical" data-composition-src="compositions/flowchart-vertical.html" data-start="0" data-duration="12" data-track-index="1" data-width="1440" data-height="2560"></div>
That writes one file: `compositions/flowchart-vertical.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1440×2560. Paste this into your composition:
```html index.html
<div
data-composition-id="flowchart-vertical"
data-composition-src="compositions/flowchart-vertical.html"
data-start="0"
data-duration="12"
data-track-index="1"
data-width="1440"
data-height="2560"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `diagram` `flowchart` `interactive` `portrait`.
@@ -503,3 +524,7 @@ Tagged `diagram` `flowchart` `interactive` `portrait`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Flowchart"
description: "Animated decision tree with SVG connectors, sticky-note nodes, cursor interaction, and typing correction"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/flowchart.json"
compositionId="flowchart"
compositionSrc="compositions/flowchart.html"
title="Flowchart"
description="Animated decision tree with SVG connectors, sticky-note nodes, cursor interaction, and typing correction"
variables={[]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Blocks","badge":"Stable","codeLines":459}}
attribution={{"path":"registry/blocks/flowchart","tags":["diagram","flowchart","interactive"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/flowchart/flowchart.html"
>
<InstallCommand command="npx hyperframes add flowchart" item="flowchart" />
<CatalogSlot slot="code">
That writes one file: `compositions/flowchart.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="flowchart preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/flowchart.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/flowchart.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="flowchart.html">
```html
```html flowchart.html
<!doctype html>
<html lang="en">
<head>
@@ -484,16 +484,37 @@ That writes one file: `compositions/flowchart.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add flowchart" item="flowchart" />
```html
<div data-composition-id="flowchart" data-composition-src="compositions/flowchart.html" data-start="0" data-duration="12" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/flowchart.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="flowchart"
data-composition-src="compositions/flowchart.html"
data-start="0"
data-duration="12"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `diagram` `flowchart` `interactive`.
@@ -503,3 +524,7 @@ Tagged `diagram` `flowchart` `interactive`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -36
View File
@@ -1,47 +1,28 @@
---
title: "Freeze-Frame Dressing"
description: "Timeline-driven paper, tape, and flash dressing for a freeze-frame or background-removed subject"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/freeze-frame-dressing.json"
compositionId="freeze-frame-dressing"
compositionSrc="compositions/freeze-frame-dressing.html"
title="Freeze-Frame Dressing"
description="Timeline-driven paper, tape, and flash dressing for a freeze-frame or background-removed subject"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Social Overlays","badge":"Stable","codeLines":120}}
attribution={{"path":"registry/blocks/freeze-frame-dressing","tags":["freeze-frame","cutout","scrapbook","paper","social","overlay","media-treatment-overlay"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/freeze-frame-dressing/freeze-frame-dressing.html"
>
<InstallCommand command="npx hyperframes add freeze-frame-dressing" item="freeze-frame-dressing" />
<CatalogSlot slot="code">
That writes one file: `compositions/freeze-frame-dressing.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="freeze-frame-dressing preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/freeze-frame-dressing.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/freeze-frame-dressing.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="freeze-frame-dressing"
data-composition-src="compositions/freeze-frame-dressing.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="freeze-frame-dressing.html">
```html
```html freeze-frame-dressing.html
<!doctype html>
<html lang="en">
<head>
@@ -164,7 +145,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add freeze-frame-dressing" item="freeze-frame-dressing" />
That writes one file: `compositions/freeze-frame-dressing.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="freeze-frame-dressing"
data-composition-src="compositions/freeze-frame-dressing.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -175,3 +185,7 @@ Tagged `freeze-frame` `cutout` `scrapbook` `paper` `social` `overlay` `media-tre
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
File diff suppressed because one or more lines are too long
+26 -472
View File
@@ -1,24 +1,27 @@
---
title: "Gallery Tunnel"
description: "An infinite corridor of panels: a square tunnel receding to a vanishing point, its floor, ceiling and walls tiled with saturated colour slabs and caller-supplied images that swell toward camera and exit past you, the far end dissolving into fog."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
import { VariablesExplorer } from "/snippets/variables-explorer.jsx";
## Install
<InstallCommand command="npx hyperframes add gallery-tunnel" item="gallery-tunnel" />
That writes one file: `compositions/gallery-tunnel.html`.
<VariablesExplorer
<CatalogDetail
previewSrc="/public/catalog/blocks/gallery-tunnel.json"
compositionId="gallery-tunnel"
compositionSrc="compositions/gallery-tunnel.html"
title="Gallery Tunnel"
description="An infinite corridor of panels: a square tunnel receding to a vanishing point, its floor, ceiling and walls tiled with saturated colour slabs and caller-supplied images that swell toward camera and exit past you, the far end dissolving into fog."
variables={[{"id":"images","type":"string","label":"Image slots: comma-separated paths, tiled into the panels (e.g. assets/shot-1.png,assets/shot-2.png)","default":"","placeholder":"assets/shot-1.png,assets/shot-2.png"},{"id":"imageMix","type":"number","label":"Image share of filled slots (0 = all colour, 1 = all images)","default":0.5,"min":0,"max":1,"step":0.05},{"id":"speed","type":"number","label":"Flight speed","default":3,"min":0,"max":8,"step":0.1,"unit":"segments/s"},{"id":"palette","type":"string","label":"Slab palette: comma-separated hex colours","default":"#FF6A00,#AB54F7,#EA3737,#0072E3,#00AA3C,#FFB200"},{"id":"grid","type":"number","label":"Grid density: slots per wall face per axis (2 = 4 per face, 16 per segment)","default":2,"min":1,"max":4,"step":1},{"id":"fill","type":"number","label":"Slot fill rate","default":0.5,"min":0,"max":1,"step":0.05},{"id":"fog","type":"number","label":"Fog distance: depth at which the corridor reaches pure backdrop","default":14.25,"min":3,"max":14.5,"step":0.25,"unit":"segments"},{"id":"edgeColor","type":"color","label":"Edge line colour","default":"#B0B0B0"},{"id":"edgeOpacity","type":"number","label":"Edge line opacity","default":0.5,"min":0,"max":1,"step":0.05},{"id":"backdrop","type":"color","label":"Backdrop and fog colour","default":"#000000"}]}
meta={{"duration":12,"width":1920,"height":1080,"category":"Showcases","badge":"Experimental","codeLines":454}}
attribution={{"path":"registry/blocks/gallery-tunnel","tags":["webgl","3d","background","showcase","depth","loop"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/gallery-tunnel/gallery-tunnel.html"
>
<CatalogSlot slot="code">
```html gallery-tunnel.html
<!doctype html>
<html lang="en">
@@ -476,7 +479,17 @@ That writes one file: `compositions/gallery-tunnel.html`.
</html>
```
</VariablesExplorer>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add gallery-tunnel" item="gallery-tunnel" />
That writes one file: `compositions/gallery-tunnel.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
@@ -541,469 +554,6 @@ defaults, so this behaves exactly like the preview above until you change one:
></div>
```
## Source
<Accordion title="gallery-tunnel.html">
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Gallery Tunnel</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
overflow: hidden;
}
#gt-root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
}
#gt-backdrop {
position: absolute;
inset: 0;
background: #000000;
}
#gt-canvas {
position: absolute;
top: 0;
left: 0;
width: 1920px;
height: 1080px;
}
/* Off-screen decode well: the <img> elements live in the DOM so the page
load event waits for them, but they are never composited themselves —
WebGL reads them as textures. The well is clipped to 1px; the images
inside keep their natural layout size on purpose, because texImage2D
uploads an <img> at its LAID-OUT size — sizing them down with CSS
uploads a 1x1 texture and every panel samples one dark pixel. */
#gt-assets {
position: absolute;
top: 0;
left: 0;
width: 1px;
height: 1px;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
</style>
</head>
<body>
<!--
Content slots. Every panel in the corridor is a slot, and `images` fills a
share of them with your own artwork — drop the files in the project's
assets/ and list them, either by editing the `images` default below or from
the host composition:
<div
data-composition-id="gallery-tunnel"
data-composition-src="compositions/gallery-tunnel.html"
data-start="0" data-duration="12" data-track-index="0"
data-width="1920" data-height="1080"
style="--images: assets/shot-1.png, assets/shot-2.png, assets/shot-3.png; --imagemix: 0.7"
></div>
Any number of images works; they are dealt to slots by hash, so one image
recurs at different depths rather than appearing once. Each is contain-fit
inside its slot, so a 16:9 screenshot keeps its aspect. `imageMix` sets the
image-vs-colour split (1 = every filled slot is an image), `fill` how many
slots are filled at all.
-->
<div
id="gt-root"
data-composition-id="gallery-tunnel"
data-root="true"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="12"
data-composition-variables='[
{"id":"images","type":"string","label":"Image slots: comma-separated paths, tiled into the panels (e.g. assets/shot-1.png,assets/shot-2.png)","default":"","placeholder":"assets/shot-1.png,assets/shot-2.png"},
{"id":"imageMix","type":"number","label":"Image share of filled slots (0 = all colour, 1 = all images)","default":0.5,"min":0,"max":1,"step":0.05},
{"id":"speed","type":"number","label":"Flight speed","default":3,"min":0,"max":8,"step":0.1,"unit":"segments/s"},
{"id":"palette","type":"string","label":"Slab palette: comma-separated hex colours","default":"#FF6A00,#AB54F7,#EA3737,#0072E3,#00AA3C,#FFB200"},
{"id":"grid","type":"number","label":"Grid density: slots per wall face per axis (2 = 4 per face, 16 per segment)","default":2,"min":1,"max":4,"step":1},
{"id":"fill","type":"number","label":"Slot fill rate","default":0.5,"min":0,"max":1,"step":0.05},
{"id":"fog","type":"number","label":"Fog distance: depth at which the corridor reaches pure backdrop","default":14.25,"min":3,"max":14.5,"step":0.25,"unit":"segments"},
{"id":"edgeColor","type":"color","label":"Edge line colour","default":"#B0B0B0"},
{"id":"edgeOpacity","type":"number","label":"Edge line opacity","default":0.5,"min":0,"max":1,"step":0.05},
{"id":"backdrop","type":"color","label":"Backdrop and fog colour","default":"#000000"}
]'
>
<div id="gt-backdrop"></div>
<canvas id="gt-canvas" width="1920" height="1080"></canvas>
<div id="gt-assets" aria-hidden="true" data-layout-allow-overflow></div>
<!-- Driver clip: gives HyperFrames a timed element to own on track 0. -->
<div
id="gt-drv"
class="clip"
data-start="0"
data-duration="12"
data-track-index="0"
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
></div>
</div>
<script>
(function () {
var DUR = 12;
var W = 1920;
var H = 1080;
// Fixed tunnel geometry, measured off the reference.
var TUNNEL_WIDTH = 2;
var TUNNEL_HEIGHT = 1.8;
var SEGMENT_DEPTH = 1;
var NUM_SEGMENTS = 15;
var LINE_RADIUS = 0.003;
var FOV = 75;
var INSET = 0.94; // slab gutter inside its cell, so tiles read as discrete
var SKIN = 0.002; // push slabs just behind the wall plane so edges stay on top
var ROOT = document.getElementById("gt-root");
var CS = getComputedStyle(ROOT);
// `data-composition-variables` is the single owner of every declared
// default — parse it rather than repeating each default a second time
// in JS. `window.__hyperframes` is NOT guaranteed to exist yet when this
// inline script runs, so it can only ever be an override, never the
// source. Precedence: host CSS custom property, runtime variable bag,
// declared default.
var DECL = {};
JSON.parse(ROOT.getAttribute("data-composition-variables") || "[]").forEach(function (v) {
DECL[v.id] = v.default;
});
var HF = window.__hyperframes;
var V = (HF && HF.getVariables && HF.getVariables()) || {};
function raw(id) {
var css = CS.getPropertyValue("--" + id.toLowerCase()).trim();
if (css !== "") return css;
if (V[id] !== undefined && V[id] !== "") return V[id];
return DECL[id];
}
function num(id) {
var n = parseFloat(raw(id));
return isFinite(n) ? n : 0;
}
function str(id) {
var s = raw(id);
return typeof s === "string" ? s.trim() : "";
}
function list(id) {
return str(id)
.split(",")
.map(function (s) {
return s.trim();
})
.filter(function (s) {
return s !== "";
});
}
var SPEED = num("speed");
var GRID = Math.max(1, Math.min(4, Math.round(num("grid"))));
var FILL = num("fill");
var IMAGE_MIX = num("imageMix");
var FOG_FAR = Math.max(3, Math.min(14.5, num("fog")));
var EDGE_OPACITY = num("edgeOpacity");
var EDGE_COLOR = str("edgeColor");
var BACKDROP = str("backdrop");
var PALETTE = list("palette");
var IMAGE_SRCS = list("images");
document.getElementById("gt-backdrop").style.background = BACKDROP;
// How far behind the camera a segment travels before it recycles. Tying
// it to the fog distance is the whole trick: the recycle boundary lands
// at exactly FOG_FAR, so a segment reappears already fogged to pure
// backdrop and nothing ever pops in at the far end. One owner for the
// invariant — move the fog and the boundary follows.
var BACK = Math.max(0, Math.min(1.5, NUM_SEGMENTS * SEGMENT_DEPTH - FOG_FAR));
// Deterministic 2D integer hash — the only source of "randomness".
// Content at frame N is a pure function of (absolute segment, slot).
function hash2(a, b) {
var h = Math.imul(a | 0, 0x27d4eb2d) ^ Math.imul(b | 0, 0x165667b1);
h = Math.imul(h ^ (h >>> 15), 0x2545f491);
h ^= h >>> 13;
return (h >>> 0) / 4294967296;
}
var THREEJS = window.THREE;
var canvas = document.getElementById("gt-canvas");
var renderer = new THREEJS.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: false,
preserveDrawingBuffer: true,
});
renderer.setPixelRatio(1);
renderer.setSize(W, H, false);
renderer.setClearColor(new THREEJS.Color(BACKDROP), 1);
var scene = new THREEJS.Scene();
scene.fog = new THREEJS.Fog(new THREEJS.Color(BACKDROP), 1, FOG_FAR * SEGMENT_DEPTH);
// Camera sits still at the origin looking down -z; the corridor moves.
// Same picture as a moving camera, and the modulo keeps every coordinate
// bounded no matter how long the clip runs.
var camera = new THREEJS.PerspectiveCamera(FOV, W / H, 0.05, 40);
camera.position.set(0, 0, 0);
camera.lookAt(0, 0, -1);
var PLANE = new THREEJS.PlaneGeometry(1, 1);
var COLOR_MATS = PALETTE.map(function (hex) {
return new THREEJS.MeshBasicMaterial({ color: new THREEJS.Color(hex), fog: true });
});
if (COLOR_MATS.length === 0) {
COLOR_MATS.push(new THREEJS.MeshBasicMaterial({ color: 0xffffff, fog: true }));
}
// Caller-supplied content slots. Real <img> elements (so the page load
// event waits for the decode) used directly as WebGL textures.
var assets = document.getElementById("gt-assets");
var IMAGES = IMAGE_SRCS.map(function (src, k) {
var el = document.createElement("img");
el.id = "gt-img-" + k;
el.alt = "";
el.src = src;
assets.appendChild(el);
var tex = new THREEJS.Texture(el);
tex.minFilter = THREEJS.LinearFilter;
tex.magFilter = THREEJS.LinearFilter;
tex.generateMipmaps = false;
var rec = {
aspect: 0,
mat: new THREEJS.MeshBasicMaterial({ map: tex, fog: true }),
};
function ready() {
if (!el.naturalWidth) return;
rec.aspect = el.naturalWidth / el.naturalHeight;
tex.needsUpdate = true;
}
el.addEventListener("load", ready);
ready(); // a cache hit can complete before the listener is attached
return rec;
});
var edgeMat = new THREEJS.MeshBasicMaterial({
color: new THREEJS.Color(EDGE_COLOR),
fog: true,
transparent: true,
opacity: EDGE_OPACITY,
});
var HW = TUNNEL_WIDTH / 2;
var HH = TUNNEL_HEIGHT / 2;
var cellZ = SEGMENT_DEPTH / GRID;
var cellX = TUNNEL_WIDTH / GRID;
var cellY = TUNNEL_HEIGHT / GRID;
// Four faces of a segment box. `place` maps a cell to a world position
// inside the segment (origin at the segment's near boundary, growing
// toward -z); `rot` orients the unit plane into that face, inward-facing.
// `cw`/`ch` are the cell's extent in the plane's own local axes.
var FACES = [
{
// floor
rot: [-Math.PI / 2, 0, 0],
cw: cellX,
ch: cellZ,
place: function (iu, iz) {
return [-HW + (iu + 0.5) * cellX, -HH - SKIN, -(iz + 0.5) * cellZ];
},
},
{
// ceiling
rot: [Math.PI / 2, 0, 0],
cw: cellX,
ch: cellZ,
place: function (iu, iz) {
return [-HW + (iu + 0.5) * cellX, HH + SKIN, -(iz + 0.5) * cellZ];
},
},
{
// left wall
rot: [0, Math.PI / 2, 0],
cw: cellZ,
ch: cellY,
place: function (iu, iz) {
return [-HW - SKIN, -HH + (iu + 0.5) * cellY, -(iz + 0.5) * cellZ];
},
},
{
// right wall
rot: [0, -Math.PI / 2, 0],
cw: cellZ,
ch: cellY,
place: function (iu, iz) {
return [HW + SKIN, -HH + (iu + 0.5) * cellY, -(iz + 0.5) * cellZ];
},
},
];
var UP = new THREEJS.Vector3(0, 1, 0);
// Edges are solid tube geometry, not lines: a cylinder thickens with
// perspective the way a real moulding does, which is the whole reason
// the corridor reads as built rather than drawn.
function tube(group, ax, ay, az, bx, by, bz) {
var dx = bx - ax,
dy = by - ay,
dz = bz - az;
var len = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (len < 1e-6) return;
var mesh = new THREEJS.Mesh(
new THREEJS.CylinderGeometry(LINE_RADIUS, LINE_RADIUS, len, 6, 1),
edgeMat,
);
mesh.position.set((ax + bx) / 2, (ay + by) / 2, (az + bz) / 2);
mesh.quaternion.setFromUnitVectors(UP, new THREEJS.Vector3(dx / len, dy / len, dz / len));
group.add(mesh);
}
function buildEdges(group) {
var D = SEGMENT_DEPTH;
var k, j, u, z;
// The four corner runs (and any interior wall divisions) come from the
// side walls; floor/ceiling only contribute their interior lines, so
// corners are drawn once and never double-blend at 50% opacity.
for (k = 1; k < GRID; k++) {
u = -HW + k * cellX;
tube(group, u, -HH, 0, u, -HH, -D);
tube(group, u, HH, 0, u, HH, -D);
}
for (k = 0; k <= GRID; k++) {
u = -HH + k * cellY;
tube(group, -HW, u, 0, -HW, u, -D);
tube(group, HW, u, 0, HW, u, -D);
}
// Ring lines at each depth boundary. The far boundary belongs to the
// next segment, so stop one short and the tiling stays seamless.
for (j = 0; j < GRID; j++) {
z = -j * cellZ;
tube(group, -HW, -HH, z, HW, -HH, z);
tube(group, -HW, HH, z, HW, HH, z);
tube(group, -HW, -HH, z, -HW, HH, z);
tube(group, HW, -HH, z, HW, HH, z);
}
}
var segs = [];
for (var i = 0; i < NUM_SEGMENTS; i++) {
var group = new THREEJS.Group();
var panels = [];
for (var f = 0; f < FACES.length; f++) {
var face = FACES[f];
for (var iz = 0; iz < GRID; iz++) {
for (var iu = 0; iu < GRID; iu++) {
var mesh = new THREEJS.Mesh(PLANE, COLOR_MATS[0]);
var p = face.place(iu, iz);
mesh.position.set(p[0], p[1], p[2]);
mesh.rotation.set(face.rot[0], face.rot[1], face.rot[2]);
mesh.visible = false;
group.add(mesh);
panels.push({ mesh: mesh, cw: face.cw * INSET, ch: face.ch * INSET });
}
}
}
buildEdges(group);
scene.add(group);
segs.push({ group: group, panels: panels });
}
// Contain-fit so a supplied screenshot keeps its aspect inside the slot
// instead of being squashed to the cell (wall cells are portrait).
function fit(p, ar) {
if (!ar) {
p.mesh.scale.set(p.cw, p.ch, 1);
return;
}
if (ar > p.cw / p.ch) p.mesh.scale.set(p.cw, p.cw / ar, 1);
else p.mesh.scale.set(p.ch * ar, p.ch, 1);
}
// Everything about frame t is computed from t alone. `d` is the distance
// flown in segments; `a` is the ABSOLUTE segment number occupying pool
// slot i right now, which is what content hashes and the every-other
// rhythm key off — so a segment carries the same panels every time it is
// looked at, no matter how the timeline is scrubbed.
function draw(t) {
var d = SPEED * t;
for (var i = 0; i < NUM_SEGMENTS; i++) {
var seg = segs[i];
var a = i + NUM_SEGMENTS * Math.ceil((d - i - BACK) / NUM_SEGMENTS);
seg.group.position.z = -(a - d) * SEGMENT_DEPTH;
var lit = a % 2 === 0;
var panels = seg.panels;
for (var s = 0; s < panels.length; s++) {
var p = panels[s];
if (!lit || hash2(a, s * 4 + 1) >= FILL) {
p.mesh.visible = false;
continue;
}
p.mesh.visible = true;
if (IMAGES.length > 0 && hash2(a, s * 4 + 2) < IMAGE_MIX) {
var im = IMAGES[Math.floor(hash2(a, s * 4 + 4) * IMAGES.length) % IMAGES.length];
p.mesh.material = im.mat;
fit(p, im.aspect);
} else {
var ci = Math.floor(hash2(a, s * 4 + 3) * COLOR_MATS.length) % COLOR_MATS.length;
p.mesh.material = COLOR_MATS[ci];
p.mesh.scale.set(p.cw, p.ch, 1);
}
}
}
renderer.render(scene, camera);
}
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// Repaint from a property SETTER, not from onUpdate: gsap's seek(t)
// suppresses events by default, so an onUpdate callback silently never
// fires on a scrub and the canvas freezes on frame 0. Tweened values are
// always written during render, suppressed or not.
var driver = { _t: 0 };
Object.defineProperty(driver, "t", {
get: function () {
return this._t;
},
set: function (v) {
this._t = v;
draw(v);
},
});
tl.to(driver, { t: DUR, duration: DUR, ease: "none" }, 0);
window.__timelines["gallery-tunnel"] = tl;
draw(0);
})();
</script>
</body>
</html>
```
</Accordion>
{/* hf:generated-footer */}
Tagged `webgl` `3d` `background` `showcase` `depth` `loop`.
@@ -1013,3 +563,7 @@ Tagged `webgl` `3d` `background` `showcase` `depth` `loop`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+419
View File
@@ -0,0 +1,419 @@
---
title: "Glass Shard Title"
description: "Glass shards fly in through fog and tile themselves into the headline."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
<CatalogDetail
previewSrc="/public/catalog/blocks/glass-shard-title.json"
compositionId="glass-shard-title"
compositionSrc="compositions/glass-shard-title/glass-shard-title.html"
title="Glass Shard Title"
description="Glass shards fly in through fog and tile themselves into the headline."
variables={[{"id":"headline","type":"string","label":"Headline","default":"Designed in glass","maxLength":40},{"id":"tileCount","type":"number","label":"Tiles","default":8,"min":1,"max":400,"step":1},{"id":"roundness","type":"number","label":"Roundness (0-1)","default":0.4,"min":0,"max":1,"step":0.01},{"id":"bevel","type":"number","label":"Bevel (0-1)","default":0.35,"min":0,"max":1,"step":0.01},{"id":"meshSmooth","type":"number","label":"Surface smoothness (0-1)","default":0.6,"min":0,"max":1,"step":0.01},{"id":"flyInTime","type":"number","label":"Fly-in time (s)","default":2.3,"min":0.2,"max":6,"step":0.05},{"id":"flyOutTime","type":"number","label":"Fly-out time (s)","default":2.4,"min":0.2,"max":6,"step":0.05},{"id":"stagger","type":"number","label":"Stagger (0-1)","default":0.27,"min":0,"max":1,"step":0.01},{"id":"flyInRotation","type":"number","label":"Fly-in rotation (0-60)","description":"Amount of inbound tumbling over the entire flight. 0 disables flight rotation; final orientation lands with each shard position.","default":5,"min":0,"max":60,"step":0.1},{"id":"flyOutRotation","type":"number","label":"Fly-out rotation (0-6)","default":6,"min":0,"max":6,"step":0.1},{"id":"chaos","type":"number","label":"Outline chaos (0-1)","default":0.25,"min":0,"max":1,"step":0.01},{"id":"padding","type":"number","label":"Edge padding (0-1)","default":1,"min":0,"max":1,"step":0.01},{"id":"sizeVariance","type":"number","label":"Size variance (0-1)","default":0.35,"min":0,"max":1,"step":0.01},{"id":"gap","type":"number","label":"Tile gap","default":0.03,"min":0,"max":0.3,"step":0.002},{"id":"easePow","type":"number","label":"Fly-in position ease (1-10)","description":"Shapes lateral and depth travel. Rotation uses the same staggered flight window with a gentle settle through arrival.","default":10,"min":1,"max":10,"step":0.1},{"id":"sideDist","type":"number","label":"Side fly distance","default":3,"min":0,"max":60,"step":0.5},{"id":"zDist","type":"number","label":"Z fly-from (negative)","default":-42,"min":-400,"max":0,"step":2},{"id":"zOut","type":"number","label":"Z fly-out to (+ = past camera)","default":14,"min":-400,"max":60,"step":2},{"id":"fog","type":"number","label":"Fog distance (0 = off)","default":36,"min":0,"max":400,"step":2}]}
meta={{"duration":12.16,"width":1920,"height":1080,"category":"3D motion","badge":"Stable","codeLines":348}}
attribution={{"path":"registry/blocks/glass-shard-title","tags":["3d-motion","title-card","glass","voronoi","shards","fog","hdr"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/glass-shard-title/glass-shard-title.html"
>
<CatalogSlot slot="code">
```html glass-shard-title.html
<!doctype html>
<html
lang="en"
data-composition-variables='[{"id":"headline","type":"string","label":"Headline","default":"Designed in glass","maxLength":40},{"id":"tileCount","type":"number","label":"Tiles","default":8,"min":1,"max":400,"step":1},{"id":"roundness","type":"number","label":"Roundness (0-1)","default":0.4,"min":0,"max":1,"step":0.01},{"id":"bevel","type":"number","label":"Bevel (0-1)","default":0.35,"min":0,"max":1,"step":0.01},{"id":"meshSmooth","type":"number","label":"Surface smoothness (0-1)","default":0.6,"min":0,"max":1,"step":0.01},{"id":"flyInTime","type":"number","label":"Fly-in time (s)","default":2.3,"min":0.2,"max":6,"step":0.05},{"id":"flyOutTime","type":"number","label":"Fly-out time (s)","default":2.4,"min":0.2,"max":6,"step":0.05},{"id":"stagger","type":"number","label":"Stagger (0-1)","default":0.27,"min":0,"max":1,"step":0.01},{"id":"flyInRotation","type":"number","label":"Fly-in rotation (0-60)","description":"Amount of inbound tumbling over the entire flight. 0 disables flight rotation; final orientation lands with each shard position.","default":5,"min":0,"max":60,"step":0.1},{"id":"flyOutRotation","type":"number","label":"Fly-out rotation (0-6)","default":6,"min":0,"max":6,"step":0.1},{"id":"chaos","type":"number","label":"Outline chaos (0-1)","default":0.25,"min":0,"max":1,"step":0.01},{"id":"padding","type":"number","label":"Edge padding (0-1)","default":1,"min":0,"max":1,"step":0.01},{"id":"sizeVariance","type":"number","label":"Size variance (0-1)","default":0.35,"min":0,"max":1,"step":0.01},{"id":"gap","type":"number","label":"Tile gap","default":0.03,"min":0,"max":0.3,"step":0.002},{"id":"easePow","type":"number","label":"Fly-in position ease (1-10)","description":"Shapes lateral and depth travel. Rotation uses the same staggered flight window with a gentle settle through arrival.","default":10,"min":1,"max":10,"step":0.1},{"id":"sideDist","type":"number","label":"Side fly distance","default":3,"min":0,"max":60,"step":0.5},{"id":"zDist","type":"number","label":"Z fly-from (negative)","default":-42,"min":-400,"max":0,"step":2},{"id":"zOut","type":"number","label":"Z fly-out to (+ = past camera)","default":14,"min":-400,"max":60,"step":2},{"id":"fog","type":"number","label":"Fog distance (0 = off)","default":36,"min":0,"max":400,"step":2}]'
>
<head>
<script>
/* ?play standalone mode only: this file lives in compositions/ but its
asset paths are project-root-relative (they resolve there when the
runtime inlines it into the host). Never runs for Studio/render. */
if (new URLSearchParams(location.search).has("play")) document.write('<base href="../">');
</script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Glass Shard Title</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-delaunay@6.0.4/dist/d3-delaunay.min.js"></script>
<style>
@font-face {
font-family: "Geist";
src: url("assets/fonts/Geist-Regular.ttf") format("truetype");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "Geist";
src: url("assets/fonts/Geist-SemiBold.ttf") format("truetype");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "Geist";
src: url("assets/fonts/Geist-Bold.ttf") format("truetype");
font-weight: 700;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0f;
}
body {
font-family: "Geist", ui-sans-serif, system-ui, sans-serif;
}
#gst-root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0f; /* flat — no gradients, no glow */
}
.clip {
position: absolute;
inset: 0;
}
#gst-canvas {
display: block;
width: 100%;
height: 100%;
}
#gst-fontload {
position: absolute;
opacity: 0;
pointer-events: none;
}
</style>
</head>
<body>
<div
data-hf-id="hf-51pv"
id="gst-root"
data-composition-id="glass-shard-title"
data-start="0"
data-duration="12.16"
data-width="1920"
data-height="1080"
>
<div
data-hf-id="hf-lsju"
id="gst-canvas-clip"
class="clip"
data-start="0"
data-duration="12.16"
data-track-index="1"
data-media-start="0"
></div>
<div
data-hf-id="hf-uo1i"
id="gst-fontload"
aria-hidden="true"
data-start="0"
data-duration="12.16"
data-media-start="0"
>
<span data-hf-id="hf-kqtg" style="font-weight: 400">Aa</span
><span data-hf-id="hf-7bxm" style="font-weight: 600">Aa</span>
</div>
</div>
<script>
/* ================================================================
glass-shard-title — iridescent glass shards fly in from the left
and right edges, assemble into the headline, idle with a slight
rotation, then fly back out and leave the frame empty.
The ONLY content is the headline. Flat dark background.
Seek-safety: the timeline drives two master progresses (tin, tout)
plus the material resolve; every per-shard position/rotation is a
pure function of those masters and st.time. No rAF, no
integrators, no Math.random(). Shard layout is seeded and built
after fonts load (cell selection samples the rasterised text).
================================================================ */
window.__timelines = window.__timelines || {};
/* Studio's variables/SDK parser fails on any composition with a <canvas>
in its MARKUP (verified by bisection) — so the canvas is created at
runtime instead. Identical for preview/render; invisible to the parser.
Guarded: if this script is evaluated twice in one document a second
<canvas id="gst-canvas"> would be appended, getElementById would keep
returning the FIRST one, and the second three.js renderer would be
built on a canvas that already owns a live WebGL context. */
(function () {
if (document.getElementById("gst-canvas")) return;
var c = document.createElement("canvas");
c.id = "gst-canvas";
c.width = 1920;
c.height = 1080;
document.getElementById("gst-canvas-clip").appendChild(c);
})();
window.gstMulberry32 = gstMulberry32;
function gstMulberry32(seed) {
return function () {
var s = (seed = (seed + 0x6d2b79f5) | 0);
var t = Math.imul(s ^ (s >>> 15), 1 | s);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
(function () {
var DUR = 9;
/* ---- Studio variables ---- */
var vars = {
headline: "Designed in glass",
tileCount: 8,
roundness: 0.4,
bevel: 0.35,
meshSmooth: 0.6,
flyInTime: 2.3,
flyOutTime: 2.4,
stagger: 0.27,
flyInRotation: 5,
flyOutRotation: 6,
chaos: 0.25,
padding: 1,
sizeVariance: 0.35,
gap: 0.03,
easePow: 10,
sideDist: 3,
zDist: -42,
zOut: 14,
fog: 36,
};
try {
if (window.__hyperframes && window.__hyperframes.getVariables) {
var v = window.__hyperframes.getVariables() || {};
if (v.headline) vars.headline = String(v.headline);
if (v.title) vars.headline = String(v.title); /* legacy id */
if (v.tileCount !== undefined && isFinite(Number(v.tileCount)))
vars.tileCount = Number(v.tileCount);
if (v.roundness !== undefined && isFinite(Number(v.roundness)))
vars.roundness = Number(v.roundness);
if (v.bevel !== undefined && isFinite(Number(v.bevel))) vars.bevel = Number(v.bevel);
if (v.meshSmooth !== undefined && isFinite(Number(v.meshSmooth)))
vars.meshSmooth = Number(v.meshSmooth);
if (v.flyInTime !== undefined && isFinite(Number(v.flyInTime)))
vars.flyInTime = Number(v.flyInTime);
if (v.flyOutTime !== undefined && isFinite(Number(v.flyOutTime)))
vars.flyOutTime = Number(v.flyOutTime);
if (v.stagger !== undefined && isFinite(Number(v.stagger)))
vars.stagger = Number(v.stagger);
/* Legacy callers may still provide spin. New controls override it
independently; the old multiplier/default keeps its meaning. */
if (v.spin !== undefined && isFinite(Number(v.spin))) {
vars.flyInRotation = vars.flyOutRotation = Math.max(0, Math.min(6, Number(v.spin)));
}
if (v.flyInRotation !== undefined && isFinite(Number(v.flyInRotation)))
vars.flyInRotation = Number(v.flyInRotation);
if (v.flyOutRotation !== undefined && isFinite(Number(v.flyOutRotation)))
vars.flyOutRotation = Number(v.flyOutRotation);
if (v.zOut !== undefined && isFinite(Number(v.zOut))) vars.zOut = Number(v.zOut);
if (v.chaos !== undefined && isFinite(Number(v.chaos))) vars.chaos = Number(v.chaos);
if (v.padding !== undefined && isFinite(Number(v.padding)))
vars.padding = Number(v.padding);
if (v.sizeVariance !== undefined && isFinite(Number(v.sizeVariance)))
vars.sizeVariance = Number(v.sizeVariance);
if (v.gap !== undefined && isFinite(Number(v.gap))) vars.gap = Number(v.gap);
if (v.easePow !== undefined && isFinite(Number(v.easePow)))
vars.easePow = Number(v.easePow);
if (v.sideDist !== undefined && isFinite(Number(v.sideDist)))
vars.sideDist = Number(v.sideDist);
if (v.zDist !== undefined && isFinite(Number(v.zDist))) vars.zDist = Number(v.zDist);
if (v.fog !== undefined && isFinite(Number(v.fog))) vars.fog = Number(v.fog);
}
} catch (e) {
/* keep defaults */
}
vars.tileCount = Math.max(1, Math.min(400, Math.round(vars.tileCount)));
vars.roundness = Math.max(0, Math.min(1, vars.roundness));
vars.bevel = Math.max(0, Math.min(1, vars.bevel));
vars.meshSmooth = Math.max(0, Math.min(1, vars.meshSmooth));
vars.flyInTime = Math.max(0.2, Math.min(6, vars.flyInTime));
vars.flyOutTime = Math.max(0.2, Math.min(6, vars.flyOutTime));
vars.stagger = Math.max(0, Math.min(1, vars.stagger));
vars.flyInRotation = Math.max(0, Math.min(60, vars.flyInRotation));
vars.flyOutRotation = Math.max(0, Math.min(6, vars.flyOutRotation));
vars.zOut = Math.max(-400, Math.min(60, vars.zOut));
vars.chaos = Math.max(0, Math.min(1, vars.chaos));
vars.padding = Math.max(0, Math.min(1, vars.padding));
vars.sizeVariance = Math.max(0, Math.min(1, vars.sizeVariance));
vars.gap = Math.max(0, Math.min(0.3, vars.gap));
vars.easePow = Math.max(1, Math.min(10, vars.easePow));
vars.sideDist = Math.max(0, Math.min(60, vars.sideDist));
/* zDist is a coordinate: accept "-200" style input, clamp behind pane. */
vars.zDist = Math.min(0, Math.max(-400, vars.zDist > 0 ? -vars.zDist : vars.zDist));
vars.fog = Math.max(0, Math.min(400, vars.fog));
/* ---- Phase windows (speed compresses fly phases, hold absorbs) ---- */
var IN_START = 0.12;
var OUT_END = DUR - 0.35;
/* Explicit flight durations; if in + out + a minimum hold exceed the
composition, both scale down proportionally. */
var MIN_HOLD = 0.5;
var avail = OUT_END - IN_START - MIN_HOLD;
var tIn = vars.flyInTime,
tOut = vars.flyOutTime;
if (tIn + tOut > avail) {
var k = avail / (tIn + tOut);
tIn *= k;
tOut *= k;
}
var IN_WINDOW = tIn;
var OUT_WINDOW = tOut;
var OUT_START = OUT_END - OUT_WINDOW;
/* Scripted state read by renderScene. */
var st = {
time: 0,
tin: 0 /* master fly-in progress 0..1 */,
tout: 0 /* master fly-out progress 0..1 */,
thickness: 0.5,
roughness: 0.07,
ior: 1.52,
};
window.__GST = { DUR: DUR, st: st, vars: vars };
/* ---- Timeline: paused, registered, hf-seek wired ---- */
var tl = gsap.timeline({ paused: true });
tl.eventCallback("onUpdate", function () {
if (window.__gstRender) window.__gstRender();
});
/* Studio's player seeks with GSAP events suppressed and dispatches an
hf-seek CustomEvent instead — re-sync and repaint so the stage follows. */
/* Exactly one listener and one live timeline per document. A second
evaluation of this script would otherwise stack another hf-seek
listener and leave the previous paused timeline registered, so every
seek would drive N timelines and repaint N times. Declared as a
named function (not an arrow) so the reference read back off window
is identical and removeEventListener actually matches. */
function gstOnSeek(event) {
var d = event && event.detail;
if (d && typeof d.time === "number")
tl.totalTime(Math.min(d.time, tl.totalDuration()), true);
var fn = tl.eventCallback("onUpdate");
if (fn) fn();
}
if (window.__gstSeekHandler) window.removeEventListener("hf-seek", window.__gstSeekHandler);
if (window.__gstTimeline && window.__gstTimeline !== tl) {
try {
window.__gstTimeline.kill();
} catch (e) {
/* best effort */
}
}
window.__gstSeekHandler = gstOnSeek;
window.__gstTimeline = tl;
window.addEventListener("hf-seek", gstOnSeek);
/* Release the listener and the timeline with the document. Skipped for
a bfcache'd page, which is coming back with this stage still live. */
window.addEventListener("pagehide", function (event) {
if (event && event.persisted === true) return;
window.removeEventListener("hf-seek", gstOnSeek);
window.__gstSeekHandler = null;
window.__gstTimeline = null;
try {
tl.kill();
} catch (e) {
/* best effort */
}
});
tl.to(st, { time: DUR, duration: DUR, ease: "none" }, 0);
tl.to(st, { tin: 1, duration: IN_WINDOW, ease: "none" }, IN_START);
tl.to(st, { tout: 1, duration: OUT_WINDOW, ease: "none" }, OUT_START);
/* Glass stays clear throughout. The old frosted->clear resolve made
still-flying shards transmit a BLURRED copy of the already-seated
text — the ghost headline — so it is gone. */
window.__timelines["glass-shard-title"] = tl;
})();
</script>
<script src="assets/glass-main.js"></script>
<script>
/* Standalone playback: open this file with ?play (add &loop to repeat).
Inert without the param — Studio and the renderer never add it, so
editing and deterministic renders are unaffected. */
(function () {
var q = new URLSearchParams(location.search);
if (!q.has("play")) return;
var tick = setInterval(function () {
var map = window.__timelines || {};
var ids = Object.keys(map);
if (!ids.length) return;
clearInterval(tick);
for (var i = 0; i < ids.length; i++) {
var tl = map[ids[i]];
if (q.has("loop")) tl.repeat(-1);
tl.play(0);
}
}, 100);
})();
</script>
</body>
</html>
```
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add glass-shard-title" item="glass-shard-title" />
That writes `compositions/glass-shard-title/glass-shard-title.html`, plus 11 supporting files under `compositions/glass-shard-title/assets/`, `compositions/glass-shard-title/assets/fonts/`, and `compositions/glass-shard-title/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 12.16 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="glass-shard-title"
data-composition-src="compositions/glass-shard-title/glass-shard-title.html"
data-start="0"
data-duration="12.16"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `3d-motion` `title-card` `glass` `voronoi` `shards` `fog` `hdr`.
## Related topics
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Glitch"
description: "Shader transition with digital glitch artifacts"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/glitch.json"
compositionId="glitch"
compositionSrc="compositions/glitch.html"
title="Glitch"
description="Shader transition with digital glitch artifacts"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Shader Transitions","badge":"Stable","codeLines":366}}
attribution={{"path":"registry/blocks/glitch","tags":["transition","shader"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/glitch/glitch.html"
>
<InstallCommand command="npx hyperframes add glitch" item="glitch" />
<CatalogSlot slot="code">
That writes one file: `compositions/glitch.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="glitch preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/glitch.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/glitch.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="glitch.html">
```html
```html glitch.html
<!doctype html>
<html lang="en">
<head>
@@ -391,16 +391,37 @@ That writes one file: `compositions/glitch.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add glitch" item="glitch" />
```html
<div data-composition-id="glitch" data-composition-src="compositions/glitch.html" data-start="0" data-duration="4" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/glitch.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="glitch"
data-composition-src="compositions/glitch.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `transition` `shader`.
@@ -410,3 +431,7 @@ Tagged `transition` `shader`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+46 -21
View File
@@ -1,28 +1,28 @@
---
title: "Gravitational Lens"
description: "Shader transition with gravitational lensing distortion"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/gravitational-lens.json"
compositionId="gravitational-lens"
compositionSrc="compositions/gravitational-lens.html"
title="Gravitational Lens"
description="Shader transition with gravitational lensing distortion"
variables={[]}
meta={{"duration":4,"width":1920,"height":1080,"category":"Shader Transitions","badge":"Stable","codeLines":365}}
attribution={{"path":"registry/blocks/gravitational-lens","tags":["transition","shader"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/gravitational-lens/gravitational-lens.html"
>
<InstallCommand command="npx hyperframes add gravitational-lens" item="gravitational-lens" />
<CatalogSlot slot="code">
That writes one file: `compositions/gravitational-lens.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="gravitational-lens preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/gravitational-lens.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/gravitational-lens.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Source
<Accordion title="gravitational-lens.html">
```html
```html gravitational-lens.html
<!doctype html>
<html lang="en">
<head>
@@ -390,16 +390,37 @@ That writes one file: `compositions/gravitational-lens.html`.
</html>
```
</Accordion>
</CatalogSlot>
## Usage
<CatalogSlot slot="install">
After installing, add the block to your host composition:
<InstallCommand command="npx hyperframes add gravitational-lens" item="gravitational-lens" />
```html
<div data-composition-id="gravitational-lens" data-composition-src="compositions/gravitational-lens.html" data-start="0" data-duration="4" data-track-index="1" data-width="1920" data-height="1080"></div>
That writes one file: `compositions/gravitational-lens.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 4 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="gravitational-lens"
data-composition-src="compositions/gravitational-lens.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
Tagged `transition` `shader`.
@@ -409,3 +430,7 @@ Tagged `transition` `shader`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+26 -396
View File
@@ -1,24 +1,27 @@
---
title: "Halftone Field"
description: "A full-bleed halftone light field: a grid of dots whose size and colour track a slow flowing simplex-noise field, so broad bands of light sweep across a mostly-black frame like a giant LED wall showing a plasma. Built to run under type."
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
import { VariablesExplorer } from "/snippets/variables-explorer.jsx";
## Install
<InstallCommand command="npx hyperframes add halftone-field" item="halftone-field" />
That writes one file: `compositions/halftone-field.html`.
<VariablesExplorer
<CatalogDetail
previewSrc="/public/catalog/blocks/halftone-field.json"
compositionId="halftone-field"
compositionSrc="compositions/halftone-field.html"
title="Halftone Field"
description="A full-bleed halftone light field: a grid of dots whose size and colour track a slow flowing simplex-noise field, so broad bands of light sweep across a mostly-black frame like a giant LED wall showing a plasma. Built to run under type."
variables={[{"id":"frequency","type":"number","label":"Field frequency","default":2,"min":1,"max":10,"step":0.1},{"id":"speed","type":"number","label":"Flow speed","default":5,"min":0,"max":10,"step":0.1},{"id":"cellSize","type":"number","label":"Cell size","default":54,"min":1,"max":100,"step":1},{"id":"gamma","type":"number","label":"Gamma (midtone crush)","default":12,"min":1,"max":20,"step":0.1},{"id":"paletteBias","type":"number","label":"Palette bias","default":2,"min":0,"max":20,"step":0.5},{"id":"palette1","type":"color","label":"Palette stop 1 (lows)","default":"#00AAFF"},{"id":"palette2","type":"color","label":"Palette stop 2","default":"#C2FF67"},{"id":"palette3","type":"color","label":"Palette stop 3","default":"#6600FF"},{"id":"palette4","type":"color","label":"Palette stop 4 (crests)","default":"#000000"},{"id":"background","type":"color","label":"Background","default":"#000000"},{"id":"quantize","type":"boolean","label":"Quantise to 30fps (stepped)","default":false}]}
meta={{"duration":10,"width":1920,"height":1080,"category":"Texture","badge":"Experimental","codeLines":378}}
attribution={{"path":"registry/blocks/halftone-field","tags":["webgl","shader","background","texture","halftone"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/halftone-field/halftone-field.html"
>
<CatalogSlot slot="code">
```html halftone-field.html
<!doctype html>
<html lang="en">
@@ -400,7 +403,17 @@ void main() {
</html>
```
</VariablesExplorer>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add halftone-field" item="halftone-field" />
That writes one file: `compositions/halftone-field.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
@@ -467,393 +480,6 @@ defaults, so this behaves exactly like the preview above until you change one:
></div>
```
## Source
<Accordion title="halftone-field.html">
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Halftone Field</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
overflow: hidden;
}
#hlf-root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
}
#hlf-backdrop {
position: absolute;
inset: 0;
background: #000000;
}
#hlf-canvas {
position: absolute;
top: 0;
left: 0;
width: 1920px;
height: 1080px;
}
</style>
</head>
<body>
<div
id="hlf-root"
data-composition-id="halftone-field"
data-root="true"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="10"
data-composition-variables='[
{"id":"frequency","type":"number","label":"Field frequency","default":2,"min":1,"max":10,"step":0.1},
{"id":"speed","type":"number","label":"Flow speed","default":5,"min":0,"max":10,"step":0.1},
{"id":"cellSize","type":"number","label":"Cell size","default":54,"min":1,"max":100,"step":1},
{"id":"gamma","type":"number","label":"Gamma (midtone crush)","default":12,"min":1,"max":20,"step":0.1},
{"id":"paletteBias","type":"number","label":"Palette bias","default":2,"min":0,"max":20,"step":0.5},
{"id":"palette1","type":"color","label":"Palette stop 1 (lows)","default":"#00AAFF"},
{"id":"palette2","type":"color","label":"Palette stop 2","default":"#C2FF67"},
{"id":"palette3","type":"color","label":"Palette stop 3","default":"#6600FF"},
{"id":"palette4","type":"color","label":"Palette stop 4 (crests)","default":"#000000"},
{"id":"background","type":"color","label":"Background","default":"#000000"},
{"id":"quantize","type":"boolean","label":"Quantise to 30fps (stepped)","default":false}
]'
>
<div id="hlf-backdrop"></div>
<canvas id="hlf-canvas" width="1920" height="1080"></canvas>
<!-- Driver clip: gives HyperFrames a timed element to own on track 0. -->
<div
id="hlf-drv"
class="clip"
data-start="0"
data-duration="10"
data-track-index="0"
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
></div>
</div>
<script>
(function () {
var DUR = 10;
var W = 1920;
var H = 1080;
var V = (window.__hyperframes && window.__hyperframes.getVariables()) || {};
var CS = getComputedStyle(document.getElementById("hlf-root"));
// The runtime defines every declared variable as `--<id>` on the root,
// so a host stylesheet can override one there too. Read the custom
// property first, fall back to the declared value when it is unset.
function raw(id) {
var css = CS.getPropertyValue("--" + id.toLowerCase()).trim();
return css !== "" ? css : V[id];
}
function num(id, fallback) {
var n = parseFloat(raw(id));
return isFinite(n) ? n : fallback;
}
function bool(id) {
var v = raw(id);
return v === true || /^(true|1|on|yes)$/i.test(String(v));
}
// #RGB / #RRGGBB -> [r, g, b] in 0..1. Anything else falls back.
function rgb(id, fallback) {
var s = String(raw(id) || "").trim();
var m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(s);
if (!m) s = fallback;
else s = "#" + m[1];
var hex = s.slice(1);
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
var n = parseInt(hex, 16);
return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
}
// The reference exposes 1-10 / 1-100 / 1-20 dials and maps them onto
// the ranges the shader actually wants.
function mapLinear(x, a1, a2, b1, b2) {
return b1 + ((x - a1) * (b2 - b1)) / (a2 - a1);
}
var FREQ = mapLinear(num("frequency", 2), 1, 10, 0.3, 6);
var SPEED = num("speed", 5) * 0.05;
var CELL = mapLinear(num("cellSize", 54), 1, 100, 6, 60);
var GAMMA = mapLinear(num("gamma", 12), 1, 20, 0.5, 8);
var BIAS = num("paletteBias", 2) * 0.05;
var PAL = [
rgb("palette1", "#00AAFF"),
rgb("palette2", "#C2FF67"),
rgb("palette3", "#6600FF"),
rgb("palette4", "#000000"),
];
var BG = rgb("background", "#000000");
var QUANTIZE = bool("quantize");
document.getElementById("hlf-backdrop").style.background =
"rgb(" +
BG.map(function (c) {
return Math.round(c * 255);
}).join(",") +
")";
var canvas = document.getElementById("hlf-canvas");
var gl =
canvas.getContext("webgl", {
alpha: false,
antialias: false,
depth: false,
stencil: false,
preserveDrawingBuffer: true,
powerPreference: "high-performance",
}) ||
canvas.getContext("experimental-webgl", {
alpha: false,
preserveDrawingBuffer: true,
});
var VERT = "attribute vec2 aPos;\nvoid main() { gl_Position = vec4(aPos, 0.0, 1.0); }";
// Single pass. The reference renders the colour field into a target and
// samples it at each cell centre, but the field is analytic, so
// evaluating it at the cell centre here is the same value without the
// round trip through a texture.
var FRAG = `
precision highp float;
uniform vec2 uRes;
uniform float uTime;
uniform float uFreq;
uniform float uSpeed;
uniform float uCell;
uniform float uGamma;
uniform float uBias;
uniform vec3 uPal0;
uniform vec3 uPal1;
uniform vec3 uPal2;
uniform vec3 uPal3;
uniform vec3 uBg;
// 3D simplex noise — Ashima Arts / Stefan Gustavson (MIT).
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
float snoise(vec3 v) {
const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - D.yyy;
i = mod289(i);
vec4 p = permute(permute(permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0)) +
i.y + vec4(0.0, i1.y, i2.y, 1.0)) +
i.x + vec4(0.0, i1.x, i2.x, 1.0));
float n_ = 0.142857142857;
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_);
vec4 x = x_ * ns.x + ns.yyyy;
vec4 y = y_ * ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0) * 2.0 + 1.0;
vec4 s1 = floor(b1) * 2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
m = m * m;
return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
}
// Full-saturation, full-value HSV: only the hue ramp survives.
vec3 hue2rgb(float h) {
return clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
}
// Piecewise-linear ramp across the four palette stops.
vec3 ramp(float v) {
float x = clamp(v, 0.0, 1.0) * 3.0;
vec3 c = mix(uPal0, uPal1, clamp(x, 0.0, 1.0));
c = mix(c, uPal2, clamp(x - 1.0, 0.0, 1.0));
c = mix(c, uPal3, clamp(x - 2.0, 0.0, 1.0));
return c;
}
void main() {
// One sample per cell, taken at the cell centre.
vec2 cell = floor(gl_FragCoord.xy / uCell);
vec2 centre = (cell + 0.5) * uCell;
vec2 st = centre / uRes;
st.x *= uRes.x / uRes.y;
float hue = abs(snoise(vec3(st * uFreq, uTime * uSpeed)));
vec3 field = hue2rgb(hue);
// The gamma is the whole look: it crushes the midtones so most of the frame
// stays background and only the crests of the field open up.
float gray = pow(dot(field, vec3(0.2126, 0.7152, 0.0722)), uGamma);
float v = clamp(gray + uBias, 0.0, 1.0);
float radius = v * 0.5 * uCell;
float d = length(gl_FragCoord.xy - centre);
float mask = 1.0 - smoothstep(radius - 1.0, radius + 1.0, d);
gl_FragColor = vec4(mix(uBg, ramp(v), mask), 1.0);
}
`;
var uni = {};
var ready = false;
function compile(type, src) {
var sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
throw new Error("halftone-field shader: " + gl.getShaderInfoLog(sh));
}
return sh;
}
if (gl) {
var prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
throw new Error("halftone-field link: " + gl.getProgramInfoLog(prog));
}
gl.useProgram(prog);
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
var loc = gl.getAttribLocation(prog, "aPos");
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
[
"uRes",
"uTime",
"uFreq",
"uSpeed",
"uCell",
"uGamma",
"uBias",
"uPal0",
"uPal1",
"uPal2",
"uPal3",
"uBg",
].forEach(function (n) {
uni[n] = gl.getUniformLocation(prog, n);
});
gl.viewport(0, 0, W, H);
gl.uniform2f(uni.uRes, W, H);
gl.uniform1f(uni.uFreq, FREQ);
gl.uniform1f(uni.uSpeed, SPEED);
gl.uniform1f(uni.uCell, CELL);
gl.uniform1f(uni.uGamma, GAMMA);
gl.uniform1f(uni.uBias, BIAS);
gl.uniform3f(uni.uPal0, PAL[0][0], PAL[0][1], PAL[0][2]);
gl.uniform3f(uni.uPal1, PAL[1][0], PAL[1][1], PAL[1][2]);
gl.uniform3f(uni.uPal2, PAL[2][0], PAL[2][1], PAL[2][2]);
gl.uniform3f(uni.uPal3, PAL[3][0], PAL[3][1], PAL[3][2]);
gl.uniform3f(uni.uBg, BG[0], BG[1], BG[2]);
ready = true;
}
// Every frame is computed from t alone — the field is a closed-form
// function of position and time. No accumulator, no clock, no PRNG.
// `quantize` snaps t to a 30fps grid for the stepped LED-wall look.
function draw(t) {
if (!ready) return;
gl.uniform1f(uni.uTime, QUANTIZE ? Math.floor(t * 30) / 30 : t);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.flush();
}
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// The canvas is repainted from a property SETTER, not from onUpdate:
// gsap's seek(t) suppresses events by default, so an onUpdate callback
// silently never fires on a scrub and the canvas freezes on frame 0.
// Tweened values are always written during render, suppressed or not,
// so this fires on every seek — and hands us the frame time directly.
var driver = { _t: 0 };
Object.defineProperty(driver, "t", {
get: function () {
return this._t;
},
set: function (v) {
this._t = v;
draw(v);
},
});
tl.to(driver, { t: DUR, duration: DUR, ease: "none" }, 0);
window.__timelines["halftone-field"] = tl;
draw(0);
})();
</script>
</body>
</html>
```
</Accordion>
{/* hf:generated-footer */}
Tagged `webgl` `shader` `background` `texture` `halftone`.
@@ -863,3 +489,7 @@ Tagged `webgl` `shader` `background` `texture` `halftone`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
@@ -1,42 +1,28 @@
---
title: "HeyGen Avatar Promo Card"
description: "A portrait HeyGen offer card pairs a moving avatar mosaic with bold promotional typography and a branded callout"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/heygen-avatar-promo-card.json"
compositionId="heygen-avatar-promo-card"
compositionSrc="compositions/heygen-avatar-promo-card.html"
title="HeyGen Avatar Promo Card"
description="A portrait HeyGen offer card pairs a moving avatar mosaic with bold promotional typography and a branded callout"
variables={[{"id":"brandLogo","type":"image","role":"content","label":"Brand logo","description":"Horizontal transparent wordmark used in the header and belt.","default":"assets/heygen-logo.svg"},{"id":"titleLine1","type":"string","role":"content","label":"Headline line 1","description":"First headline line.","default":"Your avatar,"},{"id":"titleLine2","type":"string","role":"content","label":"Headline line 2","description":"Second headline line.","default":"by HeyGen."},{"id":"subhead","type":"string","role":"content","label":"Subhead","description":"Supporting line under the headline.","default":"AI video, camera-free."},{"id":"pill1","type":"string","role":"content","label":"Pill 1","description":"First feature pill.","default":"4K AVATARS"},{"id":"pill2","type":"string","role":"content","label":"Pill 2","description":"Second feature pill.","default":"NO CAMERA NEEDED"},{"id":"offerValue","type":"string","role":"content","label":"Offer value","description":"Large offer value in the light tile.","default":"25%"},{"id":"offerSuffix","type":"string","role":"content","label":"Offer suffix","description":"Large offer suffix in the adjacent light tile.","default":"OFF"},{"id":"codeLabel","type":"string","role":"content","label":"Promo code label","description":"Small label above the promo code.","default":"CODE"},{"id":"promoPrefix","type":"string","role":"content","label":"Promo code prefix","description":"Accent-colored promo-code prefix.","default":"GEN"},{"id":"promoSuffix","type":"string","role":"content","label":"Promo code suffix","description":"Light promo-code suffix.","default":"25"},{"id":"canvasColor","type":"color","role":"style","label":"Canvas color","description":"Main card background.","default":"#eaf6fa"},{"id":"inkColor","type":"color","role":"style","label":"Ink color","description":"Primary headline, pill, and offer ink.","default":"#0a1418"},{"id":"mutedInkColor","type":"color","role":"style","label":"Muted ink color","description":"Subhead ink.","default":"#47545b"},{"id":"accentColor","type":"color","role":"style","label":"Accent color","description":"Brand accent used by tiles and promo-code prefix.","default":"#00c3ff"},{"id":"lightTileColor","type":"color","role":"style","label":"Light tile color","description":"Light belt-tile surface.","default":"#f7fcfe"},{"id":"darkTileColor","type":"color","role":"style","label":"Dark tile color","description":"Dark belt-tile surface.","default":"#0b1215"}]}
meta={{"duration":10,"width":1080,"height":1920,"category":"Showcases","badge":"Stable","codeLines":426}}
attribution={{"author":"HeyGen","path":"registry/blocks/heygen-avatar-promo-card","tags":["showcase","avatar","promotion","vertical"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/heygen-avatar-promo-card/heygen-avatar-promo-card.html"
>
<InstallCommand command="npx hyperframes add heygen-avatar-promo-card" item="heygen-avatar-promo-card" />
<CatalogSlot slot="code">
That writes `compositions/heygen-avatar-promo-card.html`, plus 15 supporting files under `assets/` and `assets/fonts/`.
<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/heygen-avatar-promo-card.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/heygen-avatar-promo-card.png" autoPlay muted loop playsInline />
## Add it to your video
It runs for 10 seconds at 1080×1920. Paste this into your composition:
```html index.html
<div
data-composition-id="heygen-avatar-promo-card"
data-composition-src="compositions/heygen-avatar-promo-card.html"
data-start="0"
data-duration="10"
data-track-index="1"
data-width="1080"
data-height="1920"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="heygen-avatar-promo-card.html">
```html
```html heygen-avatar-promo-card.html
<!doctype html>
<html
lang="en"
@@ -465,7 +451,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add heygen-avatar-promo-card" item="heygen-avatar-promo-card" />
That writes `compositions/heygen-avatar-promo-card.html`, plus 15 supporting files under `assets/` and `assets/fonts/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 10 seconds at 1080×1920. Paste this into your composition:
```html index.html
<div
data-composition-id="heygen-avatar-promo-card"
data-composition-src="compositions/heygen-avatar-promo-card.html"
data-start="0"
data-duration="10"
data-track-index="1"
data-width="1080"
data-height="1920"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -478,3 +493,7 @@ Created by HeyGen.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -36
View File
@@ -1,47 +1,28 @@
---
title: "Sketched Frame"
description: "Media in a hand-drawn border with corner doodles and a handwritten caption; the unit tilts slightly and boils (images inside the block; keep live footage in the host)"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/hw-frame.json"
compositionId="hw-frame"
compositionSrc="compositions/hw-frame.html"
title="Sketched Frame"
description="Media in a hand-drawn border with corner doodles and a handwritten caption; the unit tilts slightly and boils (images inside the block; keep live footage in the host)"
variables={[]}
meta={{"duration":8,"width":1920,"height":1080,"category":"Blocks","badge":"Stable","codeLines":302}}
attribution={{"path":"registry/blocks/hw-frame","tags":["handwritten","frame","media"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/hw-frame/hw-frame.html"
>
<InstallCommand command="npx hyperframes add hw-frame" item="hw-frame" />
<CatalogSlot slot="code">
That writes `compositions/hw-frame.html`, plus 1 supporting file under `assets/fonts/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="hw-frame preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/hw-frame.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/hw-frame.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 8 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-frame"
data-composition-src="compositions/hw-frame.html"
data-start="0"
data-duration="8"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="hw-frame.html">
```html
```html hw-frame.html
<!doctype html>
<html lang="en">
<head>
@@ -346,7 +327,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add hw-frame" item="hw-frame" />
That writes `compositions/hw-frame.html`, plus 1 supporting file under `assets/fonts/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 8 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-frame"
data-composition-src="compositions/hw-frame.html"
data-start="0"
data-duration="8"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -357,3 +367,7 @@ Tagged `handwritten` `frame` `media`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+54 -39
View File
@@ -1,47 +1,28 @@
---
title: "Text Along a Path"
description: "Handwriting flowing along any SVG path with a per-character reveal; the path is the only geometry input"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/hw-path-text.json"
compositionId="hw-path-text"
compositionSrc="compositions/hw-path-text.html"
title="Text Along a Path"
description="Handwriting flowing along any SVG path with a per-character reveal; the path is the only geometry input"
variables={[]}
meta={{"duration":6,"width":1920,"height":1080,"category":"Typography & Text","badge":"Stable","codeLines":194}}
attribution={{"path":"registry/blocks/hw-path-text","tags":["handwritten","typography","path","overlay"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/hw-path-text/hw-path-text.html"
>
<InstallCommand command="npx hyperframes add hw-path-text" item="hw-path-text" />
<CatalogSlot slot="code">
That writes `compositions/hw-path-text.html`, plus 1 supporting file under `assets/fonts/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="hw-path-text preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/hw-path-text.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/hw-path-text.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-path-text"
data-composition-src="compositions/hw-path-text.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="hw-path-text.html">
```html
```html hw-path-text.html
<!doctype html>
<html lang="en">
<head>
@@ -190,8 +171,10 @@ Move it in time with `data-start`. Put it on a different timeline row with
});
};
document.getElementById("hw-pt-path").setAttribute("d", CONFIG.d);
if (CONFIG.showGuide) document.getElementById("hw-pt-guide").setAttribute("d", CONFIG.d);
var pathEl = document.getElementById("hw-pt-path");
var guide = document.getElementById("hw-pt-guide");
pathEl.setAttribute("d", CONFIG.d);
if (CONFIG.showGuide) guide.setAttribute("d", CONFIG.d);
document.getElementById("hw-pt-text").setAttribute("font-size", CONFIG.fontSize);
/* per-char tspans along the path */
@@ -206,7 +189,6 @@ Move it in time with `data-start`. Put it on a different timeline row with
var tl = gsap.timeline({ paused: true });
/* guide draws first, then the handwriting types along it */
var guide = document.getElementById("hw-pt-guide");
if (CONFIG.showGuide) {
var glen = guide.getTotalLength();
gsap.set(guide, { strokeDasharray: "2 14", strokeDashoffset: glen });
@@ -237,7 +219,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add hw-path-text" item="hw-path-text" />
That writes `compositions/hw-path-text.html`, plus 1 supporting file under `assets/fonts/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-path-text"
data-composition-src="compositions/hw-path-text.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -248,3 +259,7 @@ Tagged `handwritten` `typography` `path` `overlay`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -36
View File
@@ -1,47 +1,28 @@
---
title: "Hand-Drawn Flowchart"
description: "Wobbled boxes with handwritten labels joined by curved connectors, drawing on in sequence from a node list"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/hw-pipeline.json"
compositionId="hw-pipeline"
compositionSrc="compositions/hw-pipeline.html"
title="Hand-Drawn Flowchart"
description="Wobbled boxes with handwritten labels joined by curved connectors, drawing on in sequence from a node list"
variables={[]}
meta={{"duration":7,"width":1920,"height":1080,"category":"Blocks","badge":"Stable","codeLines":317}}
attribution={{"path":"registry/blocks/hw-pipeline","tags":["handwritten","diagram","data-viz","overlay"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/hw-pipeline/hw-pipeline.html"
>
<InstallCommand command="npx hyperframes add hw-pipeline" item="hw-pipeline" />
<CatalogSlot slot="code">
That writes `compositions/hw-pipeline.html`, plus 1 supporting file under `assets/fonts/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="hw-pipeline preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/hw-pipeline.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/hw-pipeline.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 7 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-pipeline"
data-composition-src="compositions/hw-pipeline.html"
data-start="0"
data-duration="7"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="hw-pipeline.html">
```html
```html hw-pipeline.html
<!doctype html>
<html lang="en">
<head>
@@ -361,7 +342,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add hw-pipeline" item="hw-pipeline" />
That writes `compositions/hw-pipeline.html`, plus 1 supporting file under `assets/fonts/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 7 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-pipeline"
data-composition-src="compositions/hw-pipeline.html"
data-start="0"
data-duration="7"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -372,3 +382,7 @@ Tagged `handwritten` `diagram` `data-viz` `overlay`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+50 -36
View File
@@ -1,47 +1,28 @@
---
title: "Scribble Transition"
description: "Overlay transition: scribble bands accumulate to cover the frame with a solid backstop, the host cuts beneath, and the scribble clears"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/hw-scribble-transition.json"
compositionId="hw-scribble-transition"
compositionSrc="compositions/hw-scribble-transition.html"
title="Scribble Transition"
description="Overlay transition: scribble bands accumulate to cover the frame with a solid backstop, the host cuts beneath, and the scribble clears"
variables={[]}
meta={{"duration":2.2,"width":1920,"height":1080,"category":"CSS Transitions","badge":"Stable","codeLines":152}}
attribution={{"path":"registry/blocks/hw-scribble-transition","tags":["handwritten","transition","wipe"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/hw-scribble-transition/hw-scribble-transition.html"
>
<InstallCommand command="npx hyperframes add hw-scribble-transition" item="hw-scribble-transition" />
<CatalogSlot slot="code">
That writes one file: `compositions/hw-scribble-transition.html`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="hw-scribble-transition preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/hw-scribble-transition.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/hw-scribble-transition.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 2.2 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-scribble-transition"
data-composition-src="compositions/hw-scribble-transition.html"
data-start="0"
data-duration="2.2"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="hw-scribble-transition.html">
```html
```html hw-scribble-transition.html
<!doctype html>
<html lang="en">
<head>
@@ -196,7 +177,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add hw-scribble-transition" item="hw-scribble-transition" />
That writes one file: `compositions/hw-scribble-transition.html`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 2.2 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-scribble-transition"
data-composition-src="compositions/hw-scribble-transition.html"
data-start="0"
data-duration="2.2"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -207,3 +217,7 @@ Tagged `handwritten` `transition` `wipe`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>
+54 -39
View File
@@ -1,47 +1,28 @@
---
title: "Speech Bubble"
description: "Hand-drawn speech bubble with a positionable tail: outline draws on, paper fill fades in, handwritten text types per character; boils"
mode: "frame"
---
import { CatalogDetail, CatalogSlot } from "/snippets/catalog-detail.jsx";
import { InstallCommand } from "/snippets/install-command.jsx";
## Install
<CatalogDetail
previewSrc="/public/catalog/blocks/hw-text-cloud.json"
compositionId="hw-text-cloud"
compositionSrc="compositions/hw-text-cloud.html"
title="Speech Bubble"
description="Hand-drawn speech bubble with a positionable tail: outline draws on, paper fill fades in, handwritten text types per character; boils"
variables={[]}
meta={{"duration":6,"width":1920,"height":1080,"category":"Blocks","badge":"Stable","codeLines":286}}
attribution={{"path":"registry/blocks/hw-text-cloud","tags":["handwritten","speech-bubble","overlay"]}}
hasCode
rawUrl="https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry/blocks/hw-text-cloud/hw-text-cloud.html"
>
<InstallCommand command="npx hyperframes add hw-text-cloud" item="hw-text-cloud" />
<CatalogSlot slot="code">
That writes `compositions/hw-text-cloud.html`, plus 1 supporting file under `assets/fonts/`.
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="hw-text-cloud preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/hw-text-cloud.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/hw-text-cloud.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-text-cloud"
data-composition-src="compositions/hw-text-cloud.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
## Source
<Accordion title="hw-text-cloud.html">
```html
```html hw-text-cloud.html
<!doctype html>
<html lang="en">
<head>
@@ -272,8 +253,10 @@ Move it in time with `data-start`. Put it on a different timeline row with
var svg = document.getElementById("hw-tc-svg");
svg.setAttribute("viewBox", "0 0 " + CONFIG.w + " " + CONFIG.h);
var d = cloudPath(CONFIG.w, CONFIG.h, CONFIG.tip, CONFIG.seed);
document.getElementById("hw-tc-fill").setAttribute("d", d);
document.getElementById("hw-tc-stroke").setAttribute("d", d);
var fillEl = document.getElementById("hw-tc-fill");
var stroke = document.getElementById("hw-tc-stroke");
fillEl.setAttribute("d", d);
stroke.setAttribute("d", d);
var text = document.getElementById("hw-tc-text");
text.style.left = CONFIG.w * 0.12 + "px";
@@ -293,7 +276,6 @@ Move it in time with `data-start`. Put it on a different timeline row with
var tl = gsap.timeline({ paused: true });
/* draw outline, fade fill, type text */
var stroke = document.getElementById("hw-tc-stroke");
var len = stroke.getTotalLength();
gsap.set(stroke, { strokeDasharray: len, strokeDashoffset: len });
var t0 = CONFIG.animationIn ? 0.3 : 0;
@@ -329,7 +311,36 @@ Move it in time with `data-start`. Put it on a different timeline row with
</html>
```
</Accordion>
</CatalogSlot>
<CatalogSlot slot="install">
<InstallCommand command="npx hyperframes add hw-text-cloud" item="hw-text-cloud" />
That writes `compositions/hw-text-cloud.html`, plus 1 supporting file under `assets/fonts/`.
</CatalogSlot>
<CatalogSlot slot="docs">
## Add it to your video
It runs for 6 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="hw-text-cloud"
data-composition-src="compositions/hw-text-cloud.html"
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
{/* hf:generated-footer */}
@@ -340,3 +351,7 @@ Tagged `handwritten` `speech-bubble` `overlay`.
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)
</CatalogSlot>
</CatalogDetail>

Some files were not shown because too many files have changed in this diff Show More