function initWorkPage() { document.body.classList.remove('insight-page'); // only add when on /insight if (window.location.pathname.startsWith('/insight')) { document.body.classList.add('insight-page'); } 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 items.forEach(el => el.classList.remove('m-clamped')); if (!isMobile) return; // Only count items that are NOT hidden by 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 = (index >= 3 && index < 6) ? 'top-[22px]' : 'bottom-[22px]'; const imgWH = 'width="1000" height="1000"'; const href = (typeof it.href === 'string' && it.href) || (it.slug ? `/work-inner/${it.slug}` : '/work-inner'); const url = new URL(href, window.location.origin); const currentTab = (window.__ourWorkCurrentTab || 'All'); if (!url.searchParams.has('tab_active') && currentTab) { url.searchParams.set('tab_active', currentTab); } 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')); clickedTab.classList.add('active'); const basePath = '/our-work'; const url = new URL(basePath, window.location.origin); url.searchParams.set('tab_active', tabText); // use replaceState to avoid bloating history, or pushState if you want to create a new entry 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) Temporarily remove min-height to measure ACTUAL HEIGHT const prevMin = container.style.minHeight; container.style.minHeight = ''; // force reflow to ensure layout updates void container.offsetHeight; // 2) Measure the actual height of the content const contentH = Math.ceil(container.scrollHeight || container.offsetHeight || 0); // 3) Calculate background height based on ratio (if still used) const padY = getPaddingY(container); const bgH = computeBgHeight(); // 4) Choose strategy to set min-height let desired = Math.max(contentH, bgH + padY); // For mobile: prioritize content to avoid extra space below if (window.matchMedia('(max-width: 639px)').matches) { // Stick closely to content desired = contentH; } // 5) Set new min-height 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(); // ✅ simulate click: onclick + all old JS, underline keeps original calculation } }); }); 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(); // ✅ simulate click: onclick + all old JS, underline keeps original calculation } }); }); 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) { // select the first tab (usually All) changeTab(tabItems[0], { updateUrl: true }); // if currently on the /our-work page, reset the 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; // Get all cards currently in the DOM const allPosts = Array.from(postList.querySelectorAll(".post-card")); const totalFromData = parseInt(postList.dataset.totalPosts || allPosts.length, 10); const total = Math.min(totalFromData, allPosts.length); // Hide from perPage onwards 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(); // If there is auto-resize/min-height, call it again if (window.__recalcOurWorkMinHeight) window.__recalcOurWorkMinHeight(); } // Avoid attaching duplicate listeners when initWorkPage() runs multiple times in 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); } } // Run once on initial hard load document.addEventListener('DOMContentLoaded', initWorkPage); // Run again after each SPA page transition document.addEventListener('spa:page-ready', initWorkPage);