(() => { const { EventManager, utils } = ShopbySkin; // --- API Helper --- const fetchCategoriesFromApi = async () => { try { console.log("Fetching categories from API..."); // Use verified Client ID const clientId = 'UUBsnIVqbs6h156TJHMiRA=='; const response = await fetch('https://shop-api.e-ncp.com/categories', { headers: { 'clientid': clientId, 'version': '1.0', 'content-type': 'application/json' } }); if (response.ok) { const data = await response.json(); return data.multiLevelCategories || []; } } catch (e) { console.error("Failed to fetch categories from API", e); } return []; }; // --- Main Logic --- // State const state = { categories: [], selectedDepth1: null, activeDepth2No: null // Currently visible/active section }; // Elements const els = { tabs: document.getElementById('category-tabs-list'), sidebar: document.getElementById('category-sidebar-list'), grid: document.getElementById('category-content-grid'), // Main scroll area }; // Utils const safeHtml = (str) => { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; }; // --- ScrollSpy Logic --- let observer = null; let isScrollingFromClick = false; // Flag to disable spy during click scroll const setupScrollSpy = () => { if (observer) observer.disconnect(); // Observe sections to update sidebar active state const options = { root: els.grid.parentElement, // .category-grid-area rootMargin: '-10% 0px -70% 0px', // Trigger when section is near top threshold: 0 }; observer = new IntersectionObserver((entries) => { if (isScrollingFromClick) return; // Find the visible section let visibleSection = null; entries.forEach(entry => { if (entry.isIntersecting) { visibleSection = entry.target; } }); if (visibleSection) { const no = Number(visibleSection.dataset.no); updateSidebarActive(no); } }, options); const sections = document.querySelectorAll('.category-section-wrap'); sections.forEach(section => observer.observe(section)); }; const updateSidebarActive = (no) => { if (state.activeDepth2No === no) return; state.activeDepth2No = no; // Visual update const prev = els.sidebar.querySelector('.active'); if (prev) prev.classList.remove('active'); const next = els.sidebar.querySelector(`.category-sidebar-item[data-no="${no}"]`); if (next) { next.classList.add('active'); // Auto-scroll sidebar to keep active item in view next.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }; // --- Render Functions --- // 1. Render Top Tabs (Depth 1) const renderTabs = () => { if (!els.tabs) return; els.tabs.innerHTML = state.categories.map(cat => { const isActive = state.selectedDepth1 && state.selectedDepth1.categoryNo === cat.categoryNo; return `
  • ${safeHtml(cat.label)}
  • `; }).join(''); }; // 2. Render Sidebar (Depth 2 List) const renderSidebar = () => { if (!els.sidebar || !state.selectedDepth1) return; const children = state.selectedDepth1.children || []; // Show all Depth 2 items as sidebar links if (children.length === 0) { els.sidebar.innerHTML = ''; return; } els.sidebar.innerHTML = children.map((cat, idx) => { // Active state defaults to first one initially if null const isActive = (state.activeDepth2No === cat.categoryNo) || (!state.activeDepth2No && idx === 0); return `
  • ${safeHtml(cat.label)}
  • `; }).join(''); }; // 3. Render Content (All Sections) const renderContent = () => { if (!els.grid || !state.selectedDepth1) return; const depth2List = state.selectedDepth1.children || []; els.grid.innerHTML = ''; if (depth2List.length === 0) { els.grid.innerHTML = '
    카테고리가 없습니다.
    '; return; } const html = depth2List.map(d2 => { const d3List = d2.children || []; // Grid Content: Depth 3 Text Links let gridHtml = ''; if (d3List.length > 0) { // 2-column text grid gridHtml = d3List.map(d3 => ` ${safeHtml(d3.label)} `).join(''); } else { // No sub-categories? Just link to itself gridHtml = ` ${safeHtml(d2.label)} 전체보기 `; } return `
    ${safeHtml(d2.label)}
    ${gridHtml}
    `; }).join(''); els.grid.innerHTML = html; // Reset els.grid.parentElement.scrollTop = 0; // Init active state if (depth2List.length > 0) { state.activeDepth2No = depth2List[0].categoryNo; } // Delay spy setup to allow DOM update setTimeout(setupScrollSpy, 200); }; // --- Handlers --- const handleTabClick = (no) => { if (state.selectedDepth1?.categoryNo === no) return; const cat = state.categories.find(c => c.categoryNo === no); if (cat) { console.log("Tab Click:", cat.label); state.selectedDepth1 = cat; state.activeDepth2No = null; renderTabs(); renderSidebar(); renderContent(); } }; const handleSidebarClick = (no) => { const section = document.getElementById(`section-${no}`); if (section) { console.log("Sidebar Click:", no); isScrollingFromClick = true; updateSidebarActive(no); section.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Re-enable spy after transition setTimeout(() => { isScrollingFromClick = false; }, 800); } }; const bindEvents = () => { els.tabs?.addEventListener('click', e => { const item = e.target.closest('.category-tab-item'); if (item) handleTabClick(Number(item.dataset.no)); }); els.sidebar?.addEventListener('click', e => { const item = e.target.closest('.category-sidebar-item'); if (item) handleSidebarClick(Number(item.dataset.no)); }); }; const initialize = (categories) => { console.log("Initializing Zigzag Layout. Count:", categories.length); state.categories = categories; if (state.categories.length > 0) { state.selectedDepth1 = state.categories[0]; } renderTabs(); renderSidebar(); renderContent(); bindEvents(); }; // --- Entry --- EventManager.on('PAGE_LOAD_COMPLETED', (data) => { if (data?.multiLevelCategories?.length > 0) { initialize(data.multiLevelCategories); } else { fetchCategoriesFromApi().then(cats => { if (cats.length > 0) initialize(cats); }); } }); setTimeout(() => { if (state.categories.length === 0) { fetchCategoriesFromApi().then(cats => { if (cats.length > 0 && state.categories.length === 0) initialize(cats); }); } }, 500); })();