1document.addEventListener('DOMContentLoaded', () => { 2 const body = document.body; 3 body.classList.add('js-ready'); 4 5 const menu = document.querySelector('.site-menu'); 6 const toggle = document.querySelector('.site-header__toggle'); 7 const overlay = document.querySelector('[data-menu-overlay]'); 8 const drawer = document.querySelector('.site-menu__drawer'); 9 const closeButton = document.querySelector('.site-menu__close'); 10 let closeAnimationTimer = null; 11 const menuLinks = menu ? Array.from(menu.querySelectorAll('a')) : []; 12 let previousFocus = null; 13 14 const openMenu = () => { 15 if (!menu || !toggle || !drawer) { 16 return; 17 } 18 19 previousFocus = document.activeElement; 20 menu.classList.add('is-open'); 21 menu.setAttribute('aria-hidden', 'false'); 22 toggle.setAttribute('aria-expanded', 'true'); 23 body.classList.add('menu-open'); 24 25 window.setTimeout(() => { 26 if (closeButton) { 27 closeButton.focus(); 28 } 29 }, 40); 30 }; 31 32 const closeMenu = () => { 33 if (!menu || !toggle) { 34 return; 35 } 36 37 menu.classList.remove('is-open'); 38 menu.setAttribute('aria-hidden', 'true'); 39 toggle.setAttribute('aria-expanded', 'false'); 40 body.classList.remove('menu-open'); 41 42 if (previousFocus instanceof HTMLElement) { 43 previousFocus.focus(); 44 } 45 }; 46 47 if (menu && toggle) { 48 toggle.addEventListener('click', () => { 49 const isExpanded = toggle.getAttribute('aria-expanded') === 'true'; 50 if (isExpanded) { 51 closeMenu(); 52 return; 53 } 54 55 openMenu(); 56 }); 57 58 if (overlay) { 59 overlay.addEventListener('click', closeMenu); 60 } 61 62 if (closeButton) { 63 closeButton.addEventListener('click', (event) => { 64 event.preventDefault(); 65 closeButton.classList.add('is-animating'); 66 67 if (closeAnimationTimer) { 68 window.clearTimeout(closeAnimationTimer); 69 } 70 71 closeAnimationTimer = window.setTimeout(() => { 72 closeButton.classList.remove('is-animating'); 73 closeMenu(); 74 }, 180); 75 }); 76 } 77 78 menuLinks.forEach((link) => { 79 link.addEventListener('click', closeMenu); 80 }); 81 82 document.addEventListener('keydown', (event) => { 83 if (event.key === 'Escape' && menu.classList.contains('is-open')) { 84 closeMenu(); 85 } 86 }); 87 } 88 89 const contactTabs = document.querySelector('[data-contact-tabs]'); 90 91 if (contactTabs) { 92 const buttons = Array.from(contactTabs.querySelectorAll('[data-contact-tab]')); 93 const panels = Array.from(contactTabs.querySelectorAll('[data-contact-panel]')); 94 95 buttons.forEach((button) => { 96 button.addEventListener('click', () => { 97 const target = button.getAttribute('data-contact-tab'); 98 99 buttons.forEach((item) => { 100 const isActive = item === button; 101 item.classList.toggle('is-active', isActive); 102 item.setAttribute('aria-selected', String(isActive)); 103 }); 104 105 panels.forEach((panel) => { 106 const isActive = panel.getAttribute('data-contact-panel') === target; 107 panel.classList.toggle('is-active', isActive); 108 panel.hidden = !isActive; 109 }); 110 }); 111 }); 112 } 113 114 115 const faqAccordions = Array.from(document.querySelectorAll('[data-faq-accordion]')); 116 117 faqAccordions.forEach((accordion) => { 118 const items = Array.from(accordion.querySelectorAll('[data-faq-item]')); 119 120 items.forEach((item) => { 121 const trigger = item.querySelector('[data-faq-trigger]'); 122 const panel = item.querySelector('[data-faq-panel]'); 123 124 if (!trigger || !panel) { 125 return; 126 } 127 128 trigger.addEventListener('click', () => { 129 const isOpen = trigger.getAttribute('aria-expanded') === 'true'; 130 131 items.forEach((otherItem) => { 132 const otherTrigger = otherItem.querySelector('[data-faq-trigger]'); 133 const otherPanel = otherItem.querySelector('[data-faq-panel]'); 134 135 if (!otherTrigger || !otherPanel) { 136 return; 137 } 138 139 otherItem.classList.remove('is-open'); 140 otherTrigger.setAttribute('aria-expanded', 'false'); 141 otherPanel.hidden = true; 142 }); 143 144 if (!isOpen) { 145 item.classList.add('is-open'); 146 trigger.setAttribute('aria-expanded', 'true'); 147 panel.hidden = false; 148 } 149 }); 150 }); 151 }); 152 const portfolioCards = Array.from(document.querySelectorAll('[data-portfolio-card]')); 153 154 portfolioCards.forEach((card) => { 155 const slides = Array.from(card.querySelectorAll('[data-portfolio-slide]')); 156 const mediaSlides = Array.from(card.querySelectorAll('[data-portfolio-media-slide]')); 157 const prev = card.querySelector('[data-portfolio-prev]'); 158 const next = card.querySelector('[data-portfolio-next]'); 159 let currentIndex = 0; 160 161 if (!slides.length) { 162 return; 163 } 164 165 const setActiveSlide = (index) => { 166 currentIndex = (index + slides.length) % slides.length; 167
168 slides.forEach((slide, slideIndex) => { 169 const isActive = slideIndex === currentIndex; 170 slide.classList.toggle('is-active', isActive); 171 slide.hidden = !isActive; 172 }); 173 174 mediaSlides.forEach((slide, slideIndex) => { 175 const isActive = slideIndex === currentIndex; 176 slide.classList.toggle('is-active', isActive); 177 slide.hidden = !isActive; 178 }); 179 }; 180 181 const resetCard = () => { 182 currentIndex = 0; 183 slides.forEach((slide, slideIndex) => { 184 const isActive = slideIndex === 0; 185 slide.classList.toggle('is-active', isActive); 186 slide.hidden = !isActive; 187 }); 188 189 mediaSlides.forEach((slide, slideIndex) => { 190 const isActive = slideIndex === 0; 191 slide.classList.toggle('is-active', isActive); 192 slide.hidden = !isActive; 193 }); 194 }; 195 196 card.addEventListener('mouseenter', () => setActiveSlide(0)); 197 card.addEventListener('mouseleave', resetCard); 198 199 if (prev) { 200 prev.addEventListener('click', (event) => { 201 event.preventDefault(); 202 event.stopPropagation(); 203 setActiveSlide(currentIndex - 1); 204 }); 205 } 206 207 if (next) { 208 next.addEventListener('click', (event) => { 209 event.preventDefault(); 210 event.stopPropagation(); 211 setActiveSlide(currentIndex + 1); 212 }); 213 } 214 215 resetCard(); 216 }); 217 218 const testimonialSliders = Array.from(document.querySelectorAll('[data-testimonial-slider]')); 219 220 testimonialSliders.forEach((testimonialSlider) => { 221 const slides = Array.from(testimonialSlider.querySelectorAll('[data-testimonial-slide]')); 222 const mediaSlides = Array.from(testimonialSlider.querySelectorAll('[data-testimonial-media-slide]')); 223 const dots = Array.from(testimonialSlider.querySelectorAll('[data-testimonial-dot]')); 224 const prev = testimonialSlider.querySelector('[data-testimonial-prev]'); 225 const next = testimonialSlider.querySelector('[data-testimonial-next]'); 226 let currentIndex = 0; 227 228 const setActiveSlide = (index) => { 229 if (!slides.length) { 230 return; 231 } 232 233 currentIndex = (index + slides.length) % slides.length; 234 235 slides.forEach((slide, slideIndex) => { 236 const isActive = slideIndex === currentIndex; 237 slide.classList.toggle('is-active', isActive); 238 slide.hidden = !isActive; 239 }); 240 241 mediaSlides.forEach((slide, slideIndex) => { 242 const isActive = slideIndex === currentIndex; 243 slide.classList.toggle('is-active', isActive); 244 slide.hidden = !isActive; 245 }); 246 247 dots.forEach((dot, dotIndex) => { 248 const isActive = dotIndex === currentIndex; 249 dot.classList.toggle('is-active', isActive); 250 dot.setAttribute('aria-pressed', String(isActive)); 251 }); 252 }; 253 254 if (prev) { 255 prev.addEventListener('click', () => setActiveSlide(currentIndex - 1)); 256 } 257 258 if (next) { 259 next.addEventListener('click', () => setActiveSlide(currentIndex + 1)); 260 } 261 262 dots.forEach((dot, dotIndex) => { 263 dot.addEventListener('click', () => setActiveSlide(dotIndex)); 264 }); 265 266 setActiveSlide(0); 267 }); 268 269 const fileFieldWrappers = Array.from(document.querySelectorAll(".contact-layout__form .field:has(input[type='file']), .job-single__apply-wrap .job-manager-application-form fieldset:has(input[type='file'])")); 270 271 fileFieldWrappers.forEach((wrapper) => { 272 const input = wrapper.querySelector("input[type='file']"); 273 274 if (!input) { 275 return; 276 } 277 278 const defaultLabel = 'Upload your CV'; 279 280 const updateLabel = () => { 281 const fileName = input.files && input.files.length ? input.files[0].name : ''; 282 wrapper.dataset.uploadLabel = fileName || defaultLabel; 283 wrapper.classList.toggle('is-file-selected', Boolean(fileName)); 284 }; 285 286 wrapper.dataset.uploadLabel = defaultLabel; 287 updateLabel(); 288 289 wrapper.addEventListener('click', (event) => { 290 if (event.target !== input) { 291 input.click(); 292 } 293 }); 294 295 input.addEventListener('change', updateLabel); 296 }); 297 298 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 299 return; 300 } 301 302 const revealGroups = [ 303 { selector: '.hero__content--home', variant: 'reveal--hero' }, 304 { selector: '.logo-strip--home .logo-strip__item', variant: 'reveal--up', stagger: 0.05 }, 305 { selector: '.home-intro__content', variant: 'reveal--up' }, 306 { selector: '.home-intro__media', variant: 'reveal--right' }, 307 { selector: '.section-heading', variant: 'reveal--up' }, 308 { selector: '.card-grid--functions-home .content-card', variant: 'reveal--up', stagger: 0.08 },
309 { selector: '.portfolio-grid--home-teasers .content-card', variant: 'reveal--scale', stagger: 0.06 }, 310 { selector: '.home-testimonials', variant: 'reveal--up' }, 311 { selector: '.home-promo', variant: 'reveal--up', stagger: 0.1 } 312 ]; 313 314 const revealElements = []; 315 316 revealGroups.forEach((group) => { 317 const elements = Array.from(document.querySelectorAll(group.selector)); 318 elements.forEach((element, index) => { 319 element.classList.add('reveal', group.variant); 320 const delay = group.stagger ? index * group.stagger : 0; 321 element.style.setProperty('--reveal-delay', `${delay.toFixed(2)}s`); 322 revealElements.push(element); 323 }); 324 }); 325 326 if (!revealElements.length || !('IntersectionObserver' in window)) { 327 revealElements.forEach((element) => element.classList.add('in-view')); 328 return; 329 } 330 331 const revealObserver = new IntersectionObserver( 332 (entries, observer) => { 333 entries.forEach((entry) => { 334 if (!entry.isIntersecting) { 335 return; 336 } 337 338 entry.target.classList.add('in-view'); 339 observer.unobserve(entry.target); 340 }); 341 }, 342 { 343 threshold: 0.18, 344 rootMargin: '0px 0px -8% 0px' 345 } 346 ); 347 348 revealElements.forEach((element) => revealObserver.observe(element)); 349}); 350 351 352 353 354
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.