/** * 학습 가이드 PDF 팝업 * - btn-guide 클릭 시 레이어 오픈 * - 탭 전환 시 pdf.js viewer iframe src 교체 */ const LearningGuideModal = (function () { const DEFAULT_TAB_KEY = "player"; const VIEWER_PATH = "/js/lib/pdfjs-viewer/web/viewer.html"; let layer = null; let viewer = null; let closeBtn = null; let triggerBtn = null; let tabSwiper = null; let tabs = []; let activeTabKey = DEFAULT_TAB_KEY; let isOpen = false; function resolvePdfUrl(pdfPath) { if (!pdfPath) return ""; try { return new URL(pdfPath, window.location.origin).href; } catch (error) { console.error("[LearningGuideModal] PDF URL resolve failed:", error); return pdfPath; } } function buildViewerUrl(pdfPath) { if (!pdfPath) return ""; const viewerUrl = new URL(VIEWER_PATH, window.location.origin); const fileUrl = resolvePdfUrl(pdfPath); viewerUrl.searchParams.set("file", fileUrl); viewerUrl.hash = "zoom=60&textlayer=off"; return viewerUrl.href; } function getTabButtons() { if (!layer) return []; return Array.from(layer.querySelectorAll(".learning-guide-tab")); } function getTabButton(key) { return getTabButtons().find(function (tab) { return tab.dataset.guideKey === key; }); } function detectTabKeyFromPath() { const path = window.location.pathname.toLowerCase(); const pathMap = [ ["index", "main"], ["main", "main"], ["myclass", "myclass"], ["onboarding", "onboarding"], ["learning", "legal"], ["legal_edu", "legal"], ["legal", "legal"], ["leadership", "leadership"], ["insight", "insight"], ["biztrend", "biztrend"], ["mypage", "mypage"], ["player", "player"], ]; for (let i = 0; i < pathMap.length; i += 1) { const needle = pathMap[i][0]; const key = pathMap[i][1]; if (path.indexOf(needle) !== -1 && getTabButton(key)) return key; } return null; } function getPageDefaultTabKey() { if (!layer) return DEFAULT_TAB_KEY; const fromPath = detectTabKeyFromPath(); if (fromPath) return fromPath; const btnKey = triggerBtn && (triggerBtn.dataset.defaultGuideKey || "").trim(); if (btnKey && getTabButton(btnKey)) return btnKey; const key = (layer.dataset.defaultGuideKey || "").trim(); if (key && getTabButton(key)) return key; return DEFAULT_TAB_KEY; } function getFirstAvailableTabKey() { const availableTab = getTabButtons().find(function (tab) { return tab.dataset.pdf && !tab.disabled; }); return availableTab ? availableTab.dataset.guideKey : getPageDefaultTabKey(); } function initDefaultTab() { activeTabKey = getPageDefaultTabKey(); updateTabStates(activeTabKey); } function syncOpenState(tabKey) { const nextKey = tabKey || getPageDefaultTabKey(); loadPdfByKey(nextKey, { forceReload: true }); } function setDefaultTab(key) { if (!layer || !key) return; const nextKey = String(key).trim(); if (!getTabButton(nextKey)) return; layer.dataset.defaultGuideKey = nextKey; activeTabKey = nextKey; updateTabStates(nextKey); if (isOpen) { loadPdfByKey(nextKey, { forceReload: true }); return; } updateTabLayout(0); } function updateTabStates(key) { getTabButtons().forEach(function (tab) { const isActive = tab.dataset.guideKey === key; tab.classList.toggle("is-active", isActive); tab.setAttribute("aria-selected", isActive ? "true" : "false"); }); activeTabKey = key; } function getTabIndex(key) { const tab = getTabButton(key); if (!tab || !tabSwiper) return -1; const slide = tab.closest(".swiper-slide"); if (!slide) return -1; return Array.from(tabSwiper.slides).indexOf(slide); } function isTabOverflow() { if (!tabSwiper) return false; const swiperEl = tabSwiper.el; // 자연 너비 기준 측정을 위해 scroll 모드로 일시 전환 swiperEl.classList.remove("is-tab-even"); swiperEl.classList.add("is-tab-scroll"); tabSwiper.update(); return tabSwiper.wrapperEl.scrollWidth > swiperEl.clientWidth + 1; } function updateTabLayout(speed) { if (!tabSwiper) return; const swiperEl = tabSwiper.el; const overflow = isTabOverflow(); swiperEl.classList.toggle("is-tab-scroll", overflow); swiperEl.classList.toggle("is-tab-even", !overflow); tabSwiper.params.centeredSlides = overflow; tabSwiper.params.centeredSlidesBounds = overflow; tabSwiper.update(); const index = getTabIndex(activeTabKey); if (index >= 0) { tabSwiper.slideTo(index, speed !== undefined ? speed : 300); } } function isViewerBlank() { if (!viewer) return true; const attrSrc = viewer.getAttribute("src"); return !attrSrc || !attrSrc.trim(); } function loadPdfByKey(key, options) { const tab = getTabButton(key); if (!tab || !tab.dataset.pdf) return; const forceReload = options && options.forceReload; updateTabStates(key); if (viewer) { const nextSrc = buildViewerUrl(tab.dataset.pdf); if (forceReload) { viewer.removeAttribute("src"); requestAnimationFrame(function () { viewer.src = nextSrc; }); } else { viewer.src = nextSrc; } } updateTabLayout(300); } function initTabSwiper() { if (typeof Swiper === "undefined" || !layer) return; const swiperEl = layer.querySelector(".learning-guide-tab-swiper"); if (!swiperEl) return; tabs = getTabButtons(); tabSwiper = new Swiper(swiperEl, { slidesPerView: "auto", spaceBetween: 1, centeredSlides: false, centeredSlidesBounds: false, slideToClickedSlide: true, watchOverflow: true, speed: 300, resistanceRatio: 0.65, }); } function destroyTabSwiper() { if (tabSwiper) { tabSwiper.destroy(true, true); tabSwiper = null; } } function open(tabKey) { if (!layer || isOpen) return; layer.classList.remove("hidden"); layer.classList.add("is-open"); layer.setAttribute("aria-hidden", "false"); isOpen = true; if (typeof scrollManager !== "undefined") { scrollManager.lock(); } else if (typeof bodyLock === "function") { bodyLock(); } requestAnimationFrame(function () { requestAnimationFrame(function () { syncOpenState(tabKey); }); }); if (closeBtn) { closeBtn.focus(); } } function close() { if (!layer || !isOpen) return; layer.classList.remove("is-open"); layer.classList.add("hidden"); layer.setAttribute("aria-hidden", "true"); isOpen = false; if (typeof scrollManager !== "undefined") { scrollManager.unlock(); } else if (typeof bodyUnlock === "function") { bodyUnlock(); } if (triggerBtn) { triggerBtn.focus(); } } function handleTabClick(event) { const tab = event.currentTarget; if (!tab || tab.disabled || tab.classList.contains("is-disabled")) return; const key = tab.dataset.guideKey; if (!key || !tab.dataset.pdf) return; if (key === activeTabKey) return; loadPdfByKey(key); } function handleLayerClick(event) { if (event.target === layer) { close(); } } function handleKeydown(event) { if (!isOpen || event.key !== "Escape") return; close(); } let resizeTimer = null; function handleResize() { if (!tabSwiper) return; if (resizeTimer) clearTimeout(resizeTimer); resizeTimer = setTimeout(function () { tabSwiper.update(); updateTabLayout(0); }, 150); } function bindEvents() { if (!triggerBtn) { triggerBtn = document.querySelector(".btn-guide"); } if (triggerBtn) { triggerBtn.addEventListener("click", function () { open(); }); } if (closeBtn) { closeBtn.addEventListener("click", function (event) { event.stopPropagation(); close(); }); } if (layer) { layer.addEventListener("click", handleLayerClick); const content = layer.querySelector(".learning-guide-content"); if (content) { content.addEventListener("click", function (event) { event.stopPropagation(); }); } } getTabButtons().forEach(function (tab) { tab.addEventListener("click", handleTabClick); }); document.addEventListener("keydown", handleKeydown); window.addEventListener("resize", handleResize); } function init() { layer = document.getElementById("learningGuideLayer"); viewer = document.getElementById("learningGuideViewer"); closeBtn = document.getElementById("btnCloseLearningGuide"); triggerBtn = document.querySelector(".btn-guide"); if (!layer || !viewer) return; initTabSwiper(); initDefaultTab(); bindEvents(); } return { init: init, open: open, close: close, switchTab: loadPdfByKey, setDefaultTab: setDefaultTab, getDefaultTab: getPageDefaultTabKey, buildViewerUrl: buildViewerUrl, destroy: destroyTabSwiper, }; })(); if (typeof window !== "undefined") { window.LearningGuideModal = LearningGuideModal; }