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.