PageSourceSearch

https://youpick.best/embed/v1/loader.js

js youpick.best collected 2026-10-02 00:45:14 UTC 9,384 bytes, 204 lines download raw bytes

1(function () {
2    'use strict';
3
4    var elements = document.querySelectorAll('[data-ypb-ranking]');
5    if (!elements.length) return;
6
7    // Where the widget lives. Detected from this script's own <script src>
8    // so local and staging builds point at themselves; the constant is the
9    // fallback for host pages whose speed plugins hide that src (WP Rocket
10    // moves it to data-rocket-src, others inline or bundle the tag). With no
11    // fallback an undetected src left baseUrl empty and every iframe loaded
12    // from the blogger's own domain — their 404 page, sized like our widget.
13    var DEFAULT_BASE_URL = 'https://youpick.best';
14
15    // How long the widget gets to report its height before we assume it did
16    // not start and add a plain link under the frame.
17    var FALLBACK_DELAY_MS = 10000;
18
19    var baseUrl = '';
20
21    // Detect base URL from the script src. Strip the query string first —
22    // CMS plugins append cache busters (WordPress: ?ver=1.0.0), and replacing
23    // on the raw src glued the query onto the base URL, pointing every iframe
24    // at the homepage instead of the embed shell.
25    var scripts = document.getElementsByTagName('script');
26    for (var i = 0; i < scripts.length; i++) {
27        var src = (scripts[i].src || '').split('?')[0];
28        if (src.indexOf('/embed/v1/loader.js') !== -1) {
29            baseUrl = src.replace('/embed/v1/loader.js', '');
30            break;
31        }
32    }
33    if (!baseUrl) baseUrl = DEFAULT_BASE_URL;
34
35    // Single delegating message listener for all iframes. The first resize
36    // message is also the proof the widget started, so it lifts the fallback.
37    var iframeMap = new Map();
38    var liveIframes = new Set();
39    window.addEventListener('message', function (event) {
40        if (!event.data || event.data.type !== 'ypb-resize' || typeof event.data.height !== 'number') return;
41        var iframe = iframeMap.get(event.source);
42        if (iframe) {
43            iframe.style.height = event.data.height + 'px';
44            iframe.style.minHeight = '';
45            liveIframes.add(iframe);
46            removeFallbackLink(iframe);
47        }
48    });
49
50    // Reserve the widget's footprint before it loads (no layout shift) and
51    // make the reservation visible: a faint outlined card with a label. The
52    // reservation used to be transparent so light hosts saw no dark flash,
53    // but on a dark theme an empty transparent block reads as a black hole —
54    // exactly what a blogger saw when the iframe never arrived. Grey with
55    // alpha stays quiet on light and dark pages alike, and reads as "loading"
56    // rather than as a gap in the article if the widget never shows up.
57    // The host's own inline styles on the container are saved and put back
58    // when the iframe takes over.
59    var RESERVATION_STYLES = {
60        minHeight: '500px',
61        boxSizing: 'border-box',
62        display: 'flex',
63        alignItems: 'center',
64        justifyContent: 'center',
65        border: '1px solid rgba(128, 128, 128, 0.25)',
66        borderRadius: '12px',
67        background: 'rgba(128, 128, 128, 0.06)',
68        color: 'rgba(128, 128, 128, 0.9)',
69        font: '14px system-ui, -apple-system, sans-serif'
70    };
71    var savedStyles = new Map();
72
73    function reserveSpace(el) {
74        var saved = {};
75        for (var prop in RESERVATION_STYLES) {
76            saved[prop] = el.style[prop];
77            el.style[prop] = RESERVATION_STYLES[prop];
78        }
79        savedStyles.set(el, saved);
80        el.textContent = 'Loading YouPickBest ranking\u2026';
81    }
82
83    // Undo reserveSpace once the iframe takes over, so the container is the
84    // host's plain block again and the widget's card is the only visible frame.
85    function releaseSpace(el) {
86        el.innerHTML = '';
87        var saved = savedStyles.get(el);
88        if (!saved) return;
89        for (var prop in saved) {
90            el.style[prop] = saved[prop];
91        }
92        savedStyles.delete(el);
93    }
94
95    // If the widget has not reported its height FALLBACK_DELAY_MS after the
96    // iframe was created, it did not start — blocked frame, ad blocker,
97    // corporate proxy, whatever the cause. Add a plain link under the frame
98    // so the reader still reaches the ranking and the blogger sees the block
99    // is alive. The iframe stays: on a slow connection the widget may still
100    // arrive, and the resize listener then removes the link again, so the
101    // worst case is one extra line for a few seconds.
102    function scheduleFallbackLink(el, iframe, slug) {
103        window.setTimeout(function () {
104            if (liveIframes.has(iframe) || !iframe.parentNode) return;
105            var link = document.createElement('a');
106            link.href = baseUrl + '/ranking/' + encodeURIComponent(slug);
107            link.target = '_blank';
108            link.rel = 'noopener';
109            link.setAttribute('data-ypb-fallback', '');
110            link.textContent = 'Open this ranking on YouPickBest →';
111            link.style.display = 'block';
112            link.style.marginTop = '8px';
113            el.appendChild(link);
114        }, FALLBACK_DELAY_MS);
115    }
116
117    function removeFallbackLink(iframe) {
118        var container = iframe.parentNode;
119        if (!container) return;
120        var link = container.querySelector('[data-ypb-fallback]');
121        if (link) container.removeChild(link);
122    }
123
124    function createIframe(el) {
125        var slug = el.getAttribute('data-ypb-ranking');
126        if (!slug) return;
127
128        var utmSource = el.getAttribute('data-ypb-utm-source') || '';
129        // The referrer param keys the widget's per-page audience pool, so it
130        // must be the page hosting the widget. document.referrer is where the
131        // visitor came FROM (search engine, social) — using it first split one
132        // post's votes into per-traffic-source pools.
133        var referrer = encodeURIComponent(window.location.href || document.referrer);
134        var params = '?referrer=' + referrer;
135        if (utmSource) params += '&utm_source=' + encodeURIComponent(utmSource);
136
137        var iframe = document.createElement('iframe');
138        iframe.src = baseUrl + '/embed/ranking/' + encodeURIComponent(slug) + params;
139        iframe.style.width = '100%';
140        iframe.style.minHeight = '500px';
141        iframe.style.border = 'none';
142        iframe.style.borderRadius = '12px';
143        // No color-scheme: forcing one makes the browser paint an opaque
144        // backdrop behind the transparent widget (white via mismatch, or black
145        // when forced dark) on the host page. Transparent background + no
146        // color-scheme lets the host show through; the card has its own dark fill.
147        iframe.style.background = 'transparent';
148        // No loading="lazy": the IntersectionObserver below is the only lazy
149        // gate. Stacking the browser's own deferral on top re-delays the load
150        // by its per-browser distance heuristic (Firefox's is smaller than our
151        // preload margin), letting a scrolling reader catch the widget half-loaded.
152        iframe.setAttribute('allow', 'clipboard-write');
153        iframe.setAttribute('title', 'YouPickBest Ranking Poll');
154
155        releaseSpace(el);
156        el.appendChild(iframe);
157        scheduleFallbackLink(el, iframe, slug);
158
159        // Register the iframe's window for resize messages immediately, not on
160        // its load event. The WindowProxy returned by contentWindow is stable
161        // across the iframe's navigation, so it already equals the event.source
162        // the widget posts from. Registering on load instead raced the widget's
163        // first resize message — if that message arrived first it was dropped,
164        // leaving the iframe stuck at min-height (clipped widget in Safari and
165        // some Brave builds). The load listener stays as a belt-and-suspenders.
166        if (iframe.contentWindow) {
167            iframeMap.set(iframe.contentWindow, iframe);
168        }
169        iframe.addEventListener('load', function () {
170            if (iframe.contentWindow) {
171                iframeMap.set(iframe.contentWindow, iframe);
172            }
173        });
174    }
175
176    // Lazy loading with IntersectionObserver
177    if ('IntersectionObserver' in window) {
178        var observer = new IntersectionObserver(function (entries) {
179            for (var i = 0; i < entries.length; i++) {
180                if (entries[i].isIntersecting) {
181                    createIframe(entries[i].target);
182                    observer.unobserve(entries[i].target);
183                }
184            }
185        // 1500px ≈ 1.5–2 screens of runway: the widget must be fully rendered
186        // by the time a reader scrolls to it (a visible pop-in reads as a
187        // glitchy foreign element), while readers who never scroll down still
188        // cost no embed request. 200px started the load too late to win that race.
189        }, { rootMargin: '1500px' });
190
191        for (var j = 0; j < elements.length; j++) {
192            // No slug, nothing to load: don't promise a widget that never comes.
193            if (elements[j].getAttribute('data-ypb-ranking')) {
194                reserveSpace(elements[j]);
195            }
196            observer.observe(elements[j]);
197        }
198    } else {
199        // Fallback: load all immediately
200        for (var k = 0; k < elements.length; k++) {
201            createIframe(elements[k]);
202        }
203    }
204})();

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.