/* ==========================================================================
   모바일 청첩장 - 공통 스타일
   ========================================================================== */

:root {
  --max-width: 520px;
  --cream: #f7f5ee;
  --olive: #b7ba82;
  --olive-dark: #8f9463;
  --olive-deep: #767a4c;
  --ink: #55524c;
  --ink-soft: #7a766d;
  --red: #b23b3b;
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
  --outer-bg: #e7e2d4;
}

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--outer-bg);
  font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo",
    "Malgun Gothic", "Noto Sans KR", "Apple Color Emoji", "Segoe UI Emoji",
    "Noto Color Emoji", sans-serif;
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img {
  max-width: 100%;
  display: block;
}

button {
  font-family: inherit;
  cursor: pointer;
}

.invitation {
  max-width: var(--max-width);
  margin: 0 auto;
  background: #fff;
  box-shadow: var(--shadow);
  overflow: hidden;
  position: relative;
}

/* ---------- 공통: 레이어 기반 페이지 ---------- */

.page {
  position: relative;
  width: 100%;
  overflow: hidden;
  display: block;
}

.page .canvas {
  position: relative;
  width: 100%;
  aspect-ratio: var(--ratio);
}

.canvas {
  position: relative;
  width: 100%;
  aspect-ratio: var(--ratio);
}

