/* Perde (پەردە) — original work for Ahang, 2026-08-20; REDESIGNED 2026-08-28.
   Loaded as a <link> for this template only; every rule is scoped by
   .perde-scope / .pd-.

   THIS FILE IS PUBLIC AND THE GUEST DOWNLOADS EVERY BYTE, comments included.
   The reasoning lives in the components and in docs/templates/TEMPLATE_4_BRIEF.md
   — only traps are written here.

   ── WHAT THE REDESIGN DID, IN ONE PARAGRAPH ────────────────────────────────

   The cover is a gilded rocaille arch with gilt foliage massed in two OPPOSITE
   corners, a curtain gathered at a tieback, and two lit candles. None of it
   survived the tap: behind it were ten centred blocks of identical
   construction on a flat cream, the ground striping deeper four times, and the
   velvet returning only in the footer. So — the corner is now the shape
   language and it is rationed to PAPER, headings hang from the tieback, the
   page is lit by the candle rather than filled with a colour, and the curtain
   CLOSES on the last screen (`curtain.webp`, already in cache from the first).
   Nothing about the mechanics moved: same gate, same clip, same scratch, same
   shared components, same palette. See the header of PerdeTemplate.js.

   THE PAGE STILL CHANGES GROUND, and it is the only one in the catalogue that
   does. darga is cream throughout, hena is night throughout, zerf is linen
   throughout. Here the guest taps through a deep crimson velvet cover, arrives
   on warm cream, walks through one warmer room in the middle, and the last
   screen draws the curtain shut. */

.perde-scope {
  /* The cream interior. */
  --pd-cream: #f7efe4;
  --pd-paper: #fffaf2;
  /* THE WARM ROOM. Deeper than a component fill on purpose: it has to read as
     a change of PAGE from two metres away, and a two-level difference does not.
     Ink is still 13.1:1 on it.

     It is used ONCE now, over a contiguous run — where it is, when it runs,
     what it looks like — where it used to alternate onto four scattered
     sections and stripe the page. */
  --pd-band: #eee1cf;
  --pd-cream-deep: #efe3d3;

  /* The velvet, for the cover and the closing. */
  --pd-velvet: #2a060d;
  --pd-velvet-soft: #4a0d16;

  /* THE CONTRAST RULES INVERT BETWEEN THE COVER AND THE INTERIOR, and getting
     that backwards is the one way to make this page unreadable. Measured on
     the real values, not estimated:

       on CREAM   ink 15.2:1, crimson-deep 11.9:1, ink-soft 9.7:1,
                  crimson 8.2:1, muted 4.5:1 (small labels only).
                  GOLD IS 2.8:1 AND GOLD-LIGHT IS 1.7:1 — both may ONLY fill,
                  hairline or rule. Never text.

       on VELVET  gold-light 9.8:1, gold 5.7:1, cream ~15:1 — these are the
                  text colours there. Every ink and crimson is under 2:1 on it
                  and may only fill.

     So the gold that is illegal as text on the interior is what sets the
     cover, and the crimson that sets the interior is invisible on the cover.
     Same inversion hena wrote down, one page apart instead of one file.

     THE REDESIGN ADDED NO COLOUR. Everything below is what shipped on
     2026-08-21; what changed is where it is allowed to go. */
  --pd-ink: #2a1418;
  --pd-ink-soft: #6a2430;
  --pd-muted: #7a6a62;

  --pd-crimson: #8c1428;
  --pd-crimson-deep: #5e0c1a;
  --pd-gold: #b8862b;
  --pd-gold-light: #d9b968;

  --pd-line: rgba(140, 20, 40, 0.22);
  --pd-line-soft: rgba(140, 20, 40, 0.12);

  /* ── THE FOIL, AS A MATERIAL ──────────────────────────────────────────────

     The seven stops PerdeScratch paints into its canvas (FOIL_STOPS), written
     out as a gradient so the panel the guest rubs off the date and the gold on
     the rest of the page are demonstrably the same metal. Before this the foil
     existed in one canvas and nowhere else, which is why that panel read as a
     gold rectangle that had wandered in.

     FOUR USES AND THAT IS THE RATION: the cap across the head of the invitation
     sheet, the bar under every section heading, the countdown's draining rule,
     and the mark between wishes. It is a FILL in all four — the rule at the top
     of this file is not negotiable, and none of these is text.

     Change these stops and change FOIL_STOPS in PerdeScratch.js. */
  --pd-foil: linear-gradient(
    102deg,
    #6d4a12 0%,
    #b8862b 18%,
    #e8cf86 34%,
    #c99a3a 50%,
    #8d6420 68%,
    #d8b45f 84%,
    #7a5416 100%
  );

  /* SMOOTH, NOT SHARP (2026-08-21, founder's direction). This was 3px, on the
     reasoning that paper corners are nearly square and a large radius reads as
     an app card. That is true of the INVITATION CARD and wrong about everything
     else on the page.

     THE REDESIGN MADE THE SECOND RADIUS RARE RATHER THAN CHANGING IT. `--pd-r`
     used to be on ten things — calendar, countdown cells, wish cards, RSVP
     panel, map, slides, foil — which is exactly why nothing on the page read as
     more important than anything else. Paper is paper and nothing else is: the
     invitation, the date's leaf and the reply take `--pd-r-paper`, everything
     else has no box at all, and `--pd-r` is left for the few objects that are
     genuinely lifted rectangles (the map, the photographs, the lightbox). */
  --pd-r: 14px;
  --pd-r-paper: 4px;

  /* Shadows go wide and faint rather than tight and dark. A short shadow at
     high opacity is a hard line under an element — which is the same sharpness
     the radius was creating, arriving from underneath. */
  --pd-shadow: 0 22px 48px -12px rgba(42, 20, 24, 0.16), 0 4px 12px rgba(42, 20, 24, 0.05);

  /* One easing for everything that moves. `ease` accelerates out of the gate;
     this one starts gently, which is what reads as smooth rather than snappy. */
  --pd-ease: cubic-bezier(0.32, 0.72, 0.24, 1);
  --pd-maxw: 30rem;
  --pd-rhythm: 4.75rem;

  /* ── THE TYPE SCALE, REWORKED ─────────────────────────────────────────────

     The old page had four sizes and three of them were nearly the same: the
     names at 3rem, the section headings at 2.2rem, the venue name at 1.5rem,
     everything else at ~1rem. A heading at 2.2rem beside names at 3rem is not a
     hierarchy, it is two headlines, and the guest's eye had nothing to hold.

     Now there are five steps and they are far apart. The names are the biggest
     thing on the page by a long way; the AYAH is the only thing allowed to step
     up between them and the footer, because on a nikah card the verse outranks
     everything; the headings drop under it and are carried by the tieback and
     the foil bar rather than by size; and the labels finally have a size of
     their own instead of borrowing the body's. */
  --pd-t-label: 0.72rem;
  --pd-t-body: 1rem;
  --pd-t-head: clamp(1.34rem, 6.4vw, 1.72rem);
  --pd-t-verse: clamp(1.18rem, 5.6vw, 1.34rem);
  --pd-t-display: clamp(2.35rem, 12vw, 3.4rem);

  /* THE CARTOUCHE, declared HERE and not on either of the two elements that
     draw it. `.pd-open-copy` on the page and `.pd-gate-card` inside the gate's
     reveal are the same object seen a second apart, and the handover only reads
     as one event if they are literally the same width, the same padding and the
     same type. Two copies of four numbers is how they drift. */
  --pd-card-w: min(21.5rem, 84%);
  --pd-card-pad: 2.5rem 1.7rem 2.2rem;

  position: relative;
  width: 100%;
  min-height: 100vh;

  /* Flat cream. The velvet PICTURE is used by the cover and the closing, and
     putting it behind everything as well is what makes a page feel like a
     texture pack rather than a design. The LIGHT on top of it is `.pd-glow`
     and `.pd-room` below. */
  background-color: var(--pd-cream);

  color: var(--pd-ink);
  font-size: var(--pd-t-body);
  line-height: 1.9;
  overflow-x: clip;
  /* Never letter-space Kurdish: it pulls a joining script apart. */
  letter-spacing: normal;
}

/* ── the gate: one tap ──────────────────────────────────────────────────── */

/* THE WHOLE COVER IS A <button>, so Enter and Space work natively and there is
   no second hidden control for the keyboard to disagree with. That means
   resetting the UA button box first — a cover that inherits a button's default
   font and padding is a very confusing bug to look at. */
.perde-scope .pd-gate {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: block;
  inline-size: 100%;
  padding: 0;
  border: 0;
  margin: 0;
  overflow: hidden;
  /* svh, NOT dvh: dvh grows as a phone toolbar retracts, which would shove the
     whole cover down while the guest is reaching for it. */
  block-size: 100svh;
  background: var(--pd-velvet);
  font: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}

.perde-scope .pd-gate:focus-visible {
  outline: 2px solid var(--pd-gold-light);
  outline-offset: -6px;
}

/* THE STAGE IS FIXED-ASPECT so the picture and the clip that replaces it
   register exactly: they are the same scene one frame apart, and a box that
   cropped them by different amounts would show that as a jump on the tap.

   CENTRED WITH left/transform, NOT with grid or flex centring. The stage is
   deliberately WIDER than the viewport, and an oversized centred grid item does
   not straddle its container — it overflows toward the inline START, which on
   an RTL page is the right. Zerf measured its stage sitting 42px off with the
   scene running past the screen edge. A physical left/translateX has no such
   opinion. */
