/* ═══════════════════════════════════════════════════════════════════════════
   گوڵباران — gulbaran. A closed ivory rose on cream paper; one tap and it
   blooms, and the petals keep falling down the page.

   ORIGINAL WORK for Ahang, 2026-08-25. Every rule here was written for this
   design. Nothing was taken from any other party's stylesheet — see ASSETS.md
   §6 and the header of GulbaranTemplate.js.

   NEVER `import` this file. Next emits a <link> for every stylesheet reachable
   in the route's module graph, and the registry reaches every template, so an
   import loads all five sheets on every invitation. <TemplateStyles scope="gulbaran" />
   writes the link by hand from the component that knows its own scope.

   RTL, always. Logical properties throughout. The one trap worth restating:
   `inset-inline-start: 50%` + `translateX(-50%)` resolves to `right: 50%` on an
   RTL page while the translate still moves LEFT — a full width off centre. Use
   `inset-inline: 0; margin-inline: auto`, which has no opinion about direction.
   ═══════════════════════════════════════════════════════════════════════════ */

.gulbaran-scope {
  /* ── the palette ────────────────────────────────────────────────────────
     Every value here has a twin in TPL_VARS (GulbaranTemplate.js) and a third
     copy in lib/templates/themes.js that the admin picker reads. Change one,
     change all three — darga's two copies drifted for two days and its gallery
     swatch advertised a palette the template had already left. */

  /* MEASURED, not chosen. The two stills are printed on paper sampling
     #ddd1b9 / #d7ceb8, and the hero photograph ends part-way down the first
     screen — against a near-white page it would read as a darker rectangle with
     a seam across it. This is the same family a few percent lighter, and
     .gb-open-fade dissolves the image's bottom edge into exactly this value. */
  --gb-paper: #efe6d3;
  --gb-card: #faf4e6; /* only the marked day's ink now — nothing is lifted */
  --gb-soft: #e4d8c0;

  --gb-ink: #2b2029; /* 12.62:1 on the page */
  --gb-muted: #726069; /* 4.71:1 on the page, 5.33:1 on the card */

  --gb-plum: #6b3a4e; /* 7.23:1 */
  --gb-plum-deep: #4e2839;
  --gb-gold: #c2a06a; /* hairlines and ornament */
  --gb-gold-light: #e0c894; /* the LIGHT end — see AttendCard's "two golds" */
  --gb-rule: rgba(194, 160, 106, 0.42);

  /* The map plate's pin, drawn as a mask so it takes the palette. Kept as a
     token so the shape is declared once beside the colours it is drawn in. */
  --gb-pin: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.6' d='M12 21.5c4.2-5.1 6.6-8.5 6.6-11.5A6.6 6.6 0 0 0 5.4 10c0 3 2.4 6.4 6.6 11.5Z'/%3E%3Ccircle cx='12' cy='9.8' r='2.4' fill='none' stroke='%23000' stroke-width='1.6'/%3E%3C/svg%3E");

  /* Type scale. One place, so a heading and its ornament cannot drift. */
  --gb-t-label: 0.78rem;
  --gb-t-body: 1rem;
  --gb-t-names: clamp(2rem, 9vw, 3.1rem);

  color: var(--gb-ink);
  background: var(--gb-paper);
}

/* ═══════════════════════════════════════════════════════════════════════════
   1. THE COVER
   ═══════════════════════════════════════════════════════════════════════════ */

/* THE WHOLE COVER IS THE BUTTON. One element, so Enter and Space work natively
   and the pointer path cannot disagree with the keyboard path. */
.gulbaran-scope .gb-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(--gb-paper);
  font: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  transition: opacity 900ms ease;
}

.gulbaran-scope .gb-gate--leaving {
  opacity: 0;
  /* An invisible element still eats every tap. darga's opening <video> sat at
     opacity 0 over its gate button and the door never opened; perde's scratch
     canvas repeated it. Anything that fades out here must stop taking pointers
     on the same frame. */
  pointer-events: none;
}

.gulbaran-scope .gb-gate:focus-visible {
  outline: 2px solid var(--gb-plum);
  outline-offset: -6px;
}

/* THE STAGE IS FIXED-ASPECT so the still and the clip that plays over it
   register exactly: they are the same scene 4.33 seconds 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. A
   physical left/translate has no such opinion. */
.gulbaran-scope .gb-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));
}

