운영 기능 및 GA4 대시보드 반영

This commit is contained in:
박성필
2026-08-18 10:53:58 +09:00
parent 612e87847c
commit b492abd3b4
24 changed files with 1521 additions and 77 deletions

View File

@@ -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');