dptxa-proxy / src /components /Header.astro
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
34.7 kB
---
import { txasupabase } from '../lib/txasupabase.js';
const genres = await txasupabase.getGenres().catch(() => []);
const countries = await txasupabase.getCountries().catch(() => []);
const navLinks = [
{ href: '/', label: 'Trang chủ' },
{ href: '/danh-sach/phim-le', label: 'Phim Lẻ' },
{ href: '/danh-sach/phim-bo', label: 'Phim Bộ' },
];
---
<header
id="main-header"
class="fixed top-0 left-0 right-0 z-[100] transition-all duration-500 bg-gradient-to-b from-black/35 via-transparent to-transparent"
>
<div id="header-inner" class="w-full flex items-center justify-between px-4 md:px-6 xl:px-10 py-4 md:py-6 transition-all duration-300">
<!-- Left: Logo & Search -->
<div class="flex items-center gap-2 md:gap-4 lg:gap-6 flex-1 min-w-0">
<button id="menu-drawer-trigger" class="xl:hidden text-white hover:text-primary transition-all text-lg p-2.5 rounded-xl hover:bg-white/5 cursor-pointer shrink-0 flex items-center justify-center" aria-label="Mở menu">
<i class="fas fa-bars"></i>
</button>
<a href="/" class="flex items-center shrink-0 transition-transform hover:scale-[1.02] active:scale-95 group">
<!-- SVG Play Circle Logo (Matching TPHIMX Premium Theme) -->
<svg class="h-9 md:h-10 w-auto animate-pulse" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" style="height: 42px; width: 42px;">
<circle cx="50" cy="50" r="44" stroke="url(#purple-grad)" stroke-width="5" fill="black" fill-opacity="0.3"/>
<circle cx="50" cy="50" r="36" stroke="url(#purple-grad)" stroke-width="1.5" stroke-dasharray="3 3" opacity="0.8"/>
<path d="M44 32.5L68 50L44 67.5V32.5Z" fill="url(#purple-grad)" stroke="url(#purple-grad)" stroke-width="1" stroke-linejoin="round"/>
<defs>
<linearGradient id="purple-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#a78bfa"/>
<stop offset="50%" stop-color="#7c3aed"/>
<stop offset="100%" stop-color="#4c1d95"/>
</linearGradient>
</defs>
</svg>
<div class="flex flex-col ml-2.5 justify-center leading-none">
<span class="text-lg md:text-[22px] font-black tracking-tight text-white flex items-center leading-none">
D<span class="text-primary">P</span>TXA
</span>
<span class="hidden sm:inline-block text-[8px] font-black text-gray-400 mt-2 tracking-widest uppercase leading-none">Động Phim Đỉnh Cao</span>
</div>
</a>
<!-- Search Bar (Pill-shaped, next to Logo) -->
<div class="relative hidden xl:block w-40 focus-within:w-52 xl:w-56 xl:focus-within:w-72 transition-all duration-300 ease-out group shrink-0">
<form id="header-search-form" class="flex items-center h-9 rounded-full bg-white/[0.06] border border-white/[0.08] px-3.5 focus-within:bg-white/10 focus-within:border-white/15 transition-all cursor-pointer w-full">
<i class="fas fa-search text-[11px] text-gray-400 shrink-0 select-none"></i>
<input
type="text"
id="header-search-input"
placeholder="Tìm phim, diễn viên..."
class="flex-1 bg-transparent text-[11px] font-bold text-white outline-none placeholder:text-gray-400/40 min-w-0 ml-2 transition-all duration-300"
autocomplete="off"
/>
<span id="search-shortcut-badge" class="hidden sm:inline-flex items-center justify-center text-[8px] font-black text-gray-400 bg-white/10 border border-white/5 px-2 py-0.5 rounded ml-2 shrink-0 leading-none group-focus-within:hidden select-none">/</span>
</form>
<div id="search-suggestions" class="absolute left-0 right-0 top-full mt-2 max-h-[400px] overflow-y-auto hidden rounded-2xl border border-white/10 p-2 shadow-2xl glass-dropdown z-[120] scrollbar-thin">
<div id="suggestions-content" class="flex flex-col gap-1"></div>
</div>
</div>
</div>
<div class="flex items-center gap-4 xl:gap-6 shrink-0">
<!-- Desktop Navigation Menu (Inline in Header!) -->
<nav class="hidden xl:flex items-center gap-1 xl:gap-2 2xl:gap-3 text-[11px] xl:text-[13px] 2xl:text-[14px] font-black uppercase tracking-wider text-gray-300">
<a href="/chu-de" data-astro-prefetch class="px-2 xl:px-3 py-1.5 xl:py-2 hover:text-white rounded-xl hover:bg-white/[0.06] transition-all whitespace-nowrap">Chủ đề</a>
<div class="relative group">
<button class="flex items-center gap-1 px-2 xl:px-3 py-1.5 xl:py-2 hover:text-white rounded-xl hover:bg-white/[0.06] transition-all cursor-pointer whitespace-nowrap">
Thể loại <i class="fas fa-caret-down text-[10px] opacity-50 ml-0.5"></i>
</button>
<div class="absolute left-1/2 -translate-x-1/2 top-[80%] pt-4 w-[560px] opacity-0 translate-y-1 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto transition-all duration-300 z-[120]">
<div class="glass-dropdown rounded-3xl p-5 border border-white/10 shadow-2xl grid grid-cols-4 gap-1.5 max-h-[380px] overflow-y-auto scrollbar-thin">
{genres.length > 0 ? genres.map((g: any) => (
<a href={`/the-loai/${g.slug}`} class="px-3.5 py-2.5 rounded-xl text-[12px] font-bold text-gray-400 hover:bg-white/5 hover:text-white transition-all truncate">{g.name}</a>
)) : (
<span class="col-span-4 text-center text-gray-600 text-[12px] py-4">Chưa có thể loại</span>
)}
</div>
</div>
</div>
<a href="/danh-sach/phim-le" data-astro-prefetch class="px-2 xl:px-3 py-1.5 xl:py-2 hover:text-white rounded-xl hover:bg-white/[0.06] transition-all whitespace-nowrap">Phim Lẻ</a>
<a href="/danh-sach/phim-bo" data-astro-prefetch class="px-2 xl:px-3 py-1.5 xl:py-2 hover:text-white rounded-xl hover:bg-white/[0.06] transition-all whitespace-nowrap">Phim Bộ</a>
<a href="#" id="xem-chung-btn" class="flex items-center gap-1 xl:gap-1.5 px-2 xl:px-3 py-1.5 xl:py-2 hover:text-white rounded-xl hover:bg-white/[0.06] transition-all whitespace-nowrap group" data-txatooltip="Xem phim cùng bạn bè thời gian thực - Sắp ra mắt!">
Xem Chung
<span class="bg-gradient-to-r from-violet-500 to-fuchsia-500 text-white text-[8px] font-black px-1.5 py-0.5 rounded shadow-[0_0_12px_rgba(168,85,247,0.5)] tracking-wide uppercase leading-none scale-90 origin-left">Sắp ra mắt</span>
</a>
<div class="relative group">
<button class="flex items-center gap-1 px-2 xl:px-3 py-1.5 xl:py-2 hover:text-white rounded-xl hover:bg-white/[0.06] transition-all cursor-pointer whitespace-nowrap">
Quốc gia <i class="fas fa-caret-down text-[10px] opacity-50 ml-0.5"></i>
</button>
<div class="absolute left-1/2 -translate-x-1/2 top-[80%] pt-4 w-[460px] opacity-0 translate-y-1 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto transition-all duration-300 z-[120]">
<div class="glass-dropdown rounded-3xl p-5 border border-white/10 shadow-2xl grid grid-cols-3 gap-1.5 max-h-[380px] overflow-y-auto scrollbar-thin">
{countries.length > 0 ? countries.map((c: any) => (
<a href={`/quoc-gia/${c.slug}`} class="px-3.5 py-2.5 rounded-xl text-[12px] font-bold text-gray-400 hover:bg-white/5 hover:text-white transition-all truncate">{c.name}</a>
)) : (
<span class="col-span-3 text-center text-gray-600 text-[12px] py-4">Chưa có quốc gia</span>
)}
</div>
</div>
</div>
<a href="/dien-vien" class="px-2 xl:px-3 py-1.5 xl:py-2 hover:text-white rounded-xl hover:bg-white/[0.06] transition-all whitespace-nowrap">Diễn viên</a>
<a href="/lich-chieu" class="px-2 xl:px-3 py-1.5 xl:py-2 hover:text-white rounded-xl hover:bg-white/[0.06] transition-all whitespace-nowrap">Lịch chiếu</a>
</nav>
<!-- Auth + Mobile Buttons -->
<div class="flex items-center gap-3 shrink-0">
<div id="auth-container" class="relative group">
<div id="auth-loading" class="flex items-center gap-3">
<div class="h-8 w-24 animate-pulse rounded-lg bg-white/5"></div>
</div>
<div id="auth-guest" class="hidden">
<a href="/dang-nhap" data-astro-prefetch class="flex items-center justify-center gap-2 rounded-full bg-white/[0.08] border border-white/[0.08] px-5 py-2 text-xs font-black text-white hover:bg-white hover:text-black hover:border-white transition-all">
<i class="fas fa-user text-xs"></i>
Thành viên
</a>
</div>
<div id="auth-user" class="hidden flex items-center gap-3 cursor-pointer group">
<div class="hidden sm:flex flex-col items-end leading-none">
<span class="text-[9.5px] font-black uppercase tracking-widest text-gray-500 mb-1">Thành viên</span>
<span id="user-display-name" class="text-[13px] font-bold text-white group-hover:text-primary transition-colors">Admin</span>
</div>
<div class="relative">
<div id="user-avatar" class="h-9 w-9 rounded-xl bg-gradient-to-br from-primary to-primary-dark flex items-center justify-center text-white font-black shadow-lg ring-2 ring-white/10 overflow-hidden">
<img id="user-avatar-img" src="" class="h-full w-full object-cover hidden" />
<span id="user-avatar-text" class="text-xs">A</span>
</div>
<div class="absolute right-0 top-full pt-3 w-64 translate-y-2 opacity-0 pointer-events-none group-hover:translate-y-0 group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300 z-[110]">
<div class="glass-dropdown rounded-2xl p-2 border border-white/10 shadow-2xl overflow-hidden">
<div class="px-4 py-3.5 border-b border-white/5 mb-1.5">
<div id="user-email" class="text-xs font-semibold text-gray-500 truncate">user@example.com</div>
</div>
<a href="/ca-nhan/yeu-thich" class="flex items-center gap-3 px-4 py-3 rounded-xl text-xs font-bold text-gray-400 hover:bg-white/5 hover:text-white transition-all"><i class="fas fa-heart text-[15px] text-primary/60"></i> Phim yêu thích</a>
<a href="/ca-nhan/lich-su" class="flex items-center gap-3 px-4 py-3 rounded-xl text-xs font-bold text-gray-400 hover:bg-white/5 hover:text-white transition-all"><i class="fas fa-history text-[15px] text-primary/60"></i> Lịch sử xem</a>
<div id="admin-link-container"></div>
<button id="logout-btn" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-xs font-bold text-gray-400 hover:bg-red-500/10 hover:text-red-500 transition-all"><i class="fas fa-sign-out-alt text-[15px]"></i> Đăng xuất</button>
</div>
</div>
</div>
</div>
</div>
<!-- Mobile triggers -->
<div class="flex items-center gap-2 xl:hidden">
<button id="mobile-search-trigger" class="text-gray-400 hover:text-white h-9 w-9 flex items-center justify-center rounded-lg bg-white/5"><i class="fas fa-search text-xs"></i></button>
</div>
</div>
</div>
</div>
<!-- Mobile Search Overlay -->
<div id="mobile-search-overlay" class="fixed inset-0 z-[110] hidden bg-black/95 backdrop-blur-3xl animate-in fade-in duration-300">
<div class="p-6">
<div class="flex items-center justify-between mb-8">
<div class="text-2xl font-black tracking-tighter text-white">D<span class="text-primary">P</span>TXA</div>
<button id="mobile-search-close" class="h-12 w-12 flex items-center justify-center rounded-full bg-white/5 text-white">
<i class="fas fa-times text-xl"></i>
</button>
</div>
<form id="mobile-search-form" class="relative">
<input
type="text"
id="mobile-search-input"
placeholder="Nhập tên phim bạn muốn tìm..."
class="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-5 text-lg font-bold text-white outline-none focus:border-primary/50 focus:bg-white/10 transition-all"
autocomplete="off"
/>
<button type="submit" class="absolute right-4 top-1/2 -translate-y-1/2 h-12 w-12 flex items-center justify-center rounded-xl bg-primary text-white shadow-[0_0_20px_var(--primary)]">
<i class="fas fa-search"></i>
</button>
</form>
<!-- Mobile Search Suggestions -->
<div id="mobile-search-suggestions" class="mt-6 max-h-[calc(100vh-220px)] overflow-y-auto hidden rounded-3xl border border-white/10 p-2 glass-dropdown">
<div id="mobile-suggestions-content" class="flex flex-col gap-1"></div>
</div>
</div>
</div>
<!-- Slide-out Navigation Drawer (Glassmorphic) -->
<div
id="menu-drawer"
class="fixed inset-y-0 left-0 w-80 max-w-[85vw] z-[120] -translate-x-full hidden flex-col h-full max-h-screen overflow-y-auto scrollbar-hide bg-black/95 backdrop-blur-3xl border-r border-white/10 p-6 transition-transform duration-300 ease-out overscroll-contain"
style="-webkit-overflow-scrolling: touch;"
>
<!-- Close button & Logo -->
<div class="flex items-center justify-between mb-6 pb-4 border-b border-white/5 shrink-0">
<div class="text-[17px] font-black tracking-tight text-white flex items-center">
D<span class="text-primary mr-0.5">P</span>TXA
</div>
<button id="menu-drawer-close" class="h-8 w-8 flex items-center justify-center rounded-xl bg-white/5 text-white hover:bg-white/10 transition-all cursor-pointer">
<i class="fas fa-times text-sm"></i>
</button>
</div>
<!-- Navigation Links inside Drawer -->
<div class="space-y-1.5 pr-1">
<a href="/" class="flex items-center gap-3 rounded-xl hover:bg-white/5 px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white transition-all">
<i class="fas fa-home text-sm text-primary/60 w-5"></i> Trang chủ
</a>
<a href="/chu-de" class="flex items-center gap-3 rounded-xl hover:bg-white/5 px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white transition-all">
<i class="fas fa-compass text-sm text-primary/60 w-5"></i> Chủ đề
</a>
<a href="/danh-sach/phim-le" class="flex items-center gap-3 rounded-xl hover:bg-white/5 px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white transition-all">
<i class="fas fa-film text-sm text-primary/60 w-5"></i> Phim Lẻ
</a>
<a href="/danh-sach/phim-bo" class="flex items-center gap-3 rounded-xl hover:bg-white/5 px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white transition-all">
<i class="fas fa-tv text-sm text-primary/60 w-5"></i> Phim Bộ
</a>
<a href="#" id="drawer-xem-chung-btn" class="flex items-center justify-between rounded-xl hover:bg-white/5 px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white transition-all group">
<span class="flex items-center gap-3">
<i class="fas fa-users text-sm text-primary/60 w-5"></i> Xem Chung
</span>
<span class="bg-gradient-to-r from-violet-500 to-fuchsia-500 text-white text-[8px] font-black px-1.5 py-0.5 rounded leading-none">Sắp ra mắt</span>
</a>
<a href="/dien-vien" class="flex items-center gap-3 rounded-xl hover:bg-white/5 px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white transition-all">
<i class="fas fa-user-friends text-sm text-primary/60 w-5"></i> Diễn viên
</a>
<a href="/lich-chieu" class="flex items-center gap-3 rounded-xl hover:bg-white/5 px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white transition-all">
<i class="fas fa-calendar-alt text-sm text-primary/60 w-5"></i> Lịch chiếu
</a>
<!-- Dividers for lists -->
<div class="h-px bg-white/5 my-4"></div>
<!-- Countries list -->
<div class="px-4 py-2 text-[10px] font-black tracking-widest text-gray-500 uppercase">Quốc gia</div>
<div class="grid grid-cols-2 gap-1.5 px-2">
{countries.length > 0 ? countries.map((c: any) => (
<a href={`/quoc-gia/${c.slug}`} class="px-2.5 py-2 rounded-lg text-[11px] font-bold text-gray-400 hover:bg-white/5 hover:text-white transition-all truncate">{c.name}</a>
)) : (
<span class="col-span-2 text-center text-gray-600 text-[11px] py-2">Chưa có quốc gia</span>
)}
</div>
<!-- Genres list -->
<div class="h-px bg-white/5 my-4"></div>
<div class="px-4 py-2 text-[10px] font-black tracking-widest text-gray-500 uppercase">Thể loại</div>
<div class="grid grid-cols-2 gap-1.5 px-2 pb-6">
{genres.length > 0 ? genres.map((g: any) => (
<a href={`/the-loai/${g.slug}`} class="px-2.5 py-2 rounded-lg text-[11px] font-bold text-gray-400 hover:bg-white/5 hover:text-white transition-all truncate">{g.name}</a>
)) : (
<span class="col-span-2 text-center text-gray-600 text-[11px] py-2">Chưa có thể loại</span>
)}
</div>
</div>
</div>
<!-- Drawer Backdrop Overlay -->
<div
id="drawer-backdrop"
class="fixed inset-0 z-[115] bg-black/60 backdrop-blur-sm hidden opacity-0 transition-opacity duration-300"
></div>
</header>
<script>
function initHeader() {
const header = document.getElementById('main-header');
if (!header || header.dataset.initialized) return;
header.dataset.initialized = 'true';
const drawerTrigger = document.getElementById('menu-drawer-trigger');
const drawer = document.getElementById('menu-drawer');
const backdrop = document.getElementById('drawer-backdrop');
const drawerClose = document.getElementById('menu-drawer-close');
const searchForm = document.getElementById('header-search-form');
const searchInput = document.getElementById('header-search-input');
// Shortcut badge label detection
const shortcutBadge = document.getElementById('search-shortcut-badge');
const isMac = navigator.userAgent.toLowerCase().includes('mac');
if (shortcutBadge) {
shortcutBadge.textContent = isMac ? '⌘K' : '/';
}
// Global Keydown search shortcut focus
if (!window._hasSearchShortcut) {
window._hasSearchShortcut = true;
window.addEventListener('keydown', (e) => {
const activeEl = document.activeElement;
if (activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA' || activeEl.hasAttribute('contenteditable'))) {
return;
}
const isMacUser = navigator.userAgent.toLowerCase().includes('mac');
const sInput = document.getElementById('header-search-input');
if ((isMacUser && e.metaKey && e.key.toLowerCase() === 'k') || (!isMacUser && e.key === '/')) {
e.preventDefault();
sInput?.focus();
sInput?.select();
}
});
}
if (!header) return;
// Scroll Effect
const headerInner = document.getElementById('header-inner');
const handleScroll = () => {
if (window.scrollY > 30) {
header.classList.add('glass-scrolled', 'shadow-2xl');
header.classList.remove('bg-gradient-to-b', 'from-black/35', 'via-transparent', 'to-transparent');
headerInner?.classList.add('py-3');
headerInner?.classList.remove('py-4', 'md:py-6');
} else {
header.classList.remove('glass-scrolled', 'shadow-2xl');
header.classList.add('bg-gradient-to-b', 'from-black/35', 'via-transparent', 'to-transparent');
headerInner?.classList.add('py-4', 'md:py-6');
headerInner?.classList.remove('py-3');
}
};
window.addEventListener('scroll', handleScroll);
handleScroll(); // Init state
// Drawer Xem Chung click handling with txatoast
const drawerXemChungBtn = document.getElementById('drawer-xem-chung-btn');
drawerXemChungBtn?.addEventListener('click', (e) => {
e.preventDefault();
const { txatoast } = window;
if (txatoast) {
txatoast.info('Tính năng Xem Chung phim trực tuyến đang được phát triển và sẽ sớm ra mắt!');
} else {
alert('Tính năng Xem Chung phim trực tuyến sắp ra mắt!');
}
});
// Auto-focus search input when search form is clicked
searchForm?.addEventListener('click', () => {
searchInput?.focus();
});
// Drawer Open & Close Animation Logic
const openDrawer = () => {
drawer?.classList.remove('hidden');
backdrop?.classList.remove('hidden');
setTimeout(() => {
drawer?.classList.remove('-translate-x-full');
backdrop?.classList.add('opacity-100');
}, 10);
document.body.style.overflow = 'hidden';
};
const closeDrawer = () => {
drawer?.classList.add('-translate-x-full');
backdrop?.classList.remove('opacity-100');
setTimeout(() => {
drawer?.classList.add('hidden');
backdrop?.classList.add('hidden');
}, 300);
document.body.style.overflow = '';
};
drawerTrigger?.addEventListener('click', openDrawer);
drawerClose?.addEventListener('click', closeDrawer);
backdrop?.addEventListener('click', closeDrawer);
// Mobile Search Overlay Logic
const mobileSearchTrigger = document.getElementById('mobile-search-trigger');
const mobileSearchOverlay = document.getElementById('mobile-search-overlay');
const mobileSearchClose = document.getElementById('mobile-search-close');
const mobileSearchForm = document.getElementById('mobile-search-form');
const mobileSearchInput = document.getElementById('mobile-search-input');
mobileSearchTrigger?.addEventListener('click', () => {
mobileSearchOverlay?.classList.remove('hidden');
document.body.style.overflow = 'hidden';
setTimeout(() => mobileSearchInput?.focus(), 300);
});
mobileSearchClose?.addEventListener('click', () => {
mobileSearchOverlay?.classList.add('hidden');
if (drawer?.classList.contains('hidden')) {
document.body.style.overflow = '';
}
});
mobileSearchForm?.addEventListener('submit', (e) => {
e.preventDefault();
const q = mobileSearchInput?.value.trim();
if (q) window.location.href = `/tim-kiem?q=${encodeURIComponent(q)}`;
});
// Search Suggestions and Logic
const searchSuggestions = document.getElementById('search-suggestions');
const suggestionsContent = document.getElementById('suggestions-content');
const mobileSearchSuggestions = document.getElementById('mobile-search-suggestions');
const mobileSuggestionsContent = document.getElementById('mobile-suggestions-content');
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const formatImageUrl = (url) => {
if (!url) return '';
if (url.startsWith('http')) return url;
const cleanUrl = url.startsWith('/') ? url : `/${url}`;
return `https://phimimg.com${cleanUrl}`;
};
function highlightMatch(text, query) {
if (!query || !text) return text;
const normalize = (str) => {
return str
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.replace(/đ/g, "d");
};
const normalizedText = normalize(text);
const normalizedQuery = normalize(query);
let result = "";
let lastIdx = 0;
let idx = normalizedText.indexOf(normalizedQuery);
if (idx === -1) return text;
while (idx !== -1) {
result += text.substring(lastIdx, idx);
result += `<span class="text-primary font-black">${text.substring(idx, idx + query.length)}</span>`;
lastIdx = idx + query.length;
idx = normalizedText.indexOf(normalizedQuery, lastIdx);
}
result += text.substring(lastIdx);
return result;
}
const performSearch = async (query, container, dropdown) => {
const q = query.trim();
if (!q) {
dropdown?.classList.add('hidden');
if (container) container.innerHTML = '';
return;
}
try {
const res = await fetch(`/api/movie/search?q=${encodeURIComponent(q)}`);
const { items } = await res.json();
if (!items || items.length === 0) {
if (container) {
container.innerHTML = `
<div class="p-4 text-center text-xs font-black uppercase tracking-widest text-gray-500">
Không tìm thấy phim nào
</div>
`;
}
dropdown?.classList.remove('hidden');
return;
}
if (container) {
container.innerHTML = items.slice(0, 5).map((movie: any) => {
const poster = formatImageUrl(movie.poster_url || movie.thumb_url);
const titleHtml = highlightMatch(movie.name, q);
const originHtml = highlightMatch(movie.origin_name, q);
const status = movie.episode_current || 'HD';
const year = movie.year || '';
const type = movie.type === 'single' ? 'Phim Lẻ' : 'Phim Bộ';
return `
<a href="/phim/${movie.slug}" class="flex items-center gap-3 p-2 rounded-2xl hover:bg-white/5 transition-all group">
<div class="h-14 w-10 overflow-hidden rounded-xl border border-white/10 shrink-0">
<img src="${poster}" class="h-full w-full object-cover group-hover:scale-105 transition-transform" />
</div>
<div class="min-w-0 flex-1">
<div class="text-[11px] font-black uppercase text-white truncate group-hover:text-primary transition-colors">${titleHtml}</div>
<div class="text-[9px] font-bold text-gray-500 truncate">${originHtml}</div>
<div class="flex items-center gap-2 mt-1">
<span class="text-[8px] font-black uppercase text-primary px-1.5 py-0.5 rounded bg-primary/10 border border-primary/20">${status}</span>
${year ? `<span class="text-[8px] font-bold text-gray-400">${year}</span>` : ''}
<span class="text-[8px] font-bold text-gray-500">${type}</span>
</div>
</div>
<i class="fas fa-chevron-right text-[8px] text-gray-600 group-hover:text-primary transition-colors mr-2"></i>
</a>
`;
}).join('');
}
dropdown?.classList.remove('hidden');
} catch (err) {
console.error('Search suggestion error:', err);
}
};
const debouncedDesktopSearch = debounce((q: string) => {
performSearch(q, suggestionsContent, searchSuggestions);
}, 150);
const debouncedMobileSearch = debounce((q) => {
performSearch(q, mobileSuggestionsContent, mobileSearchSuggestions);
}, 150);
searchInput?.addEventListener('input', (e) => {
debouncedDesktopSearch(e.target.value);
});
mobileSearchInput?.addEventListener('input', (e) => {
debouncedMobileSearch(e.target.value);
});
// Hide dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!searchForm?.contains(e.target) && !searchSuggestions?.contains(e.target)) {
searchSuggestions?.classList.add('hidden');
}
});
// Focus triggers again if there's value
searchInput?.addEventListener('focus', () => {
if (searchInput.value.trim()) {
searchSuggestions?.classList.remove('hidden');
}
});
searchForm?.addEventListener('submit', (e) => {
e.preventDefault();
const query = searchInput?.value.trim();
if (query) {
window.location.href = `/tim-kiem?q=${encodeURIComponent(query)}`;
}
});
// Close menu on link click inside drawer
drawer?.querySelectorAll('a').forEach(link => {
link.addEventListener('click', closeDrawer);
});
}
async function checkAuth() {
const authContainer = document.getElementById('auth-container');
if (!authContainer || authContainer.dataset.initialized) return;
authContainer.dataset.initialized = 'true';
const loading = document.getElementById('auth-loading');
const guest = document.getElementById('auth-guest');
const userEl = document.getElementById('auth-user');
const mobileContainer = document.getElementById('mobile-auth-menu');
try {
const res = await fetch('/api/auth/me');
const { user } = await res.json();
if (loading) loading.classList.add('hidden');
if (user) {
// Desktop UI
if (userEl) {
userEl.classList.remove('hidden');
const nameEl = document.getElementById('user-display-name');
const emailEl = document.getElementById('user-email');
const avatarText = document.getElementById('user-avatar-text');
const adminContainer = document.getElementById('admin-link-container');
if (nameEl) nameEl.textContent = user.username;
if (emailEl) emailEl.textContent = user.email || `${user.username}@tphimx.com`;
const avatarImg = document.getElementById('user-avatar-img');
if (user.avatar_url && avatarImg) {
avatarImg.src = user.avatar_url;
avatarImg.classList.remove('hidden');
avatarText?.classList.add('hidden');
} else if (avatarText) {
avatarText.textContent = user.username[0].toUpperCase();
avatarText.classList.remove('hidden');
avatarImg?.classList.add('hidden');
}
if (user.role === 'admin' && adminContainer) {
adminContainer.innerHTML = `
<a href="/admin" class="flex items-center gap-3 px-4 py-3 rounded-xl text-xs font-bold text-primary hover:bg-primary/10 transition-all">
<i class="fas fa-user-shield text-[15px]"></i>
Bảng quản trị
</a>
`;
}
document.getElementById('logout-btn')?.addEventListener('click', async () => {
await fetch('/api/auth/logout', { method: 'POST' });
window.location.reload();
});
}
// Mobile UI
if (mobileContainer) {
mobileContainer.innerHTML = `
<div class="w-full flex flex-col gap-4">
<div class="flex items-center justify-between rounded-3xl bg-white/5 p-6 border border-white/10">
<div class="flex items-center gap-4">
${user.avatar_url ? `
<div class="h-14 w-14 rounded-2xl overflow-hidden shadow-[0_0_20px_var(--primary)] ring-2 ring-white/10 shrink-0">
<img src="${user.avatar_url}" class="h-full w-full object-cover" />
</div>
` : `
<div class="h-14 w-14 rounded-2xl bg-primary flex items-center justify-center text-2xl font-black text-white shadow-[0_0_20px_var(--primary)] shrink-0">${user.username[0].toUpperCase()}</div>
`}
<div class="flex flex-col">
<span class="text-[10px] font-black uppercase tracking-widest text-gray-500">Thành viên TPHIMX</span>
<span class="text-lg font-black uppercase text-white">${user.username}</span>
</div>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<a href="/ca-nhan/yeu-thich" class="flex flex-col items-center gap-3 rounded-3xl bg-white/5 p-6 border border-white/10 text-[10px] font-black uppercase tracking-widest text-gray-400">
<i class="fas fa-heart text-2xl text-primary"></i>
Yêu thích
</a>
<a href="/ca-nhan/lich-su" class="flex flex-col items-center gap-3 rounded-3xl bg-white/5 p-6 border border-white/10 text-[10px] font-black uppercase tracking-widest text-gray-400">
<i class="fas fa-history text-2xl text-primary"></i>
Lịch sử
</a>
</div>
${user.role === 'admin' ? `
<a href="/admin" class="flex items-center justify-center gap-3 rounded-3xl bg-primary/20 border border-primary/30 p-5 text-[10px] font-black uppercase tracking-widest text-primary">
<i class="fas fa-user-shield"></i>
Quản trị hệ thống
</a>
` : ''}
<button id="mobile-logout-btn" class="flex items-center justify-center gap-3 rounded-3xl bg-red-500/10 border border-red-500/20 p-5 text-[10px] font-black uppercase tracking-widest text-red-500">
<i class="fas fa-sign-out-alt"></i>
Đăng xuất tài khoản
</button>
</div>
`;
document.getElementById('mobile-logout-btn')?.addEventListener('click', async () => {
await fetch('/api/auth/logout', { method: 'POST' });
window.location.reload();
});
}
} else {
if (guest) guest.classList.remove('hidden');
}
if (window.initTxaTooltips) window.initTxaTooltips();
} catch (err) {
console.error('Auth check failed:', err);
if (guest) guest.classList.remove('hidden');
}
}
// Run immediately since DOM is ready when script executes
initHeader();
checkAuth();
// Support Astro View Transitions
document.addEventListener('astro:page-load', () => {
initHeader();
checkAuth();
});
</script>
<style>
.glass-scrolled {
backdrop-filter: blur(20px) saturate(180%);
background: rgba(10, 10, 10, 0.85);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.glass-dropdown {
background: rgba(15, 15, 15, 0.92);
backdrop-filter: blur(30px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.08);
}
</style>