.perde-scope .pd-stage {
  position: absolute;
  display: block;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  aspect-ratio: 720 / 1280;
  inline-size: max(100vw, calc(100svh * 720 / 1280));
}

.perde-scope .pd-stage-img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Tailwind preflight sets `img { max-width: 100% }`, which silently caps any
     width above the container. It bit the hena band for an hour. */
  max-inline-size: none;
  pointer-events: none;
  -webkit-user-drag: none;
}

/* The copy, centred on a scrim.
   THE COVER HAS NO QUIET PANEL. A 6x6 variance map over this picture finds no
   quiet region at all, because the velvet's fold striping runs edge to edge.

   That is not a fault in the picture — it is what a curtain looks like — so the
   copy carries its own ground instead. A soft radial scrim, sized to the text
   rather than to the frame, pulls down everything that is not type without
   flattening the whole cover.

   Centred with `inset-inline: 0; margin-inline: auto`, never with
   `inset-inline-start: 50%` and a translate — that resolves to `right: 50%` on
   an RTL page while the translate still moves left, which is a full width off
   centre. The rule is written out in ADDING_A_TEMPLATE.md. */
.perde-scope .pd-gate-copy {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  top: 50%;
  translate: 0 -50%;
  inline-size: fit-content;
  max-inline-size: min(20rem, 78%);
  padding: 2.4rem 2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.85rem;
  text-align: center;
}

.perde-scope .pd-gate-copy::before {
  content: "";
  position: absolute;
  inset: -12%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(24, 4, 9, 0.72) 0%,
    rgba(24, 4, 9, 0.52) 55%,
    rgba(24, 4, 9, 0) 100%
  );
}

.perde-scope .pd-gate-hi {
  margin: 0;
  font-size: var(--pd-t-label);
  color: var(--pd-gold);
}

.perde-scope .pd-gate-orn {
  display: block;
  inline-size: 108px;
  color: var(--pd-gold);
}

.perde-scope .pd-gate-orn svg { display: block; inline-size: 100%; block-size: auto; }

.perde-scope .pd-gate-tap {
  font-size: 1.02rem;
  color: var(--pd-gold-light);
}

/* The only moving thing on the cover, and it is the affordance: a still
   photograph gives a guest no reason to believe it is touchable.

   `translate` centres and `scale` animates — two independent properties, so
   neither has to be folded into the other's transform. A single `transform`
   doing both would have the keyframes fighting the centring. */
.perde-scope .pd-gate-ring {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  top: 50%;
  translate: 0 -50%;
  inline-size: 148px;
  block-size: 148px;
  border: 1px solid var(--pd-gold);
  border-radius: 999px;
  opacity: 0;
  scale: 0.72;
  animation: pd-pulse 3.4s ease-out infinite;
  pointer-events: none;
}

@keyframes pd-pulse {
  0% { scale: 0.72; opacity: 0; }
  30% { opacity: 0.45; }
  100% { scale: 1.22; opacity: 0; }
}

/* THE CURTAIN PARTING, over the still and pinned to the same box so the two
   register exactly. It is hidden until the tap rather than merely transparent:
   a <video> at opacity 0 over the cover is still a paint layer and, more to
   the point, darga shipped exactly that shape as a bug — an invisible video
   swallowing every tap meant for the gate underneath. `display: none` cannot
   swallow anything. */
.perde-scope .pd-stage-clip {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: none;
  pointer-events: none;
}

.perde-scope .pd-gate--parting .pd-stage-clip { display: block; }

/* Once the curtain is moving the invitation is already opening, so the copy
   that asked for a tap gets out of the way rather than riding along on top of
   it. Faster than the clip, so it is gone before the curtain is. */
.perde-scope .pd-gate--parting .pd-gate-copy {
  opacity: 0;
  transition: opacity 420ms ease;
  pointer-events: none;
}

/* ── WHAT IS BEHIND THE CURTAIN ──────────────────────────────────────────────

   Hidden until the tap, then timed against the 4.5s clip: the light is flooding
   through by ~1.6s, so the cartouche blooms from there and the names arrive at
   2.2s, leaving well over a second of them holding still before the cover lifts.

   THE REDESIGN MADE THIS THE PAGE'S OWN OBJECT. It used to be the names alone,
   centred, on a soft radial of cream — while the page underneath showed them
   ABOVE centre, under a kicker, at a slightly different clamp. The fade was
   covering a jump rather than handing anything over. `.pd-gate-card` below is
   built off the same --pd-card-* properties as `.pd-open-copy`, with the same
   three parts in the same order, so the screen does not move when the cover
   goes.

   `forwards` on both animations, so they stay put rather than snapping back for
   the last frame — an animation that resets before its element is removed is a
   flicker at exactly the moment the guest is reading. */
.perde-scope .pd-gate-reveal {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  top: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  inline-size: 100%;
  pointer-events: none;
  opacity: 0;
}

.perde-scope .pd-gate--parting .pd-gate-reveal { opacity: 1; }

/* THE BLOOM, still here and doing the job it is actually good at. It is not a
   scrim in the usual sense — it is literally the page's own --pd-cream, opening
   through the curtain — so by the time the cover lifts the middle of the screen
   is already the ground the next screen is made of. Behind the cartouche now
   rather than instead of it: it softens the panel's edges into the photograph
   while the curtain is still moving. */
.perde-scope .pd-gate-bloom {
  position: absolute;
  inset: -22% -16%;
  background: radial-gradient(
    closest-side ellipse,
    var(--pd-cream) 0%,
    rgba(247, 239, 228, 0.94) 46%,
    rgba(247, 239, 228, 0.7) 68%,
    rgba(247, 239, 228, 0) 100%
  );
  opacity: 0;
}

.perde-scope .pd-gate--parting .pd-gate-bloom {
  animation: pd-bloom 1600ms ease-out 1600ms forwards;
}

/* THE CARTOUCHE, DECLARED ONCE FOR BOTH.

   `.pd-gate-card` is what blooms through the parting curtain; `.pd-open-copy`
   is the first thing on the page under it. They are THE SAME OBJECT one second
   apart, so they are one rule rather than two that agree today.

   The old file had them as separate blocks and they had already drifted: the
   gate set the names at clamp(2rem, 10vw, 2.9rem) with no kicker and no
   tagline, the page at clamp(2.2rem, 11vw, 3rem) with both. That is a jump the
   900ms fade was covering rather than a handover. Grouping the selector is the
   only fix that cannot rot. */
.perde-scope .pd-gate-card,
.perde-scope .pd-open-copy {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  inline-size: var(--pd-card-w);
  padding: var(--pd-card-pad);
  text-align: center;
  background: var(--pd-cream);
  border-radius: var(--pd-r-paper);
  box-shadow: 0 26px 60px -18px rgba(20, 4, 8, 0.5);
}

/* The soft halo where the panel meets the photograph, so it is a card lying in
   candlelight rather than a rectangle pasted onto a render. Both again. */
.perde-scope .pd-gate-card::after,
.perde-scope .pd-open-copy::after {
  content: "";
  position: absolute;
  inset: -14% -12%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(
    closest-side ellipse,
    rgba(247, 239, 228, 0.9) 0%,
    rgba(247, 239, 228, 0.5) 52%,
    rgba(247, 239, 228, 0) 100%
  );
}

.perde-scope .pd-gate-card-kicker,
.perde-scope .pd-open-kicker {
  display: block;
  margin: 0 0 0.95rem;
  font-size: var(--pd-t-label);
  line-height: 1.7;
  color: var(--pd-crimson);
}

.perde-scope .pd-gate-card-tag,
.perde-scope .pd-open-tag {
  display: block;
  margin: 1.15rem 0 0;
  font-size: 0.94rem;
  line-height: 1.85;
  color: var(--pd-ink-soft);
}

.perde-scope .pd-gate-names,
.perde-scope .pd-open-names {
  display: block;
  margin: 0;
  font-size: var(--pd-t-display);
  line-height: 1.38;
  color: var(--pd-crimson-deep);
  text-align: center;
  text-wrap: balance;
}

.perde-scope .pd-gate-card { opacity: 0; }

.perde-scope .pd-gate--parting .pd-gate-card {
  animation: pd-bloom 900ms ease-out 1900ms forwards;
}

/* Crimson-deep is 11.9:1 on the cream the cartouche lays down — the same
   colour, the same ground and the same size as `.pd-open-names` on the page
   underneath, which is what holds the names steady while everything around
   them changes. Only the write-on belongs to the gate. */
.perde-scope .pd-gate-names {
  position: relative;
  opacity: 0;
  /* Fully clipped from the writing edge. See pd-write. */
  clip-path: inset(0 0 0 100%);
}

.perde-scope .pd-gate--parting .pd-gate-names {
  animation: pd-write 1800ms var(--pd-ease) 2000ms both;
}

