From 73cb5a5b55a5dd5d56a7ab8d0da86ef9b2a21484 Mon Sep 17 00:00:00 2001 From: James Russo Date: Thu, 14 May 2026 21:36:24 +0000 Subject: [PATCH] fix(studio,registry): unbreak studio test on main + make vignette demo legible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two surgical changes, both isolated to the catalog-previews flow: 1. `packages/studio/src/player/hooks/usePlaybackKeyboard.test.ts` PR #842 changed `seek()` to take `(time, { keepPlaying: true })` for the A/E shortcuts. The keyboard-layout tests added by #839 still asserted the single-arg form. Both landed on main without cross-checking, so `main` itself has been failing Test/Windows since. Update the two assertions to match the new signature. Same fix Miguel already authored on `feat/studio-preview-pasteboard-bg`. 2. `registry/components/vignette/demo.html` The original demo captured a frame where the vignette was at its weakest point — the effect was nearly invisible in the static preview used by docs. Reworked the demo so: - The backdrop is a layered "cinematic still" (warm key + teal rim + dark falloff) and includes a centered subject ("moon"), so the vignette has a focal point to frame. - Vignette starts soft (size 70%, alpha 0.35) and ramps to a dramatic cinematic vignette (size 26%, alpha 0.92) over 1.6s. - Peak intensity holds across t≈3.0s, which is exactly where the catalog script samples the thumbnail (`Math.min(3.0, duration*0.6)` with duration=5). - Breathing motion in t=3.4–5.2s gives the video loop visible life without disturbing the still frame. --- .../player/hooks/usePlaybackKeyboard.test.ts | 4 +- registry/components/vignette/demo.html | 178 ++++++++++++++---- 2 files changed, 144 insertions(+), 38 deletions(-) diff --git a/packages/studio/src/player/hooks/usePlaybackKeyboard.test.ts b/packages/studio/src/player/hooks/usePlaybackKeyboard.test.ts index 679cf617e..ab6f7b623 100644 --- a/packages/studio/src/player/hooks/usePlaybackKeyboard.test.ts +++ b/packages/studio/src/player/hooks/usePlaybackKeyboard.test.ts @@ -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')", () => { @@ -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", () => { diff --git a/registry/components/vignette/demo.html b/registry/components/vignette/demo.html index 522284757..d96e7ef03 100644 --- a/registry/components/vignette/demo.html +++ b/registry/components/vignette/demo.html @@ -17,6 +17,7 @@ width: 1920px; height: 1080px; overflow: hidden; + background: #000; } @@ -26,26 +27,22 @@ data-composition-id="vignette-demo" data-width="1920" data-height="1080" - data-duration="6" + data-duration="5" > +
-
- Vignette -
-
- Pulls focus toward the center + +
+
+
+ +
+
VIGNETTE
+
Frame the eye. Hold the moment.
@@ -67,13 +64,101 @@ .demo-bg { width: 1920px; height: 1080px; - background: radial-gradient(circle at 30% 30%, #c98a4b, #6d3a18 60%, #2a160a 100%); + 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; - justify-content: center; align-items: center; - font-family: "Inter", sans-serif; - position: relative; + 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 { @@ -89,32 +174,53 @@ (function () { const tl = gsap.timeline({ paused: true }); - // Fade in title and subtitle - tl.to(".demo-title", { opacity: 1, y: -10, duration: 1, ease: "power3.out" }, 0.3); - tl.to(".demo-subtitle", { opacity: 1, y: -5, duration: 0.8, ease: "power3.out" }, 0.7); + // 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); - // Fade vignette in from transparent + // 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)" }, { - "--vignette-color": "rgba(0, 0, 0, 0.75)", - duration: 1.5, - ease: "power2.out", + "--vignette-color": "rgba(0, 0, 0, 0.92)", + "--vignette-size": "26%", + duration: 1.6, + ease: "power2.inOut", }, - 1.2, + 1.4, ); - // Breathe the size in and out + // 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": "35%", duration: 1.0, ease: "sine.inOut" }, - 3.0, + { "--vignette-size": "32%", duration: 0.9, ease: "sine.inOut" }, + 3.4, ); tl.to( "#hf-vignette", - { "--vignette-size": "45%", duration: 1.0, ease: "sine.inOut" }, - 4.0, + { "--vignette-size": "26%", duration: 0.9, ease: "sine.inOut" }, + 4.3, ); window.__timelines = window.__timelines || {};