PageSourceSearch

https://www.themeadows.com/wp-content/themes/astute-block-theme/js/main.js?ver=1788893994

js themeadows.com collected 2026-09-25 22:53:29 UTC 28,303 bytes, 692 lines download raw bytes

1(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2var a11y = require('./src/a11y.js')
3var animation = require('./src/animation.js')
4var details = require('./src/details.js')
5var navigation = require('./src/navigation.js')
6var mentSubnav = require('./src/mh-subnav.js')
7var footer = require('./src/footer.js')
8var videos = require('./src/videos.js')
9var parallax = require('./src/parallax.js')
10var faq = require('./src/faq.js')
11var blogCta = require('./src/blog-cta.js')
12},{"./src/a11y.js":2,"./src/animation.js":3,"./src/blog-cta.js":4,"./src/details.js":5,"./src/faq.js":6,"./src/footer.js":7,"./src/mh-subnav.js":8,"./src/navigation.js":9,"./src/parallax.js":10,"./src/videos.js":11}],2:[function(require,module,exports){
13/* ACCESSIBILITY */
14
15// Adding aria-label to logos linking to Homepage
16let headerLogos = document.querySelectorAll('header a[href="/"]')
17if (headerLogos) {
18    headerLogos.forEach(function(each){
19        each.ariaLabel = "Return to Homepage"
20    })
21}
22let footerLogo = document.querySelector('footer a[href="/"]')
23if (footerLogo) {
24    footerLogo.ariaLabel = "Return to Homepage"
25}
26
27// Adding aria-label to images for Work Pages
28let workClients = document.querySelectorAll('.work-client-item')
29if (workClients) {
30    workClients.forEach(function(each){
31        let labelingText = each.querySelector('.label-link')
32        let toBeLabeled = each.querySelector('.work-image a')
33        toBeLabeled.ariaLabel = `Click to go to the ${labelingText.textContent} client page`
34    })
35}
36},{}],3:[function(require,module,exports){
37// Animation Handler -- checks if BODY children have been scrolled into view
38function revealElements() {
39    // Get all elements with the "has-animation" class
40    const animatedElements = document.querySelectorAll(".has-animation");
41  
42    // Loop through each animated element
43    for (let i = 0; i < animatedElements.length; i++) {
44      const element = animatedElements[i];
45  
46      // Check if the element is partially in view
47      const isPartiallyInView = isElementPartiallyInView(element);
48      if (isPartiallyInView) {
49        // If the element is partially in view, append the "has-been-revealed" class to it
50        element.classList.add("has-been-revealed");
51      }
52    }
53  }
54  
55  function isElementPartiallyInView(element) {
56    // Get the element's bounding rect
57    const boundingRect = element.getBoundingClientRect();
58  
59    // Check if the element is partially within the viewport
60    return (
61      boundingRect.top < (window.innerHeight || document.documentElement.clientHeight) &&
62      boundingRect.bottom >= 0
63    );
64  }
65  
66// Run these checks on scroll and on page load
67window.addEventListener("scroll", revealElements);
68revealElements();
69
70// Wrap buttons in <span> to support animated state
71const buttons = document.querySelectorAll('.wp-block-button a');
72buttons.forEach(button => {
73  const span = document.createElement('span');
74  span.textContent = button.textContent;
75  button.textContent = '';
76  button.appendChild(span);
77});
78
79// Pulsing H1 effect
80if (document.querySelector('h1.is-style-pulse-in-h1')) {
81  let toPulse = document.querySelector('.is-style-pulse-in-h1')
82  let words = toPulse.innerText.split(' ')
83  let wrappedWords = words.map(function(word){
84      return `<span>${word}</span>`
85  })
86  toPulse.innerHTML = wrappedWords.join(' ')
87}
88},{}],4:[function(require,module,exports){
89// Sticky Blog CTA Position Script
90// CSS-focused approach to fix flex container sticky issues
91
92// console.log('Sticky CTA Script loaded!');
93
94// Check if sticky CTA element exists on the page
95function hasStickyCTA() {
96    return document.querySelector('.blog-cta-sticky') !== null;
97}
98
99// Function to update sticky CTA position and fix container heights
100function updateStickyCTAPosition() {
101    const stickyCTA = document.querySelector('.blog-cta-sticky');
102    const header = document.querySelector('header.wp-block-template-part');
103    
104    if (!stickyCTA || !header) {
105        return;
106    }
107    
108    // Get header height
109    const headerHeight = header.offsetHeight;
110    
111    // Calculate new top position (header height + 40px)
112    const newTopPosition = headerHeight + 40;
113    
114    // Apply the new top position
115    stickyCTA.style.top = newTopPosition + 'px';
116    stickyCTA.style.zIndex = '1';
117    
118    // console.log(`Updated sticky CTA position: ${newTopPosition}px (header: ${headerHeight}px + 40px)`);
119}
120
121// Fix the flex container to allow proper sticky behavior
122function fixContainerHeights() {
123    const stickyCTA = document.querySelector('.blog-cta-sticky');
124    const stickyColumn = stickyCTA?.closest('.wp-block-column');
125    const contentColumn = document.querySelector('.wp-block-column.breakout-mobile');
126    const columnsContainer = stickyColumn?.closest('.wp-block-columns');
127    
128    if (!stickyCTA || !stickyColumn || !contentColumn || !columnsContainer) {
129        return;
130    }
131    
132    // Get the height of the main content column
133    const contentHeight = contentColumn.scrollHeight;
134    
135    // Set the sticky column to match or exceed the content height
136    // This gives the sticky element enough vertical space to work within
137    stickyColumn.style.minHeight = contentHeight + 'px';
138    stickyColumn.style.alignSelf = 'flex-start';
139    
140    // Ensure the columns container doesn't constrain height
141    columnsContainer.style.alignItems = 'flex-start';
142    
143    // Make sure the sticky element uses proper positioning
144    stickyCTA.style.position = 'sticky';
145    
146    // console.log(`Set sticky column height to: ${contentHeight}px`);
147}
148
149// Throttle function to limit how often handlers run
150function throttle(func, limit) {
151    let inThrottle;
152    return function() {
153        const args = arguments;
154        const context = this;
155        if (!inThrottle) {
156            func.apply(context, args);
157            inThrottle = true;
158            setTimeout(() => inThrottle = false, limit);
159        }
160    }
161}
162
163// Initialize the sticky CTA positioning
164function initStickyCTA() {
165    // console.log('Initializing sticky CTA positioning...');
166    
167    // Fix container heights first
168    fixContainerHeights();
169    
170    // Set initial position
171    updateStickyCTAPosition();
172    
173    // Create throttled handlers
174    const throttledUpdate = throttle(updateStickyCTAPosition, 16);
175    const throttledHeightFix = throttle(fixContainerHeights, 100);
176    
177    // Update position on window resize
178    window.addEventListener('resize', function() {
179        throttledHeightFix();
180        throttledUpdate();
181    });
182    
183    // Watch for changes to header content that might affect height
184    const header = document.querySelector('header.wp-block-template-part');
185    if (header) {
186        const headerObserver = new MutationObserver(() => {
187            setTimeout(updateStickyCTAPosition, 10);
188        });
189        
190        headerObserver.observe(header, {
191            childList: true,
192            subtree: true,
193            attributes: true,
194            attributeFilter: ['class', 'style']
195        });
196    }
197    
198    // Watch for content changes that might affect the main column height
199    const contentColumn = document.querySelector('.wp-block-column.breakout-mobile');
200    if (contentColumn) {
201        const contentObserver = new MutationObserver(() => {
202            setTimeout(throttledHeightFix, 50);
203        });
204        
205        contentObserver.observe(contentColumn, {
206            childList: true,
207            subtree: true
208        });
209    }
210}
211
212// Initialize on DOM load only if sticky CTA element exists
213document.addEventListener('DOMContentLoaded', () => {
214    // console.log('DOM loaded!');
215    if (hasStickyCTA()) {
216        // console.log('Sticky CTA element found, initializing...');
217        initStickyCTA();
218    } else {
219        // console.log('No sticky CTA element found, script will not initialize');
220    }
221});
222
223// Handle case where elements might be added dynamically
224const observer = new MutationObserver((mutations) => {
225    mutations.forEach((mutation) => {
226        if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
227            mutation.addedNodes.forEach((node) => {
228                if (node.nodeType === 1) {
229                    // Check if the added node is the sticky CTA or contains it
230                    if ((node.matches && node.matches('.blog-cta-sticky')) || 
231                        (node.querySelector && node.querySelector('.blog-cta-sticky'))) {
232                        // console.log('Sticky CTA element dynamically added');
233                        // Small delay to ensure element is fully rendered
234                        setTimeout(initStickyCTA, 100);
235                    }
236                }
237            });
238        }
239    });
240});
241
242if (hasStickyCTA()) {
243    // Start observing for dynamically added content
244    observer.observe(document.body, {
245        childList: true,
246        subtree: true
247    });
248}
249
250},{}],5:[function(require,module,exports){
251// FAQ Animation JavaScript
252(function() {
253    document.addEventListener('DOMContentLoaded', function() {
254      const detailsElements = document.querySelectorAll('.single-post .entry-content details');
255      
256      detailsElements.forEach(function(details) {
257        const summary = details.querySelector('summary');
258        
259        summary.addEventListener('click', function(e) {
260          if (details.hasAttribute('open')) {
261            // Prevent the default closing behavior
262            e.preventDefault();
263            
264            // Add closing class to trigger animation
265            details.classList.add('closing');
266            
267            // Remove open attribute after animation finishes
268            setTimeout(function() {
269              details.removeAttribute('open');
270              details.classList.remove('closing');
271            }, 300);
272          }
273        });
274      });
275    });
276  })();
277},{}],6:[function(require,module,exports){
278// FAQ Item JavaScript - handles AJAX-loaded content
279// Only runs if FAQ elements exist on the page
280
281// console.log('FAQ Item Script loaded!');
282
283// Check if FAQ elements exist on the page
284function hasFAQElements() {
285    return document.querySelector('details.is-style-faq-item') !== null;
286}
287
288// Function to initialize FAQ details
289function initFAQDetails() {
290    const faqDetails = document.querySelectorAll('details.is-style-faq-item:not([data-faq-initialized])');
291    console.log('Initializing FAQ details:', faqDetails.length);
292    
293    faqDetails.forEach((details, index) => {
294        // Mark as initialized to prevent duplicate listeners
295        details.setAttribute('data-faq-initialized', 'true');
296        
297        const summary = details.querySelector('summary');
298        const content = Array.from(details.children).filter(child => child !== summary);
299        
300        // Wrap content in a div for better animation control
301        if (!details.querySelector('.faq-content-wrapper')) {
302            const wrapper = document.createElement('div');
303            wrapper.className = 'faq-content-wrapper';
304            content.forEach(node => wrapper.appendChild(node));
305            details.appendChild(wrapper);
306        }
307        
308        const contentWrapper = details.querySelector('.faq-content-wrapper');
309        
310        summary.addEventListener('click', (e) => {
311            if (details.hasAttribute('open')) {
312                e.preventDefault();
313                
314                // Get current height for smooth transition
315                const currentHeight = contentWrapper.scrollHeight;
316                contentWrapper.style.height = currentHeight + 'px';
317                contentWrapper.style.transition = 'height 0.3s ease-out, opacity 0.3s ease-out';
318                
319                // Force reflow
320                contentWrapper.offsetHeight;
321                
322                // Animate to closed
323                contentWrapper.style.height = '0px';
324                contentWrapper.style.opacity = '0';
325                
326                setTimeout(() => {
327                    details.removeAttribute('open');
328                    contentWrapper.style.height = '';
329                    contentWrapper.style.opacity = '';
330                    contentWrapper.style.transition = '';
331                }, 300);
332            }
333        });
334
335        // Handle opening with smooth animation
336        details.addEventListener('toggle', () => {
337            if (details.hasAttribute('open')) {
338                contentWrapper.style.height = '0px';
339                contentWrapper.style.opacity = '0';
340                contentWrapper.style.transition = 'height 0.3s ease-out, opacity 0.3s ease-out';
341                
342                // Force reflow
343                contentWrapper.offsetHeight;
344                
345                // Get target height
346                const targetHeight = contentWrapper.scrollHeight;
347                contentWrapper.style.height = targetHeight + 'px';
348                contentWrapper.style.opacity = '1';
349                
350                // Clean up after animation
351                setTimeout(() => {
352                    contentWrapper.style.height = '';
353                    contentWrapper.style.opacity = '';
354                    contentWrapper.style.transition = '';
355                }, 300);
356            }
357        });
358
359        // Prevent clicks within details content from triggering close
360        details.addEventListener('click', (e) => {
361            if (e.target !== summary && !summary.contains(e.target)) {
362                e.stopPropagation();
363            }
364        });
365    });
366}
367
368// Initialize on DOM load only if FAQ elements exist
369document.addEventListener('DOMContentLoaded', () => {
370    // console.log('DOM loaded!');
371    if (hasFAQElements()) {
372        // console.log('FAQ elements found, initializing...');
373        initFAQDetails();
374        
375        // Only set up mutation observer if FAQ elements exist
376        const observer = new MutationObserver((mutations) => {
377            mutations.forEach((mutation) => {
378                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
379                    mutation.addedNodes.forEach((node) => {
380                        if (node.nodeType === 1) {
381                            if (node.matches && node.matches('details.is-style-faq-item')) {
382                                console.log('New FAQ details detected');
383                                initFAQDetails();
384                            } else if (node.querySelector && node.querySelector('details.is-style-faq-item')) {
385                                console.log('Container with FAQ details detected');
386                                initFAQDetails();
387                            }
388                        }
389                    });
390                }
391            });
392        });
393
394        // Start observing the document for changes
395        observer.observe(document.body, {
396            childList: true,
397            subtree: true
398        });
399    } else {
400        // console.log('No FAQ elements found, script will not initialize');
401    }
402});
403},{}],7:[function(require,module,exports){
404
405},{}],8:[function(require,module,exports){
406if (document.querySelector('.mental-health-subpage-menu')) {
407    let mhToggle = document.querySelector('.mh-nav-toggle')
408    let mhSubnav = document.querySelector('.mental-health-subpage-menu')
409    mhToggle.addEventListener('click',function(click){
410        mhToggle.classList.toggle('toggled')
411        mhSubnav.classList.toggle('open')
412    })
413}
414},{}],9:[function(require,module,exports){
415if (document.querySelector('.front-end .header-wrap')) {
416  window.addEventListener("load", (event) => {
417      let topLevelPages = document.querySelectorAll('.meganav-parent > a')
418      topLevelPages.forEach(function(each){
419          let currentPage = window.location.href
420          if (currentPage.includes(each.href)) {
421              each.classList.add('active-page')
422          }
423      })
424    })
425  
426  window.onscroll = function() { stickyScroll() }
427  
428  function stickyScroll() {
429    const headerWrap = document.querySelector('.header-wrap');
430    const topRowMenu = document.querySelector('.top-row-menu');
431    const meganav = document.querySelector('.meganav');
432    const scrollingDesktopToggle = document.querySelector('.scrolling-desktop-toggle');
433  
434    const scrollPosition = window.pageYOffset;
435    const scrollThresholdAdd = 120; // Threshold for adding the 'scrolling' class
436    const scrollThresholdRemove = 75; // Threshold for removing the 'scrolling' class
437  
438    if (scrollPosition >= scrollThresholdAdd) {
439      // If scroll position exceeds the 'add' threshold, add 'scrolling' class
440      headerWrap.classList.add('scrolling');
441      if (scrollingDesktopToggle) {
442        scrollingDesktopToggle.classList.remove('hidden');
443      }
444    } else if (scrollPosition < scrollThresholdRemove) {
445      // If scroll position is below the 'remove' threshold, remove 'scrolling' class
446      headerWrap.classList.remove('scrolling');
447      headerWrap.classList.remove('menu-toggled');
448      if (topRowMenu) {
449        topRowMenu.style.maxHeight = '';
450      }
451      meganav.style.display = '';
452    }
453  }
454  
455  
456  if (document.querySelector('.scrolling-menu-toggle')) {
457      let scrollingMenuToggle = document.querySelector('.scrolling-menu-toggle')
458      scrollingMenuToggle.addEventListener('click',function(event){
459          let headerWrap = document.querySelector('.header-wrap')
460          headerWrap.classList.add('menu-toggled')
461          let topRowMenu = document.querySelector('.top-row-menu')
462          topRowMenu.style.maxHeight = '300px'
463          let meganav = document.querySelector('.meganav')
464          meganav.style.display = 'flex'
465          let scrollingDesktopToggle = document.querySelector('.scrolling-desktop-toggle')
466          scrollingDesktopToggle.classList.add('hidden')
467      })
468  }
469  
470  if (document.querySelector('.mobile-menu-toggle')) {
471      let mobileMenuToggle = document.querySelector('.mobile-menu-toggle')
472      mobileMenuToggle.addEventListener('click',function(event){
473          let headerWrap = document.querySelector('.header-wrap')
474          let toggle = mobileMenuToggle.querySelector('.toggler')
475          if (headerWrap.classList.contains('menu-toggled')) {
476              headerWrap.classList.remove('menu-toggled')
477              document.body.classList.remove('no-scroll')
478              toggle.checked = false
479          } else {
480              headerWrap.classList.add('menu-toggled')
481              document.body.classList.add('no-scroll')
482              toggle.checked = true
483          }
484      })
485  }
486  
487  if (document.querySelectorAll('.meganav-subnav-toggle')) {
488      let mobileSubnavToggles = document.querySelectorAll('.meganav-subnav-toggle')
489      mobileSubnavToggles.forEach(function(each){
490          each.addEventListener('click',function(event){
491              each.classList.toggle('toggled')
492              let closestMenu = each.parentNode.querySelector('.meganav-subnav')
493              closestMenu.classList.toggle('open')
494          })
495      })
496  }
497}
498
499},{}],10:[function(require,module,exports){
500// window.addEventListener("DOMContentLoaded", function () {
501//     const parallaxCta = document.querySelector(".parallax-cta");
502
503//     // Ensure the parallax-cta section is snapped to the start
504//     parallaxCta.scrollTop = 0;
505
506//     // Optional: Smoothly scroll to the snapped position when a link is clicked
507//     document.querySelectorAll('a[href^="#"]').forEach(anchor => {
508//       anchor.addEventListener("click", function (e) {
509//         e.preventDefault();
510
511//         const targetId = this.getAttribute("href").substring(1);
512//         const targetElement = document.getElementById(targetId);
513
514//         if (targetElement) {
515//           // Scroll smoothly to the target element
516//           targetElement.scrollIntoView({
517//             behavior: "smooth",
518//             block: "start"
519//           });
520//         }
521//       });
522//     });
523//   });
524},{}],11:[function(require,module,exports){
525// --- Video consent gate (CookieYes) ------------------------------------------
526// Reimplemented from video-consent-gating.md (surface A). A YouTube embed sets
527// third-party cookies the moment its iframe is built, so we never build the iframe
528// until consent allows it. Posture is US opt-out (our fleet): load by default,
529// block only on GPC or an explicit opt-out of any non-necessary category.
530//
531// ⚠️ NEVER gate on the meta keys (consentid / consent / action). In CookieYes's
532// opt-out config `consent:no` is the DEFAULT (untouched) state and `consent:yes`
533// appears after ANY choice — they can't tell accept from decline. Only the
534// per-category flags carry the real choice.
535var VIDEO_CONSENT = {
536    mode: 'optout',    // 'categories' (GDPR) | 'optout' (US, default)
537    categories: [],    // GDPR mode only; opt-out auto-detects from the cookie
538    message: 'Accept cookies to view this video.',
539};
540
541// Cookie keys that are NOT consent categories (or are always granted), so they
542// never signal an opt-out. Everything else in the cookie is treated as a category.
543var CKY_NON_CATEGORY_KEYS = ['consentid', 'consent', 'action', 'necessary'];
544
545// LOCAL TESTING: CookieYes only fires on live. To preview the placeholder locally,
546// run  testNoConsent = true  in the console, then click a play button (no rebuild).
547function parseCkyConsent() {
548    var entry = document.cookie.split('; ').find(function (c) { return c.indexOf('cookieyes-consent=') === 0; });
549    if (!entry) { return null; }
550    var map = {};
551    decodeURIComponent(entry.substring(entry.indexOf('=') + 1)).split(',').forEach(function (pair) {
552        var i = pair.indexOf(':');
553        if (i > -1) { map[pair.substring(0, i)] = pair.substring(i + 1); }
554    });
555    return map;
556}
557
558function hasVideoConsent() {
559    if (window.testNoConsent === true) { return false; } // local override -> placeholder
560    var c = parseCkyConsent();
561    if (VIDEO_CONSENT.mode === 'optout') {
562        // US opt-out: allow by default; block only on GPC, or an explicit opt-out
563        // of ANY non-necessary category. Slugs vary per site, so scan the cookie
564        // instead of a hardcoded list; skip meta keys (NOT the `consent` flag).
565        if (navigator.globalPrivacyControl === true) { return false; }
566        if (!c) { return true; } // no choice recorded yet -> allowed (fail open)
567        return !Object.keys(c).some(function (k) {
568            return CKY_NON_CATEGORY_KEYS.indexOf(k) === -1 && c[k] === 'no';
569        });
570    }
571    // GDPR opt-in: fail closed until EVERY required category is granted (never guess).
572    if (!VIDEO_CONSENT.categories.length) { return false; }
573    if (!c) { return false; }
574    return VIDEO_CONSENT.categories.every(function (cat) { return c[cat] === 'yes'; });
575}
576
577// Placeholder shown inside the modal when consent is missing: routes the visitor to
578// their CookieYes settings via revisitCkyConsent(). No iframe is built.
579function videoConsentMarkup() {
580    // The .video-modal dialog is transparent and normally sized by the iframe, so
581    // the placeholder carries its own solid background and matches the iframe's box
582    // (aspect-ratio / min-width) — otherwise the text sits unreadable over the page.
583    return '' +
584        '<div class="video-consent" style="display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;padding:2rem;color:#fff;background:#262626;aspect-ratio:32/17;min-width:60vw;max-width:95vw;max-height:70vh;">' +
585            '<div>' +
586                '<p style="margin:0 0 1.25rem;font-size:1.1rem;line-height:1.5;max-width:36rem;">' + VIDEO_CONSENT.message + '</p>' +
587                '<a href="javascript:void(0)" class="video-consent-btn" style="display:inline-block;padding:1rem;border:1px solid #fff;color:#fff;text-decoration:none;cursor:pointer;">
587Revisit Cookie and Tracking Technology Consent</a>' +
588            '</div>' +
589        '</div>';
590}
591
592if (document.querySelector('.is-style-play-button-hl')) {
593    createModal()
594    let playButton = document.querySelectorAll('.is-style-play-button-hl a')
595    playButton.forEach(function(each){
596        let videoLink = each.href
597        each.removeAttribute('href')
598        each.addEventListener('click',function(click){
599            populateModal(videoLink)
600        })
601    })
602}
603
604if (document.querySelector('.is-style-play-button-hd')) {
605    createModal()
606    let playButton = document.querySelectorAll('.is-style-play-button-hd a')
607    playButton.forEach(function(each){
608        let videoLink = each.href
609        each.removeAttribute('href')
610        each.addEventListener('click',function(click){
611            populateModal(videoLink)
612        })
613    })
614}
615
616if (document.querySelector('.is-style-play-button-image')) {
617    createModal()
618    let playImages = document.querySelectorAll('.is-style-play-button-image a')
619    playImages.forEach(function(each){
620        let videoLink = each.href
621        each.removeAttribute('href')
622        each.addEventListener('click',function(click){
623            populateModal(videoLink)
624        })
625    })
626}
627
628function createModal() {
629    let footer = document.querySelector('footer.wp-block-template-part')
630    let videoModal = document.createElement('dialog')
631    videoModal.classList.add('video-modal')
632    videoModal.addEventListener('click',function(click){
633        videoModal.close()
634        while (videoModal.childNodes.length > 0) {
635            videoModal.removeChild(videoModal.firstChild)
636        }
637        document.body.classList.remove('no-scroll')
638    })
639    footer.appendChild(videoModal)
640}
641
642// Pull the YouTube video ID out of any of the common URL shapes and build a
643// single canonical privacy-enhanced embed URL. Handles watch?v=ID (with trailing
644// &list/&t params), youtu.be/ID short links, and already-/embed/ID URLs. Anything
645// unrecognised is returned untouched so it degrades to the raw href.
646function toNoCookieEmbed(url) {
647    let match = url.match(/[?&]v=([A-Za-z0-9_-]+)/)     // youtube.com/watch?v=ID
648        || url.match(/youtu\.be\/([A-Za-z0-9_-]+)/)     // youtu.be/ID
649        || url.match(/\/embed\/([A-Za-z0-9_-]+)/)       // youtube.com/embed/ID
650    if (!match) { return url }
651    return 'https://www.youtube-nocookie.com/embed/' + match[1]
652}
653
654function populateModal(video) {
655    video = toNoCookieEmbed(video)
656    let videoModal = document.querySelector('footer.wp-block-template-part dialog.video-modal')
657    while (videoModal.childNodes.length > 0) {
658        videoModal.removeChild(videoModal.firstChild)
659    }
660    if (hasVideoConsent()) {
661        // Consent allows it -> build the embed.
662        let videoFrame = document.createElement('iframe')
663        videoFrame.setAttribute('id','video-frame')
664        videoFrame.setAttribute('frameborder',0)
665        videoFrame.setAttribute('scrolling','no')
666        videoFrame.setAttribute('type','text/html')
667        videoFrame.setAttribute('src',video)
668        videoFrame.setAttribute('allowfullscreen', true)
669        videoModal.appendChild(videoFrame)
670    } else {
671        // Opted out (or GPC) -> placeholder + button to reopen consent settings.
672        // No iframe is built, so no request reaches YouTube.
673        videoModal.insertAdjacentHTML('beforeend', videoConsentMarkup())
674        let consentBtn = videoModal.querySelector('.video-consent-btn')
675        if (consentBtn) {
676            consentBtn.addEventListener('click', function (click) {
677                click.stopPropagation()
678                // Close the modal FIRST: a <dialog> opened with showModal() sits in
679                // the browser's top layer, so the CookieYes settings would otherwise
680                // open BEHIND it and be unreachable.
681                videoModal.close()
682                document.body.classList.remove('no-scroll')
683                if (typeof window.revisitCkyConsent === 'function') {
684                    window.revisitCkyConsent()
685                }
686            })
687        }
688    }
689    videoModal.showModal()
690    document.body.classList.add('no-scroll')
691}
692},{}]},{},[1]);

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.