/* ==========================================================================
   Emma & Rayan - scratch-to-reveal section

   The artwork is never altered. All geometry below is derived from measured
   pixel positions inside scratch-to-reveal.png (1080 x 1920):

     couple photo circle .... bbox x 266-814, y 645-1189
                              centre (540, 917), diameter 549px (r 274.5)
     coating box ............ 568px square at (256, 633), r 284. Deliberately
                              wider than the photo: the gold foil is solid
                              out to r 280 and only softens between r 280 and
                              r 284, which is over the panel rather than over
                              the photo, so no bright rim shows.
     gold frame ............. y 539-604 (top) / 1209-1299 (bottom)
     "Scratch to reveal" .... y 1400-1455
     empty panel ............ y 1470-1835   <- names + date live here
     bottom gold bar ........ y 1840-1899
   ========================================================================== */

:root {
  --art-w: 1080;
  --art-h: 1920;

  /* coating box, as percentages of the artwork box */
  --circle-left: 23.7037%;   /* 256 / 1080 */
  --circle-top:  32.9688%;   /* 633 / 1920 */
  --circle-size: 52.5926%;   /* 568 / 1080 */

  /* the "Scratch to reveal" caption baked into the artwork, y 1390-1470 */
  --caption-top:    72.3958%;
  --caption-height:  4.1667%;

  /* text centres, as percentages of artwork height */
  --names-top: 81.3%;        /* ~1561 / 1920 */
  --date-top:  87.0%;        /* ~1670 / 1920 - kept close under the names */

  /* sampled straight out of the artwork */
  --gold-light: #fdf3d6;
  --gold-mid:   #eccf8f;
  --gold-deep:  #bb9769;
  --gold-date:  #d9c49c;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: #000;              /* matches the black panel in the artwork */
  color: var(--gold-mid);
}

.stage {
  display: grid;
  place-items: center;
  min-height: 100vh;
  min-height: 100dvh;
}

/* --------------------------------------------------------------------------
   The artwork box - locked to 1080:1920 so nothing is ever cropped or
   stretched, and sized so the whole invitation always fits the viewport.
   -------------------------------------------------------------------------- */
.invite {
  position: relative;
  width: min(100vw, calc(100vh * var(--art-w) / var(--art-h)));
  width: min(100vw, calc(100dvh * var(--art-w) / var(--art-h)));
  aspect-ratio: var(--art-w) / var(--art-h);
  container-type: inline-size;   /* enables cqw units for type sizing */
  overflow: hidden;
  background: #000;

  /* Held back until the artwork can actually be painted - see the short
     script under this section in index.html.

     Without it the guest saw the page assemble itself: the gold coating is
     a canvas and paints the instant it is drawn, while the artwork behind it
     is a 1.6MB PNG that still has to be decoded. So for a frame or two there
     was a gold disc, at the wrong size, on an empty black page, and then the
     artwork arrived underneath and it jumped. Everything on this page is
     unchanged; it simply is not shown until all of it can be. */
  opacity: 0;
}

.invite.is-ready {
  /* No transition. Fading a full-screen layer in costs a phone a composited
     copy of the whole screen at the busiest moment of the load, and the page
     is already whole by the time this runs - there is nothing to ease. */
  opacity: 1;
}

.invite__art {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   One-shot golden bloom, sits between artwork and coating
   -------------------------------------------------------------------------- */
.invite__bloom {
  position: absolute;
  left: var(--circle-left);
  top: var(--circle-top);
  width: var(--circle-size);
  aspect-ratio: 1;
  z-index: 2;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  background: radial-gradient(circle at 50% 50%,
              rgba(255, 236, 184, .50) 0%,
              rgba(226, 192, 127, .16) 55%,
              rgba(226, 192, 127, 0) 72%);
}

/* --------------------------------------------------------------------------
   The scratch coating - exactly the circular photo, nothing else
   -------------------------------------------------------------------------- */
.invite__scratch {
  position: absolute;
  left: var(--circle-left);
  top: var(--circle-top);
  width: var(--circle-size);
  aspect-ratio: 1;
  z-index: 3;
  border-radius: 50%;            /* clips paint AND hit-testing to the circle */
  display: block;
  cursor: crosshair;
  touch-action: none;            /* scratching must not scroll the page */
  outline: none;
  transition: opacity .95s cubic-bezier(.4, 0, .2, 1),
              transform .95s cubic-bezier(.4, 0, .2, 1),
              filter .95s linear;
}

.invite__scratch:focus-visible {
  box-shadow: 0 0 0 .3cqw rgba(236, 207, 143, .55);
}

/* A barely-there shimmer so a guest knows where to scratch.
   Set HINT = false in scratch.js to remove it entirely. */
.invite__scratch.has-hint {
  animation: hint 3.4s ease-in-out infinite;
}
@keyframes hint {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.16); }
}

/* --------------------------------------------------------------------------
   Names and date - two independent elements, names above, date below
   -------------------------------------------------------------------------- */
.reveal {
  position: absolute;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  margin: 0;
  width: 90%;
  text-align: center;
  visibility: hidden;            /* also keeps it out of the a11y tree */
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
}

/* Great Vibes has very tight sidebearings, which closes the gaps around the
   ampersand. This restores the airy spacing the artwork's own titles use,
   without altering the text itself. */
.reveal--names .amp {
  margin: 0 .16em;
  font-size: .92em;
}

.reveal--names {
  top: var(--names-top);
  font-family: 'Great Vibes', 'Pinyon Script', cursive;
  font-size: 7.8cqw;

  /* Great Vibes' capitals (the E and the R especially) have tall swashes that
     rise well above the em box. background-clip: text paints the gradient
     only inside the element's own box, so a tight line-height was clipping
     the tops of those letters. This gives the box room for the swashes. */
  line-height: 1.5;
  padding: .12em 0 .06em;
  color: var(--gold-mid);        /* fallback if background-clip is unsupported */
  background: linear-gradient(180deg,
              var(--gold-light) 0%, var(--gold-mid) 46%, var(--gold-deep) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 .25cqw .7cqw rgba(0, 0, 0, .7))
          drop-shadow(0 0 1.7cqw rgba(238, 205, 140, .26));
}

