/* ==========================================================================
   Mira Verde / Trussardi Residences — design system
   ==========================================================================

   Structure, layout rhythm and interaction model follow the reference build
   (avenewdevelopment.ae/projects/cheval-residences-dubai-islands): a 72px page
   gutter, an 80px transparent nav over a full-height hero, alternating light /
   band / dark sections, full-bleed image blocks with the copy set into the top
   left and an accent rule along the bottom edge, 50/50 gallery-and-text rows,
   a four-up amenities carousel, and a centred two-column contact form.

   Colour, typography and the accent are Percent&Co's, not the reference's:
   green #26332F, cream #F3F1EC, burgundy #5E1B1B, Cormorant Garamond and
   EB Garamond. Every token name below is unchanged from the previous system —
   lead-capture.css is layered on top of them and must keep resolving.
   ========================================================================== */

:root {
  /* Colour */
  --green:        #26332F;   /* dark sections, buttons, ink on light */
  --green-soft:   #4E5C56;   /* secondary button fill */
  --footer-green: #1A2320;
  --shell:        #12160F;   /* logo lockup panel — community */
  --shell-alt:    #171C19;   /* logo lockup panel — project */
  --grey:         #EFEFEC;   /* primary light background */
  --grey-alt:     #E9E9E6;
  --band:         #C7CDC8;   /* the mid band between light and dark */
  --band-deep:    #B7BEB9;
  --white:        #FFFFFF;
  --cream:        #F3F1EC;   /* text on dark, light buttons */
  --muted:        #5F6E6A;   /* body copy on light */
  --muted-soft:   #6C7A75;
  --label:        #8F9993;   /* eyebrows, micro-labels on light */
  --hairline:     #DFE2DE;
  --hairline-alt: #E8E8E5;
  --burgundy:     #5E1B1B;   /* the accent — rules, borders, marks */

  --on-dark-90: rgba(243, 241, 236, .9);
  --on-dark-85: rgba(243, 241, 236, .85);
  --on-dark-70: rgba(243, 241, 236, .7);
  --on-dark-62: rgba(243, 241, 236, .62);
  --on-dark-50: rgba(243, 241, 236, .5);
  --on-dark-38: rgba(243, 241, 236, .38);
  --rule-dark:  rgba(243, 241, 236, .18);
  --fill-dark:  rgba(243, 241, 236, .06);

  /* The accent rule under every full-bleed image. The reference runs a gold
     texture here; this is its Percent&Co equivalent — burgundy lit through the
     centre so the edge reads as a struck line rather than a flat border. */
  --accent-rule: linear-gradient(90deg,
      #2F3E39 0%, #5E1B1B 24%, #8E3E3E 50%, #5E1B1B 76%, #2F3E39 100%);

  /* Type */
  --display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
  --body:    'EB Garamond', Georgia, 'Times New Roman', serif;

  /* ------------------------------------------------------ tracking (§6.3)
     Every letter-spacing in this file and in lead-capture.css reads one of
     these. They are tokens rather than literals for one reason:

       LETTER-SPACING IS NOT UNIVERSAL. Arabic is a CURSIVE script — tracking
       forces gaps into the letter joins, and the result does not read as
       elegant, it reads as broken text. It has to go to zero, everywhere,
       including the values set inside media queries, which a per-rule
       override would never reach.

     The values below are the design as approved; nothing about the Latin or
     Cyrillic pages changes. See the [data-script] blocks further down.
     ------------------------------------------------------------------- */
  --track-04: .04em;
  --track-06: .06em;
  --track-08: .08em;
  --track-10: .1em;
  --track-12: .12em;
  --track-14: .14em;
  --track-16: .16em;
  --track-18: .18em;
  --track-20: .2em;
  --track-22: .22em;
  --track-24: .24em;
  --track-30: .3em;

  /* The counterweight to tracking on a centred control — see .btn. Zeroed
     with the tracking it compensates for. */
  --track-indent: .2em;

  /* Geometry — the reference's 72px page gutter and 28.8px full-bleed inset */
  --container: 1600px;
  --container-narrow: 1440px;
  --pad:     clamp(20px, 5vw, 72px);
  --gutter:  var(--pad);
  --edge:    clamp(12px, 2vw, 28.8px);
  --rhythm:  clamp(72px, 9vw, 144px);
  --gap-tight: clamp(12px, 1.2vw, 20px);
  --gap-card:  clamp(14px, 1.6vw, 26px);
  /* Flat corners throughout, as the reference is. --r-btn is the lead layer's
     input radius as well as the button's, so it stays a small real value —
     pill buttons set 999px themselves rather than through this token. */
  --r-lg: 0px;
  --r-md: 0px;
  --r-sm: 0px;
  --r-btn: 4px;

  /* Motion */
  --ease: cubic-bezier(.2, .8, .2, 1);
  --header-h: 80px;
}

/* --------------------------------------------------------------- base --- */

*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: var(--header-h);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--grey);
  color: var(--green);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* Any component that sets `display` outranks the UA's [hidden] rule, and the
   floor-plan panel is toggled by the hidden attribute alone — without this the
   "released on request" fallback renders underneath a real drawing. */
[hidden] { display: none !important; }

img, svg, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
input, button, textarea, select { font: inherit; color: inherit; }
::placeholder { color: #93A09B; opacity: 1; }
::selection { background: var(--burgundy); color: var(--cream); }

:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  inset-inline-start: 12px; top: -100px;
  z-index: 200;
  background: var(--cream);
  color: var(--green);
  padding: 12px 20px;
  transition: top .2s;
}
.skip-link:focus { top: 12px; }

/* ---------------------------------------------------------- typography --- */

h1, h2, h3, h4 { margin: 0; font-family: var(--display); font-weight: 300; }
p { margin: 0; text-wrap: pretty; }
p + p { margin-top: 1.1em; }

/* The reference's h1: uppercase, light, and opened up with a wide track. */
.h-hero {
  font-weight: 300;
  font-size: clamp(38px, 5.4vw, 88px);
  line-height: 1.02;
  letter-spacing: var(--track-10);
  text-transform: uppercase;
  text-wrap: balance;
}
.h-hero strong { font-weight: 500; }

/* The reference's h2 — one size everywhere, uppercase, tight leading. */
.h-section {
  font-weight: 300;
  font-size: clamp(24px, 2.5vw, 38px);
  line-height: 1.08;
  letter-spacing: var(--track-06);
  text-transform: uppercase;
  text-wrap: balance;
}
.h-section--lg { font-size: clamp(28px, 3.2vw, 52px); }

/* The reference's h3 — the tiny label that sits above every h2. `.eyebrow` is
   the same thing under its previous name; both are live in the markup. */
.label,
.eyebrow {
  margin: 0;
  font-family: var(--body);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: var(--track-24);
  line-height: 1;
  text-transform: uppercase;
}

.micro {
  font-size: 11px;
  letter-spacing: var(--track-20);
  text-transform: uppercase;
  line-height: 1.4;
}

.h-card {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(20px, 1.7vw, 27px);
  line-height: 1.18;
  letter-spacing: var(--track-04);
  text-transform: uppercase;
}

.figure {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(24px, 2.6vw, 42px);
  line-height: 1;
  font-variant-numeric: lining-nums tabular-nums;
}

/* The reference keeps body copy inside a narrow measure everywhere. */
.copy { font-size: 17px; line-height: 1.55; }
.copy--set { max-width: 42ch; }
.lead { font-size: clamp(17px, 1.25vw, 20px); line-height: 1.6; }

.muted { color: var(--muted); }
/* Was --on-dark-62 (5.5:1). Body copy is not a caption: on the dark bands it
   is the only thing carrying the argument, and 62% cream on #26332F reads as
   grey rather than as text. 85% measures 8.9:1 composited, and the headings
   above it are full cream, so the hierarchy survives the change. */
.muted-dark { color: var(--on-dark-85); }
.section--dark .muted-dark,
.section--dark .duo__text > p { font-size: clamp(17px, 1.15vw, 18px); line-height: 1.6; }

/* -------------------------------------------------------------- layout --- */

.section { padding: var(--rhythm) var(--pad); }
.section--tight { padding-block: clamp(52px, 6vw, 104px); }
.section--flush { padding: 0; }
.section--edge  { padding-inline: var(--edge); }
/* Full-bleed blocks are laid edge to edge and carry no padding of their own,
   so two in a row butt together. This is the air between them, and it takes
   the colour of the section it belongs to, which keeps the band boundaries
   reading as deliberate rather than as a seam. */
.section--flush + .section--flush { padding-top: clamp(40px, 5vw, 84px); }
/* A flush block followed by a band of a different colour has no air under it:
   the next band's own top padding is that band's colour, so the two read as one
   object butted together. This gives the block a gap underneath in its OWN
   colour, matching the air above it. Used on the developer block, which runs
   straight into the dark agency band below it. */
.section--gap-below { padding-bottom: var(--rhythm); }
/* The inverse of --gap-below: closes the gap under a section so it runs
   straight into the next one. Only safe where the block below carries the
   same ground — two bands of the SAME colour read as one continuous field,
   and the next section's own top padding is all the air the join needs.
   Against a different colour this butts two grounds together with no air and
   reads as a seam. Used on the amenities row, which runs into the lead block
   below it on the same band. */
.section--flush-below { padding-bottom: 0; }
.section--dark  { background: var(--green); color: var(--cream); }
.section--light { background: var(--grey);  color: var(--green); }
.section--band  { background: var(--band);  color: var(--green); }

.wrap { max-width: var(--container); margin-inline: auto; }
.wrap--narrow { max-width: var(--container-narrow); margin-inline: auto; }
.wrap--text { max-width: 760px; margin-inline: auto; }

.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(24px, 3vw, 60px); }
.split--wide  { grid-template-columns: minmax(0, 1fr) minmax(0, .8fr); }
.split--lead  { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); }
.split--layouts   { grid-template-columns: minmax(0, .6fr) minmax(0, 1.4fr); gap: clamp(16px, 1.6vw, 26px); }
.split--lead-card { grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); }
.split--flush { gap: 0; align-items: stretch; }
.split--end   { align-items: end; }
.split--start { align-items: start; }
.split--stretch { align-items: stretch; }

/* §14.6, in its grid form. Every track below is minmax(0, 1fr) and not a
   bare 1fr, because THE AUTOMATIC MINIMUM OF AN fr TRACK IS min-content: the
   column shrinks only until its longest word, and then stops. In English that
   limit is never reached. In German it is — «Aufenthaltsvisum» and
   «Diversifizierung» pushed the four-figure stat strip 36px past a 320px
   phone, measured. minmax(0, 1fr) lets the track shrink to zero and hands the
   wrapping back to the text, where overflow-wrap and hyphens deal with it. */
.grid { display: grid; gap: var(--gap-tight); }
.grid--cards { gap: var(--gap-card); }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.stack-head { margin-bottom: clamp(28px, 3.2vw, 52px); }
.center { text-align: center; }

/* ----------------------------------------------------------------- nav --- */

/* 80px, transparent over the hero, and it takes a solid ground the moment the
   hero has passed. Cream ink stranded on a light section is the one failure
   this must never have, so the solid ground is the markup default and JS only
   ever removes it. */
.site-header {
  position: fixed;
  inset: 0 0 auto;
  z-index: 80;
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-inline: var(--pad);
  background: rgba(239, 239, 236, .96);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  color: var(--green);
  transition: background .5s ease, color .4s ease, box-shadow .5s ease;
}
.site-header::after {
  content: "";
  position: absolute; inset: auto 0 0;
  height: 1px;
  background: var(--hairline);
  opacity: 1;
  transition: opacity .5s ease;
}
.site-header.is-top {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  color: var(--cream);
}
.site-header.is-top::after { opacity: 0; }

/* Agency mark · hairline · project logotype — the lockup carries both brands
   at every width. Both marks take their colour from the bar, so the pair
   inverts with `.is-top` for free. */
.brand { display: flex; align-items: center; gap: clamp(11px, 1.1vw, 18px); flex-shrink: 0; }
.brand__stack { display: flex; flex-direction: column; gap: 3px; }
.brand__rule { width: 1px; align-self: stretch; min-height: 26px; background: currentColor; opacity: .3; }

.brand__logo {
  position: relative;
  display: block;
  height: var(--brand-logo-h, clamp(13px, 1.3vw, 19px));
  width: calc(var(--brand-logo-h, clamp(13px, 1.3vw, 19px)) * var(--brand-logo-ratio));
  background-color: currentColor;
  -webkit-mask: var(--brand-logo-src) no-repeat center / contain;
  mask: var(--brand-logo-src) no-repeat center / contain;
}
/* The ratio is the mark's own width ÷ height, straight off its viewBox
   (logo2.svg is 504.48 × 135.2). It sizes the element box; the mask is
   `contain` inside it, so a wrong ratio does not distort the mark — it pads it
   with dead space and pushes the header apart. Re-measure it when the file
   is replaced. */
/* Sized off --brand-logo-h rather than the shared default: at 3.73 this mark is
   a compact block, not a long wordmark, so the default height that suits a wide
   logotype leaves it reading as a footnote beside the agency stack. Height
   drives width through the ratio above, so this is the only number to touch. */
.brand__logo--mv {
  --brand-logo-ratio: 3.73;
  --brand-logo-h: clamp(18px, 1.85vw, 26px);
  --brand-logo-src: url(../logo/logo2.svg);
}
/* This build. The ratio is the mark's own width divided by its height, straight
   off its viewBox — mira-hills-mask.svg is 342.22 x 82.61. It sizes the
   ELEMENT BOX; the mask inside is `contain`, so a wrong ratio does not
   distort the mark, it pads it with dead space and pushes the header apart.
   Re-measure when the file is replaced.

   The mask source is the BLACK master, unrecoloured: a CSS mask reads alpha,
   not colour, so the mark takes currentColor from the header bar and inverts
   with `.is-top` for free. mira-hills.svg — the same artwork recoloured to
   the system green — is the on-light logotype used in the overview and
   amenities headings, and the two must not be swapped: recolouring the
   logotype would silently change what the header paints if they were one
   file. */
