// =============================== // script.js (FINAL - NO FOOTER, NO HEADER MENU TOGGLE) // - Swiper ÃʱâÈ­ // - Ç®ÆäÀÌÁö ½ºÅ©·Ñ(ÈÙ/Űº¸µå/ÅÍÄ¡) + ¼½¼Ç ½º³À // - ¼½¼Ç UI Ȱ¼ºÈ­(¼½¼Ç active / ÇØ½Ã) // - Biz cards ·ÎÁ÷ // - (footer °ü·Ã ÄÚµå´Â footer.js·Î ºÐ¸®) // - ✅ header(Çܹö°Å/µå·Ó´Ù¿î µî)´Â header.js¿¡¼­¸¸ ó¸® // =============================== document.addEventListener("DOMContentLoaded", () => { const sections = Array.from(document.querySelectorAll(".section")); const track = document.querySelector("#track"); // Header ¿ä¼Ò ÂüÁ¶(Å׸¶/·Î°í¸¸) const topbar = document.querySelector("#topbar"); const logoImg = document.querySelector("#topbarLogo"); const topLinks = Array.from(document.querySelectorAll(".gnb__link")); const sideLinks = Array.from(document.querySelectorAll(".indexNav__item")); if (!sections.length || !track) return; // ===== Swiper ===== const swiperEl = document.querySelector(".bannerSwiper"); if ( swiperEl && window.Swiper && !swiperEl.classList.contains("swiper-initialized") ) { new Swiper(".bannerSwiper", { loop: true, speed: 700, effect: "fade", fadeEffect: { crossFade: true }, autoplay: { delay: 3500, disableOnInteraction: false }, pagination: { el: ".swiper-pagination", clickable: true }, simulateTouch: true, grabCursor: true, mousewheel: false, }); } // ===== Utils ===== const clamp = (n, min, max) => Math.max(min, Math.min(max, n)); const lastIndex = sections.length - 1; const freeScrollEl = sections[lastIndex]?.querySelector(".freeScroll"); const bizWrap = document.querySelector(".biz_sec"); const OPT = { lerp: 0.08, wheelScale: 1.0, snapDelayMs: 120, snapDurationMs: 900, resetFreeScrollOnEnter: true, updateHash: true, respectReducedMotion: true, }; const prefersReduced = window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false; const motionOK = !(OPT.respectReducedMotion && prefersReduced); let current = 0; let targetY = 0; let currentY = 0; let rafId = 0; let snapTimer = null; let isSnapping = false; function step() { return window.innerHeight; } function maxY() { return lastIndex * step(); } function applyTransform(y) { track.style.transform = `translate3d(0, ${-y}px, 0)`; } function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } function setActiveUI(idx) { topLinks.forEach((btn) => btn.classList.toggle("is-active", Number(btn.dataset.goto) === idx), ); sideLinks.forEach((btn) => btn.classList.toggle("is-active", Number(btn.dataset.goto) === idx), ); } function setActiveSection(idx) { sections.forEach((sec, i) => sec.classList.toggle("is-active", i === idx)); } function updateHash(idx) { if (!OPT.updateHash) return; const anchor = sections[idx]?.dataset.anchor || `section${idx + 1}`; history.replaceState(null, "", `#${anchor}`); } function freeCanScrollDown() { if (!freeScrollEl) return false; return ( freeScrollEl.scrollTop + freeScrollEl.clientHeight < freeScrollEl.scrollHeight - 1 ); } function freeCanScrollUp() { if (!freeScrollEl) return false; return freeScrollEl.scrollTop > 0; } function getS2Scroller() { const s2 = sections[1]; if (!s2) return null; const inner = s2.querySelector(".section__inner"); return inner || s2; } function s2CanScrollDown() { const sc = getS2Scroller(); if (!sc) return false; return sc.scrollTop + sc.clientHeight < sc.scrollHeight - 1; } function s2CanScrollUp() { const sc = getS2Scroller(); if (!sc) return false; return sc.scrollTop > 0; } function nearestIndexFromY(y) { const idx = Math.round(y / step()); return clamp(idx, 0, lastIndex); } function animateSnap(toY, duration) { if (!motionOK || duration <= 0) { targetY = toY; return Promise.resolve(); } const from = targetY; const delta = toY - from; const start = performance.now(); isSnapping = true; return new Promise((resolve) => { function tick(now) { const t = Math.min(1, (now - start) / duration); targetY = from + delta * easeInOutCubic(t); if (t < 1) requestAnimationFrame(tick); else { isSnapping = false; resolve(); } } requestAnimationFrame(tick); }); } function loop() { const diff = targetY - currentY; currentY += diff * OPT.lerp; if (Math.abs(diff) < 0.1) currentY = targetY; applyTransform(currentY); rafId = requestAnimationFrame(loop); } // ====== topbar Å׸¶: header.js ¾ø¾îµµ Àû¿ëµÇ°Ô ÀÚü ó¸® ====== function applyTopbarThemeFromSection(idx) { if (!topbar) return; const mode = sections[idx]?.dataset?.topbar || "dark"; const isLight = mode === "light"; topbar.classList.toggle("is-light", isLight); document.body.classList.toggle("theme-light", isLight); if (logoImg) { const lightSrc = logoImg.getAttribute("data-logo-light"); const darkSrc = logoImg.getAttribute("data-logo-dark"); if (isLight && lightSrc) logoImg.src = lightSrc; if (!isLight && darkSrc) logoImg.src = darkSrc; } } function syncUI() { setActiveUI(current); setActiveSection(current); if (typeof window.setTopbarThemeBySection === "function") { window.setTopbarThemeBySection({ sections, idx: current, topbar, logoImg, }); } else { applyTopbarThemeFromSection(current); } updateHash(current); } function requestBizActivate(bizKey, { lock = true } = {}) { if (!bizWrap || !bizKey) return; bizWrap.dispatchEvent( new CustomEvent("biz:activate", { detail: { bizKey, lock } }), ); } async function goToIndex(idx, { focus = false, bizKey = null } = {}) { idx = clamp(idx, 0, lastIndex); const prev = current; current = idx; syncUI(); if ( OPT.resetFreeScrollOnEnter && current === lastIndex && prev !== lastIndex && freeScrollEl ) { freeScrollEl.scrollTop = 0; } await animateSnap(current * step(), OPT.snapDurationMs); if (focus) sections[current]?.focus({ preventScroll: true }); if (bizKey && current === 1) { requestAnimationFrame(() => { requestBizActivate(bizKey, { lock: true }); }); } } // ✅ Àü¿ª ³ëÃâ window.goToIndex = goToIndex; async function snapToNearest({ focus = false } = {}) { const idx = nearestIndexFromY(targetY); await goToIndex(idx, { focus }); } // (B) topbar / indexNav Ŭ¸¯ ¡æ goToIndex function bindGotoButtons(btns) { btns.forEach((btn) => { btn.addEventListener("click", (e) => { const gotoRaw = btn.dataset.goto; const idx = Number(gotoRaw); if (Number.isFinite(idx)) { e.preventDefault(); const bizKey = btn.dataset.biz || null; goToIndex(idx, { focus: true, bizKey }); return; } const href = (btn.getAttribute("href") || "").trim(); if (!href || href === "#") e.preventDefault(); }); }); } bindGotoButtons(topLinks); bindGotoButtons(sideLinks); // (C) ·Î°í(data-goto="0")µµ À̵¿ document.querySelectorAll(".logo[data-goto]").forEach((el) => { el.addEventListener("click", (e) => { const idx = Number(el.dataset.goto); if (!Number.isFinite(idx)) return; e.preventDefault(); goToIndex(idx, { focus: true }); }); }); // ===== Wheel ===== function onWheel(e) { if (current === 1) { const inS2 = e.target.closest?.(".section.s2"); if (inS2) { const down = e.deltaY > 0; if (down && s2CanScrollDown()) return; if (!down && s2CanScrollUp()) return; } } if (current === lastIndex && freeScrollEl) { const inFree = e.target.closest?.(".freeScroll"); if (inFree) { const down = e.deltaY > 0; if (down && freeCanScrollDown()) return; if (!down && freeCanScrollUp()) return; e.preventDefault(); if (!down) goToIndex(current - 1); return; } } e.preventDefault(); e.stopPropagation(); const mul = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? window.innerHeight : 1; if (!isSnapping) { targetY += e.deltaY * mul * OPT.wheelScale; targetY = clamp(targetY, 0, maxY()); } clearTimeout(snapTimer); snapTimer = setTimeout(() => snapToNearest(), OPT.snapDelayMs); } // ===== Keyboard ===== function onKeyDown(e) { const key = e.key; const downKeys = ["ArrowDown", "PageDown", " "]; const upKeys = ["ArrowUp", "PageUp"]; if (current === 1) { const sc = getS2Scroller(); if (sc && sc.scrollHeight > sc.clientHeight) { if (downKeys.includes(key) && s2CanScrollDown()) return; if (upKeys.includes(key) && s2CanScrollUp()) return; } } if (current === lastIndex && freeScrollEl) { if (downKeys.includes(key) && freeCanScrollDown()) return; if (upKeys.includes(key) && freeCanScrollUp()) return; if (upKeys.includes(key)) { e.preventDefault(); goToIndex(current - 1, { focus: true }); return; } if (downKeys.includes(key)) { e.preventDefault(); return; } } if (downKeys.includes(key)) { e.preventDefault(); goToIndex(current + 1, { focus: true }); } if (upKeys.includes(key)) { e.preventDefault(); goToIndex(current - 1, { focus: true }); } if (key === "Home") { e.preventDefault(); goToIndex(0, { focus: true }); } if (key === "End") { e.preventDefault(); goToIndex(lastIndex, { focus: true }); } } // ===== Touch ===== let touchStartY = 0; let touchEndY = 0; function onTouchStart(e) { if (!e.touches?.[0]) return; if (current === 1 && e.target.closest?.(".section.s2")) return; if (current === lastIndex && freeScrollEl) { const inFree = e.target.closest?.(".freeScroll"); if (inFree) return; } touchStartY = e.touches[0].clientY; touchEndY = touchStartY; } function onTouchMove(e) { if (!e.touches?.[0]) return; if (current === 1 && e.target.closest?.(".section.s2")) return; if (current === lastIndex && freeScrollEl) { const inFree = e.target.closest?.(".freeScroll"); if (inFree) return; } touchEndY = e.touches[0].clientY; } function onTouchEnd() { if (!touchStartY) return; const diff = touchStartY - touchEndY; touchStartY = 0; touchEndY = 0; if (Math.abs(diff) < 50) return; if (diff > 0) goToIndex(current + 1); else goToIndex(current - 1); } // ===== Init from hash ===== (function initFromHash() { const h = (location.hash || "").replace("#", "").trim().toLowerCase(); if (!h) return; const found = sections.findIndex( (s) => (s.dataset.anchor || "").toLowerCase() === h, ); if (found >= 0) current = found; })(); function onResize() { targetY = current * step(); currentY = targetY; applyTransform(currentY); } // ===== Biz cards (¿ø¹® ±×´ë·Î) ===== (function initBizCards() { const wrap = document.querySelector(".biz_sec"); if (!wrap) return; const items = Array.from(wrap.querySelectorAll(".biz_item")); if (!items.length) return; const mqMobile = window.matchMedia("(max-width: 768px)"); const hoverOK = () => window.matchMedia && window.matchMedia("(hover:hover) and (pointer:fine)").matches; let locked = false; let activeEl = null; const desktopDefaultEl = items.find((it) => it.classList.contains("is-active")) || null; const MOBILE_BASE = 220; const MOBILE_ACTIVE = 480; const stopVideo = (item) => { const v = item.querySelector(".biz_video"); if (!v) return; try { v.pause(); v.currentTime = 0; } catch {} item.classList.remove("is-playing"); }; const playVideo = (item) => { const v = item.querySelector(".biz_video"); if (!v) return; v.muted = true; v.playsInline = true; item.classList.add("is-playing"); v.play().catch(() => {}); }; const setMobileHeight = (item, expanded) => { if (!mqMobile.matches) return; item.style.height = expanded ? `${MOBILE_ACTIVE}px` : `${MOBILE_BASE}px`; }; const resetAll = () => { items.forEach((it) => { it.classList.remove("is-active"); stopVideo(it); if (mqMobile.matches) setMobileHeight(it, false); }); activeEl = null; }; const activateSingle = (target, { play = false } = {}) => { items.forEach((it) => { if (it === target) { it.classList.add("is-active"); activeEl = it; if (mqMobile.matches) { setMobileHeight(it, true); requestAnimationFrame(() => setMobileHeight(it, true)); } if (play) playVideo(it); else stopVideo(it); } else { it.classList.remove("is-active"); stopVideo(it); if (mqMobile.matches) setMobileHeight(it, false); } }); }; const deactivate = (target) => { target.classList.remove("is-active"); stopVideo(target); if (mqMobile.matches) setMobileHeight(target, false); if (activeEl === target) activeEl = null; }; function applyDefaultByBreakpoint() { locked = false; items.forEach((it) => (it.style.height = "")); if (mqMobile.matches) { items.forEach((it) => { stopVideo(it); it.classList.remove("is-active"); setMobileHeight(it, false); }); activeEl = null; } else { if (desktopDefaultEl) activateSingle(desktopDefaultEl, { play: false }); else resetAll(); } } applyDefaultByBreakpoint(); wrap.addEventListener("biz:activate", (e) => { const bizKey = e.detail?.bizKey; const lock = e.detail?.lock ?? true; const target = items.find( (it) => (it.dataset.biz || "").toLowerCase() === String(bizKey).toLowerCase(), ); if (!target) return; activateSingle(target, { play: true }); locked = mqMobile.matches ? false : Boolean(lock); }); items.forEach((item) => { item.addEventListener("mouseenter", () => { if (mqMobile.matches) return; if (!hoverOK()) return; if (locked) return; activateSingle(item, { play: true }); }); item.addEventListener("mouseleave", () => { if (mqMobile.matches) return; if (!hoverOK()) return; if (locked) return; stopVideo(item); }); }); wrap.addEventListener("mouseleave", () => { if (mqMobile.matches) return; if (!hoverOK()) return; if (locked) return; if (desktopDefaultEl) activateSingle(desktopDefaultEl, { play: false }); else resetAll(); }); items.forEach((item) => { const iconBtn = item.querySelector(".biz_icon"); if (!iconBtn) return; iconBtn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); const isActive = item.classList.contains("is-active"); if (mqMobile.matches) { if (isActive) deactivate(item); else activateSingle(item, { play: true }); return; } if (isActive && locked) { locked = false; stopVideo(item); if (desktopDefaultEl) activateSingle(desktopDefaultEl, { play: false }); else resetAll(); return; } if (isActive && !locked) { stopVideo(item); if (desktopDefaultEl) activateSingle(desktopDefaultEl, { play: false }); else deactivate(item); return; } activateSingle(item, { play: true }); locked = true; }); }); items.forEach((item) => { item.addEventListener("click", (e) => { const tagLink = e.target.closest?.(".biz_tags a"); const coverLink = e.target.closest?.(".biz_coverLink"); if (tagLink) { if ((tagLink.getAttribute("href") || "").trim() === "#") e.preventDefault(); e.stopPropagation(); return; } if (coverLink) { if ((coverLink.getAttribute("href") || "").trim() === "#") e.preventDefault(); } if (mqMobile.matches) { const isActive = item.classList.contains("is-active"); if (isActive) deactivate(item); else activateSingle(item, { play: true }); return; } activateSingle(item, { play: true }); locked = true; }); }); wrap.addEventListener("click", (e) => { if (mqMobile.matches) return; const clickedItem = e.target.closest?.(".biz_item"); if (clickedItem) return; locked = false; if (desktopDefaultEl) activateSingle(desktopDefaultEl, { play: false }); else resetAll(); }); window.addEventListener("keydown", (e) => { if (e.key !== "Escape") return; if (mqMobile.matches) return; locked = false; if (desktopDefaultEl) activateSingle(desktopDefaultEl, { play: false }); else resetAll(); }); mqMobile.addEventListener?.("change", applyDefaultByBreakpoint); window.addEventListener("resize", () => { if (!mqMobile.matches) return; items.forEach((it) => { if (it.classList.contains("is-active")) setMobileHeight(it, true); else setMobileHeight(it, false); }); }); })(); // ===== Boot ===== targetY = current * step(); currentY = targetY; applyTransform(currentY); syncUI(); cancelAnimationFrame(rafId); rafId = requestAnimationFrame(loop); document.addEventListener("wheel", onWheel, { passive: false, capture: true, }); window.addEventListener("keydown", onKeyDown); window.addEventListener("touchstart", onTouchStart, { passive: true }); window.addEventListener("touchmove", onTouchMove, { passive: true }); window.addEventListener("touchend", onTouchEnd); window.addEventListener("resize", onResize); document.addEventListener("visibilitychange", () => { if (!document.hidden) return; document.querySelectorAll(".biz_video").forEach((v) => { try { v.pause(); } catch {} }); document .querySelectorAll(".biz_item") .forEach((it) => it.classList.remove("is-playing")); }); });