/* ==========================================================================
   PAVILION BY RAMS - the site stylesheet
   ==========================================================================

   One file, in cascade order. This was fourteen separate sheets whose order
   lived in the <link> tags of index.html, with a rule -- keep 99-type last --
   that someone had to remember. Concatenated here, the order IS the file.
   The banners below are the old filenames, so anything written against them
   still finds its way.

   THREE FILES, AND WHY IT IS NOT ONE
   ----------------------------------
   01-brand.css   loads BEFORE this one. It is not merged in because it is the
                  layer all three documents share: index.html, sections/
                  location.html and sections/map.html each link it for the
                  --pv-* palette and the @font-face rules. Folding it in here
                  would mean a second copy for the iframes, and two copies of
                  the brand palette is a worse problem than two files.

   98-rtl.css     loads AFTER this one, for ar/ku only. Its rules are bare
                  selectors that assume an RTL document -- `body{letter-
                  spacing:0;line-height:1.85}` among them -- so loading it
                  unconditionally would reset English typography. build.py
                  appends it after the last stylesheet link.

   The iframes deliberately do NOT link this file. They are separate documents
   with their own tokens, and map.html defines its own --z against the plate's
   600px width rather than the viewport. Giving them the whole site cascade
   would put the map at the mercy of override order. Change a token here and
   check whether those two mirrors need it too.

   Sections, in order:

     01 . 00-tokens        08 . 70-villas
     02 . 05-lakefilm      09 . 75-villa-detail
     03 . 10-nav           10 . 80-hours
     04 . 20-hero          11 . 90-register
     05 . 30-intro         12 . 95-panorama
     06 . 40-location      13 . 99-type      <- keep last
     07 . 50-lake
   ========================================================================== */

/* ==============================================================================
  01 . 00-tokens.css
  Page tokens, the --z fit factor, and shared primitives (.btn, .h1/.h2, .eyebrow).
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — TOKENS & PAGE PRIMITIVES

   Colour ramp, spacing, easing, plus the shared building blocks every
   section uses: .wrap, .h2, .lede, .btn, .sec, .sechead and the reveal.
   ═══════════════════════════════════════════════════════════════════════ */

/* Page-level ramp, derived from the brand primaries in 01-brand.css.

   THE WHOLE PAGE IS PAVILION BLUE #000851. Every ground, veil, scrim and
   translucent panel resolves to it, per the brand guidelines — there is no
   longer a second ground colour, so no section reads as a band against its
   neighbours. --navy-950 is kept pointing at the same value rather than
   deleted: it is still named by a dozen rules as the FOREGROUND on a light
   fill (dark text on the aqua buttons, on ::selection, on the ivory hovers),
   and collapsing it here changes those in one place instead of twelve.
   sections/location.html mirrors this block and was already set this way.

   850/800/700/600 are unused now. They are the derived depth either side of
   the brand blue and are left defined for when something needs a lift. */
/* The page ramp, the font roles, spacing, easing and the --z fit factor all
   moved to 00-shared.css, which this page and both iframe documents link.
   They were mirrored by hand into sections/location.html before that; the
   long note explaining --z now lives beside the values, in that file. */
html{scroll-behavior:smooth}
/* ── the scrollbar ────────────────────────────────────────────────────────
   Not baked in — it has been stylable for years, in two different ways, and
   the trick is not to do both at once. Firefox and Chrome 121+ take the
   standard scrollbar-color; everything older takes the WebKit pseudo-
   elements, and modern Chrome IGNORES those the moment scrollbar-color is
   set. So the pseudo-elements go behind @supports, where only the browsers
   that need them will see them.

   COLOUR ONLY on the standard path, deliberately. A custom width changes how
   much viewport the page is given, and this page centres its brand in a grid
   that measures exactly that — a nav that drifts on Chrome and not on Firefox
   would be a genuinely horrible thing to debug later.

   scrollbar-color inherits, so the strip of thumbnails in the villa overlay
   picks it up as well; it keeps its own narrower width below. */
html{scrollbar-color:var(--pv-gold) var(--navy-950)}
@supports not (scrollbar-color:auto){
  ::-webkit-scrollbar{width:14px;height:14px;background:var(--navy-950)}
  ::-webkit-scrollbar-thumb{background:var(--pv-gold);border-radius:999px;
    border:4px solid var(--navy-950)}
  ::-webkit-scrollbar-thumb:hover{background:#DCC489}
}
body{margin:0;background:var(--navy-900);color:var(--ivory);font-family:var(--f-display);font-size:16px;line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:hidden}
img,svg,canvas{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;border:0;background:none;cursor:pointer}
::selection{background:var(--gold-500);color:var(--navy-950)}

/* film grain, kept cheap */
body::after{
  content:"";position:fixed;inset:-8%;z-index:9999;pointer-events:none;opacity:.045;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:grain 6s steps(6) infinite;
}
@keyframes grain{0%,100%{transform:translate(0,0)}16%{transform:translate(-2%,-1.5%)}33%{transform:translate(1.5%,-2.5%)}50%{transform:translate(-1.5%,2%)}66%{transform:translate(2.5%,1%)}83%{transform:translate(-1%,-2%)}}

.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad)}
/* No rule in front of an eyebrow anywhere. It was a 34px hairline leading
   into the label, which worked at the top of a section and read as a stray
   mark everywhere else; with the last of the numbered eyebrows gone there is
   nothing left for it to lead into. The `gap` went with it — it existed to
   hold the label off that rule, and an eyebrow has one child. */
.eyebrow{font-size:calc(11px * var(--zt));font-weight:600;letter-spacing:.34em;text-transform:uppercase;color:var(--gold-400);display:flex;align-items:center;margin:0}
.h2{font-size:clamp(2.2rem,6.2vw,calc(5.6rem * var(--z)));font-weight:600;line-height:.92;letter-spacing:-.035em;text-transform:uppercase;margin:0}
.h3{font-size:clamp(1.3rem,2.4vw,calc(2.1rem * var(--z)));font-weight:600;line-height:1.08;letter-spacing:-.02em;margin:0}
.lede{font-family:var(--f-accent);font-size:clamp(1.25rem,2.3vw,calc(1.95rem * var(--z)));line-height:1.45;font-weight:300;color:rgba(255,255,255,.84);max-width:60ch}
.body{font-size:calc(15.5px * var(--zt));line-height:1.75;color:var(--mute);max-width:56ch}
.num{font-variant-numeric:tabular-nums}
.rule{height:1px;background:var(--hair);border:0;margin:0}
.scene{position:absolute;inset:0}

.btn{display:inline-flex;align-items:center;gap:12px;padding:calc(16px * var(--z)) calc(28px * var(--z));border:1px solid var(--gold-500);border-radius:999px;
  font-size:calc(11.5px * var(--zt));font-weight:600;letter-spacing:.2em;text-transform:uppercase;
  position:relative;overflow:hidden;isolation:isolate;transition:color .5s var(--e)}
.btn::after{content:"";position:absolute;inset:0;z-index:-1;background:var(--gold-500);transform:translateY(101%);transition:transform .55s var(--e)}
.btn:hover{color:var(--navy-950)}
.btn:hover::after{transform:translateY(0)}
.btn--solid{background:var(--gold-500);color:var(--navy-950)}
/* The registration button is gold, on the home page and on contact.html.
   BORDER TOO: .btn above sets `border:1px solid var(--gold-500)`, and that
   token holds the aqua #60EDF4 despite its name, so overriding only the fill
   left a turquoise ring around a gold pill. The hover wash it slides up is
   --ivory from .btn--solid, which still reads against gold. */
button[type="submit"].btn--solid{background:var(--pv-gold);border-color:var(--pv-gold);
  color:var(--navy-950)}
.btn--solid::after{background:var(--ivory)}

/* ── glass ────────────────────────────────────────────────────────────────
   A wash rather than a fill: thin enough that the lake keeps moving through
   it, blurred so the label never has to fight whatever is behind it. The
   hairline and the inset top highlight are what stop it reading as flat
   translucent grey — they give the disc a lit edge the way real glass has one.

   .btn::after is the shared hover: an aqua panel slides up and the label goes
   dark. That is the opposite of this, so it is switched off here and the
   hover brightens the wash instead, with the label staying white. Specificity
   ties with .btn:hover, so this block has to stay below it in this file.

   The -webkit- prefix is here and nowhere else in the project on purpose:
   these two are the most visible controls on the page, and a glass button
   whose blur silently does not apply is exactly the janky flat chip we are
   trying not to ship. */
.btn--glass{
  /* The two numbers that decide how see-through this is. Raise the blur to
     frost it, raise the wash to make it milkier — both are here so neither
     has to be hunted for in the rules below. saturate stays high so the lake
     keeps its colour coming through rather than going grey. */
  --glass-blur:0px;
  --glass-wash:0;
  background:rgba(255,255,255,var(--glass-wash));color:var(--ivory);
  border-color:rgba(255,255,255,.26);
  -webkit-backdrop-filter:blur(var(--glass-blur)) saturate(150%);
  backdrop-filter:blur(var(--glass-blur)) saturate(150%);
  box-shadow:0 8px 28px rgba(0,0,0,.18),inset 0 1px 0 rgba(255,255,255,.18);
  transition:color .5s var(--e),background-color .45s var(--e),
             border-color .45s var(--e),box-shadow .45s var(--e)}
.btn--glass::after{display:none}
/* the hover only moves the wash — the blur stays put, so the button does not
   appear to frost over as the pointer lands on it */
.btn--glass:hover{--glass-wash:.12;color:var(--ivory);
  border-color:rgba(255,255,255,.40);
  box-shadow:0 10px 32px rgba(0,0,0,.24),inset 0 1px 0 rgba(255,255,255,.28)}
.btn .arw{transition:transform .45s var(--e)}
.btn:hover .arw{transform:translateX(4px)}

[data-rv]{opacity:0;transform:translateY(30px);transition:opacity .95s var(--e),transform 1.05s var(--e)}
[data-rv].in{opacity:1;transform:none}
[data-d="1"]{transition-delay:.09s}[data-d="2"]{transition-delay:.18s}[data-d="3"]{transition-delay:.27s}
[data-d="4"]{transition-delay:.36s}[data-d="5"]{transition-delay:.45s}


/* ==============================================================================
  02 . 05-lakefilm.css
  Split-frame film engine (PVFilm canvas hosts).
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════
   Lake film — split-frame layout.
   The reveal lives on the <canvas> and the cursor-lift on the slat,
   so a slow staggered entrance never fights a fast pointer response.
   ═══════════════════════════════════════════════════════════════ */
.pvfilm{position:relative;display:flex;overflow:hidden;isolation:isolate}
.pvfilm[data-axis="y"]{flex-direction:column}

.pvfilm__slat{
  position:relative;flex:1 1 0;min-width:0;min-height:0;overflow:hidden;
  transform:translate3d(0,var(--lift,0px),0);
  transition:transform .55s cubic-bezier(.2,.9,.25,1);
  will-change:transform;
}
.pvfilm__slat canvas{display:block}

/* staggered entrance */
.pvfilm canvas{
  transform:translateY(103%);
  transition:transform 1.15s cubic-bezier(.16,1,.3,1);
  transition-delay:calc(var(--i) * 55ms);
}
.pvfilm[data-axis="y"] canvas{transform:translateX(-103%)}
.pvfilm.is-in canvas{transform:none}

/* hairline seams between frames */
.pvfilm--seam .pvfilm__slat + .pvfilm__slat::before{
  content:"";position:absolute;inset:0 auto 0 0;width:1px;z-index:3;
  background:rgba(245,242,234,.10);
}
.pvfilm--seam[data-axis="y"] .pvfilm__slat + .pvfilm__slat::before{
  inset:0 0 auto 0;width:auto;height:1px;
}

/* visible gaps — reads as separate frames rather than one image */

@media (prefers-reduced-motion:reduce){
  .pvfilm canvas{transform:none!important;transition:none}
  .pvfilm__slat{transform:none!important;transition:none}
}


/* ==============================================================================
  03 . 10-nav.css
  Fixed navigation bar.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — NAVIGATION

   Fixed bar: links left, brand centred, actions right. Three real grid
   columns so a wider link group can never run under the logo.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── NAV (Nocturne layout, Monolith colour) ─────────────── */
/* links left · logo centred · actions right
   The brand is absolutely centred rather than a grid track: the two sides are
   different widths, so equal-fraction columns would shift it off-centre. */
/* three real columns: the brand owns the middle track, so a wider link group
   can never run underneath it. The two 1fr tracks are equal by definition, so
   the middle track is centred without absolute positioning. */
.nav{position:fixed;inset:0 0 auto;z-index:130;display:grid;grid-template-columns:1fr auto 1fr;
  align-items:center;gap:20px;padding:calc(18px * var(--z)) var(--pad);transition:.5s var(--e)}
.nav.stuck{background:rgba(0,8,81,.74);backdrop-filter:blur(20px) saturate(140%);padding-block:calc(11px * var(--z));border-bottom:1px solid var(--hair)}
/* worn for one synchronous measurement in publishNavH: the bar is put into
   its stuck state to read the condensed height, and nothing on it may be
   mid-transition when it is read */
.nav.nav--measure,.nav.nav--measure *{transition:none!important}
/* The Collection rail takes the whole screen while it plays, and a fixed bar
   sitting over a full-bleed panel is clutter. It lifts away for the length of
   the travel and comes back once the last typology has landed.
   visibility is delayed to the end of the transition so the bar fades out
   rather than vanishing, and returns with no delay on the way back — the same
   trick the language menu uses. Without it a hidden bar still takes keyboard
   focus, which is a worse bug than the clutter. */
/* The bar STAYS PUT and empties instead of sliding away. It used to lift the
   whole header out of the viewport, which is right on desktop where the links
   are the navigation — but below 1330px the burger inside it is the ONLY way
   to navigate, and the villa section is about 6,000px of scrolling on a phone,
   so lifting the bar took navigation away for the length of it.

   So: the bar keeps its position and loses everything that made it a bar —
   its ground, its blur, its rule, its own pointer target — and its contents
   fade out one by one. The burger is exempt and takes its pointer events
   back, so it floats over the panels on its own.

   Above 1330px the burger is display:none, so this leaves an empty,
   transparent, click-through box exactly where the bar was: visually identical
   to the old slide-away, without the special case. */
.nav.nav--off{transform:none;opacity:1;visibility:visible;pointer-events:none;
  background:transparent;backdrop-filter:none;border-bottom-color:transparent;
  transition:background .4s var(--e),border-color .4s var(--e)}
.nav.nav--off .brand,
.nav.nav--off .navr,
.nav.nav--off .nlinks,
.nav.nav--off .nav__prog{opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .34s var(--e),visibility 0s .34s}
/* the one thing that stays */
.nav.nav--off .burger{opacity:1;visibility:visible;pointer-events:auto}
/* ── THE MARK ─────────────────────────────────────────────────────────────
   assets/logo/logo-lockup.svg, inlined. It was the symbol beside a wordmark
   set in Metropolis; it is now the supplied lockup, which carries its own
   PAVILION and BY RAMS as vector. So there is one child here where there were
   two, and every rule that styled that text (.brand b, .brand i, and their
   stuck and phone variants) is gone rather than left to target nothing.

   Sized by HEIGHT, width auto: the lockup is 628.09 x 422.57, and pinning
   both axes on an aspect that is no longer the symbol's 1:1.69 would squash
   it. It also comes out far narrower than the symbol-plus-text it replaces,
   which is space the centred nav simply gains — see the note on .nav being
   fragile: the middle track shrinking can only help the sides fit. */
.brand{display:flex;align-items:center;justify-self:center}
/* the mark sits on a soft aqua bloom so it holds against a bright hero frame */
.brand svg{height:calc(89.7px * var(--z));width:auto;flex:none;color:var(--ivory);position:relative;z-index:1;
  filter:drop-shadow(0 0 16px rgba(96,237,244,.42)) drop-shadow(0 2px 10px rgba(0,0,0,.55));
  transition:height .5s var(--e),filter .5s var(--e)}
.brand:hover svg{filter:drop-shadow(0 0 22px rgba(96,237,244,.75)) drop-shadow(0 2px 10px rgba(0,0,0,.55))}
/* shrink a little once the bar condenses, so it never crowds the actions */
.nav.stuck .brand svg{height:calc(71.3px * var(--z))}
.navl{justify-self:start;display:flex;align-items:center;gap:12px;min-height:calc(56px * var(--z))}
/* No ground and no pill: the links sit straight on the bar. What marks the
   current section is the dot below, not a fill. */
.nlinks{display:flex;gap:3px}
/* brand subhead face: uppercase and tracked, per the guidelines. Sized so the
   left group still fits inside half the free width at 1440 — otherwise the
   wider side pushes the centre track and the logo drifts off true centre. */
.nlinks a{display:inline-flex;align-items:center;gap:calc(6px * var(--z));
  font-family:var(--f-micro);padding:calc(10px * var(--z)) calc(10px * var(--z));font-size:calc(11.5px * var(--zt));font-weight:500;
  letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;color:var(--mute);transition:color .4s var(--e)}
.nlinks a:hover{color:var(--ivory)}
/* active section: the same 4px aqua dot the language menu sets against the
   selected code, and a hairline under the bar showing how far through the
   page you are.

   Every link reserves the dot's width, not just the active one — otherwise
   the row would re-flow each time the spy moved and the labels would jitter
   sideways as you scroll. That reserve is why the group is wider now, and
   why .navl has to be re-measured against the centred brand: the two 1fr
   tracks only hold the logo on axis while this side still fits its own. */
.nlinks a::after{content:"";width:4px;height:4px;flex:none;border-radius:50%;
  background:var(--gold-500);opacity:0;transition:opacity .35s var(--e)}
.nlinks a.on{color:var(--ivory)}
.nlinks a.on::after{opacity:1}
.nav__prog{position:absolute;inset-inline-start:0;bottom:-1px;height:1px;background:var(--gold-500);
  width:0;transform-origin:0 50%;transition:width .18s linear;opacity:0}
/* :not(.nav--off) is load-bearing, not decoration. This rule and the one that
   hides the bar with the rest of the nav have the SAME specificity, and this
   one is declared later — so while the nav was hiding, the progress bar kept
   opacity:1 and only disappeared when its delayed visibility:hidden landed
   340ms afterwards. The effect was a 1px aqua hairline sitting across the top
   of the villa rail for a third of a second and then popping out. Excluded
   here, the bar fades with the brand and the links, on the same curve. */
.nav.stuck:not(.nav--off) .nav__prog{opacity:1}

.navr{display:flex;gap:8px;align-items:center;justify-self:end}
.navr .btn{padding:calc(11px * var(--z)) calc(18px * var(--z));font-size:calc(10.5px * var(--zt));letter-spacing:.16em}
.btn--ghost{border-color:var(--edge);color:var(--ivory)}
.btn--ghost::after{background:var(--ivory)}
.btn--ghost:hover{color:var(--navy-950);border-color:var(--ivory)}
/* WhatsApp — the mark on its own. The vertical padding is .btn's, untouched,
   so it stands exactly as tall as the two word buttons beside it and the row
   still sits on one line; only the sides are pulled in, to whatever a square
   glyph needs. Filled, not stroked, unlike the 360 mark next to it: it is a
   logo rather than an icon, and outlining it would be drawing someone else's
   trademark wrong. */
.btn--wa{padding-inline:calc(15px * var(--z));gap:0}
/* A zero-width space, and it is load-bearing. The vertical padding matches
   the buttons either side, but their CONTENT is a line of text and this one's
   is a 14px glyph, so the pill came out 33px against their 35 — close enough
   to look like a mistake rather than a size. The strut gives this button the
   same line box the words give theirs, so all three are one height by the
   same arithmetic instead of by a number typed here that their font could
   later disagree with. */
.btn--wa::before{content:"\200b";width:0;overflow:hidden}
.btn--wa svg{width:calc(18px * var(--z));height:calc(18px * var(--z));flex:none;
  fill:currentColor;stroke:none;display:block}
.btn--360{gap:9px}
.btn--360 svg{width:13px;height:13px;flex:none;stroke:currentColor;fill:none;stroke-width:1.6}

/* ── LANGUAGE — codes only, no flags. Deliberately quieter than the buttons
      beside it: it is a setting, not a call to action, and it survives the
      760px breakpoint that hides the ghost buttons because a visitor who
      needs Kurdish needs it most on a phone. ── */
.lang{position:relative;display:flex}
/* The tail is tighter than the nose, and the gap is smaller than it looks it
   should be. A trailing caret carries far less visual mass than two capitals,
   so equal padding leaves it adrift in the right-hand end of the pill — which
   is what read as "weird": the label looked pushed left and the caret looked
   like a separate object rather than part of the control. */
.lang__b{display:inline-flex;align-items:center;gap:5px;padding:calc(11px * var(--z)) calc(13px * var(--z));border-radius:999px;
  padding-inline-end:calc(11px * var(--z));
  border:1px solid var(--edge);font-family:var(--f-micro);font-size:calc(10.5px * var(--zt));font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:var(--mute);transition:.35s var(--e)}
.lang__b:hover,.lang.on .lang__b{color:var(--ivory);border-color:var(--edge-on)}
/* Wider and lighter than it was — 9x6 at 1.7 put a heavier stroke next to
   500-weight micro type at 10.5px, and it read as a blob rather than a caret.

   The half pixel up is optical, not arithmetic. align-items:center centres
   the caret on the label's LINE BOX, but the label is uppercase, so its ink
   sits above that centre; matched to the box, the caret looks dropped.

   translateY has to be repeated in the open state. transform is one property:
   a bare rotate(180deg) there would discard the nudge and the caret would
   drop half a pixel on the way up. */
.lang__b svg{width:10px;height:6px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;transform:translateY(-.5px);
  transition:transform .35s var(--e)}
.lang.on .lang__b svg{transform:translateY(-.5px) rotate(180deg)}
.lang__m{position:absolute;top:calc(100% + 10px);inset-inline-end:0;z-index:5;min-width:112px;
  margin:0;padding:6px;list-style:none;border-radius:14px;border:1px solid var(--edge);
  background:rgba(0,8,81,.9);backdrop-filter:blur(20px) saturate(140%);
  box-shadow:0 18px 46px rgba(0,0,0,.55);opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .28s var(--e),transform .3s var(--e),visibility .28s}
.lang.on .lang__m{opacity:1;visibility:visible;transform:none}
.lang__m button{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;
  padding:9px 11px;border-radius:9px;font-family:var(--f-micro);font-size:10.5px;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;color:var(--mute);transition:.26s var(--e)}
