/* WO07 branch (visual unification with the landing page, on the WO06 owner brief) — ROCK Cinematic Invitation.
   Colour and motion come from ./tokens.css, shared with rockresidences.com: text from the logo's stone, gold from its apricot.
   Type: IBM Plex Sans Arabic 400 / IBM Plex Sans 400 — the exact faces and single weight served by the live
   ROCK landing page (rockresidences.com, css/rla.css; provenance in assets/fonts/rockresidences/PROVENANCE.json).
   Hierarchy comes from size, tone and space — never from synthesised weights. */
@font-face{font-family:'IBM Plex Sans Arabic';font-weight:400;font-style:normal;font-display:swap;
  src:url(./assets/fonts/rockresidences/IBMPlexSansArabic-Regular.74112e1b7b.woff2) format('woff2')}
@font-face{font-family:'IBM Plex Sans';font-weight:400;font-style:normal;font-display:swap;unicode-range:U+0000-00FF,U+2000-206F;
  src:url(./assets/fonts/rockresidences/IBMPlexSans-400.e2291e842c.woff2) format('woff2')}
:root{
  --charcoal:#232323;--accent:var(--rk-stone);
  --ink-sky:16,26,42;
  --font:'IBM Plex Sans Arabic','IBM Plex Sans',system-ui,-apple-system,sans-serif;
  --safe-t:env(safe-area-inset-top,0px);--safe-b:env(safe-area-inset-bottom,0px);
  --gutter:clamp(18px,5.4vw,30px);
  --film-y:74%;                          /* vertical crop of the portrait film (read by app.js for the roofline) */
  /* WO08: the logo is the hero — one size only (it lands at its final size); the ROCK wordmark clearly legible at 375 px. */
  --logo-h:clamp(156px,21svh,196px);
}
*{box-sizing:border-box}
html,body{height:100%}
html{background:var(--charcoal)}
body{margin:0;background:var(--charcoal);color:var(--rk-ink-head);font-family:var(--font);font-size:16px;font-weight:400;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-kerning:normal;overflow:hidden;overscroll-behavior:none}
p,h1{margin:0;font-weight:400}
a{color:inherit;text-decoration:none;-webkit-tap-highlight-color:transparent}
a:focus-visible{outline:1px solid var(--accent);outline-offset:6px}
[hidden]{display:none!important}
::selection{background:var(--rk-stone);color:var(--charcoal)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* ── Stage ─────────────────────────────────────────────────────────────────────────────── */
.stage{position:fixed;inset:0;height:100vh;height:100svh;height:100dvh;overflow:hidden;isolation:isolate;background:var(--charcoal)}
.stage-film{position:absolute;inset:0;overflow:hidden;background:var(--charcoal);z-index:1}
.media-slot{position:absolute;inset:0}
.paper-composition{position:absolute;inset:0;background:var(--charcoal)}
.background-poster,.background-poster img,.background-video{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:50% var(--film-y);display:block}
.background-poster{z-index:0;opacity:0;transition:opacity .9s var(--rk-ease)}
.background-video{z-index:1;opacity:0;pointer-events:none;background:transparent;transition:opacity 1.4s var(--rk-ease)}
.background-shade{display:none}
/* iOS Low Power Mode path: the same approved MP4 shown through <img> (app.js), identical geometry to the video. */
.background-film-image{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover;object-position:50% var(--film-y);display:block;pointer-events:none}
.is-video-visible .background-video{opacity:1}
/* The Poster (identical final frame) is the fallback only — never shown before frame 0. */
[data-phase=invitation] .background-poster{opacity:1}
/* A film that only starts after the invitation (late load, safety net) is never shown behind it. */
[data-phase=invitation]:not(.film-seen) .background-video{opacity:0;transition:none}
/* Skip (tap anywhere): the film gives way to the identical final frame. */
.film-skipped .background-video,.film-skipped .background-film-image{opacity:0!important;transition:opacity var(--rk-t1) var(--rk-ease)!important}
.atmosphere{display:none}

/* Cinematic grade (WO07), gradients only — no filter or backdrop-filter over the playing film (cheapest path on iPhone;
   the film file is untouched). Graduated, anchored on the roofline of the film's final frame (--roof-y, measured by
   app.js through object-fit: cover; 49.5% of the plate on laptop):
   · sky — a deep, calm dim in the logo's hue at 8% lightness, held all the way down to the roof (no bright blue band
     between the text and the roof), over a stone-grey wash that lowers the blue's chroma;
   · from the roofline, one long soft ramp releases both, so the house keeps its natural brightness, warmth and sun;
   · at the very bottom, a slight deepening behind the footer, on a long ramp with no edge. */
.film-veil{position:absolute;inset:0;z-index:2;pointer-events:none;opacity:0;transition:opacity var(--rk-t3) var(--rk-ease) .3s;
  background:linear-gradient(115deg,rgba(var(--rk-sheen-rgb),.04) 0%,rgba(var(--rk-sheen-rgb),0) 40%),
    linear-gradient(180deg,rgba(var(--rk-grade-rgb),var(--g-sky,.62)) 0%,rgba(var(--rk-grade-rgb),var(--g-roof,.54)) var(--roof-y,49.5%),
      rgba(var(--rk-grade-rgb),var(--g-house,.06)) calc(var(--roof-y,49.5%) + 24%),rgba(var(--rk-grade-rgb),var(--g-house,.06)) 80%,
      rgba(var(--rk-grade-rgb),var(--g-foot,.3)) 100%),
    linear-gradient(180deg,rgba(var(--rk-calm-rgb),var(--g-calm,.22)) 0%,rgba(var(--rk-calm-rgb),var(--g-calm,.22)) var(--roof-y,49.5%),
      rgba(var(--rk-calm-rgb),0) calc(var(--roof-y,49.5%) + 14%))}
[data-phase=film] .film-veil,[data-phase=invitation] .film-veil{opacity:1}

/* ── Glass ────────────────────────────────────────────────────────────────────────────────
   Phones: a progressive frosted field over the sky, fitted by app.js to the live text block. Two
   backdrop layers lower the light behind the text while keeping the sky's own hue; the leaves frost;
   the field releases completely just above the roofline, so the architecture keeps the film's light.
   No card, no border, no flat overlay colour. */
.glass{position:absolute;z-index:3;pointer-events:none;opacity:0;transition:opacity 2.2s var(--rk-ease)}
.glass>i{position:absolute;inset:0;display:block}
.glass-sky{display:none;top:0;left:0;right:0;height:var(--sky-y1,48%)}
.glass-sky>i{-webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - var(--release,56px)),transparent 100%);
  mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - var(--release,56px)),transparent 100%)}
