1311 lines
34 KiB
HTML
1311 lines
34 KiB
HTML
<!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>
|