.lang__m button:hover,.lang__m button:focus-visible{background:rgba(255,255,255,.07);color:var(--ivory);outline:0}
.lang__m [aria-selected="true"] button{color:var(--ivory)}
.lang__m [aria-selected="true"] button::after{content:"";width:4px;height:4px;border-radius:50%;background:var(--gold-500)}
/* .navl .burger beats the later base `.burger{display:none}` declaration */
/* between the burger breakpoint and 1440 there is not enough free width for
   the pill to sit inside half of it, so it tightens rather than the logo
   drifting off centre */
/* the dot costs width, so the band that was already tight gives some back */
/* Seven links, not six. The sizing above was tuned when Delivery did not
   exist and the left group fitted its 1fr track down to 1440; the seventh
   pushed the group past the track between roughly 1330 and 1600, and a
   grid track that overflows does not clip — it grows, shunting the centre
   track with it. The mark was landing 23-53px right of true centre.
   Measured at 1920/1600/1500/1440/1400/1340, not guessed. */
@media(max-width:1600px){.nlinks a{padding:calc(10px * var(--z)) calc(7px * var(--z));letter-spacing:.10em;gap:calc(5px * var(--z))}}
@media(max-width:1440px){.nlinks a{padding:calc(10px * var(--z)) calc(4px * var(--z));letter-spacing:.07em;gap:calc(4px * var(--z));font-size:calc(11px * var(--zt))}}
@media(max-width:1330px){.nlinks{display:none}.navl .burger{display:grid}}
/* Between the two-column breakpoint and ~1240px the gallery column is narrow
   enough that a full-width toggle, centred on it, reaches the fixed close
   button. Shrink the toggle rather than move it off centre. */
@media(max-width:1240px){
  .vgtog__b{min-width:76px;padding:9px 12px;font-size:9.5px;letter-spacing:.12em}
}
/* Enquire and Register are the same destination (#register). Below 1200 the
   right-hand group — language, 360, Enquire, Register — outgrows its grid
   track and shoves the centred brand off axis, so the duplicate CTA was the
   first thing to go — it went to #register, where the button beside it also
   went, and two ways to reach one anchor is not worth a nav that wobbles.

   That button is now WhatsApp, which is not a duplicate of anything: it goes
   somewhere else entirely, and as a bare mark it costs about a fifth of what
   the words did. So the 1200px rule went with the words. It survives to 760
   on the line below, alongside the 360 — both are the only way to reach what
   they reach, and the compaction at 960 is what buys them the room.

   Measured, not guessed: with the mark in, the brand sits 8px off axis at
   900 — which is where it sits at every width, mark or no mark — 20px off at
   860 and 50px off at 800. So 900 is where it goes. The old words went at
   1200, so this is still the wider reach of the two. */
/* The WhatsApp mark now survives to every width: the 360 button leaves the
   bar for the burger menu below 1330px (it is a row of its own under the links
   there — see .bmenu__360), which hands back
   more room than the mark costs. The old ≤760 rule hid every ghost button —
   written when the only ghosts were Enquire and the 360; today it would
   silently take WhatsApp off phones, which is where WhatsApp matters most. */
@media(max-width:1330px){.navr .btn--360{display:none}}
/* Just above the 760px breakpoint the 360 button comes back and the right-hand
   group stops fitting its grid track, which drags the centred brand off axis.
   Compacting the actions buys back more than the language pill costs, so every
   control survives and the logo stays centred. */
@media(max-width:960px){
  /* nowrap is the fix for the CTA folding into two lines over the film —
     REGISTER / INTEREST stacked in a pill read as a broken button */
  .navr .btn{padding:10px 14px;font-size:9.5px;letter-spacing:.1em;white-space:nowrap}
  .navr .btn .arw{display:none}
  .lang__b{padding:10px 11px;font-size:9.5px;letter-spacing:.12em;gap:6px}
}
/* tighter on a phone, so EN + WhatsApp + the CTA hold one row. The CTA
   said REGISTER INTEREST when this block was cut and the wordmark had to
   go to make room; at ENQUIRE the whole bar fits with the wordmark on. */
@media(max-width:430px){
  .navr .btn{padding:9px 12px;font-size:9px;letter-spacing:.08em}
  .btn--wa{padding-inline:10px}
  .lang__b{padding:9px 8px}
}
/* keep the centred brand clear of the actions on small screens */
@media(max-width:560px){
  /* on a phone the pill and chevron cost more width than they earn — the
     language control drops to plain text so the centred brand keeps its air.
     It still opens the same menu, and the current code is still the label. */
  .nav{gap:14px}
  .lang__b{border-color:transparent;padding:10px 4px;letter-spacing:.1em}
  .lang__b svg{display:none}
  .lang__m{min-width:96px}
  .brand svg{height:62.1px}
  .nav.stuck .brand svg{height:50.6px}
  /* Below this width the bar keeps only ENQUIRE beside the burger. The brand
     can only be CENTRED while the two side tracks balance, and EN plus the
     WhatsApp mark made the right side five times the left. Both are reached
     from the burger menu instead, which carries them at every burger width. */
  .navr .btn--wa,.navr .lang{display:none}
}
/* ── the burger's menu ────────────────────────────────────────────────────
   Full-screen and opaque: under 1330px this is the only navigation there
   is, and a translucent tray over a moving film reads as debris. The links
   are clones of .nlinks (see 90-chrome.js), stacked big, staggered in.
   z-index over the nav bar (130) and under the villa overlay (150) — the
   menu belongs to the page, not to the overlays. */
/* Opening and closing are not the same gesture and do not want the same
   curve. Arriving is eased out over .46s so the panel settles rather than
   snaps; leaving is .26s on an ease-IN, because a dismissal that lingers
   reads as lag. One duration for both — which is what .4s var(--e) was —
   makes one of the two wrong. */
.bmenu{position:fixed;inset:0;z-index:140;background:var(--navy-950);
  display:grid;grid-template-rows:auto 1fr;opacity:0;
  transition:opacity .26s cubic-bezier(.4,0,1,1)}
/* Both guards are load-bearing. `hidden` only contributes the UA's
   display:none, and the author display:grid above BEATS it — without the
   attribute rule the invisible menu sat over the whole site at z-140 and
   swallowed every tap on every page width. And while the close fade plays,
   the menu is visible but leaving, so it must stop taking taps the moment
   it is dismissed, not 450ms later. */
.bmenu[hidden]{display:none}
.bmenu:not(.on){pointer-events:none}
.bmenu.on{opacity:1;transition:opacity .46s var(--e2)}
/* ── the top rail ────────────────────────────────────────────────────────
   Three tracks: an empty one holding the close's place, the mark, and
   WhatsApp. 1fr auto 1fr is what keeps the mark optically centred no matter
   how wide WhatsApp's circle is — the same device the desktop nav uses, and
   safe here because one side is empty and the other is a single circle.

   Its HEIGHT is twice the burger's centre line, so the rail's own centre
   lands exactly on that line and WhatsApp sits at the same height as the
   cross without either knowing about the other. Both numbers come from one
   measurement of the burger in 90-chrome.js. The fallbacks are the nav's
   resting geometry, for the frame before the first open. */
/* The mark is anchored NEAR THE TOP and free to grow; only the two circles
   are pinned to the burger's centre line. It used to be centred on that
   line and capped by the rail, which is what put 68px of dead air between
   the logo and the first link on a phone: the rail had to be twice the
   burger's centre to keep the circles level, the mark could only ever be
   74px inside it, and the leftover sat under the mark doing nothing.
   Anchored instead, the mark ENDS the rail — a bigger logo and a smaller
   gap out of the same change, which is the fix for both. */
.bmenu{--mark-top:clamp(8px,1.6vh,15px);--mark-h:clamp(78px,23vw,98px)}
.bmenu__rail{display:grid;grid-template-columns:1fr auto 1fr;align-items:start;
  min-height:calc(var(--bx-mid,34px) * 2);padding-inline:var(--bx-edge,20px)}
/* both slots drop by exactly enough to put their circle's centre on the
   burger's — the left one is empty and only holds the cross's place */
.bmenu__slot{display:block;margin-top:calc(var(--bx-mid,34px) - var(--bx-size,42px) / 2)}
.bmenu__slot--r{display:flex;justify-content:flex-end}
.bmenu__mark{display:grid;place-items:center;min-width:0;margin-top:var(--mark-top)}
.bmenu__mark svg{display:block;width:auto;color:var(--ivory);height:var(--mark-h)}
/* WhatsApp: the cross's twin, same circle at the other edge. It shows at
   every burger width — the bar's own copy is behind an opaque menu whenever
   this is on screen, so there is no moment where both are visible. */
.bmenu__wa{display:grid;place-items:center;border-radius:50%;
  width:var(--bx-size,42px);height:var(--bx-size,42px);
  border:1px solid rgba(255,255,255,.4);color:var(--ivory);
  transition:color .3s var(--e),border-color .3s var(--e)}
.bmenu__wa svg{width:17px;height:17px;fill:currentColor;stroke:none}
.bmenu__wa:hover{color:var(--pv-gold);border-color:rgba(195,162,86,.55)}

/* ── the body ────────────────────────────────────────────────────────────
   Centred in what the rail leaves. The row gap is the space BETWEEN groups;
   the links keep their own tighter gap so the six read as one block rather
   than as six separate rows. */
/* space-evenly, not centre. Centring parks ALL the leftover height in two
   lumps — one under the logo, one under the accounts — which is what made
   the gap under the logo 68px on a phone while the rows themselves sat
   tight. Distributed evenly, the leftover goes into every gap instead, so
   the space under the logo is the same as the space between the groups and
   the menu reads as one column at any height. It also stops the spacing
   needing a hand-tuned value per screen size: hard-coding gaps that suited
   a 375x812 phone left 1100x820 with 4px under the accounts.
   On overflow it degrades to safe centre by spec, which is what the
   scroll below depends on. */
.bmenu__body{display:grid;align-content:space-evenly;justify-items:center;
  row-gap:clamp(14px,2.6vh,26px);min-height:0;overflow-y:auto;
  /* only ever seen on a short window; a default chrome scrollbar down the
     side of this reads as a bug rather than as a menu */
  scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.22) transparent;
  padding-inline:24px;padding-bottom:clamp(12px,2.2vh,22px);
  /* The rail centres the mark on the burger's line, which leaves air BELOW
     the mark that the body cannot see — so the gap under the logo was that
     air plus this padding, while the gap under the accounts was this
     padding alone. 53px against 21px at 1100x820. Subtracting the leftover
     (the rail's half-height minus the mark's) makes the two read even at
     every width, and clamps at 0 rather than going negative on a nav bar
     tall enough to swallow it. */
  padding-top:max(0px, calc(clamp(6px,1.4vh,14px) - max(0px,
    calc(var(--bx-mid,34px) * 2 - var(--mark-top) - var(--mark-h)))))}
.bmenu__links{display:grid;gap:clamp(15px,3vh,27px);text-align:center}
.bmenu__links a{font-family:var(--f-sub);font-weight:500;font-size:clamp(19px,5.6vw,26px);
  letter-spacing:.18em;text-transform:uppercase;color:var(--ivory);
  transition:color .3s var(--e)}
.bmenu__links a:hover{color:var(--pv-gold)}

/* the 360 — the one ACTION among six destinations, so it is drawn as a
   button rather than a seventh link. Aqua, matching the button it stands in
   for: the same control lives in the bar above 1330px and the two should not
   read as different things. */
.bmenu__360{display:inline-flex;align-items:center;gap:10px;
  padding:13px 26px;border-radius:999px;background:none;cursor:pointer;
  border:1px solid rgba(96,237,244,.5);color:var(--ivory);
  font-family:var(--f-sub);font-size:11px;font-weight:500;letter-spacing:.2em;
  text-transform:uppercase;
  transition:background .3s var(--e),border-color .3s var(--e),color .3s var(--e)}
.bmenu__360:hover{background:rgba(96,237,244,.12);border-color:var(--pv-aqua);
  color:var(--pv-aqua)}
.bmenu__360 svg{width:15px;height:15px;flex:none;stroke:currentColor;fill:none;stroke-width:1.5}

/* the divider between the six destinations and the settings under them.
   Faded at both ends so it reads as a breath rather than as a border. */
.bmenu__rule{width:clamp(38px,13vw,60px);height:1px;border:0;margin:0;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.24),transparent)}

.bmenu__lang{display:flex;gap:10px}
.bmenu__lang button{font-family:var(--f-sub);font:inherit;font-size:11px;font-weight:500;letter-spacing:.18em;
  color:var(--faint);padding:9px 15px;border:1px solid transparent;border-radius:999px;
  background:none;cursor:pointer;transition:color .3s var(--e),border-color .3s var(--e)}
.bmenu__lang button[aria-current]{color:var(--ivory);border-color:rgba(255,255,255,.3)}
.bmenu__lang button:hover{color:var(--ivory)}

/* the accounts. Quieter than everything above them by design — they are the
   last thing anyone came here for, and at full strength five more circles
   under the language pills turned the foot of the menu into the loudest
   part of it. */
.bmenu__soc{display:flex;gap:clamp(10px,3.2vw,16px)}
/* The ring was rgba(255,255,255,.15) and read as a circle of DOTS rather
   than a line. A 1px curve is antialiased, so its coverage varies pixel to
   pixel around the arc, and the circles do not land on whole device pixels
   (space-evenly puts them on .55 of one) — at .15 over navy the
   low-coverage pixels fall below what an eye can see and only the peaks
   survive. The fix is contrast, not geometry: a faint fill gives the disc
   its own presence so the hairline is no longer the only thing drawing the
   circle, and the ring itself is lifted far enough that its weakest pixels
   still read. Quiet as before, but continuous. */
/* Same ring, same fix, same numbers — see the note on .fsoc a. The fill this
   rule already carried was the first half of it; the width is the half that
   was missing, and it is the half that actually flattens the arc. */
.bmenu__soc a{display:grid;place-items:center;width:38px;height:38px;border-radius:50%;
  border:2px solid rgba(255,255,255,.18);background:rgba(255,255,255,.05);
  color:var(--faint);
  transition:color .3s var(--e),border-color .3s var(--e),background .3s var(--e)}
.bmenu__soc svg{width:15px;height:15px;fill:currentColor;stroke:none}
.bmenu__soc a:hover{color:var(--pv-gold);border-color:rgba(195,162,86,.55);
  background:rgba(195,162,86,.08)}

/* One entrance for the whole menu, in reading order: the six links, then the
   360, the rule, the languages and the accounts. --bi is set per element in
   90-chrome.js so the order lives with the thing that builds it. */
.bmenu__links a,.bmenu__360,.bmenu__rule,.bmenu__lang,.bmenu__soc{
  opacity:0;transform:translateY(14px);
  transition:opacity .5s var(--e),transform .5s var(--e),color .3s var(--e),
             background .3s var(--e),border-color .3s var(--e);
  transition-delay:calc(.06s + var(--bi,0) * .05s)}
.bmenu.on :is(.bmenu__links a,.bmenu__360,.bmenu__rule,.bmenu__lang,.bmenu__soc){
  opacity:1;transform:none}
/* The stagger is for arriving only. Played in reverse on dismissal it ran
   the last item out .5s after the tap, on top of its own delay — the menu
   looked like it was thinking about it. Leaving, everything goes at once and
   faster than the panel behind it. */
.bmenu:not(.on) :is(.bmenu__links a,.bmenu__360,.bmenu__rule,.bmenu__lang,.bmenu__soc){
  transition-duration:.16s;transition-delay:0s}

/* ── short screens ───────────────────────────────────────────────────────
   The rail is a fixed cost — its height is set by where the burger sits —
   so everything given back has to come out of the body. A 360x640 phone was
   pushing the accounts 10px off the bottom; a landscape phone is 375px tall
   and does not fit at any spacing, which is what the scroll is for. Both
   `safe center` and the scroll are needed together: plain `center` on an
   overflowing grid centres the content and puts the OVERFLOW ABOVE the top
   edge, where it cannot be scrolled back to. */
@media (max-height:720px){
  /* padding-top keeps paying back the rail's leftover here too — the
     `padding-block` shorthand this used to be silently overrode it and
     put the 22px imbalance straight back on short screens. */
  .bmenu__body{row-gap:clamp(10px,1.8vh,18px);
    padding-bottom:clamp(12px,2.4vh,22px);
    padding-top:max(0px, calc(clamp(8px,1.8vh,16px) - max(0px,
      calc(var(--bx-mid,34px) * 2 - var(--mark-top) - var(--mark-h)))))}
  .bmenu__links{gap:clamp(11px,2.1vh,20px)}
  .bmenu__links a{font-size:clamp(17px,4.8vw,22px)}
  .bmenu__360{padding:11px 22px}
  .bmenu__lang button{padding:7px 13px}
  .bmenu__soc a{width:34px;height:34px}
}
/* ── the close cross, and why it is the one that has to animate ──────────
   This is painted over the burger's own measured rect, because the burger
   itself CANNOT be raised above the menu: .nav is position:fixed with
   z-index:130, which makes it a stacking context, so `.navl .burger{z-index:141}`
   only ever wins inside the nav — never against the menu's 140. The real
   burger is therefore behind an opaque panel the whole time it is open, and
   this element is what a visitor actually sees.

   Which means the burger-to-cross animation has to live HERE. It used to
   arrive already crossed — two bars at top:0, rotated — so opening the menu
   swapped one shape for another with nothing in between. It now starts as
   the same three bars in the same places as the burger under it (16 x 1.4px,
   at ∓5px) and performs the same two-phase morph: the outer bars travel to
   the centre, the middle one fades as they arrive, then they rotate. Because
   the element is pixel-aligned with the burger, the handover is invisible and
   what you see is the burger becoming a cross.

   Closing is quicker than opening on purpose, and shorter than the panel's
   own .26s exit, so the morph finishes rather than being cut off mid-rotate.

   No ring. It matched the WhatsApp circle opposite it, but the burger it
   stands in for has no ring any more, so a circle here would appear from
   nowhere the moment the menu opened. */
.bmenu__x{position:fixed;display:grid;place-items:center;
  background:none;border:0;padding:0;cursor:pointer}
.bmenu__x:focus-visible{outline:1px solid var(--pv-gold);outline-offset:2px;border-radius:4px}
.bmenu__x i{display:block;width:16px;height:1.4px;background:var(--ivory);position:relative;
  transition:background .1s var(--e)}
.bmenu__x i::before,.bmenu__x i::after{content:"";position:absolute;left:0;
  width:16px;height:1.4px;background:var(--ivory);
  transition:transform .12s var(--e), top .14s var(--e) .12s}
.bmenu__x i::before{top:-5px}
.bmenu__x i::after{top:5px}
.bmenu.on .bmenu__x i{background:transparent;transition:background .12s var(--e) .1s}
.bmenu.on .bmenu__x i::before,.bmenu.on .bmenu__x i::after{
  transition:top .18s var(--e), transform .22s var(--e) .18s}
.bmenu.on .bmenu__x i::before{top:0;transform:rotate(45deg)}
.bmenu.on .bmenu__x i::after{top:0;transform:rotate(-45deg)}

/* ── the button becomes a cross while open, in TWO phases ────────────────
   Both properties used to run together over the same .3s: the bars slid to
   the middle WHILE rotating, so for most of the animation they were three
   diagonals passing through each other and the shape read as a smear rather
   than a burger becoming a cross.

   A cross is made in two moves, and doing them in order is what makes it
   legible. Opening: the outer bars travel to the centre (.18s), the middle
   one fades as they arrive, and only then do they rotate (.22s, delayed by
   exactly the travel). Closing runs the same two moves backwards — unrotate
   first, then separate — which is why there are two sets of transitions
   rather than one. The state a transition is written on is the state it
   animates TOWARD, so the plain rules describe closing and the .on rules
   describe opening. */
.burger i{transition:background .16s var(--e) .16s}
.burger i::before,.burger i::after{
  transition:transform .18s var(--e), top .2s var(--e) .18s}
.burger.on i{transition:background .12s var(--e)}
.burger.on i::before,.burger.on i::after{
  transition:top .18s var(--e), transform .22s var(--e) .18s}
.burger.on i{background:transparent}
.burger.on i::before{top:0;transform:rotate(45deg)}
.burger.on i::after{top:0;transform:rotate(-45deg)}
/* the burger sits above its own menu, so it stays pressable as the close */
.navl .burger{position:relative;z-index:141}
@media(prefers-reduced-motion:reduce){
  .bmenu,.bmenu__links a,.bmenu__360,.bmenu__rule,.bmenu__lang,.bmenu__soc{
    transition:none;transform:none}
  /* the cross still has to BE a cross — only the journey to it goes */
  .bmenu__x i,.bmenu__x i::before,.bmenu__x i::after,
  .burger i,.burger i::before,.burger i::after{transition:none}
}

/* No ring. The 42px box stays — it is the touch target, and 42 is already at
   the floor of what a thumb can hit reliably — but the border and the round
   corner are gone, so what shows is the three bars alone. The button had no
   focus style of its own and was relying on that border to be findable at
   all, so a real focus ring goes in with the same removal. */
.burger{display:none;width:42px;height:42px;place-items:center;
  background:none;border:0;padding:0}
.burger:focus-visible{outline:1px solid var(--pv-gold);outline-offset:2px;border-radius:4px}
.burger i{display:block;width:16px;height:1.4px;background:var(--ivory);position:relative}
.burger i::before,.burger i::after{content:"";position:absolute;left:0;width:16px;height:1.4px;background:var(--ivory)}
.burger i::before{top:-5px}.burger i::after{top:5px}


/* ==============================================================================
  04 . 20-hero.css
  Hero.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — HERO

   Full-screen split-frame lake film. No headline by design.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── HERO — split-frame film, no headline ─────────────── */
/* ── GROUND ─ this section's background, and no other section's ───────────
   Everything is now 900 #000851 Pavilion Blue. --navy-950 resolves to it too.
   purple #35005B · emerald #004C15 · teal #006352 · gold #C3A256 · or any hex
   These are split per section for development. Merge before launch.
   The film covers this once it paints, so what you mostly see is the
   veil two rules below and the palette in assets/js/lakefilm.js. */
.hero{background:var(--navy-900)}
.hero{position:relative;height:100svh;min-height:540px;overflow:hidden}
.hero__film{position:absolute;inset:0}
/* THE VEIL IS NEUTRAL, not Pavilion Blue. It was rgba(0,8,81,…) top and
   bottom, which tinted the whole hero film blue — invisible while the film was
   the generated navy lake, obvious the moment a real video went in, since the
   footage arrives with its own colour and this was repainting it.

   It is not simply deleted because it is doing a job: the hero carries no
   headline, so the only things over it are the nav at the top and the brochure
   button at the bottom, and both need something behind them when the footage
   is bright. Black at the same strengths keeps that contrast and takes the
   colour cast out, so the film reads as itself. */