.glass-sky .glass-body{-webkit-backdrop-filter:blur(3px) brightness(.8) saturate(1.12);backdrop-filter:blur(3px) brightness(.8) saturate(1.12);
  background:linear-gradient(180deg,rgba(var(--ink-sky),.04) 0%,rgba(var(--ink-sky),.1) 70%,rgba(var(--ink-sky),.14) 100%)}
.glass-sky .glass-frost{-webkit-backdrop-filter:blur(5px) brightness(.95);backdrop-filter:blur(5px) brightness(.95);
  -webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - var(--release,56px) - 40px),transparent calc(100% - 18px));
  mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - var(--release,56px) - 40px),transparent calc(100% - 18px))}
/* One physically plausible light sheen across the glass. */
.glass-sky .glass-sheen{background:linear-gradient(115deg,rgba(var(--rk-sheen-rgb),.08) 0%,rgba(var(--rk-sheen-rgb),.02) 30%,rgba(var(--rk-sheen-rgb),0) 48%)}
/* Footer shade (WO07): the house is graded near-natural, so the countdown — which sits over the sunlit street — gets
   its own shade: one long ramp that begins below the house's base and has no edge, deep enough for the stone text. */
.glass-ground{left:0;right:0;bottom:0;top:var(--ground-y0,88%)}
.glass-ground .glass-body{background:linear-gradient(180deg,rgba(var(--rk-grade-rgb),0) 0%,rgba(var(--rk-grade-rgb),.36) 25%,rgba(var(--rk-grade-rgb),.6) 50%,rgba(var(--rk-grade-rgb),.66) 100%)}
/* Laptop glass fin timing (the phone sky field is retired; the whole-film dim lives on .film-veil). */
[data-phase=film] .glass-sky{opacity:1;transition-duration:2.6s;transition-delay:.4s}
[data-phase=invitation] .glass{opacity:1}
[data-phase=invitation] .glass-sky{transition-duration:1.7s;transition-delay:.1s}

/* ── Copy layer (phone: over the film) ─────────────────────────────────────────────────── */
.stage-copy{position:absolute;inset:0;z-index:4;display:flex;flex-direction:column;justify-content:space-between;align-items:center;text-align:center;
  padding:calc(var(--safe-t) + clamp(14px,2.8svh,30px)) var(--gutter) calc(var(--safe-b) + clamp(10px,1.9svh,20px))}
.register{display:flex;flex-direction:column;align-items:center;width:100%}

/* Identity. WO07: the layout is locked from the first frame and the logo never changes size. During the film the logo
   alone travels: it rises from near the foot of the frame with the tips of the wireframe lines (measured on the film,
   app.js sets --lr0…--lr4), keeps rising when they reach the roof, and decelerates into its final place — under the
   same bottom-to-top mask. Text never moves after it appears. */
.identity{display:flex;flex-direction:column;align-items:center}
.brand{line-height:0}
.brand-wordmark-image{display:block;width:auto;height:var(--logo-h)}
/* The text placeholder reserves the logo box, so the logo arriving never shifts the layout. */
.brand-placeholder{height:var(--logo-h);display:flex;align-items:flex-end;line-height:1}
.brand-text{font-size:22px;letter-spacing:.32em;color:var(--rk-stone)}
/* WO08: the headline is gone; «دعوة خاصة» is the first text line and the page's h1 — a little more presence:
   larger, the head tint rather than stone, and a touch of tracking (word-level, Arabic stays joined). */
.invitation-label{margin-top:0;font-size:clamp(20px,5.6vw,23px);line-height:1.35;color:var(--rk-ink-head);word-spacing:.14em}
/* WO09: a personal page carries its guest's name (baked into that page only) as its own line above «دعوة خاصة». */
.guest-name{margin:0 0 clamp(2px,.5svh,5px);font-size:clamp(17px,4.6vw,19px);line-height:1.4;color:var(--rk-ink-head);text-align:center;text-wrap:balance}

.invitation{display:flex;flex-direction:column;align-items:center;margin-top:clamp(14px,2.2svh,22px);max-width:34rem}
.invitation-copy{margin-top:clamp(6px,1.1svh,10px);max-width:22em;font-size:clamp(14px,3.6vw,15px);line-height:1.66;
  color:var(--rk-ink-body);text-wrap:balance}