.layer {
  position: absolute;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* pg.1 첫 화면 최초 로드 시 1회 intro — opacity/scale만 사용(translate 없음),
   layout에 영향 없는 transform이라 layout shift 없음. 반복 없음(fill-mode
   both로 시작 전/종료 후 상태 고정). 좌표/크기/구조는 그대로 유지. */
@keyframes pg1Intro {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
.pg1 {
  animation: pg1Intro 700ms ease-out both;
}
@media (prefers-reduced-motion: reduce) {
  .pg1 {
    animation: none;
  }
}

/* pg.4 하단: 원본 pg.4 전체.png 파일 자체 맨 아래에 흰색 라인(약 9px, 원본
   3469px 기준)이 포함되어 있어, 렌더링 시에만 그 영역을 crop해서 감춘다.
   원본 파일은 그대로 두고, width/내부 디자인 좌표도 변경하지 않는다.
   좌측: 원본 좌측 끝 6px(검정 테두리 1px + 흰 여백, pg.5 좌측과 동일 종류의
   원본 edge 아티팩트)도 web/assets/pg4/full.jpg 생성 시 픽셀 자체를
   crop했다(원본 pg.4 전체.png는 그대로 둠). 아래 2159는 좌측 6px crop
   이후의 원본 폭(2165-6)이다. */
.pg4 {
  aspect-ratio: 2159 / 3458.44;
}
.pg4 .full-page-img {
  margin-top: -0.0722%;
}

/* pg.4 요소13(하트 2개)/요소14(술잔) 좌우 rocking(회전) 모션 — rotate만 사용,
   x/y/크기/z-index는 유지(이동 없음). el13/el14는 원래 positioning에
   transform을 쓰지 않으므로 wrapper 없이 요소 자체에 바로 적용해도
   기존 transform을 덮어쓰지 않는다. pg.4가 viewport에 보이는 동안만
   재생(JS가 .pg4.pg4-in-view 토글), 벗어나면 정지.
   중간 회전 프레임이 보이지 않는 discrete(step) 모션: 각 각도를
   0%~49.9%(또는 50%~99.9%) 구간 동안 동일 값으로 유지시켜 사실상
   순간 전환처럼 보이게 한다(0.1% 구간에서만 보간, 2.2s 기준 약 1.4ms로
   지각 불가능한 수준). 상태 유지시간 ≈ 700ms(듀레이션의 절반). */
@keyframes pg4Rock {
  0%, 49.9% { transform: rotate(-3deg); }
  50%, 99.9% { transform: rotate(3deg); }
  100% { transform: rotate(-3deg); }
}
.pg4-rock {
  animation: pg4Rock 1400ms linear infinite;
  animation-play-state: paused;
}
.pg4-el14.pg4-rock {
  transform-origin: bottom center;
  animation-delay: 0s;
}
.pg4-el13.pg4-rock {
  transform-origin: center;
  animation-delay: 0.2s;
}
.pg4.pg4-in-view .pg4-rock {
  animation-play-state: running;
}

@media (prefers-reduced-motion: reduce) {
  .pg4-rock {
    animation: none;
  }
}

/* pg.5 상단: 원본 pg5. 전체.png 파일 자체 맨 위에 흰색 라인(3px, 원본
   3849px 기준)이 포함되어 있어 pg.4와의 경계에서 seam으로 보인다.
   렌더링 시에만 상단을 crop해서 감춘다. 원본 파일/내부 좌표는 그대로 둔다.
   좌측: 원본 좌측 끝 6px(검정 테두리 1px + 흰 여백)도 동일한 이유로
   web/assets/pg5/full.jpg 생성 시 픽셀 자체를 crop했다(원본 pg5. 전체.png는
   그대로 둠). 아래 2161은 좌측 6px crop 이후의 원본 폭(2167-6)이다. */
.pg5 {
  aspect-ratio: 2161 / 3843.6;
}
.pg5 .full-page-img {
  margin-top: -0.1388%;
}

/* pg.5 요소8(별 드로잉) sparkle — scale+rotate만 사용, x/y·opacity 변경 없음.
   요소8은 원래 positioning에 transform을 쓰지 않으므로 wrapper 없이
   요소 자체에 바로 적용해도 기존 transform을 덮어쓰지 않는다.
   부드러운 회전이 아니라 0→90→180→270→360deg가 각 150ms(총 600ms)
   동안 "그대로 유지되다가 끝나는 순간 다음 값으로 툭 튀는" discrete
   전환이 되도록 각 keyframe 구간에 animation-timing-function:
   steps(1, jump-end)를 지정한다(steps()는 구간 내내 이전 값을 유지하다
   구간이 끝나는 즉시 다음 값으로 순간 전환 — 중간 회전 프레임이 전혀
   보이지 않는다). scale도 같은 4구간에서 1→1.18→1.25→1.12→1로 동시에
   툭툭 전환된다. 이후 1.8s(25%~100%)는 rotate(360deg) scale(1)로
   완전히 정지 상태를 유지하다가 다음 주기(2400ms = 활성 600ms + 휴식
   1800ms)로 반복한다. 각 활성 구간(150ms)의 %는 duration이 바뀌어도
   절대 시간이 그대로 150ms가 되도록 150/2400으로 다시 계산한 값이라
   회전/step 속도 자체는 변경되지 않는다.
   pg.5 요소8이 viewport에 처음 들어오는 순간 반드시 0%(active motion)
   부터 시작해야 하므로, play-state paused/running 토글 대신 JS가
   진입할 때마다 .pg5-sparkle-play 클래스를 제거 후 강제 reflow하고
   다시 추가해 애니메이션을 매번 처음부터 재시작한다(휴식 구간을 이어
   받아 재생되는 문제 방지). 화면을 벗어나면 클래스를 제거해 정지 —
   다음에 다시 들어올 때도 항상 첫 프레임(0%)부터 시작한다. */
@keyframes pg5Sparkle {
  0% {
    transform: rotate(0deg) scale(1);
    animation-timing-function: steps(1, jump-end);
  }
  6.25% {
    transform: rotate(90deg) scale(1.18);
    animation-timing-function: steps(1, jump-end);
  }
  12.5% {
    transform: rotate(180deg) scale(1.25);
    animation-timing-function: steps(1, jump-end);
  }
  18.75% {
    transform: rotate(270deg) scale(1.12);
    animation-timing-function: steps(1, jump-end);
  }
  25% {
    transform: rotate(360deg) scale(1);
  }
  100% {
    transform: rotate(360deg) scale(1);
  }
}
.pg5-sparkle {
  transform-origin: center;
}
.pg5-sparkle.pg5-sparkle-play {
  animation: pg5Sparkle 2400ms linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .pg5-sparkle.pg5-sparkle-play {
    animation: none;
  }
}

/* ==========================================================================
   pg.6 갤러리 (접힘/펼침 + 라이트박스)
   ========================================================================== */

.pg6 {
  background: #f4f1e8;
}

.pg6-stage {
  position: relative;
  width: 100%;
}

.pg6-view {
  position: relative;
  width: 100%;
  aspect-ratio: var(--ratio);
}

.pg6-view[hidden] {
  display: none;
}

.pg6-bg-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* 실제 웨딩사진 위에만 얹는 투명 hotspot — 디자인을 다시 그리지 않는다 */
.gphoto {
  position: absolute;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
}

.gphoto:active {
  background: rgba(0, 0, 0, 0.06);
}

/* 접힘/펼침 컨트롤 — 완성본 이미지에 이미 그려진 화살표 위에 얹는 투명 hotspot */
.pg6-toggle-hotspot {
  position: absolute;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  cursor: pointer;
}

/* 화살표 이미지의 위치/크기는 그대로 두고, 터치 판정 영역만 최소 44x44 CSS px로 확장 */
.pg6-toggle-hotspot::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
  transform: translate(-50%, -50%);
}

/* ---------- 라이트박스 ---------- */

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(20, 18, 15, 0.92);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 999;
  padding: 24px 16px;
  -webkit-tap-highlight-color: transparent;
}

