:root {
  --animate-duration: 1.2s;
  /* Keeps the invitation phone-width everywhere, desktop included — on
     wide screens it reads as a phone preview centered on a dark backdrop
     instead of stretching full width. */
  --phone-width: 480px;
  /* Shared champagne-gold accent for buttons/links — see template.css's
     .button-57 for where this is used. */
  --gold: #cda86a;
  --gold-light: #f3e4c2;
}

* {
  box-sizing: border-box;
}

/* Frames are stacked with position:fixed and moved with a transform
   (see script.js), not scrolled — the document itself never scrolls,
   so no scroll-related CSS is needed on html/body at all. */

body {
  margin: 0;
  height: 100vh;
  overflow: hidden;
  font-family: "Montserrat", sans-serif;
  background-color: #111;
}

/* ============ MUSIC TOGGLE ============ */
/* Floats above every frame instead of belonging to one, so it never
   shifts a section's own layout. Positioned relative to the phone-width
   column (not the raw viewport) so it stays anchored to the invitation
   card on wide desktop screens instead of drifting to the real screen
   edge. Hidden until the gate opens — see script.js — since there's no
   music to control before then. */
#music-toggle {
  position: fixed;
  top: 18px;
  right: max(16px, calc(50% - (var(--phone-width) / 2) + 16px));
  z-index: 1050;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border: 1px solid rgba(205, 168, 106, 0.7);
  color: var(--gold-light);
  cursor: pointer;
  padding: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease, background-color 0.2s ease;
}

#music-toggle.visible {
  opacity: 1;
  pointer-events: auto;
}

#music-toggle:hover {
  background: rgba(0, 0, 0, 0.62);
}

/* ============ BACK TO TOP ============ */
/* Frosted-glass pill, bottom-right, mirroring #music-toggle's top-right
   placement (including the same "hidden until the gate opens, then always
   present" visibility rule) so both float above the frame stack without
   ever overlapping each other or the gate/CTA content in between. Present
   on every section (not just ones with overflowing content) — jumps back
   to the very first frame, see script.js's goToIndex(0) call. */
#btn-back-to-top {
  position: fixed;
  right: max(16px, calc(50% - (var(--phone-width) / 2) + 16px));
  bottom: 18px;
  z-index: 1050;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(205, 168, 106, 0.7);
  color: var(--gold-light);
  cursor: pointer;
  padding: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease, background-color 0.2s ease;
}

#btn-back-to-top.visible {
  opacity: 1;
  pointer-events: auto;
}

#btn-back-to-top:hover {
  background: rgba(0, 0, 0, 0.62);
}

#music-toggle .icon-pause {
  display: none;
}

#music-toggle.playing .icon-play {
  display: none;
}

#music-toggle.playing .icon-pause {
  display: block;
}

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

/* ============ HERO / GATE ============ */
#hero {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 1000;
  width: 100%;
  max-width: var(--phone-width);
  height: 100vh;
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 8vh 24px;
  background-size: cover;
  background-position: center;
  transform: translateX(-50%);
  transition: opacity 0.8s ease;
  box-shadow: 0 0 60px rgba(0, 0, 0, 0.5);
}

#hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.45));
  z-index: 0;
}

#hero > * {
  position: relative;
  z-index: 1;
}

#hero.hero-hidden {
  opacity: 0;
  pointer-events: none;
}

/* Glass card wrapping all hero text: guarantees legibility no matter
   what part of the background photo ends up behind it. */
.hero-card {
  background: rgba(0, 0, 0, 0.42);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 16px;
  padding: 36px 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  max-width: 420px;
}

#hero .judul p {
  letter-spacing: 0.2em;
  margin: 0 0 6px;
  font-size: 0.9em;
}

#hero .judul h2 {
  margin: 0 0 10px;
  font-size: 2.4em;
}

#hero .invited {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

#hero .invited p {
  margin: 0;
}

#hero .invited button {
  margin-top: 18px;
}