.brand__logo--mh {
  --brand-logo-ratio: 4.14;
  --brand-logo-h: clamp(15px, 1.5vw, 21px);
  --brand-logo-src: url(../logo/mira-hills-mask.svg);
}
.brand__logo--tr {
  --brand-logo-ratio: 9.05;
  --brand-logo-h: clamp(19px, 2vw, 28px);
  --brand-logo-src: url(../logo/trussardi-mira-verde.svg);
}
.brand__logo span {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
@supports not ((-webkit-mask-image: url(#m)) or (mask-image: url(#m))) {
  .brand__logo { width: auto; height: auto; background: none; }
  .brand__logo span {
    position: static; width: auto; height: auto; margin: 0;
    clip-path: none; overflow: visible;
    font-family: var(--display);
    font-size: clamp(17px, 1.4vw, 23px);
    letter-spacing: var(--track-22);
    text-transform: uppercase;
  }
}
.brand__mark {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(15px, 1.2vw, 19px);
  letter-spacing: var(--track-20);
  line-height: 1.1;
  text-transform: uppercase;
}
.brand__sub { font-size: clamp(8px, .62vw, 10px); letter-spacing: var(--track-30); text-transform: uppercase; opacity: .65; line-height: 1.2; }

.header__right { display: flex; align-items: center; gap: clamp(14px, 1.8vw, 30px); flex-shrink: 0; }
.header__nav { display: flex; align-items: center; gap: clamp(14px, 1.6vw, 26px); }
.header__nav a {
  position: relative;
  font-size: 12px;
  letter-spacing: var(--track-18);
  text-transform: uppercase;
  padding-block: 4px;
  transition: opacity .3s;
}
/* The rule is drawn from the left rather than switched on, so the nav answers
   the pointer with movement instead of a state change. */
.header__nav a::after {
  content: "";
  position: absolute; inset: auto 0 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform .4s var(--ease);
}
.header__nav a:hover::after { transform: scaleX(1); transform-origin: left; }

/* MENU, then the two rules — the reference's mark, in the agency's ink. */
.menu-btn {
  display: flex; align-items: center; gap: 14px;
  background: none; border: none; padding: 8px 0; cursor: pointer;
}
.menu-btn .micro { font-size: 12px; letter-spacing: var(--track-20); }
.menu-btn__lines { display: flex; flex-direction: column; gap: 5px; }
.menu-btn__lines i {
  display: block; width: 46px; height: 1px;
  background: currentColor;
  transition: transform .4s var(--ease), width .4s var(--ease);
}
.menu-btn:hover .menu-btn__lines i:last-child { width: 30px; }
.menu-btn[aria-expanded="true"] .menu-btn__lines i:first-child { transform: translateY(3px) rotate(5deg); }
.menu-btn[aria-expanded="true"] .menu-btn__lines i:last-child  { transform: translateY(-3px) rotate(-5deg); width: 46px; }

/* -------------------------------------------------------------- button --- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 12px 32px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 12px;
  letter-spacing: var(--track-20);
  /* Letter-spacing appends a trailing space after the last glyph, so centred
     text sits one track-width left of true centre. Indenting by the same
     amount puts it back. Every letter-spaced, centred control needs this. */
  text-indent: var(--track-indent);
  text-transform: uppercase;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition: background .35s, color .35s, border-color .35s,
              transform .35s var(--ease), box-shadow .35s var(--ease);
}
/* Every button answers the pointer with the same small lift, so the page has
   one hover language rather than a different one per component. */
.btn:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(24, 34, 30, .16); }
.btn:active { transform: translateY(0); box-shadow: none; }
.btn--pill { border-radius: 999px; }
.btn--solid { background: var(--green); color: var(--cream); }
.btn--solid:hover { background: var(--green-soft); }
.btn--cream { background: var(--cream); color: var(--green); }
.btn--cream:hover { background: var(--white); }
.btn--soft { background: var(--green-soft); color: var(--cream); }
.btn--soft:hover { background: var(--green); }
/* The reference's outline pill — a hairline in the accent, nothing else. */
.btn--line { border-color: var(--burgundy); color: var(--green); background: none; }
.btn--line:hover { background: var(--burgundy); border-color: var(--burgundy); color: var(--cream); }
.btn--outline-dark { border-color: rgba(243, 241, 236, .4); color: var(--cream); }
.btn--outline-dark:hover { background: var(--cream); color: var(--green); }
.btn--glass {
  background: rgba(243, 241, 236, .14);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-color: rgba(243, 241, 236, .35);
  color: var(--cream);
}
.btn--glass:hover { background: var(--cream); color: var(--green); }
.btn--block { display: flex; width: 100%; }

/* A download control carries its arrow, as in the reference. */
.btn--dl::after { content: "↓"; margin-inline-start: 12px; font-size: 13px; letter-spacing: 0; }

/* Every other letter-spaced pill in the system needs the same optical
   correction as .btn — see the note there. */
.bed-pill { text-indent: .16em; }
.pill-badge,
.chips span,
.doc-links a,
.doc-links button,
.lead-submit { text-indent: .16em; }

/* The document chips are the one control that had drifted: two different
   sizes across two stylesheets, and no letter-spacing while everything around
   them is tracked. One definition, matching the small-pill treatment. */
.doc-links a,
.doc-links button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 38px;
  padding: 8px 18px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: none;
  font-family: var(--body);
  font-size: 11px;
  letter-spacing: var(--track-16);
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--muted);
  cursor: pointer;
  transition: border-color .3s, color .3s, background .3s, transform .3s var(--ease);
}
.doc-links a:hover,
.doc-links button:hover { border-color: var(--burgundy); color: var(--green); transform: translateY(-2px); }

.link-rule {
  display: inline-block;
  padding-bottom: 5px;
  border-bottom: 1px solid currentColor;
  font-size: 13px;
  letter-spacing: var(--track-14);
  text-transform: uppercase;
  transition: opacity .35s;
}
.link-rule:hover { opacity: .7; }

/* ---------------------------------------------------------- nav panel --- */

.nav-panel {
  position: fixed;
  inset: 0;
  z-index: 79;
  background: rgba(26, 35, 32, .985);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: clamp(90px, 10vw, 140px) var(--pad) clamp(40px, 5vw, 80px);
  color: var(--cream);
  opacity: 0;
  visibility: hidden;
  transition: opacity .4s ease, visibility .4s;
}
.nav-panel[data-open="true"] { opacity: 1; visibility: visible; }
.nav-panel a {
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(22px, 2.8vw, 42px);
  letter-spacing: var(--track-08);
  text-transform: uppercase;
  padding: 11px 0;
  border-bottom: 1px solid rgba(243, 241, 236, .12);
  transition: padding-inline-start .35s var(--ease), color .3s;
}
.nav-panel a:hover { padding-inline-start: 14px; }
.nav-panel a:last-child { border-bottom: 0; }
.nav-panel .nav-panel__back {
  font-family: var(--body);
  font-size: 11px;
  letter-spacing: var(--track-24);
  padding: 0 0 22px;
  border-bottom: 0;
  color: var(--on-dark-50);
}

/* ---------------------------------------------------------------- hero --- */

/* The reference hero: full height, copy pinned to the bottom edge, the H1 on
   the left and a short kicker plus the scroll cue on the right. */
.hero {
  position: relative;
  height: 100vh;
  height: 100svh;
  min-height: 620px;
  overflow: hidden;
}
.hero__slides { position: absolute; inset: 0; }
.hero__slide { position: absolute; inset: 0; opacity: 0; transition: opacity 1.7s ease; }
.hero__slide.is-active { opacity: 1; }
/* The renders put the horizon a little below centre; a default crop on a
   viewport wider than the source loses the ridge line to sky. */
.hero__slide img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 62%; }
.hero__slide.is-active img { animation: mvDrift 16s ease-out forwards; }

.hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(26, 35, 32, .42) 0%,
    rgba(26, 35, 32, .10) 38%,
    rgba(26, 35, 32, .72) 100%);
}

.hero__inner {
  position: absolute;
  inset: auto 0 0;
  z-index: 2;
  padding: 0 var(--pad) clamp(44px, 6vw, 90px);
  color: var(--cream);
}
.hero__grid {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(24px, 3vw, 56px);
}
.hero__h1 { flex: 1 1 auto; min-width: 0; }
/* Wide enough that the CTA pair sits on one line beside the kicker — the pair
   is the page's revenue path and must not stack into a column here. */
.hero__aside { flex: 0 0 auto; width: min(580px, 48vw); display: flex; flex-direction: column; gap: clamp(16px, 1.8vw, 26px); }
.hero__kicker {
  font-size: clamp(15px, 1.15vw, 19px);
  line-height: 1.35;
  letter-spacing: var(--track-06);
  text-transform: uppercase;
  font-family: var(--display);
  font-weight: 300;
  color: var(--cream);
}
.hero .eyebrow { color: var(--on-dark-70); margin-bottom: clamp(16px, 1.8vw, 26px); }

.hero__scroll {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  align-self: flex-start;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--cream);
  font-size: 13px;
  letter-spacing: var(--track-10);
  color: var(--cream);
}
.hero__scroll i {
  display: block; width: 9px; height: 9px;
  border-inline-end: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  animation: mvNudge 2.4s var(--ease) infinite;
}

/* The word-by-word rise the reference runs on its hero, done with CSS only. */
.split-word { display: inline-block; overflow: clip; vertical-align: bottom; }
.split-word > span { display: inline-block; transform: translateY(105%); opacity: 0; }
.is-revealed .split-word > span {
  transform: none;
  opacity: 1;
  transition: transform 1s var(--ease), opacity .8s ease;
  transition-delay: calc(var(--i, 0) * 70ms);
}

/* --------------------------------------------------------------- intro --- */

/* Centred logotype, heading and one paragraph — the reference's opening beat. */
.intro { padding: clamp(90px, 10vw, 144px) var(--pad); text-align: center; }
.intro__inner { display: flex; flex-direction: column; align-items: center; }
.intro__mark { width: clamp(140px, 14vw, 200px); height: auto; margin-bottom: clamp(26px, 3vw, 44px); opacity: .9; }
.intro .h-section { max-width: 16em; }
.intro__copy { margin-top: clamp(22px, 2.6vw, 38px); max-width: 68ch; font-size: clamp(17px, 1.2vw, 19px); line-height: 1.6; color: var(--muted); }
.section--dark .intro__copy { color: var(--on-dark-70); }

/* ------------------------------------------------------ full-bleed block --- */

/* An image that runs the width of the page inside a small inset, the copy set
   into its top left, and the accent rule struck along the bottom edge. */
.imgfull { position: relative; overflow: hidden; height: clamp(420px, 56vw, 720px); }
.imgfull > img,
.imgfull > picture img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 1.6s var(--ease);
}
.imgfull:hover > img,
.imgfull:hover > picture img { transform: scale(1.04); }
/* The copy used to rely on a heavy corner scrim, which asked the render to be
   dark exactly where it is often brightest and left the text sitting in a grey
   wash across a third of the picture. The copy now carries its own plate, so
   the scrim is back to a light vignette — legibility is the plate's job, and
   the render is left looking like a render. */
.imgfull__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(22, 30, 27, .30) 0%, rgba(22, 30, 27, 0) 32%),
    linear-gradient(0deg,   rgba(22, 30, 27, .34) 0%, rgba(22, 30, 27, 0) 30%);
}
/* The plate. A frosted panel with the accent struck down its leading edge —
   the copy sits ON something rather than floating over the picture, and the
   contrast no longer depends on which render is behind it. */
.imgfull__body {
  position: absolute;
  top: clamp(20px, 3.4vw, 50px);
  inset-inline-start: clamp(14px, 4vw, 60px);
  inset-inline-end: clamp(14px, 4vw, 60px);
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.4vw, 20px);
  max-width: min(56ch, 640px);
  padding: clamp(20px, 2.2vw, 36px) clamp(20px, 2.4vw, 40px);
  background: rgba(19, 27, 24, .62);
  -webkit-backdrop-filter: blur(16px) saturate(1.08);
  backdrop-filter: blur(16px) saturate(1.08);
  border-inline-start: 3px solid var(--burgundy);
  box-shadow: 0 24px 60px rgba(10, 16, 13, .3);
  color: var(--cream);
}
/* No backdrop-filter (older Safari, Firefox with it disabled) means the panel
   would be a flat 62% wash over a busy render. Fall back to near-opaque. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .imgfull__body { background: rgba(19, 27, 24, .88); }
}
.imgfull__body--right { inset-inline-start: auto; inset-inline-end: clamp(14px, 4vw, 60px); text-align: end; align-items: flex-end; border-inline-start: 0; border-inline-end: 3px solid var(--burgundy); }
.imgfull__body--bottom { top: auto; bottom: clamp(30px, 4vw, 60px); }
.imgfull__body .label { color: var(--on-dark-70); }
.imgfull__body p { color: var(--on-dark-90); max-width: 48ch; }
.imgfull__body--right p { margin-inline-start: auto; }

/* ------------------------------------------------------- master plan --- */

/* Edge to edge, and NOT cropped: this is a drawing whose labels are the
   content, so `cover` would cut "Trussardi Residences Tower A" off the left
   edge on a wide viewport. Height follows the aspect ratio instead. */
.masterplan { position: relative; margin: clamp(30px, 3.4vw, 54px) 0 0; }
.masterplan__zoom {
  display: block;
  position: relative;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}