.reveal--date {
  top: var(--date-top);
  font-family: 'Cormorant Garamond', Cormorant, Georgia, 'Times New Roman', serif;
  font-weight: 300;
  font-size: 3.15cqw;
  letter-spacing: .14em;
  color: var(--gold-date);
  text-shadow: 0 .18cqw .6cqw rgba(0, 0, 0, .75);
}

/* --------------------------------------------------------------------------
   OPTIONAL: once the photo is revealed, the artwork's baked-in
   "Scratch to reveal" caption no longer applies. This patch hides it by
   covering that strip with a clean band of the artwork's OWN panel (same
   PNG, same x scale, just offset), so nothing is recoloured or guessed.

   OFF by default, because it does hide one of your existing visual
   elements. Set HIDE_CAPTION = true in scratch.js to switch it on.
   -------------------------------------------------------------------------- */
.invite__veil {
  position: absolute;
  left: 0;
  top: var(--caption-top);
  width: 100%;
  height: var(--caption-height);
  z-index: 3;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

.invite__veil img {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: auto;
  /* Sample clean panel from y = 1500, i.e. just below the caption rather than
     from the top of the artwork. The panel brightens very slightly towards
     the bottom, so taking the patch from immediately alongside makes it
     indistinguishable. */
  transform: translateY(-78.125%);
}

/* --------------------------------------------------------------------------
   Golden sprinkles
   -------------------------------------------------------------------------- */
.invite__confetti {
  position: absolute;
  inset: 0;
  z-index: 5;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

/* ==========================================================================
   THE SINGLE SHARED REVEAL STATE

   scratch.js sets isRevealed = true once, at 70%, and flips this one class.
   Every animation below hangs off it with animation-delay: 0s, so the
   coating dissolve, the bloom, the names and the date all begin in the very
   same animation cycle. The sprinkles are kicked off in the same tick.
   ========================================================================== */
.invite.is-revealed .invite__scratch {
  opacity: 0;
  transform: scale(1.06);
  filter: blur(7px);
  pointer-events: none;
}

.invite.is-revealed .invite__bloom {
  animation: bloom 1.5s ease-out 0s both;
}

.invite.is-revealed .reveal { visibility: visible; }

.invite.is-revealed.hide-caption .invite__veil {
  opacity: 1;
  transition: opacity .9s ease 0s;
}

.invite.is-revealed .reveal--names {
  animation: riseNames 1.45s cubic-bezier(.16, .84, .26, 1) 0s both;
}

.invite.is-revealed .reveal--date {
  animation: riseDate 1.6s cubic-bezier(.22, .90, .24, 1) 0s both;
}

@keyframes bloom {
  0%   { opacity: 0; transform: scale(.86); }
  32%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.22); }
}

