mingle-website/js/contact.js
68893236+KINDNICK@users.noreply.github.com b5008b2f5d Refactor: JS 버그 수정 23건 + 이미지 최적화 + 크리에이터 사인 추가
- JS 논리 오류 수정: gallery.js lightbox 초기화 타이밍, 터치 리스너 누적, IntersectionObserver 통합
- XSS 방지: qna.js showNoResults innerHTML → textContent, 정규식 이스케이프 추가
- 안전성 개선: popup.js ESC 가드, portfolio.js getIframe optional chaining, backgrounds/props null 가드
- 이미지 최적화: 스튜디오 12장 WebP 압축 (4.0MB → 2.2MB, 46% 감소)
- 360 이미지: git 히스토리에서 원본 복구 후 4096×2048 리사이즈 (해상도 4.6배 향상)
- 360 뷰어: image-rendering auto 전환, naturalWidth/Height 기반 렌더링으로 품질 개선
- 크리에이터 사인 추가: 얌하 (3.3KB), 구슬요 (5.9KB) WebP 변환 및 마키 삽입
- 불필요 코드 제거: gallery.js 미사용 함수 6개 삭제

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:56:48 +09:00

301 lines
9.4 KiB
JavaScript

// ========================================
// Contact 페이지 전용 JavaScript
// ========================================
document.addEventListener('DOMContentLoaded', function() {
initContactForm();
initModal();
initMap();
initFormValidation();
});
// 연락처 폼 초기화
function initContactForm() {
const form = document.getElementById('contactForm');
if (form) {
form.addEventListener('submit', handleFormSubmit);
// 실시간 유효성 검사
const inputs = form.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
input.addEventListener('blur', validateField);
input.addEventListener('input', clearFieldError);
});
// 전화번호 자동 포맷팅
const phoneInput = document.getElementById('phone');
if (phoneInput) {
phoneInput.addEventListener('input', formatPhoneNumber);
}
// 리셋 버튼 확인
const resetBtn = form.querySelector('button[type="reset"]');
if (resetBtn) {
resetBtn.addEventListener('click', handleFormReset);
}
}
}
// 폼 제출 처리
async function handleFormSubmit(e) {
e.preventDefault();
const form = e.target;
const submitBtn = form.querySelector('button[type="submit"]');
// 유효성 검사
if (!validateForm(form)) {
showNotification('입력 정보를 확인해 주세요.', 'error');
return;
}
// 제출 버튼 비활성화
const originalText = submitBtn.textContent;
submitBtn.textContent = '전송 중...';
submitBtn.disabled = true;
try {
// 폼 데이터 수집
const formData = new FormData(form);
const data = Object.fromEntries(formData);
// 실제 서버 전송 (현재는 시뮬레이션)
await submitContactForm(data);
// 성공 메시지
showNotification('문의가 성공적으로 전송되었습니다. 빠른 시일 내에 연락드리겠습니다.', 'success');
form.reset();
} catch (error) {
console.error('Form submission error:', error);
showNotification('전송 중 오류가 발생했습니다. 다시 시도해 주세요.', 'error');
} finally {
// 버튼 복원
submitBtn.textContent = originalText;
submitBtn.disabled = false;
}
}
// 서버 전송 (mailto 기반 폴백)
async function submitContactForm(data) {
// 입력값에서 줄바꿈/캐리지리턴 제거 (이메일 헤더 인젝션 방지)
const sanitize = (str) => (str || '').replace(/[\r\n]/g, ' ').trim();
const subject = encodeURIComponent(`[밍글 스튜디오 문의] ${sanitize(data.name) || '웹사이트 문의'}`);
const body = encodeURIComponent(
`이름: ${sanitize(data.name)}\n` +
`이메일: ${sanitize(data.email)}\n` +
`전화번호: ${sanitize(data.phone)}\n` +
`문의 유형: ${sanitize(data.service)}\n` +
`\n문의 내용:\n${(data.message || '').trim()}`
);
window.location.href = `mailto:help@minglestudio.co.kr?subject=${subject}&body=${body}`;
return { success: true, message: '이메일 클라이언트가 열렸습니다.' };
}
// 폼 리셋 처리
function handleFormReset(e) {
const confirmed = confirm('입력한 내용이 모두 삭제됩니다. 계속하시겠습니까?');
if (!confirmed) {
e.preventDefault();
} else {
// 에러 메시지 제거
const errorElements = document.querySelectorAll('.field-error');
errorElements.forEach(el => el.remove());
// 필드 에러 스타일 제거
const fields = document.querySelectorAll('.form-group input, .form-group select, .form-group textarea');
fields.forEach(field => field.classList.remove('error'));
}
}
// 폼 유효성 검사
function validateForm(form) {
let isValid = true;
// 필수 필드 검사
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (!validateField({ target: field })) {
isValid = false;
}
});
// 이메일 형식 검사
const emailField = form.querySelector('#email');
if (emailField && emailField.value && !isValidEmail(emailField.value)) {
showFieldError(emailField, '올바른 이메일 형식을 입력해 주세요.');
isValid = false;
}
// 전화번호 형식 검사
const phoneField = form.querySelector('#phone');
if (phoneField && phoneField.value && !isValidPhone(phoneField.value)) {
showFieldError(phoneField, '올바른 전화번호 형식을 입력해 주세요.');
isValid = false;
}
return isValid;
}
// 개별 필드 유효성 검사
function validateField(e) {
const field = e.target;
const value = field.value.trim();
// 기존 에러 메시지 제거
clearFieldError(e);
// 필수 필드 검사
if (field.required && !value) {
showFieldError(field, '필수 입력 항목입니다.');
return false;
}
// 이메일 형식 검사
if (field.type === 'email' && value && !isValidEmail(value)) {
showFieldError(field, '올바른 이메일 형식을 입력해 주세요.');
return false;
}
// 전화번호 형식 검사
if (field.type === 'tel' && value && !isValidPhone(value)) {
showFieldError(field, '올바른 전화번호 형식을 입력해 주세요.');
return false;
}
return true;
}
// 필드 에러 표시
function showFieldError(field, message) {
field.classList.add('error');
const errorEl = document.createElement('div');
errorEl.className = 'field-error';
errorEl.textContent = message;
errorEl.style.color = '#ef4444';
errorEl.style.fontSize = '0.875rem';
errorEl.style.marginTop = '0.25rem';
field.parentNode.appendChild(errorEl);
}
// 필드 에러 제거
function clearFieldError(e) {
const field = e.target;
field.classList.remove('error');
const errorEl = field.parentNode.querySelector('.field-error');
if (errorEl) {
errorEl.remove();
}
}
// 이메일 유효성 검사
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// 전화번호 유효성 검사
function isValidPhone(phone) {
const phoneRegex = /^[0-9-+\s()]+$/;
return phoneRegex.test(phone) && phone.replace(/[^0-9]/g, '').length >= 10;
}
// 전화번호 자동 포맷팅
function formatPhoneNumber(e) {
let value = e.target.value.replace(/[^0-9]/g, '');
// 최대 11자리 제한 (한국 휴대폰 번호)
if (value.length > 11) {
value = value.slice(0, 11);
}
if (value.length >= 11) {
// 01X-XXXX-XXXX 형태
value = value.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
} else if (value.length >= 7) {
// 01X-XXX-XXXX 또는 01X-XXXX-XXX 형태
value = value.replace(/(\d{3})(\d{3,4})(\d{0,4})/, '$1-$2-$3');
} else if (value.length >= 3) {
// 01X-XXX 형태
value = value.replace(/(\d{3})(\d{0,4})/, '$1-$2');
}
e.target.value = value;
}
// common.js의 showNotification 사용
const showNotification = window.commonUtils?.showNotification || function() {};
// 모달 초기화
function initModal() {
const modal = document.getElementById('privacyModal');
const privacyLink = document.querySelector('.privacy-link');
const closeBtn = modal?.querySelector('.modal-close');
let lastFocusedElement = null;
function openModal() {
lastFocusedElement = document.activeElement;
modal.classList.add('active');
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
document.body.style.top = `-${window.scrollY}px`;
// 포커스를 모달 닫기 버튼으로 이동
if (closeBtn) closeBtn.focus();
}
function closeModal() {
modal.classList.remove('active');
const scrollY = document.body.style.top;
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
document.body.style.top = '';
window.scrollTo(0, parseInt(scrollY || '0') * -1);
// 포커스 복원
if (lastFocusedElement) lastFocusedElement.focus();
}
if (privacyLink && modal) {
privacyLink.addEventListener('click', function(e) {
e.preventDefault();
openModal();
});
}
if (closeBtn && modal) {
closeBtn.addEventListener('click', closeModal);
}
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === modal) closeModal();
});
}
// ESC 키로 모달 닫기
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal?.classList.contains('active')) {
closeModal();
}
});
}
// 지도 초기화
function initMap() {
// HTML에 이미 정적으로 정보가 표시되어 있으므로
// JavaScript로 덮어쓸 필요가 없음
// 지도 기능은 외부 링크(네이버 지도, 구글 맵)를 통해 제공
}
// 폼 유효성 검사 초기화
function initFormValidation() {
// 에러/알림 스타일은 contact.css에 정의됨
}