/* ───────────────────────────────────────────────────────────────────────────
   The theatre.

   Every layer here is a transparent PNG the app drew — panel, pocket, flap,
   wax, and the stain the wax leaves behind. A flat picture cannot be opened,
   which is the whole reason rasterizeEnvelopeLayer() exists: the flap has to
   lift away from the pocket, pass vertical, go behind, and take the seal up
   with it.

   The stylesheet owns how a pose becomes a transform. reveal.js owns the
   clock. site.js owns the sizes, and it takes every one of them from
   demo.json, which the app itself wrote.
   ─────────────────────────────────────────────────────────────────────────── */

.reveal {
  --env-aspect: 1.42;
  --env-width: 82%;
  --env-inset: 9%;
  --env-bottom: 3.6%;
  --stage-pad: 97%;
  --card-w: 89.2857%;
  --card-origin: 37.6%;
  --card-y-unit: 1.183;

  /* Poses, written by reveal.js. Declared so the sealed state is correct
     before a single frame has run — and so it stays correct with no JS. */
  --open: 0;
  --rise: 0;
  --present: 0;
  --flap-deg: 0deg;
  --card-y: 0.1098;
  --card-scale: 1;
  --env-drop: 0;
  --env-alpha: 1;
  --stain-alpha: 0;

  position: relative;
  width: min(100%, var(--reveal-max, 30rem));
  margin-inline: auto;
}

.reveal-stage {
  position: relative;
  width: 100%;
  padding-top: var(--stage-pad);
  /* 1 / 0.0016, the perspective entry the app sets on the flap's matrix. */
  perspective: 625px;
  perspective-origin: 50% 30%;
}

/* Every layer is the same box: the envelope, sitting on the stage floor with
   the flap's headroom above it.

   None of them take a pointer. The sealed envelope's tap target is a real
   button and the cards want the clicks after that; an envelope faded to
   nothing still swallows them otherwise, and the card underneath stops
   answering for no visible reason. */
.reveal .layer {
  pointer-events: none;
  position: absolute;
  left: var(--env-inset);
  bottom: var(--env-bottom);
  width: var(--env-width);
  aspect-ratio: var(--env-aspect);
  height: auto;
}

/* The recede. The envelope draws back and fades as the card comes forward —
   the same gesture for every layer, so they move as one object.

   It fades all the way out, which is what the app does. Stopping short leaves
   a translucent pocket lying over the card, and a postcard read through the
   envelope it came in is not a postcard anybody would send. */
.reveal .recedes {
  transform: translateY(calc(var(--env-drop) * 100%));
  opacity: var(--env-alpha);
  will-change: transform, opacity;
}

.reveal .panel { z-index: 1; }
.reveal .pocket { z-index: 3; }

/* The impression the wax leaves on the pocket, uncovered as the seal lifts
   away with the flap. */
.reveal .stain {
  z-index: 4;
  opacity: var(--stain-alpha);
  /* Concentric with the seal by construction; both boxes come from the app. */
  left: var(--stain-x);
  bottom: auto;
  top: var(--stain-y);
  width: var(--stain-w);
  aspect-ratio: 1;
}

/* ── The flap ────────────────────────────────────────────────────────────
   Hinged along the envelope's top edge and turned through open · π · 0.92,
   which is the app's own figure. Past halfway it has gone over: it moves
   behind the envelope, shows its lining, and the wax shows the matte
   underside that was stuck to the paper.

   Nothing is pre-flipped. Rotating past vertical mirrors the artwork, and the
   app accepts that for exactly the same reason it can here — the lining and
   the wax underside carry no reading matter to come out backwards.
   ────────────────────────────────────────────────────────────────────── */
.reveal .flap-holder { z-index: 5; }
.reveal.is-past-vertical .flap-holder { z-index: 0; }

.reveal .flap-hinge {
  position: absolute;
  inset: 0;
  transform-origin: 50% 0;
  transform: rotateX(var(--flap-deg));
  transform-style: preserve-3d;
  will-change: transform;
}
.reveal .flap-face,
.reveal .flap-lining {
  position: absolute;
  inset: 0;
  width: 100%;
  height: auto;
}
.reveal .flap-lining { opacity: 0; }
.reveal.is-past-vertical .flap-face { opacity: 0; }
.reveal.is-past-vertical .flap-lining { opacity: 1; }

/* The wax is part of the flap and travels with it, intact. It is placed by
   the seal rect the app publishes, never by a number of this page's own —
   wax closes a flap onto the body, so it has to straddle the tip, and a seal
   that sat wholly above it would be holding nothing shut. */