/* ============ MAIN CONTENT ============ */
/* Each .section is a full-viewport card, stacked via z-index (assigned
   in script.js) and parked off-screen below (translateY(100%)) until its
   turn. Going to the next frame slides the incoming card up to cover the
   current one, which stays put underneath, untouched — a "cover"
   transition rather than the whole page scrolling. Going back reverses
   it: the current (topmost) card slides itself away, revealing the
   previous one, which was never moved. `.no-anim` briefly disables the
   transition for instant, non-animated jumps (Home/End) so skipped
   frames don't visibly animate through. */
.section {
  position: fixed;
  top: 0;
  left: 50%;
  width: 100%;
  max-width: var(--phone-width);
  height: 100vh;
  color: #fff;
  background-size: cover;
  background-position: center;
  display: flex;
  flex-direction: column;
  /* Not justify-content:center — when a section's content (a long story,
     lots of gallery photos, etc.) is taller than the viewport, centering
     the flex child on the main axis clips whatever overflows ABOVE the
     container's top edge, and that portion is permanently unreachable by
     scrolling (scrollTop can't go negative) — text just silently "vanishes"
     off the top instead of being scrollable. flex-start here + margin:auto
     on the child (below) gives the same centered look while content fits,
     but degrades to a normal top-aligned, fully-scrollable block the
     moment it doesn't. */
  justify-content: flex-start;
  align-items: center;
  text-align: center;
  padding: 70px 24px;
  overflow-x: hidden;
  overflow-y: auto;
  /* Safari/WebKit requires this for a finger-drag to actually scroll a
     position:fixed element with overflow:auto — without it, touch drags
     on iOS Safari are simply ignored here (desktop mouse/trackpad wheel
     scrolling isn't affected, which is why this only shows up on Safari
     touch devices). */
  -webkit-overflow-scrolling: touch;
  transform: translateX(-50%) translateY(100%);
  transition: transform 1.1s cubic-bezier(0.22, 0.61, 0.36, 1);
  box-shadow: 0 0 60px rgba(0, 0, 0, 0.5);
}

.section.is-shown {
  transform: translateX(-50%) translateY(0);
}

.section.no-anim {
  transition: none;
}

/* Per-element text reveal: slide up + fade + a subtle scale-in, staggered
   item by item. Replayed by script.js (`playReveal`) every time a section
   becomes the active one, in either direction — not a one-off on first
   view — so arriving at a frame always feels alive, not just the frame
   itself sliding with static text glued to it. */
.reveal {
  opacity: 0;
  transform: translateY(22px) scale(0.97);
  /* No transition here on purpose: resetting to hidden (when
     reveal-active is removed to replay the animation) must be instant,
     or the "hide" itself would fade — leaving nothing left to visibly
     animate when reveal-active is re-added a moment later. */
}

.section.reveal-active .reveal {
  opacity: 1;
  transform: translateY(0) scale(1);
  transition:
    opacity 0.65s cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 0.65s cubic-bezier(0.22, 0.61, 0.36, 1);
  /* No base offset here: script.js only adds reveal-active once the
     frame's own slide-in has fully finished (transitionend), so this
     delay is purely the item-by-item stagger, not a "wait for the frame"
     guess. */
  transition-delay: calc(var(--d, 0) * 90ms);
}

.section.no-anim .reveal {
  transition: none;
}

.section::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.6));
  z-index: 0;
}

.section > .section-inner {
  /* Auto margins on the flex child replicate justify-content:center's
     look while content fits (see the .section comment above), but collapse
     to 0 — instead of clipping — the moment content is taller than the
     section, keeping the whole thing scrollable from its natural top. */
  margin: auto 0;
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 640px;
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 16px;
  padding: 40px 28px;
}

.section-quote {
  font-size: 0.75em;
  display: block;
  max-width: 480px;
  margin: 18px auto 0;
  line-height: 1.6;
  /* rgba, not opacity: this element also gets .reveal (see style.css
     comment there) — opacity is how the reveal show/hide state is
     driven, so a second, unrelated opacity rule at equal specificity
     would race it and win half the time. */
  color: rgba(255, 255, 255, 0.9);
}

/* ---- opening / date strip ---- */
.tanggal {
  display: flex;
  justify-content: center;
  gap: 32px;
  margin-bottom: 40px;
}

