/* Page: the full artwork is the coordinate system for map, RSVP block, and dots. */
/* The typed face every hand-lettered element uses. Swap the first name here to
   change the whole invitation; the fallbacks keep it typewriter-shaped while
   the webfont loads or if it fails. */
:root {
  --hand-font: 'Special Elite', 'Courier New', Courier, monospace;
}
* { box-sizing: border-box; }
html, body {
  margin: 0;
  padding: 0;
  background: #8a7355;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.page {
  position: relative;
  width: 100%;
  display: block;
}
.page-bg {
  width: 100%;
  height: auto;
  display: block;
}

/* Venue map: fills the blank slot the artwork leaves between the "please click
   the map" caption and the shuttle paragraph, left of the gift QR codes -
   artwork (182,6036), 545 wide. top/left are % of the full artwork; increase =
   down/right, decrease = up/left. Height follows the image. */
.venue-map-link {
  position: absolute;
  top: 69.459149%;
  left: 12.133333%;
  width: 36.333333%;
  z-index: 4;
  display: block;
}
.venue-map {
  width: 100%;
  height: auto;
  display: block;
}

/* RSVP reference box: A (280,7710), B (1260,8540) on the 1500x8690 artwork.
   Dot centers define the box; moving a dot does not move the form.
   Increase A top/left to shrink inward; decrease B top/left to shrink inward.
   Reverse those changes to expand the box. */
.rsvp-guide-dot {
  display: none;
  position: absolute;
  width: 16px;
  height: 16px;
  border: 2px solid white;
  border-radius: 50%;
  background: #ff2020;
  transform: translate(-50%, -50%);
  z-index: 20;
  pointer-events: none;
}
.rsvp-guide-dot::after {
  content: attr(data-label);
  position: absolute;
  left: 20px;
  top: -5px;
  padding: 3px 6px;
  border-radius: 3px;
  background: #b00000;
  color: white;
  font: bold 13px/1.3 sans-serif;
  white-space: nowrap;
}
.rsvp-guide-dot--start { left: 18.666667%; top: 87.813579%; }
.rsvp-guide-dot--end { left: 84%; top: 98.273878%; }
.rsvp-guide-dot--end::after { left: auto; right: 20px; }

/* Video reference box in the Kodak film frame: A (209,1335), B (1306,1902) on
   the 1500x8690 artwork - inside the black border, which is rotated ~0.3deg, so
   the box is the largest upright rectangle that clears it. Same dot rules as
   the RSVP markers; retired after calibration, same as the RSVP pair. */
.video-guide-dot { display: none; }
.video-guide-dot--start { left: 13.933333%; top: 15.362486%; }
.video-guide-dot--end { left: 87.066667%; top: 21.887227%; }
.video-guide-dot--end::after { left: auto; right: 20px; }
.video-guide-box {
  position: absolute;
  left: 13.933333%;
  top: 15.362486%;
  width: 73.133333%;
  height: 6.524741%;
  border: 2px dashed #ff2020;
  z-index: 19;
  pointer-events: none;
  display: none;
}

/* The video fills the A-B box above; the box is wider than 16:9, so the video is
   cropped top and bottom to cover it. Plays itself when scrolled into the middle
   of the screen (see script.js). */
.film-video {
  position: absolute;
  left: 13.933333%;
  top: 15.362486%;
  width: 73.133333%;
  height: 6.524741%;
  object-fit: cover;
  display: block;
  z-index: 4;
}

/* The Kodak frame as its own layer above the video, so the video can run under
   its border. Cut out of the design at 2x, so it is 1:1 with the artwork: its
   top-left sits at artwork (63,1004) and it is 1376 wide; height follows the
   image. Width scales the whole frame; left/top move it. */
