PageSourceSearch

https://www.mymovies.it/mya/lab-widget.js?v=8

js mymovies.it collected 2026-10-02 03:37:48 UTC 14,356 bytes, 275 lines download raw bytes

1/* =====================================================================
2 * mymovies Lab — Widget "vetrina" di video verticali
3 * Da incorporare in qualsiasi pagina mymovies con una sola riga:
4 *
5 *   <div class="lab-vshelf"
6 *        data-json="https://www.mymovies.it/mya/videos.json"
7 *        data-title="Da vedere in verticale"
8 *        data-limit="12"></div>
9 *   <script src="https://www.mymovies.it/mya/lab-widget.js" defer></script>
10 *
11 * Attributi opzionali sul contenitore:
12 *   data-json   URL del catalogo (default /lab/videos.json) — un domani un endpoint DB
13 *   data-base   base del player (default /lab/) -> link finale /lab/?v=slug
14 *   data-title  titolo della sezione (opzionale)
15 *   data-limit    numero massimo di item (0 = tutti)
16 *   data-per-row  quante card riempiono la larghezza (default 4)
17 *   data-theme    "dark" per pagine a sfondo scuro
18 *
19 * - Anteprima stile Instagram: poster statico, e al passaggio del mouse
20 *   (desktop) o quando entra in vista (mobile) parte un video muto in loop
21 *   sui primi secondi. Caricamento lazy: il video si scarica solo quando serve.
22 * - Classi isolate "labv-" + CSS scoped: non interferisce col sito.
23 * ===================================================================== */
24(function () {
25  if (window.__labVShelfLoaded) return;
26  window.__labVShelfLoaded = true;
27
28  var PREVIEW_SECONDS = 6;                 // durata dell'anteprima prima del loop
29  var canHover = window.matchMedia && window.matchMedia('(hover: hover)').matches;
30
31  var CSS = '' +
32  '.labv-shelf{--labv-accent:#DE1163;--labv-per:4;--labv-gap:12px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;margin:0px 0;max-width:100%;}' +
33  '.labv-shelf *{box-sizing:border-box;}' +
34  '.labv-head{font-size:18px;font-weight:300;margin:0 0 12px 4px;color:#141414;}' +
35  '.labv-wrap{position:relative;max-width:100%;overflow:hidden;}' +
36  '.labv-row{display:flex;gap:var(--labv-gap);overflow-x:auto;padding:4px 2px 8px;scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;scrollbar-width:none;}' +
37  '.labv-row::-webkit-scrollbar{display:none;}' +
38  '.labv-card{flex:0 0 calc((100% - (var(--labv-per) - 1) * var(--labv-gap)) / var(--labv-per));min-width:0;text-decoration:none;color:inherit;scroll-snap-align:start;cursor:pointer;}' +
39  '.labv-arrow{position:absolute;top:42%;transform:translateY(-50%);width:40px;height:40px;border-radius:50%;border:none;background:rgba(20,20,20,.72);color:#fff;font-size:24px;line-height:1;cursor:pointer;z-index:5;display:none;align-items:center;justify-content:center;box-shadow:0 2px 10px rgba(0,0,0,.35);transition:opacity .2s;}' +
40  '.labv-wrap.has-more .labv-arrow{display:flex;}' +
41  '.labv-arrow:disabled{opacity:0;pointer-events:none;}' +
42  '.labv-prev{left:6px;}.labv-next{right:6px;}' +
43  '@media (max-width:700px){.labv-card{flex:0 0 calc((100% - 12px) / 2.2);}.labv-arrow{display:none!important;}.labv-head{font-size:16px;}}' +
44  '.labv-media{position:relative;width:100%;aspect-ratio:9/16;border-radius:14px;overflow:hidden;background:#000;box-shadow:0 4px 14px rgba(0,0,0,.18);transition:box-shadow .2s,transform .2s;}' +
45  '.labv-card:hover .labv-media{box-shadow:0 8px 22px rgba(0,0,0,.30);transform:translateY(-2px);}' +
46  '.labv-poster,.labv-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}' +
47  '.labv-video{opacity:0;transition:opacity .25s;}' +
48  '.labv-card.playing .labv-video{opacity:1;}' +
49  '.labv-badge{position:absolute;top:8px;right:8px;width:26px;height:26px;border-radius:50%;background:rgba(0,0,0,.5);color:#fff;display:flex;align-items:center;justify-content:center;font-size:11px;transition:opacity .2s;}' +
50  '.labv-card.playing .labv-badge{opacity:0;}' +
51  '.labv-grad{position:absolute;left:0;right:0;bottom:0;height:42%;background:linear-gradient(to top,rgba(0,0,0,.55),transparent);pointer-events:none;}' +
52  '.labv-meta{padding:8px 2px 0;}' +
53  '.labv-title{font-size:13.5px;font-weight:600;line-height:1.25;color:#141414;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}' +
54  '.labv-desc{font-size:11.5px;color:#6a6a6a;margin-top:3px;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden;}
54' +
55  '.labv-shelf.labv-dark .labv-head,.labv-shelf.labv-dark .labv-title{color:#fff;}' +
56  '.labv-shelf.labv-dark .labv-desc{color:#b6b6c0;}' +
57  '.mya-open{display:inline-flex;align-items:center;gap:7px;cursor:pointer;border:none;background:#DE1163;color:#fff;font:600 13.5px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;padding:9px 14px;border-radius:24px;text-decoration:none;}' +
58  '.mya-open::before{content:"\\25B6";font-size:11px;}' +
59  '.labv-overlay{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.92);opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;}' +
60  '.labv-overlay.open{opacity:1;}' +
61  '.labv-ov-frame{width:100%;height:100%;max-width:520px;border:0;background:#000;display:block;}';
62
63  function injectCss() {
64    if (document.getElementById('labv-css')) return;
65    var s = document.createElement('style');
66    s.id = 'labv-css';
67    s.textContent = CSS;
68    document.head.appendChild(s);
69  }
70
71  function esc(s) {
72    return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
73      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
74    });
75  }
76
77  function buildCard(v, base) {
78    var slug = v.slug || '';
79    var sep = base.indexOf('?') > -1 ? '&' : '?';
80    var a = document.createElement('a');
81    a.className = 'labv-card';
82    a.href = base + sep + 'v=' + encodeURIComponent(slug);
83    a.setAttribute('aria-label', v.title || 'video');
84    // click: apre l'overlay; lascia passare cmd/ctrl/shift/alt/middle-click e i "apri in nuova scheda"
85    a.addEventListener('click', function (e) {
86      if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
87      e.preventDefault();
88      openOverlay(a.href);
89    });
90    a.innerHTML =
91      '<div class="labv-media">' +
92        (v.poster ? '<img class="labv-poster" loading="lazy" src="' + esc(v.poster) + '" alt="' + esc(v.title) + '">' : '') +
93        '<video class="labv-video" muted loop playsinline preload="none"' +
94          (v.poster ? ' poster="' + esc(v.poster) + '"' : '') +
95          // usa la clip leggera "preview" se presente, altrimenti il video pieno
96          ' data-src="' + esc(v.preview || v.src) + '"></video>' +
97        '<span class="labv-badge">&#9658;</span>' +
98        '<span class="labv-grad"></span>' +
99      '</div>' +
100      '<div class="labv-meta">' +
101        '<div class="labv-title">' + esc(v.title) + '</div>' +
102        (v.description ? '<div class="labv-desc">' + esc(v.description) + '</div>' : '') +
103      '</div>';
104
105    var video = a.querySelector('.labv-video');
106
107    function start() {
108      if (!video.src && video.dataset.src) video.src = video.dataset.src;
109      try { video.currentTime = 0; } catch (e) {}
110      var p = video.play();
111      if (p && p.catch) p.catch(function () {});
112      a.classList.add('playing');
113    }
114    function stop() {
115      video.pause();
116      a.classList.remove('playing');
117    }
118    // limita l'anteprima ai primi secondi, poi riparte (effetto Instagram)
119    video.addEventListener('timeupdate', function () {
120      if (video.currentTime >= PREVIEW_SECONDS) { try { video.currentTime = 0; } catch (e) {} }
121    });
122
123    if (canHover) {
124      a.addEventListener('mouseenter', start);
125      a.addEventListener('mouseleave', stop);
126    }
127    a._start = start;
128    a._stop = stop;
129    return a;
130  }
131
132  // apre il video verticale come OVERLAY (iframe) sopra la pagina; X/Esc/backdrop chiudono
133  function openOverlay(url) {
134    var sep = url.indexOf('?') > -1 ? '&' : '?';
135    var ov = document.createElement('div');
136    ov.className = 'labv-overlay';
137    ov.innerHTML = '<iframe class="labv-ov-frame" src="' + url + sep + 'embed=1" allow="autoplay; fullscreen" allowfullscreen></iframe>';
138    document.body.appendChild(ov);
139    var prevOverflow = document.documentElement.style.overflow;
140    document.documentElement.style.overflow = 'hidden';   // blocca lo scroll della pagina dietro
141    function close() {
142      if (!ov.parentNode) return;
143      ov.parentNode.removeChild(ov);
144      document.documentElement.style.overflow = prevOverflow;
145      window.removeEventListener('message', onMsg);
146      document.removeEventListener('keydown', onKey);
147    }
148    function onMsg(e) { if (e.data === 'mya-close') close(); }   // la X del player chiude l'overlay
149    function onKey(e) { if (e.key === 'Escape') close(); }
150    ov.addEventListener('click', function (e) { if (e.target === ov) close(); });   // click fuori dal video
151    window.addEventListener('message', onMsg);
152    document.addEventListener('keydown', onKey);
153    requestAnimationFrame(function () { ov.classList.add('open'); });
154  }
155
156  // frecce prev/next: scorrono di una "pagina" (larghezza della fila = 4 card)
157  function setupArrows(wrap, row) {
158    wrap.classList.add('has-more');
159    var prev = wrap.querySelector('.labv-prev');
160    var next = wrap.querySelector('.labv-next');
161    function page(dir) { row.scrollBy({ left: dir * row.clientWidth, behavior: 'smooth' }); }
162    prev.addEventListener('click', function () { page(-1); });
163    next.addEventListener('click', function () { page(1); });
164    function update() {
165      prev.disabled = row.scrollLeft <= 4;
166      next.disabled = row.scrollLeft + row.clientWidth >= row.scrollWidth - 4;
167    }
168    row.addEventListener('scroll', update, { passive: true });
169    window.addEventListener('resize', update);
170    update();
171  }
172
173  // su mobile (niente hover): UNA sola anteprima alla volta, quella piu' centrata
174  function setupAutoplay(cards) {
175    if (!('IntersectionObserver' in window)) return;
176    var ratios = new Map();
177    var io = new IntersectionObserver(function (entries) {
178      entries.forEach(function (en) {
179        ratios.set(en.target, en.isIntersecting ? en.intersectionRatio : 0);
180      });
181      var best = null, bestR = 0.6;
182      ratios.forEach(function (r, card) { if (r > bestR) { bestR = r; best = card; } });
183      cards.forEach(function (c) { if (c === best) c._start(); else c._stop(); });
184    }, { threshold: [0, 0.5, 0.75, 1] });
185    cards.forEach(function (c) { io.observe(c); });
186  }
187
188  function render(container) {
189    var url = container.getAttribute('data-json') || '/mya/videos.json';
190    var base = container.getAttribute('data-base') || '/mya/';
191    var title = container.getAttribute('data-title') || '';
192    var limit = parseInt(container.getAttribute('data-limit') || '0', 10);
193    var perRow = parseInt(container.getAttribute('data-per-row') || '4', 10);
194    if (!(perRow >= 1)) perRow = 4;   // quante card riempiono la larghezza (default 4)
195    var dark = (container.getAttribute('data-theme') || '') === 'dark';
196
197    function paint(data) {
198      var vids = (data && data.videos) || [];
199      if (limit > 0) vids = vids.slice(0, limit);
200      if (!vids.length) { container.style.display = 'none'; return; }
201
202      container.classList.add('labv-shelf');
203      container.style.setProperty('--labv-per', perRow);   // numero di card per riga (dinamico)
204      if (dark) container.classList.add('labv-dark');
205      container.innerHTML =
206        (title ? '<div class="labv-head">' + esc(title) + '</div>' : '') +
207        '<div class="labv-wrap">' +
208          '<button class="labv-arrow labv-prev" type="button" aria-label="Precedenti">&#8249;</button>' +
209          '<div class="labv-row"></div>' +
210          '<button class="labv-arrow labv-next" type="button" aria-label="Successivi">&#8250;</button>' +
211        '</div>';
212
213      var wrap = container.querySelector('.labv-wrap');
214      var row = container.querySelector('.labv-row');
215      var cards = [];
216      vids.forEach(function (v) { var c = buildCard(v, base); row.appendChild(c); cards.push(c); });
217      if (!canHover) setupAutoplay(cards);
218
219      // frecce solo se ci sono piu card del numero visibile (su mobile nascoste: si scorre col dito)
220      if (cards.length > perRow) setupArrows(wrap, row);
221    }
222
223    // 1) JSON inline (<script type="application/json" class="labv-data">) — utile per demo/offline
224    var inline = container.querySelector('script.labv-data');
225    if (inline) {
226      try { paint(JSON.parse(inline.textContent)); return; } catch (e) {}
227    }
228    // 2) altrimenti fetch dal catalogo (oggi videos.json, domani endpoint DB)
229    fetch(url, { credentials: 'omit' })
230      .then(function (r) { return r.json(); })
231      .then(paint)
232      .catch(function () { container.style.display = 'none'; });
233  }
234
235  // Schede film: badge cliccabili (.mya-open[data-slug]) + auto-apertura UNA volta per utente.
236  // La scheda film (lato PHP) stampa, se esiste un verticale per quel film:
237  //   <button class="mya-open" data-slug="SLUG">Guarda in verticale</button>
238  //   <script>window.MYA_AUTOPLAY = 'SLUG';</script>   (per l'auto-apertura una tantum)
239  function initFilmTriggers() {
240    var triggers = document.querySelectorAll('.mya-open[data-slug]');
241    for (var i = 0; i < triggers.length; i++) {
242      (function (el) {
243        el.addEventListener('click', function (e) { e.preventDefault(); window.MYA.open(el.getAttribute('data-slug')); });
244      })(triggers[i]);
245    }
246    if (window.MYA_AUTOPLAY) {
247      var seen = false;
248      try { seen = localStorage.getItem('mya_autoseen') === '1'; } catch (e) {}
249      if (!seen) {
250        try { localStorage.setItem('mya_autoseen', '1'); } catch (e) {}
251        setTimeout(function () { window.MYA.open(window.MYA_AUTOPLAY); }, 600); // piccolo ritardo dopo il render
252      }
253    }
254  }
255
256  function init() {
257    injectCss();
258    var nodes = document.querySelectorAll('.lab-vshelf');
259    for (var i = 0; i < nodes.length; i++) render(nodes[i]);
260    initFilmTriggers();
261  }
262
263  // API globale: apri l'overlay verticale da qualsiasi pagina (es. scheda film).
264  //   MYA.open('slug-del-video')  -> apre /mya/?v=slug come overlay
265  window.MYA = window.MYA || {
266    open: function (slug, base) {
267      base = base || '/mya/';
268      var sep = base.indexOf('?') > -1 ? '&' : '?';
269      openOverlay(base + sep + 'v=' + encodeURIComponent(slug));
270    }
271  };
272
273  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
274  else init();
275})();

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.