/* =====================================================================================
   The guest page - the invitation as the guest meets it.

   One full-screen stage, several layers, one state machine. The state lives in a single
   attribute on the root, data-phase, and every layer reads it:

       sealed   -> the envelope, address side up, breathing
       turned   -> turned over, the seal invites a tap
       opening  -> the flap swings, the card rises out
       reveal   -> the envelope sinks away while the card flies to the middle of the screen
       card     -> the card alone, alive: music, tilt, celebration on every tap

       curtain  -> (curtain style) closed velvet
       parting  -> the drapes open and the card rises on the stage behind them

   "card" is also what the server renders. A browser that never runs guest.js - blocked, broken,
   or a link-preview bot - gets the finished invitation straight away, and the inline script at
   the top of the stage moves it back to "sealed" before the first paint for everybody else.

   Layers, bottom to top: ground, wave, envelope scene, card, curtain, fireworks, controls.
   Anything position:fixed lives outside the 3D scene on purpose: perspective and container-type
   both make an element the containing block for fixed descendants, so a card inside the scene
   could never escape it.
   ===================================================================================== */

body:has(.gs) {
    overflow: hidden;
    background: #16131a;
}

.gs {
    --gs-accent: #8a6d4b;
    --gs-velvet: #6a0d1c;
    --gs-ease-out: cubic-bezier(.2, .8, .2, 1);
    --gs-ease-swing: cubic-bezier(.45, .05, .2, 1);
    /* How far the envelope steps down once it is open. Measured, not guessed: at this value the
       card standing on the open envelope sits centred between the top buttons and the dock, on
       a 390x844 phone and on a 1440x900 desktop alike. */
    --gs-drop: 4vh;
    --gs-drop: 4dvh;
    /* The card is as large as the screen allows, leaving room for the controls above and
       below it. vh first, for the browsers that do not know dvh. */
    --gs-card-w: min(calc(100vw - 40px), calc((100vh - 240px) * 2 / 3), 470px);
    --gs-card-w: min(calc(100vw - 40px), calc((100dvh - 240px) * 2 / 3), 470px);

    position: fixed;
    inset: 0;
    overflow: hidden;
    color: #fff;
    font-family: 'Vazirmatn', system-ui, sans-serif;
    -webkit-tap-highlight-color: transparent;
    user-select: none;
    -webkit-user-select: none;
}

.gs button {
    font: inherit;
    color: inherit;
}

/* For screen readers: the page's heading, which the card itself carries for everybody else. */
.gs-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ------------------------------------------------ ground */

