1(() => { 2 const desktopView = window.matchMedia("(min-width: 961px)"); 3 const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); 4 const easing = "cubic-bezier(0.22, 1, 0.36, 1)"; 5 const revealedSections = new WeakSet(); 6 const activeAnimations = new Set(); 7 let observer = null; 8 let initialized = false; 9 10 const play = (element, keyframes, options = {}) => { 11 if (!element || typeof element.animate !== "function") return null; 12 13 const animation = element.animate(keyframes, { 14 duration: 520, 15 delay: 0, 16 easing, 17 fill: "both", 18 ...options, 19 }); 20 21 activeAnimations.add(animation); 22 animation.finished 23 .catch(() => {}) 24 .finally(() => { 25 activeAnimations.delete(animation); 26 animation.cancel(); 27 }); 28 return animation; 29 }; 30 31 const fadeUp = (element, delay = 0, distance = 16, duration = 520) => 32 play( 33 element, 34 [ 35 { opacity: 0, transform: `translate3d(0, ${distance}px, 0)` }, 36 { opacity: 1, transform: "translate3d(0, 0, 0)" }, 37 ], 38 { delay, duration }, 39 ); 40 41 const fadeSide = (element, direction, delay = 0, distance = 22, duration = 560) => 42 play( 43 element, 44 [ 45 { opacity: 0, transform: `translate3d(${direction * distance}px, 0, 0)` }, 46 { opacity: 1, transform: "translate3d(0, 0, 0)" }, 47 ], 48 { delay, duration }, 49 ); 50 51 const revealCases = (section) => { 52 fadeSide(section.querySelector(".section-heading"), 1, 0, 20, 540); 53 fadeSide(section.querySelector(".case-featured"), -1, 100, 24, 620); 54 55 section.querySelectorAll(".case-supporting > article").forEach((card, index) => { 56 fadeSide(card, 1, 170 + index * 90, 20, 560); 57 }); 58 fadeSide(section.querySelector(".case-heading-more"), 1, 350, 18, 520); 59 }; 60 61 const revealScenes = (section) => { 62 fadeUp(section.querySelector(".section-heading"), 0, 14, 500); 63 section.querySelectorAll(".scene-panorama > a").forEach((card, index) => { 64 fadeUp(card, 110 + index * 65, 18, 540); 65 }); 66 fadeUp(section.querySelector(".scene-more-links"), 420, 12, 460); 67 }; 68 69 const revealProcess = (section) => { 70 fadeUp(section.querySelector(".section-heading"), 0, 14, 500); 71 72 section.querySelectorAll(".delivery-journey > li").forEach((step, index) => { 73 const delay = 90 + index * 100; 74 play( 75 step.querySelector(":scope > span"), 76 [ 77 { opacity: 0.2, filter: "brightness(0.7)", transform: "scale(0.82)" }, 78 { opacity: 1, filter: "brightness(1.2)", transform: "scale(1.045)", offset: 0.72 }, 79 { opacity: 1, filter: "brightness(1)", transform: "scale(1)" }, 80 ], 81 { delay, duration: 520 }, 82 ); 83 fadeUp(step.querySelector("h3"), delay + 90, 8, 400); 84 fadeUp(step.querySelector("p"), delay + 130, 7, 400); 85 play( 86 step.querySelector(".process-step-arrow"), 87 [ 88 { opacity: 0, transform: "translate3d(-8px, 0, 0) scaleX(0.72)" }, 89 { opacity: 1, transform: "translate3d(0, 0, 0) scaleX(1)" }, 90 ], 91 { delay: delay + 110, duration: 460 }, 92 ); 93 }); 94 95 const evidence = section.querySelector(".delivery-evidence"); 96 if (!evidence) return; 97 const figures = evidence.querySelectorAll(":scope > figure"); 98 fadeSide(figures[0], -1, 880, 18, 520); 99 fadeUp(evidence.querySelector(":scope > span"), 980, 10, 420); 100 fadeSide(figures[1], 1, 1060, 18, 520); 101 fadeSide(evidence.querySelector(":scope > aside"), 1, 1160, 16, 500); 102 }; 103 104 const revealKnowledge = (section) => { 105 fadeUp(section.querySelector(".section-heading"), 0, 14, 500); 106 section.querySelectorAll(".project-start-paths > article").forEach((card, index) => { 107 fadeUp(card, 110 + index * 80, 16, 540); 108 }); 109 fadeSide(section.querySelector(".project-consult-card"), 1, 260, 18, 560); 110 fadeUp(section.querySelector(".project-assurances"), 410, 12, 500); 111 }; 112 113 const revealFaq = (section) => { 114 fadeUp(section.querySelector(".section-heading"), 0, 14, 500); 115 fadeSide(section.querySelector(".faq-list"), -1, 120, 18, 560); 116 fadeSide(section.querySelector(".faq-expert-card"), 1, 220, 18, 560); 117 }; 118 119 const revealContact = (section) => { 120 fadeSide(section.querySelector(".home-cta-copy"), -1, 0, 24, 560); 121 fadeSide(section.querySelector(".home-cta-media"), 1, 100, 24, 620); 122 }; 123 124 const revealers = new Map([ 125 ["cases", revealCases], 126 ["home-scenes", revealScenes], 127 ["home-process", revealProcess], 128 ["home-knowledge", revealKnowledge], 129 ["home-faq", revealFaq], 130 ["home-contact", revealContact], 131 ]); 132 133 const revealSection = (section) => { 134 if (!section || revealedSections.has(section)) return; 135 revealedSections.add(section); 136 section.classList.add("secondary-motion-revealed"); 137 revealers.get(section.id)?.(section); 138 observer?.unobserve(section); 139 }; 140 141 const setupFaqMotion = () => { 142 document.querySelectorAll("#home-faq .faq-list details").forEach((details) => { 143 const summary = details.querySelector("summary"); 144 const panel = details.querySelector(":scope > div"); 145 if (!summary || !panel || typeof panel.animate !== "function" || summary.dataset.motionReady) return; 146 147 summary.dataset.motionReady = "true"; 148 let panelAnimation = null; 149 150 summary.addEventListener("click", (event) => { 151 if (!desktopView.matches || reduceMotion.matches) return; 152 event.preventDefault(); 153 panelAnimation?.cancel(); 154 155 if (details.open) { 156 const startHeight = panel.getBoundingClientRect().height; 157 panelAnimation = panel.animate( 158 [ 159 { height: `${startHeight}px`, opacity: 1, transform: "translate3d(0, 0, 0)" }, 160 { height: "0px", opacity: 0, transform: "translate3d(0, -6px, 0)" }, 161 ], 162 { duration: 220, easing, fill: "both" }, 163 ); 164 panelAnimation.finished 165 .then(() => { 166 details.open = false;
167 panelAnimation.cancel(); 168 panelAnimation = null; 169 }) 170 .catch(() => {}); 171 return; 172 } 173 174 details.open = true; 175 const endHeight = panel.scrollHeight; 176 panelAnimation = panel.animate( 177 [ 178 { height: "0px", opacity: 0, transform: "translate3d(0, -6px, 0)" }, 179 { height: `${endHeight}px`, opacity: 1, transform: "translate3d(0, 0, 0)" }, 180 ], 181 { duration: 260, easing, fill: "both" }, 182 ); 183 panelAnimation.finished 184 .then(() => { 185 panelAnimation.cancel(); 186 panelAnimation = null; 187 }) 188 .catch(() => {}); 189 }); 190 }); 191 }; 192 193 const stopMotion = () => { 194 observer?.disconnect(); 195 observer = null; 196 activeAnimations.forEach((animation) => animation.cancel()); 197 activeAnimations.clear(); 198 initialized = false; 199 }; 200 201 const init = () => { 202 if (initialized || !desktopView.matches || reduceMotion.matches) return; 203 initialized = true; 204 document.body.classList.add("secondary-motion-enabled"); 205 setupFaqMotion(); 206 207 const sections = Array.from(revealers.keys()) 208 .map((id) => document.getElementById(id)) 209 .filter(Boolean); 210 211 if (!("IntersectionObserver" in window)) { 212 sections.forEach(revealSection); 213 return; 214 } 215 216 observer = new IntersectionObserver( 217 (entries) => { 218 entries.forEach((entry) => { 219 if (entry.isIntersecting && entry.intersectionRatio >= 0.14) { 220 revealSection(entry.target); 221 } 222 }); 223 }, 224 { threshold: [0, 0.14, 0.28], rootMargin: "0px 0px -10% 0px" }, 225 ); 226 sections.forEach((section) => { 227 if (!revealedSections.has(section)) observer.observe(section); 228 }); 229 }; 230 231 desktopView.addEventListener?.("change", (event) => { 232 if (event.matches) init(); 233 else stopMotion(); 234 }); 235 reduceMotion.addEventListener?.("change", (event) => { 236 if (event.matches) stopMotion(); 237 else init(); 238 }); 239 240 init(); 241})();
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.