1document.addEventListener('DOMContentLoaded', function () { 2 (() => { 3 "use strict"; 4 5 /****************************************************************** 6 * A) Delad gate: whenPageEnterEnd() 7 ******************************************************************/ 8 function whenPageEnterEndFactory() { 9 let resolveFn; 10 const p = new Promise((r) => (resolveFn = r)); 11 return { 12 promise: p, 13 resolve: () => resolveFn?.(), 14 }; 15 } 16 17 // global-ish (pÃ¥ window för säker delning över moduler) 18 const PAGE_ENTER = (window.__PAGE_ENTER__ ||= whenPageEnterEndFactory()); 19 20 const prefersReduced = 21 window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 22 23 const waitForAnim = (el, durMs) => 24 new Promise((resolve) => { 25 let done = false; 26 const off = () => { 27 if (done) return; 28 done = true; 29 el.removeEventListener("animationend", onEnd, true); 30 clearTimeout(timer); 31 resolve(); 32 }; 33 const onEnd = (e) => { if (e.target === el) off(); }; 34 const timer = setTimeout(off, durMs + 50); 35 el.addEventListener("animationend", onEnd, true); 36 }); 37 38 /****************************************************************** 39 * B) Motion-lager: flytta allt som ska animeras i ett eget lager 40 ******************************************************************/ 41 function ensureMotionLayer() { 42 const wrap = document.querySelector('.page-transition'); 43 if (!wrap) return null; 44 45 // Redan strukturerad? 46 let motion = wrap.querySelector(':scope > .page-transition__motion'); 47 if (motion) return motion; 48 49 // Skapa lagret 50 motion = document.createElement('div'); 51 motion.className = 'page-transition__motion'; 52 53 // Vad ska INTE flyttas in i motion-lagret? 54 const EXCLUDE_SEL = [ 55 'header.is-fixed', 56 '.site-nav.is-fixed', 57 '[data-stay-fixed]', 58 '[data-no-motion]', 59 '.clip-path-header', // <-- NYTT: lÃ¥t clip-path header stÃ¥ utanför 60 '.page-transition__motion' 61 ].join(','); 62 63 // Flytta alla barn som inte matchar EXCLUDE_SEL 64 const children = Array.from(wrap.children); 65 children.forEach(el => { 66 if (!el.matches(EXCLUDE_SEL)) motion.appendChild(el); 67 }); 68 69 // Sätt in motion-lagret först 70 wrap.prepend(motion); 71 72 return motion; 73 } 74 75 // Kör direkt: 76 const motionLayer = ensureMotionLayer(); 77 78 /****************************************************************** 79 * C) Page transition modul 80 ******************************************************************/ 81 (function pageTransitionModule() { 82 const WRAP_SEL = ".page-transition"; 83 const ENTER_CLASS = "is-entering"; 84 const LEAVE_CLASS = "is-leaving"; 85 const DURATION_MS = 220; 86 const NO_TRANSITION_ATTR = "data-no-transition"; 87 88 const wrap = document.querySelector(WRAP_SEL); 89 90 // Om transition saknas â släpp innehÃ¥llet direkt efter DOM-ready. 91 if (!wrap) { 92 setTimeout(PAGE_ENTER.resolve, 0); 93 return; 94 } 95 96 const runEnter = async () => { 97 if (!prefersReduced) { 98 wrap.classList.remove(LEAVE_CLASS); 99 100 // Se till att preload-state är aktivt när vi triggar animationen 101 if (!wrap.classList.contains("is-preload")) { 102 wrap.classList.add("is-preload"); 103 } 104 105 // Force reflow sÃ¥ browsern hinner "ta" preload-läget 106 void wrap.offsetWidth; 107 108 // Starta enter-animationen 109 wrap.classList.add(ENTER_CLASS); 110 await waitForAnim(wrap, DURATION_MS); 111 wrap.classList.remove(ENTER_CLASS); 112 } 113 114 // När enter-animationen är klar â ta bort preload 115 wrap.classList.remove("is-preload"); 116 117 PAGE_ENTER.resolve(); 118 if (window.ScrollTrigger) { 119 requestAnimationFrame(() => window.ScrollTrigger.refresh()); 120 } 121 }; 122 123 const runLeave = async (navigate) => { 124 if (!prefersReduced) { 125 wrap.classList.add(LEAVE_CLASS); 126 await waitForAnim(wrap, DURATION_MS); 127 } 128 navigate(); 129 }; 130
131 // Init ENTER 132 if (document.readyState === "loading") { 133 document.addEventListener("DOMContentLoaded", runEnter, { once: true }); 134 } else { 135 runEnter(); 136 } 137 138 // bfcache 139 window.addEventListener("pageshow", (e) => { 140 if (e.persisted) { 141 // skapa en NY gate för nästa cykel 142 window.__PAGE_ENTER__ = whenPageEnterEndFactory(); 143 // uppdatera vÃ¥r referens 144 Object.assign(PAGE_ENTER, window.__PAGE_ENTER__); 145 runEnter(); 146 } 147 }); 148 149 // Link-delegation 150 document.addEventListener("click", (e) => { 151 if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; 152 const a = e.target.closest("a"); 153 if (!a) return; 154 if (a.closest('[data-archive-filter-nav="1"]')) return; 155 if (a.hasAttribute(NO_TRANSITION_ATTR)) return; 156 157 const href = a.getAttribute("href"); 158 if (!href) return; 159 160 const url = new URL(a.href, window.location.href); 161 if (a.target && a.target !== "_self") return; 162 if (a.hasAttribute("download")) return; 163 if (url.origin !== window.location.origin) return; 164 if (href.startsWith("mailto:") || href.startsWith("tel:")) return; 165 if (url.pathname === window.location.pathname && url.hash && url.hash !== "#") return; 166 167 e.preventDefault(); 168 runLeave(() => { window.location.href = url.href; }); 169 }, { capture: true }); 170 })(); 171 172 /****************************************************************** 173 * D) Sequential modul â väntar pÃ¥ PAGE_ENTER.promise 174 ******************************************************************/ 175 (function sequentialModule() { 176 // Viktigt: dynamisk flagga (kan dyka upp sent) 177 const isSeqEnabled = () => !!(window.w_functions && window.w_functions.sequentialAnimations); 178 179 // anti-hopp: lägg pÃ¥ inert tidigt 180 const markInert = () => { 181 document.querySelectorAll(".sequential").forEach((el) => { 182 if (!el.classList.contains("sequential-disabled")) el.classList.add("sequential-inert"); 183 }); 184 }; 185 const clearInert = () => { 186 document.querySelectorAll(".sequential.sequential-inert").forEach((el) => el.classList.remove("sequential-inert")); 187 }; 188 189 // === Helpers (GSAP + fallback) === 190 function initSequentialOnce(scroller) { 191 // Startläge för alla (bara en gÃ¥ng â respektera redan initierade) 192 const fresh = Array.from(document.querySelectorAll('.sequential:not([data-seq-init])')); 193 fresh.forEach(el => { 194 el.setAttribute('data-seq-init', '1'); 195 }); 196 // sätt startläge i ett svep (effektivare) 197 if (fresh.length && typeof gsap !== "undefined") gsap.set(fresh, { autoAlpha: 0, y: 24 }); 198 199 // Batcha nya och tidigare oinitierade element 200 if (typeof ScrollTrigger !== "undefined") { 201 ScrollTrigger.batch('.sequential', { 202 start: "top 95%", 203 scroller: scroller || null, 204 once: true, 205 onEnter: (batch) => { 206 // ta bort inert pÃ¥ de som faktiskt ska in just nu 207 batch.forEach(el => el.classList.remove('sequential-inert')); 208 209 gsap.to(batch, { 210 autoAlpha: 1, 211 y: 0, 212 duration: 0.42, 213 ease: "power2.out", 214 stagger: 0.045, 215 overwrite: true, 216 onComplete: () => { 217 // rensa inline-styles för ren layout efterÃ¥t
218 batch.forEach(el => { 219 el.classList.add('revealed'); 220 gsap.set(el, { clearProps: "opacity,visibility,transform" }); 221 }); 222 } 223 }); 224 } 225 }); 226 } 227 } 228 229 function refreshScrollTriggersAfterImages(container = document) { 230 requestAnimationFrame(() => { 231 const imgs = Array.from(container.querySelectorAll('img')); 232 if (!imgs.length) { window.ScrollTrigger?.refresh(); return; } 233 let pending = imgs.length; 234 const done = () => { if (--pending <= 0) window.ScrollTrigger?.refresh(); }; 235 imgs.forEach(img => { 236 if (img.complete) done(); 237 else { 238 img.addEventListener('load', done, { once: true }); 239 img.addEventListener('error', done, { once: true }); 240 } 241 }); 242 }); 243 } 244 245 function setupSequentialMultipleObserver() { 246 const elements = document.querySelectorAll(".sequential-multiple"); 247 if (!elements.length) return; 248 const observer = new IntersectionObserver((entries, obs) => { 249 entries.forEach((entry, index) => { 250 if (entry.isIntersecting) { 251 const el = entry.target; 252 if (!el.classList.contains("animation-done")) { 253 if (typeof gsap !== 'undefined') { 254 gsap.fromTo(el, { y: 24, opacity: 0 }, { y: 0, opacity: 1, duration: 0.35, ease: "power2.out", delay: 0.045 * index }); 255 } else { 256 el.style.opacity = 1; 257 el.style.transform = "translateY(0)"; 258 } 259 el.classList.add("animation-done"); 260 } 261 obs.unobserve(el); 262 } 263 }); 264 }, { threshold: 0.1 }); 265 elements.forEach(el => observer.observe(el)); 266 } 267 268 const HAS_GSAP = () => (typeof window.gsap !== "undefined" && typeof window.ScrollTrigger !== "undefined"); 269 270 const reinit = () => { 271 if (!isSeqEnabled()) { // avstängt â visa direkt 272 document.querySelectorAll(".sequential").forEach(el => el.classList.add("sequential-disabled")); 273 clearInert(); 274 return; 275 } 276 if (!HAS_GSAP()) { // GSAP saknas â fallback + ta bort inert 277 clearInert(); 278 setupSequentialMultipleObserver(); 279 return; 280 } 281 gsap.registerPlugin(ScrollTrigger); 282 // sätt startläge för nytillkomna innan vi batchar 283 const newOnes = Array.from(document.querySelectorAll('.sequential:not([data-seq-init])')); 284 if (newOnes.length) gsap.set(newOnes, { autoAlpha: 0, y: 24 }); 285 286 initSequentialOnce(); 287 refreshScrollTriggersAfterImages(document); 288 setupSequentialMultipleObserver(); 289 }; 290 291 // inert tidigt 292 if (document.readyState === "loading") { 293 document.addEventListener("DOMContentLoaded", markInert, { once: true }); 294 } else { 295 markInert(); 296 } 297 298 // Vänta bara pÃ¥ sidövergÃ¥ngen, inte pÃ¥ alla bilder/iframes. 299 PAGE_ENTER.promise.then(() => { 300 reinit(); 301 }); 302 303 // bfcache 304 window.addEventListener("pageshow", (e) => { 305 if (e.persisted) reinit(); 306 }); 307 308 // WooCommerce + AJAX 309 if (window.jQuery) { 310 const $ = window.jQuery; 311 $(document.body).on( 312 'wc_fragments_loaded wc_fragments_refreshed updated_wc_div ' + 313 'added_to_cart removed_from_cart cart_page_refreshed price_slider_slide', 314 reinit 315 ); 316 $(document).on( 317 'found_variation show_variation hide_variation reset_data woocommerce_update_variation_values', 318 reinit 319 ); 320 $(document).ajaxComplete(reinit); 321 } else { 322 const container = document.querySelector('.single-product') || document.body; 323 const mo = new MutationObserver(() => { 324 reinit(); 325 refreshScrollTriggersAfterImages(container); 326 }); 327 mo.observe(container, { childList: true, subtree: true }); 328 } 329
330 // Om config/flaggan kommer sent (t.ex. i footer) â enkel poll 331 let tries = 0; 332 const waitForConfig = setInterval(() => { 333 tries++; 334 if (isSeqEnabled() || tries > 40) { // 40 x 50ms â 2s 335 clearInterval(waitForConfig); 336 reinit(); // kör om när flaggan blivit sann (eller ge upp tyst) 337 } 338 }, 50); 339 340 // Om du kan trigga ett event när w_functions sätts 341 window.addEventListener('w_functions_ready', reinit); 342 })(); 343 344 /****************************************************************** 345 * E) Clip Path Animation â väntar pÃ¥ PAGE_ENTER 346 ******************************************************************/ 347 (function clipPathModule() { 348 const container = document.querySelector(".clip-path-header"); 349 if (!container) return; 350 if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") return; 351 352 const pageGate = window.__PAGE_ENTER__ || { promise: Promise.resolve() }; 353 354 pageGate.promise.then(() => { 355 let tl = gsap.timeline({ 356 scrollTrigger: { 357 trigger: container, 358 pin: true, 359 scrub: 2, 360 start: "top top", 361 end: "+=5000" 362 } 363 }) 364 .to(".clip-path-header figure", { 365 clipPath: "circle(100%)" 366 }); 367 }); 368 })(); 369 370 })(); 371}); 372 373 374 375 376//Clip Path Animation 377document.addEventListener("DOMContentLoaded", function () { 378 "use strict"; 379 380 const container = document.querySelector(".clip-path-header"); 381 if (!container) return; // avbryt om inte elementet finns 382 383 let tl = gsap.timeline({ 384 scrollTrigger: { 385 trigger: container, 386 pin: true, 387 scrub: 2, 388 start: "top top", 389 end: "+=5000" 390 } 391 }) 392 .to(".clip-path-header figure", { 393 clipPath: "circle(100%)" 394 }); 395}); 396 397 398 399// Scale Animation 400// Scale Animation (bidirectional scrub â alltid upp & ner) 401document.addEventListener("DOMContentLoaded", () => { 402 "use strict"; 403 //gsap.registerPlugin(ScrollTrigger); 404 405 const container = document.querySelector(".scale-header"); 406 if (!container) return; 407 408 // Minska iOS-jitter vid adressfälts-resizes 409 ScrollTrigger.config({ ignoreMobileResize: true }); 410 411 const targets = document.querySelectorAll(".scale-header img, .scale-header video"); 412 if (!targets.length) return; 413 414 // --- CSS sätter redan startläge: scale(0.6) translateY(24px) + opacity:0 --- 415 // Intro pÃ¥ load: fade + lyft till y:0 (pÃ¥verkar inte scroll-scalingen) 416 window.addEventListener("load", () => { 417 gsap.to(targets, { 418 autoAlpha: 1, 419 y: 0, 420 duration: 0.6, 421 ease: "power2.out", 422 overwrite: "auto" 423 }); 424 }); 425 426 // Scroll-scaling: frÃ¥n CSS scale(0.6) â scale(1) (scrubbar fritt Ã¥t bÃ¥da hÃ¥ll) 427 const tl = gsap.timeline({ ease: "none" }) 428 .to(targets, { 429 scale: 1, 430 duration: 1, 431 force3D: true 432 }) 433 .to({}, { duration: 1 }); // liten buffert för mjuk pin 434 435 const makeTrigger = (opts) => ScrollTrigger.create({ 436 trigger: ".scale-header", 437 start: "top top", 438 end: opts.end, 439 pin: true, 440 scrub: opts.scrub, // numerisk scrub = inbyggd smoothing vid snabb scroll 441 anticipatePin: 1, 442 invalidateOnRefresh: true, 443 animation: tl, 444 445 // Viktigt: inga lÃ¥sningar â lÃ¥t scrubbning styra bÃ¥da hÃ¥ll 446 // Du kan tillfälligt felsöka med markers: true 447 // markers: true 448 }); 449
450 // Breakpoints 451 ScrollTrigger.matchMedia({ 452 "(max-width: 767px)": function () { 453 makeTrigger({ end: "+=80%", scrub: 0.35 }); 454 }, 455 "(min-width: 768px) and (max-width: 1023px)": function () { 456 makeTrigger({ end: "+=100%", scrub: 0.45 }); 457 }, 458 "(min-width: 1024px)": function () { 459 makeTrigger({ end: "+=120%", scrub: 0.6 }); 460 } 461 }); 462 463 // Orientation-fix 464 window.addEventListener("orientationchange", () => { 465 setTimeout(() => ScrollTrigger.refresh(), 200); 466 }); 467});
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.