.lightbox.open {
  display: flex;
}

.lightbox-img-wrap {
  max-width: 100%;
  max-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.lightbox img {
  max-width: 100%;
  max-height: 82vh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 4px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
  animation: lightboxIn 0.25s ease;
  transition: opacity 0.15s ease;
}

.lightbox.loading img {
  opacity: 0.35;
}

@keyframes lightboxIn {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
  background: rgba(255, 255, 255, 0.12);
  border: none;
  color: #fff;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  font-size: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.lightbox-close {
  top: -8px;
  right: -8px;
  top: 12px;
  right: 12px;
}

.lightbox-prev {
  left: 8px;
  top: 50%;
  transform: translateY(-50%);
}

.lightbox-next {
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
}

.lightbox-counter {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  color: #fff;
  font-size: 13px;
  opacity: 0.8;
  letter-spacing: 0.05em;
}

/* ==========================================================================
   pg.8 방명록 — 디자이너 에셋 캔버스 + 동적 메시지 오버레이
   ========================================================================== */

.pg8 {
  background: var(--cream);
}

.pg8-canvas {
  position: relative;
  width: 100%;
  aspect-ratio: var(--ratio);
  container-type: inline-size;
}

/* pg.8 요소3(상단 하트) pulse — scale만 사용, x/y는 고정.
   요소3는 원래 positioning에 transform을 쓰지 않으므로 wrapper 없이
   요소 자체에 바로 적용해도 기존 transform을 덮어쓰지 않는다.
   "2회 pulse(1.35s) → 정지(3.5s)" 주기를 4.85s 하나의 keyframes에 담아
   무한 반복한다. pulse 구간의 각 keyframe %는 기존 1.35s 버전의 %에
   (1.35/4.85)를 곱해 그대로 축소 배치했기 때문에, 각 구간의 실제 소요
   시간·scale값·easing은 기존과 완전히 동일하고 27.835%~100% 구간은
   scale(1)로 고정되어 쉬는 시간에는 완전히 정지한다.
   pg.8이 viewport에 보이는 동안만 재생(JS가 .pg8.pg8-in-view 토글),
   완전히 벗어나면 정지, 다시 들어오면 재개. */
@keyframes pg8HeartPulse {
  0% { transform: scale(1); }
  6.959% { transform: scale(1.08); }
  13.918% { transform: scale(1); }
  20.876% { transform: scale(1.08); }
  27.835% { transform: scale(1); }
  100% { transform: scale(1); }
}
.pg8-heart-pulse {
  transform-origin: center;
  animation: pg8HeartPulse 4.85s ease-in-out infinite;
  animation-play-state: paused;
}
.pg8.pg8-in-view .pg8-heart-pulse {
  animation-play-state: running;
}

@media (prefers-reduced-motion: reduce) {
  .pg8-heart-pulse {
    animation: none;
  }
}

/* pg.9 요소1.2(하트) ending animation — rotate+scale만 사용, x/y는 고정.
   요소1.2는 원래 positioning에 transform을 쓰지 않으므로 wrapper 없이
   요소 자체에 바로 적용해도 기존 transform을 덮어쓰지 않는다.
   시퀀스(총 1150ms, 1회만): 0~600ms clockwise 360도 회전(ease-out) →
   600~700ms 정지(rotate 360deg 유지) → 700~1150ms pulse(scale 1→1.10→1,
   ease-in-out). rotate는 항상 360deg 값을 유지해 0deg로 되돌아가며 튀는
   현상 없이 그대로 정지한다. viewport에 충분히 들어오면(threshold 0.6)
   JS가 클래스를 1회만 부여하고 즉시 unobserve — 스크롤을 벗어났다 와도
   다시 실행되지 않는다. */
@keyframes pg9HeartEnding {
  0% {
    transform: rotate(0deg) scale(1);
    animation-timing-function: ease-out;
  }
  52.17% {
    transform: rotate(360deg) scale(1);
    animation-timing-function: linear;
  }
  60.87% {
    transform: rotate(360deg) scale(1);
    animation-timing-function: ease-in-out;
  }
  80.43% {
    transform: rotate(360deg) scale(1.1);
    animation-timing-function: ease-in-out;
  }
  100% {
    transform: rotate(360deg) scale(1);
  }
}
.pg9-heart-ending.pg9-heart-play {
  transform-origin: center;
  animation: pg9HeartEnding 1150ms 1 forwards;
}

@media (prefers-reduced-motion: reduce) {
  .pg9-heart-ending.pg9-heart-play {
    animation: none;
  }
}

.pg8-canvas .layer {
  position: absolute;
  pointer-events: none;
  user-select: none;
}

/* 카드 슬롯: 위치/크기는 고정(design canvas 기준), 배경은 디자이너 요소7.png 그대로 사용 */
.gb-card-slot {
  position: absolute;
  z-index: 5;
  display: none; /* 메시지가 있을 때만 JS가 표시 */
}

.gb-card-slot.filled {
  display: block;
}

.gb-card-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}