.gs-ground {
    position: absolute;
    inset: 0;
    z-index: 0;
    /* A card without a chosen background still gets a room of its own colour: light in the
       middle, falling off to a deep tone of the same accent at the edges. */
    background: #2a2330;
    background:
        radial-gradient(120% 85% at 50% 40%,
            color-mix(in srgb, var(--gs-accent) 58%, #4a3a44) 0%,
            color-mix(in srgb, var(--gs-accent) 32%, #1d1519) 55%,
            #0c090d 100%);
}

/* A picture of the guest's choosing is shown whole - a portrait on a wide screen, a landscape
   on a phone - never cropped down to whatever happens to be in its middle. The empty margins
   around it are filled with the same picture, blurred and darkened, so the room still reads as
   one scene rather than a photo on a black board. */
.gs-ground-fill {
    position: absolute;
    inset: -6%;
    width: 112%;
    height: 112%;
    object-fit: cover;
    filter: blur(28px) brightness(.55) saturate(1.15);
}

.gs-ground-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* When the picture and the screen are close in shape - a phone photo on a phone - it simply
   fills the screen: what is cut off is not worth a blurred frame around it. guest.js decides,
   from the picture's real size and the window's. */
.gs-ground.is-cover .gs-ground-img {
    object-fit: cover;
}

/* A phone held upright. A wide picture shown whole there is a thin strip across the middle of
   the screen - so here it always fills the screen, and shows the slice chosen for phones
   (--gs-focus-x, set per picture). A site background with an upright version shows that instead:
   <picture> in GuestStage swaps it in under the same media query, and it is centred. The blurred
   fill has nothing left to fill. */
@media (max-aspect-ratio: 4/5) {
    .gs-ground .gs-ground-img {
        object-fit: cover;
        object-position: var(--gs-focus-x, 50%) 50%;
    }

    .gs-ground.has-portrait .gs-ground-img { object-position: 50% 50%; }

    .gs-ground-fill { display: none; }
}

/* A chosen picture is the guest's to see: only the faintest darkening at the very edges. */
.gs-ground:has(.gs-ground-img)::after,
.gs[data-phase] .gs-ground:has(.gs-ground-img)::after {
    opacity: 1;
    background: radial-gradient(120% 90% at 50% 50%, transparent 60%, rgba(10, 6, 12, .35) 100%);
}

/* Darkening at the edges keeps whatever the picture is from competing with the card. */
.gs-ground::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(100% 75% at 50% 45%, transparent 35%, rgba(10, 6, 12, .55) 100%);
    transition: opacity 1.2s ease;
}

.gs[data-phase="sealed"] .gs-ground::after,
.gs[data-phase="turned"] .gs-ground::after,
.gs[data-phase="curtain"] .gs-ground::after {
    opacity: 1.25;
    background: radial-gradient(90% 70% at 50% 50%, transparent 20%, rgba(10, 6, 12, .7) 100%);
}

/* ------------------------------------------------ the song, drawn */

.gs-wave {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    pointer-events: none;
    opacity: 0;
    transition: opacity 1.2s ease;
}

.gs.is-playing .gs-wave,
.gs.is-paused .gs-wave {
    opacity: 1;
}

/* ------------------------------------------------ the envelope scene */

.gs-stage {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: grid;
    place-items: center;
    pointer-events: none;
}

.gs-scene {
    position: relative;
    width: min(88vw, 620px, 74vh);
    width: min(88vw, 620px, 74dvh);
    aspect-ratio: 3 / 2;
    container-type: inline-size;
    perspective: 1600px;
    perspective-origin: 50% 60%;
    pointer-events: auto;
    transition: transform 1s var(--gs-ease-out), opacity .9s ease, filter .9s ease;
}

/* The envelope floats a little while it waits. It says "touch me" without saying it. */
.gs[data-phase="sealed"] .gs-scene,
.gs[data-phase="turned"] .gs-scene {
    animation: gs-float 5s ease-in-out infinite;
}

@keyframes gs-float {
    0%, 100% { translate: 0 0; }
    50%      { translate: 0 -8px; }
}

/* Opened: the scene steps down so the card has room to rise above it, and stays there. The
   envelope never leaves the stage - when the card comes forward it is still underneath, and a
   tap on the zoom button sends the card back to stand on it. */
.gs[data-phase="opening"] .gs-scene,
.gs[data-phase="reveal"] .gs-scene,
.gs[data-phase="card"] .gs-scene {
    transform: translateY(var(--gs-drop));
}

/* While the card is in front, the envelope steps back a little into the room. */
.gs[data-phase="reveal"] .gs-scene,
.gs[data-phase="card"]:not([data-zoom="out"]) .gs-scene {
    filter: brightness(.8) saturate(.9);
}

/* Behind closed curtains there is nothing to touch yet. */
.gs[data-phase="curtain"] .gs-scene,
.gs[data-phase="parting"] .gs-scene {
    pointer-events: none;
}

/* The shared envelope layers from digicard.css do the painting; only the timing is ours. */
.gs .env-stack {
    transition: transform 1s var(--gs-ease-swing);
}

.gs .env-card {
    width: 44cqi;
    filter: drop-shadow(0 12px 26px rgba(20, 12, 10, .35));
}

.gs .env-card .card {
    max-width: none;
    box-shadow: none;
}

.gs[data-phase="turned"] .env-stack {
    transform: rotateY(0deg);
}

/* The flap swings back... */
.gs[data-phase="opening"] .env-flap,
.gs[data-phase="reveal"] .env-flap,
.gs[data-phase="card"] .env-flap {
    transform: translateZ(3px) rotateX(-164deg);
}

.gs[data-phase="opening"] .env-lining,
.gs[data-phase="reveal"] .env-lining,
.gs[data-phase="card"] .env-lining {
    opacity: 1;
    transition: opacity .01s linear .4s;
}

/* ...the card comes out, turning upright on the way... */
.gs[data-phase="opening"] .env-card,
.gs[data-phase="reveal"] .env-card,
.gs[data-phase="card"] .env-card {
    transform: translateZ(1px) translate(-50%, -128%) rotate(0deg) scale(1);
    transition: transform 1.4s cubic-bezier(.22, .78, .24, 1) .45s;
}

.gs[data-phase="opening"] .env-sheen {
    animation: env-sheen 1.1s ease-out 1.05s 1;
}

/* ...and the envelope leans back, leaving the card standing on its own. */
.gs[data-phase="opening"] .env-stack,
.gs[data-phase="reveal"] .env-stack,
.gs[data-phase="card"] .env-stack {
    transform: rotateY(0deg) translateY(9cqi) rotateX(26deg) scale(.82);
    transition: transform 1s cubic-bezier(.3, .7, .3, 1) .9s;
}

/* Only one card is ever on screen. While the big one is out, the one on the envelope steps
   aside without a trace; zoomed out, it is the other way round. */
.gs[data-phase="reveal"] .env-card,
.gs[data-phase="card"]:not([data-zoom="out"]) .env-card {
    opacity: 0;
    transition: none;
}

.gs[data-phase="card"][data-zoom="out"] .gs-card-box {
    visibility: hidden;
}

/* Zoomed out, the card on the envelope is the way back in. */
.gs-hit-card {
    left: 50%;
    top: 50%;
    width: 44cqi;
    aspect-ratio: 2 / 3;
    transform: translate(-50%, -128%);
    display: none;
    cursor: zoom-in;
}

.gs[data-phase="card"][data-zoom="out"] .gs-hit-card {
    display: block;
}

/* A sealed envelope shows nothing of what is inside it. The card only exists from the moment
   the seal breaks - before that, whatever the artwork's silhouette, not a corner of it may show
   past the paper. */
.gs[data-phase="sealed"] .env-card,
.gs[data-phase="turned"] .env-card,
.gs[data-phase="curtain"] .env-card,
.gs[data-phase="parting"] .env-card {
    visibility: hidden;
}

/* Uploaded envelopes: two photographs dissolving into each other, as on the template page.
   The depth and the hidden open picture have to be stated here too: digicard.css keys them to
   .env-flat, which is EnvelopeCard's class and not this stage's. Missing, the open picture sat
   at full strength under the closed one and its flap showed above the sealed envelope. */
.gs-env-flat .env-flat-open {
    transform: translateZ(1.5px);
    opacity: 0;
    transition: opacity .35s ease;
}

.gs-env-flat .env-flat-closed {
    transform: translateZ(2.5px);
    transition: opacity .7s ease;
}

.gs-env-flat .env-card {
    transform: translateZ(0) translate(-50%, -50%) rotate(-90deg) scale(.94);
}

.gs-env-flat[data-phase="opening"] .env-card,
.gs-env-flat[data-phase="reveal"] .env-card,
.gs-env-flat[data-phase="card"] .env-card {
    transform: translateZ(3px) translate(-50%, -128%) rotate(0deg) scale(1);
}

.gs-env-flat[data-phase="opening"] .env-flat-open,
.gs-env-flat[data-phase="reveal"] .env-flat-open,
.gs-env-flat[data-phase="card"] .env-flat-open {
    opacity: 1;
}

.gs-env-flat[data-phase="opening"] .env-flat-closed,
.gs-env-flat[data-phase="reveal"] .env-flat-closed,
.gs-env-flat[data-phase="card"] .env-flat-closed {
    opacity: 0;
}

/* ------------------------------------------------ what can be tapped */

.gs-hit {
    position: absolute;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    outline-offset: 4px;
}

.gs-hit:focus-visible {
    outline: 2px solid #fff;
    border-radius: 10px;
}

.gs-hit-turn {
    inset: 0;
}

.gs-hit-seal {
    left: 50%;
    top: 56%;
    width: 17cqi;
    height: 17cqi;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    display: grid;
    place-items: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity .35s ease;
}

.gs-env-ribbon .gs-hit-seal {
    top: 52%;
    width: 28cqi;
    height: 16cqi;
    border-radius: 999px;
}

.gs-env-ribbon .gs-hit-seal .env-cue-ring {
    border-radius: 999px;
}

.gs-hit-seal .env-cue-ring {
    border-color: rgba(255, 236, 214, .95);
    box-shadow: 0 0 18px rgba(255, 220, 180, .55), inset 0 0 12px rgba(255, 220, 180, .35);
}

.gs[data-phase="sealed"] .gs-hit-turn {
    pointer-events: auto;
}

.gs:not([data-phase="sealed"]) .gs-hit-turn {
    pointer-events: none;
}

.gs[data-phase="turned"] .gs-hit-seal {
    opacity: 1;
    pointer-events: auto;
    transition: opacity .4s ease .8s;
}

/* ------------------------------------------------ the card, full size */

.gs-card {
    position: absolute;
    inset: 0;
    z-index: 4;
    display: grid;
    place-items: center;
    /* Room at the bottom for the dock and, above it, the ceremony button. */
    padding: 64px 16px calc(150px + env(safe-area-inset-bottom));
    perspective: 1500px;
    pointer-events: none;
}

.gs-card-box {
    width: var(--gs-card-w);
    /* On the beat, a hair of scale - guest.js writes --gs-beat from the song's energy. */
    scale: var(--gs-beat, 1);
    transition: scale .12s ease-out;
    transform-style: preserve-3d;
    pointer-events: auto;
    will-change: transform;
}

.gs[data-phase="sealed"] .gs-card-box,
.gs[data-phase="turned"] .gs-card-box,
.gs[data-phase="opening"] .gs-card-box,
.gs[data-phase="curtain"] .gs-card-box,
.gs[data-phase="parting"] .gs-card-box {
    visibility: hidden;
}

/* Follows the pointer or the phone's tilt; guest.js writes --gs-rx / --gs-ry. */
.gs-card-tilt {
    position: relative;
    transform-style: preserve-3d;
    transform: rotateX(var(--gs-rx, 0deg)) rotateY(var(--gs-ry, 0deg));
    transition: transform .25s ease-out;
}

.gs-card-tilt .card {
    max-width: none;
    border-radius: 6px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 30px 80px rgba(10, 5, 10, .55);
}

/* Light moving across the paper as it tilts. */
.gs-glare {
    position: absolute;
    inset: 0;
    border-radius: 6px;
    pointer-events: none;
    background: radial-gradient(60% 45% at var(--gs-gx, 30%) var(--gs-gy, 20%),
        rgba(255, 255, 255, .28), transparent 70%);
    mix-blend-mode: soft-light;
    opacity: 0;
    transition: opacity .6s ease;
    transform: translateZ(1px);
}

.gs[data-phase="card"] .gs-glare {
    opacity: 1;
}

/* ------------------------------------------------ one card, front and back

   Every style draws the same card. What differs is what is behind it: the spin needs a back,
   the letter needs two halves and a crease. */

.gc {
    position: relative;
    transform-style: preserve-3d;
}

.gc-face {
    position: relative;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
}

.gc-back {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    border-radius: 6px;
    background:
        radial-gradient(circle at 50% 50%, transparent 58%, color-mix(in srgb, var(--gs-accent) 10%, transparent) 100%),
        var(--gs-paper, #fbf7f0);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gs-accent) 30%, transparent);
    transform: rotateY(180deg);
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    overflow: hidden;
}

/* A thin frame and the two initials, in the card's own accent. */
.gc-back::before {
    content: "";
    position: absolute;
    inset: 5%;
    border: 1px solid color-mix(in srgb, var(--gs-accent) 55%, transparent);
    border-radius: 4px;
}

.gc-mono {
    font-family: 'Lalezar', 'Vazirmatn', serif;
    font-size: calc(var(--gs-card-w) * .16);
    line-height: 1;
    color: var(--gs-accent);
    letter-spacing: .02em;
    opacity: .9;
}

.gc-mono small {
    display: block;
    margin-top: .5em;
    font-family: 'Vazirmatn', sans-serif;
    font-size: .16em;
    letter-spacing: .3em;
    text-align: center;
    opacity: .75;
}

/* --- the letter: the same card twice, each copy clipped to one half, with a hinge between */

.gc-letter .gc-fold {
    position: relative;
    aspect-ratio: 2 / 3;
    transform-style: preserve-3d;
    transition: transform 1.1s var(--gs-ease-out);
}

.gc-half {
    position: absolute;
    left: 0;
    width: 100%;
    height: 50%;
    transform-style: preserve-3d;
}

.gc-top {
    top: 0;
    transform-origin: 50% 100%;
    transform: translateZ(.5px) rotateX(0deg);
    transition: transform 1.25s cubic-bezier(.3, .1, .2, 1);
}

.gc-bottom {
    top: 50%;
}

.gc-sheet {
    position: absolute;
    left: 0;
    width: 100%;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
}

.gc-top .gc-sheet {
    top: 0;
    clip-path: inset(0 0 50% 0);
}

.gc-bottom .gc-sheet {
    top: -100%;
    clip-path: inset(50% 0 0 0);
}

.gc-top .gc-back {
    transform: rotateX(180deg);
    border-radius: 6px 6px 0 0;
}

/* The fold's shadow: strongest just as the top half stands upright. */
.gc-bottom::after {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 40%;
    background: linear-gradient(to bottom, rgba(40, 20, 10, .28), transparent);
    opacity: 0;
    transition: opacity 1.2s ease;
    pointer-events: none;
}

.gc-letter .gc-back .gc-mono { font-size: calc(var(--gs-card-w) * .12); }

/* Folded: the top half lies over the bottom one, showing its back, and the whole letter is
   lifted by a quarter so the folded piece sits in the middle of its box. */
.gs[data-js]:not([data-unfolded]) .gc-letter .gc-top {
    transform: translateZ(.5px) rotateX(-180deg);
}

.gs[data-js]:not([data-unfolded]) .gc-letter .gc-fold {
    transform: translateY(-25%);
}

.gs[data-js]:not([data-unfolded]) .gc-letter .gc-bottom::after {
    opacity: 1;
}

/* ------------------------------------------------ curtain */

.gs-curtain {
    position: absolute;
    inset: 0;
    z-index: 5;
    pointer-events: none;
    display: none;
}

.gs-open-curtain .gs-curtain {
    display: block;
}

.gs-drape {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 51%;
    background:
        linear-gradient(to bottom, rgba(0, 0, 0, .35), transparent 18%, transparent 80%, rgba(0, 0, 0, .45)),
        repeating-linear-gradient(90deg,
            color-mix(in srgb, var(--gs-velvet) 70%, #000) 0,
            var(--gs-velvet) 2.2%,
            color-mix(in srgb, var(--gs-velvet) 75%, #ff8a8a) 3.6%,
            var(--gs-velvet) 5%,
            color-mix(in srgb, var(--gs-velvet) 70%, #000) 7.2%);
    box-shadow: inset 0 0 60px rgba(0, 0, 0, .45);
    transition: transform 1.9s cubic-bezier(.65, .02, .25, 1);
}

.gs-drape-r { right: 0; transform-origin: 100% 50%; }
.gs-drape-l { left: 0;  transform-origin: 0 50%; }

/* The inner edges carry a gold trim, the way theatre curtains do. */
.gs-drape-r::before,
.gs-drape-l::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 10px;
    background: linear-gradient(90deg, #7a5a1c, #f3d27a 45%, #9a7428);
}

.gs-drape-r::before { left: 0; }
.gs-drape-l::before { right: 0; }

.gs-valance {
    position: absolute;
    inset: 0 0 auto;
    height: 12vh;
    min-height: 70px;
    background:
        radial-gradient(60% 100% at 50% 0, transparent 0, transparent 60%, rgba(0, 0, 0, .35) 61%, transparent 75%) 0 100% / 12vh 38% repeat-x,
        linear-gradient(to bottom, color-mix(in srgb, var(--gs-velvet) 60%, #000), var(--gs-velvet));
    box-shadow: 0 8px 30px rgba(0, 0, 0, .5);
    z-index: 1;
}

.gs-valance::after {
    content: "";
    position: absolute;
    inset: auto 0 -8px;
    height: 12px;
    background: repeating-linear-gradient(90deg, #f3d27a 0 3px, #9a7428 3px 5px, transparent 5px 8px);
    filter: drop-shadow(0 3px 3px rgba(0, 0, 0, .5));
}

.gs-hit-curtain {
    inset: 0;
    pointer-events: none;
}

.gs[data-phase="curtain"] .gs-hit-curtain {
    pointer-events: auto;
}

/* Closed curtains sway, just enough to look like fabric. */
.gs[data-phase="curtain"] .gs-drape {
    animation: gs-sway 6s ease-in-out infinite;
}

/* On `translate`, not `transform`: the parting is a transition on transform, and a transition
   does not start cleanly from a value an animation was holding - the drapes would jump open. */
@keyframes gs-sway {
    0%, 100% { translate: 0 0; }
    50%      { translate: .5% 0; }
}

/* Parted, and afterwards: gathered at the sides as a frame for the stage. Each drape is
   squeezed towards its own outer edge (that is where its transform-origin is), so it bunches
   up like fabric on a rail instead of sliding away. Narrow enough on a phone to clear the card. */
.gs[data-js]:not([data-phase="curtain"]) .gs-drape {
    transform: scaleX(.3);
}

@media (max-width: 700px) {
    .gs[data-js]:not([data-phase="curtain"]) .gs-drape {
        transform: scaleX(.09);
    }
}

/* Without the script there is nobody to open them, so they start open. */
.gs:not([data-js]) .gs-drape {
    transform: scaleX(.09);
}

/* A spotlight on the stage behind the curtain. */
.gs-open-curtain .gs-ground::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: radial-gradient(38% 55% at 50% 48%, rgba(255, 244, 220, .28), transparent 70%);
    opacity: 0;
    transition: opacity 1.5s ease .6s;
}

.gs-open-curtain[data-js]:not([data-phase="curtain"]) .gs-ground::before {
    opacity: 1;
}

/* ------------------------------------------------ celebration */

.gs-fx {
    position: absolute;
    inset: 0;
    z-index: 6;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* ------------------------------------------------ hint */

.gs-hint {
    position: absolute;
    left: 50%;
    bottom: calc(34px + env(safe-area-inset-bottom));
    z-index: 7;
    margin: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 18px 10px 16px;
    border-radius: 999px;
    background: rgba(20, 14, 20, .5);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .14) inset;
    font-size: 14px;
    white-space: nowrap;
    transform: translate(-50%, 16px);
    opacity: 0;
    transition: opacity .5s ease, transform .5s var(--gs-ease-out);
    pointer-events: none;
}

.gs-hint.is-on {
    opacity: 1;
    transform: translate(-50%, 0);
}

.gs-hint svg {
    width: 20px;
    height: 20px;
    flex: none;
    animation: gs-tap 1.6s ease-in-out infinite;
}

@keyframes gs-tap {
    0%, 100% { transform: translateY(0) scale(1); }
    45%      { transform: translateY(-3px) scale(.92); }
}

/* ------------------------------------------------ controls */

.gs-tools {
    position: absolute;
    top: calc(14px + env(safe-area-inset-top));
    left: 14px;
    z-index: 8;
    display: flex;
    gap: 8px;
}

.gs-round {
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(20, 14, 20, .42);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .16) inset;
    cursor: pointer;
    transition: background .2s ease, transform .2s ease, opacity .4s ease;
}

.gs-round:hover { background: rgba(20, 14, 20, .62); }
.gs-round:active { transform: scale(.92); }

.gs-round:focus-visible,
.gs-pill:focus-visible,
.gs-seek:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 3px;
}

.gs-round svg {
    width: 20px;
    height: 20px;
}

.gs-round[hidden] { display: none; }

/* Replay makes sense only once there is something to replay. */
.gs-replay {
    opacity: 0;
    pointer-events: none;
}

.gs[data-js][data-phase="card"] .gs-replay,
.gs[data-js][data-phase="card"] .gs-zoom {
    opacity: 1;
    pointer-events: auto;
}

/* Zoom: in front of you, or back on its envelope. The icon shows where a tap will take it. */
.gs-zoom {
    opacity: 0;
    pointer-events: none;
}

.gs-zoom .gs-ico-in { display: none; }
.gs[data-zoom="out"] .gs-zoom .gs-ico-in { display: block; }
.gs[data-zoom="out"] .gs-zoom .gs-ico-out { display: none; }

.gs-full .gs-ico-exit { display: none; }
.gs.is-full .gs-full .gs-ico-enter { display: none; }
.gs.is-full .gs-full .gs-ico-exit { display: block; }

/* "I have seen it before": straight to the card. */
.gs-skip {
    display: none;
    width: auto;
    height: 42px;
    padding: 0 16px;
    border-radius: 999px;
    font-size: 13px;
}

.gs.can-skip[data-phase="sealed"] .gs-skip,
.gs.can-skip[data-phase="turned"] .gs-skip,
.gs.can-skip[data-phase="curtain"] .gs-skip {
    display: grid;
}

/* Without the script these buttons would do nothing, so they are not offered. The card - the
   part that matters - is already on screen. */
.gs:not([data-js]) .gs-tools,
.gs:not([data-js]) .gs-player {
    display: none;
}

/* ------------------------------------------------ the dock */

.gs-dock {
    position: absolute;
    left: 50%;
    bottom: calc(16px + env(safe-area-inset-bottom));
    z-index: 8;
    display: flex;
    align-items: center;
    gap: 10px;
    width: min(calc(100vw - 24px), 560px);
    padding: 8px;
    border-radius: 999px;
    background: rgba(20, 14, 20, .46);
    backdrop-filter: blur(14px) saturate(1.2);
    -webkit-backdrop-filter: blur(14px) saturate(1.2);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .14) inset, 0 12px 40px rgba(0, 0, 0, .3);
    transform: translate(-50%, 140%);
    opacity: 0;
    transition: transform .8s var(--gs-ease-out), opacity .6s ease;
}

.gs[data-phase="card"] .gs-dock {
    transform: translate(-50%, 0);
    opacity: 1;
}

.gs-player {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 0;
}

.gs-play {
    flex: none;
    width: 44px;
    height: 44px;
    background: var(--gs-accent);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .25) inset, 0 4px 14px color-mix(in srgb, var(--gs-accent) 55%, transparent);
}

.gs-play .gs-ico-pause { display: none; }
.gs.is-playing .gs-play .gs-ico-pause { display: block; }
.gs.is-playing .gs-play .gs-ico-play { display: none; }

.gs-seek {
    flex: 1;
    min-width: 0;
    height: 28px;
    margin: 0;
    background: transparent;
    -webkit-appearance: none;
    appearance: none;
    cursor: pointer;
    --gs-seek: 0%;
}

.gs-seek::-webkit-slider-runnable-track {
    height: 4px;
    border-radius: 4px;
    background: linear-gradient(to left, #fff var(--gs-seek), rgba(255, 255, 255, .25) var(--gs-seek));
}

.gs-seek::-moz-range-track {
    height: 4px;
    border-radius: 4px;
    background: rgba(255, 255, 255, .25);
}

.gs-seek::-moz-range-progress {
    height: 4px;
    border-radius: 4px;
    background: #fff;
}

.gs-seek::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 14px;
    height: 14px;
    margin-top: -5px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--gs-accent) 55%, transparent);
}

.gs-seek::-moz-range-thumb {
    width: 14px;
    height: 14px;
    border: 0;
    border-radius: 50%;
    background: #fff;
}

.gs-time {
    flex: none;
    min-width: 38px;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    opacity: .8;
    direction: ltr;
}

.gs-pill {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 44px;
    padding: 0 18px;
    border: 0;
    border-radius: 999px;
    background: #fff;
    color: #231a22;
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: transform .2s ease, box-shadow .2s ease;
}

.gs-pill:hover { box-shadow: 0 6px 20px rgba(0, 0, 0, .25); }
.gs-pill:active { transform: scale(.96); }

.gs-pill svg { width: 18px; height: 18px; }

/* No song: the dock is just its buttons, centred. */
.gs-dock.is-bare {
    width: auto;
    justify-content: center;
}

/* ------------------------------------------------ the demo's own bar (template gallery only) */

.gs-demo {
    position: absolute;
    top: calc(14px + env(safe-area-inset-top));
    right: 14px;
    z-index: 9;
    display: flex;
    gap: 8px;
    align-items: flex-start;
}

.gs-demo a { color: #fff; }

.gs-demo-back {
    display: grid;
    place-items: center;
    text-decoration: none;
}

.gs-demo details {
    position: relative;
}

.gs-demo summary {
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 42px;
    padding: 0 16px;
    border-radius: 999px;
    background: rgba(20, 14, 20, .42);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .16) inset;
    font-size: 13px;
    cursor: pointer;
}

.gs-demo summary::-webkit-details-marker { display: none; }

.gs-demo summary svg { width: 18px; height: 18px; }

.gs-demo-menu {
    position: absolute;
    top: 50px;
    right: 0;
    width: min(290px, calc(100vw - 28px));
    padding: 14px;
    border-radius: 18px;
    background: rgba(24, 18, 26, .86);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .12) inset, 0 20px 50px rgba(0, 0, 0, .45);
}

.gs-demo-menu h2 {
    margin: 0 0 8px;
    font-size: 12px;
    font-weight: 500;
    opacity: .7;
}

.gs-demo-menu h2:not(:first-child) { margin-top: 14px; }

.gs-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.gs-chips a {
    padding: 6px 12px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .08);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .12) inset;
    color: #fff;
    font-size: 13px;
    text-decoration: none;
}