.masterplan img { width: 100%; height: auto; display: block; }
.masterplan__hint {
  position: absolute;
  inset-inline-end: clamp(14px, 2vw, 28px); bottom: clamp(18px, 2.2vw, 32px);
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid rgba(243, 241, 236, .22);
  background: rgba(18, 22, 15, .78);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: var(--cream);
  font-size: 12px;
  letter-spacing: var(--track-14);
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  transition: background .3s;
}
.masterplan__zoom:hover .masterplan__hint { background: var(--burgundy); }
/* Bottom right is where the drawing's own labels are densest, and at phone
   width the hint lands on top of three of them. */
@media (max-width: 760px) {
  .masterplan__hint {
    top: 12px; inset-inline-end: 12px; bottom: auto;
    padding: 6px 12px;
    font-size: 11px;
  }
}

/* The rule. 4px, and it reads as struck metal rather than a flat border. */
.accent-line {
  position: absolute; inset: auto 0 0;
  height: 4px;
  background: var(--accent-rule);
  z-index: 2;
}

/* --------------------------------------------------- gallery + text row --- */

/* Half the row is a gallery, half is a centred block of copy. `--flip` swaps
   the sides on wide screens; DOM order stays reading order. */
/* minmax(0,1fr), not 1fr: a grid item's automatic minimum is its min-content,
   and a slider track full of 100%-wide slides has a large one. With plain 1fr
   the column refuses to shrink and the page gains a horizontal scrollbar on a
   phone. Same reason for .dev below. */
.duo { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; }
/* Held inside the same inset as the full-bleed blocks, so every block on the
   page shares one left and right edge. The reference lets its gallery rows run
   edge to edge while its image blocks sit inset, and the two-pixel disagreement
   between them is visible at every boundary. */
.duo, .dev { margin-inline: var(--edge); }
.duo__media { position: relative; min-width: 0; }
.duo__text {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  gap: clamp(24px, 3vw, 50px);
  padding: clamp(40px, 5vw, 80px) clamp(24px, 4vw, 72px);
}
.duo__text > p { max-width: 46ch; color: var(--muted); }
.section--dark .duo__text > p,
.section--band .duo__text > p { color: inherit; }
.section--band .duo__text > p { color: #3B4844; }
@media (min-width: 1001px) {
  .duo--flip .duo__media { order: 2; }
}

/* --------------------------------------------------------------- slider --- */

/* One vanilla slider drives the gallery rows, the amenities row and the
   developer row. No CDN: the reference loads Splide and Swiper; this is
   ~120 lines of CSS and JS against two dependencies. */
.slider { position: relative; min-width: 0; }
/* pan-y hands horizontal gestures to the pointer handlers in main.js while
   leaving vertical page scroll to the browser. Without it a touch swipe is
   claimed by the native pan, the pointer stream is cancelled mid-drag, and the
   gallery cannot be paged by finger at all. */
.slider__viewport { overflow: hidden; touch-action: pan-y; }
/* A finger drag across a photo must not start a native image drag instead. */
.slider__slide img { -webkit-user-drag: none; user-select: none; }
.slider__track {
  display: flex;
  transition: transform .8s var(--ease);
  will-change: transform;
}
.slider__slide { flex: 0 0 100%; min-width: 0; }

.slider--gallery .slider__viewport { position: relative; }
.slider--gallery .slider__slide img {
  width: 100%;
  height: clamp(320px, 46vw, 648px);
  object-fit: cover;
  display: block;
  transition: transform 1.6s var(--ease);
}
/* The reference lifts its gallery slightly on hover. Scaling the image rather
   than the slide keeps the track transform — which is what pages the carousel
   — free of anything else. */
.slider--gallery:hover .slider__slide img { transform: scale(1.035); }
.slider--gallery .accent-line { z-index: 4; }

/* Enlarge control over the gallery. */
.slider__zoom {
  position: absolute;
  top: clamp(14px, 1.6vw, 24px); inset-inline-end: clamp(14px, 1.6vw, 24px);
  z-index: 4;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(243, 241, 236, .5);
  background: rgba(26, 35, 32, .3);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: var(--cream);
  font-size: 16px; line-height: 1;
  cursor: zoom-in;
  opacity: 0;
  transition: opacity .35s, background .35s;
}
.slider:hover .slider__zoom, .slider__zoom:focus-visible { opacity: 1; }
.slider__zoom:hover { background: var(--burgundy); border-color: var(--burgundy); }

/* Counter — "10 / 18", as the reference announces its slide position. */
.slider__count {
  position: absolute;
  inset-inline-start: clamp(14px, 1.6vw, 24px); bottom: clamp(20px, 2.2vw, 32px);
  z-index: 4;
  font-size: 12px;
  letter-spacing: var(--track-20);
  color: var(--cream);
  text-shadow: 0 1px 12px rgba(20, 28, 24, .8);
  font-variant-numeric: lining-nums tabular-nums;
}

/* Dot rail. */
.slider__dots {
  position: absolute;
  inset: auto clamp(14px, 1.6vw, 24px) clamp(20px, 2.2vw, 32px) auto;
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
  max-width: 60%;
}
.slider__dots button {
  width: 22px; height: 2px;
  padding: 0;
  border: 0;
  background: rgba(243, 241, 236, .38);
  cursor: pointer;
  transition: background .3s;
}
.slider__dots button[aria-selected="true"] { background: var(--cream); }
.slider__dots button:hover { background: rgba(243, 241, 236, .75); }

/* Round arrows, hairlined in the accent — the reference's control. */
.slider__arrow {
  width: 46px; height: 46px;
  border-radius: 50%;
  border: 1px solid var(--burgundy);
  background: rgba(255, 255, 255, .4);
  color: var(--green);
  font-size: 17px; line-height: 1;
  cursor: pointer;
  transition: background .35s, color .35s, transform .35s var(--ease), border-color .35s;
}
.slider__arrow:hover { background: var(--burgundy); color: var(--cream); }
.slider__arrow--prev:hover { transform: translateX(-3px); }
.slider__arrow--next:hover { transform: translateX(3px); }
.slider__arrow[disabled] { opacity: .3; cursor: default; }
.slider__arrow[disabled]:hover { transform: none; background: rgba(255, 255, 255, .4); color: var(--green); }
.section--dark .slider__arrow { border-color: var(--on-dark-50); background: rgba(243, 241, 236, .06); color: var(--cream); }
.section--dark .slider__arrow:hover { background: var(--cream); color: var(--green); }
/* Both disabled-hover resets must be restated on the dark ground: the dark
   hover rule above carries the same specificity and comes later, so without
   this a paged-out arrow lights up cream. */
.section--dark .slider__arrow[disabled]:hover { background: rgba(243, 241, 236, .06); color: var(--cream); transform: none; }
.slider__arrows { display: flex; gap: 10px; justify-content: center; margin-top: clamp(24px, 2.6vw, 40px); }

/* ----------------------------------------------------------- amenities --- */

/* Four up, static. This was a paged, autoplaying rail; with exactly four cards
   it had nothing to page on a desktop and only ever moved under the reader.
   It is a grid now — four across, two on a tablet, one on a phone. */
.amenities__head { text-align: center; display: flex; flex-direction: column; align-items: center; }
.amenities__head .intro__mark { margin-bottom: clamp(20px, 2.4vw, 34px); }
.amenity-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(20px, 2.4vw, 38px) clamp(10px, 1.2vw, 18px);
  margin-top: clamp(34px, 4vw, 62px);
}
/* Six amenities here, so three across rather than four — four would leave a
   widow pair on the second row. The 1100px rule further down still takes it to
   two and 640px to one; this only sets the desktop count. */
.amenity-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* The facet is centred in its column by `margin-inline:auto`, so the title and
   the sentence have to be centred against it or the card reads as two pieces
   that miss each other. One centred column, and the copy takes a measure
   rather than the full column width so the ragged edge stays symmetrical. */
.amenity-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
/* The reference cuts each amenity image into a faceted crystal inside a struck
   metal edge, and gives each one a different cut so the row reads as four
   objects rather than four thumbnails. Same device here, in the brand accent:
   the frame is the accent gradient, the image is clipped 2px inside it.

   Each facet is a closed polygon on the same five-point armature, nudged per
   card. Keep them convex — a concave point reads as a rendering fault, not a
   cut stone. */
.amenity__frame {
  --facet: polygon(38% 0%, 100% 22%, 88% 88%, 20% 100%, 0% 42%);
  position: relative;
  width: min(100%, 300px);
  aspect-ratio: 1 / 1;
  margin-inline: auto;
  padding: 3px;
  background: var(--accent-rule);
  clip-path: var(--facet);
  transition: filter .5s var(--ease), transform .5s var(--ease);
}
.amenity__frame img {
  width: 100%; height: 100%;
  object-fit: cover;
  clip-path: var(--facet);
  transition: transform 1.4s var(--ease);
}
.amenity-card:hover .amenity__frame img { transform: scale(1.06); }
.amenity-card:hover .amenity__frame { filter: brightness(1.06); transform: translateY(-5px); }
.amenity-card .amenity__title { transition: color .4s; }
.amenity-card:hover .amenity__title { color: var(--burgundy); }
.section--dark .amenity-card:hover .amenity__title { color: var(--cream); }

.amenity-card:nth-child(4n+2) .amenity__frame {
  --facet: polygon(58% 2%, 100% 42%, 74% 100%, 10% 90%, 0% 26%);
}
.amenity-card:nth-child(4n+3) .amenity__frame {
  --facet: polygon(44% 0%, 96% 14%, 100% 74%, 32% 100%, 0% 58%);
}
.amenity-card:nth-child(4n+4) .amenity__frame {
  --facet: polygon(64% 0%, 100% 50%, 64% 100%, 6% 84%, 0% 22%);
}
/* Two lines' worth of room whether the title needs it or not, so the
   paragraphs below sit on one baseline across the row. */
.amenity__title {
  margin-top: clamp(16px, 1.8vw, 24px);
  min-height: 2.4em;
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(18px, 1.5vw, 24px);
  letter-spacing: var(--track-08);
  text-transform: uppercase;
  line-height: 1.2;
}
.amenity__text { margin-top: 10px; max-width: 32ch; margin-inline: auto; font-size: 16px; line-height: 1.5; color: #3E4B47; }
.section--dark .amenity__text { color: var(--on-dark-62); }

/* ------------------------------------------------------- reason cards --- */

/* Nine equal boxes stacked three-by-three read as a specification sheet: the
   eye has no entry point and no reason to move. The same nine reasons on a
   paged rail give it one — three at a time, numbered, with the accent struck
   across the top of each so the row reads as a sequence being worked through
   rather than a wall. Same slider as the amenities row, wider slides. */
.slider--cards { margin-top: clamp(28px, 3.2vw, 48px); }
.slider--cards .slider__track { gap: var(--gap-card); align-items: stretch; }
.slider--cards .slider__slide { flex: 0 0 calc((100% - 2 * var(--gap-card)) / 3); display: flex; }

.reason {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  padding: clamp(24px, 2.4vw, 38px) clamp(20px, 2.2vw, 32px);
  background: var(--white);
  overflow: hidden;
  transition: transform .5s var(--ease), box-shadow .5s var(--ease);
}
.reason::before {
  content: "";
  position: absolute; inset: 0 0 auto;
  height: 3px;
  background: var(--accent-rule);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .6s var(--ease);
}
.reason:hover { transform: translateY(-6px); box-shadow: 0 22px 50px rgba(24, 34, 30, .13); }
.reason:hover::before { transform: scaleX(1); }
.reason__no {
  font-family: var(--display);
  font-size: clamp(30px, 2.8vw, 44px);
  line-height: 1;
  letter-spacing: var(--track-04);
  color: var(--burgundy);
  opacity: .32;
  font-variant-numeric: lining-nums tabular-nums;
  transition: opacity .5s var(--ease);
}
.reason:hover .reason__no { opacity: .7; }
.reason__t { font-family: var(--display); font-weight: 400; font-size: clamp(19px, 1.6vw, 25px); line-height: 1.2; letter-spacing: var(--track-04); text-transform: uppercase; }
.reason__p { font-size: 16px; line-height: 1.55; color: var(--muted); }

/* Three reasons fit on one row, so they are a static grid rather than the
   paged rail nine of them need. `.reason` is self-contained — it sets its own
   width, ground and padding — so it drops into a plain grid unchanged. */
.reason-grid { margin-top: clamp(28px, 3.2vw, 48px); align-items: stretch; }

/* A project card with no page behind it yet is an <article>, not an <a>. The
   hover zoom is the affordance of a link, so it is switched off here — the
   button inside the card is the thing that is actually clickable. */
.project-card--static:hover img { transform: none; }

/* ---------------------------------------------------------- developer --- */

.dev { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: stretch; }
.dev__media { position: relative; overflow: hidden; min-height: clamp(340px, 40vw, 620px); }
/* :not() matters — the logotype is a sibling <img> in the same box, and a bare
   `> img` rule stretches it to the full panel. The ground is inside a
   <picture> now, so that arm is named too: a `> img` rule alone stops
   matching the moment the image gains a webp source set. */
.dev__media > img:not(.dev__logo),
.dev__media > picture img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.dev__media .lockup__scrim { position: absolute; inset: 0; background: linear-gradient(125deg, rgba(12, 18, 14, .38), rgba(12, 18, 14, .74)); }
.dev__logo {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: min(280px, 52%);
  max-height: 34%;
  height: auto;
  object-fit: contain;
  z-index: 2;
}
.dev__body {
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(40px, 5vw, 86px) clamp(24px, 4vw, 72px);
  text-align: center;
  align-items: center;
}
.dev__body .slider { width: 100%; }
.dev__slide { display: flex; flex-direction: column; align-items: center; gap: clamp(18px, 2vw, 30px); padding-inline: 4px; }
.dev__slide p { max-width: 52ch; color: var(--muted); }
.section--dark .dev__slide p { color: var(--on-dark-70); }

/* The strip of partner house marks that stood here has been removed, along
   with assets/logo/brands/. Percent&Co have no licence to reproduce Bentley,
   ETRO, ELIE SAAB, Jacob & Co., Trussardi, John Richmond or Gianfranco Ferré
   trade marks, and a brochure is not a licence. The developer paragraph still
   names the houses in running text, which is nominative use and needs none. */

/* The developer row is a full-bleed block with no padding of its own, so the
   dark agency band below butted straight onto it. It gets the same air below
   it that the section above gives it. */
#developer.section--flush { padding-bottom: var(--rhythm); }

/* ------------------------------------------------------------- contact --- */

.contact { padding: clamp(80px, 9vw, 140px) var(--pad); }
.contact__head { text-align: center; display: flex; flex-direction: column; align-items: center; gap: clamp(16px, 2vw, 26px); }
.contact__head p { max-width: 52ch; color: var(--muted); }
.contact__form { margin: clamp(40px, 4.6vw, 72px) auto 0; max-width: 860px; }

/* The reference lays its contact fields out two to a row. The form itself is
   built by lead-capture.js as a flex column and is shared with the dialogs, so
   the two-column arrangement is applied here, on this one mount, rather than
   in the module — the dialogs stay single-column where width is scarce (§11).
   The three fields are the first three divs in the form; everything after them
   (honeypot, consent, submit, note) runs the full width. */
.contact__form .lead-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(20px, 3vw, 44px);
  align-items: start;
}
.contact__form .lead-form > .lead-field:nth-of-type(3),
.contact__form .lead-form > .lead-trap,
.contact__form .lead-form > .lead-consent,
.contact__form .lead-form > .lead-submit,
.contact__form .lead-form > .lead-note { grid-column: 1 / -1; }
/* The shared submit is width:100% for the dialogs; here it becomes the
   reference's centred 270px pill. */
