(() => { 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 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 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 (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, zoomControl: true, zoomControlOptions: { position: window.naver.maps.Position.RIGHT_CENTER, }, }); new window.naver.maps.Marker({ map, position, title: label, }); 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); })();