.gs-chips a[aria-current="true"] {
    background: #fff;
    color: #231a22;
}

/* ------------------------------------------------ phones */

@media (max-width: 520px) {
    .gs-time { display: none; }
    .gs-pill { padding: 0 14px; font-size: 13px; }
    .gs-demo summary span { display: none; }
    .gs-demo summary { width: 42px; padding: 0; justify-content: center; }
}

/* ------------------------------------------------ reduced motion

   The invitation is the content. Nobody should have to sit through choreography to read it,
   so guest.js goes straight to the card, and nothing here moves on its own. */

@media (prefers-reduced-motion: reduce) {
    .gs *,
    .gs *::before,
    .gs *::after {
        animation: none !important;
        transition: none !important;
    }
}

/* While a replay resets the stage, nothing should visibly fly backwards. */
.gs.is-instant *,
.gs.is-instant *::before,
.gs.is-instant *::after {
    transition: none !important;
    animation: none !important;
}


/* ------------------------------------------------ the ceremony: button and panel */

/* The two things to do once the card is out - answer, and read when and where - side by side
   above the dock. They arrive with the card, a moment after it settles. */
.gs-asks {
    position: absolute;
    left: 50%;
    bottom: calc(90px + env(safe-area-inset-bottom));
    z-index: 8;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    width: max-content;
    max-width: calc(100vw - 24px);
    transform: translate(-50%, 16px);
    opacity: 0;
    pointer-events: none;
    transition: opacity .6s ease .3s, transform .7s var(--gs-ease-out) .3s;
}