.gb-card-content {
  position: absolute;
  inset: 0;
  /* .pg8-canvas가 container-type:inline-size 이므로 cqw는 canvas(=페이지) 폭
     기준. 390px 기준 좌우 약 13px(3.33cqw), 상하 약 11px(2.82cqw).
     하단만 gb-meta(날짜/From, 절대배치)를 위한 공간을 추가로 확보. */
  padding: 2.82cqw 3.33cqw 7.5cqw;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden; /* 카드 테두리 밖으로 텍스트가 절대 넘치지 않도록 하는 안전장치 */
}

.gb-card-slot.filled {
  cursor: pointer;
}

.gb-card-content .gb-message {
  /* 모바일 QA: 1.8cqw는 360~390px 폭에서 실제 6~7px로 렌더링되어 가독성이
     떨어졌다. 카드 크기/padding은 그대로 두고, 2줄이 잘리지 않는 한도
     내에서 읽기 가능한 최소 크기로 상향(약 10~11px, line-height 유지). */
  font-size: 3.3cqw;
  line-height: 1.45;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0;
  flex: 0 0 auto;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gb-card-content .gb-meta {
  /* 본문 길이와 무관하게 항상 카드 우측 하단 고정. flex 흐름에서 분리해
     position:absolute로 배치 — inset은 .gb-card-content의 padding과 동일하게
     맞춰 기존 시각적 위치를 그대로 유지. */
  position: absolute;
  right: 3.33cqw;
  bottom: 2.82cqw;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.8%;
  /* '축하 메시지를 남겨주세요'(heading.png 부제) 실측 기준 2.68cqw의 55% */
  font-size: 1.47cqw;
  line-height: 1.2;
  color: var(--ink-soft);
  text-align: right;
}

.gb-empty {
  position: absolute;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  box-sizing: border-box;
  z-index: 5;
}

.gb-empty-title {
  font-size: 2.3cqw;
  color: #5c5e3c;
  font-weight: 600;
  margin: 0 0 2.6%;
}

.gb-empty-desc {
  font-size: 2cqw;
  color: var(--ink-soft);
  line-height: 1.6;
  margin: 0;
}

.gb-more-link {
  position: absolute;
  z-index: 5;
  background: none;
  border: none;
  color: #6c6f45;
  font-size: 1.9cqw;
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 0;
  text-align: center;
  display: none;
}

/* 시각적 크기는 그대로 두고 터치 판정 영역만 최소 44x44 CSS px로 확장 */
.gb-more-link::after,
.gb-write-btn::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
  transform: translate(-50%, -50%);
}

.gb-write-btn {
  position: absolute;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.gb-write-btn img {
  width: 100%;
  height: 100%;
  display: block;
}

/* ---------- 방명록 작성 모달 ---------- */

.gb-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 18, 15, 0.5);
  display: none;
  align-items: flex-end;
  justify-content: center;
  z-index: 1000;
}

.gb-modal-backdrop.open {
  display: flex;
}