.hero__veil{position:absolute;inset:0;z-index:3;pointer-events:none;
  background:linear-gradient(180deg,rgba(0,0,0,.5),rgba(0,0,0,.04) 38%,rgba(0,0,0,.66))}
.hero__edge{position:absolute;left:0;right:0;bottom:0;z-index:4;height:1px;background:var(--hair)}

/* ── brochure CTA — the hero carries no headline, so this is the only thing
      asking the visitor for anything above the fold. Centred on the bottom
      edge, clear of the scroll cue at the right. ── */
.hero__broc{position:absolute;left:50%;transform:translateX(-50%);z-index:5;
  bottom:clamp(26px,5vh,54px);display:grid;justify-items:center}
/* The fill, border and shadow now come from .btn--glass in 00-tokens.css.
   What is left here is what is particular to this button: the download mark
   and the busy state below. */
.btn--broc svg{width:14px;height:14px;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}

/* while the files are being fetched. Local placeholders come back instantly,
   but a real multi-megabyte brochure does not, and a button that looks inert
   for two seconds gets clicked again. */
.hero__broc.is-busy .btn--broc{opacity:.72;cursor:progress}
.hero__broc.is-busy .btn--broc svg{animation:brocpulse 1s ease-in-out infinite}
@keyframes brocpulse{0%,100%{opacity:1;transform:translateY(0)}50%{opacity:.35;transform:translateY(2px)}}

@media(max-width:560px){
  .hero__broc{bottom:20px}
  .btn--broc{padding:13px 20px;font-size:10px;letter-spacing:.14em}
}


/* ==============================================================================
  05 . 30-intro.css
  01 - Pavilion.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — 01 - PAVILION (introduction)

   Specification left, prose right, hairline between.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── INTRODUCTION — prose | rule | specification ─────────────── */
/* ── GROUND ─ this section's background, and no other section's ───────────
   Everything is now 900 #000851 Pavilion Blue. --navy-950 resolves to it too.
   purple #35005B · emerald #004C15 · teal #006352 · gold #C3A256 · or any hex
   These are split per section for development. Merge before launch.
   This one had no background at all until now — it was showing body. */
/* 01 About runs on pure black rather than the brand navy every other band
   uses. Written as a literal rather than a token: --navy-900 is shared by the
   hero and the villa rail, so recolouring the token would take those with it,
   and there is no black in the palette to reach for. The neighbours are
   #000851 on both sides, so the section reads as a deliberate step down. */
/* Was #000, asked for when every other section was Pavilion Blue and this
   one had to read as deeper. The whole site is #000851 now, so a pure
   black here is a second near-black doing no work — it takes the ground
   like everything else. Put `background:#000` back if About should still
   sit a shade under the rest. */
.intro{background:var(--navy-950)}
.intro{padding-block:clamp(80px,12vh,calc(170px * var(--z)))}
/* Solstice arrangement: the specification reads first on the left, the prose
   sits to its right, divided by the hairline. Order is used rather than DOM
   position so the prose still comes first when the columns stack. */
/* Even columns since the plan arrived. It was .88fr against 1.12fr, drawn
   when this side held nine rows of figures and wanted less width than the
   prose; the plan is the widest thing in the section now and every pixel the
   column gains is one the labels gain. Even is as far as it goes — the prose
   is 474px here, about 60 characters, and taking more would push it under
   the line where a paragraph starts to read as a column of scraps. */
.intro__g{display:grid;grid-template-columns:minmax(0,1fr) 1px minmax(0,1fr);gap:clamp(28px,5vw,calc(84px * var(--z)));align-items:start}
/* ── BELIEVE IN MIRACLES — the tier study's Gold, in slow motion ──────────
   The ramp is the study's verbatim: the brand C3A256 as the mid-tone,
   brown-amber in the shadows, pale cream at the crest — a hue, not a
   brightness — slid across glyph-clipped text forever, with one white glint
   crossing per cycle. Slow motion is the study's own knob: its normal 6.5s
   slide at the demo's 0.25x is 26s here.

   Two lessons already paid for elsewhere, both load-bearing:

   · ONE ELEMENT, LAYERED BACKGROUNDS. The study's first glint was an
     ::after redrawing the word, and the copy sat a fraction off — ghosting.
     Two background layers on one element cannot misregister.
   · PER-LAYER REPEAT. The ramp TILES (repeat) so it can slide 0 to -300%
     forever — its cream-to-amber seam reads as one more shadow band. The
     glint must NOT tile (a lone band, not a picket fence), and with
     no-repeat at 300% size its position is only valid between 0% and 100%:
     outside that window the layer slides off the glyphs entirely, which is
     how the scrapped landing cameo lost its letters. Both layers ride one
     keyframe track; the ramp's rate is constant across every segment, so
     the glint's stops cost it nothing.

   The stroke that makes the flat version read bold stays, in explicit gold
   rather than currentColor — the fill is transparent now, and a transparent
   stroke is no stroke. It rims the shine with the mid-tone, which keeps the
   edges defined where the crest goes near-white. Reduced motion holds the
   ramp still at its first frame: the material, without the motion. */
/* .h2.h2--miracles, doubled on purpose: 99-type is the LAST section and
   restates .h2's family, tracking and case at equal specificity, so a bare
   .h2--miracles written mid-file loses exactly those three declarations —
   the shine landed while the face silently stayed BR Firma. Two classes
   outrank one wherever the source order puts them. */
.h2.h2--miracles{font-family:var(--f-sub);font-weight:700;text-transform:uppercase;
  letter-spacing:.12em;font-size:clamp(1.35rem,2.75vw,calc(2.7rem * var(--z)));
  line-height:1.1;white-space:nowrap;
  color:var(--pv-gold);                     /* what a non-clipping browser shows */
  /* The sweep runs with the reading direction, so the angle is a variable and
     98-rtl.css mirrors it to 260deg for Arabic and Kurdish — 360 minus this,
     which is the reflection of the axis, keeping the same tilt off vertical.
     A variable rather than a second copy of both gradients in that file: the
     ramp is fourteen stops and would drift the first time either is retuned.
     Mirroring the angle only reflects the bands; what reverses the TRAVEL is
     animation-direction, which 98-rtl.css sets alongside it. */
  --mir-a:100deg;
  background-image:
    linear-gradient(var(--mir-a), transparent 0%, transparent 42%,
      rgba(255,255,255,.9) 50%, transparent 58%, transparent 100%),
    linear-gradient(var(--mir-a),
      #6d4f18 0%, #C3A256 10%, #f7e6a8 18%, #fffbe8 24%, #C3A256 34%,
      #8a6a24 44%, #e8cf86 56%, #fffbe8 64%, #C3A256 74%, #7a5c1e 86%,
      #e0c479 100%);
  background-size:300% 100%,300% 100%;
  background-repeat:no-repeat,repeat;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;
  -webkit-text-stroke:.6px var(--pv-gold);
  filter:drop-shadow(0 0 24px rgba(195,162,86,.45)) drop-shadow(0 2px 12px rgba(0,0,0,.6));
  animation:mirGold 26s linear infinite}
@keyframes mirGold{
  0%  {background-position:8% 50%, 0% 50%}
  40% {background-position:8% 50%, -120% 50%}
  55% {background-position:92% 50%, -165% 50%}
  100%{background-position:92% 50%, -300% 50%}
}
@media(prefers-reduced-motion:reduce){
  .h2--miracles{animation:none}
}

.intro__spec{order:1}.intro__line{order:2}.intro__l{order:3}
/* The column divider is a painted element rather than a border, so it takes
   the token as a background — same colour, same switch. */
.intro__line{background:var(--rule);align-self:stretch;min-height:100%}
.intro__l h2{margin:calc(26px * var(--z)) 0 calc(30px * var(--z))}
/* The prose here runs white rather than the shared --mute .body carries
   elsewhere. Scoped to this column on purpose: .body is also the rail
   panels, the day copy and the register note, and none of those were
   asked to change. */
.intro__l .body{color:var(--ivory)}
.intro__l .lede em{font-style:italic;color:var(--gold-300)}
.intro__spec{display:grid}
/* The grid item is the wrapper, not the list: the master plan sits under the
   rows and a <dl> may only carry dt/dd/div, so the figure is its sibling.
   The list's own margin has to go with it — it is a UA 1em otherwise, and it
   would push the plan down by a line for no reason anyone could see. */
.intro__spec dl{margin:0}
.intro__plan{margin:calc(30px * var(--z)) 0 0}
/* The hairline is the row rule carried on round the picture, so the plan
   reads as the last entry in the table rather than as a loose image. The
   plan's own ground is already this dark, so nothing is being framed in a
   colour it does not have. */
.intro__plan__b{display:block;width:100%;position:relative;padding:0;border:1px solid var(--rule);
  background:none;transition:border-color .35s var(--e)}
.intro__plan__b:hover{border-color:var(--pv-gold)}
.intro__plan__b:focus-visible{outline:1px solid var(--pv-gold);outline-offset:3px}
.intro__plan img{width:100%;height:auto}
/* The site's own expand glyph, same path as the gallery's. It rests low and
   comes up on hover rather than sitting on the picture permanently — the
   plan is the thing being looked at, not the button. */
.intro__plan__i{position:absolute;right:10px;bottom:10px;width:30px;height:30px;display:grid;place-items:center;
  border-radius:50%;background:rgb(0 6 41 / .72);border:1px solid var(--edge);
  opacity:0;transform:translateY(4px);transition:opacity .3s var(--e),transform .3s var(--e)}
.intro__plan__b:hover .intro__plan__i,.intro__plan__b:focus-visible .intro__plan__i{opacity:1;transform:none}
.intro__plan__i svg{width:15px;height:15px;fill:none;stroke:var(--ivory);stroke-width:1.6}

/* ── the plan, full size ──────────────────────────────────────────────────
   Straight copy of .v360's shape: fixed, faded in on .on, above everything.
   The picture is centred and contained rather than covering, because a plan
   cropped to fill the screen is a plan with its labels cut off. */
.plan{position:fixed;inset:0;z-index:200;background:var(--navy-900);
  display:grid;place-items:center;padding:clamp(16px,4vh,54px);
  /* The tracks are pinned, and they have to be. An auto row GROWS to the
     picture, and the picture's own max-height:100% then resolves against the
     grown row — no constraint at all, and a 2800x1980 plan hangs off the
     bottom of the screen. minmax(0,1fr) makes the row the box's own height,
     which is what max-height:100% needs to mean anything. */
  grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);
  opacity:0;visibility:hidden;transition:opacity .5s var(--e),visibility 0s linear .5s}
.plan.on{opacity:1;visibility:visible;transition:opacity .5s var(--e),visibility 0s}
.plan__img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain}
.plan__bar{position:absolute;inset:0 0 auto;z-index:3;pointer-events:none;display:flex;justify-content:flex-end;
  padding:calc(22px * var(--z)) var(--pad)}
.plan__x{pointer-events:auto;width:42px;height:42px;border:1px solid var(--edge);border-radius:50%;
  display:grid;place-items:center;font-size:15px;transition:background .35s var(--e),color .35s var(--e),border-color .35s var(--e)}
.plan__x:hover{background:var(--ivory);color:var(--navy-950);border-color:var(--ivory)}
/* The rule under the head is the table's own top edge and stays the stronger
   of the two: full gold against the rows' --rule, the same hierarchy it had
   when it was a full-strength accent over hairlines. (It was --gold-500,
   which is aqua in this palette whatever it is called.) */
.intro__spec .hd{display:flex;justify-content:space-between;align-items:baseline;gap:14px;padding-bottom:calc(16px * var(--z));border-bottom:1px solid var(--pv-gold)}
.intro__spec .hd span{font-size:calc(10px * var(--zt));letter-spacing:.28em;text-transform:uppercase;color:var(--gold-400)}
.srow{display:flex;justify-content:space-between;align-items:baseline;gap:20px;padding:calc(17px * var(--z)) 0;border-bottom:1px solid var(--rule);transition:padding-left .45s var(--e)}
.srow:hover{padding-inline-start:10px}
.srow dt{font-size:calc(12.5px * var(--zt));letter-spacing:.2em;text-transform:uppercase;color:var(--ivory)}
.srow dd{margin:0;font-size:clamp(1.05rem,1.9vw,calc(1.5rem * var(--z)));font-weight:600;letter-spacing:-.02em;text-align:right;white-space:nowrap}
.srow dd small{font-size:.56em;font-weight:500;color:var(--gold-400);margin-inline-start:6px;letter-spacing:.04em}
@media(max-width:940px){
  .intro__g{grid-template-columns:1fr;gap:clamp(30px,6vw,50px)}
  .intro__l{order:1}.intro__line{order:2}.intro__spec{order:3}
  .intro__line{min-height:1px;height:1px}
}


/* ==============================================================================
  06 . 40-location.css
  02 - Location (the iframe frame only; the map is its own document).
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — 05 - LOCATION (embed frame)

   Only the frame. The section itself lives in sections/location.html
   and reports its own height back to this page.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── LOCATION (Monolith) ─────────────── */
.sec{padding-block:clamp(80px,12vh,calc(170px * var(--z)))}
.sechead{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.25fr);gap:clamp(28px,5vw,calc(90px * var(--z)));align-items:end;margin-bottom:clamp(40px,6.5vh,calc(88px * var(--z)))}
@media(max-width:900px){.sechead{grid-template-columns:1fr}}
/* 02 Location is its own document — this is only the frame it sits in.
   It is one screen tall so the nav anchor lands on the whole section, and
   the iframe fills it; the sizing that makes it fit lives in location.html. */
/* ── GROUND ─ this section's background, and no other section's ───────────
   Everything is now 900 #000851 Pavilion Blue. --navy-950 resolves to it too.
   purple #35005B · emerald #004C15 · teal #006352 · gold #C3A256 · or any hex
   These are split per section for development. Merge before launch.
   The frame inside is transparent so this line is what paints. It did
   nothing before: location.html painted its own ground over the top. */
.loc-embed{background:var(--navy-900)}
.loc-embed{display:block;border-top:1px solid var(--hair);
  min-height:100svh;position:relative}
.loc-embed iframe{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}
/* fallback only — the real height is reported by the frame on load */
@media(max-width:900px){.loc-embed{min-height:0;height:1180px}}


/* ==============================================================================
  07 . 50-lake.css
  03 - The Lake, and the amenity grid.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — 03 - THE LAKE (amenities)

   Twelve amenities on one screen: 4 x 3, pinned to the viewport.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── THE LAKE — amenity grid (Monolith) ─────────────── */
/* ── 03 THE LAKE — twelve amenities, one screen ──────────────────────
   The section is pinned to the viewport and the grid takes whatever is
   left after the heading, so three rows of four always land inside one
   window without scrolling. Everything inside a cell is sized in vh/vw
   so it compresses with the box instead of overflowing it. Below 1101px
   the column count drops and the section goes back to normal flow —
   twelve cells cannot fit a phone screen and should not try. */
/* ── GROUND ─ this section's background, and no other section's ───────────
   Everything is now 900 #000851 Pavilion Blue. --navy-950 resolves to it too.
   purple #35005B · emerald #004C15 · teal #006352 · gold #C3A256 · or any hex
   These are split per section for development. Merge before launch.
   The twelve cells have their own ground below — .amen__cell. */
.amen{background:var(--navy-900)}
.amen{border-block:1px solid var(--hair)}
/* ── the grid lines ───────────────────────────────────────────────────────
   The lines are not drawn. They ARE this element's background, seen through
   the --amen-gap gaps and the frame — the twelve cells are opaque and sit on
   top of it. So anything painted here becomes the lines, and they can be lit
   rather than flat: a FLAT resting colour, and a pool of light that follows
   the pointer, so the nearest lines come up as you cross the grid and fade
   behind you.

   The resting colour is flat on purpose. It was a 180deg gradient — gold at
   .8 along the top, .5 at the middle, .36 at the floor — which meant no two
   dividers in the grid were the same colour, and the horizontal ones visibly
   stepped down row by row. A faded line also reads as a THINNER line, so the
   grid looked unevenly ruled as well as unevenly lit. Flat .5 is that
   gradient's own midpoint, so the grid sits where it always did overall; it
   just no longer has a bright end and a dim end. If it wants to be quieter,
   var(--rule) is the site's standard gold hairline at .42.

   The lines are PBR gold, 195 162 86, and one pixel — both the gaps and the
   border, which is why the border reads var(--amen-gap) rather than a
   literal: the frame is the same background seen at the outer edge, and a
   frame of a different weight to the lines it frames reads as a mistake.
   The pointer pool went gold with them:
   left aqua, the lines nearest the cursor changed COLOUR rather than
   brightness, which is a different effect from the one this is. Putting
   96 237 244 back in the radial below is all it takes to have that instead.

   The pool is 400px because a column is ~310: at 260 only the single nearest
   line reacted, which read as a cursor effect rather than the grid responding.
   It went up with the column — the number that matters is the ratio, since
   what it buys is the lines either side of the one being crossed.

   The border is transparent for the same reason. A solid border colour paints
   over the background, which would leave the outer frame a flat hairline
   round a lit interior; letting the background through (it already extends
   under the border) makes the frame part of the same light.

   --amen-glow is registered, so it can be transitioned — an unregistered
   custom property jumps. It scales the pool's alpha, which is what fades the
   whole effect in and out on enter and leave rather than snapping.
   --mx/--my are written by index.html on pointermove. */
@property --amen-glow{syntax:"<number>";inherits:true;initial-value:0}
.amen__grid{--amen-gap:1px;--mx:50%;--my:0%;position:relative;
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--amen-gap);
  border:var(--amen-gap) solid transparent;   /* the frame is one of the same lines */
  background:
    radial-gradient(400px 400px at var(--mx) var(--my),
      rgb(195 162 86 / calc(1 * var(--amen-glow))) 0%,
      rgb(195 162 86 / calc(.6 * var(--amen-glow))) 40%,
      rgb(195 162 86 / calc(.2 * var(--amen-glow))) 70%,
      rgb(195 162 86 / 0) 100%),
    rgb(195 162 86 / .5);
  transition:--amen-glow .5s var(--e)}
.amen__grid.lit{--amen-glow:1}
@media(prefers-reduced-motion:reduce){ .amen__grid{transition:none} }
/* ── the hover plate ──────────────────────────────────────────────────────
   One picture across a 2x2 block of cells — see the mapping in index.html.
   It is a single grid item spanning two columns and two rows, which is what
   makes the image continuous: it covers the gaps as well as the cells,
   so there is nothing to line up and no seam to hide.

   Placed on the first block and MOVED with a transform rather than replaced
   by a different grid-area. grid-column is not animatable, so re-placing it
   would jump; the three blocks are all the same size, so a translate slides
   between them, and the compositor does it. One column step is half the
   plate's own width plus half a gap: the plate is 2c+g wide, so 50% is
   c+g/2, and c+g is one column plus one gap.

   pointer-events:none is load-bearing. The plate sits over four cells, and
   without it, moving onto one of them would hover the picture instead of the
   cell and the aqua fill would never fire.

   Position and pop are the separate `translate` and `scale` properties, not
   one `transform`, so the slide between blocks and the settle on appearing
   can carry their own durations.

   The step itself is set from script, in pixels, and deliberately so. It was
   written here first as translate:calc(50% + var(--amen-gap)/2) — correct
   arithmetic, and the plate landed exactly on the column when the transition
   was switched off. Chrome will not INTERPOLATE it: the transition sat in
   `running` forever at the first value, so the plate reached block B and
   never moved again. A plain length interpolates, and index.html measures the
   step off two real cells, so it also needs no gap constant and comes out
   negative under RTL without anything here knowing about direction.

   position:absolute, and it has to be. A grid item with an explicit area is
   placed FIRST and the auto-placed ones then flow around it: in flow, this
   pushed the twelve cells out of their own block and grew a fourth row. Out
   of flow it takes no part in placement, and an absolutely-positioned grid
   child still gets its grid area as its containing block — so inset:0 gives
   it exactly two columns, two rows and the gap between, measured off the
   real grid lines rather than arithmetic that could drift from them. */
.amen__plate{grid-column:1/3;grid-row:1/3;position:absolute;inset:0;z-index:2;
  overflow:hidden;pointer-events:none;background:var(--navy-950);
  opacity:0;visibility:hidden;translate:0;scale:1.015;
  /* Leaving is quicker than arriving, on purpose: a picture that lingers
     after the pointer has gone reads as lag, where one that takes its time
     appearing reads as deliberate. So the durations live on the two states
     rather than once on the element — .on is the way in, this is the way
     out, and `visibility` waits for the fade either way so the plate is not
     yanked out from under its own opacity. */
  transition:opacity .54s cubic-bezier(.33,1,.68,1),
             translate .72s cubic-bezier(.33,1,.68,1),
             scale .6s cubic-bezier(.33,1,.68,1),
             visibility 0s linear .54s}
.amen__plate.on{opacity:1;visibility:visible;scale:1;
  transition:opacity 1.1s cubic-bezier(.33,1,.68,1),
             translate .72s cubic-bezier(.33,1,.68,1),
             scale 1.1s cubic-bezier(.33,1,.68,1),
             visibility 0s linear 0s}
/* One picture per layer, stacked, so a change of amenity dissolves instead of
   cutting — see amenShow() in index.html. AMEN_FADE there is this duration. */
.amen__layer{position:absolute;inset:0;opacity:0;
  transition:opacity 1.1s cubic-bezier(.33,1,.68,1)}
.amen__layer.on{opacity:1}
.amen__plate img,.amen__plate svg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
/* a hair of the brand over the picture, so it belongs to this section */
.amen__plate::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(0,8,81,.34),rgba(0,8,81,.06) 45%,rgba(0,8,81,.42));
  box-shadow:inset 0 0 0 1px rgba(96,237,244,.32)}
/* The block mapping was drawn against the four-column grid. Two columns is a
   different set of neighbours and one column has none, so the plate simply
   does not exist there — index.html gates the hover on the same width. */
@media(max-width:1100px){ .amen__plate{display:none} }

/* ── the cue chip ─────────────────────────────────────────────────────────
   Trails the pointer until the first tile opens — see amenCue in index.html.

   translate carries the pointer position and the offset in one go: 18px down
   and across, so the chip sits clear of the cursor instead of under it, and
   the pointer stays the thing being aimed. It is set from script as a length
   pair, which interpolates; a percentage there would be measured against the
   chip's own box and drift as the text length changes between languages.

   No transition on translate. This has to sit ON the pointer, and easing the
   position would leave it lagging behind by a frame or two, which reads as
   the page being slow rather than the chip being smooth. Only the fade is
   timed. */
