@widelab-nc/widelab 1.1.36 → 1.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -2
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/src/index.js +289 -96
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -16,8 +16,87 @@ function customGsapEasing() {
|
|
|
16
16
|
CustomEase.create('loader2', '0.77, 0, 0.175, 1');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Smoothly scrolls to section if current URL contains a hash
|
|
20
|
+
function scrollToHashOnLoad(offsetY = 0) {
|
|
21
|
+
window.addEventListener('load', () => {
|
|
22
|
+
const hash = window.location.hash;
|
|
23
|
+
if (!hash) return;
|
|
24
|
+
|
|
25
|
+
const target = document.querySelector(hash);
|
|
26
|
+
if (!target) return;
|
|
27
|
+
|
|
28
|
+
gsap.to(window, {
|
|
29
|
+
scrollTo: { y: target, offsetY: offsetY },
|
|
30
|
+
duration: 1,
|
|
31
|
+
ease: 'power2.out'
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
// Functions definitions
|
|
20
37
|
|
|
38
|
+
// === Skills: ScrollTrigger + GSAP init (definition only) ===
|
|
39
|
+
// You will call this yourself later, e.g. initSkillsTriggers({ section: '#skills' })
|
|
40
|
+
// Client logos reveal inside a section (definition only; you call it later)
|
|
41
|
+
function initClientLogosAnimation(rootEl, userOpts = {}) {
|
|
42
|
+
// ---- guards ----
|
|
43
|
+
if (!window.gsap || !window.ScrollTrigger) {
|
|
44
|
+
console.warn('[client-logos] GSAP/ScrollTrigger missing');
|
|
45
|
+
return { ok: false, reason: 'gsap-or-plugin-missing' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const gsap = window.gsap;
|
|
49
|
+
const ScrollTrigger = window.ScrollTrigger;
|
|
50
|
+
try { gsap.registerPlugin(ScrollTrigger); } catch (_) {}
|
|
51
|
+
|
|
52
|
+
// ---- config ----
|
|
53
|
+
const opts = {
|
|
54
|
+
// adjust these to your real structure if needed
|
|
55
|
+
listSelector: '.clients_wrapper-bento',
|
|
56
|
+
itemSelector: '.clients_wrapper-bento .bento_item, .client-logo, .logo',
|
|
57
|
+
start: 'top 80%',
|
|
58
|
+
end: 'bottom top',
|
|
59
|
+
markers: false,
|
|
60
|
+
...userOpts
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ---- scope ----
|
|
64
|
+
const root = rootEl && rootEl.nodeType === 1 ? rootEl : document;
|
|
65
|
+
const list = root.querySelector(opts.listSelector);
|
|
66
|
+
const items = list ? Array.from(list.querySelectorAll(opts.itemSelector)) : [];
|
|
67
|
+
|
|
68
|
+
// nothing to animate? bail quietly (prevents "GSAP target … not found")
|
|
69
|
+
if (!list || !items.length) {
|
|
70
|
+
return { ok: false, reason: 'targets-not-found', listFound: !!list, count: items.length };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// kill old triggers bound to this list (idempotent reruns)
|
|
74
|
+
ScrollTrigger.getAll().forEach(st => {
|
|
75
|
+
if (st && st.trigger === list) st.kill(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ---- animation ----
|
|
79
|
+
gsap.from(items, {
|
|
80
|
+
y: 16,
|
|
81
|
+
opacity: 0,
|
|
82
|
+
duration: 0.6,
|
|
83
|
+
ease: 'power2.out',
|
|
84
|
+
stagger: 0.06,
|
|
85
|
+
scrollTrigger: {
|
|
86
|
+
trigger: list,
|
|
87
|
+
start: opts.start,
|
|
88
|
+
end: opts.end,
|
|
89
|
+
toggleActions: 'play none none reverse',
|
|
90
|
+
markers: opts.markers,
|
|
91
|
+
invalidateOnRefresh: true
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// refresh after layout settles
|
|
96
|
+
requestAnimationFrame(() => { try { ScrollTrigger.refresh(); } catch(_) {} });
|
|
97
|
+
|
|
98
|
+
return { ok: true, count: items.length };
|
|
99
|
+
}
|
|
21
100
|
// Contact form open animation
|
|
22
101
|
let contactFormOpen;
|
|
23
102
|
function contactFormOpenAnimation() {
|
|
@@ -222,6 +301,114 @@ if (infoBar) {
|
|
|
222
301
|
});
|
|
223
302
|
}
|
|
224
303
|
|
|
304
|
+
// === SKILLS TRIGGER HARD-FIX (drop-in) ===
|
|
305
|
+
(function () {
|
|
306
|
+
const DEBUG = false; // flip to true to see console + markers
|
|
307
|
+
|
|
308
|
+
if (!window.gsap || !window.ScrollTrigger) {
|
|
309
|
+
console.error('[skills-fix] GSAP/ScrollTrigger missing');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
313
|
+
|
|
314
|
+
// 1) Pick the VISIBLE "skills" section (ignore duplicates/hidden)
|
|
315
|
+
const candidates = Array.from(document.querySelectorAll('#skills, .section.is-skills'));
|
|
316
|
+
const el = candidates.find(n => {
|
|
317
|
+
const r = n.getBoundingClientRect();
|
|
318
|
+
const style = getComputedStyle(n);
|
|
319
|
+
return r.width > 0 && r.height > 0 && style.display !== 'none' && style.visibility !== 'hidden';
|
|
320
|
+
});
|
|
321
|
+
if (!el) {
|
|
322
|
+
console.error('[skills-fix] skills section not found or not visible');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 2) Diagnose ancestors that break ScrollTrigger (transform / overflow != visible)
|
|
327
|
+
const badAncestors = [];
|
|
328
|
+
let p = el.parentElement;
|
|
329
|
+
while (p && p !== document.body) {
|
|
330
|
+
const cs = getComputedStyle(p);
|
|
331
|
+
const badTransform = cs.transform && cs.transform !== 'none';
|
|
332
|
+
const badOverflow = (cs.overflow !== 'visible' || cs.overflowY !== 'visible' || cs.overflowX !== 'visible');
|
|
333
|
+
if (badTransform || badOverflow) badAncestors.push({ node: p, badTransform, badOverflow });
|
|
334
|
+
p = p.parentElement;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 3) Neutralize only the dangerous bits, minimally
|
|
338
|
+
badAncestors.forEach(({ node, badTransform }) => {
|
|
339
|
+
// overflow hidden na wrapperach zostawiamy (często layout), ScrollTrigger sobie radzi;
|
|
340
|
+
// PRZEWAŻNIE zabija go transform na rodzicu — zdejmujemy go klasą.
|
|
341
|
+
if (badTransform) node.classList.add('st-fix-transform');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// 4) Inject minimal CSS to neutralize transforms (non-destructive)
|
|
345
|
+
const styleTag = document.createElement('style');
|
|
346
|
+
styleTag.textContent = `
|
|
347
|
+
.st-fix-transform { transform: none !important; }
|
|
348
|
+
`;
|
|
349
|
+
document.head.appendChild(styleTag);
|
|
350
|
+
|
|
351
|
+
// 5) Kill duplicated triggers on the same element
|
|
352
|
+
ScrollTrigger.getAll().forEach(st => { if (st.trigger === el) st.kill(false); });
|
|
353
|
+
|
|
354
|
+
// 6) The exact action you expect
|
|
355
|
+
const run = (target) => {
|
|
356
|
+
try { window.sectionBgChange && window.sectionBgChange(target); } catch (e) {}
|
|
357
|
+
try { window.badgeColorChange && window.badgeColorChange(target); } catch (e) {}
|
|
358
|
+
if (DEBUG) console.log('[skills-fix] run()', target);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// 7) Fire immediately if already on screen (new layout often loads in-view)
|
|
362
|
+
const r = el.getBoundingClientRect();
|
|
363
|
+
if (r.top < innerHeight && r.bottom > 0) run(el);
|
|
364
|
+
|
|
365
|
+
// 8) Create robust trigger (same feel as old), with optional markers
|
|
366
|
+
ScrollTrigger.create({
|
|
367
|
+
trigger: el,
|
|
368
|
+
start: 'top 80%',
|
|
369
|
+
end: 'bottom top',
|
|
370
|
+
onEnter: self => run(self.trigger),
|
|
371
|
+
onEnterBack: self => run(self.trigger),
|
|
372
|
+
markers: !!DEBUG,
|
|
373
|
+
fastScrollEnd: true,
|
|
374
|
+
invalidateOnRefresh: true
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// 9) Refresh after things that commonly shift layout on new home
|
|
378
|
+
const doRefresh = () => {
|
|
379
|
+
if (DEBUG) console.log('[skills-fix] refresh()');
|
|
380
|
+
ScrollTrigger.refresh();
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// after images
|
|
384
|
+
if (document.readyState === 'complete') requestAnimationFrame(doRefresh);
|
|
385
|
+
else window.addEventListener('load', () => requestAnimationFrame(doRefresh));
|
|
386
|
+
|
|
387
|
+
// after Webflow IX (if present)
|
|
388
|
+
try {
|
|
389
|
+
if (window.Webflow && Webflow.require) {
|
|
390
|
+
const ix2 = Webflow.require('ix2');
|
|
391
|
+
if (ix2 && ix2.ready) setTimeout(doRefresh, 300); // allow IX to apply styles
|
|
392
|
+
}
|
|
393
|
+
} catch (e) {}
|
|
394
|
+
|
|
395
|
+
// after Alpine renders (x-init etc.)
|
|
396
|
+
document.addEventListener('alpine:init', () => setTimeout(doRefresh, 100));
|
|
397
|
+
document.addEventListener('DOMContentLoaded', () => setTimeout(doRefresh, 150));
|
|
398
|
+
|
|
399
|
+
// As a last resort: one more refresh when user starts scrolling
|
|
400
|
+
let firstScroll = false;
|
|
401
|
+
const onFirstScroll = () => { if (!firstScroll) { firstScroll = true; doRefresh(); } };
|
|
402
|
+
window.addEventListener('scroll', onFirstScroll, { passive: true, once: true });
|
|
403
|
+
|
|
404
|
+
if (DEBUG) {
|
|
405
|
+
console.group('[skills-fix] diagnostics');
|
|
406
|
+
console.log('picked element:', el);
|
|
407
|
+
console.log('bad ancestors:', badAncestors);
|
|
408
|
+
console.groupEnd();
|
|
409
|
+
}
|
|
410
|
+
})();
|
|
411
|
+
|
|
225
412
|
// Header scroll trigger
|
|
226
413
|
window.standardHeaderScrollAnimation = function() {
|
|
227
414
|
ScrollTrigger.create({
|
|
@@ -343,54 +530,111 @@ window.customCursorTextUpdate = function(el) {
|
|
|
343
530
|
);
|
|
344
531
|
}
|
|
345
532
|
|
|
346
|
-
// Case study mask animation
|
|
347
|
-
window.caseStudyInteraction = function(e) {
|
|
348
|
-
// Video mask animation
|
|
349
|
-
const mask = e.querySelector('.portfolio_item_image-mask');
|
|
350
|
-
const maskAnimation = gsap.from(mask, {
|
|
351
|
-
width: '100%',
|
|
352
|
-
duration: 0.35,
|
|
353
|
-
ease: 'loader2',
|
|
354
|
-
paused: true,
|
|
355
|
-
});
|
|
356
|
-
ScrollTrigger.create({
|
|
357
|
-
animation: maskAnimation,
|
|
358
|
-
trigger: mask,
|
|
359
|
-
start: 'top 80%',
|
|
360
|
-
onEnter: () => maskAnimation.play(),
|
|
361
|
-
});
|
|
362
533
|
|
|
363
|
-
// Video on hover actions
|
|
364
|
-
const videoElements = e.getElementsByTagName('video');
|
|
365
|
-
if (window.innerWidth > 992) {
|
|
366
|
-
const videoElement = videoElements.length > 0 ? videoElements[0] : null;
|
|
367
|
-
if (!videoElement) return;
|
|
368
534
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
535
|
+
// Case study mask animation + pill fade-in (last step)
|
|
536
|
+
window.caseStudyInteraction = function (e) {
|
|
537
|
+
const mask = e.querySelector('.portfolio_item_image-mask');
|
|
538
|
+
const pill = e.querySelector('.cs_pill');
|
|
539
|
+
const triggerEl = e.querySelector('.portfolio_item_image-wrapper') || e; // safer trigger
|
|
374
540
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
541
|
+
// --- iOS detection (reliable enough for layout fallbacks) ---
|
|
542
|
+
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
543
|
+
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
|
378
544
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
545
|
+
// Ensure initial states
|
|
546
|
+
if (pill) gsap.set(pill, { autoAlpha: 0 }); // hide pill
|
|
547
|
+
if (mask) {
|
|
548
|
+
// Use transform-based reveal, much more reliable on iOS
|
|
549
|
+
gsap.set(mask, {
|
|
550
|
+
scaleX: 1,
|
|
551
|
+
transformOrigin: 'left center',
|
|
552
|
+
willChange: 'transform'
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Timeline: mask reveal -> pill fade-in
|
|
557
|
+
const tl = gsap.timeline({ paused: true, defaults: { ease: 'loader2' } });
|
|
558
|
+
if (mask) {
|
|
559
|
+
// Reveal by scaling mask from full (1) to zero (0)
|
|
560
|
+
tl.to(mask, { scaleX: 0, duration: 0.35 });
|
|
561
|
+
}
|
|
562
|
+
if (pill) {
|
|
563
|
+
tl.to(pill, { autoAlpha: 1, duration: 0.25 }, '>-0.05');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Scroll trigger (use wrapper, not absolute mask)
|
|
567
|
+
ScrollTrigger.create({
|
|
568
|
+
animation: tl,
|
|
569
|
+
trigger: triggerEl,
|
|
570
|
+
start: 'top 80%',
|
|
571
|
+
once: true
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// --- Video handling ---
|
|
575
|
+
const videoElements = e.getElementsByTagName('video');
|
|
576
|
+
|
|
577
|
+
if (window.innerWidth > 992) {
|
|
578
|
+
const videoElement = videoElements.length > 0 ? videoElements[0] : null;
|
|
579
|
+
if (!videoElement) return;
|
|
580
|
+
|
|
581
|
+
// Force Safari compositing on the video
|
|
582
|
+
videoElement.style.transform = 'translateZ(0)';
|
|
583
|
+
|
|
584
|
+
// Desktop: hover play/pause
|
|
585
|
+
const hoverIN = () => videoElement.play();
|
|
586
|
+
const hoverOUT = () => {
|
|
587
|
+
videoElement.pause();
|
|
588
|
+
// videoElement.currentTime = 0; // optional reset
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// Pause once metadata is ready
|
|
592
|
+
videoElement.addEventListener('loadeddata', () => {
|
|
593
|
+
videoElement.pause();
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
e.addEventListener('mouseenter', hoverIN, false);
|
|
597
|
+
e.addEventListener('mouseleave', hoverOUT, false);
|
|
598
|
+
|
|
599
|
+
} else {
|
|
600
|
+
// Mobile
|
|
601
|
+
Array.from(videoElements).forEach((v) => {
|
|
602
|
+
const poster = v.getAttribute('poster');
|
|
603
|
+
|
|
604
|
+
if (isIOS) {
|
|
605
|
+
// iOS: safest is to replace <video> with an <img> using the poster
|
|
606
|
+
if (poster) {
|
|
607
|
+
const img = new Image();
|
|
608
|
+
img.src = poster;
|
|
609
|
+
img.alt = '';
|
|
610
|
+
img.className = v.className; // preserve classes if needed
|
|
611
|
+
// Keep sizing via CSS from the video
|
|
612
|
+
v.replaceWith(img);
|
|
613
|
+
} else {
|
|
614
|
+
// If no poster, fall back to pausing & keeping sources
|
|
615
|
+
v.removeAttribute('autoplay');
|
|
616
|
+
v.pause();
|
|
617
|
+
v.preload = 'none';
|
|
382
618
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
}
|
|
619
|
+
} else {
|
|
620
|
+
// Android: keep poster only, but DON'T remove sources (some browsers need metadata for sizing)
|
|
621
|
+
v.removeAttribute('autoplay');
|
|
622
|
+
v.pause();
|
|
623
|
+
v.preload = 'none';
|
|
624
|
+
// Keeping sources avoids 0-height issues in some engines
|
|
625
|
+
// v.load(); // optional: only if you observe stale frames
|
|
626
|
+
}
|
|
627
|
+
});
|
|
392
628
|
}
|
|
393
629
|
|
|
630
|
+
// After images/posters load, refresh ScrollTrigger (prevents late layout causing missed triggers)
|
|
631
|
+
// Safe to call; GSAP debounces
|
|
632
|
+
setTimeout(() => ScrollTrigger.refresh(), 100);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
|
|
394
638
|
// Reviews slider
|
|
395
639
|
window.reviewsSlider = function(e) {
|
|
396
640
|
// Put content of static slide into list
|
|
@@ -446,61 +690,6 @@ window.customCursorTextUpdate = function(el) {
|
|
|
446
690
|
});
|
|
447
691
|
}
|
|
448
692
|
|
|
449
|
-
// Reviews slider
|
|
450
|
-
window.reviewsSlider = function(e) {
|
|
451
|
-
// Put content of static slide into list
|
|
452
|
-
const staticContent = e.querySelector(
|
|
453
|
-
'.splide__slide.reviews_collection_item.is-static'
|
|
454
|
-
).innerHTML;
|
|
455
|
-
const dynamicElement = e.querySelector('.reviews_collection_item');
|
|
456
|
-
dynamicElement.innerHTML = staticContent;
|
|
457
|
-
dynamicElement.classList.add('is-static');
|
|
458
|
-
|
|
459
|
-
// Init reviews splide
|
|
460
|
-
const reviewsSplide = new Splide(e, {
|
|
461
|
-
type: 'slide',
|
|
462
|
-
fixedWidth: '24.5rem',
|
|
463
|
-
rewind: true,
|
|
464
|
-
focus: 0,
|
|
465
|
-
padding: { left: '2.5rem', right: 0 },
|
|
466
|
-
gap: 0,
|
|
467
|
-
omitEnd: true,
|
|
468
|
-
arrows: false,
|
|
469
|
-
drag: false,
|
|
470
|
-
easing: 'cubic-bezier(0.83, 0, 0.17, 1)',
|
|
471
|
-
snap: true,
|
|
472
|
-
classes: {
|
|
473
|
-
pagination: 'splide__pagination reviews-pagination',
|
|
474
|
-
page: 'splide__pagination__page reviews-page',
|
|
475
|
-
},
|
|
476
|
-
breakpoints: {
|
|
477
|
-
992: { fixedWidth: '17.438rem', padding: { left: '1.75rem' }, drag: 'free', snap: false },
|
|
478
|
-
480: { fixedWidth: '16.25rem', padding: { left: '1.375rem' }},
|
|
479
|
-
},
|
|
480
|
-
}).mount();
|
|
481
|
-
|
|
482
|
-
// Next slide on click
|
|
483
|
-
if (window.innerWidth > 992) {
|
|
484
|
-
reviewsSplide.on('click', function () {
|
|
485
|
-
reviewsSplide.go('+1');
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Animation in
|
|
490
|
-
gsap.from(e, {
|
|
491
|
-
scrollTrigger: {
|
|
492
|
-
trigger: e,
|
|
493
|
-
start: 'top bottom',
|
|
494
|
-
end: 'bottom bottom-=15%',
|
|
495
|
-
scrub: 1,
|
|
496
|
-
once: true,
|
|
497
|
-
},
|
|
498
|
-
x: '10%',
|
|
499
|
-
opacity: 0,
|
|
500
|
-
ease: 'linear',
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
|
|
504
693
|
// Facts slider
|
|
505
694
|
window.factsSlider = function(e) {
|
|
506
695
|
// Facts slider splide initialize
|
|
@@ -1684,6 +1873,7 @@ window.initBentoRevealScrub = function (container) {
|
|
|
1684
1873
|
}
|
|
1685
1874
|
);
|
|
1686
1875
|
};
|
|
1876
|
+
|
|
1687
1877
|
window.initPartnerVideos = function (container) {
|
|
1688
1878
|
const items = Array.from(container.querySelectorAll('.partner_item'));
|
|
1689
1879
|
const vids = items.map(i => i.querySelector('video')).filter(Boolean);
|
|
@@ -2038,6 +2228,7 @@ window.closeInfoBar = function() {
|
|
|
2038
2228
|
lenis.destroy();
|
|
2039
2229
|
}
|
|
2040
2230
|
customGsapEasing();
|
|
2231
|
+
|
|
2041
2232
|
reloadOnBack();
|
|
2042
2233
|
revealFooter();
|
|
2043
2234
|
pageLoadAnimation();
|
|
@@ -2045,4 +2236,6 @@ window.closeInfoBar = function() {
|
|
|
2045
2236
|
customCursorOnClick();
|
|
2046
2237
|
contactFormOpenAnimation();
|
|
2047
2238
|
disableScrollMenu();
|
|
2239
|
+
scrollToHashOnLoad();
|
|
2240
|
+
initClientLogosAnimation();
|
|
2048
2241
|
});
|