@widelab-nc/widelab 1.1.33 → 1.1.34

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.34",
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
@@ -1431,6 +1431,132 @@ window.servicesSliders = function(e) {
1431
1431
  });
1432
1432
  }
1433
1433
 
1434
+ // Play-once timeline: children fade-in from bottom with stagger
1435
+ window.initBentoRevealTimeline = function (container) {
1436
+ // Respect reduced motion
1437
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
1438
+ container.querySelectorAll(':scope > *').forEach(el => {
1439
+ el.style.opacity = 1;
1440
+ el.style.transform = 'none';
1441
+ });
1442
+ return;
1443
+ }
1444
+
1445
+ const items = Array.from(container.children); // direct children only
1446
+
1447
+ // Set initial state
1448
+ gsap.set(items, { opacity: 0, y: 24 });
1449
+
1450
+ // Build timeline that plays when scrolled into view
1451
+ const tl = gsap.timeline({
1452
+ scrollTrigger: {
1453
+ trigger: container,
1454
+ start: 'top 80%', // when top of container hits 80% of viewport
1455
+ end: 'bottom 40%', // not critical for play-only, but nice bounds
1456
+ toggleActions: 'play none none none',
1457
+ once: true
1458
+ }
1459
+ });
1460
+
1461
+ tl.to(items, {
1462
+ opacity: 1,
1463
+ y: 0,
1464
+ duration: 0.6,
1465
+ ease: 'power2.out',
1466
+ stagger: 0.12
1467
+ });
1468
+ };
1469
+
1470
+ // Scrubbed timeline: progress tied to scroll progress
1471
+ window.initBentoRevealScrub = function (container) {
1472
+ // Respect reduced motion
1473
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
1474
+ container.querySelectorAll(':scope > *').forEach(el => {
1475
+ el.style.opacity = 1;
1476
+ el.style.transform = 'none';
1477
+ });
1478
+ return;
1479
+ }
1480
+
1481
+ const items = Array.from(container.children); // direct children only
1482
+
1483
+ // Important: define from state in timeline for scrub to interpolate smoothly
1484
+ const tl = gsap.timeline({
1485
+ scrollTrigger: {
1486
+ trigger: container,
1487
+ start: 'top 90%', // start easing in slightly later
1488
+ end: 'bottom 65%', // full reveal by near the end of container
1489
+ scrub: 0.5, // smoothing; use true for no smoothing
1490
+ markers: false, // uncomment for debugging
1491
+ }
1492
+ });
1493
+
1494
+ tl.fromTo(items,
1495
+ { opacity: 0, y: 28 },
1496
+ {
1497
+ opacity: 1,
1498
+ y: 0,
1499
+ ease: 'power2.out',
1500
+ // Duration is ignored by scrub; timeline length maps to scroll distance.
1501
+ // Stagger distributes the items across the timeline for a nice cascade.
1502
+ stagger: { each: 0.12 }
1503
+ }
1504
+ );
1505
+ };
1506
+
1507
+ window.initPartnerVideos = function (container) {
1508
+ const items = Array.from(container.querySelectorAll('.partner_item'));
1509
+ const vids = items.map(i => i.querySelector('video')).filter(Boolean);
1510
+
1511
+ const pauseAll = (except) => {
1512
+ vids.forEach(v => {
1513
+ if (v !== except) {
1514
+ v.pause();
1515
+ v.muted = true;
1516
+ }
1517
+ });
1518
+ };
1519
+
1520
+ items.forEach((item) => {
1521
+ const v = item.querySelector('video');
1522
+ if (!v) return;
1523
+
1524
+ v.controls = false;
1525
+ v.loop = false;
1526
+ v.muted = true;
1527
+ v.setAttribute('playsinline', '');
1528
+ v.playsInline = true;
1529
+
1530
+ item.addEventListener('click', () => {
1531
+ if (v.paused) {
1532
+ pauseAll(v);
1533
+ v.muted = false;
1534
+ v.play();
1535
+ } else {
1536
+ v.pause();
1537
+ }
1538
+ });
1539
+ });
1540
+
1541
+ const rootEl =
1542
+ document.querySelector('[data-scroll-root]') ||
1543
+ document.querySelector('[data-smooth-scroll-root]') ||
1544
+ null;
1545
+
1546
+ const io = new IntersectionObserver(
1547
+ (entries) => {
1548
+ entries.forEach(({ isIntersecting, target }) => {
1549
+ const v = target.querySelector('video');
1550
+ if (!v) return;
1551
+ if (!isIntersecting) v.pause();
1552
+ });
1553
+ },
1554
+ { root: rootEl, threshold: 0.25 }
1555
+ );
1556
+
1557
+ items.forEach((item) => io.observe(item));
1558
+ };
1559
+
1434
1560
 
1435
1561
 
1436
1562
  // Case study - What's next interaction