.contact__form .lead-form > .lead-submit {
  justify-self: center;
  width: auto;
  min-width: 270px;
  border-radius: 999px;
  font-size: 12px;
  letter-spacing: var(--track-20);
  text-transform: uppercase;
}

@media (max-width: 700px) {
  .contact__form .lead-form { grid-template-columns: 1fr; }
  .contact__form .lead-form > .lead-field { grid-column: 1 / -1; }
}

/* ------------------------------------------------------------ invest --- */

/* A dark band carrying only two bordered boxes of text reads as a placeholder.
   This gives it a ground (a render held well back so the copy still measures
   past 4.5:1), a figure strip built from the numbers the approved copy already
   states, and an accent edge on each panel. */
.invest { position: relative; overflow: hidden; }
/* Lift the content off the ground image by naming the wrapper, not `> *`. A
   bare child selector matches .accent-line too and, at equal specificity,
   beats its `position: absolute` — which drops the rule into the flow and
   lands it on top of the CTA. */
.invest > .wrap--narrow { position: relative; z-index: 1; }
.invest__bg { position: absolute; inset: 0; z-index: 0; }
.invest__bg img { width: 100%; height: 100%; object-fit: cover; }
.invest__bg::after {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(30, 41, 37, .90) 0%, rgba(30, 41, 37, .95) 55%, rgba(30, 41, 37, .99) 100%);
}
.invest .accent-line { z-index: 2; }

.panel {
  position: relative;
  padding: clamp(26px, 3vw, 46px);
  background: rgba(243, 241, 236, .05);
  border: 1px solid rgba(243, 241, 236, .13);
  transition: background .4s, border-color .4s;
}
.panel::before {
  content: "";
  position: absolute; inset: 0 0 auto;
  height: 2px;
  background: var(--accent-rule);
}
.panel:hover { background: rgba(243, 241, 236, .08); border-color: rgba(243, 241, 236, .22); }

/* ------------------------------------------------------------- cards --- */

.card {
  background: var(--white);
  padding: clamp(26px, 2.8vw, 46px);
  transition: transform .5s var(--ease), box-shadow .5s var(--ease);
}
.card:hover { transform: translateY(-5px); box-shadow: 0 20px 46px rgba(24, 34, 30, .12); }
.card--sm { padding: clamp(20px, 2.2vw, 32px); }
.card--dark { background: var(--fill-dark); border: 1px solid rgba(243, 241, 236, .12); padding: clamp(20px, 2.2vw, 34px); }
.card--line { background: none; border: 1px solid var(--hairline); }
.section--band .card--line { border-color: rgba(38, 51, 47, .18); }

.figure-card { border-top: 1px solid var(--hairline); padding-top: clamp(14px, 1.4vw, 22px); }
.section--dark .figure-card { border-top-color: var(--rule-dark); }

/* -------------------------------------------------------- project card --- */

.project-card { display: block; position: relative; overflow: hidden; color: var(--cream); }
.project-card img { width: 100%; height: clamp(340px, 46vw, 700px); object-fit: cover; transition: transform 1.4s var(--ease); }
.project-card:hover img { transform: scale(1.04); }
.project-card__scrim { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(26, 35, 32, .1) 30%, rgba(26, 35, 32, .88)); }
.project-card__body { position: absolute; inset: 0; padding: clamp(22px, 2.8vw, 48px); display: flex; flex-direction: column; justify-content: space-between; }
.project-card__foot { display: flex; flex-wrap: wrap; gap: clamp(20px, 3vw, 56px); align-items: flex-end; justify-content: space-between; }
.pill-badge {
  align-self: flex-start;
  background: rgba(243, 241, 236, .14);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(243, 241, 236, .3);
  padding: 8px 18px;
  border-radius: 999px;
  font-size: 11px;
  letter-spacing: var(--track-20);
  text-transform: uppercase;
}
.project-card__cta { background: var(--cream); color: var(--green); }
.project-card:hover .project-card__cta { background: var(--burgundy); color: var(--cream); }

/* On a phone the copy stops being an overlay. A 340px picture cannot hold a
   heading, ten lines of body and a button, and darkening the photograph enough
   to make it try costs the photograph. So below 760px the card unstacks: the
   image is the image, at its own height, and the copy sits beneath it on solid
   green — no scrim, nothing dimmed, and the card is as tall as its text plus
   the padding at any string length. */
@media (max-width: 760px) {
  .project-card { display: grid; grid-template-columns: minmax(0, 1fr); }
  .project-card > picture { display: block; overflow: hidden; }
  .project-card img { height: clamp(200px, 56vw, 320px); }
  .project-card__scrim { display: none; }
  .project-card__body {
    position: relative; inset: auto;
    background: var(--green);
    padding: 50px 22px;
    gap: clamp(20px, 5vw, 30px);
  }
  .project-card__foot { gap: clamp(18px, 4vw, 26px); align-items: stretch; }
  .project-card__cta { width: 100%; }
}

/* ------------------------------------------------------------ location --- */

/* Name over time, not name-then-time-across-a-gap. On one line the two halves
   of each pair sat a third of the row apart and the eye had to travel to pair
   them up; stacked, each destination is one object. */
.travel {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 6px; padding: clamp(14px, 1.4vw, 20px) 0;
  border-top: 1px solid var(--hairline);
  transition: border-top-color .4s, padding-inline-start .4s var(--ease);
}
.travel:hover { border-top-color: var(--burgundy); padding-inline-start: 8px; }
.section--dark .travel { border-top-color: var(--rule-dark); }
.travel span:first-child {
  font-size: 11px; letter-spacing: var(--track-20); text-transform: uppercase;
  color: var(--label);
}
.section--dark .travel span:first-child { color: var(--on-dark-50); }
.travel span:last-child { font-family: var(--display); font-size: clamp(22px, 1.9vw, 30px); line-height: 1; white-space: nowrap; transition: color .4s; }
.travel:hover span:last-child { color: var(--burgundy); }
.section--dark .travel:hover span:last-child { color: var(--cream); }

/* The map is a real Google embed, built lazily — nothing is requested from
   Google on a visit that never reaches Location. It used to run the full width
   of the page, where a raw Google tile set is simply a large ugly rectangle;
   it is a framed panel beside the travel times now, at half the row. */
.location__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(28px, 3.4vw, 60px);
  align-items: stretch;
  margin-top: clamp(30px, 3.4vw, 52px);
}
.location__times { display: flex; flex-direction: column; justify-content: center; }
.map {
  position: relative;
  margin: 0;
  min-height: clamp(300px, 34vw, 480px);
  overflow: hidden;
  border: 1px solid var(--hairline);
  background-color: #2E3C37;
  background-image: repeating-linear-gradient(135deg, rgba(243, 241, 236, .05) 0 2px, transparent 2px 12px);
}
@media (max-width: 1000px) {
  .location__row { grid-template-columns: minmax(0, 1fr); }
  .map { min-height: clamp(260px, 52vw, 380px); }
}
.map iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; opacity: 0; transition: opacity .7s var(--ease); }
.map[data-loaded="true"] iframe { opacity: 1; }
.map[data-touch="true"]:not([data-active="true"]) iframe { pointer-events: none; }
.map__hint {
  position: absolute; z-index: 3;
  left: 50%; bottom: 22px; transform: translateX(-50%);
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid rgba(243, 241, 236, .18);
  background: rgba(18, 22, 15, .82);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: var(--cream);
  font-size: 12px;
  letter-spacing: var(--track-14);
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  transition: opacity .3s;
}
.map:not([data-touch="true"]) .map__hint,
.map[data-active="true"] .map__hint { opacity: 0; }
.map__fallback {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; text-align: center; padding: 24px;
  background: linear-gradient(rgba(24, 34, 30, .72), rgba(24, 34, 30, .72));
  color: var(--cream);
}
.map__fallback p { max-width: 40ch; font-size: 16px; color: var(--on-dark-70); }

/* ------------------------------------------------------------ lightbox --- */

.lightbox {
  position: fixed; inset: 0; z-index: 120;
  background: rgba(20, 28, 25, .96);
  display: flex; align-items: center; justify-content: center;
  padding: clamp(16px, 4vw, 64px);
  cursor: zoom-out;
  opacity: 0; visibility: hidden;
  transition: opacity .4s ease, visibility .4s;
}
.lightbox[data-open="true"] { opacity: 1; visibility: visible; }
.lightbox img { max-width: 100%; max-height: 100%; object-fit: contain; transition: opacity .35s ease; }
.lightbox[data-swapping="true"] img { opacity: 0; }
.lightbox__close {
  position: absolute; top: clamp(14px, 2vw, 28px); inset-inline-end: clamp(14px, 2vw, 28px);
  width: 48px; height: 48px; border-radius: 50%;
  border: 1px solid rgba(243, 241, 236, .4);
  background: none; color: var(--cream);
  font-size: 22px; line-height: 1; cursor: pointer;
  transition: background .3s, color .3s;
}
.lightbox__close:hover { background: var(--cream); color: var(--green); }

/* An enlarged gallery image that can only be closed and re-opened to see the
   next one is a dead end — the visitor is deepest in the imagery at exactly
   the moment the interface stops helping. The arrows page the gallery the
   image came from, and the slider is moved to match on close, so the two are
   never showing different pictures. */
.lightbox__nav {
  position: absolute; top: 50%;
  transform: translateY(-50%);
  width: 54px; height: 54px;
  border-radius: 50%;
  border: 1px solid rgba(243, 241, 236, .4);
  background: rgba(20, 28, 25, .4);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: var(--cream);
  font-size: 19px; line-height: 1;
  cursor: pointer;
  transition: background .3s, color .3s, border-color .3s, transform .3s var(--ease);
}
.lightbox__nav--prev { inset-inline-start: clamp(10px, 2vw, 30px); }
.lightbox__nav--next { inset-inline-end: clamp(10px, 2vw, 30px); }
.lightbox__nav:hover { background: var(--burgundy); border-color: var(--burgundy); }
.lightbox__nav--prev:hover { transform: translateY(-50%) translateX(-3px); }
.lightbox__nav--next:hover { transform: translateY(-50%) translateX(3px); }
.lightbox__count {
  position: absolute;
  left: 50%; bottom: clamp(14px, 2.4vw, 30px);
  transform: translateX(-50%);
  margin: 0;
  font-size: 12px;
  letter-spacing: var(--track-22);
  color: var(--on-dark-70);
  font-variant-numeric: lining-nums tabular-nums;
}
/* A single-image target keeps the plain lightbox it had. */
.lightbox[data-single="true"] .lightbox__nav,
.lightbox[data-single="true"] .lightbox__count { display: none; }

@media (max-width: 640px) {
  .lightbox { padding-inline: 12px; }
  .lightbox__nav { width: 44px; height: 44px; }
  .lightbox__nav--prev { inset-inline-start: 6px; }
  .lightbox__nav--next { inset-inline-end: 6px; }
}

/* --------------------------------------------------------- units table --- */

/* Every secondary size in this table used to be set at the smallest value in
   the system — 11px tracked labels, a 13px unit conversion, a 15px note — all
   of it in a low-alpha cream on dark green. On a 15-inch laptop that is a
   squint. The row values were already large; the supporting text now sits at a
   size and an alpha that can be read at arm's length. */
.table-scroll { border: 1px solid var(--rule-dark); overflow-x: auto; }
.units-table { min-width: 560px; }
/* Three columns, not the sibling sites' four: this project has no source
   figure for a saleable area, so there is no Area column. See the note on
   UNITS in src/data/figures.mjs. */
