fix(templates): resolve all lint errors and warnings in bundled templates (#197)

## What

Added GSAP CDN imports to all template compositions and improved project linting to skip template placeholders.

## Why

Templates were missing GSAP script imports, causing JavaScript errors when GSAP animations tried to execute. The linter was also incorrectly flagging template placeholder values like `__VIDEO_SRC__` as missing audio sources.

## How

- Added `<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>` to all composition HTML files
- Updated `lintAudioSrcNotFound` function to skip template placeholders matching the pattern `__[A-Z_]+__`
- Fixed GSAP animation properties by adding `overwrite: "auto"` to prevent conflicts
- Replaced CSS transforms with GSAP `gsap.set()` calls for better animation control
- Moved video elements outside timed containers in some templates to avoid nesting issues
- Standardized transcript data format to use double quotes for JSON consistency
- Added proper timeline initialization with `window.__timelines = window.__timelines || {}`
- Enhanced caption styling with overflow handling and max-width constraints

## Test plan

- [x] Manual testing performed
- [x] Verified GSAP animations work correctly in all templates
- [x] Confirmed linter no longer flags template placeholders as errors
- [x] Tested video playback and audio synchronization
This commit is contained in:
Miguel Ángel
2026-04-02 14:38:24 -07:00
committed by GitHub
parent 7294803fbc
commit 44fcc8e8b2
20 changed files with 102 additions and 104 deletions
+1
View File
@@ -130,6 +130,7 @@ function lintAudioSrcNotFound(projectDir: string, htmlSources: string[]): Hyperf
while ((match = audioSrcRe.exec(html)) !== null) {
const src = match[1]!;
if (/^(https?:|data:|blob:)/i.test(src)) continue;
if (/^__[A-Z_]+__$/.test(src)) continue; // Skip template placeholders
const resolved = resolve(projectDir, src);
if (!existsSync(resolved)) {
missingSrcs.push(src);
@@ -272,6 +272,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
@@ -358,13 +359,21 @@
}, "hold3+=1");
// Visual click feedback
tl.to("#cursor", { scale: 0.8, duration: 0.05, yoyo: true, repeat: 1 }, "hold3+=1");
tl.to(
"#cursor",
{ scale: 0.8, duration: 0.05, yoyo: true, repeat: 1, overwrite: "auto" },
"hold3+=1",
);
// 11. Hold 0.3s
tl.addLabel("hold4", "+=0.3");
// 12. Cursor double-clicks "Pythom" — highlight in blue
tl.to("#cursor", { scale: 0.8, duration: 0.05, yoyo: true, repeat: 3 }, "hold4");
tl.to(
"#cursor",
{ scale: 0.8, duration: 0.05, yoyo: true, repeat: 3, overwrite: "auto" },
"hold4",
);
tl.add(() => {
const textEl = document.querySelector("#python-text");
if (!textEl) return;
@@ -400,8 +409,12 @@
tl.addLabel("hold6", typingEnd + 0.5);
// 16. Cursor moves slightly and clicks away to deselect
tl.to("#cursor", { left: 500, top: 580, duration: 0.3 }, "hold6");
tl.to("#cursor", { scale: 0.8, duration: 0.05, yoyo: true, repeat: 1 }, "hold6+=0.3");
tl.to("#cursor", { left: 500, top: 580, duration: 0.3, overwrite: "auto" }, "hold6");
tl.to(
"#cursor",
{ scale: 0.8, duration: 0.05, yoyo: true, repeat: 1, overwrite: "auto" },
"hold6+=0.3",
);
tl.to(".selection-border", { opacity: 0, duration: 0.1 }, "hold6+=0.3");
// 17. 👍 emoji pops in
@@ -413,6 +426,7 @@
// 19. Fade entire screen to white over 0.5s
tl.to("#fade-overlay", { opacity: 1, duration: 0.5 }, "hold7");
window.__timelines = window.__timelines || {};
window.__timelines["decision-tree"] = tl;
})();
</script>
@@ -215,7 +215,6 @@
display: flex;
padding: 40px;
color: black;
transform: translateX(100%);
z-index: 2;
}
.swiss-grid {
@@ -492,6 +491,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
const tl = gsap.timeline({ paused: true });
@@ -523,8 +523,9 @@
tl.to("#white-wipe", { xPercent: 100, duration: 0.2, ease: "power2.out" }, 2.2);
// --- Scene 2: Swiss Style (2.0s - 5.0s) ---
gsap.set(".swiss-panel", { xPercent: 100 });
tl.to("#scene-2", { opacity: 1, duration: 0.1 }, 2.2);
tl.to(".swiss-panel", { x: 0, duration: 0.8, ease: "expo.out" }, 2.3);
tl.to(".swiss-panel", { xPercent: 0, duration: 0.8, ease: "expo.out" }, 2.3);
tl.to(".swiss-title", { opacity: 1, duration: 0.1 }, 2.5);
tl.to(".swiss-subtitle", { opacity: 1, y: 0, duration: 0.4 }, 2.7);
tl.to(
@@ -534,7 +535,7 @@
);
tl.to(".swiss-circle", { opacity: 1, scale: 1.2, duration: 0.6, ease: "back.out" }, 3.2);
tl.to(".swiss-square", { opacity: 1, rotation: 45, duration: 0.6, ease: "back.out" }, 3.4);
tl.to(".swiss-panel", { x: "120%", duration: 0.7, ease: "expo.in" }, 4.7);
tl.to(".swiss-panel", { xPercent: 120, duration: 0.7, ease: "expo.in" }, 4.7);
tl.to("#scene-2", { opacity: 0, duration: 0.1 }, 5.0);
// --- Scene 3: Figma Config Style (5.3s - 9.6s) ---
@@ -443,6 +443,7 @@
);
// Register timeline
window.__timelines = window.__timelines || {};
window.__timelines["nyt-chart"] = tl;
})();
</script>
@@ -45,6 +45,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const TRANSCRIPT = [
@@ -39,6 +39,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
-10
View File
@@ -133,7 +133,6 @@
<div
id="aroll-comp"
data-composition-id="aroll-layer"
data-start="0"
data-duration="__VIDEO_DURATION__"
data-width="1920"
data-height="1080"
@@ -220,15 +219,6 @@
data-track-index="150"
></div>
<!-- Audio Clips -->
<audio id="sfx-bounce-0" src="" data-start="0" data-track-index="5"></audio>
<audio id="sfx-whoosh-1" src="" data-start="1.839" data-track-index="6"></audio>
<audio id="sfx-pop-1" src="" data-start="2.039" data-track-index="7"></audio>
<audio id="sfx-whoosh-2" src="" data-start="4.659" data-track-index="6"></audio>
<audio id="sfx-pop-2" src="" data-start="4.859" data-track-index="7"></audio>
<audio id="sfx-whoosh-3" src="" data-start="8.88" data-track-index="6"></audio>
<audio id="sfx-pop-3" src="" data-start="9.08" data-track-index="7"></audio>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
@@ -88,6 +88,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
@@ -132,7 +132,6 @@
left: 50%;
width: 1440px;
height: 900px;
transform: translate(-50%, -50%) scale(0);
background: white;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
@@ -235,19 +234,16 @@
font-size: 14px;
font-weight: 600;
border-radius: 4px;
transform: scale(0);
}
.width-label {
top: -30px;
left: 50%;
transform: translateX(-50%) scale(0);
}
.height-label {
right: -45px;
top: 50%;
transform: translateY(-50%) rotate(90deg) scale(0);
}
.selection-handles {
@@ -296,7 +292,6 @@
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-right: 1px solid rgba(255, 255, 255, 0.1);
transform: translateX(-320px);
padding: 24px;
z-index: 10;
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.2);
@@ -421,7 +416,6 @@
white-space: nowrap;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
opacity: 0;
transform: translateX(-10px);
}
.cursor {
@@ -455,6 +449,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
@@ -462,6 +457,11 @@
// Organic Cursor Ease
const cursorEase = "back.out(1.2)";
gsap.set(".web-frame", { xPercent: -50, yPercent: -50, scale: 0 });
gsap.set(".width-label", { xPercent: -50, scale: 0 });
gsap.set(".height-label", { yPercent: -50, rotation: 90, scale: 0 });
gsap.set(".comment-bubble", { x: -10 });
gsap.set(".assets-panel", { x: -320 });
gsap.set(".designer-cursor", { x: 200, y: 200 });
gsap.set(".engineer-cursor", { x: -100, y: 500 });
gsap.set(".pm-cursor", { x: 2000, y: 1100 });
@@ -489,7 +489,11 @@
// Collaboration
tl.to(".engineer-cursor", { x: 400, y: 400, duration: 1.2, ease: cursorEase }, 3.5);
tl.to(".pm-cursor", { x: 1500, y: 800, duration: 1.5, ease: cursorEase }, 3.7);
tl.to(".engineer-cursor", { x: 960, y: 680, duration: 0.9, ease: cursorEase }, 4.5);
tl.to(
".engineer-cursor",
{ x: 960, y: 680, duration: 0.9, ease: cursorEase, overwrite: "auto" },
4.5,
);
tl.to(".selection-handles", { opacity: 1, duration: 0.2 }, 5.3);
tl.to(".cta-button", { outline: "2px solid #1ABCFE", duration: 0.1 }, 5.3);
@@ -124,6 +124,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
@@ -45,9 +45,12 @@
line-height: 1;
white-space: nowrap;
text-align: center;
max-width: 1600px;
overflow: hidden;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const TRANSCRIPT = [
@@ -71,6 +71,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
+22 -36
View File
@@ -85,36 +85,7 @@
data-duration="__VIDEO_DURATION__"
>
<!-- Background Grid -->
<img
id="bg-grid"
class="background-grid"
src="assets/swiss-grid.svg"
alt="Grid"
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="0"
/>
<!-- A-roll Video -->
<div id="short_mag_cut_frame">
<video
id="short_mag_cut"
src="__VIDEO_SRC__"
muted
playsinline
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="1"
></video>
</div>
<audio
id="a-roll-audio"
src="__VIDEO_SRC__"
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="5"
data-volume="1"
></audio>
<img id="bg-grid" class="background-grid" src="assets/swiss-grid.svg" alt="Grid" />
<!-- Intro Sub-composition -->
<div
@@ -149,12 +120,6 @@
data-track-index="4"
></div>
<!-- Sound Effects (src set dynamically; omitted to avoid 404s) -->
<audio id="sfx-intro" data-start="0" data-track-index="5" data-duration="1"></audio>
<audio id="sfx-stat1" data-start="1.86" data-track-index="5" data-duration="1"></audio>
<audio id="sfx-stat2" data-start="4.68" data-track-index="5" data-duration="1"></audio>
<audio id="sfx-stat3" data-start="8.88" data-track-index="5" data-duration="1"></audio>
<script>
window.__timelines = window.__timelines || {};
@@ -230,5 +195,26 @@
window.__timelines["master"] = masterTL;
</script>
</div>
<!-- A-roll Video (direct child of body to avoid video_nested_in_timed_element) -->
<div id="short_mag_cut_frame">
<video
id="short_mag_cut"
src="__VIDEO_SRC__"
muted
playsinline
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="1"
></video>
</div>
<audio
id="a-roll-audio"
src="__VIDEO_SRC__"
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="5"
data-volume="1"
></audio>
</body>
</html>
@@ -50,9 +50,12 @@
display: inline-block;
letter-spacing: -0.02em; /* Tight Helvetica spacing */
white-space: nowrap; /* Prevent wrapping at the element level */
max-width: 900px;
overflow: hidden;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const TRANSCRIPT = [
@@ -171,6 +174,9 @@
},
group.end - 0.3,
);
// Hard kill to ensure caption is hidden
tl.set(div, { opacity: 0, visibility: "hidden" }, group.end);
});
window.__timelines = window.__timelines || {};
@@ -222,6 +222,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
const tl = gsap.timeline({ paused: true });
const TRANSCRIPT = [
+21 -21
View File
@@ -108,27 +108,6 @@
data-start="0"
data-duration="__VIDEO_DURATION__"
>
<!-- A-Roll -->
<div id="a-roll-wrapper">
<video
id="a-roll"
src="__VIDEO_SRC__"
muted
playsinline
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="0"
></video>
</div>
<audio
id="a-roll-audio"
src="__VIDEO_SRC__"
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="5"
data-volume="1"
></audio>
<!-- Transitions -->
<div id="curtain-black" class="curtain"></div>
<div id="curtain-red" class="curtain red"></div>
@@ -186,5 +165,26 @@
window.__timelines["main-comp"] = tl;
</script>
</div>
<!-- A-Roll (direct child of body to avoid video_nested_in_timed_element) -->
<div id="a-roll-wrapper">
<video
id="a-roll"
src="__VIDEO_SRC__"
muted
playsinline
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="0"
></video>
</div>
<audio
id="a-roll-audio"
src="__VIDEO_SRC__"
data-start="0"
data-duration="__VIDEO_DURATION__"
data-track-index="5"
data-volume="1"
></audio>
</body>
</html>
@@ -43,9 +43,12 @@
text-align: center;
line-height: 1.2;
white-space: nowrap;
max-width: 1600px;
overflow: hidden;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const script = [
@@ -114,6 +117,7 @@
lines.forEach((line, index) => {
// Fade in and set text
tl.set(box, { visibility: "visible" }, line.start);
tl.to(
box,
{
@@ -137,6 +141,9 @@
},
line.end,
);
// Hard kill to ensure caption is hidden
tl.set(box, { opacity: 0, visibility: "hidden" }, line.end + 0.1);
});
window.__timelines = window.__timelines || {};
@@ -119,6 +119,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
@@ -50,6 +50,7 @@
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
-23
View File
@@ -175,29 +175,6 @@
data-track-index="3"
></div>
<!-- SFX -->
<audio
id="sfx-swipe-1"
src=""
data-start="1.8"
data-duration="1"
data-track-index="4"
></audio>
<audio
id="sfx-swipe-2"
src=""
data-start="4.6"
data-duration="1"
data-track-index="4"
></audio>
<audio
id="sfx-swipe-3"
src=""
data-start="8.8"
data-duration="1"
data-track-index="4"
></audio>
<script>
const tl = gsap.timeline({ paused: true });