Setting the file. One moment. Demo Page Scroll Spotlight · Hyperframes Animation · heygen-com/hyperframes · Skills DocsBlueprints Index
(opens in a new tab)
examples/demo-page-scroll-spotlight.html
HTML·759 lines·24 KB
8 <!--
9 HyperFrames composition.tsx.
10
11 Choreography (4 phases, 9 seconds total):
12 0.00 – 0.80s 3D-tilted page card scales 0.95 → 1.0
13 Navbar / title / CTA fade in (staggered)
14 0.12 – 2.80s Title keywords glow synced to ASR words:
15 "1" "long" "video" "10" "viral" "clips"
16 3.08 – 4.08s Page content scrolls up 280 px (programmatic scroll feel)
17 Carousel section fades in + scales from 0.9
18 3.58 – 8.84s Main video pops forward in 3D (translateZ 80 px) + scales up
19 Radial spotlight dims the surroundings; decays at end
20 (attack 0→1 then decay 1→REST_LEVEL). All visual effects (color,
21 text-shadow, scale) derive from --glow via CSS calc()
22 - Pop-target uses the same --glow pattern; translateZ + scale derived
23 - Spotlight is a separate overlay with simple opacity tween
24 - Video placeholders are colored divs (no asset files needed)
25 - Tilt is static CSS, never tweened
26 -->
27
28 <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
29
30 <style>
31 :root {
32 --page-bg: #0a0a0f;
33 --text-primary: #ffffff;
34 --text-secondary: #a1a1aa;
35 --accent: #edcb50;
36 --input-bg: #1a1a1f;
37 --green: #22c55e;
38 }
39 * {
40 margin: 0;
41 padding: 0;
42 box-sizing: border-box;
43 }
44 html,
45 body {
46 margin: 0;
47 width: 1920px;
48 height: 1080px;
49 overflow: hidden;
50 background: #000;
51 font-family:
52 "Inter",
53 system-ui,
54 -apple-system,
55 sans-serif;
56 color: var(--text-primary);
57 }
58
59 /* ============================================================
60 BACKGROUND
61 ============================================================ */
62 .bg {
63 position: absolute;
64 inset: 0;
65 background:
66 radial-gradient(
67 ellipse 100% 60% at 50% -10%,
68 rgba(100, 100, 150, 0.08) 0%,
69 transparent 60%
70 ),
71 var(--page-bg);
72 }
73
74 /* ============================================================
75 3D PERSPECTIVE + TILTED CARD
76 ============================================================ */
77 .perspective-wrap {
78 position: absolute;
79 inset: 0;
80 display: flex;
81 align-items: center;
82 justify-content: center;
83 perspective: 1200px;
84 }
85 .page-card {
86 width: 92%;
87 height: 88%;
88 background-color: var(--page-bg);
89 border-radius: 20px;
90 overflow: hidden;
91 transform-style: preserve-3d;
92 /* tilt + initial scale set by gsap.set() below so subsequent tweens preserve them */
93 box-shadow:
94 -30px 30px 60px rgba(0, 0, 0, 0.4),
95 -15px 15px 30px rgba(0, 0, 0, 0.3),
96 0 0 80px rgba(0, 0, 0, 0.2);
97 border: 1px solid rgba(255, 255, 255, 0.1);
98 position: relative;
99 }
100
101 /* ============================================================
102 NAVBAR (sticks to top of card; NOT inside scroll content)
103 ============================================================ */
104 .page-navbar {
105 position: absolute;
106 top: 0;
107 left: 0;
108 right: 0;
109 height: 72px;
110 padding: 0 48px;
111 display: flex;
112 align-items: center;
113 justify-content: space-between;
114 z-index: 100;
115 opacity: 0; /* GSAP fades in */
116 background: linear-gradient(to bottom, rgba(10, 10, 15, 0.95), rgba(10, 10, 15, 0.6));
117 }
118 .nav-left {
119 display: flex;
120 align-items: center;
121 gap: 10px;
122 }
123 .nav-logo {
124 width: 32px;
125 height: 32px;
126 color: var(--text-primary);
127 }
128 .nav-brand {
129 font-size: 20px;
130 font-weight: 600;
131 letter-spacing: -0.5px;
132 }
133 .nav-center {
134 display: flex;
135 align-items: center;
136 gap: 28px;
137 }
138 .nav-item {
139 font-size: 14px;
140 font-weight: 500;
141 color: var(--text-secondary);
142 display: flex;
143 align-items: center;
144 }
145 .nav-item .chev {
146 margin-left: 4px;
147 font-size: 10px;
148 }
149 .nav-tag {
150 font-size: 10px;
151 background: var(--green);
152 color: #fff;
153 padding: 2px 6px;
154 border-radius: 4px;
155 font-weight: 600;
156 margin-right: 6px;
157 }
158 .nav-right {
159 display: flex;
160 align-items: center;
161 gap: 12px;
162 }
163 .nav-signin {
164 padding: 8px 16px;
165 border: 1px solid rgba(255, 255, 255, 0.2);
166 border-radius: 8px;
167 color: var(--text-primary);
168 font-size: 14px;
169 font-weight: 500;
170 }
171 .nav-signup {
172 padding: 8px 16px;
173 background: var(--accent);
174 border-radius: 8px;
175 color: var(--page-bg);
176 font-size: 14px;
177 font-weight: 600;
178 }
179
180 /* ============================================================
181 SCROLL CONTENT — GSAP tweens its y
182 ============================================================ */
183 .scroll-content {
184 padding-top: 140px;
185 padding-bottom: 60px;
186 display: flex;
187 flex-direction: column;
188 align-items: center;
189 will-change: transform;
190 }
191
192 /* ============================================================
193 HERO SECTION
194 ============================================================ */
195 .hero {
196 text-align: center;
197 max-width: 1200px;
198 width: 90%;
199 opacity: 0; /* GSAP fades in */
200 }
201 .hero-eyebrow {
202 font-size: 14px;
203 color: var(--accent);
204 text-transform: uppercase;
205 letter-spacing: 2px;
206 margin-bottom: 24px;
207 font-weight: 600;
208 }
209 .hero-title {
210 font-size: 64px;
211 font-weight: 700;
212 line-height: 1.1;
213 letter-spacing: -1px;
214 }
215 .hero-sub {
216 font-size: 20px;
217 color: rgba(255, 255, 255, 0.6);
218 margin-top: 24px;
219 font-weight: 400;
220 line-height: 1.5;
221 max-width: 800px;
222 margin-left: auto;
223 margin-right: auto;
224 }
225
226 /* ============================================================
227 KEYWORD GLOW — all derived from --glow CSS custom property
228 ============================================================ */
229 .kw {
230 --glow: 0;
231 display: inline-block;
232 position: relative;
233 z-index: 0;
234 padding: 0 0.015em;
235 color: rgb(
236 calc(255 - var(--glow) * 18) calc(255 - var(--glow) * 30) calc(255 - var(--glow) * 150)
237 );
238 text-shadow:
239 0 0 calc(var(--glow) * 10px) rgba(255, 235, 120, calc(var(--glow) * 0.95)),
240 0 0 calc(var(--glow) * 24px) rgba(237, 203, 80, calc(var(--glow) * 0.78)),
241 0 0 calc(var(--glow) * 44px) rgba(237, 203, 80, calc(var(--glow) * 0.38));
242 transform: scale(calc(1 + var(--glow) * 0.035));
243 }
244 .kw::before {
245 content: "";
246 position: absolute;
247 left: -0.025em;
248 right: -0.025em;
249 top: 0.08em;
250 bottom: 0.05em;
251 z-index: -1;
252 border-radius: 0.16em;
253 background: linear-gradient(
254 90deg,
255 rgba(237, 203, 80, 0.08),
256 rgba(255, 222, 80, 0.72),
257 rgba(237, 203, 80, 0.12)
258 );
259 opacity: calc(var(--glow) * 0.78);
260 transform: scaleX(calc(0.72 + var(--glow) * 0.24)) skewX(-5deg);
261 transform-origin: left center;
262 box-shadow:
263 0 0 calc(var(--glow) * 14px) rgba(237, 203, 80, calc(var(--glow) * 0.7)),
264 0 0 calc(var(--glow) * 28px) rgba(237, 203, 80, calc(var(--glow) * 0.32));
265 }
266
267 /* ============================================================
268 CTA INPUT ROW
269 ============================================================ */
270 .cta-row {
271 display: flex;
272 align-items: center;
273 justify-content: center;
274 gap: 16px;
275 margin-top: 36px;
276 opacity: 0; /* GSAP fades in */
277 }
278 .cta-input {
279 display: flex;
280 align-items: center;
281 background: var(--input-bg);
282 border-radius: 28px;
283 padding: 6px 6px 6px 18px;
284 gap: 12px;
285 }
286 .cta-link-icon {
287 width: 16px;
288 height: 16px;
289 color: rgba(255, 255, 255, 0.4);
290 }
291 .cta-placeholder {
292 font-size: 14px;
293 color: rgba(255, 255, 255, 0.4);
294 min-width: 130px;
295 }
296 .cta-button {
297 padding: 12px 24px;
298 background: #fff;
299 border-radius: 22px;
300 color: var(--page-bg);
301 font-size: 14px;
302 font-weight: 600;
303 }
304 .cta-or {
305 color: rgba(255, 255, 255, 0.4);
306 font-size: 14px;
307 }
308 .cta-upload {
309 padding: 12px 24px;
310 border: 1px solid rgba(255, 255, 255, 0.2);
311 border-radius: 22px;
312 color: var(--text-primary);
313 font-size: 14px;
314 font-weight: 500;
315 }
316
317 /* ============================================================
318 VIDEO CAROUSEL (Phase 2 entry, Phase 4 pop-out)
319 ============================================================ */
320 .carousel-wrap {
321 opacity: 0; /* GSAP fades in */
322 transform: scale(0.9); /* GSAP scales to 1 */
323 transform-origin: center top;
324 margin-top: 48px;
325 display: flex;
326 justify-content: center;
327 align-items: center;
328 gap: 20px;
329 width: 100%;
330 max-width: 1400px;
331 padding: 0 20px;
332 perspective: 800px;
333 transform-style: preserve-3d;
334 }
335 .carousel-arrow {
336 width: 44px;
337 height: 44px;
338 border-radius: 50%;
339 background: rgba(60, 60, 65, 0.8);
340 display: flex;
341 align-items: center;
342 justify-content: center;
343 color: #fff;
344 flex-shrink: 0;
345 font-size: 24px;
346 font-weight: 300;
347 }
348
349 /* Main video — Phase 4 pop-out target */
350 .carousel-main {
351 --glow: 0;
352 width: 720px;
353 height: 420px;
354 background: var(--input-bg);
355 border-radius: 16px;
356 position: relative;
357 overflow: hidden;
358 flex-shrink: 0;
359 transform-style: preserve-3d;
360 transform: translateZ(calc(var(--glow) * 80px)) scale(calc(1 + var(--glow) * 0.15));
361 box-shadow:
362 0 0 calc(var(--glow) * 25px) rgba(237, 203, 80, calc(var(--glow) * 0.7)),
363 0 0 calc(var(--glow) * 50px) rgba(237, 203, 80, calc(var(--glow) * 0.4)),
364 0 calc(20px + var(--glow) * 40px) calc(60px + var(--glow) * 40px)
365 rgba(0, 0, 0, calc(0.6 + var(--glow) * 0.2));
366 border: 3px solid rgba(237, 203, 80, calc(var(--glow) * 0.8));
367 z-index: 200;
368 }
369 .carousel-main-placeholder {
370 position: absolute;
371 inset: 0;
372 display: flex;
373 align-items: center;
374 justify-content: center;
375 font-size: 28px;
376 font-weight: 600;
377 color: rgba(255, 255, 255, 0.5);
378 background: linear-gradient(135deg, #1a2540 0%, #3d2a4f 50%, #4f1d2e 100%);
379 }
380 .carousel-main-placeholder span {
381 background: rgba(0, 0, 0, 0.4);
382 padding: 8px 20px;
383 border-radius: 8px;
384 }
385
386 /* Side panel */
387 .carousel-side {
388 width: 420px;
389 height: 280px;
390 background: var(--input-bg);
391 border-radius: 16px;
392 position: relative;
393 overflow: hidden;
394 flex-shrink: 0;
395 }
396 .carousel-side-placeholder {
397 position: absolute;
398 inset: 0;
399 background: linear-gradient(135deg, #2a3550 0%, #4a3a5a 100%);
400 display: flex;
401 align-items: center;
402 justify-content: center;
403 font-size: 18px;
404 font-weight: 600;
405 color: rgba(255, 255, 255, 0.5);
406 }
407 .presets-stack {
408 position: absolute;
409 top: 16px;
410 right: 16px;
411 display: flex;
412 flex-direction: column;
413 gap: 8px;
414 z-index: 5;
415 }
416 .preset-label {
417 padding: 8px 14px;
418 background: rgba(255, 255, 255, 0.12);
419 border-radius: 8px;
420 color: rgba(255, 255, 255, 0.8);
421 font-size: 12px;
422 font-weight: 500;
423 }
424 .preset-tile {
425 width: 44px;
426 height: 44px;
427 background: rgba(255, 255, 255, 0.08);
428 border-radius: 8px;
429 align-self: flex-end;
430 }
431
432 /* ============================================================
433 SPOTLIGHT OVERLAY (above page card, dims surroundings)
434 ============================================================ */
435 .spotlight {
436 position: absolute;
437 inset: 0;
438 background: radial-gradient(
439 ellipse 850px 550px at 40% 60%,
440 transparent 0%,
441 transparent 50%,
442 rgba(0, 0, 0, 0.65) 100%
443 );
444 pointer-events: none;
445 opacity: 0; /* GSAP fades in during Phase 4 */
446 z-index: 150;
447 }
448
449 /* ============================================================
450 VIGNETTE
451 ============================================================ */
452 .vignette {
453 position: absolute;
454 inset: 0;
455 background: radial-gradient(ellipse at center, transparent 50%, rgba(0, 0, 0, 0.3) 100%);
456 pointer-events: none;
457 z-index: 400;
458 }
459 </style>
460 </head>
461 <body>
462 <div
463 id="root"
464 data-composition-id="main"
465 data-start="0"
466 data-duration="9"
467 data-width="1920"
468 data-height="1080"
469 >
470 <div class="bg"></div>
471
472 <div class="perspective-wrap">
473 <div
474 class="page-card clip"
475 data-start="0"
476 data-duration="9"
477 data-track-index="1"
478 id="page-card"
479 >
480 <!-- Navbar (anchored to card top) -->
481 <nav class="page-navbar" id="navbar">
482 <div class="nav-left">
483 <svg
484 class="nav-logo"
485 viewBox="0 0 24 24"
486 fill="none"
487 xmlns="http://www.w3.org/2000/svg"
488 aria-hidden="true"
489 >
490 <path
491 fill="currentColor"
492 d="M11.954 3.817c-4.49 0-8.132 3.644-8.152 8.146v12H0V12C0 5.373 5.352 0 11.954 0s11.953 5.373 11.953 12-5.351 12-11.953 12a12 12 0 0 1-1.718-.123v-3.876q.831.181 1.718.182c4.502 0 8.152-3.663 8.152-8.183s-3.65-8.183-8.152-8.183"
493 />
494 <path
495 fill="currentColor"
496 d="M5.118 24V11.995c0-3.79 3.062-6.857 6.836-6.857 3.773 0 6.836 3.068 6.836 6.857s-3.063 6.857-6.836 6.857c-.594 0-1.17-.076-1.718-.218V14.5c.488.337 1.08.534 1.718.534a3.037 3.037 0 0 0 3.034-3.04 3.037 3.037 0 0 0-3.034-3.04 3.037 3.037 0 0 0-3.034 3.008V24z"
497 />
498 </svg>
499 <span class="nav-brand">OpusClip</span>
500 </div>
501 <div class="nav-center">
502 <span class="nav-item">Features<span class="chev">▾</span></span>
503 <span class="nav-item">Solutions<span class="chev">▾</span></span>
504 <span class="nav-item">Resources<span class="chev">▾</span></span>
505 <span class="nav-item">Pricing</span>
506 <span class="nav-item">For business<span class="chev">▾</span></span>
507 <span class="nav-item"><span class="nav-tag">New</span>Agent Opus</span>
508 </div>
509 <div class="nav-right">
510 <span class="nav-signin">Sign in</span>
511 <span class="nav-signup">Sign up - It's FREE</span>
512 </div>
513 </nav>
514
515 <!-- Scrollable content -->
516 <div class="scroll-content" id="scroll-content">
517 <div class="hero" id="hero">
518 <div class="hero-eyebrow">#1 AI VIDEO CLIPPING TOOL</div>
519 <h1 class="hero-title">
520 <span class="kw" data-glow-start="0.12" data-glow-end="0.28">1</span>
521 <span class="kw" data-glow-start="0.52" data-glow-end="0.72">long</span>
522 <span class="kw" data-glow-start="0.78" data-glow-end="1.44">video,</span>
523 <span class="kw" data-glow-start="1.48" data-glow-end="1.80">10</span>
524 <span class="kw" data-glow-start="1.94" data-glow-end="2.22">viral</span>
525 <span class="kw" data-glow-start="2.28" data-glow-end="2.80">clips.</span>
526 <br />Create 10x faster.
527 </h1>
528 <p class="hero-sub">
529 OpusClip turns long videos into shorts, and publishes them to all social platforms
530 in one click.
531 </p>
532 </div>
533
534 <div class="cta-row" id="cta-row">
535 <div class="cta-input">
536 <svg
537 class="cta-link-icon"
538 viewBox="0 0 24 24"
539 fill="none"
540 stroke="currentColor"
541 stroke-width="2"
542 stroke-linecap="round"
543 stroke-linejoin="round"
544 aria-hidden="true"
545 >
546 <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
547 <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
548 </svg>
549 <span class="cta-placeholder">Drop a video link</span>
550 <div class="cta-button">Get free clips</div>
551 </div>
552 <span class="cta-or">or</span>
553 <div class="cta-upload">Upload files</div>
554 </div>
555
556 <!-- Video carousel section -->
557 <div class="carousel-wrap" id="carousel">
558 <div class="carousel-arrow">‹</div>
559
560 <div class="carousel-main pop-target" id="pop-target">
561 <div class="carousel-main-placeholder">
562 <span>Hero animation</span>
563 </div>
564 </div>
565
566 <div class="carousel-arrow">›</div>
567
568 <div class="carousel-side">
569 <div class="carousel-side-placeholder">Demo · reframe</div>
570 <div class="presets-stack">
571 <div class="preset-label">Presets</div>
572 <div class="preset-tile"></div>
573 <div class="preset-tile"></div>
574 <div class="preset-tile"></div>
575 <div class="preset-tile"></div>
576 </div>
577 </div>
578 </div>
579 </div>
580 <!-- /.scroll-content -->
581 </div>
582 <!-- /.page-card -->
583 </div>
584 <!-- /.perspective-wrap -->
585
586 <div class="spotlight" id="spotlight"></div>
587 <div class="vignette"></div>
588 </div>
589
590 <script>
591 /* ================================================================
592 TIMING — all in local seconds (scene starts at 0).
593 ASR timestamps are direct from the source, with SCENE_START = 16
594 subtracted to get local time.
595 ================================================================ */
596 const ASR = {
597 // Phase 2 title keywords
598 one: { start: 0.119, end: 0.279 },
599 long: { start: 0.52, end: 0.719 },
600 video: { start: 0.779, end: 1.44 },
601 ten: { start: 1.479, end: 1.799 },
602 viral: { start: 1.94, end: 2.219 },
603 clips: { start: 2.279, end: 2.8 },
604 };
605
606 const TIMING = {
607 // Phase 1: entry
608 cardEntryStart: 0.0,
609 cardEntryDur: 0.8,
610 navbarFadeAt: 0.0,
611 navbarFadeDur: 0.6,
612 titleFadeAt: 0.17,
613 titleFadeDur: 0.7,
614 ctaFadeAt: 0.5,
615 ctaFadeDur: 0.7,
616
617 // Phase 2: keyword glows handled per-element via data attrs
618
619 // Phase 3: scroll + carousel
620 phase2StartAt: 3.08,
621 scrollDur: 1.0,
622 scrollDistance: 280,
623 carouselFadeDur: 0.5,
624
625 // Phase 4: pop-out + spotlight
626 popStart: 3.58,
627 popAttackDur: 0.67, // ramp 0 → 1
628 popDecayDur: 0.33, // ramp 1 → 0.5 near end
629 popEnd: 8.84,
630 popRestLevel: 0.5,
631 };
632
633 const KEYWORD_REST_LEVEL = 0.14;
634 const KEYWORD_SUSTAIN = 0.5; // seconds after ASR end before decay completes
635
636 /* ================================================================
637 TIMELINE
638 ================================================================ */
639 window.__timelines = window.__timelines || {};
640 const tl = gsap.timeline({ paused: true });
641 window.__timelines["main"] = tl;
642
643 // Set the static 3D tilt + initial scale via GSAP so subsequent
644 // scale tweens preserve the rotation aliases. (If we left
645 // `transform: rotateY(...) rotateX(...) scale(0.95)` in CSS, a GSAP
646 // tween touching only `scale` could overwrite the full matrix and
647 // lose the rotation. Owning the transform state in GSAP avoids the
648 // problem entirely.)
649 gsap.set(".page-card", {
650 rotationY: -8,
651 rotationX: 3,
652 scale: 0.95,
653 });
654
655 /* ----------------------------------------------------------------
656 PHASE 1: Page card entry + navbar/title/CTA fade-in
657 ---------------------------------------------------------------- */
658 tl.to(
659 ".page-card",
660 { scale: 1.0, duration: TIMING.cardEntryDur, ease: "power2.out" },
661 TIMING.cardEntryStart,
662 );
663
664 tl.fromTo(
665 "#navbar",
666 { opacity: 0 },
667 { opacity: 1, duration: TIMING.navbarFadeDur, ease: "power2.out" },
668 TIMING.navbarFadeAt,
669 );
670
671 tl.fromTo(
672 "#hero",
673 { opacity: 0 },
674 { opacity: 1, duration: TIMING.titleFadeDur, ease: "power2.out" },
675 TIMING.titleFadeAt,
676 );
677
678 tl.fromTo(
679 "#cta-row",
680 { opacity: 0 },
681 { opacity: 1, duration: TIMING.ctaFadeDur, ease: "power2.out" },
682 TIMING.ctaFadeAt,
683 );
684
685 /* ----------------------------------------------------------------
686 PHASE 2: Keyword glows — two tweens per word.
687 Attack (0 → 1) + decay (1 → REST_LEVEL). GSAP holds REST forever.
688 ---------------------------------------------------------------- */
689 document.querySelectorAll(".kw").forEach((kw) => {
690 const start = Number(kw.dataset.glowStart);
691 const end = Number(kw.dataset.glowEnd);
692 const peak = start + (end - start) / 2;
693 const restAt = end + KEYWORD_SUSTAIN;
694
695 tl.fromTo(
696 kw,
697 { "--glow": 0 },
698 { "--glow": 1.18, duration: peak - start, ease: "power2.out" },
699 start,
700 );
701
702 tl.to(
703 kw,
704 { "--glow": KEYWORD_REST_LEVEL, duration: restAt - peak, ease: "power2.out" },
705 peak,
706 );
707 });
708
709 /* ----------------------------------------------------------------
710 PHASE 3: Scroll + carousel entry
711 ---------------------------------------------------------------- */
712 tl.fromTo(
713 "#scroll-content",
714 { y: 0 },
715 { y: -TIMING.scrollDistance, duration: TIMING.scrollDur, ease: "power2.inOut" },
716 TIMING.phase2StartAt,
717 );
718
719 tl.fromTo(
720 "#carousel",
721 { opacity: 0, scale: 0.9 },
722 { opacity: 1, scale: 1.0, duration: TIMING.carouselFadeDur, ease: "power2.out" },
723 TIMING.phase2StartAt,
724 );
725
726 /* ----------------------------------------------------------------
727 PHASE 4: Pop-out (driven by --glow on .carousel-main) + spotlight
728 ---------------------------------------------------------------- */
729 // Attack the pop-out glow.
730 tl.fromTo(
731 "#pop-target",
732 { "--glow": 0 },
733 { "--glow": 1, duration: TIMING.popAttackDur, ease: "power2.out" },
734 TIMING.popStart,
735 );
736
737 // Decay at the end of the scene (mirrors original phase2End-10 / phase2End ramp).
738 tl.to(
739 "#pop-target",
740 { "--glow": TIMING.popRestLevel, duration: TIMING.popDecayDur, ease: "power2.out" },
741 TIMING.popEnd - TIMING.popDecayDur,
742 );
743
744 // Spotlight fades in alongside the pop attack, decays with the pop.
745 tl.fromTo(
746 "#spotlight",
747 { opacity: 0 },
748 { opacity: 1, duration: 0.5, ease: "power2.out" },
749 TIMING.popStart,
750 );
751
752 tl.to(
753 "#spotlight",
754 { opacity: TIMING.popRestLevel, duration: TIMING.popDecayDur, ease: "power2.out" },
755 TIMING.popEnd - TIMING.popDecayDur,
756 );
757 </script>
758 </body>
759</html>