Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/catalog/components/vignette.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: "Vignette"
description: "Cinematic radial vignette overlay using a pure-CSS gradient — darkens the edges to pull focus toward the center"
---

# Vignette

Cinematic radial vignette overlay using a pure-CSS gradient — darkens the edges to pull focus toward the center

`vignette` `overlay` `cinematic` `effect`

<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/components/vignette.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/vignette.png" autoPlay muted loop playsInline />

## Install

<CodeGroup>

```bash Terminal
npx hyperframes add vignette
```

</CodeGroup>

## Details

| Property | Value |
| --- | --- |
| Type | Component |

## Files

| File | Target | Type |
| --- | --- | --- |
| `vignette.html` | `compositions/components/vignette.html` | hyperframes:snippet |

## Usage

Open `compositions/components/vignette.html` and paste its contents into your composition. See the comment header in the file for detailed instructions.
3 changes: 2 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@
"catalog/components/grain-overlay",
"catalog/components/grid-pixelate-wipe",
"catalog/components/shimmer-sweep",
"catalog/components/texture-mask-text"
"catalog/components/texture-mask-text",
"catalog/components/vignette"
]
},
{
Expand Down
14 changes: 14 additions & 0 deletions docs/public/catalog-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,20 @@
"href": "/catalog/blocks/vfx-text-cursor",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/vfx-text-cursor.png"
},
{
"name": "vignette",
"type": "component",
"title": "Vignette",
"description": "Cinematic radial vignette overlay using a pure-CSS gradient — darkens the edges to pull focus toward the center",
"tags": [
"vignette",
"overlay",
"cinematic",
"effect"
],
"href": "/catalog/components/vignette",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/vignette.png"
},
{
"name": "vpn-youtube-spot",
"type": "block",
Expand Down
4 changes: 2 additions & 2 deletions packages/studio/src/player/hooks/usePlaybackKeyboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe("usePlaybackKeyboard — keyboard layout independence (#834)", () => {
dispatch(keydown({ code: "KeyA", key: "a" }));
});

expect(spies.seek).toHaveBeenCalledWith(1.5);
expect(spies.seek).toHaveBeenCalledWith(1.5, { keepPlaying: true });
});

it("'Jump to in-point' fires on AZERTY (physical KeyQ produces e.key='a')", () => {
Expand All @@ -104,7 +104,7 @@ describe("usePlaybackKeyboard — keyboard layout independence (#834)", () => {
dispatch(keydown({ code: "KeyQ", key: "a" }));
});

expect(spies.seek).toHaveBeenCalledWith(2.5);
expect(spies.seek).toHaveBeenCalledWith(2.5, { keepPlaying: true });
});