@keyframes pd-bloom {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* THE NAMES ARE WRITTEN ON, RIGHT TO LEFT, AS THE CURTAIN PARTS.
   The founder asked for hena's stroke-drawing — `pathLength` plus an animated
   `stroke-dashoffset`, which draws a line along its own length.

   THAT TECHNIQUE CANNOT BE USED ON THIS TEXT, and the reason is worth writing
   down because the obvious workaround is much worse than the problem:

     * stroke-drawing needs PATHS. Live text is not a path, and turning a
       customer's name into one needs a font-to-outline pass at runtime — a
       dependency, for a string that changes with every invitation sold.

     * the obvious substitute — split the name into per-character spans and
       stagger them — BREAKS THE SCRIPT. Kurdish is joining: every letter is
       shaped by its neighbours, and a letter alone in its own element has no
       neighbours, so the word comes apart into isolated forms. It is the same
       failure as a font falling back mid-word, which app/globals.css already
       warns about at length. It would look broken in exactly the way nobody
       tests for, because a Latin demo name renders perfectly.

   So the text stays ONE unbroken run and a clip-path edge travels across it.
   Visually it is the same thing — the name appears as if being written — and
   the shaping engine never learns it is happening.

   RIGHT TO LEFT because that is the direction the script is written: `inset`'s
   left value goes 100% → 0, so the visible region grows from the right edge
   leftward. A purely Latin name therefore appears from its end, which is the
   one cost of doing this without per-letter control; on this page the names are
   Kurdish and the trade is worth it. */
@keyframes pd-write {
  from { opacity: 1; clip-path: inset(0 0 0 100%); }
  to { opacity: 1; clip-path: inset(0 0 0 0); }
}

.perde-scope .pd-gate--leaving {
  opacity: 0;
  transition: opacity 900ms ease;
  pointer-events: none;
}

/* The reveal has to SURVIVE the lift, or the cartouche vanishes a beat before
   the page arrives and the handover reads as two separate events instead of
   one. */
.perde-scope .pd-gate--leaving .pd-gate-reveal { opacity: 1; }
.perde-scope .pd-gate--leaving .pd-gate-bloom { opacity: 1; }
.perde-scope .pd-gate--leaving .pd-gate-card { opacity: 1; }
.perde-scope .pd-gate--leaving .pd-gate-names { opacity: 1; clip-path: inset(0 0 0 0); }

/* The page under the cover. */
.perde-scope .pd-page {
  opacity: 0;
  transition: opacity 700ms ease;
}

.perde-scope .pd-page.is-open { opacity: 1; }

/* ── THE GILT CORNER ─────────────────────────────────────────────────────────

   The redesign's whole shape language, and it appears in exactly four places on
   the page: the gate's cartouche, the page's cartouche, the invitation sheet,
   the date's calendar leaf and the reply. Every one of those is PAPER. Nothing
   else may take it — not the countdown, not the programme, not the notes, not
   the wishes, not the photographs.

   ONE FIGURE, MIRRORED. The second corner is the first under `scale(-1, -1)`,
   so the source holds one path set and the page cannot grow two that drift
   apart. `--pd-gilt` is the size, declared on the paper so a smaller sheet can
   take a smaller corner without touching this block.

   ── PHYSICAL CORNERS, NOT LOGICAL ONES, AND THAT IS THE WHOLE POINT ───────

   TOP-RIGHT and BOTTOM-LEFT, in `right`/`left`, in every direction. This is the
   one place on the page where a logical property would be wrong: the diagonal
   is not a reading order, it is the arrangement of the photograph the guest
   just tapped — gilt and foliage massed top-right and bottom-left, empty
   between — and a page that mirrors it under a Latin name has thrown away the
   reason it exists.

   THE FIGURE IS MIRRORED INTO ITS CORNER. It is drawn once for a TOP-LEFT
   corner, so the pair are `scaleX(-1)` and `scaleY(-1)` of it — which are also
   180° rotations of each other, i.e. what a matched pair of corner pieces on a
   printed card is. The first cut placed them with `inset-inline-start` and did
   not mirror at all, so on an RTL page a top-left drawing landed in the
   top-RIGHT corner with its scroll curling off the card. It read as a stray
   glyph rather than as a corner, which is exactly how it looked.

   `translate` carries the settle and `transform` carries the mirror — two
   independent properties, so neither has to be folded into the other's matrix
   and the animation cannot flip the drawing halfway through. */
.perde-scope .pd-gilt {
  --pd-gilt: 46px;
  position: absolute;
  inline-size: var(--pd-gilt);
  block-size: var(--pd-gilt);
  color: var(--pd-gold);
  pointer-events: none;
  opacity: 0;
  transition: opacity 900ms var(--pd-ease) 260ms, translate 900ms var(--pd-ease) 260ms;
}

.perde-scope .pd-gilt svg { display: block; inline-size: 100%; block-size: 100%; }

.perde-scope .pd-gilt--a {
  top: 10px;
  right: 10px;
  transform: scaleX(-1);
  translate: 8px -8px;
}

.perde-scope .pd-gilt--b {
  bottom: 10px;
  left: 10px;
  transform: scaleY(-1);
  translate: -8px 8px;
}

/* THE CORNERS ARRIVE AFTER THE PAPER THEY ARE ON, which is the whole reason
   they animate at all: a sheet that fades up with its gilt already drawn is one
   object, and a sheet whose corners settle a quarter-second later is a printed
   thing being laid down. `.is-visible` is ScrollReveal's class on the ancestor
   `.pd-reveal`; the gate's cartouche has no ScrollReveal above it, so it is
   opened by the parting phase instead. */
.perde-scope .is-visible .pd-gilt,
.perde-scope .pd-gate--parting .pd-gilt,
.perde-scope .pd-gate--leaving .pd-gilt {
  opacity: 1;
  translate: 0 0;
}

/* ── THE LIGHT: the room, and the candle ────────────────────────────────────

   Two layers, both pure paint, no markup cost beyond one span each.

   `.pd-room` is FIXED and runs the whole scroll: a very soft crimson falling
   off from the two screen edges, so the cream the guest reads on never stops
   being light inside an opening. It is deliberately not a bar and not a border
   — nothing here has a hard edge, and it eats no measure, which matters on a
   360px phone where the section padding is already only 1.4rem.

   `.pd-glow` is ABSOLUTE to `.pd-body` and belongs to the first two screens:
   the pool of candlelight the invitation is read by. It breathes on a slow and
   deliberately UNEVEN cycle — a flame that pulses on a clean sine is a
   notification light. */
.perde-scope .pd-room {
  position: fixed;
  inset: 0;
  /* OVER the grounds, not under them. Below the sections it would be hidden
     wherever the warm room paints full-bleed, which is precisely the stretch
     where a page most needs to still feel enclosed. It is faint enough to cross
     type without touching it: 0.09 crimson at the very edge, at zero by 6%,
     where the section padding already starts the measure at about 6.1% on the
     narrowest phone this design supports. */
  z-index: 5;
  pointer-events: none;
  background:
    linear-gradient(to right, rgba(140, 20, 40, 0.09) 0%, rgba(140, 20, 40, 0) 6%),
    linear-gradient(to left, rgba(140, 20, 40, 0.09) 0%, rgba(140, 20, 40, 0) 6%);
}

.perde-scope .pd-glow {
  position: absolute;
  inset-inline: -20%;
  inset-block-start: -6vh;
  block-size: 132vh;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(
    58% 42% at 50% 22%,
    rgba(255, 232, 186, 0.72) 0%,
    rgba(250, 224, 178, 0.34) 42%,
    rgba(247, 239, 228, 0) 100%
  );
  animation: pd-candle 9.2s ease-in-out infinite;
}

@keyframes pd-candle {
  0% { opacity: 0.86; }
  17% { opacity: 1; }
  31% { opacity: 0.9; }
  52% { opacity: 0.99; }
  63% { opacity: 0.84; }
  84% { opacity: 0.96; }
  100% { opacity: 0.86; }
}

/* ── the opening: the names, in the opening ─────────────────────────────── */

/* THE CURTAIN STAYS OPEN AND THE INVITATION IS WRITTEN ON IT.
   For a day this was flat cream, on the reasoning that a second full-screen
   picture after the cover would be the same beat twice. That was wrong about
   which beat it is: the video ENDS on the opened curtain, so cutting to blank
   paper throws away the thing the guest just watched happen. The scene stays,
   and the invitation is written into it.

   The picture is the clip's own last frame on the SAME fixed-aspect stage the
   gate uses, so the fade hands over a frame that is already cropped
   identically. The video does not end and give way to a page; the page is where
   the video left off. */
.perde-scope .pd-open {
  position: relative;
  min-block-size: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--pd-velvet);
  /* Over `.pd-glow`, which is the page's light and has no business washing over
     a photograph of a lit room. */
  z-index: 1;
}

/* Same maths as .pd-stage, and it has to be the same or the two crops disagree
   and the handover jumps. See the note there about left/transform centring
   rather than grid: an oversized centred grid item overflows toward the inline
   START, which on an RTL page is the right. */
.perde-scope .pd-open-scene {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  aspect-ratio: 720 / 1280;
  inline-size: max(100vw, calc(100svh * 720 / 1280));
  z-index: 0;
}

.perde-scope .pd-open-img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Tailwind preflight caps any width above the container at 100%. */
  max-inline-size: none;
  display: block;
  /* THE BACKGROUND MOVES. A very slow drift and push, so the scene the curtain
     opened onto keeps breathing instead of freezing the moment the video ends.

     Done with TRANSFORM, never by animating width or inset. hena's band lost an
     hour to that: an <img> is a replaced element, so `width: auto` is its
     intrinsic width rather than "fill the offsets", and Tailwind's preflight
     then silently caps anything above 100%. A transform touches none of that
     and is composited rather than laid out. */
  transform-origin: 50% 45%;
  animation: pd-breathe 34s ease-in-out infinite alternate;
}