.amen__chip{position:fixed;left:0;top:0;z-index:60;pointer-events:none;
  translate:-100px -100px;
  padding:8px 14px;border-radius:999px;
  border:1px solid rgba(96,237,244,.4);background:rgba(0,8,81,.82);
  backdrop-filter:blur(10px);
  font-family:var(--f-sub);font-size:9.5px;font-weight:500;letter-spacing:.2em;
  text-transform:uppercase;color:var(--gold-400);white-space:nowrap;
  opacity:0;transition:opacity .45s var(--e);
  margin:18px 0 0 18px}
.amen__chip.on{opacity:1}
/* Touch has no pointer to follow and no hover to advertise, and the block
   mapping does not exist below 1101px either. */
@media(max-width:1100px){ .amen__chip{display:none} }
@media(prefers-reduced-motion:reduce){ .amen__chip{transition:opacity .15s linear} }

@media(prefers-reduced-motion:reduce){
  .amen__plate,.amen__plate.on{scale:1;transition:opacity .2s linear,visibility 0s linear .2s}
  .amen__layer{transition:opacity .2s linear}
  .amen__plate.on{transition-delay:0s,0s}
}
/* The bottom padding is the odd one out, and deliberately so. The cell is
   space-between, so the copy hangs off the bottom edge; when the cells grew
   fifteen per cent the copy went with it and left a third of the tile empty
   between the icon and the words. Padding the bottom is what lifts the copy
   back up — the block still bottom-aligns across a row, which is what keeps
   the twelve reading as a grid, it just bottom-aligns higher. */
.amen__cell{background:var(--navy-900);
  padding:clamp(18px,2vw,30px) clamp(15px,1.5vw,24px) clamp(26px,4.1vh,48px);
  display:flex;flex-direction:column;justify-content:space-between;gap:clamp(10px,1.4vh,20px);
  /* The fifteen per cent belongs to the four-column grid, which is where the
     cells are tall and narrow and where the hover picture lives. Stacked at
     two columns or one they are already half as wide again, and buying more
     height there only buys more of the empty middle this was meant to fix —
     so the floor here is the one it always was. */
  position:relative;overflow:hidden;min-height:calc(190px * var(--z))}
/* ── the hover ────────────────────────────────────────────────────────────
   Aqua #60EDF4 rises from the bottom of the cell and the copy flips to
   Pavilion Blue #000851 against it. The fill was briefly the same value as
   the cell itself, which is why the hover looked broken — it was running,
   just invisible.

   The copy is dark on a fill that takes .6s to arrive, so the colour change
   is DELAYED rather than immediate: turn the text at t=0 and it is dark on a
   still-dark cell until the aqua catches up. The cell is space-between, so
   the icon sits at the top and the copy at the bottom — the fill reaches the
   copy first (~.2s) and the corner index last, which is why they wait
   different amounts. Coming back out there is no delay: the fill drops and
   the text returns to white together. */
/* scaleY, not height. Height is a paint property: the browser has to lay the
   box out and repaint it every frame, twelve cells' worth, and it can never
   hand the work to the compositor. A transform is composited — the fill is
   the same solid block either way, so scaling one that already fills the cell
   looks identical and costs a fraction of it.

   The curve changed with it. --e is cubic-bezier(.22,1,.36,1), which reaches
   ~95% in the first third of its time and then crawls through the last few
   percent; over .6s that is nearly 0.4s of movement too small to see, which
   is what read as lag. This one lands and stops. */
.amen__cell::before{content:"";position:absolute;inset:0;background:var(--gold-500);
  transform:scaleY(0);transform-origin:bottom;
  transition:transform .42s cubic-bezier(.33,1,.68,1);z-index:0}
.amen__cell:hover::before{transform:scaleY(1)}
.amen__cell>*{position:relative;z-index:1}
/* The delay follows the fill, which clears the copy at the bottom of the
   cell inside ~50ms. (There was a second, longer delay here for the corner
   index; the numbers are gone, and the cells are identified by name now.) */
.amen__cell h4,.amen__cell p{transition:color .22s var(--e)}
/* The delay follows the fill, and the fill now has further to climb: the copy
   sits about 50px off the floor rather than 29, which the rise crosses in
   ~.09s. Turning the text at .06 would leave it dark on a still-dark cell for
   a frame or two — the exact fault this delay was added to fix. */
.amen__cell:hover h4,.amen__cell:hover p{color:var(--navy-900);transition-delay:.09s}
/* Gold — PBR secondary, RGB 195 162 86 — at rest. It used to stay gold on
   hover as well, deliberately, on the reasoning that the icon carried its own
   colour; that was reversed on request, because a cell whose title, copy and
   ground all turn while one mark stays gold reads as the icon having been
   missed rather than as an accent. All three turn together now. */
/* The icon grew with the tile too, and it had to: at 35px in a 309x228 cell
   it was a mark floating alone at the top with forty pixels of nothing under
   it. Every pixel it gains is a pixel the empty middle loses, which is the
   same arithmetic as the type below — space-between hands whatever is left
   over to the gap, so the only way to shrink the gap is to fill the cell. */
.amen__cell svg{width:clamp(32px,3.3vw,52px);height:clamp(32px,3.3vw,52px);color:var(--pv-gold);stroke-width:1.15;flex:none;
  transition:color .22s var(--e)}
/* The icon flips to navy with everything else. Its delay is the LONGEST
   of the three because the fill rises from the bottom and the icon is the
   thing furthest from it: space-between puts the icon at the top of the
   cell and the copy at the floor, so the aqua reaches the copy in ~.09s
   and the icon only near the end of the .42s climb. Turning it any
   earlier is the same fault the copy delay exists to prevent — dark ink
   on a still-dark cell for a few frames. */
.amen__cell:hover svg{color:var(--navy-900);transition-delay:.2s}
/* Both went up with the tile. The title carries most of it — it is the thing
   being read at a glance, and at the old size it was a caption under an icon
   rather than the name of a place. The description follows more gently: it
   is two lines at this width and a third line would cost more than the size
   is worth. The line-height comes in as the size goes up, which is the usual
   trade — the same setting that reads as generous at 13px reads as loose at
   15. */
.amen__cell h4{margin:0 0 7px;font-size:clamp(15px,1.35vw,calc(23px * var(--z)));line-height:1.2}
/* Two more switches from the amenities tool, independent of the faces above.
   Caps needs tracking or the letters crowd; the icon-only plate keeps the
   words in the DOM for screen readers and search, and hides them visually. */
body[data-acaps="on"] .amen__cell h4{text-transform:uppercase;letter-spacing:.06em}
body[data-atext="off"] .amen__cell h4,
body[data-atext="off"] .amen__cell p{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap}
body[data-atext="off"] .amen__cell{place-content:center}
body[data-atext="off"] .amen__cell svg{margin:0 auto}
.amen__cell p{margin:0;font-size:clamp(12.5px,1.02vw,16px);line-height:1.5;color:rgba(255,255,255,.5)}

@media(min-width:1101px){
  /* ── one VISIBLE screen, and the content centred in it ────────────────
     The section is a screen of its own, but the screen it gets is the part
     you can actually see. The nav is fixed over the top of the page and
     #amenities carries scroll-margin-top:var(--nav-h), so landing on this section
     puts its top edge exactly at the bottom of the bar — and a full 100svh
     measured from there hangs a nav's worth of section below the fold, with
     the last row of cells inside it.

     It used to give all of its slack to the TOP instead, to keep the head out
     from under the nav. scroll-margin-top does that job now, so the old
     compensation was being paid twice: ~96px of deliberate clearance under a
     bar the anchor had already cleared, and the head sitting that much lower
     than it was drawn. Centred is what it was before that workaround and
     centred is where it goes back to, now against the height it really has.

     `safe` still earns its keep. From about 1080p down the cells are at their
     ceiling and the content is taller than the screen; plain centring would
     push the head off the TOP, where nothing can scroll it back. safe falls
     back to start alignment there, so the head stays reachable and the
     overflow goes where overflow can go. The unprefixed `center` before it is
     for anything that cannot parse the keyword. */
  .amen{min-height:calc(100svh - var(--nav-h, var(--navh)));
    display:flex;align-items:center;align-items:safe center;
    padding-block:clamp(20px,3.4vh,46px)}
  /* ── 15% ─────────────────────────────────────────────────────────────
     The grid is the section, so it gets a column of its own: fifteen per
     cent wider than the one the rest of the site is set on. The HEAD comes
     with it rather than the grid alone — a heading that no longer starts on
     the grid's left edge reads as a mistake, not as emphasis.

     Written against the content width, not the max-width: --pad does not
     scale with it, so multiplying the outer box would grow the inside by
     more than fifteen. width:100% is still underneath, so a viewport too
     narrow to give it simply gives what it has. */
  .amen>.wrap{width:100%;
    max-width:calc((var(--maxw) - 2 * var(--pad)) * 1.15 + 2 * var(--pad))}
  .amen .sechead{margin-bottom:clamp(14px,2.2vh,30px);align-items:end}
  .amen .h2{font-size:clamp(1.9rem,3.4vw,calc(3.4rem * var(--z)));line-height:.96}
  .amen .lede{font-size:clamp(1rem,1.35vw,calc(1.35rem * var(--z)));line-height:1.4}
  .amen__grid{flex:1;min-height:0;grid-template-rows:repeat(3,minmax(0,1fr))}
  /* The grid is content-sized and the section had ~190px of slack under it, so
     the cells can take a fifth more height and the twelve still land on one
     screen. In vh rather than a flat number: it has to give that height back
     on a short viewport, which is what min-height:0 used to guarantee.

     All three numbers then went up fifteen per cent with the column, so the
     block grows in both directions and the cells keep their shape. At the
     drawn size that is a 228px cell against a 900px screen, and the twelve
     still land on one — but the vh term is what has to give first, which is
     why the growth is in it and not in a flat height.

     The vw term is a ceiling on that, and it earns its keep at the bottom of
     this range: a 1101px window still draws four columns, so the cells are
     243 wide, and 228 of height on top of that is a tall narrow box with the
     type at its floor and seventy pixels of nothing in the middle. Capping
     the height against the width keeps a cell roughly the shape it was drawn
     as. It binds below ~1340px and does nothing above it. */
  .amen__cell{min-height:clamp(172px,min(25.3vh,17vw),241px)}
}
@media(max-width:1100px){.amen__grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.amen__grid{grid-template-columns:1fr}}


/* ==============================================================================
  08 . 65-highlights.css
  04 - Highlights (vendor component, then the PAVILION OVERRIDES block).
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — 04 - HIGHLIGHTS (pinned media stack)

   The upper half of this file is Scroll Highlights v1.0 as delivered,
   unmodified, so it can be replaced wholesale when the component is
   updated. Everything Pavilion changes about it lives in the OVERRIDES
   block at the bottom — nothing above that line is ours.
   ═══════════════════════════════════════════════════════════════════════ */

/* ============================================================
   Scroll Highlights v1.0
   A pinned, cross-fading media stack driven by scroll position.
   Works with video, images, CSS backgrounds, or nothing at all.
   ============================================================ */

.sh {
  /* --- tuning knobs --- */
  --sh-step-vh: 37.5;          /* scroll distance per step, in vh */
  --sh-lead-vh: 46.9;          /* dead scroll before step 2 arms  */
  --sh-tail-vh: 9;             /* hold on the last step           */
  --sh-fade: 600ms;            /* media cross-fade                */
  --sh-ease: cubic-bezier(.44, 0, .56, 1);
  --sh-bg: #111;
  --sh-scrim: rgba(0, 0, 0, .6);
  --sh-fg: #faf9f6;
  --sh-text-top: 42%;
  --sh-pad: 40px;
  --sh-maxw: 800px;

  position: relative;
  width: 100%;
  background: var(--sh-bg);
  /* total height is set inline by the JS from the step count */
}

.sh__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;          /* avoids the mobile URL-bar jump */
  width: 100%;
  overflow: hidden;
  overflow: clip;
}

/* ---- media stack ---- */
.sh__media { position: absolute; inset: 0; }

.sh__layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--sh-fade) var(--sh-ease);
  will-change: opacity;
}
.sh__layer[data-active] { opacity: 1; }

.sh__layer > video,
.sh__layer > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
}

/* ---- scrim: keeps type legible over any footage ---- */
.sh__scrim {
  position: absolute;
  inset: 0;
  background: var(--sh-scrim);
  pointer-events: none;
}

/* ---- copy ---- */
.sh__text {
  position: absolute;
  top: var(--sh-text-top);
  left: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
  max-width: var(--sh-maxw);
  padding: 0 var(--sh-pad);
  color: var(--sh-fg);
  pointer-events: none;
}

.sh__eyebrow {
  margin: 0;
  font-size: 16px;
  font-weight: 300;
  line-height: 1.4;
  opacity: .9;
}

.sh__desc {
  margin: 0;
  font-size: clamp(21px, 2.1vw, 28px);
  font-weight: 400;
  line-height: 1.4;
  text-wrap: pretty;
}
.sh__desc strong { font-weight: 700; }

/* ---- per-token reveal ---- */
.sh__tok {
  display: inline-block;
  opacity: .001;
  transform: translateY(16px);
}
.sh__tok[data-in] {
  opacity: 1;
  transform: none;
  transition: opacity 450ms var(--sh-ease), transform 450ms var(--sh-ease);
  transition-delay: var(--d, 0ms);
}

/* ---- progress dots ---- */
.sh__dots {
  position: absolute;
  bottom: 160px;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  pointer-events: none;
}

.sh__dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .35);
  transition: width 400ms var(--sh-ease), background-color 400ms var(--sh-ease);
}
.sh__dot[data-active] {
  width: 24px;
  background: rgba(255, 255, 255, .95);
}

@media (max-width: 809.98px) {
  .sh { --sh-step-vh: 28; --sh-lead-vh: 35; --sh-pad: 24px; --sh-text-top: 38%; }
  .sh__dots { bottom: 96px; }
}

