/* Zerf (زەرف) — original work for Ahang, 2026-08-19, redesigned 2026-08-28.
   Loaded as a <link> for this template only; every rule is scoped by
   .zerf-scope / .zf-.

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

   THE PAGE IS A STATIONERY SUITE LYING ON A CLOTH. Four pieces of paper — the
   invitation, the details, the reply, the guests' notes — with a length of sage
   silk threading between them and tying off at the foot. The cloth is the
   photograph the design was built on; the ribbon is the one object in that
   photograph the old page never used.

   THE ELEVATION RULE, which replaces "only ONE thing may be lifted":
   EVERYTHING PRINTED IS A CARD AND NOTHING ELSE IS. Every piece of the suite
   carries the SAME shadow (--zf-lift), so no card competes with another, and
   nothing on this page is lifted that is not paper. Things ON a card are
   PRESSED INTO it — the calendar leaf, the countdown cells, the form's fields —
   never raised off it. A second elevation on a light ground is how a design
   like this turns into a dashboard. */

.zerf-scope {
  /* ── the locked palette ───────────────────────────────────────────────
     Three copies of these must agree: TPL_VARS in ZerfTemplate.js, `vars` in
     lib/templates/themes.js, and this block. Darga's two copies drifted for two
     days and its gallery swatch advertised a palette the template had left.

     UNCHANGED BY THE REDESIGN. The brief was a new composition, not a new
     colour; darga and hena both held their palettes through theirs. */
  --zf-linen: #f6f2ea;
  --zf-linen-deep: #efeade;
  --zf-paper: #fffdf8;

  /* THE CONTRAST RULES ARE THE NORMAL WAY ROUND HERE, which is worth stating
     because hena's are inverted and the two files sit side by side:

       on LINEN   ink 10.6:1, ink-soft 7.1:1, muted 3.9:1 (labels only, never
                  body text). Brass is 2.4:1 and may ONLY fill or hairline.
       on PAPER   the same, a shade higher.

     Sage at 4.6:1 is legal as text but is reserved for fills and rules, so the
     page has exactly one text colour family and does not go stripy. */
  --zf-ink: #2f3a33;
  --zf-ink-soft: #4a5a4e;
  --zf-muted: #7c857c;

  --zf-sage: #6f8168;
  --zf-sage-deep: #55654f;
  --zf-brass: #b99a5b;
  --zf-brass-soft: #d3bb8b;

  --zf-line: rgba(111, 129, 104, 0.25);
  --zf-line-soft: rgba(111, 129, 104, 0.14);

  /* ── derived, and only used by the ribbon ─────────────────────────────
     SILK IS NOT WAX. The seal in the photograph is the sage above; the ribbon
     beside it is the same dye in a fabric that catches light, so it reads
     several steps lighter. --zf-silk is --zf-sage lifted toward the paper and
     --zf-silk-back is the shadowed underside, which is --zf-sage-deep exactly.
     Written as literals rather than color-mix() because these are large FILLS
     and this audience includes in-app WebViews whose engine version the OS does
     not always advertise; a fill that falls back to `unset` is a hole. */
  --zf-silk: #93a58b;
  --zf-silk-back: var(--zf-sage-deep);

  /* THE ONE ELEVATION. Every piece of the suite wears this and nothing else
     does. The third layer is a hairline, not a blur: a paper edge on a light
     ground is a line, and a shadow alone leaves the card looking printed on
     the cloth rather than laid on it. */
  --zf-lift:
    0 22px 42px -28px rgba(47, 58, 51, 0.5),
    0 2px 4px -2px rgba(47, 58, 51, 0.08),
    0 0 0 1px rgba(47, 58, 51, 0.05);
  --zf-lift-sm:
    0 10px 20px -14px rgba(47, 58, 51, 0.42),
    0 0 0 1px rgba(47, 58, 51, 0.045);

  --zf-r: 3px; /* paper corners are nearly square — a 14px radius reads as an app card */
  --zf-card-w: 30rem;
  --zf-gut: 1.15rem;
  --zf-pad: 1.5rem; /* a card's own inner margin; the creases bleed by exactly this */

  /* THE TYPE RANK THE OLD PAGE DID NOT HAVE. It ran 0.76 / 1.02 / 1.42 / 3rem,
     and the 1.42 heading sat one step above body text with a caption directly
     under it — two rows that read as one stalled sentence. The heading is a
     real step up now, and the label rank is carried by a card's own printed
     caption rather than by a second line under every title. */
  --zf-t-label: 0.74rem;
  --zf-t-body: 1rem;
  --zf-t-head: 1.55rem;
  --zf-t-display: 3.1rem;

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

  /* Under everything, so nothing is ever transparent onto the app's own page
     colour. The CLOTH is painted by .zf-ground; this is what it sits on and
     what shows during the one frame before it decodes. */
  background-color: var(--zf-linen);

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

/* ── the gate ─────────────────────────────────────────────────────────────
   UNCHANGED IN EVERY RESPECT THAT MATTERS. The stage's aspect, the mouth at
   50.2%, the seal at 49%/65.5% and the card's geometry are registered against
   a photograph and cannot move. What the redesign touched is the card's face
   and the copy's ranking. */

.zerf-scope .zf-gate {
  position: fixed;
  inset: 0;
  z-index: 60;
  overflow: hidden;
  /* svh, NOT dvh: dvh grows as a phone toolbar retracts and shoves the whole
     cover down mid-gesture — which on a DRAG would move the target under the
     thumb that is dragging it. */
  height: 100svh;
  background: var(--zf-linen);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  cursor: grab;
}

.zerf-scope .zf-gate--pulling { cursor: grabbing; }

/* THE STAGE IS FIXED-ASPECT, AND THAT IS LOAD-BEARING.
   The envelope is baked into the photograph, so the crack has to land on the
   photographed seal and the card has to come out of the photographed mouth.
   `background-size: cover` crops by a different amount at every viewport,
   which would put the crack NEXT TO the seal instead of on it.

   Locking the aspect and sizing to cover means one coordinate system: every
   percentage below is a percentage of this box, measured off the file with a
   grid overlay, and the picture and the overlays crop together.

   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. Measured: the stage sat at left -85 with
   its right edge flush at 390, so the whole scene was shifted 42px off and the
   card ran off the screen. A physical left/translateX has no such opinion. */
.zerf-scope .zf-stage {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  aspect-ratio: 720 / 1280;
  inline-size: max(100vw, calc(100svh * 720 / 1280));
  --zf-p: 0;
}

.zerf-scope .zf-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;
  /* OUT OF HIT-TESTING, so the picture can never become a drag source. See the
     note on `draggable={false}` in ZerfGate — a native image drag ate the
     pointermove stream and the card moved 3px of a 70px pull. This is the
     half that also covers browsers which ignore the attribute. */
  pointer-events: none;
  -webkit-user-drag: none;
}

/* The card, clipped to the envelope's mouth at 50.2%.
   It is not BEHIND the envelope — the envelope is part of the photograph and
   cannot occlude anything. Clipping the layer gets the same result honestly:
   below that line nothing in here paints, so the card can only ever be seen
   coming out. */
.zerf-scope .zf-cardclip {
  position: absolute;
  inset: 0;
  clip-path: inset(0 0 49.8% 0);
  pointer-events: none;
}

