Files
baron_qa_write/SW문의_소스패키지/assets/js/common.js
T
root 308c06516c
Deploy EG-BIM QA Gateway / deploy (push) Successful in 1m0s
fix: 전체 UI 개선
2026-11-02 13:26:14 +09:00

700 lines
22 KiB
JavaScript

import { ensureBaronSsoAuth } from "./baron-sso-auth.js?v=20260721-worker2";
import "./header-login.js?v=20261030-1";
// --window-inner-height: 실제 뷰포트 높이를 CSS 변수로 반영 (모바일 100vh 이슈 대응, kngil 레이아웃에서 사용)
function updateWindowInnerHeight() {
document.documentElement.style.setProperty("--window-inner-height", window.innerHeight + "px");
}
updateWindowInnerHeight();
window.addEventListener("resize", updateWindowInnerHeight);
const pathSegments = location.pathname.split('/').filter(Boolean);
const rootMarkerSegments = ['ko', 'en', 'callback', 'assets', 'protected', 'public', 'recruit'];
const rootPrefix = pathSegments.length && !rootMarkerSegments.includes(pathSegments[0]) ? `/${pathSegments[0]}` : '';
const normalizePath = (path) => {
const normalizedPath = (path || '/').replace(/\/index\.html$/i, '/');
if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {
return normalizedPath.slice(0, -1);
}
return normalizedPath;
};
const currentPath = normalizePath(location.pathname);
const matchesPublicPath = (path, entry) => {
const normalizedEntry = normalizePath(entry);
if (normalizedEntry.endsWith('_')) {
return path.startsWith(normalizedEntry);
}
return path === normalizedEntry || path.startsWith(`${normalizedEntry}/`);
};
const landingPagePaths = [
`${rootPrefix}/ko`,
`${rootPrefix}/ko/`,
`${rootPrefix}/ko/index.html`,
`${rootPrefix}/en`,
`${rootPrefix}/en/`,
`${rootPrefix}/en/index.html`,
];
const publicPagePaths = [
...landingPagePaths,
`${rootPrefix}/ko/br_`,
`${rootPrefix}/en/br_`,
`${rootPrefix}/ko/dt_`,
`${rootPrefix}/en/dt_`,
`${rootPrefix}/recruit/`,
`${rootPrefix}/ko/sv_sw.html`,
`${rootPrefix}/en/sv_sw.html`,
`${rootPrefix}/ko/sv_sw_`,
`${rootPrefix}/en/sv_sw_`,
`${rootPrefix}/ko/egbim`,
`${rootPrefix}/en/egbim`,
`${rootPrefix}/ko/tova`,
`${rootPrefix}/en/tova`,
`${rootPrefix}/ko/pr_`,
`${rootPrefix}/en/pr_`,
`${rootPrefix}/ko/gaia`,
`${rootPrefix}/en/gaia`,
`${rootPrefix}/ko/kngil`,
`${rootPrefix}/en/kngil`,
`${rootPrefix}/ko/sv_bigroom.html`,
`${rootPrefix}/en/sv_bigroom.html`,
];
const normalizedLandingPagePaths = landingPagePaths.map(normalizePath);
const isLandingPage = normalizedLandingPagePaths.includes(currentPath);
const isPublicInfoPage = publicPagePaths.some((entry) => matchesPublicPath(currentPath, entry));
const loginRequested = isPublicInfoPage && new URL(window.location.href).searchParams.get('login') === '1';
if (isLandingPage) {
document.documentElement.dataset.baronPageMode = 'landing';
} else if (isPublicInfoPage) {
document.documentElement.dataset.baronPageMode = 'public-info';
}
const authResult = await ensureBaronSsoAuth(
isPublicInfoPage
? {
publicPaths: publicPagePaths,
forceAuth: loginRequested,
}
: {}
);
const isAuthenticated = Boolean(authResult?.session);
document.documentElement.dataset.baronAuthState = isAuthenticated ? 'authenticated' : 'anonymous';
// Disabled links in anonymous navigation must not navigate, including keyboard/programmatic clicks.
document.addEventListener('click', (event) => {
const target = event.target instanceof Element ? event.target : null;
const disabledLink = target?.closest('a[data-auth-disabled="true"]');
if (!disabledLink) return;
event.preventDefault();
event.stopImmediatePropagation();
}, true);
function followLoginTooltip(event) {
const target = event.target instanceof Element ? event.target : null;
const menuItem = target?.closest('li.nav-login-required[data-login-tooltip]');
if (!menuItem) return;
const rect = menuItem.getBoundingClientRect();
const margin = 8;
const offset = 16;
const tooltipWidth = Math.min(320, Math.max(0, window.innerWidth - margin * 2));
const tooltipHeight = 84;
let left = event.clientX + offset;
if (left + tooltipWidth > window.innerWidth - margin) {
left = event.clientX - tooltipWidth - offset;
}
left = Math.max(margin, left);
let top = event.clientY + offset;
if (top + tooltipHeight > window.innerHeight - margin) {
top = event.clientY - tooltipHeight - offset;
}
top = Math.max(margin, top);
menuItem.style.setProperty('--login-tooltip-left', `${left - rect.left}px`);
menuItem.style.setProperty('--login-tooltip-top', `${top - rect.top}px`);
}
document.addEventListener('pointermove', followLoginTooltip, { passive: true });
// ── AJAX 관련 SCRIPT
$(function () {
const includeVersion = '20261030-1';
// 비로그인 요청은 Worker가 nav-public.html만 허용하므로 로그인 상태에 맞는 메뉴 파일을 선택한다.
const navIncludeFile = isAuthenticated ? 'nav.html' : 'nav-public.html';
const includeBase = `${rootPrefix}/_include`;
$.ajaxSetup({ cache: false });
// Share both in-flight and completed static includes within this page.
const htmlRequests = new Map();
function loadHTML(url, target, callback) {
let request = htmlRequests.get(url);
if (!request) {
request = $.ajax({ url, cache: true, dataType: "html", timeout: 5000 });
htmlRequests.set(url, request);
request.fail(function () { htmlRequests.delete(url); });
}
request.done(function (data) {
$(target).html(data);
if (typeof callback === "function") callback();
}).fail(function (xhr, status, error) {
console.error(`Failed to load ${url}:`, error || status);
if (typeof callback === "function") callback();
});
}
function normalizeSiteLinks(root) {
if (!root) {
return;
}
root.querySelectorAll('a[href], img[src], source[src]').forEach((node) => {
const attributeName = node.hasAttribute('href') ? 'href' : 'src';
const rawValue = node.getAttribute(attributeName);
if (!rawValue || /^(?:https?:|mailto:|tel:|javascript:|#)/i.test(rawValue)) {
return;
}
const normalizedValue = rawValue
.replace(/^\.\.\/(ko|en|recruit)\//, `${rootPrefix}/$1/`)
.replace(/^\/(ko|en|recruit)\//, `${rootPrefix}/$1/`)
.replace(/^\.\.\/callback\.html$/, `${rootPrefix}/callback.html`)
.replace(/^\/callback\.html$/, `${rootPrefix}/callback.html`);
if (normalizedValue !== rawValue) {
node.setAttribute(attributeName, normalizedValue);
}
});
}
// nav 연결 (depth3 지원)
function connectNavToMapList() {
const currentPathname = location.pathname; // 전체 경로로 비교 (egbim/tova/gaia value.html 구분)
const navLinks = document.querySelectorAll(
"header .corp nav ol li.depth1 ul.depth2 li a"
);
let matchedLink = null;
navLinks.forEach((link) => {
const href = link.getAttribute("href"); // 절대경로 그대로 사용
if (href === currentPathname || currentPathname.endsWith(href)) {
if (matchedLink && matchedLink.closest('.depth3')) {
return;
}
if (!matchedLink || link.closest('.depth3')) {
matchedLink = link;
}
}
});
if (matchedLink) {
const mapList = document.querySelector(".map_list");
if (mapList) {
mapList.innerHTML = "";
matchedLink.closest(".depth1").classList.add("active");
const hasDepth3 = matchedLink.closest("li.has_depth3");
if (hasDepth3) {
matchedLink.closest(".depth1").querySelectorAll("li.has_depth3").forEach(el => {
el.classList.remove("active");
});
hasDepth3.classList.add("active");
}
const liHome = document.createElement("li");
liHome.innerHTML = '<i class="home"></i>';
mapList.appendChild(liHome);
const categoryTitle = matchedLink
.closest(".depth1")
?.querySelector("span")
?.textContent.trim();
const subTitle = matchedLink
.closest(".depth1")
?.querySelector("em")
?.textContent.trim();
const mainTitle = categoryTitle.replace(subTitle, "").trim();
const liCategory = document.createElement("li");
liCategory.textContent = mainTitle || "";
mapList.appendChild(liCategory);
const depth3El = matchedLink.closest('.depth3');
if (depth3El) {
const depth2Link = matchedLink.closest('.has_depth3')?.querySelector('a');
const depth2Title = depth2Link ? depth2Link.textContent.trim() : "";
const liDepth2 = document.createElement("li");
liDepth2.textContent = depth2Title;
mapList.appendChild(liDepth2);
const targetTitle = matchedLink.textContent.trim();
const liOn = document.createElement("li");
liOn.classList.add("on");
liOn.textContent = targetTitle || "";
mapList.appendChild(liOn);
} else {
const targetTitle = matchedLink.textContent.trim();
const liOn = document.createElement("li");
liOn.classList.add("on");
liOn.textContent = targetTitle || "";
mapList.appendChild(liOn);
}
}
}
}
// PC GNB 패키지 S/W / 서비스 S/W 3depth 탭 전환 기능
// 드롭다운이 여러 개(패키지 S/W, 서비스 S/W 등) 존재할 수 있으므로 각각 독립적으로 스코프 처리
function initPackageSWTab() {
$('.package_sw_dropdown').each(function () {
const $dropdown = $(this);
const $menuItems = $dropdown.find('> li.has_depth3');
if (!$menuItems.length) return;
// 1depth 메뉴 호버 시 드롭다운이 열리면 첫 번째 항목 기본 활성화
$dropdown.closest('.depth1').on('mouseenter', function () {
if (!$menuItems.filter('.active').length) {
$menuItems.removeClass('active');
$menuItems.first().addClass('active');
}
});
// 2depth 메뉴 호버 시 active 클래스 전환
$menuItems.on('mouseenter', function () {
$menuItems.removeClass('active');
$(this).addClass('active');
});
});
}
function trimPopupDepth3Nav() {
const popupNav = document.querySelector('.popup_wrap.sitemap .popup_contents_wrap nav');
if (!popupNav) {
return;
}
popupNav.querySelectorAll('li.has_depth3 > .depth3').forEach((depth3) => {
depth3.remove();
});
}
function configureHeaderActions(root) {
if (!root) {
return;
}
const loginWrap = root.querySelector('.header_login');
const loginLink = root.querySelector('.header_login_link');
if (!loginWrap || !loginLink) {
return;
}
if (isAuthenticated) {
loginWrap.hidden = false;
loginLink.textContent = '로그아웃';
loginLink.href = '#';
loginLink.onclick = (event) => {
event.preventDefault();
window.baronSsoLogout?.();
};
return;
}
if (!isPublicInfoPage) {
loginWrap.hidden = true;
return;
}
loginWrap.hidden = false;
loginLink.textContent = '로그인';
loginLink.href = `${location.pathname}?login=1`;
loginLink.onclick = null;
}
function loadSitemapNav() {
if (!$('.container').hasClass('recruit')) {
loadHTML(`${includeBase}/${navIncludeFile}?v=${includeVersion}`, '.popup_wrap.sitemap .popup_contents_wrap nav', function () {
normalizeSiteLinks(document.querySelector('.popup_wrap.sitemap .popup_contents_wrap nav'));
mobileMenu();
});
} else {
loadHTML(`${includeBase}/nav_recruit.html?v=${includeVersion}`, '.popup_wrap.sitemap .popup_contents_wrap nav', function () {
mobileMenu();
});
}
}
if (!$('.container').hasClass('recruit')) {
loadHTML(`${includeBase}/header.html?v=${includeVersion}`, '#header', function () {
normalizeSiteLinks(document.querySelector('#header'));
loadHTML(`${includeBase}/${navIncludeFile}?v=${includeVersion}`, '#header .corp .nav', function () {
normalizeSiteLinks(document.querySelector('#header .corp .nav'));
connectNavToMapList();
initPackageSWTab();
});
configureHeaderActions(document.querySelector('#header'));
loadSitemapNav();
});
} else {
loadHTML(`${includeBase}/header_recruit.html?v=${includeVersion}`, "#header_recruit", function () {
loadSitemapNav();
if (!$(".container").hasClass("recruit")) {
} else {
const currentPath = location.pathname.split("/").pop();
const navLinks = document.querySelectorAll(
"header .recruit nav ol li.depth1 a"
);
navLinks.forEach((link) => {
const href = link.getAttribute("href").split("/").pop();
if (href === currentPath) {
link.closest(".depth1").classList.add("active");
}
});
}
});
}
loadHTML(`${includeBase}/footer.html?v=${includeVersion}`, '#footer', function () {
normalizeSiteLinks(document.querySelector('#footer'));
loadHTML(`${includeBase}/${navIncludeFile}?v=${includeVersion}`, '#footer .nav', function () {
normalizeSiteLinks(document.querySelector('#footer .nav'));
});
});
});
// ── TITLE 관련 SCRIPT
// ── S: title 다일링기
$(function () {
const currentPath = location.pathname;
if (currentPath.indexOf("/recruit/") > 0) {
document.querySelector("head title").textContent =
"인재채용 | (주)바론컨설턴트";
} else {
document.querySelector("head title").textContent = "(주)바론컨설턴트";
}
});
// E : title 다일링기
// ── S : header 숨기기
$(function () {
const showNav = gsap
.from("#header, #header_recruit", {
yPercent: -200,
paused: true,
duration: 0.2,
onStart: () => {
const stHeader = document.querySelector("header");
if (window.scrollY > 100) {
stHeader.classList.add("open");
}
},
onReverseComplete: () => {
const stHeader = document.querySelector("header");
stHeader.classList.remove("open");
},
})
.progress(1);
let showMapList = null;
const headerNavMM = gsap.matchMedia();
headerNavMM.add("(max-width: 1400px)", () => {
showMapList = gsap
.from(".map_list", {
y: -40,
autoAlpha: 0,
paused: true,
duration: 0.2,
})
.progress(1);
return () => {
showMapList = null;
};
});
ScrollTrigger.create({
start: "top top",
end: 99999,
onUpdate: (self) => {
if (lenis && lenis.isStopped) return;
if (self.direction === -1) {
showNav.play();
showMapList && showMapList.play();
} else {
showNav.reverse();
showMapList && showMapList.reverse();
}
},
});
});
// E : header 숨기기
// ── POPUP SCRIPT
// ── S : 팝업 열기
$(function () {
$(document).on("click", "button", function () {
const button = this;
const value = button.getAttribute("data-value");
const popup = document.querySelector(`.popup_wrap.${value}`);
if (popup) {
// 현재 스크롤 위치 저장
const scrollY = window.scrollY;
document.body.setAttribute("data-scroll-lock", scrollY);
// body 고정
document.body.style.position = "fixed";
document.body.style.top = `-${scrollY}px`;
document.body.style.left = "0";
document.body.style.right = "0";
// Lenis 일시 중단
if (lenis) {
lenis.destroy();
}
// 팝업 표시
popup.style.display = "block";
// 팝업 내부 스크롤 강제 활성화
const popupContents = popup.querySelector(".popup_contents_wrap");
if (popupContents) {
popupContents.removeAttribute("data-lenis-prevent-wheel");
popupContents.removeAttribute("data-lenis-prevent");
popupContents.style.cssText = `
overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important;
overscroll-behavior: contain !important;
height: 100vh !important;
touch-action: pan-y !important;
`;
// 터치 이벤트 강제 허용
popupContents.addEventListener(
"touchstart",
function (e) {
e.stopPropagation();
},
{ passive: true }
);
popupContents.addEventListener(
"touchmove",
function (e) {
e.stopPropagation();
},
{ passive: true }
);
}
}
});
});
// E : 팝업 열기
// ── S : 팝업 닫기
$(function () {
$(document).on("click", function (e) {
const $target = $(e.target);
// 닫기 버튼 클릭 시
if ($target.closest(".btn_close").length) {
const $popupWrap = $target.closest(".popup_wrap");
if ($popupWrap.length) {
$popupWrap.hide();
enableBodyScroll();
}
return;
}
// popup_contents_wrap 클릭 자체가 배경 클릭인 경우 닫기
const $popupContents = $target.closest(".popup_contents_wrap");
if ($popupContents.length && $target.is(".popup_contents_wrap")) {
const $popupWrap = $target.closest(".popup_wrap");
if ($popupWrap.length) {
$popupWrap.hide();
enableBodyScroll();
}
}
});
// 팝업 닫힐 때 body 스크롤 복원
function enableBodyScroll() {
// body 스크롤 위치 복원
const scrollY = document.body.style.top;
document.body.style.position = "";
document.body.style.top = "";
document.body.style.width = "";
window.scrollTo({
top: parseInt(scrollY || "0") * -1,
left: 0,
behavior: "instant", // 또는 'auto'
});
// Lenis 재시작
handleStartLenis();
}
});
// E : 팝업 닫기
// ── S : map list 움직이기 /* 250604추가 */
let lastScrollY = window.scrollY;
const stickyBox = document.querySelector(".map_list");
window.addEventListener("scroll", () => {
const currentScrollY = window.scrollY;
const width = window.innerWidth;
if (width > 1440) {
//250604 추가
if (stickyBox != null) {
if (currentScrollY > lastScrollY) {
// 아래로 스크롤할 때
stickyBox.style.top = "24px";
} else {
// 위로 스크롤할 때
stickyBox.style.top = "124px";
}
}
} else {
if (stickyBox != null) {
if (currentScrollY > lastScrollY) {
// 아래로 스크롤할 때
stickyBox.style.top = "24px";
} else {
// 위로 스크롤할 때
stickyBox.style.top = "74px";
}
}
}
lastScrollY = currentScrollY;
});
// E : map list 움직이기
// ── SCROLL SCRIPT
// ── AOS
if (window.AOS && typeof window.AOS.init === "function") {
window.AOS.init();
}
// ── Lenis
let lenis;
let lenisTickerCallback;
function handleStartLenis() {
if (lenisTickerCallback) {
gsap.ticker.remove(lenisTickerCallback);
lenisTickerCallback = null;
}
if (lenis) {
lenis.destroy();
lenis = null;
}
if (typeof window.Lenis !== "function") {
return;
}
lenis = new Lenis({
lerp: 0.1, // 스크롤의 부드러운 정도
smoothWheel: true,
smoothTouch: false,
});
lenis.on('scroll', () => {
if (window.ScrollTrigger) {
window.ScrollTrigger.update();
}
});
lenisTickerCallback = (time) => {
if (!lenis || typeof lenis.raf !== "function") return;
lenis.raf(time * 1000);
};
gsap.ticker.add(lenisTickerCallback);
gsap.ticker.lagSmoothing(0);
}
handleStartLenis();
export default lenis;
// ── 로드 UI fallback 해제
function releaseLoadingUI() {
document.documentElement.classList.remove("loading");
document.body.classList.remove("loading", "is-loading");
[".loading", ".loading_wrap", ".loader", ".preloader"].forEach((selector) => {
document.querySelectorAll(selector).forEach((el) => {
el.style.display = "none";
el.setAttribute("aria-hidden", "true");
});
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", releaseLoadingUI, { once: true });
} else {
releaseLoadingUI();
}
window.addEventListener("load", releaseLoadingUI, { once: true });
window.setTimeout(releaseLoadingUI, 5000);
window.addEventListener("pageshow", releaseLoadingUI);
window.addEventListener("error", releaseLoadingUI);
window.addEventListener("unhandledrejection", releaseLoadingUI);
function mobileMenu() {
const mNav = document.querySelectorAll(".sitemap ol > li.depth1");
mNav.forEach((item) => {
const trigger = item.querySelector(":scope > span");
const depth2 = item.querySelector(":scope > .depth2");
if (!trigger || !depth2 || trigger.dataset.mobileMenuBound === "true") {
return;
}
trigger.dataset.mobileMenuBound = "true";
trigger.addEventListener("click", function () {
if (window.innerWidth > 1440) {
return;
}
const isActive = item.classList.contains("active");
// 다른 모든 활성화된 메뉴 닫기 (아코디언 동작)
mNav.forEach((otherItem) => {
if (otherItem !== item) {
otherItem.classList.remove("active");
const otherDepth2 = otherItem.querySelector(":scope > .depth2");
if (otherDepth2) {
otherDepth2.style.maxHeight = null;
}
}
});
if (!isActive) {
item.classList.add("active");
depth2.style.maxHeight = depth2.scrollHeight + "px";
} else {
item.classList.remove("active");
depth2.style.maxHeight = null;
}
});
});
}