.gulbaran-scope .gb-stage-img,
.gulbaran-scope .gb-stage-clip {
  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 clip is invisible until the tap, then it simply plays on top. It does not
   cross-fade in: the still IS its frame 0 (extracted with ffmpeg, not generated
   beside it), so there is nothing to dissolve between. */
.gulbaran-scope .gb-stage-clip {
  opacity: 0;
}

.gulbaran-scope .gb-gate--blooming .gb-stage-clip,
.gulbaran-scope .gb-gate--leaving .gb-stage-clip {
  opacity: 1;
}

/* ── the copy, on the measured quiet band ────────────────────────────────
   NO SCRIM, and that is the difference from perde. A 6x6 variance map over
   bud.webp reads 1.1–1.6 out of 255 across y 17–50% × x 17–83% — flat paper and
   nothing else — so the words sit on the stock directly. perde's cover is a
   closed curtain whose fold striping runs edge to edge, which is why its gate
   copy has to carry a radial scrim; painting one here would be a grey cloud
   over clean paper.

   `top: 26%` puts the block in the middle of that band, and .gb-open-copy below
   uses the same offset so the names do not move when the cover lifts. */
.gulbaran-scope .gb-gate-copy {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  top: 26%;
  inline-size: fit-content;
  max-inline-size: min(22rem, 78%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  text-align: center;
}

.gulbaran-scope .gb-gate-hi {
  margin: 0;
  font-size: var(--gb-t-label);
  letter-spacing: 0.04em;
  color: var(--gb-muted);
}

.gulbaran-scope .gb-gate-names,
.gulbaran-scope .gb-open-names {
  margin: 0;
  font-size: var(--gb-t-names);
  line-height: 1.25;
  font-weight: 400;
  color: var(--gb-plum-deep);
}

.gulbaran-scope .gb-gate-orn {
  display: block;
  inline-size: 116px;
  color: var(--gb-gold);
}

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

.gulbaran-scope .gb-gate-tap {
  font-size: var(--gb-t-label);
  letter-spacing: 0.05em;
  color: var(--gb-plum);
}

/* 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. */
.gulbaran-scope .gb-gate-ring {
  inline-size: 46px;
  block-size: 46px;
  border-radius: 50%;
  border: 1px solid var(--gb-rule);
  animation: gb-pulse 2.4s ease-in-out infinite;
}

@keyframes gb-pulse {
  0%,
  100% {
    transform: scale(0.86);
    opacity: 0.35;
  }
  50% {
    transform: scale(1.06);
    opacity: 0.9;
  }
}

/* Once the rose is opening, the prompt has done its job and the ring competing
   with the bloom is noise. The names stay — the flower opens beneath them. */
.gulbaran-scope .gb-gate--blooming .gb-gate-ring,
.gulbaran-scope .gb-gate--blooming .gb-gate-tap,
.gulbaran-scope .gb-gate--leaving .gb-gate-ring,
.gulbaran-scope .gb-gate--leaving .gb-gate-tap {
  opacity: 0;
  transition: opacity 500ms ease;
}

/* The interior is inert until the cover goes, so a stray tap through it cannot
   land and a screen reader does not read the page behind the cover. */
.gulbaran-scope .gb-page {
  visibility: hidden;
}

.gulbaran-scope .gb-page.is-open {
  visibility: visible;
}

/* ═══════════════════════════════════════════════════════════════════════════
   2. THE PAGE, AND ITS GROUND
   ═══════════════════════════════════════════════════════════════════════════ */

/* The paper is a TILE, like darga's. Measured before trusting it: the left and
   right edges differ by 3.08/255 and the top and bottom by 4.85, against the
   texture's own stdev of 3.3-4.5 — so the join is the same magnitude as the
   grain and there is no coherent line to see. Verified by tiling it 3x2. */
.gulbaran-scope .gb-body {
  position: relative;
  z-index: 1;
  background-color: var(--gb-paper);
  background-image: url("/templates/gulbaran/paper.webp");
  background-size: 380px auto;
  background-repeat: repeat;
}

/* ── THE AMBIENT GROUND IS GONE, AND IT IS A MEASUREMENT, NOT A TASTE CALL ──

   `.gb-drift` / `.gb-drift-vid` used to stand here, and GulbaranTemplate no
   longer renders either. The full account is in that file; the numbers:

     * The layer painted NOTHING for the whole life of this template — fixed at
       z-index 0 under a `.gb-body` at z-index 1 with an opaque background.
       Red-block pixel count: 0.00% at scrollY 900, 2600 and 4200.
     * Fixing the paint order made it visible, and that is when the real problem
       appeared: THE CLIP'S OWN PAPER IS DARKER THAN THIS TEMPLATE'S. Over its
       ten frames, 99.9% of its pixels are darker than #efe6d3 on all three
       channels and 0.0% are lighter.
     * At the 0.14 it was tuned to, 95.30% of the frame drags `--gb-muted` below
       the 4.5:1 AA floor. That token is documented at 4.71:1 on flat paper —
       0.21 of headroom — and the composite costs it 1.05.
     * The largest normal-blend alpha that holds 4.5:1 is 0.027, where the layer
       is invisible again. `mix-blend-mode: lighten`, which cannot darken
       anything and so could not have cost a single ratio, covers 0.1% of the
       frame. Both are 452KB for nothing, which is where this started.

   DO NOT RE-ADD THIS AS A CSS CHANGE. If the petals are wanted, REGRADE
   `drift.mp4`: lift its ground to sit at or above #efe6d3 and compress its
   shadows so the darkest pixel stays within a point or two of it, and then a
   normal blend at a real opacity composites as petals over cream with every
   ratio in this file intact. Re-tuning the opacity cannot get there — the
   arithmetic above is why.

   The 452KB it stops fetching is the other half of it: this audience is phones
   in Iraq, and the clip was never the subject. */

/* ── the first screen: the open rose, and the names that never moved ────── */

.gulbaran-scope .gb-open {
  position: relative;
  z-index: 1;
  min-block-size: 100svh;
  overflow: hidden;
}

/* Same fixed-aspect stage as the cover, cropped identically — so when the cover
   fades, the scene underneath is already the exact frame the video stopped on.
   The video does not end and hand over to a different page; the page IS where
   the video left off. */
.gulbaran-scope .gb-open-scene {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.gulbaran-scope .gb-open-img {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  aspect-ratio: 720 / 1280;
  inline-size: max(100vw, calc(100svh * 720 / 1280));
  max-inline-size: none;
  block-size: auto;
  object-fit: cover;
}

/* The photograph's bottom edge dissolved into the page ground. Both are the
   same cream family by construction — the token is sampled from the paper in
   these very files — but without it the image ends on a hard horizontal line. */
.gulbaran-scope .gb-open-fade {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: 22%;
  background: linear-gradient(to bottom, rgba(239, 230, 211, 0) 0%, var(--gb-paper) 92%);
}

/* THE SAME OFFSET THE COVER USED. Both frames were generated with a quiet band
   at this height (1.1-1.7 variance on each), so the type is the one thing on
   screen that holds still while the flower opens underneath it. */
.gulbaran-scope .gb-open-copy {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  top: 26%;
  inline-size: fit-content;
  max-inline-size: min(22rem, 78%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  text-align: center;
}

.gulbaran-scope .gb-open-kicker {
  margin: 0;
  font-size: var(--gb-t-label);
  letter-spacing: 0.16em;
  color: var(--gb-muted);
}

.gulbaran-scope .gb-open-tag {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.7;
  color: var(--gb-muted);
}

/* ═══════════════════════════════════════════════════════════════════════════
   3. SECTIONS — thin, and never a shell

   Darga's rule, and two rejected cuts of this template are the evidence for it:
   *the moment this had a shell every section started looking like every other
   one.* A hairline, a heading, and whatever the section itself draws. No frame,
   no background, no decoration here.
   ═══════════════════════════════════════════════════════════════════════════ */

/* AIR IS THE OTHER HALF OF THE RHYTHM. 2.8rem between sections is close enough
   that the end of one and the head of the next are read together; the reference
   leaves roughly a third of a phone screen. Raised to 3.6rem, which at 390px is
   58px of nothing above and below every heading. */
.gulbaran-scope .gb-sec {
  position: relative;
  z-index: 1;
  padding-block: 3.6rem;
  padding-inline: 1.5rem;
}

.gulbaran-scope .gb-inner {
  inline-size: 100%;
  max-inline-size: 28rem;
  margin-inline: auto;
}

.gulbaran-scope .gb-inner--wide {
  max-inline-size: 42rem;
}

/* THE ONE DARK BAND — the countdown, and nothing else. With the closing panel
   at the foot it is the page's whole spine: a run of cream with a single dark
   band across the middle of it, which is what stops eleven cream sections
   reading as one undifferentiated scroll. Give a second section this and the
   band stops being an event.

   It REDEFINES THE TOKENS rather than restating colours on every child, so
   nothing inside a dark section has to know it is in one.

   ── THE EDGES ARE FEATHERED, AND THE BAND STAYS ─────────────────────────

   Cut edge to edge, this was the sharpest object on the page: two dead-straight
   1px boundaries where #efe6d3 meets #3b1f2c, which is a 12:1 step across a
   single pixel row. That is a literal reading of "sharp", and the obvious fix —
   delete the band — is the wrong one, because the band is doing real work: it
   is the only thing stopping eleven cream sections reading as one scroll.

   So the FILL keeps its full weight in the middle and only the two boundaries
   go. A mask fading the first and last 12% to nothing dissolves the plum into
   the paper instead of butting against it, and the section still lands as an
   event. `-webkit-mask` alongside: older iOS Safari is a real share of the
   guest list and it does not take the unprefixed property. */
.gulbaran-scope .gb-sec--dark {
  --gb-ink: #f6ecdc;
  --gb-muted: #cbb2b8;
  --gb-plum-deep: #f3e3c9;
  --gb-rule: rgba(224, 200, 148, 0.4);
  color: var(--gb-ink);
  padding-block: 4rem;
}

/* The plum itself, as its own layer, so the mask that softens its edges cannot
   touch the numerals sitting on it. */
.gulbaran-scope .gb-sec--dark::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: #3b1f2c;
  background-image: url("/templates/gulbaran/band.webp");
  background-size: cover;
  background-position: center;
  -webkit-mask: linear-gradient(
    to bottom,
    transparent 0,
    #000 12%,
    #000 88%,
    transparent 100%
  );
  mask: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
}

/* ── THE HEAD: A HEADING, THEN A BROKEN RULE ─────────────────────────────
   This was a 46px hairline ABOVE a 1.32rem heading, and the pair was the whole
   reason the page had no rhythm. Measured against the reference the founder
   pointed at: its section titles are 30px in a light display face — optically
   nearer 46px, because that face has a very tall cap height — with a subtitle
   under them and then a rule broken around a small mark. Every title on this
   page was 21.12px, which is barely above the 16px body. Nothing announced
   itself, so everything arrived at once, and a page where everything arrives
   at once reads as BUSY. That is most of what "sharp" was.

   Three changes, and the order matters as much as the size:

     THE TITLE IS FIRST     a rule above a heading is a separator between the
                            section before and this one; a rule below it is
                            part of the heading. The second is what the eye
                            reads as hierarchy.
     THE TITLE IS BIGGER    1.32rem -> 1.78rem, a 1.35x jump over body. Enough
                            to be a heading; not enough to shout in Vazirmatn,
                            which has no light weight to fall back on.
     THE RULE IS BROKEN     around the page's own bud — the same three-piece
                            gesture as the calendar leaf's rule and the closing
                            panel, so the page repeats one shape rather than
                            inventing a new ornament per section. */
.gulbaran-scope .gb-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  margin-block-end: 2.1rem;
  text-align: center;
}

.gulbaran-scope .gb-h2 {
  margin: 0;
  font-size: 1.78rem;
  font-weight: 400;
  /* Tight, because at this size the default leading opens a gap inside a
     two-line Kurdish title that reads as two separate headings. */
  line-height: 1.35;
  letter-spacing: 0.01em;
  color: var(--gb-plum-deep);
}

.gulbaran-scope .gb-head-rule {
  display: block;
  inline-size: 8.5rem;
  color: var(--gb-gold);
}

.gulbaran-scope .gb-head-rule svg {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.gulbaran-scope .gb-lede {
  margin: 0 0 1.3rem;
  text-align: center;
  font-size: 0.95rem;
  line-height: 1.8;
  color: var(--gb-muted);
}

/* ── the painted rule between movements ──────────────────────────────────
   THREE USES on the page, never more. Darga's limit and its reason: use it at
   every section and it is wallpaper. */
.gulbaran-scope .gb-div {
  display: flex;
  justify-content: center;
  padding-inline: 1.5rem;
}

.gulbaran-scope .gb-div img {
  inline-size: min(19rem, 74%);
  block-size: auto;
  opacity: 0.9;
}

/* ── THE INVITATION SHEET ────────────────────────────────────────────────
   THE ONE PIECE OF PAPER, and the only thing on the page lifted off the ground
   with an edge and a shadow. Square corners on purpose: this is printed
   stationery, not UI, and a radius here is the single fastest way to make an
   invitation look like a web form. Darga's carries the identical note.

   Cut 2 of this template printed the invitation straight onto the page with no
   card at all, on the argument that a panel inside a framed sheet is a card on
   a card. That was the wrong lesson: darga's sections have no shells and its
   SHEET still does, because the sheet is not a section shell — it is the object
   the section contains. */
.gulbaran-scope .gb-sec--sheet {
  padding-block: 3.2rem;
}

.gulbaran-scope .gb-sheet {
  position: relative;
  max-inline-size: 28rem;
  margin-inline: auto;
  padding: 3.4rem 1.9rem 5.4rem;
  background: var(--gb-card);
  border: 1px solid var(--gb-rule);
  box-shadow: 0 20px 52px -30px rgba(43, 32, 41, 0.55);
  text-align: center;
  /* The corner sprays are absolutely placed and hang past the padding box;
     without this they would spill onto the page and lose the card's edge. */
  overflow: hidden;
}

/* THE CORNER SPRAY, TWICE. An open L that tapers, placed at two opposite
   corners and the second turned 180 — a wreath could not do this, which is why
   the prompt asked for a corner rather than a frame.

   PHYSICAL top/left and bottom/right, not logical. The two are one drawing
   rotated, and swapping them by text direction would put the dense end of both
   at the same corner.

   40%, and the second at 34%: two sprays at equal weight read as a border
   rather than as a corner. Darga measured the same thing and settled on 40%. */
.gulbaran-scope .gb-sheet-corner {
  position: absolute;
  /* FLUSH TO THE CORNER, not bled past it. These were at top:-2% left:-4% and
     the card's `overflow: hidden` then cropped away the dense cluster of roses
     that IS the corner — the drawing was trimmed to its own bounding box, so
     0,0 puts its densest point exactly where the card's corner is. */
  top: 0;
  left: 0;
  inline-size: 42%;
  max-inline-size: none;
  block-size: auto;
  pointer-events: none;
  opacity: 0.95;
}

.gulbaran-scope .gb-sheet-corner--b {
  top: auto;
  left: auto;
  bottom: 0;
  right: 0;
  inline-size: 35%;
  transform: rotate(180deg);
  opacity: 0.8;
}

/* Lifts the sheet's CONTENT above the corner sprays. Everything inside the card
   that is not a spray or the seal needs this, or a long name runs under the
   painted roses. */
.gulbaran-scope .gb-sheet > *:not(.gb-sheet-corner):not(.gb-seal) {
  position: relative;
  z-index: 1;
}

/* The fleuron: ONE use, between what is said above it and the invitation
   itself. A fleuron repeated is a border. */
.gulbaran-scope .gb-fleuron {
  display: block;
  inline-size: 9rem;
  block-size: auto;
  margin: 1.7rem auto;
  opacity: 0.9;
}

/* THE SEAL. `bottom` and `left` are PHYSICAL: a seal is pressed onto a page by
   hand and does not swap corners because the text runs the other way. Darga's
   carries the same note for the same reason. */
.gulbaran-scope .gb-seal {
  position: absolute;
  bottom: 1.5rem;
  left: 1.6rem;
  z-index: 2;
  inline-size: 4.6rem;
  block-size: auto;
  pointer-events: none;
}

.gulbaran-scope .gb-bismillah {
  margin: 0 0 1.4rem;
  font-size: 1.05rem;
  line-height: 1.9;
  color: var(--gb-plum);
}

.gulbaran-scope .gb-ayah {
  margin-block: 1.8rem;
}

.gulbaran-scope .gb-ayah-rule {
  display: block;
  inline-size: 38%;
  margin-inline: auto;
  block-size: 1px;
  background: var(--gb-rule);
}

.gulbaran-scope .gb-ayah-text {
  margin: 1.2rem 0;
  font-size: 1.2rem;
  line-height: 2.1;
  color: var(--gb-ink);
}

.gulbaran-scope .gb-sheet-msg {
  margin: 0;
  font-size: var(--gb-t-body);
  line-height: 2.1;
  color: var(--gb-ink);
}

.gulbaran-scope .gb-hosts {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.gulbaran-scope .gb-host {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.gulbaran-scope .gb-host-lbl {
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  color: var(--gb-muted);
}

.gulbaran-scope .gb-host-name {
  font-size: 1.05rem;
  color: var(--gb-plum-deep);
}

.gulbaran-scope .gb-host-name--solo {
  margin: 2rem 0 0;
}

/* ── THE DATE: A BOUND CALENDAR LEAF ─────────────────────────────────────
   A page torn off a desk calendar — binding at the top, a coloured header
   carrying the month, the day large under it, a perforation, and the action
   below the tear. Darga's four pieces in darga's order, in gulbaran's material.

   THERE IS NO MONTH GRID and there should not be one again. See GulbaranDate
   for the founder's verdict on the two that were tried. */
.gulbaran-scope .gb-date {
  display: flex;
  justify-content: center;
}

.gulbaran-scope .gb-date-card {
  position: relative;
  inline-size: min(17.5rem, 100%);
  background: var(--gb-card);
  border: 1px solid var(--gb-rule);
  box-shadow: 0 16px 40px -26px rgba(43, 32, 41, 0.5);
  /* NO `overflow: hidden`, however much the header band wants it — the binding
     pins straddle the top edge and would be sheared off. Darga's leaf carries
     the identical warning. */
  margin-block-start: 0.65rem;
}

/* THE BINDING. Three pins straddling the top edge, gold because the rest of
   the leaf's furniture is. Two would read as a pair of tabs; three reads as a
   spiral. */
.gulbaran-scope .gb-bind {
  position: absolute;
  inset-block-start: -0.65rem;
  inset-inline: 0;
  display: flex;
  justify-content: center;
  gap: 3.1rem;
  pointer-events: none;
}

.gulbaran-scope .gb-bind-pin {
  inline-size: 0.42rem;
  block-size: 1.3rem;
  border-radius: 0.21rem;
  background: linear-gradient(
    to bottom,
    var(--gb-gold-light),
    var(--gb-gold) 55%,
    #9c7d4c
  );
  box-shadow: 0 1px 2px rgba(43, 32, 41, 0.35);
}

/* THE HEADER. The month, and the one place on this card the plum is a ground
   rather than ink. */
.gulbaran-scope .gb-date-band {
  margin: 0;
  padding-block: 0.62rem;
  text-align: center;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  color: #f6ecdc;
  background: var(--gb-plum-deep);
}

.gulbaran-scope .gb-date-year {
  font-size: 0.78rem;
  opacity: 0.78;
  font-variant-numeric: tabular-nums lining-nums;
}

.gulbaran-scope .gb-date-body {
  padding: 1.15rem 1.1rem 1.25rem;
  text-align: center;
}

.gulbaran-scope .gb-date-wd {
  margin: 0;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  color: var(--gb-muted);
}

/* THE DAY. Vazirmatn, NOT the display face — Aref Ruqaa's numerals fall apart
   at this size, which is the note darga's leaf carries on the same numeral. */
.gulbaran-scope .gb-date-num {
  margin: 0.15rem 0 0;
  font-size: 4.1rem;
  line-height: 1.02;
  font-weight: 300;
  color: var(--gb-plum-deep);
  font-variant-numeric: tabular-nums lining-nums;
}

/* The rule breaks in the middle and the page's own bud sits in the gap. */
.gulbaran-scope .gb-date-rule {
  display: block;
  inline-size: 8.6rem;
  margin: 0.7rem auto 0.6rem;
  color: var(--gb-gold);
}

.gulbaran-scope .gb-date-rule svg {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.gulbaran-scope .gb-date-time {
  margin: 0;
  font-size: 0.9rem;
  color: var(--gb-ink);
}

.gulbaran-scope .gb-date-clock {
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--gb-plum);
}

/* THE PERFORATION. Negative inline margins so it runs the full width of the
   card rather than stopping inside its padding — a tear that stops short of
   both edges is a dotted line, not a tear. */
.gulbaran-scope .gb-date-tear {
  display: block;
  margin: 1.15rem -1.1rem 1.05rem;
  block-size: 1px;
  background-image: radial-gradient(circle, var(--gb-rule) 0 42%, transparent 45%);
  background-size: 7px 1px;
  background-repeat: repeat-x;
}

/* SIZED TO ITS LABEL, not to the card — darga's note, and its reason: a
   full-width slab under a torn edge is a form control, not an action on
   stationery. */
.gulbaran-scope .gb-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.64rem 1.25rem;
  border: 1px solid var(--gb-rule);
  border-radius: 999px; /* same pill as .gb-chip — one control shape per page */
  background: transparent;
  color: var(--gb-plum-deep);
  font: inherit;
  font-size: 0.86rem;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 200ms ease, border-color 200ms ease;
}

.gulbaran-scope .gb-btn:hover,
.gulbaran-scope .gb-btn:focus-visible {
  background: var(--gb-soft);
  border-color: var(--gb-gold);
}

.gulbaran-scope .gb-btn-ico {
  display: inline-flex;
  color: var(--gb-gold);
}

/* ── chips (add to calendar, maps, phone) ───────────────────────────────── */

.gulbaran-scope .gb-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  justify-content: center;
  margin-block-start: 1rem;
}

.gulbaran-scope .gb-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.62rem 1.2rem;
  border: 1px solid var(--gb-rule);
  /* A PILL, not a rectangle. Every outlined control on the page was a hard
     right-angled box, and a row of them is what a form looks like — the
     reference rounds all of its. 999px rather than a radius, so the ends stay
     semicircular whatever the label's length does to the height. */
  border-radius: 999px;
  background: transparent;
  color: var(--gb-plum-deep);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  text-decoration: none;
  transition: background 200ms ease, border-color 200ms ease;
}

.gulbaran-scope .gb-chip:hover,
.gulbaran-scope .gb-chip:focus-visible {
  background: var(--gb-soft);
  border-color: var(--gb-gold);
}

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

.gulbaran-scope .gb-chip-ico {
  inline-size: 1em;
  block-size: 1em;
  flex: none;
}

/* ── countdown ──────────────────────────────────────────────────────────── */

.gulbaran-scope .gb-cd {
  display: flex;
  justify-content: center;
  gap: 0.6rem;
}

/* Four numbers on paper, divided by hairlines rather than boxed. Four bordered
   tiles was the single most "web page" thing left on the sheet. */
.gulbaran-scope .gb-cd-unit {
  position: relative;
  flex: 1 1 0;
  max-inline-size: 6rem;
  /* The foot is new, and it is the only thing the drain needed: a bar has to
     have somewhere to sit that is not under the label. */
  padding: 0 0.4rem 0.85rem;
  text-align: center;
}

.gulbaran-scope .gb-cd-unit + .gb-cd-unit {
  border-inline-start: 1px solid var(--gb-rule);
}

/* ── THE DRAIN — added 2026-08-28, and it is a bug fix, not an addition ───
   shared/Countdown writes `--f` on every cell — seconds against a minute,
   minutes against an hour, hours against a day, days against a year — and this
   stylesheet never mentioned it. `.gb-cd-unit::before` computed `content: none`,
   so the cells were four numerals where nothing moved, which is the exact
   objection the shared component was built to answer. Its own header says so:

     Four numerals where nothing moves read as a picture. So each cell carries a
     fill running to the fraction its unit represents ... It is alive without a
     digit ever jumping.

   perde carried the identical defect under a different name — it read
   `var(--fill, 0)` where the component writes `--f` — and both were found the
   same week. A decorative layer that raises no error is not evidence that it
   renders.

   THE FILL IS `::after` AND THE TRACK IS `::before`. Both are absolutely
   positioned in the same place with no z-index between them, so DOM order
   decides and `::after` paints on top. Written the other way round the track
   covers the fill and the bug looks identical to having no fill at all.

   `transform` and not `inline-size`: what the browser animates is then a
   composited transform on a pseudo-element and never a layout — the shared
   component's own reason for making `--f` a custom property in the first place.

   `transform-origin: right` IS THE READING START on this RTL-only sheet. There
   is no logical keyword for it; see the header of this file. */
.gulbaran-scope .gb-cd-unit::before,
.gulbaran-scope .gb-cd-unit::after {
  content: "";
  position: absolute;
  inset-inline: 0.4rem;
  inset-block-end: 0;
  block-size: 1px;
}

/* the track it drains along */
.gulbaran-scope .gb-cd-unit::before {
  background: var(--gb-rule);
  opacity: 0.55;
}

/* the fill itself */
.gulbaran-scope .gb-cd-unit::after {
  background: var(--gb-gold);
  transform: scaleX(var(--f, 0));
  transform-origin: right center;
  transition: transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
}

.gulbaran-scope .gb-cd-num {
  display: block;
  font-size: 1.5rem;
  line-height: 1.2;
  color: var(--gb-plum-deep);
  /* Tabular, or every tick of the seconds column shifts the row's width. */
  font-variant-numeric: tabular-nums;
}

.gulbaran-scope .gb-cd-lbl {
  display: block;
  margin-block-start: 0.3rem;
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  color: var(--gb-muted);
}

.gulbaran-scope .gb-arrived {
  margin: 0;
  text-align: center;
  font-size: 1.05rem;
  color: var(--gb-plum);
}

/* ── venue and map ──────────────────────────────────────────────────────── */

.gulbaran-scope .gb-venue {
  text-align: center;
}

.gulbaran-scope .gb-venue-name {
  margin: 0 0 0.4rem;
  font-size: 1.15rem;
  color: var(--gb-plum-deep);
}

.gulbaran-scope .gb-venue-addr {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.8;
  color: var(--gb-muted);
}

/* ── the map ────────────────────────────────────────────────────────────
   THE STRUCTURE IS NOT WHAT THE NAMES SUGGEST, and this cost a real bug.
   shared/VenueBlock renders:

     .gb-map            the container, always present
       .gb-map-frame    ← the IFRAME ITSELF, and only once the guest opts in
       .gb-map-hit      ← the button, instead, until then

   This stylesheet had `.gb-map-frame` as a positioned WRAPPER carrying the
   aspect-ratio and border, with `.gb-map-frame iframe` as its child. Both were
   wrong, so in the un-clicked state no element with that class existed at all
   — `.gb-map` collapsed to height 0 and `.gb-map-hit`, absolute with no
   positioned ancestor, escaped and printed itself on top of the venue address.

   IT WAS INVISIBLE FOR AS LONG AS THE PLATE HAD A FILL. A solid beige
   rectangle looked like a deliberate plate sitting there; removing the fill for
   the redesign is what exposed the overlap underneath it. Measured:
   `.gb-map` h=0, `.gb-map-frame` not rendered, `.gb-map-hit` absolute at
   top 263 over text at top 381.

   Same lesson as the calendar's `is-mark`/`is-day`: read the shared component,
   do not infer its structure from its class names. */
/* ── THE BOX BEFORE THE MAP IS GONE ──────────────────────────────────────
   This reserved a 4:3 plate — at 390px an empty 342x256 rectangle with a
   hairline border and a small pin floating in the middle of it. It was the
   largest object in the venue section and it was a placeholder for something
   that was not there yet, which is what it looked like.

   The reference the founder pointed at has NO box at rest: an address, and a
   pill that says "view on map". So does this now. The container reserves
   nothing; whichever child VenueBlock renders gives it its height.

   Both children are IN FLOW, which also retires the trap recorded above: with
   nothing absolutely positioned there is nothing that can escape the container
   and print itself over the address. */
.gulbaran-scope .gb-map {
  position: relative;
  display: flex;
  justify-content: center;
  margin-block-start: 1.2rem;
  inline-size: 100%;
  overflow: hidden;
  border-radius: 8px;
}

/* The invitation, before the guest opts in: the same pill as every other
   control on the page, sized to its own label. */
.gulbaran-scope .gb-map-hit {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.64rem 1.25rem;
  background: transparent;
  border: 1px solid var(--gb-rule);
  border-radius: 999px;
  font: inherit;
  color: var(--gb-plum-deep);
  cursor: pointer;
  transition: background-color 200ms ease, border-color 200ms ease;
}

.gulbaran-scope .gb-map-hit:hover,
.gulbaran-scope .gb-map-hit:focus-visible {
  background: var(--gb-soft);
  border-color: var(--gb-gold);
}

/* DRAWN HERE, because shared/VenueBlock renders this as an EMPTY <span> and
   leaves the mark to the template. perde sets a size and a colour on the same
   empty span and therefore shows nothing at all — not copied, and not fixed
   from here either; that is perde's file to change.

   A mask rather than a background image, so the shape takes `background-color`
   and stays on the palette like every other drawn mark on this page. Inline
   data URI: a strict CSP blocks external assets, and this is one path. */
.gulbaran-scope .gb-map-pin {
  /* Sized to the label beside it now that the two sit on one line, not the
     1.5rem it was when it floated alone in the middle of a plate. */
  inline-size: 1.05rem;
  block-size: 1.05rem;
  flex: none;
  background-color: var(--gb-gold);
  -webkit-mask: var(--gb-pin) center / contain no-repeat;
  mask: var(--gb-pin) center / contain no-repeat;
}

.gulbaran-scope .gb-map-lbl {
  font-size: 0.85rem;
  letter-spacing: 0.04em;
}

/* The embed itself. No border: once it loads it is a real picture, and a rule
   around it would be a second frame inside the card's own. The `sandbox` and
   `referrer-policy` come from the shared component — the invitation's URL is a
   secret and this is a cross-origin request. */
/* THE EMBED SIZES THE CONTAINER, rather than being stretched to fill a box the
   container reserved. `aspect-ratio` on the iframe itself: an iframe's default
   150px height would otherwise be the whole map. */
.gulbaran-scope .gb-map-frame {
  display: block;
  inline-size: 100%;
  aspect-ratio: 4 / 3;
  block-size: auto;
  border: 0;
}


/* ── THE EVENING: time, node, title ──────────────────────────────────────
   Darga's shape. Three columns to look at, TWO in the grid: the hour on the
   reading-start side, the entry on the other, and the nodes on a rail down the
   middle. That is how an order of the day is printed, and it is what lets the
   rail be continuous — with `1fr 1fr` and a symmetric gap, the row's centre
   line IS the middle of the gutter, so there is no third column to keep in
   sync. */
.gulbaran-scope .gb-prog-wrap {
  padding: 0;
}

.gulbaran-scope .gb-prog {
  position: relative;
  max-inline-size: 23rem;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

/* THE RAIL. It FADES OUT at both ends rather than stopping on a measured
   inset: rows are not a fixed height once a title wraps, so any rem value that
   lines up with the first and last node at one width is wrong at another.
   Darga learned this; it is not worth relearning. */
.gulbaran-scope .gb-prog::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(50% - 0.5px);
  inline-size: 1px;
  background: linear-gradient(
    to bottom,
    rgba(194, 160, 106, 0),
    var(--gb-rule) 9%,
    var(--gb-rule) 91%,
    rgba(194, 160, 106, 0)
  );
}

/* DRIVEN BY THE SCROLL: `--prog-p` is how far the reading line has travelled
   between the first node and the last, set every frame by shared/Timeline.
   `transform` only, so it never costs layout however fast the guest flicks. */
.gulbaran-scope .gb-prog::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(50% - 0.5px);
  inline-size: 1px;
  background: linear-gradient(to bottom, rgba(194, 160, 106, 0), var(--gb-gold) 9%, var(--gb-plum));
  transform: scaleY(var(--prog-p, 0));
  transform-origin: top;
}

/* `--i` is the row's index, set inline — no CSS counter multiplies into a
   delay. `align-items: center` rather than a baseline: a title that wraps to
   two lines still has to sit level with its hour and with its node. */
.gulbaran-scope .gb-prog-row {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(1.9rem, 9vw, 2.6rem);
  align-items: center;
  padding-block: 1.15rem;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.55s ease-out, transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--i, 0) * 90ms + 250ms);
}