.units-head, .units-row { display: grid; grid-template-columns: 1.4fr 1.2fr .9fr; gap: 16px; align-items: center; }
.units-head {
  padding: 16px clamp(16px, 2vw, 32px);
  color: var(--on-dark-70);
  background: rgba(243, 241, 236, .04);
  border-bottom: 1px solid var(--rule-dark);
}
.units-head.micro { font-size: 13px; letter-spacing: var(--track-18); }
.units-row { padding: clamp(18px, 1.8vw, 26px) clamp(16px, 2vw, 32px); transition: background .35s; }
.units-row + .units-row { border-top: 1px solid var(--rule-dark); }
.units-row:hover { background: rgba(243, 241, 236, .05); }
.units-row .cell {
  font-family: var(--display);
  font-size: clamp(21px, 1.6vw, 27px);
  line-height: 1.2;
  letter-spacing: var(--track-04);
  font-variant-numeric: lining-nums tabular-nums;
}
.units-row .cell small { display: block; margin-top: 3px; font-family: var(--body); font-size: 16px; letter-spacing: var(--track-04); color: var(--on-dark-70); }
.units-row .cell-action {
  font-size: 13px;
  letter-spacing: var(--track-16);
  text-transform: uppercase;
  color: var(--on-dark-70);
  text-align: end;
  transition: color .3s;
}
.units-row:hover .cell-action { color: var(--cream); }
.table-note { margin-top: clamp(16px, 1.8vw, 26px); font-size: 17px; line-height: 1.55; color: var(--on-dark-70); }

/* ---------------------------------------------------------- unit cards --- */

.unit-card {
  position: relative;
  background: var(--white);
  padding: clamp(24px, 2.4vw, 38px);
  display: flex; flex-direction: column;
  overflow: hidden;
  transition: transform .5s var(--ease), box-shadow .5s var(--ease);
}
.unit-card::before {
  content: "";
  position: absolute; inset: 0 0 auto;
  height: 3px;
  background: var(--accent-rule);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .6s var(--ease);
}
.unit-card:hover { transform: translateY(-5px); box-shadow: 0 20px 46px rgba(24, 34, 30, .12); }
.unit-card:hover::before { transform: scaleX(1); }
.unit-card h4 { font-family: var(--display); font-weight: 300; font-size: clamp(21px, 1.9vw, 30px); letter-spacing: var(--track-06); text-transform: uppercase; line-height: 1.15; margin-bottom: clamp(14px, 1.6vw, 22px); }
.unit-card dl { margin: 0; display: grid; gap: 12px; }
.unit-card dt { font-size: 11px; letter-spacing: var(--track-20); text-transform: uppercase; color: var(--label); }
.unit-card dd { margin: 4px 0 0; font-family: var(--display); font-size: clamp(18px, 1.4vw, 24px); line-height: 1.25; font-variant-numeric: lining-nums tabular-nums; }
/* margin-top:auto pins the button to the card's bottom edge so a row of cards
   with unequal copy still lines its buttons up. It must not also carry
   padding-top — that pads the button itself and pushes its label off centre. */
.unit-card dl { margin-bottom: clamp(22px, 2.4vw, 32px); }
.unit-card .btn { margin-top: auto; }

/* ------------------------------------------------------------- layouts --- */

.bed-pills { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: clamp(18px, 1.8vw, 26px); }
.bed-pill {
  flex: 0 0 auto;
  min-width: 54px; height: 46px;
  padding-inline: 18px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--white);
  color: var(--green);
  font-size: 12px; letter-spacing: var(--track-16); text-transform: uppercase;
  cursor: pointer;
  transition: background .3s, color .3s, border-color .3s;
}
.bed-pill[aria-selected="true"] { background: var(--green); border-color: var(--green); color: var(--cream); }

.plan-choice { position: relative; text-align: start; background: var(--white); border: 1px solid var(--hairline); padding: 14px; cursor: pointer; transition: border-color .3s; }
.plan-choice[aria-selected="true"] { border-color: var(--burgundy); }
.plan-choice__radio { position: absolute; top: 12px; inset-inline-end: 12px; width: 16px; height: 16px; border-radius: 50%; background: var(--grey); border: 1px solid #DDDFDC; }
.plan-choice[aria-selected="true"] .plan-choice__radio { background: var(--burgundy); border-color: var(--burgundy); }
.plan-choice__thumb { display: block; width: 100%; height: clamp(80px, 8vw, 110px); background: var(--cream); object-fit: contain; padding: 6px; }
.plan-choice__size { display: block; margin-top: 10px; font-family: var(--display); font-size: 18px; }

.plan-panel { background: var(--white); padding: clamp(24px, 2.6vw, 44px); text-align: center; }
/* contain, not cover: a floor plan cropped to fill its frame is a floor
   plan with rooms missing. The tray carries the letterboxing, and it is the
   SAME --cream the drawings' own paper was retinted to — a contained image on
   a near-but-not-equal ground shows its bars as an inner border. */
.plan-panel__img { margin: clamp(20px, 2.2vw, 34px) auto 0; max-width: 620px; height: clamp(240px, 28vw, 430px); background: var(--cream); object-fit: contain; padding: 12px; width: 100%; }
.chips { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: clamp(20px, 2.2vw, 34px); }
.chips span { border: 1px solid var(--hairline); border-radius: 999px; padding: 9px 18px; font-size: 14px; color: var(--muted); }
.plan-cta { background: var(--white); padding: clamp(20px, 2.2vw, 34px); display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; }

/* ------------------------------------------------------- instalments --- */

/* Same correction as the units table above: the schedule is the single most
   scrutinised block on the page and it was set in 12px tracked caps and 15px
   copy at 62% cream. The "when" column now carries --on-dark-85 (8.9:1 on
   #26332F, measured composited) at 17px. */
.instalments { border: 1px solid var(--rule-dark); overflow-x: auto; }
.instalments__inner { min-width: 620px; }
.instalment { display: grid; grid-template-columns: 1.2fr .5fr 2fr; gap: 16px; align-items: center; padding: clamp(18px, 1.8vw, 26px) clamp(16px, 2vw, 32px); transition: background .35s; }
.instalment + .instalment { border-top: 1px solid var(--rule-dark); }
.instalment:hover { background: rgba(243, 241, 236, .05); }
.instalment__name { font-size: 14px; letter-spacing: var(--track-14); text-transform: uppercase; color: var(--on-dark-85); }
.instalment__pct { font-family: var(--display); font-size: clamp(24px, 2.1vw, 34px); line-height: 1; font-variant-numeric: lining-nums tabular-nums; }
.instalment__when { color: var(--on-dark-85); font-size: 17px; line-height: 1.5; }
.instalment--final { background: rgba(243, 241, 236, .07); }
.instalment--final .instalment__when { color: var(--cream); }

/* --------------------------------------------------------- checklist --- */

.checklist { list-style: none; margin: clamp(20px, 2.2vw, 32px) 0 0; padding: 0; display: grid; gap: 13px; }
.checklist li { display: flex; gap: 14px; align-items: flex-start; font-size: 16px; line-height: 1.55; }
.checklist li::before { content: ""; flex: 0 0 auto; width: 6px; height: 6px; margin-top: .6em; border-radius: 50%; background: var(--burgundy); }
.checklist--dark li::before { background: var(--cream); }

/* -------------------------------------------------------- service cards --- */

/* The four things the agency actually does. As a bulleted list inside one
   bordered box on a dark ground they ran together into a paragraph of grey;
   four cards give each one its own edge, its own numeral and its own measure. */
.svc-cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap-card);
  margin-top: clamp(24px, 2.6vw, 40px);
}
.svc-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.2vw, 18px);
  padding: clamp(22px, 2.2vw, 34px) clamp(20px, 2vw, 30px);
  background: var(--fill-dark);
  border: 1px solid rgba(243, 241, 236, .13);
  overflow: hidden;
  transition: transform .5s var(--ease), background .4s, border-color .4s;
}
.svc-card::before {
  content: "";
  position: absolute; inset: 0 0 auto;
  height: 2px;
  background: var(--accent-rule);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .6s var(--ease);
}
.svc-card:hover { transform: translateY(-5px); background: rgba(243, 241, 236, .09); border-color: rgba(243, 241, 236, .22); }
.svc-card:hover::before { transform: scaleX(1); }
.svc-card__no {
  font-family: var(--display);
  font-size: clamp(26px, 2.2vw, 36px);
  line-height: 1;
  letter-spacing: var(--track-04);
  color: var(--cream);
  opacity: .38;
  font-variant-numeric: lining-nums tabular-nums;
  transition: opacity .5s var(--ease);
}
.svc-card:hover .svc-card__no { opacity: .8; }
.svc-card p { font-size: 16px; line-height: 1.55; color: var(--on-dark-70); }

/* The light-ground version is not used yet; it exists so the component can be
   reused without being redefined at the point of use. */
.section--light .svc-card { background: var(--white); border-color: transparent; }
.section--light .svc-card__no { color: var(--burgundy); }
.section--light .svc-card p { color: var(--muted); }

@media (max-width: 1100px) { .svc-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px)  { .svc-cards { grid-template-columns: minmax(0, 1fr); } }

/* --------------------------------------------------------- photo CTA --- */

/* A centred heading, a line of copy and two buttons on flat green is a slide,
   not a section. Same copy and the same two buttons, on a render held back far
   enough that both still measure past 4.5:1. */
.cta-photo { position: relative; overflow: hidden; }
.cta-photo__bg { position: absolute; inset: 0; z-index: 0; }
.cta-photo__bg img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 58%; }
/* Held back far enough that cream copy still measures 5.6:1 against the
   BRIGHTEST patch of the render behind it (a sunlit window at sRGB 220
   composites to 95), not against its average — but no further, or the band is
   just a slightly darker green and the render might as well not be there. */
.cta-photo__bg::after {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(24, 33, 30, .76) 0%, rgba(24, 33, 30, .66) 45%, rgba(24, 33, 30, .84) 100%);
}
.cta-photo > .wrap--text { position: relative; z-index: 1; }
.cta-photo .accent-line { z-index: 2; }
.cta-photo { padding-block: clamp(80px, 9vw, 150px); }
/* The lead variant is the one that has to be read across a room: heading one
   step up, body copy at reading size in near-full cream rather than the muted
   grey the flat-green version used. */
.cta-photo__lead { max-width: 900px; }
.cta-photo__lead > p { font-size: clamp(18px, 1.35vw, 21px); line-height: 1.55; color: var(--on-dark-90); }
/* This band is half the height of the community page's, so a `cover` crop of a
   wide render keeps only the dark middle of it and the picture disappears into
   the overlay. Hold the sky, and lighten the overlay to match — the measured
   contrast behind the smallest control here is recorded in site/README.md. */
.cta-photo--lead .cta-photo__bg img { object-position: 50% 34%; }
.cta-photo--lead .cta-photo__bg::after {
  background:
    linear-gradient(180deg, rgba(24, 33, 30, .70) 0%, rgba(24, 33, 30, .62) 45%, rgba(24, 33, 30, .80) 100%);
}

/* ------------------------------------------------------- agency stats --- */

.agency-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--gap-tight); margin-top: clamp(28px, 3vw, 46px); }
.agency-stat {
  position: relative;
  border-top: 1px solid var(--hairline);
  padding-top: clamp(14px, 1.4vw, 22px);
}
/* The figure strip is the one place the accent can appear without competing
   with an image, so the rule above each number is redrawn on hover. */
.agency-stat::before {
  content: "";
  position: absolute; inset: -1px 0 auto;
  height: 1px;
  background: var(--burgundy);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .55s var(--ease);
}
.agency-stat:hover::before { transform: scaleX(1); }
.section--dark .agency-stat::before { background: var(--cream); }
.agency-stat .figure { transition: transform .55s var(--ease); }
.agency-stat:hover .figure { transform: translateY(-3px); }
.agency-stat .micro { margin-top: 8px; color: var(--label); }
.section--dark .agency-stat { border-top-color: var(--rule-dark); }
.section--dark .agency-stat .micro { color: var(--on-dark-50); }

/* ------------------------------------------------------------ lead card --- */

.lead-card { background: var(--white); overflow: hidden; }
.lead-card__body { padding: clamp(28px, 3.4vw, 60px); display: flex; flex-direction: column; justify-content: center; }
.lead-card__media { min-height: clamp(240px, 30vw, 480px); overflow: hidden; }
.lead-card__media img { width: 100%; height: 100%; object-fit: cover; }
.doc-links { display: flex; flex-wrap: wrap; gap: 10px; margin-top: clamp(18px, 2vw, 28px); }
.doc-links a {
  display: inline-flex; align-items: center; gap: 8px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 9px 18px;
  font-size: 14px;
  color: var(--muted);
  transition: border-color .3s, color .3s;
}
.doc-links a:hover { border-color: var(--burgundy); color: var(--green); }
.doc-links a::before { content: "↓"; font-size: 13px; }

/* --------------------------------------------------------------- faq --- */

.faq { background: var(--white); padding: 0 clamp(22px, 2.4vw, 42px); }
.faq__item + .faq__item { border-top: 1px solid var(--hairline); }
.faq__q { width: 100%; background: none; border: 0; padding: clamp(22px, 2.2vw, 30px) 0; display: flex; justify-content: space-between; align-items: center; gap: 24px; cursor: pointer; text-align: start; color: var(--green); }
.faq__q span:first-child { font-family: var(--display); font-weight: 400; font-size: clamp(19px, 1.6vw, 26px); line-height: 1.25; letter-spacing: var(--track-04); }
.faq__icon { flex-shrink: 0; font-family: var(--display); font-weight: 300; font-size: 28px; line-height: 1; transition: transform .45s var(--ease); }
.faq__q[aria-expanded="true"] .faq__icon { transform: rotate(45deg); }
.faq__a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .5s var(--ease); }
.faq__q[aria-expanded="true"] + .faq__a { grid-template-rows: 1fr; }
.faq__a > div { overflow: hidden; }
.faq__a p { padding-bottom: clamp(22px, 2.2vw, 30px); max-width: 78ch; font-size: 16px; line-height: 1.65; color: var(--muted); }

