PageSourceSearch

https://worldcitiescultureforum.com/wp-content/plugins/wccf-site/public/scripts/wccf-site.js?ver=1.0.0

js worldcitiescultureforum.com collected 2026-10-02 03:27:11 UTC 20,418 bytes, 471 lines download raw bytes

vendor: 12,960 bytes, lines 1-340
1/**
2 *
3 * The shadowDom / Intersection Observer version of Paul's concept:
4 * https://github.com/paulirish/lite-youtube-embed
5 *
6 * A lightweight YouTube embed. Still should feel the same to the user, just
7 * MUCH faster to initialize and paint.
8 *
9 * Thx to these as the inspiration
10 *   https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html
11 *   https://autoplay-youtube-player.glitch.me/
12 *
13 * Once built it, I also found these (👍👍):
14 *   https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube
15 *   https://github.com/Daugilas/lazyYT https://github.com/vb/lazyframe
16 */
17/*
18 * Vimeo example embed markup:
19<iframe src="https://player.vimeo.com/video/364402896"
20  width="640" height="360"
21  frameborder="0"
22  allow="autoplay; fullscreen" allowfullscreen>
23</iframe>
24<p><a href="https://vimeo.com/364402896">
25  Alex Russell - The Mobile Web: MIA</a> from
26    <a href="https://vimeo.com/fronteers">Fronteers</a>
27    on <a href="https://vimeo.com">Vimeo</a>.
28</p>
29 */
30let LiteVimeoEmbed = /** @class */ (() => {
31    class LiteVimeoEmbed extends HTMLElement {
32        constructor() {
33            super();
34            this.iframeLoaded = false;
35            this.setupDom();
36        }
37        static get observedAttributes() {
38            return ['videoid'];
39        }
40        connectedCallback() {
41            this.addEventListener('pointerover', LiteVimeoEmbed.warmConnections, {
42                once: true,
43            });
44            this.addEventListener('click', () => this.addIframe());
45        }
46        get videoId() {
47            return encodeURIComponent(this.getAttribute('videoid') || '');
48        }
49        set videoId(id) {
50            this.setAttribute('videoid', id);
51        }
52        get videoTitle() {
53            return this.getAttribute('videotitle') || 'Video';
54        }
55        set videoTitle(title) {
56            this.setAttribute('videotitle', title);
57        }
58        get videoPlay() {
59            return this.getAttribute('videoPlay') || 'Play';
60        }
61        set videoPlay(name) {
62            this.setAttribute('videoPlay', name);
63        }
64        get videoStartAt() {
65            return this.getAttribute('videoPlay') || '0s';
66        }
67        set videoStartAt(time) {
68            this.setAttribute('videoPlay', time);
69        }
70        get autoLoad() {
71            return this.hasAttribute('autoload');
72        }
73        set autoLoad(value) {
74            if (value) {
75                this.setAttribute('autoload', '');
76            }
77            else {
78                this.removeAttribute('autoload');
79            }
80        }
81        get autoPlay() {
82            return this.hasAttribute('autoplay');
83        }
84        set autoPlay(value) {
85            if (value) {
86                this.setAttribute('autoplay', 'autoplay');
87            }
88            else {
89                this.removeAttribute('autoplay');
90            }
91        }
92        /**
93         * Define our shadowDOM for the component
94         */
95        setupDom() {
96            const shadowDom = this.attachShadow({ mode: 'open' });
97            shadowDom.innerHTML = `
98      <style>
99        :host {
100          contain: content;
101          display: block;
102          position: relative;
103          width: 100%;
104          padding-bottom: calc(100% / (16 / 9));
105        }
106
107        #frame, #fallbackPlaceholder, iframe {
108          position: absolute;
109          width: 100%;
110          height: 100%;
111        }
112
113        #frame {
114          cursor: pointer;
115        }
116
117        #fallbackPlaceholder {
118          object-fit: cover;
119        }
120
121        #frame::before {
122          content: '';
123          display: block;
124          position: absolute;
125          top: 0;
126          background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
127          background-position: top;
128          background-repeat: repeat-x;
129          height: 60px;
130          padding-bottom: 50px;
131          width: 100%;
132          transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
133          z-index: 1;
134        }
135        /* play button */
136        .lvo-playbtn {
137          width: 70px;
138          height: 46px;
139          background-color: #212121;
140          z-index: 1;
141          opacity: 0.8;
142          border-radius: 10%;
143          transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
144          border: 0;
145        }
146        #frame:hover .lvo-playbtn {
147          background-color: rgb(98, 175, 237);
148          opacity: 1;
149        }
150        /* play button triangle */
151        .lvo-playbtn:before {
152          content: '';
153          border-style: solid;
154          border-width: 11px 0 11px 19px;
155          border-color: transparent transparent transparent #fff;
156        }
157        .lvo-playbtn,
158        .lvo-playbtn:before {
159          position: absolute;
160          top: 50%;
161          left: 50%;
162          transform: translate3d(-50%, -50%, 0);
163        }
164
165        /* Post-click styles */
166        .lvo-activated {
167          cursor: unset;
168        }
169
170        #frame.lvo-activated::before,
171        .lvo-activated .lvo-playbtn {
172          display: none;
173        }
174      </style>
175      <div id="frame">
176        <picture>
177          <source id="webpPlaceholder" type="image/webp">
178          <source id="jpegPlaceholder" type="image/jpeg">
179          <img id="fallbackPlaceholder" referrerpolicy="origin">
180        </picture>
181        <button class="lvo-playbtn"></button>
182      </div>
183    `;
184            this.domRefFrame = this.shadowRoot.querySelector('#frame');
185            this.domRefImg = {
186                fallback: this.shadowRoot.querySelector('#fallbackPlaceholder'),
187                webp: this.shadowRoot.querySelector('#webpPlaceholder'),
188                jpeg: this.shadowRoot.querySelector('#jpegPlaceholder'),
189            };
190            this.domRefPlayButton = this.shadowRoot.querySelector('.lvo-playbtn');
191        }
192        /**
193         * Parse our attributes and fire up some placeholders
194         */
195        setupComponent() {
196            this.initImagePlaceholder();
197            this.domRefPlayButton.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`);
198            this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`);
199            if (this.autoLoad) {
200                this.initIntersectionObserver();
201            }
202        }
203        /**
204         * Lifecycle method that we use to listen for attribute changes to period
205         * @param {*} name
206         * @param {*} oldVal
207         * @param {*} newVal
208         */
209        attributeChangedCallback(name, oldVal, newVal) {
210            switch (name) {
211                case 'videoid': {
212                    if (oldVal !== newVal) {
213                        this.setupComponent();
214                        // if we have a previous iframe, remove it and the activated class
215                        if (this.domRefFrame.classList.contains('lvo-activated')) {
216                            this.domRefFrame.classList.remove('lvo-activated');
217                            this.shadowRoot.querySelector('iframe').remove();
218                        }
219                    }
220                    break;
221                }
222                default:
223                    break;
224            }
225        }
226        /**
227         * Inject the iframe into the component body
228         */
229        addIframe() {
230            if (!this.iframeLoaded) {
231                /**
232                 * Vimeo example embed markup:
233                 *
234                 *  <iframe src="https://player.vimeo.com/video/364402896#t=1m3s"
235                 *    width="640" height="360"
236                 *    frameborder="0"
237                 *    allow="autoplay; fullscreen" allowfullscreen>
238                 *  </iframe>
239                 */
240                // FIXME: add a setting for autoplay
241                let apValue = ((this.autoLoad && this.autoPlay) || (!this.autoLoad)) ?
242                    "autoplay=1" : "";
243                let srcUrl = new URL(`/video/${this.videoId}?${apValue}&#t=${this.videoStartAt}`, "https://player.vimeo.com/");
244                // TODO: construct src value w/ URL constructor
245                const iframeHTML = `
246<iframe frameborder="0"
247  allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" 
248  allowfullscreen src="${srcUrl}"></iframe>`;
249                this.domRefFrame.insertAdjacentHTML('beforeend', iframeHTML);
250                this.domRefFrame.classList.add('lvo-activated');
251                this.iframeLoaded = true;
252            }
253        }
254        /**
255         * Setup the placeholder image for the component
256         */
257        async initImagePlaceholder() {
258            // TODO(slightlyoff): TODO: cache API responses
259            // we don't know which image type to preload, so warm the connection
260            LiteVimeoEmbed.addPrefetch('preconnect', 'https://i.vimeocdn.com/');
261            // API is the video-id based
262            // http://vimeo.com/api/v2/video/364402896.json
263            let api_url = `https://vimeo.com/api/v2/video/${this.videoId}.json`;
264            // Now fetch the JSON that locates our placeholder from vimeo's JSON API
265            let api_response = (await (await fetch(api_url)).json())[0];
266            // Extract the image id, e.g. 819916979, from a URL like:
267            // thumbnail_large: "https://i.vimeocdn.com/video/819916979_640.jpg"
268            let tn_large = api_response.thumbnail_large;
269            let imgId = (tn_large.substr(tn_large.lastIndexOf("/") + 1)).split("_")[0];
270            // const posterUrlWebp = `https://i.ytimg.com/vi_webp/${this.videoId}/hqdefault.webp`;
271            const posterUrlWebp = `https://i.vimeocdn.com/video/${imgId}.webp?mw=1100&mh=619&q=70`;
272            const posterUrlJpeg = `https://i.vimeocdn.com/video/${imgId}.jpg?mw=1100&mh=619&q=70`;
273            this.domRefImg.webp.srcset = posterUrlWebp;
274            this.domRefImg.jpeg.srcset = posterUrlJpeg;
275            this.domRefImg.fallback.src = posterUrlJpeg;
276            this.domRefImg.fallback.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`);
277            this.domRefImg.fallback.setAttribute('alt', `${this.videoPlay}: ${this.videoTitle}`);
278        }
279        /**
280         * Setup the Intersection Observer to load the iframe when scrolled into view
281         */
282        initIntersectionObserver() {
283            if ('IntersectionObserver' in window &&
284                'IntersectionObserverEntry' in window) {
285                const options = {
286                    root: null,
287                    rootMargin: '0px',
288                    threshold: 0,
289                };
290                const observer = new IntersectionObserver((entries, observer) => {
291                    entries.forEach(entry => {
292                        if (entry.isIntersecting && !this.iframeLoaded) {
293                            LiteVimeoEmbed.warmConnections();
294                            this.addIframe();
295                            observer.unobserve(this);
296                        }
297                    });
298                }, options);
299                observer.observe(this);
300            }
301        }
302        /**
303         * Add a <link rel={preload | preconnect} ...> to the head
304         * @param {*} kind
305         * @param {*} url
306         * @param {*} as
307         */
308        static addPrefetch(kind, url, as) {
309            const linkElem = document.createElement('link');
310            linkElem.rel = kind;
311            linkElem.href = url;
312            if (as) {
313                linkElem.as = as;
314            }
315            linkElem.crossOrigin = 'true';
316            document.head.append(linkElem);
317        }
318        /**
319         * Begin preconnecting to warm up the iframe load Since the embed's netwok
320         * requests load within its iframe, preload/prefetch'ing them outside the
321         * iframe will only cause double-downloads. So, the best we can do is warm up
322         * a few connections to origins that are in the critical path.
323         *
324         * Maybe `<link rel=preload as=document>` would work, but it's unsupported:
325         * http://crbug.com/593267 But TBH, I don't think it'll happen soon with Site
326         * Isolation and split caches adding serious complexity.
327         */
328        static warmConnections() {
329            if (LiteVimeoEmbed.preconnected)
330                return;
331            // Host that Vimeo uses to serve JS needed by player
332            LiteVimeoEmbed.addPrefetch('preconnect', 'https://f.vimeocdn.com');
333            // The iframe document comes from player.vimeo.com
334            LiteVimeoEmbed.addPrefetch('preconnect', 'https://player.vimeo.com');
335            // Image for placeholder comes from i.vimeocdn.com
336            LiteVimeoEmbed.addPrefetch('preconnect', 'https://i.vimeocdn.com');
337            LiteVimeoEmbed.preconnected = true;
338        }
339    }
340    LiteVimeoEmbed.preconnected = false;
341    return LiteVimeoEmbed;
342})();
343export { LiteVimeoEmbed };
344// Register custom element
345customElements.define('lite-vimeo', LiteVimeoEmbed);
346//# sourceMappingURL=lite-vimeo.js.map
347/**
348 * A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint.
349 *
350 * Thx to these as the inspiration
351 *   https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html
352 *   https://autoplay-youtube-player.glitch.me/
353 *
354 * Once built it, I also found these:
355 *   https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (👍👍)
356 *   https://github.com/Daugilas/lazyYT
357 *   https://github.com/vb/lazyframe
358 */
359class LiteYTEmbed extends HTMLElement {
360    connectedCallback() {
361        this.videoId = this.getAttribute('videoid');
362
363        let playBtnEl = this.querySelector('.lty-playbtn');
364        // A label for the button takes priority over a [playlabel] attribute on the custom-element
365        this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play';
366
367        /**
368         * Lo, the youtube placeholder image!  (aka the thumbnail, poster image, etc)
369         *
370         * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md
371         *
372         */
373        this.posterUrl = `https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg`;
374        // Warm the connection for the poster image
375        LiteYTEmbed.addPrefetch('preload', this.posterUrl, 'image');
376
377        this.style.backgroundImage = `url("${this.posterUrl}")`;
378
379        // Set up play button, and its visually hidden label
380        if (!playBtnEl) {
381            playBtnEl = document.createElement('button');
382            playBtnEl.type = 'button';
383            playBtnEl.classList.add('lty-playbtn');
384            this.append(playBtnEl);
385        }
386        if (!playBtnEl.textContent) {
387            const playBtnLabelEl = document.createElement('span');
388            playBtnLabelEl.className = 'lyt-visually-hidden';
389            playBtnLabelEl.textContent = this.playLabel;
390            playBtnEl.append(playBtnLabelEl);
391        }
392
393        // On hover (or tap), warm up the TCP connections we're (likely) about to use.
394        this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true});
395
396        // Once the user clicks, add the real iframe and drop our play button
397        // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time
398        //   We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003
399        this.addEventListener('click', e => this.addIframe());
400    }
401
402    // // TODO: Support the the user changing the [videoid] attribute
403    // attributeChangedCallback() {
404    // }
405
406    /**
407     * Add a <link rel={preload | preconnect} ...> to the head
408     */
409    static addPrefetch(kind, url, as) {
410        const linkEl = document.createElement('link');
411        linkEl.rel = kind;
412        linkEl.href = url;
413        if (as) {
414            linkEl.as = as;
415        }
416        document.head.append(linkEl);
417    }
418
419    /**
420     * Begin pre-connecting to warm up the iframe load
421     * Since the embed's network requests load within its iframe,
422     *   preload/prefetch'ing them outside the iframe will only cause double-downloads.
423     * So, the best we can do is warm up a few connections to origins that are in the critical path.
424     *
425     * Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267
426     * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity.
427     */
428    static warmConnections() {
429        if (LiteYTEmbed.preconnected) return;
430
431        // The iframe document and most of its subresources come right off youtube.com
432        LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com');
433        // The botguard script is fetched off from google.com
434        LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com');
435
436        // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling.
437        LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net');
438        LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net');
439
440        LiteYTEmbed.preconnected = true;
441    }
442
443    addIframe() {
444        const params = new URLSearchParams(this.getAttribute('params') || []);
445        params.append('autoplay', '1');
446
447        const iframeEl = document.createElement('iframe');
448        iframeEl.width = 560;
449        iframeEl.height = 315;
450        // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include
451        iframeEl.title = this.playLabel;
452        iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture';
453        iframeEl.allowFullscreen = true;
454        // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL
455        // https://stackoverflow.com/q/64959723/89484
456        iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.videoId)}?${params.toString()}`;
457        this.append(iframeEl);
458
459        this.classList.add('lyt-activated');
460
461        // Set focus for a11y
462        this.querySelector('iframe').focus();
463    }
464}
465// Register custome element
466customElements.define('lite-youtube', LiteYTEmbed);
467
468// import '../vendor/lite-vimeo-embed'; //Youtube embed - https://github.com/paulirish/lite-youtube-embed
469// import '../vendor/lite-youtube-embed'; //Vimeo embed - https://github.com/slightlyoff/lite-vimeo
470
471// var wpDebugBar;!function(e){var i;wpDebugBar=i={body:void 0,init:function(){i.body=e(document.body),i.toggle.init(
471),i.tabs(),i.actions.init()},isVisible:function(){return i.body.hasClass("debug-bar-visible")},toggle:{init:function(){e("#wp-admin-bar-debug-bar").click(function(t){if(t.preventDefault(),t.target.hash){var s=e(t.target.rel);if(i.isVisible()){if(s.hasClass("current"))return s.removeClass("current"),void i.toggle.visibility()}else i.toggle.visibility();e(".debug-menu-target").hide().trigger("debug-bar-hide"),e(".debug-menu-link").removeClass("current"),s.addClass("current"),e(t.target.hash).show().trigger("debug-bar-show")}else i.toggle.visibility()})},visibility:function(t){t=void 0===t?!i.isVisible():t,i.body.toggleClass("debug-bar-visible",t),e(this).toggleClass("active",t)}},tabs:function(){var i=e(".debug-menu-link"),t=e(".debug-menu-target");i.click(function(s){var r=e(this);s.preventDefault(),r.hasClass("current")||(t.hide().trigger("debug-bar-hide"),i.removeClass("current"),r.addClass("current"),e("#"+this.href.substr(this.href.indexOf("#")+1)).show().trigger("debug-bar-show"))})},actions:{init:function(){var t=e("#debug-bar-actions");e(document).keydown(function(e){var t=e.key||e.which||e.keyCode;27===t&&i.isVisible()&&(e.preventDefault(),i.actions.close())}),e(".maximize",t).click(i.actions.maximize),e(".restore",t).click(i.actions.restore),e(".close",t).click(i.actions.close)},maximize:function(){i.body.removeClass("debug-bar-partial"),i.body.addClass("debug-bar-maximized")},restore:function(){i.body.removeClass("debug-bar-maximized"),i.body.addClass("debug-bar-partial")},close:function(){i.toggle.visibility(!1)}}},e(wpDebugBar.init)}(jQuery);

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.