.gulbaran-scope .gb-reveal.is-visible .gb-prog-row {
  opacity: 1;
  transform: none;
}

/* THE NODE — this page's own bud, reduced to a rounded lozenge on the rail.
   Outlined until the hour is reached and filled once it is: unreached, then
   reached, is what the row is saying, and the scroll IS the timing, so there
   is no transition delay.

   The outer ring is a HALO in the page's colour, and it is what stops the rail
   showing through an outlined node. */
.gulbaran-scope .gb-prog-row::before {
  content: "";
  position: absolute;
  inset-inline-start: calc(50% - 0.26rem);
  inset-block-start: calc(50% - 0.26rem);
  inline-size: 0.52rem;
  block-size: 0.52rem;
  border-radius: 50% 50% 50% 0;
  rotate: 45deg;
  background: var(--gb-paper);
  box-shadow: inset 0 0 0 1px var(--gb-gold), 0 0 0 4px var(--gb-paper);
  transition: background-color 0.35s ease-out, box-shadow 0.35s ease-out,
    scale 0.45s cubic-bezier(0.34, 1.5, 0.64, 1);
}

.gulbaran-scope .gb-prog-row.is-on::before {
  background: var(--gb-plum);
  box-shadow: inset 0 0 0 1px var(--gb-plum), 0 0 0 4px var(--gb-paper);
  scale: 1.2;
}