/* -------------------------------------------------------------- footer --- */

/* The reference footer is one dark bar: legal left, the mark and the phone in
   the centre, contact right. */
.site-footer { background: var(--footer-green); color: var(--cream); }
.footer-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(20px, 3vw, 48px);
  padding: clamp(26px, 3vw, 34px) var(--pad);
}
.footer-bar__side { flex: 1 1 0; display: flex; flex-wrap: wrap; gap: 8px 22px; align-items: center; }
.footer-bar__side--end { justify-content: flex-end; }
.footer-bar__mid { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 12px; }
.footer-bar__mid img { width: clamp(140px, 15vw, 200px); }
.footer-bar a, .footer-bar address {
  font-size: 12px;
  letter-spacing: var(--track-18);
  text-transform: uppercase;
  font-style: normal;
  color: var(--on-dark-62);
  transition: color .3s;
}
.footer-bar a:hover { color: var(--cream); }
.footer-bar__phone { font-family: var(--display); font-size: clamp(19px, 1.6vw, 26px) !important; letter-spacing: var(--track-16) !important; color: var(--cream) !important; text-transform: none !important; }

.footer-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 56px);
  padding: clamp(50px, 6vw, 88px) var(--pad) clamp(30px, 3.4vw, 48px);
  border-bottom: 1px solid rgba(243, 241, 236, .1);
}
.footer-col { display: flex; flex-direction: column; gap: 11px; }
.footer-col > span:first-child { font-size: 11px; letter-spacing: var(--track-22); text-transform: uppercase; color: var(--on-dark-38); margin-bottom: 4px; }
.footer-col a, .footer-col address { font-size: 15px; font-style: normal; color: var(--on-dark-70); transition: color .3s, transform .35s var(--ease); }
.footer-col a { display: inline-block; }
.footer-col a:hover { color: var(--cream); transform: translateX(5px); }
.footer-col p { font-size: 15px; color: var(--on-dark-50); }
.footer-legal { padding: 0 var(--pad) clamp(24px, 3vw, 36px); }
.footer-legal p { font-size: 13px; line-height: 1.6; color: var(--on-dark-38); max-width: 92ch; }

/* ------------------------------------------------------------ reveals --- */

@keyframes mvFadeUp { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: none; } }
@keyframes mvDrift  { from { transform: scale(1); } to { transform: scale(1.08); } }
@keyframes mvNudge {
  0%, 100% { transform: rotate(45deg) translate(-1px, -1px); }
  50%      { transform: rotate(45deg) translate(2px, 2px); }
}

/* Content is visible by default; JS only adds .is-hidden once it is running. */
.reveal { transition: opacity 1s var(--ease), transform 1s var(--ease); }
.reveal.is-hidden { opacity: 0; transform: translateY(26px); }

/* A full-bleed block is far too large for a 26px nudge to read as movement, so
   it settles out of a slight overscale instead — the block arrives, rather
   than the block sliding. Its own 1.6s image transition carries it. */
.imgfull.reveal.is-hidden > img,
.imgfull.reveal.is-hidden > picture img,
.project-card.reveal.is-hidden img { transform: scale(1.07); }

/* The gallery rows and the developer panel enter from the side they sit on, so
   the two halves of a 50/50 row do not arrive as one slab. */
.reveal--left.is-hidden  { transform: translateX(-32px); }
.reveal--right.is-hidden { transform: translateX(32px); }
@media (max-width: 1000px) {
  .reveal--left.is-hidden,
  .reveal--right.is-hidden { transform: translateY(26px); }
}

/* -------------------------------------------------------- responsive --- */

@media (max-width: 1200px) { .header__nav { display: none; } }

@media (max-width: 1100px) {
  .amenity-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .slider--cards .slider__slide { flex: 0 0 calc((100% - var(--gap-card)) / 2); }
}

@media (max-width: 1000px) {
  :root { --header-h: 70px; }
  .desktop-only { display: none !important; }
  .split, .split--wide, .split--lead, .split--layouts, .split--lead-card { grid-template-columns: 1fr; }
  .grid-3, .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .duo, .dev { grid-template-columns: minmax(0, 1fr); }
  .duo__text { padding-inline: var(--pad); }
  .dev__media { min-height: clamp(300px, 60vw, 460px); }
  .footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-bar { flex-direction: column; text-align: center; }
  .footer-bar__side, .footer-bar__side--end { justify-content: center; }
  .imgfull { height: clamp(400px, 78vw, 560px); }
  .imgfull__body { max-width: none; }
  .hero__grid { flex-direction: column; align-items: flex-start; gap: clamp(22px, 4vw, 40px); }
  .hero__aside { width: 100%; }
}

@media (max-width: 760px) {
  .slider--cards .slider__slide { flex: 0 0 100%; }
}

@media (min-width: 1001px) { .mobile-only { display: none !important; } }

@media (max-width: 640px) {
  .site-header { padding-inline: 16px; gap: 10px; }
  .header__right { gap: 10px; }
  .brand { flex-direction: column; align-items: flex-start; gap: 5px; }
  .brand__rule { display: none; }
  .brand__sub { display: none; }
  .brand__mark { font-size: 14px; letter-spacing: var(--track-14); }
  .brand__logo--mv { --brand-logo-h: 15px; }
  .brand__logo--mh { --brand-logo-h: 13px; }
  .brand__logo--tr { --brand-logo-h: 14px; }
  .menu-btn__lines i { width: 28px; }
  .menu-btn[aria-expanded="true"] .menu-btn__lines i:last-child { width: 28px; }
  /* The header CTA stays at every width (playbook §9). It is shortened to
     "Request Details" and tightened by lead-capture.css, which loads after this
     file — the stacked brand lockup above is what makes room for it. */

  .grid-3, .grid-4 { grid-template-columns: 1fr; }
  .amenity-grid { grid-template-columns: 1fr; }
  .footer-cols { grid-template-columns: 1fr; }
  .instalment { grid-template-columns: 1fr .5fr 1.4fr; }
  .agency-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .slider__dots { max-width: 50%; }
  .slider__dots button { width: 14px; }
}

/* The setting asks us to remove *travel*, not to remove the interface. Killing
   every transition also kills hover feedback and cross-fades, which reads as a
   broken page rather than a calm one. So: no movement, no loops, no parallax —
   but opacity and colour still transition, and the reveals still fade in. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-property: opacity, background-color, color, border-color, box-shadow, fill, stroke, filter !important;
    transition-duration: .35s !important;
  }
  .reveal.is-hidden,
  .reveal--left.is-hidden,
  .reveal--right.is-hidden { transform: none; }
  .split-word > span { transform: none; }
  [data-parallax] { transform: none !important; }
  .hero__slide.is-active img { animation: none; }
  .imgfull.reveal.is-hidden > img,
  .imgfull.reveal.is-hidden > picture img,
  .project-card.reveal.is-hidden img,
  .imgfull:hover > img,
  .imgfull:hover > picture img,
  .project-card:hover img,
  .amenity-card:hover .amenity__frame,
  .amenity-card:hover .amenity__frame img,
  .slider--gallery:hover .slider__slide img { transform: none; }
  /* No lift on anything — the whole hover language above is movement, and the
     colour half of each rule still fires through the allowed properties. */
  .btn:hover,
  .card:hover,
  .unit-card:hover,
  .reason:hover,
  .travel:hover,
  .doc-links a:hover,
  .doc-links button:hover,
  .footer-col a:hover,
  .agency-stat:hover .figure,
  .svc-card:hover,
  .slider__arrow--prev:hover,
  .slider__arrow--next:hover { transform: none; box-shadow: none; }
  /* The struck rules stay — they are a state change, not travel, and without a
     transition they simply appear on hover. */
}