.gb-modal {
  background: var(--cream);
  width: 100%;
  max-width: var(--max-width);
  border-radius: 22px 22px 0 0;
  padding: 22px 22px calc(22px + env(safe-area-inset-bottom, 0px));
  animation: modalUp 0.3s cubic-bezier(0.2, 0.9, 0.3, 1);
  max-height: 88vh;
  overflow-y: auto;
}

@keyframes modalUp {
  from {
    transform: translateY(30px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.gb-modal h3 {
  margin: 0 0 18px;
  font-size: 17px;
  text-align: center;
  color: var(--ink);
  font-weight: 700;
}

.gb-field {
  margin-bottom: 14px;
}

.gb-field label {
  display: block;
  font-size: 12.5px;
  color: var(--ink-soft);
  margin-bottom: 6px;
  letter-spacing: 0.02em;
}

.gb-field input[type="text"],
.gb-field textarea {
  width: 100%;
  border: 1.5px solid #d8d6c4;
  border-radius: 10px;
  padding: 11px 13px;
  /* iOS Safari는 입력창 font-size가 16px 미만이면 focus 시 자동 확대된다 */
  font-size: 16px;
  font-family: inherit;
  color: var(--ink);
  background: #fff;
  resize: none;
}

.gb-field input[type="text"]:focus,
.gb-field textarea:focus {
  outline: none;
  border-color: var(--olive-deep);
}

.gb-field textarea {
  height: 92px;
}

.gb-charcount {
  text-align: right;
  font-size: 11.5px;
  color: #b3b09e;
  margin-top: 4px;
}

.gb-modal-actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}

.gb-modal-actions button {
  flex: 1;
  border: none;
  border-radius: 10px;
  padding: 13px 0;
  font-size: 14.5px;
  font-weight: 600;
}

.gb-cancel-btn {
  background: #ece9dd;
  color: var(--ink-soft);
}

.gb-submit-btn {
  background: var(--olive-deep);
  color: #fff;
}

.gb-submit-btn:disabled {
  opacity: 0.5;
}

.gb-message-modal-body {
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 1.55;
  font-size: 15px;
  color: var(--ink);
  margin: 0 0 14px;
}

.gb-message-modal-meta {
  text-align: right;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink-soft);
}

.gb-toast {
  position: fixed;
  left: 50%;
  bottom: 32px;
  transform: translateX(-50%) translateY(10px);
  background: rgba(30, 28, 22, 0.92);
  color: #fff;
  padding: 12px 18px;
  border-radius: 999px;
  font-size: clamp(14px, 3.8vw, 16px);
  white-space: nowrap;
  width: max-content;
  max-width: calc(100vw - 24px);
  box-sizing: border-box;
  z-index: 1100;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.gb-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ---------- 전체 방명록 bottom sheet ---------- */

.gb-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 18, 15, 0.5);
  display: none;
  align-items: flex-end;
  justify-content: center;
  z-index: 1000;
}

.gb-sheet-backdrop.open {
  display: flex;
}

.gb-sheet {
  background: var(--cream);
  width: 100%;
  max-width: var(--max-width);
  border-radius: 22px 22px 0 0;
  padding: 10px 22px calc(20px + env(safe-area-inset-bottom, 0px));
  animation: modalUp 0.3s cubic-bezier(0.2, 0.9, 0.3, 1);
  max-height: 80vh;
  display: flex;
  flex-direction: column;
}

.gb-sheet-handle {
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: #d8d6c4;
  margin: 6px auto 14px;
  flex: 0 0 auto;
}

.gb-sheet h3 {
  margin: 0 0 14px;
  font-size: 16px;
  text-align: center;
  color: var(--ink);
  font-weight: 700;
  flex: 0 0 auto;
}

.gb-sheet-list {
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-bottom: 8px;
}

.gb-sheet-list .gb-card {
  border: 1.4px solid #cfcdb9;
  border-radius: 16px;
  padding: 16px 18px 14px;
  position: static;
  display: block;
}

.gb-sheet-list .gb-message {
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0 0 10px;
  -webkit-line-clamp: unset;
  display: block;
}

.gb-sheet-list .gb-meta {
  flex-direction: row;
  justify-content: flex-end;
  gap: 6px;
  font-size: 11.5px;
}

.gb-sheet-close {
  flex: 0 0 auto;
  position: relative;
  margin-top: 10px;
  border: none;
  background: #ece9dd;
  color: var(--ink-soft);
  border-radius: 10px;
  padding: 12px 0;
  font-size: 14px;
  font-weight: 600;
}

