529 lines
18 KiB
JavaScript
529 lines
18 KiB
JavaScript
(() => {
|
|
const root = document.querySelector('[data-analytics-consent]');
|
|
if (!root) return;
|
|
|
|
const banner = document.querySelector('[data-cookie-consent]');
|
|
const settingsButton = document.querySelector('[data-cookie-settings]');
|
|
const acceptButton = banner?.querySelector('[data-cookie-accept]');
|
|
const declineButton = banner?.querySelector('[data-cookie-decline]');
|
|
const enabled = root.dataset.analyticsEnabled === 'true';
|
|
const measurementId = root.dataset.analyticsMeasurementId || '';
|
|
const consentKey = root.dataset.analyticsConsentKey || 'gtsit_analytics_consent_v1';
|
|
let analyticsLoaded = false;
|
|
|
|
const readConsent = () => {
|
|
try {
|
|
return window.localStorage.getItem(consentKey);
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const saveConsent = (value) => {
|
|
try {
|
|
window.localStorage.setItem(consentKey, value);
|
|
} catch {
|
|
// 저장소를 사용할 수 없는 경우 현재 페이지만 선택을 유지합니다.
|
|
}
|
|
};
|
|
|
|
const setGoogleConsent = (analyticsStorage) => {
|
|
window.dataLayer = window.dataLayer || [];
|
|
window.gtag = window.gtag || function gtag() {
|
|
window.dataLayer.push(arguments);
|
|
};
|
|
window.gtag('consent', 'update', {
|
|
analytics_storage: analyticsStorage,
|
|
ad_storage: 'denied',
|
|
ad_user_data: 'denied',
|
|
ad_personalization: 'denied',
|
|
});
|
|
};
|
|
|
|
const loadAnalytics = () => {
|
|
if (!enabled || !measurementId || analyticsLoaded) return;
|
|
analyticsLoaded = true;
|
|
window.dataLayer = window.dataLayer || [];
|
|
window.gtag = window.gtag || function gtag() {
|
|
window.dataLayer.push(arguments);
|
|
};
|
|
window.gtag('consent', 'default', {
|
|
analytics_storage: 'denied',
|
|
ad_storage: 'denied',
|
|
ad_user_data: 'denied',
|
|
ad_personalization: 'denied',
|
|
});
|
|
window.gtag('consent', 'update', {
|
|
analytics_storage: 'granted',
|
|
ad_storage: 'denied',
|
|
ad_user_data: 'denied',
|
|
ad_personalization: 'denied',
|
|
});
|
|
window.gtag('js', new Date());
|
|
window.gtag('config', measurementId, {
|
|
allow_google_signals: false,
|
|
allow_ad_personalization_signals: false,
|
|
});
|
|
|
|
const script = document.createElement('script');
|
|
script.async = true;
|
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`;
|
|
document.head.append(script);
|
|
};
|
|
|
|
const clearAnalyticsCookies = () => {
|
|
const names = document.cookie.split(';').map((cookie) => cookie.split('=')[0].trim()).filter((name) => name.startsWith('_ga'));
|
|
names.forEach((name) => {
|
|
document.cookie = `${name}=; Max-Age=0; path=/; SameSite=Lax`;
|
|
document.cookie = `${name}=; Max-Age=0; path=/; domain=.gtsit.co.kr; SameSite=Lax`;
|
|
});
|
|
};
|
|
|
|
const hideBanner = () => {
|
|
if (banner) banner.hidden = true;
|
|
};
|
|
|
|
const showBanner = () => {
|
|
if (!banner) return;
|
|
banner.hidden = false;
|
|
acceptButton?.focus();
|
|
};
|
|
|
|
acceptButton?.addEventListener('click', () => {
|
|
saveConsent('granted');
|
|
hideBanner();
|
|
if (analyticsLoaded) setGoogleConsent('granted');
|
|
else loadAnalytics();
|
|
});
|
|
|
|
declineButton?.addEventListener('click', () => {
|
|
saveConsent('denied');
|
|
setGoogleConsent('denied');
|
|
clearAnalyticsCookies();
|
|
hideBanner();
|
|
});
|
|
|
|
settingsButton?.addEventListener('click', showBanner);
|
|
|
|
const consent = readConsent();
|
|
if (consent === 'granted') loadAnalytics();
|
|
else if (consent !== 'denied') showBanner();
|
|
})();
|
|
|
|
(() => {
|
|
const toggle = document.querySelector('.menu-toggle');
|
|
const navigation = document.querySelector('.site-navigation');
|
|
if (!toggle || !navigation) return;
|
|
|
|
const close = () => {
|
|
navigation.classList.remove('is-open');
|
|
toggle.setAttribute('aria-expanded', 'false');
|
|
toggle.querySelector('.sr-only').textContent = '메뉴 열기';
|
|
};
|
|
|
|
toggle.addEventListener('click', () => {
|
|
const open = !navigation.classList.contains('is-open');
|
|
navigation.classList.toggle('is-open', open);
|
|
toggle.setAttribute('aria-expanded', String(open));
|
|
toggle.querySelector('.sr-only').textContent = open ? '메뉴 닫기' : '메뉴 열기';
|
|
});
|
|
|
|
navigation.addEventListener('click', (event) => {
|
|
if (event.target.closest('a')) close();
|
|
});
|
|
|
|
window.addEventListener('resize', () => {
|
|
if (window.innerWidth > 960) close();
|
|
});
|
|
})();
|
|
|
|
(() => {
|
|
const slider = document.querySelector('[data-hero-slider]');
|
|
if (!slider) return;
|
|
|
|
const slides = [...slider.querySelectorAll('.hero-slide')];
|
|
const dots = [...slider.querySelectorAll('[data-slider-dot]')];
|
|
const previous = slider.querySelector('[data-slider-prev]');
|
|
const next = slider.querySelector('[data-slider-next]');
|
|
const pause = slider.querySelector('[data-slider-pause]');
|
|
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
let current = 0;
|
|
let timer = null;
|
|
let isPaused = false;
|
|
let isInteracting = false;
|
|
|
|
const show = (index) => {
|
|
current = (index + slides.length) % slides.length;
|
|
slides.forEach((slide, slideIndex) => {
|
|
const isActive = slideIndex === current;
|
|
slide.classList.toggle('is-active', isActive);
|
|
slide.setAttribute('aria-hidden', String(!isActive));
|
|
});
|
|
dots.forEach((dot, dotIndex) => {
|
|
const isActive = dotIndex === current;
|
|
dot.classList.toggle('is-active', isActive);
|
|
if (isActive) dot.setAttribute('aria-current', 'true');
|
|
else dot.removeAttribute('aria-current');
|
|
});
|
|
};
|
|
|
|
const stop = () => {
|
|
if (timer !== null) window.clearInterval(timer);
|
|
timer = null;
|
|
};
|
|
|
|
const start = () => {
|
|
stop();
|
|
if (isPaused || isInteracting || reducedMotion.matches || document.hidden) return;
|
|
timer = window.setInterval(() => show(current + 1), 5000);
|
|
};
|
|
|
|
const move = (direction) => {
|
|
show(current + direction);
|
|
start();
|
|
};
|
|
|
|
previous?.addEventListener('click', () => move(-1));
|
|
next?.addEventListener('click', () => move(1));
|
|
dots.forEach((dot) => dot.addEventListener('click', () => {
|
|
show(Number(dot.dataset.sliderDot));
|
|
start();
|
|
}));
|
|
pause?.addEventListener('click', () => {
|
|
isPaused = !isPaused;
|
|
pause.setAttribute('aria-pressed', String(isPaused));
|
|
pause.textContent = isPaused ? '자동재생 시작' : '자동재생 일시정지';
|
|
start();
|
|
});
|
|
slider.addEventListener('mouseenter', () => {
|
|
isInteracting = true;
|
|
stop();
|
|
});
|
|
slider.addEventListener('mouseleave', () => {
|
|
isInteracting = false;
|
|
start();
|
|
});
|
|
slider.addEventListener('focusin', () => {
|
|
isInteracting = true;
|
|
stop();
|
|
});
|
|
slider.addEventListener('focusout', () => {
|
|
isInteracting = false;
|
|
start();
|
|
});
|
|
document.addEventListener('visibilitychange', start);
|
|
reducedMotion.addEventListener('change', start);
|
|
start();
|
|
})();
|
|
|
|
(() => {
|
|
const phoneInput = document.querySelector('[data-phone-input]');
|
|
const emailField = document.querySelector('[data-email-field]');
|
|
|
|
if (phoneInput) {
|
|
const formatPhone = (value) => {
|
|
let numbers = value.replace(/\D/g, '');
|
|
|
|
if (numbers.startsWith('02')) {
|
|
numbers = numbers.slice(0, 10);
|
|
if (numbers.length <= 2) return numbers;
|
|
if (numbers.length <= 5) return `${numbers.slice(0, 2)}-${numbers.slice(2)}`;
|
|
if (numbers.length <= 9) return `${numbers.slice(0, 2)}-${numbers.slice(2, 5)}-${numbers.slice(5)}`;
|
|
return `${numbers.slice(0, 2)}-${numbers.slice(2, 6)}-${numbers.slice(6)}`;
|
|
}
|
|
|
|
if (/^1\d{3}/.test(numbers)) {
|
|
numbers = numbers.slice(0, 8);
|
|
return numbers.length <= 4 ? numbers : `${numbers.slice(0, 4)}-${numbers.slice(4)}`;
|
|
}
|
|
|
|
if (/^050\d/.test(numbers)) {
|
|
numbers = numbers.slice(0, 12);
|
|
if (numbers.length <= 4) return numbers;
|
|
if (numbers.length <= 8) return `${numbers.slice(0, 4)}-${numbers.slice(4)}`;
|
|
return `${numbers.slice(0, 4)}-${numbers.slice(4, 8)}-${numbers.slice(8)}`;
|
|
}
|
|
|
|
numbers = numbers.slice(0, 11);
|
|
if (numbers.length <= 3) return numbers;
|
|
if (numbers.length <= 6) return `${numbers.slice(0, 3)}-${numbers.slice(3)}`;
|
|
if (numbers.length <= 10) return `${numbers.slice(0, 3)}-${numbers.slice(3, 6)}-${numbers.slice(6)}`;
|
|
return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7)}`;
|
|
};
|
|
|
|
const applyPhoneFormat = () => {
|
|
phoneInput.value = formatPhone(phoneInput.value);
|
|
};
|
|
|
|
phoneInput.addEventListener('input', applyPhoneFormat);
|
|
applyPhoneFormat();
|
|
}
|
|
|
|
document.querySelectorAll('[data-pin-input]').forEach((pinInput) => {
|
|
const applyPinFormat = () => {
|
|
pinInput.value = pinInput.value.replace(/\D/g, '').slice(0, 4);
|
|
};
|
|
pinInput.addEventListener('input', applyPinFormat);
|
|
applyPinFormat();
|
|
});
|
|
|
|
if (!emailField) return;
|
|
|
|
const template = emailField.querySelector('[data-email-template]');
|
|
const localInput = emailField.querySelector('[data-email-local]');
|
|
const domainSelect = emailField.querySelector('[data-email-domain]');
|
|
const directRow = emailField.querySelector('[data-email-direct]');
|
|
const fullInput = emailField.querySelector('[data-email-full]');
|
|
const returnButton = emailField.querySelector('[data-email-return]');
|
|
if (!template || !localInput || !domainSelect || !directRow || !fullInput || !returnButton) return;
|
|
|
|
const domains = [...domainSelect.options].map((option) => option.value).filter((value) => value !== 'direct');
|
|
const splitEmail = (email) => {
|
|
const separator = email.lastIndexOf('@');
|
|
if (separator <= 0) return null;
|
|
return { local: email.slice(0, separator), domain: email.slice(separator + 1).toLowerCase() };
|
|
};
|
|
const syncEmail = () => {
|
|
fullInput.value = localInput.value && domainSelect.value !== 'direct'
|
|
? `${localInput.value}@${domainSelect.value}`
|
|
: '';
|
|
};
|
|
const showTemplate = () => {
|
|
const parsed = splitEmail(fullInput.value);
|
|
localInput.value = parsed?.local || fullInput.value;
|
|
domainSelect.value = parsed && domains.includes(parsed.domain) ? parsed.domain : 'naver.com';
|
|
template.hidden = false;
|
|
directRow.hidden = true;
|
|
returnButton.hidden = true;
|
|
syncEmail();
|
|
};
|
|
const showDirect = () => {
|
|
const local = localInput.value.trim();
|
|
fullInput.value = local ? `${local}@` : '';
|
|
template.hidden = true;
|
|
directRow.hidden = false;
|
|
returnButton.hidden = false;
|
|
fullInput.focus();
|
|
};
|
|
|
|
localInput.addEventListener('input', syncEmail);
|
|
domainSelect.addEventListener('change', () => {
|
|
if (domainSelect.value === 'direct') showDirect();
|
|
else syncEmail();
|
|
});
|
|
returnButton.addEventListener('click', () => {
|
|
showTemplate();
|
|
localInput.focus();
|
|
});
|
|
emailField.closest('form')?.addEventListener('submit', () => {
|
|
if (!template.hidden) syncEmail();
|
|
});
|
|
|
|
const initialEmail = splitEmail(fullInput.value);
|
|
if (!fullInput.value || (initialEmail && domains.includes(initialEmail.domain))) showTemplate();
|
|
else returnButton.hidden = false;
|
|
})();
|
|
|
|
(() => {
|
|
const slider = document.querySelector('[data-article-slider]');
|
|
if (!slider) return;
|
|
|
|
const slides = [...slider.querySelectorAll('.article-gallery-slide')];
|
|
const dots = [...slider.querySelectorAll('[data-article-dot]')];
|
|
const previous = slider.querySelector('[data-article-prev]');
|
|
const next = slider.querySelector('[data-article-next]');
|
|
const pause = slider.querySelector('[data-article-pause]');
|
|
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
let current = 0;
|
|
let timer = null;
|
|
let isPaused = false;
|
|
let isInteracting = false;
|
|
|
|
const show = (index) => {
|
|
current = (index + slides.length) % slides.length;
|
|
slides.forEach((slide, slideIndex) => {
|
|
const active = slideIndex === current;
|
|
slide.classList.toggle('is-active', active);
|
|
slide.setAttribute('aria-hidden', String(!active));
|
|
});
|
|
dots.forEach((dot, dotIndex) => {
|
|
const active = dotIndex === current;
|
|
dot.classList.toggle('is-active', active);
|
|
if (active) dot.setAttribute('aria-current', 'true');
|
|
else dot.removeAttribute('aria-current');
|
|
});
|
|
};
|
|
const stop = () => {
|
|
if (timer !== null) window.clearInterval(timer);
|
|
timer = null;
|
|
};
|
|
const start = () => {
|
|
stop();
|
|
if (isPaused || isInteracting || reducedMotion.matches || document.hidden) return;
|
|
timer = window.setInterval(() => show(current + 1), 5000);
|
|
};
|
|
const move = (direction) => {
|
|
show(current + direction);
|
|
start();
|
|
};
|
|
|
|
previous?.addEventListener('click', () => move(-1));
|
|
next?.addEventListener('click', () => move(1));
|
|
dots.forEach((dot) => dot.addEventListener('click', () => {
|
|
show(Number(dot.dataset.articleDot));
|
|
start();
|
|
}));
|
|
pause?.addEventListener('click', () => {
|
|
isPaused = !isPaused;
|
|
pause.setAttribute('aria-pressed', String(isPaused));
|
|
pause.textContent = isPaused ? '자동재생 시작' : '자동재생 일시정지';
|
|
start();
|
|
});
|
|
slider.addEventListener('mouseenter', () => {
|
|
isInteracting = true;
|
|
stop();
|
|
});
|
|
slider.addEventListener('mouseleave', () => {
|
|
isInteracting = false;
|
|
start();
|
|
});
|
|
slider.addEventListener('focusin', () => {
|
|
isInteracting = true;
|
|
stop();
|
|
});
|
|
slider.addEventListener('focusout', () => {
|
|
isInteracting = false;
|
|
start();
|
|
});
|
|
document.addEventListener('visibilitychange', start);
|
|
reducedMotion.addEventListener('change', start);
|
|
start();
|
|
})();
|
|
|
|
(() => {
|
|
const mapFrame = document.querySelector('[data-naver-map]');
|
|
if (!mapFrame) return;
|
|
|
|
const mapElement = mapFrame.querySelector('.naver-map');
|
|
const statusElement = mapFrame.querySelector('[data-naver-map-status]');
|
|
const fullscreenButton = mapFrame.querySelector('[data-naver-map-fullscreen]');
|
|
const zoomControl = mapFrame.querySelector('[data-naver-map-zoom]');
|
|
const zoomInButton = mapFrame.querySelector('[data-naver-map-zoom-in]');
|
|
const zoomOutButton = mapFrame.querySelector('[data-naver-map-zoom-out]');
|
|
const externalLink = mapFrame.parentElement?.querySelector('[data-naver-map-external]');
|
|
const clientId = mapFrame.dataset.clientId;
|
|
const latitude = Number(mapFrame.dataset.latitude);
|
|
const longitude = Number(mapFrame.dataset.longitude);
|
|
const label = mapFrame.dataset.label;
|
|
let settled = false;
|
|
|
|
const setState = (state, message = '') => {
|
|
settled = state !== 'loading';
|
|
mapFrame.dataset.mapState = state;
|
|
mapFrame.setAttribute('aria-busy', String(state === 'loading'));
|
|
if (fullscreenButton) fullscreenButton.hidden = state !== 'ready';
|
|
if (zoomControl) zoomControl.hidden = state !== 'ready';
|
|
if (statusElement && message) statusElement.textContent = message;
|
|
};
|
|
|
|
const fail = () => {
|
|
setState('error', '지도를 불러오지 못했습니다. 아래 네이버 지도 링크를 이용해 주세요.');
|
|
};
|
|
|
|
const renderMap = () => {
|
|
if (!mapElement || !window.naver?.maps || !Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
fail();
|
|
return;
|
|
}
|
|
|
|
const position = new window.naver.maps.LatLng(latitude, longitude);
|
|
const map = new window.naver.maps.Map(mapElement, {
|
|
center: position,
|
|
zoom: 17,
|
|
minZoom: 10,
|
|
maxZoom: 21,
|
|
draggable: false,
|
|
keyboardShortcuts: false,
|
|
scrollWheel: false,
|
|
pinchZoom: false,
|
|
disableDoubleClickZoom: true,
|
|
disableDoubleTapZoom: true,
|
|
disableTwoFingerTapZoom: true,
|
|
zoomControl: false,
|
|
});
|
|
|
|
new window.naver.maps.Marker({
|
|
map,
|
|
position,
|
|
title: label,
|
|
});
|
|
|
|
const updateZoomButtons = () => {
|
|
const zoom = map.getZoom();
|
|
if (zoomInButton) zoomInButton.disabled = zoom >= 21;
|
|
if (zoomOutButton) zoomOutButton.disabled = zoom <= 10;
|
|
};
|
|
const setFixedZoom = (difference) => {
|
|
const nextZoom = Math.min(21, Math.max(10, map.getZoom() + difference));
|
|
map.setCenter(position);
|
|
map.setZoom(nextZoom, false);
|
|
map.setCenter(position);
|
|
updateZoomButtons();
|
|
};
|
|
zoomInButton?.addEventListener('click', () => setFixedZoom(1));
|
|
zoomOutButton?.addEventListener('click', () => setFixedZoom(-1));
|
|
updateZoomButtons();
|
|
|
|
const refreshMapSize = () => {
|
|
window.setTimeout(() => {
|
|
window.naver.maps.Event.trigger(map, 'resize');
|
|
map.setCenter(position);
|
|
}, 0);
|
|
};
|
|
document.addEventListener('fullscreenchange', () => {
|
|
const isFullscreen = document.fullscreenElement === mapFrame;
|
|
mapFrame.classList.toggle('is-fullscreen', isFullscreen);
|
|
if (fullscreenButton) {
|
|
fullscreenButton.setAttribute('aria-label', isFullscreen ? '전체 화면 종료' : '지도를 전체 화면으로 보기');
|
|
fullscreenButton.title = isFullscreen ? '전체 화면 종료' : '전체 화면';
|
|
}
|
|
refreshMapSize();
|
|
});
|
|
if (fullscreenButton && !mapFrame.requestFullscreen) {
|
|
fullscreenButton.setAttribute('aria-label', '네이버 지도에서 크게 보기');
|
|
fullscreenButton.title = '네이버 지도에서 열기';
|
|
}
|
|
fullscreenButton?.addEventListener('click', async () => {
|
|
if (document.fullscreenElement === mapFrame) {
|
|
await document.exitFullscreen();
|
|
} else if (mapFrame.requestFullscreen) {
|
|
await mapFrame.requestFullscreen();
|
|
} else if (externalLink) {
|
|
window.open(externalLink.href, '_blank', 'noopener');
|
|
}
|
|
});
|
|
setState('ready', '지도를 불러왔습니다.');
|
|
};
|
|
|
|
if (!clientId) {
|
|
fail();
|
|
return;
|
|
}
|
|
|
|
setState('loading');
|
|
window.navermap_authFailure = fail;
|
|
const script = document.createElement('script');
|
|
const parameters = new URLSearchParams({
|
|
ncpKeyId: clientId,
|
|
});
|
|
script.src = `https://oapi.map.naver.com/openapi/v3/maps.js?${parameters}`;
|
|
script.async = true;
|
|
script.onload = renderMap;
|
|
script.onerror = fail;
|
|
document.head.append(script);
|
|
|
|
window.setTimeout(() => {
|
|
if (!settled) fail();
|
|
}, 12000);
|
|
})();
|