/* Both columns are set TOWARD THE GUTTER, so the two of them frame the rail
   instead of drifting to the outer edges. `end` on the hour and `start` on the
   entry is the same instruction twice — each is the edge nearer the middle —
   and it stays correct if this is ever read left to right.

   The hour goes from a note to a fact as the guest reaches it. Colour only:
   changing the weight would reflow the row under their thumb. */
.gulbaran-scope .gb-prog-time {
  align-self: center;
  text-align: end;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--gb-plum);
  font-variant-numeric: tabular-nums lining-nums;
  transition: color 0.35s ease-out;
}

.gulbaran-scope .gb-prog-row:not(.is-on) .gb-prog-time {
  color: var(--gb-muted);
}

/* THE ONLY BOLD TEXT ON THE PAGE WAS HERE, and it was the last thing reading as
   hard. Four titles at 600 against a page where every heading is 400 made the
   programme the heaviest block on the invitation — heavier than the section
   heading above it, which is the wrong way round. 500 still separates the title
   from the hour beside it; the colour was already doing most of that work. */
.gulbaran-scope .gb-prog-title {
  align-self: center;
  text-align: start;
  font-size: 1.02rem;
  font-weight: 500;
  line-height: 1.6;
  color: var(--gb-plum-deep);
  overflow-wrap: break-word;
}

