PageSourceSearch

https://scalz.ai/luminous/homepage/orbital-story.js?v=all-phones-1

js scalz.ai collected 2026-10-05 12:37:24 UTC 12,659 bytes, 153 lines download raw bytes

1(() => {
2  'use strict';
3  const {gsap,ScrollTrigger,ScrollToPlugin}=window;
4  if(!gsap||!ScrollTrigger)return;
5  gsap.registerPlugin(ScrollTrigger);if(ScrollToPlugin)gsap.registerPlugin(ScrollToPlugin);
6  const root=document.documentElement,reduced=matchMedia('(prefers-reduced-motion: reduce)');
7  const off=()=>reduced.matches||root.classList.contains('preview-effects-off');
8  ScrollTrigger.config({ignoreMobileResize:true});
9  // Use the small viewport height (100svh) so the iOS toolbar showing or hiding never toggles the orbit layout.
10  const touch=matchMedia('(hover: none) and (pointer: coarse)').matches;
11  let vhProbe=document.getElementById('scalz-vh-probe');
12  if(!vhProbe){vhProbe=document.createElement('div');vhProbe.id='scalz-vh-probe';vhProbe.setAttribute('aria-hidden','true');vhProbe.style.cssText='position:fixed;top:0;left:0;width:1px;height:100vh;height:100svh;visibility:hidden;pointer-events:none;z-index:-1';document.body.appendChild(vhProbe);}
13  const stableHeight=()=>vhProbe.offsetHeight||innerHeight;
14  const applyPhoneTier=()=>{const phone=innerWidth<=760,h=stableHeight();root.classList.toggle('phone-short',phone&&h<700);root.classList.toggle('phone-tiny',phone&&h<640);};
15  const canOrbit=()=>!off()&&(innerWidth<=760||stableHeight()>=700);
16  const track=document.querySelector('.orbit-track'),cards=[...document.querySelectorAll('.orbit-ring>.spotlight-card')];
17  const selectors=[...document.querySelectorAll('[data-orbit-index]')],prev=document.querySelector('.orbit-prev'),next=document.querySelector('.orbit-next');
18  let context,trigger,navigation,current=0,orbitState={position:0,arrival:1,field:1};
19  const controls=document.querySelector('.orbit-controls');
20  const explorationStart=.38;
21  const loops=[];
22  function renderOrbit(value){
23    const radius=Math.min(440,track.clientWidth*.34),mobile=innerWidth<=760;
24    cards.forEach((card,i)=>{
25      let angle=(i-value)*72;angle=((angle+180)%360+360)%360-180;
26      const radians=angle*Math.PI/180,cos=Math.cos(radians),arrival=orbitState.arrival,front=i===Math.round(value)&&arrival>.65;
27      const lateral=Math.sin(radians)*radius*(mobile?.22+.78*(1-Math.max(0,cos)):1);
28      gsap.set(card,{xPercent:-50,x:lateral,y:(1-arrival)*90,z:(cos-1)*(mobile?radius*.42:radius*.66)-(1-arrival)*500,rotationX:(1-arrival)*18,rotationY:-angle*.54+(1-arrival)*32,scale:(.86+.14*Math.max(0,cos))*(.76+.24*arrival),opacity:Math.max(.08,.32+cos*.68)*arrival,zIndex:Math.round(100+cos*100)});
29      card.inert=!front;card.setAttribute('aria-hidden',String(!front));card.classList.toggle('is-front',front);card.removeAttribute('tabindex');
30    });
31    current=Math.max(0,Math.min(4,Math.round(value)));
32    selectors.forEach((button,i)=>button.setAttribute('aria-pressed',String(i===current)));
33    if(prev)prev.disabled=current===0;if(next)next.disabled=current===4;
34    track.dataset.position=value.toFixed(3);
35    track.dataset.arrival=orbitState.arrival.toFixed(3);
36    track.dataset.field=orbitState.field.toFixed(3);
37    if(controls){gsap.set(controls,{opacity:orbitState.arrival});controls.inert=orbitState.arrival<.65;}
38  }
39  function navigate(index){
40    const i=Math.max(0,Math.min(cards.length-1,index));navigation?.kill();
41    if(trigger&&!off()&&ScrollToPlugin)navigation=gsap.to(window,{scrollTo:{y:trigger.start+(explorationStart+i/4*(1-explorationStart))*(trigger.end-trigger.start),autoKill:true},duration:.8,ease:'power2.inOut',overwrite:'auto'});
42    else {orbitState.position=i;renderOrbit(i);}
43  }
44  selectors.forEach((button,i)=>button.addEventListener('click',()=>navigate(i)));
45  prev?.addEventListener('click',()=>navigate(current-1));next?.addEventListener('click',()=>navigate(current+1));
46  document.querySelector('.orbit-controls')?.addEventListener('keydown',event=>{
47    if(event.key==='ArrowRight'||event.key==='ArrowLeft'){
48      event.preventDefault();const i=Math.max(0,Math.min(4,current+(event.key==='ArrowRight'?1:-1)));selectors[i].focus();navigate(i);
49    }
50  });
51  // Each boundary has a different route through the same atmosphere.
52  const sectorConfigs=[
53    ['.foundation','M-100 230 Q600 -100 1300 230',0],
54    ['.industries','M-100 320 C160 100 700 340 1300 -50',1],
55    ['.experience','M-100 20 C380 350 860 -70 1300 260',2],
56    ['.proof','M-100 230 C240 20 750 360 1300 90',0],
57    ['.process','M-100 360 Q530 -150 1300 180',2],
58    ['.faq-section','M-100 90 C350 340 880 70 1300 190',0],
59    ['.closing','M-100 260 Q620 -130 1300 260',1],
60    ['.mega-footer','M-100 180 C300 350 880 -60 1300 200',0]
61  ];
62  sectorConfigs.forEach(([selector,path,direction])=>{
63    const section=document.querySelector(selector);if(!section)return;
64    section.classList.add('has-sector');
65    const crossing=document.createElement('div');crossing.className=`sector-crossing crossing-direction-${direction}`;crossing.setAttribute('aria-hidden','true');
66    crossing.innerHTML=`<div class="crossing-field"><div class="crossing-cloud"></div><svg viewBox="0 0 1200 360" preserveAspectRatio="none"><path class="crossing-path echo" d="${path}"/><path class="crossing-path" d="${path}"/></svg></div>`;
67    section.prepend(crossing);
68  });
69  function loopInView(element,timeline){
70    const state={timeline,visible:false};loops.push(state);
71    ScrollTrigger.create({trigger:element,start:'top bottom',end:'bottom top',onToggle:self=>{state.visible=self.isActive;self.isActive&&!document.hidden?timeline.play():timeline.pause();}});
72  }
73  function setup(){
74    navigation?.kill();context?.revert();loops.length=0;trigger=null;applyPhoneTier();
75    if(track){track.classList.toggle('orbit-ready',canOrbit());track.dataset.field='1';track.dataset.arrival='1';if(controls)controls.inert=false;cards.forEach(card=>{card.inert=false;card.removeAttribute('aria-hidden');card.removeAttribute('tabindex');card.classList.remove('is-front');});}
76    context=gsap.context(()=>{
77      if(off())return;
78      if(track&&canOrbit()){
79        orbitState={position:0,arrival:0,field:0};
80        const animation=gsap.timeline({onUpdate:()=>renderOrbit(orbitState.position),scrollTrigger:{id:'discovery-orbit',trigger:track,start:()=> 'top '+Math.max(96,(stableHeight()-track.querySelector('.orbit-viewport').offsetHeight)/2),end:()=>'+='+Math.max(500,track.offsetHeight-track.querySelector('.orbit-viewport').offsetHeight),scrub:.55,invalidateOnRefresh:true}});
81        animation.addLabel('signal',0).to(orbitState,{field:1,duration:.16,ease:'power1.out'},'signal')
82          .addLabel('assemble',.18).to(orbitState,{arrival:1,duration:.2,ease:'power2.out'},'assemble')
83          .addLabel('explore',explorationStart).to(orbitState,{position:4,duration:1-explorationStart,ease:'none'},'explore');
84        trigger=animation.scrollTrigger;renderOrbit(orbitState.position);
85      }
86      document.querySelectorAll('.sector-crossing').forEach((crossing,i)=>{
87        const field=crossing.querySelector('.crossing-field'),path=crossing.querySelector('.crossing-path:not(.echo)'),cloud=crossing.querySelector('.crossing-cloud');
88        const length=path.getTotalLength();gsap.set(path,{strokeDasharray:length,strokeDashoffset:length*.75});
89        gsap.timeline({scrollTrigger:{trigger:crossing.parentElement,start:'top bottom',end:'top 20%',scrub:.65}})
90          .fromTo(field,{x:i%2?140:-140,y:100,scale:.85},{x:i%2?-110:110,y:-65,scale:1.3,duration:1,ease:'none'},0)
91          .to(path,{strokeDashoffset:0,duration:.8,ease:'none'},0)
92          .fromTo(cloud,{xPercent:i%2?-7:7,scale:1.0},{xPercent:i%2?10:-10,scale:1.22,duration:1,ease:'none'},0);
93      });
94      document.querySelectorAll('.measurement-card').forEach((card,i)=>{
95        const tl=gsap.timeline({paused:true});
96        if(i===0)tl.fromTo(card.querySelector('.instrument-scan'),{yPercent:-100},{yPercent:100,duration:4.2,repeat:-1,ease:'sine.inOut'});
97        if(i===1){
98          const line=card.querySelector('.signal-path'),length=line.getTotalLength();
99          tl.fromTo(line,{strokeDasharray:length,strokeDashoffset:length},{strokeDashoffset:0,duration:3,repeat:-1,repeatDelay:1.6,ease:'power1.inOut'});
100          tl.to(card.querySelector('.signal-marker'),{opacity:.3,duration:.9,repeat:-1,yoyo:true,ease:'sine.inOut'},0);
101        }
102        if(i===2)tl.to(card.querySelectorAll('.decision-instrument i'),{backgroundColor:'#b7efd5',borderColor:'#3da081',duration:.8,stagger:.9,repeat:-1,yoyo:true,ease:'sine.inOut'});
103        loopInView(card,tl);
104      });
105      const lab=document.querySelector('.experience-lab');
106      if(lab){
107        const tl=gsap.timeline({paused:true});
108        tl.to(lab.querySelector('.lab-world'),{xPercent:4,scale:1.07,duration:12,repeat:-1,yoyo:true,ease:'sine.inOut'},0);
109        tl.to(lab.querySelector('.lab-floating-chip'),{y:-10,duration:3.2,repeat:-1,yoyo:true,ease:'sine.inOut'},0);
110        tl.to(lab.querySelector('.lab-orbit'),{rotationZ:18,duration:10,repeat:-1,yoyo:true,ease:'sine.inOut'},0);
111        loopInView(lab,tl);
112      }
113      const launch=document.querySelector('.launch-instrument');
114      if(launch){
115        const tl=gsap.timeline({paused:true});
116        tl.to(launch.querySelector('.ring-one'),{rotationZ:340,duration:24,repeat:-1,ease:'none'},0)
117          .to(launch.querySelector('.ring-two'),{rotationZ:-360,duration:32,repeat:-1,ease:'none'},0)
118          .to(launch.querySelector('.ring-three'),{rotationY:80,rotationX:24,duration:9,repeat:-1,yoyo:true,ease:'sine.inOut'},0)
119          .to(launch.querySelector('.launch-core'),{y:-9,duration:3.5,repeat:-1,yoyo:true,ease:'sine.inOut'},0);
120        loopInView(launch,tl);
121      }
122    });
123    ScrollTrigger.refresh(true);
124  }
125  const labButtons=[...document.querySelectorAll('[data-lab]')],lab=d
125ocument.querySelector('.experience-lab'),readout=document.querySelector('#lab-readout');
126  const labCopy=[['Move with the story.','Use depth, transitions and responsive feedback to explain how the experience connects. Give visitors control over motion.','Motion with purpose'],['Make the next step obvious.','Use clear hierarchy, useful content and thoughtful spacing so people can understand the offer on any screen.','Clarity at every size'],['Be understood beyond the screen.','Connect a memorable experience to readable content, relevant pages and a clear information structure.','Built for discovery']];
127  let labTween;
128  function selectLab(i){
129    labButtons.forEach((b,n)=>{b.setAttribute('aria-selected',String(n===i));b.tabIndex=n===i?0:-1;});readout.setAttribute('aria-labelledby',labButtons[i].id);
130    readout.querySelector('span').textContent=labCopy[i][0];readout.querySelector('p').textContent=labCopy[i][1];lab.querySelector('.lab-floating-chip span').textContent=labCopy[i][2];
131    labTween?.kill();labTween=gsap.timeline();const d=off()?0:.65;
132    labTween.to(lab.querySelector('.lab-page'),{rotationY:i===0?-12:i===1?0:8,rotationX:i===0?4:0,x:i===2?-10:0,scale:i===1?1.06:1,duration:d,ease:'power3.out'},0)
133      .to(lab.querySelector('.lab-grid'),{opacity:i===2?.7:0,duration:d},0)
134      .to(lab.querySelectorAll('.lab-page-line'),{scaleX:i===1?1.08:1,transformOrigin:'left',duration:d},0);
135  }
136  labButtons.forEach((button,i)=>{
137    button.addEventListener('click',()=>selectLab(i));
138    button.addEventListener('keydown',event=>{let n=i;if(event.key==='ArrowRight')n=(i+1)%3;else if(event.key==='ArrowLeft')n=(i+2)%3;else if(event.key==='Home')n=0;else if(event.key==='End')n=2;else return;event.preventDefault();labButtons[n].focus();selectLab(n);});
139  });
140  const sync=()=>{if(off())labTween?.progress(1);setup();};
141  addEventListener('scalz:preview-effects',sync);document.addEventListener('scalz:preview-effects',sync);reduced.addEventListener('change',sync);
142  document.addEventListener('visibilitychange',()=>loops.forEach(({timeline,visible})=>document.hidden||!visible?timeline.pause():timeline.play()));
143  // Like the film stage, give the orbit controls their own space: the chat bubble steps aside while the orbit fills the screen.
144  const orbitStage=track?.querySelector('.orbit-viewport');
145  if(orbitStage&&'IntersectionObserver' in window)new IntersectionObserver(entries=>{root.classList.toggle('in-orbit-stage',track.classList.contains('orbit-ready')&&entries[0].isIntersecting&&entries[0].intersectionRatio>=.6);},{threshold:[0,.6]}).observe(orbitStage);
146  let timer,lastWidth=innerWidth,lastHeight=stableHeight();
147  addEventListener('resize',()=>{clearTimeout(timer);timer=setTimeout(()=>{
148    const width=innerWidth,height=stableHeight();
149    if(touch&&width===lastWidth&&Math.abs(height-lastHeight)<2)return;
150    lastWidth=width;lastHeight=height;setup();
151  },260);},{passive:true});
152  setup();document.fonts?.ready.then(()=>ScrollTrigger.refresh(true));
153})();

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.