.zerf-scope .zf-card {
  position: absolute;
  /* `left`/`right`, NOT logical properties. These are coordinates in a
     photograph and must not flip with text direction — the envelope does not
     move when the invitation is authored in Badini. */
  /* The envelope spans 14%-87% of the stage. At 16% the card was very nearly
     as wide as the thing it is supposed to have come out of, which reads as a
     white slab laid over the photograph rather than as a card. */
  left: 19%;
  right: 19%;
  top: 50.2%;
  height: 30%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1rem;
  border-radius: 2px;
  background: var(--zf-paper);
  box-shadow: 0 18px 34px -20px rgba(47, 58, 51, 0.45), 0 1px 0 rgba(47, 58, 51, 0.08);
  /* translateY is a percentage OF THE CARD, and the card is 30% of the stage —
     so a full pull lifts it exactly its own height, clearing the mouth. */
  transform: translateY(calc(var(--zf-p, 0) * -100%));
}

/* The engraved frame. INSET FROM THE PAPER'S OWN EDGE, and a separate element
   rather than a border, because the card's edge is paper and this is ink on
   it — two different things at two different colours, one inside the other. */
.zerf-scope .zf-card-frame {
  position: absolute;
  inset: 7px;
  border: 1px solid var(--zf-line-soft);
  border-radius: 1px;
  pointer-events: none;
}

.zerf-scope .zf-card-rule {
  inline-size: 34px;
  block-size: 1px;
  background: var(--zf-brass);
  opacity: 0.7;
}

.zerf-scope .zf-card-names {
  margin: 0;
  font-size: clamp(1.3rem, 6vw, 1.75rem);
  font-weight: 300;
  line-height: 1.45;
  color: var(--zf-ink);
  text-align: center;
}

.zerf-scope .zf-card-sprig {
  inline-size: 38%;
  block-size: auto;
  max-inline-size: 100%;
  mix-blend-mode: multiply;
}

/* The crack, drawn on the photographed seal. Again `left`/`top`, for the same
   reason as the card. */
.zerf-scope .zf-crack {
  position: absolute;
  left: 49%;
  top: 65.5%;
  inline-size: 13.5%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  overflow: visible;
  pointer-events: none;
}

/* pathLength="1" makes each path one unit long whatever its real geometry, so
   the dash needs nothing measured at runtime. The offset is driven straight
   off the drag rather than by a transition, so the crack tracks the thumb —
   including backwards, when the card is let go and slides home. */
.zerf-scope .zf-crack path {
  fill: none;
  stroke: #2a2f26;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-dasharray: 1;
  opacity: 0.88;
}

.zerf-scope .zf-crack-main {
  stroke-dashoffset: clamp(0, calc((0.46 - var(--zf-p, 0)) / 0.36), 1);
}

/* The fork runs later than its parent: a branch that appears at the same
   instant is a drawing, one that appears after it is a break. */
.zerf-scope .zf-crack-fork {
  stroke-width: 2;
  stroke-dashoffset: clamp(0, calc((0.66 - var(--zf-p, 0)) / 0.3), 1);
}

/* The copy sits in the empty upper cloth and FADES AS THE CARD RISES INTO IT.
   The instruction stops being needed at exactly the moment the card needs the
   room, so the two never fight for the same pixels. */
.zerf-scope .zf-gate-copy {
  position: absolute;
  inset-inline: 0;
  /* CLAMPED INTO THE VIEWPORT, and this fixes a bug the redesign inherited.
     The copy is positioned against the STAGE, because it has to fade against
     `--zf-p`, which the drag writes onto the stage — but the stage is sized to
     COVER, so on a wide, short viewport it is far taller than the screen. At
     1280x900 it is 2276px tall and 12% of it is 415px ABOVE the top of the
     glass: the desktop cover rendered with no kicker, no instruction and no
     arrow on it at all, and had done since the template shipped.

     The stage is centred on the viewport, so `50% - 50svh` is the stage-
     relative coordinate of the top of the screen. Taking the larger of the two
     leaves the phone case at exactly the 12% it always used and pins every
     wider one just under the top edge. */
  top: max(12%, calc(50% - 50svh + 3.5rem));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
  padding: 0 1.5rem;
  text-align: center;
  opacity: clamp(0, calc(1 - var(--zf-p, 0) * 1.9), 1);
}

.zerf-scope .zf-gate-kicker {
  margin: 0;
  font-size: var(--zf-t-label);
  letter-spacing: normal;
  color: var(--zf-muted);
}

/* THE INSTRUCTION IS THE BIGGEST THING ON THE COVER, which it was not: it was
   set at 1.02rem, the same size as body text seven screens further down, under
   a kicker of very nearly the same weight. A cover asks the guest to do one
   thing and that sentence should look like the thing being asked. */
.zerf-scope .zf-gate-pull {
  margin: 0;
  max-inline-size: 17rem;
  font-size: 1.2rem;
  line-height: 1.75;
  color: var(--zf-ink);
}

/* TWO chevrons in file, drawn in CSS. One is an arrow and points at a
   destination; a pair travelling together reads as a direction to keep going
   in, which is what a drag with a commit threshold is actually asking for. The
   second is this element's ::after, offset along the same diagonal. */
.zerf-scope .zf-gate-arrow {
  position: relative;
  inline-size: 15px;
  block-size: 15px;
  margin-block-start: 0.2rem;
  border-inline-start: 1.4px solid var(--zf-sage);
  border-block-start: 1.4px solid var(--zf-sage);
  transform: rotate(45deg);
  animation: zf-bob 2.1s ease-in-out infinite;
}

.zerf-scope .zf-gate-arrow::after {
  content: "";
  position: absolute;
  /* Down the diagonal, which in the rotated frame is +x +y. */
  inset-inline-start: 7px;
  top: 7px;
  inline-size: 15px;
  block-size: 15px;
  border-inline-start: 1.4px solid var(--zf-sage);
  border-block-start: 1.4px solid var(--zf-sage);
  opacity: 0.45;
}

@keyframes zf-bob {
  0%, 100% { transform: rotate(45deg) translate(0, 0); opacity: 0.5; }
  50% { transform: rotate(45deg) translate(4px, 4px); opacity: 1; }
}

/* Let go too early: the card slides home and the instruction says so once. */
.zerf-scope .zf-gate.is-nudged .zf-gate-pull { color: var(--zf-sage-deep); }

.zerf-scope .zf-gate--leaving {
  opacity: 0;
  pointer-events: none;
  transition: opacity 520ms ease-out;
}

.zerf-scope .zf-gate-skip {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  bottom: calc(1.6rem + env(safe-area-inset-bottom));
  inline-size: fit-content;
  padding: 0.55rem 1.3rem;
  border: 1px solid var(--zf-line);
  border-radius: 999px;
  background: rgba(255, 253, 248, 0.86);
  color: var(--zf-ink-soft);
  font: inherit;
  font-size: 0.86rem;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 700ms ease-out;
}

.zerf-scope .zf-gate-skip.is-shown { opacity: 1; pointer-events: auto; }

/* Visually hidden until focused — the keyboard's only way in, since a drag has
   no keyboard equivalent. */
.zerf-scope .zf-gate-a11y {
  position: absolute;
  inset-inline-start: -9999px;
  top: 0;
  padding: 0.6rem 1rem;
  border: 0;
  background: var(--zf-sage);
  color: var(--zf-paper);
  font: inherit;
  cursor: pointer;
}

.zerf-scope .zf-gate-a11y:focus { inset-inline-start: 0.75rem; top: 0.75rem; z-index: 5; }