.tanggal span {
  display: block;
  font-size: 0.75em;
  letter-spacing: 0.15em;
}

.tanggal strong {
  display: block;
  font-size: 1.3em;
  margin-top: 4px;
  font-weight: 600;
}

/* ---- bride / groom full-bleed portrait frames ---- */
.section-portrait {
  /* flex-start, not flex-end — same overflow-clipping reasoning as the
     base .section rule above, just bottom-anchored instead of centered
     (see .portrait-caption's margin-top:auto below). */
  justify-content: flex-start;
  align-items: flex-start;
  text-align: left;
  padding: 0 32px 64px;
}

.section-portrait::before {
  background: linear-gradient(to top,
      rgba(0, 0, 0, 0.85) 0%,
      rgba(0, 0, 0, 0.55) 28%,
      rgba(0, 0, 0, 0.1) 55%,
      rgba(0, 0, 0, 0) 75%);
}

.portrait-caption {
  /* Pushes the caption to the bottom while it fits, same as the previous
     justify-content:flex-end did, but collapses to 0 instead of clipping
     the top when the caption is taller than the section. */
  margin-top: auto;
  position: relative;
  z-index: 1;
  max-width: 380px;
}

.portrait-caption h1 {
  padding: 0;
  margin: 4px 0 0;
  font-size: 1.9em;
}

.portrait-caption hr {
  width: 25%;
  margin: 14px 0;
  border-color: rgba(255, 255, 255, 0.5);
}

.portrait-caption .label {
  font-size: 0.8em;
  letter-spacing: 0.1em;
  /* rgba, not opacity — see .section-quote comment above. */
  color: rgba(255, 255, 255, 0.85);
}

.portrait-caption .parents {
  font-size: 0.85em;
  margin: 2px 0;
}

/* ---- our story timeline ---- */
.timeline {
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.timeline-item {
  border-left: 2px solid rgba(255, 255, 255, 0.5);
  padding-left: 16px;
}

.timeline-item .date {
  font-size: 0.75em;
  letter-spacing: 0.1em;
  opacity: 0.85;
  display: block;
  margin-bottom: 4px;
}

.timeline-item p {
  margin: 0;
  font-size: 0.9em;
  line-height: 1.6;
  opacity: 0.95;
}

/* ---- countdown ---- */
.countdown-grid {
  display: flex;
  justify-content: center;
  gap: 14px;
  margin: 18px 0 26px;
  flex-wrap: wrap;
}

.countdown-cell {
  min-width: 68px;
  padding: 10px 6px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 6px;
}

.countdown-cell .num {
  display: block;
  font-size: 1.6em;
  font-weight: 600;
}

.countdown-cell .label {
  font-size: 0.7em;
  letter-spacing: 0.08em;
  opacity: 0.85;
}

#countdown-end-message {
  display: none;
  font-size: 1.1em;
  margin: 18px 0 26px;
}

/* ---- akad / resepsi ---- */
.event-card h1 {
  margin-bottom: 6px;
}

.event-card p {
  margin: 2px 0;
}

.event-card .venue {
  font-size: 0.9em;
  margin-top: 10px;
}

.event-card .address {
  font-size: 0.75em;
  /* rgba, not opacity — see .section-quote comment above. */
  color: rgba(255, 255, 255, 0.85);
  max-width: 420px;
  margin: 4px auto 14px;
}

.event-card .btn-row {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

/* Inline embedded map (from $data->map_akad / map_resepsi), shown below
   the "Lihat Lokasi" button when the couple provided an embed — plain
   inline box rather than a modal, since a Bootstrap-style modal would
   have to fight the fixed, full-viewport frame stack for z-index. */
.map-embed {
  margin-top: 16px;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.25);
}

.map-embed iframe {
  display: block;
  width: 100%;
  height: 220px;
  border: 0;
}

/* ---- wedding gift / amplop digital ---- */
.gift-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 20px;
  text-align: left;
}

.gift-item {
  display: flex;
  align-items: center;
  gap: 12px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 10px;
  padding: 12px 14px;
}