@media (prefers-reduced-motion: reduce) {
  .sh__layer { transition-duration: 1ms; }
  .sh__tok { opacity: 1; transform: none; }
  .sh__tok[data-in] { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   PAVILION OVERRIDES — everything below this line is ours
   ═══════════════════════════════════════════════════════════════════════ */

/* ── GROUND ─ this section's background, and no other section's ───────────
   Everything is now 900 #000851 Pavilion Blue. --navy-950 resolves to it too.
   The stage sits over four lake films, so what you mostly see is the scrim
   below rather than this — see the palettes in assets/js/lakefilm.js. */
.sh{
  --sh-bg:var(--navy-900);
  --sh-fg:var(--ivory);
  /* Flat 60% black was doing the legibility work in the component. This is a
     flat 15% of deep navy instead — the films are the section, and the veil's
     job is now to tint them rather than to cover them.

     #000318 is below the ramp entirely — 950 deep · 900 Pavilion Blue #000851
     · 850 lifted — and deeper again than the #000629 this used to carry. The
     hue is held while the value drops, so it stays navy rather than sliding
     to black: 0 · 3 · 24 keeps the same blue-to-green ratio as 0 · 6 · 41 at
     roughly three fifths the brightness. Written out rather than as a token
     because no token goes this deep.

     It went .62/.34/.78 → .72/.46/.88 → a flat .15 → a flat .45, and the
     gradient went with it: three stops exist to weight a veil top and bottom,
     and one even wash is what this section turned out to want instead.

     Keep the .15 stop on that path in mind if this is ever tuned again. At
     that strength the colour contributes about four parts in 255, so
     deepening the navy from #000629 to #000318 moved the composited picture
     by a mean of 2/255 — measured across the frame, not reasoned about. The
     ALPHA is the lever that shows; the colour only decides which way the tint
     leans. At .45 both are doing visible work, and white type is off the
     bright water at the horizon again. */
  --sh-scrim:rgba(0,3,24,.45);
  /* the copy lines up with .wrap on every other section */
  --sh-pad:var(--pad);
  --sh-maxw:calc(760px * var(--z));
  --sh-fade:520ms;
  /* PACE — the knob to reach for if the section feels fast or slow.
     --sh-step-vh is the scroll distance per step, in vh: raise it to slow the
     stepping down, lower it to speed up. --sh-lead-vh is the scroll before
     step 2 arms, so it is how long the opening step holds before anything
     moves; it is set equal to the step here so the first film behaves like
     every other one rather than sitting there.

     ONE SCROLL, ONE FILM. This was 70/72 — about seven tenths of a screen per
     step, which on a 1080px window is roughly seven notches of a mouse wheel
     before the picture changes, and four films took some 3.2 screens to get
     through. 12vh is ~130px, near enough one notch, so a step now answers a
     single scroll the way the rest of the page does. The component's own
     default is 37.5/46.9 and even that was four notches.

     They set the section's total height between them: with four steps it is
     1 + lead + 2·step + tail viewports, so 12/12 gives ~1.4 screens.

     The cost is a trackpad, where one flick can travel several hundred pixels
     and will carry more than one film with it. That is the trade for a step
     this short — raise both numbers together if it overshoots. */
  --sh-step-vh:12;
  --sh-lead-vh:12;
}

/* The component ships its own tuning under 810px, but this block sits below
   it in the file and would win at every width — so the small-screen pace has
   to be restated here or it never applies. LARGER than desktop, which is the
   opposite of what it used to be: a thumb flick travels two or three hundred
   pixels at once where a wheel notch travels about a hundred, so the step
   that reads as "one scroll" on a phone is the longer of the two. */
@media (max-width: 809.98px){
  .sh{ --sh-step-vh:26; --sh-lead-vh:26; }
}

/* the label reads as the section index it replaces */
.sh__eyebrow{font-family:var(--f-micro);font-size:calc(11px * var(--zt));font-weight:500;
  letter-spacing:.3em;text-transform:uppercase;color:var(--gold-400);opacity:1}

/* headline role, same as .lede elsewhere */
.sh__desc{font-family:var(--f-firma);font-weight:300;letter-spacing:-.02em;
  font-size:clamp(1.25rem,2.3vw,calc(2.1rem * var(--z)));line-height:1.35}
.sh__desc strong{font-weight:300;color:var(--gold-300)}

/* the dots match the one the nav puts against the current section */
.sh__dot{background:rgba(255,255,255,.3)}
.sh__dot[data-active]{background:var(--gold-500)}

/* The component positions its copy with `left`, which does not flip. Arabic
   and Kurdish need the logical side or the text lands against the wrong edge
   and reads ragged-in from the wrong margin. */
.sh__text{left:auto;inset-inline-start:0}

/* The films are canvases mounted into each layer, so they need to fill it the
   way the component's own <video>/<img> do. */
.sh__layer > .sh__film{position:absolute;inset:0}
.sh__layer > .sh__film canvas{display:block;width:100%;height:100%}

/* ── scroll cue ───────────────────────────────────────────────────────────
   Centred under the dots. Micro caps and a
   1px gold hairline fading to nothing -- but horizontal, because it is
   sitting under a centred row rather than pinned to an edge.

   The travelling segment is a pseudo-element sliding down the hairline, so
   the whole thing is one compositor-friendly transform and no layout. It
   retires on the last step: the JS sets data-done, and pointer-events stay
   off throughout so it never eats a click meant for the section. */
/* The 20px is the cue sitting lower than it used to: the word was three
   pixels under the dots, which read as one control rather than as a label
   and a hint. The whole cue drops, not the word alone — the box is pinned by
   its BOTTOM edge, so adding space above the label only grows the box upward
   and the word never moves. */
.sh__cue{position:absolute;left:0;right:0;bottom:calc(160px - 62px - 20px);z-index:2;
  display:flex;flex-direction:column;align-items:center;gap:9px;
  pointer-events:none;opacity:1;transition:opacity .5s var(--sh-ease)}
.sh__cue[data-done]{opacity:0}
.sh__cue-l{font-family:var(--f-micro);font-size:calc(9.5px * var(--zt));
  font-weight:500;letter-spacing:.32em;text-transform:uppercase;
  color:var(--pv-gold)}
/* The fall stays as it was — a faint white track and an aqua segment running
   down it. Only the word above it went gold: the line is the movement, and
   gold moving down a gold track lost the thing that made it read as falling. */
.sh__cue i{position:relative;display:block;width:1px;height:38px;
  background:rgba(255,255,255,.16);overflow:hidden}
.sh__cue i::after{content:"";position:absolute;inset-inline-start:0;top:0;
  width:1px;height:18px;background:linear-gradient(to bottom,transparent,var(--gold-500));
  animation:sh-cue 2.4s var(--sh-ease) infinite}
@keyframes sh-cue{
  0%{transform:translateY(-18px);opacity:0}
  30%{opacity:1}
  80%{opacity:1}
  100%{transform:translateY(38px);opacity:0}
}
/* Half the drop on a phone: there is less room under it to give away. */
@media (max-width:809.98px){ .sh__cue{bottom:calc(96px - 58px - 10px)} }
@media (prefers-reduced-motion: reduce){
  .sh__cue i::after{animation:none;transform:translateY(10px)}
}


/* ==============================================================================
  09 . 70-villas.css
  05 - The Collection: the rail, and the --rs/--rw fit factors.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — 02 - THE COLLECTION (rail)

   Horizontal rail driven by vertical scroll. Villa figures are pinned
   to one fixed size so they never resize with the viewport.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── VILLAS — Meridian rail ─────────────── */
/* ── GROUND ─ this section's background, and no other section's ───────────
   Everything is now 900 #000851 Pavilion Blue. --navy-950 resolves to it too.
   purple #35005B · emerald #004C15 · teal #006352 · gold #C3A256 · or any hex
   These are split per section for development. Merge before launch.
   A film runs behind the rail; .rail__veil below sets how much shows. */
/* ── THE RAIL KEEPS PAVILION BLUE ─────────────────────────────────────────
   Reverted by request after the site went black: this one section reads
   better in the original colour.

   It is done by REDECLARING the tokens on .rail rather than by rewriting the
   forty rules underneath it. Custom properties inherit, so every descendant
   that already says var(--navy-850) or var(--rule) resolves to the old value
   inside here and to the black palette everywhere else — no rule below this
   line had to change, and deleting this block is the whole revert.

   The gold rules come back with it: the rail's own top and bottom edges and
   the divider between panels were on that switch, and they are what the
   section looked like. */
.rail{
  /* Navy, with the rest of the page. This block used to force the ground to
     #000 — copied from pavilion-black, where a pure black ground under a pure
     black veil let the blue arrive only from the panorama glowing through.
     The client has asked for Pavilion Blue back, here as everywhere, so the
     three ground declarations are REMOVED rather than set to #000851: the
     section follows the page from now on instead of keeping its own copy of a
     value that has now changed twice.

     Removing them also fixed the Enquire page, which was black for a reason
     nobody would guess. contact.html has its own .rail — a layout rail, the
     vertical lines its header, content and footer align to — and its three
     bands are <header>/<main>/<footer> class="ct__* rail". They therefore
     matched THIS rule too and took the villa section's ground with it. The
     names still collide; it is invisible now only because both want the same
     ground. Give this section its own ground again and give it a selector of
     its own at the same time — the villa rail is section.rail#villas. */
  --ivory:#FFFFFF;
  --hair:rgba(255,255,255,.12); --hair-2:rgba(255,255,255,.06);
  --rule:rgb(195 162 86 / .42); --rule-2:rgb(195 162 86 / .3);
  --mute:rgba(255,255,255,.62); --faint:rgba(255,255,255,.36);
  background:var(--navy-900);
}
/* pan-y hands horizontal drags to the swipe handler while the browser keeps
   vertical scrolling native — see "SWIPE THE RAIL" in index.html */
.rail__track{touch-action:pan-y}
.rail{position:relative}
.rail__sticky{position:sticky;top:0;height:100svh;overflow:hidden;border-block:1px solid var(--rule)}
.rail__pano{position:absolute;inset:0;width:250%;will-change:transform}
/* Written out rather than tokenised: a gradient cannot take a colour from
   a var without the channels being a var too, and this is the only place
   that would need it.

   PAVILION BLUE, the original three stops. It was black for the span of the
   black palette — the argument then was that black at .26 through the middle
   lets the panorama's own blue come through as light where a navy veil tints
   the plate and raises its floor. True, and the raised floor is the point:
   the client has asked for the blue scenery back.

   The alphas are unchanged, so the veil still does its other job of holding
   contrast under the panel copy. Only the colour moved.

   The panorama behind it needed nothing. Its palette is PVFilm.PAL.duskBlue,
   which is the ORIGINAL dusk values under a new name: when the site went
   black, dusk was regraded to greys and the blue set was preserved beside it
   so the rail could keep pointing at it. So this one gradient is the whole
   difference between the rail's scenery now and before. */
.rail__veil{position:absolute;inset:0;z-index:1;background:linear-gradient(180deg,rgba(0,8,81,.72),rgba(0,8,81,.26) 42%,rgba(0,8,81,.94))}
.rail__track{position:absolute;inset:0;z-index:2;display:flex;align-items:center;will-change:transform}

/* ── FIT THE PANEL TO THE VIEWPORT ────────────────────────────────────────
   A villa panel is a fixed-size card: 700px wide, and ~820px of content that
   does not shrink — the 540x304 figure alone is over a third of it. Inside
   its padding it wants about 1120px of viewport height. A 27" desktop hands
   it ~1330px and it breathes; a 13" laptop hands it ~790px, so the same card
   overran the panel by ~264px, clipped against the top of the sticky
   viewport and sat on top of the scrubber.

   Zooming the browser out to 80% fixed it because zoom re-tunes nothing: it
   shrinks every fixed length by one factor and leaves the proportions alone.
   --rs is that factor, measured rather than chosen. It is exactly 1 wherever
   there is room -- so nothing above ~1120px tall changes by a single pixel --
   and eases down from there with no breakpoint to resize through. At the two
   machines this was drawn on it lands on .80 (1512x830) and 1.0 (2560x1330).

   tan(atan2()) is how you get a unitless ratio out of a length in plain CSS:
   calc(100svh / 1120px) is not a number, so it cannot be a multiplier.

   The width term only bites below ~900px, where the 700px panel starts to
   fill the screen; above that it is inert and height alone decides. Width has
   its own .82 floor because the vertical squeeze is the real problem -- let
   the panel get too narrow and the special-features pills wrap onto an extra
   line, which costs back more height than the narrowing saved.

   SAFARI, and why height and width are no longer combined with min().
   This was written as min(tan(atan2(100svh,1120px)), tan(atan2(100vw,900px))).
   Safari resolves a single trig call inside a clamp() -- that is what --z and
   --vs are, and both work there -- but drops the factor when a min() wraps two
   of them. A custom property is only checked when it is substituted, so the
   bad value did not fail here: it failed in all 21 declarations that read it,
   each one at once. Padding, panel width and the figure box all fell back to
   auto/0, the card burst its panel, and the rail looked broken in Safari only.

   The two terms are separated instead. Height drives it everywhere; width
   takes over exactly where it used to win the min(), which is the aspect ratio
   below -- 900/1120 is the point where 100vw/900 dips under 100svh/1120, so the
   media query is the same comparison, made by the engine rather than by CSS.
   Measured equal at 2560x1440, 1470x956, 1280x800, 820x1180 and 768x1024.

   The @property registrations below are the second half of the guard. An
   unregistered custom property that fails is poison -- it takes every
   declaration reading it down with it, and var(--rs,.85) does NOT catch it,
   because the fallback is only for a property that was never set. Registered
   with a syntax and an initial-value, a failed value falls back to that
   initial-value instead, so an engine that cannot do this arithmetic gets a
   rail at a fixed scale rather than no rail at all. */
@property --rs{syntax:"<number>";inherits:true;initial-value:.85}
@property --rw{syntax:"<number>";inherits:true;initial-value:.88}
@property --pill{syntax:"<number>";inherits:true;initial-value:1}

.rail{--rs:clamp(.68, tan(atan2(100svh,1120px)), 1);
  --rw:max(var(--rs),.82);
  /* see --pill on .spec__list below */
  --pill:var(--rs)}
/* Tall and narrow: the width term is the smaller of the two here, and only
   here. Equivalent to the min() it replaces -- see the note above. */
@media(max-width:900px) and (max-aspect-ratio:900/1120){
  .rail{--rs:clamp(.68, tan(atan2(100vw,900px)), 1)}
}
.rpanel{flex:0 0 auto;height:100%;display:flex;flex-direction:column;justify-content:center;
  /* The bottom reserve is deliberately larger than the top one. A panel taller
     than its box overflows both ends equally, and the scrubber is the only
     thing either end can collide with — so the room has to be taken out of
     the bottom, where it is needed, rather than left sitting above the
     heading. With the fuller feature lists the six typologies now carry, 17vh
     left the Gallery button touching the scrubber on every laptop while 40-50px
     went spare at the top. The 180px cap is unchanged, so a 1440p desktop --
     where the cap binds -- is pixel-identical to before. */
  padding:calc(clamp(84px,13vh,150px)*var(--rs)) calc(clamp(28px,4.4vw,80px)*var(--rs)) calc(clamp(140px,22vh,180px)*var(--rs));
  position:relative}
/* ── the divider, and why it is not a border ──────────────────────────────
   It was border-inline-start, and a border is the full height of its box.
   The box here is the full height of the sticky viewport, which is where the
   scrubber lives — so six hairlines ran down through THE COLLECTION and the
   tick labels, and the strip read as part of the panels rather than as the
   navigation for them. A pseudo-element can stop where a border cannot.

   --scale-h is the scrubber's measured height, published by 50-villas.js on
   load, on fonts.ready and on resize. Measured rather than guessed: the strip
   drops its meta line under 730px of viewport and scales with --rs, so no
   constant is right at two sizes. The 0px fallback is deliberate — if the
   measurement never runs the divider is full height, which is what it was
   before, rather than a hairline floating in the middle of the panel. */
.rpanel::before{content:"";position:absolute;top:0;bottom:var(--scale-h,0px);inset-inline-start:0;
  width:1px;background:var(--rule-2);pointer-events:none}
.rpanel:first-child::before{display:none}
.rpanel--intro{width:min(94vw,calc(820px*var(--rw)))}
.rpanel--villa{width:calc(700px*var(--rw))}
.rpanel--end{width:min(94vw,calc(780px*var(--rw)))}
.rpanel__k{display:flex;align-items:baseline;gap:14px;margin-bottom:calc(18px*var(--rs))}
/* The type floors above and here are the one place this parts company with
   browser zoom, which would happily take a 9.5px label down to 6.5px. Space
   is what the panel is short of, not legibility: the padding, the figure and
   the vertical rhythm carry the shrink, and the small labels hold near full
   size. */
.rpanel .body{font-size:calc(15.5px*max(var(--rs),.9));line-height:1.68;margin-block:calc(15.5px*var(--rs))}
.rpanel__k .no{font-size:calc(clamp(1.9rem,3.6vw,3rem)*var(--rs));font-weight:700;color:var(--gold-400);line-height:1;letter-spacing:-.04em;font-variant-numeric:tabular-nums}
.rpanel__k .lb{font-size:10.5px;letter-spacing:.26em;text-transform:uppercase;color:var(--faint)}
/* ── TIER SHINE — the tier-shine study's four materials, tool-switched ────
   display.nameShine in data/villas.json turns the six typology names into
   their tier's material — Diamond, Platinum, Gold, Premium, each the
   study's verbatim ramp — on the rail and in the overlay, in WHICHEVER face
   the font switch chose: these classes only paint, they never set type.

   Mechanics identical to the About line (.h2--miracles), because they were
   paid for there: one element, glint layer over ramp layer, both clipped to
   the glyphs (an ::after ghost-doubles); glint no-repeat and parked inside
   the 0-100% window (outside it the layer slides off the glyphs); ramp
   repeats and travels -300% per cycle, which tiles seamlessly. One shared
   keyframe track — only the ramp, the glow and the pace differ per tier,
   at the study's own speeds. Reduced motion holds frame one: the material
   without the motion. The intro and end headings carry no tier, so the
   switch never touches them. */
.tshine{background-size:300% 100%,300% 100%;background-repeat:no-repeat,repeat;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;
  animation:tshine var(--tsd,6s) linear infinite}
@keyframes tshine{
  0%  {background-position:8% 50%, 0% 50%}
  40% {background-position:8% 50%, -120% 50%}
  55% {background-position:92% 50%, -165% 50%}
  100%{background-position:92% 50%, -300% 50%}
}
.tshine--diamond{--tsd:5s;
  background-image:
    linear-gradient(100deg, transparent 0%, transparent 42%, rgba(255,255,255,.95) 50%, transparent 58%, transparent 100%),
    linear-gradient(100deg,
      #cfeeff 0%, #ffffff 6%, #8fe3ff 12%, #ffffff 18%, #dfd6ff 24%,
      #ffffff 30%, #b7f0ff 36%, #ffffff 42%, #eaf6ff 48%, #ffffff 54%,
      #9fe8ff 60%, #ffffff 66%, #e3d9ff 72%, #ffffff 78%, #c2f0ff 84%,
      #ffffff 90%, #cfeeff 100%);
  filter:drop-shadow(0 0 26px rgba(150,225,255,.5)) drop-shadow(0 2px 12px rgba(0,0,0,.6))}
.tshine--platinum{--tsd:7s;
  background-image:
    linear-gradient(100deg, transparent 0%, transparent 42%, rgba(255,255,255,.95) 50%, transparent 58%, transparent 100%),
    linear-gradient(100deg,
      #7b8493 0%, #c9d2de 10%, #ffffff 18%, #aab4c2 28%, #6f7887 40%,
      #dfe6ef 52%, #ffffff 60%, #9aa4b3 72%, #6f7887 84%, #cdd6e2 100%);
  filter:drop-shadow(0 0 20px rgba(200,215,235,.35)) drop-shadow(0 2px 12px rgba(0,0,0,.6))}
.tshine--gold{--tsd:6.5s;
  background-image:
    linear-gradient(100deg, transparent 0%, transparent 42%, rgba(255,255,255,.9) 50%, transparent 58%, transparent 100%),
    linear-gradient(100deg,
      #6d4f18 0%, #C3A256 10%, #f7e6a8 18%, #fffbe8 24%, #C3A256 34%,
      #8a6a24 44%, #e8cf86 56%, #fffbe8 64%, #C3A256 74%, #7a5c1e 86%,
      #e0c479 100%);
  filter:drop-shadow(0 0 24px rgba(195,162,86,.45)) drop-shadow(0 2px 12px rgba(0,0,0,.6))}
.tshine--premium{--tsd:6s;
  background-image:
    linear-gradient(100deg, transparent 0%, transparent 42%, rgba(255,255,255,.9) 50%, transparent 58%, transparent 100%),
    linear-gradient(100deg,
      #12545e 0%, #2f9aa8 10%, #9df3fa 18%, #ffffff 25%, #60EDF4 34%,
      #1b6f7a 46%, #8fe9f2 58%, #ffffff 66%, #60EDF4 76%, #17606b 88%,
      #a6f2f8 100%);
  filter:drop-shadow(0 0 24px rgba(96,237,244,.42)) drop-shadow(0 2px 12px rgba(0,0,0,.6))}
@media(prefers-reduced-motion:reduce){ .tshine{animation:none} }

/* The intro heading's size follows the font switch: caps in the sub face
   run wider than the title case the original clamp was cut for. */
body[data-vfont="sub"] .rpanel--intro h3{font-size:clamp(2rem,4.6vw,4rem)}
body[data-vfont="display"] .rpanel--intro h3{font-size:clamp(2.2rem,5.6vw,4.8rem)}
.rpanel h3{font-size:calc(clamp(1.8rem,4.2vw,3.4rem)*var(--rs));font-weight:600;letter-spacing:-.035em;line-height:1;text-transform:uppercase;margin:0 0 calc(14px*var(--rs))}
/* Fixed, identical on every typology. The panel used to be min(90vw,700px)
   with fluid padding, so the figure resized with the viewport and the two
   villas carrying a special-features row squeezed it further — flex:none
   stops it being compressed by taller siblings. */
.rpanel__fig{position:relative;width:calc(540px*var(--rs));height:calc(304px*var(--rs));flex:none;margin-top:calc(22px*var(--rs));border:1px solid var(--hair);overflow:hidden;background:var(--navy-900);cursor:pointer;transition:border-color .45s var(--e)}
.rpanel__fig svg,.rpanel__fig canvas,.rpanel__fig img{position:absolute;inset:0;width:100%;height:100%;transition:transform 1s var(--e2)}
.rpanel__fig:hover{border-color:rgba(196,163,99,.55)}
.rpanel__fig:hover svg{transform:scale(1.07)}
/* ── a supplied cut-out has no frame ──────────────────────────────────────
   The plate and the hairline belong to the generated stand-in, which is
   drawn as a framed elevation and needs a ground to sit on. A rail.png is a
   silhouette on a real alpha channel — behind one, those two are a box
   nobody asked for, and the gold hover outline becomes a rectangle drawn
   around a floating object. So the frame goes, and the hover affordance
   moves to the lift the drawing already had.

   :has() rather than a class from the render path, because the same art()
   feeds the rail and the detail overlay and neither should have to know.
   Where :has() is unsupported the rule is simply dropped and the framed
   version comes back, which is the old behaviour rather than a broken one. */
.rpanel__fig:has(img),.rpanel__fig:has(img):hover{background:none;border-color:transparent}
.rpanel__fig:hover img{transform:scale(1.07)}

/* ── a cut-out is drawn 10% larger, and nothing else moves ─────────────────
   The frame is already invisible behind a rail.png, so the box can grow past
   the size the panel was measured around — as long as the growth is handed
   straight back as margin. 540x304 becomes 594x334.4; the extra 30.4px of
   height is split, 15.2 taken off the top margin and 15.2 given back as a
   negative bottom margin, so the heading above and the Gallery button below
   sit exactly where they did and the panel measures the same.

   The width grows only towards the inline end. Anchoring it there keeps the
   drawing's leading edge on the same line as the heading, the copy and the
   button — growing it symmetrically would push a 27px bulge past the column
   everything else in the panel is aligned to. Five of the six cut-outs are
   wider than the box, so the drawing fills that width and the alignment is
   the visible edge of the villa itself.

   Only :has(img). The generated stand-in is a framed elevation with a plate
   and a hairline, and enlarging THAT enlarges a visible rectangle sitting off
   the text column — the exact thing the anchoring above avoids. Where :has()
   is unsupported both rules drop together and the old size comes back.

   The query is the exact complement of the one that unrolls the rail into a
   vertical stack, below. Keep the two in step: in the stack the figure is
   width:100% with a 16/9 ratio, and a fixed pixel width here outranks it —
   which on a short wide screen (1600x600 unrolls the rail) would have put a
   594px card in a full-width column.                                         */
@media(min-width:701px) and (min-height:680px){
  .rpanel__fig:has(img){
    width:calc(594px*var(--rs));height:calc(334.4px*var(--rs));
    margin-block:calc(6.8px*var(--rs)) calc(-15.2px*var(--rs))}
}
.rpanel__open{display:inline-flex;align-items:center;gap:11px;margin-top:calc(20px*var(--rs));align-self:flex-start;
  padding:calc(12px*var(--rs)) calc(20px*var(--rs));border:1px solid var(--edge);border-radius:999px;
  font-size:calc(10.5px*max(var(--rs),.92));font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:var(--ivory);
  transition:.4s var(--e)}
.rpanel__open em{font-style:normal;color:var(--gold-400);font-variant-numeric:tabular-nums}
/* White at rest — it was --mute, 62% white, which on this navy reads as a
   disabled control rather than a quiet one — and gold on hover, ring and
   label together. The hover border was --gold-500, which is aqua in this
   palette however it is named. */
.rpanel__open:hover{border-color:var(--pv-gold);color:var(--pv-gold)}
/* ── the micro-labels, and why they are painted rather than resized ────────
   At 9.5px with .22em tracking, Metropolis's curved capitals are about
   two-thirds of a pixel wide. Antialiasing turns that into a part-covered
   pixel while the flat stems of T, A and R land on whole ones, so S, O, C and
   G come out lighter than their neighbours: START read as "sTART" and
   BEDROOMS lost both its O's. The letters were always correct capitals —
   measured, an uppercased "start" paints identically to a typed "START" —
   and the S is in fact 3/200ths TALLER than the T. Only the ink was wrong.

   Two ways to add ink. Size is the obvious one and is the one not taken: the
   rail clears its scrubber by 26px on five of the six typologies at 1500x950,
   and a point on two label rows spends an eighth of that before a single
   short laptop is considered. So the fix is colour plus a hairline stroke —
   --ivory instead of --faint, and a 0.28px stroke centred on the outline.
   A stroke is painted, not laid out: advance widths, line heights and every
   wrap in the specification list are bit-identical to before, which is the
   whole reason it is used here instead of a heavier weight. (A heavier cut
   would also mean a second Metropolis file; the one we have claims 400 700
   precisely so the browser never fakes one.) */
.rpanel__stats{display:flex;gap:calc(clamp(18px,2.6vw,42px)*var(--rs));flex-wrap:wrap;margin-top:calc(22px*var(--rs));padding-top:calc(18px*var(--rs));border-top:1px solid var(--rule)}
.rpanel__stats b{display:block;font-size:9.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--ivory);-webkit-text-stroke:.16px currentColor;margin-bottom:calc(6px*var(--rs));font-weight:500}
.rpanel__stats span{font-size:calc(18px*max(var(--rs),.86));font-weight:600;font-variant-numeric:tabular-nums}
/* scale bar lives inside the sticky viewport, so it only exists while the
   rail is on screen — a fixed bar would follow the whole page */

/* special-feature cell: breaks out of the fixed stat columns and wraps as a
   list of aqua pills, so a six-item plan does not stretch the row */
.st--spec{flex:1 1 100%;max-width:100%}
@media(min-width:1100px){.st--spec{flex:1 1 260px}}
/* ── the pill row reserves its full height ────────────────────────────────
   The panel is justify-content:center, everything above the figure is the
   same height on every typology, and this list is the only thing that is
   not: it wraps to between one and four rows depending on how many special
   features a typology has. Centring then splits that difference and the
   figures sit at six different heights — 32px apart at 1512x872.

   So the list always reserves four rows, the most any typology uses, and the
   ones with fewer pad out below. Nothing moves on the tallest panel; the
   others come up to meet it. If a typology ever needs a fifth row, change
   the 4 here and re-check the scrubber clearance, because every panel gets
   taller together.

   The row height is measured, not guessed: 1lh is this element's own line
   box, so it follows the font the page is set in — Arabic's line box is
   18.5px against English's 16px at the same 10px size, and a fixed number
   would have left Arabic short. That is also why font-size moves from 0 to
   the pill's own size: 0 made lh useless. It only ever existed to beat
   .rpanel__stats span above, which the !important still does, and a flex
   container renders no whitespace between its children either way. */
/* ── the pill component is shared, so it cannot name --rs ─────────────────
   statsHTML() renders this list into two places: .rpanel__stats in the rail,
   which scales by --rs, and .vdet__stats in the villa overlay, which is
   position:fixed outside .rail and scales by --vs. Naming --rs here made
   every declaration below invalid at computed-value time inside the overlay:
   the font-size fell back to the inherited 16px of .vdet__stats span and the
   padding and gap to zero, so the pills came out huge and touching.

   --pill is whichever factor is in scope — .rail sets it from --rs, .vdet
   from --vs — and falls back to 1 so a third home for this list would render
   at full size rather than collapse. */
.spec__list{display:flex!important;flex-wrap:wrap;align-content:flex-start;gap:calc(6px*var(--pill,1));
  font-size:calc(10px*max(var(--pill,1),.92))!important}
/* Only in the rail, and only while it is a rail. The reserve exists so six
   panels standing side by side line up; in the villa overlay there is one
   panel and nothing to line it up with, and in the stacked mobile layout the
   panels are above one another — both would just get up to three rows of
   dead space under the pills. */
.rpanel__stats .spec__list{min-height:calc(4 * (1lh + 12px*var(--pill,1) + 2px) + 3 * 6px*var(--pill,1))}
/* The edge and the fill stay aqua; the LABEL is white. Deliberate — do not
   "match" the text back to the border. --ivory is #FFFFFF inside .rail and
   #F5F5F5 in the villa overlay, so each pill takes its own surface white. */
.spec__list i{font-style:normal;font-family:var(--f-micro);font-size:calc(10px*max(var(--pill,1),.92));font-weight:500;letter-spacing:.14em;
  text-transform:uppercase;padding:calc(6px*var(--pill,1)) calc(11px*var(--pill,1));border-radius:999px;
  border:1px solid rgba(96,237,244,.42);color:var(--ivory);background:rgba(96,237,244,.07);white-space:nowrap}
.rail__scale{position:absolute;left:0;right:0;bottom:0;z-index:5;padding:0 var(--pad) calc(20px*var(--rs))}
/* ── the scrubber reads in white, and gold is where you are ───────────────
   One rule for the whole strip: everything that names a place you are NOT is
   white, and the one you are ON turns gold.

   The rest alphas are NOT a straight swap of the gold ones they replace.
   Emphasis has to keep running toward the active item, and #C3A256 at full is
   no brighter than white at ~58% — so white at the old 72% would have made
   ARRIVING at a villa a step down. Each rest value is instead matched to the
   luminance its gold predecessor had: white .48 measures Y .196 where gold .72
   measured .186, and white .33 measures .089 where gold .5 measured .087. The
   strip is therefore exactly as quiet at rest as it was, and arriving is a
   1.7x step on the label and 3.7x on the tick, on top of the 6->10px growth.

   Written as literals on both sides so a later edit cannot reach for a token
   that sounds right: --gold-300/400/500 are AQUA in this palette whatever they
   are called, and the rail's own --ivory is pure #FFFFFF, not the site's
   #F5F5F5 — which is why the whites here are rgb(255 255 255 / a).

   The cursor and the readout's villa name are gold for the same reason the
   active label is: they say where you are, not what is on offer. The fill was
   already gold and stays — it is the ground you have covered. */
.rs__meta{display:flex;justify-content:space-between;align-items:baseline;gap:16px;margin-bottom:calc(12px*var(--rs));
  font-size:10px;letter-spacing:.24em;text-transform:uppercase;color:rgb(255 255 255 / .48)}
.rs__meta b{color:var(--pv-gold);font-weight:500}
.rs__line{position:relative;height:1px;background:var(--rule);margin-bottom:calc(9px*var(--rs))}
.rs__fill{position:absolute;inset-inline-start:0;top:0;height:1px;background:var(--pv-gold);width:0}
.rs__cursor{position:absolute;top:-4px;width:1px;height:9px;background:var(--pv-gold);inset-inline-start:0}
.rs__ticks{position:relative;height:calc(24px*var(--rs))}
.rs__t{position:absolute;top:0;transform:translateX(-50%);display:grid;justify-items:center;gap:5px;cursor:pointer}
.rs__t i{display:block;width:1px;height:6px;background:rgb(255 255 255 / .33);transition:.3s}
.rs__t span{font-size:8.5px;letter-spacing:.16em;text-transform:uppercase;color:rgb(255 255 255 / .48);white-space:nowrap;transition:.3s}
.rs__t:hover i,.rs__t.on i{background:var(--pv-gold);height:10px}
.rs__t:hover span,.rs__t.on span{color:var(--pv-gold)}
@media(max-width:820px){.rs__t span{display:none}}
/* Under ~730px of viewport height the panel has taken all the shrink it can
   without the type going small, and the two villas carrying a features row
   still reach the scrubber. The meta line is the thing to give up: the tick
   labels directly beneath it already name the villa, and they are still on
   screen at these widths. */
@media(max-height:730px) and (min-width:821px){.rs__meta{display:none}}
/* Below 700px wide the rail unrolls into an ordinary vertical stack — and
   below 680px TALL for the same reason. A panel needs ~820px of content to
   sit in; a 1080p Windows screen at 150% scaling hands the page 608px of
   viewport, and no honest value of --rs fits the card into that. Squeezing it
   would mean 16px headings and a floor plan the size of a stamp, so the rail
   gives up the horizontal format and becomes the stack it already is on a
   phone. 680 is measured, not chosen: at 641px the worst panel still sat 2px
   over the scrubber, at 660 it cleared by 8 and at 680 by 16. Keep this
   condition in step with `wide` in index.html — the JS that drives the travel
   has to agree about when the rail is a rail. */
@media(max-width:700px),(max-height:679px){
  .rail{height:auto!important;--rs:1;--rw:1}
  .rpanel__stats .spec__list{min-height:0}
  .rail__sticky{position:static;height:auto}
  .rail__track{position:relative;transform:none!important;flex-direction:column;align-items:stretch}
  /* NO FILM BEHIND THE STACK. The panels are transparent — measured
     rgba(0,0,0,0) — so a film behind them is not a backdrop, it is a moving
     picture read THROUGH the copy: the veil is only 26% opaque through its
     middle, which is exactly where the villa names and prose sit. On the
     horizontal rail that is the whole idea, one film travelling behind six
     panels. Stacked, it is texture behind body text.

     Hidden rather than left to show in the gaps, because there are no gaps:
     the panels are contiguous, so the only place it could appear is through
     them. Hiding it also STOPS it — PVFilm.simple() runs off an
     IntersectionObserver and a display:none element never intersects, so a
     phone is no longer animating a full-screen canvas nobody can see.

     The veil goes with it: over the flat ground it would only darken the top
     and bottom of this section against its neighbours, which is the banding
     the per-section ground rules exist to avoid. What is left is .rail's own
     --navy-900, the same ground as the sections either side.

     IF IT EVER COMES BACK, it must be position:sticky and never fixed. Fixed
     escapes every ancestor: the pano became a viewport-covering canvas for
     the whole page, painting its lake over the hero (the film underneath
     playing, invisibly) and over the whole of About — which is what "the
     about is gone on mobile" was. Sticky is clipped to the rail's own life,
     and the negative margin hands its slot back so the panels overlap it
     rather than stacking below:
         .rail__pano{position:sticky;top:0;height:100svh;width:100%;
                     margin-bottom:-100svh} */
  .rail__pano,.rail__veil{display:none}
  /* Belt and braces: an opaque panel cannot show anything behind it, whatever
     anyone puts back there later. */
  .rpanel{position:relative;z-index:1;background:var(--navy-900)}
  .rpanel{width:auto!important;height:auto;padding:clamp(56px,9vh,90px) var(--pad);border-inline-start:0;border-top:1px solid var(--rule-2)}
  /* stacked: the divider is the border-top above, and the scrubber is gone */
  .rpanel::before{display:none}
  .rpanel__fig{width:100%;height:auto;aspect-ratio:16/9}
  
.rail__scale{display:none}
}



/* ==============================================================================
  10 . 75-villa-detail.css
  Villa overlay + set toggle, and the --vs fit factor.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — VILLA DETAIL OVERLAY

   The full-screen villa panel: detail left, gallery right, and the
   Exterior/Interior toggle centred on the gallery column.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── VILLA DETAIL + GALLERY ───────────────
   Click a typology: its detail settles on the left, the right half becomes a
   gallery. Fixed rather than nested in the rail so it never inherits the
   rail's transform, and calm enough that it reads as a change of view rather
   than a pop-up. */
/* ── FIT THE PANEL TO THE VIEWPORT ────────────────────────────────────────
   Same idea as --rs on the rail, and for the same reason — see the long note
   in 70-villas.css. This panel never overflowed: .vdet__fig is the only
   flexible thing in the left column, so a short viewport does not clip
   anything, it just quietly eats the floor plan. On a 27" the plan is 583px
   tall; on a 1280x700 window it had collapsed to 111px, which is no longer a
   drawing you can read.

   So --vs buys the plan its height back, by taking it out of the padding, the
   gaps and the display type — everything in the column that is a fixed cost.
   Not the top padding, though: that is the room the close button, the
   prev/next pair and the gallery toggle sit in, and none of those scale.
   1 above ~1000px tall, so the desktop is untouched. Held above .76 because
   there is nothing to rescue below that: the panel is sound, only tight. */
.vdet{--vs:clamp(.76, tan(atan2(100svh,1000px)), 1);
  /* the shared pill row reads this — see .spec__list in 70-villas.css */
  --pill:var(--vs);
  position:fixed;inset:0;z-index:150;display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);
  /* OPAQUE, AND NO BACKDROP FILTER — this is a performance rule, not a look.
     It was rgba(0,8,81,.96) with blur(20px) saturate(130%), and the two
     together cost the page everything while the panel was open: a filter over
     a full-viewport element makes the browser rasterise the ENTIRE page
     behind it — four canvas films, six villa drawings, the amenity plates —
     and blur that raster, continuously, for whatever the four per cent of
     backdrop showing through was worth. On this navy it was worth nothing:
     rendered both ways at 1440x900, not one pixel of 1,296,000 differed by a
     single level. Opaque also lets the browser skip the page behind entirely
     rather than compositing it under a translucent sheet.

     What made it visible was leaving native fullscreen. In fullscreen the
     browser stops rendering everything outside the promoted element, so
     coming back had to rebuild the whole page from cold AND blur it — which
     is the three to five seconds the panel hung for on the way out. */
  background:var(--navy-950);
  /* visibility switches at once on the way in and only after the fade on
     the way out. Transitioned over the fade instead, its computed value at
     the first instant is still `hidden`, and a focus() made then — the
     close button, as the panel opens — is refused. */
  opacity:0;visibility:hidden;transition:opacity .5s var(--e2),visibility 0s linear .5s}
/* ── UNDER A FULL-SCREEN LAYER, THE PAGE IS NOT SHOWN ──────────────────────
   body.pv-layer is set by PVNav while any layer but the menu is open — see
   apply() in 00-core.js for the iPhone strip this closes. The bar and main
   are hidden; the open layer, which lives inside main, is switched back on
   (visibility, unlike display, can be re-enabled by a descendant). Only the
   OPEN one: a closed overlay left visible at opacity 0 would sit over the
   page and take every click. And keyed on .on for all three, on purpose:
   keyed on :not([hidden]) the 360° matched a frame BEFORE its .on landed,
   so its visibility flipped under the base rule's half-second delay and the
   focus made on its close button as it opened was refused. The ground goes
   to the villa panel's navy so whatever Safari still exposes at the edges is
   the same colour as the panel. */
body.pv-layer{background:var(--navy-950)}
body.pv-layer > :is(.nav,main){visibility:hidden}
body.pv-layer :is(.vdet.on,.v360.on,.plan.on){visibility:visible}
.vdet.on{opacity:1;visibility:visible;transition:opacity .5s var(--e2),visibility 0s}
.vdet__l{padding:clamp(72px,9vh,110px) calc(clamp(24px,3.4vw,58px)*var(--vs)) calc(clamp(28px,4vh,54px)*var(--vs));
  display:flex;flex-direction:column;justify-content:center;gap:calc(16px*var(--vs));overflow-y:auto;min-height:0;
  transform:translateX(-26px);transition:transform .65s var(--e2) .04s}
.vdet.on .vdet__l{transform:none}
.vdet__r{position:relative;border-inline-start:2px solid var(--rule);min-height:0;
  padding:clamp(72px,9vh,110px) calc(clamp(24px,3.4vw,52px)*var(--vs)) calc(clamp(24px,3.4vh,44px)*var(--vs));
  display:grid;grid-template-rows:auto auto minmax(0,1fr);gap:calc(14px*var(--vs));
  transform:translateX(26px);transition:transform .65s var(--e2) .09s}
.vdet.on .vdet__r{transform:none}
.vdet__k{display:flex;align-items:baseline;gap:14px}
.vdet__k .no{font-size:calc(clamp(1.9rem,3.4vw,2.8rem)*var(--vs));font-weight:700;color:var(--gold-400);line-height:1;letter-spacing:-.04em}
.vdet__k .lb{font-size:10.5px;letter-spacing:.26em;text-transform:uppercase;color:var(--faint)}
.vdet__l h3{font-size:calc(clamp(1.7rem,3.6vw,3rem)*var(--vs));font-weight:600;letter-spacing:-.035em;line-height:1;text-transform:uppercase;margin:0}
.vdet__fig{position:relative;aspect-ratio:16/9;border:1px solid var(--hair);overflow:hidden;background:var(--navy-900)}
.vdet__fig svg{position:absolute;inset:0;width:100%;height:100%}
/* same as the rail — see the note in 70-villas.css. Both draw v.art(). */
.vdet__fig:has(img){background:none;border-color:transparent}
.vdet__stats{display:flex;gap:calc(clamp(16px,2.4vw,38px)*var(--vs));flex-wrap:wrap;padding-top:calc(16px*var(--vs));border-top:1px solid var(--rule);margin:0}
.vdet__stats b{display:block;font-size:9.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--ivory);-webkit-text-stroke:.16px currentColor;margin-bottom:calc(6px*var(--vs));font-weight:500}
.vdet__stats span{font-size:calc(17px*max(var(--vs),.88));font-weight:600;font-variant-numeric:tabular-nums}

/* Lives inside the gallery column and centres on it, because that is what it
   controls. Centring on the viewport put it far to the left once the panel
   splits into detail + gallery. It still lands on the same line as the close
   button and the prev/next arrows. */
/* ── the two calls to action ──────────────────────────────────────────────
   Enquire is the quiet one and stays an outline. The floor plans are what a
   buyer actually wants next, so that one is filled, carries a download mark,
   and is the only thing in this panel wearing Turquoise — the colour the site
   already uses to mean "this hands you a file", set on the hero brochure
   button. Two identical-looking buttons side by side would make neither of
   them the answer. */
.vdet__cta{display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  margin-top:calc(10px*var(--vs));align-self:stretch}
/* Both sit on one line in the panel at desktop width, which needs the pair to
   come in under the column. Enquire is the quieter of the two, so it gives up
   the room: tighter tracking and less padding than a .btn elsewhere. wrap
   stays as the fallback — on a narrow panel they stack rather than overflow. */
.vdet__cta .btn{padding:calc(13px*var(--vs)) 18px;letter-spacing:.12em;white-space:nowrap}

.btn--plan{background:var(--turq);border-color:var(--turq);color:var(--navy-950);
  gap:9px;padding-inline:20px;
  box-shadow:0 8px 26px rgba(87,180,190,.28);
  transition:color .5s var(--e),box-shadow .5s var(--e),border-color .5s var(--e)}
.btn--plan::after{background:var(--ivory)}
.btn--plan:hover{border-color:var(--ivory);box-shadow:0 12px 34px rgba(87,180,190,.44)}
.btn--plan svg{width:15px;height:15px;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* "PDF · dimensioned" is the title attribute rather than a chip inside the
   button. As a chip it read well but cost ~95px, which is the difference
   between this sitting beside Enquire and dropping below it — and the pair on
   one line is the point. */

/* A slow sheen crossing the fill. Enough to catch the eye on a panel that is
   otherwise still, and slow enough not to nag — 5s, and it holds for most of
   that between passes. */
.btn--plan::before{content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,.42) 50%,transparent 62%);
  transform:translateX(-120%);animation:planSheen 5s var(--e) 1.4s infinite}
@keyframes planSheen{0%{transform:translateX(-120%)}22%,100%{transform:translateX(120%)}}
.btn--plan:hover::before{animation-play-state:paused}
/* while the file is being fetched */
.btn--plan.is-busy{opacity:.72;cursor:progress}
/* brocpulse is defined in 20-hero.css, which loads first — keyframes are
   global, but do not delete it there thinking only the hero uses it. */
.btn--plan.is-busy svg{animation:brocpulse 1s ease-in-out infinite}

@media(prefers-reduced-motion:reduce){ .btn--plan::before{animation:none} }
@media(max-width:560px){ .btn--plan{padding-inline:16px} }

/* No plate behind it. The toggle sits on the panel's own ground, never over a
   photograph, so the translucent pill and its blur were 14px of work to tint
   navy with navy. The hairline stays: it is the track the indicator slides
   along, and without it the two labels read as loose words. */
.vgtog{position:absolute;top:clamp(16px,2.4vh,26px);left:50%;translate:-50% 0;z-index:5;
  display:flex;padding:4px;border:1px solid var(--edge);border-radius:999px}
.vgtog__i{position:absolute;top:4px;bottom:4px;inset-inline-start:4px;width:calc(50% - 4px);
  border-radius:999px;background:var(--gold-500);
  transition:transform .45s var(--e2)}
.vgtog[data-mode="interior"] .vgtog__i{transform:translateX(100%)}
.vgtog__b{position:relative;z-index:1;min-width:104px;padding:9px 18px;border-radius:999px;
  font-family:var(--f-sub);font-size:10.5px;font-weight:500;letter-spacing:.18em;text-transform:uppercase;
  color:var(--mute);transition:color .4s var(--e)}
.vgtog__b.on{color:var(--navy-950)}
.vgtog__b:not(.on):hover{color:var(--ivory)}
@media(max-width:760px){
  .vgtog__b{min-width:78px;padding:8px 10px;font-size:9px;letter-spacing:.1em}
  .vdet__nav button{width:34px;height:34px}
}
/* below ~430px the three clusters are only a couple of pixels apart, which
   reads as a collision even though it is not one */
@media(max-width:430px){
  .vgtog__b{min-width:68px;padding:8px 8px;font-size:8.5px;letter-spacing:.06em}
  /* the centred toggle would run into a full-width close button — drop the
     word and keep the glyph so all three clusters share the one line */
  .vdet__x-t{display:none}
  .vdet__x{padding:10px 14px}
}
/* ── the brand, in the corner the overlay leaves empty ────────────────────
   The mark is cloned out of the nav (see index.html), so it arrives with the
   nav's own markup — an <svg> and a <span><b>/<i> — and none of the nav's
   sizes, which is the point: this is a signature, not a header. It sits at
   the inline end of the left column, on the top line the stepper and Close
   already share, and it is quiet enough that the typology name stays the
   loudest thing on that side.

   Hidden below 1000px, where the panel is a single column and this corner is
   the fixed Close button rather than empty space.

   Placed as a grid item in the left cell rather than positioned absolutely,
   so it follows the column split instead of restating it as a percentage —
   .vdet is a two-column grid and this shares row 1 with .vdet__l. The
   margins are that column's own padding and the top line the stepper uses,
   so all three stay level without measuring each other. pointer-events:none
   keeps it clear of the copy it floats above.

   The two columns are pinned explicitly alongside it. They were auto-placed
   before, and an item claiming cell 1/1 pushes auto-placed siblings along:
   the left column landed in the right cell and the gallery dropped to a
   second row. Only above 1000px — below it the brand is display:none, which
   takes it out of the grid entirely and auto-placement is right again. */
@media(min-width:1001px){
  .vdet__l{grid-column:1;grid-row:1}
  .vdet__r{grid-column:2;grid-row:1}
  .vdet__brand{grid-column:1;grid-row:1;justify-self:end;align-self:start;
    margin-top:clamp(16px,2.4vh,26px);
    margin-inline-end:calc(clamp(24px,3.4vw,58px)*var(--vs));
    z-index:3;display:flex;align-items:center;gap:11px;color:var(--ivory);
    pointer-events:none}
}
/* The nav's own treatment at a smaller size — the aqua bloom on the mark, the
   wordmark in ivory, BY RAMS in aqua. Only the scale is this overlay's; the
   colour is the brand's, so the two read as the same mark rather than a live
   one and a watermark. It was set at half opacity first and came out grey. */
/* Cloned from .brand at run time, so it is the lockup too and the wordmark
   rules that used to sit here went with the text they styled. */
.vdet__brand svg{height:69.6px;width:auto;flex:none;
  filter:drop-shadow(0 0 13px rgba(96,237,244,.5)) drop-shadow(0 2px 8px rgba(0,0,0,.55))}
@media(max-width:1000px){ .vdet__brand{display:none} }

.vdet__x{position:fixed;top:clamp(16px,2.4vh,26px);inset-inline-end:var(--pad);z-index:4;display:inline-flex;align-items:center;gap:10px;
  padding:10px 18px;border:1px solid var(--hair);border-radius:999px;font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;
  /* the three properties hover changes, NOT `all`: with `all`, the visibility
     the button inherits when its overlay opens is transitioned too, and at
     the first instant it still reads hidden — so the focus() every overlay
     makes on its close button as it opens was refused. Same on .v360__x and
     .plan__x. */
  transition:background .35s var(--e),color .35s var(--e),border-color .35s var(--e)}
.vdet__x:hover{background:var(--ivory);color:var(--navy-950);border-color:var(--ivory)}
.vdet__nav{position:fixed;top:clamp(16px,2.4vh,26px);inset-inline-start:var(--pad);z-index:4;display:flex;gap:7px}
/* The typology stepper. A drawn arrow rather than the ← glyph: the font's is
   a different weight from every other line on the page, and at 38px it read
   as a placeholder. The shaft grows out of the ring on hover — the movement
   is on the SVG, never on the button, because 98-rtl.css mirrors the BUTTON
   and a transform here would quietly replace that and point both arrows the
   English way.

   Carrying the brand's accent rather than the interface greys: a --mute glyph
   inside a --hair ring is 62% white on 12% white, which on this navy reads as
   switched off. Full white arrow, an aqua ring at half strength, and the same
   soft bloom the mark has. */
.vdet__nav button{width:40px;height:40px;border:1px solid rgba(96,237,244,.5);border-radius:50%;
  display:grid;place-items:center;color:var(--ivory);
  box-shadow:0 0 15px rgba(96,237,244,.18),inset 0 0 12px rgba(96,237,244,.1);
  transition:color .4s var(--e),border-color .4s var(--e),background-color .4s var(--e),box-shadow .4s var(--e)}
.vdet__nav button svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;
  filter:drop-shadow(0 0 7px rgba(96,237,244,.45));transition:transform .45s var(--e2)}
.vdet__nav button:hover{color:var(--navy-950);background-color:var(--gold-400);border-color:var(--gold-400);
  box-shadow:0 0 22px rgba(96,237,244,.45)}
.vdet__nav button:hover svg{filter:none}
#vdPrev:hover svg{transform:translateX(-2px)}
#vdNext:hover svg{transform:translateX(2px)}

/* gallery */
/* pan-y so the page still scrolls under the hero on a phone and pinch-zoom so
   a photograph is still pinchable — but the horizontal axis belongs to the
   swipe in 60-villa-detail.js. Without this the browser claims a sideways
   drag for itself and pointermove never fires. It is set on the stage rather
   than the hero because touch-action is resolved up the ancestor chain, and
   this way the arrows and the expand button are covered by the same rule. */
.vg__stage{position:relative;touch-action:pan-y pinch-zoom}
.vg__hero{position:relative;aspect-ratio:16/9;overflow:hidden;border:1px solid var(--hair);background:var(--navy-900)}
.vg__hero svg,.vg__hero canvas,.vg__hero img{position:absolute;inset:0;width:100%;height:100%;animation:vgIn .55s var(--e2) both}
/* Every art host also accepts a bitmap, so generated SVG can be swapped for
   photography later without touching layout: drop an <img> in and it crops
   to the same box. */
.rpanel__fig img,.vdet__fig img,.plate img,.amen__cell img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
@keyframes vgIn{from{opacity:0;transform:scale(1.03)}}
.vg__meta{display:flex;justify-content:space-between;align-items:baseline;gap:14px;
  font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--faint)}