it("AZERTY 'A' physical key (e.key='q') no longer triggers in-point seek", () => {
Expand Down
232 changes: 232 additions & 0 deletions registry/components/vignette/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Vignette — Demo</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000;
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="vignette-demo"
data-width="1920"
data-height="1080"
data-duration="5"
>
<!-- Backdrop: a layered "cinematic still" — a warm key light at upper-left,
a teal rim light at lower-right, and a soft global glow. The vignette
overlay reads against this so the effect is unambiguous. -->
<div class="demo-bg">
<!-- Subject shapes — a stylized "moon" centered, with subtle haze rings
so the eye has something to anchor on. Without a subject the demo
looks like an abstract gradient and the vignette gets lost. -->
<div class="demo-haze haze-far"></div>
<div class="demo-haze haze-near"></div>
<div class="demo-subject"></div>

<div class="demo-text">
<div class="demo-title">VIGNETTE</div>
<div class="demo-subtitle">Frame the eye. Hold the moment.</div>
</div>
</div>

<!-- The vignette component -->
<div
id="hf-vignette"
style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 90;
"
></div>

<style>
.demo-bg {
width: 1920px;
height: 1080px;
background:
radial-gradient(
circle at 22% 28%,
rgba(255, 196, 130, 0.85),
rgba(255, 196, 130, 0) 38%
),
radial-gradient(circle at 78% 78%, rgba(64, 175, 200, 0.55), rgba(64, 175, 200, 0) 42%),
radial-gradient(circle at 50% 50%, #3b2616 0%, #1a0e07 68%, #060403 100%);
position: relative;
overflow: hidden;
font-family: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.demo-haze {
position: absolute;
left: 50%;
top: 50%;
border-radius: 50%;
transform: translate(-50%, -50%) scale(1);
pointer-events: none;
opacity: 0;
}
.haze-far {
width: 1400px;
height: 1400px;
background: radial-gradient(
circle,
rgba(255, 220, 180, 0.18),
rgba(255, 220, 180, 0) 65%
);
filter: blur(40px);
}
.haze-near {
width: 900px;
height: 900px;
background: radial-gradient(
circle,
rgba(255, 240, 210, 0.32),
rgba(255, 240, 210, 0) 60%
);
filter: blur(20px);
}

.demo-subject {
position: absolute;
left: 50%;
top: 50%;
width: 520px;
height: 520px;
border-radius: 50%;
transform: translate(-50%, -50%);
background: radial-gradient(
circle at 35% 32%,
#fff6e0 0%,
#f4c47e 28%,
#b87a3f 62%,
#6b3d1c 88%,
#2a1607 100%
);
box-shadow:
inset -40px -40px 80px rgba(0, 0, 0, 0.55),
0 0 120px rgba(255, 180, 100, 0.35);
opacity: 0;
}

.demo-text {
position: absolute;
left: 0;
right: 0;
bottom: 12%;
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
pointer-events: none;
/* Sits above the subject in document order; below the vignette in z-index */
z-index: 5;
}

.demo-title {
font-size: 108px;
font-weight: 800;
letter-spacing: 0.18em;
color: #f6ecd0;
text-shadow: 0 4px 24px rgba(0, 0, 0, 0.55);
opacity: 0;
}

.demo-subtitle {
font-size: 30px;
font-weight: 400;
letter-spacing: 0.32em;
text-transform: uppercase;
color: rgba(246, 236, 208, 0.72);
opacity: 0;
}

#hf-vignette {
background: radial-gradient(
var(--vignette-shape, ellipse) at center,
transparent var(--vignette-size, 45%),
var(--vignette-color, rgba(0, 0, 0, 0.7)) var(--vignette-edge, 100%)
);
}
</style>

<script>
(function () {
const tl = gsap.timeline({ paused: true });

// Reveal the backdrop subject and haze first so the vignette has
// something to frame. Without a subject the effect reads as a flat
// dark gradient — uninteresting in a still preview.
tl.to(".haze-far", { opacity: 1, duration: 0.8, ease: "power2.out" }, 0.0);
tl.to(".haze-near", { opacity: 1, duration: 0.8, ease: "power2.out" }, 0.15);
tl.to(".demo-subject", { opacity: 1, scale: 1, duration: 1.0, ease: "power3.out" }, 0.1);

// Title + subtitle settle in.
tl.fromTo(
".demo-title",
{ opacity: 0, y: 18, letterSpacing: "0.32em" },
{ opacity: 1, y: 0, letterSpacing: "0.18em", duration: 1.0, ease: "power3.out" },
0.6,
);
tl.to(".demo-subtitle", { opacity: 1, duration: 0.8, ease: "power3.out" }, 1.0);

// Vignette: start visibly soft (so viewers see the unframed scene),
// then ramp to a strong cinematic vignette and hold through the
// capture-time peak (~3.0s, picked to match the catalog thumbnail
// sampling at `Math.min(3.0, duration * 0.6)` for duration=5).
gsap.set("#hf-vignette", {
"--vignette-color": "rgba(0, 0, 0, 0.35)",
"--vignette-size": "70%",
"--vignette-edge": "100%",
});
tl.to(
"#hf-vignette",
{
"--vignette-color": "rgba(0, 0, 0, 0.92)",
"--vignette-size": "26%",
duration: 1.6,
ease: "power2.inOut",
},
1.4,
);

// Gentle breathing for the video loop so the effect reads as alive,
// not static. Keeps the still-frame at peak intensity around t≈3s.
tl.to(
"#hf-vignette",
{ "--vignette-size": "32%", duration: 0.9, ease: "sine.inOut" },
3.4,
);
tl.to(
"#hf-vignette",
{ "--vignette-size": "26%", duration: 0.9, ease: "sine.inOut" },
4.3,
);

window.__timelines = window.__timelines || {};
window.__timelines["vignette-demo"] = tl;
})();
</script>
</div>
</body>
</html>
15 changes: 15 additions & 0 deletions registry/components/vignette/registry-item.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "vignette",
"type": "hyperframes:component",
"title": "Vignette",
"description": "Cinematic radial vignette overlay using a pure-CSS gradient — darkens the edges to pull focus toward the center",
"tags": ["vignette", "overlay", "cinematic", "effect"],
"files": [
{
"path": "vignette.html",
"target": "compositions/components/vignette.html",
"type": "hyperframes:snippet"
}
]
}
51 changes: 51 additions & 0 deletions registry/components/vignette/vignette.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!--
Vignette — cinematic radial darkening overlay.

Usage: paste this snippet inside your composition's positioned stage.
The overlay covers the full viewport with pointer-events: none so it
sits on top of all content without blocking interaction.

Customize:
- --vignette-color: edge color and alpha (default rgba(0, 0, 0, 0.7))
- --vignette-size: where the transparent center ends (default 45%)
- --vignette-edge: where the color reaches its final stop (default 100%)
- --vignette-shape: ellipse | circle (default ellipse)
- z-index: defaults to 90 so grain-overlay (100) reads on top
-->

<div
id="hf-vignette"
style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 90;
"
></div>

<style>
#hf-vignette {
background: radial-gradient(
var(--vignette-shape, ellipse) at center,
transparent var(--vignette-size, 45%),
var(--vignette-color, rgba(0, 0, 0, 0.7)) var(--vignette-edge, 100%)
);
}
</style>

<!--
Timeline integration example (add to your composition's GSAP timeline):

// Fade the vignette in over the first second
tl.fromTo("#hf-vignette",
{ "--vignette-color": "rgba(0, 0, 0, 0)" },
{ "--vignette-color": "rgba(0, 0, 0, 0.7)", duration: 1, ease: "power2.out" },
0,
);

// Or breathe the size in and out (works with any easing)
tl.to("#hf-vignette", { "--vignette-size": "35%", duration: 1, ease: "sine.inOut" }, 2);
-->
4 changes: 4 additions & 0 deletions registry/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
"name": "texture-mask-text",
"type": "hyperframes:component"
},
{
"name": "vignette",
"type": "hyperframes:component"
},
{
"name": "instagram-follow",
"type": "hyperframes:block"
Expand Down
Loading
Loading