/* ── the hero ─────────────────────────────────────────────────────────────
   THE COPY MOVED OFF THE CENTRE. It was vertically centred, which put the
   couple's names directly over the envelope and the wax seal — the busiest
   part of the only photograph in the design. The top half of that frame is
   bare cloth; the names go there, and the envelope stays visible under them. */

.zerf-scope .zf-hero {
  position: relative;
  /* Above the fixed cloth and the petals, both of which are z-index 0. Every
     lifted or printed thing on this page is z-index 1 and every ambient one is
     0; there is no third value in this file. */
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100svh;
  padding: 15svh 1.5rem 5rem;
  text-align: center;
  overflow: hidden;
}

.zerf-scope .zf-hero-scene { position: absolute; inset: 0; z-index: 0; }

.zerf-scope .zf-hero-clip {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  max-inline-size: none;
}

/* TWO FEATHERS, NOT ONE WASH. The old scrim ran 86% linen → 50% → 88% over the
   WHOLE frame, which is a veil across the only moving photograph in the design:
   it was showing at roughly half strength everywhere, including the middle,
   where nothing was being read. These cover the two bands the type actually
   sits in and leave the picture alone between them.

   AND THEY LIGHTEN. On darga and hena the picture is dark and the type is pale,
   so their scrims push the picture back with black; here the picture is bright
   linen and the type is ink, so the wash lifts the cloth AWAY from the text
   with white. Copying a dark template's scrim onto a light one is how a light
   design ends up muddy. */
.zerf-scope .zf-hero-veil { position: absolute; inset-inline: 0; }

.zerf-scope .zf-hero-veil--top {
  top: 0;
  block-size: 58%;
  background: linear-gradient(
    180deg,
    rgba(246, 242, 234, 0.94) 0%,
    rgba(246, 242, 234, 0.78) 38%,
    rgba(246, 242, 234, 0) 100%
  );
}

.zerf-scope .zf-hero-veil--foot {
  bottom: 0;
  block-size: 34%;
  background: linear-gradient(0deg, rgba(246, 242, 234, 0.95) 0%, rgba(246, 242, 234, 0) 100%);
}

.zerf-scope .zf-hero-copy { position: relative; z-index: 1; }

.zerf-scope .zf-hero-kicker {
  margin: 0 0 0.9rem;
  font-size: var(--zf-t-label);
  color: var(--zf-muted);
}

.zerf-scope .zf-hero-names {
  margin: 0;
  font-size: clamp(2.1rem, 11vw, var(--zf-t-display));
  font-weight: 300;
  line-height: 1.3;
  color: var(--zf-ink);
}

.zerf-scope .zf-hero-tag {
  margin: 1.1rem auto 0;
  max-inline-size: 22rem;
  font-size: 0.95rem;
  color: var(--zf-ink-soft);
}

.zerf-scope .zf-hero-cue {
  position: absolute;
  inset-inline: 0;
  /* Clear of the flap, not on it. */
  bottom: calc(3.9rem + env(safe-area-inset-bottom));
  z-index: 1;
  font-size: 0.8rem;
  /* INK-SOFT, NOT MUTED. Muted is 3.9:1 and is a labels-only colour by the rule
     at the top of this file; every other use of it on the page is over paper or
     over a flat linen ground, but this one is over a PHOTOGRAPH — the silk in
     the lower right of the frame is nearly white and the line was sitting on
     it at something well under 3:1. The foot veil does most of the work and
     this is the rest of it. */
  color: var(--zf-ink-soft);
}

/* ── the flap ─────────────────────────────────────────────────────────────
   The envelope's own shape, and one of the three places it is allowed: the
   edge the interior comes out from under (here), the edge it goes back under
   (the closing), and the 44px rule over a heading. A card never gets one — a
   piece of paper with a V cut out of its top is a torn card, not stationery.

   `preserveAspectRatio="none"` so the V spans any viewport while staying a few
   dozen pixels deep; a V drawn to scale would be a metre tall on a desktop.
   The fold line is `vector-effect="non-scaling-stroke"` for exactly that
   reason — without it the stretch that widens the shape also thickens the
   hairline into a sage bar. */

.zerf-scope .zf-flap {
  position: absolute;
  inset-inline: 0;
  bottom: -1px; /* a hairline of overlap, so no seam shows at fractional zoom */
  z-index: 2;
  inline-size: 100%;
  block-size: clamp(30px, 8.5vw, 58px);
  display: block;
}

.zerf-scope .zf-flap-fill { fill: var(--zf-linen); }

.zerf-scope .zf-flap-line {
  fill: none;
  stroke: var(--zf-brass);
  stroke-width: 1;
  opacity: 0.5;
}

/* The closing's head is the same figure turned over: the page above, the bare
   cloth rising into it. One path, one class. */
.zerf-scope .zf-flap--up {
  bottom: auto;
  top: -1px;
  transform: scaleY(-1);
}

/* ── the ground: the cloth the suite lies on ──────────────────────────────
   `cloth.webp` was bought for this design and spent on one panel at the very
   end. It is the table now — ONE fixed layer, one decode, washed back to
   within two levels of --zf-linen so the flap's solid fill meets it invisibly.

   FIXED, NOT `background-attachment: fixed`, which is a full repaint per frame
   on iOS and is the reason that property is effectively unusable on a phone.
   The suite scrolls over a cloth that does not move, which is what a table
   does. */

.zerf-scope .zf-ground {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(246, 242, 234, 0.9), rgba(246, 242, 234, 0.9)),
    url("/media/zerf/cloth.webp");
  background-size: cover, cover;
  background-position: center, center;
}

/* NO STACKING CONTEXT HERE. `position: relative; z-index: 1` on this element is
   what the old file had, and it would trap the fixed cloth inside it — the
   layer would then paint over the hero, which sits earlier in the document.
   Everything in this file resolves against the root context: 0 is ambient, 1
   is printed. */
.zerf-scope .zf-body { position: relative; }

/* ── the reveal ─────────────────────────────────────────────────────────── */

.zerf-scope .zf-reveal {
  opacity: 0;
  transform: translateY(15px);
  transition: opacity 700ms ease-out, transform 700ms ease-out;
}

.zerf-scope .zf-reveal.is-visible { opacity: 1; transform: none; }

/* The ribbon does not fade in — it POURS in, and a run that also slid up would
   be two animations disagreeing about where its top edge is. */
.zerf-scope .zf-rib,
.zerf-scope .zf-tie { opacity: 1; transform: none; }

/* ── the ribbon ───────────────────────────────────────────────────────────
   Geometry and the argument for generating it: components/templates/zerf/
   ribbon.js. What the stylesheet owns is where the silk lies and how it
   arrives.

   Z-INDEX 0, WHICH IS THE WHOLE TRICK. Every piece of the suite is z-index 1,
   so the ribbon passes UNDER the card above it and UNDER the card below it and
   is only visible in the gap. With the negative margins that is one continuous
   length of silk threaded through four pieces of paper, and there is no
   clipping, no masking and no second copy of anything. */

.zerf-scope .zf-rib {
  position: relative;
  z-index: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Tucked under both neighbours. Against a PLATE — the gallery and the wishes,
     which have no paper under them — this lands inside the plate's own top
     padding instead, on the cloth, which reads as the ribbon simply carrying
     on. */
  margin-block: -18px;
  pointer-events: none;
}

/* A ribbon dropped on a cloth is not centred, and a centred one reads as a
   spine. It alternates, and because a run's ends are hidden under the cards the
   change of side is never visible. */