.gs[data-phase="card"] .gs-asks,
.gs:not([data-js]) .gs-asks {
    opacity: 1;
    transform: translate(-50%, 0);
    pointer-events: auto;
}

/* ".gs button" makes every button inherit the stage's white text; these sit on white. */
.gs .gs-ask {
    color: #231a22;
}

.gs-ask {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 44px;
    padding: 0 18px;
    border: 0;
    border-radius: 999px;
    background: rgba(255, 255, 255, .94);
    font-size: 14px;
    font-weight: 600;
    white-space: nowrap;
    box-shadow: 0 8px 26px rgba(0, 0, 0, .3);
    cursor: pointer;
    transition: transform .2s ease, box-shadow .2s ease, background .3s ease;
}

.gs-ask:hover { box-shadow: 0 10px 30px rgba(0, 0, 0, .36); }
.gs-ask:active { transform: scale(.96); }
.gs-ask svg { width: 18px; height: 18px; flex: none; color: var(--gs-accent); }
.gs-ask:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* The answer is the one the host is waiting for, so it wears the card's colour. */
.gs .gs-rsvp-btn {
    background: var(--gs-accent);
    color: #fff;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .28) inset, 0 8px 26px color-mix(in srgb, var(--gs-accent) 50%, rgba(0, 0, 0, .35));
}