/* ── THE PHOTOGRAPHS: A PRINT ON A FLOWERED SHEET ────────────────────────
   One photographic print lying tilted on a flowered sheet, with a second print
   showing behind it, and the arrows and the count under them.

   ── WHAT THE FIRST CUT GOT WRONG ─────────────────────────────────────────

   This was a PILE: three cards stacked square-on, each a little smaller and
   fainter than the one in front. With real photographs it was fine. Driven
   with the demo's flat placeholders it was a smear of slabs — and a design
   that only survives good content is leaning on the content.

   The founder pointed at invitlo.com/e/demo-teatro, whose gallery is one
   print, tilted, wide cream margins with a much deeper one at the foot, a soft
   wide shadow, and a 01 / 03 counter between two circular arrows. Four things
   this now takes from it, each for its own reason:

     ONE HERO      three competing rectangles is a widget; one is a photograph.
     DEEP FOOT     the asymmetric margin is the whole reason a polaroid reads
                   as a physical object, and it gives the caption a home.
     THE TILT      square-on is a UI card. 1.6deg off is something put down.
     REAL ARROWS   "tap the picture" is undiscoverable — a guest who never
                   tries it never learns there are more. The arrows say so at
                   rest, and the count says how many.

   ── THE FLOWERS RECEDED, THEY DID NOT LEAVE ──────────────────────────────

   The earlier ask was *a paper of flower and in the center of it the picture*.
   The sheet is still here; what changed is which of the two is the subject.
   The sprays were 33% wide at 0.9/0.72 opacity and were the loudest thing in
   the section — a mount that shouts over the photograph it is presenting. At
   24% and 0.5/0.38 they are the paper UNDER the print instead of a frame
   around it. */
