@widelab-nc/widelab 1.1.33 → 1.1.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widelab-nc/widelab",
3
- "version": "1.1.33",
3
+ "version": "1.1.35",
4
4
  "description": "Widelab starter template based on Finsweet template + add-ons.",
5
5
  "homepage": "https://widelab.co",
6
6
  "license": "ISC",
@@ -57,3 +57,4 @@
57
57
  "gsap": "npm:@gsap/shockingly@^3.12.5"
58
58
  }
59
59
  }
60
+
package/src/index.js CHANGED
@@ -627,6 +627,52 @@ window.reviewsSlider = function(e) {
627
627
  });
628
628
  }
629
629
 
630
+ // Partner slider
631
+ window.partnerSlider = function (e) {
632
+ // Keep the same querySelector for consistency with teamSlider
633
+ const teamList = e.querySelector('.team_collection_list');
634
+
635
+ // Initialize Splide with drag restored
636
+ const splide = new Splide(e, {
637
+ type: 'slide',
638
+ fixedWidth: '20%',
639
+ padding: { left: '2.5rem', right: 0 },
640
+ focus: 0,
641
+ gap: 0,
642
+ pagination: true,
643
+ omitEnd: true,
644
+ arrows: false,
645
+ drag: true, // restored dragging
646
+ easing: 'cubic-bezier(0.83, 0, 0.17, 1)',
647
+ snap: true,
648
+ classes: {
649
+ pagination: 'splide__pagination facts-pagination partner-bullets',
650
+ page: 'splide__pagination__page facts-page',
651
+ },
652
+ breakpoints: {
653
+ 992: { fixedWidth: '40%', padding: { left: '1.75rem' }, drag: 'free', snap: false },
654
+ 560: { fixedWidth: '75%', padding: { left: '1.375rem' }, drag: 'free', snap: false },
655
+ },
656
+ });
657
+
658
+ splide.mount();
659
+
660
+ // Removed: click-to-next handler
661
+
662
+ // GSAP in-view animation
663
+ gsap.from(e, {
664
+ scrollTrigger: {
665
+ trigger: e,
666
+ start: 'top bottom',
667
+ end: 'bottom bottom',
668
+ scrub: 1,
669
+ once: true,
670
+ },
671
+ x: '10%',
672
+ opacity: 0,
673
+ ease: 'linear',
674
+ });
675
+ };
630
676
  // Element padding change on scroll