.vg__meta b{color:var(--gold-400);font-weight:500}
.vg__grid{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;overflow-y:auto;min-height:0;align-content:start;
  padding-inline-end:6px;scrollbar-width:thin;scrollbar-color:rgb(195 162 86 / .6) transparent}
.vg__grid::-webkit-scrollbar{width:6px}
.vg__grid::-webkit-scrollbar-thumb{background:rgb(195 162 86 / .6);border-radius:3px}
/* The ratio comes from a padding-top spacer, not aspect-ratio: an empty
   button has no intrinsic content height, so an auto grid row collapses to
   ~35px while aspect-ratio still paints the box at 98px — every thumbnail
   then overlapped the two rows beneath it. A percentage padding resolves
   against the width and gives the row real height to measure. */
.vgt{position:relative;overflow:hidden;background:var(--navy-900);cursor:pointer;
  border:1px solid transparent;transition:border-color .3s,opacity .3s}
.vgt::before{content:"";display:block;padding-top:75%}
.vgt svg,.vgt canvas,.vgt img{position:absolute;inset:0;width:100%;height:100%}
.vgt::after{content:"";position:absolute;inset:0;background:rgba(0,8,81,.42);transition:opacity .3s}
.vgt:hover::after{opacity:0}
.vgt.on{border-color:var(--gold-500)}
.vgt.on::after{opacity:0}
/* ── gallery controls ──────────────────────────────────────────────────────
   Prev / next sit on the image itself and the expand button in its trailing
   corner. All three are placed with logical properties, so in Arabic the pair
   swaps ends with the thumbnail strip they are stepping through; only the
   chevrons themselves have to be mirrored, in 98-rtl.css. */