@keyframes riseNames {
  0%   { opacity: 0; transform: translate(-50%, calc(-50% + 3.4cqw)) scale(.94); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@keyframes riseDate {
  0%   { opacity: 0; transform: translate(-50%, calc(-50% + 2.2cqw)); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translate(-50%, -50%); }
}

/* -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .invite__scratch.has-hint { animation: none; }
  .invite.is-revealed .invite__scratch { transition-duration: .3s; }
  .invite.is-revealed .invite__bloom,
  .invite.is-revealed .reveal--names,
  .invite.is-revealed .reveal--date { animation-duration: .4s; }

  @keyframes riseNames {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(1); }
    100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }
  @keyframes riseDate {
    0%   { opacity: 0; transform: translate(-50%, -50%); }
    100% { opacity: 1; transform: translate(-50%, -50%); }
  }
}

/* ==========================================================================
   SCENE 2 - CLOSED CURTAIN -> BRIDE & GROOM

   Everything below is additive. Scene 1 keeps every one of its own rules;
   the single rule that touches it is `.stage > *`, which only parks each
   scene in the same grid cell so they can overlay. `.invite` was already
   the sole child of that cell and is still centred at the same size, so
   its appearance is unchanged.

   Geometry comes from measured pixels in closed-curtain.png (1080x1920):
     the artwork's own tap pill ... x 406-683, y 1779-1889, fill #000000,
                                    capsule shape (radius = height / 2)
   The button is drawn 3px larger all round so it also covers the pill's
   anti-aliased edge.
   ========================================================================== */

.stage > * { grid-area: 1 / 1; }

/* The scene fills the whole screen, so there is never a black band above or
   below. The artwork inside it keeps exactly the width it had before and its
   full 1080:1920 frame - see .scene__box - and the leftover strips are filled
   by .scene__backdrop rather than by page background. */
.scene {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background: #000;
  z-index: 10;

  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 1.2s ease, visibility 0s linear 1.2s;
}

/* The artwork at its original width and untouched aspect ratio, centred.
   The width rule is the same one the scene used before, so the image is
   exactly the size it has always been. */
.scene__box {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;

  /* Fill the screen's full height at the artwork's true ratio. There is
     therefore never a band above or below. On a phone this makes the box
     wider than the screen and the outer edges are clipped by .scene - the
     artwork is never stretched, and the centre is never touched. */
  height: 100%;
  width: auto;
  aspect-ratio: var(--art-w) / var(--art-h);
  container-type: inline-size;   /* keeps cqw sizing for the button */
}

/* the curtain page fading gently in over scene 1 */
.scene.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  cursor: pointer;
  transition: opacity 1.2s ease, visibility 0s linear 0s;
}

.scene__art {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
}

/* The curtain layer - the curtain image AND its label together, so they
   fade as one flat unit. Fading the label on its own would uncover the
   "Tap to open" pill printed on the curtain beneath it.
   Opacity ONLY: never slid, stretched or rotated. */
.curtain {
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 1;
  transition: opacity 3.2s cubic-bezier(.37, .06, .30, 1);
}

.scene.is-open .curtain { opacity: 0; }

/* --------------------------------------------------------------------------
   THE THREE PRINTED LINES, ARRIVING ONE AT A TIME

   couple.png prints its own lettering: "We are getting married", then
   "Emma & Rayan", then "December 3 , 2026". None of it is HTML, so none of
   it can be animated as text - and re-typesetting it was never an option,
   because no font matches that hand.

   So each line is covered instead by a patch of the artwork's OWN paper.
   The patch is a crop of couple.png with the ink painted out and the paper
   closed over it, which means every pixel of the patch except the strokes
   themselves is the artwork, unchanged - there is no rectangle to see, at
   any opacity. Lifting the three patches in turn makes the lines arrive in
   reading order, in their own type, at their own size.

   Boxes, in artwork pixels of 1080x1920:
     greeting ... x 359-723,  y 577-721
     names ...... x 231-853,  y 740-861
     date ....... x 300-780,  y 864-960
   All three sit inside the x 108..972 safe zone, so nothing here is touched
   by the crop on a tall phone.
   -------------------------------------------------------------------------- */
.veil {
  position: absolute;
  z-index: 3;                 /* over the artwork, under .curtain */
  background-repeat: no-repeat;
  background-size: 100% 100%;
  pointer-events: none;

  /* Default OFF. If scenes.js never runs, the guest simply gets the
     artwork with all of its lettering, exactly as drawn. */
  opacity: 0;
}

/* scenes.js arms the patches long before scene 3 is ever on screen, so
   they are already in place when the curtain starts to go. */
html.veils-armed .veil { opacity: 1; }

/* One slow, even fade each - the same curve the curtain itself uses, so
   the page keeps one motion language. Opacity only: nothing slides,
   scales or blurs, and no letter ever moves. */
html.veils-armed .veil.is-lifted {
  opacity: 0;
  transition: opacity 2.2s cubic-bezier(.37, .06, .30, 1);
}

.veil--greeting {
  left:   33.2407%;   /* 359 / 1080 */
  top:    30.0521%;   /* 577 / 1920 */
  width:  33.7963%;   /* 365 / 1080 */
  height:  7.5521%;   /* 145 / 1920 */
  background-image: url("couple-veil-1.webp");
}

.veil--names {
  left:   21.3889%;   /* 231 / 1080 */
  top:    38.5417%;   /* 740 / 1920 */
  width:  57.6852%;   /* 623 / 1080 */
  height:  6.3542%;   /* 122 / 1920 */
  background-image: url("couple-veil-2.webp");
}

.veil--date {
  left:   27.7778%;   /* 300 / 1080 */
  top:    45.0000%;   /* 864 / 1920 */
  width:  44.5370%;   /* 481 / 1080 */
  height:  5.0521%;   /*  97 / 1920 */
  background-image: url("couple-veil-3.webp");
}

/* --------------------------------------------------------------------------
   "Tap to Reveal" - an independent element, sitting on the artwork's pill
   -------------------------------------------------------------------------- */
.cta {
  position: absolute;
  left:   37.3148%;          /* 403 / 1080  (pill 406, less 3px) */
  top:    92.5000%;          /* 1776 / 1920 */
  width:  26.2963%;          /* 284 / 1080 */
  height:  6.0938%;          /* 117 / 1920 */
  z-index: 3;

  display: grid;
  place-items: center;
  border: 0;
  padding: 0 1.5cqw;
  border-radius: 999px;
  background: #000;          /* identical to the pill baked into the PNG */
  color: #f6efe3;            /* the artwork's own cream */

  font-family: 'Cormorant Garamond', Cormorant, Georgia, serif;
  font-weight: 400;
  /* sized with ~15% headroom inside the pill, so the label cannot spill over
     the rounded ends even if the browser falls back to a wider serif */
  font-size: 2.7cqw;
  letter-spacing: .10em;
  line-height: 1;
  white-space: nowrap;

  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* The pill is opaque from the moment the scene appears, so the artwork's own
   "Tap to open" is covered at all times. Only the label animates. */
.scene.is-open .cta { pointer-events: none; }

.cta:focus-visible {
  outline: none;
  box-shadow: 0 0 0 .35cqw rgba(236, 207, 143, .6);
}

/* A gentle fade-in, then a very subtle breath. The pill itself never moves
   or changes opacity, so it cannot drift off the artwork's pill underneath. */
.cta__label {
  display: block;
  opacity: 0;
}

.scene.is-armed .cta__label {
  animation: ctaIn .9s ease both,
             ctaBreathe 3s ease-in-out .9s infinite;
}

@keyframes ctaIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes ctaBreathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: .78; }
}

@media (prefers-reduced-motion: reduce) {
  .scene { transition-duration: .3s; }
  .curtain { transition-duration: .9s; }
  .scene.is-armed .cta__label { animation: none; opacity: 1; }

  /* The lines still arrive in order - a cross-fade is not motion, and the
     scene and curtain above keep their own fades here too. Only quicker. */
  html.veils-armed .veil.is-lifted { transition-duration: .9s; }

  /* same for the deck pages: still in order, just quicker */
  html.bands-armed .page.is-printed .band {
    transition-duration: .5s;
    transition-delay: calc(var(--in, 0s) / 3);
  }

  /* the chandelier hangs still and the car stays parked - which is each
     artwork exactly as drawn */
  .lamp__art,
  .drive__car,
  .sway__art { animation: none; }

  /* the sheet still clears the page, just briskly */
  html.bands-armed .page.is-printed .sheet { transition-duration: .6s; }
}

/* ==========================================================================
   THE DECK - the six scrolling pages after the curtain opens

   Everything below is additive. Scenes 1-3 keep every rule they had; the
   only rules that reach back are scoped to `html.deck-open`, and they take
   effect after the sequence has finished, never during it.

   Geometry rule for this whole block: the page box is locked to 1080:1920,
   so ONE scale factor maps artwork pixels to screen for both axes. A
   horizontal offset of N artwork px is `N / 1080 * 100%` of the box width;
   a vertical offset of N artwork px is `N / 1920 * 100%` of the box height
   - and because the box keeps its ratio, both equal `N / 1080 * 100cqw`.
   Anything measured in one axis and placed in the other therefore uses cqw.
   ========================================================================== */

/* The deck does not exist until the curtain fade is over. While it is
   display:none the document is exactly one screen tall, so the intro cannot
   be scrolled away from - no scroll lock, no overflow tricks, nothing a
   phone browser can get stuck behind. Images inside still preload, because
   display:none suppresses painting, not loading. */