.film-frame {
  position: absolute;
  left: 4.2%;
  top: 11.553510%;
  width: 91.733333%;
  z-index: 5;
  display: block;
  pointer-events: none;
}
.film-frame img {
  width: 100%;
  height: auto;
  display: block;
}
/* Static game doodles (parked interactive games, see AGENTS.md). Tilted so
   they read as drawn on the paper. Tic-tac-toe: artwork (178,4335), 240 wide,
   left of the gift-bearer names and above the tree photo. Dino: (655,4830),
   500 wide, with its score cropped separately above the dinosaur.
   Rotation is about each box's centre. */
.game-doodle {
  position: absolute;
  z-index: 2;
  display: block;
  pointer-events: none;
}
.game-doodle img { width: 100%; height: auto; display: block; }
.game-doodle--ttt { left: 11.866667%; top: 49.884925%; width: 16%; transform: rotate(-4deg); }
.game-doodle--dino { left: 43.666667%; top: 55.581128%; width: 33.333333%; transform: rotate(2deg); }
/* Reuse the original pencil art: hide only the old score in the scene,
   then move a score-only crop left above the dino, at the old score height. */
.dino-scene { display: block; clip-path: polygon(0 0, 80% 0, 80% 25%, 100% 25%, 100% 100%, 0 100%); }
.dino-score { position: absolute; inset: 0; display: block; clip-path: inset(12% 1% 77% 85%); transform: translate(-80%, 8%); }
/* Frame reference dots: A is the PNG's top-left corner, B its bottom-right -
   (63,1004) and (1439,2150) on the artwork. Retired after calibration. */
.frame-guide-dot { display: none; background: #2060ff; }
.frame-guide-dot::after { background: #103080; }
.frame-guide-dot--start { left: 4.2%; top: 11.553510%; }
.frame-guide-dot--end { left: 95.933333%; top: 24.741082%; }
.frame-guide-dot--end::after { left: auto; right: 20px; }

/* RSVP parent: moves lookup + party list together. Still artwork-relative, not the A-B box.
   Increase top/left = down/right toward B; decrease = up/left toward A.
   Width/height resize the parent and change its children's percentage positions. */
.rsvp-block {
  position: absolute;
  top: 87.813579%;
  left: 18.666667%;
  width: 65.333333%;
  height: 10.460299%;
  z-index: 4;
}

/* Partition 1 - lookup: input + feedback; top/left are relative to .rsvp-block.
   Increase top = both move down; decrease = up. Increase left = right; decrease = left.
   Height controls this partition's scale; it currently occupies the first 33% of the block. */
.guest-lookup {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 18.261826%;
}

/* Input: top/height are % of .guest-lookup height; left/width are % of its width.
   Increase top/left = down/right within the group; decrease = up/left.
   Increase width/height = larger input area, which also affects text centering. */
.guest-name-input {
  position: absolute;
  left: 17.652%;
  width: 60.918%;
  top: 0;
  height: 40.361%;
  border: none;
  outline: none;
  background: transparent;
  text-align: center;
  font-family: var(--hand-font);
  font-size: clamp(17.6px, 3.3vw, 30.8px);
  color: #014b10;
}
.guest-name-input::placeholder {
  color: rgba(26,16,6,0.55);
  font-family: var(--hand-font);
}

/* Feedback: same lookup-relative positioning as the input. Increase top = farther down.
   Width controls wrapping; longer messages can extend below this partition. */
.guest-status {
  position: absolute;
  left: 2.041%;
  width: 91.837%;
  top: 51.163%;
  text-align: center;
  color: #221408;
  font-family: var(--hand-font);
  font-size: clamp(17.9px, 2.3vw, 25.6px);
  line-height: 1.45;
}
.guest-status .found-name,
.guest-status .seat-note {
  font-weight: 700;
  color: #0b0602;
}

/* Partition 2 - party picker: top/left are % of .rsvp-block, not .guest-lookup or A-B.
   Increase top/left = down/right toward B; decrease = up/left toward A.
   Increase gap = more space between rows; decrease = tighter rows. Height grows with content.
   A list too tall for the room between its top and B is scaled by script.js;
   the origin keeps it pinned to its top edge and centred while it shrinks. */
.party-list {
  position: absolute;
  /* Shifted 18 artwork px left of A-B so the list centres on the paper
     (x 205-1300 in this band) rather than on the box, whose right edge sits
     closer to the paper's edge than its left. Still 100% of the box wide. */
  left: -1.836735%;
  width: 100%;
  top: 21.770343%;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: clamp(7.7px, 1.08vw, 15.39px);
  transform-origin: top center;
}

/* Guest rows: normal layout inside the picker. Larger gap separates names and controls;
   larger name width reserves more of each row for names. */
.party-row {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1.3vw, 18px);
  cursor: pointer;
}
.party-row--open,
.party-row.is-locked,
.party-row.is-blocked { cursor: default; }
.party-row .guest-name {
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--hand-font);
  font-size: clamp(20.57px, 2.6vw, 29.04px);
  color: #1a1006;
}
.party-row .guest-role {
  margin-left: 6px;
  font-size: clamp(13.31px, 1.45vw, 16.94px);
  color: #0d1a07;
}
.party-controls {
  display: flex;
  align-items: center;
  flex: none;
}

