PageSourceSearch

https://www.datafirefly.com/wp-content/themes/datafirefly/assets/js/review-rail.js?ver=1.30.1

js datafirefly.com collected 2026-09-24 08:46:10 UTC 5,102 bytes, 147 lines download raw bytes

1/**
2 * Review rail — gentle continuous vertical scroll + sticky offset fix.
3 *
4 * 1. OFFSET STICKY (v1.18.1) — la colonne .df-prod-aside colle sous les
5 *    DEUX barres sticky de la fiche produit : .df-nav (top:0) et
6 *    .df-prod-tabs (top:61px). L'offset réel = hauteur nav + hauteur tabs
7 *    + 16px de respiration, mesuré ici et posé dans --df-aside-top
8 *    (fallback CSS : 132px). Recalculé au resize (les onglets peuvent
9 *    passer sur 2 lignes / la hauteur du header varie).
10 *
11 * 2. MARQUEE — défilement vertical très lent (≈14 px/s) des derniers avis.
12 *    - Piste clonée pour couvrir largement le viewport → boucle invisible.
13 *    - Pause au survol / focus clavier / touch.
14 *    - Clones aria-hidden, liens clonés hors tab-order.
15 *    - prefers-reduced-motion: reduce → aucun défilement (CSS: scrollable).
16 *    - Si l'espace résiduel de l'aside laisse moins de 150px au viewport
17 *      du rail (petit écran en hauteur), le rail est masqué : la carte
18 *      d'achat garde toujours la priorité.
19 *
20 * @package DataFirefly
21 */
22( function () {
23	'use strict';
24
25	/* ------------------------------------------------------------------
26	 * 1. Offset sticky de l'aside (s'applique même sans rail).
27	 * ------------------------------------------------------------------ */
28	function syncAsideOffset() {
29		var aside = document.querySelector( '.df-prod-aside' );
30		if ( ! aside ) {
31			return;
32		}
33		var nav  = document.querySelector( '.df-nav' );
34		var tabs = document.querySelector( '.df-prod-tabs' );
35		var top  = ( nav ? nav.offsetHeight : 0 ) + ( tabs ? tabs.offsetHeight : 0 ) + 16;
36		document.documentElement.style.setProperty( '--df-aside-top', top + 'px' );
37	}
38
39	/* ------------------------------------------------------------------
40	 * 2. Rail : masquage si trop peu d'espace + marquee.
41	 * ------------------------------------------------------------------ */
42	function maybeHideRail() {
43		var rail = document.querySelector( '[data-df-rvrail]' );
44		if ( ! rail ) {
45			return null;
46		}
47		var viewport = rail.querySelector( '.df-rv-rail-viewport' );
48		if ( ! viewport ) {
49			return null;
50		}
51		// Colonne non sticky (mobile) : jamais masqué.
52		var isStatic = window.matchMedia && window.matchMedia( '(max-width: 980px)' ).matches;
53		var tooSmall = ! isStatic && viewport.clientHeight < 150;
54		rail.style.display = tooSmall ? 'none' : '';
55		return tooSmall ? null : { rail: rail, viewport: viewport };
56	}
57
58	function initMarquee( parts ) {
59		var viewport = parts.viewport;
60		var track    = parts.rail.querySelector( '.df-rv-rail-track' );
61		if ( ! track || track.children.length < 2 ) {
62			return;
63		}
64		if ( window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches ) {
65			return; // CSS fallback: static, scrollable.
66		}
67
68		// Hauteur du contenu original AVANT clonage : c'est le modulo de la boucle.
69		var loopHeight = track.scrollHeight;
70		if ( loopHeight < 40 ) {
71			return;
72		}
73
74		// Cloner les items jusqu'à couvrir généreusement le viewport (le
75		// max-height CSS est 380px, on vise loop + 500px de marge : la
76		// couverture reste valable même si le viewport regrandit au resize).
77		var originals = Array.prototype.slice.call( track.children );
78		var safety    = 0;
79		while ( track.scrollHeight < loopHeight + 500 && safety < 8 ) {
80			originals.forEach( function ( li ) {
81				var clone = li.cloneNode( true );
82				clone.setAttribute( 'aria-hidden', 'true' );
83				clone.querySelectorAll( 'a' ).forEach( function ( a ) {
84					a.setAttribute( 'tabindex', '-1' );
85				} );
86				track.appendChild( clone );
87			} );
88			safety++;
89		}
90
91		var SPEED  = 14; // px / seconde — "défile doucement".
92		var y      = 0;
93		var paused = false;
94		var last   = null;
95
96		[ 'mouseenter', 'focusin', 'touchstart' ].forEach( function ( ev ) {
97			viewport.addEventListener( ev, function () { paused = true; }, { passive: true } );
98		} );
99		[ 'mouseleave', 'focusout', 'touchend', 'touchcancel' ].forEach( function ( ev ) {
100			viewport.addEventListener( ev, function () { paused = false; }, { passive: true } );
101		} );
102
103		function step( ts ) {
104			if ( last === null ) {
105				last = ts;
106			}
107			var dt = Math.min( 0.1, ( ts - last ) / 1000 ); // clamp (onglet inactif).
108			last = ts;
109			if ( ! paused ) {
110				y += SPEED * dt;
111				if ( y >= loopHeight ) {
112					y -= loopHeight;
113				}
114				track.style.transform = 'translate3d(0,' + ( -y ).toFixed( 2 ) + 'px,0)';
115			}
116			window.requestAnimationFrame( step );
117		}
118		window.requestAnimationFrame( step );
119	}
120
121	/* ------------------------------------------------------------------ */
122	function init() {
123		syncAsideOffset();
124
125		var parts = maybeHideRail();
126		if ( parts ) {
127			initMarquee( parts );
128		}
129
130		var raf = null;
131		window.addEventListener( 'resize', function () {
132			if ( raf ) {
133				window.cancelAnimationFrame( raf );
134			}
135			raf = window.requestAnimationFrame( function () {
136				syncAsideOffset();
137				maybeHideRail();
138			} );
139		}, { passive: true } );
140	}
141
142	if ( 'loading' === document.readyState ) {
143		document.addEventListener( 'DOMContentLoaded', init );
144	} else {
145		init();
146	}
147} )();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.