/* DASMA VIP — public invitation. Colours and fonts arrive as CSS variables set inline by the server
   from the wedding's theme, so this file only describes structure, rhythm and motion. */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--text); font-family: var(--body); font-size: 16px; line-height: 1.6;
  -webkit-font-smoothing: antialiased; min-height: 100dvh;
}
img { max-width: 100%; display: block; }
a { color: var(--accent); }
h1, h2, h3 { font-family: var(--display); font-weight: 500; line-height: 1.1; margin: 0; text-wrap: balance; }
p { margin: 0; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
[hidden] { display: none !important; }
.muted { color: var(--muted); }
/* Small text needs more contrast than the themes' display colours give. Blending towards --text
   works on light and dark themes alike. Browsers without color-mix keep the original colours. */
:root { --ink-muted: var(--muted); --ink-accent: var(--accent); --ease-out: cubic-bezier(.16, 1, .3, 1); --ease-draw: cubic-bezier(.65, 0, .35, 1); --ease-settle: cubic-bezier(.33, 1, .68, 1); --ease-back: cubic-bezier(.34, 1.56, .64, 1); }
@supports (color: color-mix(in srgb, red 50%, blue)) {
  :root {
    --ink-muted: color-mix(in srgb, var(--muted) 62%, var(--text));
    --ink-accent: color-mix(in srgb, var(--accent) 58%, var(--text));
  }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }
.vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
/* Moving between the invitation and its photo wall, or switching language, cross-fades instead of
   flashing white. Browsers without cross-document view transitions simply ignore this. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  ::view-transition-old(root) { animation-duration: .35s; }
  ::view-transition-new(root) { animation-duration: .55s; }
  .actbar { view-transition-name: actbar; }
  .music-btn { view-transition-name: music; }
}

/* ---------- bars ---------- */
.preview-bar { position: sticky; top: 0; z-index: 40; background: #1F1B16; color: #fff; font-family: system-ui, sans-serif; font-size: .8rem; padding: 10px 16px; display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; justify-content: center; text-align: center; }
.preview-bar a { color: #F1E6CC; font-weight: 700; }
.preview-bar .btn-mini { background: #B8923E; color: #fff; padding: 6px 12px; border-radius: 999px; text-decoration: none; font-weight: 700; }
.demo-sel { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; }
.demo-sel select { font: inherit; font-size: .8rem; border-radius: 8px; border: 1px solid #5a534a; background: #2b2622; color: #fff; padding: 4px 8px; }

/* ---------- opening moment ----------
   The seal covers the whole invitation, and only JavaScript can take it away. So it becomes an
   overlay ONLY once js-on.js has proved scripts run here; otherwise it stays an ordinary card at
   the top of the page and the guest scrolls straight to the date, the venue and the RSVP form.
   Second net: if js-on.js ran but invite.js never did (dropped request, error), nothing would arm
   the seal, so after six seconds it fades itself out. The !important beats the global
   prefers-reduced-motion override above — a guest who cannot open the invitation is not a
   motion preference, it is a broken page. */
.intro { position: relative; background: var(--bg); color: var(--text); display: grid; place-items: center; padding: 24px; }
html.js-ready .intro { position: fixed; inset: 0; z-index: 60; }
html.js-ready:not(.intro-armed) .intro { animation: introFailsafe 0s linear 6s forwards !important; }
@keyframes introFailsafe { to { opacity: 0; visibility: hidden; pointer-events: none; } }
.intro::before { content: ""; position: absolute; inset: 0; background: radial-gradient(60% 50% at 50% 40%, color-mix(in srgb, var(--accent) 16%, transparent) 0%, transparent 70%); pointer-events: none; }
.intro-card { position: relative; text-align: center; max-width: 420px; }
/* The unveiling: the seal, the rule drawing itself, the names letter by letter, then the date and
   the button. Plain CSS on purpose: it starts with the first paint and needs no script. `backwards`
   hides a piece only until its turn; once it has played, the ordinary styles rule again, so the
   tap (below) can lift the whole card away whether or not the entrance has finished. */
.intro-card > * { animation: unveil 1.1s var(--ease-out) backwards; }
.intro-card > .intro-seal { animation: sealIn 1.4s var(--ease-out) .1s backwards; }
.intro-rule { width: 84px; height: 1px; margin: 0 auto 22px; background: var(--accent); opacity: .75; }
.intro-card > .intro-rule { animation: ruleIn 1.2s var(--ease-draw) .5s backwards; }
.intro-card > .intro-names { animation: none; }
.intro-names .w { display: inline-block; white-space: nowrap; overflow: hidden; vertical-align: top; padding: .16em .05em; margin: -.16em -.05em; }
/* 38 ms a letter, but never more than 600 ms for the whole line (--n is the number of letters) */
.intro-names .ch { display: inline-block; animation: letterIn 1.15s var(--ease-out) calc(.85s + var(--i) * min(38ms, 600ms / var(--n, 12))) backwards; }
.intro-names i.ch { animation-name: ampIn; }
.intro-card > .intro-date { animation-delay: 1.45s; }
.intro-card > .intro-open { animation-delay: 1.65s; }
.intro-card > .intro-hint { animation-delay: 1.9s; }
@keyframes unveil { from { opacity: 0; transform: translateY(12px); } }
@keyframes sealIn { from { opacity: 0; transform: scale(.88); } }
@keyframes ruleIn { from { opacity: 0; transform: scaleX(0); } }
@keyframes letterIn { from { transform: translateY(115%); } }
@keyframes ampIn { from { opacity: 0; transform: scale(.7); } }
.intro-seal { width: 128px; height: 128px; margin: 0 auto 24px; border-radius: 50%; border: 1.5px solid var(--accent); background: var(--surface); color: var(--accent); display: grid; place-items: center; font-family: var(--display); font-size: 2.6rem; letter-spacing: .06em; cursor: pointer; position: relative; box-shadow: 0 24px 50px -30px rgba(0,0,0,.5); transition: transform .3s ease; }
.intro-seal::after { content: ""; position: absolute; inset: 6px; border-radius: 50%; border: 1px solid var(--accent2); }
.intro-seal::before { content: ""; position: absolute; inset: -14px; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); opacity: 0; animation: pulse 2.4s ease-out 2.4s infinite; }
.intro-seal:hover { transform: scale(1.04); }
.intro-names { font-family: var(--display); font-size: clamp(1.9rem, 7vw, 2.6rem); line-height: 1.15; }
.intro-names i { font-style: italic; color: var(--accent); }
.intro-date { margin-top: 10px; font-size: .78rem; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }
/* A personal link is addressed above the seal: the couple's own words for this family, or a small
   "Ftesë për" over the guest's name. Long texts step down a size so the card still fits a phone. */
.intro-for { margin: 0 auto 32px; max-width: 24ch; font-family: var(--display); font-size: clamp(1.3rem, 5.6vw, 1.7rem); line-height: 1.25; color: var(--text); overflow-wrap: break-word; }
.intro-for span { display: block; margin-bottom: 8px; font-family: var(--body); font-size: .72rem; letter-spacing: .26em; text-transform: uppercase; color: var(--accent); }
.intro-for-long { font-size: clamp(1.15rem, 4.9vw, 1.45rem); }
.intro-for-xlong { max-width: 30ch; font-size: clamp(1.02rem, 4.3vw, 1.25rem); }
@media (max-height: 700px) {
  .has-for .intro-seal { width: 100px; height: 100px; margin-bottom: 18px; font-size: 2.05rem; }
  .has-for .intro-for { margin-bottom: 26px; }
  .has-for button.intro-open { margin-top: 20px; }
}
/* button.: invite.js also puts the class intro-open on <html> when the seal is opened, and the page
   itself must not pick up the button's margin and padding (it did: every opened invitation was
   64px narrower on a phone). */
button.intro-open { margin-top: 28px; padding: 14px 32px; font-size: 1rem; }
.intro-hint { margin-top: 12px; font-size: .82rem; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-muted); }
html.intro-lock { overflow: hidden; }
/* The tap. The seal is pressed, the card lifts away, then the veil dissolves: all of it here, in
   CSS, on the compositor. The same tap starts the music, which keeps the main thread busy, and no
   script (invite.js only counts 1250 ms to intro-done) can ever hold the veil up. */
html.intro-open .intro { opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .9s var(--ease-draw) .25s, visibility 0s linear 1.15s; }
html.intro-open .intro-card { opacity: 0; transform: translateY(-10px) scale(1.03); transition: opacity .5s var(--ease-draw), transform .5s var(--ease-draw); }
html.intro-open .intro-seal { animation: sealPress .52s var(--ease-out) both; transition: none; }
html.intro-open .intro-seal::before { animation: none; }
@keyframes sealPress { 0% { transform: none; } 23% { transform: scale(.96); } 100% { transform: scale(1.03); } }
html.intro-done .intro { display: none; }
/* The hero arrives piece by piece, in reading order, 70 ms apart: the plain version, in CSS, for a
   returning guest, a #hash landing, the still embeds, and whenever the choreography layer is absent. */
html.intro-open .inv .hero > *, html.intro-done .inv .hero > *, html.intro-open .hero-cover-text > *, html.intro-done .hero-cover-text > * { animation: heroIn .9s cubic-bezier(.215, .61, .355, 1) calc(.1s + var(--hs, 0) * 70ms) backwards; }
html.intro-open .inv .hero > .hero-cover, html.intro-done .inv .hero > .hero-cover { animation-name: fadeIn; }
.hero > :nth-child(2), .hero-cover-text > :nth-child(2) { --hs: 1; } .hero > :nth-child(3), .hero-cover-text > :nth-child(3) { --hs: 2; }
.hero > :nth-child(4) { --hs: 3; } .hero > :nth-child(5) { --hs: 4; } .hero > :nth-child(6) { --hs: 5; } .hero > :nth-child(7) { --hs: 6; } .hero > :nth-child(n+8) { --hs: 7; }
@keyframes heroIn { from { opacity: 0; transform: translateY(12px); } }
/* With the choreography layer (invite-motion.js), GSAP brings the hero's layers in underneath the
   dissolving veil instead. motion-hero is set only when the layer really owns the hero (and then it
   stays): a CSS animation would beat the values GSAP writes. The photograph's slow settle from 106%
   stays in CSS too (hero-play), on the individual `scale` property, off the main thread. */
html.motion-hero .inv .hero > *, html.motion-hero .hero-cover-text > * { animation: none; }
html.motion-hero:not(.hero-play) .hero-cover img, html.motion-hero:not(.hero-play) .hero-photo img { scale: 1.06; }
html.motion-hero.hero-play .hero-cover img, html.motion-hero.hero-play .hero-photo img { scale: 1; transition: scale 2.8s cubic-bezier(.33, 1, .68, 1) .2s; }
/* The embedded preview has no seal to hide behind, so the hero waits (invisibly, and never longer
   than 2.5 s) for the layer to arrive instead of flashing in and being hidden again. */
@media (prefers-reduced-motion: no-preference) {
  html.js-ready[data-motion] .is-embed .hero { animation: heroWait 0s linear 2.5s both; }
  html.js-ready.motion-hero[data-motion] .is-embed .hero { animation: none; }
}
@keyframes heroWait { from { opacity: 0; } to { opacity: 1; } }
@keyframes pulse { 0% { transform: scale(.85); opacity: .9; } 100% { transform: scale(1.35); opacity: 0; } }

/* ---------- page ---------- */
.inv { max-width: 680px; margin-inline: auto; padding-inline: 16px; padding-block: 0 calc(84px + env(safe-area-inset-bottom, 0px)); }
.inv-lang { display: flex; justify-content: flex-end; padding-top: 12px; }
.inv-lang a { font-family: var(--body); font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; text-decoration: none; color: var(--muted); border: 1px solid var(--accent2); padding: 6px 12px; border-radius: 999px; }
.inv-lang a:hover { color: var(--accent); border-color: var(--accent); }

/* reveal on scroll: only hidden when scripting is available, so no-JS still reads everything */
@media (scripting: enabled) {
  .inv .sec[data-reveal]:not(.is-revealed) { opacity: 0; transform: translateY(18px); }
  .inv .sec[data-reveal] { transition: opacity .7s ease, transform .7s ease; }
}

/* ---------- hero (classic) ---------- */
.hero { text-align: center; padding-block: clamp(40px, 8vw, 76px) clamp(36px, 7vw, 60px); position: relative; }
.hero .invite { font-family: var(--body); font-size: .74rem; letter-spacing: .22em; text-transform: uppercase; color: var(--muted); }
.hero-photo { width: min(320px, 78vw); aspect-ratio: 4 / 5; margin: 0 auto 34px; border-radius: 160px 160px 18px 18px; overflow: hidden; border: 1px solid var(--accent2); box-shadow: 0 24px 50px -30px rgba(0,0,0,.45); background: var(--surface); }
.hero-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.monogram { width: 96px; height: 96px; margin: 0 auto 30px; border-radius: 50%; border: 1px solid var(--accent); display: grid; place-items: center; font-family: var(--display); font-size: 2rem; color: var(--accent); letter-spacing: .06em; position: relative; }
.monogram::after { content: ""; position: absolute; inset: 5px; border-radius: 50%; border: 1px solid var(--accent2); }
.ornament-line { width: 120px; height: 1px; background: var(--accent); margin: 0 auto 30px; position: relative; }
.ornament-line::before, .ornament-line::after { content: ""; position: absolute; top: -3px; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
.ornament-line::before { left: -3px; } .ornament-line::after { right: -3px; }
.ornament-line.thin { width: 40px; margin-bottom: 18px; }
.ornament-line.thin::before, .ornament-line.thin::after { display: none; }
.ornament-floral { width: 140px; margin: 0 auto 26px; color: var(--accent); }
.names { margin-top: 26px; font-size: clamp(2.6rem, 11vw, 4.6rem); line-height: 1; font-weight: 500; }
.names .amp { display: block; font-style: italic; font-size: .5em; color: var(--accent); margin-block: 6px; }
.names.script { font-family: var(--script, var(--display)); font-weight: 400; font-size: clamp(3rem, 13vw, 5.4rem); }
.names.script .amp { font-family: var(--display); }
/* Set up and taken away again by invite-motion.js while the names arrive. ln: a name rises from behind
   its own line (clip, not hidden: an inline mask must keep its baseline). nm: a script name is written
   in by a soft-edged wipe; --p rests past the end, so an untouched name is simply visible. */
.names .ln { display: block; overflow: hidden; overflow: clip; padding-block: .16em; margin-block: -.16em; }
.names .ln-i { display: block; }
.layout-editorial .names .ln, .layout-editorial .names .ln-i { display: inline-block; }
.layout-editorial .names .ln { padding-inline: .06em; margin-inline: -.06em; }
.names .nm { display: inline-block; padding: .25em .2em; margin: -.25em -.2em; --p: 118%; -webkit-mask-image: linear-gradient(90deg, #000 calc(var(--p) - 18%), transparent var(--p)); mask-image: linear-gradient(90deg, #000 calc(var(--p) - 18%), transparent var(--p)); }
.tagline { margin-top: 28px; font-family: var(--display); font-style: italic; font-size: 1.2rem; color: var(--muted); }
.date-block { margin-top: 40px; display: inline-grid; grid-template-columns: auto auto auto auto auto; align-items: center; justify-items: center; gap: 10px 14px; font-family: var(--display); max-width: 100%; }
.date-block .day { font-size: 3.4rem; line-height: 1; font-weight: 500; }
.date-block .month { letter-spacing: .2em; text-transform: uppercase; font-family: var(--body); font-size: .8rem; color: var(--muted); }
.date-block .sep { width: 1px; height: 54px; background: var(--accent2); }
.date-block .year { font-size: 1.6rem; }
.date-block .weekday { grid-column: 1 / -1; font-family: var(--body); font-size: .8rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.countdown { margin-top: 36px; display: flex; justify-content: center; gap: 10px; }
.countdown div { min-width: 64px; padding: 10px 6px; border: 1px solid var(--accent2); border-radius: 12px; background: var(--surface); }
.countdown b { display: block; font-family: var(--display); font-size: 1.9rem; line-height: 1; font-weight: 500; font-variant-numeric: lining-nums tabular-nums; }
.countdown b.tick { animation: tick .35s ease; }
/* one cell per digit, so only the digit that changed rolls (see set() in invite.js) */
.countdown .dg { display: inline-block; position: relative; overflow: hidden; vertical-align: top; padding-block: .12em; margin-block: -.12em; }
.countdown .dg span { display: block; }
.countdown .dg .dg-out { position: absolute; left: 0; right: 0; top: .12em; }
@keyframes tick { from { transform: translateY(-4px); opacity: .4; } to { transform: none; opacity: 1; } }
.countdown div > span { display: block; margin-top: 4px; font-size: .74rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-muted); }
.countdown-msg { margin-top: 32px; font-family: var(--display); font-size: 1.3rem; font-style: italic; color: var(--accent); }

/* ---------- hero: editorial ---------- */
.layout-editorial .hero-photo { border-radius: 18px; width: min(420px, 88vw); aspect-ratio: 3 / 2; }
.bigdate { margin: 14px auto 0; display: inline-flex; align-items: center; gap: 14px; font-family: var(--display); }
/* opsz: Bodoni (the only face here with optical sizes) would pick its display cut, whose hairlines
   vanish on a phone and turn "14" into "/ /"; the other faces ignore it */
.bigdate .d { font-size: clamp(5.5rem, 26vw, 9rem); line-height: .9; font-weight: 500; letter-spacing: -.03em; font-variation-settings: 'opsz' 24; }
.bigdate .my { display: grid; text-align: left; gap: 4px; border-left: 1px solid var(--accent2); padding-left: 14px; }
.bigdate .my b { font-family: var(--body); font-weight: 700; letter-spacing: .22em; text-transform: uppercase; font-size: .8rem; color: var(--accent); }
.bigdate .my i { font-style: normal; font-size: 1.5rem; }
.layout-editorial .names { margin-top: 22px; font-size: clamp(2.2rem, 9vw, 3.6rem); }
.layout-editorial .names .amp { display: inline; font-size: .7em; margin-inline: .2em; }
.layout-editorial .names.script { font-size: clamp(2.8rem, 12vw, 4.8rem); }
.weekday-line { margin-top: 16px; font-family: var(--body); font-size: .78rem; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }

/* ---------- hero: photo (full bleed) ---------- */
.layout-photo .hero { padding-top: 0; }
.layout-photo .inv-lang { position: absolute; right: 16px; top: 0; z-index: 2; }
.layout-photo .inv-lang a { background: rgba(0,0,0,.35); color: #fff; border-color: rgba(255,255,255,.4); backdrop-filter: blur(6px); }
.layout-photo { position: relative; padding-inline: 0; }
.layout-photo .sec, .layout-photo .tagline, .layout-photo .countdown, .layout-photo .countdown-msg, .layout-photo .inv-footer { margin-inline: 16px; }
.hero-cover { position: relative; width: 100%; min-height: min(78vh, 720px); aspect-ratio: 4 / 5; overflow: hidden; background: var(--surface); border-radius: 0 0 28px 28px; box-shadow: 0 30px 60px -40px rgba(0,0,0,.6); }
.hero-cover img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero-cover::after { content: ""; position: absolute; inset: 0; background: linear-gradient(to bottom, rgba(0,0,0,.05) 35%, rgba(0,0,0,.72) 100%); }
.hero-cover-text { position: absolute; left: 0; right: 0; bottom: 0; padding: 28px 22px 34px; color: #fff; z-index: 1; text-align: center; }
.hero-cover-text .invite { color: rgba(255,255,255,.8); }
.hero-cover-text .names { color: #fff; margin-top: 10px; text-shadow: 0 2px 16px rgba(0,0,0,.35); }
.hero-cover-text .names .amp { color: rgba(255,255,255,.85); }
.cover-date { margin-top: 12px; font-family: var(--body); font-size: .78rem; letter-spacing: .18em; text-transform: uppercase; color: rgba(255,255,255,.85); }
.layout-photo .tagline { margin-top: 26px; }
@media (min-width: 700px) { .hero-cover { border-radius: 0 0 36px 36px; aspect-ratio: 16 / 11; min-height: 0; } }

/* ---------- hero: minimal ---------- */
.layout-minimal .hero { padding-block: clamp(44px, 10vw, 90px) clamp(26px, 6vw, 48px); }
.layout-minimal .names { font-weight: 400; font-size: clamp(2rem, 8.5vw, 3.4rem); letter-spacing: .04em; }
.layout-minimal .names .amp { font-size: .55em; }
.layout-minimal .date-block .day { font-weight: 400; }
.layout-minimal .countdown div { border: 0; background: transparent; padding: 6px; min-width: 56px; }
.layout-minimal .countdown b { font-weight: 400; }
.layout-minimal .sec { border-top-width: 0; padding-block: clamp(30px, 5vw, 44px); }
.layout-minimal .sec + .sec::before { content: ""; display: block; width: 40px; height: 1px; background: var(--accent2); margin: 0 auto 34px; }
.layout-minimal .place, .layout-minimal .rsvp-box, .layout-minimal .event { border-radius: 4px; }

/* ---------- sections ---------- */
.sec { padding-block: clamp(38px, 7vw, 60px); border-top: 1px solid var(--accent2); margin-top: 12px; }
.sec-title { text-align: center; font-family: var(--body); font-size: .84rem; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-accent); margin-bottom: 28px; }
.text { text-align: center; font-family: var(--display); font-size: clamp(1.15rem, 2.6vw, 1.35rem); line-height: 1.55; max-width: 44ch; margin-inline: auto; }
.note { text-align: center; font-size: 1rem; line-height: 1.65; max-width: 52ch; margin-inline: auto; color: var(--text); }
.note a { word-break: break-all; }
.family { display: grid; gap: 26px; text-align: center; }
@media (min-width: 560px) { .family { grid-template-columns: 1fr 1fr; } .family .full { grid-column: 1 / -1; } }
.family .lbl { font-family: var(--body); font-size: .8rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-muted); }
.family .val { font-family: var(--display); font-size: 1.35rem; margin-top: 4px; }
.places { display: grid; gap: 18px; }
@media (min-width: 560px) { .places.two { grid-template-columns: 1fr 1fr; } }
.place { background: var(--surface); border: 1px solid var(--accent2); border-radius: 16px; padding: 28px 24px; text-align: center; }
.place .lbl { font-family: var(--body); font-size: .8rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-accent); }
.place h3 { font-size: 1.5rem; margin-top: 8px; }
.place .time { font-family: var(--display); font-size: 1.9rem; margin-top: 6px; font-weight: 500; }
.place .addr { font-size: 1rem; color: var(--ink-muted); margin-top: 6px; }
.place .btn { margin-top: 14px; }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-family: var(--body); font-weight: 700; font-size: .88rem; padding: 11px 20px; border-radius: 999px; text-decoration: none; border: 1px solid var(--accent); color: var(--accent); background: transparent; cursor: pointer; transition: background .15s, color .15s, transform .45s var(--ease-back); line-height: 1.2; position: relative; isolation: isolate; }
.btn:hover { background: var(--accent); color: var(--bg); transform: translateY(-1px); }
.btn:active { transform: scale(.97); transition-duration: .15s, .15s, .12s; }
.btn-solid { background: var(--accent); color: var(--bg); }
.btn-solid:hover { filter: brightness(.92); }
.btn-sm { padding: 8px 14px; font-size: .8rem; }
.btn svg { width: 16px; height: 16px; flex: none; }
/* small manners: the map arrow leans forward, the calendar tips its head, WhatsApp breathes three
   times when it comes into view (is-live is set by invite-motion.js) and then stays quiet */
.ico-arrow { transition: transform .35s var(--ease-out); }
.btn-go:hover .ico-arrow, .btn-go:focus-visible .ico-arrow { transform: translateX(5px); }
.ico-cal { transition: transform .5s cubic-bezier(.34, 1.56, .64, 1); transform-origin: 50% 60%; }
.btn-cal:hover .ico-cal, .btn-cal:focus-visible .ico-cal { transform: rotate(-12deg); }
/* a phone has no hover, so each makes its move once as it scrolls in (is-nudged, from invite-motion.js) */
.btn-go.is-nudged .ico-arrow { animation: arrowNudge 1s var(--ease-out) .5s; }
.btn-cal.is-nudged .ico-cal { animation: calTip 1s cubic-bezier(.34, 1.56, .64, 1) .5s; }
@keyframes arrowNudge { 0%, 100% { transform: none; } 45% { transform: translateX(5px); } }
@keyframes calTip { 0%, 100% { transform: none; } 40% { transform: rotate(-12deg); } }
/* WhatsApp: a ring leaves the button three times once it is on screen, and the icon lifts with it */
.btn-wa::before { content: ""; position: absolute; inset: -1px; border-radius: inherit; border: 1px solid var(--accent); opacity: 0; pointer-events: none; }
.btn-wa.is-live::before { animation: waRing 6s var(--ease-out) .6s 3; }
.btn-wa.is-live .ico-wa { animation: waFloat 6s var(--ease-out) .6s 3; }
@keyframes waRing { 0% { opacity: .4; transform: none; } 40%, 100% { opacity: 0; transform: scale(1.12, 1.5); } }
@keyframes waFloat { 0%, 40%, 100% { transform: none; } 12% { transform: translateY(-2px); } }
/* desktop only: a soft light follows the cursor across a button (--mx/--my come from invite-motion.js),
   and a magnetic button must not smooth the transform GSAP is already easing */
@media (hover: hover) and (pointer: fine) {
  .btn::after { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none; opacity: 0; transition: opacity .3s; background: radial-gradient(120px circle at var(--mx, 50%) var(--my, 50%), rgba(255,255,255,.16), transparent 70%); }
  .btn:hover::after { opacity: 1; }
  .btn.is-magnetic { transition: background .15s, color .15s; }
}
.btn-row { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }

/* map: a lightweight placeholder until the guest asks for the real embed */
.map { margin-top: 22px; border-radius: 16px; overflow: hidden; border: 1px solid var(--accent2); aspect-ratio: 16 / 10; max-width: 100%; background: var(--surface); position: relative; }
.map iframe { width: 100%; height: 100%; border: 0; display: block; filter: saturate(.85); }
.map-load { position: absolute; inset: 0; width: 100%; border: 0; cursor: pointer; display: grid; place-content: center; gap: 10px; justify-items: center; font: inherit; font-family: var(--body); font-weight: 700; color: var(--accent); background:
  radial-gradient(circle at 30% 40%, color-mix(in srgb, var(--accent) 10%, transparent) 0 14%, transparent 15%),
  radial-gradient(circle at 72% 62%, color-mix(in srgb, var(--accent) 8%, transparent) 0 18%, transparent 19%),
  repeating-linear-gradient(0deg, transparent 0 34px, color-mix(in srgb, var(--accent2) 60%, transparent) 34px 35px),
  repeating-linear-gradient(90deg, transparent 0 34px, color-mix(in srgb, var(--accent2) 60%, transparent) 34px 35px),
  var(--surface); }
.map-load svg { width: 34px; height: 34px; }
.map-load span { padding: 9px 18px; border-radius: 999px; background: var(--surface); border: 1px solid var(--accent); }
.map-load:hover span { background: var(--accent); color: var(--bg); }

/* The day as a timeline: the hour, a rail with a dot for every moment, then what happens. Each row
   draws the piece of rail down to the next dot, so rows of any height line up. By default the rail
   is fully drawn and every dot is lit; invite-motion.js (is-drawn) winds it back and lets the
   guest's scrolling draw it: --seg is how much of a row's piece is filled, is-lit its dot. */
.schedule { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; max-width: 420px; margin-inline: auto; }
.schedule li { display: grid; grid-template-columns: 64px 1fr; column-gap: 44px; align-items: baseline; padding: 16px 0; position: relative; }
.schedule li::before { content: ""; position: absolute; z-index: 1; left: 81px; top: 25px; width: 9px; height: 9px; box-sizing: border-box; border-radius: 50%; border: 1px solid var(--accent); background: var(--accent); box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 16%, transparent); transition: background .5s ease, box-shadow .6s ease, transform .6s cubic-bezier(.34, 1.56, .64, 1); }
.schedule li:not(:last-child)::after { content: ""; position: absolute; left: 85px; top: 29px; width: 1px; height: 100%; background: linear-gradient(var(--accent), var(--accent)) top / 100% calc(var(--seg, 1) * 100%) no-repeat var(--accent2); }
.schedule.is-drawn li:not(.is-lit)::before { background: var(--bg); box-shadow: 0 0 0 0 transparent; transform: scale(.7); }
.schedule .t { font-family: var(--display); font-size: 1.35rem; color: var(--accent); font-variant-numeric: tabular-nums; text-align: right; }
.schedule .d { font-size: 1rem; }
.dress { text-align: center; font-family: var(--display); font-size: 1.3rem; max-width: 40ch; margin-inline: auto; }
.swatches { display: flex; justify-content: center; gap: 12px; margin-top: 26px; }
.swatches i { width: 34px; height: 34px; border-radius: 50%; border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--accent2), 0 6px 14px -8px rgba(0,0,0,.5); }
.center { text-align: center; }

/* events (wedding days) */
.events { display: grid; gap: 16px; }
.event { display: grid; grid-template-columns: 74px 1fr; gap: 18px; align-items: center; background: var(--surface); border: 1px solid var(--accent2); border-radius: 16px; padding: 20px 18px; }
.ev-date { text-align: center; border-right: 1px solid var(--accent2); padding-right: 12px; }
.ev-date b { display: block; font-family: var(--display); font-size: 2.2rem; line-height: 1; font-weight: 500; color: var(--accent); }
.ev-date span { display: block; font-size: .78rem; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted); margin-top: 4px; }
.event h3 { font-size: 1.3rem; }
.ev-meta { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: .9rem; margin-top: 4px; }
.ev-body .btn { margin-top: 10px; }

/* gallery strip */
.gallery { display: flex; gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; padding-bottom: 6px; margin-inline: -16px; padding-inline: 16px; scrollbar-width: none; }
.gallery::-webkit-scrollbar { display: none; }
.shot { flex: 0 0 min(72%, 300px); aspect-ratio: 4 / 5; scroll-snap-align: center; border-radius: 16px; overflow: hidden; background: var(--surface); border: 1px solid var(--accent2); }
.shot img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s ease; }
/* While invite-motion.js deals the photographs onto the table (is-dealing) they tilt and rise from
   below: the strip gets room for that, so nothing is clipped and it never scrolls vertically, and
   the hover zoom keeps out of the way. Padding and margin cancel out, so the layout does not move. */
html.motion .gallery { overflow-y: hidden; padding-block: 26px 40px; margin-block: -26px -34px; }
/* a single photograph is a print on its own, not the start of a strip */
.gallery .shot:only-child { flex-basis: min(86%, 420px); margin-inline: auto; }
.gallery.is-dealing { scroll-snap-type: none; }
.gallery.is-dealing .shot { box-shadow: 0 22px 40px -26px rgba(0,0,0,.55); }
.gallery.is-dealing .shot img { transition: none; }
/* Where the browser can do it by itself, each picture drifts a little inside its frame as the strip
   is swiped: tied to the native scroll, no script per frame. Individual properties, so the hover
   zoom above still composes; every photograph, so the crop is the same for all. Others ignore it. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    html[data-motion] .gallery > .shot > img { scale: 1.06; animation: shotDrift linear both; animation-timeline: view(inline); }
    @keyframes shotDrift { from { translate: 2.5% 0; } to { translate: -2.5% 0; } }
  }
}
.shot:hover img { transform: scale(1.03); }
.lightbox { position: fixed; inset: 0; z-index: 70; background: rgba(0,0,0,.92); display: grid; place-items: center; padding: 16px; animation: fadeIn .2s ease; }
.lightbox img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 8px; }
.lb-close { position: absolute; top: 12px; right: 12px; width: 44px; height: 44px; border-radius: 50%; border: 0; background: rgba(255,255,255,.14); color: #fff; font-size: 1.8rem; line-height: 1; cursor: pointer; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* ---------- RSVP ---------- */
#rsvp, #where { scroll-margin-top: 64px; }
.rsvp-box { background: var(--surface); border: 1px solid var(--accent2); border-radius: 20px; padding: clamp(26px, 6vw, 40px); }
.rsvp-box h2 { text-align: center; font-size: 1.9rem; }
.rsvp-box .sub { text-align: center; color: var(--ink-muted); margin-top: 6px; font-size: 1.02rem; }
.rsvp-box .deadline { text-align: center; margin-top: 6px; font-family: var(--body); font-size: .84rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-accent); }
.rsvp-box form { margin-top: 28px; display: grid; gap: 20px; }
.f { display: grid; gap: 6px; }
.f label { font-family: var(--body); font-size: .95rem; font-weight: 700; color: var(--ink-muted); }
.in { font: inherit; font-family: var(--body); font-size: 16px; width: 100%; padding: 12px 14px; border: 1px solid var(--accent2); border-radius: 12px; background: var(--bg); color: var(--text); }
.in:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
textarea.in { min-height: 90px; resize: vertical; }
select.in { appearance: none; padding-right: 36px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23888' stroke-width='1.6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; }
.seg { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.seg label { position: relative; cursor: pointer; }
.seg input { position: absolute; opacity: 0; inset: 0; }
.seg span { display: block; text-align: center; padding: 13px 10px; border: 1px solid var(--accent2); border-radius: 12px; font-family: var(--body); font-weight: 700; font-size: .92rem; transition: .15s; }
.seg input:checked + span { background: var(--accent); color: var(--bg); border-color: var(--accent); }
.seg input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.seg.no input:checked + span { background: var(--text); border-color: var(--text); color: var(--bg); }
.members { gap: 8px; }
.member { display: grid; gap: 6px; grid-template-columns: 1fr; }
@media (min-width: 480px) { .member { grid-template-columns: 1.4fr 1fr; } }
.checks { display: grid; gap: 8px; }
.check { display: flex; gap: 10px; align-items: center; font-size: .95rem; cursor: pointer; padding: 10px 12px; border: 1px solid var(--accent2); border-radius: 12px; }
.check input { width: 18px; height: 18px; accent-color: var(--accent); margin: 0; flex: none; }
details.more { border-top: 1px dashed var(--accent2); padding-top: 12px; }
details.more summary { cursor: pointer; font-family: var(--body); font-size: .88rem; font-weight: 700; color: var(--accent); list-style: none; display: flex; align-items: center; gap: 8px; }
details.more summary::-webkit-details-marker { display: none; }
details.more summary::before { content: "+"; font-size: 1.2rem; line-height: 1; }
details.more[open] summary::before { content: "–"; }
details.more .f { margin-top: 14px; }
.rsvp-box .btn-solid[type="submit"] { width: 100%; padding: 15px; font-size: 1rem; }
.rsvp-err { padding: 12px 14px; border-radius: 10px; background: #F7E0E0; color: #9B2C2C; font-family: var(--body); font-size: .9rem; font-weight: 600; }
.rsvp-note { text-align: center; color: var(--ink-muted); font-size: 1rem; margin-top: 8px; }
.personal { text-align: center; font-family: var(--body); font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
.personal b { color: var(--text); letter-spacing: 0; text-transform: none; font-family: var(--display); font-size: 1.2rem; display: block; margin-top: 2px; }

/* confirmation moment */
.celebrate { text-align: center; }
.celebrate .check { width: 72px; height: 72px; margin: 0 auto 16px; border-radius: 50%; background: var(--accent); color: var(--bg); display: grid; place-items: center; animation: pop .6s cubic-bezier(.2,.9,.3,1.3) both; }
.celebrate .check svg { width: 34px; height: 34px; }
.celebrate h2 { font-size: 1.7rem; }
.celebrate p { margin-top: 8px; color: var(--muted); }
.celebrate .btn-row { margin-top: 18px; }
.celebrate.is-no h2 { font-size: 1.4rem; font-style: italic; }
.rsvp-change { margin-top: 18px; }
.rsvp-change summary { cursor: pointer; color: var(--muted); font-size: .85rem; text-decoration: underline; text-underline-offset: 3px; list-style: none; }
.rsvp-change summary::-webkit-details-marker { display: none; }
.rsvp-change form { text-align: left; }
@keyframes pop { from { transform: scale(.4); opacity: 0; } to { transform: none; opacity: 1; } }
.confetti { position: fixed; inset: 0; pointer-events: none; z-index: 55; }

/* wishes */
.wishes { display: grid; gap: 16px; }
.wish { margin: 0; background: var(--surface); border: 1px solid var(--accent2); border-radius: 16px; padding: 20px 22px; }
.wish p { font-family: var(--display); font-size: 1.15rem; line-height: 1.45; font-style: italic; }
.wish footer { margin-top: 8px; font-size: .8rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }

/* contact & footer */
.contact { text-align: center; }
.contact .name { font-family: var(--display); font-size: 1.4rem; margin-top: 4px; }
.contact .phone { color: var(--muted); font-size: .95rem; margin-top: 2px; }
.inv-footer { text-align: center; padding-block: 44px 12px; border-top: 1px solid var(--accent2); font-family: var(--body); font-size: .74rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.inv-footer a { color: var(--muted); text-decoration: none; }
.inv-footer a b { color: var(--accent); letter-spacing: .14em; }
.inv-footer-space { height: 16px; }

/* ---------- sticky action bar (phones) ---------- */
.actbar { position: fixed; left: 50%; bottom: calc(14px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%); z-index: 45; display: flex; gap: 6px; padding: 6px; border-radius: 999px; background: color-mix(in srgb, var(--surface) 88%, transparent); backdrop-filter: blur(12px); border: 1px solid var(--accent2); box-shadow: 0 16px 40px -22px rgba(0,0,0,.6); transition: transform .3s ease, opacity .3s ease; }
.actbar a { display: inline-flex; align-items: center; gap: 6px; padding: 10px 14px; border-radius: 999px; font-family: var(--body); font-weight: 700; font-size: .82rem; text-decoration: none; color: var(--text); white-space: nowrap; }
.actbar a svg { width: 16px; height: 16px; }
.actbar a.primary { background: var(--accent); color: var(--bg); }
.actbar.is-hidden { transform: translateX(-50%) translateY(120%); opacity: 0; pointer-events: none; }
html.intro-lock .actbar { display: none; }
@media (min-width: 900px) { .actbar { display: none; } .inv { padding-bottom: 48px; } }

/* ---------- music ---------- */
.music-btn { position: fixed; right: 14px; top: calc(14px + env(safe-area-inset-top, 0px)); z-index: 46; width: 46px; height: 46px; border-radius: 50%; border: 1px solid var(--accent2); background: color-mix(in srgb, var(--surface) 90%, transparent); backdrop-filter: blur(10px); color: var(--accent); display: grid; place-items: center; cursor: pointer; box-shadow: 0 10px 30px -16px rgba(0,0,0,.6); }
.music-btn svg { width: 20px; height: 20px; }
.music-btn .bars { position: absolute; right: -2px; bottom: -2px; display: none; gap: 2px; align-items: flex-end; height: 12px; padding: 2px 4px; border-radius: 6px; background: var(--accent); }
.music-btn .bars b { width: 2px; height: 4px; background: var(--bg); border-radius: 1px; animation: eq .8s ease-in-out infinite alternate; }
.music-btn .bars b:nth-child(2) { animation-delay: .2s; } .music-btn .bars b:nth-child(3) { animation-delay: .4s; }
.music-btn[aria-pressed="true"] .bars { display: flex; }
@keyframes eq { from { height: 3px; } to { height: 8px; } }
.has-intro .music-btn { top: calc(14px + env(safe-area-inset-top, 0px)); }
.preview-bar ~ .music-btn { top: auto; bottom: calc(78px + env(safe-area-inset-bottom, 0px)); }
.yt-holder { position: fixed; width: 2px; height: 2px; left: -4px; bottom: -4px; opacity: .01; pointer-events: none; overflow: hidden; }
.yt-holder iframe { width: 200px; height: 120px; border: 0; }
html.intro-lock .music-btn { display: none; }
.layout-photo .music-btn { color: #fff; background: rgba(0,0,0,.35); border-color: rgba(255,255,255,.4); }

/* dark themes: soften shadows on surfaces */
.is-dark .place, .is-dark .rsvp-box, .is-dark .event, .is-dark .wish { box-shadow: none; }