.gulbaran-scope .gb-pile {
  position: relative;
  /* Measured, not guessed: at 390px the section leaves the sheet ~342px, and
     the print is capped below at 74% of that. Flowers need somewhere to be,
     and so does the tilt — a rotated card sweeps wider than its own box. */
  padding: 2.6rem 1.4rem 1.4rem;
}

/* THE FLOWERED SHEET the prints lie on. Its own paper, so the mount reads as a
   separate leaf from the page under it. */
.gulbaran-scope .gb-pile::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--gb-card);
  border: 1px solid var(--gb-rule);
  border-radius: 3px;
  box-shadow: 0 16px 44px -30px rgba(43, 32, 41, 0.5);
}

/* PHYSICAL corners: the pair is one drawing rotated 180deg, and swapping them
   by text direction would put both dense ends at the same corner. */
.gulbaran-scope .gb-pile-paper {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 3px;
  pointer-events: none;
}

/* ── A MODIFIER CANNOT OUTRANK AN ELEMENT SELECTOR ───────────────────────
   This pair was `.gb-pile-paper img` and `.gb-pile-corner--b`, and the second
   spray came out at the TOP right, upside down, at the wrong size.

   Specificity, and nothing else. `.gulbaran-scope .gb-pile-paper img` is
   (0,2,1) — two classes AND an element — while `.gulbaran-scope
   .gb-pile-corner--b` is only (0,2,0). The base rule therefore beat the
   modifier on every property they shared: `top: auto` lost to `top: 0`,
   `left: auto` lost to `left: 0`, `inline-size: 20%` lost to 24%, and
   `opacity: 0.38` lost to 0.5. The only declarations that survived were
   `right`, `bottom` and `transform` — the three the base rule never mentions.

   Then `bottom: 0` was quietly discarded too: an absolutely positioned
   REPLACED element with `height: auto` and both `top` and `bottom` set is
   over-constrained, and CSS resolves that by keeping `top` and solving for
   `bottom`. So the drawing pinned itself to the top edge, took `right: 0` from
   the surviving pair, and rotated 180deg in place.

   Measured before the fix: top 0, left 260, right 0, bottom 361, w 82 — where
   it should read top 361, right 0, w 68.

   NOTHING REPORTS THIS. No error, no warning, valid CSS, lint and build both
   green — the same silent-failure class as the shared-component selectors in
   §8c. The invitation sheet's identical pair has always been correct because
   BOTH of its rules are class-only, so source order decides. Matched here: the
   base is a class now and both images carry it. Do not reintroduce an element
   selector in this pair. */
