const revealItems = document.querySelectorAll(".reveal"); if ("IntersectionObserver" in window) { const observer = new IntersectionObserver( (entries) => { for (const entry of entries) { if (entry.isIntersecting) { entry.target.classList.add("visible"); observer.unobserve(entry.target); } } }, { rootMargin: "0px 0px -10% 0px", threshold: 0.12 } ); revealItems.forEach((item) => observer.observe(item)); } else { revealItems.forEach((item) => item.classList.add("visible")); } const productDialog = document.querySelector("#product-dialog"); const contactDialog = document.querySelector("#contact-dialog"); const contactForm = document.querySelector("#contact-form"); const formStatus = document.querySelector("#form-status"); const copyrightYear = document.querySelector(".copyright-year"); const navSectionLinks = [...document.querySelectorAll("[data-nav-section]")]; const pageSections = [...document.querySelectorAll("[data-page-section]")]; if (copyrightYear) copyrightYear.textContent = new Date().getFullYear(); if (formStatus) { formStatus.textContent = ""; formStatus.classList.remove("error", "success"); } function updateActiveNav() { const headerHeight = document.querySelector(".site-header")?.offsetHeight || 0; const marker = window.scrollY + headerHeight + Math.min(window.innerHeight * 0.3, 240); let activeSection = pageSections[0]?.dataset.pageSection; for (const section of pageSections) { if (section.offsetTop <= marker) activeSection = section.dataset.pageSection; } for (const link of navSectionLinks) { const isActive = link.dataset.navSection === activeSection; link.classList.toggle("active", isActive); if (isActive) link.setAttribute("aria-current", "location"); else link.removeAttribute("aria-current"); } } updateActiveNav(); window.addEventListener("scroll", updateActiveNav, { passive: true }); window.addEventListener("resize", updateActiveNav); function openDialog(dialog) { if (!dialog) return; if (dialog.open) return; if (typeof dialog.showModal === "function") { dialog.showModal(); } else { dialog.setAttribute("open", ""); dialog.classList.add("fallback-modal"); } document.body.classList.add("modal-open"); } function closeDialog(dialog) { if (!dialog || !dialog.open) return; if (typeof dialog.close === "function") { dialog.close(); } else { dialog.removeAttribute("open"); dialog.classList.remove("fallback-modal"); } if (!document.querySelector("dialog[open]")) { document.body.classList.remove("modal-open"); } } document.addEventListener("click", (event) => { const productButton = event.target.closest("[data-open-product]"); if (productButton) { event.preventDefault(); openDialog(productDialog); return; } const contactButton = event.target.closest("[data-open-contact]"); if (contactButton) { event.preventDefault(); closeDialog(productDialog); if (contactButton.dataset.contactCategory) { contactForm.elements.category.value = contactButton.dataset.contactCategory; } if (contactButton.dataset.contactTitle) { contactForm.elements.subject.value = contactButton.dataset.contactTitle; } formStatus.textContent = ""; openDialog(contactDialog); return; } const closeButton = event.target.closest("[data-close-dialog]"); if (closeButton) { event.preventDefault(); closeDialog(closeButton.closest("dialog")); } }); document.querySelectorAll("dialog").forEach((dialog) => { dialog.addEventListener("click", (event) => { if (event.target === dialog) closeDialog(dialog); }); dialog.addEventListener("close", () => { if (!document.querySelector("dialog[open]")) { document.body.classList.remove("modal-open"); } }); }); if (contactForm) contactForm.addEventListener("submit", async (event) => { event.preventDefault(); if (!contactForm.reportValidity()) return; const submitButton = contactForm.querySelector('button[type="submit"]'); const payload = Object.fromEntries(new FormData(contactForm)); submitButton.disabled = true; submitButton.textContent = "발송 중..."; formStatus.classList.remove("error", "success"); formStatus.textContent = "문의 내용을 전송하고 있습니다."; try { const requestBody = JSON.stringify(payload); const requestHash = [...new Uint8Array( await crypto.subtle.digest("SHA-256", new TextEncoder().encode(requestBody)) )].map((byte) => byte.toString(16).padStart(2, "0")).join(""); const response = await fetch("/api/contact", { method: "POST", headers: { "Content-Type": "application/json", "x-amz-content-sha256": requestHash }, body: requestBody }); const result = await response.json().catch(() => ({})); if (!response.ok) throw new Error(result.message || "문자 발송에 실패했습니다."); contactForm.reset(); formStatus.classList.add("success"); formStatus.textContent = "문의가 접수되었습니다. 확인 후 연락드리겠습니다."; } catch (error) { formStatus.classList.add("error"); formStatus.textContent = error.message || "문자 발송에 실패했습니다. 잠시 후 다시 시도해 주세요."; } finally { submitButton.disabled = false; submitButton.textContent = "문의하기"; } });