@media print {
  .site-header, .nav-panel, .lightbox, .hero__scroll, .slider__arrows, .slider__zoom { display: none !important; }
  body { background: #fff; color: #000; }
}


/* ==========================================================================
   THE MULTILINGUAL LAYER
   ==========================================================================

   Everything above this line is direction-neutral: the stylesheet was
   converted to LOGICAL PROPERTIES (padding-inline-start, inset-inline-end,
   text-align: start) so that mirroring is the browser's job. That is
   deliberate, and it is why there is no [dir="rtl"] sheet restating every
   rule backwards — a parallel sheet drifts the first time somebody edits one
   and not the other (SOP §6.2).

   What follows is only what genuinely remains after that conversion: the
   per-writing-system typography, the handful of glyphs and widgets that
   encode a direction of their own, and the language switcher.
   ========================================================================== */

/* ---------------------------------------------- fonts per writing system --
   §6.1. Cormorant Garamond and EB Garamond carry Latin AND Cyrillic, so the
   Russian pages need no substitute — this is the Garamond case the SOP notes.

   NEITHER HAS ARABIC. Left alone, an Arabic page falls back to a system font,
   silently, and looks like a different website. Amiri is a naskh revival with
   the same restrained, high-contrast, bookish intent as Cormorant; Noto Naskh
   Arabic holds up at running-text sizes the way EB Garamond does. Both are
   loaded only on the Arabic pages (see src/templates/head.mjs), and every face
   is declared with unicode-range by the Google stylesheet itself, so the
   English pages download exactly what they downloaded before. */
[data-script="arabic"] {
  --display: 'Amiri', 'Noto Naskh Arabic', 'Geeza Pro', 'Times New Roman', serif;
  --body:    'Noto Naskh Arabic', 'Amiri', 'Geeza Pro', 'Segoe UI', Tahoma, sans-serif;

  /* ---------------------------------------------------------------- §6.3
     ARABIC MUST NOT BE LETTER-SPACED. It is cursive: tracking forces gaps
     into the letter joins and the word stops being a word. The labels earn
     their hierarchy from size, weight and colour instead — which is what
     Arabic typography uses in place of the capitalisation it does not have.

     Zeroed at the token, so this reaches the values set inside media queries
     too. That is the whole reason the tokens exist. */
  --track-04: 0;
  --track-06: 0;
  --track-08: 0;
  --track-10: 0;
  --track-12: 0;
  --track-14: 0;
  --track-16: 0;
  --track-18: 0;
  --track-20: 0;
  --track-22: 0;
  --track-24: 0;
  --track-30: 0;
  --track-indent: 0;
}

/* §6.4 — scripts with deep descenders and marks below the baseline need more
   leading than a Latin face at the same size, and naskh headings set in a
   display face sit low in their line box. */
[data-script="arabic"] body { line-height: 1.85; }
[data-script="arabic"] h1,
[data-script="arabic"] h2,
[data-script="arabic"] h3,
[data-script="arabic"] h4,
[data-script="arabic"] .h-hero,
[data-script="arabic"] .h-section,
[data-script="arabic"] .h-card { line-height: 1.45; }
[data-script="arabic"] .travel span:last-child,
[data-script="arabic"] .figure { line-height: 1.35; }

/* text-transform: uppercase is a no-op on Arabic itself, but it would shout
   the Latin brand names embedded in Arabic labels. Left in place deliberately:
   the market publishes those names in Latin capitals anyway, and suppressing
   it here would make one label in an otherwise consistent row look unstyled. */

/* --------------------------------------------------------------- bidi (§6.2)
   Latin runs inside RTL prose — brand names, currency codes, prices — are
   wrapped in <bdi> by the templates. This is the type-level half: the runs
   keep the Latin face rather than inheriting a naskh fallback that has no
   Latin of its own. */
[data-script="arabic"] bdi,
[data-script="arabic"] code {
  font-family: 'EB Garamond', Georgia, 'Times New Roman', serif;
  letter-spacing: normal;
}

/* --------------------------------------------------- Chinese (zh-Hans) ----
   NO HAN WEBFONT — see the note in src/templates/head.mjs. Cormorant and EB
   Garamond stay at the FRONT of both stacks precisely because they cover no
   Han at all: every Latin run on the Chinese page — the project name, the
   brands, "AED 20 million" — is drawn in the design's own type, and only the
   Han falls through to the reader's system face. That fall-through is the
   whole mechanism; the families after the Garamonds are not last resorts,
   they are what the Chinese is actually set in.

   SONG FOR DISPLAY, HEI FOR TEXT. The design is serif over serif, but a Song
   at 17px is a hairline face on a Windows screen (SimSun), which is why every
   Chinese reading interface sets running text in Hei. In the headings the
   size carries Song's modulated strokes, and they are the Han counterpart to
   Cormorant. Each list runs Apple, then Windows, then Android/Linux. */
[data-script="cjk"] {
  --display: 'Cormorant Garamond', 'Songti SC', 'STSong', 'Noto Serif SC',
             'Noto Serif CJK SC', 'Source Han Serif SC', SimSun, Georgia, serif;
  --body:    'EB Garamond', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
             'Noto Sans SC', 'Noto Sans CJK SC', 'Source Han Sans SC', sans-serif;
}

/* §6.4 — a Han glyph fills its em box, with no ascenders or descenders to
   open the line, so a paragraph at the Latin 1.55 reads as a grey block. 1.8
   is the leading Chinese body text is normally set at.

   `line-break: strict` stops a line OPENING on punctuation that may not open
   one (，。、」and the rest). Browsers default to `auto`, which is allowed to
   relax exactly those rules at narrow widths — on a phone, where most of this
   traffic reads. Inherited, so one declaration covers the page. */
[data-script="cjk"] body { line-height: 1.8; line-break: strict; }
[data-script="cjk"] :is(.copy, .lead, .muted-dark, .hero__kicker, .intro__copy,
  .amenity__text, .reason__p, .svc-card p, .cta-photo__lead > p, .faq__a p,
  .footer-legal p, .footnote, .masterplan__cap, .checklist li,
  .lead-check, .lead-legal, .lead-confirm__lead, .lead-error) { line-height: 1.8; }

/* Headings: Cormorant's tight 1.08 leading was drawn for capitals with a
   small x-height. Han set that tight collides line to line. */
[data-script="cjk"] :is(h2, h3, h4, .h-section, .h-card, .reason__t,
  .amenity__title, .faq__q span:first-child) { line-height: 1.4; }

/* AND ONE STEP SMALLER. The heading sizes were chosen for Cormorant capitals,
   which occupy about two thirds of the em; a Han glyph occupies nearly all of
   it, so at the same font-size a Chinese heading reads a third larger than
   the English one beside it in the design. The hero h1 is untouched — it is
   the project name, in Latin, in every language. */
[data-script="cjk"] .h-section     { font-size: clamp(22px, 2.1vw, 32px); }
[data-script="cjk"] .h-section--lg { font-size: clamp(25px, 2.7vw, 44px); }
[data-script="cjk"] .h-card,
[data-script="cjk"] .reason__t     { font-size: clamp(18px, 1.4vw, 22px); }
[data-script="cjk"] .faq__q span:first-child { font-size: clamp(17px, 1.35vw, 22px); }

/* The other way at the bottom of the scale: 11px Han is below the size at
   which the counters of a dense character stay open. The labels go up a
   pixel. Tracking is KEPT — unlike Arabic, Han is not cursive, and loose-set
   small labels (疏排) are ordinary Chinese editorial practice. */
[data-script="cjk"] .label,
[data-script="cjk"] .eyebrow { font-size: 13px; line-height: 1.3; }
[data-script="cjk"] .micro   { font-size: 12px; }

/* -------------------------------------------------------------- §6.2 RTL --
   PHONE INPUTS STAY LEFT-TO-RIGHT. A phone number typed right-to-left is
   unreadable, and every bank and airline in the region does this. The input
   also carries dir="ltr" from lead-capture.js — belt and braces, because the
   attribute is what the caret follows and this rule is what the text follows
   if the attribute is ever dropped. */
[dir="rtl"] input[type="tel"],
[dir="rtl"] .iti__tel-input,
[dir="rtl"] .lead-input[type="tel"] {
  direction: ltr;
  text-align: start;
}

/* The intl-tel-input widget builds a left-to-right UI of its own: the flag
   dropdown is absolutely positioned against the input's left edge and the
   input is padded to clear it. Under RTL the flag must move to the right edge
   and the padding with it, or the two overlap (§6.2, third-party widgets). */
[dir="rtl"] .iti { direction: ltr; }

/* ------------------------------------------------------------------ §14.2 --
   A LONG WORD BREAKING A FIXED GRID OUT OF THE VIEWPORT.

   The cell shrinks correctly; shrinking a box does nothing for a word wider
   than the box. German compounds and long Russian inflected forms overflow
   the cell, and the cell overflows the page — the symptom is horizontal
   overflow on some languages only, with the header CTA off the right edge.

   `hyphens` uses the page's lang attribute, which is already set correctly on
   every page. Applied to the fixed-column layouts that carry sentences: the
   payment schedule, the travel-time rows, the spec tables and the stat
   strips. */
.travel span,
.units-row .cell,
.unit-card dt,
.unit-card dd,
.instalment__name,
.agency-stat .micro,
.amenity__title,
.footer-col a,
.footer-col address,
.lead-split__list li,
.header__nav a,
.nav-panel a,
/* HEADINGS TOO, and this is not belt-and-braces — it is the case that
   actually shipped. The privacy page's h1 is capped at 16ch, and in Russian
   the single word «Политика конфиденциальности» measures 331px inside a
   183px box: 46px of horizontal scroll on a 320px phone, from one word, on a
   page with no images and no tables. English never trips it, which is exactly
   why it went unnoticed.

   Inert where the word already fits: `hyphens: auto` only breaks a word that
   cannot fit, so the approved English headings render unchanged. It uses the
   page's `lang`, which is set correctly on every page — that is what makes it
   hyphenate «конфиденци-альности» rather than chop it anywhere. */
.h-hero,
.h-section,
.h-card,
.reason__t,
.unit-card h4,
.faq__q span:first-child,
.legal h1,
.legal h2 {
  overflow-wrap: break-word;
  hyphens: auto;
}

/* The travel row's value column is nowrap by design — a time must not break
   across lines. The NAME column is the one that has to yield. */
.travel span:first-child { min-width: 0; }

/* ------------------------------------------------------------------ §14.6 --
   FLEX ITEMS THAT CANNOT SHRINK. `flex: 1` does not let an item shrink past
   its min-content width; the automatic minimum IS min-content. Two buttons of
   translated text in a fixed-width bar is exactly the shape that eventually
   overflows, so min-width: 0 is set pre-emptively rather than after the bug
   report. */
.footer-bar__side,
.header__right,
.project-card__foot > div,
.duo__text,
.split > * { min-width: 0; }

/* ==========================================================================
   THE LANGUAGE SWITCHER — §5.3
   ==========================================================================

   Built on <details> so it works with JavaScript switched off. On a fixed
   header, a script-dependent menu is the one control that breaks before
   hydration — and this one is how a visitor reaches their own language.
   ========================================================================== */
.lang { position: relative; flex-shrink: 0; }

/* §14.1 — A CONTROL THAT INHERITS A COLOUR PAST ITS CONTAINER.
   The nav beside this declares its colour explicitly, which is why the nav
   looks fine over the hero and a `color: inherit` control would not. This
   takes the same contract, including the transition to the scrolled state, so
   the two can never diverge. */
.lang__btn {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 2px;
  cursor: pointer;
  list-style: none;
  color: currentColor;
  font-size: 12px;
  letter-spacing: var(--track-18);
  text-transform: uppercase;
  transition: opacity .3s;
}
.lang__btn::-webkit-details-marker { display: none; }
.lang__btn:hover { opacity: .7; }
.lang__cur { line-height: 1; }

/* A stroked SVG path taking currentColor, not a "▾" character: at this size
   the glyph renders as an indistinct dot on some platforms, and most of its
   box is padding, so it cannot simply be sized up (§14.1). */
.lang__chev { flex-shrink: 0; transition: transform .3s var(--ease); }
.lang[open] .lang__chev { transform: rotate(180deg); }

.lang__list {
  position: absolute;
  inset-block-start: calc(100% + 8px);
  inset-inline-end: 0;
  z-index: 120;
  min-width: 168px;
  margin: 0;
  padding: 6px 0;
  list-style: none;
  background: var(--cream);
  color: var(--green);
  border: 1px solid var(--hairline);
  box-shadow: 0 18px 40px rgba(18, 22, 15, .16);

  /* §5.3 — THE MENU NEEDS A STACK THAT CAN DRAW ALL THE ENDONYMS.
     The page's own stack is the ONE script guaranteed not to cover the
     others: an English page loads no Arabic face, so «العربية» would render as
     tofu in the very control a visitor uses to escape a language they cannot
     read. The system faces below are the ones that ship with each platform. */
  font-family: 'EB Garamond', 'Noto Naskh Arabic', 'Geeza Pro', 'Segoe UI',
               'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
               'Noto Sans CJK SC', Tahoma, Georgia, serif;
  letter-spacing: normal;
  text-transform: none;
  font-size: 15px;
}
.lang__list a {
  display: block;
  padding: 8px 16px;
  color: var(--green);
  white-space: nowrap;
  transition: background .2s, color .2s;
}
.lang__list a:hover { background: var(--grey-alt); }

/* The current language STAYS IN THE LIST. A switcher that hides where you are
   makes the reader hunt for confirmation that they are in the right place. */
.lang__list a[aria-current="true"] {
  color: var(--burgundy);
  background: var(--grey-alt);
}

/* On a phone the switcher must not sit against the header CTA (§12.3). It
   keeps its place in the bar — it is not dropped, because a visitor on the
   wrong language has no other way out — but it gives up its padding first. */
@media (max-width: 900px) {
  .lang__btn { padding-inline: 0; }
  .lang__list { min-width: 150px; }
}
@media (max-width: 420px) {
  .lang__cur { font-size: 11px; }
  .lang__list {
    /* Anchored to the viewport edge rather than the button, so a menu opened
       from a control near the edge cannot open off-screen — which in RTL is
       the opposite edge, and is why this is an inline offset and not a right. */
    inset-inline-end: calc(var(--pad) * -1 + 8px);
  }
}

/* ==========================================================================
   ABU DHABI MIRA HILLS — additions to the shared system.

   Everything above this line is the design system as it ships on Mira Verde
   and Mira Ocean Estates and is UNCHANGED, so a fix made there can still be
   dropped in over the top. Everything below is what this page needs and
   those two do not: a video block (neither sibling carries video), a caption
   under the master plan, a plain variant of the reason card for the two
   developer boilerplates, and two grid corrections.
   ========================================================================== */

/* ------------------------------------------------------------- figures ----
   `.agency-stats` is a hard four-column grid. The plots block has THREE
   figures, and in a four-column grid three cards sit in a row with a hole
   where the fourth would be — which reads as a card that failed to load
   rather than as a deliberate count. statStrip() adds the modifier from the
   array's own length, so this cannot drift from the copy. */
.agency-stats--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.agency-stats--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* On a light or band ground the stat's micro line takes --label, which is
   already its colour; the figure needs the ink explicitly, because
   `.agency-stat` was written for the dark sections and inherits cream there. */
.section--light .agency-stat .figure,
.section--band  .agency-stat .figure { color: var(--green); }

/* -------------------------------------------------------------- footnote --
   The disclaimer under the return target. Deliberately NOT muted into
   invisibility: it is the qualifier on a regulated claim, and 12px at 40%
   opacity is a disclaimer written to be missed. Same size as the micro
   labels, full contrast, with the rule above it doing the separating. */
.footnote {
  margin-top: clamp(26px, 3vw, 44px);
  padding-top: clamp(14px, 1.6vw, 22px);
  border-top: 1px solid var(--hairline);
  max-width: 78ch;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
}
.section--dark .footnote { border-top-color: var(--rule-dark); color: var(--on-dark-70); }

/* ------------------------------------------------- a still in a duo row ----
   Until 4 September 2026 every `.duo__media` held a slider, and
   `.slider--gallery` sizes its own slides — so nothing in this file ever had
   to size a bare <picture> in a duo, and nothing did.

   When the client replaced those sliders with single stills the images came
   out 665×1013: `picture()` writes the intrinsic `width` and `height` onto
   the <img> (correctly — it is what reserves the box and prevents layout
   shift), the global `img { max-width: 100% }` clamped the width to the
   column, and with no rule for the height the attribute stood. The result
   was a 16:9 render stretched into a portrait.

   The height below is the one `.slider--gallery` used, so a row with a still
   in it stands exactly as tall as the carousel it replaced and the page's
   proportions did not change with the content. */
.duo__media > picture img {
  width: 100%;
  height: clamp(320px, 46vw, 648px);
  object-fit: cover;
}

/* ------------------------------------------------------ developer cards ----
   `.reason` with no number. The card is a flex column with a gap, so
   dropping `.reason__no` needs no rule of its own — but the two boilerplate
   paragraphs are long, and at three-across measure they set to a column of
   soup. Two across, wider measure. */
.dev-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap-card);
  margin-top: clamp(24px, 2.8vw, 40px);
}
.dev-pair .reason__p { max-width: 56ch; }
@media (max-width: 900px) {
  .dev-pair { grid-template-columns: minmax(0, 1fr); }
}
/* On the band ground the white card needs a hairline, or it floats. */
.section--band .reason { border: 1px solid var(--hairline-alt); }

/* --------------------------------------------------- master plan caption --
   The drawing is annotated with district names and destinations. The caption
   states the one thing it cannot: that those are indicative. */
.masterplan__cap {
  margin-top: clamp(12px, 1.4vw, 18px);
  font-size: 14px;
  line-height: 1.5;
  color: var(--on-dark-62);
  max-width: 80ch;
}
.section--light .masterplan__cap,
.section--band  .masterplan__cap { color: var(--muted); }

/* ==========================================================================
   THE VIDEO BLOCK

   Neither sibling site carries video, so this is new rather than a variant of
   something. It is built to the same rules as the rest of the system: flat
   corners, the burgundy accent rule struck along the bottom edge, one hover
   language shared with every other control.

   The mechanics are in assets/js/main.js -> initVideo(). The short version:
   the <video> carries preload="none" and no <source>, so nothing is fetched
   until the play control is pressed; the poster is a real <img> on top of it
   rather than the native `poster` attribute alone, because a native poster
   cannot be given a `loading="lazy"` or an alt, and this page has two of them
   below the fold. Both are painted — the native attribute is what shows after
   the overlay is removed and before the first frame decodes, which is the
   flash this arrangement exists to prevent.
   ========================================================================== */

.videoblock { margin: clamp(30px, 3.4vw, 54px) 0 0; }

.videoblock__frame {
  position: relative;
  /* The films are 16:9. Reserving the box from the ratio rather than from the
     poster's load means no layout shift when it arrives — and the poster is
     lazy, so it arrives late by design. */
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--shell);
}
.videoblock__el,
.videoblock__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.videoblock__poster { z-index: 1; }
/* Once play has been pressed the poster is out of the way for good. It is
   removed from the accessibility tree as well as from view: an <img> with a
   description of a still, sitting invisibly on top of a playing film, is read
   out by a screen reader as though it were the content. */
.videoblock[data-playing="true"] .videoblock__poster,
.videoblock[data-playing="true"] .videoblock__play { display: none; }

/* The play control. Covers the whole frame, so the target is the picture —
   which is what everyone aims at — rather than a 64px disc. */