.gs .gs-rsvp-btn svg { color: #fff; }
.gs-rsvp-btn .gs-ico-done { display: none; }
.gs-rsvp-btn.is-answered .gs-ico-done { display: block; }
.gs-rsvp-btn.is-answered .gs-ico-rsvp { display: none; }

/* Answered: back to white, with the card's colour in the tick - done, not asking any more. */
.gs .gs-rsvp-btn.is-answered {
    background: rgba(255, 255, 255, .94);
    color: #231a22;
    box-shadow: 0 8px 26px rgba(0, 0, 0, .3);
}

.gs .gs-rsvp-btn.is-answered svg { color: var(--gs-accent); }

.gs-rsvp-btn.is-pop { animation: gs-pop .6s var(--gs-ease-out); }
@keyframes gs-pop { 40% { transform: scale(1.12); } }

/* Not answered yet: a slow breath, so the eye finds it without it shouting. */
.gs[data-phase="card"] .gs-rsvp-btn:not(.is-answered) { animation: gs-breathe 2.8s ease-in-out 1.6s 3; }
@keyframes gs-breathe {
    50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--gs-accent) 30%, transparent), 0 8px 26px rgba(0, 0, 0, .3); }
}

@media (max-width: 380px) {
    .gs-ask { padding: 0 13px; font-size: 13px; gap: 6px; }
}

