Files
1st.doah.life/index.html
T
2026-05-07 01:44:44 +09:00

1311 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>도아의 돌잔치에 초대합니다</title>
<meta name="description" content="도아의 첫 번째 생일에 초대합니다.">
<meta property="og:type" content="website">
<meta property="og:title" content="도아의 돌잔치에 초대합니다">
<meta property="og:description" content="2026년 5월 10일 오후 12시, 호텔인터시티 16층 더스크래치뷔페">
<meta property="og:image" content="./images/hero.jpg">
<meta property="og:image:width" content="2160">
<meta property="og:image:height" content="3840">
<meta property="og:locale" content="ko_KR">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="도아의 돌잔치에 초대합니다">
<meta name="twitter:description" content="2026년 5월 10일 오후 12시, 호텔인터시티 16층 더스크래치뷔페">
<meta name="twitter:image" content="./images/hero.jpg">
<link rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='14' fill='%233E342D'/%3E%3C/svg%3E">
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<style>
@font-face {
font-family: "S-CoreDream";
src: url("https://cdn.salondeletter.com/cdn/font/S-CoreDream-3Light.woff") format("woff");
font-weight: 300;
font-style: normal;
font-display: swap;
}
:root {
--paper: #ffffff;
--ink: #3e342d;
--muted: #8a8a8a;
--line: #e7e7e7;
--rose: #3e342d;
--sage: #777777;
--gold: #3e342d;
--cream: #ffffff;
--white: #ffffff;
--shadow: 0 18px 48px rgba(0, 0, 0, .08);
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
color: var(--ink);
font-family: "S-CoreDream", "Pretendard", "Apple SD Gothic Neo", "Malgun Gothic", system-ui, sans-serif;
background: #ffffff;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
cursor: pointer;
}
a {
color: inherit;
text-decoration: none;
}
.shell {
width: min(100%, 430px);
min-height: 100vh;
margin: 0 auto;
background: var(--paper);
box-shadow: 0 0 42px rgba(0, 0, 0, .08);
overflow: hidden;
}
.hero {
position: fixed;
inset: 0 auto auto 50%;
z-index: 30;
width: 430px;
max-width: 100%;
height: 100vh;
height: 100svh;
display: flex;
align-items: stretch;
justify-content: center;
padding: 0;
background: #d8cbc4;
overflow: hidden;
margin-left: -215px;
transition:
opacity 420ms cubic-bezier(.2, .8, .2, 1),
transform 420ms cubic-bezier(.2, .8, .2, 1),
visibility 420ms step-end;
will-change: opacity;
}
.hero.is-dismissed {
opacity: 0;
pointer-events: none;
visibility: hidden;
transform: translateY(-20px);
}
.hero-spacer {
height: 100vh;
height: 100svh;
background: #d8cbc4;
transition: height 420ms cubic-bezier(.2, .8, .2, 1);
}
.hero-spacer.is-collapsed {
height: 0;
}
.main-invitation {
width: 100%;
height: 100%;
display: block;
object-fit: contain;
background: #d8cbc4;
image-rendering: auto;
}
[data-aos="hero-cover"] {
opacity: 0;
transition-property: opacity;
}
[data-aos="hero-cover"].aos-animate {
opacity: 1;
}
[data-aos="hero-cover"].is-dismissed,
[data-aos="hero-cover"].is-dismissed.aos-animate {
opacity: 0;
transform: translateY(-20px);
}
.scroll-cue {
position: absolute;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
color: var(--muted);
font-size: 12px;
}
.scroll-cue span {
display: block;
width: 1px;
height: 34px;
margin: 9px auto 0;
background: linear-gradient(var(--gold), transparent);
animation: drop 1.7s ease-in-out infinite;
}
@keyframes drop {
0%,
100% {
transform: scaleY(.55);
transform-origin: top;
opacity: .45;
}
45% {
transform: scaleY(1);
opacity: 1;
}
}
section {
padding: 58px 26px;
background: var(--paper);
}
section:nth-of-type(even) {
background: var(--white);
}
[data-aos="soft-fade-up"] {
opacity: 0;
transform: translate3d(0, 72px, 0);
transition-property: opacity, transform;
}
[data-aos="soft-fade-up"].aos-animate {
opacity: 1;
transform: translate3d(0, 0, 0);
}
[data-aos="soft-image"] {
opacity: 0;
transform: translate3d(0, 82px, 0) scale(1.04);
transition-property: opacity, transform;
}
[data-aos="soft-image"].aos-animate {
opacity: 1;
transform: translate3d(0, 0, 0) scale(1);
}
[data-aos="soft-fade"] {
opacity: 0;
transform: translate3d(0, 40px, 0);
transition-property: opacity, transform;
}
[data-aos="soft-fade"].aos-animate {
opacity: 1;
transform: translate3d(0, 0, 0) scale(1);
}
.section-title {
margin: 0 0 24px;
text-align: center;
font-size: 24px;
font-weight: 500;
line-height: 1.35;
}
.intro {
text-align: center;
color: #555555;
font-size: 16px;
line-height: 2.05;
word-break: keep-all;
}
.portrait {
width: 100%;
aspect-ratio: 4 / 5;
margin-top: 32px;
object-fit: cover;
border-radius: 8px;
box-shadow: var(--shadow);
}
.family-line {
margin-top: 28px;
text-align: center;
line-height: 1.9;
color: var(--muted);
}
.family-line strong {
color: var(--ink);
font-size: 18px;
font-weight: 500;
}
.info-box {
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
padding: 24px 0;
text-align: center;
line-height: 1.8;
}
.calendar {
margin-top: 28px;
padding: 24px 18px;
background: #ffffff;
border: 1px solid var(--line);
border-radius: 8px;
}
.calendar-head {
margin-bottom: 18px;
text-align: center;
color: var(--gold);
font-size: 22px;
font-family: Georgia, "Times New Roman", serif;
}
.fc-calendar {
--fc-border-color: transparent;
--fc-page-bg-color: transparent;
--fc-neutral-bg-color: transparent;
--fc-today-bg-color: transparent;
--fc-event-bg-color: transparent;
--fc-event-border-color: transparent;
--fc-event-text-color: var(--rose);
font-size: 13px;
}
.fc-calendar .fc-scrollgrid,
.fc-calendar .fc-scrollgrid table,
.fc-calendar .fc-theme-standard td,
.fc-calendar .fc-theme-standard th {
border: 0;
}
.fc-calendar .fc-col-header-cell {
padding-bottom: 8px;
}
.fc-calendar .fc-col-header-cell-cushion {
color: var(--muted);
font-weight: 300;
}
.fc-calendar .fc-daygrid-day-frame {
min-height: 40px;
display: grid;
place-items: start center;
padding-top: 2px;
}
.fc-calendar .fc-daygrid-day-top {
justify-content: center;
line-height: 1;
}
.fc-calendar .fc-daygrid-day-number {
width: 34px;
height: 34px;
display: grid;
place-items: center;
border-radius: 50%;
color: var(--ink);
padding: 0;
font-size: 13px;
}
.fc-calendar .fc-day-sun .fc-daygrid-day-number {
color: var(--rose);
}
.fc-calendar .fc-day-sat .fc-daygrid-day-number {
color: var(--sage);
}
.fc-calendar .fc-day-other {
visibility: hidden;
}
.fc-calendar .is-party-day .fc-daygrid-day-number {
color: var(--white);
background: var(--ink);
box-shadow: none;
}
.fc-calendar .fc-daygrid-day-events {
min-height: 15px;
margin: 0;
}
.fc-calendar .fc-daygrid-event {
margin-top: 1px;
justify-content: center;
white-space: nowrap;
font-size: 11px;
line-height: 1.1;
}
.fc-calendar .fc-event-title,
.fc-calendar .fc-event-time {
color: var(--rose);
font-weight: 300;
}
.fc-calendar .fc-daygrid-event-dot {
display: none;
}
.timeline {
position: relative;
display: grid;
gap: 44px;
width: 100%;
padding: 28px 0 4px;
}
.timeline::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 1px;
background: var(--line);
transform: translateX(-50%);
}
.milestone {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) 22px minmax(0, 1fr);
gap: 0;
min-height: 136px;
align-items: center;
}
.milestone::before {
content: "";
grid-column: 2;
grid-row: 1;
justify-self: center;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--ink);
box-shadow: 0 0 0 4px var(--paper);
z-index: 1;
}
.milestone img,
.gallery-grid img,
.slider .swiper-slide img {
width: 100%;
object-fit: cover;
display: block;
}
.gallery-grid-slider {
overflow: hidden;
touch-action: pan-y;
}
.gallery-grid-slider .swiper-slide {
background: transparent;
}
.milestone img {
aspect-ratio: 1.2;
border-radius: 0;
box-shadow: 0 8px 18px rgba(0, 0, 0, .08);
cursor: zoom-in;
}
.milestone-visual,
.milestone-copy {
min-width: 0;
grid-row: 1;
}
.milestone-visual {
width: min(100%, 140px);
}
.milestone-copy {
color: #444444;
line-height: 1.45;
width: min(100%, 150px);
}
.milestone:nth-child(odd) .milestone-visual {
grid-column: 1;
justify-self: end;
padding-right: 18px;
}
.milestone:nth-child(odd) .milestone-copy {
grid-column: 3;
padding-left: 18px;
justify-self: start;
text-align: left;
}
.milestone:nth-child(even) .milestone-copy {
grid-column: 1;
padding-right: 18px;
justify-self: end;
text-align: left;
}
.milestone:nth-child(even) .milestone-visual {
grid-column: 3;
justify-self: start;
padding-left: 18px;
}
.milestone time {
color: #777777;
font-size: 12px;
}
.milestone p {
margin: 5px 0 0;
color: #333333;
font-size: 16px;
font-weight: 600;
line-height: 1.45;
word-break: keep-all;
overflow-wrap: break-word;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.gallery-grid img {
aspect-ratio: 1;
border-radius: 6px;
cursor: zoom-in;
}
.slider {
margin-top: 28px;
overflow: hidden;
border-radius: 8px;
background: var(--cream);
touch-action: pan-y;
}
.swiper-wrapper {
align-items: stretch;
}
.swiper-slide {
margin: 0;
position: relative;
overflow: hidden;
background: var(--cream);
}
.slider .swiper-slide img {
aspect-ratio: 4 / 5;
cursor: zoom-in;
}
.slider-controls {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 14px;
}
.gallery-grid-pagination {
position: static;
display: flex;
justify-content: center;
gap: 8px;
margin-top: 16px;
line-height: 1;
}
.gallery-grid-pagination .swiper-pagination-bullet {
width: 7px;
height: 7px;
margin: 0;
background: #d6d6d6;
opacity: 1;
}
.gallery-grid-pagination .swiper-pagination-bullet-active {
background: var(--ink);
}
.icon-btn {
width: 38px;
height: 38px;
border: 1px solid var(--line);
border-radius: 50%;
background: var(--white);
color: var(--ink);
}
.icon-btn.swiper-button-disabled {
opacity: .35;
cursor: default;
}
.slider-status {
padding: 0 0 4px;
background: var(--white);
}
.slider-progress {
position: relative;
height: 8px;
background: #e9e3de;
overflow: hidden;
}
.slider-progress .swiper-pagination-progressbar-fill {
background: var(--ink);
}
.slider-fraction {
min-height: 42px;
display: grid;
place-items: center;
color: #888888;
font-size: 14px;
letter-spacing: 0;
}
.map {
aspect-ratio: 1.35;
margin: 24px 0 18px;
border-radius: 8px;
overflow: hidden;
background: #ffffff;
border: 1px solid var(--line);
}
.map-fallback {
height: 100%;
display: grid;
place-items: center;
padding: 24px;
color: #555555;
text-align: center;
line-height: 1.7;
}
.nav-buttons {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.nav-buttons a,
.primary-btn,
.ghost-btn {
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
border: 1px solid var(--line);
background: var(--white);
color: var(--ink);
font-size: 14px;
}
.closing {
padding-bottom: 74px;
text-align: center;
color: var(--muted);
}
footer {
padding: 24px;
text-align: center;
color: #999999;
font-size: 11px;
background: #ffffff;
}
.lightbox {
position: fixed;
inset: 0;
z-index: 100;
display: grid;
place-items: center;
padding: 56px 18px 28px;
background: rgba(20, 16, 14, .88);
opacity: 0;
pointer-events: none;
transition: opacity .22s ease;
}
.lightbox.is-open {
opacity: 1;
pointer-events: auto;
}
.lightbox img {
max-width: min(100%, 900px);
max-height: 88vh;
display: block;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 20px 80px rgba(0, 0, 0, .34);
transform: scale(.96);
transition: transform .22s ease;
cursor: zoom-out;
}
.lightbox.is-open img {
transform: scale(1);
}
.lightbox-close {
position: absolute;
top: 18px;
right: 18px;
width: 42px;
height: 42px;
display: grid;
place-items: center;
border: 1px solid rgba(255, 255, 255, .38);
border-radius: 50%;
color: var(--white);
background: rgba(255, 255, 255, .08);
font-size: 26px;
line-height: 1;
}
@media (max-width: 430px) {
.hero {
left: 0;
width: 100%;
margin-left: 0;
}
}
@media (max-width: 360px) {
.hero {
padding-inline: 0;
}
section {
padding-inline: 20px;
}
.timeline {
width: 100%;
}
.milestone {
grid-template-columns: minmax(0, 1fr) 20px minmax(0, 1fr);
}
.milestone-visual {
width: min(100%, 126px);
}
.milestone-copy {
width: min(100%, 126px);
}
}
</style>
</head>
<body>
<main class="shell">
<header class="hero" data-aos="hero-cover" data-aos-duration="680" data-aos-once="true">
<img class="main-invitation" src="./images/hero.jpg" alt="김도아 돌잔치 초대장">
<!-- <a class="scroll-cue" href="#intro">스크롤<span></span></a> -->
</header>
<div class="hero-spacer" aria-hidden="true"></div>
<!--
<section id="intro">
<h2 class="section-title">소중한 첫 생일에 초대합니다</h2>
<div class="intro">
<p>작고 여렸던 아이가 사랑 속에서 자라<br>첫 번째 생일을 맞이하게 되었습니다.</p>
<p>따뜻한 마음으로 함께해 주시면<br>그날의 기쁨이 더욱 오래 남을 것 같습니다.</p>
</div>
<img class="portrait" data-img="heroPhoto" alt="초대장 대표 사진">
<div class="family-line">
오늘의 주인공 <strong data-bind="shortName">도아</strong>의<br>
아빠 <strong data-bind="fatherCompact">김찬솔</strong> · 엄마 <strong data-bind="motherCompact">김수경</strong>
</div>
</section>
<section>
<h2 class="section-title">생일파티 안내</h2>
<div class="info-box">
<strong data-bind="eventDate2">2026년 5월 10일 일요일 오후 12시</strong><br>
<span data-bind="venue2">호텔인터시티 16층 [더스크래치뷔페]</span>
</div>
<div class="calendar" aria-label="5월 달력">
<div class="calendar-head">5월</div>
<div class="fc-calendar" id="partyCalendar"></div>
</div>
</section>
-->
<section>
<h2 class="section-title">도아의 우당탕탕 사계절</h2>
<div class="timeline" id="timeline"></div>
</section>
<section>
<h2 class="section-title">갤러리</h2>
<div class="gallery-grid-slider swiper" aria-label="갤러리 4장 슬라이더">
<div class="swiper-wrapper" id="galleryGridSlides"></div>
</div>
<div class="gallery-grid-pagination swiper-pagination" aria-label="갤러리 페이지"></div>
<!--
<div class="slider swiper" aria-label="갤러리 슬라이더">
<div class="swiper-wrapper" id="slides"></div>
</div>
<div class="slider-status" aria-label="갤러리 진행 상태">
<div class="slider-progress"></div>
<div class="slider-fraction"></div>
</div>
<div class="slider-controls">
<button class="icon-btn slider-prev" type="button" aria-label="이전 사진"></button>
<button class="icon-btn slider-next" type="button" aria-label="다음 사진"></button>
</div>
-->
</section>
<section>
<h2 class="section-title">오시는 길</h2>
<div class="info-box">
<strong data-bind="venue3">호텔인터시티 16층 [더스크래치뷔페]</strong><br>
<span data-bind="address">대전광역시 유성구 온천로 92</span>
</div>
<div class="map" id="naverMap">
<div class="map-fallback">
<strong data-bind="venue4">호텔인터시티 16층 [더스크래치뷔페]</strong><br>
<span data-bind="address">대전광역시 유성구 온천로 92</span>
</div>
</div>
<div class="nav-buttons">
<a data-link="tmap" href="#">티맵</a>
<a data-link="kakao" href="#">카카오내비</a>
<a data-link="naver" href="#">네이버지도</a>
</div>
</section>
<section class="closing">
<img class="portrait" data-img="closingPhoto" alt="마무리 사진">
<p>꼭 오셔서 축하해주세요 :)</p>
</section>
<footer>COPYRIGHT. CSKIM All rights reserved.</footer>
</main>
<div class="lightbox" id="galleryLightbox" aria-hidden="true">
<button class="lightbox-close" type="button" aria-label="확대 이미지 닫기">×</button>
<img src="" alt="확대된 갤러리 이미지">
</div>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.20/index.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=gv0csx0e60"></script>
<script>
if ("scrollRestoration" in history) {
history.scrollRestoration = "manual";
}
window.scrollTo(0, 0);
const DATA = {
dateMark: "05.10",
babyName: "김 도아",
shortName: "도아",
eventDate: "2026년 5월 10일 일요일 오후 12시",
venue: "호텔인터시티 16층 [더스크래치뷔페]",
address: "대전광역시 유성구 온천로 92",
destination: {
name: "호텔인터시티",
lat: 36.3542931,
lng: 127.3478601
},
father: "김찬솔",
mother: "김수경",
fatherCompact: "김찬솔",
motherCompact: "김수경",
images: {
heroPhoto: "https://images.unsplash.com/photo-1522771930-78848d9293e8?auto=format&fit=crop&w=900&q=80",
closingPhoto: "/images/end.gif"
},
timeline: [
["2025.05.19", "세상에 처음 인사한 날", "/images/time/250524.jpg"],
["2025.08.26", "웃음이 많아진 백일", "/images/time/250826.jpg"],
["2025.10.26", "처음 미용을 한 날", "/images/time/251026.jpg"],
["2025.10.31", "첫 할로윈 데이", "/images/time/251031.jpg"],
["2025.11.30", "인생 첫 불꽃놀이", "/images/time/251130.jpg"],
["2025.12.25", "처음 맞이한 크리스마스", "/images/time/251206.jpg"],
["2026.02.14", "사회인으로서 첫 출발", "/images/time/260214.jpg"]
],
gallery: [
"./images/gallery/250525.jpg",
"./images/gallery/250625.jpg",
"./images/gallery/250806.jpg",
"./images/gallery/250829_100.jpg",
"./images/gallery/251009.jpg",
"./images/gallery/251030.jpg",
"./images/gallery/251130_2.jpg",
"./images/gallery/251206_2.jpg",
"./images/gallery/251216.jpg",
"./images/gallery/251225.jpg",
"./images/gallery/260109.jpg",
"./images/gallery/260203.jpg",
"./images/gallery/260207_1.jpg",
"./images/gallery/260207_2.jpg",
"./images/gallery/260221.jpg",
"./images/gallery/260326.jpg",
"./images/gallery/260404.jpg",
"./images/gallery/260425_2.jpg",
"./images/gallery/260425.jpg",
"./images/gallery/260429.jpg"
],
links: {}
};
const destinationName = encodeURIComponent(DATA.destination.name);
const destinationAddress = encodeURIComponent(DATA.address);
const destinationLat = DATA.destination.lat;
const destinationLng = DATA.destination.lng;
DATA.links = {
tmap: {
app: `tmap://route?goalname=${destinationName}&goalx=${destinationLng}&goaly=${destinationLat}`,
web: `https://apis.openapi.sk.com/tmap/app/routes?name=${destinationName}&lon=${destinationLng}&lat=${destinationLat}`
},
kakao: {
app: `kakaomap://route?ep=${destinationLat},${destinationLng}&by=CAR`,
web: `https://map.kakao.com/link/to/${destinationName},${destinationLat},${destinationLng}`
},
naver: {
app: `nmap://route/public?dlat=${destinationLat}&dlng=${destinationLng}&dname=${destinationName}&appname=salondeletter.doa`,
web: `https://map.naver.com/p/search/${destinationAddress}`
}
};
const bind = (key, value) => {
document.querySelectorAll(`[data-bind="${key}"]`).forEach((el) => {
el.textContent = value;
});
};
[
"dateMark", "babyName", "shortName", "eventDate", "venue", "address",
"father", "mother", "fatherCompact", "motherCompact"
].forEach((key) => bind(key, DATA[key]));
bind("eventDate2", DATA.eventDate);
bind("venue2", DATA.venue);
bind("venue3", DATA.venue);
bind("venue4", DATA.venue);
document.querySelectorAll("[data-img]").forEach((img) => {
img.src = DATA.images[img.dataset.img];
});
const openRouteLink = (event) => {
const route = DATA.links[event.currentTarget.dataset.link];
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
if (!route) {
return;
}
if (isMobile) {
event.preventDefault();
window.location.href = route.app;
window.setTimeout(() => {
window.location.href = route.web;
}, 700);
}
};
document.querySelectorAll("[data-link]").forEach((link) => {
const route = DATA.links[link.dataset.link];
if (!route) {
return;
}
link.href = route.web;
link.target = "_blank";
link.rel = "noopener noreferrer";
link.addEventListener("click", openRouteLink);
});
const setupNaverMap = () => {
const mapElement = document.querySelector("#naverMap");
const fallbackMarkup = `
<div class="map-fallback">
<strong>${DATA.venue}</strong><br>
<span>${DATA.address}</span>
</div>
`;
try {
const position = new naver.maps.LatLng(36.3542931, 127.3478601);
mapElement.innerHTML = "";
const map = new naver.maps.Map(mapElement, {
center: position,
zoom: 16,
minZoom: 10,
scaleControl: false,
mapDataControl: false,
logoControlOptions: {
position: naver.maps.Position.BOTTOM_LEFT
}
});
const marker = new naver.maps.Marker({
position,
map,
title: "호텔인터시티"
});
const infoWindow = new naver.maps.InfoWindow({
content: '<div style="padding:10px 12px;font-size:13px;line-height:1.5;color:#333;">호텔인터시티<br>대전광역시 유성구 온천로 92</div>',
borderWidth: 1,
disableAnchor: false
});
naver.maps.Event.addListener(marker, "click", () => {
if (infoWindow.getMap()) {
infoWindow.close();
} else {
infoWindow.open(map, marker);
}
});
window.setTimeout(() => {
naver.maps.Event.trigger(map, "resize");
map.setCenter(position);
}, 250);
} catch (error) {
mapElement.innerHTML = fallbackMarkup;
}
};
const showMapFallback = () => {
const mapElement = document.querySelector("#naverMap");
mapElement.innerHTML = `
<div class="map-fallback">
<strong>${DATA.venue}</strong><br>
<span>${DATA.address}</span>
</div>
`;
};
if (window.naver && window.naver.maps) {
setupNaverMap();
} else {
showMapFallback();
}
window.setTimeout(() => {
const mapElement = document.querySelector("#naverMap");
if (mapElement && mapElement.children.length === 0) {
showMapFallback();
}
}, 1800);
const partyDate = "2026-05-10";
const partyCalendarEl = document.querySelector("#partyCalendar");
const formatCalendarDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
if (window.FullCalendar && partyCalendarEl) {
const partyCalendar = new FullCalendar.Calendar(partyCalendarEl, {
initialView: "dayGridMonth",
initialDate: partyDate,
locale: "ko",
firstDay: 0,
height: "auto",
fixedWeekCount: false,
showNonCurrentDates: false,
headerToolbar: false,
dayHeaderFormat: { weekday: "short" },
dayCellContent: (info) => info.dayNumberText.replace("일", ""),
dayCellClassNames: (info) => {
return formatCalendarDate(info.date) === partyDate ? ["is-party-day"] : [];
},
events: [{
title: "오후 12시",
start: partyDate,
allDay: true,
classNames: ["party-time"]
}]
});
partyCalendar.render();
}
document.querySelector("#timeline").innerHTML = DATA.timeline.map(([date, text, image]) => `
<article class="milestone">
<div class="milestone-visual">
<img src="${image}" alt="">
</div>
<div class="milestone-copy">
<time>${date}</time>
<p>${text}</p>
</div>
</article>
`).join("");
const galleryGridSlides = document.querySelector("#galleryGridSlides");
const galleryGroups = DATA.gallery.reduce((groups, image, index) => {
const groupIndex = Math.floor(index / 4);
if (!groups[groupIndex]) {
groups[groupIndex] = [];
}
groups[groupIndex].push(image);
return groups;
}, []);
galleryGridSlides.innerHTML = galleryGroups.map((group) => `
<div class="swiper-slide">
<div class="gallery-grid">
${group.map((image) => `<img src="${image}" alt="">`).join("")}
</div>
</div>
`).join("");
const galleryGridSwiper = new Swiper(".gallery-grid-slider", {
slidesPerView: 1,
spaceBetween: 12,
speed: 560,
grabCursor: true,
resistanceRatio: .72,
pagination: {
el: ".gallery-grid-pagination",
clickable: true
},
keyboard: {
enabled: true,
onlyInViewport: true
}
});
const setupLightbox = () => {
const lightbox = document.querySelector("#galleryLightbox");
const lightboxImage = lightbox.querySelector("img");
const closeButton = lightbox.querySelector(".lightbox-close");
let lastFocused = null;
const openLightbox = (src, alt) => {
lastFocused = document.activeElement;
lightboxImage.src = src;
lightboxImage.alt = alt || "확대된 갤러리 이미지";
lightbox.classList.add("is-open");
lightbox.setAttribute("aria-hidden", "false");
closeButton.focus();
document.body.style.overflow = "hidden";
};
const closeLightbox = () => {
lightbox.classList.remove("is-open");
lightbox.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
window.setTimeout(() => {
lightboxImage.src = "";
}, 220);
if (lastFocused && typeof lastFocused.focus === "function") {
lastFocused.focus();
}
};
document.querySelectorAll(".milestone img, .gallery-grid img, .slider .swiper-slide img").forEach((image) => {
image.addEventListener("click", () => openLightbox(image.currentSrc || image.src, image.alt));
});
closeButton.addEventListener("click", closeLightbox);
lightboxImage.addEventListener("click", closeLightbox);
lightbox.addEventListener("click", (event) => {
if (event.target === lightbox) {
closeLightbox();
}
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && lightbox.classList.contains("is-open")) {
closeLightbox();
}
});
};
const setupAosAnimations = () => {
const targets = [
...document.querySelectorAll(
"section .section-title, section .intro p, .portrait, .family-line, .info-box, .calendar, .milestone, .gallery-grid-slider, .gallery-grid img, .map, .nav-buttons, .closing p"
)
];
targets.forEach((target) => {
const parent = target.closest("section");
const siblings = parent ? targets.filter((item) => item.closest("section") === parent) : targets;
const siblingIndex = siblings.indexOf(target);
const isImage = target.matches("img, .portrait, .gallery-grid-slider, .gallery-grid img, .map");
target.setAttribute("data-aos", isImage ? "soft-image" : "soft-fade-up");
target.setAttribute("data-aos-duration", isImage ? "950" : "850");
target.setAttribute("data-aos-delay", String(Math.min(siblingIndex * 55, 220)));
target.setAttribute("data-aos-offset", "130");
target.setAttribute("data-aos-anchor-placement", "top-bottom");
});
const setupFallbackAnimation = () => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
entry.target.classList.add("aos-init");
entry.target.classList.toggle("aos-animate", entry.isIntersecting);
});
}, {
rootMargin: "0px 0px -18% 0px",
threshold: .12
});
targets.forEach((target) => observer.observe(target));
};
if (window.AOS) {
AOS.init({
once: false,
mirror: true,
duration: 900,
easing: "ease-out-cubic",
offset: 130,
throttleDelay: 16,
debounceDelay: 50,
anchorPlacement: "top-bottom"
});
[80, 500, 1200].forEach((delay) => {
window.setTimeout(() => AOS.refreshHard(), delay);
});
} else {
setupFallbackAnimation();
}
};
const setupHeroCover = () => {
const hero = document.querySelector(".hero");
const heroSpacer = document.querySelector(".hero-spacer");
let touchStartY = 0;
let isDismissed = false;
if (!hero) {
return;
}
window.scrollTo(0, 0);
hero.classList.add("aos-init", "aos-animate");
hero.classList.remove("is-dismissed");
heroSpacer?.classList.remove("is-collapsed");
const dismissHero = () => {
if (isDismissed) {
return;
}
isDismissed = true;
hero.classList.add("is-dismissed");
heroSpacer?.classList.add("is-collapsed");
if (window.AOS) {
window.setTimeout(() => AOS.refreshHard(), 460);
}
};
window.addEventListener("wheel", (event) => {
if (isDismissed || event.deltaY <= 0) {
return;
}
event.preventDefault();
dismissHero();
}, { passive: false });
window.addEventListener("touchstart", (event) => {
touchStartY = event.touches[0]?.clientY || 0;
}, { passive: true });
window.addEventListener("touchmove", (event) => {
const currentY = event.touches[0]?.clientY || touchStartY;
const isSwipeUp = touchStartY - currentY > 8;
if (isDismissed || !isSwipeUp) {
return;
}
event.preventDefault();
dismissHero();
}, { passive: false });
window.addEventListener("keydown", (event) => {
const scrollKeys = ["ArrowDown", "PageDown", " ", "Spacebar"];
if (isDismissed || !scrollKeys.includes(event.key)) {
return;
}
event.preventDefault();
dismissHero();
});
};
if (document.readyState === "complete") {
setupHeroCover();
setupLightbox();
setupAosAnimations();
} else {
window.addEventListener("load", () => {
setupHeroCover();
setupLightbox();
setupAosAnimations();
}, { once: true });
}
</script>
</body>
</html>