.zerf-scope .zf-rib--a { transform: translateX(-2.4rem); }
.zerf-scope .zf-rib--b { transform: translateX(2.4rem); }

.zerf-scope .zf-rib-seg {
  display: block;
  /* 68px, not 86. The run's LENGTH is this page's spacing — one segment is
     roughly 1.45 of its own width tall — so the width is the rhythm knob as
     well as the scale of the silk, and at 86 a two-segment run put 250px of
     ribbon between two cards on a phone. It also read as a leaf chain rather
     than a ribbon: past about 80px on a 390 viewport the twists are large
     enough to be looked AT instead of followed. */
  inline-size: 68px;
  block-size: auto;
  /* IT POURS IN. `clip-path` and not stroke-dashoffset: the silhouette is a
     FILL, and there is no dash on a fill to offset. Fired by ScrollReveal's
     `.is-visible`, so there is no scroll handler, no animation frame and
     nothing measured. */
  clip-path: inset(0 0 100% 0);
  transition: clip-path 720ms cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(var(--k, 0) * 200ms);
}

.zerf-scope .zf-rib.is-visible .zf-rib-seg { clip-path: inset(0 0 0 0); }

/* ONE SEGMENT IS ONE HALF-TWIST, so every other copy shows the UNDERSIDE of the
   silk and is mirrored. The two facts share a class because they share a
   cause; see the note at the top of ribbon.js for why the mirror is what makes
   a repeat join without a kink. */
.zerf-scope .zf-rib-seg.is-back { transform: scaleX(-1); }

.zerf-scope .zf-rib-silk { fill: var(--zf-silk); }
.zerf-scope .zf-rib-seg.is-back .zf-rib-silk,
.zerf-scope .zf-rib-silk.is-back,
.zerf-scope .zf-cardband .is-back .zf-rib-silk { fill: var(--zf-silk-back); }

/* Silk is not a flat colour, and a flat fill is exactly what makes a drawn
   ribbon read as a sticker. */
.zerf-scope .zf-rib-sheen {
  fill: none;
  stroke: var(--zf-paper);
  stroke-width: 1.7;
  stroke-linecap: round;
  opacity: 0.34;
}

.zerf-scope .zf-rib-seg.is-back .zf-rib-sheen,
.zerf-scope .zf-cardband .is-back .zf-rib-sheen { opacity: 0.1; }

/* The band tied across the invitation card. It BLEEDS OFF BOTH EDGES, because
   a ribbon round a card goes round the back of it — the card's `overflow:
   hidden` is what cuts it, so the two ends are hidden rather than drawn.

   IN FLOW, NOT ABSOLUTE, and that is worth stating because absolute is the
   obvious way to bleed something. The band's HEIGHT is its width over a fixed
   aspect, so it changes with the card, with the viewport and with --zf-pad —
   and every absolute version of this needed the label under it to reserve a
   matching amount of room, which meant a magic number that was right at one
   width and wrong at the next. In flow it reserves its own. */
.zerf-scope .zf-cardband {
  display: block;
  /* WIDE ENOUGH THAT THE PINCHES FALL OUTSIDE THE CARD. A run's first and last
     half-twist taper to a point, so a band sized flush to the bleed appears to
     stop short of both edges — the silk is there and is one pixel wide. Seven
     rem of overhang puts both points off the paper, where `overflow: hidden`
     eats them and the band reads as passing behind the card. */
  inline-size: calc(100% + var(--zf-pad) * 2 + 7rem);
  margin-inline: calc(var(--zf-pad) * -1 - 3.5rem);
  margin-block: 0.2rem 2.4rem;
  block-size: auto;
  pointer-events: none;
}

/* The bow. Tucked under the last card the same way a run is, and it draws
   itself from the knot outward for the same reason a run pours: it is the
   sentence ending. */
.zerf-scope .zf-tie {
  position: relative;
  z-index: 0;
  display: flex;
  justify-content: center;
  margin-block: -18px 0;
  pointer-events: none;
}

.zerf-scope .zf-tie-art {
  inline-size: 138px;
  block-size: auto;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 900ms cubic-bezier(0.22, 1, 0.36, 1) 120ms;
}

.zerf-scope .zf-tie.is-visible .zf-tie-art { clip-path: inset(0 0 0 0); }

.zerf-scope .zf-tie-knot { fill: var(--zf-sage); }

/* ── the suite ────────────────────────────────────────────────────────────
   A piece of the set. Same paper, same edge, same shadow every time — the
   elevation rule at the top of this file. What changes between them is size
   and what is printed. */

.zerf-scope .zf-piece {
  position: relative;
  z-index: 1;
  inline-size: calc(100% - var(--zf-gut) * 2);
  max-inline-size: var(--zf-card-w);
  margin-inline: auto;
  padding: 2.6rem var(--zf-pad) 2.8rem;
  border-radius: var(--zf-r);
  background: var(--zf-paper);
  box-shadow: var(--zf-lift);
  /* What cuts the ribbon band at the card's edges. */
  overflow: hidden;
}

/* The caption a real details or reply card carries at its head. THIS IS THE
   PAGE'S LABEL RANK — the old design put a caption under every heading, which
   made two rows of the same weight and read as one stalled sentence. */
.zerf-scope .zf-piece-label {
  margin: 0 0 1.6rem;
  text-align: center;
  font-size: var(--zf-t-label);
  line-height: 1.6;
  color: var(--zf-muted);
}

/* ── I. the invitation ─────────────────────────────────────────────────── */

/* The ceremonial piece, and the tallest. Its label sits directly over the
   ribbon rather than over a gap, because the two are one thing: the card is
   captioned and then it is tied. */
.zerf-scope .zf-piece--invite .zf-piece-label { margin-block-end: 1rem; }

.zerf-scope .zf-piece-mark {
  display: block;
  inline-size: 104px;
  block-size: auto;
  margin: 0 auto 1.3rem;
  color: var(--zf-sage);
}

.zerf-scope .zf-bismillah {
  margin: 0 0 1.1rem;
  text-align: center;
  font-size: 0.9rem;
  color: var(--zf-brass);
  /* Brass is 2.4:1 on paper and is ILLEGAL as body text. It is allowed here
     and only here because this line is a formula every reader already knows,
     it is one short line, and it is set beside the verse rather than instead
     of it. If it ever grows to a paragraph, change the colour. */
}

.zerf-scope .zf-verse {
  margin: 0 0 2rem;
  text-align: center;
  font-size: 1.06rem;
  line-height: 2.05;
  color: var(--zf-ink);
}

/* THE BIGGEST TYPE ON THE PAGE AFTER THE HERO, which it was not: it was set at
   most a fifth larger than the heading of the section under it. The names on an
   invitation are the invitation. */
.zerf-scope .zf-names {
  margin: 0;
  text-align: center;
  font-size: clamp(2rem, 9.5vw, 2.6rem);
  font-weight: 300;
  line-height: 1.35;
  color: var(--zf-ink);
}

.zerf-scope .zf-invite-msg {
  margin: 1.6rem auto 0;
  max-inline-size: 24rem;
  text-align: center;
  font-size: 0.98rem;
  color: var(--zf-ink-soft);
}

.zerf-scope .zf-hosts {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin: 2.2rem 0 0;
  padding: 0;
  list-style: none;
  text-align: center;
}

