운영 기능 및 GA4 대시보드 반영
This commit is contained in:
@@ -1,3 +1,115 @@
|
||||
(() => {
|
||||
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');
|
||||
@@ -212,6 +324,82 @@
|
||||
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;
|
||||
@@ -219,6 +407,9 @@
|
||||
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);
|
||||
@@ -231,6 +422,7 @@
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -249,10 +441,15 @@
|
||||
center: position,
|
||||
zoom: 17,
|
||||
minZoom: 10,
|
||||
zoomControl: true,
|
||||
zoomControlOptions: {
|
||||
position: window.naver.maps.Position.RIGHT_CENTER,
|
||||
},
|
||||
maxZoom: 21,
|
||||
draggable: false,
|
||||
keyboardShortcuts: false,
|
||||
scrollWheel: false,
|
||||
pinchZoom: false,
|
||||
disableDoubleClickZoom: true,
|
||||
disableDoubleTapZoom: true,
|
||||
disableTwoFingerTapZoom: true,
|
||||
zoomControl: false,
|
||||
});
|
||||
|
||||
new window.naver.maps.Marker({
|
||||
@@ -261,6 +458,22 @@
|
||||
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');
|
||||
|
||||
Reference in New Issue
Block a user