fix(examples): host the kinetic-type A-roll so the video actually renders (#1799)

* fix(examples): host the kinetic-type A-roll so the video actually renders

The A-roll <video> was authored inside the main-graphics sub-composition
template. The runtime only seeks and decodes media that is a direct child of
the host root, so the footage was never driven by the live runtime and renders
blank in Studio preview and the player (the producer's compile step happened to
hoist it, masking the contract violation in server renders). The example also
failed lint with eight errors.

Root cause and fixes:
- Move the <video> out of compositions/main-graphics.html and into index.html
  as a direct child of #root, with data-start/data-duration/data-track-index
  and class="clip". Its per-scene opacity reveal now runs on the main timeline
  at global time, since a sub-composition timeline cannot reach host elements.
- Make the sub-composition a transparent overlay shell (background: transparent)
  and stack it above the host video so the type renders over the footage.
- Drop the crossorigin attribute (plain displayed media never needs it) and mark
  the audible footage with data-has-audio="true".
- Replace the three CSS translateY values that GSAP would overwrite with
  fromTo tweens (carson-line-1, wes-text-top, wes-text-bottom).
- Swap the unbundled Libre Baskerville for the bundled Playfair Display serif so
  the renderer can supply the Wes Anderson typography, and drop the redundant
  -apple-system token from the body font stack.

lint reports 0 errors, validate passes, and frame extraction confirms the A-roll
composites correctly under every type scene.

* docs(examples): note the A-roll reveal-time coupling to the sub-composition
This commit is contained in:
Miguel Ángel
2026-06-30 10:55:14 -07:00
committed by GitHub
parent 036e3660cd
commit c01d1ae03d
2 changed files with 58 additions and 37 deletions
@@ -5,20 +5,12 @@
data-height="1080"
data-duration="14.72"
>
<!-- A-Roll Video -->
<video
id="a-roll-video"
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/67cf5a05_e134ec83a57042b38776c952da595870.mp4"
style="
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 1;
z-index: 0;
"
crossorigin="anonymous"
></video>
<!--
The A-Roll footage lives in the host root (index.html), positioned UNDER
this overlay shell. This sub-composition is the transparent graphics layer:
scenes and overlays paint over the footage and its reveal is timed on the
main timeline. Media nested in a sub-composition is never seeked/decoded.
-->
<!-- Scene 1: Hook -->
<div
@@ -156,7 +148,7 @@
<style>
[data-composition-id="main-graphics"] {
background: black;
background: transparent;
font-family: "Inter", sans-serif;
color: white;
overflow: hidden;
@@ -401,7 +393,6 @@
left: 10%;
font-size: 140px;
font-weight: 900;
transform: translateY(200px);
z-index: 2;
}
.carson-line-1 .red-dot {
@@ -448,10 +439,9 @@
overflow: hidden;
}
.wes-text-top {
font-family: "Libre Baskerville", serif;
font-family: "Playfair Display", serif;
font-size: 44px;
font-style: italic;
transform: translateY(-100px);
}
.wes-rule {
width: 0;
@@ -460,9 +450,8 @@
margin: 20px auto;
}
.wes-text-bottom {
font-family: "Libre Baskerville", serif;
font-family: "Playfair Display", serif;
font-size: 44px;
transform: translateY(100px);
}
/* Scene 6: Vignelli Style */
@@ -495,14 +484,11 @@
<script>
const tl = gsap.timeline({ paused: true });
const video = document.getElementById("a-roll-video");
// The A-roll footage lives in the host root (index.html); its reveal is
// driven on the main timeline there. This timeline only animates the
// graphics overlay's own elements.
// --- Initial Setup ---
tl.set(
video,
{ width: "100%", height: "100%", left: 0, top: 0, borderRadius: 0, scale: 1, opacity: 0 },
0,
);
tl.set("#white-wipe", { xPercent: -100 }, 0);
// --- Scene 1: Hook (0s - 2.0s) ---
@@ -519,7 +505,6 @@
// --- Transition to Scene 2 (2.0s - 2.4s) ---
tl.to("#white-wipe", { xPercent: 0, duration: 0.2, ease: "power2.in" }, 2.0);
tl.set("#scene-1", { opacity: 0 }, 2.2);
tl.set(video, { opacity: 1 }, 2.2);
tl.to("#white-wipe", { xPercent: 100, duration: 0.2, ease: "power2.out" }, 2.2);
// --- Scene 2: Swiss Style (2.0s - 5.0s) ---
@@ -554,21 +539,19 @@
// --- Scene 4: David Carson Style (9.6s - 11.7s) ---
tl.to("#scene-4", { opacity: 1, duration: 0.1 }, 9.6);
tl.to(".carson-line-1", { y: 0, duration: 0.6, ease: "expo.out" }, 9.7);
tl.fromTo(".carson-line-1", { y: 200 }, { y: 0, duration: 0.6, ease: "expo.out" }, 9.7);
tl.to(".carson-line-2", { opacity: 1, x: -50, duration: 0.5, ease: "back.out" }, 10.2);
tl.to(".carson-container", { y: 300, duration: 0.6, ease: "expo.in" }, 11.4);
tl.to("#scene-4", { opacity: 0, duration: 0.1 }, 11.7);
// --- Scene 5: Wes Anderson (11.7s - 13.5s) ---
tl.to(video, { opacity: 0, duration: 0.4 }, 11.7);
tl.to("#scene-5", { opacity: 1, duration: 0.1 }, 11.7);
tl.to(".wes-text-top", { y: 0, duration: 0.6, ease: "back.out(1.2)" }, 12.0);
tl.fromTo(".wes-text-top", { y: -100 }, { y: 0, duration: 0.6, ease: "back.out(1.2)" }, 12.0);
tl.to(".wes-rule", { width: 400, duration: 0.6 }, 12.3);
tl.to(".wes-text-bottom", { y: 0, duration: 0.6, ease: "back.out(1.2)" }, 12.6);
tl.fromTo(".wes-text-bottom", { y: 100 }, { y: 0, duration: 0.6, ease: "back.out(1.2)" }, 12.6);
tl.to("#scene-5", { opacity: 0, duration: 0.5 }, 13.0);
// --- Scene 6: Vignelli Style (13.5s - 14.72s) ---
tl.set(video, { opacity: 1 }, 13.5);
tl.set("#scene-6", { opacity: 1 }, 13.5);
tl.set("#scene-6-overlay", { opacity: 1 }, 13.5);
+43 -5
View File
@@ -8,7 +8,7 @@
<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:ital,wght@0,400;0,700;0,900;1,400&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=block"
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,700;0,900;1,400&family=Playfair+Display:ital,wght@0,400;0,700;1,400&display=block"
rel="stylesheet"
/>
<style>
@@ -24,10 +24,7 @@
height: 1080px;
overflow: hidden;
background-color: #0a0a0f;
font-family:
"Inter",
-apple-system,
sans-serif;
font-family: "Inter", sans-serif;
}
</style>
</head>
@@ -40,6 +37,31 @@
data-width="1920"
data-height="1080"
>
<!--
A-Roll video. Media is only seeked/decoded when it is a DIRECT child of
the host root, so the footage lives here (not inside the sub-composition
template) and its per-scene reveal is driven on the main timeline below.
-->
<video
id="a-roll-video"
class="clip"
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/67cf5a05_e134ec83a57042b38776c952da595870.mp4"
data-start="0"
data-duration="14.72"
data-track-index="0"
data-has-audio="true"
playsinline
style="
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 0;
opacity: 0;
"
></video>
<div
id="graphics-layer"
data-composition-id="main-graphics"
@@ -49,12 +71,28 @@
data-track-index="1"
data-width="1920"
data-height="1080"
style="position: absolute; inset: 0; z-index: 1"
></div>
</div>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// The A-roll <video> is a host-root element, so the sub-composition
// timeline cannot reach it. Drive its reveal here on the main timeline at
// global time (the graphics sub-comp starts at 0, so scene-local == global).
//
// COUPLING: the reveal/hide times below (2.2 / 11.7 / 13.5) are hand-tuned
// to the graphics-layer sub-composition's scene boundaries and assume it is
// mounted at data-start="0". If you re-time the sub-composition's scenes or
// change its data-start, these absolute times drift out of alignment and
// must be updated to match the new scene boundaries.
tl.set("#a-roll-video", { opacity: 0 }, 0);
tl.set("#a-roll-video", { opacity: 1 }, 2.2); // reveal after the Scene 1 white wipe
tl.to("#a-roll-video", { opacity: 0, duration: 0.4 }, 11.7); // hide for the Wes Anderson scene
tl.set("#a-roll-video", { opacity: 1 }, 13.5); // back for the Vignelli outro
window.__timelines["main"] = tl;
</script>
</body>