.gulbaran-scope .gb-pile-corner {
  position: absolute;
  top: 0;
  left: 0;
  inline-size: 24%;
  max-inline-size: none;
  block-size: auto;
  opacity: 0.5;
}

/* THE OPPOSITE CORNER — physical, not logical. The pair is one drawing rotated
   180deg, and flipping it by text direction would put both dense ends at the
   same corner. Lighter and smaller than the first: two sprays at equal weight
   read as a border rather than as two corners. */
.gulbaran-scope .gb-pile-corner--b {
  top: auto;
  left: auto;
  bottom: 0;
  right: 0;
  inline-size: 20%;
  transform: rotate(180deg);
  opacity: 0.38;
}

/* THE STACK. A grid with one cell, so both prints are in the same place and
   overlap by construction. Sized by the top print — the one behind is
   absolutely placed and contributes no height, or the sheet would grow by its
   own offset. */
.gulbaran-scope .gb-pile-stack {
  position: relative;
  z-index: 1;
  display: grid;
  inline-size: min(15.5rem, 74%);
  margin-inline: auto;
}

/* THE PRINT BEHIND, tilted the OTHER WAY. Two cards at the same angle is one
   card with a thick edge; opposed angles is a pair of photographs somebody
   dropped on a table.

   INSET, and this was a real bug rather than a preference. At `inset: 0` it
   matched the top card exactly and then rotated — and a rotated square sweeps a
   WIDER box than the square itself, so measured it came out 238×319 against the
   top card's 228×312 and read as the bigger of the two, with an edge poking out
   above the card that was supposed to be in front of it. 3.4deg was also too
   much: at that angle the sweep alone is most of the offset. Inset by 5% and
   turned 2.2deg, the whole card now lives inside the top one's footprint except
   for the two corners that are meant to show. */
.gulbaran-scope .gb-pile-under {
  position: absolute;
  inset: 5% 5% 0;
  display: block;
  padding: 0.5rem 0.5rem 1.5rem;
  background: var(--gb-card);
  border: 1px solid var(--gb-rule);
  rotate: 2.2deg;
  opacity: 0.85;
  pointer-events: none;
}

/* THE PRINT ON TOP, and the whole photograph is the target — a guest told to
   tap the picture should not have to find a control on it. A real <button>, so
   Enter and Space work natively and the focus ring is real.

   THE PADDING IS THE DESIGN. Equal at the sides and top, roughly triple at the
   foot: that asymmetry is the only thing separating a print from a rectangle
   with a border, and it is why the caption sits where it does. */
.gulbaran-scope .gb-pile-top {
  position: relative;
  z-index: 1;
  display: block;
  inline-size: 100%;
  padding: 0.62rem 0.62rem 1.9rem;
  border: 1px solid var(--gb-rule);
  background: var(--gb-card);
  color: inherit;
  font: inherit;
  cursor: pointer;
  rotate: -1.6deg;
  /* Wide and soft, thrown mostly downward — a print resting ON something, not
     a card floating over it. Two shadows: a tight one that draws the contact
     edge and a wide one for the throw. Cream on cream needs both, or the print
     dissolves into the sheet it is lying on. */
  box-shadow: 0 2px 4px -2px rgba(43, 32, 41, 0.28),
    0 22px 40px -20px rgba(43, 32, 41, 0.42);
  transition: rotate 300ms cubic-bezier(0.34, 1.2, 0.64, 1),
    scale 220ms cubic-bezier(0.34, 1.4, 0.64, 1);
}

.gulbaran-scope .gb-pile-top:hover,
.gulbaran-scope .gb-pile-top:focus-visible {
  rotate: -0.6deg;
}

.gulbaran-scope .gb-pile-top:active {
  scale: 0.985;
}

.gulbaran-scope .gb-pile-top:focus-visible {
  outline: 2px solid var(--gb-plum);
  outline-offset: 4px;
}

/* The window. Its own element rather than the image's own box, so the fade can
   run on the <img> without the mount flickering with it. */
.gulbaran-scope .gb-pile-win {
  display: block;
  overflow: hidden;
  background: var(--gb-soft);
}

.gulbaran-scope .gb-pile-under img,
.gulbaran-scope .gb-pile-win img {
  display: block;
  inline-size: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  background: var(--gb-soft);
}

/* The new photograph fades up rather than cutting. Keyed on the image's own
   key, so it restarts on every advance. */
.gulbaran-scope .gb-pile-win img {
  animation: gb-photo 420ms ease-out both;
}

@keyframes gb-photo {
  from {
    opacity: 0;
    scale: 1.012;
  }
  to {
    opacity: 1;
    scale: 1;
  }
}

/* THE CAPTION, in the deep foot. Absolutely placed and centred on the WINDOW
   rather than flowed after it, so a long instruction cannot push the foot
   taller and break the proportion that is doing all the work here. */
