kv-netflix/backend/static/assets/main-BGz66_54.js
Khoa.vo 9d1d9bc741 v1.0.10: Android TV D-pad navigation + new app icons
- Added tabindex to video cards for D-pad focus
- Auto-detect TV mode and auto-focus first card
- Enhanced red glow focus styles for TV viewing distance
- Regenerated Android launcher icons with StreamFlix branding
2025-12-24 20:59:56 +07:00

392 lines
68 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import{a as k,h as j,s as $,K as ie,S as z,b as se,c as oe,d as ne}from"./keyboard-nav-CjQOo0Sk.js";async function le(){try{console.log("📂 Loading themed categories...");const t=await(await fetch("/api/rophim/categories/all")).json();return t&&t.categories?(console.log(`✓ Loaded ${Object.keys(t.categories).length} category sections`),t.categories):null}catch(e){return console.error("Error loading categories:",e),null}}function J(e){const t=document.createElement("div");return t.className="video-card__ranking",e<=3&&t.classList.add(`video-card__ranking--${e}`),t.textContent=`#${e}`,t}function Q(e){if(!e)return null;const t=document.createElement("div");t.className="video-card__badge";const s=e.toUpperCase();return s.includes("HOT")?t.classList.add("video-card__badge--hot"):s.includes("NEW")?t.classList.add("video-card__badge--new"):s.includes("CINEMA")?t.classList.add("video-card__badge--cinema"):s.includes("FULL")&&t.classList.add("video-card__badge--full"),t.textContent=s,t}function ce(e,t){if(!e)return e;const s=e.querySelector(".video-card__container");if(!s)return e;if(t.badge){const r=Q(t.badge);r&&s.appendChild(r)}if(t.ranking){const r=J(t.ranking);s.appendChild(r)}return e}typeof window<"u"&&(window.categorySystem={loadCategories:le,createRankingBadge:J,createQualityBadge:Q,enhanceVideoCardWithBadges:ce});const R="kvstream-images-v1",de=500;class me{constructor(){this.memoryCache=new Map,this.cacheEnabled="caches"in window,this.pendingRequests=new Map}async getCachedImage(t){if(!t||!this.cacheEnabled)return t;if(this.memoryCache.has(t))return this.memoryCache.get(t);if(this.pendingRequests.has(t))return this.pendingRequests.get(t);const s=this._fetchAndCache(t);this.pendingRequests.set(t,s);try{return await s}finally{this.pendingRequests.delete(t)}}async _fetchAndCache(t){try{const s=await caches.open(R),r=await s.match(t);if(r){const d=await r.blob(),m=URL.createObjectURL(d);return this.memoryCache.set(t,m),m}const i=await fetch(t,{mode:"cors",credentials:"omit"});if(i.ok){const d=i.clone();s.put(t,d);const m=await i.blob(),a=URL.createObjectURL(m);return this.memoryCache.set(t,a),this._cleanupCache(s),a}}catch{console.warn("Image cache failed:",t)}return t}async preloadImages(t){if(!t||t.length===0)return;const s=6;for(let r=0;r<t.length;r+=s){const i=t.slice(r,r+s);await Promise.allSettled(i.map(d=>this.getCachedImage(d)))}}createCachedImage(t,s="",r=""){const i=document.createElement("img");return i.alt=s,i.className=r,i.loading="lazy",i.decoding="async",i.src='data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 450"%3E%3Crect fill="%23222"%3E%3C/rect%3E%3C/svg%3E',t&&this.getCachedImage(t).then(d=>{i.src=d}),i}async _cleanupCache(t){try{const s=await t.keys();if(s.length>de){const r=Math.floor(s.length*.2);for(let i=0;i<r;i++)await t.delete(s[i])}}catch{}}async clearCache(){this.memoryCache.clear(),this.cacheEnabled&&await caches.delete(R)}async getCacheStats(){const t={memoryItems:this.memoryCache.size,cacheItems:0,cacheSize:0};if(this.cacheEnabled)try{const r=await(await caches.open(R)).keys();t.cacheItems=r.length}catch{}return t}}const ue=new me;function pe(e){var r;const t=new Date().getFullYear();if(e.year===t)return!0;const s=(e.quality||"").toLowerCase();if(s.includes("mới")||s.includes("new"))return!0;if((r=e.modified)!=null&&r.time){const i=new Date(e.modified.time),d=new Date(Date.now()-7*24*60*60*1e3);if(i>d)return!0}return!1}function he(e){var i;const t=(e.quality||"").toLowerCase(),s=((i=e.episodes)==null?void 0:i.length)||0,r=(e.category||e.type||"").toLowerCase();return t.includes("trailer")||r.includes("trailer")?"trailer":s>1||r.includes("series")||r.includes("phim-bo")||t.includes("tập")||t.includes("ep")?"series":r.includes("hoathinh")||r.includes("animation")||r.includes("anime")?"animation":"movie"}function ge(e){var i;const t=e.quality||"";if(t.match(/(?:tập\s*)?(\d+)(?:\s*\/\s*(\d+))?/i))return t;const r=((i=e.episodes)==null?void 0:i.length)||0;return r>1?`${r} Tập`:null}function fe(e,t,s){var T;const r=document.createElement("div");r.className="video-card",r.dataset.videoId=e.id,r.setAttribute("tabindex","0");const d=window.innerWidth<768?180:200,m=e.thumbnail||"",a=k.getProxyUrl(m,d),o=e.year||new Date().getFullYear(),u=pe(e),l=he(e),h=ge(e);let c=e.quality||"HD";c=c.replace(/(?:tập\s*)?\d+(?:\s*\/\s*\d+)?/gi,"").trim()||"HD",c.length>6&&(c="HD");const f=parseFloat(e.rating||0),g=f>=7,y=Math.round(f*10);let v="";f>0&&(v=`
<div class="numeric-rating">
<span class="numeric-rating__score">${f.toFixed(1)}</span>
</div>
`);let E="";f>0&&(E=`
<div class="tomato-badge ${g?"tomato-badge--fresh":"tomato-badge--rotten"}">
<span class="tomato-badge__icon">${g?"🍅":"🥀"}</span>
<span class="tomato-badge__score">${y}%</span>
</div>
`);const G='data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 450"%3E%3Crect width="300" height="450" fill="%2314141c"/%3E%3C/svg%3E';let x="";u&&(x+='<span class="video-tag video-tag--new">MỚI</span>'),l==="trailer"?x+='<span class="video-tag video-tag--trailer">TRAILER</span>':l==="series"?x+='<span class="video-tag video-tag--series">PHIM BỘ</span>':l==="animation"&&(x+='<span class="video-tag video-tag--animation">HOẠT HÌNH</span>'),r.innerHTML=`
<div class="video-card__container">
<div class="video-card__poster">
<img src="${G}" data-src="${a}" alt="${U(e.title)}" loading="lazy" referrerpolicy="no-referrer" class="video-card__img" onerror="this.onerror=null;this.src='https://placehold.co/400x600/14141c/e5c07b?text=Movie'">
<!-- Top Left Tags -->
<div class="video-tags">
${x}
</div>
<!-- Bottom Right Info (Ratings & Quality) -->
<div class="card-meta-bottom-right">
${E}
${v}
<span class="poster-badge">${c}</span>
</div>
<!-- Bottom Left Info (Year & Episodes) -->
<div class="card-meta-bottom-left">
<span class="year-badge">${o}</span>
${h?`<span class="episode-badge">${h}</span>`:""}
</div>
<!-- Watch Progress Bar -->
${e.progress&&e.progress.percentage>0?`
<div class="video-card__progress">
<div class="video-card__progress-fill" style="width: ${e.progress.percentage}%"></div>
</div>
`:""}
<!-- Play overlay on hover -->
<div class="video-card__overlay">
<button class="video-card__play-btn" data-action="play" aria-label="Play">
<svg viewBox="0 0 24 24" fill="currentColor" width="40" height="40">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</div>
</div>
</div>
<!-- Movie Title -->
<div class="video-card__title">
<span class="video-card__name">${U(e.title)}</span>
</div>
`;const w=r.querySelector(".video-card__img");if(w&&a){const _=new IntersectionObserver(te=>{te.forEach(ae=>{ae.isIntersecting&&(ue.getCachedImage(a).then(re=>{w.src=re,w.classList.add("loaded")}).catch(()=>{w.src=a,w.onload=()=>w.classList.add("loaded"),w.onerror=()=>w.classList.add("loaded")}),_.unobserve(w))})},{rootMargin:"800px",threshold:0});_.observe(w)}return(T=r.querySelector('[data-action="play"]'))==null||T.addEventListener("click",_=>{_.stopPropagation(),t==null||t(e)}),r.addEventListener("click",()=>{t==null||t(e)}),r}function U(e){if(!e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML}function ye(e,t){let s;return function(...i){const d=()=>{clearTimeout(s),e(...i)};clearTimeout(s),s=setTimeout(d,t)}}function ve(e,t,s){if(!e||!t)return;const r=300;let i="";async function d(a){if(i=a,!a||a.length<2){t.classList.remove("active"),t.innerHTML="";return}try{const o=await k.searchRophim(a),u=(o==null?void 0:o.movies)||[];if(a!==i)return;u.length===0?t.innerHTML=`
<div class="search__result" style="opacity: 0.5;">
<span>No results found for "${F(a)}"</span>
</div>
`:(t.innerHTML=u.map(l=>{const h=k.getProxyUrl(l.poster_url||l.thumb_url||l.thumbnail,80);return`
<div class="search__result" data-video-slug="${l.slug}">
<img
src="${h||'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 45" fill="%231a1a1a"%3E%3Crect width="80" height="45"/%3E%3C/svg%3E'}"
alt="${F(l.name||l.title)}"
class="search__result-thumb"
loading="lazy"
>
<div class="search__result-info">
<div class="search__result-title">${F(l.name||l.title)}</div>
<div class="search__result-meta">
${l.quality?`${l.quality}`:""}
${l.year||""}
</div>
</div>
</div>
`}).join(""),t.querySelectorAll(".search__result[data-video-slug]").forEach(l=>{l.addEventListener("click",()=>{const h=l.dataset.videoSlug;window.location.href=`/watch.html?id=${h}&slug=${h}`})})),t.classList.add("active")}catch(o){console.error("Search error:",o),t.innerHTML=`
< div class="search__result" style = "color: var(--color-error);" >
<span>Search failed. Please try again.</span>
</div >
`,t.classList.add("active")}}const m=ye(d,r);e.addEventListener("input",a=>{m(a.target.value.trim())}),document.addEventListener("click",a=>{e&&t&&!e.contains(a.target)&&!t.contains(a.target)&&t.classList.remove("active")}),e.addEventListener("keydown",a=>{a.key==="Escape"&&(e.blur(),t.classList.remove("active"))}),e.addEventListener("focus",()=>{e.value.trim().length>=2&&t.classList.add("active")})}function F(e){if(!e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML}const M={elements:{overlay:document.getElementById("splash-screen"),bar:document.getElementById("loading-bar"),text:document.getElementById("loading-text")},progress:0,isFinished:!1,update(e,t){this.isFinished||(this.progress=Math.min(e,100),this.elements.bar&&(this.elements.bar.style.width=`${this.progress}%`),this.elements.text&&t&&(this.elements.text.textContent=t),this.progress>=100&&this.finish())},finish(){this.isFinished||(this.isFinished=!0,setTimeout(()=>{this.elements.overlay&&(this.elements.overlay.classList.add("fade-out"),setTimeout(()=>this.elements.overlay.remove(),1e3))},500))}},p={videos:[],currentCategory:"all",currentVideo:null,isLoading:!1,featuredVideo:null,heroMovies:[],currentHeroIndex:0,heroInterval:null,page:1,hasMore:!0},n={videoGrid:document.getElementById("videoGrid")||document.getElementById("mainContent"),mainContent:document.getElementById("mainContent"),loading:document.getElementById("loading"),emptyState:document.getElementById("emptyState"),categories:document.getElementById("categories"),mainHeader:document.getElementById("mainHeader"),searchWrapper:document.getElementById("searchWrapper"),searchToggle:document.getElementById("searchToggle"),searchInput:document.getElementById("searchInput"),searchResults:document.getElementById("searchResults"),navLinks:document.querySelectorAll(".header__nav-link"),playerModal:document.getElementById("playerModal"),playerContainer:document.getElementById("playerContainer"),playerTitle:document.getElementById("playerTitle"),playerMeta:document.getElementById("playerMeta"),closePlayer:document.getElementById("closePlayer"),modalBackdrop:document.getElementById("modalBackdrop"),mobileNavItems:document.querySelectorAll(".mobile-nav__item, .sidebar__nav-item"),mobileBottomNavButtons:document.querySelectorAll("#mobileBottomNav .nav-item")};function D(e){document.querySelectorAll("#mobileBottomNav .nav-item").forEach(s=>{const r=s.dataset.view===e;s.classList.toggle("active",r),s.classList.toggle("text-white",r),s.classList.toggle("text-gray-400",!r);const i=s.querySelector(".material-symbols-outlined");i&&(i.style.fontVariationSettings=r?"'FILL' 1":"'FILL' 0")})}async function V(){M.update(10,"Initializing services..."),ve(n.searchInput,n.searchResults),M.update(20,"Setting up navigation..."),n.mobileBottomNavButtons&&n.mobileBottomNavButtons.forEach(r=>{r.addEventListener("click",i=>{i.preventDefault();const d=r.dataset.view;if(d)if(n.mobileBottomNavButtons.forEach(m=>m.classList.remove("active")),r.classList.add("active"),j(),d==="home")Ce(),window.scrollTo({top:0,behavior:"smooth"});else if(d==="search")if(window.innerWidth<768)try{W()}catch(m){console.error("Search render failed",m)}else n.searchWrapper.classList.add("active"),n.searchInput.focus();else d==="mylist"?window.innerWidth<768?Y():N("mylist"):d==="downloads"?$("Downloads feature coming soon!","info"):d==="profile"?Ee():d==="cinema"?(D("cinema"),C("cinema"),window.scrollTo({top:0,behavior:"smooth"})):(C(d),window.scrollTo({top:0,behavior:"smooth"}))})}),xe(),M.update(40,"Fetching movie catalog...");try{await C("home")}catch(r){console.error("Home render failed",r)}M.update(70,"Preparing featured content...");try{await S()}catch(r){console.error("Hero render failed",r)}M.update(90,"Applying final touches...");const t=new URLSearchParams(window.location.search).get("view");t&&window.innerWidth<768&&(t==="search"?W():t==="mylist"?Y():t==="cinema"&&C("cinema")),new ie().init(),"serviceWorker"in navigator&&window.addEventListener("load",()=>{navigator.serviceWorker.register("/sw.js")}),M.update(100,"Welcome to StreamFlix");try{await z.setStyle({style:se.Dark}),await z.setBackgroundColor({color:"#141414"})}catch{}}function S(e=null){const t=document.getElementById("heroTitle"),s=document.getElementById("heroDescription"),r=document.getElementById("heroBg"),i=document.getElementById("heroTag"),d=document.getElementById("heroTagContainer"),m=document.getElementById("heroPlayBtn"),a=document.getElementById("heroInfoBtn"),o=document.getElementById("heroContent"),u=e||p.featuredVideo||p.videos[0];u&&(r&&(r.style.opacity="0.5"),o&&(o.style.opacity="0"),setTimeout(()=>{t&&(t.textContent=u.name||u.title||"Featured Movie"),s&&(s.textContent=u.description||u.content||"Watch now on StreamFlix");const l=u.backdrop||u.poster_url||u.thumb_url||u.thumbnail||"";if(r&&l&&(r.style.backgroundImage=`url('${l}')`),i&&d){const h=u.genres||u.category;d.classList.remove("hidden"),h&&Array.isArray(h)&&h.length>0?i.textContent=h[0]:typeof h=="string"?i.textContent=h:i.textContent="#1 in Movies Today"}if(m&&m.parentNode){const h=m.cloneNode(!0);m.parentNode.replaceChild(h,m),h.addEventListener("click",()=>{oe(),L(u)})}if(a&&a.parentNode){const h=a.cloneNode(!0);a.parentNode.replaceChild(h,a),h.addEventListener("click",()=>Z(u))}r&&(r.style.opacity="1"),o&&(o.style.opacity="1")},300),p.featuredVideo=u)}function be(){p.heroInterval&&clearInterval(p.heroInterval),!(!p.heroMovies||p.heroMovies.length<=1)&&(p.heroInterval=setInterval(()=>{p.currentHeroIndex++,p.currentHeroIndex>=p.heroMovies.length&&(p.currentHeroIndex=0),S(p.heroMovies[p.currentHeroIndex])},8e3))}function xe(){var o,u,l,h;const e=document.getElementById("backToTop"),t=()=>{const c=window.scrollY;n.mainHeader&&(c>100?(n.mainHeader.classList.add("scrolled"),n.mainHeader.style.backgroundColor="#141414"):(n.mainHeader.classList.remove("scrolled"),n.mainHeader.style.backgroundColor="transparent")),e&&(c>500?e.classList.add("visible"):e.classList.remove("visible"))};window.addEventListener("scroll",t,{passive:!0}),t(),e&&e.addEventListener("click",()=>{window.scrollTo({top:0,behavior:"smooth"})}),(o=n.navLinks)==null||o.forEach(c=>{c.addEventListener("click",f=>{f.preventDefault();const g=c.dataset.category;n.navLinks.forEach(y=>y.classList.remove("active")),c.classList.add("active"),p.currentCategory=g,B(g,!0)})}),(u=n.mobileNavItems)==null||u.forEach(c=>{c.addEventListener("click",f=>{f.preventDefault();const g=c.dataset.view;if(n.mobileNavItems.forEach(y=>y.classList.remove("active")),c.classList.add("active"),n.mobileNavItems.forEach(y=>{y.dataset.view===g&&y.classList.add("active")}),g==="home"){n.videoGrid.style.display="block";const y=document.getElementById("newHotContainer");y&&(y.style.display="none"),p.currentCategory="all",B("all",!0)}else if(["movies","series","animation","cinema"].includes(g)){n.videoGrid.style.display="block";const y=document.getElementById("newHotContainer");y&&(y.style.display="none"),p.currentCategory=g,B(g,!0)}else if(g==="history"){n.videoGrid.style.display="block";const y=document.getElementById("newHotContainer");y&&(y.style.display="none"),N()}else if(g==="search"){const y=document.getElementById("headerSearchBtn");y&&y.click()}window.scrollTo({top:0,behavior:"smooth"})})});const s=document.querySelectorAll(".netflix-header__nav-link");s.forEach(c=>{c.addEventListener("click",f=>{f.preventDefault();const g=c.dataset.view;s.forEach(v=>v.classList.remove("active")),c.classList.add("active"),n.mobileNavItems.forEach(v=>{v.classList.remove("active"),v.dataset.view===g&&v.classList.add("active")}),n.videoGrid.style.display="block";const y=document.getElementById("newHotContainer");y&&(y.style.display="none"),g==="home"?(p.currentCategory="all",B("all",!0)):["movies","series","animation","cinema"].includes(g)?(p.currentCategory=g,B(g,!0)):g==="history"&&N(),window.scrollTo({top:0,behavior:"smooth"})})});const r=document.getElementById("headerSearchBtn");r&&r.addEventListener("click",c=>{c.preventDefault();const f=document.getElementById("searchModal"),g=document.getElementById("searchInput");f&&(f.classList.add("active"),g&&setTimeout(()=>g.focus(),100))});const i=document.getElementById("mobileSearchBtn");i&&i.addEventListener("click",c=>{c.preventDefault();const f=document.getElementById("searchModal"),g=document.getElementById("searchInput");f&&(j(),f.classList.add("active"),g&&setTimeout(()=>g.focus(),100))});const d=document.getElementById("closeSearch");d&&d.addEventListener("click",()=>{const c=document.getElementById("searchModal");c&&c.classList.remove("active")});const m=document.getElementById("modalPlayerBackButton");m&&m.addEventListener("click",()=>{var c;j(),(c=window.history.state)!=null&&c.playerOpen?window.history.back():A()}),window.addEventListener("popstate",c=>{var f,g;(f=n.playerModal)!=null&&f.classList.contains("active")&&!((g=c.state)!=null&&g.playerOpen)&&A(!1)});const a=document.querySelectorAll(".nav-link");a.forEach(c=>{c.addEventListener("click",f=>{const g=c.getAttribute("href");if(g&&g!=="#"&&!g.startsWith("#"))return;f.preventDefault();const y=c.dataset.view;a.forEach(v=>{v.classList.remove("active","text-white"),v.classList.add("text-gray-300")}),c.classList.add("active","text-white"),c.classList.remove("text-gray-300"),y==="home"?(p.currentCategory="all",C("home")):y==="series"?(p.currentCategory="series",C("series")):y==="movies"?(p.currentCategory="movies",C("movies")):y==="cinema"?(p.currentCategory="cinema",C("cinema")):y==="history"&&N(),window.scrollTo({top:0,behavior:"smooth"})})}),(l=n.closePlayer)==null||l.addEventListener("click",A),(h=n.modalBackdrop)==null||h.addEventListener("click",A),document.addEventListener("keydown",c=>{var f,g,y;if(c.key==="Escape"){(f=n.playerModal)!=null&&f.classList.contains("active")&&((g=window.history.state)!=null&&g.playerOpen?window.history.back():A()),(y=n.searchWrapper)!=null&&y.classList.contains("active")&&n.searchWrapper.classList.remove("active");const v=document.getElementById("searchModal");v!=null&&v.classList.contains("active")&&v.classList.remove("active")}})}async function B(e="all",t=!1){if(p.isLoading||(t&&(p.page=1,p.hasMore=!0,p.videos=[],n.videoGrid.innerHTML=""),!p.hasMore))return;p.isLoading=!0,H(p.page===1);const s=(i,d=12e3)=>Promise.race([i,new Promise((m,a)=>setTimeout(()=>a(new Error("Timeout")),d))]),r=document.getElementById("topSearchBtn");r&&r.addEventListener("click",i=>{i.preventDefault();const d=document.getElementById("searchModal"),m=document.getElementById("searchInput");d&&(d.classList.add("active"),m&&setTimeout(()=>m.focus(),100))});try{let i=null,d=!1;if(i||(i=await s(k.getRophimCatalog({category:e!=="all"?e:null,page:p.page,limit:24}),12e3)),i&&i.movies&&i.movies.length>0){const m=i.movies.map(l=>({id:l.id||`api_${Date.now()}_${Math.random()}`,title:l.title||"Unknown Title",thumbnail:l.thumbnail||"https://via.placeholder.com/300x450?text=No+Image",backdrop:l.backdrop||l.thumbnail||"https://via.placeholder.com/1920x1080?text=No+Backdrop",preview_url:l.preview_url||"",duration:l.duration||0,resolution:l.quality||"HD",category:l.category||"movies",year:l.year||new Date().getFullYear(),description:l.description||"",matchScore:Math.floor(Math.random()*15)+85,source_url:l.source_url,slug:l.slug,cast:l.cast||[],director:l.director,country:l.country,episodes:l.episodes||[]})),a=new Set(p.videos.map(l=>l.id)),o=m.filter(l=>!a.has(l.id));p.videos=[...p.videos,...o],p.page+=1,m.length<24,p.page===2?P(p.videos,!1):P(o,!0),Le(),b&&b.classList.remove("loading"),p.isLoading=!1,H(!1);return}else p.hasMore=!1,b&&(b.classList.remove("loading"),b.style.display="none"),p.isLoading=!1,H(!1)}catch(i){if(console.warn("API load failed:",i),p.page===1){$("Using offline mode","info");const d=ee();p.videos=d,p.featuredVideo=d[0],P(d)}p.isLoading=!1,H(!1)}}function q(e,t,s="poster"){const r=document.createElement("section");r.className="flex flex-col gap-4 mb-12 relative";const i=document.createElement("h2");i.className="text-xl md:text-2xl font-bold text-white hover:text-primary cursor-pointer transition-colors flex items-center gap-2 group px-4 md:px-12",i.innerHTML=`
${e}
<span class="material-symbols-outlined text-sm opacity-0 group-hover:opacity-100 transition-opacity text-primary">arrow_forward_ios</span>
`,r.appendChild(i);const d=document.createElement("div");d.className="relative group/slider";const m=document.createElement("button");m.className="absolute left-0 top-1/2 -translate-y-1/2 z-20 w-12 h-full bg-gradient-to-r from-black/80 to-transparent opacity-0 group-hover/slider:opacity-100 transition-opacity flex items-center justify-start pl-2",m.innerHTML='<span class="material-symbols-outlined text-white text-3xl">chevron_left</span>';const a=document.createElement("button");a.className="absolute right-0 top-1/2 -translate-y-1/2 z-20 w-12 h-full bg-gradient-to-l from-black/80 to-transparent opacity-0 group-hover/slider:opacity-100 transition-opacity flex items-center justify-end pr-2",a.innerHTML='<span class="material-symbols-outlined text-white text-3xl">chevron_right</span>';const o=document.createElement("div");o.className="flex gap-3 overflow-x-auto scroll-smooth no-scrollbar px-4 md:px-12 pb-4",t.forEach((l,h)=>{let c;s==="landscape"?c=ke(l):c=we(l,!1,0,"horizontal"),c.className=c.className.replace("w-full",""),c.style.minWidth="280px",c.style.maxWidth="380px",c.style.flex="0 0 auto",o.appendChild(c)});const u=600;return m.addEventListener("click",()=>{o.scrollBy({left:-u,behavior:"smooth"})}),a.addEventListener("click",()=>{o.scrollBy({left:u,behavior:"smooth"})}),d.appendChild(m),d.appendChild(o),d.appendChild(a),r.appendChild(d),r}function we(e,t=!1,s=0,r="vertical"){const i=document.createElement("div"),d=r==="horizontal"?"aspect-video":"aspect-[2/3]";i.className="w-full cursor-pointer snap-start group relative transition-all duration-300 ease-in-out hover:z-30 hover:scale-105";let m=e.poster_url||e.thumb_url||e.thumbnail||"";r==="horizontal"&&e.backdrop&&(m=e.backdrop);const o=window.innerWidth<768?180:r==="horizontal"?400:200,u=m?k.getProxyUrl(m,o):"",l=e.name||e.title||"Untitled",h=e.year||"",c=e.quality||"HD",f=e.slug||e.id||"",g=e.matchScore||Math.floor(Math.random()*10+90),y=Math.floor(Math.random()*19+80);i.innerHTML=`
<div class="relative ${d} rounded-md overflow-hidden bg-surface-dark shadow-lg transition-all duration-300 group-hover:shadow-2xl ring-0 group-hover:ring-2 group-hover:ring-white/20">
<div class="absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-110" style="background-image: url('${u}');"></div>
<!-- Gradient Overlay (Only visible on hover) -->
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<!-- Badges Container -->
<div class="absolute top-2 left-2 flex flex-col gap-1 z-20">
${!t&&h===new Date().getFullYear().toString()?'<span class="bg-primary text-white text-[9px] font-bold px-1.5 py-0.5 rounded shadow">NEW</span>':""}
${e.quality?`<span class="bg-black/60 backdrop-blur-md text-white text-[9px] font-bold px-1.5 py-0.5 rounded border border-white/10 uppercase">${e.quality.replace("FHD","HD")}</span>`:""}
${e.current_episode?`<span class="bg-black/60 backdrop-blur-md text-white text-[9px] font-bold px-1.5 py-0.5 rounded border border-white/10">EP ${e.current_episode}</span>`:""}
</div>
<!-- Number Badge -->
${t?`<span class="absolute top-0 right-0 bg-primary text-white text-4xl font-black p-2 leading-none shadow-lg z-20">${s}</span>`:""}
<!-- Hover Content -->
<div class="absolute inset-0 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col justify-end p-3 pointer-events-none">
<!-- Action Buttons -->
<div class="flex items-center justify-between mb-3 pointer-events-auto">
<div class="flex gap-2">
<button class="bg-white text-black h-8 w-8 rounded-full flex items-center justify-center hover:bg-gray-200 transition-transform hover:scale-110 btn-play" title="Play">
<span class="material-symbols-outlined text-[20px] fill-current" style="font-variation-settings: 'FILL' 1;">play_arrow</span>
</button>
<button class="bg-zinc-800/60 backdrop-blur-md border border-gray-400 text-white h-8 w-8 rounded-full flex items-center justify-center hover:border-white hover:bg-zinc-700 transition-transform hover:scale-110 btn-add-list" data-slug="${f}" title="Add to List">
<span class="material-symbols-outlined text-[18px]">add</span>
</button>
</div>
<button class="bg-zinc-800/60 backdrop-blur-md border border-gray-400 text-white h-8 w-8 rounded-full flex items-center justify-center hover:border-white hover:bg-zinc-700 transition-transform hover:scale-110 btn-info" data-slug="${f}" title="More Info">
<span class="material-symbols-outlined text-[18px]">info</span>
</button>
</div>
<!-- Metadata -->
<div class="space-y-1">
<div class="flex items-center gap-2 text-[10px] font-semibold">
<span class="text-green-400">${g}% Match</span>
<span class="border border-gray-400 px-1 rounded text-gray-200">${c}</span>
<span class="text-gray-300">${h}</span>
</div>
<!-- Ratings & Tags -->
<div class="flex items-center gap-3 text-[10px] font-bold">
<div class="flex items-center gap-1 text-yellow-500">
<span class="bg-[#FA320A] text-white px-1 rounded flex items-center gap-0.5 h-3.5">
<span class="material-symbols-outlined text-[10px]">local_pizza</span> ${y}%
</span>
</div>
${e.genres&&e.genres.length>0?`<span class="text-white/70 font-normal truncate max-w-[100px]">${e.genres[0]}</span>`:""}
</div>
<h3 class="text-sm font-bold text-white leading-tight line-clamp-2 drop-shadow-md mt-1">
${l}
</h3>
</div>
</div>
</div>
`,i.addEventListener("click",x=>{x.target.closest("button")||L(e)});const v=i.querySelector(".btn-play");v&&v.addEventListener("click",x=>{x.stopPropagation(),L(e)});const E=i.querySelector(".btn-add-list");E&&E.addEventListener("click",x=>{if(x.stopPropagation(),window.historyService){const w=window.historyService.toggleFavorite(e),T=E.querySelector("span");w?(T.textContent="check",$("Added to My List","success")):(T.textContent="add",$("Removed from My List","info"))}});const G=i.querySelector(".btn-info");return G&&G.addEventListener("click",x=>{x.stopPropagation(),Z(e)}),i}function ke(e){var m,a;const t=document.createElement("div");t.className="flex-none w-[280px] group/card cursor-pointer snap-start";const s=e.backdrop||e.thumb_url||e.thumbnail||"",r=e.name||e.title||"Untitled",i=((m=e.progress)==null?void 0:m.percentage)||0,d=(a=e.progress)!=null&&a.episode?`S${e.season||1}:E${e.progress.episode}`:"";return t.innerHTML=`
<div class="relative aspect-video rounded-md overflow-hidden bg-surface-dark card-hover">
<div class="absolute inset-0 bg-cover bg-center" style="background-image: url('${s}');"></div>
<div class="absolute inset-0 bg-black/30 group-hover/card:bg-black/10 transition-colors flex items-center justify-center opacity-0 group-hover/card:opacity-100">
<span class="material-symbols-outlined text-5xl bg-black/50 rounded-full p-2 border-2 border-white">play_arrow</span>
</div>
<div class="absolute bottom-0 left-0 right-0 h-1 bg-gray-700">
<div class="h-full bg-primary" style="width: ${i}%;"></div>
</div>
</div>
<div class="mt-2 flex justify-between items-center px-1">
<span class="text-sm font-semibold text-gray-200">${r}</span>
${d?`<span class="text-xs text-gray-400">${d}</span>`:""}
</div>
`,t.addEventListener("click",()=>L(e)),t}function P(e,t=!1){if(t||(n.videoGrid.innerHTML="",n.videoGrid.innerHTML="",n.videoGrid.className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-x-4 gap-y-10"),e.length===0&&!t){n.emptyState&&(n.emptyState.style.display="flex");return}n.emptyState&&(n.emptyState.style.display="none"),e.forEach(s=>{const r=fe(s,L);n.videoGrid.appendChild(r)})}let I,b=null,O=0;function Le(){if(!p.hasMore){b&&(b.classList.remove("loading"),b.style.display="none"),I&&I.disconnect();return}I&&I.disconnect(),document.querySelectorAll(".scroll-sentinel").forEach(s=>s.remove()),b=null;const e={root:null,rootMargin:"50px",threshold:0};I=new IntersectionObserver(s=>{s.forEach(r=>{const i=Date.now();i-O<1500||r.isIntersecting&&!p.isLoading&&p.hasMore&&(O=i,b&&b.classList.add("loading"),B(p.currentCategory))})},e),b=document.createElement("div"),b.className="scroll-sentinel",b.id="scrollSentinel";const t=document.getElementById("infinite-scroll-container");t?t.parentNode.insertBefore(b,t.nextSibling):n.videoGrid.appendChild(b),I.observe(b)}function Z(e){X(e)}function N(e="history"){if(n.mainHeader&&(n.mainHeader.style.display=""),!window.historyService){console.error("HistoryService not initialized");return}n.videoGrid.innerHTML="",n.emptyState&&(n.emptyState.style.display="none");const t=document.querySelector(".view-tabs");t&&t.remove();const s=document.createElement("div");s.className="view-tabs",s.innerHTML=`
<button class="view-tab ${e==="history"?"active":""}" data-tab="history">Watch History</button>
<button class="view-tab ${e==="mylist"?"active":""}" data-tab="mylist">My List</button>
`,n.videoGrid.before(s),s.querySelectorAll(".view-tab").forEach(a=>{a.addEventListener("click",()=>{s.remove(),N(a.dataset.tab)})});let r=[];if(e==="history"?r=window.historyService.getHistory():r=window.historyService.getFavorites(),r.length===0){if(n.emptyState){n.emptyState.style.display="flex";const a=n.emptyState.querySelector("h2"),o=n.emptyState.querySelector("p");e==="history"?(a&&(a.textContent="No history yet"),o&&(o.textContent="Movies you watch will appear here.")):(a&&(a.textContent="My List is empty"),o&&(o.textContent="Add movies to your list to watch later."))}return}r.sort((a,o)=>{const u=a.timestamp||a.year||0;return(o.timestamp||o.year||0)-u});const i=r.map((a,o)=>({...a,id:a.id||a.slug,orientation:"horizontal"}));n.mainHeader&&(n.mainHeader.style.display="block");const m=q(e==="history"?"Continue Watching":"My List",i,"poster");n.videoGrid.appendChild(m)}function L(e){sessionStorage.setItem("currentVideo",JSON.stringify(e)),sessionStorage.setItem("allVideos",JSON.stringify(p.videos)),X(e)}function X(e){window.location.href=`/watch.html?slug=${e.slug}`}function A(e=!0){var t;n.playerModal&&(n.playerModal.classList.add("hidden"),n.playerModal.classList.remove("active"),n.playerModal.style.display="none",ne()),e&&((t=window.history.state)!=null&&t.playerOpen),n.playerContainer.innerHTML="",p.currentVideo=null}function H(e){n.loading&&(n.loading.style.display=e?"flex":"none"),n.videoGrid&&(n.videoGrid.style.display=e?"none":"block")}async function C(e){const t=document.querySelector(".view-tabs");t&&t.remove(),n.mainHeader&&(n.mainHeader.style.display=""),H(!0),n.videoGrid.innerHTML="",n.videoGrid.className="space-y-12";const s={home:[{title:"Continue Watching",type:"history",limit:12,cardType:"landscape"},{title:"Cinema Releases",category:"phim-chieu-rap",limit:12,isHeroSource:!0},{title:"Top Rated",category:"phim-le",sort:"rating",limit:12},{title:"Action & Adventure",category:"hanh-dong",limit:12},{title:"Animation",category:"hoat-hinh",limit:12},{title:"Korean Hits",category:"han-quoc",limit:12},{title:"Horror & Thriller",category:"kinh-di",limit:12},{title:"Romance",category:"tinh-cam",limit:12}],series:[{title:"Popular TV Shows",category:"phim-bo",limit:12,isHeroSource:!0},{title:"Korean Dramas",category:"korean",limit:12},{title:"Chinese Dramas",category:"china",limit:12},{title:"Anime Series",category:"hoat-hinh",limit:12},{title:"Documentaries",category:"tai-lieu",limit:12}],movies:[{title:"Blockbuster Movies",category:"phim-le",sort:"year",limit:12,isHeroSource:!0},{title:"Action & Adventure",category:"action",limit:12},{title:"Comedy Films",category:"comedy",limit:12},{title:"Cinema Releases",category:"phim-chieu-rap",limit:12},{title:"Horror Movies",category:"kinh-di",limit:12},{title:"Sci-Fi & Fantasy",category:"vien-tuong",limit:12}],cinema:[{title:"Now Showing",category:"phim-chieu-rap",limit:12,isHeroSource:!0},{title:"New Releases",category:"phim-le",sort:"year",limit:12},{title:"Top Rated",category:"phim-le",sort:"rating",limit:12},{title:"Action Blockbusters",category:"action",limit:12},{title:"Animated Features",category:"hoat-hinh",limit:12}]},r=s[e]||s.home,i=3;try{let d=null;for(let a=0;a<Math.min(i,r.length);a++){const o=r[a],u=await K(o);if(u&&u.length>0){d||(d=u),o.isHeroSource&&(!p.heroMovies||p.heroMovies.length===0)&&u.length>0&&(p.heroMovies=u.slice(0,10),p.featuredVideo=u[0],p.videos=u,p.currentHeroIndex=0,S(p.heroMovies[0]),be());const l=q(o.title,u,o.cardType||"poster");n.videoGrid.appendChild(l)}}(e==="home"||e==="cinema")&&sessionStorage.setItem(`view_cache_${e}`,n.videoGrid.innerHTML);const m=new IntersectionObserver(async(a,o)=>{for(const u of a)if(u.isIntersecting){const l=u.target,h=parseInt(l.dataset.configIndex),c=r[h];o.unobserve(l),l.innerHTML='<div class="flex justify-center py-8"><div class="loading-spinner"></div></div>';const f=await K(c);if(f&&f.length>0){const g=q(c.title,f,c.cardType||"poster");l.replaceWith(g),(e==="home"||e==="cinema")&&sessionStorage.setItem(`view_cache_${e}`,n.videoGrid.innerHTML)}else l.remove()}},{rootMargin:"800px"});for(let a=i;a<r.length;a++){const o=document.createElement("div");o.className="lazy-section-placeholder h-32 mb-12",o.dataset.configIndex=a,o.innerHTML=`<h2 class="text-xl md:text-2xl font-bold text-white/30 px-4 md:px-12">${r[a].title}</h2>`,n.videoGrid.appendChild(o),m.observe(o)}if(!p.featuredVideo)if(d&&d.length>0)p.featuredVideo=d[0],p.videos=d,S();else try{const a=ee();a&&a.length>0&&(p.featuredVideo=a[0],p.videos=a,S())}catch(a){console.warn("Demo content fallback failed",a)}n.videoGrid.children.length===0&&(n.videoGrid.innerHTML=`
<div class="flex flex-col items-center justify-center py-20 text-gray-400">
<span class="material-symbols-outlined text-6xl mb-4 opacity-30">movie</span>
<p>No content available for this category</p>
</div>
`)}catch(d){console.error("Error rendering category view:",d),$("Connection failed: "+d.message,"error"),n.videoGrid.innerHTML=`
<div class="flex flex-col items-center justify-center py-20 text-gray-400">
<span class="material-symbols-outlined text-6xl mb-4 opacity-30">error</span>
<p>Failed to load content. Please try again.</p>
</div>
`}H(!1)}async function K(e){try{if(e.type==="history")return window.historyService?window.historyService.getHistory().slice(0,e.limit).map(o=>({id:o.slug||o.id,title:o.title,thumbnail:o.thumbnail||o.poster_url,slug:o.slug,year:o.year,quality:o.quality||"HD",view_progress:o.view_progress||0})):[];const t={category:e.category||null,limit:e.limit||40,sort:e.sort||"year"};e.country&&(t.country=e.country),e.genre&&(t.genre=e.genre);const s=async a=>{const o=[1,2,3,4,5,6,7,8].map(l=>k.getRophimCatalog({...a,page:l}).catch(h=>({movies:[]})));return(await Promise.all(o)).flatMap(l=>l.movies||[])};let r=await s(t);if(r.length<20&&e.sort&&e.sort!=="modified"){const a=await s({...t,sort:"modified"});r=[...r,...a]}const i=[],d=new Set;for(const a of r){if(!a)continue;const o=a.slug||a.id;d.has(o)||(d.add(o),i.push({id:a.id||a.slug,title:a.title,thumbnail:a.thumbnail,poster_url:a.poster_url||a.thumbnail,backdrop:a.backdrop||a.poster_url||a.thumbnail,slug:a.slug,year:a.year,quality:a.quality||"HD",rating:a.rating,category:a.category}))}const m=Math.max(e.limit||40,48);return i.slice(0,m)}catch(t){return console.error(`Error fetching section "${e.title}":`,t),[]}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",V):V();function ee(){const e="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",t={VENOM:"https://image.tmdb.org/t/p/w500/aosm8NMQ3UyoBVpSxyimorCQykC.jpg",SQUID:"https://images.unsplash.com/photo-1536440136628-849c177e76a1?w=800&auto=format&fit=crop",ARCANE:"https://images.unsplash.com/photo-1542751371-adc38448a05e?w=800&auto=format&fit=crop",PENGUIN:"https://images.unsplash.com/photo-1478720568477-152d9b164e63?w=800&auto=format&fit=crop",GLADIATOR:"https://images.unsplash.com/photo-1565060416-522204c35613?w=800&auto=format&fit=crop",MOANA:"https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=800&auto=format&fit=crop",WICKED:"https://images.unsplash.com/photo-1518709268805-4e9042af9f23?w=800&auto=format&fit=crop",DBZ:"https://images.unsplash.com/photo-1578632767115-351597cf2477?w=800&auto=format&fit=crop"};return[{id:"d1",title:"Venom: The Last Dance",thumbnail:t.VENOM,backdrop:"https://image.tmdb.org/t/p/original/3V4kLQg0kSqPLctI5ziYWabAZYF.jpg",preview_url:e,duration:7200,resolution:"4K",category:"action",year:2024,matchScore:98,director:"Kelly Marcel",country:"USA",cast:["Tom Hardy","Chiwetel Ejiofor","Juno Temple"],description:"Eddie and Venom are on the run. Hunted by both of their worlds and with the net closing in, the duo are forced into a devastating decision.",episodes:[]},{id:"d2",title:"Squid Game Season 2",thumbnail:t.SQUID,backdrop:t.SQUID,preview_url:"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",duration:3600,resolution:"HD",category:"series",year:2024,matchScore:99,director:"Hwang Dong-hyuk",country:"Korea",cast:["Lee Jung-jae","Lee Byung-hun","Wi Ha-jun"],description:"Gi-hun returns to the death games after three years with a new resolution: to find the people behind and to put an end to the sport.",episodes:[{number:1,title:"Red Light, Green Light",url:e},{number:2,title:"The Man with the Umbrella",url:e},{number:3,title:"Stick to the Team",url:e}]},{id:"d3",title:"Arcane Season 2",thumbnail:t.ARCANE,backdrop:t.ARCANE,preview_url:"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4",duration:2400,resolution:"4K",category:"anime",year:2024,matchScore:97,director:"Christian Linke",country:"USA, France",cast:["Hailee Steinfeld","Ella Purnell","Katie Leung"],description:"As conflict between Piltover and Zaun reaches a boiling point, Jinx and Vi must decide what kind of future they are fighting for.",episodes:[{number:1,title:"Heavy Is the Crown",url:e},{number:2,title:"Watch It All Burn",url:e},{number:3,title:"Finally Got It Right",url:e}]},{id:"d4",title:"The Penguin",thumbnail:t.PENGUIN,backdrop:t.PENGUIN,preview_url:"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",duration:3600,resolution:"HD",category:"series",year:2024,matchScore:95,director:"Craig Zobel",country:"USA",cast:["Colin Farrell","Cristin Milioti","Rhenzy Feliz"],description:"Following the events of The Batman, Oz Cobb makes a play for power in the underworld of Gotham City.",episodes:[]},{id:"d5",title:"Gladiator II",thumbnail:t.GLADIATOR,backdrop:t.GLADIATOR,preview_url:"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4",duration:8400,resolution:"4K",category:"action",year:2024,matchScore:96,director:"Ridley Scott",country:"USA, UK",cast:["Paul Mescal","Pedro Pascal","Denzel Washington"],description:"Years after witnessing the death of the revered hero Maximus at the hands of his uncle, Lucius is forced to enter the Colosseum.",episodes:[]},{id:"d6",title:"Moana 2",thumbnail:t.MOANA,backdrop:t.MOANA,preview_url:"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4",duration:6e3,resolution:"HD",category:"theater",year:2024,matchScore:94,director:"David G. Derrick Jr.",country:"USA",cast:["Auliʻi Cravalho","Dwayne Johnson","Alan Tudyk"],description:"After receiving an unexpected call from her wayfinding ancestors, Moana must journey to the far seas of Oceania.",episodes:[]},{id:"d7",title:"Wicked",thumbnail:t.WICKED,backdrop:t.WICKED,preview_url:"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4",duration:9e3,resolution:"4K",category:"theater",year:2024,matchScore:93,director:"Jon M. Chu",country:"USA",cast:["Cynthia Erivo","Ariana Grande","Jeff Goldblum"],description:"Elphaba, a misunderstood young woman with green skin, and Glinda, a popular blonde, forge an unlikely friendship.",episodes:[]},{id:"d8",title:"Dragon Ball Daima",thumbnail:t.DBZ,backdrop:t.DBZ,preview_url:"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4",duration:1440,resolution:"HD",category:"anime",year:2024,matchScore:98,director:"Yoshitaka Yashima",country:"Japan",cast:["Masako Nozawa","Ryō Horikawa"],description:"Goku and his friends are turned small due to a conspiracy. To fix things, they head off to a new world.",episodes:[{number:1,title:"Conspiracy",url:e}]}]}function Ee(){n.mainHeader&&(n.mainHeader.style.display="");const e=document.getElementById("heroContainer");e&&(e.style.display="",S()),D("profile"),n.videoGrid.innerHTML="",n.videoGrid.className="profile-view pb-24 bg-background-light dark:bg-background-dark min-h-screen";const t=`
<!-- Sticky Top Bar (at offset) -->
<div class="sticky top-[60px] md:top-[80px] z-40 flex items-center bg-background-light/95 dark:bg-background-dark/95 backdrop-blur-md px-4 py-3 justify-between border-b border-gray-200 dark:border-white/10">
<button class="flex size-10 shrink-0 items-center justify-center rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors" onclick="renderHome()">
<span class="material-symbols-outlined text-slate-900 dark:text-white" style="font-size: 24px;">arrow_back</span>
</button>
<h2 class="text-slate-900 dark:text-white text-lg font-bold leading-tight tracking-tight flex-1 text-center">Profile</h2>
<button class="flex w-12 items-center justify-center rounded text-sm font-semibold text-primary hover:text-red-500 transition-colors">Edit</button>
</div>
<div class="flex-1 overflow-y-auto no-scrollbar">
<!-- Profile Header -->
<div class="flex flex-col items-center pt-6 pb-6 px-4">
<div class="relative group cursor-pointer">
<div class="bg-center bg-no-repeat bg-cover rounded-lg w-28 h-28 shadow-lg ring-2 ring-transparent group-hover:ring-primary transition-all duration-300"
style='background-image: url("https://wallpapers.com/images/hd/netflix-profile-pictures-1000-x-1000-qo9h82134t9nv0j0.jpg");'>
</div>
<div class="absolute -bottom-2 -right-2 bg-surface-dark p-1.5 rounded-full border border-gray-700 shadow-md">
<span class="material-symbols-outlined text-white text-xs block">edit</span>
</div>
</div>
<h3 class="mt-4 text-2xl font-bold text-slate-900 dark:text-white tracking-tight">Isabella Hall</h3>
<button class="mt-2 text-sm font-medium text-secondary-text hover:text-white transition-colors flex items-center gap-1">
Manage Profiles <span class="material-symbols-outlined text-sm">chevron_right</span>
</button>
</div>
<!-- Profile Stats -->
<div class="grid grid-cols-3 gap-3 px-4 mb-8">
<div class="flex flex-col gap-1 rounded-lg bg-white dark:bg-[#1E1E1E] p-3 items-center text-center shadow-sm border border-gray-100 dark:border-white/5">
<p class="text-primary text-xl font-bold leading-tight">42</p>
<p class="text-slate-500 dark:text-[#B3B3B3] text-[11px] font-medium uppercase tracking-wider">Movies</p>
</div>
<div class="flex flex-col gap-1 rounded-lg bg-white dark:bg-[#1E1E1E] p-3 items-center text-center shadow-sm border border-gray-100 dark:border-white/5">
<p class="text-primary text-xl font-bold leading-tight">128h</p>
<p class="text-slate-500 dark:text-[#B3B3B3] text-[11px] font-medium uppercase tracking-wider">Streamed</p>
</div>
<div class="flex flex-col gap-1 rounded-lg bg-white dark:bg-[#1E1E1E] p-3 items-center text-center shadow-sm border border-gray-100 dark:border-white/5">
<p class="text-primary text-xl font-bold leading-tight">15</p>
<p class="text-slate-500 dark:text-[#B3B3B3] text-[11px] font-medium uppercase tracking-wider">Reviews</p>
</div>
</div>
<!-- Continue Watching Container -->
<div id="profileHistoryContainer" class="mb-8"></div>
<!-- Menu List -->
<div class="flex flex-col px-4 gap-2 mb-8">
<a class="flex items-center justify-between p-4 rounded-lg bg-white dark:bg-[#1E1E1E] hover:bg-gray-50 dark:hover:bg-white/5 transition-colors group border border-gray-100 dark:border-white/5 cursor-pointer" onclick="renderHistoryView('mylist'); return false;">
<div class="flex items-center gap-4">
<div class="p-2 rounded-full bg-slate-100 dark:bg-white/10 text-slate-600 dark:text-white group-hover:text-primary transition-colors">
<span class="material-symbols-outlined">checklist</span>
</div>
<span class="text-base font-medium text-slate-900 dark:text-white">My List</span>
</div>
<span class="material-symbols-outlined text-secondary-text text-xl">chevron_right</span>
</a>
<a class="flex items-center justify-between p-4 rounded-lg bg-white dark:bg-[#1E1E1E] hover:bg-gray-50 dark:hover:bg-white/5 transition-colors group border border-gray-100 dark:border-white/5 cursor-pointer">
<div class="flex items-center gap-4">
<div class="p-2 rounded-full bg-slate-100 dark:bg-white/10 text-slate-600 dark:text-white group-hover:text-primary transition-colors">
<span class="material-symbols-outlined">settings</span>
</div>
<span class="text-base font-medium text-slate-900 dark:text-white">App Settings</span>
</div>
<span class="material-symbols-outlined text-secondary-text text-xl">chevron_right</span>
</a>
<a class="flex items-center justify-between p-4 rounded-lg bg-white dark:bg-[#1E1E1E] hover:bg-gray-50 dark:hover:bg-white/5 transition-colors group border border-gray-100 dark:border-white/5 cursor-pointer">
<div class="flex items-center gap-4">
<div class="p-2 rounded-full bg-slate-100 dark:bg-white/10 text-slate-600 dark:text-white group-hover:text-primary transition-colors">
<span class="material-symbols-outlined">person</span>
</div>
<span class="text-base font-medium text-slate-900 dark:text-white">Account</span>
</div>
<span class="material-symbols-outlined text-secondary-text text-xl">chevron_right</span>
</a>
<a class="flex items-center justify-between p-4 rounded-lg bg-white dark:bg-[#1E1E1E] hover:bg-gray-50 dark:hover:bg-white/5 transition-colors group border border-gray-100 dark:border-white/5 cursor-pointer">
<div class="flex items-center gap-4">
<div class="p-2 rounded-full bg-slate-100 dark:bg-white/10 text-slate-600 dark:text-white group-hover:text-primary transition-colors">
<span class="material-symbols-outlined">help</span>
</div>
<span class="text-base font-medium text-slate-900 dark:text-white">Help</span>
</div>
<span class="material-symbols-outlined text-secondary-text text-xl">chevron_right</span>
</a>
</div>
<!-- Footer Actions -->
<div class="px-4 pb-8 flex flex-col items-center gap-4">
<button class="w-full py-3.5 px-4 rounded-lg bg-white dark:bg-transparent border border-gray-200 dark:border-gray-700 text-slate-900 dark:text-white font-semibold text-base hover:bg-gray-50 dark:hover:bg-white/5 hover:border-gray-300 dark:hover:border-gray-500 transition-all">
Sign Out
</button>
<p class="text-xs text-secondary-text">Version 4.12.0</p>
</div>
</div>
`;if(n.videoGrid.innerHTML=t,window.historyService){const s=window.historyService.getHistory().slice(0,10);if(s.length>0){const r=document.getElementById("profileHistoryContainer"),i=q("Continue Watching",s,"landscape");r.appendChild(i)}}}async function Ce(){n.mainHeader&&(n.mainHeader.style.display="");const e=document.getElementById("heroContainer");if(e&&(e.style.display=""),D("home"),window.innerWidth<768){document.querySelectorAll("footer").forEach(s=>s.style.display="none");const t=document.getElementById("searchModal");t&&t.classList.remove("active")}else document.querySelectorAll("footer").forEach(t=>t.style.display="");await C("home")}async function W(){n.mainHeader&&(n.mainHeader.style.display="");const e=document.getElementById("heroContainer");e&&(e.style.display="",S()),document.querySelectorAll("footer").forEach(o=>o.style.display="none");const t=document.getElementById("searchModal");t&&t.classList.remove("active"),D("search"),n.videoGrid.innerHTML="",n.videoGrid.className="mobile-search-view bg-background-light dark:bg-background-dark";const s=`
<!-- Search Header (Sticky below main header) -->
<div class="shrink-0 bg-background-dark/80 backdrop-blur-md pt-4 z-50 px-4 py-2 sticky top-[60px] md:top-[80px] w-full border-b border-white/5">
<div class="flex items-center gap-3">
<div class="relative flex-1">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-slate-400 dark:text-[#cc8f92]">
<span class="material-symbols-outlined text-[20px]">search</span>
</div>
<input autofocus class="block w-full pl-10 pr-3 py-2.5 border-none rounded-lg text-sm bg-gray-100 dark:bg-[#361618] text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-[#cc8f92]/70 focus:ring-2 focus:ring-primary focus:outline-none transition-shadow" placeholder="Search for shows, movies, genres..." type="text" id="mobileSearchInput">
<div class="absolute inset-y-0 right-0 pr-3 flex items-center cursor-pointer text-slate-400 dark:text-[#cc8f92]">
<span class="material-symbols-outlined text-[20px]">mic</span>
</div>
</div>
<button class="text-sm font-medium text-slate-500 dark:text-white/80 active:text-white" id="mobileSearchCancel">Cancel</button>
</div>
<!-- Filter Chips -->
<div id="searchFilterChips" class="mt-3 flex gap-2 overflow-x-auto no-scrollbar pb-1">
<button class="search-chip active flex h-8 shrink-0 items-center justify-center rounded-full bg-white text-black px-4" data-genre="trending">
<p class="text-xs font-bold leading-normal">Top Searches</p>
</button>
<button class="search-chip flex h-8 shrink-0 items-center justify-center rounded-full bg-gray-200 dark:bg-surface-dark border border-transparent dark:border-white/10 px-4" data-genre="hanh-dong">
<p class="text-slate-700 dark:text-gray-300 text-xs font-medium leading-normal">Action</p>
</button>
<button class="search-chip flex h-8 shrink-0 items-center justify-center rounded-full bg-gray-200 dark:bg-surface-dark border border-transparent dark:border-white/10 px-4" data-genre="hoat-hinh">
<p class="text-slate-700 dark:text-gray-300 text-xs font-medium leading-normal">Anime</p>
</button>
<button class="search-chip flex h-8 shrink-0 items-center justify-center rounded-full bg-gray-200 dark:bg-surface-dark border border-transparent dark:border-white/10 px-4" data-genre="vien-tuong">
<p class="text-slate-700 dark:text-gray-300 text-xs font-medium leading-normal">Sci-Fi</p>
</button>
<button class="search-chip flex h-8 shrink-0 items-center justify-center rounded-full bg-gray-200 dark:bg-surface-dark border border-transparent dark:border-white/10 px-4" data-genre="hai-huoc">
<p class="text-slate-700 dark:text-gray-300 text-xs font-medium leading-normal">Comedy</p>
</button>
</div>
</div>
<!-- Results/Content Area with bottom padding for nav bar -->
<div id="mobileSearchResults" class="flex-1 overflow-y-auto no-scrollbar pt-4 pb-24">
<div class="mb-3">
<h2 class="text-slate-900 dark:text-white text-lg font-bold px-4">Top Searches</h2>
</div>
<div id="topSearchesList" class="flex flex-col gap-1"></div>
<div class="pt-8 px-4">
<h2 class="text-slate-900 dark:text-white text-lg font-bold mb-4">Recommended for You</h2>
<div id="recommendedGrid" class="grid grid-cols-3 gap-3"></div>
</div>
</div>
`;n.videoGrid.innerHTML=s;const r=document.getElementById("mobileSearchInput"),i=document.getElementById("mobileSearchResults");let d=null;r&&i&&(r.addEventListener("input",o=>{clearTimeout(d);const u=o.target.value.trim();d=setTimeout(async()=>{if(!(u.length<2)){i.innerHTML='<div class="flex justify-center py-12"><div class="loading-spinner"></div></div>';try{const l=await k.searchRophim(u);if(l&&l.movies&&l.movies.length>0){i.innerHTML=`
<h2 class="text-white text-sm font-bold px-4 mb-3">Results for "${u}"</h2>
<div class="grid grid-cols-3 gap-3 px-4"></div>
`;const h=i.querySelector(".grid");l.movies.forEach(c=>{const f=document.createElement("div");f.className="relative group aspect-[2/3] overflow-hidden rounded-lg cursor-pointer",f.innerHTML=`
<div class="absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-110" style='background-image: url("${c.thumbnail}");'></div>
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity">
<div class="absolute bottom-0 left-0 right-0 p-2">
<p class="text-white text-[10px] font-bold line-clamp-1">${c.title}</p>
</div>
</div>
`,f.addEventListener("click",()=>L(c)),h.appendChild(f)})}else i.innerHTML=`
<div class="text-center py-12">
<span class="material-symbols-outlined text-4xl text-white/30 mb-2">search_off</span>
<p class="text-white/50">No results for "${u}"</p>
</div>
`}catch(l){console.error("Mobile search failed:",l),i.innerHTML='<div class="text-center py-12 text-white/50">Search failed. Try again.</div>'}}},300)}),r.focus());const m=document.getElementById("mobileSearchCancel");m&&m.addEventListener("click",()=>{const o=document.getElementById("mobileSearchInput");o&&(o.value="",o.focus()),W()});try{const o=await k.getRophimCatalog({category:"trending",limit:5});if(o&&o.movies){const l=document.getElementById("topSearchesList");o.movies.forEach(h=>{const c=document.createElement("div");c.className="group flex items-center gap-3 px-4 py-2 hover:bg-gray-100 dark:hover:bg-white/5 cursor-pointer transition-colors",c.innerHTML=`
<div class="shrink-0 relative">
<div class="bg-center bg-cover rounded-lg h-16 w-28 shadow-sm" style='background-image: url("${h.thumbnail}");'></div>
</div>
<div class="flex flex-col justify-center flex-1 min-w-0">
<p class="text-slate-900 dark:text-white text-sm font-semibold leading-normal truncate group-hover:text-primary transition-colors">${h.title}</p>
<p class="text-slate-500 dark:text-[#cc8f92] text-xs font-normal leading-normal truncate">${h.year||"2024"}</p>
</div>
<div class="shrink-0">
<span class="material-symbols-outlined text-slate-400 dark:text-white text-[28px] group-hover:text-primary">play_circle</span>
</div>
`,c.addEventListener("click",()=>L(h)),l.appendChild(c)})}const u=await k.getRophimCatalog({category:"phim-le",limit:9});if(u&&u.movies){const l=document.getElementById("recommendedGrid");u.movies.forEach(h=>{const c=document.createElement("div");c.className="relative group aspect-[2/3] overflow-hidden rounded-lg cursor-pointer",c.innerHTML=`
<div class="absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-110" style='background-image: url("${h.thumbnail}");'></div>
`,c.addEventListener("click",()=>L(h)),l.appendChild(c)})}}catch(o){console.error("Failed to load mobile search content",o)}const a=document.querySelectorAll(".search-chip");a.forEach(o=>{o.addEventListener("click",async()=>{var c;const u=o.dataset.genre;if(!u)return;a.forEach(f=>{f.classList.remove("active","bg-white","text-black"),f.classList.add("bg-gray-200","dark:bg-surface-dark");const g=f.querySelector("p");g&&(g.classList.remove("font-bold"),g.classList.add("font-medium","text-slate-700","dark:text-gray-300"))}),o.classList.add("active","bg-white","text-black"),o.classList.remove("bg-gray-200","dark:bg-surface-dark");const l=o.querySelector("p");l&&(l.classList.add("font-bold"),l.classList.remove("font-medium","text-slate-700","dark:text-gray-300"));const h=document.getElementById("mobileSearchResults");if(h){h.innerHTML='<div class="flex justify-center py-12"><div class="loading-spinner"></div></div>';try{const f=await k.getRophimCatalog({category:u,limit:12});if(f&&f.movies&&f.movies.length>0){const g=((c=o.querySelector("p"))==null?void 0:c.textContent)||u;h.innerHTML=`
<h2 class="text-white text-lg font-bold px-4 mb-4">${g}</h2>
<div class="grid grid-cols-3 gap-3 px-4"></div>
`;const y=h.querySelector(".grid");f.movies.forEach(v=>{const E=document.createElement("div");E.className="relative group aspect-[2/3] overflow-hidden rounded-lg cursor-pointer",E.innerHTML=`
<div class="absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-110" style='background-image: url("${v.thumbnail}");'></div>
`,E.addEventListener("click",()=>L(v)),y.appendChild(E)})}else h.innerHTML='<p class="text-center text-gray-400 py-12">No results found</p>'}catch(f){console.error("Genre filter error:",f),h.innerHTML='<p class="text-center text-gray-400 py-12">Failed to load content</p>'}}})})}async function Y(){n.mainHeader&&(n.mainHeader.style.display="");const e=document.getElementById("heroContainer");e&&(e.style.display="",S()),document.querySelectorAll("footer").forEach(m=>m.style.display="none");const t=document.getElementById("searchModal");t&&t.classList.remove("active"),D("mylist");const s=window.historyService?window.historyService.getFavorites():[];n.videoGrid.innerHTML="",n.videoGrid.className="mobile-mylist-view min-h-screen bg-background-dark pb-24";const r=`
<!-- Sticky Header (Using sticky at an offset to allow scrolling past hero and main header) -->
<header class="sticky top-[60px] md:top-[80px] left-0 right-0 z-[100] flex flex-col bg-background-dark/90 backdrop-blur-md pt-4 border-b border-white/5">
<div class="flex items-center justify-between px-4 pb-2">
<h1 class="text-2xl font-bold tracking-tight text-white">My List</h1>
<button class="flex h-10 w-10 items-center justify-center rounded-full text-white hover:bg-white/10 transition-colors">
<span class="material-symbols-outlined text-[24px]">edit</span>
</button>
</div>
<!-- Filter Chips -->
<div id="mylistFilterChips" class="flex w-full gap-3 overflow-x-auto px-4 pb-4 pt-2 no-scrollbar">
<button class="mylist-chip active flex h-8 shrink-0 items-center justify-center rounded-full bg-white px-4 shadow-lg shadow-white/10" data-filter="all" data-category="trending">
<p class="text-xs font-bold text-black">All</p>
</button>
<button class="mylist-chip flex h-8 shrink-0 items-center justify-center rounded-full bg-surface-dark border border-white/20 px-4 hover:bg-white/10" data-filter="movies" data-category="phim-le">
<p class="text-xs font-medium text-gray-200">Movies</p>
</button>
<button class="mylist-chip flex h-8 shrink-0 items-center justify-center rounded-full bg-surface-dark border border-white/20 px-4 hover:bg-white/10" data-filter="tvshows" data-category="phim-bo">
<p class="text-xs font-medium text-gray-200">TV Shows</p>
</button>
<button class="mylist-chip flex h-8 shrink-0 items-center justify-center rounded-full bg-surface-dark border border-white/20 px-4 hover:bg-white/10" data-filter="anime" data-category="hoat-hinh">
<p class="text-xs font-medium text-gray-200">Anime</p>
</button>
</div>
</header>
<!-- Grid Container -->
<main class="px-4 pt-4 pb-24">
<div id="mylistGrid" class="grid grid-cols-3 gap-3"></div>
</main>
`;n.videoGrid.innerHTML=r;const i=document.getElementById("mylistGrid");if(s.length>0)s.forEach(m=>{const a=document.createElement("div");a.className="group relative flex flex-col gap-2 cursor-pointer",a.innerHTML=`
<div class="relative w-full overflow-hidden rounded-md bg-surface-dark shadow-md aspect-[2/3]">
<div class="absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-105"
style='background-image: url("${m.thumbnail||m.poster_url}");'></div>
<div class="absolute inset-0 bg-black/0 transition-colors group-active:bg-black/20"></div>
</div>
`,a.addEventListener("click",()=>L(m)),i.appendChild(a)});else try{const m=await k.getRophimCatalog({category:"trending",limit:12});m&&m.movies&&m.movies.forEach((a,o)=>{const u=document.createElement("div");u.className="group relative flex flex-col gap-2 cursor-pointer",u.innerHTML=`
<div class="relative w-full overflow-hidden rounded-md bg-surface-dark shadow-md aspect-[2/3]">
<div class="absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-105"
style='background-image: url("${a.thumbnail}");'></div>
${o===0?'<div class="absolute top-0 right-0 rounded-bl-md bg-primary px-1.5 py-0.5"><span class="text-[10px] font-bold uppercase text-white tracking-wider">New</span></div>':""}
<div class="absolute inset-0 bg-black/0 transition-colors group-active:bg-black/20"></div>
</div>
`,u.addEventListener("click",()=>L(a)),i.appendChild(u)})}catch(m){console.error("Failed to load my list content",m)}const d=document.querySelectorAll(".mylist-chip");d.forEach(m=>{m.addEventListener("click",async()=>{const a=m.dataset.filter,o=m.dataset.category;if(!a||!o)return;d.forEach(h=>{h.classList.remove("active","bg-white"),h.classList.add("bg-surface-dark");const c=h.querySelector("p");c&&(c.classList.remove("font-bold","text-black"),c.classList.add("font-medium","text-gray-200"))}),m.classList.add("active","bg-white"),m.classList.remove("bg-surface-dark");const u=m.querySelector("p");u&&(u.classList.add("font-bold","text-black"),u.classList.remove("font-medium","text-gray-200"));const l=document.getElementById("mylistGrid");if(l){l.innerHTML='<div class="col-span-3 flex justify-center py-12"><div class="loading-spinner"></div></div>';try{const h=await k.getRophimCatalog({category:o,limit:12});l.innerHTML="",h&&h.movies&&h.movies.length>0?h.movies.forEach((c,f)=>{const g=document.createElement("div");g.className="group relative flex flex-col gap-2 cursor-pointer",g.innerHTML=`
<div class="relative w-full overflow-hidden rounded-md bg-surface-dark shadow-md aspect-[2/3]">
<div class="absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-105"
style='background-image: url("${c.thumbnail}");'></div>
${f===0?'<div class="absolute top-0 right-0 rounded-bl-md bg-primary px-1.5 py-0.5"><span class="text-[10px] font-bold uppercase text-white tracking-wider">New</span></div>':""}
<div class="absolute inset-0 bg-black/0 transition-colors group-active:bg-black/20"></div>
</div>
`,g.addEventListener("click",()=>L(c)),l.appendChild(g)}):l.innerHTML='<p class="col-span-3 text-center text-gray-400 py-12">No content found</p>'}catch(h){console.error("Filter error:",h),l.innerHTML='<p class="col-span-3 text-center text-gray-400 py-12">Failed to load content</p>'}}})})}