.reveal .seal {
  position: absolute;
  left: var(--seal-x);
  top: var(--seal-y);
  width: var(--seal-w);
  aspect-ratio: 1;
  height: auto;
}
.reveal .seal-underside { opacity: 0; }
.reveal.is-past-vertical .seal-face { opacity: 0; }
.reveal.is-past-vertical .seal-underside { opacity: 1; }

/* ── The cards ───────────────────────────────────────────────────────────
   Clipped at the envelope's bottom edge and nowhere else. A postcard is
   nearly as large as its envelope, so there is no rest position where it is
   both hidden and inside the box; cutting it at the floor is what lets it
   rise clear of the mouth while the part still inside stays out of sight.
   ────────────────────────────────────────────────────────────────────── */
.reveal .cards-clip {
  position: absolute;
  z-index: 2;
  left: var(--env-inset);
  top: 0;
  bottom: var(--env-bottom);
  width: var(--env-width);
  /* Cut at the envelope's bottom edge and nowhere else. `overflow: hidden`
     cuts all four, which took the fanned pile off at the envelope's right
     edge and the presented card off at its top. */
  clip-path: inset(-200% -200% 0 -200%);
  pointer-events: none;
}

/* The card climbs out of the mouth and then keeps going as it is presented,
   growing to 1.22 of itself and nudging toward the middle of the frame. The
   scale is anchored at the top edge, as it is in the app, so the card grows
   downward out of the envelope rather than away from its own centre.

   The `--slot-*` values are where this card sits in the pile, written by
   deck.js from the desk's own fan. At rest the front card's are all zero, so
   the reveal ends exactly where it would have anyway. */
.reveal .card {
  --slot-x: 0%;
  --slot-y: 0%;
  --slot-scale: 1;
  --slot-rot: 0deg;

  position: absolute;
  left: 50%;
  top: var(--card-origin);
  width: var(--card-w);
  transform:
    translate(
      calc(-50% + var(--slot-x)),
      calc(
        (var(--card-y) * var(--card-y-unit) - var(--present) * 0.1) * 100% +
          var(--slot-y)
      )
    )
    scale(calc(var(--card-scale) * var(--slot-scale)))
    rotate(var(--slot-rot));
  transform-origin: top center;
  will-change: transform;
}

/* The cards still in the envelope, behind the one being read. They arrive as
   the deck is presented and are dealt through after that. */
.reveal .card.behind { opacity: var(--present); }
/* The clip swallows pointer events so the sealed envelope's own tap target
   keeps them. Once it is open the cards take them back, or a tap on a card
   does nothing at all. */
.reveal.is-open .card { opacity: 1; cursor: pointer; pointer-events: auto; }

/* A card turning over in the hand — Motion.flip, 720 ms. */
/* Driven frame by frame from deck.js, because the app's flip is not a plain
   rotation: the card tips and shrinks as it comes edge on. A CSS transition
   would fight the script for the same property. */
.card-faces {
  position: relative;
  transform-style: preserve-3d;
}
.card.is-flipped .card-faces {
  transform: perspective(769px) rotateY(180deg);
}
.card-faces img {
  width: 100%;
  height: auto;
  backface-visibility: hidden;
  /* A mounted print gets a hairline, not a drop shadow. */
  border-radius: 2px;
}
.card-faces .back {
  position: absolute;
  inset: 0;
  transform: rotateY(180deg);
}

/* The two cards still in the envelope, behind the one being read. They only
   appear once the deck has been dealt. */
/* ── The controls ────────────────────────────────────────────────────── */
.reveal-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
  min-height: 44px;
}
.reveal-hint {
  font-size: 0.8125rem;
  color: var(--ink-muted);
  transition: opacity var(--medium) var(--ease);
}
/* Gone, not merely invisible. Left in place it goes on holding its width and
   the replay button sits off centre for no reason anybody can see. */
.reveal.is-open .reveal-hint { display: none; }

.tap-target {
  position: absolute;
  inset: 0;
  z-index: 10;
  width: 100%;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: var(--r-card);
}
.reveal.is-open .tap-target { display: none; }

/* Reduced motion lands on the last frame at once: the open envelope and the
   cards, no animation. The replay button still works — being able to ask for
   a thing is not the same as having it sprung on you. */
@media (prefers-reduced-motion: reduce) {
  .reveal .recedes,
  .reveal .flap-hinge,
  .reveal .card { will-change: auto; }
}
