/* ================================================================
   main.css — the only stylesheet the page links to.
   A rule in a later layer beats every rule in an earlier one.
   ================================================================ */
@layer tokens, base, components, sections, motion;

@layer tokens {
/* ================================================================
   tokens.css — the whole look, in one place.
   White paper, black type, a lot of blue, a little yellow.
   ================================================================ */
@font-face {
    font-family: 'Soligant';
    src: url('../font/Soligant.woff2') format('woff2'),
        url('../font/Soligant.woff') format('woff');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

   :root{
  --paper:  #FFFFFF;
  --ink:    #000000;
  --main:   #16521b;   /* the main accent: serif lines, whole panels, the menu */
  --accent: #FFD43B;   /* the spark: only ever on blue, or as a fill behind black */
  --mute:   #6E6E6E;
  --line:   #E2E2E2;
  --stock:  #F6F4EE;   /* photographic paper, for the prints in the ring */

  --sans:  "Hanken Grotesk", "Helvetica Neue", Arial, sans-serif;
  --serif: "Cormorant Upright", "Bodoni 72", Didot, Georgia, serif;

  --d-xl: clamp(76px, 19vw, 360px);
  --d-l:  clamp(46px, 9vw, 168px);
  --d-m:  clamp(32px, 4.6vw, 82px);
  --d-s:  clamp(22px, 2.3vw, 38px);

  --gut: clamp(20px, 4vw, 64px);
  --bay: clamp(110px, 20vh, 240px);

  --out:   cubic-bezier(.16, 1, .3, 1);
  --in:    cubic-bezier(.7, 0, .84, 0);
  --inout: cubic-bezier(.76, 0, .24, 1);
}

}

@layer base {
/* ================================================================
   base.css — reset, page, the two type voices, colour roles
   ================================================================ */
*, *::before, *::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; scroll-behavior:smooth; }
body{ margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--sans); font-size:16px; line-height:1.55; -webkit-font-smoothing:antialiased; }
body.is-locked{ overflow:hidden; }
::selection{ background:var(--accent); color:var(--ink); }
img{ display:block; max-width:100%; }
button{ font:inherit; color:inherit; background:none; border:0; padding:0; cursor:pointer; }
a{ color:inherit; text-decoration:none; }
h1, h2, h3, h4, p, figure{ margin:0; }
h1, h2, h3, h4{ font-weight:inherit; }
:focus-visible{ outline:1px solid currentColor; outline-offset:4px; }

/* overflow-x:clip, not hidden — hidden would break every sticky inside */
main{ overflow-x:clip; }

/* ---------- the two voices ---------- */
.t-hair { font-family:var(--sans);  font-weight:200; letter-spacing:-.035em; }
.t-serif{ font-family:var(--serif); font-style:normal; font-weight:400; letter-spacing:-.02em; color:var(--main); }

/* ---------- blue panels: white hairline, yellow serif ---------- */
.on-blue{ background:var(--main); color:#fff; }
.on-blue .t-serif{ color:var(--accent); }

.sr{ position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }

.link{ position:relative; display:inline-block; font-size:14px; font-weight:500; padding-bottom:3px; }
.link::before, .link::after{ content:""; position:absolute; left:0; right:0; bottom:0; height:1px; background:currentColor; }
.link::before{ opacity:.25; }
.link::after{ transform-origin:right; scale:0 1; transition:scale .5s var(--inout); }
.link:hover::after, .link:focus-visible::after{ transform-origin:left; scale:1 1; }

}

