PageSourceSearch

https://beckycashforindiana.com/js/yt-facade.js

js beckycashforindiana.com collected 2026-10-10 00:38:34 UTC 7,178 bytes, 175 lines download raw bytes

1// ============================================
2// YouTube Facade - click-to-load embeds
3// ADD TO: global site scripts (alongside click-to-zoom.js)
4// Server renders <div class="yt-facade" data-id="..."> in place of
5// eager iframes; this injects the real player only on interaction.
6// Delegated handlers, so it works for any number of facades and for
7// content added after load. No init call required.
8//
9// ORIENTATION (new)
10// Vertical videos (Shorts) get blurred side bars when the player sits in
11// a 16:9 box. Each facade is checked once on load; videos taller than
12// 16:9 are tagged so the stylesheet can size the box to match:
13//
14//   .yt-facade            gets .yt-facade-tall  or .yt-facade-wide
15//   .video-container      gets .video-container-tall (ratio > 16:9)
16//                         and  .video-container-portrait (taller than wide)
17//                         plus --video-ratio = height / width (e.g. 1.7778)
18//
19// Detection order:
20//   1. data-ratio="1.7778" or data-orientation="portrait|landscape" on
21//      the facade. The server can set these at save time (YouTube's
22//      oEmbed reports the real size when queried with the /shorts/ URL
23//      form, for any video ID). No probe is made in this case.
24//   2. Otherwise the "original aspect ratio" thumbnail
25//      https://i.ytimg.com/vi/<id>/oardefault.jpg is loaded off-screen and
26//      its natural dimensions are read. No API key, no CORS issue
27//      (naturalWidth/Height are readable cross-origin). Anything that
28//      fails to load, or comes back as ytimg's 120x90 placeholder, is
29//      treated as landscape - i.e. today's behaviour.
30//
31// For tall videos the 4:3 hqdefault poster (black bars on Shorts) is
32// swapped for the true-ratio thumbnail as well.
33//
34// Facades added after load: window.YTFacade.scan(rootElement)
35// ============================================
36
37(function () {
38    'use strict';
39
40    var ID_RE = /^[A-Za-z0-9_-]{5,20}$/;
41    var LANDSCAPE = 9 / 16;          // height / width of a 16:9 box
42    var TALL_THRESHOLD = 0.6;        // taller than 16:9, with a little slack
43    var PLACEHOLDER_MAX_WIDTH = 120; // i.ytimg.com serves a 120x90 grey tile for missing thumbs
44
45    // ---------- orientation ----------
46
47    function oarUrl(id) {
48        return 'https://i.ytimg.com/vi/' + id + '/oardefault.jpg';
49    }
50
51    // Loads the original-aspect-ratio thumbnail; cb(img) on success, cb(null) if
52    // it is missing or is the placeholder tile.
53    function loadOarThumb(id, cb) {
54        var probe = new Image();
55        probe.onload = function () {
56            var ok = probe.naturalWidth > PLACEHOLDER_MAX_WIDTH && probe.naturalHeight > 0;
57            cb(ok ? probe : null);
58        };
59        probe.onerror = function () { cb(null); };
60        probe.src = oarUrl(id);
61    }
62
63    function setPoster(facade, src) {
64        var img = facade.querySelector('img');
65        if (img && img.src !== src) img.src = src;
66    }
67
68    function applyRatio(facade, ratio) {
69        var tall = ratio > TALL_THRESHOLD;
70        var portrait = ratio > 1;
71
72        facade.classList.add(tall ? 'yt-facade-tall' : 'yt-facade-wide');
73        facade.dataset.ratio = ratio.toFixed(4);
74
75        var container = facade.closest('.video-container');
76        if (container) {
77            container.classList.toggle('video-container-tall', tall);
78            container.classList.toggle('video-container-portrait', portrait);
79            if (tall) container.style.setProperty('--video-ratio', ratio.toFixed(4));
80        }
81        return tall;
82    }
83
84    function detectOrientation(facade) {
85        // Already done (or done server-side and applied on a previous scan)
86        if (facade.classList.contains('yt-facade-tall') ||
87            facade.classList.contains('yt-facade-wide')) return;
88
89        var id = facade.dataset.id;
90        if (!id || !ID_RE.test(id)) { applyRatio(facade, LANDSCAPE); return; }
91
92        // 1. Explicit value from the server / editor wins - no network probe
93        var explicit = parseFloat(facade.dataset.ratio);
94        if (!(isFinite(explicit) && explicit > 0)) {
95            if (facade.dataset.orientation === 'portrait') explicit = 16 / 9;
96            else if (facade.dataset.orientation === 'landscape') explicit = LANDSCAPE;
97        }
98        if (explicit) {
99            if (applyRatio(facade, explicit)) {
100                // Still want the true-ratio poster instead of the 4:3 one
101                loadOarThumb(id, function (img) { if (img) setPoster(facade, img.src); });
102            }
103            return;
104        }
105
106        // 2. Probe the original-aspect-ratio thumbnail
107        loadOarThumb(id, function (img) {
108            if (!img) { applyRatio(facade, LANDSCAPE); return; }
109            var ratio = img.naturalHeight / img.naturalWidth;
110            if (applyRatio(facade, ratio)) setPoster(facade, img.src);
111        });
112    }
113
114    function scan(root) {
115        var facades = (root || document).querySelectorAll('.yt-facade');
116        for (var i = 0; i < facades.length; i++) detectOrientation(facades[i]);
117    }
118
119    if (document.readyState === 'loading') {
120        document.addEventListener('DOMContentLoaded', function () { scan(); });
121    } else {
122        scan();
123    }
124
125    // Public hook for facades injected after load (mirrors VideoResizer.refresh)
126    window.YTFacade = { scan: scan };
127
128    // ---------- activation (unchanged) ----------
129
130    function activate(facade) {
131        if (facade.querySelector('iframe')) return; // already activated
132
133        var id = facade.dataset.id;
134        if (!id || !ID_RE.test(id)) return;
135
136        var params = 'autoplay=1&rel=0';
137        // Only accept a plain non-negative integer for the start offset
138        if (facade.dataset.start && /^\d{1,6}$/.test(facade.dataset.start)) {
139            params += '&start=' + facade.dataset.start;
140        }
141
142        var titleEl = facade.querySelector('.yt-facade-title');
143        var videoTitle = titleEl ? titleEl.textContent : 'YouTube video player';
144
145        var iframe = document.createElement('iframe');
146        iframe.src = 'https://www.youtube-nocookie.com/embed/' + id + '?' + params;
147        iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture');
148        iframe.setAttribute('allowfullscreen', '');
149        iframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
150        iframe.setAttribute('title', videoTitle);
151        iframe.setAttribute('frameborder', '0');
152
153        facade.replaceChildren(iframe);
154        facade.removeAttribute('role');
155        facade.removeAttribute('tabindex');
156        facade.classList.add('yt-facade-active');
157    }
158
159    document.addEventListener('click', function (e) {
160        var facade = e.target.closest && e.target.closest('.yt-facade');
161        if (facade) {
162            e.preventDefault();
163            activate(facade);
164        }
165    });
166
167    document.addEventListener('keydown', function (e) {
168        if (e.key !== 'Enter' && e.key !== ' ') return;
169        var facade = e.target.closest && e.target.closest('.yt-facade');
170        if (facade) {
171            e.preventDefault();
172            activate(facade);
173        }
174    });
175})();

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.