@keyframes pd-breathe {
  from { transform: scale(1.015) translate3d(0, 0, 0); }
  to { transform: scale(1.07) translate3d(-1.2%, -1.4%, 0); }
}

/* THE CARTOUCHE ON THE PAGE — the geometry, the ground, the halo and the three
   type sizes are all declared once, up with the gate's copy of it; see the note
   there. What is only true down here is that it sits over the photograph and
   centres itself in the header.

   It replaced a soft radial blob, which was the honest fix for a photograph
   whose luminance runs 45 to 174 across this band (crimson-deep against its
   darkest pixel is 1.3:1) and which looked like nothing at all. An opaque panel
   solves the same contrast problem and is an OBJECT: the first piece of paper,
   standing in the opening the curtain was pulled back from. */
.perde-scope .pd-open-copy {
  z-index: 1;
  margin-inline: auto;
}

/* ── sections ───────────────────────────────────────────────────────────── */

.perde-scope .pd-body { position: relative; z-index: 1; background: var(--pd-cream); }

.perde-scope .pd-sec {
  position: relative;
  z-index: 1;
  padding: 0 1.4rem;
  margin-block: var(--pd-rhythm);
}

/* THE SHEET STANDS IN THE OPENING. The invitation is pulled up over the foot of
   the photograph, so the picture and the card interlock instead of stacking —
   which is what turns two screens into one composition rather than a cover
   followed by a page.

   The overlap lands on the massed roses and the floor at the bottom of the
   frame, and it is a long way below the fold: `.pd-open` is 100svh, so nothing
   about the cover's handover is touched by this.

   z-index over the header, which carries the petals at z-index 1. */
.perde-scope .pd-open + .pd-sec:not(.pd-sec--band) {
  margin-block-start: -4rem;
  z-index: 2;
}

/* ── THE WARM ROOM ──────────────────────────────────────────────────────────

   ONE contiguous run — where it is, when it runs, what it looks like — where
   this used to alternate onto four scattered sections and stripe the page.

   FADED AT BOTH EDGES, not a hard step. A flat fill gives the section two
   razor-straight horizontal lines across the page, which is most of what
   "sharp" was: the eye reads a rule where none was drawn. Fading the first and
   last few percent means the ground arrives and leaves without an edge. */
.perde-scope .pd-sec--band {
  padding-block: var(--pd-rhythm);
  margin-block: 0;
  background: linear-gradient(
    to bottom,
    rgba(238, 225, 207, 0) 0%,
    var(--pd-band) 7%,
    var(--pd-band) 93%,
    rgba(238, 225, 207, 0) 100%
  );
}

/* Consecutive banded sections are ONE room, not three with two seams. The
   middle of a run must not fade at all, or the guest walks past two ghost rules
   in the middle of the same ground. */
.perde-scope .pd-sec--band + .pd-sec--band {
  padding-block-start: 0;
  background: linear-gradient(
    to bottom,
    var(--pd-band) 0%,
    var(--pd-band) 93%,
    rgba(238, 225, 207, 0) 100%
  );
}

.perde-scope .pd-sec--band:has(+ .pd-sec--band) {
  /* BACKGROUND ONLY — never the padding. Rule B above already removes the
     following section's top padding, so the gap between two halves of one room
     is carried by THIS section's bottom padding. Zeroing both is how the venue
     and the programme end up touching. */
  background: linear-gradient(
    to bottom,
    rgba(238, 225, 207, 0) 0%,
    var(--pd-band) 7%,
    var(--pd-band) 100%
  );
}

.perde-scope .pd-sec--band + .pd-sec--band:has(+ .pd-sec--band) {
  background: var(--pd-band);
}

.perde-scope .pd-sec--band + .pd-sec:not(.pd-sec--band) { margin-block-start: var(--pd-rhythm); }

.perde-scope .pd-inner { max-inline-size: var(--pd-maxw); margin-inline: auto; }
.perde-scope .pd-inner--wide { max-inline-size: 36rem; }

.perde-scope .pd-reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 720ms var(--pd-ease), transform 720ms var(--pd-ease);
}

.perde-scope .pd-reveal.is-visible { opacity: 1; transform: none; }

.perde-scope .pd-head {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  margin-block-end: 2rem;
}

/* ── THE TIEBACK ────────────────────────────────────────────────────────────

   What a heading hangs from, in place of the diamond between two rules that
   opened all six movements identically. It is the cord that holds the curtain
   back in the middle-left of `opened.webp` — the only object in that photograph
   whose job is to KEEP SOMETHING OPEN, on a page that is entirely about a
   curtain having been drawn.

   IT OPENS FROM THE CENTRE OUTWARD as the section arrives: a clip-path inset
   released by ScrollReveal's `.is-visible`, no handler, no measurement, no
   animation frame. The template's own gesture at ornament scale, and the reason
   this is not simply another static stamp.

   The width is capped in ch-free absolute units because the figure is drawn at
   a fixed 260x34 and letting it run to the full 30rem measure would stretch the
   knot into a lozenge. */
.perde-scope .pd-tie {
  display: block;
  inline-size: min(15rem, 68%);
  color: var(--pd-gold);
  clip-path: inset(0 50% 0 50%);
  transition: clip-path 1100ms var(--pd-ease) 120ms;
}

.perde-scope .pd-tie svg { display: block; inline-size: 100%; block-size: auto; }

.perde-scope .is-visible .pd-tie { clip-path: inset(0 0 0 0); }

.perde-scope .pd-h2 {
  margin: 0;
  font-size: var(--pd-t-head);
  font-weight: 400;
  line-height: 1.5;
  /* Crimson-deep at 11.9:1, not gold at 2.8:1 — a heading is text, and the
     rule at the top of this file is not negotiable for type.

     IT GOT SMALLER, not bigger. At 2.2rem it was a second headline beside 3rem
     names; the tieback above it and the foil below carry the weight now, which
     is what leaves the couple's names and the ayah room to be the two loudest
     things on the page. */
  color: var(--pd-crimson-deep);
  text-align: center;
}

/* The bar of gold leaf under every heading — the second of the foil's four
   rationed uses. A FILL, which is the only thing gold is legal for on cream. */
.perde-scope .pd-head-foil {
  display: block;
  inline-size: 34px;
  block-size: 3px;
  border-radius: 2px;
  background: var(--pd-foil);
  opacity: 0;
  transition: opacity 700ms var(--pd-ease) 520ms;
}

.perde-scope .is-visible .pd-head-foil { opacity: 1; }

.perde-scope .pd-lede {
  margin: 0 0 1.7rem;
  text-align: center;
  font-size: 0.94rem;
  color: var(--pd-ink-soft);
}

/* ── no divider ─────────────────────────────────────────────────────────────
   Asked for four times and shipped three ways — full-bleed at ~110px opening
   every section, then framing only the names, then a 56px sprig — and removed
   each time. The rule, now paid for three times over: when a decorative element
   keeps coming back for another round of tuning, the answer is usually that it
   does not belong, not that the dose is wrong.

   STILL TRUE AFTER THE REDESIGN, and it is worth saying why the tieback is not
   the divider returning for a fifth attempt. A divider sits BETWEEN sections
   and belongs to neither; the tieback belongs to a heading, arrives with it,
   and is absent wherever a heading is. There is no free-standing ornament
   anywhere on this page. */

/* ── the scratch: the date, under gold foil ─────────────────────────────── */

/* IT GATES NOTHING. The calendar underneath is real content in the DOM from
   first render and the canvas is `aria-hidden`, so a screen reader never meets
   the foil at all. See the header of PerdeScratch.js.

   UNTOUCHED BY THE REDESIGN BELOW THE SKIN: same canvas, same CLEAR_ENOUGH,
   same nib, same escape hatch, same sampling. */
.perde-scope .pd-scratch {
  --pd-cleared: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.95rem;
}

.perde-scope .pd-scratch-hint {
  margin: 0;
  font-size: 0.88rem;
  color: var(--pd-crimson);
  /* Fades from under the guest's hand as the foil comes off — the sentence
     stops being needed at the rate it stops being true. 4 puts it at zero
     around 25%, just under CLEAR_ENOUGH (0.26), so the last thing to go is the
     foil and not the line telling you to rub it. Keep the two in step. */
  opacity: calc(1 - var(--pd-cleared) * 4);
  transition: opacity 160ms linear;
}

.perde-scope .pd-scratch-box {
  position: relative;
  inline-size: 100%;
  max-inline-size: 20rem;
}

/* THE FOIL IS SQUARE-EDGED NOW, because what is under it is paper. At 14px it
   was a rounded gold tile sitting over a rounded card, and the reveal was one
   rectangle dissolving into another of the same shape. At the paper radius the
   panel reads as foil laid ON the leaf, and coming off it uncovers a printed
   card rather than clearing a block. */