/* Paper checkbox: 44px box in the layout around an ink-outlined square; independent of A-B.
   A named guest's whole row is the label, so the name is a tap target too. */
.attendance-checkbox {
  position: relative;
  display: inline-block;
  width: 44px;
  height: 44px;
  flex: none;
}
/* The invisible input bleeds past the 44px box into the gaps around it without
   taking layout room. Vertical bleed must stay under half the row gap (~10px at
   the 980px layout) or neighbouring rows would overlap. */
.attendance-checkbox input {
  position: absolute;
  inset: -4px -10px;
  width: auto;
  height: auto;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}
.attendance-box {
  position: absolute;
  left: 4.5px;
  top: 3.5px;
  width: 35px;
  height: 35px;
  border: 2px solid #795b39;
  border-radius: 3px 5px 3px 4px;
  background: #f2e3c5;
  box-shadow: inset 0 0 4px rgba(121,91,57,0.12), 1px 2px 0 rgba(82,57,30,0.12);
  transform: rotate(-3deg);
  pointer-events: none;
}
.attendance-box::after {
  content: '';
  position: absolute;
  left: 10px;
  top: 1px;
  width: 11px;
  height: 21px;
  border: solid #36532a;
  border-width: 0 3px 3px 0;
  transform: rotate(42deg);
  opacity: 0;
}
.attendance-checkbox input:checked + .attendance-box {
  background: #e4e5c6;
  border-color: #536342;
}
.attendance-checkbox input:checked + .attendance-box::after {
  opacity: 1;
}
.attendance-checkbox input:focus-visible + .attendance-box {
  outline: 2px solid #36532a;
  outline-offset: 4px;
}

/* Selection states: fixed seats stay checked; unavailable choices are faded. */
/* Open seats: blank lines a selected family fills in themselves. Same row shape as a
   named guest - the input replaces the name span and takes the measured column width,
   so its line sits under the names above it. Drawn from the party's existing Seats. */
.party-row--open .guest-name-open {
  min-width: 0;
  font-family: var(--hand-font);
  font-size: clamp(20.57px, 2.6vw, 29.04px);
  color: #1a1006;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(74,58,38,0.4);
  outline: none;
  padding: 0 2px 1px;
}
.party-row--open .guest-name-open::placeholder {
  color: rgba(26,16,6,0.5);
}
.party-row--open .guest-name-open:focus {
  border-bottom-color: #4a3a26;
}

.party-row.is-locked .attendance-checkbox { opacity: 0.75; }
.party-row.is-blocked { opacity: 0.45; }
.attendance-checkbox input:disabled { cursor: not-allowed; }

/* Not-attending block: separated from the guest rows by a ruled line, because it
   answers for the whole party instead of for one seat. Its width is set at
   runtime to the centred content width, same as the confirm block. */
