Cargando sitio

Innovision TV

Cargando …

RP
Innovision TVStreaming

Destacados

'; var frame = document.createElement('iframe'); frame.width = String(w); frame.height = String(h); frame.title = 'Publicidad'; frame.setAttribute('scrolling', 'no'); frame.setAttribute('frameborder', '0'); frame.referrerPolicy = 'strict-origin-when-cross-origin'; frame.srcdoc = doc; return frame; } function runAdCountdown(label, seconds, done){ var left = seconds; label.textContent = 'Contenido disponible en ' + left + ' segundos'; var timer = setInterval(function(){ left--; label.textContent = left > 0 ? 'Contenido disponible en ' + left + ' segundos' : 'Cargando contenido…'; if (left <= 0) { clearInterval(timer); done(); } }, 1000); } function showPlayAd(){ return new Promise(function(resolve){ var player = document.getElementById('mPlayer'); var sessionId = 'play-' + Math.random().toString(36).slice(2) + '-' + Date.now(); player.innerHTML = '
Publicidad de Routicket

Cargando anuncio…

'; var label = document.getElementById('playAdCountdown'), creative = document.getElementById('playAdCreative'); if (Math.random() < 0.5) { var box = document.createElement('div'); box.style.cssText = 'display:flex;justify-content:center;margin:12px auto'; box.appendChild(hrfAdFrame()); if (creative) creative.appendChild(box); runAdCountdown(label, 5, resolve); return; } fetch(AD_API + '?action=serve&placement=routicket-play&session_id=' + encodeURIComponent(sessionId), {cache:'no-store'}).then(function(r){return r.json();}).then(function(ad){ if (!ad || !ad.ok) { resolve(); return; } if (creative) creative.innerHTML = '' + (ad.image_url ? 'Anuncio' : '') + esc(ad.title || 'Ver promoción') + ''; runAdCountdown(label, 5, function(){ var fd = new FormData(); fd.append('token', ad.token); fetch(AD_API + '?action=complete', {method: 'POST', body: fd}).finally(resolve); }); }).catch(resolve); }); } function copyText(t){ if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(t).then(function(){ showToast('Enlace copiado'); }); var ta = document.createElement('textarea'); ta.value = t; ta.style.position='fixed'; ta.style.opacity='0'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); showToast('Enlace copiado'); } catch(_){} document.body.removeChild(ta); return Promise.resolve(); } function permalink(it){ var base = location.origin + location.pathname; if (it._src === 'tmdb') return base + '?q=' + encodeURIComponent(it.name || '') + '&title=' + encodeURIComponent(it.name || ''); var key = (it.id && String(it.id) !== '0') ? it.id : (isUrl(it.code) ? it.id : it.code); return base + '?v=' + encodeURIComponent(key) + '&title=' + encodeURIComponent(it.name || ''); } function setDocumentTitle(name){ var clean = String(name || '').trim(); document.title = clean ? clean + ' | ' + DEFAULT_DOCUMENT_TITLE : DEFAULT_DOCUMENT_TITLE; } function share(it){ var url = permalink(it), name = it.name || ''; if (navigator.share) { navigator.share({ title: name, text: name, url: url }).catch(function(){}); } else { copyText(url); } gaEvent('content_share', {content_source: it._src || 'local', share_method: navigator.share ? 'native' : 'copy'}); } function setPlayLoading(btn, on){ btn.classList.toggle('disabled', !!on); btn.innerHTML = on ? '' : ''; } function esc(s){ return String(s==null?'':s).replace(/[&<>"']/g, function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];}); } function fixText(s){ var value=String(s||''); if(!/[ÃÂ]/.test(value)) return value; try{return decodeURIComponent(escape(value));}catch(_){return value;} } function isUrl(v){ return /^https?:\/\//i.test(String(v||'')); } function isYoutube(it){ var s = String((it.code||'')+' '+(it.url||'')+' '+(it.img||'')).toLowerCase(); return /youtube\.com|youtu\.be|ytimg\.com/.test(s); } function parseVideoUrl(raw){ var url=String(raw||'').trim(), parsed; try{ parsed=new URL(url); }catch(_){ return null; } if (/youtube\.com|youtu\.be/i.test(parsed.hostname)) { var id=parsed.hostname==='youtu.be'?parsed.pathname.slice(1):(parsed.searchParams.get('v')||parsed.pathname.split('/').filter(Boolean).pop()); if (!id) return null; return {url:url,video_url:'https://www.youtube.com/embed/'+encodeURIComponent(id)+'?rel=0',video_kind:'Video YouTube',domain:'youtube'}; } if (/facebook\.com|fb\.watch/i.test(parsed.hostname)) return {url:url,video_url:'https://www.facebook.com/plugins/video.php?href='+encodeURIComponent(url)+'&show_text=0',video_kind:'Video Facebook',domain:'facebook'}; return null; } function saveCustomVideo(rawUrl,title,meta){ var parsed=parseVideoUrl(rawUrl); if(!parsed) return false; var list=loadCustomVideos().filter(function(x){return x.url!==parsed.url;}); meta=meta||{}; list.unshift({id:'custom-'+Date.now(),key:parsed.url,name:title||meta.title||'Video guardado',desc:meta.description||'Video añadido por ti',img:meta.image||'',code:parsed.url,url:parsed.url,video_url:parsed.video_url,video_kind:parsed.video_kind,_src:'local'}); try{localStorage.setItem(CUSTOM_VIDEOS_KEY,JSON.stringify(list.slice(0,100)));}catch(_){return false;} CATALOG = CATALOG.filter(function(x){return x._src!=='local'||!x.id||String(x.id).indexOf('custom-')!==0;}).concat(list); return true; } function widgetUrl(u){ return WIDGET + encodeURIComponent(u); } function fmtViews(n){ n=+n||0; return n>=1000 ? (n/1000).toFixed(n%1000?1:0)+'k' : String(n); } function setStatus(msg, err){ statusEl.hidden = !msg; statusEl.textContent = msg||''; statusEl.classList.toggle('error', !!err); } function openPlay(it){ open(it); } /* ── Gustos del usuario (localStorage) + relevancia ── */ var PREFS_KEY='rtkPlayPrefs_v1', RECENTS_KEY='rtkPlayRecents_v1', STOP=['de','la','el','los','las','un','una','unos','unas','del','en','y','a','al','para','por','con','sin','es','lo','que','se','su','sus','cuando','como','más','mas','ser']; function loadRecents(){ try{ return JSON.parse(localStorage.getItem(RECENTS_KEY)||'[]'); }catch(_){ return []; } } function saveRecents(a){ try{ localStorage.setItem(RECENTS_KEY, JSON.stringify(a)); }catch(_){} } function addRecent(it){ if (it._src === 'tmdb') return; // externas no se reproducen var recents = loadRecents().filter(function(r){ return r.key !== (it.id || it.code); }); recents.unshift({ key: it.id || it.code, id: it.id, code: it.code, url: it.url, name: it.name, img: it.img, views: it.views || 0, _src: it._src }); saveRecents(recents.slice(0, 12)); renderRecents(); } function renderRecents(){ var recents = loadRecents(), el = document.getElementById('recents'), list = document.getElementById('rlist'); if (!recents.length){ el.hidden = true; return; } el.hidden = false; list.innerHTML = recents.map(function(r){ var img = r.img ? ''+esc(r.name)+'' : ''; return '
'+(img||''+esc((r.name||'?').slice(0,2).toUpperCase())+'')+'
'+esc(r.name)+'
'; }).join(''); } document.getElementById('rlist').addEventListener('click', function(e){ var c = e.target.closest('.rcard'); if(!c) return; var rk = c.getAttribute('data-rk'); if (e.target.closest('.rdel')){ saveRecents(loadRecents().filter(function(x){ return String(x.key) !== rk; })); renderRecents(); gaEvent('recents_remove'); return; } var r = loadRecents().filter(function(x){ return String(x.key) === rk; })[0]; if (!r) return; var full = CATALOG.filter(function(m){ return String(m.id) === rk || (m.code && String(m.code) === rk); })[0]; lastCard = c; open(full || r); }); document.getElementById('forget').addEventListener('click', function(){ try{ localStorage.removeItem(PREFS_KEY); localStorage.removeItem(RECENTS_KEY); }catch(_){} renderRecents(); showToast('Preferencias borradas'); state.seq++; run(state.seq); gaEvent('preferences_reset'); }); function showSkeleton(){ grid.innerHTML = Array.from({length:6}).map(function(){ return '
'; }).join(''); grid.className = 'grid skel'; } function hideSkeleton(){ grid.className = 'grid'; } function loadPrefs(){ try{ return JSON.parse(localStorage.getItem(PREFS_KEY)||'{}').kw || {}; }catch(_){ return {}; } } function savePrefs(kw){ try{ localStorage.setItem(PREFS_KEY, JSON.stringify({kw:kw,t:Date.now()})); }catch(_){} } function terms(text){ return String(text||'').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').split(/[^a-z0-9]+/).filter(function(t){ return t.length>2 && STOP.indexOf(t)<0; }); } function remember(text, w){ w = w||2; if (!text) return; var kw = loadPrefs(), ts = terms(text), max = 0, changed = false; ts.forEach(function(x){ kw[x]=(kw[x]||0)+w; if(kw[x]>max)max=kw[x]; changed=true; }); if (changed && max > 40){ for(var k in kw){ kw[k]=Math.round(kw[k]*40/max); } } if (changed) savePrefs(kw); } function hasImg(it){ return !!it.img; } function relevance(it){ var kw = loadPrefs(), s = 0; terms(it.name).forEach(function(t){ s += (kw[t]||0); }); if (it.desc) terms(it.desc).forEach(function(t){ s += (kw[t]||0)*0.6; }); return s; } function seededScore(value, seed){ var str = (seed || VISIT_SEED) + ':' + String(value || ''); var h = 2166136261; for (var i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); } return ((h >>> 0) % 10000) / 10000; } // Inicio: foto + gustos + variación estable por hora/visita. Búsqueda: precisión. function rank(list, opts){ opts = opts || {}; var randomize = !!opts.randomize; var mapped = list.map(function(it, i){ return { it: it, i: i, img: hasImg(it) ? 0 : 1, rel: relevance(it), views: +it.views || 0, rnd: seededScore((it.id || it.code || it.name || i) + ':' + i) }; }); mapped.sort(function(a,b){ if (a.img !== b.img) return a.img - b.img; if (randomize) { var rel = (b.rel - a.rel); if (Math.abs(rel) > 2) return rel; return (b.rnd - a.rnd) || ((b.views > 0) - (a.views > 0)) || (a.i-b.i); } return (b.rel-a.rel) || (a.i-b.i); }); return mapped.map(function(m){ return m.it; }); } function featuredList(list){ return rank((list || CATALOG).filter(function(m){ return m && m.img; }), {randomize:true}).slice(0, 10); } function poster(it, yt, num, loading){ var mono = esc((it.name||'?').trim().split(/\s+/)[0].slice(0,2).toUpperCase()); // Si la foto no carga (YouTube arruinado u otra rota), ocultamos la tarjeta para no mostrar rotas. var onerr = "var c=this.closest('.card');if(c)c.style.display='none';"; var img = it.img ? (''+esc(it.name)+'') : ''; return '
'+mono+''+img+'
'+(num?''+num+'':'')+'
'; } var SRC_META = { local:['loc','Mi catálogo'], rtk:['rtk','Routicket'], rss:['rss','RSS externo'], tmdb:['tmx','Externas'] }; function tag(src){ var m=SRC_META[src]||SRC_META.local; return ''+m[1]+''; } function videoTag(it){ return it.video_url ? '🎬 '+esc(it.video_kind||'Video')+'' : ''; } // Aplica el filtro/orden elegido sobre la base ordenada (foto primero + relevancia). function applyFilters(){ var out = (state.base || []).slice(); if (state.filter === 'photo') out = out.filter(hasImg); else if (state.filter === 'mine') out = out.filter(isInMyList); else if (state.filter === 'views') // Más vistos: views reales primero; si hay empate (o todo es 0, como en catálogos // que aún no registran reproducciones) usamos un orden determinista por nombre. // Así "Más vistos" no repite el shuffle aleatorio de "Destacados". out = out.slice().sort(function(a,b){ var v = ((+b.views||0) - (+a.views||0)); if (v) return v; return seededScore(b.name+'#'+b.id, 'views') - seededScore(a.name+'#'+a.id, 'views'); }); state.results = out; return out; } document.getElementById('chips').addEventListener('click', function(e){ var b = e.target.closest('.chip'); if(!b) return; document.querySelectorAll('#chips .chip').forEach(function(x){ x.classList.toggle('on', x===b); }); state.filter = b.getAttribute('data-f'); state.shown = 40; applyFilters(); render(); gaEvent('catalog_filter', {filter: state.filter}); }); function render(list){ hideSkeleton(); list = list || state.results; var shown = state.shown >= 0 ? list.slice(0, state.shown) : list; countEl.textContent = list.length ? (list.length + ' resultados') : ''; grid.innerHTML = shown.map(function(it, j){ var yt = isYoutube(it); var i = list.indexOf(it); var delay = Math.min(j * 35, 420); var loading = j < 14 ? 'eager' : 'lazy'; return '
'+ poster(it, yt, i<10 ? i+1 : 0, loading)+ '
'+esc(it.name)+'
'+ '
'+esc(it.desc||'')+'
'+ '
'+tag(it._src)+videoTag(it)+ ''+(it.views?'👁 '+fmtViews(it.views):(it.code?'▶ '+esc(it.code):'HD'))+'
'+ '
'+ '
'; }).join(''); // Precarga suave: pide las imágenes siguientes a las visibles para que carguen antes. prefetchImages(list, state.shown); emptyEl.hidden = list.length > 0; statusEl.hidden = true; } function prefetchImages(list, from){ if (!list) return; var end = Math.min((from||0) + 24, list.length); for (var i = (from||0); i < end; i++){ var img = list[i] && list[i].img; if (img){ var im = new Image(); im.src = img; } } } function renderBillboard(){ var f = state.featured, i = state.fb % Math.max(1, f.length), it = f[i]; if (!f.length) { billboard.style.display='none'; return; } billboard.style.display='block'; document.getElementById('bbBg').style.backgroundImage = it.img ? ('url("'+it.img.replace(/"/g,'\\"')+'")') : 'none'; document.getElementById('bbTitle').textContent = it.name || ''; document.getElementById('bbIdx').textContent = (i+1) + ' de ' + f.length; document.getElementById('bbPlay').onclick = function(){ lastCard = null; gaEvent('hero_play', {content_position: i + 1}); openPlay(it); }; document.getElementById('bbInfo').onclick = function(){ lastCard = null; gaEvent('hero_info', {content_position: i + 1}); openPlay(it); }; var dots = document.getElementById('bbDots'); dots.innerHTML = ''; f.forEach(function(_, j){ var b=document.createElement('button'); b.className = j===i?'on':''; b.onclick=function(){ state.fb=j; renderBillboard(); restartFeatured(); }; dots.appendChild(b); }); } function startFeatured(){ if (!state.featured.length) return; state.featuredTimer = setInterval(function(){ state.fb++; renderBillboard(); }, 6000); } function restartFeatured(){ clearInterval(state.featuredTimer); startFeatured(); } function localResults(q){ q = (q||'').toLowerCase(); var list = q ? CATALOG.filter(function(m){ return (m.name||'').toLowerCase().indexOf(q)>-1 || (m.code||'').toLowerCase().indexOf(q)>-1; }) : CATALOG.slice(); return list; } function fetchJson(url){ return fetch(url, {credentials:'same-origin'}).then(function(r){ if(!r.ok) throw 0; return r.json(); }); } function fetchRtk(q){ var url = SEARCH_GO + (q ? '?query='+encodeURIComponent(q)+'&sort=recent' : '?mode=code&sort=recent'); return fetchJson(url).then(function(rows){ return (Array.isArray(rows)?rows:[]).map(function(r){ return { id:r.id, name:r.text||r.code||'Contenido', img:r.foto, code:r.code, views:r.vistoxs||0, url:(isUrl(r.code)?r.code:''), video_url:r.video_url||r.trailer_url||r.youtube_url||'', video_kind:'Video', _src:'rtk' }; }); }).catch(function(){ return []; }); } function fetchRss(q){ var term = (q || '').toLowerCase(); return Promise.all(RSS_FEEDS.map(function(feed){ return fetchJson(feed.url + '?limit=300').then(function(payload){ var data = payload && payload.data && typeof payload.data === 'object' ? payload.data : payload; var rows = (data && Array.isArray(data.videos) ? data.videos : []).filter(function(row){ var source = String((row && row.url) || '') + ' ' + String((row && row.embedUrl) || ''); return !feed.youtubeOnly || /youtube\.com|youtu\.be|youtube-nocookie\.com/i.test(source); }).map(function(row){ return {kind:'video', row:row}; }); return rows.map(function(entry, index){ var row = entry.row || {}, kind = entry.kind; var name = row.name || row.title || row.nombre || ('Contenido RSS #' + (row.id || index)); var desc = row.description || row.descripcion || row.type || row.category || ''; var image = row.image || row.thumbnail || row.fallbackImage || row.foto || ((row.url && /\.(jpe?g|png|webp|gif)(\?|$)/i.test(row.url)) ? row.url : ''); var url = row.url || row.permalink || row.contactUrl || ''; var videoUrl = row.embedUrl || row.video_url || ''; return {id:'rss-'+feed.source.toLowerCase().replace(/\W+/g,'-')+'-'+(row.id || index),name:String(name),img:String(image || ''),desc:String(desc),code:String(url),url:String(url),views:Number(row.views || 0),video_url:String(videoUrl),video_kind:String(row.type || ('Video '+feed.source)),rss_source:feed.source,_src:'rss'}; }); }).catch(function(){ return []; }); })).then(function(parts){ return [].concat.apply([], parts).filter(function(item){ return !term || (item.name+' '+item.desc+' '+item.code).toLowerCase().indexOf(term) >= 0; }); }); } function addTmdbTrailers(items){ return Promise.all((items||[]).map(function(it){ if (!it.id || it.media_type === 'person') return Promise.resolve(it); var media = it.media_type === 'tv' ? 'tv' : 'movie'; return fetchJson(SEARCH_TMDB + '?action=videos&movie_id='+encodeURIComponent(it.id)+'&media='+media+'&language=es-MX').then(function(data){ if ((data.results||[]).length) return data; return fetchJson(SEARCH_TMDB + '?action=videos&movie_id='+encodeURIComponent(it.id)+'&media='+media+'&language=en-US'); }).then(function(data){ var videos = (data.results||[]).filter(function(v){ return v.site === 'YouTube' && v.key; }); var chosen = videos.find(function(v){ return String(v.type||'').toLowerCase() === 'trailer'; }) || videos.find(function(v){ return String(v.type||'').toLowerCase() === 'teaser'; }) || videos[0]; if (chosen) { it.video_url = 'https://www.youtube.com/embed/'+encodeURIComponent(chosen.key)+'?rel=0'; it.video_kind = String(chosen.type||'Tráiler'); } return it; }).catch(function(){ return it; }); })); } function fetchTmdb(q){ // En home sin búsqueda usamos tendencia popular para dar más resultados y variados. if (!q || q.length < 2) return fetchJson(SEARCH_TMDB + '?action=trending&type=movie&media=week&page=1&language=es-MX').then(function(data){ return addTmdbTrailers((data.results||[]).slice(0, 30).map(function(r){ return { id:r.id, name:r.title || r.name || 'Película', img: r.poster_path ? 'https://image.tmdb.org/t/p/w342' + r.poster_path : '', code:'', views:r.vote_count||0, url:'', media_type:r.media_type||'movie', _src:'tmdb' }; })); }).catch(function(){ return []; }); return fetchJson(SEARCH_TMDB + '?action=search&type=multi&query='+encodeURIComponent(q)+'&language=es-MX®ion=MX').then(function(data){ return addTmdbTrailers((data.results||[]).slice(0, 20).map(function(r){ var t = r.title || r.name || 'Película'; return { id:r.id, name:t, img: r.poster_path ? 'https://image.tmdb.org/t/p/w342' + r.poster_path : '', code:'', views:r.vote_count||0, url:'', media_type:r.media_type||'movie', _src:'tmdb' }; })); }).catch(function(){ return []; }); } function run(seq){ var q = (qEl ? qEl.value : '').trim(); state.query = q; if (q && q.length > 2) remember(q, 1); // aprendemos de lo que busca el usuario var src = state.src, reqs = [], showStatus = false; // Live-rotate del hero solo en el modo de inicio sin búsqueda. var home = (src==='all' && !q) || (src==='local' && !q); if (!home) { state.featured = []; billboard.style.display='none'; clearInterval(state.featuredTimer); } state.fb = 0; if (src==='all' || src==='local') reqs.push({k:'local', p:localResults(q)}); if (src==='all' || src==='rtk') { reqs.push({k:'rtk', p:fetchRtk(q)}); if (q) showStatus=true; } if (src==='all' || src==='rss') { reqs.push({k:'rss', p:fetchRss(q)}); if (q) showStatus=true; } if (src==='all' || src==='tmdb') { reqs.push({k:'tmdb', p:fetchTmdb(q)}); if (q) showStatus=true; } sectionTitle.textContent = q ? ('Resultados para «' + q + '»') : ( src==='all' ? 'Destacados' : src==='local' ? 'Mi catálogo' : src==='rtk' ? 'Contenido Routicket' : src==='rss' ? 'RSS externo' : 'Películas externas'); if (showStatus) setStatus(' Buscando…'); showSkeleton(); var results = []; Promise.all(reqs.map(function(r){ return r.p; })).then(function(parts){ if (seq !== state.seq) return; parts.forEach(function(part, i){ results = results.concat(part.map(function(it){ it._src = it._src || reqs[i].k; return it; })); }); state.base = rank(results, {randomize: home && !q}); if (home) state.featured = featuredList(state.base); state.shown = 40; applyFilters(); render(); renderBillboard(); if (home && !state.featuredTimer) startFeatured(); }); } var debounce; function onInput(){ clearTimeout(debounce); debounce = setTimeout(function(){ state.seq++; run(state.seq); }, 280); } function open(it){ gaEvent('content_open', {content_source: it._src || 'local', has_image: !!it.img}); remember(it.name, 5); remember(it.desc, 2); remember(it.code, 1); // aprende gustos al reproducir addRecent(it); setDocumentTitle(it.name); document.getElementById('mTitle').textContent = it.name || ''; var sm = SRC_META[it._src] || SRC_META.local; document.getElementById('mSrc').textContent = sm[1] + (it.views ? ' · 👁 ' + fmtViews(it.views) : ''); var md = document.getElementById('mDesc'); md.hidden = !(it.desc && String(it.desc).trim()); md.textContent = it.desc || ''; var player = document.getElementById('mPlayer'), mPlay = document.getElementById('mPlay'); player.innerHTML = ''; // destruye reproductores previos (corta el audio) modal.classList.add('open'); armIdle(); document.getElementById('mClose').focus(); setPlayLoading(mPlay, true); player.innerHTML = '
Cargando reproductor…
'; showPlayAd().then(function(){ // URL dinámica compartible + controles copiar/compartir. var url = permalink(it); if (it._src !== 'tmdb') history.replaceState(null, '', url); document.getElementById('mCopy').onclick = function(){ copyText(url); gaEvent('content_copy_link', {content_source: it._src || 'local'}); }; document.getElementById('mShare').onclick = function(){ share(it); }; var listButton = document.getElementById('mList'); if (listButton) { listButton.textContent = isInMyList(it) ? '♥' : '♡'; listButton.title = isInMyList(it) ? 'Quitar de Mi lista' : 'Añadir a Mi lista'; listButton.onclick = function(){ var added=toggleMyList(it); listButton.textContent=added?'♥':'♡'; listButton.title=added?'Quitar de Mi lista':'Añadir a Mi lista'; showToast(added?'Añadido a Mi lista':'Quitado de Mi lista'); }; } if (it._src === 'tmdb') { if (it.video_url) { player.innerHTML = ''; setPlayLoading(mPlay, false); mPlay.onclick = function(){ window.open(it.video_url,'_blank'); gaEvent('trailer_play'); }; } else { player.innerHTML = '
No encontramos tráiler disponible para este título.
'; setPlayLoading(mPlay, false); mPlay.onclick = function(){ qEl.value = it.name; state.seq++; run(state.seq); modal.classList.remove('open'); gaEvent('external_title_search'); }; } document.getElementById('mReport').href = 'https://www.themoviedb.org/movie/' + it.id; return; } var resolvePlay = function(purl){ if (!purl) return fetchJson(SOURCES + '?code=' + encodeURIComponent(it.id)).then(function(d){ var i = d && d.items && d.items[0]; var u = i && (i.play_url || i.url); return isUrl(u) ? u : ''; }).catch(function(){ return ''; }); return Promise.resolve(purl); }; var srcUrl = it.url || (isUrl(it.code) ? it.code : ''); if (it.video_url) { player.innerHTML = ''; setPlayLoading(mPlay, false); mPlay.onclick = function(){ window.open(it.video_url,'_blank'); gaEvent('custom_video_play'); }; document.getElementById('mReport').href = it.url || '#'; return; } resolvePlay(srcUrl).then(function(final){ if (!final) { player.innerHTML = '
No se pudo resolver la fuente.
Abrir reproductor Routicket
'; setPlayLoading(mPlay, false); mPlay.onclick = function(){ window.open(REPRODUCTOR+'?id='+it.id,'_blank'); gaEvent('player_fallback_open', {content_source: it._src || 'local'}); }; document.getElementById('mReport').href = '/plugins/movies/reporte.php?id=' + it.id; return; } var w = widgetUrl(final); player.innerHTML = ''; setPlayLoading(mPlay, false); mPlay.onclick = function(){ window.open(w,'_blank'); gaEvent('play_start', {content_source: it._src || 'local'}); }; document.getElementById('mReport').href = '/plugins/movies/reporte.php?url=' + encodeURIComponent(final); }); }); } grid.addEventListener('click', function(e){ var card = e.target.closest('.card'); if(!card) return; lastCard = card; var it = state.results[+card.getAttribute('data-i')]; if (it) open(it); }); grid.addEventListener('keydown', function(e){ if (e.key !== 'Enter' && e.key !== ' ') return; var card = e.target.closest('.card'); if(!card) return; e.preventDefault(); lastCard = card; var it = state.results[+card.getAttribute('data-i')]; if (it) open(it); }); document.getElementById('go').addEventListener('click', function(){ state.seq++; run(state.seq); gaEvent('catalog_search', {query_length: qEl.value.trim().length, method: 'button'}); }); qEl.addEventListener('input', onInput); qEl.addEventListener('keydown', function(e){ if(e.key==='Enter'){ clearTimeout(debounce); state.seq++; run(state.seq); gaEvent('catalog_search', {query_length: qEl.value.trim().length}); } }); var addVideoModal=document.getElementById('addVideoModal'), addVideoUrl=document.getElementById('addVideoUrl'), addVideoTitle=document.getElementById('addVideoTitle'); document.getElementById('addVideoBtn').addEventListener('click',function(){ addVideoModal.classList.add('open'); addVideoModal.setAttribute('aria-hidden','false'); addVideoUrl.focus(); }); document.getElementById('addVideoCancel').addEventListener('click',function(){ addVideoModal.classList.remove('open'); addVideoModal.setAttribute('aria-hidden','true'); }); document.getElementById('addVideoSave').addEventListener('click',async function(){ var raw=addVideoUrl.value.trim(), title=addVideoTitle.value.trim(), parsed=parseVideoUrl(raw); if(!parsed){showToast('Pega una URL válida de YouTube o Facebook');return;} var meta={}; try{var og=await fetch('/generador/v2/fetch_og.php?url='+encodeURIComponent(raw),{cache:'no-store'}).then(function(r){return r.json()});if(og&&og.success)meta=og.data||{};}catch(_){} if(!saveCustomVideo(raw,title,meta)){showToast('No se pudo guardar el video');return;} addVideoModal.classList.remove('open');addVideoModal.setAttribute('aria-hidden','true');addVideoUrl.value='';addVideoTitle.value='';state.seq++;run(state.seq);showToast('Video añadido con sus datos'); }); addVideoModal.addEventListener('click',function(e){if(e.target===addVideoModal) addVideoModal.classList.remove('open');}); function closeModal(){ modal.classList.remove('open'); modal.classList.remove('cinema'); clearTimeout(idleTimer); var p = document.getElementById('mPlayer'); if (p) p.innerHTML = ''; setDocumentTitle(''); if (lastCard && document.body.contains(lastCard)) lastCard.focus(); } document.addEventListener('keydown', function(e){ if (e.key === 'Escape' && modal.classList.contains('open')) { closeModal(); return; } var bbOn = billboard.style.display !== 'none'; if (e.key === 'ArrowLeft' && bbOn){ state.fb--; renderBillboard(); restartFeatured(); } if (e.key === 'ArrowRight' && bbOn){ state.fb++; renderBillboard(); restartFeatured(); } if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')){ e.preventDefault(); qEl.focus(); qEl.select(); } }); /* ── Modo cine (oculta controles, no agranda) + toggles ── */ var idleTimer = null; function armIdle(){ clearTimeout(idleTimer); idleTimer = setTimeout(function(){ if (modal.classList.contains('open')) modal.classList.add('cinema'); }, IDLE_CINEMA); } function activity(){ modal.classList.remove('cinema'); armIdle(); } modal.addEventListener('mousemove', activity); modal.addEventListener('mousedown', activity); modal.addEventListener('touchstart', activity); modal.addEventListener('keydown', activity); document.getElementById('mCinema').addEventListener('click', function(){ modal.classList.toggle('cinema'); clearTimeout(idleTimer); gaEvent('player_cinema_toggle', {enabled: modal.classList.contains('cinema')}); if (modal.classList.contains('cinema')) idleTimer = setTimeout(function(){ modal.classList.remove('cinema'); }, IDLE_CINEMA); }); document.getElementById('mExpand').addEventListener('click', function(e){ modal.classList.toggle('expand'); e.stopPropagation(); gaEvent('player_expand_toggle', {enabled: modal.classList.contains('expand')}); }); document.getElementById('mClose').addEventListener('click', closeModal); modal.addEventListener('click', function(e){ if(e.target===modal) closeModal(); }); tabs.forEach(function(b){ b.addEventListener('click', function(){ tabs.forEach(function(x){ x.classList.toggle('on', x===b); }); state.src = b.getAttribute('data-src'); state.seq++; run(state.seq); gaEvent('catalog_tab', {tab: state.src}); }); }); // Carga infinita: cuando el sentinel entra en pantalla, mostramos más resultados. if ('IntersectionObserver' in window) { var sent = document.getElementById('sent'); new IntersectionObserver(function(entries){ entries.forEach(function(en){ if (en.isIntersecting && state.results && state.shown < state.results.length){ state.shown += 40; render(); } }); }, { rootMargin: '400px' }).observe(sent); } state.seq++; run(state.seq); renderRecents(); refreshCustomVideoMetadata(); if (CATALOG.length === 0) setStatus('Catálogo vacío. Revisa la fuente de datos.', true); // Auto-abre un video compartido: /?v= (o busca con ?q=) (function deepLink(){ var p = new URLSearchParams(location.search); var v = p.get('v'), q = p.get('q'), title = fixText(p.get('title') || ''); if (v) { var found = CATALOG.filter(function(m){ return String(m.id)===v || (m.code && String(m.code)===v); })[0]; if (found) setTimeout(function(){ open(found); }, 400); else if (title || q) { var query = title || q; setTimeout(function(){ if(qEl) qEl.value = query; state.seq++; run(state.seq); var tries=0, timer=setInterval(function(){ var match=state.results.filter(function(item){ return String(item.id)===v || String(item.name||'').toLowerCase().indexOf(query.toLowerCase())>=0; })[0]; if(match){ clearInterval(timer); open(match); } if(++tries>20) clearInterval(timer); },500); },400); } } else if (q || title) { setTimeout(function(){ if(qEl){ qEl.value = q || title; state.seq++; run(state.seq); } }, 400); } })(); })();