.perde-scope .pd-scratch-foil {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  display: block;
  border-radius: var(--pd-r-paper);
  /* `touch-action: none` is what stops the browser claiming the gesture as a
     scroll before the first pointermove arrives. Without it the first stroke is
     eaten on every touch device — and here that would ALSO mean the guest
     cannot scroll past a panel they did not want to scratch, so the box is
     sized to the calendar and never to the screen. */
  touch-action: none;
  cursor: crosshair;
  box-shadow: 0 0 0 1px rgba(184, 134, 43, 0.45), 0 10px 26px rgba(42, 20, 24, 0.2);
  transition: opacity 620ms var(--pd-ease), scale 620ms var(--pd-ease);
}

/* POINTER-EVENTS OFF ONCE IT HAS GONE, and this is the line that matters: the
   canvas stays mounted so it can fade, and an invisible canvas still eats every
   tap. Darga shipped exactly this bug — an opacity-0 <video> over the gate
   button, and the door never opened. The add-to-calendar button is directly
   underneath this one. */
.perde-scope .pd-scratch.is-open .pd-scratch-foil {
  opacity: 0;
  scale: 1.03;
  pointer-events: none;
}

/* THE SHIMMER. A band of light crossing the foil every few seconds, so the
   panel reads as an object that catches light rather than as a decorative
   block. `pointer-events: none` is not optional — this sits directly over the
   canvas it exists to advertise, and darga shipped precisely this shape as a
   bug: an invisible layer over the thing the guest was trying to touch. */
.perde-scope .pd-scratch-shine {
  position: absolute;
  inset: 0;
  border-radius: var(--pd-r-paper);
  overflow: hidden;
  pointer-events: none;
}

.perde-scope .pd-scratch-shine::after {
  content: "";
  position: absolute;
  top: -30%;
  bottom: -30%;
  inline-size: 42%;
  background: linear-gradient(
    to right,
    rgba(255, 248, 224, 0) 0%,
    rgba(255, 248, 224, 0.5) 50%,
    rgba(255, 248, 224, 0) 100%
  );
  filter: blur(3px);
  /* Long pause between passes: a light that sweeps constantly is a loading
     spinner. One glint every four seconds is a surface catching a candle. */
  animation: pd-shine 4200ms var(--pd-ease) infinite;
}

@keyframes pd-shine {
  0% { transform: translateX(-180%) rotate(9deg); }
  42% { transform: translateX(340%) rotate(9deg); }
  100% { transform: translateX(340%) rotate(9deg); }
}

.perde-scope .pd-scratch-skip {
  padding: 0.4rem 1.2rem;
  border: 1px solid var(--pd-line);
  border-radius: 999px;
  background: transparent;
  color: var(--pd-ink-soft);
  font-size: 0.8rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 500ms var(--pd-ease);
  cursor: pointer;
}

.perde-scope .pd-scratch-skip.is-shown { opacity: 1; pointer-events: auto; }

.perde-scope .pd-scratch.is-open .pd-scratch-skip { display: none; }

/* ── the invitation: THE ONE PIECE OF PAPER ─────────────────────────────── */

.perde-scope .pd-sheet {
  position: relative;
  max-inline-size: var(--pd-maxw);
  margin-inline: auto;
  padding: 3.4rem 1.9rem 3rem;
  background: var(--pd-paper);
  /* NO BORDER. It had a 1px crimson hairline all round, which is the frame the
     redesign replaced with two corners: a full border makes every sheet on the
     page the same object, and the gilt at two corners says the same thing while
     leaving the other two open. What holds the edge now is the shadow and the
     foil cap. */
  border-radius: var(--pd-r-paper);
  box-shadow: var(--pd-shadow);
  text-align: center;
  overflow: hidden;
}

/* THE CAP OF GOLD LEAF, full width across the head of the sheet — the first of
   the foil's four rationed uses, and the one that does the most work: it is
   what makes the scratch panel five hundred pixels below read as the SAME
   METAL rather than as a gold rectangle that wandered in.

   It replaced a drawn boteh mark centred over the card. The mark broke no rule
   — it was vector, not a cut-out — but it was the third centred ornament inside
   two screens, and a sheet with a foiled head reads as printed stock where a
   sheet with a stamp reads as a card someone decorated. */
.perde-scope .pd-sheet-cap {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 5px;
  background: var(--pd-foil);
}

.perde-scope .pd-bismillah {
  margin: 0 0 1.1rem;
  font-size: 1.05rem;
  line-height: 2;
  color: var(--pd-crimson-deep);
}

.perde-scope .pd-sheet-msg {
  margin: 0;
  font-size: 0.98rem;
  line-height: 2;
  color: var(--pd-ink-soft);
}

.perde-scope .pd-hosts {
  list-style: none;
  margin: 2.2rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.15rem;
}

.perde-scope .pd-host { display: flex; flex-direction: column; gap: 0.1rem; }
.perde-scope .pd-host-lbl { font-size: var(--pd-t-label); color: var(--pd-muted); }
.perde-scope .pd-host-name { font-size: 1.05rem; color: var(--pd-ink); }
.perde-scope .pd-host-name--solo { margin: 2.2rem 0 0; }

/* ── the ayah ───────────────────────────────────────────────────────────── */

/* Set as scripture, not as a line of prose: larger than the body, in Amiri,
   between two gold rules, with room around it. On a nikah card the verse is the
   one thing allowed to outweigh the names, and this is the only block on the
   page that gets that weight.

   THE REDESIGN GAVE IT MORE OF IT, not less. It is now the single step-up in
   the type scale between the names and the footer (--pd-t-verse), it sits on
   its own field of the deeper cream rather than on the sheet's white, and it
   runs full-bleed to the sheet's edges — a printed card sets the verse in a
   panel, not in the middle of a paragraph stack.

   The TEXT is the customer's own, from `details.dua`. Nothing scriptural is
   hard-coded anywhere in this template — see the note in PerdeTemplate. */
.perde-scope .pd-ayah {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.3rem;
  margin: 2.1rem -1.9rem 2.4rem;
  padding: 2.1rem 1.6rem;
  background: linear-gradient(
    to bottom,
    rgba(239, 227, 211, 0) 0%,
    var(--pd-cream-deep) 14%,
    var(--pd-cream-deep) 86%,
    rgba(239, 227, 211, 0) 100%
  );
}

.perde-scope .pd-ayah-rule {
  inline-size: 54px;
  block-size: 1px;
  background: linear-gradient(
    to right,
    rgba(184, 134, 43, 0) 0%,
    var(--pd-gold) 50%,
    rgba(184, 134, 43, 0) 100%
  );
}

.perde-scope .pd-ayah-text {
  margin: 0;
  font-size: var(--pd-t-verse);
  /* Arabic script needs more room between lines than Kurdish prose does, and a
     verse that is read slowly needs more again. */
  line-height: 2.4;
  color: var(--pd-crimson-deep);
  text-align: center;
  text-wrap: balance;
}

/* ── the date (shared CalendarLeaf, pd- skin) ───────────────────────────── */

/* PAPER, by the redesign's rule, so it is corner-marked and near-square like
   the invitation. That is also what makes the foil coming off read as
   uncovering a printed card rather than as clearing a rectangle.

   THE PAPER IS `.pd-leaf`, A WRAPPER, NOT `.pd-cal`. CalendarLeaf is a SHARED
   component and takes no children, so the gilt has nowhere to go inside it —
   and reaching into a component four templates render, to hang one template's
   ornament in it, is how a shared component stops being shared. The template
   wraps it instead, and `.pd-cal` is left holding only the type.

   No `overflow: hidden` and no clipping: the corners sit 9px inside the leaf,
   and clipping would eat the ones that overhang. */
.perde-scope .pd-leaf {
  position: relative;
  max-inline-size: 20rem;
  margin-inline: auto;
  background: var(--pd-paper);
  border-radius: var(--pd-r-paper);
  box-shadow: var(--pd-shadow);
}

.perde-scope .pd-leaf .pd-gilt { --pd-gilt: 34px; }

.perde-scope .pd-cal-top {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.5rem;
  margin: 0;
  padding: 1.15rem 0.9rem 0.5rem;
  /* NOT A CRIMSON HEADER BAR ANY MORE. A filled bar across the top of a card is
     an app's list header; the month is a line of type on the paper, in the ink
     the rest of the page uses for headings. The leaf now has one fill on it and
     it is the day. */
  color: var(--pd-crimson-deep);
}

.perde-scope .pd-cal-month { font-size: 1.16rem; }

.perde-scope .pd-cal-year {
  font-size: 0.76rem;
  color: var(--pd-muted);
}

.perde-scope .pd-cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 0.15rem;
  padding: 0.9rem 1.15rem 0.5rem;
}

.perde-scope .pd-cal-wd {
  font-size: 0.6rem;
  text-align: center;
  color: var(--pd-muted);
  padding-block-end: 0.35rem;
}

.perde-scope .pd-cal-cell {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  font-size: 0.78rem;
  color: var(--pd-ink-soft);
  border-radius: 999px;
}

.perde-scope .pd-cal-cell.is-day {
  background: var(--pd-crimson);
  color: #fff6f1;
  font-weight: 600;
}

.perde-scope .pd-cal-foot { padding: 0.4rem 1.15rem 1.3rem; text-align: center; }

.perde-scope .pd-cal-line {
  display: block;
  margin: 0 0 0.85rem;
  font-size: 0.92rem;
  color: var(--pd-ink);
}

.perde-scope .pd-cal-time {
  display: block;
  font-size: 0.8rem;
  color: var(--pd-muted);
}