.zerf-scope .zf-host { display: flex; flex-direction: column; gap: 0.15rem; }
.zerf-scope .zf-host-lbl { font-size: var(--zf-t-label); color: var(--zf-muted); }
.zerf-scope .zf-host-name { font-size: 1.02rem; color: var(--zf-ink); }
.zerf-scope .zf-host-name--solo { display: block; margin: 2.2rem 0 0; text-align: center; }

/* ── inside a card ────────────────────────────────────────────────────────
   A card that carries three things is FOLDED, not cut into three. The suite's
   spacing is the ribbon; this is all that is left for the inside of a piece of
   paper, and it is deliberately much tighter than the old 5rem section rhythm —
   two parts of one card are two parts of one card. */

.zerf-scope .zf-part { position: relative; }

/* Tight against what is above it, and no crease: the date and the countdown
   are one thought. The old page said so by putting them on a shared band; a
   card cannot change its ground, so it says it with distance. */
.zerf-scope .zf-part--tight { margin-block-start: 2rem; }

/* The crease. A hairline with the light catching the fold under it, bleeding
   the card's full width — a crease that stops short of the edge is a rule. */
.zerf-scope .zf-fold {
  display: block;
  block-size: 1px;
  margin: 2.6rem calc(var(--zf-pad) * -1);
  background: var(--zf-line-soft);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85);
}

/* ── heads ────────────────────────────────────────────────────────────────
   The flap at 44px, then the title. It replaces a 26px straight hairline that
   sat under eight headings and said nothing beyond "a heading". Drawn rather
   than built from two rotated borders, because a V out of borders has a mitre
   at the point that no negative margin hides. */

.zerf-scope .zf-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 1.5rem;
  text-align: center;
}

.zerf-scope .zf-flapmark {
  display: block;
  inline-size: 44px;
  block-size: auto;
  color: var(--zf-brass);
}

.zerf-scope .zf-flapmark path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
}

.zerf-scope .zf-h2 {
  margin: 0;
  font-size: var(--zf-t-head);
  font-weight: 400;
  line-height: 1.45;
  color: var(--zf-ink);
}

.zerf-scope .zf-lede {
  margin: 0 auto 1.4rem;
  max-inline-size: 22rem;
  text-align: center;
  font-size: 0.95rem;
  color: var(--zf-ink-soft);
}

/* ── on the cloth: what is NOT stationery ────────────────────────────────
   Photographs and other people's handwriting. Keeping them off the paper is
   what stops the suite becoming a texture, and it is honest — a print and a
   note that arrived in the post are not part of the set that came out of this
   envelope. */

.zerf-scope .zf-plate {
  position: relative;
  z-index: 1;
  inline-size: calc(100% - var(--zf-gut) * 2);
  max-inline-size: var(--zf-card-w);
  margin-inline: auto;
  /* Enough top padding that the ribbon's 18px of overlap lands in empty cloth
     above the flap mark rather than through the heading. */
  padding-block: 2.6rem 1rem;
}

.zerf-scope .zf-plate--wide { max-inline-size: 38rem; }

/* ── the date, pressed into the card ─────────────────────────────────────
   A page torn out of a calendar. A 3.4rem numeral over a month name stood here
   once, which is only a number set large; the leaf draws the month so the day
   reads as its weekday and its week at a glance. Behaviour is
   components/templates/shared/CalendarLeaf.

   PRESSED, NOT LIFTED. It used to sit on a band and take the page colour to
   separate from it. On paper the direction reverses: it goes INTO the card —
   the linen ground, a hairline, and an inner shadow along the top edge, which
   is what a debossed panel on card stock actually looks like. Nothing on a
   piece of this suite is raised off it. */

.zerf-scope .zf-cal {
  max-inline-size: 20rem;
  margin-inline: auto;
  overflow: hidden;
  border: 1px solid var(--zf-line-soft);
  border-radius: var(--zf-r);
  background: var(--zf-linen);
  box-shadow: inset 0 2px 4px -2px rgba(47, 58, 51, 0.16);
}

.zerf-scope .zf-cal-top {
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0;
  padding: 0.85rem 1rem;
  background: var(--zf-sage);
  color: var(--zf-paper);
}

.zerf-scope .zf-cal-month { font-size: 1.05rem; }

.zerf-scope .zf-cal-year {
  font-size: 0.85rem;
  opacity: 0.82;
  direction: ltr;
  unicode-bidi: isolate;
}

/* Seven columns. In an RTL document the grid lays them out right to left on its
   own, so Sunday lands at the right edge — nothing here overrides direction.

   `minmax(0, 1fr)` AND NOT `1fr`. A bare `1fr` is `minmax(auto, 1fr)`, so every
   column has the widest weekday name in it as a hard floor — and on a 320px
   phone, inside a card with 1rem of padding, seven of those floors are wider
   than the card. The leaf pushed a scrollbar's worth of itself out through the
   paper's right edge. Zero as the minimum lets the row shrink; the clamp below
   is what keeps it legible while it does. */
.zerf-scope .zf-cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  padding: 0.85rem 0.65rem 0.9rem;
}

.zerf-scope .zf-cal-wd {
  font-size: clamp(0.5rem, 2.2vw, 0.6rem);
  color: var(--zf-muted);
  text-align: center;
  padding-block-end: 0.45rem;
  border-bottom: 1px solid var(--zf-line-soft);
  margin-bottom: 0.35rem;
}

.zerf-scope .zf-cal-cell {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  font-size: 0.8rem;
  color: var(--zf-ink-soft);
  direction: ltr;
  unicode-bidi: isolate;
}

/* The day itself: a filled disc. `aspect-ratio: 1` on every cell is what keeps
   it a circle rather than an oval on a narrow phone. */
.zerf-scope .zf-cal-cell.is-day {
  background: var(--zf-sage);
  color: var(--zf-paper);
  border-radius: 50%;
  font-weight: 600;
}

.zerf-scope .zf-cal-foot { padding: 0 0.95rem 1rem; text-align: center; }

/* THE CONTENT. Everything above is a picture of this line, which is why the
   grid is aria-hidden and this is a <time>. */
.zerf-scope .zf-cal-line {
  display: block;
  padding-block: 0.85rem 0.95rem;
  border-top: 1px solid var(--zf-line-soft);
  font-size: 0.96rem;
  color: var(--zf-ink);
}

.zerf-scope .zf-cal-time {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.88rem;
  color: var(--zf-sage-deep);
  direction: ltr;
  unicode-bidi: isolate;
}

/* Across the foot of the same card, so saving the date is part of reading it. */
.zerf-scope .zf-chip--full {
  display: flex;
  inline-size: 100%;
  justify-content: center;
}

/* ── countdown (shared component, zf- skin) ─────────────────────────────── */

.zerf-scope .zf-cd {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.55rem;
  max-inline-size: 20rem;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

/* Pressed, like the leaf above them and for the same reason. */
.zerf-scope .zf-cd-unit {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.9rem 0.3rem 0.8rem;
  border: 1px solid var(--zf-line-soft);
  border-radius: var(--zf-r);
  background: var(--zf-linen);
  box-shadow: inset 0 2px 4px -2px rgba(47, 58, 51, 0.16);
  overflow: hidden;
}

/* The fill fraction the shared component writes as --f. It is a quiet wash
   rising up the cell, not a progress bar — this is an ornament reporting a
   number that is already printed above it. */
.zerf-scope .zf-cd-unit::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  block-size: calc(var(--f, 0) * 100%);
  background: rgba(111, 129, 104, 0.14);
}

.zerf-scope .zf-cd-num {
  position: relative;
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1;
  color: var(--zf-ink);
  font-variant-numeric: tabular-nums;
}