/* A sheet from the bottom on a phone, a centred panel on a wide screen. The browser's own
   popover box is emptied first. */
.gs-sheet {
    box-sizing: border-box;
    position: fixed;
    inset: auto 0 0 0;
    width: min(100%, 560px);
    max-width: none;
    height: auto;
    max-height: 88vh;
    max-height: 88dvh;
    margin: 0 auto;
    padding: 10px 18px calc(22px + env(safe-area-inset-bottom));
    overflow: auto;
    border: 0;
    border-radius: 22px 22px 0 0;
    background: #fbf8f4;
    color: #2a2229;
    font-family: 'Vazirmatn', system-ui, sans-serif;
    box-shadow: 0 -20px 60px rgba(0, 0, 0, .4);
    user-select: text;
    -webkit-user-select: text;
    overscroll-behavior: contain;
}

.gs-sheet:popover-open { animation: gs-sheet-up .45s var(--gs-ease-out); }
.gs-sheet::backdrop { background: rgba(10, 6, 12, .5); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
.gs-sheet:popover-open::backdrop { animation: gs-fade .3s ease; }

@keyframes gs-sheet-up { from { transform: translateY(100%); } }
@keyframes gs-fade { from { opacity: 0; } }

@media (min-width: 700px) {
    .gs-sheet {
        inset: 0;
        height: fit-content;
        margin: auto;
        border-radius: 22px;
        padding-bottom: 22px;
    }

    .gs-sheet:popover-open { animation: gs-sheet-pop .35s var(--gs-ease-out); }
    @keyframes gs-sheet-pop { from { opacity: 0; transform: translateY(16px) scale(.97); } }
}

.gs-sheet-head {
    position: sticky;
    top: -10px;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: -10px -18px 6px;
    padding: 14px 18px 10px;
    background: #fbf8f4;
}

/* The grip a bottom sheet is expected to have. */
.gs-sheet-head::before {
    content: "";
    position: absolute;
    top: 6px;
    left: 50%;
    width: 40px;
    height: 4px;
    margin-left: -20px;
    border-radius: 4px;
    background: rgba(40, 30, 40, .18);
}

.gs-sheet-head h2 { margin: 0; font-size: 17px; }

.gs .gs-sheet-close { color: #2a2229; }

.gs-sheet-close {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(40, 30, 40, .07);
    color: inherit;
    cursor: pointer;
}

.gs-sheet-close svg { width: 18px; height: 18px; }

.gs-sample {
    margin: 0 0 10px;
    padding: 8px 12px;
    border-radius: 10px;
    background: color-mix(in srgb, var(--gs-accent) 12%, #fff);
    font-size: 12.5px;
}

.gs-sec {
    padding: 14px 0;
    border-top: 1px solid rgba(40, 30, 40, .08);
}

.gs-sec:first-of-type { border-top: 0; }

.gs-count-label {
    margin: 0 0 10px;
    font-size: 13px;
    text-align: center;
    opacity: .75;
}

.gs-count {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
    direction: rtl;
}

.gs-count div {
    display: grid;
    justify-items: center;
    gap: 2px;
    padding: 10px 4px;
    border-radius: 14px;
    background: color-mix(in srgb, var(--gs-accent) 14%, #fff);
}

.gs-count b {
    font-size: 26px;
    line-height: 1.1;
    color: color-mix(in srgb, var(--gs-accent) 80%, #111);
    font-variant-numeric: tabular-nums;
}

.gs-count span { font-size: 12px; opacity: .7; }

.gs-count.is-hidden { display: none; }

.gs-date {
    margin: 12px 0 0;
    font-size: 16px;
    font-weight: 600;
    text-align: center;
}

.gs-time-text {
    margin: 2px 0 0;
    font-size: 14px;
    text-align: center;
    opacity: .8;
}

.gs-place { margin: 0; font-size: 16px; }
.gs-address { margin: 4px 0 0; font-size: 14px; line-height: 1.8; opacity: .85; }

.gs-map {
    height: 200px;
    margin-top: 12px;
    border-radius: 14px;
    overflow: hidden;
    background: #e9e4dc;
    direction: ltr;
}

.gs-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 12px;
}

.gs-links-title {
    width: 100%;
    font-size: 12.5px;
    opacity: .7;
}

.gs-links a {
    flex: 1 1 0;
    min-width: 0;
    padding: 9px 4px;
    white-space: nowrap;
    border-radius: 12px;
    background: #fff;
    box-shadow: inset 0 0 0 1px rgba(40, 30, 40, .12);
    color: inherit;
    font-size: 13.5px;
    text-align: center;
    text-decoration: none;
}

.gs-links a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
}

.gs-links a svg {
    flex: none;
    width: 15px;
    height: 15px;
    color: var(--gs-accent);
}

.gs-links a .gs-brand {
    flex: none;
    width: 18px;
    height: 18px;
    border-radius: 4px;
    object-fit: contain;
}

.gs-links a:hover { box-shadow: inset 0 0 0 1.5px var(--gs-accent); }
.gs-links.is-hidden { display: none; }


/* ------------------------------------------------ the answer sheet */

.gs-rsvp-lead {
    margin: 0 0 14px;
    font-size: 14.5px;
    line-height: 1.9;
}

.gs-field { display: grid; gap: 6px; }
.gs-field > span { font-size: 13px; opacity: .75; }

.gs-field input {
    box-sizing: border-box;
    width: 100%;
    height: 50px;
    padding: 0 14px;
    border: 0;
    border-radius: 14px;
    background: #fff;
    box-shadow: inset 0 0 0 1.5px rgba(40, 30, 40, .14);
    color: inherit;
    font: inherit;
    font-size: 16px; /* 16px or more, or iOS zooms the page when the field is tapped */
    transition: box-shadow .2s ease;
}

.gs-field input:focus { outline: 0; box-shadow: inset 0 0 0 2px var(--gs-accent); }
.gs-field input::placeholder { color: rgba(40, 30, 40, .38); }

/* Out of sight and out of the tab order; only a script that fills every box ever finds it. */
.gs-trap {
    position: absolute !important;
    left: -9999px !important;
    width: 1px !important;
    height: 1px !important;
    opacity: 0 !important;
}

.gs-choice {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin: 16px 0 0;
    padding: 0;
    border: 0;
}

.gs-choice legend {
    padding: 0;
    margin-bottom: 8px;
    font-size: 13px;
    opacity: .75;
}

.gs-opt { position: relative; cursor: pointer; }

.gs-opt input {
    position: absolute;
    inset: 0;
    opacity: 0;
    margin: 0;
    cursor: pointer;
}

.gs-opt span {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 88px;
    padding: 12px 8px;
    border-radius: 16px;
    background: #fff;
    box-shadow: inset 0 0 0 1.5px rgba(40, 30, 40, .12);
    font-size: 14.5px;
    font-weight: 600;
    text-align: center;
    transition: box-shadow .2s ease, background .2s ease, transform .2s ease;
}

.gs-opt svg {
    width: 26px;
    height: 26px;
    padding: 6px;
    border-radius: 50%;
    background: rgba(40, 30, 40, .06);
    color: rgba(40, 30, 40, .45);
    transition: background .2s ease, color .2s ease, transform .35s var(--gs-ease-out);
}

.gs-opt:hover span { box-shadow: inset 0 0 0 1.5px rgba(40, 30, 40, .24); }
.gs-opt input:focus-visible + span { outline: 2px solid var(--gs-accent); outline-offset: 2px; }

.gs-opt-yes input:checked + span {
    background: color-mix(in srgb, var(--gs-accent) 12%, #fff);
    box-shadow: inset 0 0 0 2px var(--gs-accent);
}

.gs-opt-yes input:checked + span svg {
    background: var(--gs-accent);
    color: #fff;
    transform: scale(1.12);
}

.gs-opt-no input:checked + span {
    background: #f3eff0;
    box-shadow: inset 0 0 0 2px #8d7f86;
}

.gs-opt-no input:checked + span svg {
    background: #8d7f86;
    color: #fff;
}

.gs-rsvp-error {
    min-height: 0;
    margin: 10px 0 0;
    color: #b0373a;
    font-size: 13px;
}

.gs-rsvp-error:empty { display: none; }

.gs-submit {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 52px;
    margin-top: 16px;
    border: 0;
    border-radius: 16px;
    background: var(--gs-accent);
    color: #fff;
    font: inherit;
    font-size: 15.5px;
    font-weight: 700;
    cursor: pointer;
    box-shadow: 0 8px 22px color-mix(in srgb, var(--gs-accent) 40%, transparent);
    transition: transform .15s ease, filter .2s ease;
}

.gs .gs-submit { color: #fff; }
.gs-submit:hover { filter: brightness(1.06); }
.gs-submit:active { transform: scale(.98); }

.gs-spin {
    display: none;
    width: 20px;
    height: 20px;
    border: 2.5px solid rgba(255, 255, 255, .45);
    border-top-color: #fff;
    border-radius: 50%;
    animation: gs-turn .8s linear infinite;
}

.gs-rsvp-form.is-busy .gs-submit span { display: none; }
.gs-rsvp-form.is-busy .gs-spin { display: block; }
@keyframes gs-turn { to { transform: rotate(360deg); } }

/* The site's own submit-button rule (digicard.css) is more specific than a single class; this
   one outranks it so the answer button keeps the card's colour. */
.gs-rsvp-form button.gs-submit[type="submit"],
.gs-rsvp-form button.gs-submit[type="submit"]:hover {
    height: 52px;
    padding: 0;
    border: 0;
    border-radius: 16px;
    background: var(--gs-accent);
    color: #fff;
    font-size: 15.5px;
}

/* Form or thank-you, never both. */
.gs-rsvp[data-state="done"] .gs-rsvp-form { display: none; }
.gs-rsvp[data-state="form"] .gs-rsvp-done { display: none; }

.gs-rsvp-done {
    display: grid;
    justify-items: center;
    gap: 6px;
    padding: 10px 4px 4px;
    text-align: center;
}

.gs-rsvp-done h3 { margin: 6px 0 0; font-size: 19px; }
.gs-rsvp-done p { margin: 0; font-size: 14px; line-height: 1.9; opacity: .85; }

/* The tick draws itself: a circle, then the check. */
.gs-done-mark {
    width: 76px;
    height: 76px;
    fill: none;
    stroke: var(--gs-accent);
    stroke-width: 3.2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.gs-done-mark circle {
    fill: color-mix(in srgb, var(--gs-accent) 12%, #fff);
    stroke-dasharray: 176;
    stroke-dashoffset: 176;
    transform: rotate(-90deg);
    transform-box: fill-box;
    transform-origin: center;
}

.gs-done-mark path { stroke-dasharray: 40; stroke-dashoffset: 40; }

.gs-rsvp[data-state="done"]:popover-open .gs-done-mark circle { animation: gs-draw .7s var(--gs-ease-out) .1s forwards; }
.gs-rsvp[data-state="done"]:popover-open .gs-done-mark path { animation: gs-draw .45s var(--gs-ease-out) .65s forwards; }
@keyframes gs-draw { to { stroke-dashoffset: 0; } }

/* A "no" is thanked just as warmly, in a quieter colour. */
.gs-rsvp[data-answer="no"] .gs-done-mark { stroke: #8d7f86; }
.gs-rsvp[data-answer="no"] .gs-done-mark circle { fill: #f3eff0; }

.gs-link-btn {
    margin-top: 8px;
    padding: 8px 14px;
    border: 0;
    border-radius: 999px;
    background: rgba(40, 30, 40, .06);
    color: inherit;
    font: inherit;
    font-size: 13.5px;
    cursor: pointer;
}

.gs .gs-link-btn { color: #2a2229; }

@media (prefers-reduced-motion: reduce) {
    .gs-done-mark circle, .gs-done-mark path { stroke-dashoffset: 0; animation: none !important; }
    .gs-rsvp-btn, .gs-rsvp-btn.is-pop { animation: none !important; }
}


/* ------------------------------------------------ how many are coming */

.gs-seats {
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 14px;
    padding: 10px 14px;
    border-radius: 16px;
    background: color-mix(in srgb, var(--gs-accent) 9%, #fff);
    box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--gs-accent) 30%, transparent);
    animation: gs-fade .3s ease;
}

.gs-rsvp-form:has(.gs-opt-yes input:checked) .gs-seats { display: flex; }
/* A browser without :has() shows it always; the number is ignored on a "no". */
@supports not selector(:has(*)) { .gs-seats { display: flex; } }

.gs-seats label { font-size: 14px; font-weight: 600; }
.gs-seats small { font-weight: 400; opacity: .65; }

.gs-stepper { display: flex; align-items: center; gap: 6px; direction: ltr; }

.gs-stepper input {
    width: 48px;
    height: 40px;
    border: 0;
    border-radius: 12px;
    background: #fff;
    box-shadow: inset 0 0 0 1.5px rgba(40, 30, 40, .14);
    color: inherit;
    font: inherit;
    font-size: 18px;
    font-weight: 700;
    text-align: center;
    -moz-appearance: textfield;
    appearance: textfield;
}

.gs-stepper input::-webkit-outer-spin-button,
.gs-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.gs .gs-step {
    width: 40px;
    height: 40px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--gs-accent);
    color: #fff;
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
}

.gs .gs-step:active { transform: scale(.92); }

/* ------------------------------------------------ the wishes wall */

.gs .gs-wish-btn svg { color: var(--gs-accent); }

.gs-wishes textarea {
    box-sizing: border-box;
    width: 100%;
    padding: 12px 14px;
    border: 0;
    border-radius: 14px;
    background: #fff;
    box-shadow: inset 0 0 0 1.5px rgba(40, 30, 40, .14);
    color: inherit;
    font: inherit;
    font-size: 16px;
    line-height: 1.8;
    resize: vertical;
}

.gs-wishes textarea:focus { outline: 0; box-shadow: inset 0 0 0 2px var(--gs-accent); }
.gs-wishes .gs-field + .gs-field { margin-top: 12px; }
.gs-count-chars { min-height: 0; font-size: 12px; opacity: .6; text-align: left; }
.gs-count-chars:empty { display: none; }

.gs-wish-form button.gs-submit[type="submit"],
.gs-wish-form button.gs-submit[type="submit"]:hover {
    height: 50px;
    padding: 0;
    border: 0;
    border-radius: 16px;
    background: var(--gs-accent);
    color: #fff;
    font-size: 15px;
}

.gs-wish-form.is-busy .gs-submit span { display: none; }
.gs-wish-form.is-busy .gs-spin { display: block; }

.gs-wish-note { margin: 8px 0 0; font-size: 12px; text-align: center; opacity: .6; }

.gs-wishes[data-state="sent"] .gs-wish-form { display: none; }
.gs-wishes[data-state="form"] .gs-wish-sent { display: none; }

.gs-wish-sent { display: grid; justify-items: center; gap: 4px; padding: 6px 4px 10px; text-align: center; }
.gs-wish-sent h3 { margin: 4px 0 0; font-size: 18px; }
.gs-wish-sent p { margin: 0; font-size: 14px; opacity: .8; }
.gs-wishes[data-state="sent"]:popover-open .gs-done-mark circle { animation: gs-draw .7s var(--gs-ease-out) .1s forwards; }
.gs-wishes[data-state="sent"]:popover-open .gs-done-mark path { animation: gs-draw .45s var(--gs-ease-out) .65s forwards; }

/* The notes themselves: little squares of coloured paper, pinned slightly askew, in the card's
   own colour and two gentle neighbours of it. */
.gs-wall {
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid rgba(40, 30, 40, .08);
}

.gs-wall ul {
    columns: 2;
    column-gap: 10px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.gs-wall li {
    break-inside: avoid;
    position: relative;
    margin: 0 0 10px;
    padding: 14px 14px 10px;
    border-radius: 3px 14px 4px 12px;
    background: color-mix(in srgb, var(--gs-accent) 16%, #fffdf6);
    box-shadow: 0 1px 1px rgba(0, 0, 0, .06), 0 8px 18px rgba(40, 30, 40, .09);
    transform: rotate(-1.2deg);
}

.gs-wall li:nth-child(3n + 2) { background: #fff4d9; transform: rotate(1.4deg); }
.gs-wall li:nth-child(3n) { background: #eaf3ec; transform: rotate(-.4deg); }

/* A strip of tape, holding it to the wall. */
.gs-wall li::before {
    content: "";
    position: absolute;
    top: -7px;
    left: 50%;
    width: 44px;
    height: 14px;
    margin-left: -22px;
    background: rgba(255, 255, 255, .6);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
    transform: rotate(-3deg);
}

.gs-wall li p { margin: 0; font-size: 14px; line-height: 1.85; white-space: pre-line; overflow-wrap: anywhere; }
.gs-wall li span { display: block; margin-top: 6px; font-size: 12.5px; font-weight: 600; opacity: .7; text-align: left; }

.gs-wall-empty { margin: 4px 0 0; font-size: 13.5px; text-align: center; opacity: .65; }

@media (max-width: 380px) {
    .gs-wall ul { columns: 1; }
}


/* ------------------------------------------------ playing itself (the front page's phone) */

.gs[data-autoplay] .gs-tools,
.gs[data-autoplay] .gs-dock,
.gs[data-autoplay] .gs-hint,
.gs[data-autoplay] .gs-demo { display: none; }
.gs[data-autoplay] .gs-asks { bottom: calc(18px + env(safe-area-inset-bottom)); }

/* The free plan's line at the foot of each panel (docs/HANDOFF.md 9-B). Quiet on purpose: it is
   read by a guest who has already opened the card, and it must never compete with the card. */
.gs-brand {
    margin: 18px 0 0;
    padding-top: 12px;
    border-top: 1px solid rgba(40, 30, 40, .08);
    text-align: center;
    font-size: 12px;
}

.gs-brand a {
    color: rgba(42, 34, 41, .55);
    text-decoration: none;
}

.gs-brand a b { color: rgba(42, 34, 41, .8); font-weight: 600; }