.perde-scope .pd-chip--full { inline-size: 100%; justify-content: center; }

/* ── countdown (shared Countdown, pd- skin) ─────────────────────────────── */

/* FOUR NUMERALS PRINTED ON THE PAGE, not four cards. This was four bordered,
   rounded, filled boxes — the clearest single case of the disease the redesign
   was called to fix, because a countdown is four numbers and the boxes were
   saying "app" four times in a row directly under the date.

   Now: the numbers sit on the cream over one hairline, separated by nothing at
   all, and each drains a bar of gold leaf as its unit runs down. */
.perde-scope .pd-cd {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  max-inline-size: 22rem;
  margin-inline: auto;
  border-block-start: 1px solid var(--pd-line-soft);
}

.perde-scope .pd-cd-unit {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  padding: 1.15rem 0.2rem 1rem;
}

/* The rules between the units are hairlines drawn BETWEEN, not borders on each
   — a border-inline-start on every unit puts one on the outer edge too, which
   is a stray tick hanging off the end of a row that has no box. */
.perde-scope .pd-cd-unit + .pd-cd-unit::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block: 1.1rem;
  inline-size: 1px;
  background: var(--pd-line-soft);
}

/* THE DRAIN, and the bug it was hiding.

   The shared component writes `--f` on each unit (components/templates/shared/
   Countdown.js). This stylesheet read `var(--fill, 0)`, which resolves to
   nothing, so the fill computed to zero and the cell drew NOTHING — for the
   whole life of the template. It is the exact defect this file already
   documents at length for the motes (`--s`/`--d` against `--size`/`--dur`),
   in the same file, from the same cause: a skin written fresh instead of copied
   from the templates that worked.

   THE LESSON IS THE ONE ALREADY WRITTEN DOWN HERE: a decorative layer that
   renders no error is not evidence that it renders. If you cannot see it on the
   page, it is not there. */
.perde-scope .pd-cd-unit::before {
  content: "";
  position: absolute;
  inset-inline: 22%;
  inset-block-end: 0;
  block-size: 2px;
  border-radius: 2px;
  background: var(--pd-foil);
  transform-origin: center;
  transform: scaleX(var(--f, 0));
  transition: transform 900ms linear;
}

.perde-scope .pd-cd-num {
  position: relative;
  font-size: 1.62rem;
  line-height: 1.25;
  color: var(--pd-crimson-deep);
}

.perde-scope .pd-cd-lbl {
  position: relative;
  font-size: 0.66rem;
  color: var(--pd-muted);
}

.perde-scope .pd-arrived {
  text-align: center;
  font-size: 1.08rem;
  color: var(--pd-crimson);
}

/* ── programme (shared Timeline, pd- skin) ──────────────────────────────── */

.perde-scope .pd-prog-wrap { max-inline-size: 24rem; margin-inline: auto; }

.perde-scope .pd-prog {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0;
  padding-inline-start: 1.7rem;
}

/* The rail, and the lit part of it. The shared component writes --prog-p. */
.perde-scope .pd-prog::before,
.perde-scope .pd-prog::after {
  content: "";
  position: absolute;
  inset-inline-start: 0.62rem;
  top: 0.6rem;
  bottom: 0.6rem;
  inline-size: 1px;
}

.perde-scope .pd-prog::before { background: var(--pd-line-soft); }

.perde-scope .pd-prog::after {
  background: var(--pd-gold);
  transform-origin: top;
  transform: scaleY(var(--prog-p, 0));
  transition: transform 220ms linear;
}

.perde-scope .pd-prog-row {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding-block: 0.95rem;
  border-bottom: 1px solid var(--pd-line-soft);
}

.perde-scope .pd-prog-row:last-child { border-bottom: 0; }

/* The bead on the rail. A small LOZENGE rather than a disc, so it belongs to
   the tieback's knot and to the gilt's bud instead of to a generic timeline. */
.perde-scope .pd-prog-row::before {
  content: "";
  position: absolute;
  inset-inline-start: -1.35rem;
  top: 1.3rem;
  inline-size: 9px;
  block-size: 9px;
  background: var(--pd-cream);
  border: 1px solid var(--pd-line);
  rotate: 45deg;
  transition: background 260ms ease, border-color 260ms ease;
}

.perde-scope .pd-sec--band .pd-prog-row::before { background: var(--pd-band); }

.perde-scope .pd-prog-row.is-on::before {
  background: var(--pd-gold);
  border-color: var(--pd-gold);
}

.perde-scope .pd-prog-time {
  font-size: 0.76rem;
  color: var(--pd-muted);
  opacity: 0.6;
  transition: opacity 260ms ease;
}

.perde-scope .pd-prog-row.is-on .pd-prog-time { opacity: 1; }

.perde-scope .pd-prog-title { font-size: 1rem; color: var(--pd-ink); }

/* ── venue (shared VenueBlock, pd- skin) ────────────────────────────────── */

.perde-scope .pd-venue { text-align: center; }

.perde-scope .pd-venue-name {
  margin: 0;
  font-size: 1.34rem;
  font-weight: 400;
  line-height: 1.6;
  color: var(--pd-ink);
}

.perde-scope .pd-venue-addr {
  margin: 0.45rem 0 0;
  font-size: 0.92rem;
  color: var(--pd-ink-soft);
}

.perde-scope .pd-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  justify-content: center;
  margin-block-start: 1.5rem;
}

/* NOT A FILLED PILL. The chip was a paper-coloured capsule with a border, which
   on the warm room's ground was a fifth kind of light rectangle. It is a line
   of type over a hairline now, and the hairline is what moves on hover. */
.perde-scope .pd-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.55rem 0.9rem;
  border: 0;
  border-block-end: 1px solid var(--pd-line);
  background: none;
  color: var(--pd-ink);
  font-size: 0.88rem;
  text-decoration: none;
  transition: border-color 240ms var(--pd-ease), color 240ms var(--pd-ease);
}

.perde-scope .pd-chip:hover {
  border-block-end-color: var(--pd-crimson);
  color: var(--pd-crimson-deep);
}

.perde-scope .pd-chip-ico { inline-size: 16px; block-size: 16px; color: var(--pd-crimson); }

/* The add-to-calendar control is the exception, because it is the one chip that
   is a CALL rather than a link, and it sits on paper where a hairline under a
   full-width row reads as a table rule. */
.perde-scope .pd-chip--full {
  padding-block: 0.7rem;
  border: 1px solid var(--pd-line);
  border-radius: 999px;
  color: var(--pd-crimson-deep);
}

.perde-scope .pd-chip--full:hover { border-color: var(--pd-crimson); background: var(--pd-cream); }

/* The map sits behind a tap: the third-party iframe is not loaded until the
   guest asks for it, so an invitation's secret URL is not handed to Google on
   every open. */
.perde-scope .pd-map {
  position: relative;
  margin-block-start: 1.6rem;
  border-radius: var(--pd-r);
  overflow: hidden;
}

.perde-scope .pd-map-frame {
  display: block;
  inline-size: 100%;
  block-size: 260px;
  border: 0;
}

.perde-scope .pd-map-hit {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  inline-size: 100%;
  block-size: 190px;
  background: var(--pd-cream);
  border: 1px solid var(--pd-line-soft);
  border-radius: var(--pd-r);
  color: var(--pd-ink-soft);
  font-size: 0.88rem;
  cursor: pointer;
  transition: background 240ms var(--pd-ease);
}

.perde-scope .pd-map-hit:hover { background: var(--pd-paper); }

.perde-scope .pd-map-pin { inline-size: 22px; block-size: 22px; color: var(--pd-crimson); }
.perde-scope .pd-map-lbl { font-size: 0.86rem; }

/* ── gallery (shared Slider, pd- skin) ──────────────────────────────────── */

.perde-scope .pd-slides {
  display: flex;
  gap: 0.9rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding-block: 1rem 0.5rem;
  /* Room for the brackets, which sit outside the tile. */
  padding-inline: 0.5rem;
  margin-inline: -0.5rem;
  list-style: none;
}

.perde-scope .pd-slides::-webkit-scrollbar { display: none; }

.perde-scope .pd-slide {
  position: relative;
  flex: 0 0 74%;
  scroll-snap-align: center;
  /* THE PHOTOGRAPHS ARE PRESENTED, NOT STACKED. The off-centre ones sit back a
     little and lift as they come to the middle, so a strip that used to be an
     even row of tiles now has a front and a back. `is-active` is the shared
     Slider's own class and was going unused here. */
  transform: scale(0.93);
  opacity: 0.72;
  transition: transform 520ms var(--pd-ease), opacity 520ms var(--pd-ease);
}

.perde-scope .pd-slide.is-active { transform: none; opacity: 1; }

/* Corner brackets rather than a full border — the SAME rule the gilt follows,
   and the place it was already being followed before the redesign made it the
   page's language. A photograph inside a complete frame reads as a framed
   picture on a wall; two opposite corners read as a photograph being presented.
   `overflow: hidden` is deliberately NOT set here — it would clip the brackets
   that sit outside the tile — so the image radius is set on the image. */
.perde-scope .pd-slide::before,
.perde-scope .pd-slide::after {
  content: "";
  position: absolute;
  inline-size: 26px;
  block-size: 26px;
  border: 1px solid var(--pd-gold);
  opacity: 0;
  pointer-events: none;
  transition: opacity 520ms var(--pd-ease);
}