.zerf-scope .zf-cd-lbl { position: relative; font-size: 0.68rem; color: var(--zf-muted); }

.zerf-scope .zf-arrived { text-align: center; font-size: 1.05rem; color: var(--zf-sage-deep); }

/* ── programme ────────────────────────────────────────────────────────────
   The day, lit by the scroll. The rail fills as the guest moves through it and
   empties on the way back; each hour's node fills as it passes the reading
   line. Behaviour is components/templates/shared/Timeline. */

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

.zerf-scope .zf-prog {
  position: relative;
  margin: 0;
  padding: 0;
  padding-inline-start: 1.4rem;
  list-style: none;
}

.zerf-scope .zf-prog::before,
.zerf-scope .zf-prog::after {
  content: "";
  position: absolute;
  inset-inline-start: 3.5px;
  inset-block: 26px;
  inline-size: 1px;
}

.zerf-scope .zf-prog::before { background: var(--zf-line-soft); }

.zerf-scope .zf-prog::after {
  background: var(--zf-sage);
  transform-origin: top;
  transform: scaleY(var(--prog-p, 0));
}

.zerf-scope .zf-prog-row {
  position: relative;
  display: grid;
  grid-template-columns: 4.6rem 1fr;
  gap: 1rem;
  align-items: baseline;
  padding: 0.95rem 0;
  border-bottom: 1px solid var(--zf-line-soft);
  transition-delay: calc(var(--i, 0) * 70ms);
}

.zerf-scope .zf-prog-row:last-child { border-bottom: 0; }

/* Unlit the node is a hollow ring on the rail; lit it fills. No glow — this is
   daylight, and a halo on linen reads as a smudge. The ring's own fill is the
   PAPER now, not the page: it sits on a card. */
.zerf-scope .zf-prog-row::before {
  content: "";
  position: absolute;
  inset-inline-start: -1.4rem;
  top: 26px;
  inline-size: 8px;
  block-size: 8px;
  margin-inline-start: -3.5px;
  border-radius: 50%;
  border: 1px solid var(--zf-line);
  background: var(--zf-paper);
  transition: background-color 420ms ease, border-color 420ms ease;
}

.zerf-scope .zf-prog-row.is-on::before {
  background: var(--zf-sage);
  border-color: var(--zf-sage);
}

.zerf-scope .zf-prog-time {
  font-size: 0.86rem;
  color: var(--zf-sage-deep);
  font-variant-numeric: tabular-nums;
  opacity: 0.55;
  transition: opacity 420ms ease;
}

.zerf-scope .zf-prog-row.is-on .zf-prog-time { opacity: 1; }

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

/* ── venue ──────────────────────────────────────────────────────────────── */

.zerf-scope .zf-venue { text-align: center; }

.zerf-scope .zf-venue-name { margin: 0; font-size: 1.5rem; font-weight: 300; color: var(--zf-ink); }
.zerf-scope .zf-venue-addr { margin: 0.4rem 0 0; font-size: 0.92rem; color: var(--zf-ink-soft); }

.zerf-scope .zf-acts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
  margin-top: 1.3rem;
}

.zerf-scope .zf-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px is the touch-target floor. Small is a VISUAL property here, not a
     hit-target one — the padding is what gives the target back. */
  min-block-size: 44px;
  padding: 0.5rem 1.2rem;
  border: 1px solid var(--zf-line);
  border-radius: 999px;
  font-size: 0.88rem;
  color: var(--zf-ink);
  text-decoration: none;
  transition: background 0.25s ease-out;
  /* A <button> may also wear this class — the add-to-calendar control is a
     button on Apple devices, because it builds the .ics locally rather than
     navigating. Without these three a button renders in the UA's own font at
     the UA's own size and does not match the links beside it. */
  font: inherit;
  cursor: pointer;
  -webkit-appearance: none;
}

.zerf-scope .zf-chip:hover { background: var(--zf-linen-deep); }

/* ── the map, behind a tap ───────────────────────────────────────────────
   A CARD with a pin drawn on it, so the section reads as a map that has not
   loaded rather than as a link that has lost its button. The iframe is not
   requested until it is pressed — see shared/VenueBlock for why that is about
   privacy as much as weight. Pressed into the paper, like everything else on a
   piece of the suite. */

.zerf-scope .zf-map {
  position: relative;
  margin-top: 1.4rem;
  border: 1px solid var(--zf-line-soft);
  border-radius: var(--zf-r);
  overflow: hidden;
  background: var(--zf-linen-deep);
  box-shadow: inset 0 2px 4px -2px rgba(47, 58, 51, 0.16);
}

.zerf-scope .zf-map-frame {
  display: block;
  inline-size: 100%;
  block-size: 15rem;
  border: 0;
}

.zerf-scope .zf-map-hit {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0.9rem;
  inline-size: 100%;
  block-size: 12rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--zf-ink-soft);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}

/* The pin, drawn in CSS: `border-radius: 50% 50% 50% 0` is three round corners
   and one square, turned 45°, so the square corner becomes the point. */
.zerf-scope .zf-map-pin {
  position: relative;
  inline-size: 22px;
  block-size: 22px;
  border-radius: 50% 50% 50% 0;
  background: var(--zf-sage);
  transform: rotate(-45deg);
}

.zerf-scope .zf-map-pin::after {
  content: "";
  position: absolute;
  inset: 6px;
  border-radius: 50%;
  background: var(--zf-linen-deep);
}

.zerf-scope .zf-map-lbl {
  padding: 0.45rem 1.15rem;
  border: 1px solid var(--zf-line);
  border-radius: 999px;
  background: var(--zf-linen);
}

/* The chip's mark. A row of centred Kurdish text with two more bits of centred
   Kurdish text in pills is not obviously two things to press; the glyph says
   "open" so the label can shorten to the destination. See shared/Chip. */
.zerf-scope .zf-chip-ico {
  display: inline-grid;
  place-items: center;
  inline-size: 1rem;
  block-size: 1rem;
  margin-inline-end: 0.45rem;
  flex: none;
  color: var(--zf-sage);
}

.zerf-scope .zf-chip-ico svg { inline-size: 100%; block-size: 100%; }

/* ── the reply card ─────────────────────────────────────────────────────── */

/* ONE LINE, AND IT IS LOAD-BEARING. The shared RSVP form paints its fields with
   `bg-surface`, which on this template IS --tpl-surface, which IS the paper the
   card is printed on — so on a card every input, every choice button and the
   success panel would be the same colour as the thing under them and only their
   hairlines would show. They take the pressed linen instead, which is what every
   other recessed thing on a piece of this suite does.

   It works because globals.css declares the theme with `@theme inline`, so
   `bg-surface` compiles to `var(--tpl-surface)` at the point of use rather than
   to a copy resolved on :root. Under a non-inline @theme this override would do
   nothing at all. */
.zerf-scope .zf-piece--reply {
  --tpl-surface: var(--zf-linen);
  --tpl-soft: var(--zf-linen-deep);
}

.zerf-scope .zf-rsvp { margin-top: 0.4rem; }

.zerf-scope .zf-notes { margin: 0; padding: 0; list-style: none; }

.zerf-scope .zf-note {
  position: relative;
  padding: 0.55rem 0;
  padding-inline-start: 1.1rem;
  font-size: 0.96rem;
  color: var(--zf-ink-soft);
}

.zerf-scope .zf-note::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 1.35em;
  inline-size: 5px;
  block-size: 5px;
  border-radius: 50%;
  background: var(--zf-brass);
}

