PageSourceSearch

https://toaf.ca/wp-content/themes/bds-child-theme/assets/scripts/homepage-animations.js?ver=1778791981

js toaf.ca collected 2026-10-04 00:25:44 UTC 32,148 bytes, 997 lines download raw bytes

1/**
2 * TOAF Homepage — GSAP Animation System
3 *
4 * Scroll-triggered animations for the homepage sections.
5 * Uses ScrollTrigger for viewport-based reveals.
6 * Animation language aligned with Collectors Collective page.
7 *
8 * @package    BDS Child Theme
9 * @author     Umami Studio <[email protected]>
10 * @link       https://umami.studio/
11 */
12
13(function () {
14  'use strict';
15
16  if (typeof gsap === 'undefined' || typeof ScrollTrigger === 'undefined') {
17    return;
18  }
19
20  gsap.registerPlugin(ScrollTrigger);
21
22  var SPEED = 1.0;
23  var EASE = 'power3.out';
24
25  var CONFIG = {
26    header: {
27      wordY: 40,
28      wordDuration: 1 * SPEED,
29      wordStagger: 0.04,
30      contentY: 32,
31      contentDuration: 1 * SPEED,
32      overlineOffset: 0.32 * SPEED,
33      bodyOffset: 0.64 * SPEED,
34      linksOffset: 1.12 * SPEED
35    },
36    block: {
37      y: 48,
38      duration: 1 * SPEED,
39      stagger: 0.16
40    },
41    scroll: {
42      triggerMobile: 'top 70%',
43      triggerDesktop: 'top 80%'
44    },
45    hero: {
46      parallaxScale: 1.1
47    },
48    revealImages: {
49      initialOffset: 25,
50      finalOffset: 0,
51      durationMultiplier: 1.2,
52      delay: 0.1
53    }
54  };
55
56  function isMobile() {
57    return window.innerWidth <= 768;
58  }
59
60  function getScrollTriggerStart() {
61    return isMobile() ? CONFIG.scroll.triggerMobile : CONFIG.scroll.triggerDesktop;
62  }
63
64  /**
65   * Split heading text into individual word spans for staggered animation.
66   */
67  function splitHeadingIntoWords(heading) {
68    if (!heading || heading.dataset.wordsSplit === 'true') {
69      return heading ? Array.from(heading.querySelectorAll('.word')) : [];
70    }
71
72    var words = [];
73
74    function processNode(node) {
75      if (node.nodeType === Node.TEXT_NODE) {
76        var fragment = document.createDocumentFragment();
77        var lines = node.textContent.split(/\r\n|\r|\n/);
78
79        lines.forEach(function (line, lineIndex) {
80          if (lineIndex > 0) {
81            fragment.appendChild(document.createElement('br'));
82          }
83
84          var parts = line.split(/(\s+)/);
85          parts.forEach(function (part) {
86            if (part.trim() === '') {
87              if (part) fragment.appendChild(document.createTextNode(part));
88            } else {
89              var span = document.createElement('span');
90              span.className = 'word';
91              span.style.display = 'inline-block';
92              span.textContent = part;
93              fragment.appendChild(span);
94              words.push(span);
95            }
96          });
97        });
98
99        node.parentNode.replaceChild(fragment, node);
100      } else if (node.nodeType === Node.ELEMENT_NODE) {
101        if (node.classList.contains('word')) {
102          words.push(node);
103          return;
104        }
105        Array.from(node.childNodes).forEach(processNode);
106      }
107    }
108
109    processNode(heading);
110    heading.dataset.wordsSplit = 'true';
111    return words;
112  }
113
114  /**
115   * Set initial animation states for header elements.
116   */
117  function setHeaderInitialStates(header) {
118    if (!header) return;
119    var overline = header.querySelector('.overline');
120    var body = header.querySelector('.body');
121    var links = header.querySelector('.links');
122
123    if (overline) gsap.set(overline, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
124    if (body) gsap.set(body, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
125    if (links) gsap.set(links, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
126  }
127
128  /**
129   * Animate the hero header immediately on load (no ScrollTrigger).
130   */
131  function animateHeroHeader(header) {
132    if (!header) return;
133
134    var overline = header.querySelector('.overline');
135    var heading = header.querySelector('.heading');
136    var body = header.querySelector('.body');
137    var links = header.querySelector('.links');
138
139    var words = heading ? splitHeadingIntoWords(heading) : [];
140
141    var tl = gsap.timeline();
142
143    if (overline) {
144      tl.to(overline, {
145        opacity: 1,
146        y: 0,
147        duration: CONFIG.header.contentDuration,
148        ease: EASE,
149        onComplete: function () { gsap.set(overline, { willChange: 'auto' }); }
150      }, CONFIG.header.overlineOffset);
151    }
152
153    if (words.length) {
154      gsap.set(words, { opacity: 0, y: CONFIG.header.wordY, willChange: 'transform, opacity' });
155      tl.to(words, {
156        opacity: 1,
157        y: 0,
158        duration: CONFIG.header.wordDuration,
159        stagger: CONFIG.header.wordStagger,
160        ease: EASE,
161        onComplete: function () { gsap.set(words, { willChange: 'auto' }); }
162      }, CONFIG.header.overlineOffset + 0.1);
163    }
164
165    if (body) {
166      tl.to(body, {
167        opacity: 1,
168        y: 0,
169        duration: CONFIG.header.contentDuration,
170        ease: EASE,
171        onComplete: function () { gsap.set(body, { willChange: 'auto' }); }
172      }, CONFIG.header.bodyOffset);
173    }
174
175    if (links) {
176      tl.to(links, {
177        opacity: 1,
178        y: 0,
179        duration: CONFIG.header.contentDuration,
180        ease: EASE,
181        onComplete: function () { gsap.set(links, { willChange: 'auto' }); }
182      }, CONFIG.header.linksOffset);
183    }
184  }
185
186  /**
187   * Animate a section header: overline, heading words, body, links.
188   */
189  function animateSectionHeader(header, trigger) {
190    if (!header) return;
191
192    var overline = header.querySelector('.overline');
193    var heading = header.querySelector('.heading');
194    var body = header.querySelector('.body');
195    var links = header.querySelector('.links');
196
197    var words = heading ? splitHeadingIntoWords(heading) : [];
198
199    var tl = gsap.timeline({
200      scrollTrigger: {
201        trigger: trigger || header,
202        start: getScrollTriggerStart(),
203        toggleActions: 'play none play none'
204      }
205    });
206
207    if (overline) {
208      tl.to(overline, {
209        opacity: 1,
210        y: 0,
211        duration: CONFIG.header.contentDuration,
212        ease: EASE,
213        onComplete: function () { gsap.set(overline, { willChange: 'auto' }); }
214      }, CONFIG.header.overlineOffset);
215    }
216
217    if (words.length) {
218      gsap.set(words, { opacity: 0, y: CONFIG.header.wordY, willChange: 'transform, opacity' });
219      tl.to(words, {
220        opacity: 1,
221        y: 0,
222        duration: CONFIG.header.wordDuration,
223        stagger: CONFIG.header.wordStagger,
224        ease: EASE,
225        onComplete: function () { gsap.set(words, { willChange: 'auto' }); }
226      }, CONFIG.header.overlineOffset + 0.1);
227    }
228
229    if (body) {
230      tl.to(body, {
231        opacity: 1,
232        y: 0,
233        duration: CONFIG.header.contentDuration,
234        ease: EASE,
235        onComplete: function () { gsap.set(body, { willChange: 'auto' }); }
236      }, CONFIG.header.bodyOffset);
237    }
238
239    if (links) {
240      tl.to(links, {
241        opacity: 1,
242        y: 0,
243        duration: CONFIG.header.contentDuration,
244        ease: EASE,
245        onComplete: function () { gsap.set(links, { willChange: 'auto' }); }
246      }, CONFIG.header.linksOffset);
247    }
248  }
249
250  /**
251   * Animate generic blocks with fade-up on scroll.
252   * Only handles elements still using data-animate-block.
253   */
254  function animateBlocks(container) {
255    var blocks = container.querySelectorAll('[data-animate-block]');
256    if (!blocks.length) return;
257
258    gsap.set(blocks, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
259
260    blocks.forEach(function (block) {
261      var revealImg = block.querySelector('.img-reveal img');
262      if (revealImg) {
263        gsap.set(revealImg, { yPercent: CONFIG.revealImages.initialOffset, willChange: 'transform' });
264      }
265
266      gsap.to(block, {
267        opacity: 1,
268        y: 0,
269        duration: CONFIG.block.duration,
270        ease: EASE,
271        scrollTrigger: {
272          trigger: block,
273          start: getScrollTriggerStart(),
274          toggleActions: 'play none play none'
275        },
276        onComplete: function () { gsap.set(block, { willChange: 'auto' }); }
277      });
278
279      if (revealImg) {
280        gsap.to(revealImg, {
281          yPercent: CONFIG.revealImages.finalOffset,
282          duration: CONFIG.block.duration * CONFIG.revealImages.durationMultiplier,
283          ease: 'power3.out',
284          scrollTrigger: {
285            trigger: block,
286            start: getScrollTriggerStart(),
287            toggleActions: 'play none play none'
288          },
289          onComplete: function () { gsap.set(revealImg, { willChange: 'auto' }); }
290        });
291      }
292    });
293  }
294
295  /**
296   * Animate a group of cards/items with stagger and optional img-reveal.
297   */
298  function animateCardGroup(cards, trigger, startOffset) {
299    if (!cards.length) return;
300
301    gsap.set(cards, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
302
303    cards.forEach(function (card) {
304      var revealImg = card.querySelector('.img-reveal img');
305      if (revealImg) {
306        gsap.set(revealImg, { yPercent: CONFIG.revealImages.initialOffset, willChange: 'transform' });
307      }
308    });
309
310    var tl = gsap.timeline({
311      scrollTrigger: {
312        trigger: trigger,
313        start: getScrollTriggerStart(),
314        toggleActions: 'play none play none'
315      }
316    });
317
318    tl.to(cards, {
319      opacity: 1,
320      y: 0,
321      duration: CONFIG.block.duration,
322      stagger: CONFIG.block.stagger,
323      ease: EASE,
324      onComplete: function () { gsap.set(cards, { willChange: 'auto' }); }
325    }, startOffset || 0);
326
327    cards.forEach(function (card, index) {
328      var revealImg = card.querySelector('.img-reveal img');
329      if (revealImg) {
330        tl.to(revealImg, {
331          yPercent: CONFIG.revealImages.finalOffset,
332          duration: CONFIG.block.duration * CONFIG.revealImages.durationMultiplier,
333          ease: 'power3.out',
334          onComplete: function () { gsap.set(revealImg, { willChange: 'auto' }); }
335        }, (startOffset || 0) + (index * CONFIG.block.stagger) + CONFIG.revealImages.delay);
336      }
337    });
338
339    return tl;
340  }
341
342  /* ================================================================
343     Hero Section
344     ================================================================ */
345
346  function initHero() {
347    var section = document.querySelector('.hero-section');
348    if (!section) return;
349
350    var header = section.querySelector('[data-animate-header]');
351    setHeaderInitialStates(header);
352    animateHeroHeader(header);
353
354    var bg = section.querySelector('.hero-section__bg');
355    if (bg && !bg.querySelector('iframe')) {
356      gsap.to(bg, {
357        scale: CONFIG.hero.parallaxScale,
358        ease: 'none',
359        scrollTrigger: {
360          trigger: section,
361          start: 'top top',
362          end: 'bottom top',
363          scrub: 0.5
364        }
365      });
366    }
367  }
368
369  /* ================================================================
370     What's Happening Section
371     ================================================================ */
372
373  function initWhatsHappening() {
374    var section = document.querySelector('.whats-happening-section');
375    if (!section) return;
376
377    var header = section.querySelector('[data-animate-header]');
378    setHeaderInitialStates(header);
379    animateSectionHeader(header, section);
380
381    var cards = section.querySelectorAll('.wh-card');
382    if (!cards.length) return;
383
384    gsap.set(cards, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
385
386    cards.forEach(function (card) {
387      var revealImg = card.querySelector('.img-reveal img');
388      if (revealImg) {
389        gsap.set(revealImg, { yPercent: CONFIG.revealImages.initialOffset, willChange: 'transform' });
390      }
391    });
392
393    if (isMobile()) {
394      cards.forEach(function (card) {
395        gsap.to(card, {
396          opacity: 1,
397          y: 0,
398          duration: CONFIG.block.duration,
399          ease: EASE,
400          scrollTrigger: {
401            trigger: card,
402            start: CONFIG.scroll.triggerMobile,
403            toggleActions: 'play none play none'
404          },
405          onComplete: function () { gsap.set(card, { willChange: 'auto' }); }
406        });
407
408        var revealImg = card.querySelector('.img-reveal img');
409        if (revealImg) {
410          gsap.to(revealImg, {
411            yPercent: CONFIG.revealImages.finalOffset,
412            duration: CONFIG.block.duration * CONFIG.revealImages.durationMultiplier,
413            ease: 'power3.out',
414            scrollTrigger: {
415              trigger: card,
416              start: CONFIG.scroll.triggerMobile,
417              toggleActions: 'play none play none'
418            },
419            onComplete: function () { gsap.set(revealImg, { willChange: 'auto' }); }
420          });
421        }
422      });
423    } else {
424      animateCardGroup(cards, section, CONFIG.header.bodyOffset);
425    }
426  }
427
428  /* ================================================================
429     In-Person Fair Section
430     ================================================================ */
431
432  function initInPersonFair() {
433    var section = document.querySelector('.in-person-fair-section');
434    if (!section) return;
435
436    var details = section.querySelector('.ipf-details');
437    if (!details) return;
438
439    var title = details.querySelector('.ipf-details__title');
440    var date = details.querySelector('.ipf-details__date');
441    var link = details.querySelector('.ipf-details__header .links');
442    var headerItems = [title, date, link].filter(Boolean);
443
444    var scheduleRows = details.querySelectorAll('.ipf-schedule__row');
445    var scheduleNote = details.querySelector('.ipf-schedule__note');
446    var scheduleItems = Array.from(scheduleRows);
447    if (scheduleNote) scheduleItems.push(scheduleNote);
448
449    var allItems = headerItems.concat(scheduleItems);
450    gsap.set(allItems, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
451
452    var tl = gsap.timeline({
453      scrollTrigger: {
454        trigger: section,
455        start: getScrollTriggerStart(),
456        toggleActions: 'play none play none'
457      }
458    });
459
460    tl.to(headerItems, {
461      opacity: 1,
462      y: 0,
463      duration: CONFIG.block.duration,
464      stagger: CONFIG.block.stagger,
465      ease: EASE,
466      onComplete: function () { gsap.set(headerItems, { willChange: 'auto' }); }
467    }, 0);
468
469    var scheduleStart = CONFIG.block.stagger * headerItems.length;
470    tl.to(scheduleItems, {
471      opacity: 1,
472      y: 0,
473      duration: CONFIG.block.duration,
474      stagger: CONFIG.block.stagger,
475      ease: EASE,
476      onComplete: function () { gsap.set(scheduleItems, { willChange: 'auto' }); }
477    }, scheduleStart);
478  }
479
480  /* ================================================================
481     Online Fair Section
482     ================================================================ */
483
484  function initOnlineFair() {
485    var section = document.querySelector('.online-fair-section');
486    if (!section) return;
487
488    var content = section.querySelector('.of-content');
489    if (!content) return;
490
491    var overline = content.querySelector('.overline');
492    var heading = content.querySelector('.of-heading');
493    var body = content.querySelector('.of-body');
494    var links = content.querySelector('.links');
495
496    var words = heading ? splitHeadingIntoWords(heading) : [];
497
498    if (overline) gsap.set(overline, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
499    if (words.length) gsap.set(words, { opacity: 0, y: CONFIG.header.wordY, willChange: 'transform, opacity' });
500    if (body) gsap.set(body, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
501    if (links) gsap.set(links, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
502
503    var tl = gsap.timeline({
504      scrollTrigger: {
505        trigger: section,
506        start: getScrollTriggerStart(),
507        toggleActions: 'play none play none'
508      }
509    });
510
511    if (overline) {
512      tl.to(overline, {
513        opacity: 1,
514        y: 0,
515        duration: CONFIG.header.contentDuration,
516        ease: EASE,
517        onComplete: function () { gsap.set(overline, { willChange: 'auto' }); }
518      }, CONFIG.header.overlineOffset);
519    }
520
521    if (words.length) {
522      tl.to(words, {
523        opacity: 1,
524        y: 0,
525        duration: CONFIG.header.wordDuration,
526        stagger: CONFIG.header.wordStagger,
527        ease: EASE,
528        onComplete: function () { gsap.set(words, { willChange: 'auto' }); }
529      }, CONFIG.header.overlineOffset + 0.1);
530    }
531
532    if (body) {
533      tl.to(body, {
534        opacity: 1,
535        y: 0,
536        duration: CONFIG.header.contentDuration,
537        ease: EASE,
538        onComplete: function () { gsap.set(body, { willChange: 'auto' }); }
539      }, CONFIG.header.bodyOffset);
540    }
541
542    if (links) {
543      tl.to(links, {
544        opacity: 1,
545        y: 0,
546        duration: CONFIG.header.contentDuration,
547        ease: EASE,
548        onComplete: function () { gsap.set(links, { willChange: 'auto' }); }
549      }, CONFIG.header.linksOffset);
550    }
551  }
552
553  /* ================================================================
554     Discover Art Section
555     ================================================================ */
556
557  function initDiscoverArt() {
558    var section = document.querySelector('.discover-art-section');
559    if (!section) return;
560
561    var header = section.querySelector('[data-animate-header]');
562    if (header) {
563      var heading = header.querySelector('.heading');
564      var button = header.querySelector('.button');
565
566      var words = heading ? splitHeadingIntoWords(heading) : [];
567
568      if (words.length) {
569        gsap.set(words, { opacity: 0, y: CONFIG.header.wordY, willChange: 'transform, opacity' });
570      }
571      if (button) {
572        gsap.set(button, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
573      }
574
575      var tl = gsap.timeline({
576        scrollTrigger: {
577          trigger: section,
578          start: getScrollTriggerStart(),
579          toggleActions: 'play none play none'
580        }
581      });
582
583      if (words.length) {
584        tl.to(words, {
585          opacity: 1,
586          y: 0,
587          duration: CONFIG.header.wordDuration,
588          stagger: CONFIG.header.wordStagger,
589          ease: EASE,
590          onComplete: function () { gsap.set(words, { willChange: 'auto' }); }
591        }, 0);
592      }
593
594      if (button) {
595        tl.to(button, {
596          opacity: 1,
597          y: 0,
598          duration: CONFIG.header.contentDuration,
599          ease: EASE,
600          onComplete: function () { gsap.set(button, { willChange: 'auto' }); }
601        }, CONFIG.header.linksOffset);
602      }
603    }
604
605    var cards = section.querySelectorAll('.da-card');
606    if (cards.length) {
607      animateCardGroup(cards, section, CONFIG.header.bodyOffset);
608    }
609  }
610
611  /* ================================================================
612     Fair Programs Section
613     ================================================================ */
614
615  function initFairPrograms() {
616    var section = document.querySelector('.fair-programs-section');
617    if (!section) return;
618
619    var programLinks = section.querySelectorAll('.fp-program-name');
620    var bodies = section.querySelectorAll('.fp-body');
621    var images = section.querySelectorAll('.fp-image');
622
623    // --- Staggered entrance animation ---
624    var overline = section.querySelector('.fp-header .overline');
625    var divider = section.querySelector('.fp-divider');
626    var bodyArea = section.querySelector('.fp-body-area');
627    var fpRight = section.querySelector('.fp-right');
628
629    var staggerTargets = [];
630    if (overline) staggerTargets.push(overline);
631    programLinks.forEach(function (l) { staggerTargets.push(l); });
632    if (divider) staggerTargets.push(divider);
633    if (bodyArea) staggerTargets.push(bodyArea);
634
635    gsap.set(staggerTargets, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
636    if (fpRight) gsap.set(fpRight, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
637
638    var tl = gsap.timeline({
639      scrollTrigger: {
640        trigger: section,
641        start: getScrollTriggerStart(),
642        toggleActions: 'play none play none'
643      }
644    });
645
646    tl.to(staggerTargets, {
647      opacity: 1,
648      y: 0,
649      duration: CONFIG.block.duration,
650      stagger: CONFIG.block.stagger,
651      ease: EASE,
652      onComplete: function () { gsap.set(staggerTargets, { willChange: 'auto' }); }
653    }, 0);
654
655    if (fpRight) {
656      var fpRightRevealImg = fpRight.querySelector('.fp-image.is-active .img-reveal img');
657      if (fpRightRevealImg) {
658        gsap.set(fpRightRevealImg, { yPercent: CONFIG.revealImages.initialOffset, willChange: 'transform' });
659      }
660
661      tl.to(fpRight, {
662        opacity: 1,
663        y: 0,
664        duration: CONFIG.block.duration,
665        ease: EASE,
666        onComplete: function () { gsap.set(fpRight, { willChange: 'auto' }); }
667      }, CONFIG.block.stagger * 2);
668
669      if (fpRightRevealImg) {
670        tl.to(fpRightRevealImg, {
671          yPercent: CONFIG.revealImages.finalOffset,
672          duration: CONFIG.block.duration * CONFIG.revealImages.durationMultiplier,
673          ease: 'power3.out',
674          onComplete: function () { gsap.set(fpRightRevealImg, { willChange: 'auto' }); }
675        }, CONFIG.block.stagger * 2 + CONFIG.revealImages.delay);
676      }
677    }
678
679    // --- Hover interaction ---
680    function activateProgram(idx) {
681      programLinks.forEach(function (l) { l.classList.remove('is-active'); });
682      bodies.forEach(function (b) { b.classList.remove('is-active'); });
683      images.forEach(function (b) { b.classList.remove('is-active'); });
684
685      var target = section.querySelector('.fp-program-name[data-fp-index="' + idx + '"]');
686      var body = section.querySelector('[data-fp-body="' + idx + '"]');
687      var image = section.querySelector('[data-fp-image="' + idx + '"]');
688      if (target) target.classList.add('is-active');
689      if (body) body.classList.add('is-active');
690      if (image) {
691        image.classList.add('is-active');
692        var revealImg = image.querySelector('.img-reveal img');
693        if (revealImg) gsap.set(revealImg, { yPercent: 0 });
694      }
695    }
696
697    var isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
698
699    programLinks.forEach(function (link) {
700      link.addEventListener('mouseenter', function () {
701        activateProgram(this.getAttribute('data-fp-index'));
702      });
703
704      if (isTouchDevice) {
705        link.addEventListener('click', function (e) {
706          if (!this.classList.contains('is-active')) {
707            e.preventDefault();
708            activateProgram(this.getAttribute('data-fp-index'));
709          }
710        });
711      }
712    });
713  }
714
715  /* ================================================================
716     Collectors Collective Section
717     ================================================================ */
718
719  function initCollectorsCollective() {
720    var section = document.querySelector('.collectors-collective-section');
721    if (!section) return;
722
723    var logo = section.querySelector('.cc-card__logo');
724    var heading = section.querySelector('.cc-card__heading');
725    var desc = section.querySelector('.cc-card__desc');
726    var actions = section.querySelector('.cc-card__actions');
727    var right = section.querySelector('.cc-card__right');
728
729    var words = heading ? splitHeadingIntoWords(heading) : [];
730
731    if (logo) gsap.set(logo, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
732    if (words.length) gsap.set(words, { opacity: 0, y: CONFIG.header.wordY, willChange: 'transform, opacity' });
733    if (desc) gsap.set(desc, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
734    if (actions) gsap.set(actions, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
735    if (right) gsap.set(right, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
736
737    var rightRevealImg = right ? right.querySelector('.img-reveal img') : null;
738    if (rightRevealImg) {
739      gsap.set(rightRevealImg, { yPercent: CONFIG.revealImages.initialOffset, willChange: 'transform' });
740    }
741
742    var tl = gsap.timeline({
743      scrollTrigger: {
744        trigger: section,
745        start: getScrollTriggerStart(),
746        toggleActions: 'play none play none'
747      }
748    });
749
750    if (logo) {
751      tl.to(logo, {
752        opacity: 1,
753        y: 0,
754        duration: CONFIG.block.duration,
755        ease: EASE,
756        onComplete: function () { gsap.set(logo, { willChange: 'auto' }); }
757      }, 0);
758    }
759
760    if (words.length) {
761      tl.to(words, {
762        opacity: 1,
763        y: 0,
764        duration: CONFIG.header.wordDuration,
765        stagger: CONFIG.header.wordStagger,
766        ease: EASE,
767        onComplete: function () { gsap.set(words, { willChange: 'auto' }); }
768      }, CONFIG.header.overlineOffset);
769    }
770
771    if (desc) {
772      tl.to(desc, {
773        opacity: 1,
774        y: 0,
775        duration: CONFIG.header.contentDuration,
776        ease: EASE,
777        onComplete: function () { gsap.set(desc, { willChange: 'auto' }); }
778      }, CONFIG.header.bodyOffset);
779    }
780
781    if (actions) {
782      tl.to(actions, {
783        opacity: 1,
784        y: 0,
785        duration: CONFIG.header.contentDuration,
786        ease: EASE,
787        onComplete: function () { gsap.set(actions, { willChange: 'auto' }); }
788      }, CONFIG.header.linksOffset);
789    }
790
791    if (right) {
792      tl.to(right, {
793        opacity: 1,
794        y: 0,
795        duration: CONFIG.block.duration,
796        ease: EASE,
797        onComplete: function () { gsap.set(right, { willChange: 'auto' }); }
798      }, CONFIG.header.overlineOffset);
799
800      if (rightRevealImg) {
801        tl.to(rightRevealImg, {
802          yPercent: CONFIG.revealImages.finalOffset,
803          duration: CONFIG.block.duration * CONFIG.revealImages.durationMultiplier,
804          ease: 'power3.out',
805          onComplete: function () { gsap.set(rightRevealImg, { willChange: 'auto' }); }
806        }, CONFIG.header.overlineOffset + CONFIG.revealImages.delay);
807      }
808    }
809  }
810
811  /* ================================================================
812     Artist Opportunities Section
813     ================================================================ */
814
815  function initArtistOpportunities() {
816    var section = document.querySelector('.artist-opportunities-section');
817    if (!section) return;
818
819    var header = section.querySelector('[data-animate-header]');
820    setHeaderInitialStates(header);
821    animateSectionHeader(header, section);
822
823    var cards = section.querySelectorAll('.ao-card');
824    if (cards.length) {
825      animateCardGroup(cards, section, CONFIG.header.bodyOffset);
826    }
827  }
828
829  /* ================================================================
830     Curated Collections Section
831     ================================================================ */
832
833  function initCuratedCollections() {
834    var section = document.querySelector('.curated-collections-section');
835    if (!section) return;
836
837    // --- Intro: heading word-split + desc + button (triggers on intro entering viewport) ---
838    var intro = section.querySelector('.cc-intro');
839    if (intro) {
840      var heading = intro.querySelector('.cc-intro__heading');
841      var desc = intro.querySelector('.cc-intro__desc');
842      var button = intro.querySelector('.button');
843
844      var words = heading ? splitHeadingIntoWords(heading) : [];
845
846      if (words.length) gsap.set(words, { opacity: 0, y: CONFIG.header.wordY, willChange: 'transform, opacity' });
847      if (desc) gsap.set(desc, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
848      if (button) gsap.set(button, { opacity: 0, y: CONFIG.header.contentY, willChange: 'transform, opacity' });
849
850      var introTl = gsap.timeline({
851        scrollTrigger: {
852          trigger: intro,
853          start: getScrollTriggerStart(),
854          toggleActions: 'play none play none'
855        }
856      });
857
858      if (words.length) {
859        introTl.to(words, {
860          opacity: 1,
861          y: 0,
862          duration: CONFIG.header.wordDuration,
863          stagger: CONFIG.header.wordStagger,
864          ease: EASE,
865          onComplete: function () { gsap.set(words, { willChange: 'auto' }); }
866        }, 0);
867      }
868
869      if (desc) {
870        introTl.to(desc, {
871          opacity: 1,
872          y: 0,
873          duration: CONFIG.header.contentDuration,
874          ease: EASE,
875          onComplete: function () { gsap.set(desc, { willChange: 'auto' }); }
876        }, CONFIG.header.bodyOffset);
877      }
878
879      if (button) {
880        introTl.to(button, {
881          opacity: 1,
882          y: 0,
883          duration: CONFIG.header.contentDuration,
884          ease: EASE,
885          onComplete: function () { gsap.set(button, { willChange: 'auto' }); }
886        }, CONFIG.header.linksOffset);
887      }
888    }
889
890    // --- Cards: stagger with img-reveal (separate ScrollTrigger on cards container) ---
891    var cards = section.querySelectorAll('.cc-collection-card');
892    if (cards.length) {
893      var cardsContainer = section.querySelector('.cc-cards') || section;
894
895      gsap.set(cards, { opacity: 0, y: CONFIG.block.y, willChange: 'transform, opacity' });
896
897      cards.forEach(function (card) {
898        var revealImg = card.querySelector('.cc-collection-card__image img');
899        if (revealImg) {
900          gsap.set(revealImg, { yPercent: CONFIG.revealImages.initialOffset, willChange: 'transform' });
901        }
902      });
903
904      var cardsRevealed = false;
905      function revealCcCards(self) {
906        if (cardsRevealed) return;
907        cardsRevealed = true;
908
909        gsap.to(cards, {
910          opacity: 1,
911          y: 0,
912          duration: CONFIG.block.duration,
913          stagger: CONFIG.block.stagger,
914          ease: EASE,
915          onComplete: function () { gsap.set(cards, { willChange: 'auto' }); }
916        });
917
918        cards.forEach(function (card, index) {
919          var revealImg = card.querySelector('.cc-collection-card__image img');
920          if (revealImg) {
921            gsap.to(revealImg, {
922              yPercent: CONFIG.revealImages.finalOffset,
923              duration: CONFIG.block.duration * CONFIG.revealImages.durationMultiplier,
924              ease: 'power3.out',
925              delay: index * CONFIG.block.stagger + CONFIG.revealImages.delay,
926              onComplete: function () { gsap.set(revealImg, { willChange: 'auto' }); }
927            });
928          }
929        });
930
931        self.kill();
932      }
933
934      ScrollTrigger.create({
935        trigger: cardsContainer,
936        start: getScrollTriggerStart(),
937        onEnter: revealCcCards,
938        onEnterBack: revealCcCards
939      });
940    }
941  }
942
943  /* ================================================================
944     Init
945     ================================================================ */
946
947  function init() {
948    // Set hero initial states immediately so content stays hidden behind the loader
949    var heroSection = document.querySelector('.hero-section');
950    if (heroSection) {
951      var heroHeader = heroSection.querySelector('[data-animate-header]');
952      setHeaderInitialStates(heroHeader);
953
954      // Split heading into word spans NOW so the CSS opacity:0 rule on .word takes effect
955      // before the loader finishes (prevents flash of unsplit text)
956      var heroHeading = heroHeader ? heroHeader.querySelector('.heading') : null;
957      if (heroHeading) {
958        var words = splitHeadingIntoWords(heroHeading);
959        if (words.length) {
960          gsap.set(words, { opacity: 0, y: CONFIG.header.wordY, willChange: 'transform, opacity' });
961        }
962      }
963    }
964
965    initWhatsHappening();
966    initInPersonFair();
967    initOnlineFair();
968    initDiscoverArt();
969    initFairPrograms();
970    initCollectorsCollective();
971    initArtistOpportunities();
972    initCuratedCollections();
973
974    document.querySelectorAll('[data-animate-section]').forEach(function (section) {
975      animateBlocks(section);
976    });
977
978    // Defer hero animation until the global loader finishes
979    if (document.body.classList.contains('toaf-loader-complete')) {
980      initHero();
981    } else {
982      window.addEventListener('toafLoaderComplete', function () {
983        initHero();
984      }, { once: true });
985    }
986
987    requestAnimationFrame(function () {
988      ScrollTrigger.refresh();
989    });
990  }
991
992  if (document.readyState === 'loading') {
993    document.addEventListener('DOMContentLoaded', init);
994  } else {
995    init();
996  }
997})();

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.