@layer components {
/* ================================================================
   components.css — photo plates, frames, the A/B frame
   ================================================================ */

/* ---------- photo plate ----------
   While there's no real photo, each file gets its own colour and number
   (placeholders.js). Same filename = same colour everywhere, so you can
   follow one photo from the loader into the page. */
.plate{ position:relative; overflow:hidden; background:var(--ph-color, #EDEDEB); }
.plate__img{ width:100%; height:100%; object-fit:cover; will-change:transform; backface-visibility:hidden; }
.plate.is-empty .plate__img{ visibility:hidden; }
.plate:not(.is-loaded)::before{
  content:attr(data-n); position:absolute; left:50%; top:50%; translate:-50% -62%;
  font:200 clamp(26px, 4vw, 72px)/1 var(--sans); letter-spacing:-.04em; color:var(--ph-ink, #000);
  white-space:nowrap; pointer-events:none;
}
.plate:not(.is-loaded)::after{
  content:attr(data-ph); position:absolute; left:50%; top:50%; translate:-50% 90%;
  width:max-content; max-width:90%; text-align:center;
  font-size:11px; line-height:1.3; color:var(--ph-ink, #000); opacity:.75; pointer-events:none;
}

/* a frame is a fixed window; the photo inside is bigger so it can drift */
.frame{ position:relative; overflow:hidden; }
.frame > .plate{ position:absolute; inset:0; }
.plate__img[data-parallax], .plate__img[data-hparallax]{ scale:1.28; }
/* the second photo behind a drifting one: when the front photo slides
   past the frame's edge, this is what shows instead of the colour */
.plate__back{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.plate__back + .plate__img{ position:relative; }

/* ---------- the A/B frame ----------
   flip.js adds .is-b once the frame passes the middle of the screen.
   B wipes in from the right over A; scroll back and it wipes away. */
.flip{ position:absolute; inset:0; }
.flip > .plate{ position:absolute; inset:0; }
/* each frame can set its own pace: --flip-time (the wipe) and --flip-delay */
.flip > .plate:first-child{ transition:scale calc(var(--flip-time, 1.8s) * .78) var(--out) var(--flip-delay, 0s); }
.flip > .plate + .plate{ clip-path:inset(0 0 0 100%); transition:clip-path var(--flip-time, 1.8s) var(--inout) var(--flip-delay, 0s); }
.is-b .flip > .plate + .plate{ clip-path:inset(0); }
.is-b .flip > .plate:first-child{ scale:1.12; }

.flip__cap{ display:grid; margin-top:18px; font-size:clamp(24px, 2.2vw, 38px); line-height:1; }
.flip__cap span{ grid-area:1 / 1; transition:opacity .5s, translate .9s var(--out); }
.flip__cap span + span{ opacity:0; translate:0 .6em; }
.is-b .flip__cap span:first-child{ opacity:0; translate:0 -.6em; }
.is-b .flip__cap span + span{ opacity:1; translate:0 0; }

/* ---------- ornaments — the lotus and fronds from the invitation box ----------
   Faint, behind a section's content (the section gets isolation so -1 stays inside it).
     <div class="orn" aria-hidden="true">
       <div class="orn__pos orn--y" style="--w:30vw; --wm:60vw; right:0; top:0">
         <img src="assets/img/ornament/fern.png" alt="" data-bloom data-hparallax=".1" data-parallax=".06">
     .orn__pos is placed and sized inline: --w (width), --wm (on phones), --o (opacity).
     .orn--x mirrors it left↔right, .orn--y turns it upside down (hanging), .orn--xy both.
   The picture inside grows with the scroll (data-bloom, bloom.js / motion.css) and drifts
   (data-hparallax in a sideways track, data-parallax up/down). Grey on paper, faint white on blue. */
:where(.panel, section, article, .hold__stage):has(> .orn){ position:relative; isolation:isolate; }
.orn{ position:absolute; inset:0; z-index:-1; overflow:hidden; pointer-events:none; }
.orn__pos{ position:absolute; width:var(--w, 30vw); }
.orn__pos img{ display:block; width:100%; height:auto; opacity:var(--o, .2); user-select:none; -webkit-user-select:none; }
.on-blue .orn__pos img{ filter:brightness(0) invert(1); opacity:var(--o, .1); }
.orn--x{ rotate:y 180deg; }
.orn--y{ rotate:x 180deg; }
.orn--xy{ rotate:z 180deg; }
@media (max-width:760px){ .orn__pos{ width:var(--wm, calc(var(--w, 30vw) * 2)); } }

}


@layer sections {
/* ================================================================
   loader.css — zooming straight through a stack of prints.
   loader.js sizes the frame and scales each print every frame;
   this file only sets up the pieces.
   ================================================================ */
#loader{ display:none; }
.js #loader{ display:block; position:fixed; inset:0; z-index:500; background:var(--paper);
  transition:background-color .35s; }
.js #loader.is-out{ background-color:transparent; }
.no-reveal #loader{ display:none !important; }

/* the frame: a portrait window in the middle that slowly grows */
.tunnel{ position:absolute; left:50%; top:50%; width:30px; height:40px; translate:-50% -50%; overflow:hidden; }
.tunnel__layer{ position:absolute; inset:0; visibility:hidden; transform-origin:50% 50%; will-change:transform; }
.tunnel__layer .plate{ position:absolute; inset:0; }

/* the last print is the full-screen hero photo seen through the same
   window — so it can reshape into the hero window with no jump */
.loader__final{ position:absolute; inset:0; visibility:hidden; transform-origin:50% 50%; will-change:transform, clip-path; }
.loader__final .plate{ position:absolute; inset:0; }
.loader__final .plate__img{ scale:1.45; }

.loader__names{ position:absolute; left:var(--gut); bottom:22px; font-size:19px; transition:opacity .3s; }
.loader__names .t-hair{ font-weight:300; letter-spacing:0; }
.is-out .loader__names{ opacity:0; }

}

@layer sections {
/* ================================================================
   nav.css — the bar inverts what's under it (so on blue it turns
   yellow, by itself), and a blue full-screen menu
   ================================================================ */
.bar{ position:fixed; top:0; left:0; right:0; z-index:120;
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
  padding:22px var(--gut); color:#fff; mix-blend-mode:difference; pointer-events:none; }
.bar > *{ pointer-events:auto; }
.bar__mark{ justify-self:start; font-family:var(--serif); font-style:normal; font-size:21px; }
.bar__pct{ font-size:13px; font-variant-numeric:tabular-nums; }
.bar__menu{ justify-self:end; font-size:14px; font-weight:500; }

#menu{ position:fixed; inset:0; z-index:150; background:var(--main); color:#fff;
  display:flex; flex-direction:column; justify-content:center; padding:80px var(--gut);
  clip-path:inset(0 0 100% 0); visibility:hidden;
  transition:clip-path .9s var(--inout), visibility 0s linear .9s; }
body.menu-open #menu{ clip-path:inset(0); visibility:visible; transition:clip-path .9s var(--inout); }
.m-close{ position:absolute; top:22px; right:var(--gut); font-size:14px; font-weight:500; }

.m-list{ list-style:none; margin:0; padding:0; }
.m-link{ display:block; font-size:clamp(40px, min(8vw, 11vh), 130px); line-height:1.04; }
.m-list li:nth-child(odd)  .m-link{ font-family:var(--sans); font-weight:200; letter-spacing:-.035em; }
.m-list li:nth-child(even) .m-link{ font-family:var(--serif); font-style:normal; color:var(--accent); padding-left:9vw; }
.roll{ display:block; overflow:hidden; height:1.04em; translate:0 110%; transition:translate .5s var(--in); }
body.menu-open .roll{ translate:0 0; transition:translate 1s var(--out) calc(.3s + var(--n, 0) * .06s); }
.roll i{ display:block; font-style:inherit; line-height:1.04; transition:translate .7s var(--out); }
.m-link:hover .roll i, .m-link:focus-visible .roll i{ translate:0 -100%; }

.m-foot{ position:absolute; left:var(--gut); right:var(--gut); bottom:26px; display:flex; flex-wrap:wrap; gap:10px 32px;
  font-size:14px; color:rgba(255,255,255,.7); opacity:0; transition:opacity .3s; }
body.menu-open .m-foot{ opacity:1; transition:opacity .6s .8s; }

}

@layer sections {
/* ================================================================
   track.css — the sideways page.

   Desktop (≥900px): each [data-hscroll] section pins its stage and
   slides the track left as you scroll down. A panel with data-hold
   stops the track for a while and animates in place.
   Phones: the same panels simply stack; hold panels pin vertically.
   ================================================================ */
.panel{ position:relative; }
.hold__stage{ position:sticky; top:0; height:100vh; height:100svh; overflow:hidden; }
.hs-progress{ display:none; }

@media (min-width:900px){
  .hs-stage{ position:sticky; top:0; height:100vh; height:100svh; overflow:clip; }
  .hs-track{ position:relative; display:flex; height:100%; width:max-content; will-change:transform; }
  .panel{ flex:none; height:100%; }
  .hold__stage{ position:relative; height:100%; }

  /* inverts like the top bar: black on white, yellow on blue */
  .hs-progress{ display:block; position:absolute; z-index:5; left:var(--gut); right:var(--gut); bottom:3vh;
    height:1px; background:rgba(255,255,255,.3); mix-blend-mode:difference; pointer-events:none; }
  .hs-progress i{ position:absolute; inset:0; background:#fff; transform-origin:left; scale:var(--hp, 0) 1; }
}

}

@layer sections {
/* ================================================================
   hero.css — the names pull apart and the window opens to the full
   photo (with the date and a quote); then the photo parts like a pair
   of doors and swings open onto Salam, arriving behind it.
   Values are data-* in index.html.
   ================================================================ */
/* above Salam, which arrives underneath it; only its controls take clicks */
.p-hero{ width:100vw; z-index:2; }
.p-hero .hold__stage{ pointer-events:none; }
.hero__paper{ position:absolute; inset:0; background:var(--paper); }
@media (min-width:900px){
  /* the photo and what's on it hold still while the track moves on (hscroll.js sets --exit-x) */
  .p-hero .hold__stage{ overflow:visible; }
  .hero__window, .hero__capbox{ translate:var(--exit-x, 0px) 0; }
}

.hero__names{ position:absolute; inset:0; z-index:2; pointer-events:none; font-size:var(--d-xl); line-height:.82; }
.hero__a{ position:absolute; left:var(--gut); top:15vh; }
.hero__b{ position:absolute; right:var(--gut); bottom:12vh; }
.hero__a, .hero__b{ will-change:transform, opacity; }
/* both names and the "&" in the serif (Cormorant Upright) */
.hero__names .t-hair, .hero__names .t-serif, .hero__window { font-family:var(--serif); font-weight:300; letter-spacing:-.1em; }
.hero__amp{font-family:var(--serif); font-weight:600}

.hero__window{ position:absolute; inset:0; z-index:1; will-change:clip-path; }
.hero__window .plate{ position:absolute; inset:0; }

/* the photo is a pair of doors: two copies, each cut to its half and hinged
   on its outer edge. Turning them (data-turn) swings them inward. */
.hero__doors{ position:absolute; inset:0; perspective:1100px; will-change:transform; }
.hero__door{ position:absolute; inset:0; backface-visibility:hidden; -webkit-backface-visibility:hidden; will-change:transform; }
.hero__door--l{ clip-path:inset(0 calc(50% - .5px) 0 0); transform-origin:0 50%; }
.hero__door--r{ clip-path:inset(0 0 0 calc(50% - .5px)); transform-origin:100% 50%; }
.hero__dim{ position:absolute; inset:0; background:#000; opacity:0; pointer-events:none; }   /* turning away from the light */
.hero__shade{ position:absolute; inset:0; z-index:2; pointer-events:none; }

/* the quote, on the full photo: bottom right, opposite the date */
.hero__note{ position:absolute; right:var(--gut); bottom:7vh; width:min(400px, 32vw); text-align:right; color:#fff; opacity:0; }
.hero__note blockquote{ margin:0; }
.hero__verse{ font-size:clamp(17px, 1.35vw, 22px); line-height:1.3; color:var(--accent); }
.hero__gloss{ margin-top:12px; font-size:13px; line-height:1.55; color:rgba(255,255,255,.88); }
.hero__source{ margin-top:10px; font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:rgba(255,255,255,.7); }
/* placeholder label sits under the "&" (loader.css matches this for the handover) */
.hero__window .plate::before, .loader__final .plate::before{ top:61%; }
.hero__window .plate::after,  .loader__final .plate::after{ top:61%; }
.hero__amp{ position:absolute; left:50%; top:50%; translate:-50% -54%; z-index:3;
  font-size:clamp(64px, 9vw, 160px); line-height:1; color:var(--accent); }
.hero__scrim{ position:absolute; inset:0; z-index:2; background:linear-gradient(180deg, transparent 45%, rgba(0,0,0,.55)); }

.hero__meta{ position:absolute; inset:0; z-index:3; pointer-events:none; }
.hero__to{ position:absolute; left:var(--gut); top:82px; font-size:clamp(18px, 1.6vw, 24px); }
.hero__om, .hero__date{ position:absolute; bottom:4.5vh; font-size:14px; }
.hero__om{ left:var(--gut); }
.hero__date{ right:var(--gut); font-variant-numeric:tabular-nums; }

/* the cover's way in: "Buka undangan", and scrolling as the other way —
   under the small window. Centred by stretching, not translate, so the
   reveal's own translate can't knock it off centre. */
.hero__start{ position:absolute; left:0; right:0; bottom:4.5vh; z-index:4; pointer-events:none;
  display:flex; justify-content:center; align-items:center; gap:24px; }
.hero__start > *{ pointer-events:auto; }
.hero__start.is-gone > *{ pointer-events:none; }               /* faded with the cover: not clickable */
.hero__open{ padding:14px 30px; border-radius:999px; border:1px solid var(--main); background:var(--main); color:#fff;
  font-size:15px; font-weight:500; transition:background .4s var(--out), color .4s var(--out), border-color .4s var(--out); }
.hero__open:hover{ background:var(--accent); border-color:var(--accent); color:var(--ink); }
.hero__open:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }

/* "Scroll" — beside the button, and again on the full photo */
.hero__cue{ position:absolute; left:50%; bottom:4.5vh; translate:-50% 0; z-index:4;
  display:flex; flex-direction:column; align-items:center; gap:10px;
  font-size:12px; letter-spacing:.14em; text-transform:uppercase; color:inherit; text-decoration:none;
  pointer-events:auto; }
.hero__cue i{ display:block; width:1px; height:34px; background:currentColor; transform-origin:top;
  animation:cue 1.8s var(--inout) infinite; }
.hero__start .hero__cue{ position:static; translate:none; flex-direction:row; gap:10px; color:var(--mute); }
.hero__start .hero__cue i{ height:22px; }
/* on the full photo: bottom centre, between the date and the quote */
.hero__cue--end{ bottom:4.5vh; color:#fff; }
.hero__capbox{ position:absolute; inset:0; z-index:4; pointer-events:none; }
@keyframes cue{ 0%{ scale:1 0; opacity:1; } 60%{ scale:1 1; opacity:1; } 100%{ scale:1 1; opacity:0; } }
@media (prefers-reduced-motion:reduce){ .hero__cue i{ animation:none; } }

.hero__cap{ position:absolute; z-index:4; left:var(--gut); bottom:7vh; color:#fff; }
.hero__cap .t-serif{ font-size:var(--d-m); line-height:1; color:var(--accent); }
.hero__cap p + p{ margin-top:12px; font-size:15px; }

@media (max-width:760px){
  /* on phones the button sits on the photo, clear of the names */
  .hero__start{ bottom:auto; top:57%; flex-direction:column; gap:12px; }
  .hero__start .hero__cue{ color:#fff; text-shadow:0 1px 8px rgba(0,0,0,.4); }
  /* the quote above the date, both full width; the cue bottom right */
  .hero__note{ left:var(--gut); width:auto; bottom:calc(7vh + 150px); }
  .hero__cue--end{ left:auto; right:var(--gut); translate:none; }
  .hero__names{ font-size:27vw; }
  .hero__a{ top:21vh; }
  .hero__b{ bottom:20vh; }
  .hero__to{ top:74px; }
}

}

@layer sections {
/* ================================================================
   salam.css — composition, highlight paragraph, the A/B frame
   ================================================================ */
.p-salam{ padding:var(--bay) var(--gut); display:grid; row-gap:10vh; }
.compose__line{ font-size:12.4vw; line-height:.98; padding-left:calc(var(--indent, 0) * .45); white-space:nowrap; will-change:transform; }
.salam__col{ display:grid; row-gap:8vh; }
.salam__text{ font-size:22px; font-weight:300; line-height:1.3; letter-spacing:-.01em; }
.salam__small{ width:52%; }
.salam__small .frame{ aspect-ratio:4/5; }
.salam__flip{ width:86%; justify-self:end; }
.salam__flip .frame{ aspect-ratio:3/4; }


@media (min-width:900px){
  /* two columns — the greeting (with the small frame) and the big frame —
     sized to what's in them, and never narrower than the screen, so the
     sideways track ends on Salam with no empty space after it.
     (The "Dua keluarga" lines are commented out in index.html; bringing
     them back needs a first column of about 88vw again.) */
  .p-salam{ min-width:100vw; padding:0 9vw; grid-template-columns:minmax(0, 40vw) auto; column-gap:9vw;
    justify-content:center; align-items:center; }
  .compose__line{ font-size:var(--d-l); padding-left:var(--indent, 0); }
  .salam__col{ align-self:center; }
  .salam__text{ font-size:var(--d-s); }
  .salam__small{ width:15vw; justify-self:end; }
  .salam__flip{ width:auto; justify-self:start; }
  .salam__flip .frame{ height:70vh; width:auto; }
}

}

@layer sections {
/* ================================================================
   cerita.css — a blue title panel, then three chapters
   ================================================================ */
.p-cerita{ padding:var(--bay) var(--gut); position:relative; isolation:isolate; overflow:hidden; }
/* the photo behind the title, darkened so the white type stays readable */
.cerita__bg{ position:absolute; inset:0; z-index:-1; }
.cerita__bg > .plate{ position:absolute; inset:0; }
.cerita__bg::after{ content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,.25), rgba(0,0,0,.55)); pointer-events:none; }
.cerita__title{ font-size:var(--d-l); line-height:.9; display:flex; flex-direction:column; }
.cerita__title .t-hair{ padding-left:9vw; }
.cerita__lede{ margin-top:5vh; font-size:17px; max-width:24ch; }

.chapter{ padding:calc(var(--bay) * .6) var(--gut); display:grid; grid-template-columns:1fr; row-gap:18px; }
.chapter__no{ font-size:var(--d-m); line-height:1; font-variant-numeric:tabular-nums; color:var(--main); }
.chapter__title{ font-size:var(--d-m); line-height:.95; display:flex; flex-direction:column; }
.chapter__text{ font-size:16px; max-width:30ch; color:var(--mute); }
.chapter__big{ aspect-ratio:3/4; width:78%; margin-top:18px; }
.chapter__small{ aspect-ratio:4/5; width:44%; justify-self:end; margin-top:-22%; }

@media (min-width:900px){
  .p-cerita{ width:56vw; padding:0 6vw; display:flex; flex-direction:column; justify-content:center; }
  .chapter{ width:80vw; padding:14vh 2vw 12vh; grid-template-columns:24vw auto 14vw;
    grid-template-rows:auto 1fr auto auto; column-gap:3vw; row-gap:0; align-items:end; }
  .chapter__no{ grid-column:1; grid-row:1; align-self:start; }
  .chapter__title{ grid-column:1; grid-row:3; margin-bottom:22px; }
  .chapter__text{ grid-column:1; grid-row:4; }
  .chapter__big{ grid-column:2; grid-row:1 / -1; align-self:center; margin:0; height:min(40vw, 72vh); width:auto; }
  .chapter__small{ grid-column:3; grid-row:1 / -1; align-self:end; width:100%; margin:0 0 4vh; }
}

}

@layer sections {
/* ================================================================
   acara.css — the track stops; scrolling walks through the day
   ================================================================ */
.stage-pad{ padding:13vh var(--gut) 10vh; }
.hold__stage.swap{ display:grid; grid-template-columns:1fr 1fr; column-gap:6vw; padding:13vh var(--gut) 10vh; background:var(--paper); }

.swap__title{ font-size:clamp(46px, 7vw, 130px); line-height:.9; display:flex; flex-direction:column; margin-bottom:5vh; }
.swap__title .t-serif{ padding-left:7vw; }
.swap__list ol{ list-style:none; margin:0; padding:0; }
.swap__item{ display:grid; grid-template-columns:clamp(84px, 8vw, 140px) 1fr; column-gap:2vw;
  padding:2.2vh 0; border-top:1px solid var(--line); color:#B4B4B4; transition:color .6s var(--out); }
.swap__item:last-child{ border-bottom:1px solid var(--line); }
.swap__item.is-active{ color:var(--ink); }
.swap__item.is-active .swap__time{ color:var(--main); }
.swap__time{ font-size:clamp(30px, 3.4vw, 58px); line-height:.95; font-variant-numeric:tabular-nums; transition:color .6s var(--out); }
.swap__item h3{ font-family:var(--serif); font-style:normal; font-size:clamp(22px, 2vw, 34px); line-height:1.1; }

.swap__more{ display:grid; grid-template-rows:0fr; opacity:0; transition:grid-template-rows .8s var(--inout), opacity .4s; }
.swap__more > div{ overflow:hidden; }
.swap__item.is-active .swap__more{ grid-template-rows:1fr; opacity:1; transition:grid-template-rows .8s var(--inout), opacity .6s .25s; }
.swap__more p{ margin-top:8px; font-size:15px; max-width:40ch; color:var(--mute); }
.swap__more .link{ margin-top:12px; }

.swap__media{ position:relative; min-height:0; }
.swap__img{ position:absolute; inset:0; clip-path:inset(100% 0 0 0); transition:clip-path 1.1s var(--inout); }
.swap__img:first-child, .swap__img.is-shown{ clip-path:inset(0); }
.swap__img .plate{ position:absolute; inset:0; scale:1.2; transition:scale 1.6s var(--out); }
.swap__img:first-child .plate, .swap__img.is-shown .plate{ scale:1; }
.swap__count{ position:absolute; right:0; bottom:-4.2vh; font-size:13px; font-variant-numeric:tabular-nums; }

.p-notes{ padding:var(--bay) var(--gut); display:grid; gap:7vh; }
.note__rule{ margin-bottom:22px; }
.note h3{ font-family:var(--serif); font-style:normal; color:var(--main); font-size:clamp(24px, 2.2vw, 36px); margin-bottom:10px; }
.note p{ font-size:15px; max-width:32ch; color:var(--mute); }

@media (min-width:900px){
  .p-acara{ width:100vw; }
  .p-notes{ width:58vw; padding:0 6vw; align-content:center; }
}
@media (max-width:899px){
  .hold__stage.swap{ grid-template-columns:1fr; grid-template-rows:auto minmax(0, 1fr); row-gap:3vh; padding:11vh var(--gut) 7vh; }
  .swap__title{ font-size:13vw; margin-bottom:2vh; }
  .swap__item{ padding:1.4vh 0; }
  .swap__time{ font-size:8vw; }
  .swap__item h3{ font-size:19px; }
}

}

@layer sections {
/* ================================================================
   couple.css — the vertical break. On desktop this is the one place
   the page scrolls up and down; it's blue so the change is felt.
   ================================================================ */
.vbreak{ padding:var(--bay) var(--gut) 0; }
.vbreak__cue{ font-family:var(--serif); font-style:normal; color:var(--accent); font-size:clamp(22px, 2vw, 32px); margin-bottom:14vh; }

.couple{ display:grid; grid-template-columns:repeat(12, 1fr); column-gap:2vw; }
.person--a{ grid-column:1 / span 5; }
.person--b{ grid-column:7 / span 5; margin-top:30vh; }
.person__img{ aspect-ratio:3/4; }
.person__name{ margin:5vh 0 14px; font-size:var(--d-m); line-height:.95; display:flex; flex-direction:column; }
.person__name > span + span{ padding-left:4vw; }
.person__line{ font-size:15px; color:rgba(255,255,255,.75); max-width:34ch; }

.mq{ overflow:hidden; margin:var(--bay) calc(var(--gut) * -1) 0; padding:5vh 0;
  border-top:1px solid rgba(255,255,255,.25); }
.mq__track{ display:flex; width:max-content; will-change:transform; }
.mq__track span{ font-size:var(--d-l); line-height:1.08; padding-right:.45em; white-space:nowrap; }

@media (max-width:760px){
  .couple{ grid-template-columns:1fr; row-gap:14vh; }
  .person--a, .person--b{ grid-column:1; margin-top:0; }
  .person--b{ width:86%; justify-self:end; }
}

}

@layer sections {
/* ================================================================
   momen.css — twelve prints standing in a ring, seen from above.
   ring.js places them, spins them, and handles drag + focus.
   ================================================================ */
.p-momen{ width:100vw; }
.momen{ background:var(--paper); }
.momen__title{ position:absolute; z-index:5; left:var(--gut); top:12vh; pointer-events:none;
  font-size:clamp(40px, 6vw, 110px); line-height:.9; display:flex; flex-direction:column; }
.momen__title .t-serif{ padding-left:5vw; }
.momen__hint{ position:absolute; z-index:5; left:var(--gut); bottom:7vh; font-size:14px; color:var(--mute); }
.hint--touch{ display:none; }
@media (hover:none){ .hint--mouse{ display:none; } .hint--touch{ display:inline; } }

.ring-scene{ position:absolute; inset:0; perspective:1500px; perspective-origin:50% 38%;
  touch-action:pan-y; cursor:grab; user-select:none; -webkit-user-select:none; }
.momen.is-dragging .ring-scene{ cursor:grabbing; }
.ring{ position:absolute; left:50%; top:60%; width:0; height:0; transform-style:preserve-3d; }

.ring__card{ --cw:clamp(110px, 12.5vw, 230px); position:absolute; margin:0;
  width:var(--cw); height:calc(var(--cw) * 1.34); left:calc(var(--cw) * -.5); top:calc(var(--cw) * -.67);
  transform-style:preserve-3d; outline:none; }
.ring__face, .ring__back{ position:absolute; inset:0; backface-visibility:hidden; -webkit-backface-visibility:hidden; }
.ring__face{ background:var(--stock); box-shadow:0 1px 0 rgba(0,0,0,.06), 0 14px 26px rgba(0,0,0,.14);
  transition:opacity .45s var(--out); }
.ring__face .plate{ position:absolute; inset:6px; }
.ring__face .plate::before{ font-size:clamp(22px, 2.6vw, 44px); }
.ring__face .plate::after{ display:none; }
.ring__back{ transform:rotateY(180deg); background:#ECE9E1; }
.ring__card:focus-visible .ring__face{ outline:2px solid var(--main); outline-offset:3px; }

/* focus: the others dim, the chosen print comes forward to centre stage */
.momen.is-focus .ring__face{ opacity:.22; }
.momen.is-focus .ring__card.is-hot .ring__face{ opacity:0; }
.ring__stage{ position:absolute; z-index:6; left:50%; top:52%; width:min(30vw, 46vh); aspect-ratio:3/4;
  translate:-50% -50%; pointer-events:none; }
.ring__big{ position:absolute !important; inset:0 !important; border:9px solid var(--stock);
  box-shadow:0 30px 70px rgba(0,0,0,.28); transition:transform .8s var(--out), opacity .45s; }
.ring__big::before{ font-size:clamp(40px, 6vw, 96px) !important; }

@media (max-width:760px){
  .ring__card{ --cw:24vw; }
  .ring{ top:58%; }
  .ring__stage{ width:62vw; }
  .momen__title{ top:11vh; font-size:13vw; }
}

/* ---------- WebGL version ----------
   While three.js loads, the CSS ring stays hidden; if it never loads,
   ring-gl.js removes .gl-pending and the CSS ring shows instead.
   Once running, the CSS prints stay in the page (invisible) so they
   can still be reached with the keyboard. */
.momen.gl-pending .ring-scene{ visibility:hidden; }
.momen.is-gl .ring-scene{ opacity:0; pointer-events:none; }
.ring-gl{ position:absolute; inset:0; z-index:1; width:100%; height:100%; display:block;
  touch-action:pan-y; cursor:grab; }
.momen.is-hover .ring-gl{ cursor:pointer; }
.momen.is-dragging .ring-gl{ cursor:grabbing; }
.momen.is-open .ring-gl{ cursor:zoom-out; }
.momen__title, .momen__hint{ transition:opacity .5s var(--out); }
.momen.is-open .momen__title, .momen.is-open .momen__hint{ opacity:0; }

.ring__ui{ position:absolute; z-index:6; left:50%; bottom:4.5vh; translate:-50% 12px;
  display:flex; align-items:center; gap:28px; font-size:14px;
  opacity:0; pointer-events:none; transition:opacity .4s, translate .6s var(--out); }
.momen.is-open .ring__ui{ opacity:1; translate:-50% 0; pointer-events:auto; transition-delay:.35s; }
.ring__count{ font-variant-numeric:tabular-nums; color:var(--mute); }
.ring__now{ color:var(--main); }

/* With WebGL the title sits above the ring, centred, in one line;
   ring-gl.js keeps the ring low enough to leave it the top of the screen. */
.momen.is-gl .momen__hint{ bottom:5vh; }
.momen.is-gl .momen__title{ z-index:2; left:50%; top:clamp(24px, 6vh, 72px); translate:-50% 0; flex-direction:row; gap:.22em;
  white-space:nowrap; font-size:clamp(44px, 6.4vw, 118px); line-height:1; }
.momen.is-gl .momen__title .t-serif{ padding-left:0; }
@media (max-width:760px){ .momen.is-gl .momen__title{ top:9vh; font-size:13vw; flex-direction:column; align-items:center; gap:0; } }

/* ---------- the story of an open print (ring-story.js) ----------
   Each print's own story waits, hidden, in its .ring__tale; opening the print
   copies it here. Beside the print on a wide screen, under it on a tall one —
   ring-gl.js moves the open print aside to match (OPEN_X / OPEN_Y). */
.ring__tale{ display:none; }
.ring__story{ position:absolute; z-index:6; left:58vw; top:50%; width:min(34vw, 460px); translate:0 -46%;
  opacity:0; pointer-events:none; transition:opacity .35s, translate .7s var(--out); }
.momen.is-open .ring__story, .momen.is-told .ring__story{ opacity:1; translate:0 -50%; transition-delay:.45s; }
.ring__story.is-swapping{ opacity:0 !important; transition-delay:0s !important; }
.ring__story-title{ margin-bottom:16px; font-size:clamp(26px, 2.4vw, 40px); line-height:1.05; color:var(--main); }
.ring__story-text{ max-width:46ch; font-size:clamp(15px, 1.15vw, 18px); line-height:1.65; }

/* the CSS ring (no WebGL): its big print steps aside the same way while the story shows */
.ring__stage{ transition:left .7s var(--out), top .7s var(--out), width .7s var(--out); }
.momen.is-told .ring__stage{ left:30%; }

@media (max-aspect-ratio:1/1){
  .ring__story{ left:var(--gut); right:var(--gut); top:auto; bottom:calc(4.5vh + 48px); width:auto; translate:0 12px; }
  .momen.is-open .ring__story, .momen.is-told .ring__story{ translate:0 0; }
  .ring__story-title{ margin-bottom:10px; }
  .momen.is-told .ring__stage{ left:50%; top:34%; width:min(64vw, 46vh); }
}

}

@layer sections {
/* ================================================================
   rsvp.css — the reply form, reduced to lines
   ================================================================ */
.p-rsvp{ padding:var(--bay) var(--gut); }
.rsvp{ display:grid; grid-template-columns:repeat(12, 1fr); column-gap:2vw; row-gap:6vh; }
.rsvp__title{ grid-column:1 / -1; font-size:var(--d-l); line-height:.92; display:flex; flex-direction:column; }
.rsvp__title .t-serif{ padding-left:12vw; }
.rsvp__note{ grid-column:1 / span 4; font-size:15px; color:var(--mute); max-width:30ch; }
.form{ grid-column:6 / span 7; display:grid; grid-template-columns:1fr 1fr; gap:34px 2vw; }
.f{ grid-column:1 / -1; }
.f--half{ grid-column:auto; }
.f label{ display:block; margin-bottom:6px; font-size:13px; color:var(--mute); }
.f input, .f select, .f textarea{ width:100%; padding:10px 0; border:0; border-bottom:1px solid var(--ink); border-radius:0;
  background:transparent; color:var(--ink); font:300 clamp(20px, 1.8vw, 28px)/1.3 var(--sans); -webkit-appearance:none; appearance:none; }
.f select{ padding-right:26px; cursor:pointer;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23000'/%3E%3C/svg%3E") right 4px center no-repeat; }
.f textarea{ resize:vertical; min-height:74px; }
.f-count{ margin-top:8px; font-size:13px; color:var(--mute); text-align:right; font-variant-numeric:tabular-nums;
  transition:color .3s var(--out); }
.f-count.is-low{ color:var(--main); font-weight:500; }
.f input:focus, .f select:focus, .f textarea:focus{ outline:none; border-bottom:2px solid var(--main); padding-bottom:9px; }
.btn{ grid-column:1 / -1; justify-self:start; padding:18px 34px; border-radius:999px; border:1px solid var(--main);
  background:var(--main); color:#fff; font-size:15px; font-weight:500;
  transition:background .4s var(--out), color .4s var(--out), border-color .4s var(--out); }
.btn:hover{ background:var(--accent); border-color:var(--accent); color:var(--ink); }
.btn:disabled{ opacity:.45; cursor:wait; }
.f-msg{ grid-column:1 / -1; min-height:1.4em; font-family:var(--serif); font-style:normal; color:var(--main); font-size:21px; }
.f-msg a{ border-bottom:1px solid; }

@media (min-width:900px){
  .p-rsvp{ width:100vw; padding:15vh 6vw 9vh; display:flex; align-items:center; }
  .rsvp{ width:100%; }
}
@media (max-width:760px){
  .rsvp{ grid-template-columns:1fr; }
  .rsvp__note, .form{ grid-column:1; }
  .rsvp__title{ font-size:14vw; }
}

/* the form, folding itself into a paper plane (plane.js). Each piece is
   one face of the plane: a printed front (a copy of the form) and a
   plain back, cut to the face's shape and turned in 3D. */
.pf{ position:fixed; inset:0; z-index:110; pointer-events:none; }
.pf__space{ position:absolute; inset:0; perspective:2000px; transform-style:preserve-3d; }
.pf__piece{ position:absolute; left:0; top:0; transform-origin:0 0; transform-style:preserve-3d; will-change:transform; }
.pf__front, .pf__back{ position:absolute; left:0; top:0; overflow:hidden; backface-visibility:hidden; -webkit-backface-visibility:hidden; }
.pf__front{ background:var(--paper); }
.pf__back{ background:linear-gradient(135deg, #FBF9F4, #EFEBE2); transform:rotateY(180deg); }
.pf__shade{ position:absolute; inset:0; background:#2A2014; opacity:0; }

}

@layer sections {
/* ================================================================
   closing.css — gift, wishes, and the blue last page
   ================================================================ */
.p-closing{ padding:var(--bay) var(--gut); display:grid; gap:12vh; }
.closing__title{ font-size:var(--d-m); line-height:.95; display:flex; flex-direction:column; margin-bottom:4vh; }
.closing__title > span + span{ padding-left:4vw; }
.closing__text{ font-size:17px; max-width:40ch; margin-bottom:4vh; }
.acct{ display:flex; align-items:center; justify-content:space-between; gap:20px; padding:20px 0; border-top:1px solid var(--line); }
.acct:last-child{ border-bottom:1px solid var(--line); }
.acct b{ display:block; font-weight:500; }
.acct span{ font-size:14px; color:var(--mute); }
.copy{ font-size:14px; font-weight:500; border-bottom:1px solid; padding-bottom:2px; color:var(--main); }
.wishes__empty{ font-family:var(--serif); font-style:normal; font-size:clamp(21px, 1.9vw, 30px); line-height:1.35; color:var(--mute); }
#ucapan.has-wishes .wishes__empty{ display:none; }

/* wishes — for now only the guest's own (wishes.js) */
.wishes{ list-style:none; margin:0 -6px; padding:0; }
.wish{ padding:22px 0; border-top:1px solid var(--line); }
.wish:last-child{ border-bottom:1px solid var(--line); }
/* the guest's own wish is the sheet their plane unfolded into. It's as big
   as its words, up to max-width × max-height (longer wishes shrink their
   text to fit, then scroll). The plane lands on it at the form's
   proportions, and then it settles to this size (wishes.js). */
.wish--own, .wish--own:last-child{ border:0; width:fit-content; min-width:min(220px, 78%);
  max-width:min(78%, 380px); max-height:min(56vh, 440px); overflow:auto;
  padding:clamp(16px, 1.4vw, 22px); margin:6px 6px 18px; display:flex; flex-direction:column;
  background:var(--paper); box-shadow:0 1px 2px rgba(40,30,15,.08), 0 10px 26px -14px rgba(40,30,15,.28); }
.wish__text{ font-family:var(--serif); font-size:clamp(21px, 1.9vw, 30px); line-height:1.35; white-space:pre-line; overflow-wrap:anywhere; }
.wish--own .wish__text{ font-size:clamp(16px, 1.3vw, 20px); }
.wish--own .wish__by{ margin-top:8px; font-size:12px; }
.wish__by{ margin-top:10px; font-size:14px; color:var(--mute); }
.wish__by::before{ content:"— "; }

.p-foot{ min-height:90vh; min-height:90svh; display:flex; flex-direction:column; justify-content:space-between; gap:7vh;
  padding:12vh var(--gut) 5vh; }
/* the thank-you ribbon, edge to edge (the ribbon itself is .mq, couple.css) */
.foot__mq{ margin:0 calc(var(--gut) * -1); border-bottom:1px solid rgba(255,255,255,.25); }
.foot__mq .mq__track span::after{ content:"·"; padding-left:.45em; opacity:.5; }

/* thanks — one column per person */
.credits{ display:grid; gap:18px; }
.credits__head{ font-size:12px; letter-spacing:.14em; text-transform:uppercase; color:rgba(255,255,255,.65); }
.credits__list{ list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(4, minmax(0, 1fr)); gap:28px 3vw; }
.credits__list li{ display:flex; flex-direction:column; align-items:flex-start; gap:6px;
  padding-top:14px; border-top:1px solid rgba(255,255,255,.3); }
.credits__role{ font-size:12px; letter-spacing:.12em; text-transform:uppercase; color:rgba(255,255,255,.65); }
.credits__name{ font-size:clamp(20px, 1.7vw, 28px); line-height:1.15; }
.credits__at{ font-size:14px; color:inherit; text-decoration:none; border-bottom:1px solid rgba(255,255,255,.4); }
.credits__at:hover{ border-bottom-color:currentColor; }

.foot__row{ display:flex; flex-wrap:wrap; justify-content:space-between; gap:12px 30px;
  padding-top:22px; border-top:1px solid rgba(255,255,255,.3); font-size:14px; }

@media (min-width:900px){
  .p-closing{ width:100vw; padding:0 6vw; grid-template-columns:1fr 1fr; gap:6vw; align-items:center; }
  .p-foot{ width:100vw; min-height:0; padding:12vh 6vw 8vh; }
  .foot__mq{ margin:0 -6vw; }
}
@media (max-width:760px){
  .credits__list{ grid-template-columns:1fr 1fr; }
}

}


@layer motion {
/* ================================================================
   motion.css — enter AND exit. Last layer, so it always wins.

   reveal.js gives every [data-reveal] a data-state:
     below  not reached yet     → waiting underneath
     in     in the middle band  → visible
     above  scrolled past       → has left upward
   Scroll back and it all plays in reverse.

   Arrivals use --out (fast, long soft landing) with a stagger.
   Departures use --in (slow start, then gone), shorter, barely staggered.

   Three systems share an element without fighting because each owns
   its own CSS property:
     translate / scale / clip-path  → this file
     transform (inline, per frame)  → scroll-fx.js, parallax.js, hscroll.js
   Never list `transform` in a transition here.
   ================================================================ */

[data-reveal]{ --step:70ms; }


/* ---------- words — each word rises out of its own mask ---------- */
.w{
  display:inline-block; vertical-align:top; overflow:hidden;
  /* room for ascenders, descenders and Bodoni's italic overhang */
  padding:.14em .12em .24em 0; margin:-.14em -.12em -.24em 0;
}
.w__i{ display:inline-block; transition:translate .6s var(--in) calc(var(--i, 0) * 18ms); }
[data-reveal="words"][data-state="in"] .w__i{
  transition:translate 1.25s var(--out) calc(var(--d, 0ms) + var(--i, 0) * var(--step));
}
.js:not(.no-reveal) [data-reveal="words"]:not([data-split]){ opacity:0; }
.js:not(.no-reveal) [data-reveal="words"]:not([data-state="in"]) .w__i{ translate:0 1.3em; }
.js:not(.no-reveal) [data-reveal="words"][data-state="above"] .w__i{ translate:0 -1.3em; }


/* ---------- fade — small text ---------- */
[data-reveal="fade"]{ transition:opacity .45s var(--in), translate .6s var(--in); }
[data-reveal="fade"][data-state="in"]{
  transition:opacity 1s var(--out) var(--d, 0ms), translate 1.2s var(--out) var(--d, 0ms);
}
.js:not(.no-reveal) [data-reveal="fade"]:not([data-state="in"]){ opacity:0; translate:0 32px; }
.js:not(.no-reveal) [data-reveal="fade"][data-state="above"]{ translate:0 -32px; }


/* ---------- clip — photos wipe open from below, close upward ---------- */
[data-reveal="clip"]{ clip-path:inset(0); transition:clip-path .75s var(--inout); }
[data-reveal="clip"] > *{ transition:scale .9s var(--inout); }
[data-reveal="clip"][data-state="in"]{ transition:clip-path 1.35s var(--inout) var(--d, 0ms); }
[data-reveal="clip"][data-state="in"] > *{ transition:scale 1.8s var(--out) var(--d, 0ms); }
.js:not(.no-reveal) [data-reveal="clip"]:not([data-state="in"]){ clip-path:inset(100% 0 0 0); }
.js:not(.no-reveal) [data-reveal="clip"][data-state="above"]{ clip-path:inset(0 0 100% 0); }
.js:not(.no-reveal) [data-reveal="clip"]:not([data-state="in"]) > *{ scale:1.3; }

/* the Then/Now frame arrives quicker than the other photos */
.salam__flip [data-reveal="clip"][data-state="in"]{ transition-duration:1.2s; }
.salam__flip [data-reveal="clip"][data-state="in"] > *{ transition-duration:1.1s; }


/* ---------- line — hairlines draw in from the left, leave to the right ---------- */
[data-reveal="line"]{ display:block; height:1px; background:var(--ink);
  transform-origin:left; transition:scale .6s var(--in); }
[data-reveal="line"][data-state="in"]{ transition:scale 1.4s var(--out) var(--d, 0ms); }
.js:not(.no-reveal) [data-reveal="line"]:not([data-state="in"]){ scale:0 1; }
.js:not(.no-reveal) [data-reveal="line"][data-state="above"]{ transform-origin:right; }


/* ---------- bloom — the ornaments grow up from the foot of their stems, with the scroll ----------
   bloom.js writes --bloom, 0 → 1, as the ornament comes across the screen (data-bloom="a, z":
   that stretch of its trip, default .02 → .4), so it follows the scroll both ways. A soft-edged
   mask rises through it — the mask is 2.4× its height, opaque below and clear above — while it
   grows a little from its base: leaves first, flowers last. Without it, --bloom stays 1. */
[data-bloom]{
  -webkit-mask-image:linear-gradient(to top, #000 42%, transparent 58%); mask-image:linear-gradient(to top, #000 42%, transparent 58%);
  -webkit-mask-size:100% 240%; mask-size:100% 240%; -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:0 calc(var(--bloom, 1) * 100%); mask-position:0 calc(var(--bloom, 1) * 100%);
  transform-origin:50% 100%; scale:calc(.94 + var(--bloom, 1) * .06);
}


/* ---------- highlight — grey → black as you read (highlight.js writes --p) ---------- */
.hw{ opacity:clamp(.14, calc((var(--p, 0) * (var(--n, 1) + 6) - var(--i, 0)) / 6), 1); }
.no-reveal .hw{ opacity:1; }


/* ---------- reduced motion: static, complete, readable ---------- */
@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }
  *, *::before, *::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; transition-delay:0s !important;
  }
  #loader{ display:none !important; }
}

}