.zerf-scope .zf-contact { text-align: center; }
.zerf-scope .zf-contact-name { font-size: 1.2rem; color: var(--zf-ink); }
.zerf-scope .zf-contact-role { margin: 0.2rem 0 0; font-size: var(--zf-t-label); color: var(--zf-muted); }

/* ── the wishes: smaller papers, off the suite ───────────────────────────
   Other people's handwriting, so they are not part of the set that came out of
   this envelope: same paper, a smaller shadow, and a sage wax dot at the head
   holding each one to the cloth. That is the photograph's seal at the one size
   where repeating it is a texture rather than a stamp. */

.zerf-scope .zf-wishes { display: grid; gap: 1.1rem; margin: 0; padding: 0; list-style: none; }

.zerf-scope .zf-wish {
  position: relative;
  padding: 1.5rem 1.2rem 1.2rem;
  border-radius: var(--zf-r);
  background: var(--zf-paper);
  box-shadow: var(--zf-lift-sm);
}

.zerf-scope .zf-wish-dot {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  top: -7px;
  inline-size: 14px;
  block-size: 14px;
  border-radius: 50%;
  background: var(--zf-sage);
  /* A drop of wax is not a flat disc — it has a rim it pooled against and a
     highlight where the press lifted. Two inset shadows, no artwork. */
  box-shadow:
    inset 0 -2px 3px -1px rgba(42, 47, 38, 0.45),
    inset 0 2px 2px -1px rgba(255, 253, 248, 0.35);
}

.zerf-scope .zf-wish-msg { margin: 0; font-size: 0.96rem; color: var(--zf-ink); }
.zerf-scope .zf-wish-name { margin: 0.6rem 0 0; font-size: var(--zf-t-label); color: var(--zf-muted); }

/* ── gallery (shared Slider, zf- skin) ────────────────────────────────────
   THE SLIDES ARE PRINTS. A photograph is not stationery, so it does not go on a
   card — but a print has a paper margin of its own, deeper at the foot, and
   that is the one thing on this page that is allowed to look like paper without
   being part of the suite. It also solves a real problem: a bare photograph on
   a linen cloth has no edge, and every one of them ended at a different
   colour. */

.zerf-scope .zf-slides {
  display: flex;
  gap: 0.7rem;
  margin: 0;
  padding: 0.4rem 0 0.6rem;
  list-style: none;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.zerf-scope .zf-slides::-webkit-scrollbar { display: none; }

/* The centring spacers are FLEX ITEMS, not padding on the track. With
   border-box sizing the track's padding comes off the width before a slide's
   percentage basis resolves against it, so 11% padding and a 78% slide gave a
   213px slide in a 350px track. As items they share one basis: 78+11+11 = 100. */
.zerf-scope .zf-slides::before,
.zerf-scope .zf-slides::after {
  content: "";
  flex: 0 0 11%;
}

.zerf-scope .zf-slide {
  flex: 0 0 78%;
  scroll-snap-align: center;
  opacity: 0;
  transform: translateY(12px) scale(0.985);
  transition: opacity 0.6s ease-out, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--i, 0) * 70ms + 150ms);
}

.zerf-scope .zf-reveal.is-visible .zf-slide { opacity: 1; transform: none; }

.zerf-scope .zf-slide-btn {
  display: block;
  inline-size: 100%;
  /* The print's margin. Deeper at the foot, the way one is trimmed. */
  padding: 9px 9px 26px;
  border: 0;
  border-radius: 2px;
  background: var(--zf-paper);
  box-shadow: var(--zf-lift);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transform: scale(0.92);
  opacity: 0.62;
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.45s ease-out;
}

.zerf-scope .zf-slide.is-active .zf-slide-btn { transform: scale(1); opacity: 1; }

.zerf-scope .zf-slide img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  background: var(--zf-linen-deep);
}

.zerf-scope .zf-dots { display: flex; justify-content: center; gap: 0.45rem; margin-top: 1.2rem; }

.zerf-scope .zf-dot {
  width: 6px;
  height: 6px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(111, 129, 104, 0.3);
  cursor: pointer;
  transition: background 0.3s ease-out, width 0.3s ease-out;
}

.zerf-scope .zf-dot.is-on { width: 18px; border-radius: 999px; background: var(--zf-sage); }

.zerf-scope .zf-gallery-hint {
  margin: 0.9rem 0 0;
  text-align: center;
  font-size: 0.8rem;
  color: var(--zf-muted);
}

/* The viewer. Portalled to .zerf-scope so these custom properties resolve —
   parented to <body> every one would fall back and it would render untinted.
   .zerf-scope must therefore never take a z-index of its own; see the note on
   .zf-body. */
.zerf-scope .zf-lb {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
}

.zerf-scope .zf-lb-veil {
  position: absolute;
  inset: 0;
  border: 0;
  padding: 0;
  /* Dark even on a light template: a photograph viewed full-screen wants the
     room lights off, and a pale veil would leave the page showing through. */
  background: rgba(28, 33, 28, 0.95);
  cursor: zoom-out;
}

.zerf-scope .zf-lb-bar {
  position: absolute;
  inset-inline: 0;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: calc(0.9rem + env(safe-area-inset-top)) 1rem 0.9rem;
}

/* SIZE THE BOX, AND SIZE THE GLYPH INSIDE IT SEPARATELY. An SVG with no
   dimensions takes its aspect from its viewBox and stretches its parent —
   hena's arrows measured 48x81, a tall lozenge rather than a button. */