/* 실측 41.5px로 44px에 근소하게 못 미쳐 터치 판정 영역만 보정 */
.gb-sheet-close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 44px;
  transform: translateY(-50%);
}

@media (min-width: 640px) {
  body {
    background: linear-gradient(180deg, #efece1, #e2ded0);
    padding: 24px 0;
  }
}

/* 버튼류 press feedback(기능성 인터랙션, 장식 애니메이션 아님) — pg.6
   접기/펼치기 화살표(.pg6-toggle-hotspot)와 갤러리 사진 hotspot(.gphoto)은
   의도적으로 제외 */
.gb-write-btn:active,
.gb-modal-actions button:active,
.gb-more-link:active,
.gb-sheet-close:active,
.lightbox-close:active,
.lightbox-prev:active,
.lightbox-next:active {
  transform: scale(0.97);
}

@media (prefers-reduced-motion: reduce) {
  .gb-write-btn:active,
  .gb-modal-actions button:active,
  .gb-more-link:active,
  .gb-sheet-close:active,
  .lightbox-close:active,
  .lightbox-prev:active,
  .lightbox-next:active {
    transform: none;
  }
}

/* ==========================================================================
   pg.7 사진 4장: 스크롤 진행에 맞춰 한 장씩 개별 reveal
   opacity/transform만 사용 — left/top/width/height 등 좌표는 그대로 유지.
   기본 상태(=이 클래스만 있고 -hidden이 없는 상태)는 완전히 보이는 상태라
   JS가 실행되지 않아도(또는 IntersectionObserver 미지원) 사진은 항상 보인다.
   ========================================================================== */
.pg7-photo-reveal {
  transition: opacity 500ms ease-out, transform 500ms ease-out;
}

.pg7-photo-reveal.pg7-photo-hidden {
  opacity: 0;
  transform: translateY(10px);
}

@media (prefers-reduced-motion: reduce) {
  .pg7-photo-reveal {
    transition: none;
  }
  .pg7-photo-reveal.pg7-photo-hidden {
    opacity: 1;
    transform: none;
  }
}

/* ==========================================================================
   pg.2 인사말/비둘기/부모님 성함: 스크롤로 뷰포트에 들어올 때 개별 reveal
   opacity/transform만 사용 — left/top/width/height 등 좌표는 그대로 유지.
   기본 상태(=이 클래스만 있고 -hidden이 없는 상태)는 완전히 보이는 상태라
   JS가 실행되지 않아도(또는 IntersectionObserver 미지원) 정상적으로 보인다.
   ========================================================================== */
.pg2-fade-reveal,
.pg2-scale-reveal {
  transition: opacity 450ms ease-out, transform 450ms ease-out;
}

.pg2-fade-reveal.pg2-reveal-hidden {
  opacity: 0;
  transform: translateY(7px);
}

.pg2-scale-reveal.pg2-reveal-hidden {
  opacity: 0;
  transform: scale(0.92);
}

@media (prefers-reduced-motion: reduce) {
  .pg2-fade-reveal,
  .pg2-scale-reveal {
    transition: none;
  }
  .pg2-fade-reveal.pg2-reveal-hidden,
  .pg2-scale-reveal.pg2-reveal-hidden {
    opacity: 1;
    transform: none;
  }
}

/* pg.2 기본 골격(종이 배경+레이스 합성 base.webp) 기준 순서:
   ①순백색 대신 bg.jpg 가장자리 실측 평균색(#ede7e0)을 canvas 배경색으로
   깔아 흰 flash를 막고, ②base.webp 이외 정적 overlay(하트 모노그램/이름
   스크립트 등)는 흰 canvas 위에 먼저 뜨지 않도록 숨겨둔다. canvas 전체를
   가리지는 않으므로 base.webp 자체는 게이트 대상이 아니라 준비되는
   즉시 그대로 보인다(fade/reveal 없음). 준비가 끝나면 JS가
   .pg2.pg2-base-ready를 부여해 정적 overlay를 즉시 표시한다. */
.pg2 .canvas {
  background-color: #ede7e0;
}
.pg2-static-gate {
  opacity: 0;
}
.pg2.pg2-base-ready .pg2-static-gate {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .pg2-static-gate {
    opacity: 1;
  }
}
