22/**
33 * Infinite horizontally scrolling ticker using CSS animation.
44 * Two copies of the content sit side-by-side and translate continuously.
5- * Hover pause is pure CSS (:hover) to avoid JS-induced animation jumps.
6- * JS handles: touch pause, per-card colors, and reshuffling on each cycle.
5+ * JS smoothly ramps playbackRate on hover for gentle pause/resume.
76 */
87interface Props {
98 direction? : ' left' | ' right' ;
@@ -14,7 +13,6 @@ interface Props {
1413
1514const { direction = ' left' , variant = ' press' , duration = 60 } = Astro .props ;
1615const animName = direction === ' right' ? ' ticker-scroll-right' : ' ticker-scroll-left' ;
17- // Add ±10% random jitter so tickers don't appear perfectly synchronized
1816const jitter = duration * (0.9 + Math .random () * 0.2 );
1917const actualDuration = Math .round (jitter );
2018---
@@ -38,10 +36,11 @@ const actualDuration = Math.round(jitter);
3836 * SPDX-License-Identifier: Apache-2.0
3937 */
4038(function() {
41- if (window.__tickerV5 ) return;
42- window.__tickerV5 = true;
39+ if (window.__tickerV6 ) return;
40+ window.__tickerV6 = true;
4341
4442 var TOUCH_PAUSE_MS = 10000;
43+ var RAMP_MS = 600;
4544 var PALETTE = [
4645 {h:207,s:70},{h:220,s:65},{h:190,s:60},{h:260,s:55},{h:340,s:60},
4746 {h:15,s:65},{h:170,s:50},{h:280,s:50},{h:45,s:55},{h:150,s:45}
@@ -52,7 +51,6 @@ const actualDuration = Math.round(jitter);
5251 else window.addEventListener('load', fn);
5352 }
5453
55- // Fisher-Yates shuffle for DOM children
5654 function shuffleChildren(container) {
5755 var children = Array.from(container.children);
5856 for (var i = children.length - 1; i > 0; i--) {
@@ -62,6 +60,25 @@ const actualDuration = Math.round(jitter);
6260 }
6361 }
6462
63+ // Smoothly ramp playbackRate from current to target over RAMP_MS
64+ function rampRate(anim, target, ms) {
65+ if (!anim) return;
66+ var start = anim.playbackRate;
67+ var startTime = null;
68+ function step(ts) {
69+ if (!startTime) startTime = ts;
70+ var elapsed = ts - startTime;
71+ var progress = Math.min(elapsed / ms, 1);
72+ // Ease-in-out cubic
73+ var ease = progress < 0.5
74+ ? 4 * progress * progress * progress
75+ : 1 - Math.pow(-2 * progress + 2, 3) / 2;
76+ anim.playbackRate = start + (target - start) * ease;
77+ if (progress < 1) requestAnimationFrame(step);
78+ }
79+ requestAnimationFrame(step);
80+ }
81+
6582 ready(function() {
6683 // Assign per-card colors
6784 document.querySelectorAll('.ticker-card').forEach(function(card) {
@@ -83,18 +100,33 @@ const actualDuration = Math.round(jitter);
83100 var halves = track.querySelectorAll('.ticker-half');
84101 var touchTimer = null;
85102
86- // Touch-only pause (mouse hover is handled by pure CSS)
103+ // Get the CSS animation object from the track element
104+ function getAnim() {
105+ var anims = track.getAnimations();
106+ return anims.length > 0 ? anims[0] : null;
107+ }
108+
109+ // Mouse hover: gently ramp to 0 on enter, back to 1 on leave
110+ el.addEventListener('pointerenter', function(e) {
111+ if (e.pointerType !== 'mouse') return;
112+ rampRate(getAnim(), 0, RAMP_MS);
113+ });
114+ el.addEventListener('pointerleave', function(e) {
115+ if (e.pointerType !== 'mouse') return;
116+ rampRate(getAnim(), 1, RAMP_MS);
117+ });
118+
119+ // Touch: instant pause, resume after timeout
87120 el.addEventListener('touchstart', function() {
88- track.style.animationPlayState = 'paused';
121+ var anim = getAnim();
122+ if (anim) anim.playbackRate = 0;
89123 if (touchTimer) clearTimeout(touchTimer);
90124 touchTimer = setTimeout(function() {
91- track.style.animationPlayState = '' ;
125+ rampRate(getAnim(), 1, RAMP_MS) ;
92126 }, TOUCH_PAUSE_MS);
93127 }, {passive: true});
94128
95- // Reshuffle both halves when the animation completes one full cycle.
96- // The shuffle happens at the exact loop point where the two halves
97- // are visually identical, so the reorder is invisible to the user.
129+ // Reshuffle on each animation cycle
98130 track.addEventListener('animationiteration', function() {
99131 for (var i = 0; i < halves.length; i++) {
100132 shuffleChildren(halves[i]);
0 commit comments