.videoblock__play {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  width: 100%;
  padding: 0;
  border: 0;
  background: linear-gradient(180deg, rgba(18, 22, 15, .12) 0%, rgba(18, 22, 15, .42) 100%);
  color: var(--cream);
  cursor: pointer;
  transition: background .4s var(--ease);
}
.videoblock__play:hover { background: linear-gradient(180deg, rgba(18, 22, 15, .2) 0%, rgba(18, 22, 15, .55) 100%); }

.videoblock__glyph {
  position: relative;
  width: clamp(64px, 6vw, 92px);
  aspect-ratio: 1;
  border: 1px solid rgba(243, 241, 236, .55);
  border-radius: 999px;
  background: rgba(18, 22, 15, .3);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background .4s var(--ease), border-color .4s var(--ease), transform .5s var(--ease);
}
/* The triangle, as a border trick rather than an SVG or a play glyph: at this
   size the character renders as a different shape on every platform, and half
   its box is padding so it cannot be optically centred. Nudged left of true
   centre by a sixth of its width, which is where a triangle's visual centre
   actually sits. */
.videoblock__glyph::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-38%, -50%);
  border-style: solid;
  border-width: 11px 0 11px 18px;
  border-color: transparent transparent transparent var(--cream);
}
.videoblock__play:hover .videoblock__glyph {
  background: var(--burgundy);
  border-color: var(--burgundy);
  transform: scale(1.06);
}
.videoblock__play:focus-visible { outline: 2px solid var(--cream); outline-offset: -6px; }

.videoblock__dur {
  font-size: 12px;
  letter-spacing: var(--track-20);
  text-indent: var(--track-indent);
  text-transform: uppercase;
  font-variant-numeric: lining-nums tabular-nums;
}
/* Shown while a 53 MB file is being fetched. Silence on a control that has
   visibly done nothing reads as a broken button. */
.videoblock[data-loading="true"] .videoblock__glyph {
  animation: videoblock-pulse 1.4s var(--ease) infinite;
}
@keyframes videoblock-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .45; }
}
@media (prefers-reduced-motion: reduce) {
  .videoblock[data-loading="true"] .videoblock__glyph { animation: none; opacity: .6; }
}

.videoblock__cap {
  margin-top: clamp(10px, 1.2vw, 16px);
  font-size: 13px;
  letter-spacing: var(--track-04);
  color: var(--label);
}
.section--dark .videoblock__cap { color: var(--on-dark-50); }

.videoblock__noscript { margin-top: 14px; }

/* The byline that stood under the interview — the speaker's name and role —
   was removed on 4 September 2026: the block's intro paragraph names both in
   running prose instead. Its rules are gone with it rather than left here to
   match nothing; git has them if it ever comes back. */

/* ----------------------------------------------------- overview heading --
   `.intro .h-section` is capped at 16em in the shared system, which suits the
   sibling sites' four-word community headings. This page's is fifteen words
   ("Abu Dhabi Mira Hills: Development Opportunities Within an AED 55 Billion
   Master Plan") and at 16em `text-wrap: balance` breaks it after "MIRA",
   splitting the project's own name across two lines. Wider measure, and the
   name stays whole. */
.intro .h-section { max-width: 22em; }

/* The developer block's body is a centred column with a 52ch measure, sized
   for the sibling sites' single paragraph. Two boilerplate cards side by side
   inside it were squeezed to half that and set centred, which on a 90-word
   paragraph reads as a ragged column. The cards take the full body width and
   set left; the label and heading above them stay centred as designed. */
.dev-pair { width: 100%; text-align: start; }
.dev-pair .reason__p { max-width: none; }
.dev__slide:has(.dev-pair) { align-items: stretch; }
.dev__slide:has(.dev-pair) > .label,
.dev__slide:has(.dev-pair) > .h-section { align-self: center; }

/* ==========================================================================
   REVIEW CHANGES — 4 September 2026
   ========================================================================== */

/* -------------------------------------------------------- the gallery ----
   Twelve renders where the deck's two overview images used to be. Full
   width inside the same edge inset every other full-bleed block uses, so the
   page keeps one left and right margin all the way down.

   `.slider--gallery` already sizes its slides (clamp(320px, 46vw, 648px),
   cover) and already carries the counter, the dot rail and the accent rule;
   nothing about it needed changing to stand on its own rather than inside a
   half-column. */
/* The picture is the target. Cursor says so on a pointer device; on touch
   there is no cursor and no hover, which is why the zoom disc below stops
   hiding itself there. The `.gallery` box and its height live in the
   VERTICAL RHYTHM section at the end of this file, with the rest of the
   spacing model. */
.slider--gallery .slider__slide img { cursor: zoom-in; }

/* On a touch screen `.slider:hover` never resolves, so the enlarge control
   was permanently invisible — the one affordance telling a phone user the
   pictures open. Shown outright wherever hover is not available. */
@media (hover: none) {
  .slider__zoom { opacity: 1; }
}

/* ------------------------------------------------- the master plan still --
   The project block's picture is the rendered master plan, and a plan is
   read, not admired: `cover` in a half-column crops the districts off both
   ends. It is contained on the section's own ground instead, which means the
   media column no longer fills its height — so it is centred in it. */
.duo__media--plan {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--grey-alt);
}
/* The exception to the rule above. Cropping a plan to a column crops its
   districts off both ends, so this one keeps its own proportions and is
   simply as tall as it is — which leaves the project row shorter than the two
   below it, and that is the right way round: a diagram is not a hero shot. */
.duo__media--plan > picture img { height: auto; object-fit: contain; }

/* ==========================================================================
   VERTICAL RHYTHM — one model, stated once.

   The first build had the full-bleed rows on `.section--flush`, which is
   `padding: 0`. A row's picture then ran straight into the next section's
   ground colour, and the only separation was whatever padding the NEXT
   section happened to own — so a light row against a band row showed the
   colour change hard against the photograph, and three of them in sequence
   read as blocks colliding rather than as a sequence.

   Adding margins between sections is the wrong repair: a margin shows the
   body's ground through the gap, and it compounds with the padding the
   neighbour already has (measured: 329px between the developer block and the
   agency section, from three rules none of which knew about the others).

   THE MODEL: every section owns symmetric vertical padding and no margins.
   Text sections keep the design's `--rhythm`; the full-bleed rows take a
   slightly tighter one, because a row is a picture beside its caption rather
   than a chapter. The colour break between two rows lands midway between
   their two pictures with equal air on each side, which is the whole of what
   "tidy" means here.
   ========================================================================== */

.section--row {
  padding-block: clamp(48px, 5vw, 80px);
  padding-inline: 0;
}

/* The overview already sets a generous bottom padding of its own, and the
   gallery is its continuation rather than a new chapter — two paddings
   stacked put 210px of empty cream between the sentence and the pictures it
   introduces. */
.intro + .section--row { padding-top: 0; }

/* A row against the footer, or against a section that opens with its own
   full rhythm, needs no second helping. */
.section--row:last-child { padding-bottom: 0; }

/* -------------------------------------------------------- the gallery ----
   Twelve renders where the deck's two overview images used to be. Full width
   inside the same edge inset every other full-bleed block uses, so the page
   keeps one left and right margin all the way down. */
.gallery { position: relative; margin-inline: var(--edge); }

/* A gallery standing on its own deserves a taller frame than the half-column
   slider `.slider--gallery` was sized for. */
@media (min-width: 1000px) {
  .gallery .slider__slide img { height: clamp(420px, 42vw, 720px); }
}

/* The duo rows centre their two columns against each other. A short text
   column beside a 648px picture then floats with no relation to the frame;
   a floor under the copy gives the pair a shared vertical measure. */
@media (min-width: 861px) {
  .duo__text { padding-block: clamp(24px, 3vw, 48px); }
}

/* Stacked on a phone, the picture and the copy that belongs to it need air
   between them — and the next row needs to start further away than that, or
   the two readings run together. */
@media (max-width: 860px) {
  .duo { row-gap: clamp(20px, 4vw, 32px); }
  .section--row { padding-block: clamp(36px, 7vw, 56px); }
}

/* ==========================================================================
   REVIEW CHANGES — round two, 4 September 2026
   ========================================================================== */

/* ------------------------------------------------- the feature-row copy ----
   The duo's text column was centred with a single `gap` of up to 50px doing
   every job: the space under the eyebrow, the space under the heading, and
   the space between paragraphs. One value cannot be right for three
   relationships, and at 50px the two paragraphs read as two separate blocks
   of text that happened to land in the same column.

   Left-aligned, with the gap replaced by margins that say what each space is
   for. The eyebrow sits tight to its heading, the heading opens a little
   more, and consecutive paragraphs take the page's ordinary `p + p` measure. */
.duo__text {
  align-items: flex-start;
  text-align: start;
  gap: 0;
}
.duo__text > .label     { margin-bottom: clamp(12px, 1.4vw, 18px); }
.duo__text > .h-section { margin-bottom: clamp(18px, 2vw, 30px); }
.duo__text > p          { max-width: 52ch; }
.duo__text > p + p      { margin-top: 1.1em; }
.duo__text > .cta-pair  { margin-top: clamp(26px, 3vw, 42px); }
/* The pair is centred by default on this page; left-aligned copy wants it
   left too, and the modifier is doing no other work here. */
.duo__text > .cta-pair--center { justify-content: flex-start; }

/* --------------------------------------------------------- figure strip ----
   `--gap-tight` (12–20px) is right for a row of cards with a border between
   them. It is not right for four columns of running text with nothing but
   whitespace between them: at 1400px the micro line of one stat finished
   about fourteen pixels from where the next one started, and the four read as
   one wrapped paragraph. Wider columns, and a measure on the label so it
   wraps before it reaches its neighbour. */
.agency-stats {
  column-gap: clamp(24px, 2.6vw, 48px);
  row-gap: clamp(22px, 2.4vw, 36px);
}
.agency-stat .micro { max-width: 38ch; }

/* Air before a CTA that closes a section, rather than the section's own
   bottom padding doing double duty. */
.cta-pair--spaced { margin-top: clamp(34px, 4vw, 60px); }

/* A panel set to a reading measure instead of the page width. */
.panel--set { max-width: 68ch; }

/* -------------------------------------------------- master plan section ----
   Heading across the top, the two paragraphs as columns beneath it, then the
   drawing with room under it. */
/* Measured in em on the HEADING, not in ch on its container: ch resolves
   against the element it is set on, and on .mp-head that is the 17px body
   size — 22ch came out at 176px and broke the heading to one word a line. */
.mp-head .h-section { max-width: 20em; }
.mp-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 56px);
  margin-top: clamp(26px, 3vw, 44px);
}
/* 52ch left roughly a third of each column empty on a 1400px viewport — the
   measure was set for a single narrow text column and these are two wide
   ones. 68ch fills the column at the widths this page is actually read at
   and still caps the line before it gets unreadable on an ultrawide. */
.mp-cols p { max-width: 68ch; }
/* The two paragraphs are ADJACENT SIBLINGS, so the page's `p + p` rule gave
   the second one a top margin — inside its own grid cell, which pushed the
   right-hand column ~19px below the left and left the two columns starting
   at different heights. In a grid the gap does the separating; the sibling
   margin is measuring a relationship that no longer exists. */
.mp-cols p + p { margin-top: 0; }
@media (max-width: 860px) {
  .mp-head .h-section { max-width: none; }
  .mp-cols { grid-template-columns: minmax(0, 1fr); gap: 1.1em; }
}
/* Room under the drawing. Without the accent rule closing it, the caption was
   the only thing between the plan and the section's edge. */
.masterplan { padding-bottom: clamp(20px, 2.6vw, 40px); }

/* ------------------------------------------------------- the pack aside ----
   The still under the contents list. Bordered rather than bled, because it
   sits inside a panel that already has its own edge. */
.lead-split__fig {
  margin: clamp(20px, 2.4vw, 32px) 0 0;
  border: 1px solid var(--hairline);
  background: var(--grey-alt);
}
.lead-split__fig img { width: 100%; height: auto; display: block; }
.section--dark .lead-split__fig,
.section--band .lead-split__fig { border-color: rgba(38, 51, 47, .16); }

/* --------------------------------------------------------- the video ----
   The spinner shown while a 50 MB file buffers. The poster is gone by then —
   the overlay is dismissed on the first press, not on `canplay` — so without
   this there is nothing on screen between the press and the first frame. */
.videoblock__spinner {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: none;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.videoblock[data-loading="true"] .videoblock__spinner { display: flex; }
.videoblock__spinner span {
  width: 46px; height: 46px;
  border: 2px solid rgba(243, 241, 236, .28);
  border-top-color: var(--cream);
  border-radius: 50%;
  animation: videoblock-spin 900ms linear infinite;
}
@keyframes videoblock-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .videoblock__spinner span { animation-duration: 2.4s; }
}

/* ---------------------------------------------------- master developers ----
   Was `.dev`: a 50/50 grid with the photograph in one half and BOTH developer
   boilerplates in the other, so two ~250-word paragraphs were set in two
   columns of roughly 300px each. The image is now a band across the top and
   the cards have the full measure under it. */
.dev-band {
  position: relative;
  margin-inline: var(--edge);
  height: clamp(180px, 22vw, 300px);
  overflow: hidden;
}
.dev-band picture, .dev-band > img:not(.dev-band__logo) { display: block; }
.dev-band picture img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
}
.dev-band__logo {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  width: clamp(160px, 20vw, 260px);
  height: auto;
}
.dev-head {
  margin-top: clamp(34px, 4vw, 60px);
  text-align: center;
}
.dev-head .h-section { max-width: 24em; margin-inline: auto; }
/* Two cards, full width, so the boilerplate sets to a readable measure
   instead of a column. */
.dev-pair .reason__p { max-width: none; }