.editorial-host{color:var(--rk-ink-head)}
.event-when{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:2px .9em;margin-top:clamp(5px,1svh,10px);
  font-size:clamp(13px,3.4vw,14px);line-height:1.5;color:var(--rk-ink-body)}

/* Map button — frosted glass + stone hairline (owner choice B, refined). The link keeps a 44 px hit area; the visible
   pill is inset inside it (--pill-inset), so it reads small and balances the logo. The pane is frosted (backdrop
   blur on this small area only) with a faint top-edge highlight; a 1.5 px hairline in the logo stone (built by app.js
   as two half-pills from the top centre) draws round to the bottom centre. Then a 3 px float. */
/* WO08 final: the map pill is the PRIMARY action of the footer bar — a touch more weight than «اكتشفوا ROCK»
   (taller visible pill, head-tint label, a small location pin). A plain <a href>: it works with zero JS (then a CSS
   hairline stands in for the drawn one). */
.map-link{--pill-inset:5px;position:relative;display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 17px;
  border-radius:999px;color:var(--rk-ink-head);font-size:13.5px;line-height:1;letter-spacing:.01em;white-space:nowrap}
.map-link:not(.has-edge)::after{content:'';position:absolute;inset:var(--pill-inset) 0;border-radius:999px;border:1.5px solid var(--rk-stone);pointer-events:none}
.map-text::before{content:'';display:block;flex:none;width:10px;height:13px;background:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 13'%3E%3Cpath d='M5 12.3S1 7.9 1 5a4 4 0 0 1 8 0c0 2.9-4 7.3-4 7.3z' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3Ccircle cx='5' cy='5' r='1.4' fill='%23000'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 13'%3E%3Cpath d='M5 12.3S1 7.9 1 5a4 4 0 0 1 8 0c0 2.9-4 7.3-4 7.3z' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3Ccircle cx='5' cy='5' r='1.4' fill='%23000'/%3E%3C/svg%3E") center/contain no-repeat}

/* Pill light (both pills): warm golden sunlight on a stone edge. A long, wide highlight — a near-white core with a
   warm gold falloff — glides along the hairline (right → left), with a faint bloom 1.5 px either side of the edge that
   exists only while it passes. app.js adds <span class="pill-light"><i class="pill-bloom"></i><i class="pill-core"></i>
   </span> to each pill; both layers are masked to rings, so the light never reaches the pane or the label. */
.pill-light{position:absolute;inset:var(--pill-inset) 0;border-radius:999px;pointer-events:none;opacity:0}
.pill-light>i{position:absolute;border-radius:999px;
  background:linear-gradient(100deg,rgba(var(--rk-light-gold-rgb),0) 20%,rgba(var(--rk-light-gold-rgb),.3) 33%,rgba(var(--rk-light-gold-rgb),.75) 43%,
    var(--rk-light-core) 50%,rgba(var(--rk-light-gold-rgb),.75) 57%,rgba(var(--rk-light-gold-rgb),.3) 67%,rgba(var(--rk-light-gold-rgb),0) 80%) 0 0/300% 100% no-repeat;
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}
.pill-core{inset:0;padding:1.5px}
.pill-bloom{inset:-1.5px;padding:4.5px;opacity:.32}
/* The bloom follows only the brightest part of the light, so it never reads as a second outline. */
.pill-light>.pill-bloom{background-image:linear-gradient(100deg,rgba(var(--rk-light-gold-rgb),0) 40%,rgba(var(--rk-light-gold-rgb),.55) 46.5%,
  var(--rk-light-core) 50%,rgba(var(--rk-light-gold-rgb),.55) 53.5%,rgba(var(--rk-light-gold-rgb),0) 60%)}
.map-link::before{content:'';position:absolute;inset:var(--pill-inset) 0;border-radius:999px;
  background:linear-gradient(180deg,rgba(var(--rk-field-rgb),calc(var(--rk-smoke) - .12)) 0%,rgba(var(--rk-field-rgb),var(--rk-smoke)) 100%);
  box-shadow:inset 0 1px 0 rgba(var(--rk-sheen-rgb),.22);-webkit-backdrop-filter:blur(10px) saturate(1.05);backdrop-filter:blur(10px) saturate(1.05)}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){.map-link::before{background-color:rgba(var(--rk-field-rgb),.45)}}
.map-link:hover::before{background-color:rgba(var(--rk-field-rgb),.1)}
.map-link:active::before{background-color:rgba(var(--rk-field-rgb),.2)}
.map-link:focus-visible{outline:none}
.map-link:focus-visible::before,.keep-link:focus-visible::before{outline:2px solid var(--rk-stone);outline-offset:3px}
/* the pin sits to the LEFT of the label (owner, round 2): row-reverse in RTL puts ::before at the end side */
.map-text{position:relative;display:inline-flex;flex-direction:row-reverse;align-items:center;gap:6px}
.map-edge{position:absolute;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none}
.map-edge .edge{fill:none;stroke:var(--rk-stone);stroke-width:1.5;stroke-dasharray:1 1;stroke-dashoffset:0}

/* Footer action bar (WO08 final): two pills side by side — «موقع المشروع» (primary) at the start side (right, RTL) and
   «اكتشفوا ROCK» (secondary) at the end side; the same stone hairline and frosted pane; 44 px hit areas. */
