PageSourceSearch

https://brightwm.com/wp-content/plugins/media-api-widget/assets/media-api-widget.js?ver=5.5.0

js brightwm.com collected 2026-10-01 15:39:04 UTC 90,288 bytes, 1,577 lines download raw bytes

1// Play button Icon
2
3            function media_item_play_button_icon(color = "#fff") {
4                return `
5                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" data-name="Layer 1" viewBox="0 0 145.2 145.2"><defs>
6                        <style>
7                            .cls-1 { fill: none; }      
8                            .cls-2 { clip-path: url(#clip-path); }      
9                            .cls-3 { opacity: 1; }      
10                            .cls-4 { clip-path: url(#clip-path-3); }         
11                        </style>
12                        <clipPath id="clip-path" transform="translate(-264.41 -245.59)">
13                            <rect class="cls-1" x="264.41" y="245.59" width="145.2" height="145.2"></rect>
14                        </clipPath>
15                        <clipPath id="clip-path-3" transform="translate(-264.41 -245.59)">
16                            <rect class="cls-1" x="255.41" y="238.59" width="163.2" height="153.2"></rect>
17                        </clipPath></defs>
18                        <g class="cls-2">
19                        <g class="cls-2">
20                        <g class="cls-3">
21                        <g class="cls-4">
22                        <path style="fill: ${color}" class="cls-5" d="M378.93,318.19,311,357.4V279Zm30.68,0a72.6,72.6,0,1,0-72.6,72.6,72.6,72.6,0,0,0,72.6-72.6" transform="translate(-264.41 -245.59)"></path>
23                        </g></g></g></g>
24                    </svg>`;
25            }
26
27            // Audio Playbar
28
29            function audio_play_bar(color = "#fff") {
30                return `
31                    <svg xmlns="http://www.w3.org/2000/svg" id="audio_play_bar" class="audio-play-bar" data-name="Layer 1" viewBox="0 0 453 45">
32                        <defs>
33                            <style>        
34                                .pb-2 {        
35                                    fill: #231f20;      
36                                }      
37                                .pb-3 {        
38                                    fill: none;        
39                                    stroke: #231f20;        
40                                    stroke-width: 3px;      
41                                }    
42                            </style>
43                        </defs>
44                        <rect style="fill: ${color}" width="453" height="45"></rect>
45                        <polygon class="pb-2" points="42.99 22 17.01 7 17.01 37 42.99 22"></polygon>
46                        <line class="pb-3" x1="52" y1="9" x2="52" y2="36"></line><line class="pb-3" x1="58" y1="9" x2="58" y2="36"></line><line class="pb-3" x1="64" y1="9" x2="64" y2="36"></line><line class="pb-3" x1="70" y1="9" x2="70" y2="36"></line><line class="pb-3" x1="76" y1="9" x2="76" y2="36"></line><line class="pb-3" x1="82" y1="9" x2="82" y2="36"></line><line class="pb-3" x1="88" y1="9" x2="88" y2="36"></line><line class="pb-3" x1="94" y1="9" x2="94" y2="36"></line><line class="pb-3" x1="100" y1="9" x2="100" y2="36"></line><line class="pb-3" x1="106" y1="9" x2="106" y2="36"></line><line class="pb-3" x1="112" y1="9" x2="112" y2="36"></line><line class="pb-3" x1="118" y1="9" x2="118" y2="36"></line><line class="pb-3" x1="124" y1="9" x2="124" y2="36"></line><line class="pb-3" x1="130" y1="9" x2="130" y2="36"></line><line class="pb-3" x1="136" y1="9" x2="136" y2="36"></line><line class="pb-3" x1="142" y1="9" x2="142" y2="36"></line><line class="pb-3" x1="148" y1="9" x2="148" y2="36"></line><line class="pb-3" x1="154" y1="9" x2="154" y2="36"></line><line class="pb-3" x1="160" y1="9" x2="160" y2="36"></line><line class="pb-3" x1="166" y1="9" x2="166" y2="36"></line><line class="pb-3" x1="172" y1="9" x2="172" y2="36"></line><line class="pb-3" x1="178" y1="9" x2="178" y2="36"></line><line class="pb-3" x1="184" y1="9" x2="184" y2="36"></line><line class="pb-3" x1="190" y1="9" x2="190" y2="36"></line><line class="pb-3" x1="196" y1="9" x2="196" y2="36"></line><line class="pb-3" x1="202" y1="9" x2="202" y2="36"></line><line class="pb-3" x1="208" y1="9" x2="208" y2="36"></line><line class="pb-3" x1="214" y1="9" x2="214" y2="36"></line><line class="pb-3" x1="220" y1="9" x2="220" y2="36"></line><line class="pb-3" x1="226" y1="9" x2="226" y2="36"></line><line class="pb-3" x1="232" y1="9" x2="232" y2="36"></line><line class="pb-3" x1="238" y1="9" x2="238" y2="36"></line><line class="pb-3" x1="244" y1="9" x2="244" y2="36"></line><line class="pb-3" x1="250" y1="9" x2="250" y2="36"></line><line class="pb-3" x1="256" y1="9" x2="256" y2="36"></line><line class="pb-3" x1="262" y1="9" x2="262" y2="36"></line><line class="pb-3" x1="268" y1="9" x2="268" y2="36"></line><line class="pb-3" x1="274" y1="9" x2="274" y2="36"></line><line class="pb-3" x1="280" y1="9" x2="280" y2="36"></line><line class="pb-3" x1="286" y1="9" x2="286" y2="36"></line><line class="pb-3" x1="292" y1="9" x2="292" y2="36"></line><line class="pb-3" x1="298" y1="9" x2="298" y2="36"></line><line class="pb-3" x1="304" y1="9" x2="304" y2="36"></line><line class="pb-3" x1="310" y1="9" x2="310" y2="36"></line><line class="pb-3" x1="316" y1="9" x2="316" y2="36"></line><line class="pb-3" x1="322" y1="9" x2="322" y2="36"></line><line class="pb-3" x1="328" y1="9" x2="328" y2="36"></line><line class="pb-3" x1="334" y1="9" x2="334" y2="36"></line><line class="pb-3" x1="340" y1="9" x2="340" y2="36"></line><line class="pb-3" x1="346" y1="9" x2="346" y2="36"></line><line class="pb-3" x1="352" y1="9" x2="352" y2="36"></line><line class="pb-3" x1="358" y1="9" x2="358" y2="36"></line><line class="pb-3" x1="364" y1="9" x2="364" y2="36"></line><line class="pb-3" x1="370" y1="9" x2="370" y2="36"></line><line class="pb-3" x1="376" y1="9" x2="376" y2="36"></line><line class="pb-3" x1="382" y1="9" x2="382" y2="36"></line><line class="pb-3" x1="388" y1="9" x2="388" y2="36"></line><line class="pb-3" x1="394" y1="9" x2="394" y2="36"></line><line class="pb-3" x1="400" y1="9" x2="400" y2="36"></line><line class="pb-3" x1="406" y1="9" x2="406" y2="36"></line><line class="pb-3" x1="412" y1="9" x2="412" y2="36"></line><line class="pb-3" x1="418" y1="9" x2="418" y2="36"></line><line class="pb-3" x1="424" y1="9" x2="424" y2="36"></line><line class="pb-3" x1="430" y1="9" x2="430" y2="36"></line><line class="pb-3" x1="436" y1="9" x2="436" y2="36"></line>
47                        </svg>
48	                `
49	            }
50
51	            function escape_html(value) {
52	                return String(value ?? "")
53	                    .replace(/&/g, "&amp;")
54	                    .replace(/</g, "&lt;")
55	                    .replace(/>/g, "&gt;")
56	                    .replace(/"/g, "&quot;")
57	                    .replace(/'/g, "&#39;");
58	            }
59
60	            function escape_attr(value) {
61	                return escape_html(value);
62	            }
63
64	            function format_overlay_title(item) {
65	                if (item.episode !== -1) {
66	                    return `Episode ${item.episode}`;
67	                }
68
69	                const words = String(item.title ?? "").split(" ");
70	                if (words.length > 3) {
71	                    const first = words.filter((l, i) => i < 3).join(" ");
72	                    const rest = words.filter((l, i) => i >= 3).join(" ");
73	                    return `${escape_html(first)}<br><span class="sub-text">${escape_html(rest)}</span>`;
74	                }
75
76	                return escape_html(item.title ?? "");
77	            }
78
79	            function media_title_text(item) {
80	                return item.episode !== -1 ? `Episode ${item.episode}` : String(item.title ?? "");
81	            }
82
83	            function sanitize_multiple_grid_text(value) {
84	                const normalized = String(value ?? "").trim().toLowerCase();
85	                return ["title", "description", "both", "numberedtitle", "numberedtitleanddescription"].includes(normalized) ? normalized : "";
86	            }
87
88	            // Build the episode/season-aware title text for a numbered grid item.
89	            // When the item has no episode number (episode === -1, i.e. the
90	            // playlist's sort mode is "Normal" or no number was detected), the
91	            // plain video title is used. When an episode number is present and a
92	            // valid season was parsed via the season/episode regex, returns
93	            // "Season {s}, Episode {e}"; otherwise returns "Episode {e}".
94	            function numbered_title_text(item) {
95	                const episode = Number(item?.episode);
96	                if (!Number.isFinite(episode) || episode === -1) {
97	                    return String(item?.title ?? "").trim();
98	                }
99	                const season = Number(item?.season);
100	                if (Number.isFinite(season) && season !== -1) {
101	                    return `Season ${season} Episode ${episode}`;
102	                }
103	                return `Episode ${episode}`;
104	            }
105
106	            function render_multiple_grid_text(item, mode) {
107	                if (mode === "both") {
108	                    const title = String(item?.title ?? "").trim();
109	                    const description = String(item?.description ?? "").trim();
110	                    if (!title && !description) return "";
111	                    let html = "";
112	                    if (title) html += `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-title">${escape_html(title)}</div>`;
113	                    if (description) html += `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-description">${escape_html(description)}</div>`;
114	                    return html;
115	                }
116
117	                if (mode === "numberedtitle" || mode === "numberedtitleanddescription") {
118	                    const title = numbered_title_text(item);
119	                    const description = String(item?.description ?? "").trim();
120	                    let html = "";
121	                    if (title) html += `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-title">${escape_html(title)}</div>`;
122	                    if (mode === "numberedtitleanddescription" && description) html += `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-description">${escape_html(description)}</div>`;
123	                    return html;
124	                }
125
126	                if (mode !== "title" && mode !== "description") {
127	                    return "";
128	                }
129
130	                const text = String(item?.[mode] ?? "").trim();
131	                if (!text) {
132	                    return "";
133	                }
134
135	                return `<div class="media-item-multiple-grid-text media-item-multiple-grid-text-${mode}">${escape_html(text)}</div>`;
136	            }
137
138	            // Render Media Item
139
140	            function render_media_item(item, settings, playlistItem, info) {
141
142                // Set Styling For Lightbox Playlist Items
143                
144                if (playlistItem) {
145                    settings = {
146                        showPlayButton: true,
147                        playButtonIconImgUrl: null,
148                        playButtonStyling: "width: 50%; height: 50%; opacity: 0.3;",
149                        showTextOverlay: false,
150                        instructionMessage: null,
151                        fontFamily: null,
152                        lightboxshowplaylist: false,
153                        showLightbox: true
154                    }
155                }
156
157                // Checks If Podcast And Is The Custom Player
158
159                const customPodcastPlayer = settings.podcastplayermode ? true : false;
160
161                let podcastPlayerData = customPodcastPlayer ? `data-podcastplayermode="${settings.podcastplayermode}" data-podcastplayerbuttoncolor="${settings.podcastplayerbuttoncolor}"
162                    data-podcastplayercolor="${settings.podcastplayercolor}" data-podcastprogressplayerbarcolor="${settings.podcastprogressplayerbarcolor}" data-podcastplayerhighlightcolor="${settings.podcastplayerhighlightcolor}"
163                    data-podcastplayerfont="${settings.podcastplayerfont}" data-podcastplayerscrollcolor="${settings.podcastplayerscrollcolor}"
164                    data-podcastplayertextcolor="${settings.podcastplayertextcolor}" data-showepisodedateaftertitle="${settings.showepisodedateaftertitle}"
165                    `: false;
166
167	                const { name, type } = info;
168	                const mediaType = type === "youtube" || type === "vimeo" ? "video" : "audio";
169	                const { title, thumbnail, publishedDate, id, description } = item;
170	                const { showPlayButton, playButtonIconImgUrl, playButtonStyling, showTextOverlay, instructionMessage, fontFamily, lightboxshowplaylist, showPlaybar, playbarColor, showLightbox } = settings;
171	                const safeTitleAttr = escape_attr(title ?? "");
172	                const safeOverlayTitle = format_overlay_title(item);
173	                const safePlaylistTitle = escape_html(media_title_text(item));
174	                const safeInstructionMessage = escape_html(instructionMessage ?? "");
175	                const multipleGridTextHtml = !playlistItem ? render_multiple_grid_text(item, settings.multipleGridText) : "";
176	                const mediaItemHtml = `
177	                    <a ${settings.fontFamily ? `style=${fontFamily}` : ""} class="media_item${showTextOverlay ? `-text-overlay-enabled` : ``}" data-itemclickablemediatype="${mediaType}" data-itemclickable="true" data-itemclickableplaylist="${name}_${type}" 
178	                        data-id="${id}" ${lightboxshowplaylist ? `data-lightboxshowplaylist="true"` : ""} ${item.trackSelect !== null ? `data-trackselect=${Number(item.trackSelect)}` : ""}
179	                        ${customPodcastPlayer  ? podcastPlayerData : ""}
180	                        ${showLightbox === false ? `data-showlightbox="false"` : ""}
181	                    >
182	                        <div class="media-item-thumbnail-text-wrapper">
183	                            <img class="media-item-thumbnail" src="${thumbnail.url}" width="${thumbnail.width ? thumbnail.width : 1280}" height="${thumbnail.height ? thumbnail.height : 720}" alt="${safeTitleAttr}">
184	                            ${showPlayButton ? 
185	                                `
186	                                    <div class="media-item-play-button" style="${playButtonStyling}">
187                                        ${showPlayButton ? 
188                                            `${playButtonIconImgUrl ? `<img src="${playButtonIconImgUrl}">` :
189                                                media_item_play_button_icon()
190                                            }` : ``
191                                        }
192                                    </div>
193                                ` 
194                            : ``}
195	                            ${showTextOverlay ? 
196	                                `
197	                                    <div class="media-item-text-overlay" ${mediaType === "audio" ? `style="padding-bottom: max(10vw, 48px);"` : "" }>
198	                                        <h3>${safeOverlayTitle}</h3>
199	                                        <p>${safeInstructionMessage}</p>
200	                                    </div>
201	                                ` 
202	                            : ``}
203	                        </div>
204	                        ${ playlistItem ? `<h3 class="playlist-episode-text">${safePlaylistTitle}</h3>` : ""} 
205	                        ${mediaType === "audio" && showPlaybar ? audio_play_bar(playbarColor) : ""}
206	                    </a>
207	                `;
208
209	                if (multipleGridTextHtml) {
210	                    return `
211	                        <!-- media item -->
212	                        <div class="media-item-multiple-grid-entry">
213	                            ${mediaItemHtml}
214	                            ${multipleGridTextHtml}
215	                        </div>
216	                    `;
217	                }
218
219	                return `
220	                    <!-- media item -->
221	                    ${mediaItemHtml}
222	                `;
223            }
224
225            // Initialize Loading Of Media Items And Lightbox
226
227            function initialize_media(media_items, media_data, media_name, media_type, podcast_platform) {
228
229                // Variables For Lightbox
230
231                let showThemeColor;
232                let showLogoImgUrl;
233                let lightboxFont;
234
235                if (media_items.length > 0) {
236                    media_items.forEach(item => {
237                        
238                        if (!media_data || media_data.length === 0) {
239                            if (media_type === "youtube" || media_type === "vimeo") {
240                                item.innerHTML = "<h2>Error loading video.</h2>";
241                            }
242                            if (media_type === "podcast") {
243                                item.innerHTML = "<h2>Error loading podcast.</h2>";
244                            }
245                            return;
246                        }
247
248                        const itemData = item.dataset;
249
250                        // Check That Item Corresponds To Correct Playlist.  If Not, Return To Exit
251
252                        if (itemData.playlistname !== media_name && itemData.mediaplatform !== media_type) {
253                            return
254                        } 
255
256                        // CSS Styling
257
258                        const showPlayButton = itemData.showplaybutton === "true" ? true : itemData.showplaybutton === "false" ? false : true;
259
260                        const playButtonIconImgUrl = itemData.playButtonIconImgUrl ? itemData.playButtonIconImgUrl : null;
261
262                        const playButtonStyling = itemData.playbuttonstyling ? itemData.playbuttonstyling :  "width: 35%; height: 35%; opacity: 0.3;"
263
264                        const showTextOverlay = itemData.showtextoverlay === "true" ? true : itemData.showtextoverlay === "false" ? false : true;
265                        
266                        const fontFamily = itemData.fontfamily ? `"font-family: ${itemData.fontfamily}"` : `"font-family: Roboto;"`;
267
268                        showThemeColor = itemData.lightboxthemecolor ? itemData.lightboxthemecolor : showThemeColor;
269
270                        showLogoImgUrl = itemData.lightboxshowlogoimgurl ? itemData.lightboxshowlogoimgurl : showLogoImgUrl;
271
272                        lightboxFont = itemData.lightboxfont ? itemData.lightboxfont : lightboxFont;
273                        
274                        const showPlaybar = itemData.showplaybar === "true" ? true : false;
275                        
276                        const playbarColor = itemData.playbarcolor ? itemData.playbarcolor : "#fff";
277                        
278                        // Text Overlay Messages
279
280                        const instructionMessage = itemData.instructionMessage ? itemData.instructionMessage : media_type === "youtube" ? "Click Here To Watch" : media_type === "podcast" ? "Click Here To Listen" : "";
281
282                        const lightboxshowplaylist = itemData.lightboxshowplaylist && itemData.lightboxshowplaylist === "true" ? true : false;
283                        const showLightbox = itemData.showlightbox !== "fal
283se";
284
285                        // Thumbnail
286
287                        const thumbnailimg = itemData.thumbnail ? itemData.thumbnail : null;
288
289                        // Data Parsing
290
291                        let renderData;
292
293                        if (podcast_platform !== "embed") {
294                            if (media_type === "podcast") {
295                                if (!media_data.channel.item.length) {
296                                    media_data.channel.item = [media_data.channel.item];
297                                }
298                            }
299                            renderData = media_type === "youtube" ? media_data 
300                            : media_type === "podcast" ? media_data.channel.item.map(item => ({...item, thumbnail: { url: thumbnailimg }, publishedDate: item.pubDate, id: item.guid, episode: -1 })) 
301                            : [];  
302                        } else {
303                            renderData = [{ thumbnail: { url: thumbnailimg} , publishedDate: "unknown", id: "unknown", episode: -1, title: `Embedded Podcast` }];
304                        }
305
306                        if (podcast_platform === "soundcloud") {
307                            renderData = renderData.map(i => ({...i, trackSelect: Number(itemData.orderdescending) - 1}));
308                        }
309
310                        if (podcast_platform === "custom") {
311                            renderData = renderData.map(i => ({...i, trackSelect: Number(itemData.orderdescending)}));
312                        }
313
314                        const settings = {
315                            showPlayButton,
316                            playButtonIconImgUrl,
317                            playButtonStyling,
318                            showTextOverlay,
319                            instructionMessage,
320                            fontFamily,
321                            lightboxshowplaylist,
322                            showPlaybar,
323                            playbarColor,
324                            showLightbox
325                        };
326
327                        const info = {
328                            name: media_name,
329                            type: media_type
330                        };
331
332                        // Select Item
333
334                        const nameSelect = itemData.nameselect;
335                        const episodeNumber = itemData.episodenumber;
336                        let index;
337
338                        if (episodeNumber && media_type === "youtube") {
339                            index = renderData.findIndex(item => item.episode === Number(episodeNumber));
340                        }
341                        if (nameSelect) {
342                            index = renderData.findIndex(item => item.title.toLowerCase().includes(nameSelect.toLowerCase()));
343                        }
344                        if (itemData.orderdescending) {
345                            index = Number(itemData.orderdescending) - 1;
346                        }
347
348                        // If Podcast Is embed or Soundcloud, Index Will Automatically Be Set To 0
349
350                        if (podcast_platform === "embed") {
351                            index = 0;
352                        }
353
354                        // If Podcast Is Custom, Add Fields In Settings
355
356                        if (podcast_platform === "custom") {
357                            settings.podcastplayermode = itemData.podcastplayermode ? itemData.podcastplayermode.slice(1) : "dark";
358                            settings.podcastplayerbuttoncolor = itemData.podcastplayerbuttoncolor ? itemData.podcastplayerbuttoncolor.slice(1) : "";
359                            settings.podcastplayercolor = itemData.podcastplayercolor ? itemData.podcastplayercolor.slice(1) : "";
360                            settings.podcastprogressplayerbarcolor = itemData.podcastprogressplayerbarcolor ? itemData.podcastprogressplayerbarcolor.slice(1) : "";
361                            settings.podcastplayerhighlightcolor = itemData.podcastplayerhighlightcolor ? itemData.podcastplayerhighlightcolor.slice(1) : "";
362                            settings.podcastplayerfont = itemData.podcastplayerfont ? itemData.podcastplayerfont : "";
363                            settings.podcastplayerscrollcolor = itemData.podcastplayerscrollcolor ? itemData.podcastplayerscrollcolor.slice(1) : "";
364                            settings.podcastplayertextcolor = itemData.podcastplayertextcolor ? itemData.podcastplayertextcolor.slice(1) : "";
365                            settings.showepisodedateaftertitle = itemData.showepisodedateaftertitle ? itemData.showepisodedateaftertitle : "";
366                        }
367
368                        let trackSelect = null;
369
370                        // If Track Is Soundcloud, trackselect will be set from itemData.orderdescending
371
372                        if (podcast_platform === "soundcloud") {
373                            trackSelect = Number(itemData.orderdescending) - 1;
374                        }
375
376                        if (podcast_platform === "custom") {
377                            trackSelect = Number(itemData.orderdescending)
378                        }
379
380                        //  Output Text For Media Description Or Text Only Text And Exit
381
382                        if (itemData.mediatitle === "true" || itemData.mediadescription === "true") {
383
384                            if (renderData[index]) {
385                                let descriptionOrTitle;
386
387                                if (itemData.mediadescription === "true") {
388                                    descriptionOrTitle = renderData[index].description;
389                                }
390
391                                if (itemData.mediatitle === "true" && !descriptionOrTitle) {
392                                    descriptionOrTitle = renderData[index].title;
393                                }
394
395	                                if (descriptionOrTitle && typeof descriptionOrTitle !== "object") {
396	                                    item.outerHTML = `<p style="color: ${itemData.mediadescriptiontextcolor}" class="media-description-text">${escape_html(descriptionOrTitle)}</p>`;
397	                                } else {
398	                                    console.warn(`No description or title for "${descriptionOrTitle}" under order selection of "${index + 1}".  Description or title text will auto populate once the video or podcast has it.`);
399	                                    const parentContainer = item.parentNode;
400                                    if (parentContainer.classList.toString().includes("elementor-widget-container")) {
401                                        parentContainer.outerHTML = "";
402                                    } else {
403                                        item.outerHTML = "";
404                                    }
405                                }
406                                return;
407                            }
408                        } else {
409
410                            // Checks If multiplegrid parameter was provided and filters accordingly prior to rendering
411
412                            if (itemData.multiplegrid === "true" && media_data.length > 1) {
413                                let renderGridData = [...renderData];
414                                const multipleGridText = media_type === "youtube"
415                                    ? sanitize_multiple_grid_text(itemData.multiplegridtext || itemData.mutiplegridtext)
416                                    : "";
417
418                                // If multiplegridshowall is not set to true, then playlist will be filtered accordingly.  
419
420                                if (!itemData.multiplegridshowall || itemData.multiplegridshowall !== "true") {
421                                    if (itemData.multiplegridsearch) {
422                                        renderGridData = renderGridData.filter(item => item.title.toLowerCase().includes(itemData.multiplegridsearch.toLowerCase()));   
423                                    }
424                                    if (Number(itemData.multiplegridlimititems)) {
425                                        renderGridData = renderGridData.filter((item, index) => index < Number(itemData.multiplegridlimititems));
426                                    }
427
428                                    // Will override search and limit items if parameter is present for episode range
429
430                                    if (itemData.multiplegridepisoderange && itemData.multiplegridepisoderange !== "" && itemData.multiplegridepisoderange.includes("-") && itemData.multiplegridepisoderange !== "-" && /[0-9]/.test(itemData.multiplegridepisoderange)) {
431                                        const splitter = itemData.multiplegridepisoderange.split("-");
432                                        renderGridData = [...media_data];
433                                        renderGridData = renderGridData.filter(item => item.episode >= Number(splitter[0]) && item.episode <= Number(splitter[1]));
434                                    }
435                                }
436                                const gapBetweenItems = itemData.multiplegridgap ? itemData.multiplegridgap : "48px";
437                                const minSize = itemData.multiplegridminsize ? itemData.multiplegridminsize : "400px";
438                                if (renderGridData.length > 0) {
439                                    item.outerHTML = 
440                                        `<div class="media_items_multiple_grid_layout" style="gap: ${gapBetweenItems}; grid-template-columns: repeat(auto-fill, minmax(min(100%, ${minSize}), 1fr));">
441                                            ${renderGridData.map(row => render_media_item(row, {...settings, multipleGridText}, false, info)).join("")}
442                                        </div>`
443                                } else item.outerHTML = `<h3 class="media-api-widget-err-msg">No ${media_type} items found in playlist based upon search parameters provided.</h3>`
444                                return
445                            }
446
447                            // Render Single Item
448
449                            if (renderData[index]) {
450                                item.outerHTML = render_media_item(renderData[index], settings, false, info);
451                                return;
452                            } else item.outerHTML = `<h3 class="media-api-widget-err-msg">No ${media_type} item found in playlist based upon search parameters provided.</h3>` 
453                        }
454                    });
455                }
456
457                // Generate Lightbox HTML Root Tag In Body
458
459                const media_lightbox_div = document.createElement("div");
460                media_lightbox_div.dataset.lightboxmediacontenttype = media_type === "youtube" || media_type === "vimeo" ? "video" : "audio";
461                media_lightbox_div.dataset.lightboxid = `${media_name}_${media_type}`;
462                document.body.appendChild(media_lightbox_div);
463                const lightbox = document.querySelector(`[data-lightboxid = "${media_name}"]`);
464
465                // Video Lightbox HTML
466
467                function media_video_lightbox_html() {
468                    return `
469                        <div class="lightbox-content-container" style="font-family: ${lightboxFont};">
470                            <fieldset style="border: 8px ${showThemeColor} solid;" class="lightbox-playlist-container" data-lightboxplaylistcontainer="true">
471                            <legend>
472                                ${showLogoImgUrl ? `<img class="playlist-logo" src="${showLogoImgUrl}">` : `<h1 class="playlist-heading">Videos</h1>`}
473                            </legend>
474                            <div class="grid-layout" data-lightboxplaylistcontent="true"></div>
475                            </fieldset>
476                            <div class="lightbox-player-container" data-lightboxplayer="true">
477                            <svg version="1.0" xmlns="http://www.w3.org/2000/svg" class="arrow-left" data-hideonidle="true" data-lightboxarrowdirection="left" data-lightboxarrow="true"
478                                width="100.000000pt" height="100.000000pt" viewBox="0 0 100.000000 100.000000"
479                                preserveAspectRatio="xMidYMid meet">
480                                <g data-hideonidle="true" transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)"
481                                fill="#ffffff" stroke="none">
482                                <path data-hideonidle="true" d="M415 720 l-220 -220 223 -222 222 -223 72 73 73 72 -148 148 -147 147 145 145 c80 80 145 149 145 155 0 0 -140 145 -140 145 0 0 -104 -99 -225 -220z"/>
483                                </g>
484                            </svg>
485                            <div class="lightbox-box-frame">
486                                <div class="lightbox-frames" data-lightboxframes="true">
487                                <div data-frameposition="-1" class="frame-transition-left video-thumbnail-wrapper" data-lightboxframe="true">
488                                    <iframe width="560" height="315" title="${media_type} video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
489                                    </iframe>
490                                    <div class="hover-text-container">
491                                    <h1></h1>
492                                    </div>
493                                </div>
494                                <div data-frameposition="0" class="video-thumbnail-wrapper" data-lightboxframe="true">
495                                    <iframe width="560" height="315" title="${media_type} video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
496                                    </iframe>
497                                    <div class="hover-text-container">
498                                    <h1></h1>
499                                    </div>
500                                </div>
501                                <div data-frameposition="1" class="frame-transition-right video-thumbnail-wrapper" data-lightboxframe="true">
502                                    <iframe width="560" height="315" title="${media_type} video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
503                                    </iframe>
504                                    <div class="hover-text-container">
505                                    <h1></h1>
506                                    </div>
507                                </div>
508                                </div>
509                                <div class="fast-forward-overlay element-invisible" data-fastforwardoverlay="true">
510                                <h1></h1>
511                                </div>
512                            </div>
513                            <svg version="1.0" xmlns="http://www.w3.org/2000/svg" class="arrow-right" data-hideonidle="true" data-lightboxarrowdirection="right" data-lightboxarrow="true"
514                                width="100.000000pt" height="100.000000pt" viewBox="0 0 100.000000 100.000000"
515                                preserveAspectRatio="xMidYMid meet">
516                                <g data-hideonidle="true" transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)"
517                                fill="#ffffff" stroke="none">
518                                <path data-hideonidle="true" d="M415 720 l-220 -220 223 -222 222 -223 72 73 73 72 -148 148 -147 147 145 145 c80 80 145 149 145 155 0 0 -140 145 -140 145 0 0 -104 -99 -225 -220z"/>
519                                </g>
520                            </svg>
521                            </div>
522                            <div class="lightbox-close-button" data-hideonidle="true" data-lightboxclosebutton="true">X</div>
523                            <svg class="lightbox-playlist-button" data-lightboxplaylistbutton="true" data-hideonidle="true" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 101.66" style="enable-background:new 0 0 122.88 101.66" xml:space="preserve">
524                            <g>
525                                <path xmlns="http://www.w3.org/2000/svg" class="st0" fill="#ffffff" d="M0,0h97.6v16.12H0V0L0,0z M122.88,77.46l-38-24.21v48.41L122.88,77.46L122.88,77.46z M0,61.46h73.62v16.12H0 V61.46L0,61.46z M0,30.77h97.6v16.12H0V30.77L0,30.77z"/>
526                            </g>
527                            </svg>
528                        </div>
529                    `;
530                }
531
532                // Initialize Show Playlist Variable
533
534                let showPlaylist = false;
535
536                // Activate Video Lightbox On Video Item Click
537
538                let media_lightbox_activated = false;
539
540                function video_lightbox_activate_handler(itemClicked, lightboxshowplaylist, lightboxStyling = null) {
541
542                    // Checks That Type Is Either Youtube Or Vimeo.  If Not, Return To Exit
543
544                    if (media_type !== "youtube" && media_type !== "vimeo") {
545                        return
546                    }
547
548                    const lightbox = media_lightbox_div;
549                    lightbox.innerHTML = "";
550                    lightbox.innerHTML = media_video_lightbox_html();
551                    let startingLightboxActive = lightbox.querySelector(`[data-frameposition="0"]`);
552                    let lightboxStartingLeft = lightbox.querySelector(`[data-frameposition="-1"]`);
553                    let lightboxStartingRight = lightbox.querySelector(`[data-frameposition="1"]`);
554                    const lightboxFramesContainer = lightbox.querySelector(`[data-lightboxframes="true"]`);
555                    const lightboxFrames = lightbox.querySelectorAll(`[data-lightboxframe="true"]`);
556                    const lightboxFastForwardOverlay = lightbox.querySelector(`[data-fastforwardoverlay="true"]`);
557                    const lightboxFastForwardText = lightboxFastForwardOverlay.querySelector(`h1`);
558                    const lightboxArrowLeft = lightbox.querySelector(`[data-lightboxarrowdirection="left"]`);
559                    const lightboxArrowRight = lightbox.querySelector(`[data-lightboxarrowdirection="right"]`);
560                    const lightboxCloseButton = lightbox.querySelector(`[data-lightboxclosebutton="true"]`);
561                    const lightboxPlayerContainer = lightbox.querySelector(`[data-lightboxplayer="true"]`);
562                    const lightboxPlaylistContainer = lightbox.querySelector(`[data-lightboxplaylistcontainer="true"]`);
563                    const lightboxPlaylistContent = lightbox.querySelector(`[data-lightboxplaylistcontent="true"]`);
564                    const lightboxPlaylistButton = lightbox.querySelector(`[data-lightboxplaylistbutton="true"]`);
565
566                    if (lightboxStyling) {
567                        if (lightboxStyling.image) {
568                            lightbox.querySelector(`.lightbox-content-container fieldset legend`).innerHTML  = `<img class="playlist-logo" src="${lightboxStyling.image}">`;
569                        }
570                        if (lightboxStyling.themeColor) {
571                            lightbox.querySelector(`.lightbox-content-container fieldset`).style.border = `min(8px, 1.5vw) ${lightboxStyling.themeColor} solid`;
572                        }
573                        if (lightboxStyling.font) {
574                            lightbox.querySelector(`.lightbox-content-container`).style.fontFamily = `"${lightboxStyling.font}"`;
575                        }
576                    }
577
578                    // Playlist Button
579
580                    const playlistButton = lightboxshowplaylist ? media_item_play_button_icon() : null;
581
582                    // `${media_type}` Playlist Selected
583
584                    let playListSorted = lightboxshowplaylist ? media_data : media_data.filter(item => item.id === itemClicked.dataset.id);
585
586                    // Removes Non Episode Numbered Items From Playlist If Sort Mode Is Set To "number_in_title"
587
588                    if (media_type === "youtube" && lightboxshowplaylist && !playListSorted.every(item => item.episode === -1)) {
589                        playListSorted = playListSorted.filter(item => item.episode && item.episode !== -1);
590                    }
591                    
592                    // Elements To Hide When User Idle In Lightbox After 5 Seconds
593
594                    const elementsToHideWhenIdle = lightbox.querySelectorAll(`[data-hideonidle="true"]`);
595
596                    let mouseOverElement = false;
597                    let lastMouseOverTime = new Date().getTime();
598                    const idleDelayTime = 5000;
599
600                    // Checks If Lightbox Arrow Was Clicked
601
602                    let lightboxArrowClicked = false;
603
604                    // Lightbox Base Url Origin
605
606                    const lightboxFrameVideoBaseUrl = `https://www.youtube.com/embed/`;
607
608                    // Sets Media Query Break Point Of When Video Containers Should Stack In One Column.  Calculation Based Upon minimumWidthOfEachVideo Number.  Default Value Is: minimumWidthOfEachGridVideoItem * 2
609
610                    const mediaQueryMobileBreakpoint = (400 * 2) + (48 * 1.5);
611
612                    // Select HTML tag to Hide Scroll Bar
613
614                    const html = document.querySelector(`html`);
615
616                    // Set Thumbnail Episode Number Or Title Text
617
618	                    function setThumbnailText(item) {
619	                        return media_title_text(item);
620	                    }
621
622                    // Monitors Mouse Movements And Clicks In Lightbox
623
624                    function lightboxMouseMoveHandler(e, elementsToHideWhenIdle) {
625                        mouseOverElement = false;
626                        lastMouseOverTime = new Date().getTime();
627                        if (e && e.path) {
628                        e.path.forEach(p => {
629                            if (p.dataset && p.dataset.hideonidle) {
630                            mouseOverElement = true;
631                            }
632                        })
633                        }
634                        if (elementsToHideWhenIdle && elementsToHideWhenIdle[0] !== null && !mouseOverElement) {
635                        elementsToHideWhenIdle.forEach(element => { 
636                            if (!element.dataset.lightboxplaylistbutton) {
637                            element.classList.remove(`element-invisible`);
638                            }
639                            if (element.dataset.lightboxplaylistbutton && showPlaylist && !element.classList.toString().includes(`element-invisible`)) {
640                            element.classList.add(`element-invisible`);
641                            }
642                            if (!showPlaylist && element.dataset.lightboxplaylistbutton && element.classList.toString().includes(`element-invisible`)) {
643                            element.classList.remove(`element-invisible`);
644                            }
645                        });
646                        setTimeout(() => {
647                            if (!mouseOverElement && (new Date().getTime() - lastMouseOverTime) >= idleDelayTime && !showPlaylist) {
648                                elementsToHideWhenIdle.forEach(element => element.classList.add(`element-invisible`));
649                            }
650                        }, idleDelayTime)
651                        }
652                        if (!playlistButton) {
653                            lightboxPlaylistButton.classList.add(`element-invisible`);
654                        }
655                    }
656
657                    // Sets Data
658
659                    function setData(index) {
660                        return playListSorted[currentVideoIndex + index]
661                    }
662
663                    // Sets Video Id
664
665                    function setVideoId(index) {
666                        return playListSorted[currentVideoIndex + index].id
667                    }
668
669                    // Calls Upon lightboxMouseMoveHandler Function
670
671                    lightboxMouseMoveHandler();
672
673                    lightbox.addEventListener(`mousemove`, e => lightboxMouseMoveHandler(e, elementsToHideWhenIdle));
674                    lightbox.addEventListener(`click`, e => lightboxMouseMoveHandler(e, elementsToHideWhenIdle));
675
676                    // Video Index And Base Url For `${media_type}` Embed Iframe.  Will Be Set To 0 If Returned Value Is -1
677
678                    let currentVideoIndex;
679
680                    if (itemClicked) {
681                        currentVideoIndex = playListSorted.findIndex(video => video.id === itemClicked.dataset.id);
682                    } 
683
684                    if (!currentVideoIndex || currentVideoIndex === -1) {
685                        currentVideoIndex = 0;
686                    }
687
688                    // Check For Starting Position To See If Arrows Should Be Shown Based Upon Starting Video Index Position And Set Corresponding Iframe Urls
689
690                    function initLightboxFrames() {
691                        startingLightboxActive = lightbox.querySelector(`[data-frameposition="0"]`);
692                        lightboxStartingLeft = lightbox.querySelector(`[data-frameposition="-1"]`);
693                        lightboxStartingRight = lightbox.querySelector(`[data-frameposition="1"]`);
694                        
695                        lightboxFrames.forEach(frame => {
696                            frame.classList.remove(`frame-transitioning-left`);
697                            frame.classList.remove(`frame-transitioning-right`);
698                        });
699
700                        if (!playListSorted[currentVideoIndex + 1]) {
701                            lightboxArrowRight.classList.add(`disable-arrow`);
702                        } else { 
703                            lightboxArrowRight.classList.remove(`disable-arrow`);
704                            lightboxStartingRight.classList.add(`frame-transitioning-right`);
705                            // Preload adjacent carousel videos without playing them.
706                            lightboxStartingRight.querySelector(`iframe`).src = lightboxFrameVideoBaseUrl + setVideoId(1);
707	                            lightboxStartingRight.querySelector(`h1`).textContent = setThumbnailText(setData(1));
708                        }
709                        if (!playListSorted[currentVideoIndex - 1]) {
710                            lightboxArrowLeft.classList.add(`disable-arrow`);
711                        } else { 
712                            lightboxArrowLeft.classList.remove(`disable-arrow`);
713                            lightboxStartingRight.classList.add(`frame-transitioning-left`);
714                            lightboxStartingLeft.querySelector(`iframe`).src = lightboxFrameVideoBaseUrl + setVideoId(-1);
715	                            lightboxStartingLeft.querySelector(`h1`).textContent = setThumbnailText(setData(-1));
716                        }
717
718                        // Check If Video Index Has Video And Check For Browser Window Width And Set For Lightbox Or Full Screen Iframe On Mobile, Tablet
719
720                        if (playListSorted[currentVideoIndex]) {
721                        if (window.innerWidth > mediaQueryMobileBreakpoint || showPlaylist) {
722                            startingLightboxActive.querySelector(`iframe`).src = lightboxFrameVideoBaseUrl + setVideoId(0) + `?autoplay=1`;
723	                            startingLightboxActive.querySelector(`h1`).textContent = setThumbnailText(setData(0));
724                        } else window.open(lightboxFrameVideoBaseUrl + setVideoId(0) + `?autoplay=1`);
725                        }
726                        if (window.innerWidth > mediaQueryMobileBreakpoint || showPlaylist) {
727                            lightbox.classList.add(`show-lightbox`);
728                            html.classList.add(`hide-scroll`);
729                        } else {
730                            lightbox_clear();
731                        }      
732                    }
733
734                    initLightboxFrames();
735
736                    // Render Grid Items In Playlist Container
737
738                    // Grid Layout Handling
739
740                    function gridItemsProcessing(outputList, playlist) {
741
742                        const sortItemsBy = "number-descending";
743
744                        // Removes Non Episode Numbered Items From Playlist
745
746                        if (media_type === "youtube" && !outputList.every(item => item.episode === - 1)) {
747                            outputList = outputList.filter(item => item.episode && item.episode !== -1);
748                        }
749
750                        const info = {
751                            name: media_name,
752                            type: media_type
753                        };
754
755                        return outputList.map(item => {
756                            if (!item.id) {
757                                console.error(`Playlist Item Failed To Load Due To Insufficient Data`);
758                                failedItemTally++
759                            } else if (!item.title) {
760                                console.error(`Video Playlist Item ${item.id} Did Not Have A Title And Could Not Be Loaded.`)
761                            } else if (item.title.toLowerCase() === `deleted video` || item.title.toLowerCase() === `private video`) {
762                                console.error(`Video Playlist Item ${item.id} Could Not Be Loaded As Its Status Is: ${item.title}`);
763                            }
763 else if (sortItemsBy === `number-ascending` || sortItemsBy === `number-descending` && item.episode.toString() === `NaN`) {
764                                console.error(`Video Playlist Item ${item.id} Entitled "${item.title}" Could Not Be Loaded As Number(s) Were Detected In Its Title Name But Could Not Generate An Episode Number.  This Can Occur If The Video Title Has Two Or More Numbers In It.  It Must Have Only One Number In Its Title Name That Pertains To Its Episode Number When Sorting Items In The "${sortItemsBy}" Mode.  If This Is The Case, Please Change The Title Name Accordingly.`)
765                            } else return render_media_item(item, null, playlist, info);
766                        }).join(``)
767                    }
768
769                    lightboxPlaylistContent.innerHTML = gridItemsProcessing(playListSorted, true);
770
771                    // Event Listeners On Playlist Items Clicked
772
773                    lightboxPlaylistContent.addEventListener(`click`, e => {
774                        const clickedItem = e.target.closest(`[data-itemclickableplaylist="${media_name}_${media_type}"]`);
775                        if (clickedItem) {
776                            currentVideoIndex = playListSorted.findIndex(video => video.id === clickedItem.dataset.id);
777                            toggleLightboxPlayerOrPlaylist();
778                            initLightboxFrames();
779                        }
780                    });
781
782                    // Records When Last Time Carousel Was Advanced To Avoid User Click/Auto Interval Conflict
783
784                    let lastTimeAdvanced = new Date().getTime();
785
786                    // Event Listener On Exit Button Click To Exit LightBox
787
788                    lightboxCloseButton.addEventListener(`click`, lightbox_clear);
789
790                    // Set lightboxArrowClicked To True For 500 Milliseconds Then Changed Back To False
791
792                    function setLightboxArrowToggled(fastForwarded) {
793                        lightboxArrowClicked = true;
794                        setTimeout(() => {
795                            lightboxArrowClicked = false;
796                        }, 250)
797                    }
798
799                    // Mouse Hold Speed Through Carousel Frames Event Handling.  Variables For Checking For Mouse Down And Running Repeat Interval
800
801                    let mouseDownInterval;
802                    let mouseDown = false;
803
804                    // Lightbox Fast Forward Speed
805
806                    const video_lightbox_fast_forward_speed = 150;
807
808                    // Fast Forward Handling
809
810                    function toggleFastForward(enabled, item) {
811
812                        if (enabled) {
813                            lightboxFastForwardOverlay.classList.remove(`element-invisible`);
814                            lightboxFramesContainer.classList.add(`fast-forward-transitioning`);
815	                            lightboxFastForwardText.textContent = setThumbnailText(item);
816	                        } else {
817	                            lightboxFastForwardText.textContent = ``;
818	                            lightboxFastForwardOverlay.classList.add(`element-invisible`);
819	                            lightboxFramesContainer.classList.remove(`fast-forward-transitioning`);
820	                        }
821                    }
822
823                    // Left Arrow Click/Mousedown And Auto Transitioning
824
825                    function advanceLightboxLeft(fastForward) {
826                        // Unload the outgoing player before the preloaded left
827                        // frame becomes the sole autoplaying frame.
828                        const activeIframe = lightbox.querySelector(`[data-frameposition="0"] iframe`);
829                        if (activeIframe) {
830                            activeIframe.src = ``;
831                        }
832                        currentVideoIndex -= 1;
833
834                        if (playListSorted[currentVideoIndex - 1]) {
835                        lightboxArrowLeft.classList.remove(`disable-arrow`);
836                        } else { 
837                        clearFastForwarding();
838                        lightboxArrowLeft.classList.add(`disable-arrow`);
839                        }
840
841                        if (playListSorted[currentVideoIndex + 1]) {
842                        lightboxArrowRight.classList.remove(`disable-arrow`)
843                        } else lightboxArrowRight.classList.add(`disable-arrow`)
844
845                        if (fastForward && mouseDown && playListSorted[currentVideoIndex]) {
846                        lightboxArrowFastForwarded = true;
847                        toggleFastForward(true, playListSorted[currentVideoIndex]);
848                        } else {
849                        toggleFastForward();
850                        }
851                        
852                        setLightboxArrowToggled();
853
854                        lightboxFrames.forEach(frame => frameTransitionHandler(frame, `left`, `lightbox`, currentVideoIndex));
855                    }
856
857                    // Right Arrow Click/Mousedown And Auto Transitioning
858
859                    function advanceLightboxRight(fastForward) {
860                        // Hidden iframes keep playing audio, so stop the outgoing
861                        // frame before promoting the next one.
862                        const activeIframe = lightbox.querySelector(`[data-frameposition="0"] iframe`);
863                        if (activeIframe) {
864                            activeIframe.src = ``;
865                        }
866                        currentVideoIndex += 1;
867
868                        if (playListSorted[currentVideoIndex + 1]) {
869                        lightboxArrowRight.classList.remove(`disable-arrow`);
870                        } else { 
871                        clearFastForwarding();
872                        lightboxArrowRight.classList.add(`disable-arrow`);
873                        }
874
875                        if (playListSorted[currentVideoIndex - 1]) {
876                        lightboxArrowLeft.classList.remove(`disable-arrow`);
877                        } else lightboxArrowLeft.classList.add(`disable-arrow`);
878
879                        if (fastForward && mouseDown && playListSorted[currentVideoIndex]) {
880                        lightboxArrowFastForwarded = true;
881                        toggleFastForward(true, playListSorted[currentVideoIndex]);
882                        } else { 
883                        toggleFastForward();
884                        }
885                        
886                        setLightboxArrowToggled();
887
888                        lightboxFrames.forEach(frame => frameTransitionHandler(frame, `right`, `lightbox`, currentVideoIndex));
889                    }
890
891                    // Left Arrow Click Event Handler
892
893                    lightboxArrowLeft.addEventListener(`click`, () => {
894                        if (!mouseDown) {
895                        lastTimeAdvanced = new Date().getTime();
896                        advanceLightboxLeft()
897                        } else {
898                        clearInterval(mouseDownInterval);
899                        mouseDown = false;
900                        }
901                    });
902
903                    // Right Arrow Click Event Handler
904
905                    lightboxArrowRight.addEventListener(`click`, () => {
906                        if (!mouseDown) {
907                        lastTimeAdvanced = new Date().getTime();
908                        advanceLightboxRight()
909                        } else {
910                        clearInterval(mouseDownInterval);
911                        mouseDown = false;
912                        }
913                    });
914
915                    // Left Arrow Mouse Down Event Handler
916
917                    lightboxArrowLeft.addEventListener(`mousedown`, () => {
918                        mouseDown = true;
919                        setTimeout(() => {
920                        if (mouseDown && playListSorted[currentVideoIndex - 1]) {
921                            mouseDownInterval = setInterval(() => {
922                            if (mouseDown) {
923                                advanceLightboxLeft(true), video_lightbox_fast_forward_speed;
924                            }
925                            }, video_lightbox_fast_forward_speed);
926                            lightboxArrowLeft.classList.add(`lightbox-arrow-hold-transitioning`);
927                        }
928                        }, 500)
929                    });
930
931                    // Right Arrow Mouse Down Event Handler
932
933                    lightboxArrowRight.addEventListener(`mousedown`, () => {
934                        mouseDown = true;
935                        setTimeout(() => {
936                        if (mouseDown && playListSorted[currentVideoIndex + 1]) {
937                            mouseDownInterval = setInterval(() => {
938                            if (mouseDown) {
939                                advanceLightboxRight(true), video_lightbox_fast_forward_speed;
940                            }
941                            }, video_lightbox_fast_forward_speed);
942                            lightboxArrowRight.classList.add(`lightbox-arrow-hold-transitioning`);
943                        }
944                        }, 500)
945                    });
946
947                    // Arrow Mouse Up Event Handling
948
949                    function clearFastForwarding() {
950                        mouseDown = false;
951                        clearInterval(mouseDownInterval);
952                        mouseDownInterval = ``;
953                        lightboxArrowLeft.classList.remove(`lightbox-arrow-hold-transitioning`);
954                        lightboxArrowRight.classList.remove(`lightbox-arrow-hold-transitioning`);
955                        toggleFastForward();
956                        setTimeout(() => { 
957                        mouseDown = false;
958                        clearInterval(mouseDownInterval);
959                        lightboxArrowLeft.classList.remove(`lightbox-arrow-hold-transitioning`);
960                        lightboxArrowRight.classList.remove(`lightbox-arrow-hold-transitioning`);
961                        clearInterval(mouseDownInterval);
962                        }, 500);
963                    }
964
965                    window.addEventListener(`mouseup`, clearFastForwarding);
966
967                    // Playlist Button Event Listener
968
969                    function toggleLightboxPlayerOrPlaylist(initialized) {
970                        if (!initialized) {
971                            showPlaylist = !showPlaylist;
972                        }
973                        if (showPlaylist) {
974                            lightboxFrames.forEach(frame => {
975                                frame.querySelector(`iframe`).src = ``;
976                                frame.querySelector(`h1`).textContent = ``;
977                            });
978                            lightboxPlaylistButton.classList.add(`element-invisible`);
979                            lightboxPlayerContainer.classList.add(`lightbox-container-off`);
980                            lightboxPlaylistContainer.classList.remove(`lightbox-container-off`);
981                        } else {
982                            if (playlistButton) {
983                                lightboxPlaylistButton.classList.remove(`element-invisible`);
984                            } else lightboxPlaylistButton.classList.add(`element-invisible`);
985                            lightboxPlayerContainer.classList.remove(`lightbox-container-off`);
986                            lightboxPlaylistContainer.classList.add(`lightbox-container-off`);
987                        }
988                    }
989
990                    toggleLightboxPlayerOrPlaylist(true);
991
992                    lightboxPlaylistButton.addEventListener(`click`, () => toggleLightboxPlayerOrPlaylist());
993                }
994
995                // Lightbox and Carousel Arrow Click Event Handlers
996
997                function frameTransitionHandler(frame, direction, type, currentVideoIndex, transitionType, fastForward) {
998                
999                    // Video Playlist Selected
1000
1001                    const playListSorted = media_data;
1002
1003                    // Lightbox Base Url Origin
1004
1005                    const lightboxFrameVideoBaseUrl = "https://www.youtube.com/embed/";
1006
1007                    // Frame Transition Time
1008
1009                    const frameTransition = 500;
1010
1011                    // Lightbox Fast Forward Speed
1012
1013                    const video_lightbox_fast_forward_speed = 150;
1014
1015                    // Set Thumbnail Episode Number Or Title Text
1016
1017	                    function setThumbnailText(item) {
1018	                        return media_title_text(item);
1019	                    }
1020                    
1021                    const currentFrameData = playListSorted[currentVideoIndex];
1022                    const nextFrameData = playListSorted[currentVideoIndex + 1];
1023                    const previousFrameData = playListSorted[currentVideoIndex - 1];
1024
1025                    const iframe = frame.querySelector(`iframe`);
1026                    const iframeText = frame.querySelector(`h1`);
1027                    // Arrow navigation loads the newly active video paused. Only
1028                    // the item that originally opened the lightbox autoplays.
1029                    const currentIframeUrl = currentFrameData ? lightboxFrameVideoBaseUrl + currentFrameData.id : null;
1030                    const nextIframeUrl = nextFrameData ? lightboxFrameVideoBaseUrl + nextFrameData.id : null;
1031                    const previousIframeUrl = previousFrameData ? lightboxFrameVideoBaseUrl + previousFrameData.id : null;
1032                    
1033                    const transitionSpeed = 500;
1034
1035                    if (type === `carousel` && transitionType !== `carouselAutomated`) {
1036                        frame.classList.add(`carousel-fast-transition`);
1037                        setTimeout(() => frame.classList.remove(`carousel-fast-transition`) ,transitionSpeed)
1038                    }
1039
1040                    if (fastForward) {
1041                        frame.classList.remove(`frame-transitioning-show-text`);
1042                    }
1043
1044                    switch(frame.dataset.frameposition) {
1045                        case `-1`:
1046                        if (direction === `left`) {
1047                            if (type === `lightbox` && currentFrameData && 
1048                            iframe.src !== currentIframeUrl ) {
1049                                iframe.src = currentIframeUrl
1050	                                iframeText.textContent = setThumbnailText(currentFrameData);
1051                            }
1052                            if (type === `carousel` && currentFrameData) {
1053                            setCarouselThumbnail(frame, currentFrameData)
1054                            }
1055                            setTimeout(() => {
1056                            frame.classList.remove(`frame-transitioning-show-text`);
1057                            }, transitionSpeed)
1058                            frame.classList.remove(`frame-transition-left`);
1059                            frame.dataset.frameposition = 0;
1060                        }
1061                        if (direction === `right`) {
1062                            frame.classList.remove(`frame-transition-left`);
1063                            frame.classList.add(`frame-transition-right`);
1064                            frame.dataset.frameposition = 1;
1065                            setTimeout(() => { 
1066                            if (type === `lightbox` && nextFrameData) {
1067                                iframe.src = nextIframeUrl; 
1068	                                iframeText.textContent = setThumbnailText(nextFrameData);
1069                            }
1070                            if (type === `carousel` && nextFrameData) {
1071                                setCarouselThumbnail(frame, nextFrameData)
1072                            }
1073                            if (nextFrameData && !fastForward) {
1074                                frame.classList.add(`frame-transitioning-show-text`);
1075                            }
1076                            }, transitionSpeed)
1077                        }
1078                        break;
1079                        case `0`:
1080                        if (direction === `left`) {
1081                            frame.classList.add(`frame-transition-right`);
1082                            frame.dataset.frameposition = 1;
1083                            setTimeout(() => { 
1084                            if (type === `lightbox` && nextFrameData) {
1085                                iframe.src = nextIframeUrl;
1086	                                iframeText.textContent = setThumbnailText(nextFrameData);
1087                            }
1088                            if (type === `carousel` && nextFrameData) {
1089                                setCarouselThumbnail(frame, nextFrameData)
1090                            }
1091                            if (nextFrameData && !fastForward) {
1092                                frame.classList.add(`frame-transitioning-show-text`);
1093                            }
1094                            }, transitionSpeed)
1095                        }
1096                        if (direction === `right`) { 
1097                            frame.classList.add(`frame-transition-left`);
1098                            frame.dataset.frameposition = -1;
1099                            setTimeout(() => { 
1100                            if (type === `lightbox` && previousFrameData) {
1101                                iframe.src = previousIframeUrl;
1102	                                iframeText.textContent = setThumbnailText(previousFrameData);
1103                            }
1104                            if (type === `carousel` && previousFrameData) {
1105                                setCarouselThumbnail(frame, previousFrameData)
1106                            }
1107                            if (previousFrameData && !fastForward) {
1108                                frame.classList.add(`frame-transitioning-show-text`);
1109                            }
1110                            }, transitionSpeed)
1111                        }
1112                        break;
1113                        case `1`:
1114                        if (direction === `left`) {
1115                            frame.classList.remove(`frame-transition-right`);
1116                            frame.classList.add(`frame-transition-left`);
1117                            frame.dataset.frameposition = -1;
1118                            setTimeout(() => { 
1119                            if (type === `lightbox` && previousFrameData) {
1120                                iframe.src = previousIframeUrl;
1121	                                iframeText.textContent = setThumbnailText(previousFrameData);
1122                            }
1123                            if (type === `carousel` && previousFrameData) {
1124                                setCarouselThumbnail(frame, previousFrameData)
1125                            }
1126                            if (previousFrameData && !fastForward) {
1127                                frame.classList.add(`frame-transitioning-show-text`);
1128                            }
1129                            }, transitionSpeed) 
1130                        }
1131                        if (direction === `right`) { 
1132                            if (type === `lightbox` && currentFrameData && 
1133                            iframe.src !== currentIframeUrl) {
1134                                iframe.src = currentIframeUrl;
1135                                iframeText.innerText = setThumbnailText(currentFrameData);
1136                            }
1137                            if (type === `carousel` && currentFrameData) {
1138                            setCarouselThumbnail(frame, currentFrameData)
1139                            }
1140                            if (currentFrameData && !fastForward) {
1141                            frame.classList.add(`frame-transitioning-show-text`);
1142                                setTimeout(() => {
1143                                    frame.classList.remove(`frame-transitioning-show-text`);
1144                                }, transitionSpeed)
1145                            }
1146                            frame.classList.remove(`frame-transition-right`);
1147                            frame.dataset.frameposition = 0;
1148                        }
1149                        break;
1150                        }
1151                    }
1152
1153                    // Clear Lightbox
1154
1155                    function lightbox_clear() {
1156                        const lightbox = media_lightbox_div;
1157                        lightbox.innerHTML = ``;
1158                        lightboxToggled = false;
1159                        showPlaylist = false;
1160                        lightbox.classList.remove(`show-lightbox`);
1161                        document.querySelector(`html`).classList.remove(`hide-scroll`);
1162                    }
1163
1164                    // Monitor Click Of Video Items Rendered
1165
1166                    window.addEventListener("click", e => {
1167                        const target = e.target instanceof Element ? e.target : null;
1168                        const node = target ? target.closest('a[data-itemclickable="true"]') : null;
1169                        if (!node || node.dataset.itemclickablemediatype !== "video") {
1170                            return;
1171                        }
1172                        if (node.closest('[data-lightboxplaylistcontent="true"]')) {
1173                            return;
1174                        }
1175                        if (node.dataset.itemclickableplaylist !== `${media_name}_${media_type}`) {
1176                            return;
1177                        }
1178
1179                        const videoEmbedBaseUrl = media_type === "vimeo" ? "https://player.vimeo.com/video/" : "https://www.youtube.com/embed/";
1180                        const embedUrl = `${videoEmbedBaseUrl}${node.dataset.id}`;
1181
1182                        document.dispatchEvent(new CustomEvent("mediaApiWidgetItemClick", {
1183                            bubbles: true,
1184                            detail: {
1185                                playlistName: media_name,
1186                                mediaType: media_type,
1187                                embedUrl,
1188                                itemId: node.dataset.id,
1189                                showLightbox: node.dataset.showlightbox !== "fal
1189se",
1190                                element: node
1191                            }
1192                        }));
1193
1194                        if (node.dataset.showlightbox === "false") {
1195                            return;
1196                        }
1197
1198                        const lightboxImage = node.dataset.lightboxshowlogoimgurl ? node.dataset.lightboxshowlogoimgurl : null;
1199                        const lightboxThemeColor = node.dataset.lightboxthemecolor ? node.dataset.lightboxthemecolor : null;
1200                        const lightboxFont = node.dataset.lightboxfont ? node.dataset.lightboxfont : null;
1201                        const lightBoxStyling = {
1202                            image : lightboxImage,
1203                            themeColor : lightboxThemeColor,
1204                            font : lightboxFont
1205                        };
1206
1207                        if (node.dataset.lightboxshowplaylist && node.dataset.lightboxshowplaylist === "true") {
1208                            video_lightbox_activate_handler(node, true, lightBoxStyling);
1209                        } else video_lightbox_activate_handler(node, false);
1210                    });
1211                    // Clear Lightbox If Window Is Resized Below 872
1212
1213                    window.addEventListener(`resize`, () => {
1214                        if (!showPlaylist && window.innerWidth < 872) {
1215                            lightbox_clear();
1216                        }
1217                    });
1218
1219                    // Builds the /podcast/player URL for a custom RSS feed, URL-encoding
1220                    // every query value so feed/shortcode-derived data cannot break out
1221                    // of the query string (or, downstream, an attribute).
1222
1223                    function maw_build_custom_player_url(rssUrl, itemClicked) {
1224                        const ds = itemClicked.dataset;
1225                        const params = new URLSearchParams();
1226                        params.set("url", rssUrl);
1227                        params.set("track", ds.trackselect || "");
1228                        params.set("mode", ds.podcastplayermode || "");
1229                        params.set("buttoncolor", ds.podcastplayerbuttoncolor || "");
1230                        params.set("color1", ds.podcastplayercolor || "");
1231                        params.set("progressbarcolor", ds.podcastprogressplayerbarcolor || "");
1232                        params.set("highlightcolor", ds.podcastplayerhighlightcolor || "");
1233                        params.set("font", ds.podcastplayerfont || "");
1234                        params.set("scrollcolor", ds.podcastplayerscrollcolor || "");
1235                        params.set("textcolor", ds.podcastplayertextcolor || "");
1236                        params.set("autoplay", "1");
1237                        if (ds.showepisodedateaftertitle === "true") {
1238                            params.set("adddatetotitle", "true");
1239                        }
1240                        return `${window.location.origin}/podcast/player?${params.toString()}`;
1241                    }
1242
1243                    // Computes the podcast embed URL for a clicked audio item (shared by event dispatch and lightbox handler)
1244
1245                    function compute_audio_embed_url(itemClicked) {
1246                        if (podcast_platform === "omny") {
1247                            const { channel } = media_data;
1248                            const showName = channel.item[0].link.split("/")[4];
1249                            return `https://omny.fm/shows/${showName}/playlists/podcast/embed?selectedClip=${itemClicked.dataset.id}&autoplay=1`;
1250                        }
1251                        if (podcast_platform === "soundcloud") {
1252                            const showEpisodeUrl = media_data.channel.item[0].link;
1253                            const spliced = showEpisodeUrl.split("/");
1254                            const urlOutput = `${spliced[0]}/${spliced[1]}/${spliced[2]}/${spliced[3]}`;
1255                            return `https://w.soundcloud.com/player/?url=${urlOutput}&start_track=${itemClicked.dataset.trackselect}&auto_play=true`;
1256                        }
1257                        if (podcast_platform === "buzzsprout") {
1258                            const { channel } = media_data;
1259                            const playlistId = channel.rssUrl.split("/")[3].split(".")[0];
1260                            const episodeId = itemClicked.dataset.id.split("-")[1];
1261                            return `https://www.buzzsprout.com/${playlistId}/${episodeId}`;
1262                        }
1263                        if (podcast_platform === "embed") {
1264                            return typeof media_data === "string" ? media_data : null;
1265                        }
1266                        if (podcast_platform === "custom") {
1267                            const rssUrl = media_data.channel?.rssUrl;
1268                            if (!rssUrl) return null;
1269                            return maw_build_custom_player_url(rssUrl, itemClicked);
1270                        }
1271                        return null;
1272                    }
1273
1274                    // Podcast Audio Lightbox Activate Handler
1275
1276                    function audio_lightbox_activate_handler(itemClicked) {
1277
1278                        // Set Ifram Url Based Upon Podcast Player Type
1279
1280                        let embedPlayerUrl;
1281
1282                        if (podcast_platform === "omny") {
1283
1284                            // Get Podcast Data, All Episodes, And Episode Clicked On
1285                        
1286                            const { channel } = media_data;
1287
1288                            const podcastInfo = { 
1289                                copyright: channel.copyright, 
1290                                title: channel.title, 
1291                                description: channel.description,
1292                                playlistId: channel.playlistId,
1293                                collectionViewUrl: channel.collectionViewUrl 
1294                            };
1295
1296                            const showName = channel.item[0].link.split("/")[4];
1297
1298                            embedPlayerUrl = `https://omny.fm/shows/${showName}/playlists/podcast/embed?selectedClip=${itemClicked.dataset.id}&autoplay=1`;
1299                        }
1300
1301                        if (podcast_platform === "soundcloud") {
1302                            const showEpisodeUrl = media_data.channel.item[0].link;
1303                            const spliced = showEpisodeUrl.split("/");
1304                            const urlOutput = `${spliced[0]}/${spliced[1]}/${spliced[2]}/${spliced[3]}`;
1305                            embedPlayerUrl = `https://w.soundcloud.com/player/?url=${urlOutput}&start_track=${itemClicked.dataset.trackselect}&auto_play=true`;
1306                        }
1307
1308                        if (podcast_platform === "buzzsprout") {
1309                            const { channel } = media_data;
1310                            const playlistId = channel.rssUrl.split("/")[3].split(".")[0];
1311                            const episodeId = itemClicked.dataset.id.split("-")[1];
1312                            embedPlayerUrl = `https://www.buzzsprout.com/${playlistId}/${episodeId}`;
1313                        }
1314
1315                        if (podcast_platform === "embed") {
1316                            embedPlayerUrl = media_data;
1317                        }
1318
1319                        if (podcast_platform === "custom") {
1320                            const rssUrl = media_data.channel?.rssUrl;
1321                            if (!rssUrl) return;
1322                            embedPlayerUrl = maw_build_custom_player_url(rssUrl, itemClicked);
1323                        }
1324
1325                        // If Window Resolution Is Less Than 872px, Open New Window In Apple Podcasts
1326
1327                        if (window.innerWidth < 872) {
1328                            window.open(embedPlayerUrl);
1329                            return
1330                        }
1331                        
1332                        // Init Lightbox
1333
1334                        const lightbox = media_lightbox_div;
1335
1336                        lightboxToggled = true;
1337                        showPlaylist = true;
1338                        lightbox.classList.add(`show-lightbox`);
1339                        document.querySelector(`html`).classList.add(`hide-scroll`);
1340
1341                        // Build the lightbox via DOM nodes so the embed URL is assigned as
1342                        // an iframe.src property and is never parsed as HTML — even if a
1343                        // feed or shortcode attribute contains markup.
1344
1345                        lightbox.replaceChildren();
1346
1347                        const contentContainer = document.createElement("div");
1348                        contentContainer.className = "lightbox-content-container";
1349
1350                        const boxFrame = document.createElement("div");
1351                        boxFrame.className = "lightbox-box-frame";
1352
1353                        const embedIframe = document.createElement("iframe");
1354                        embedIframe.className = "lightbox-podcast-embed-player";
1355                        embedIframe.setAttribute("allow", "autoplay");
1356                        embedIframe.src = embedPlayerUrl;
1357
1358                        boxFrame.appendChild(embedIframe);
1359                        contentContainer.appendChild(boxFrame);
1360
1361                        const closeButtonEl = document.createElement("div");
1362                        closeButtonEl.className = "lightbox-close-button";
1363                        closeButtonEl.dataset.hideonidle = "true";
1364                        closeButtonEl.dataset.lightboxclosebutton = "true";
1365                        closeButtonEl.textContent = "X";
1366
1367                        lightbox.appendChild(contentContainer);
1368                        lightbox.appendChild(closeButtonEl);
1369
1370                        // Monitor Click Of Close Button
1371
1372                        lightbox.querySelector(`[data-lightboxclosebutton="true"]`).addEventListener("click", () => {
1373                            lightbox_clear();
1374                        });
1375
1376                        // Monitor Mouse Movement To Hide Close Button If Idle For More Than 5 Seconds
1377
1378                        const closeButton = document.querySelector(`[data-lightboxclosebutton="true"]`);
1379
1380                        let lastTimeMouseMoved;
1381
1382                        function audioLightboxMouseMoveHandler(e) {
1383                            lastTimeMouseMoved = new Date().getTime();
1384                            closeButton.classList.remove("element-invisible");
1385                            setTimeout(() => {
1386                                if (new Date().getTime() - lastTimeMouseMoved >= 5000 && !e.target.dataset.hideonidle) {
1387                                    closeButton.classList.add("element-invisible");
1388                                }
1389                            }, 5000)
1390                        }
1391
1392                        lightbox.addEventListener("mousemove", audioLightboxMouseMoveHandler);
1393                        lightbox.addEventListener("click", audioLightboxMouseMoveHandler);
1394                    }
1395
1396                    // Monitor Click Of Podcast Audio Items Rendered
1397
1398                    window.addEventListener("click", e => {
1399                        const target = e.target instanceof Element ? e.target : null;
1400                        const node = target ? target.closest('a[data-itemclickable="true"]') : null;
1401                        if (!node) {
1402                            return;
1403                        }
1404
1405                        if (
1406                            node.dataset.itemclickableplaylist === `${media_name}_${media_type}` &&
1407                            node.dataset.itemclickablemediatype === "audio"
1408                        ) {
1409                            document.dispatchEvent(new CustomEvent("mediaApiWidgetItemClick", {
1410                                bubbles: true,
1411                                detail: {
1412                                    playlistName: media_name,
1413                                    mediaType: media_type,
1414                                    embedUrl: compute_audio_embed_url(node),
1415                                    itemId: node.dataset.id,
1416                                    showLightbox: node.dataset.showlightbox !== "fal
1416se",
1417                                    element: node
1418                                }
1419                            }));
1420
1421                            if (node.dataset.lightboxshowplaylist === "true" && node.dataset.showlightbox !== "false") {
1422                                audio_lightbox_activate_handler(node);
1423                            }
1424                        }
1425                    });
1426                }
1427
1428// -- Grid Search & Pagination --
1429
1430(function () {
1431    "use strict";
1432
1433    function initMawGridSearch() {
1434        document.querySelectorAll(".maw-grid-search-wrapper").forEach(function (wrapper) {
1435            var gridId       = wrapper.dataset.mawGridId;
1436            var playlistName = wrapper.dataset.mawPlaylist;
1437            var mediaType    = wrapper.dataset.mawMediatype;
1438            var perPage      = parseInt(wrapper.dataset.mawPerpage, 10) || 12;
1439            var gridKey      = wrapper.dataset.mawGridKey;
1440
1441            if (!playlistName || !mediaType || !gridKey) {
1442                return;
1443            }
1444
1445            var searchBar    = document.querySelector(".maw-grid-search-bar[data-maw-for=\"" + gridId + "\"]");
1446            var gridItemsEl  = wrapper.querySelector(".maw-grid-items");
1447            var paginationEl = wrapper.querySelector(".maw-grid-pagination");
1448
1449            var pageParam = wrapper.dataset.mawPageParam || ("maw_page_" + gridId);
1450            var baseUrl   = window.location.origin + window.location.pathname;
1451
1452            function readPageFromUrl() {
1453                var p = parseInt(new URLSearchParams(window.location.search).get(pageParam), 10);
1454                return p && p > 0 ? p : 1;
1455            }
1456
1457            var currentPage     = readPageFromUrl();
1458            var currentSearch   = "";
1459            var currentSearchBy = "any";
1460            var debounceTimer   = null;
1461
1462            function syncUrl() {
1463                var params = new URLSearchParams(window.location.search);
1464                if (currentPage > 1) {
1465                    params.set(pageParam, currentPage);
1466                } else {
1467                    params.delete(pageParam);
1468                }
1469                var qs = params.toString();
1470                window.history.pushState({ mawPage: currentPage }, "", baseUrl + (qs ? "?" + qs : ""));
1471            }
1472
1473            function doRequest(page, updateUrl) {
1474                currentPage = page || 1;
1475                wrapper.classList.add("loading");
1476
1477                var formData = new FormData();
1478                formData.append("action",        "maw_grid_search");
1479                formData.append("nonce",         mawGridSearchData.nonce);
1480                formData.append("playlist_name", playlistName);
1481                formData.append("media_platform",mediaType);
1482                formData.append("grid_key",      gridKey);
1483                formData.append("search_term",   currentSearch);
1484                formData.append("search_by",     currentSearchBy);
1485                formData.append("page",          currentPage);
1486                formData.append("per_page",      perPage);
1487                formData.append("base_url",      baseUrl);
1488
1489                fetch(mawGridSearchData.ajaxUrl, { method: "POST", body: formData })
1490                    .then(function (r) { return r.json(); })
1491                    .then(function (data) {
1492                        if (data.success) {
1493                            if (gridItemsEl)  gridItemsEl.innerHTML  = data.data.html;
1494                            if (paginationEl) paginationEl.innerHTML = data.data.pagination;
1495                            bindPaginationButtons();
1496                            // Only plain pagination is reflected in the URL; search
1497                            // stays AJAX-only so query variants aren't crawled.
1498                            if (updateUrl && currentSearch === "") {
1499                                syncUrl();
1500                            }
1501                        }
1502                        wrapper.classList.remove("loading");
1503                    })
1504                    .catch(function () {
1505                        wrapper.classList.remove("loading");
1506                    });
1507            }
1508
1509            function bindPaginationButtons() {
1510                if (!paginationEl) { return; }
1511                paginationEl.querySelectorAll("a.maw-page-btn, button.maw-page-btn").forEach(function (ctrl) {
1512                    ctrl.addEventListener("click", function (e) {
1513                        if (ctrl.tagName === "A") { e.preventDefault(); }
1514                        if (ctrl.disabled || ctrl.getAttribute("aria-disabled") === "true") { return; }
1515                        var page = parseInt(ctrl.dataset.page, 10);
1516                        if (page && page !== currentPage) {
1517                            doRequest(page, true);
1518                        }
1519                    });
1520                });
1521            }
1522
1523            bindPaginationButtons();
1524
1525            window.addEventListener("popstate", function () {
1526                if (currentSearch !== "") { return; }
1527                var page = readPageFromUrl();
1528                if (page !== currentPage) {
1529                    doRequest(page, false);
1530                }
1531            });
1532
1533            if (searchBar) {
1534                var input       = searchBar.querySelector(".maw-search-input");
1535                var select      = searchBar.querySelector(".maw-search-by");
1536                var clearButton = searchBar.querySelector(".maw-search-clear");
1537
1538                if (input) {
1539                    input.addEventListener("input", function () {
1540                        clearTimeout(debounceTimer);
1541                        debounceTimer = setTimeout(function () {
1542                            currentSearch   = input.value;
1543                            currentSearchBy = select ? select.value : "any";
1544                            doRequest(1, false);
1545                        }, 400);
1546                    });
1547                }
1548
1549                if (select) {
1550                    select.addEventListener("change", function () {
1551                        currentSearchBy = select.value;
1552                        if (currentSearch !== "") {
1553                            doRequest(1, false);
1554                        }
1555                    });
1556                }
1557
1558                if (clearButton && input) {
1559                    clearButton.addEventListener("click", function () {
1560                        clearTimeout(debounceTimer);
1561                        input.value     = "";
1562                        currentSearch   = "";
1563                        currentSearchBy = select ? select.value : "any";
1564                        doRequest(1, false);
1565                        input.focus();
1566                    });
1567                }
1568            }
1569        });
1570    }
1571
1572    if (document.readyState === "loading") {
1573        document.addEventListener("DOMContentLoaded", initMawGridSearch);
1574    } else {
1575        initMawGridSearch();
1576    }
1577})();

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.