Во время обработки запрошенного шаблона произошла ошибка...
The following has evaluated to null or missing:
==> playlabel  [in template "20099#20135#4873491" at line 275, column 195]

----
Tip: If the failing expression is known to be legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
	- Failed at: ${playlabel.getData()}  [in template "20099#20135#4873491" at line 275, column 193]
----
1<#-- 
2Web content templates are used to lay out the fields defined in a web 
3content structure. 
4 
5Please use the left panel to quickly add commonly used variables. 
6Autocomplete is also available and can be invoked by typing "${". 
7--> 
8 
9<style> 
10lite-youtube { 
11    background-color: #000; 
12    position: relative; 
13    display: block; 
14    contain: content; 
15    background-position: center center; 
16    background-size: cover; 
17    cursor: pointer; 
18    max-width: 720px; 
19
20 
21/* gradient */ 
22lite-youtube::before { 
23    content: ''; 
24    display: block; 
25    position: absolute; 
26    top: 0; 
27    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==); 
28    background-position: top; 
29    background-repeat: repeat-x; 
30    height: 60px; 
31    padding-bottom: 50px; 
32    width: 100%; 
33    transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); 
34
35 
36/* responsive iframe with a 16:9 aspect ratio 
37    thanks https://css-tricks.com/responsive-iframes/ 
38*/ 
39lite-youtube::after { 
40    content: ""; 
41    display: block; 
42    padding-bottom: calc(100% / (16 / 9)); 
43
44lite-youtube > iframe { 
45    width: 100%; 
46    height: 100%; 
47    position: absolute; 
48    top: 0; 
49    left: 0; 
50    border: 0; 
51
52 
53/* play button */ 
54lite-youtube > .lty-playbtn { 
55    display: block; 
56    /* Make the button element cover the whole area for a large hover/click target… */ 
57    width: 100%; 
58    height: 100%; 
59    /* …but visually it's still the same size */ 
60    background: no-repeat center/68px 48px; 
61    /* YT's actual play button svg */ 
62    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 48"><path d="M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z" fill="red"/><path d="M45 24 27 14v20" fill="white"/></svg>'); 
63    position: absolute; 
64    cursor: pointer; 
65    z-index: 1; 
66    filter: grayscale(100%); 
67    transition: filter .1s cubic-bezier(0, 0, 0.2, 1); 
68    border: 0; 
69
70 
71lite-youtube:hover > .lty-playbtn, 
72lite-youtube .lty-playbtn:focus { 
73    filter: none; 
74
75 
76/* Post-click styles */ 
77lite-youtube.lyt-activated { 
78    cursor: unset; 
79
80lite-youtube.lyt-activated::before, 
81lite-youtube.lyt-activated > .lty-playbtn { 
82    opacity: 0; 
83    pointer-events: none; 
84
85 
86.lyt-visually-hidden { 
87    clip: rect(0 0 0 0); 
88    clip-path: inset(50%); 
89    height: 1px; 
90    overflow: hidden; 
91    position: absolute; 
92    white-space: nowrap; 
93    width: 1px; 
94
95</style> 
96 
97<script> 
98/** 
99 * A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint. 
100
101 * Thx to these as the inspiration 
102 *   https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html 
103 *   https://autoplay-youtube-player.glitch.me/ 
104
105 * Once built it, I also found these: 
106 *   https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (👍👍) 
107 *   https://github.com/Daugilas/lazyYT 
108 *   https://github.com/vb/lazyframe 
109 */ 
110 
111class LiteYTEmbed extends HTMLElement { 
112    connectedCallback() { 
113        this.videoId = this.getAttribute('videoid'); 
114 
115        let playBtnEl = this.querySelector('.lty-playbtn'); 
116        // A label for the button takes priority over a [playlabel] attribute on the custom-element 
117        this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play'; 
118 
119        /** 
120         * Lo, the youtube placeholder image!  (aka the thumbnail, poster image, etc) 
121
122         * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md 
123
124         * TODO: Do the sddefault->hqdefault fallback 
125         *       - When doing this, apply referrerpolicy (https://github.com/ampproject/amphtml/pull/3940) 
126         * TODO: Consider using webp if supported, falling back to jpg 
127         */ 
128        if (!this.style.backgroundImage) { 
129          this.style.backgroundImage = `url("https://i.ytimg.com/vi/${r"${this.videoId}"}/hqdefault.jpg")`; 
130
131 
132        // Set up play button, and its visually hidden label 
133        if (!playBtnEl) { 
134            playBtnEl = document.createElement('button'); 
135            playBtnEl.type = 'button'; 
136            playBtnEl.classList.add('lty-playbtn'); 
137            this.append(playBtnEl); 
138
139        if (!playBtnEl.textContent) { 
140            const playBtnLabelEl = document.createElement('span'); 
141            playBtnLabelEl.className = 'lyt-visually-hidden'; 
142            playBtnLabelEl.textContent = this.playLabel; 
143            playBtnEl.append(playBtnLabelEl); 
144
145        playBtnEl.removeAttribute('href'); 
146 
147        // On hover (or tap), warm up the TCP connections we're (likely) about to use. 
148        this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true}); 
149 
150        // Once the user clicks, add the real iframe and drop our play button 
151        // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time 
152        //   We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 
153        this.addEventListener('click', this.addIframe); 
154 
155        // Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1 
156        // However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe, 
157        // so they don't autoplay automatically. Instead we must load an additional 2 sequential JS files (1KB + 165KB) (un-br) for the YT Player API 
158        // TODO: Try loading the the YT API in parallel with our iframe and then attaching/playing it. #82 
159        this.needsYTApiForAutoplay = navigator.vendor.includes('Apple') || navigator.userAgent.includes('Mobi'); 
160
161 
162    /** 
163     * Add a <link rel={preload | preconnect} ...> to the head 
164     */ 
165    static addPrefetch(kind, url, as) { 
166        const linkEl = document.createElement('link'); 
167        linkEl.rel = kind; 
168        linkEl.href = url; 
169        if (as) { 
170            linkEl.as = as; 
171
172        document.head.append(linkEl); 
173
174 
175    /** 
176     * Begin pre-connecting to warm up the iframe load 
177     * Since the embed's network requests load within its iframe, 
178     *   preload/prefetch'ing them outside the iframe will only cause double-downloads. 
179     * So, the best we can do is warm up a few connections to origins that are in the critical path. 
180
181     * Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267 
182     * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. 
183     */ 
184    static warmConnections() { 
185        if (LiteYTEmbed.preconnected) return; 
186 
187        // The iframe document and most of its subresources come right off youtube.com 
188        LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com'); 
189        // The botguard script is fetched off from google.com 
190        LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 
191 
192        // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. 
193        LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 
194        LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 
195 
196        LiteYTEmbed.preconnected = true; 
197
198 
199    fetchYTPlayerApi() { 
200        if (window.YT || (window.YT && window.YT.Player)) return; 
201 
202        this.ytApiPromise = new Promise((res, rej) => { 
203            var el = document.createElement('script'); 
204            el.src = 'https://www.youtube.com/iframe_api'; 
205            el.async = true; 
206            el.onload = _ => { 
207                YT.ready(res); 
208            }; 
209            el.onerror = rej; 
210            this.append(el); 
211        }); 
212
213 
214    async addYTPlayerIframe(params) { 
215        this.fetchYTPlayerApi(); 
216        await this.ytApiPromise; 
217 
218        const videoPlaceholderEl = document.createElement('div') 
219        this.append(videoPlaceholderEl); 
220 
221        const paramsObj = Object.fromEntries(params.entries()); 
222 
223        new YT.Player(videoPlaceholderEl, { 
224            width: '100%', 
225            videoId: this.videoId, 
226            playerVars: paramsObj, 
227            events: { 
228                'onReady': event => { 
229                    event.target.playVideo(); 
230
231
232        }); 
233
234 
235    async addIframe(){ 
236        if (this.classList.contains('lyt-activated')) return; 
237        this.classList.add('lyt-activated'); 
238 
239        const params = new URLSearchParams(this.getAttribute('params') || []); 
240        params.append('autoplay', '1'); 
241        params.append('playsinline', '1'); 
242 
243        if (this.needsYTApiForAutoplay) { 
244            return this.addYTPlayerIframe(params); 
245
246 
247        const iframeEl = document.createElement('iframe'); 
248        iframeEl.width = 560; 
249        iframeEl.height = 315; 
250        // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include 
251        iframeEl.title = this.playLabel; 
252        iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 
253        iframeEl.allowFullscreen = true; 
254        // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL 
255        // https://stackoverflow.com/q/64959723/89484 
256        iframeEl.src = `https://www.youtube-nocookie.com/embed/${r"${encodeURIComponent(this.videoId)}"}?${r"${params.toString()}"}`; 
257        this.append(iframeEl); 
258 
259        // Set focus for a11y 
260        iframeEl.focus(); 
261
262
263 
264// Register custom element 
265try { 
266    customElements.define('lite-youtube', LiteYTEmbed); 
267} catch (e) { 
268    console.log('lite-youtube already defined'); 
269
270</script> 
271 
272${content.getData()} 
273 
274<div class="embed-responsive-16by9"> 
275            <lite-youtube allowfullscreen="" class="embed-responsive-item" width="580" height="315" marginheight="0" marginwidth="0" scrolling="no" videoid="${reference.getData()}" playlabel="${playlabel.getData()}" style="margin: 0 auto;"></lite-youtube> 
276 
277</div> 
278<hr> 
279<br> 
Во время обработки запрошенного шаблона произошла ошибка...
The following has evaluated to null or missing:
==> playlabel  [in template "20099#20135#4873491" at line 275, column 195]

----
Tip: If the failing expression is known to be legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
	- Failed at: ${playlabel.getData()}  [in template "20099#20135#4873491" at line 275, column 193]
----
1<#-- 
2Web content templates are used to lay out the fields defined in a web 
3content structure. 
4 
5Please use the left panel to quickly add commonly used variables. 
6Autocomplete is also available and can be invoked by typing "${". 
7--> 
8 
9<style> 
10lite-youtube { 
11    background-color: #000; 
12    position: relative; 
13    display: block; 
14    contain: content; 
15    background-position: center center; 
16    background-size: cover; 
17    cursor: pointer; 
18    max-width: 720px; 
19
20 
21/* gradient */ 
22lite-youtube::before { 
23    content: ''; 
24    display: block; 
25    position: absolute; 
26    top: 0; 
27    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==); 
28    background-position: top; 
29    background-repeat: repeat-x; 
30    height: 60px; 
31    padding-bottom: 50px; 
32    width: 100%; 
33    transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); 
34
35 
36/* responsive iframe with a 16:9 aspect ratio 
37    thanks https://css-tricks.com/responsive-iframes/ 
38*/ 
39lite-youtube::after { 
40    content: ""; 
41    display: block; 
42    padding-bottom: calc(100% / (16 / 9)); 
43
44lite-youtube > iframe { 
45    width: 100%; 
46    height: 100%; 
47    position: absolute; 
48    top: 0; 
49    left: 0; 
50    border: 0; 
51
52 
53/* play button */ 
54lite-youtube > .lty-playbtn { 
55    display: block; 
56    /* Make the button element cover the whole area for a large hover/click target… */ 
57    width: 100%; 
58    height: 100%; 
59    /* …but visually it's still the same size */ 
60    background: no-repeat center/68px 48px; 
61    /* YT's actual play button svg */ 
62    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 48"><path d="M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z" fill="red"/><path d="M45 24 27 14v20" fill="white"/></svg>'); 
63    position: absolute; 
64    cursor: pointer; 
65    z-index: 1; 
66    filter: grayscale(100%); 
67    transition: filter .1s cubic-bezier(0, 0, 0.2, 1); 
68    border: 0; 
69
70 
71lite-youtube:hover > .lty-playbtn, 
72lite-youtube .lty-playbtn:focus { 
73    filter: none; 
74
75 
76/* Post-click styles */ 
77lite-youtube.lyt-activated { 
78    cursor: unset; 
79
80lite-youtube.lyt-activated::before, 
81lite-youtube.lyt-activated > .lty-playbtn { 
82    opacity: 0; 
83    pointer-events: none; 
84
85 
86.lyt-visually-hidden { 
87    clip: rect(0 0 0 0); 
88    clip-path: inset(50%); 
89    height: 1px; 
90    overflow: hidden; 
91    position: absolute; 
92    white-space: nowrap; 
93    width: 1px; 
94
95</style> 
96 
97<script> 
98/** 
99 * A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint. 
100
101 * Thx to these as the inspiration 
102 *   https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html 
103 *   https://autoplay-youtube-player.glitch.me/ 
104
105 * Once built it, I also found these: 
106 *   https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (👍👍) 
107 *   https://github.com/Daugilas/lazyYT 
108 *   https://github.com/vb/lazyframe 
109 */ 
110 
111class LiteYTEmbed extends HTMLElement { 
112    connectedCallback() { 
113        this.videoId = this.getAttribute('videoid'); 
114 
115        let playBtnEl = this.querySelector('.lty-playbtn'); 
116        // A label for the button takes priority over a [playlabel] attribute on the custom-element 
117        this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play'; 
118 
119        /** 
120         * Lo, the youtube placeholder image!  (aka the thumbnail, poster image, etc) 
121
122         * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md 
123
124         * TODO: Do the sddefault->hqdefault fallback 
125         *       - When doing this, apply referrerpolicy (https://github.com/ampproject/amphtml/pull/3940) 
126         * TODO: Consider using webp if supported, falling back to jpg 
127         */ 
128        if (!this.style.backgroundImage) { 
129          this.style.backgroundImage = `url("https://i.ytimg.com/vi/${r"${this.videoId}"}/hqdefault.jpg")`; 
130
131 
132        // Set up play button, and its visually hidden label 
133        if (!playBtnEl) { 
134            playBtnEl = document.createElement('button'); 
135            playBtnEl.type = 'button'; 
136            playBtnEl.classList.add('lty-playbtn'); 
137            this.append(playBtnEl); 
138
139        if (!playBtnEl.textContent) { 
140            const playBtnLabelEl = document.createElement('span'); 
141            playBtnLabelEl.className = 'lyt-visually-hidden'; 
142            playBtnLabelEl.textContent = this.playLabel; 
143            playBtnEl.append(playBtnLabelEl); 
144
145        playBtnEl.removeAttribute('href'); 
146 
147        // On hover (or tap), warm up the TCP connections we're (likely) about to use. 
148        this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true}); 
149 
150        // Once the user clicks, add the real iframe and drop our play button 
151        // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time 
152        //   We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 
153        this.addEventListener('click', this.addIframe); 
154 
155        // Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1 
156        // However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe, 
157        // so they don't autoplay automatically. Instead we must load an additional 2 sequential JS files (1KB + 165KB) (un-br) for the YT Player API 
158        // TODO: Try loading the the YT API in parallel with our iframe and then attaching/playing it. #82 
159        this.needsYTApiForAutoplay = navigator.vendor.includes('Apple') || navigator.userAgent.includes('Mobi'); 
160
161 
162    /** 
163     * Add a <link rel={preload | preconnect} ...> to the head 
164     */ 
165    static addPrefetch(kind, url, as) { 
166        const linkEl = document.createElement('link'); 
167        linkEl.rel = kind; 
168        linkEl.href = url; 
169        if (as) { 
170            linkEl.as = as; 
171
172        document.head.append(linkEl); 
173
174 
175    /** 
176     * Begin pre-connecting to warm up the iframe load 
177     * Since the embed's network requests load within its iframe, 
178     *   preload/prefetch'ing them outside the iframe will only cause double-downloads. 
179     * So, the best we can do is warm up a few connections to origins that are in the critical path. 
180
181     * Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267 
182     * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. 
183     */ 
184    static warmConnections() { 
185        if (LiteYTEmbed.preconnected) return; 
186 
187        // The iframe document and most of its subresources come right off youtube.com 
188        LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com'); 
189        // The botguard script is fetched off from google.com 
190        LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 
191 
192        // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. 
193        LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 
194        LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 
195 
196        LiteYTEmbed.preconnected = true; 
197
198 
199    fetchYTPlayerApi() { 
200        if (window.YT || (window.YT && window.YT.Player)) return; 
201 
202        this.ytApiPromise = new Promise((res, rej) => { 
203            var el = document.createElement('script'); 
204            el.src = 'https://www.youtube.com/iframe_api'; 
205            el.async = true; 
206            el.onload = _ => { 
207                YT.ready(res); 
208            }; 
209            el.onerror = rej; 
210            this.append(el); 
211        }); 
212
213 
214    async addYTPlayerIframe(params) { 
215        this.fetchYTPlayerApi(); 
216        await this.ytApiPromise; 
217 
218        const videoPlaceholderEl = document.createElement('div') 
219        this.append(videoPlaceholderEl); 
220 
221        const paramsObj = Object.fromEntries(params.entries()); 
222 
223        new YT.Player(videoPlaceholderEl, { 
224            width: '100%', 
225            videoId: this.videoId, 
226            playerVars: paramsObj, 
227            events: { 
228                'onReady': event => { 
229                    event.target.playVideo(); 
230
231
232        }); 
233
234 
235    async addIframe(){ 
236        if (this.classList.contains('lyt-activated')) return; 
237        this.classList.add('lyt-activated'); 
238 
239        const params = new URLSearchParams(this.getAttribute('params') || []); 
240        params.append('autoplay', '1'); 
241        params.append('playsinline', '1'); 
242 
243        if (this.needsYTApiForAutoplay) { 
244            return this.addYTPlayerIframe(params); 
245
246 
247        const iframeEl = document.createElement('iframe'); 
248        iframeEl.width = 560; 
249        iframeEl.height = 315; 
250        // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include 
251        iframeEl.title = this.playLabel; 
252        iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 
253        iframeEl.allowFullscreen = true; 
254        // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL 
255        // https://stackoverflow.com/q/64959723/89484 
256        iframeEl.src = `https://www.youtube-nocookie.com/embed/${r"${encodeURIComponent(this.videoId)}"}?${r"${params.toString()}"}`; 
257        this.append(iframeEl); 
258 
259        // Set focus for a11y 
260        iframeEl.focus(); 
261
262
263 
264// Register custom element 
265try { 
266    customElements.define('lite-youtube', LiteYTEmbed); 
267} catch (e) { 
268    console.log('lite-youtube already defined'); 
269
270</script> 
271 
272${content.getData()} 
273 
274<div class="embed-responsive-16by9"> 
275            <lite-youtube allowfullscreen="" class="embed-responsive-item" width="580" height="315" marginheight="0" marginwidth="0" scrolling="no" videoid="${reference.getData()}" playlabel="${playlabel.getData()}" style="margin: 0 auto;"></lite-youtube> 
276 
277</div> 
278<hr> 
279<br> 
Во время обработки запрошенного шаблона произошла ошибка...
The following has evaluated to null or missing:
==> playlabel  [in template "20099#20135#4873491" at line 275, column 195]

----
Tip: If the failing expression is known to be legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
	- Failed at: ${playlabel.getData()}  [in template "20099#20135#4873491" at line 275, column 193]
----
1<#-- 
2Web content templates are used to lay out the fields defined in a web 
3content structure. 
4 
5Please use the left panel to quickly add commonly used variables. 
6Autocomplete is also available and can be invoked by typing "${". 
7--> 
8 
9<style> 
10lite-youtube { 
11    background-color: #000; 
12    position: relative; 
13    display: block; 
14    contain: content; 
15    background-position: center center; 
16    background-size: cover; 
17    cursor: pointer; 
18    max-width: 720px; 
19
20 
21/* gradient */ 
22lite-youtube::before { 
23    content: ''; 
24    display: block; 
25    position: absolute; 
26    top: 0; 
27    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==); 
28    background-position: top; 
29    background-repeat: repeat-x; 
30    height: 60px; 
31    padding-bottom: 50px; 
32    width: 100%; 
33    transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); 
34
35 
36/* responsive iframe with a 16:9 aspect ratio 
37    thanks https://css-tricks.com/responsive-iframes/ 
38*/ 
39lite-youtube::after { 
40    content: ""; 
41    display: block; 
42    padding-bottom: calc(100% / (16 / 9)); 
43
44lite-youtube > iframe { 
45    width: 100%; 
46    height: 100%; 
47    position: absolute; 
48    top: 0; 
49    left: 0; 
50    border: 0; 
51
52 
53/* play button */ 
54lite-youtube > .lty-playbtn { 
55    display: block; 
56    /* Make the button element cover the whole area for a large hover/click target… */ 
57    width: 100%; 
58    height: 100%; 
59    /* …but visually it's still the same size */ 
60    background: no-repeat center/68px 48px; 
61    /* YT's actual play button svg */ 
62    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 48"><path d="M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z" fill="red"/><path d="M45 24 27 14v20" fill="white"/></svg>'); 
63    position: absolute; 
64    cursor: pointer; 
65    z-index: 1; 
66    filter: grayscale(100%); 
67    transition: filter .1s cubic-bezier(0, 0, 0.2, 1); 
68    border: 0; 
69
70 
71lite-youtube:hover > .lty-playbtn, 
72lite-youtube .lty-playbtn:focus { 
73    filter: none; 
74
75 
76/* Post-click styles */ 
77lite-youtube.lyt-activated { 
78    cursor: unset; 
79
80lite-youtube.lyt-activated::before, 
81lite-youtube.lyt-activated > .lty-playbtn { 
82    opacity: 0; 
83    pointer-events: none; 
84
85 
86.lyt-visually-hidden { 
87    clip: rect(0 0 0 0); 
88    clip-path: inset(50%); 
89    height: 1px; 
90    overflow: hidden; 
91    position: absolute; 
92    white-space: nowrap; 
93    width: 1px; 
94
95</style> 
96 
97<script> 
98/** 
99 * A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint. 
100
101 * Thx to these as the inspiration 
102 *   https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html 
103 *   https://autoplay-youtube-player.glitch.me/ 
104
105 * Once built it, I also found these: 
106 *   https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (👍👍) 
107 *   https://github.com/Daugilas/lazyYT 
108 *   https://github.com/vb/lazyframe 
109 */ 
110 
111class LiteYTEmbed extends HTMLElement { 
112    connectedCallback() { 
113        this.videoId = this.getAttribute('videoid'); 
114 
115        let playBtnEl = this.querySelector('.lty-playbtn'); 
116        // A label for the button takes priority over a [playlabel] attribute on the custom-element 
117        this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play'; 
118 
119        /** 
120         * Lo, the youtube placeholder image!  (aka the thumbnail, poster image, etc) 
121
122         * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md 
123
124         * TODO: Do the sddefault->hqdefault fallback 
125         *       - When doing this, apply referrerpolicy (https://github.com/ampproject/amphtml/pull/3940) 
126         * TODO: Consider using webp if supported, falling back to jpg 
127         */ 
128        if (!this.style.backgroundImage) { 
129          this.style.backgroundImage = `url("https://i.ytimg.com/vi/${r"${this.videoId}"}/hqdefault.jpg")`; 
130
131 
132        // Set up play button, and its visually hidden label 
133        if (!playBtnEl) { 
134            playBtnEl = document.createElement('button'); 
135            playBtnEl.type = 'button'; 
136            playBtnEl.classList.add('lty-playbtn'); 
137            this.append(playBtnEl); 
138
139        if (!playBtnEl.textContent) { 
140            const playBtnLabelEl = document.createElement('span'); 
141            playBtnLabelEl.className = 'lyt-visually-hidden'; 
142            playBtnLabelEl.textContent = this.playLabel; 
143            playBtnEl.append(playBtnLabelEl); 
144
145        playBtnEl.removeAttribute('href'); 
146 
147        // On hover (or tap), warm up the TCP connections we're (likely) about to use. 
148        this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true}); 
149 
150        // Once the user clicks, add the real iframe and drop our play button 
151        // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time 
152        //   We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 
153        this.addEventListener('click', this.addIframe); 
154 
155        // Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1 
156        // However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe, 
157        // so they don't autoplay automatically. Instead we must load an additional 2 sequential JS files (1KB + 165KB) (un-br) for the YT Player API 
158        // TODO: Try loading the the YT API in parallel with our iframe and then attaching/playing it. #82 
159        this.needsYTApiForAutoplay = navigator.vendor.includes('Apple') || navigator.userAgent.includes('Mobi'); 
160
161 
162    /** 
163     * Add a <link rel={preload | preconnect} ...> to the head 
164     */ 
165    static addPrefetch(kind, url, as) { 
166        const linkEl = document.createElement('link'); 
167        linkEl.rel = kind; 
168        linkEl.href = url; 
169        if (as) { 
170            linkEl.as = as; 
171
172        document.head.append(linkEl); 
173
174 
175    /** 
176     * Begin pre-connecting to warm up the iframe load 
177     * Since the embed's network requests load within its iframe, 
178     *   preload/prefetch'ing them outside the iframe will only cause double-downloads. 
179     * So, the best we can do is warm up a few connections to origins that are in the critical path. 
180
181     * Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267 
182     * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. 
183     */ 
184    static warmConnections() { 
185        if (LiteYTEmbed.preconnected) return; 
186 
187        // The iframe document and most of its subresources come right off youtube.com 
188        LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com'); 
189        // The botguard script is fetched off from google.com 
190        LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 
191 
192        // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. 
193        LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 
194        LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 
195 
196        LiteYTEmbed.preconnected = true; 
197
198 
199    fetchYTPlayerApi() { 
200        if (window.YT || (window.YT && window.YT.Player)) return; 
201 
202        this.ytApiPromise = new Promise((res, rej) => { 
203            var el = document.createElement('script'); 
204            el.src = 'https://www.youtube.com/iframe_api'; 
205            el.async = true; 
206            el.onload = _ => { 
207                YT.ready(res); 
208            }; 
209            el.onerror = rej; 
210            this.append(el); 
211        }); 
212
213 
214    async addYTPlayerIframe(params) { 
215        this.fetchYTPlayerApi(); 
216        await this.ytApiPromise; 
217 
218        const videoPlaceholderEl = document.createElement('div') 
219        this.append(videoPlaceholderEl); 
220 
221        const paramsObj = Object.fromEntries(params.entries()); 
222 
223        new YT.Player(videoPlaceholderEl, { 
224            width: '100%', 
225            videoId: this.videoId, 
226            playerVars: paramsObj, 
227            events: { 
228                'onReady': event => { 
229                    event.target.playVideo(); 
230
231
232        }); 
233
234 
235    async addIframe(){ 
236        if (this.classList.contains('lyt-activated')) return; 
237        this.classList.add('lyt-activated'); 
238 
239        const params = new URLSearchParams(this.getAttribute('params') || []); 
240        params.append('autoplay', '1'); 
241        params.append('playsinline', '1'); 
242 
243        if (this.needsYTApiForAutoplay) { 
244            return this.addYTPlayerIframe(params); 
245
246 
247        const iframeEl = document.createElement('iframe'); 
248        iframeEl.width = 560; 
249        iframeEl.height = 315; 
250        // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include 
251        iframeEl.title = this.playLabel; 
252        iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 
253        iframeEl.allowFullscreen = true; 
254        // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL 
255        // https://stackoverflow.com/q/64959723/89484 
256        iframeEl.src = `https://www.youtube-nocookie.com/embed/${r"${encodeURIComponent(this.videoId)}"}?${r"${params.toString()}"}`; 
257        this.append(iframeEl); 
258 
259        // Set focus for a11y 
260        iframeEl.focus(); 
261
262
263 
264// Register custom element 
265try { 
266    customElements.define('lite-youtube', LiteYTEmbed); 
267} catch (e) { 
268    console.log('lite-youtube already defined'); 
269
270</script> 
271 
272${content.getData()} 
273 
274<div class="embed-responsive-16by9"> 
275            <lite-youtube allowfullscreen="" class="embed-responsive-item" width="580" height="315" marginheight="0" marginwidth="0" scrolling="no" videoid="${reference.getData()}" playlabel="${playlabel.getData()}" style="margin: 0 auto;"></lite-youtube> 
276 
277</div> 
278<hr> 
279<br> 
Во время обработки запрошенного шаблона произошла ошибка...
The following has evaluated to null or missing:
==> playlabel  [in template "20099#20135#4873491" at line 275, column 195]

----
Tip: If the failing expression is known to be legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
	- Failed at: ${playlabel.getData()}  [in template "20099#20135#4873491" at line 275, column 193]
----
1<#-- 
2Web content templates are used to lay out the fields defined in a web 
3content structure. 
4 
5Please use the left panel to quickly add commonly used variables. 
6Autocomplete is also available and can be invoked by typing "${". 
7--> 
8 
9<style> 
10lite-youtube { 
11    background-color: #000; 
12    position: relative; 
13    display: block; 
14    contain: content; 
15    background-position: center center; 
16    background-size: cover; 
17    cursor: pointer; 
18    max-width: 720px; 
19
20 
21/* gradient */ 
22lite-youtube::before { 
23    content: ''; 
24    display: block; 
25    position: absolute; 
26    top: 0; 
27    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==); 
28    background-position: top; 
29    background-repeat: repeat-x; 
30    height: 60px; 
31    padding-bottom: 50px; 
32    width: 100%; 
33    transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); 
34
35 
36/* responsive iframe with a 16:9 aspect ratio 
37    thanks https://css-tricks.com/responsive-iframes/ 
38*/ 
39lite-youtube::after { 
40    content: ""; 
41    display: block; 
42    padding-bottom: calc(100% / (16 / 9)); 
43
44lite-youtube > iframe { 
45    width: 100%; 
46    height: 100%; 
47    position: absolute; 
48    top: 0; 
49    left: 0; 
50    border: 0; 
51
52 
53/* play button */ 
54lite-youtube > .lty-playbtn { 
55    display: block; 
56    /* Make the button element cover the whole area for a large hover/click target… */ 
57    width: 100%; 
58    height: 100%; 
59    /* …but visually it's still the same size */ 
60    background: no-repeat center/68px 48px; 
61    /* YT's actual play button svg */ 
62    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 48"><path d="M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z" fill="red"/><path d="M45 24 27 14v20" fill="white"/></svg>'); 
63    position: absolute; 
64    cursor: pointer; 
65    z-index: 1; 
66    filter: grayscale(100%); 
67    transition: filter .1s cubic-bezier(0, 0, 0.2, 1); 
68    border: 0; 
69
70 
71lite-youtube:hover > .lty-playbtn, 
72lite-youtube .lty-playbtn:focus { 
73    filter: none; 
74
75 
76/* Post-click styles */ 
77lite-youtube.lyt-activated { 
78    cursor: unset; 
79
80lite-youtube.lyt-activated::before, 
81lite-youtube.lyt-activated > .lty-playbtn { 
82    opacity: 0; 
83    pointer-events: none; 
84
85 
86.lyt-visually-hidden { 
87    clip: rect(0 0 0 0); 
88    clip-path: inset(50%); 
89    height: 1px; 
90    overflow: hidden; 
91    position: absolute; 
92    white-space: nowrap; 
93    width: 1px; 
94
95</style> 
96 
97<script> 
98/** 
99 * A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint. 
100
101 * Thx to these as the inspiration 
102 *   https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html 
103 *   https://autoplay-youtube-player.glitch.me/ 
104
105 * Once built it, I also found these: 
106 *   https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (👍👍) 
107 *   https://github.com/Daugilas/lazyYT 
108 *   https://github.com/vb/lazyframe 
109 */ 
110 
111class LiteYTEmbed extends HTMLElement { 
112    connectedCallback() { 
113        this.videoId = this.getAttribute('videoid'); 
114 
115        let playBtnEl = this.querySelector('.lty-playbtn'); 
116        // A label for the button takes priority over a [playlabel] attribute on the custom-element 
117        this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play'; 
118 
119        /** 
120         * Lo, the youtube placeholder image!  (aka the thumbnail, poster image, etc) 
121
122         * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md 
123
124         * TODO: Do the sddefault->hqdefault fallback 
125         *       - When doing this, apply referrerpolicy (https://github.com/ampproject/amphtml/pull/3940) 
126         * TODO: Consider using webp if supported, falling back to jpg 
127         */ 
128        if (!this.style.backgroundImage) { 
129          this.style.backgroundImage = `url("https://i.ytimg.com/vi/${r"${this.videoId}"}/hqdefault.jpg")`; 
130
131 
132        // Set up play button, and its visually hidden label 
133        if (!playBtnEl) { 
134            playBtnEl = document.createElement('button'); 
135            playBtnEl.type = 'button'; 
136            playBtnEl.classList.add('lty-playbtn'); 
137            this.append(playBtnEl); 
138
139        if (!playBtnEl.textContent) { 
140            const playBtnLabelEl = document.createElement('span'); 
141            playBtnLabelEl.className = 'lyt-visually-hidden'; 
142            playBtnLabelEl.textContent = this.playLabel; 
143            playBtnEl.append(playBtnLabelEl); 
144
145        playBtnEl.removeAttribute('href'); 
146 
147        // On hover (or tap), warm up the TCP connections we're (likely) about to use. 
148        this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true}); 
149 
150        // Once the user clicks, add the real iframe and drop our play button 
151        // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time 
152        //   We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 
153        this.addEventListener('click', this.addIframe); 
154 
155        // Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1 
156        // However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe, 
157        // so they don't autoplay automatically. Instead we must load an additional 2 sequential JS files (1KB + 165KB) (un-br) for the YT Player API 
158        // TODO: Try loading the the YT API in parallel with our iframe and then attaching/playing it. #82 
159        this.needsYTApiForAutoplay = navigator.vendor.includes('Apple') || navigator.userAgent.includes('Mobi'); 
160
161 
162    /** 
163     * Add a <link rel={preload | preconnect} ...> to the head 
164     */ 
165    static addPrefetch(kind, url, as) { 
166        const linkEl = document.createElement('link'); 
167        linkEl.rel = kind; 
168        linkEl.href = url; 
169        if (as) { 
170            linkEl.as = as; 
171
172        document.head.append(linkEl); 
173
174 
175    /** 
176     * Begin pre-connecting to warm up the iframe load 
177     * Since the embed's network requests load within its iframe, 
178     *   preload/prefetch'ing them outside the iframe will only cause double-downloads. 
179     * So, the best we can do is warm up a few connections to origins that are in the critical path. 
180
181     * Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267 
182     * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. 
183     */ 
184    static warmConnections() { 
185        if (LiteYTEmbed.preconnected) return; 
186 
187        // The iframe document and most of its subresources come right off youtube.com 
188        LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com'); 
189        // The botguard script is fetched off from google.com 
190        LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 
191 
192        // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. 
193        LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 
194        LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 
195 
196        LiteYTEmbed.preconnected = true; 
197
198 
199    fetchYTPlayerApi() { 
200        if (window.YT || (window.YT && window.YT.Player)) return; 
201 
202        this.ytApiPromise = new Promise((res, rej) => { 
203            var el = document.createElement('script'); 
204            el.src = 'https://www.youtube.com/iframe_api'; 
205            el.async = true; 
206            el.onload = _ => { 
207                YT.ready(res); 
208            }; 
209            el.onerror = rej; 
210            this.append(el); 
211        }); 
212
213 
214    async addYTPlayerIframe(params) { 
215        this.fetchYTPlayerApi(); 
216        await this.ytApiPromise; 
217 
218        const videoPlaceholderEl = document.createElement('div') 
219        this.append(videoPlaceholderEl); 
220 
221        const paramsObj = Object.fromEntries(params.entries()); 
222 
223        new YT.Player(videoPlaceholderEl, { 
224            width: '100%', 
225            videoId: this.videoId, 
226            playerVars: paramsObj, 
227            events: { 
228                'onReady': event => { 
229                    event.target.playVideo(); 
230
231
232        }); 
233
234 
235    async addIframe(){ 
236        if (this.classList.contains('lyt-activated')) return; 
237        this.classList.add('lyt-activated'); 
238 
239        const params = new URLSearchParams(this.getAttribute('params') || []); 
240        params.append('autoplay', '1'); 
241        params.append('playsinline', '1'); 
242 
243        if (this.needsYTApiForAutoplay) { 
244            return this.addYTPlayerIframe(params); 
245
246 
247        const iframeEl = document.createElement('iframe'); 
248        iframeEl.width = 560; 
249        iframeEl.height = 315; 
250        // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include 
251        iframeEl.title = this.playLabel; 
252        iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 
253        iframeEl.allowFullscreen = true; 
254        // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL 
255        // https://stackoverflow.com/q/64959723/89484 
256        iframeEl.src = `https://www.youtube-nocookie.com/embed/${r"${encodeURIComponent(this.videoId)}"}?${r"${params.toString()}"}`; 
257        this.append(iframeEl); 
258 
259        // Set focus for a11y 
260        iframeEl.focus(); 
261
262
263 
264// Register custom element 
265try { 
266    customElements.define('lite-youtube', LiteYTEmbed); 
267} catch (e) { 
268    console.log('lite-youtube already defined'); 
269
270</script> 
271 
272${content.getData()} 
273 
274<div class="embed-responsive-16by9"> 
275            <lite-youtube allowfullscreen="" class="embed-responsive-item" width="580" height="315" marginheight="0" marginwidth="0" scrolling="no" videoid="${reference.getData()}" playlabel="${playlabel.getData()}" style="margin: 0 auto;"></lite-youtube> 
276 
277</div> 
278<hr> 
279<br>