.perde-scope .pd-slide.is-active::before,
.perde-scope .pd-slide.is-active::after { opacity: 1; }

.perde-scope .pd-slide::before {
  inset-block-start: -7px;
  inset-inline-start: -7px;
  border-inline-end: 0;
  border-block-end: 0;
}

.perde-scope .pd-slide::after {
  inset-block-end: -7px;
  inset-inline-end: -7px;
  border-inline-start: 0;
  border-block-start: 0;
}

.perde-scope .pd-slide-btn {
  display: block;
  inline-size: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}

.perde-scope .pd-slide img {
  inline-size: 100%;
  block-size: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
  border-radius: var(--pd-r);
  box-shadow: 0 16px 34px -14px rgba(42, 20, 24, 0.34);
}

.perde-scope .pd-dots {
  display: flex;
  gap: 0.5rem;
  justify-content: center;
  margin-block-start: 1.2rem;
}

/* Lozenges, turned 45°, so the dots belong to the tieback's knot and to the
   programme's beads. Three marks on this page are the same small diamond and
   that is on purpose — it is the only figure repeated anywhere. */
.perde-scope .pd-dot {
  inline-size: 6px;
  block-size: 6px;
  padding: 0;
  border: 0;
  rotate: 45deg;
  background: var(--pd-line);
  cursor: pointer;
  transition: background 260ms var(--pd-ease), scale 260ms var(--pd-ease);
}

.perde-scope .pd-dot[aria-current="true"] { background: var(--pd-crimson); scale: 1.35; }

.perde-scope .pd-gallery-hint {
  margin: 1rem 0 0;
  text-align: center;
  font-size: 0.72rem;
  color: var(--pd-muted);
}

/* The viewer. It portals to [data-tpl-scope], which is this element — parented
   to <body> every custom property below would resolve to its fallback and the
   viewer would render untinted. */
.perde-scope .pd-lb {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
}

.perde-scope .pd-lb-veil { position: absolute; inset: 0; background: rgba(20, 4, 8, 0.94); border: 0; }

.perde-scope .pd-lb-img {
  position: relative;
  max-inline-size: 92vw;
  max-block-size: 82svh;
  object-fit: contain;
  border-radius: var(--pd-r);
}

.perde-scope .pd-lb-bar {
  position: absolute;
  inset-inline: 0;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.9rem 1.1rem;
}

.perde-scope .pd-lb-count { font-size: 0.8rem; color: var(--pd-gold-light); }

/* Fixed boxes with an explicitly sized glyph. An SVG with no dimensions takes
   its aspect from its viewBox and stretches its parent — zerf's arrows came
   out 48x81, a lozenge rather than a button. 44px is the touch-target floor
   and no larger. */
.perde-scope .pd-lb-x,
.perde-scope .pd-lb-arrow {
  display: grid;
  place-items: center;
  border: 1px solid rgba(217, 185, 104, 0.4);
  border-radius: 999px;
  background: rgba(42, 6, 13, 0.6);
  color: var(--pd-gold-light);
  cursor: pointer;
}

.perde-scope .pd-lb-x { inline-size: 40px; block-size: 40px; }
.perde-scope .pd-lb-arrow { inline-size: 44px; block-size: 44px; }

.perde-scope .pd-lb-x svg,
.perde-scope .pd-lb-arrow svg { inline-size: 18px; block-size: 18px; }

.perde-scope .pd-lb-nav {
  position: absolute;
  inset-inline: 0;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  justify-content: space-between;
  padding-inline: 0.8rem;
  pointer-events: none;
}

.perde-scope .pd-lb-nav button { pointer-events: auto; }

/* ── rsvp, notes, contact, wishes ───────────────────────────────────────── */

/* THE THIRD PIECE OF PAPER — and it is paper properly now, rather than a panel
   with a hairline: same near-square edge, same shadow, same two gilt corners as
   the invitation and the date. Those three are what a guest screenshots, and
   they are the only three lifted things on nine screens. */
.perde-scope .pd-rsvp {
  position: relative;
  max-inline-size: 26rem;
  margin-inline: auto;
  padding: 2.6rem 1.6rem 2rem;
  background: var(--pd-paper);
  border-radius: var(--pd-r-paper);
  box-shadow: var(--pd-shadow);
}

/* Smaller here than on the invitation, and the padding above is what buys the
   room: this is the only piece of paper with CONTROLS on it, and a hairline
   crossing the corner of a text field reads as a rendering fault rather than as
   gilt. */
.perde-scope .pd-rsvp .pd-gilt { --pd-gilt: 32px; }

.perde-scope .pd-notes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  max-inline-size: 26rem;
  margin-inline: auto;
}

.perde-scope .pd-note {
  position: relative;
  padding-inline-start: 1.5rem;
  font-size: 0.96rem;
  color: var(--pd-ink-soft);
}

/* The same lozenge again, at bullet size. */
.perde-scope .pd-note::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0.82em;
  inline-size: 6px;
  block-size: 6px;
  rotate: 45deg;
  background: var(--pd-gold);
}

.perde-scope .pd-contact { text-align: center; }

.perde-scope .pd-contact-name {
  font-size: 1.24rem;
  line-height: 1.7;
  color: var(--pd-ink);
}

.perde-scope .pd-contact-role {
  margin: 0.25rem 0 0;
  font-size: 0.86rem;
  color: var(--pd-muted);
}

/* WISHES ARE QUOTATIONS, NOT CARDS. Six bordered rectangles in a column was the
   single biggest source of "everything on this page is the same object", and a
   wish is somebody's sentence rather than a component. Each is set as a quote
   with the name under it, and between them the fourth and last rationed mark of
   gold leaf. */
.perde-scope .pd-wishes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  max-inline-size: 26rem;
  margin-inline: auto;
}

.perde-scope .pd-wish {
  position: relative;
  padding-block: 1.6rem;
  text-align: center;
}

.perde-scope .pd-wish + .pd-wish::before {
  content: "";
  position: absolute;
  inset-block-start: -4px;
  inset-inline: 0;
  margin-inline: auto;
  inline-size: 8px;
  block-size: 8px;
  rotate: 45deg;
  background: var(--pd-foil);
}

.perde-scope .pd-wish-msg {
  margin: 0;
  font-size: 1rem;
  line-height: 2;
  color: var(--pd-ink-soft);
}

.perde-scope .pd-wish-name {
  margin: 0.7rem 0 0;
  font-size: var(--pd-t-label);
  color: var(--pd-crimson);
}

/* ── THE CURTAIN CLOSES ─────────────────────────────────────────────────────

   The footer was a crimson gradient with a radial lit into the middle. It is
   now the cover's own photograph, so an invitation that opens by parting a
   curtain ends by closing one — the beat this template was missing.

   IT COSTS NOTHING. `curtain.webp` is fetched at fetchPriority high on the very
   first screen, so nine screens later it is a cache hit. That is also why it is
   the right file and `cloth.webp` is not: the story card's ground is 77KB the
   guest has no other reason to download. */
.perde-scope .pd-closing {
  position: relative;
  display: grid;
  place-items: center;
  min-block-size: 78svh;
  padding: 8rem 1.5rem 6.5rem;
  text-align: center;
  overflow: hidden;
  background: var(--pd-velvet);
}

/* Same fixed-aspect stage as the gate and the opening — a third crop of the
   same picture at a different aspect would read as a different photograph. */
.perde-scope .pd-closing-scene {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  aspect-ratio: 720 / 1280;
  inline-size: max(100vw, calc(100svh * 720 / 1280));
  z-index: 0;
}

.perde-scope .pd-closing-img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  max-inline-size: none;
  display: block;
  /* Slowly drawing closer, mirrored against the opening's drift so the two
     screens are not the same move twice. */
  transform-origin: 50% 55%;
  animation: pd-draw 46s ease-in-out infinite alternate;
}

@keyframes pd-draw {
  from { transform: scale(1.06) translate3d(0.8%, 0, 0); }
  to { transform: scale(1.015) translate3d(0, 0, 0); }
}

/* THE VEIL, and it is measured rather than eyeballed. Gold-light is 9.8:1 on
   flat velvet; over this picture the crimson runs to about (120, 20, 35) in the
   lit folds, where it would fall to roughly 4.4:1. At this veil the brightest
   band under the type sits near (58, 12, 20) and gold-light is back over 8:1.

   The cream at the top does not stop at a line — it dissolves into the picture
   over a couple of centimetres. This was the hardest edge on the page: a
   full-width cut from #f7efe4 straight to #2a060d. */
.perde-scope .pd-closing-veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(to bottom, var(--pd-cream) 0%, rgba(42, 6, 13, 0) 22%),
    radial-gradient(78% 52% at 50% 52%, rgba(20, 4, 8, 0.62) 0%, rgba(20, 4, 8, 0.9) 100%);
  pointer-events: none;
}

.perde-scope .pd-closing-copy {
  position: relative;
  z-index: 2;
  max-inline-size: 22rem;
}

.perde-scope .pd-closing-rule {
  display: block;
  inline-size: 54px;
  block-size: 1px;
  margin: 0 auto 2rem;
  background: linear-gradient(
    to right,
    rgba(217, 185, 104, 0) 0%,
    var(--pd-gold-light) 50%,
    rgba(217, 185, 104, 0) 100%
  );
}