.gift-icon {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.gift-icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.gift-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.gift-bank {
  font-size: 0.85em;
  font-weight: 600;
}

.gift-number {
  font-size: 0.8em;
  letter-spacing: 0.03em;
}

.gift-owner {
  font-size: 0.7em;
  color: rgba(255, 255, 255, 0.7);
}

.gift-copy {
  flex: 0 0 auto;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(205, 168, 106, 0.6);
  color: var(--gold-light);
  border-radius: 999px;
  padding: 7px 14px;
  font-size: 0.72em;
  letter-spacing: 0.04em;
  font-family: "Montserrat", sans-serif;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.gift-copy:hover {
  background: rgba(205, 168, 106, 0.2);
  border-color: rgba(205, 168, 106, 0.9);
}

.gift-copy.copied {
  background: rgba(120, 200, 150, 0.35);
  border-color: rgba(120, 200, 150, 0.6);
}

/* ---- ucapan & doa (wishes form) ---- */
.guestbook-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 18px;
  text-align: left;
}

.guestbook-form input,
.guestbook-form select,
.guestbook-form textarea {
  width: 100%;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 8px;
  padding: 10px 12px;
  color: #fff;
  font-size: 0.85em;
  font-family: "Montserrat", sans-serif;
}

.guestbook-form input::placeholder,
.guestbook-form textarea::placeholder {
  color: rgba(255, 255, 255, 0.6);
}

.guestbook-form select option {
  /* Native dropdown list renders with a light background in most
     browsers regardless of this stylesheet, so option text needs a dark
     color to stay legible there. */
  color: #18181a;
}

.guestbook-form textarea {
  resize: vertical;
  min-height: 70px;
  font-family: "Montserrat", sans-serif;
}

.guestbook-form button {
  align-self: flex-end;
  margin-top: 2px;
}

.guestbook-form [class^="invalid-feedback-"] {
  display: block;
  color: #ffb4b4;
  font-size: 0.72em;
  margin-top: -4px;
}

.guestbook-form .is-invalid {
  border-color: #ff8a8a !important;
}

/* ---- turut mengundang (invited families) ---- */
.family-wrap {
  text-align: left;
  margin-top: 20px;
}

.family-wrap ul {
  list-style: none;
  margin: 0;
  padding: 0;
  columns: 2;
  column-gap: 24px;
}

.family-wrap li {
  font-size: 0.85em;
  padding: 6px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.15);
  break-inside: avoid;
}

/* ---- gallery ---- */
.gallery-1 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 20px;
}

.gallery-1 a {
  display: block;
}

.gallery-1 img {
  width: 100%;
  height: 120px;
  object-fit: cover;
  border-radius: 6px;
}

/* ---- gallery photo preview (lightbox) ---- */
.gallery-lightbox {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.92);
  padding: 24px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0.25s;
}

.gallery-lightbox.open {
  opacity: 1;
  visibility: visible;
}

.gallery-lightbox-img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 8px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}

.gallery-lightbox-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.gallery-lightbox-close:hover {
  background: rgba(255, 255, 255, 0.25);
}

.gallery-lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 28px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gallery-lightbox-nav:hover {
  background: rgba(255, 255, 255, 0.25);
}

.gallery-lightbox-prev {
  left: 16px;
}

.gallery-lightbox-next {
  right: 16px;
}

/* ---- footer ---- */
#footer p {
  margin: 4px 0;
}

#footer .made-with {
  margin-top: 24px;
  font-size: 0.7em;
  /* rgba, not opacity — see .section-quote comment above. */
  color: rgba(255, 255, 255, 0.6);
}

/* ---- headings shared ---- */
.section h1.nanum {
  font-size: 1.8em;
  margin-bottom: 18px;
}

@media (max-width: 576px) {
  #hero .judul h2 {
    font-size: 1.9em;
  }

  .section h1.nanum {
    font-size: 1.4em;
  }

  .gallery-1 {
    grid-template-columns: repeat(2, 1fr);
  }

  .countdown-cell {
    min-width: 58px;
  }

  .family-wrap ul {
    columns: 1;
  }
}