.register-ground{position:relative;flex-direction:row;align-items:center;justify-content:center;gap:10px;padding-top:10px}
.register-ground::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;
  background:linear-gradient(90deg,rgba(207,199,184,0),var(--rk-hairline) 14%,var(--rk-hairline) 86%,rgba(207,199,184,0))}
.keep-link{--pill-inset:5px;position:relative;isolation:isolate;display:inline-flex;align-items:center;gap:6px;min-height:44px;padding:0 17px;
  font-size:13.5px;line-height:1;color:var(--rk-stone);white-space:nowrap}
.keep-link::before{content:'';position:absolute;inset:var(--pill-inset) 0;z-index:-1;border-radius:999px;border:1.5px solid var(--rk-stone);
  background:linear-gradient(180deg,rgba(var(--rk-field-rgb),calc(var(--rk-smoke) - .12)) 0%,rgba(var(--rk-field-rgb),var(--rk-smoke)) 100%);
  box-shadow:inset 0 1px 0 rgba(var(--rk-sheen-rgb),.18);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
.keep-link:focus-visible{outline:none}
.keep-link::after{content:'';flex:none;width:12px;height:9px;background:currentColor;transition:transform var(--rk-t1) var(--rk-ease);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 9'%3E%3Cpath d='M11.5 4.5H1.3M4.7 1.1L1.2 4.5l3.5 3.4' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 9'%3E%3Cpath d='M11.5 4.5H1.3M4.7 1.1L1.2 4.5l3.5 3.4' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E") center/contain no-repeat}
.keep-link:hover::after{transform:translate3d(-2px,0,0)}
.keep-link:hover::before{background-color:rgba(var(--rk-field-rgb),.1)}

/* Countdown (WO08 final): ONE quiet line directly under the date — days · hours · minutes, no seconds; small, body
   tint, thin separators; no big numerals, no label row. True values only. */
.countdown{display:flex;justify-content:center;align-items:baseline;margin:clamp(4px,.8svh,8px) 0 0;direction:rtl;
  font-size:12.5px;line-height:1.5;color:var(--rk-ink-body)}
.count-unit{position:relative;display:inline-flex;align-items:baseline;gap:.28em;padding:0 .75em}
.count-unit+.count-unit::before{content:'';position:absolute;inset-inline-start:0;top:.4em;width:1px;height:.85em;background:var(--rk-hairline)}
.count-number{display:inline-block;min-width:var(--num-w,1.3em);text-align:center;font-variant-numeric:tabular-nums;font-feature-settings:'tnum' 1}
.count-label{font-size:inherit}

/* ── Entrance (WO07) ──────────────────────────────────────────────────────────────────────
   Base styles above ARE the final state. body.entrance (first visit, motion allowed) hides and then plays it:
   film-relative from [data-phase=film] (film frame 0), invitation-relative from [data-phase=invitation] (film 6.2 s).
   Removing .entrance (skip, fallback, reduced motion, returning visit) shows the final state; .is-final fades it in.
   Arabic is animated by line only — never by letter — so the script stays joined. */
@keyframes rk-reveal{from{clip-path:inset(100% 0 0 0)}to{clip-path:inset(0 0 0 0)}}
@keyframes rk-rise{from{opacity:0;transform:translate3d(0,10px,0);filter:blur(4px)}to{opacity:1;transform:none;filter:blur(0)}}
@keyframes rk-rise-8{from{opacity:0;transform:translate3d(0,8px,0)}to{opacity:1;transform:none}}
@keyframes rk-fade{from{opacity:0}to{opacity:1}}
@keyframes rk-open{from{clip-path:inset(0 50% 0 50% round 999px)}to{clip-path:inset(0 0 0 0 round 999px)}}
@keyframes rk-draw{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
@keyframes rk-rule{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes rk-float{0%,100%{transform:translate3d(0,0,0)}50%{transform:translate3d(0,-3px,0)}}
/* Edge light: one glide of ~2.5 s per 6 s cycle (right → left, reading direction), fading in and out; then still. */
/* Pill light: one 1200 ms glide per 6 s cycle (20%), fading in and out; the rest of the cycle is still. */
@keyframes rk-pill-light{0%{opacity:0}4%{opacity:1}15%{opacity:1}20%,100%{opacity:0}}
@keyframes rk-pill-glide{0%{background-position:0 0}20%,100%{background-position:100% 0}}
/* Logo rise: film-tracked stops (the film's own motion, linear between measured positions: 0.6 / 0.9 / 1.0 / 1.2 /
   1.4 s), then the shared curve decelerates it into its final place at 3.0 s. */
@keyframes rk-logo-rise{
  0%{transform:translate3d(0,var(--lr0,0px),0);animation-timing-function:linear}
  12.5%{transform:translate3d(0,var(--lr1,0px),0);animation-timing-function:linear}
  16.667%{transform:translate3d(0,var(--lr2,0px),0);animation-timing-function:linear}
  25%{transform:translate3d(0,var(--lr3,0px),0);animation-timing-function:linear}
  33.333%{transform:translate3d(0,var(--lr4,0px),0);animation-timing-function:var(--rk-ease)}
  100%{transform:translate3d(0,0,0)}}

/* Hidden until their moment */
.entrance :is(.brand>*,.guest-name,.label-text,.invitation-copy .w,.event-when>*,.countdown,.map-text,.keep-link){opacity:0}
.entrance .map-link::before{clip-path:inset(0 50% 0 50% round 999px)}
.entrance .map-edge .edge{stroke-dashoffset:1}
.entrance .register-ground::before{transform:scaleX(0)}
.entrance .glass-ground{opacity:0;transition:none}
.label-text,.invitation-copy .w,.event-when>*{display:inline-block}

/* Film clock (0 = first rendered frame): the logo rises 600–3000 with the wireframe lines and settles; the text begins
   with «دعوة خاصة» at the invitation trigger (film 6.2 s). */
.entrance:is([data-phase=film],[data-phase=invitation]) .brand>*{opacity:1;
  animation:rk-reveal var(--rk-t3) var(--rk-ease) calc(600ms - var(--film-t0,0ms)) both,rk-logo-rise var(--rk-t3) linear calc(600ms - var(--film-t0,0ms)) both}
/* No-film story path (slow connection, film not started in 4 s, film error): no lines to rise with — the logo reveals in
   place, bottom to top, with the same small rise as the text. The day frame is shown from the start. */
.entrance.no-film:is([data-phase=film],[data-phase=invitation]) .brand>*{animation:rk-reveal var(--rk-t3) var(--rk-ease) 300ms both,rk-rise var(--rk-t2) var(--rk-ease) 300ms both}
.no-film .background-poster{opacity:1}
.no-film .film-veil{transition:none}

/* Invitation clock (0 = film 6.2 s; LPM image path 6.4 s): one event at a time — the sweep has finished. */
.entrance[data-phase=invitation] :is(.guest-name,.label-text){animation:rk-rise var(--rk-t2) var(--rk-ease) 0ms both}
.entrance[data-phase=invitation] .invitation-copy .w{animation:rk-rise var(--rk-t2) var(--rk-ease) calc((var(--ln,0) + 1) * var(--rk-stagger)) both}
.entrance[data-phase=invitation] .event-when>:nth-child(1){animation:rk-rise-8 var(--rk-t2) var(--rk-ease) 600ms both}
.entrance[data-phase=invitation] .event-when>:nth-child(2){animation:rk-rise-8 var(--rk-t2) var(--rk-ease) calc(600ms + var(--rk-stagger)) both}
/* The countdown line +900; the footer action bar: rule +1500, the map pill draws from its centre +1500 (label +2100),
   «اكتشفوا ROCK» +1950. Settled at +3150 (app.js ENTRANCE_TAIL_MS). */
.entrance[data-phase=invitation] .countdown{animation:rk-rise-8 var(--rk-t2) var(--rk-ease) 900ms both}
.entrance[data-phase=invitation] .glass-ground{animation:rk-fade var(--rk-t2) var(--rk-ease) 1500ms both}
.entrance[data-phase=invitation] .register-ground::before{animation:rk-rule var(--rk-t2) var(--rk-ease) 1500ms both}
.entrance[data-phase=invitation] .map-link::before{animation:rk-open var(--rk-t2) var(--rk-ease) 1500ms both}
.entrance[data-phase=invitation] .map-edge .edge{animation:rk-draw var(--rk-t2) var(--rk-ease) 1500ms both}
.entrance[data-phase=invitation] .map-text{animation:rk-rise-8 var(--rk-t1) var(--rk-ease) 2100ms both}
.entrance[data-phase=invitation] .keep-link{animation:rk-rise-8 var(--rk-t2) var(--rk-ease) 1950ms both}

/* Ambient, after the button is in place: float from +2100 ms, first edge light +2900 ms (final state: 600 / 1200 ms). */
[data-phase=invitation] .map-link{animation:rk-float var(--rk-cycle) var(--rk-wave) var(--rk-float-at,2700ms) infinite}
/* First pass right after each pill appears — map +2200 ms (drawn by +2100), explore +3700 ms (risen by ≈+3700) —
   then every 6 s. 1.5 s apart, each pass 1.2 s: the two never fire together. */
[data-phase=invitation] .map-link .pill-light{animation:rk-pill-light var(--rk-cycle) var(--rk-wave) var(--rk-light-at,2800ms) infinite}
[data-phase=invitation] .map-link .pill-light>i{animation:rk-pill-glide var(--rk-cycle) var(--rk-wave) var(--rk-light-at,2800ms) infinite}
[data-phase=invitation] .keep-link .pill-light{animation:rk-pill-light var(--rk-cycle) var(--rk-wave) calc(var(--rk-light-at,2800ms) + 1500ms) infinite}
[data-phase=invitation] .keep-link .pill-light>i{animation:rk-pill-glide var(--rk-cycle) var(--rk-wave) calc(var(--rk-light-at,2800ms) + 1500ms) infinite}

/* Final state at once with a 600 ms fade: returning visit, tap-to-skip, film fallback. */
.is-final{--rk-float-at:var(--rk-t1);--rk-light-at:var(--rk-t1)}
.is-final .stage-copy{animation:rk-fade var(--rk-t1) var(--rk-ease) both}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition:none!important;animation:none!important}
  .is-final .stage-copy{animation:rk-fade var(--rk-t1) linear both!important}
}

/* ── Night finale (WO08, owner-approved timing) ─────────────────────────────────────────────
   Built by night.js inside the film box. Round 3 (owner): NO hold — the finale starts (F = 0) 0.5 s before the last
   text line lands, and the change is visible from its first frame to its last (≈6 s):
   0.0 s  the sun leaves the house (1.2 s) AND the sky starts its top-down dusk (4.1 s) with the night grade; deep
          night carries the same descent on to 6.0 s; the day grade's sky part leaves from 0.65 s (3.3 s)
   3.1 s  lights during the descent, in order: windows → lower canopy → upper soffit (+ its bounce, with the house's
          day grade) → LED line, the hero light: up the left edge 450 ms, across the top 450 ms → fence-base lines →
          neighbours (last, dimmer)
   5.45 s the pills' golden light right after the house lights (map pill, then «اكتشفوا ROCK» 1.5 s later)
   6.0 s  lit night. Text never moves.
   Light layers are patches of the lit plate, crossfaded with normal blending: the building ends on its own colours. */
.night{position:absolute;inset:0;z-index:2;pointer-events:none}
.night-plate{position:absolute;left:var(--np-x,0);top:var(--np-y,0);width:var(--np-w,100%);height:var(--np-h,100%);isolation:isolate}
.night-plate>.night-house,.night-plate>.night-sky{position:absolute;inset:0;width:100%;height:100%;display:block}
.night-house{opacity:0;-webkit-mask-image:linear-gradient(180deg,transparent 40%,#000 48%);mask-image:linear-gradient(180deg,transparent 40%,#000 48%)}
.night-sky{-webkit-mask-image:linear-gradient(180deg,#000 0 33.333%,transparent 66.667%);mask-image:linear-gradient(180deg,#000 0 33.333%,transparent 66.667%);
  -webkit-mask-size:100% 300%;mask-size:100% 300%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:0 100%;mask-position:0 100%}
.night-lights{position:absolute;inset:0}
.night-light{position:absolute;left:var(--l);top:var(--t);width:var(--w);height:var(--h);display:block;opacity:0}
.night-light-led-up{clip-path:inset(100% 0 0 0)}.night-light-led-across{clip-path:inset(0 100% 0 0)}
/* Deep night (owner, round 5): ONE continuous natural night sky. It deepens smoothly toward the top and is only a few
   percent lighter near the horizon, with a long falloff and no boundary; it covers the sky down to the roofline and
   fades out within 2 % below it, on the building's own edge. Nearly opaque, so the sky is this gradient, not the plate's
   brighter horizon band. A fine noise layer (1.6 %) dithers the dark gradient so it does not band on iPhone. */
/* Both sky layers sit inside the plate (frame coordinates) under the sky mask, so they cover exactly the sky. */
.night-plate>.night-deep,.night-plate>.night-sky-veil{position:absolute;inset:0;opacity:0;
  -webkit-mask-image:var(--sky-mask);mask-image:var(--sky-mask);-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
.night-plate>.night-deep{background:linear-gradient(180deg,rgba(6,9,20,.94) 0%,rgba(7,10,22,.93) 15%,rgba(8,12,25,.915) 30%,rgba(10,14,29,.9) 42%,
    rgba(11,16,32,.885) 50%,rgba(12,17,34,.875) 62%,rgba(13,18,36,.87) 100%)}
.night-deep::after{content:'';position:absolute;inset:0;opacity:.016;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E") 0 0/180px 180px}
/* The night grade on the sky: enough dim on the blue-hour sky to hold the text before deep night arrives. */
.night-plate>.night-sky-veil{background:linear-gradient(180deg,rgba(var(--rk-grade-rgb),.5) 0%,rgba(var(--rk-grade-rgb),.44) 50%,rgba(var(--rk-grade-rgb),.4) 100%)}
/* Night grade: enough dim on the blue-hour sky to hold the text before deep night arrives (measured on rendered
   pixels through the finale); it ends at the roofline (never on the building)
   and returns only below the fence, under the footer. */
/* Stage-level night grade: only the street under the footer (the sky part is .night-sky-veil, under the sky mask). */
.night-veil{position:absolute;inset:0;z-index:2;pointer-events:none;opacity:0;
  background:linear-gradient(180deg,rgba(var(--rk-grade-rgb),0) 0%,rgba(var(--rk-grade-rgb),0) 91%,rgba(var(--rk-grade-rgb),.42) 100%)}

@keyframes rk-night-dusk{from{-webkit-mask-position:0 100%;mask-position:0 100%}to{-webkit-mask-position:0 0;mask-position:0 0}}
@keyframes rk-night-out{from{opacity:1}to{opacity:0}}
@keyframes rk-lamp{from{opacity:0}to{opacity:var(--to,1)}}
@keyframes rk-led-up{from{clip-path:inset(100% 0 0 0)}to{clip-path:inset(0 0 0 0)}}
@keyframes rk-led-across{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}
/* The pills' light, re-timed by the finale (new names restart the cycle at F). */
@keyframes rk-pill-light-n{0%{opacity:0}4%{opacity:1}15%{opacity:1}20%,100%{opacity:0}}
@keyframes rk-pill-glide-n{0%{background-position:0 0}20%,100%{background-position:100% 0}}

/* Round 6 (owner): the sky is ONE continuous descent, F 1.2 s → 8.3 s, a single ease-in-out curve slowing at the end —
   no plateau, no second start. Every sky layer starts together; the dusk sweep and the grade hand-over complete by
   6.0 s inside it, and deep night carries the same descent on to 8.3 s. The lights fire DURING the descent (windows at its
   midpoint); deep night completes ≈1.6 s after the LED line. Total 8.3 s (was 10.3 s). */
.night-play .night-house{animation:rk-fade 1200ms var(--rk-wave) 0ms both}
.night-play .night-sky{animation:rk-night-dusk 4100ms var(--rk-wave) 0ms both}
/* The day grade splits along the sky mask (two complementary masks recombine the same grade): its SKY part leaves on
   the sky's curve; its HOUSE part leaves together with the wall's light — the unlit plate's wall is brighter than the lit
   plate's, so letting the grade go first made the wall rise and then dim back (the pulse the owner saw). */
/* Hand-over (owner, live iPhone review — an ~80 ms flash of the ungraded day frame): Safari decodes a mask image on its
   FIRST paint, so a mask first applied at the hand-over left the grade empty for a few frames. Now both copies carry their
   masks from the start, are painted at 0.4 % while the film/day frame holds (.night-armed, several frames), and take
   over from the unmasked day grade in ONE frame (.night-split) — the two masks recombine the same picture. */
.night-house-veil,.night-sky-grade{visibility:hidden}
.night-sky-grade{-webkit-mask:var(--sky-mask) var(--np-x,0) var(--np-y,0)/var(--np-w,100%) var(--np-h,100%) no-repeat;mask:var(--sky-mask) var(--np-x,0) var(--np-y,0)/var(--np-w,100%) var(--np-h,100%) no-repeat}
.night-house-veil{-webkit-mask:linear-gradient(#000 0 0),var(--sky-mask) var(--np-x,0) var(--np-y,0)/var(--np-w,100%) var(--np-h,100%) no-repeat;-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0),var(--sky-mask) var(--np-x,0) var(--np-y,0)/var(--np-w,100%) var(--np-h,100%) no-repeat;mask-composite:exclude}
.night-armed :is(.night-house-veil,.night-sky-grade){visibility:visible;opacity:.004;transition:none}
.night-armed .night-plate>:is(.night-house,.night-deep,.night-sky-veil){opacity:.004}
.night-split :is(.night-house-veil,.night-sky-grade){opacity:1}
.night-split .film-veil:not(.night-house-veil):not(.night-sky-grade){visibility:hidden}
/* (Its sky part lets go from 2.0 s — once the darkening layers are under way — so the sky never brightens first.) */
.night-play .night-sky-grade{animation:rk-night-out 3300ms var(--rk-wave) 650ms both}
.night-play .night-house-veil{animation:rk-night-out 820ms var(--rk-wave) 3600ms both}
.night-play :is(.night-veil,.night-sky-veil){animation:rk-fade 4100ms var(--rk-wave) 0ms both}
.night-play .night-light-windows{animation:rk-lamp 740ms var(--rk-wave) 3100ms both}
.night-play .night-light-canopy{animation:rk-lamp 740ms var(--rk-wave) 3350ms both}
.night-play :is(.night-light-soffit,.night-light-ambient){animation:rk-lamp 820ms var(--rk-wave) 3600ms both}
.night-play .night-light-led-up{opacity:1;animation:rk-led-up 450ms var(--rk-wave) 3850ms both}
.night-play .night-light-led-across{opacity:1;animation:rk-led-across 450ms var(--rk-wave) 4300ms both}
.night-play .night-light-fence{animation:rk-lamp 660ms var(--rk-wave) 4750ms both}
.night-play .night-light-neighbours{--to:.72;animation:rk-lamp 530ms var(--rk-wave) 5050ms both}
.night-play .night-deep{animation:rk-fade 6000ms var(--rk-wave) 0ms both}
/* The pills' light arrives right after the house lights (fence lines done at 7.5 s). */
.night-play .map-link .pill-light{animation:rk-pill-light-n var(--rk-cycle) var(--rk-wave) 5450ms infinite}
.night-play .map-link .pill-light>i{animation:rk-pill-glide-n var(--rk-cycle) var(--rk-wave) 5450ms infinite}
.night-play .keep-link .pill-light{animation:rk-pill-light-n var(--rk-cycle) var(--rk-wave) 6950ms infinite}
.night-play .keep-link .pill-light>i{animation:rk-pill-glide-n var(--rk-cycle) var(--rk-wave) 6950ms infinite}

/* Final state — lit night at once (skip, reduced motion, fallbacks), never through the day frame: primed at 0.4 %
   (.night-priming), faded in UNDER the day grade with the footer's night grade (.night-in), and only then does the day
   grade leave (.night-day-out). A tap during the finale instead runs the finale's remaining course within 600 ms. */
.night-lit :is(.night-house,.night-light){opacity:1}
.night-lit .night-light-neighbours{opacity:.72}
.night-lit .night-sky{-webkit-mask-position:0 0;mask-position:0 0}
.night-lit :is(.night-light-led-up,.night-light-led-across){clip-path:none}
.night-lit :is(.night-deep,.night-veil,.night-sky-veil){opacity:1}
.night-lit :is(.night,.night-veil){transition:opacity var(--rk-t1) var(--rk-ease)}
.night-priming :is(.night,.night-veil){opacity:.004;transition:none}
.night-day-out .film-veil{opacity:0;transition:opacity var(--rk-t1) var(--rk-ease)}

/* Data Saver: one pre-composited lit-night image instead of the day frame and the night layers. */
.night-plate>.night-composite{position:absolute;inset:0;width:100%;height:100%;display:block}

/* Static completion (boot.js) when the app could not start: the final frame behind the complete text. */
.boot-poster{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% var(--film-y);display:block}

/* Short phones (≈9:16 or wider, e.g. iPhone SE in Safari): keep the sky, trim the street. */
@media (max-height:700px) and (max-aspect-ratio:4/5){
  :root{--film-y:30%;--logo-h:clamp(150px,24svh,162px)}
  .stage-copy{padding-top:calc(var(--safe-t) + 8px)}
  .invitation{margin-top:6px}
  .invitation-label{font-size:19px;line-height:1.3}
  .invitation-copy{margin-top:3px;font-size:13.5px;line-height:1.55;max-width:24em}
  .event-when{margin-top:3px;font-size:12.5px;line-height:1.45}
  .map-link{margin-top:2px}
}

/* ── Laptop / wide: the film is a portrait plate; the invitation stands beside it on glass. ── */
@media (min-width:700px) and (min-aspect-ratio:4/5){
  .stage{display:grid;grid-template-columns:minmax(0,1fr) auto}
  .stage-film{position:relative;inset:auto;grid-column:2;grid-row:1;height:100%;aspect-ratio:9/16}
  :root{--film-y:50%;--logo-h:clamp(180px,28vh,260px)}
  /* The residence's light reaches faintly into the room once the house is complete; charcoal stays dominant. */
  .atmosphere{display:block;position:absolute;inset:-12%;z-index:0;opacity:0;pointer-events:none;
    background:url(./assets/posters/WO03C/rock-v2-last-frame.b63c68a3b6.jpg) 50% 40%/cover no-repeat;
    filter:blur(80px) saturate(.7) brightness(.5);transition:opacity 3.2s var(--rk-ease) .3s}
  [data-phase=invitation] .atmosphere{opacity:.2}
  /* The same single grade, over the film plate only. */
  .film-veil,.night-veil{left:0;right:auto;width:calc(100dvh * 9 / 16)}
  /* Glass fin: a frosted vertical pane standing at the film's inner edge, with one glazing hairline.
     It arrives with the film and stays — the film is always seen through the edge of the glass. */
  .glass-sky{display:block;--film-w:calc(100dvh * 9 / 16);top:0;bottom:0;height:auto;left:calc(var(--film-w) - 46px);right:auto;width:70px}
  .glass-sky>i{-webkit-mask-image:linear-gradient(90deg,transparent 0,#000 30%,#000 66%,transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 30%,#000 66%,transparent 100%)}
  .glass-sky .glass-body{-webkit-backdrop-filter:blur(12px) brightness(.78) saturate(1.05);backdrop-filter:blur(12px) brightness(.78) saturate(1.05);
    background:linear-gradient(90deg,rgba(35,35,35,0) 0%,rgba(35,35,35,.35) 66%,rgba(35,35,35,.7) 100%)}
  .glass-sky .glass-frost{-webkit-backdrop-filter:blur(26px);backdrop-filter:blur(26px);
    -webkit-mask-image:linear-gradient(90deg,transparent 30%,#000 60%,#000 66%,transparent 90%);mask-image:linear-gradient(90deg,transparent 30%,#000 60%,#000 66%,transparent 90%)}
  .glass-sky .glass-sheen{-webkit-mask-image:none;mask-image:none;
    background:linear-gradient(90deg,transparent calc(65.7% - 1px),rgba(var(--rk-sheen-rgb),.2) 65.7%,transparent calc(65.7% + 1px))}
  .glass-ground{display:none}
  .stage-copy{position:relative;inset:auto;grid-column:1;grid-row:1;justify-content:center;
    padding:clamp(28px,7vh,72px) clamp(32px,6vw,96px)}
  .register-sky{flex:0 0 auto}
  /* Footer row at the foot of the copy column, centred to the text measure. */
  .register-ground{position:absolute;left:50%;bottom:clamp(22px,4.6vh,46px);width:min(30rem,calc(100% - 2 * clamp(32px,6vw,96px)));transform:translateX(-50%)}
  .invitation-label{font-size:clamp(23px,1.8vw,28px)}
  .invitation{margin-top:clamp(18px,3.2vh,32px)}
  .invitation-copy{margin-top:clamp(14px,2.2vh,22px);font-size:clamp(16px,1.22vw,18.5px);max-width:23em}
  .event-when{margin-top:clamp(12px,2vh,20px);font-size:clamp(14.5px,1.1vw,16.5px)}
  .map-link{font-size:14px;padding:0 20px}
  .keep-link{font-size:14px;padding:0 20px}
  .countdown{font-size:13.5px}
}
/* Short wide screens (landscape phones, small laptop windows). */
@media (min-width:700px) and (min-aspect-ratio:4/5) and (max-height:620px){
  :root{--logo-h:clamp(110px,24vh,160px)}
  .stage-copy{padding-block:16px}
  .invitation{margin-top:8px}
  .invitation-copy{margin-top:6px;font-size:15px;line-height:1.6}
  .event-when{margin-top:6px;font-size:14px}
  .map-link{margin-top:4px}
  .register-ground{position:relative;left:auto;bottom:auto;transform:none;width:min(30rem,100%);margin-top:18px}

}

/* WO09: a personal page carries one more line (its guest's name) — on phones the logo gives back that room (≈10 %),
   so the text block still clears the roofline. The general page is unchanged; laptops are unchanged. */
@media (max-aspect-ratio:4/5){
  .stage-copy:has(.guest-name){--logo-h:clamp(146px,19svh,178px)}
}
@media (max-height:700px) and (max-aspect-ratio:4/5){
  .stage-copy:has(.guest-name){--logo-h:clamp(134px,21svh,144px)}
}