/* Glyph only — no pill, no ring, no blur. What keeps them legible over a
   photograph is the shadow on the stroke itself, which follows the shape
   instead of stamping a disc over the picture: dark for the bright half of a
   plate, and a faint light halo so the stroke does not vanish into a dark
   one. Hover brightens and thickens; it must NOT move them, because
   98-rtl.css mirrors .vg__arw with a transform that a hover transform here
   would silently replace. */
/* PBR gold, 195 162 86, resting as well as on hover — they used to be white
   going aqua. Written as a literal rather than as --gold-400, which is aqua
   in this palette for historical reasons and is exactly what these were
   hovering to. The pale tint on hover is the same hue lifted, so the change
   reads as the control brightening rather than as a second colour arriving.

   The two drop-shadows below stay as they are and are what make this legible
   at all: a dark one for the bright half of a photograph, a light halo for
   the dark half. Gold is a mid-tone, so it needs both more than white did. */
.vg__arw,.vg__full{position:absolute;z-index:2;display:grid;place-items:center;
  color:#C3A256;opacity:.85;cursor:pointer;
  transition:opacity .3s var(--e),color .3s var(--e)}
.vg__arw:hover,.vg__full:hover{opacity:1;color:#E6D2A0}
.vg__arw:focus-visible,.vg__full:focus-visible{opacity:1;outline:2px solid #C3A256;outline-offset:4px;border-radius:6px}
.vg__arw svg,.vg__full svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.85)) drop-shadow(0 0 10px rgba(255,255,255,.28));
  transition:stroke-width .3s var(--e)}
.vg__arw:hover svg,.vg__full:hover svg{stroke-width:1.9}
/* The boxes are invisible now and exist only as a place to press — kept at
   full size for that reason, and a touch bigger than the glyph they hold. */
.vg__arw{top:50%;translate:0 -50%;width:48px;height:48px}
.vg__arw--prev{inset-inline-start:10px}
.vg__arw--next{inset-inline-end:10px}
.vg__full{bottom:10px;inset-inline-end:10px;width:44px;height:44px}
/* one icon per state, so the button says which way it is about to go */
.vg__full .vg__i--out,.vg__stage.is-max .vg__full .vg__i--in{display:none}
.vg__stage.is-max .vg__full .vg__i--out{display:block}
/* a set of one has nowhere to step */
.vg--solo .vg__arw{display:none}

/* Only legible over the photograph while enlarged; the caption under the hero
   says the same thing at every other size. */
/* Same treatment as the buttons beside it: no plate, a shadow on the type. */
.vg__hud{position:absolute;z-index:2;bottom:18px;inset-inline-start:20px;display:none;
  font-family:var(--f-sub);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--mute);text-shadow:0 1px 4px rgba(0,0,0,.9)}
.vg__stage.is-max .vg__hud{display:block}

/* ── maximised ─────────────────────────────────────────────────────────────
   One way in now: the stage is fixed over the overlay. Native fullscreen is
   deliberately not used any more — leaving it made the browser rebuild the
   whole page and Firefox held a black curtain up for the duration; see the
   maximise block in 60-villa-detail.js before reintroducing it. */
/* PROMOTED, NOT REARRANGED. The stage takes the viewport on its own and
   covers what is behind it; nothing else moves or hides.

   It used to work the other way: the overlay became a single-column grid and
   the left column, the nav, the toggle, the caption and the whole strip of
   thumbnails were set to display:none. That is what made LEAVING fullscreen
   cost seconds in Firefox — coming back out asked the engine to lay out and
   rasterise eleven generated SVG scenes in one frame, on top of repainting a
   page it had stopped rendering while the element was promoted. The window
   showed the enlarged frame, then black, then finally the panel.

   Out of flow, none of that is thrown away: everything underneath keeps its
   layout and its raster the whole time it is covered, so coming back is a
   composite rather than a rebuild. */
.vg__stage.is-max{position:fixed;inset:0;width:auto;height:auto;z-index:6;
  display:grid;place-items:center;background:var(--navy-950)}
.vg__stage.is-max .vg__hero{aspect-ratio:auto;width:100%;height:100%;border:0;background:transparent}
/* IMG() writes object-fit inline so a bitmap can be dropped into any art host
   in the page — an inline style is what has to be beaten here, not a weaker
   rule. Enlarged, a crop is the wrong answer: show the whole plate. */
.vg__stage.is-max .vg__hero img{object-fit:contain !important}
.vg__stage.is-max .vg__arw{width:60px;height:60px}
.vg__stage.is-max .vg__arw svg,.vg__stage.is-max .vg__full svg{width:34px;height:34px}
/* MAXIMISED ONLY: the minimise control moves onto CLOSE's line. Inside the
   panel it belongs to the picture, so it sits 10px off the plate's own corner.
   Once the stage goes fixed and fills the window that same 10px puts it in the
   extreme corner of the screen, unrelated to anything, while CLOSE sits a full
   --pad in from the same edge. Sharing that inset makes them read as one pair
   holding the right-hand side, and mirroring CLOSE's top offset as the bottom
   offset squares the corner off. */
.vg__stage.is-max .vg__full{inset-inline-end:var(--pad);bottom:clamp(16px,2.4vh,26px)}

/* the faked path: give the stage the whole overlay to fill */
/* the single-column stack below 1000px leaves the panel a row of auto height
   and a scrollbar it must not have here — both are overridden, not inherited */
/* Nothing to restructure any more — the stage is out of flow and covers the
   panel. The only thing still worth saying is that the panel must not scroll
   behind it. */
.vdet.is-max{overflow:hidden}
/* nothing else is left to click, so the close button keeps its place */
/* Above the promoted stage, not level with it — the close button comes
   BEFORE the stage in the markup, so an equal z-index would put it under. */
.vdet.is-max .vdet__x{z-index:7}

/* THE MARK IN THE MAXIMISED VIEW.
   The brand was already in this overlay, sitting at the inner edge of the
   left column at z-index 3. Maximising promotes the stage to fixed at
   z-index 6, so the mark went straight under it and the top left corner of
   a full screen photograph was the one empty corner on the page.

   It is not moved so much as re-anchored: fixed to the viewport, at the same
   distance from the top as CLOSE and mirrored across to the leading edge with
   the same --pad, so the two corners balance. inset-inline-start rather than
   left, so it swaps to the right in Arabic and Kurdish and stays opposite the
   close button rather than landing on top of it.

   Shown at every width here, unlike the panel treatment which hides it below
   1000px: the reason it hides there is that the panel becomes one narrow
   column with no room beside the heading, and a maximised photograph has the
   whole viewport. The media query that hides it carries no extra specificity,
   so this rule wins wherever it applies without needing to be later.

   Still pointer-events:none, inherited: it is a mark, not a control, and it
   must never eat a tap meant for the picture or the previous arrow. */
.vdet.is-max .vdet__brand{
  display:flex;
  position:fixed;
  top:clamp(16px,2.4vh,26px);
  inset-inline-start:var(--pad);
  margin:0;
  z-index:7;
}

@media(max-width:760px){
  .vg__arw{width:40px;height:40px}
  .vg__arw--prev{inset-inline-start:4px}
  .vg__arw--next{inset-inline-end:4px}
  .vg__full{width:38px;height:38px;bottom:4px;inset-inline-end:4px}
  .vg__arw svg,.vg__full svg{width:21px;height:21px}
  .vg__hud{bottom:10px;inset-inline-start:12px;font-size:9px;letter-spacing:.12em}
}

@media(max-width:1000px){
  .vdet{--vs:1;grid-template-columns:1fr;grid-template-rows:auto minmax(0,1fr);overflow-y:auto}
  .vdet__l{padding-top:clamp(76px,10vh,100px);justify-content:flex-start}
  .vdet__r{border-inline-start:0;border-top:2px solid var(--rule);padding-top:22px;
    grid-template-rows:auto auto auto minmax(0,1fr)}
  /* single column here, so absolute placement in the top padding would drop
     it on top of the hero — it becomes a normal centred row instead */
  /* RELATIVE, not static. The sliding indicator inside is position:absolute
     and needs this element to be its containing block; `static` is the one
     value that is not one, so the indicator escaped to the nearest positioned
     ancestor and painted as a large aqua slab beside the toggle. Relative
     keeps it in normal flow exactly as static did, and takes the indicator
     back.

     `inset:auto` goes with it, and is not optional. The desktop rule places
     this thing with BOTH `top` and `left:50%`; static ignores offsets
     entirely, so they sat there harmlessly, but relative honours them —
     switching the position value alone shoved the toggle half a viewport
     right AND ~22px down, straight into the top of the photograph. Reset
     every offset in the same rule that makes them count, rather than the one
     that happens to be noticed first. */
  /* STRETCHED, not centred. A 166px pill floating above a photograph that
     spans the whole column left most of the row empty and read as something
     dropped in rather than placed. Stretching it to the stage's own width
     puts the control, the picture and the caption on one edge, which is the
     thing that was missing: three elements, one measure.

     The two labels then split that width evenly (flex:1), and the indicator's
     `width:calc(50% - 4px)` is already written against the container rather
     than the label, so it lands on exactly half without further arithmetic. */
  .vgtog{position:relative;inset:auto;translate:none;justify-self:stretch;
    margin-bottom:clamp(12px,1.8vh,18px)}
  .vgtog__b{flex:1;min-width:0}
  .vg__grid{grid-template-columns:repeat(4,1fr);max-height:40vh}
}




/* ==============================================================================
  12 . 90-register.css
  07 - Register, FAQ, footer.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — 07 - REGISTER, FAQ & FOOTER

   Registration form, the five-question accordion and the footer, all
   over a single shared film background.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── CLOSE — register + footer over one film ─────────────── */
/* ── GROUND ─ this section's background, and no other section's ───────────
   Everything is now 900 #000851 Pavilion Blue. --navy-950 resolves to it too.
   purple #35005B · emerald #004C15 · teal #006352 · gold #C3A256 · or any hex
   These are split per section for development. Merge before launch.
   A film covers this; .close__veil below decides how much of it shows. */
.close{background:var(--navy-900)}
.close{position:relative;overflow:hidden;border-top:1px solid var(--rule)}
.close__bg{position:absolute;inset:0;opacity:.30}
.close__bg canvas{width:100%;height:100%}
.close__veil{position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,8,81,.72),rgba(0,8,81,.86))}
.close__in{position:relative;z-index:2}
.reg__g{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.95fr);gap:clamp(34px,6vw,calc(100px * var(--z)));align-items:start}
form{display:grid}
/* country + city share a row; each keeps its own underline and floating
   label because each is its own .f. Stacks back to the form's single column
   where a phone would squeeze two short fields into two narrow ones. */
.f-pair{display:grid;grid-template-columns:1fr 1fr;gap:0 clamp(18px,3vw,36px)}
@media(max-width:560px){.f-pair{grid-template-columns:1fr}}
/* A field's underline is the same hairline as everything else at rest, and
   the one place gold has to do a second job: focus takes it to full strength
   rather than to another colour, so the field you are typing in is obvious
   without the form growing a second accent. (Focus was --gold-500, which is
   aqua in this palette however it is named.) */