.gulbaran-scope .gb-pile-foot {
  position: absolute;
  inset-inline: 0.62rem;
  inset-block-end: 0.52rem;
  display: block;
  text-align: center;
  font-size: 0.68rem;
  line-height: 1.5;
  letter-spacing: 0.07em;
  color: var(--gb-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── the controls ────────────────────────────────────────────────────────
   Clears the tilted print's corners: a card rotated 1.6deg inside a 15.5rem
   box hangs about 7px below its own bottom edge, and without the margin the
   row prints into it. */
.gulbaran-scope .gb-pile-nav {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.1rem;
  margin-block-start: 1.6rem;
}

.gulbaran-scope .gb-pile-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  flex: none;
  padding: 0;
  border: 1px solid var(--gb-rule);
  border-radius: 50%;
  background: transparent;
  color: var(--gb-plum-deep);
  cursor: pointer;
  transition: background-color 200ms ease, border-color 200ms ease;
}

/* THE CHEVRONS POINT IN THE DIRECTION OF TRAVEL, and this page reads RIGHT TO
   LEFT — so "next" travels leftward and "previous" travels rightward, which is
   the opposite of what the same two glyphs mean on an English page.

   The component keeps the honest names (`back` draws a left chevron, because
   that is what a left chevron is) and the mirroring happens HERE, where the
   direction actually lives. Driven before this: the back button rendered on the
   right, correctly, and pointed left — an arrow on the right edge pointing away
   from the pile it would move you toward.

   Not `transform`, which the arrow does not otherwise use, and not swapping the
   glyphs in JS, which would leave a component whose prop names lie. */
.gulbaran-scope .gb-pile-arrow svg {
  scale: -1 1;
}

.gulbaran-scope .gb-pile-arrow:hover,
.gulbaran-scope .gb-pile-arrow:focus-visible {
  background: var(--gb-soft);
  border-color: var(--gb-gold);
}

.gulbaran-scope .gb-pile-arrow:focus-visible {
  outline: 2px solid var(--gb-plum);
  outline-offset: 2px;
}

/* The count. tabular-nums and the fixed inline-size together: without them the
   two arrows walk apart every time a digit changes width. */
.gulbaran-scope .gb-pile-count {
  margin: 0;
  min-inline-size: 4.2rem;
  text-align: center;
  font-size: 0.82rem;
  letter-spacing: 0.14em;
  font-variant-numeric: tabular-nums;
  color: var(--gb-muted);
  /* The numerals are Latin here as they are in the countdown and the
     programme, and Latin digits do not reorder — so the pair reads 01 / 03 in
     both directions without a bidi isolate. */
  direction: ltr;
}

.gulbaran-scope .gb-pile-slash {
  margin-inline: 0.4rem;
  color: var(--gb-gold);
}

/* ── rsvp ───────────────────────────────────────────────────────────────── */

/* The form sits on the sheet like everything else. Its own controls still
   carry edges — a field a guest must type into has to look like one — but the
   block around them does not. */
.gulbaran-scope .gb-rsvp {
  margin-block-start: 0.4rem;
}

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

.gulbaran-scope .gb-notes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.gulbaran-scope .gb-wishes {
  list-style: none;
  margin: 0;
  padding: 0;
}

.gulbaran-scope .gb-note {
  padding-inline-start: 1.2rem;
  position: relative;
  font-size: 0.95rem;
  line-height: 1.9;
  color: var(--gb-ink);
}

.gulbaran-scope .gb-note::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.85em;
  inline-size: 0.34rem;
  block-size: 0.34rem;
  border-radius: 50%;
  background: var(--gb-gold);
}

.gulbaran-scope .gb-contact {
  text-align: center;
}

.gulbaran-scope .gb-contact-name {
  font-size: 1.1rem;
  color: var(--gb-plum-deep);
}

.gulbaran-scope .gb-contact-role {
  margin: 0.3rem 0 0;
  font-size: 0.85rem;
  color: var(--gb-muted);
}

/* Hairline-separated entries, not stacked cards. */
.gulbaran-scope .gb-wish {
  padding-block: 1.1rem;
}

.gulbaran-scope .gb-wish + .gb-wish {
  border-block-start: 1px solid var(--gb-rule);
}

.gulbaran-scope .gb-wish-msg {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.85;
  color: var(--gb-ink);
}

.gulbaran-scope .gb-wish-name {
  margin: 0.5rem 0 0;
  font-size: 0.78rem;
  color: var(--gb-muted);
}

/* ── the close ──────────────────────────────────────────────────────────── */

/* The scroll ends where it started: the same closed rose the guest tapped,
   drawn and small. */
/* ── THE CLOSING PANEL ───────────────────────────────────────────────────
   The one other dark thing on the page. With the countdown band it is the
   page's whole spine — a run of cream with one band across the middle and this
   at the foot.

   The blessing is set on the damask in gold, which is why that file was asked
   for TONE ON TONE: a high-contrast pattern turns the words into noise. */
.gulbaran-scope .gb-closing {
  position: relative;
  z-index: 1;
  padding: 4.6rem 2.4rem 5.6rem;
  text-align: center;
  overflow: hidden;
  background-color: #3b1f2c;
  background-image: url("/templates/gulbaran/panel.webp");
  background-size: cover;
  background-position: center;
}

/* THE VINE, BOTH EDGES — taller than the panel and cropped by it, so it reads
   as growing past the frame rather than as a sticker placed inside it.

   PHYSICAL left/right: the two are mirror images and direction has no say in
   which is which. Cropped by the panel's own `overflow: hidden`. */
.gulbaran-scope .gb-vine {
  position: absolute;
  top: -8%;
  left: -6%;
  inline-size: 9.5rem;
  max-inline-size: none;
  block-size: auto;
  opacity: 0.55;
  pointer-events: none;
}

.gulbaran-scope .gb-vine--b {
  left: auto;
  right: -6%;
  transform: scaleX(-1);
}

.gulbaran-scope .gb-closing-msg {
  position: relative;
  z-index: 1;
  margin: 0;
  font-size: 1.2rem;
  line-height: 2.05;
  color: var(--gb-gold-light);
}

.gulbaran-scope .gb-hashtag {
  position: relative;
  z-index: 1;
  margin: 1.1rem 0 0;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  color: rgba(246, 236, 220, 0.72);
}

/* ═══════════════════════════════════════════════════════════════════════════
   4. THE SCROLL REVEAL
   ═══════════════════════════════════════════════════════════════════════════ */

.gulbaran-scope .gb-reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 700ms ease, transform 700ms ease;
}

.gulbaran-scope .gb-reveal.is-visible,
.gulbaran-scope .gb-reveal.is-in {
  opacity: 1;
  transform: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   5. REDUCED MOTION

   A different product, not a disabled one. The mechanic collapses WITH the
   feedback: GulbaranGate skips the clip entirely under `reduce` and fades
   straight through, so there is no tap into silence. Here the ambient layer
   goes away completely — a fixed layer of fourteen infinite animations is
   exactly what this query is for — and every reveal starts already revealed
   rather than waiting for a transition that will never run.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  .gulbaran-scope .gb-gate-ring {
    animation: none;
    opacity: 0.6;
  }

  .gulbaran-scope .gb-reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .gulbaran-scope .gb-gate {
    transition: none;
  }

  /* The drain stops EASING; it does not stop being drawn. `transform: none`
     here would be the obvious line and it would be wrong — it resets scaleX to
     1 and every cell reads as full, which is a countdown that lies rather than
     one that holds still. The fill is a static picture of `--f` under `reduce`,
     which is what this query asks for. */
  .gulbaran-scope .gb-cd-unit::after {
    transition: none;
  }

}
