PageSourceSearch

https://bikeattack.pages.dev/motion.js

js bikeattack.pages.dev collected 2026-10-03 06:32:33 UTC 5,443 bytes, 128 lines download raw bytes

1/* ============================================================================
2   MOTION · comportamentele care nu se pot face doar din CSS
3     1. bară de progres la scroll
4     2. întârziere în cascadă la reveal (--i pe fiecare copil dintr-o grilă)
5     3. înclinare ușoară a cardurilor după cursor (doar pe mouse, nu pe touch)
6     4. parallax fin pe fotografiile mari
7     5. eticheta „Vezi traseul" injectată în cardurile de traseu
8     6. numărătoare pentru cifre, dacă pagina nu are deja propriul mecanism
9   Totul se dezactivează la prefers-reduced-motion.
10   Rulează și după schimbarea paginii în propunerea 6 (MutationObserver).
11   ============================================================================ */
12(function(){
13  var reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
14  var fine   = matchMedia('(hover: hover) and (pointer: fine)').matches;
15
16  /* ---- 1. bară de progres ---- */
17  var bar=document.getElementById('sprog');
18  if(!bar){bar=document.createElement('div');bar.id='sprog';document.body.appendChild(bar);}
19  function prog(){
20    var h=document.documentElement.scrollHeight-innerHeight;
21    bar.style.width=(h>0? (scrollY/h*100):0)+'%';
22  }
23  addEventListener('scroll',prog,{passive:true});
24  addEventListener('resize',prog); prog();
25
26  /* ---- 2. cascadă pe reveal ---- */
27  function stagger(){
28    document.querySelectorAll('.g2,.g3,.g4,.tgrid,.sup,.evl,.dash,.blz-row').forEach(function(grid){
29      var i=0;
30      [].forEach.call(grid.children,function(c){
31        if(c.classList.contains('rv')||c.classList.contains('tc')||c.classList.contains('cd')||c.classList.contains('evr')){
32          c.style.setProperty('--i',i++);
33        }
34      });
35    });
36  }
37
38  /* ---- 3. înclinare după cursor pe cardurile de traseu ---- */
39  function tilt(){
40    if(reduce||!fine)return;
41    document.querySelectorAll('.tc:not([data-tilt])').forEach(function(card){
42      card.dataset.tilt='1'; card.classList.add('tilt');
43      card.addEventListener('mousemove',function(e){
44        var r=card.getBoundingClientRect();
45        var px=(e.clientX-r.left)/r.width-.5, py=(e.clientY-r.top)/r.height-.5;
46        card.style.setProperty('--ry',(px*5).toFixed(2)+'deg');
47        card.style.setProperty('--rx',(-py*4).toFixed(2)+'deg');
48      });
49      card.addEventListener('mouseleave',function(){
50        card.style.setProperty('--ry','0deg'); card.style.setProperty('--rx','0deg');
51      });
52    });
53  }
54
55  /* ---- 4. parallax pe fotografii mari ---- */
56  var pars=[];
57  function collectPar(){
58    pars=[];
59    if(reduce)return;
60    document.querySelectorAll('.hph img,.evi img,.story .im img,.h2img img').forEach(function(img){
61      if(img.closest('.hv'))return;                 /* hero-ul video nu se atinge */
62      img.classList.add('par'); pars.push(img);
63    });
64  }
65  function runPar(){
66    if(reduce||!pars.length)return;
67    var vh=innerHeight;
68    pars.forEach(function(img){
69      var r=img.parentElement.getBoundingClientRect();
70      if(r.bottom<-100||r.top>vh+100)return;
71      var p=(r.top+r.height/2-vh/2)/vh;          // -1 .. 1
72      img.style.setProperty('--py',(p*-16).toFixed(1)+'px');
73    });
74  }
75  addEventListener('scroll',function(){requestAnimationFrame(runPar)},{passive:true});
76
77  /* ---- 5. eticheta GPX în cardurile de traseu ---- */
78  function gpx(){
79    document.querySelectorAll('.tc .ph:not([data-gx])').forEach(function(ph){
80      ph.dataset.gx='1';
81      var s=document.createElement('span');
82      s.className='gx'; s.textContent='Vezi traseul';
83      ph.appendChild(s);
84    });
85  }
86
87  /* ---- 6. numărătoare, dacă pagina nu are deja ---- */
88  function counters(){
89    if(window.__hasCounters)return;
90    if(reduce)return;
91    var io=new IntersectionObserver(function(es){es.forEach(function(x){
92      if(!x.isIntersecting)return; io.unobserve(x.target);
93      var t=parseFloat(x.target.dataset.c),s=performance.now();
94      (function f(n){var p=Math.min((n-s)/1200,1);
95        x.target.textContent=Math.round(t*(1-Math.pow(1-p,3)));
96        if(p<1)requestAnimationFrame(f)})(s);
97    })},{threshold:.6});
98    document.querySelectorAll('[data-c]:not([data-done])').forEach(function(el){el.dataset.done='1';io.observe(el)});
99  }
100
101  /* ---- orchestrare defensivă ----
102     Observatorul se deconectează cât timp rulează, ca modificările pe care le
103     face chiar el (adăugare de clase) să nu îl declanșeze din nou. Plus debounce,
104     ca zecile de clase .in adăugate la scroll să nu producă zeci de treceri. */
105  var mo=null, t=null, running=false;
106  function all(){
107    if(running)return; running=true;
108    if(mo)mo.disconnect();
109    try{ stagger(); tilt(); collectPar(); runPar(); gpx(); }
110    catch(err){ console.warn('motion.js:',err); }
111    if(mo)mo.observe(document.body,{subtree:true,childList:true});
112    running=false;
113  }
114  function schedule(){ clearTimeout(t); t=setTimeout(all,180); }
115  all();
116  /* la schimbarea paginii în propunerea 6, elementele noi primesc același tratament.
117     Urmărim doar apariția de noduri, nu schimbările de clasă. */
118  mo=new MutationObserver(function(recs){
119    /* ignorăm complet mutațiile din interiorul hero-ului video */
120    for(var i=0;i<recs.length;i++){
121      var t=recs[i].target;
122      if(t&&t.closest&&t.closest('.hv'))continue;
123      return schedule();
124    }
125  });
126  mo.observe(document.body,{subtree:true,childList:true});
127  addEventListener('hashchange',schedule);
128})();

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.