function initWorkPage() { document.body.classList.remove('insight-page'); const header = document.getElementById('site-header'); const isInsightListing = window.location.pathname === '/insight' || window.location.pathname.startsWith('/insight'); if (isInsightListing) { document.body.classList.add('insight-page'); header?.classList.add('header-insight-listing'); } else { header?.classList.remove('header-insight-listing'); } const workList = document.getElementById('work-list'); const container = document.querySelector('.our-work-container'); let currentCategory = 'null'; function applyMobileClamp() { const isMobile = window.matchMedia('(max-width: 639px)').matches; const items = workList ? Array.from(workList.querySelectorAll('.card-item')) : []; if (!items.length) return; // reset clamp trước items.forEach(el => el.classList.remove('m-clamped')); if (!isMobile) return; // Chỉ tính các item đang KHÔNG bị ẩn bởi filter (.d-none) const visible = items.filter(el => !el.classList.contains('d-none')); visible.forEach((el, i) => { if (i >= 5) el.classList.add('m-clamped'); }); } function initialRender() { if (!workList || workList.children.length > 0) return; const items = JSON.parse(workList.dataset.items || '[]'); items.forEach((it, index) => { const wrapper = document.createElement('div'); wrapper.className = 'flex card-item'; wrapper.setAttribute('data-category', (it.category || 'all').toLowerCase()); const copyPosition = 'bottom-[22px]'; const imgWH = 'width="1000" height="1000"'; // —— NEW: ưu tiên dùng href từ server; fallback từ slug nếu có // Ensure we always use /our-work/ path, not /work-inner/ let href = (typeof it.href === 'string' && it.href) || (it.slug ? `/our-work/${it.slug}` : '/our-work'); // Fix any old /work-inner/ links to /our-work/ if (href && href.includes('/work-inner/')) { href = href.replace('/work-inner/', '/our-work/'); } // (tuỳ chọn) giữ tab khi quay lại listing const url = new URL(href, window.location.origin); const currentTab = (window.__ourWorkCurrentTab || 'All'); // nếu bạn có biến này if (!url.searchParams.has('tab_active') && currentTab) { url.searchParams.set('tab_active', currentTab); } const colorImage = it.overlay_img || it.img; wrapper.innerHTML = `
${it.title ?? ''} ${it.title ?? ''}

${it.title ?? ''}

${it.subtitle ? `

${it.subtitle}

` : ''}
`; workList.appendChild(wrapper); }); } function filterCards(category = 'all') { const next = (category || 'all').toLowerCase(); if (next === currentCategory) return; const items = workList ? Array.from(workList.querySelectorAll('.card-item')) : []; if (items.length === 0) return; const state = Flip.getState(items); items.forEach(el => { const match = (next === 'all' || el.dataset.category === next); el.classList.toggle('d-none', !match); }); Flip.from(state, { duration: 0.7, scale: true, ease: "power1.inOut", absolute: true, onEnter: elements => gsap.from(elements, { opacity: 0, scale: 0.8, duration: 0.4, delay: 0.1 }), onLeave: elements => gsap.to(elements, { opacity: 0, scale: 0.8, duration: 0.4 }), onComplete: () => { applyMobileClamp(); if (window.__recalcOurWorkMinHeight) window.__recalcOurWorkMinHeight(); } }); currentCategory = next; } function changeTab(clickedTab) { const tabItems = document.querySelectorAll('.tab-item'); const tabText = clickedTab.querySelector('h2').textContent.trim(); const cat = tabText.toLowerCase(); if (clickedTab.classList.contains('active') && cat === currentCategory) return; tabItems.forEach(tab => { tab.classList.remove('active'); tab.setAttribute('aria-selected', 'false'); }); clickedTab.classList.add('active'); clickedTab.setAttribute('aria-selected', 'true'); // ----- CHỖ SỬA: ép base path về /our-work ----- const basePath = '/our-work'; // hoặc container?.dataset.basePath const url = new URL(basePath, window.location.origin); url.searchParams.set('tab_active', tabText); // hoặc tabText.toLowerCase() // dùng replaceState để không phình history, hoặc pushState nếu muốn tạo entry mới window.history.replaceState({}, '', url); filterCards(cat); } window.changeTab = changeTab; function initializePage() { initialRender(); applyMobileClamp(); if (container) { let bgRatio = null; let bgUrl = null; const parseBgUrl = (csBg) => { const matches = [...csBg.matchAll(/url\((?:'|")?([^"')]+)(?:'|")?\)/g)]; if (!matches.length) return null; return matches[matches.length - 1][1]; }; const getPaddingY = (el) => { const cs = getComputedStyle(el); const pt = parseFloat(cs.paddingTop) || 0; const pb = parseFloat(cs.paddingBottom) || 0; return pt + pb; }; const ensureBgRatio = () => new Promise((resolve) => { const cs = getComputedStyle(container); const url = parseBgUrl(cs.backgroundImage); if (!url) { bgUrl = null; bgRatio = null; resolve(null); return; } if (url === bgUrl && bgRatio) { resolve(bgRatio); return; } bgUrl = url; const img = new Image(); img.onload = () => { bgRatio = img.naturalHeight && img.naturalWidth ? (img.naturalHeight / img.naturalWidth) : null; resolve(bgRatio); }; img.onerror = () => { bgRatio = null; resolve(null); }; img.src = url; }); const computeBgHeight = () => { if (!bgRatio) return 0; const w = container.clientWidth || 0; return Math.round(w * bgRatio); }; const setMin = async () => { await ensureBgRatio(); // 1) Gỡ min-height tạm thời để đo CHIỀU CAO THỰC const prevMin = container.style.minHeight; container.style.minHeight = ''; // force reflow để chắc chắn layout cập nhật void container.offsetHeight; // 2) Đo lại height thật của content const contentH = Math.ceil(container.scrollHeight || container.offsetHeight || 0); // 3) Tính chiều cao nền theo tỉ lệ (nếu còn dùng) const padY = getPaddingY(container); const bgH = computeBgHeight(); // 4) Chọn chiến lược đặt min-height let desired = Math.max(contentH, bgH + padY); // Với mobile: ưu tiên content để không dư khoảng trống dưới if (window.matchMedia('(max-width: 639px)').matches) { // Cách A: bám sát content hoàn toàn desired = contentH; // Hoặc Cách B: cho nền “nhỉnh” tối đa +20px so với content // desired = Math.min(desired, contentH + 20); } // 5) Set lại min-height mới container.style.minHeight = desired ? `${desired}px` : prevMin; }; if (window.ResizeObserver) { const ro = new ResizeObserver(() => { requestAnimationFrame(setMin); }); ro.observe(container); } let rAF; window.addEventListener('resize', () => { cancelAnimationFrame(rAF); rAF = requestAnimationFrame(() => { setMin(); applyMobileClamp(); }); }, { passive: true }); const mo = new MutationObserver(() => requestAnimationFrame(setMin)); mo.observe(container, { childList: true, subtree: true }); window.__recalcOurWorkMinHeight = () => requestAnimationFrame(setMin); setTimeout(() => { if (window.__recalcOurWorkMinHeight) { window.__recalcOurWorkMinHeight(); } applyMobileClamp(); }, 150); } const params = new URLSearchParams(window.location.search); const activeTab = params.get('tab_active'); const tabItems = document.querySelectorAll('.tab-item'); tabItems.forEach(tab => { if (tab.__kbEnhanced) return; tab.__kbEnhanced = true; tab.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ' || e.code === 'Space') { e.preventDefault(); tab.click(); // ✅ giả lập click: onclick + mọi JS cũ, underline giữ nguyên cách tính } }); }); tabItems.forEach(tab => { if (tab.__kbEnhanced) return; tab.__kbEnhanced = true; tab.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ' || e.code === 'Space') { e.preventDefault(); tab.click(); // ✅ giả lập click: onclick + mọi JS cũ, underline giữ nguyên cách tính } }); }); if (tabItems.length > 0) { let activated = false; if (activeTab) { for (const tab of tabItems) { const label = tab.querySelector('h2').textContent.trim(); if (label.toLowerCase() === activeTab.toLowerCase()) { changeTab(tab); activated = true; break; } } } if (!activated) { // chọn tab đầu tiên (thường là All) changeTab(tabItems[0], { updateUrl: true }); // nếu đang ở đúng trang /our-work thì set lại query param const cur = new URL(window.location.href); if (cur.pathname === '/our-work') { cur.searchParams.set('tab_active', 'All'); window.history.replaceState(window.history.state, '', cur); } } } else { filterCards('all'); } requestAnimationFrame(() => { const cnt = (workList?.querySelectorAll('.card-item') || []).length; document.dispatchEvent(new CustomEvent('ourwork:list-ready', { detail: { when: Date.now(), count: cnt } })); }); } initializePage(); // ==== Load more ==== const postList = document.getElementById("post-list"); const loadMoreBtn = document.getElementById("load-more"); if (postList && loadMoreBtn) { const perPage = parseInt(postList.dataset.itemsPerPage, 10) || 4; // Lấy tất cả card trong DOM hiện có const allPosts = Array.from(postList.querySelectorAll(".post-card")); // total thực tế an toàn const totalFromData = parseInt(postList.dataset.totalPosts || allPosts.length, 10); const total = Math.min(totalFromData, allPosts.length); // Ẩn từ perPage trở đi allPosts.forEach((el, i) => { if (i >= perPage) el.classList.add("hidden"); else el.classList.remove("hidden"); }); let currentIndex = Math.min(perPage, total); function updateButtonState() { const done = currentIndex >= total; loadMoreBtn.classList.toggle('invisible', done); loadMoreBtn.classList.toggle('pointer-events-none', done); loadMoreBtn.setAttribute('aria-hidden', done ? 'true' : 'false'); loadMoreBtn.tabIndex = done ? -1 : 0; } updateButtonState(); function loadMorePosts() { if (currentIndex >= total) return; const nextEnd = Math.min(currentIndex + perPage, total); for (let i = currentIndex; i < nextEnd; i++) { allPosts[i].classList.remove("hidden"); } currentIndex = nextEnd; updateButtonState(); // Nếu có auto-resize/min-height thì gọi lại if (window.__recalcOurWorkMinHeight) window.__recalcOurWorkMinHeight(); } // Tránh gắn trùng listener khi initWorkPage() chạy nhiều lần trong SPA if (loadMoreBtn.__onClick) { loadMoreBtn.removeEventListener("click", loadMoreBtn.__onClick); } loadMoreBtn.__onClick = loadMorePosts; loadMoreBtn.addEventListener("click", loadMoreBtn.__onClick); function handleScroll() { const footer = document.querySelector(".footer-container"); const footerHeight = footer ? footer.offsetHeight : 0; if (window.innerHeight + window.scrollY >= document.body.offsetHeight - footerHeight - 200) { if (currentIndex < totalPosts) loadMorePosts(); } } loadMoreBtn.addEventListener("click", loadMorePosts); // window.addEventListener("scroll", handleScroll); } } // chạy lần đầu khi load cứng document.addEventListener('DOMContentLoaded', initWorkPage); // chạy lại sau mỗi lần SPA chuyển trang document.addEventListener('spa:page-ready', initWorkPage);