631
677
  window.elementPaddingChange = function(e) {
632
678
  gsap.to(e, {
@@ -1431,6 +1477,161 @@ window.servicesSliders = function(e) {
1431
1477
  });
1432
1478
  }
1433
1479
 
1480
+ // Play-once timeline: children fade-in from bottom with stagger
1481
+ window.initBentoRevealTimeline = function (container) {
1482
+ // Respect reduced motion
1483
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
1484
+ container.querySelectorAll(':scope > *').forEach(el => {
1485
+ el.style.opacity = 1;
1486
+ el.style.transform = 'none';
1487
+ });
1488
+ return;
1489
+ }
1490
+
1491
+ const items = Array.from(container.children); // direct children only
1492
+
1493
+ // Set initial state
1494
+ gsap.set(items, { opacity: 0, y: 24 });
1495
+
1496
+ // Build timeline that plays when scrolled into view
1497
+ const tl = gsap.timeline({
1498
+ scrollTrigger: {
1499
+ trigger: container,
1500
+ start: 'top 80%', // when top of container hits 80% of viewport
1501
+ end: 'bottom 40%', // not critical for play-only, but nice bounds
1502
+ toggleActions: 'play none none none',
1503
+ once: true
1504
+ }
1505
+ });
1506
+
1507
+ tl.to(items, {
1508
+ opacity: 1,
1509
+ y: 0,
1510
+ duration: 0.6,
1511
+ ease: 'power2.out',
1512
+ stagger: 0.12
1513
+ });
1514
+ };
1515
+
1516
+ // Scrubbed timeline: progress tied to scroll progress
1517
+ window.initBentoRevealScrub = function (container) {
1518
+ // Respect reduced motion
1519
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
1520
+ container.querySelectorAll(':scope > *').forEach(el => {
1521
+ el.style.opacity = 1;
1522
+ el.style.transform = 'none';
1523
+ });
1524
+ return;
1525
+ }
1526
+
1527
+ const items = Array.from(container.children); // direct children only
1528
+
1529
+ // Important: define from state in timeline for scrub to interpolate smoothly
1530
+ const tl = gsap.timeline({
1531
+ scrollTrigger: {
1532
+ trigger: container,
1533
+ start: 'top 90%', // start easing in slightly later
1534
+ end: 'bottom 65%', // full reveal by near the end of container
1535
+ scrub: 0.5, // smoothing; use true for no smoothing
1536
+ markers: false, // uncomment for debugging
1537
+ }
1538
+ });
1539
+
1540
+ tl.fromTo(items,
1541
+ { opacity: 0, y: 28 },
1542
+ {
1543
+ opacity: 1,
1544
+ y: 0,
1545
+ ease: 'power2.out',
1546
+ // Duration is ignored by scrub; timeline length maps to scroll distance.
1547
+ // Stagger distributes the items across the timeline for a nice cascade.
1548
+ stagger: { each: 0.12 }
1549
+ }
1550
+ );
1551
+ };
1552
+ window.initPartnerVideos = function (container) {
1553
+ const items = Array.from(container.querySelectorAll('.partner_item'));
1554
+ const vids = items.map(i => i.querySelector('video')).filter(Boolean);
1555
+
1556
+ // Helper: pause all other videos
1557
+ const pauseAll = (except) => {
1558
+ vids.forEach(v => {
1559
+ if (v !== except) {
1560
+ v.pause();
1561
+ v.muted = true;
1562
+ const parent = v.closest('.partner_item');
1563
+ if (parent) toggleIcons(parent, false);
1564
+ }
1565
+ });
1566
+ };
1567
+
1568
+ // Helper: toggle play/pause icons
1569
+ function toggleIcons(item, isPlaying) {
1570
+ const playIcon = item.querySelector('.is-play');
1571
+ const pauseIcon = item.querySelector('.is-pause');
1572
+ if (isPlaying) {
1573
+ if (playIcon) playIcon.style.display = 'none';
1574
+ if (pauseIcon) pauseIcon.style.display = 'block';
1575
+ } else {
1576
+ if (playIcon) playIcon.style.display = 'block';
1577
+ if (pauseIcon) pauseIcon.style.display = 'none';
1578
+ }
1579
+ }
1580
+
1581
+ items.forEach((item) => {
1582
+ const v = item.querySelector('video');
1583
+ if (!v) return;
1584
+
1585
+ v.controls = false;
1586
+ v.loop = false;
1587
+ v.muted = true;
1588
+ v.setAttribute('playsinline', '');
1589
+ v.playsInline = true;
1590
+
1591
+ // Default state on init (show play icon)
1592
+ toggleIcons(item, false);
1593
+
1594
+ // Handle click (play/pause toggle)
1595
+ item.addEventListener('click', () => {
1596
+ if (v.paused) {
1597
+ pauseAll(v);
1598
+ v.muted = false;
1599
+ v.play();
1600
+ toggleIcons(item, true);
1601
+ } else {
1602
+ v.pause();
1603
+ toggleIcons(item, false);
1604
+ }
1605
+ });
1606
+
1607
+ // Handle pause and ended (ensure icons reset)
1608
+ v.addEventListener('pause', () => toggleIcons(item, false));
1609
+ v.addEventListener('ended', () => toggleIcons(item, false));
1610
+ v.addEventListener('play', () => toggleIcons(item, true));
1611
+ });
1612
+
1613
+ // Pause videos when out of viewport
1614
+ const rootEl =
1615
+ document.querySelector('[data-scroll-root]') ||
1616
+ document.querySelector('[data-smooth-scroll-root]') ||
1617
+ null;
1618
+
1619
+ const io = new IntersectionObserver(
1620
+ (entries) => {
1621
+ entries.forEach(({ isIntersecting, target }) => {
1622
+ const v = target.querySelector('video');
1623
+ if (!v) return;
1624
+ if (!isIntersecting) {
1625
+ v.pause();
1626
+ toggleIcons(target, false);
1627
+ }
1628
+ });
1629
+ },
1630
+ { root: rootEl, threshold: 0.25 }
1631
+ );
1632
+
1633
+ items.forEach((item) => io.observe(item));
1634
+ };
1434
1635
 
1435
1636
 
1436
1637
  // Case study - What's next interaction