.rsvp-decline {
  grid-column: 1 / -1;
  margin-top: clamp(6px, 0.9vw, 12px);
}
.decline-rule {
  height: 0;
  border-top: 1px solid rgba(74,58,38,0.45);
  margin-bottom: clamp(4px, 0.7vw, 10px);
  transform: rotate(-0.3deg);
}
.decline-row {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1.3vw, 18px);
  cursor: pointer;
}
/* The checkbox follows the label at the guest rows' gap, like a name and its
   box, rather than being pushed out to the far column. */
.decline-text {
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--hand-font);
  font-size: clamp(18.15px, 2.3vw, 25.6px);
  color: #221408;
}
.rsvp-decline.is-on .decline-text { color: #7a2718; }
.rsvp-decline.is-on .attendance-checkbox input:checked + .attendance-box {
  background: #f0d9cf;
  border-color: #7a2718;
}
.rsvp-decline.is-on .attendance-checkbox input:checked + .attendance-box::after {
  border-color: #7a2718;
}

/* Declining fades the guest rows it has just locked. */
.party-list.is-declining .party-row { opacity: 0.4; }
.party-list.is-declining .guest-name-open { cursor: not-allowed; }

/* Confirm block: last item in the picker, spans both columns. Its width is set at
   runtime to the centred content width, so the button sits under the guest columns
   rather than the full list box. */
.rsvp-confirm {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4em;
  margin-top: clamp(10px, 1.6vw, 22px);
}

/* Contact number: one line for the whole party, under the guest rows and above
   the rule that separates them from the not-attending row. Drawn as a blank
   line on the paper like a write-in name, not a web field. Spans the list, so
   its width is set at runtime to the centred content width. */
.rsvp-contact {
  grid-column: 1 / -1;
  margin-top: clamp(6px, 0.9vw, 12px);
}
.contact-row {
  display: flex;
  align-items: baseline;
  gap: clamp(8px, 1.1vw, 14px);
  cursor: text;
}
.contact-label {
  flex: none;
  font-family: var(--hand-font);
  font-size: clamp(15.4px, 1.95vw, 21.5px);
  color: #221408;
}
.contact-input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--hand-font);
  font-size: clamp(17.6px, 2.2vw, 25px);
  color: #1a1006;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(74,58,38,0.4);
  outline: none;
  padding: 0 2px 1px;
}
.contact-input::placeholder { color: rgba(26,16,6,0.5); }
.contact-input:focus { border-bottom-color: #4a3a26; }
.contact-input:read-only { border-bottom-style: dashed; }

/* Styled as one of the artwork's brown tape labels, not a web button. */
.confirm-button {
  font-family: var(--hand-font);
  font-size: clamp(17.6px, 2.2vw, 25px);
  color: #fdf3e2;
  background: #8a5a33;
  border: none;
  border-radius: 2px;
  padding: 0.28em 1.5em 0.34em;
  transform: rotate(-1.1deg);
  box-shadow: 0 2px 5px rgba(60,40,20,0.28);
  cursor: pointer;
  letter-spacing: 0.02em;
}
.confirm-button:hover:not(:disabled) {
  background: #7a4d2a;
}
.confirm-button:active:not(:disabled) {
  transform: rotate(-1.1deg) translateY(1px);
  box-shadow: 0 1px 3px rgba(60,40,20,0.3);
}
.confirm-button:focus-visible {
  outline: 2px solid #4a3a26;
  outline-offset: 3px;
}
.confirm-button:disabled {
  opacity: 0.55;
  cursor: default;
}

/* Shown only after a reply is sent - families are allowed to change their answer.
   It keeps its space before then, so sending does not resize a list that is
   already scaled to fit inside the A-B box; the ruled line rides on the button
   itself, so it appears and hides with it and needs no separate element.
   Sized between the note and the send button: clearly an action, still second
   to it. Spans the confirm block, whose width is set at runtime. */
.confirm-reopen[hidden] {
  display: block;
  visibility: hidden;
}
.confirm-reopen {
  position: relative;
  display: block;
  align-self: stretch;
  font-family: var(--hand-font);
  font-size: clamp(15.4px, 1.95vw, 21.5px);
  color: #221408;
  background: none;
  border: none;
  padding: clamp(7px, 1.1vw, 15px) 4px 0;
  text-decoration: underline;
  text-underline-offset: 0.16em;
  cursor: pointer;
}
/* Same rule as the one over the not-attending row, tilt included. */
.confirm-reopen::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-top: 1px solid rgba(74,58,38,0.45);
  transform: rotate(-0.3deg);
}
.confirm-reopen:hover { color: #1a1006; }
.confirm-reopen:focus-visible { outline: 2px solid #4a3a26; outline-offset: 2px; }

.confirm-note {
  font-family: var(--hand-font);
  font-size: clamp(12.5px, 1.5vw, 17px);
  color: #221408;
  text-align: center;
  min-height: 1.2em;
}
.confirm-note.is-error {
  color: #7a2718;
}

/* Two-column guest list; larger column-gap separates the columns. */
.party-list.is-two-column {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(24px, 4vw, 56px);
}
.party-list.is-two-column .guest-role {
  display: block;
  margin-left: 0;
}

/* Scroll progress: fixed to the screen, outside A-B. Increase right = farther left;
   increase top = down. Fill height is updated by script.js. */
.progress-track {
  position: fixed;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 140px;
  background: rgba(122,106,82,0.25);
  border-radius: 2px;
  z-index: 10;
}
.progress-fill {
  width: 100%;
  background: #7a6a52;
  border-radius: 2px;
  height: 0%;
}

/* Tap-to-open entrance. Two opaque halves hide the page; scroll-3d.js draws
   the rolls on a canvas above them and moves the halves apart as it plays.
   Without 3D (or with reduced motion) the whole cover fades instead. */
html.cover-pending, .cover-pending body { overflow: hidden; }
.invitation-cover[hidden], .music-toggle[hidden], .preview-controls[hidden] { display: none; }
/* The head script adds cover-pending before the body paints, so the cover is
   up from the first frame instead of waiting for script.js at the end of the
   body (which let the page flash through on a cold load). No JS, no class,
   no cover. */
html.cover-pending .invitation-cover[hidden] { display: block; }
.invitation-cover { position: fixed; inset: 0; z-index: 100; }
.scroll-open {
  position: absolute; inset: 0; width: 100%; height: 100%; padding: 0;
  border: 0; background: transparent; color: #f8eddb; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* Split at 35% from the top to match SEAM in scroll-3d.js. */
.cover-half { position: absolute; left: 0; width: 100%; background: #8a7355; }
.cover-half--top { top: 0; height: 35%; }
.cover-half--bottom { bottom: 0; height: 65%; }
.scroll-3d-canvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.scroll-hint, .scroll-loading {
  position: absolute; z-index: 1; top: calc(35% + 16vw); left: 0; width: 100%;
  font-family: var(--hand-font); font-size: 2.8vw; letter-spacing: .04em;
  transition: opacity .18s ease;
}
.scroll-hint { opacity: 0; }
.is-ready .scroll-hint { opacity: 1; }
/* CSS-only loader in the hint's place while the 3D rolls load; it swaps to
   "tap to open" on is-ready, which every path (built, failed, 10s cap) sets. */
.scroll-loading i { font-style: normal; animation: loading-dot 1.2s ease-in-out infinite; }
.scroll-loading i:nth-child(2) { animation-delay: .2s; }
.scroll-loading i:nth-child(3) { animation-delay: .4s; }
@keyframes loading-dot { 0%, 80%, 100% { opacity: .2; } 40% { opacity: 1; } }
.is-ready .scroll-loading { opacity: 0; pointer-events: none; }
.scroll-open:focus-visible { outline: none; }
.scroll-open:focus-visible .scroll-hint { text-decoration: underline; text-underline-offset: .4em; }
.invitation-cover.is-opening { pointer-events: none; }
.is-opening .scroll-hint { opacity: 0; }
.invitation-cover.is-fading { opacity: 0; transition: opacity .3s ease; }
.music-toggle {
  position: fixed; bottom: 3vw; right: 3vw; z-index: 20;
  border: 0; padding: 1.4vw 2vw; color: #fff4dd; background: #8a5a33;
  font: 2.2vw var(--hand-font); transform: rotate(2deg); cursor: pointer;
  box-shadow: 0 .2vw .5vw #39271940;
}
/* Review panel for the entrance; shown only with ?preview in the URL. */
.preview-controls {
  position: fixed; z-index: 102; top: 2vw; left: 2vw; padding: 1vw;
  background: #f2e7d0; font: 16px sans-serif; border-radius: 4px;
}

/* Monogram box: artwork (690, 335), width 280; above the printed wedding date. */
.hero-monogram { position:absolute;left:46%;top:5.097813%;width:18.666667%;aspect-ratio:700/790;z-index:2;pointer-events:none;filter:drop-shadow(0 1px 1px rgba(20,28,19,.9)) drop-shadow(0 0 3px rgba(20,28,19,.55)); }
.hero-monogram img,.hero-monogram svg {display:block;width:100%;height:100%;}
.hero-monogram [hidden] {display:none;}

/* Original Agrandir Grand date: artwork box (525,667), 610×70. */
.hero-wedding-date {position:absolute;left:35%;top:8.342922%;width:40.666667%;aspect-ratio:610/70;z-index:2;pointer-events:none;}
.hero-wedding-date svg {display:block;width:100%;height:100%;overflow:visible;}

/* Headline's top line over the green paper: artwork (472,865), 618.6 wide.
   "it's official" replaces "you are invited" for links without a valid code. */
.hero-kicker {position:absolute;left:31.466667%;top:9.953970%;width:41.24%;aspect-ratio:300/40;z-index:2;pointer-events:none;}
.hero-kicker img {display:block;width:100%;height:100%;}

/* Gift-bearers polaroid: artwork (-167,4568), 652 wide, hanging off the left edge.
   The closed photo is aligned to the open one's white frame (scale .992, −2.5°). */
.gift-polaroid {position:absolute;left:-11.133333%;top:52.566168%;width:43.466667%;aspect-ratio:1;z-index:1;pointer-events:none;}
.gift-polaroid img {display:block;width:100%;height:auto;}
.gift-polaroid--closed {position:absolute;left:.115630%;top:.087719%;width:97.775120%;transform:rotate(-2.5deg);}

.page[data-rsvp-state="closed"] .hero-kicker--open,
.page:not([data-rsvp-state="closed"]) .hero-kicker--closed,
.page[data-rsvp-state="closed"] .gift-polaroid--open,
.page:not([data-rsvp-state="closed"]) .gift-polaroid--closed {display:none;}

/* RSVP artwork keeps the original 1500×8690 coordinate system and A–B box.
   The open PNG's printed line (445,360) lands at artwork (476,7698). */
.rsvp-open-art { position:absolute; left:2.066667%; top:84.441887%; width:90%; z-index:3; pointer-events:none; }
.rsvp-open-art img, .rsvp-closed-art img { display:block; width:100%; height:auto; }
/* Due line, per party (rsvp-status `due`): was baked into open.png at artwork x 494–1055, cap top 7507, baseline 7534. */
.rsvp-due { position:absolute; left:51.633333%; top:86.398%; transform:translateX(-50%); margin:0; z-index:4; pointer-events:none;
  white-space:nowrap; font-family:var(--hand-font); font-size:2.6vw; line-height:1; color:#080604; }
/* Closed state: roll at artwork (325,7235) width 990; the header box (657,7550) is drawn over the wax seal's lower edge. */
.rsvp-closed-art { position:absolute; left:21.666667%; top:83.256617%; width:66%; z-index:3; }
.rsvp-closed-art .rsvp-closed-header { position:absolute; z-index:1; left:50%; top:47.727273%; width:32.929293%; transform:translateX(-50%); }
/* Expired: the party's reply (rsvp-status `reply`) printed under the roll. */
.rsvp-closed-message { position:absolute; z-index:1; left:50%; top:88%; width:76%; transform:translateX(-50%); margin:0;
  text-align:center; font-family:var(--hand-font); font-size:2vw; line-height:1.4; color:#080604; }
/* No-code/invalid-code ending: mounted photo at artwork (250,7445), width 1000 — centred on the paper,
   ~77px under the timeline's "2026" (ink ends y 7368) and ~80px above the torn edge (y ~8115 in the compact page). */
.proposal-art { display:none; position:absolute; left:16.666667%; top:85.673188%; width:66.666667%; z-index:3; }
.page[data-rsvp-state="closed"] .proposal-art { display:block; }
.page[data-rsvp-state="closed"] .rsvp-closed-art { display:none; }
.proposal-photo { position:relative; padding:1.9%; background:linear-gradient(120deg,#f6f0e3,#e9e0d4 78%,#ddd2c5);
  border:1px solid rgba(110,83,54,.25); border-radius:.15vw; transform:rotate(-2.5deg);
  box-shadow:0 .13vw .15vw rgba(57,38,20,.48), .25vw .5vw .6vw rgba(57,38,20,.28), .5vw .9vw 1.5vw rgba(57,38,20,.13), inset 0 .09vw .08vw rgba(255,255,255,.85); }
.proposal-video { display:block; width:100%; height:auto; aspect-ratio:16/9; object-fit:contain;
  background:#27261e; outline:1px solid rgba(91,76,57,.25); }
/* Album corners sit above the print; each casts its own small contact shadow. */
.proposal-corner { position:absolute; width:6%; aspect-ratio:1; pointer-events:none;
  filter:drop-shadow(.12vw .16vw .12vw rgba(34,24,15,.5)); }
.proposal-corner::before { content:""; display:block; width:100%; height:100%;
  clip-path:polygon(8% 0,100% 0,100% 16%,16% 100%,0 100%,0 8%);
  background:linear-gradient(135deg,#4b4030 0%,#29251d 30%,#24221c 58%,#514534 60%,#30291f 65%);
  border-radius:12% 4% 0 4%; box-shadow:inset .1vw .1vw .15vw rgba(255,234,192,.15); }
.proposal-corner--tl { top:-.5%; left:-.4%; }
.proposal-corner--tr { top:-.5%; right:-.4%; transform:rotate(90deg); }
.proposal-corner--bl { bottom:-.5%; left:-.4%; transform:rotate(-90deg); }
.proposal-corner--br { bottom:-.5%; right:-.4%; transform:rotate(180deg); }
.rsvp-paper-ending { display:none; position:absolute; inset:0; pointer-events:none; }
.rsvp-paper-ending img { width:100%; height:auto; display:block; clip-path:inset(98.388953% 0 0); transform:translateY(-5.75374%); }
.page.rsvp-compact { clip-path:inset(0 0 5.75374%); }
.rsvp-compact .page-bg { clip-path:inset(0 0 6.904488%); }
.rsvp-compact .rsvp-paper-ending { display:block; }
.page:not([data-rsvp-state="open"]) .rsvp-open-art,
.page:not([data-rsvp-state="open"]) .rsvp-due,
.page:not([data-rsvp-state="open"]) .rsvp-block,
.page[data-rsvp-state="open"] .rsvp-closed-art,
.page:not([data-rsvp-state="expired"]) .rsvp-closed-header { display:none; }
.rsvp-state-note { position:absolute; top:92%; left:30%; width:58%; text-align:center; z-index:5; font-family:var(--hand-font); font-size:2vw; }
.rsvp-state-note button { font:inherit; color:inherit; cursor:pointer; }

.page-shell.is-compact { height:546vw; overflow:clip; }
.rsvp-paper-ending img { mask-image:linear-gradient(to bottom, transparent 98.388953%, black 98.849252%); }

.page-shell { overflow:clip; }
