- 다크모드 지원 추가 (모든 페이지 CSS) - 글래스모피즘 카드 스타일 통일 - 하드코딩 컬러 → CSS 변수 전환 - 접근성 개선: skip nav, aria-label, sr-only, role="dialog" - 파일 구조 정리: 이미지/로고 images/ 폴더로 이동, 미사용 파일 삭제 - 서비스 패키지 8시간 → 6시간으로 변경 - OG 이미지를 전용 mingle-OG.png로 변경 - 컨택트 다크모드 위치 박스 투명 처리 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
460 lines
15 KiB
JavaScript
460 lines
15 KiB
JavaScript
// ========================================
|
|
// FAQ 페이지 전용 JavaScript
|
|
// ========================================
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initFAQ();
|
|
initSearch();
|
|
initCategories();
|
|
initAnimations();
|
|
initEmailForm();
|
|
});
|
|
|
|
// FAQ 기능 초기화
|
|
function initFAQ() {
|
|
const faqItems = document.querySelectorAll('.faq-item');
|
|
|
|
faqItems.forEach(item => {
|
|
const question = item.querySelector('.faq-question');
|
|
const toggle = item.querySelector('.faq-toggle');
|
|
const answer = item.querySelector('.faq-answer');
|
|
|
|
if (question && toggle && answer) {
|
|
question.addEventListener('click', () => toggleFAQ(item));
|
|
|
|
// 키보드 접근성
|
|
question.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
toggleFAQ(item);
|
|
}
|
|
});
|
|
|
|
// 포커스 가능하도록 설정
|
|
question.setAttribute('tabindex', '0');
|
|
question.setAttribute('role', 'button');
|
|
question.setAttribute('aria-expanded', 'false');
|
|
}
|
|
});
|
|
}
|
|
|
|
// FAQ 아이템 토글
|
|
function toggleFAQ(item) {
|
|
const isActive = item.classList.contains('active');
|
|
const answer = item.querySelector('.faq-answer');
|
|
const question = item.querySelector('.faq-question');
|
|
|
|
if (isActive) {
|
|
// 닫기
|
|
item.classList.remove('active');
|
|
question.setAttribute('aria-expanded', 'false');
|
|
answer.style.maxHeight = '0';
|
|
} else {
|
|
// 다른 모든 FAQ 닫기 (아코디언 효과)
|
|
document.querySelectorAll('.faq-item.active').forEach(activeItem => {
|
|
if (activeItem !== item) {
|
|
activeItem.classList.remove('active');
|
|
activeItem.querySelector('.faq-question').setAttribute('aria-expanded', 'false');
|
|
activeItem.querySelector('.faq-answer').style.maxHeight = '0';
|
|
}
|
|
});
|
|
|
|
// 현재 FAQ 열기
|
|
item.classList.add('active');
|
|
question.setAttribute('aria-expanded', 'true');
|
|
|
|
// 정확한 높이 계산을 위해 잠시 보이게 한 후 측정
|
|
answer.style.maxHeight = 'none';
|
|
answer.style.overflow = 'visible';
|
|
const height = answer.scrollHeight;
|
|
answer.style.maxHeight = '0';
|
|
answer.style.overflow = 'hidden';
|
|
|
|
// 애니메이션을 위해 약간의 지연 후 높이 설정
|
|
setTimeout(() => {
|
|
answer.style.maxHeight = (height + 50) + 'px';
|
|
}, 10);
|
|
|
|
// 스크롤 애니메이션
|
|
setTimeout(() => {
|
|
item.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'nearest'
|
|
});
|
|
}, 100);
|
|
}
|
|
}
|
|
|
|
// 검색 기능 초기화
|
|
function initSearch() {
|
|
const searchInput = document.getElementById('faqSearch');
|
|
const searchBtn = document.querySelector('.search-btn');
|
|
const suggestions = document.getElementById('searchSuggestions');
|
|
|
|
if (searchInput) {
|
|
let searchTimeout;
|
|
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
handleSearch(this.value.trim());
|
|
updateSearchSuggestions(this.value.trim());
|
|
}, 300);
|
|
});
|
|
|
|
searchInput.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
handleSearch(this.value.trim());
|
|
hideSuggestions();
|
|
}
|
|
});
|
|
|
|
// 검색 버튼 클릭
|
|
if (searchBtn) {
|
|
searchBtn.addEventListener('click', () => {
|
|
handleSearch(searchInput.value.trim());
|
|
hideSuggestions();
|
|
});
|
|
}
|
|
|
|
// 클릭 외부 영역 시 제안 사항 숨기기
|
|
document.addEventListener('click', function(e) {
|
|
if (!searchInput.contains(e.target) && !suggestions?.contains(e.target)) {
|
|
hideSuggestions();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 검색 처리
|
|
function handleSearch(query) {
|
|
const faqItems = document.querySelectorAll('.faq-item');
|
|
const noResults = document.querySelector('.no-results');
|
|
let hasResults = false;
|
|
|
|
if (!query) {
|
|
// 검색어가 없으면 모든 항목 표시
|
|
faqItems.forEach(item => {
|
|
item.classList.remove('hidden');
|
|
clearSearchHighlight(item);
|
|
});
|
|
hideNoResults();
|
|
return;
|
|
}
|
|
|
|
const searchRegex = new RegExp(query, 'gi');
|
|
|
|
faqItems.forEach(item => {
|
|
const question = item.querySelector('.faq-question h3');
|
|
const answer = item.querySelector('.faq-answer');
|
|
const questionText = question.textContent;
|
|
const answerText = answer.textContent;
|
|
|
|
// 검색어 매칭 확인
|
|
const questionMatch = searchRegex.test(questionText);
|
|
const answerMatch = searchRegex.test(answerText);
|
|
|
|
if (questionMatch || answerMatch) {
|
|
item.classList.remove('hidden');
|
|
hasResults = true;
|
|
|
|
// 검색어 하이라이트
|
|
highlightSearchTerm(item, query);
|
|
|
|
// 답변에 매칭되는 경우 자동으로 열기
|
|
if (answerMatch && !questionMatch) {
|
|
toggleFAQ(item);
|
|
}
|
|
} else {
|
|
item.classList.add('hidden');
|
|
clearSearchHighlight(item);
|
|
}
|
|
});
|
|
|
|
// 검색 결과가 없는 경우
|
|
if (!hasResults) {
|
|
showNoResults(query);
|
|
} else {
|
|
hideNoResults();
|
|
}
|
|
}
|
|
|
|
// 검색어 하이라이트
|
|
function highlightSearchTerm(item, term) {
|
|
const question = item.querySelector('.faq-question h3');
|
|
const answer = item.querySelector('.faq-answer');
|
|
|
|
const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
const highlightRegex = new RegExp(`(${escapedTerm})`, 'gi');
|
|
|
|
// 질문 하이라이트
|
|
const originalQuestionText = question.dataset.originalText || question.textContent;
|
|
question.dataset.originalText = originalQuestionText;
|
|
question.innerHTML = originalQuestionText.replace(highlightRegex, '<span class="search-highlight">$1</span>');
|
|
|
|
// 답변 하이라이트
|
|
const answerElements = answer.querySelectorAll('p, li');
|
|
answerElements.forEach(el => {
|
|
const originalText = el.dataset.originalText || el.textContent;
|
|
el.dataset.originalText = originalText;
|
|
el.innerHTML = originalText.replace(highlightRegex, '<span class="search-highlight">$1</span>');
|
|
});
|
|
}
|
|
|
|
// 검색 하이라이트 제거
|
|
function clearSearchHighlight(item) {
|
|
const question = item.querySelector('.faq-question h3');
|
|
const answer = item.querySelector('.faq-answer');
|
|
|
|
// 질문 하이라이트 제거
|
|
if (question.dataset.originalText) {
|
|
question.textContent = question.dataset.originalText;
|
|
delete question.dataset.originalText;
|
|
}
|
|
|
|
// 답변 하이라이트 제거
|
|
const answerElements = answer.querySelectorAll('p, li');
|
|
answerElements.forEach(el => {
|
|
if (el.dataset.originalText) {
|
|
el.textContent = el.dataset.originalText;
|
|
delete el.dataset.originalText;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 검색 제안사항 업데이트
|
|
function updateSearchSuggestions(query) {
|
|
const suggestions = document.getElementById('searchSuggestions');
|
|
if (!suggestions || !query || query.length < 2) {
|
|
hideSuggestions();
|
|
return;
|
|
}
|
|
|
|
// 미리 정의된 검색 키워드
|
|
const searchKeywords = [
|
|
'예약', '취소', '환불', '요금', '가격', '결제',
|
|
'장비', '모션캡쳐', '시간', '인원', '준비물',
|
|
'데이터', '파일', '형식', '스트리밍', '버튜버',
|
|
'주차', '위치', '견학', '투어', '방역', '코로나'
|
|
];
|
|
|
|
const matchingKeywords = searchKeywords.filter(keyword =>
|
|
keyword.includes(query) || query.includes(keyword)
|
|
);
|
|
|
|
if (matchingKeywords.length > 0) {
|
|
suggestions.innerHTML = matchingKeywords
|
|
.slice(0, 5)
|
|
.map(keyword =>
|
|
`<div class="suggestion-item" onclick="selectSuggestion('${keyword}')">${keyword}</div>`
|
|
).join('');
|
|
suggestions.classList.add('active');
|
|
} else {
|
|
hideSuggestions();
|
|
}
|
|
}
|
|
|
|
// 제안사항 선택
|
|
function selectSuggestion(keyword) {
|
|
const searchInput = document.getElementById('faqSearch');
|
|
if (searchInput) {
|
|
searchInput.value = keyword;
|
|
handleSearch(keyword);
|
|
}
|
|
hideSuggestions();
|
|
}
|
|
|
|
// 제안사항 숨기기
|
|
function hideSuggestions() {
|
|
const suggestions = document.getElementById('searchSuggestions');
|
|
if (suggestions) {
|
|
suggestions.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// 검색 결과 없음 표시
|
|
function showNoResults(query) {
|
|
const safeQuery = query.replace(/[<>&"]/g, c => ({'<':'<','>':'>','&':'&','"':'"'})[c]);
|
|
let noResults = document.querySelector('.no-results');
|
|
if (!noResults) {
|
|
noResults = document.createElement('div');
|
|
noResults.className = 'no-results';
|
|
noResults.innerHTML = `
|
|
<div class="no-results-icon">🔍</div>
|
|
<h3>검색 결과가 없습니다</h3>
|
|
<p><strong>"${safeQuery}"</strong>와 관련된 질문을 찾을 수 없습니다.</p>
|
|
<p>다른 키워드로 검색해보시거나 <a href="contact.html">직접 문의</a>해 주세요.</p>
|
|
`;
|
|
document.querySelector('.faq-list').appendChild(noResults);
|
|
} else {
|
|
noResults.querySelector('p strong').textContent = `"${query}"`;
|
|
}
|
|
noResults.classList.add('active');
|
|
}
|
|
|
|
// 검색 결과 없음 숨기기
|
|
function hideNoResults() {
|
|
const noResults = document.querySelector('.no-results');
|
|
if (noResults) {
|
|
noResults.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// 카테고리 필터 초기화
|
|
function initCategories() {
|
|
const categoryBtns = document.querySelectorAll('.category-btn');
|
|
|
|
categoryBtns.forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const category = this.dataset.category;
|
|
|
|
// 활성 버튼 업데이트
|
|
categoryBtns.forEach(b => b.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
// FAQ 필터링
|
|
filterByCategory(category);
|
|
|
|
// 검색 초기화
|
|
const searchInput = document.getElementById('faqSearch');
|
|
if (searchInput) {
|
|
searchInput.value = '';
|
|
}
|
|
hideSuggestions();
|
|
hideNoResults();
|
|
});
|
|
});
|
|
}
|
|
|
|
// 카테고리별 필터링
|
|
function filterByCategory(category) {
|
|
const faqItems = document.querySelectorAll('.faq-item');
|
|
|
|
faqItems.forEach(item => {
|
|
// 모든 FAQ 닫기
|
|
item.classList.remove('active');
|
|
item.querySelector('.faq-answer').style.maxHeight = '0';
|
|
item.querySelector('.faq-question').setAttribute('aria-expanded', 'false');
|
|
|
|
// 검색 하이라이트 제거
|
|
clearSearchHighlight(item);
|
|
|
|
if (category === 'all' || item.dataset.category === category) {
|
|
item.classList.remove('hidden');
|
|
} else {
|
|
item.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// 페이지 상단으로 스크롤
|
|
document.querySelector('.faq-list').scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
|
|
// 애니메이션 초기화
|
|
function initAnimations() {
|
|
// Intersection Observer를 사용한 스크롤 애니메이션
|
|
const observerOptions = {
|
|
threshold: 0.1,
|
|
rootMargin: '50px 0px'
|
|
};
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach((entry, index) => {
|
|
if (entry.isIntersecting) {
|
|
setTimeout(() => {
|
|
entry.target.style.opacity = '1';
|
|
entry.target.style.transform = 'translateY(0)';
|
|
}, index * 100);
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, observerOptions);
|
|
|
|
// FAQ 아이템들에 초기 스타일 적용 및 관찰 시작
|
|
const faqItems = document.querySelectorAll('.faq-item');
|
|
faqItems.forEach(item => {
|
|
item.style.opacity = '0';
|
|
item.style.transform = 'translateY(30px)';
|
|
item.style.transition = 'all 0.6s cubic-bezier(0.4, 0, 0.2, 1)';
|
|
observer.observe(item);
|
|
});
|
|
}
|
|
|
|
// URL 해시로 특정 FAQ 열기
|
|
function openFAQByHash() {
|
|
const hash = window.location.hash.substring(1);
|
|
if (hash) {
|
|
const faqItem = document.querySelector(`[data-id="${hash}"]`);
|
|
if (faqItem) {
|
|
toggleFAQ(faqItem);
|
|
faqItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
}
|
|
}
|
|
|
|
// 페이지 로드 시 해시 확인
|
|
window.addEventListener('load', openFAQByHash);
|
|
|
|
// FAQ 공유 기능 (선택사항)
|
|
function shareFAQ(faqId) {
|
|
const url = `${window.location.origin}${window.location.pathname}#${faqId}`;
|
|
|
|
if (navigator.share) {
|
|
navigator.share({
|
|
title: 'FAQ - 밍글 스튜디오',
|
|
url: url
|
|
});
|
|
} else {
|
|
// 클립보드에 복사
|
|
navigator.clipboard.writeText(url).then(() => {
|
|
if (window.commonUtils && window.commonUtils.showNotification) {
|
|
window.commonUtils.showNotification('링크가 클립보드에 복사되었습니다.', 'success');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 이메일 폼 초기화
|
|
function initEmailForm() {
|
|
const emailBtn = document.getElementById('showEmailFormFAQ');
|
|
const emailForm = document.getElementById('emailFormFAQ');
|
|
|
|
if (emailBtn && emailForm) {
|
|
emailBtn.addEventListener('click', function() {
|
|
if (emailForm.style.display === 'none' || !emailForm.style.display) {
|
|
emailForm.style.display = 'block';
|
|
emailBtn.textContent = '📧 양식 숨기기';
|
|
|
|
// FAQ 답변 높이 재계산
|
|
const faqItem = emailForm.closest('.faq-item');
|
|
const answer = faqItem.querySelector('.faq-answer');
|
|
if (faqItem.classList.contains('active')) {
|
|
// 높이 재계산을 위해 잠시 auto로 설정
|
|
answer.style.maxHeight = 'none';
|
|
const newHeight = answer.scrollHeight;
|
|
answer.style.maxHeight = (newHeight + 100) + 'px'; // 여유분 추가
|
|
}
|
|
|
|
setTimeout(() => {
|
|
emailForm.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
}, 100);
|
|
} else {
|
|
emailForm.style.display = 'none';
|
|
emailBtn.innerHTML = '<span>📧</span> 이메일 문의하기';
|
|
|
|
// FAQ 답변 높이 재계산
|
|
const faqItem = emailForm.closest('.faq-item');
|
|
const answer = faqItem.querySelector('.faq-answer');
|
|
if (faqItem.classList.contains('active')) {
|
|
answer.style.maxHeight = 'none';
|
|
const newHeight = answer.scrollHeight;
|
|
answer.style.maxHeight = (newHeight + 50) + 'px';
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} |