.zerf-scope .zf-lb-x {
  display: grid;
  place-items: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  padding: 0;
  border: 1px solid rgba(211, 187, 139, 0.35);
  border-radius: 999px;
  background: rgba(47, 58, 51, 0.6);
  color: var(--zf-brass-soft);
  font: inherit;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

/* `1 / 4` renders as `4 / 1` in an RTL page — bidi reorders the numbers around
   the slash, so the DOM text is right and the screen is wrong. Only a
   screenshot catches it, which is why the count is forced LTR. */
.zerf-scope .zf-lb-count {
  direction: ltr;
  font-size: 0.85rem;
  color: rgba(255, 253, 248, 0.75);
  font-variant-numeric: tabular-nums;
}

.zerf-scope .zf-lb-img {
  position: relative;
  z-index: 1;
  max-inline-size: 92vw;
  max-block-size: 78svh;
  object-fit: contain;
  border-radius: var(--zf-r);
}

.zerf-scope .zf-lb-nav {
  position: absolute;
  inset-inline: 0;
  bottom: calc(1.4rem + env(safe-area-inset-bottom));
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: 1.4rem;
}

.zerf-scope .zf-lb-arrow {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  padding: 0;
  border: 1px solid rgba(211, 187, 139, 0.35);
  border-radius: 999px;
  background: rgba(47, 58, 51, 0.6);
  color: var(--zf-brass-soft);
  cursor: pointer;
}

.zerf-scope .zf-lb-arrow svg { inline-size: 18px; block-size: 18px; }

/* ── the close, back under the flap ──────────────────────────────────────
   The SAME cloth as the page's ground with the wash lifted — so the guest ends
   looking at the bare linen the envelope was lying on in the very first frame,
   and the page bookends the gate they came in through. It is a change of
   exposure on one photograph, not a second picture. */

.zerf-scope .zf-closing {
  position: relative;
  z-index: 1;
  overflow: hidden;
  margin-block-start: 4.5rem;
  padding: 6rem 1.5rem calc(5.5rem + env(safe-area-inset-bottom));
  text-align: center;
  background-image:
    linear-gradient(180deg, rgba(246, 242, 234, 0.5), rgba(239, 234, 222, 0.66)),
    url("/media/zerf/cloth.webp");
  background-size: cover, cover;
  background-position: center, center;
}

.zerf-scope .zf-closing-msg {
  position: relative;
  margin: 0 auto;
  max-inline-size: 24rem;
  font-size: 1.18rem;
  line-height: 2;
  color: var(--zf-ink);
}

.zerf-scope .zf-hashtag {
  position: relative;
  margin: 1.5rem 0 0;
  font-size: 0.9rem;
  color: var(--zf-sage-deep);
}

/* ── narrower than a phone ──────────────────────────────────────────────
   320 is a real device and it is the width every fixed-width thing on a card
   fails at first. The suite loses padding rather than width: a card that has
   shrunk still reads as a card, and one whose margins have gone reads as a
   panel. */

@media (max-width: 22.5rem) {
  .zerf-scope {
    --zf-pad: 1.05rem;
    --zf-gut: 0.75rem;
    --zf-t-head: 1.4rem;
  }

  .zerf-scope .zf-piece { padding-block: 2.1rem 2.3rem; }
  .zerf-scope .zf-prog-row { grid-template-columns: 4rem 1fr; gap: 0.7rem; }
}

/* ── wider than a phone ─────────────────────────────────────────────────
   The suite gets air rather than more width. A wedding invitation is a
   portrait object and a 30rem card on a 1200px screen is correct; what is
   wrong on a desktop is a page of 30rem cards with 18px of ribbon between
   them, because the gaps stop reading as gaps. */

@media (min-width: 40rem) {
  .zerf-scope {
    --zf-pad: 2.4rem;
    --zf-gut: 2rem;
  }

  .zerf-scope .zf-piece { padding-block: 3.4rem 3.6rem; }
  .zerf-scope .zf-rib-seg { inline-size: 88px; }
  .zerf-scope .zf-rib { margin-block: -22px; }
  .zerf-scope .zf-tie { margin-block-start: -22px; }
  .zerf-scope .zf-tie-art { inline-size: 168px; }
  .zerf-scope .zf-rib--a { transform: translateX(-6rem); }
  .zerf-scope .zf-rib--b { transform: translateX(6rem); }
  .zerf-scope .zf-plate { padding-block: 3.4rem 1.4rem; }
  .zerf-scope .zf-wishes { grid-template-columns: 1fr 1fr; }
}

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

@media (prefers-reduced-motion: reduce) {
  .zerf-scope .zf-reveal { opacity: 1; transform: none; transition: none; }

  /* BY NAME, and the ribbon is the reason this matters. Suppressing only the
     transition leaves `clip-path: inset(0 0 100% 0)` in place, which is silk
     that is invisible forever rather than silk that does not pour. Exactly the
     trap hena's stroke-dashoffset ornaments have, wearing a different property.

     The `--a`/`--b` offsets are NOT removed: they are the composition, not the
     motion, and the ribbon has to still lie where it lies. */
  .zerf-scope .zf-rib-seg,
  .zerf-scope .zf-tie-art { clip-path: none; transition: none; }

  .zerf-scope .zf-gate-arrow { animation: none; opacity: 0.9; }

  /* The ambient layer goes entirely. Not paused — REMOVED: a fixed layer of
     twelve infinite animations is exactly what `reduce` exists for, and a
     hidden infinite animation is not throttled just because you cannot see it.
     The component renders the same markup either way, so this rule is the only
     switch and it must not be dropped. */
  .zerf-scope .zf-petals { display: none; }

  .zerf-scope .zf-prog-row,
  .zerf-scope .zf-prog-row::before,
  .zerf-scope .zf-prog-time { transition: none; }

  /* The gate collapses to one tap (see ZerfGate), so the card is already out
     and the seal already broken the moment it is touched. */
  .zerf-scope .zf-card { transition: none; }
  .zerf-scope .zf-slide,
  .zerf-scope .zf-slide-btn { transition: none; }
}

/* ── the ambient layer: an olive leaf, a blossom, a speck of brass ───────
   ALL THREE ARE IN THE PHOTOGRAPH. They were a leaf and two anonymous dots,
   which is weather in general rather than this table's weather: the frame the
   whole design is built on has an olive sprig and two small white blossoms
   lying on the cloth beside the envelope, and these are those, falling.

   FIXED, NOT ABSOLUTE, which is what makes the count mean anything: scattered
   down an eight-screen page these distribute evenly, so the number you can SEE
   would be total ÷ 8. Darga measured that at thirteen leaves and two on screen.
   Fixed, the twelve ARE the twelve you are looking at.

   Behaviour is components/templates/shared/Motes. */

.zerf-scope .zf-petals {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity 900ms ease-out;
}

.zerf-scope .zf-petals.is-live { opacity: 1; }

.zerf-scope .zf-petal {
  position: absolute;
  top: -8vh;
  inset-inline-start: var(--x);
  inline-size: var(--s);
  block-size: var(--s);
  /* The leaf shape: three round corners and one square, which is the almond a
     petal actually is. Turned by the animation rather than here, so the corner
     that is square lands somewhere different on every element. */
  border-radius: 50% 0 50% 0;
  /* A GRADIENT, NOT A FLAT FILL, and on this page that is the difference
     between weather and litter. A solid mark on linen has an edge, and an edge
     at 6px reads as a crumb on the screen however low the opacity goes; fading
     to transparent at the rim leaves something that is only ever half-seen,
     which is what these are supposed to be. */
  background: radial-gradient(circle at 40% 40%, var(--zf-sage) 0%, rgba(111, 129, 104, 0) 72%);
  animation: zf-fall var(--d) linear var(--delay) infinite;
  will-change: transform, opacity;
}

/* The blossom. A white mark on linen at a peak of 0.16 is nothing at all, so
   it is given a brass rim to sit against — which is also what one of those
   flowers looks like: pale petals round a warm centre, seen small. */
.zerf-scope .zf-petal.is-v1 {
  border-radius: 50%;
  background: radial-gradient(
    circle at 42% 38%,
    rgba(255, 253, 248, 0.95) 0%,
    rgba(255, 253, 248, 0.7) 34%,
    rgba(211, 187, 139, 0.6) 58%,
    rgba(211, 187, 139, 0) 80%
  );
}

/* And a speck of brass, half the height of a leaf. Three shapes over twelve
   elements, so each appears exactly four times — past about one shape in six
   the eye starts RECOGNISING the same petal falling again, and recognising it
   is what breaks the illusion, not the count. */
.zerf-scope .zf-petal.is-v2 {
  border-radius: 50% 0 50% 0;
  background: radial-gradient(circle at 40% 40%, var(--zf-brass) 0%, rgba(185, 154, 91, 0) 72%);
  block-size: calc(var(--s) * 0.62);
}

.zerf-scope .zf-petals:not(.is-live) .zf-petal { animation-play-state: paused; }

/* DOWN, not up. Hena's motes rise because they are embers off a candle; these
   are petals, and a petal that goes up is a page with the wrong physics. */
@keyframes zf-fall {
  0% { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 0; }
  12% { opacity: var(--peak); }
  85% { opacity: var(--peak); }
  100% { transform: translate3d(var(--drift), 116vh, 0) rotate(260deg); opacity: 0; }
}