.f{position:relative;border-bottom:1px solid var(--rule);transition:border-color .4s}
.f:focus-within{border-color:var(--pv-gold)}
.f.bad{border-color:#C4636B}
/* Longhands, not the `font:` shorthand. `font:500 calc(…)/1.2 …` puts a
   calc() immediately before the line-height slash, which is the one form of
   the shorthand engines disagree about — and a shorthand that fails to parse
   takes the family and weight down with it. Longhands cannot misparse. */
.f input,.f select{width:100%;background:none;border:0;outline:0;color:var(--ivory);
  font-family:var(--f-display);font-weight:500;font-size:calc(16px * var(--zt));line-height:1.2;
  padding:calc(34px * var(--z)) 0 calc(13px * var(--z))}

/* ── why the select needs appearance:none ────────────────────────────────
   A <select> left as appearance:menulist is a native control, and engines
   refuse the author's line-height on it — computed style came back `normal`
   on the select while the identical declaration gave the inputs 1.2. Safari
   then resolves `normal` differently AND centres the value inside the
   control, so the text floated to its own height no matter what padding said
   and landed on the raised label. Chromium hid the problem by resolving
   `normal` close enough to 1.2 to still clear it.

   Dropping the native appearance makes the select an ordinary box: the same
   line-height and padding as the inputs beside it, and the same result in
   both engines. The trade is the native arrow, redrawn below. */
.f select{cursor:pointer;appearance:none;-webkit-appearance:none;
  padding-inline-end:calc(24px * var(--z))}

/* The chevron the native control used to draw, in the site's own hairline
   style — the same stroked mark the language button uses. On the wrapper, so
   inset-inline-end flips it for Arabic and Kurdish. */
.f--sel::after{content:"";position:absolute;inset-inline-end:3px;bottom:calc(22px * var(--z));
  width:7px;height:7px;pointer-events:none;
  border-right:1.6px solid var(--mute);border-bottom:1.6px solid var(--mute);
  transform:rotate(45deg);transition:border-color .4s var(--e)}
.f--sel:focus-within::after{border-color:var(--pv-gold)}
/* and while the list is open, so the chevron agrees with what is on screen */
.f--sel:has(.sel.on)::after{border-color:var(--pv-gold);transform:rotate(225deg);
  bottom:calc(18px * var(--z))}
/* ── the property-of-interest list ───────────────────────────────────────
   The <select> is still the control and still the value; it is only its
   POPUP that has been replaced, because a popup is the one part of a select
   CSS cannot reach. Painting <option> did not restyle it — it put a navy
   list inside the operating system's grey box with the OS highlight running
   across it, which is the state this replaces.

   Off-screen rather than display:none: hidden that way it would stop being
   announced and stop being submitted, and the point of keeping it is that it
   still does both. The drawn button carries the same padding as the inputs
   beside it so the three fields sit on one rhythm. */
.f select.sel__native{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.sel{position:relative}
.sel__b{display:flex;align-items:center;width:100%;background:none;border:0;outline:0;
  padding:calc(34px * var(--z)) calc(24px * var(--z)) calc(13px * var(--z)) 0;
  font-family:var(--f-display);font-weight:500;font-size:calc(16px * var(--zt));line-height:1.2;
  color:var(--ivory);text-align:start;cursor:pointer}
.sel__b:focus-visible{outline:1px solid var(--pv-gold);outline-offset:2px;border-radius:3px}

/* Anchored to the field and drawn over what follows, the way the language
   menu in the nav is. Same fade-and-lift in, so the page has one way of
   opening a menu rather than two. */
.sel__m{position:absolute;top:calc(100% - 4px);inset-inline-start:0;right:0;z-index:6;
  list-style:none;margin:0;padding:6px;border-radius:12px;
  background:rgba(0,8,81,.96);border:1px solid var(--rule);
  box-shadow:0 24px 60px -20px rgba(0,0,0,.7);
  backdrop-filter:blur(14px) saturate(140%);
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .28s var(--e),transform .28s var(--e),visibility 0s linear .28s}
.sel.on .sel__m{opacity:1;visibility:visible;transform:none;transition-delay:0s,0s,0s}
/* A CLOSED popup must not lengthen the page. visibility:hidden stops it being
   seen and stops it being tabbed to, but an absolutely positioned box still
   counts toward scrollHeight — so this listbox, hanging below the last field,
   was adding its own height to the document and giving the contact page a
   phantom scrollbar over a layout that fitted exactly. Collapsing the box
   while it is closed costs nothing: it is repainted from its own content the
   moment .on returns, and the transition still has opacity and transform to
   animate. */
.sel:not(.on) .sel__m{max-height:0;padding-block:0;overflow:hidden}
/* Gold on offer, white on the one you are on — the same rule the scrubber
   under the villa rail reads by. The button above shows the current value in
   white for that reason: it is not an offer, it is where you are. */
.sel__m li{padding:calc(10px * var(--z)) calc(12px * var(--z));border-radius:8px;
  font-size:calc(15px * var(--zt));color:var(--pv-gold);cursor:pointer;
  transition:background-color .2s var(--e),color .2s var(--e)}
.sel__m li:hover,.sel__m li:focus-visible{background:rgba(195,162,86,.14);color:var(--ivory);outline:0}
/* The chosen one keeps a mark of its own, so the list still says which it is
   when the pointer is somewhere else — the same dot the language menu uses. */
.sel__m li[aria-selected="true"]{color:var(--ivory);display:flex;align-items:center;
  justify-content:space-between;gap:12px}
.sel__m li[aria-selected="true"]::after{content:"";width:4px;height:4px;border-radius:50%;
  background:var(--pv-gold);flex:none}
@media(prefers-reduced-motion:reduce){
  .sel__m{transition:opacity .15s linear,visibility 0s linear .15s;transform:none}
}
.f label{position:absolute;inset-inline-start:0;top:calc(25px * var(--z));font-size:calc(10.5px * var(--zt));letter-spacing:.22em;text-transform:uppercase;color:var(--faint);pointer-events:none;transition:.4s var(--e)}
.f input:focus+label,.f input:not(:placeholder-shown)+label{top:5px;font-size:9.5px;color:var(--gold-400)}
.f--sel label{top:5px;font-size:9.5px;color:var(--gold-400)}
.note{font-size:calc(11.5px * var(--zt));color:var(--faint);line-height:1.6;margin:calc(20px * var(--z)) 0 0}
.ok{display:none;border:1px solid var(--gold-500);padding:calc(26px * var(--z)) calc(28px * var(--z));margin-top:calc(24px * var(--z));background:rgba(196,163,99,.07)}
.ok.on{display:block}
.meta{display:flex;gap:clamp(20px,3.4vw,54px);flex-wrap:wrap;margin:0}
.meta b{display:block;font-size:10px;letter-spacing:.26em;text-transform:uppercase;color:var(--gold-400);margin-bottom:8px;font-weight:500}
.meta span{font-size:calc(16px * var(--zt));font-weight:500}

/* ── FAQ accordion — grid-rows trick so the open animates to real height ── */
.faq__i{border-bottom:1px solid var(--rule)}
.faq__i:first-of-type{border-top:1px solid var(--rule)}
/* The question row is the same height as a form field, because the two sit
   side by side in #register and their rules read as one set of lines across
   the section. They used to drift: a field row is 54.3 + 1px and fixed, while
   this row was content-driven — 59.3 in English, 63.1 in Arabic and Kurdish,
   where the heavier line box makes it taller. By the fifth rule they were
   21px apart in English and 40px in the other two.
   The FAQ's opening rule already sits exactly one field-row above the first
   underline in all three languages, so matching the heights lands every
   divider on an underline. --reg-row is the input's own box written out —
   calc(34px*z) + calc(13px*z) padding around a 16px*zt line at 1.2 — so the
   two columns cannot come apart again if that padding is ever retuned.
   min-height, not height: a question that grows to two lines should push the
   rules out of step rather than crop itself. */
#register{--reg-row:calc(34px * var(--z) + 13px * var(--z) + 16px * var(--zt) * 1.2)}
.faq__q{width:100%;display:flex;justify-content:space-between;align-items:center;gap:20px;
  padding:0;min-height:var(--reg-row);
  text-align:start;font-size:clamp(15px,1.5vw,calc(18px * var(--z)));font-weight:500;letter-spacing:-.01em;
  color:var(--ivory);transition:color .35s}
/* The first item carries the opening rule as a border-top, so its box is one
   pixel taller than every other. Take that pixel back out of its own row, or
   each divider below it lands one pixel under its field underline. */
.faq__i:first-child .faq__q{min-height:calc(var(--reg-row) - 1px)}
.faq__q:hover{color:var(--gold-300)}
.faq__i.on .faq__q{color:var(--gold-400)}
.faq__s{position:relative;flex:none;width:16px;height:16px}
/* --pv-gold, NOT --gold-400. The gold-* names are a leftover from a gold
   accent this brand no longer uses: --gold-300/400/500 all hold Pavilion
   Aqua now, so "gold-400" paints #60EDF4. The real gold is --pv-gold. */
.faq__s::before,.faq__s::after{content:"";position:absolute;background:var(--pv-gold);transition:transform .45s var(--e2),opacity .3s}
.faq__s::before{left:0;top:7.5px;width:16px;height:1.5px}
.faq__s::after{left:7.25px;top:0;width:1.5px;height:16px}
.faq__i.on .faq__s::after{transform:rotate(90deg);opacity:0}
.faq__a{display:grid;grid-template-rows:0fr;transition:grid-template-rows .5s var(--e2)}
.faq__i.on .faq__a{grid-template-rows:1fr}
.faq__a>div{overflow:hidden}
.faq__a p{margin:0;padding:0 44px calc(24px * var(--z)) 0;font-size:calc(14.5px * var(--zt));line-height:1.75;color:var(--mute)}
.fgrid{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:clamp(26px,4vw,calc(64px * var(--z)));padding-block:clamp(56px,8vh,calc(96px * var(--z))) calc(52px * var(--z));margin-top:clamp(56px,8vh,calc(110px * var(--z)));border-top:1px solid var(--rule)}
.fgrid h6{margin:0 0 calc(18px * var(--z));font-size:calc(10px * var(--zt));letter-spacing:.28em;text-transform:uppercase;color:var(--gold-400)}
.fgrid ul{list-style:none;margin:0;padding:0;display:grid;gap:11px}
/* The columns rest quiet and answer in gold. They went to white before,
   which is what everything else on the page does when it is the thing you
   are on — down here the pointer is the only thing that lights anything, so
   gold is the clearer signal. */
.fgrid a{font-size:calc(14px * var(--zt));color:var(--mute);transition:color .35s}
.fgrid a:hover{color:var(--pv-gold)}
.fgrid svg{width:calc(48px * var(--z));color:var(--ivory);margin-bottom:calc(20px * var(--z))}
/* The lockup is 628x422 rather than the symbol's 235x397, so it is sized by
   height with the width left to follow. Without this the rule above would set
   its width and squash it to the symbol's proportions. */
.fgrid .pvlock{width:auto;height:calc(100.8px * var(--z))}
/* ── social ───────────────────────────────────────────────────────────────
   Its own row rather than a fifth column: six marks read as a set, and the
   columns above are text lists of different lengths.

   The size is set on the <a>, not the mark, because .fgrid svg already sizes
   the wordmark at 48px and these sit close enough to inherit an argument
   nobody wants. The <svg> just fills the box it is given.

   Marks are filled paths, not stroked like the amenity icons: a brand mark is
   a shape, and outlining one at 17px turns it into a smudge. */
.fsoc{list-style:none;display:flex;flex-wrap:wrap;gap:10px;margin:0;padding:0 0 calc(30px * var(--z))}
/* ── 2px, and the width is the fix ────────────────────────────────────────
   These read as a circle of DOTS: four bright points at 3, 6, 9 and 12
   o'clock with the arc between them fading out. It is antialiasing, not a
   stray element. A 1px curve covers a whole pixel where it runs straight
   along an axis and about half of two pixels where it cuts a diagonal, so the
   axis points come out brightest — measured on the shipped ring, 71 against
   55 out of 255, a 1.30 ratio the eye reads as four dots.

   Widening the ring is what removes it, not lifting its colour: at 2px almost
   every pixel around the arc is fully covered, and the same measurement gives
   1.01 — a spread of 5 out of 255 rather than 30. The alpha drops from .26 to
   .18 so twice the width lands at the same weight. The faint fill is the
   burger row's trick and does the rest: it gives the disc its own presence so
   the hairline is not the only thing drawing the circle.

   Also measured, and rejected: a radial-gradient ring, which sounds like it
   should be perfectly even and renders visibly DASHED — the gradient
   rasteriser bands it at exactly the four axis points. Do not go back to it.
   Landing the circles on whole pixels barely helps either (1.30 -> 1.24):
   they sit at x.22 in the footer, but the variation is inherent to the arc,
   not to where it starts. */
.fsoc a{display:grid;place-items:center;width:38px;height:38px;border-radius:50%;
  border:2px solid rgba(255,255,255,.18);background:rgba(255,255,255,.05);color:var(--mute);
  transition:color .35s var(--e),border-color .35s var(--e),background-color .35s var(--e)}
.fsoc a:hover{color:var(--navy-950);background-color:var(--pv-gold);border-color:var(--pv-gold)}
.fsoc a:focus-visible{outline:2px solid var(--pv-gold);outline-offset:3px}
.fsoc svg{width:17px;height:17px;fill:currentColor;margin:0}
/* ── the WhatsApp mark sits off centre, and it is the artwork, not the box ──
   Every one of these marks has its ink bbox centred in its 24-square, this
   one included — but WhatsApp's is a bubble with a TAIL hanging off the
   bottom-left, so the extremes balance while the mass does not. Measured on
   the rendered mark, its centre of ink lands 0.49px left and 0.51px below the
   circle's centre, where the other four sit within 0.11px. The eye reads the
   bubble, not the bbox, so it looks pushed down-left.

   The nudge is in PER CENT, not pixels, so it holds at any size this mark is
   drawn at — translate percentages resolve against the element's own box.
   Measured after: 0.25px right, 0.09px above centre. Shrinking the mark
   instead was tried and is worse; it moves the same mass around a smaller
   box (-1.08px at 15.5px) and puts this one out of step with its row. */
:is(.fsoc,.bmenu__soc) a[href*="wa.me"] svg{transform:translate(2.65%,-2.35%)}
@media(max-width:560px){ .fsoc{justify-content:center} }

.fbar{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;padding:calc(24px * var(--z)) 0;border-top:1px solid var(--rule);
  font-size:calc(11px * var(--zt));letter-spacing:.12em;text-transform:uppercase;color:var(--pv-gold)}
/* The third slot of the bottom bar is two links now rather than a line of
   text. The separator is drawn rather than typed, so neither the markup nor
   a translator has to carry a middot that means nothing on its own. */
.fbar__legal{display:inline-flex;gap:14px}
.fbar__legal a{color:var(--pv-gold);transition:color .35s var(--e)}
/* The bar is already gold, so these lift to a paler tint of the same hue
   rather than changing colour — the same move the gallery arrows make. */
.fbar__legal a:hover{color:#E6D2A0}
.fbar__legal a + a{padding-inline-start:14px;position:relative}
.fbar__legal a + a::before{content:"";position:absolute;inset-inline-start:0;top:.15em;bottom:.15em;
  width:1px;background:var(--rule)}
@media(max-width:900px){.reg__g{grid-template-columns:1fr}.fgrid{grid-template-columns:1fr 1fr}}
@media(max-width:560px){.fgrid{grid-template-columns:1fr}}


/* ==============================================================================
  12b . 92-delivery.css
  07 Delivery — the progress film, then the programme bar.
   ============================================================================== */
/* ── GROUND ─ this section's background, and no other section's ───────────
   Sits between Gallery and Register, both of which are --navy-900, so this
   holds the same ground rather than inventing a third value and drawing a
   seam across the scroll. (It was The Day above, until that section was
   parked — see parked/the-day. It was --navy-900 too, so nothing here has
   to change when it comes back.) */
/* ONE FRAME, AND A LANDING. The section was 968px tall against an 800px
   viewport, so the bar fell off the bottom, and the nav is fixed, so clicking
   Delivery put the heading underneath it. Both are the same fix.

   The top padding is --navh, the nav's own height, rather than a
   scroll-margin. A scroll-margin would push the section down the viewport and
   take its last 98px off the bottom, which is the fault being fixed, one end
   further along. Padding keeps the section's top ON the viewport's top, where
   a native anchor puts it, and holds the nav's space inside it. --navh is the
   bar BEFORE it condenses on scroll, so a landed section has a few pixels of
   slack, never a deficit.

   The film absorbs the slack: flex:0 1 auto with min-height:0 lets it keep
   21/9 while there is room and shrink past it when there is not, so on a
   short screen the plate gets wider rather than the bar getting cut. It may
   shrink but never grow — the ratio is the ceiling, not a target. */
.prog{background:var(--navy-900)}

/* Only where one frame is actually possible. Below 981px the bar drops to two
   columns and then to one, so the section is three or five rows of cards tall
   and no height will hold it; there it keeps flowing and takes a
   scroll-margin instead, which is the right tool for a section that is taller
   than the viewport either way. */
@media(min-width:981px){
  /* height, not min-height. min-height was the first attempt and did nothing:
     the box grows to its content, so there is no overflow for the film to
     shrink against and the section stayed 896px in an 800px viewport. A
     bounded height is what gives the flex line something to distribute. */
  .prog{height:100svh;display:flex;flex-direction:column;
    padding-block:calc(var(--nav-h, var(--navh)) + clamp(14px,2vh,30px)) clamp(24px,4vh,54px)}
  /* width:100% is not redundant. .wrap carries margin-inline:auto, and an auto
     margin on the cross axis of a flex container stops the item stretching —
     it shrink-wraps to content and centres instead. So the section came out
     as wide as its widest phase name: 1217px in English, where max-content
     happened to exceed the cap, and 965 in Arabic, where it did not. */
  .prog>.wrap{width:100%;flex:1;min-height:0;display:flex;flex-direction:column;justify-content:center}
  /* 0 1 auto: the film may shrink but never grow, so 21/9 is a ceiling rather
     than a target and a short screen widens the plate instead of cutting the
     bar off the bottom. min-height:0 is what permits the shrink at all. */
  .prog__film{flex:0 1 auto;min-height:0}
  /* The bar is the one thing that must not give. Flex items shrink by
     default, so it was taking its share too — a 147px box around 152px cards,
     which put the phase names through the section's bottom padding. The film
     is the only thing here with slack in it. */
  .prog__head,.prog__bar{flex:none}
}
@media(max-width:980px){ .prog{scroll-margin-top:var(--nav-h, var(--navh))} }

/* ANCHOR LANDINGS for the sections that do NOT hold the bar's space inside
   themselves. A native anchor puts a section's top at the viewport's top,
   which is under a fixed bar; scroll-margin-top is the offset the browser
   adds when it scrolls to the target, and it is what makes "Gallery" in the
   nav land on the word Gallery rather than 98px above it.

   Only these four. Delivery pads itself by the bar's height and is exactly
   one screen, so a margin there would push its foot off the bottom — the
   fault at the other end. Location is a frame that pads itself the same
   way, once the parent has told it the bar's real height (pv:nav-h). The
   villa rail hides the bar for its whole length, so its top IS the
   viewport's top. --nav-h is the measured bar, published by 90-chrome.js;
   --navh is the stale token it falls back to before the first measurement. */
#about,#amenities,#gallery,#register{scroll-margin-top:var(--nav-h, var(--navh))}

/* The film. A 21:9 plate, because it is a establishing shot under a heading and
   not a hero — tall enough to read, short enough that the bar below it is on
   screen at the same time, which is the whole point of putting them together. */
/* The section's one line of copy. Not .sechead: that is a two-column grid for
   a headline beside a standfirst, and with only a heading in it the title
   would be squeezed into the narrower of the two tracks for no reason. This
   takes .sechead's bottom margin and nothing else, so the film sits the same
   distance below the words as every other section's content does. */
.prog__head{margin-bottom:clamp(40px,6.5vh,calc(88px * var(--z)));position:relative}
/* ── the note beside the heading ─────────────────────────────────────────
   OUT OF FLOW above 981px, and that is the whole point of it. This section
   is height:100svh with the film on `flex:0 1 auto`, so anything that makes
   the head taller comes straight off the film: putting this in the flow as a
   second grid column grew the head from 36px to 128px and shrank the video
   from 1158x453 to 1158x361. Absolute means it cannot cost the film a pixel,
   whatever the copy grows to or however it wraps in Arabic and Kurdish.

   inset-inline-end, not right — the heading sits on the opposite side in
   Arabic and Kurdish, and the note follows it there with no second rule.

   Below 981px the section is not a fixed-height flex column at all, so there
   is nothing to protect: the note drops back into the flow under the heading,
   where it reads better on a phone than a floated column would. */
.prog__note{color:rgba(255,255,255,.62);font-size:calc(15.5px * var(--zt));
  line-height:1.6;max-width:46ch;margin:14px 0 0}
@media(min-width:981px){
  .prog__note{position:absolute;top:0;inset-inline-end:0;margin:0;
    width:min(48%,calc(600px * var(--z)))}
}
/* The title in the SUBHEADING role rather than the headline one — uppercase,
   tracked, --f-sub. Two classes on purpose, the same reason .h2--miracles
   carries two: 99-type is the last section of this file and restates .h2's
   family, tracking and case at equal specificity, so a bare .h2--sub written
   here would lose exactly those three declarations and quietly stay Kumbh.
   Sized below the display headlines: uppercase tracked type reads larger than
   its point size, and this is a section label, not a statement. */
.h2.h2--sub{font-family:var(--f-sub);font-weight:600;text-transform:uppercase;
  letter-spacing:.1em;line-height:1.25;
  font-size:clamp(1.15rem,2.3vw,calc(2.15rem * var(--z)))}
.prog__film{position:relative;aspect-ratio:21/9;border-radius:18px;overflow:hidden;
  background:var(--navy-950);border:1px solid var(--rule)}
.prog__film canvas{display:block;width:100%;height:100%}
/* the plate darkens toward the bar so the two read as one object rather than a
   picture with a table under it */
.prog__film::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,transparent 55%,rgba(0,8,81,.55))}

/* ── the programme bar ────────────────────────────────────────────────────
   One hairline grid: the gap IS the rule, drawn by letting the container's
   background show through a 1px gap. Five borders would double up against
   each other and land at 2px wherever two cells meet.

   The cards are therefore the GROUND (#000851) and never the line colour.
   They were --navy-850, the raised navy #000A6B — one step off the
   value as the rules — so the dividers between the five phases disappeared
   the moment the palette changed. Black cells, grey grid: that is the
   client's two values doing exactly what they are for. The current phase
   still lifts, by the wash on .is-now rather than by its own ground. */
.prog__bar{display:grid;grid-template-columns:repeat(5,1fr);gap:1px;
  background:var(--rule);border:1px solid var(--rule);border-radius:18px;
  overflow:hidden;margin-top:clamp(18px,2.6vh,28px)}
.prog__s{position:relative;background:var(--navy-950);padding:clamp(20px,2.4vw,32px);
  min-height:clamp(150px,19vh,190px);display:flex;flex-direction:column;
  justify-content:space-between;gap:14px;
  transition:background .45s var(--e)}

/* ── where we are, and what is still to come ──────────────────────────────
   The five phases were identical but for a word, so the row read as a list
   rather than as a position. Two states now: the phase being BUILT, and the
   ones still ahead.

   NOW — lifted off the row, its own colour drawn along the top edge, its
   swatch lit. It is the only card at full strength. */
/* LIFTED, not darkened. --navy-900 (#000851) is DARKER than --navy-850
   (#000A6B) — the scale runs the opposite way to the usual convention here —
   so setting it recessed the current phase into a well, which is the wrong
   signal entirely. An elevation wash over the card's own ground raises it
   without inventing a token. */
.prog__s.is-now{background-image:linear-gradient(rgba(255,255,255,.055),
                                                 rgba(255,255,255,.018))}
.prog__s.is-now::before{content:"";position:absolute;inset:0 0 auto;height:2px;
  background:var(--phase)}
.prog__s.is-now .prog__dot{box-shadow:0 0 0 1px rgba(255,255,255,.10) inset,
  0 0 18px -2px var(--phase)}
.prog__s.is-now h4{color:var(--ivory)}
.prog__s.is-now .prog__d{color:var(--pv-white)}

/* AHEAD — held back rather than switched off. Dimmed enough that the current
   phase leads, bright enough to still be worth wanting; and they ANSWER, so
   the row rewards a cursor instead of just sitting there. */
.prog__s:not(.is-now) .prog__dot{opacity:.5;transform:scale(.88)}
.prog__s:not(.is-now) h4{color:var(--mute)}
/* hover lifts for the same reason, rather than sinking */
.prog__s:hover{background-image:linear-gradient(rgba(255,255,255,.05),
                                                rgba(255,255,255,.015))}
.prog__s:not(.is-now):hover .prog__dot{opacity:1;transform:scale(1);
  box-shadow:0 0 0 1px rgba(255,255,255,.10) inset, 0 0 16px -3px var(--phase)}
.prog__s:not(.is-now):hover h4{color:var(--ivory)}
/* A rounded SQUARE, not a circle, and in the phase's own colour — the brand's
   phase strip draws them that way and this row is the same five phases. The
   colour arrives as an inline --phase from the data file, so adding a phase is
   a JSON edit and never a stylesheet one. */
.prog__dot{width:13px;height:13px;border-radius:4px;background:var(--phase,var(--rule));
  flex:none;box-shadow:0 0 0 1px rgba(255,255,255,.06) inset;
  transition:opacity .45s var(--e),transform .45s var(--e),box-shadow .45s var(--e)}
/* The NAME is the card, so it carries the weight — uppercase and tracked like
   the strip it comes from. It was the small line under a big date until the
   phases replaced the quarters; a name three words long cannot be display
   type at 215px of column. */
.prog__s h4{margin:0;font-family:var(--f-sub);font-weight:600;
  font-size:calc(14px * var(--zt));line-height:1.45;color:var(--ivory);
  letter-spacing:.11em;text-transform:uppercase}
/* the status under it, quiet — "Coming soon" four times over should not shout */
.prog__d{display:block;margin-top:10px;font-family:var(--f-micro);
  font-size:calc(11px * var(--zt));letter-spacing:.2em;text-transform:uppercase;
  color:var(--faint)}

@media(max-width:980px){
  .prog__bar{grid-template-columns:repeat(2,1fr)}
  /* 21:9 is a letterbox on a narrow screen — give the film back some height
     rather than a 60px strip */
  .prog__film{aspect-ratio:16/9}
}
@media(max-width:560px){
  .prog__bar{grid-template-columns:1fr}
  .prog__film{aspect-ratio:4/3}
}


/* ==============================================================================
  13 . 95-panorama.css
  360 overlay.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — 360 TOUR OVERLAY

   A Matterport tour in a frame. This was a drag-to-look strip of generated
   lake with a compass under it; the stage, the pano strip, the vignette, the
   hint and the compass all went when the real tour arrived.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─────────────── 360° TOUR ─────────────── */
/* ── GROUND ─ this section's background, and no other section's ───────────
   Everything is now 900 #000851 Pavilion Blue. --navy-950 resolves to it too.
   purple #35005B · emerald #004C15 · teal #006352 · gold #C3A256 · or any hex
   These are split per section for development. Merge before launch.
   The 360 overlay, behind the panorama strip. */
.v360{background:var(--navy-900)}
.v360{position:fixed;inset:0;z-index:200;opacity:0;visibility:hidden;transition:opacity .5s var(--e),visibility 0s linear .5s}
.v360.on{opacity:1;visibility:visible;transition:opacity .5s var(--e),visibility 0s}
/* The frame fills the overlay. Matterport draws its own controls, so the only
   chrome over it is the title bar and the close button. */
.v360__tour{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}
.v360__bar{position:absolute;inset:0 0 auto;z-index:3;pointer-events:none;display:flex;justify-content:space-between;align-items:center;gap:16px;padding:calc(22px * var(--z)) var(--pad)}
.v360__ttl{display:grid;gap:4px}
.v360__ttl b{font-size:calc(15px * var(--zt));font-weight:600;letter-spacing:-.01em}
.v360__ttl span{font-size:10px;letter-spacing:.26em;text-transform:uppercase;color:var(--gold-400)}
.v360__x{pointer-events:auto;width:42px;height:42px;border:1px solid var(--edge);border-radius:50%;display:grid;place-items:center;font-size:15px;transition:background .35s var(--e),color .35s var(--e),border-color .35s var(--e)}
.v360__x:hover{background:var(--ivory);color:var(--navy-950);border-color:var(--ivory)}

@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
  [data-rv]{opacity:1;transform:none}
}


/* ==============================================================================
  14 . 99-type.css
  Brand type roles. MUST STAY LAST - these win by source order, nothing else.
   ============================================================================== */
/* ═══════════════════════════════════════════════════════════════════════
   PAVILION BY RAMS — BRAND TYPE - PBR Guidelines 2025

   The four roles, and only four. LOADS LAST ON PURPOSE: it wins by
   source order, so moving it earlier lets section rules override the
   brand faces. Keep it at the end of the <link> list.
   ═══════════════════════════════════════════════════════════════════════ */

/* ══════════════════ BRAND TYPE — PBR Guidelines 2025 ══════════════════
   Four roles, and only four. Every piece of type on this page resolves to
   one of them, and nothing below this block sets a family or a weight.

     SUBHEADING   Termina Medium 500        labels, eyebrows, nav, buttons
     HEADLINE     BR Firma Light 300        h1–h4 and lede-scale prose
     BODY         BR Firma Regular 400      paragraphs, values, form fields
     DISPLAY      PP Pangaia Light Italic   emphasis and pull quotes only

   The three brand families are commercially licensed and not bought, so each
   stack names the real face first and falls through to the free stand-in that
   ships with the site: Metropolis Medium, Kumbh Sans, Hedvig Letters Serif.
   The @font-face rules and the reasoning live in 01-brand.css — that file is
   the one the two documents in sections/ load, so it has to own them. This
   file only names the roles.

   Subheading is the only uppercase role. Display is reserved for highlight
   — a whole paragraph set in light italic serif stops being a highlight. */
/* The --f-* role aliases moved to 00-shared.css. They were declared twice in
   this file -- here and in the tokens section -- and a third time by hand in
   sections/location.html. This section still owns the ROLES below, meaning
   which element gets which face, and still has to stay last. */

/* ── 3 · BODY — the default everything inherits ── */
body,p,li,dd,input,textarea,select,.body,.copy,
.srow dd,.rpanel__stats span,.vdet__stats span,.faq__a,
body[data-abody="body"] .amen__cell p,
.hour span,.num{
  font-family:var(--f-firma);
  font-weight:400;
  font-style:normal;
}

/* The amenity titles in the body face — the third of the three choices the
   amenities tool offers. It sits here, after the BODY block it copies and
   before HEADLINE, so the role lists above stay readable as roles. */
body[data-atitle="body"] .amen__cell h4{
  font-family:var(--f-firma);
  font-weight:400;
  font-style:normal;
}

/* ── 2 · HEADLINE — every heading, plus lede-scale prose ── */
h1,h2,h3,h4,.h1,.h2,.h3,.lede,
body[data-atitle="head"] .amen__cell h4,
body[data-abody="head"] .amen__cell p,
body[data-vfont="display"] .rpanel h3,body[data-vfont="display"] .vdet__l h3,
.rpanel--end h3,
.faq__q{
  font-family:var(--f-firma);
  font-weight:300;
  font-style:normal;
  letter-spacing:-.02em;
  text-transform:none;
}

/* ── 1 · SUBHEADING — the only uppercase role ── */
/* The rail headings — the six typology names, the intro's "Six ways to
   live here", and the overlay title — sit in whichever role the villa
   tool chose: data/villas.json carries display.nameFont, the build injects
   it as PV_VCONF, and 50-villas.js stamps body[data-vfont]. "sub" lands
   them here (uppercase comes with the role); "display" matches them in
   the HEADLINE list above instead. The rail's END heading is pinned to
   HEADLINE either way. The static default on <body> is "sub", so a page
   with no script still reads styled. */
body[data-vfont="sub"] .rpanel:not(.rpanel--end) h3,
body[data-vfont="sub"] .vdet__l h3,
.eyebrow,.idx,.nlinks a,.btn,.vdet__x,.rs__t,.vg__meta,
.rpanel__k .lb,.rpanel__k .no,
.rpanel__stats b,.vdet__stats b,.spec__list i,.intro__spec .hd span,
.srow dt,.amen__cell b,.hour b,.reg label,
body[data-atitle="sub"] .amen__cell h4,
body[data-abody="sub"] .amen__cell p{
  font-family:var(--f-sub);
  font-weight:500;
  font-style:normal;
  text-transform:uppercase;
}

/* ── 4 · DISPLAY — highlight only, never a whole block ── */
.lede em,.h2 em,.h1 em,h1 em,h2 em,h3 em,blockquote{
  font-family:var(--f-pangaia);
  font-weight:300;
  font-style:italic;
  letter-spacing:0;
  text-transform:none;
}

/* figures line up in columns */
.num,.srow dd,.rpanel__stats span,.vdet__stats span{
  font-variant-numeric:tabular-nums;
}