.deck {
  display: none;
  position: relative;
  z-index: 20;              /* above .scene's z-index: 10, which is global */
}

.deck.is-open { display: block; }

/* Snap only once there is something to snap to. `scroll-snap-stop: always`
   keeps a fast flick from skipping a page. */
html.deck-open           { scroll-snap-type: y mandatory; }
html.deck-open .stage    { height: 100dvh; scroll-snap-align: start; scroll-snap-stop: always; }

/* A phone keyboard resizes the viewport, and mandatory snapping fights that
   by yanking the focused field out from under the caret. Suspend it while
   the guest is actually typing. */
html.is-typing { scroll-snap-type: none; }

/* Same rule without the JavaScript. A field can take focus in ways that
   never reach a focus listener - a browser restoring a form on back, or an
   autofill - so the snapping is suspended by the focus itself wherever
   :has() is supported, and by the class everywhere else. */
html:has(.field__in:focus) { scroll-snap-type: none; }

/* --------------------------------------------------------------------------
   One page = one screen, one artwork
   -------------------------------------------------------------------------- */
.page {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  scroll-snap-align: start;
  scroll-snap-stop: always;

  /* Only ever seen on a screen wider than 9:16, where the full-height box
     leaves strips at the sides. Each page sets --edge to the mean colour of
     its own artwork's left and right edges, so the strips read as more
     paper rather than as a black border. */
  background: var(--edge, #eae2d0);
}

/* dress-code.png turns black at y 1452 (75.625%), so a single flat colour
   would show a pale strip beside the dark band. Two stops, at the artwork's
   own crossover, instead. */
.page--dress {
  background: linear-gradient(180deg,
              #eae2d1 0%, #eae2d1 75.625%, #0b0908 75.625%, #0b0908 100%);
}

/* .page reuses .scene__box and .scene__art unchanged - same full-height
   1080:1920 box, same untouched artwork, same clipping at the sides. */

/* --------------------------------------------------------------------------
   Hit areas

   Every control on these pages sits on a shape the artwork has already
   painted, label and all. So each one is a hit area with no appearance of
   its own: nothing to fade, nothing that can drift off the thing underneath,
   and no second label that could ever contradict the printed one.
   -------------------------------------------------------------------------- */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.hit {
  position: absolute;
  z-index: 4;
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  appearance: none;
  -webkit-appearance: none;
}

/* Press feedback that tints what is underneath instead of covering it, so
   the printed label stays readable all the way through the press. */
.hit--pill::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: rgba(60, 40, 16, .16);
  opacity: 0;
  transition: opacity .18s ease;
  pointer-events: none;
}
.hit--pill:active::after { opacity: 1; transition-duration: 0s; }

/* the SEND pill is near-black; a dark tint would not read on it */
.hit--send::after { background: rgba(255, 244, 222, .16); }

.hit:focus-visible {
  outline: none;
  box-shadow: 0 0 0 .32cqw rgba(176, 132, 62, .75);
  border-radius: 999px;
}

/* --------------------------------------------------------------------------
   DETAILS - the two pills the artwork paints at its foot
     Get directions .. x 126-565,  y 1784-1882
     Calendar ........ x 579-1017, y 1784-1882
   -------------------------------------------------------------------------- */
.hit--directions,
.hit--calendar {
  top:     93.1250%;   /* 1788 / 1920 */
  height:   4.7396%;   /*   91 / 1920 */
}
.hit--directions { left: 11.6667%; width: 37.7778%; }   /* 126 / 1080, 408 wide */
.hit--calendar   { left: 50.6481%; width: 37.7778%; }   /* 547 / 1080, 408 wide */

/* --------------------------------------------------------------------------
   GIFTS - the "bank details" pill, x 326-753, y 1765-1854
   -------------------------------------------------------------------------- */
.hit--bank {
  left:    30.1852%;   /*  326 / 1080 */
  top:     91.9271%;   /* 1765 / 1920 */
  width:   39.6296%;   /*  428 / 1080 */
  height:   4.6875%;   /*   90 / 1920 */
}

/* ==========================================================================
   A PAGE PRINTING ITSELF ON, WHEN IT ARRIVES

   A deck page is one flat artwork, so there is no element in it to animate.
   Instead the page is covered by shutters in its own paper colour - the same
   --edge it already uses for the strips beside it - and they are lifted in
   turn. What the guest sees is blank paper filling in with what is printed
   on it, band by band, top to bottom.

   Rules of the trick, and why it is safe:
     - the artwork is never touched, moved, scaled or recoloured;
     - every cut line is placed in a row the artwork leaves blank, so a line
       of type or a frame edge is never sliced across;
     - the shutters carry no shape or shadow of their own - flat --edge, the
       mean of this page's own paper - so the only thing a lift changes is
       that the ink underneath becomes visible;
     - when the last one is gone the page is pixel-for-pixel the artwork.

   pages.js arms them (so a page with no script is just the artwork) and adds
   .is-live to a page once it is properly on screen - which is what starts
   this - and takes it off only after the page has left completely.
   ========================================================================== */
.band {
  position: absolute;
  left: 0;
  width: 100%;
  z-index: 3;                 /* over the artwork, under the hit areas */
  background: var(--edge, #eae2d0);
  pointer-events: none;
  opacity: 0;                 /* default off - see the note above */
}

html.bands-armed .page .band { opacity: 1; }

/* The lift itself. The transition lives here, on .is-live, so taking the
   class off again snaps the shutters back with no visible rewind. */
html.bands-armed .page.is-printed .band {
  opacity: 0;
  transition: opacity 1s ease;
  transition-delay: var(--in, 0s);
}

/* --------------------------------------------------------------------------
   DETAILS - the five bands, in artwork rows of 1920

     1  heading .............    0-203   ("Details", "WHEN & WHERE")
     2  the framed picture ...  204-1312
     3  venue and address .... 1313-1612
     4  date and time ........ 1613-1786
     5  the two buttons ...... 1787-1919

   Every boundary falls inside a band of rows with no ink in it at all -
   181-203, 1303-1312, 1594-1612 and 1726-1786 - so nothing is cut through.
   -------------------------------------------------------------------------- */
#pageDetails .band--1 { top:  0;        height: 10.6250%; --in: .25s; }
#pageDetails .band--2 { top: 10.6250%;  height: 57.7604%; --in: .75s; }
#pageDetails .band--3 { top: 68.3854%;  height: 15.6250%; --in: 1.25s; }
#pageDetails .band--4 { top: 84.0104%;  height:  9.0625%; --in: 1.75s; }
#pageDetails .band--5 { top: 93.0729%;  height:  6.9271%; --in: 2.25s; }

/* ==========================================================================
   CEREMONY - the timeline unrolls

   Bands could not be used here. The rule and the climbing vine run the whole
   height of the page, so there is no row anywhere in the middle of it with
   nothing on it: only 72-107, 1056-1100 and 1184-1220 are crossed by the
   rule alone, and five events need more cuts than that. Any other cut would
   have gone straight through a leaf.

   So nothing is cut at all. One sheet of this artwork's own paper covers the
   page and slides off the bottom, and its leading edge is a soft gradient
   rather than an edge - about 150px of fade - so the rule, the vine, each
   arrow and each hour rise through it instead of being uncovered by a line
   travelling down the page. When it has gone it is completely off the page.

   ease-in-out, not an ease-out: measured, an ease-out curve had the sheet
   two thirds of the way down the page within half a second and then crawling
   the rest, which reads as a slip rather than an unroll. This eases in, holds
   one pace down the page, and settles.

   --paper is this artwork's paper, measured at (235, 227, 210); it is NOT
   --edge, which on this page is darker because the artwork's own edges carry
   the urns and the planting at the foot.
   ========================================================================== */
.sheet {
  position: absolute;
  left: 0;
  right: 0;
  top: -15%;
  height: 115%;
  z-index: 6;                 /* over the artwork AND over the countdown */
  pointer-events: none;

  background: linear-gradient(to bottom,
              rgba(234, 227, 210, 0) 0%,
              var(--paper, #eae3d2) 12%,
              var(--paper, #eae3d2) 100%);

  /* Default: parked above the page and clipped away by .page's overflow.
     With no script the page is simply the artwork. */
  transform: translateY(-200%);
  will-change: transform;
}

/* armed - the sheet drops into place, covering the page. This happens off
   screen, with no transition, so it is never seen arriving. */
html.bands-armed .page .sheet { transform: translateY(0); }

/* and off it goes, once this is the page on screen */
html.bands-armed .page.is-printed .sheet {
  transform: translateY(115%);
  transition: transform 2.8s ease-in-out;
}

/* ==========================================================================
   PROGRAMME - the chandelier swings

   It is the one thing on this page that moves. The fixture was cut out of
   programme.png (chandelier.png, on transparency) and the hole closed with
   the artwork's own paper (programme-patch.png); see the note in the markup.

   Box, in artwork px of 1080x1920: x 370-720, y 400-945. The pivot is the
   point the chain hangs from, (546, 412), which lands at 50.2857% / 2.2018%
   inside that box.

   Only the fixture is transformed, and only ever rotated - never moved,
   scaled or recoloured - so nothing else on the page can be disturbed by it.
   ========================================================================== */
.lamp {
  position: absolute;
  left:   34.2593%;    /* 370 / 1080 */
  top:    20.8333%;    /* 400 / 1920 */
  width:  32.4074%;    /* 350 / 1080 */
  height: 28.3854%;    /* 545 / 1920 */
  z-index: 3;
  pointer-events: none;

  /* Off until pages.js has BOTH halves. Until then the page keeps the
     chandelier the artwork prints, so it is never missing from the arch. */
  opacity: 0;
}

.lamp.is-set,
.drive.is-set,
.sway.is-set { opacity: 1; }

.lamp img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
}

/* A chain swings like a pendulum: slowest at the ends of the arc, quickest
   through the middle. That is exactly ease-in-out between the two extremes,
   which is why this is two keyframes and `alternate` rather than a four-step
   loop - a loop would put the slow point in the middle, where the swing is
   in fact at its fastest, and the whole thing would read as wrong.

   1.6deg carries the bottom of the chandelier about 27 artwork px across,
   roughly 10px on a phone: plainly moving, never swaying about. The swing
   was 2.4s to begin with and the user asked for a little more pace, so it
   is 1.75s end to end - a 3.5s round trip. The arc itself is unchanged. */
.lamp__art {
  transform-origin: 50.2857% 2.2018%;    /* (546, 412) - where it hangs from */
  animation: sway 1.75s ease-in-out infinite alternate;
  animation-play-state: paused;
  will-change: transform;
}

/* On screen, and not before both halves of the fixture are in place. If it
   started moving first, the page would still be showing the one the artwork
   prints, and the swap when the halves arrive would be a jump instead of
   nothing at all. */
.page.is-live .lamp.is-set .lamp__art { animation-play-state: running; }

@keyframes sway {
  from { transform: rotate(-1.6deg); }
  to   { transform: rotate( 1.6deg); }
}

/* ==========================================================================
   TRANSPORT - the car drives off, and comes round again

   The car was lifted off gift.png (car.png on transparency) and the paper it
   stood on put back underneath (gift-patch.png); see the note in the markup
   for how that paper was rebuilt, which mattered here because the car clears
   the rectangle completely and every pixel of it is on show while it is away.

   Box, in artwork px of 1080x1920: x 228-858, y 437-699.

   .drive is the window it travels in, and it is exactly the width of the
   artwork with overflow hidden - so on a screen wider than 9:16 the car
   still leaves and returns at the edge of the page and never drives out
   across the strips beside it.

   The loop: off to the right until it is clear of the frame, half a second
   away behind the page, then on from the far left back to where it started.
   The two runs are 852 and 858 artwork px and are given 47.2% and 47.5% of
   the cycle, so the pace is the same going and coming; `linear` throughout,
   because that is what a car does and an eased curve would not.
   ========================================================================== */
.drive {
  position: absolute;
  left: 0;
  width: 100%;
  top:    22.7604%;    /* 437 / 1920 */
  height: 13.6458%;    /* 262 / 1920 */
  z-index: 3;
  overflow: hidden;
  pointer-events: none;

  /* off until pages.js has both halves - see .lamp, which works the same way */
  opacity: 0;
}

.drive img {
  position: absolute;
  left:   21.1111%;    /* 228 / 1080 */
  top: 0;
  width:  58.3333%;    /* 630 / 1080 */
  height: 100%;
  display: block;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
}

.drive__car {
  animation: drive 9.5s linear infinite;
  animation-play-state: paused;
  will-change: transform;
}

/* on screen, and not before both halves are in place - see .lamp__art */
.page.is-live .drive.is-set .drive__car { animation-play-state: running; }

@keyframes drive {
  0%     { transform: translateX(0); }

  /* clear of the frame. step-end holds it here for the next stretch instead
     of interpolating to the far side - without it the browser can land a
     frame part way through the jump and the car flashes across the page. */
  47.2%  { transform: translateX( 135.24%); animation-timing-function: step-end; }

  /* half a second off the page, then round it comes */
  52.5%  { transform: translateX(-136.19%); }
  100%   { transform: translateX(0); }
}

/* ==========================================================================
   DRESS CODE - the couple keep dancing

   They were lifted off the artwork (dancers.png on transparency) and the
   paper they stood on put back underneath (dress-patch.png).

   Box, in artwork px of 1080x1920: x 325-796, y 246-928. They turn about
   (560, 918) - the floor between them - which is 49.8938% / 98.5337% inside
   that box, so the hem of her gown and his shoes barely move and the turn
   is carried by their shoulders, the way a shift of weight is.

   1.8deg, which carries their hands about 21 artwork px each way - 10 on a
   phone. It was half that to begin with and the user asked for more; at this
   size the hem of her gown and his shoes still stay put, so it reads as the
   two of them swaying rather than as the picture tilting.
   ========================================================================== */
.sway {
  position: absolute;
  left:   30.0926%;    /* 325 / 1080 */
  top:    12.8125%;    /* 246 / 1920 */
  width:  43.6111%;    /* 471 / 1080 */
  height: 35.5208%;    /* 682 / 1920 */
  z-index: 3;
  pointer-events: none;

  /* off until pages.js has both halves - see .lamp */
  opacity: 0;
}

.sway img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
}

/* Slower than the chandelier and half its arc. Two keyframes and alternate
   again: slowest at the ends of the sway, easiest through the middle. */
.sway__art {
  transform-origin: 49.8938% 98.5337%;
  animation: waltz 2.3s ease-in-out infinite alternate;
  animation-play-state: paused;
  will-change: transform;
}

/* on screen, and not before both halves are in place - see .lamp__art */
.page.is-live .sway.is-set .sway__art { animation-play-state: running; }

@keyframes waltz {
  from { transform: rotate(-1.8deg); }
  to   { transform: rotate( 1.8deg); }
}

/* ==========================================================================
   RSVP - real controls on the boxes the artwork paints
   ========================================================================== */

/* The form itself must not lay anything out: every control is positioned
   against .scene__box, which is the nearest positioned ancestor. */
.rsvp {
  position: static;
  margin: 0;
  padding: 0;
  border: 0;
}

/* ----- radio rings -------------------------------------------------------
   The artwork draws the empty ring and the label beside it. The hit area
   covers both, and the only thing this stylesheet adds is the dot that
   appears inside the ring when that option is chosen.

   Ring centres (artwork px):
     attend ..... (165, 656) and (504, 656)
     persons .... x 166 / 332 / 501 / 663 / 824, all at y 815
   ------------------------------------------------------------------------- */
.hit--radio input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}

.hit__dot {
  position: absolute;
  width:  2.0370cqw;        /* 22 / 1080 - a little under half the ring */
  height: 2.0370cqw;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 34%, #d8b071 0%, #b0843e 62%, #8a6323 100%);
  box-shadow: 0 0 .5cqw rgba(138, 99, 35, .45);
  transform: scale(0);
  opacity: 0;
  transition: transform .22s cubic-bezier(.2, .9, .3, 1.35), opacity .16s ease;
  pointer-events: none;
}

.hit--radio input:checked ~ .hit__dot { transform: scale(1); opacity: 1; }

.hit--radio:focus-within {
  outline: none;
  box-shadow: 0 0 0 .3cqw rgba(176, 132, 62, .6);
  border-radius: .8cqw;
}

/* "Will you attend?" - hit boxes y 628-686 */
.r-att-yes, .r-att-no { top: 32.7083%; height: 3.0208%; }
.r-att-yes { left: 12.7778%; width: 30.7407%; }   /* 138 -> 470 */
.r-att-no  { left: 44.2593%; width: 32.5926%; }   /* 478 -> 830 */
.r-att-yes .hit__dot { left: 1.4815cqw; top: 1.6204cqw; }   /* ring 165,656 */
.r-att-no  .hit__dot { left: 1.4352cqw; top: 1.6204cqw; }   /* ring 504,656 */

/* "How many persons?" - hit boxes y 786-846 */
.r-num-1, .r-num-2, .r-num-3, .r-num-4, .r-num-5 { top: 40.9375%; height: 3.1250%; }
.r-num-1 { left: 12.7778%; width: 15.0000%; }   /* 138 -> 300 */
.r-num-2 { left: 28.2407%; width: 15.0926%; }   /* 305 -> 468 */
.r-num-3 { left: 43.7963%; width: 14.5370%; }   /* 473 -> 630 */
.r-num-4 { left: 58.7963%; width: 14.8148%; }   /* 635 -> 795 */
.r-num-5 { left: 73.7037%; width: 10.0926%; }   /* 796 -> 905 */
.r-num-1 .hit__dot { left: 1.6204cqw; top: 1.7130cqw; }
.r-num-2 .hit__dot { left: 1.5278cqw; top: 1.7130cqw; }
.r-num-3 .hit__dot { left: 1.6204cqw; top: 1.7130cqw; }
.r-num-4 .hit__dot { left: 1.5741cqw; top: 1.7130cqw; }
.r-num-5 .hit__dot { left: 1.6204cqw; top: 1.7130cqw; }

/* A group the guest has skipped. Soft, and inside the artwork's palette. */
.hit--radio.is-missing {
  border-radius: .8cqw;
  box-shadow: inset 0 0 0 .26cqw rgba(178, 74, 46, .5);
}

/* ----- text fields -------------------------------------------------------
   Each .field sits exactly on the gold box the artwork draws, and contains
   nothing that paints a border - the artwork's own box is the border.
   All four share x 143-957 (width 815).
   ------------------------------------------------------------------------- */
.field {
  position: absolute;
  z-index: 4;
  left:  13.2407%;   /* 143 / 1080 */
  width: 75.4630%;   /* 815 / 1080 */
}
.f-name  { top: 48.3854%; height: 3.9063%; }   /* y  929-1003 */
.f-diet  { top: 57.1875%; height: 5.0521%; }   /* y 1098-1194 */
.f-phone { top: 67.5521%; height: 4.1667%; }   /* y 1297-1376 */
.f-msg   { top: 76.7188%; height: 8.5938%; }   /* y 1473-1637 */

/* The patch that covers the placeholder text printed inside the box.

   It is a 807x50 crop of THIS artwork's own paper, taken from the clean
   band at y 1378-1427, so the texture, the grain and the brightness are
   not matched - they are the same paper. Because the patch is 807/1080 of
   the box wide and 50/1080 of the box tall, `background-size: 100% 100%`
   lands the crop at exactly 1:1 with the artwork around it; there is no
   stretch and therefore no seam.

   It is only there while the field is focused or holds text, so at rest
   the artwork is exactly as drawn. Same idea as the scratch page's caption
   veil, and the same reason: patch with the artwork, never with a guess. */
.patch {
  position: absolute;
  left:   0.3704cqw;    /* 147 - 143 */
  width: 74.7222cqw;    /* 807 */
  height: 4.6296cqw;    /*  50 */
  background-image: url("paper-tile.webp");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  opacity: 0;
  transition: opacity .22s ease;
  pointer-events: none;
}
.f-name  .patch { top: 1.1111cqw; }   /* artwork y  941 */
.f-diet  .patch { top: 2.5926cqw; }   /* artwork y 1126 */
.f-phone .patch { top: 1.2963cqw; }   /* artwork y 1311 */
.f-msg   .patch { top: 2.5000cqw; }   /* artwork y 1500 */

.field:focus-within .patch,
.field.has-value .patch { opacity: 1; }

.field__in {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  background: transparent;   /* the artwork draws the box */
  border-radius: 1.2cqw;
  padding: 0 3.1481cqw;      /* 34px, where the printed placeholder starts */

  font-family: 'Cormorant Garamond', Cormorant, Georgia, 'Times New Roman', serif;
  font-weight: 400;
  font-size: 3.3cqw;
  line-height: 1.25;
  color: #2a1f14;
  caret-color: #8a6323;

  appearance: none;
  -webkit-appearance: none;
  outline: none;
  -webkit-tap-highlight-color: transparent;
}

/* Our own placeholder only ever shows once the patch has covered the
   printed one, so the two can never be read at the same time. */
.field__in::placeholder { color: rgba(42, 31, 20, .42); opacity: 1; }
.field:not(:focus-within):not(.has-value) .field__in::placeholder { color: transparent; }

.field__in--area {
  padding: 2.4cqw 3.1481cqw 0;   /* first line lands on the printed one */
  resize: none;
  overflow: auto;
  line-height: 1.45;
}

.field__in:focus-visible { outline: none; }

/* A field the guest has to come back to. Soft, and on the artwork's own
   gold rather than a browser red. */
.field.is-missing .field__in { box-shadow: inset 0 0 0 .28cqw rgba(178, 74, 46, .55); border-radius: 1.2cqw; }

/* ----- SEND RSVP - pill x 220-871, y 1677-1770 --------------------------- */
.hit--send {
  left:    20.3704%;
  top:     87.3438%;
  width:   60.3704%;
  height:   4.8958%;
}

.rsvp__status {
  position: absolute;
  z-index: 5;
  left: 10%;
  width: 80%;
  top: 93.2%;
  margin: 0;
  text-align: center;
  font-family: 'Cormorant Garamond', Cormorant, Georgia, serif;
  font-size: 2.5cqw;
  letter-spacing: .06em;
  color: #8a3a22;
  opacity: 0;
  transition: opacity .25s ease;
  pointer-events: none;
}
.rsvp__status.is-shown { opacity: 1; }

/* ==========================================================================
   PANELS - bank details, and the RSVP confirmation

   A panel is a layer over its page, inside the same box, so it is clipped
   and scaled with the artwork and can never sit half off a phone screen.
   ========================================================================== */
.panel {
  position: absolute;
  inset: 0;
  z-index: 8;
  display: grid;
  place-items: center;
  padding: 0 6cqw;
  background: rgba(24, 18, 10, .55);
  opacity: 0;
  transition: opacity .3s ease;
}
.panel[hidden] { display: none; }
.panel.is-open { opacity: 1; }

.panel__card {
  width: 100%;
  max-width: 76cqw;
  padding: 5cqw 5cqw 4cqw;
  border-radius: 2.2cqw;
  background: #f3ecdd;
  box-shadow: 0 1.4cqw 4cqw rgba(0, 0, 0, .45);
  text-align: center;
  transform: translateY(2.4cqw) scale(.97);
  transition: transform .34s cubic-bezier(.2, .9, .3, 1.1);
}
.panel.is-open .panel__card { transform: none; }

.panel__title {
  margin: 0 0 2.4cqw;
  font-family: 'Great Vibes', 'Pinyon Script', cursive;
  font-weight: 400;
  font-size: 7cqw;
  line-height: 1.35;
  color: #6d4f1f;
}

.panel__text,
.panel__note {
  margin: 0;
  font-family: 'Cormorant Garamond', Cormorant, Georgia, serif;
  font-size: 3.1cqw;
  line-height: 1.5;
  color: #3a2c1b;
}

.panel__note { margin-top: 2cqw; min-height: 1.2em; color: #6d4f1f; }

.panel__rows {
  margin: 0 0 3.4cqw;
  font-family: 'Cormorant Garamond', Cormorant, Georgia, serif;
  text-align: left;
}
.panel__rows dt {
  font-size: 2.5cqw;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #8a6323;
}
.panel__rows dd {
  margin: 0 0 2.2cqw;
  font-size: 3.4cqw;
  line-height: 1.35;
  color: #2a1f14;
  word-break: break-word;
}
.panel__rows dd:last-child { margin-bottom: 0; }

.panel__actions {
  display: flex;
  gap: 2.4cqw;
  justify-content: center;
  flex-wrap: wrap;
}

.panel__btn {
  padding: 1.9cqw 4.2cqw;
  border: 0;
  border-radius: 999px;
  background: #1b140c;
  color: #f3e6cb;
  font-family: 'Cormorant Garamond', Cormorant, Georgia, serif;
  font-size: 2.9cqw;
  letter-spacing: .12em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.panel__btn--quiet {
  background: transparent;
  color: #6d4f1f;
  box-shadow: inset 0 0 0 .22cqw rgba(109, 79, 31, .5);
}
.panel__btn:focus-visible { outline: none; box-shadow: 0 0 0 .32cqw rgba(176, 132, 62, .8); }

@media (prefers-reduced-motion: reduce) {
  .hit__dot,
  .patch,
  .panel,
  .panel__card,
  .rsvp__status { transition-duration: .01s; }
  .panel__card { transform: none; }
  html.deck-open { scroll-snap-type: none; }
}

/* ==========================================================================
   CEREMONY - "Love awaits" and the countdown

   Placed in the clear paper this artwork leaves between the foot of the
   timeline and the stones: nothing darker than paper anywhere in
   x 244-860, y 1320-1545. The block uses x 250-830 (centred on 540) and
   y 1320-1545, so it needs nothing behind it - it sits on the paper in the
   artwork's own ink, exactly like every other line on the page.
   ========================================================================== */

.countdown {
  position: absolute;
  z-index: 4;
  left:  23.1481%;   /*  250 / 1080 */
  width: 53.7037%;   /*  580 / 1080 */
  top:   71.3542%;   /* 1370 / 1920 */
  height:11.7188%;   /*  225 / 1920 */

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  text-align: center;
  pointer-events: none;   /* nothing here is tappable; never block a scroll */
}

/* The page writes its headings in a black script. This is the same voice. */
.countdown__title {
  margin: 0;
  font-family: 'Great Vibes', 'Pinyon Script', cursive;
  font-weight: 400;
  font-size: 6.4cqw;
  line-height: 1.12;
  color: #14100b;
}

.countdown__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  width: 100%;
  margin-top: 1.5cqw;
}

.cd {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* a hairline between the units, in the artwork's own gold */
  box-shadow: inset -0.09cqw 0 0 rgba(120, 96, 56, .35);
}
.cd:last-child { box-shadow: none; }

.cd__num {
  font-family: 'Cormorant Garamond', Cormorant, Georgia, 'Times New Roman', serif;
  font-weight: 600;
  font-style: normal;
  font-size: 5.7cqw;
  line-height: 1.06;
  color: #14100b;
  font-variant-numeric: tabular-nums;   /* digits must not jiggle each second */
}

.cd__lab {
  margin-top: .35cqw;
  font-family: 'Cormorant Garamond', Cormorant, Georgia, serif;
  font-style: normal;
  font-weight: 600;
  font-size: 1.9cqw;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #382c1c;
}

.countdown__here {
  margin: 1.6cqw 0 0;
  font-family: 'Great Vibes', 'Pinyon Script', cursive;
  font-size: 5.2cqw;
  line-height: 1.2;
  color: #14100b;
}
.countdown__here[hidden] { display: none; }

/* ==========================================================================
   THE MUSIC BUTTON

   Fixed to the screen, not to the artwork box, because the music carries on
   through the deck and the control has to stay reachable there too. It sits
   above every scene and page but is small, dim and out of the way: the
   artwork is the thing being looked at.

   It is hidden until music.js has actually got a sound playing. A control
   that cannot do anything yet is worse than no control at all.
   ========================================================================== */
.music {
  position: fixed;
  top: max(12px, env(safe-area-inset-top));
  right: max(12px, env(safe-area-inset-right));
  z-index: 60;                   /* over .scene (10) and the deck's pages */

  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid rgba(236, 207, 143, .34);
  border-radius: 50%;
  background: rgba(0, 0, 0, .42);
  -webkit-backdrop-filter: blur(3px);
          backdrop-filter: blur(3px);
  color: var(--gold-mid);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;

  display: grid;
  place-items: center;

  opacity: 0;
  transform: scale(.82);
  transition: opacity .5s ease, transform .5s cubic-bezier(.2, .9, .3, 1.2),
              border-color .25s ease, background-color .25s ease;
}

.music.is-in { opacity: .82; transform: none; }
.music:hover { opacity: 1; }
.music:active { background: rgba(0, 0, 0, .62); }

.music:focus-visible {
  outline: none;
  border-color: var(--gold-mid);
  box-shadow: 0 0 0 3px rgba(236, 207, 143, .3);
}

.music svg {
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  overflow: visible;
}

.music__horn { fill: currentColor; stroke-width: 1.2; }

/* The two arcs and the slash are the whole state change: on, the arcs are
   there and the slash is not; off, the reverse. They are drawn as strokes so
   the swap can be animated instead of snapping. */
.music__wave { opacity: 1; transition: opacity .22s ease; }
.music__wave--far { transition-delay: .05s; }

.music__slash {
  stroke-dasharray: 21;
  stroke-dashoffset: 21;         /* drawn from nothing when muted */
  transition: stroke-dashoffset .28s ease;
}

.music.is-off { color: var(--gold-deep); }
.music.is-off .music__wave { opacity: 0; transition-delay: 0s; }
.music.is-off .music__slash { stroke-dashoffset: 0; }

@media (prefers-reduced-motion: reduce) {
  .music,
  .music__wave,
  .music__slash { transition-duration: .01ms; }
  .music.is-in { transform: none; }
}