.perde-scope .pd-closing-msg {
  margin: 0;
  font-size: var(--pd-t-verse);
  line-height: 2.2;
  color: var(--pd-gold-light);
}

.perde-scope .pd-hashtag {
  margin: 1.8rem 0 0;
  font-size: 0.84rem;
  color: var(--pd-gold);
}

/* ── the petals, over the opened curtain ────────────────────────────────── */

/* ABSOLUTE, not fixed — the opposite of the embers below, and deliberately.
   These belong to the first screen and must stop at its bottom edge; a fixed
   layer would follow the guest down eight screens of cream and be a second
   copy of the embers. `.pd-open` is the positioned ancestor. */
.perde-scope .pd-petals {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1200ms var(--pd-ease);
}

.perde-scope .pd-petals.is-live { opacity: 1; }

/* THE CUSTOM PROPERTIES ARE `--s` AND `--d`, NOT `--size` AND `--dur`.
   Written down because this file had them wrong from the day it was created and
   nothing said so: shared/Motes emits --x, --s, --d, --delay, --drift, --peak,
   and a var() that resolves to nothing makes `inline-size` compute to 0 and
   `animation-name` compute to `none`. So the layer mounted, reported eighteen
   elements to any DOM check, and painted absolutely nothing.

   That is the whole reason the founder asked for "something moving in the
   background" — there already was, and it had been invisible since the day it
   shipped. hena and zerf both had it right; only this file, written fresh
   instead of copied from the ones that worked, got it wrong.

   THE COUNTDOWN HAD THE SAME DEFECT and it survived until 2026-08-28 — see the
   note on `.pd-cd-unit::before`. A DOM count is not evidence that a decorative
   layer renders. Measure the computed size. */
.perde-scope .pd-petal {
  position: absolute;
  inset-inline-start: var(--x);
  top: -12vh;
  inline-size: var(--s);
  block-size: calc(var(--s) * 0.72);
  /* A petal shape rather than a disc: round one pair of opposite corners hard
     and leave the others soft, and it reads as something that fell off a
     flower instead of as a bubble. */
  border-radius: 60% 12% 60% 12%;
  background: linear-gradient(140deg, rgba(196, 40, 62, 0.92), rgba(120, 12, 28, 0.72));
  opacity: 0;
  animation: pd-fall var(--d) linear var(--delay) infinite;
}

.perde-scope .pd-petal.is-v1 {
  background: linear-gradient(140deg, rgba(230, 90, 108, 0.9), rgba(150, 24, 42, 0.7));
}

.perde-scope .pd-petal.is-v2 {
  background: linear-gradient(140deg, rgba(217, 185, 104, 0.85), rgba(160, 116, 34, 0.68));
}

/* Falling AND turning. A petal that drops without rotating is a rectangle on a
   string; the tumble is most of what sells it as weightless. */
@keyframes pd-fall {
  0% { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 0; }
  10% { opacity: var(--peak); }
  85% { opacity: var(--peak); }
  100% {
    transform: translate3d(var(--drift), 122vh, 0) rotate(420deg);
    opacity: 0;
  }
}

/* ── the embers (shared Motes, pd- skin) ────────────────────────────────── */

/* FIXED, not absolute. A scattered absolute layer spread over nine screens
   shows total ÷ 9 — the density trap darga measured at thirteen leaves and two
   on screen. The shared component pauses the whole layer when its host is off
   screen, so a fixed layer costs nothing while the guest is in the footer. */
.perde-scope .pd-embers {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity 900ms ease;
}

.perde-scope .pd-embers.is-live { opacity: 1; }

.perde-scope .pd-ember {
  position: absolute;
  inset-inline-start: var(--x);
  bottom: -8vh;
  inline-size: var(--s);
  block-size: var(--s);
  border-radius: 999px;
  /* A radial fade to transparent, never a solid disc. Zerf's petals shipped
     solid for an hour and read as crumbs scattered on the page rather than as
     anything falling through it; on a light ground a hard-edged mark is debris
     long before it is weather. */
  background: radial-gradient(circle, rgba(140, 20, 40, 0.9) 0%, rgba(140, 20, 40, 0) 70%);
  opacity: 0;
  animation: pd-rise var(--d) linear var(--delay) infinite;
}

.perde-scope .pd-ember.is-v1 {
  background: radial-gradient(circle, rgba(184, 134, 43, 0.9) 0%, rgba(184, 134, 43, 0) 70%);
}

.perde-scope .pd-ember.is-v2 {
  background: radial-gradient(circle, rgba(94, 12, 26, 0.85) 0%, rgba(94, 12, 26, 0) 70%);
}

@keyframes pd-rise {
  0% { transform: translate3d(0, 0, 0); opacity: 0; }
  12% { opacity: var(--peak); }
  88% { opacity: var(--peak); }
  100% { transform: translate3d(var(--drift), -118vh, 0); opacity: 0; }
}

/* ── the small phone ────────────────────────────────────────────────────────

   The corner is the shape language and a corner has a fixed size, so it is the
   one thing on this page that does not scale itself. On a 360px screen the
   sheet's measure is 360 - 2.8rem of section padding - 3.8rem of sheet padding
   = about 253px, and two 46px corners inside that is a lot of gilt for very
   little type. They come down; nothing else has to. */
@media (max-width: 24rem) {
  .perde-scope { --pd-card-pad: 2.2rem 1.4rem 1.9rem; }
  .perde-scope .pd-gilt { --pd-gilt: 36px; }
  .perde-scope .pd-leaf .pd-gilt { --pd-gilt: 28px; }
  .perde-scope .pd-sheet { padding: 3rem 1.5rem 2.6rem; }
  .perde-scope .pd-ayah { margin-inline: -1.5rem; }
  .perde-scope .pd-cd-num { font-size: 1.42rem; }
}

/* ── reduced motion ─────────────────────────────────────────────────────── */

/* A DIFFERENT PRODUCT, NOT A DISABLED ONE. Two of these matter more than the
   rest and are named rather than swept up by a blanket rule:

   The FOIL is not styled away here — PerdeScratch never paints it, and one tap
   opens the cover. A scratch surface that cannot be scratched is content that
   is invisible forever, and suppressing only the animation would leave a guest
   rubbing at something that never answers.

   The REVEAL must land at opacity 1. Killing the transition alone leaves every
   section at the 0 it starts from, which is a blank page that returns 200. That
   now covers three MORE things the redesign added — the gilt corners, the
   tieback's clip-path and the heading's foil bar — and every one of them is the
   same trap: an ornament whose resting state is invisible is an ornament that
   is invisible forever for the guest who asked for no motion. */
@media (prefers-reduced-motion: reduce) {
  .perde-scope .pd-reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* The gilt is simply drawn, the tieback is simply open, the foil bar is
     simply there. Not animated in, not hidden. */
  .perde-scope .pd-gilt {
    opacity: 1;
    translate: 0 0;
    transition: none;
  }

  .perde-scope .pd-tie {
    clip-path: inset(0 0 0 0);
    transition: none;
  }

  .perde-scope .pd-head-foil { opacity: 1; transition: none; }

  .perde-scope .pd-embers,
  .perde-scope .pd-petals,
  .perde-scope .pd-scratch-shine { display: none; }

  /* The candle holds still. It is light, not decoration, so it stays lit — what
     goes is the flicker. */
  .perde-scope .pd-glow { animation: none; opacity: 0.94; }

  /* The backgrounds hold still, and the names simply ARE there rather than
     being written on — a clip-path animation that never runs would leave them
     clipped to nothing, which is the "ornament invisible forever" trap the
     reduced-motion rules exist to prevent. */
  .perde-scope .pd-open-img { animation: none; transform: none; }
  .perde-scope .pd-closing-img { animation: none; transform: none; }
  .perde-scope .pd-gate-names { opacity: 1; clip-path: inset(0 0 0 0); animation: none; }
  .perde-scope .pd-gate-card { opacity: 1; animation: none; }

  .perde-scope .pd-slide { transform: none; opacity: 1; transition: none; }
  .perde-scope .pd-slide::before,
  .perde-scope .pd-slide::after { opacity: 1; transition: none; }

  .perde-scope .pd-prog::after { transition: none; }
  .perde-scope .pd-prog-row::before { transition: none; }
  .perde-scope .pd-cd-unit::before { transition: none; }

  /* The ring is the cover's only motion and it is pure affordance; the words
     carry it alone here. */
  .perde-scope .pd-gate-ring { display: none; }

  .perde-scope .pd-gate--leaving { transition-duration: 1ms; }

  /* No curtain animation for a guest who asked for none: openIt() never plays
     the clip under reduced motion, so this only makes certain the layer can
     never appear. */
  .perde-scope .pd-stage-clip { display: none; }

  /* And with no curtain there is nothing to be behind. The names are stated
     properly on the page a moment later, so nothing is lost by never running
     this — where animating it anyway would be motion for the one guest who
     asked for none. */
  .perde-scope .pd-gate-reveal { display: none; }

  /* The FOIL is not styled away — PerdeScratch never paints it and never
     mounts the canvas, so the date is simply visible. Suppressing only the
     animation would leave a guest rubbing at something that never answers, and
     a foil that never comes off is content that is invisible forever. */
  .perde-scope .pd-scratch-foil { transition: none; }

  .perde-scope .pd-page { transition: none; }
}
