/* ==========================================================================
   Avenor Park — homepage section patterns
   ========================================================================== */

/* ---- hero --------------------------------------------------------------- */
/* The slideshow artwork is a 16:9 composite that already contains the wordmark,
   the tagline and the "Visual Representation" disclaimer, so the hero is just
   the image — no text overlay, or the wording would appear twice. */
/* Measured on the live site: the hero section is 1440x1000 at a 1000px-tall
   viewport, i.e. full viewport height, with the artwork as background-size:cover. */
.hero {
  position: relative;
  /* The fixed navy header no longer floats over the hero — the hero starts below
     it, so the banners' baked-in logos/RERA can never collide with the nav. */
  margin-top: var(--header-h);
  min-height: 100svh;
  background: var(--navy);
  color: var(--white);
  overflow: hidden;
}
/* On desktop the hero is sized to the 16:9 landscape artwork rather than full
   viewport height, so every landscape slide — lifestyle and promo alike — fills
   it at the same size with no crop and no letterbox. On a 16:9 monitor this is
   still full-screen; on taller ratios it leaves a small peek of the next section.
   Phones get the same treatment at 9:16 (see the max-width rule below). */
@media (min-width: 768px) {
  .hero { min-height: 0; aspect-ratio: 16 / 9; }
}
.hero__media { position: absolute; inset: 0; }
.hero__media picture { position: absolute; inset: 0; }
.hero__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  position: absolute; inset: 0;
}

/* Four slides: the two Ganesh Chaturthi promo creatives in two orientations —
   slides 1-2 are the LANDSCAPE composites (shown on desktop), slides 3-4 the
   PORTRAIT ones (shown on phones). object-fit: cover can only ever suit one
   orientation per viewport, so we hide the pair that does not match and
   cross-fade the pair that do.

   Offset keyframes (heroA/heroB) rather than one keyframe with an 8s
   animation-delay: a positive delay leaves the incoming slide at opacity 0 until
   it pops in, so the outgoing slide fades to bare navy before the next appears.
   heroA + heroB always sum to 1, so it truly dissolves — 7s hold, ~1s
   cross-fade, 16s round trip. */
@media (min-width: 768px) {
  .hero__media picture:nth-of-type(3),
  .hero__media picture:nth-of-type(4) { display: none; }
  .hero__media picture:nth-of-type(1) img { animation: heroA 16s infinite; }
  .hero__media picture:nth-of-type(2) img { animation: heroB 16s infinite; }
}
@media (max-width: 767px) {
  .hero__media picture:nth-of-type(1),
  .hero__media picture:nth-of-type(2) { display: none; }
  .hero__media picture:nth-of-type(3) img { animation: heroA 16s infinite; }
  .hero__media picture:nth-of-type(4) img { animation: heroB 16s infinite; }
}
@keyframes heroA {
  0%, 44%  { opacity: 1; }
  50%, 94% { opacity: 0; }
  100%     { opacity: 1; }
}
@keyframes heroB {
  0%, 44%  { opacity: 0; }
  50%, 94% { opacity: 1; }
  100%     { opacity: 0; }
}
/* One still frame when motion is unwelcome — the first slide of whichever
   orientation is on show (picture:first-of-type would be a hidden landscape
   slide on mobile, leaving the hero blank). */
@media (prefers-reduced-motion: reduce) {
  .hero__media img { animation: none !important; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) and (min-width: 768px) {
  .hero__media picture:nth-of-type(1) img { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) and (max-width: 767px) {
  .hero__media picture:nth-of-type(3) img { opacity: 1; }
}

/* Sits over the photographic half, clear of the baked-in wordmark on the right. */
/* The hero CTAs travel with the page rather than scrolling away with the hero,
   mirroring the floating buttons on the right. `fixed` rather than `sticky`:
   the hero is only one screen tall, so a sticky element would stop following
   the moment its own section left the viewport. */
.hero__actions {
  position: fixed;
  left: var(--s-5);
  bottom: var(--s-5);
  z-index: 90;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
/* The blog and post pages reuse this class for an inline, centred pair —
   those set position:static inline, which must keep winning. */
.hero__actions[style*="static"] { position: static !important; }
.hero__actions .btn--ghost {
  background: rgb(1 31 51 / 0.55);
  backdrop-filter: blur(2px);
}

@media (max-width: 767px) {
  /* On phones the hero shows the PORTRAIT composites (1440x2560). A fixed svh
     height gave the box an aspect ratio that never matched the art, so
     object-fit:cover zoomed in and sliced off the top/foot — where the wordmark
     and tagline are baked in. Sizing the hero to the composite's own 9:16 ratio
     makes cover fill a box that already matches the image: the full banner shows
     edge-to-edge, no crop and no letterbox bars, on every phone width. */
  .hero { min-height: 0; aspect-ratio: 1440 / 2560; }
  .hero__actions { left: var(--s-4); right: var(--s-4); bottom: var(--s-4); }
  .hero__actions .btn { flex: 1 1 auto; justify-content: center; }
}

/* ---- image bands --------------------------------------------------------
   Not full-bleed: measured at 861x577 on a 1440 viewport, i.e. a centred
   image at roughly 60% of the viewport width. Rendering these edge-to-edge
   made the page more than twice as tall as the original. */
.band {
  padding: var(--s-5) var(--section-pad-x);
  display: grid;
  place-items: center;
}

/* The original's band image straddles the boundary into the dark section
   below it: measured on avenorpark.com, the band ends 301px AFTER the navy
   section begins, and that section carries 320px of top padding to clear the
   overlap. That straddle is the signature of the transition — without it the
   320px would just be dead navy. Scaled down on narrow screens, where the
   image is shorter and a 300px pull would swallow the heading. */
.band--overlap { padding-bottom: 0; }
/* The negative margin belongs on the IMAGE, not the section. On the section it
   drags the whole marble background down over the navy, hiding the very
   boundary the straddle is meant to cross. On the image, the marble ends where
   it should and only the building crosses into the dark. */
.band--overlap picture {
  margin-bottom: -300px;
  position: relative;
  z-index: 1;
}
.section--under-band { padding-top: 320px; }

@media (max-width: 1024px) {
  .band--overlap picture { margin-bottom: -140px; }
  .section--under-band { padding-top: 170px; }
}
@media (max-width: 767px) {
  /* Measured: the render's foot was landing 5px INTO the heading below it.
     Less overlap and more clearance, since the heading wraps to two lines here
     and there is far less room to play with than on desktop. */
  .band--overlap picture { margin-bottom: -36px; }
  .section--under-band { padding-top: 104px; }
}
.band picture, .band img {
  width: 100%;
  max-width: 861px;
  height: auto;
}
/* Gold frame on the elevation render, matching the card radius used elsewhere. */
.band picture {
  display: block;
  border: 2px solid var(--gold);
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: 0 10px 30px rgb(0 0 0 / 0.25);
}
.band img { display: block; border-radius: calc(var(--radius-card) - 2px); }

/* ---- location: accordion + map ------------------------------------------ */
.location__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--col-gap);
  align-items: start;
}
/* The original puts the eyebrow and heading inside the left column above the
   accordion (heading x=100 w=570), not full-width above both columns. */
.location__col > .eyebrow { margin-top: 0; }
.location__col > h2 { margin-bottom: var(--s-4); }
@media (max-width: 1024px) { .location__grid { grid-template-columns: 1fr; } }

.accordion { list-style: none; }
.accordion__item { border-bottom: 1px solid var(--hairline); }
.accordion__btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-3) 0;
  text-align: left;
  font-family: var(--font-sans);
  font-size: var(--fs-body-lg);
  font-weight: 600;
  color: var(--teal);
}
.accordion__btn::after {
  content: "";
  flex: 0 0 auto;
  width: 9px; height: 9px;
  border-right: 2px solid var(--gold);
  border-bottom: 2px solid var(--gold);
  transform: rotate(45deg);
  transition: transform var(--dur) var(--ease);
}
.accordion__btn[aria-expanded="true"]::after { transform: rotate(-135deg); }
.accordion__panel { padding: 0 0 var(--s-4); }
.accordion__panel ul { list-style: none; }
.accordion__panel li {
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  padding: 4px 0;
  border-bottom: 1px dotted var(--hairline);
}
.accordion__panel li span:last-child { color: var(--gold-ink); font-weight: 600; white-space: nowrap; }

/* ---- why-avenor cards (on navy) ----------------------------------------- */
.why-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-6) var(--s-8);
  list-style: none;
}
@media (max-width: 1024px) { .why-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 767px)  { .why-grid { grid-template-columns: 1fr; gap: var(--s-5); } }

.why-card { display: flex; gap: var(--s-3); align-items: flex-start; }
.why-card__icon { flex: 0 0 auto; width: 56px; }
.why-card__icon img { width: 56px; height: auto; }
.why-card__text {
  font-size: var(--fs-body);
  font-weight: 500;
  line-height: 1.5;
  color: inherit;
}

/* ---- courtyards image accordion ----------------------------------------- */
.courtyards {
  display: flex;
  gap: 4px;
  list-style: none;
  height: 420px;
}
.courtyards__item { flex: 1 1 0; transition: flex var(--dur) var(--ease); min-width: 0; }
.courtyards__item:has(.courtyards__btn[aria-expanded="true"]) { flex: 3 1 0; }
.courtyards__btn {
  position: relative;
  display: block;
  width: 100%; height: 100%;
  overflow: hidden;
  padding: 0;
}
.courtyards__img, .courtyards__btn img {
  width: 100%; height: 100%;
  object-fit: cover;
}
.courtyards__btn::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 45%, rgb(1 31 51 / 0.85));
}
.courtyards__name {
  position: absolute;
  left: 0; right: 0; bottom: var(--s-4);
  z-index: 1;
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  color: var(--white);
  text-align: center;
}
@media (max-width: 767px) {
  .courtyards { flex-direction: column; height: auto; }
  .courtyards__item, .courtyards__item:has([aria-expanded="true"]) { flex: none; height: 180px; }
}

/* ---- amenities ---------------------------------------------------------- */
.amenities-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-4);
  list-style: none;
}
@media (max-width: 1024px) { .amenities-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 767px)  { .amenities-grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-2); } }

.amenity {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-5) var(--s-3);
  background: var(--white);
  border-radius: var(--radius-card);
  text-align: center;
}
.amenity__icon { width: 72px; height: 72px; object-fit: contain; }
@media (max-width: 767px) { .amenity__icon { width: 36px; height: 36px; } }
.amenity__label { font-size: var(--fs-h3); font-weight: 600; color: var(--black); }

/* ---- gallery ------------------------------------------------------------ */
.tabs {
  display: flex;
  justify-content: center;
  gap: var(--s-2);
  margin-bottom: var(--s-5);
  list-style: none;
}
.tabs button {
  padding: 10px 28px;
  border-radius: 999px;
  border: 1px solid var(--gold);
  color: var(--gold);
  font-size: var(--fs-body);
  font-weight: 500;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.tabs button[aria-selected="true"] { background: var(--gold); color: var(--navy); }
.on-dark .tabs button[aria-selected="true"] { color: var(--navy); }

/* Live gallery is a carousel of 380x280 slides with four visible at 1440.
   Built as a scroll-snap strip: native horizontal scrolling means it works
   with keyboard, touch and no JS at all, and needs no carousel library. */
.gallery-grid {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 380px;
  gap: var(--s-3);
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding: var(--s-3);
  padding-bottom: var(--s-3);
}
.gallery__item { scroll-snap-align: start; }
.gallery__item img {
  width: 100%;
  height: 280px;
  object-fit: cover;
  border-radius: var(--radius-card);
}
@media (max-width: 767px) {
  .gallery-grid { grid-auto-columns: 78vw; }
  .gallery__item img { height: 220px; }
}

/* Without JS both panels stay visible, each under its own visible label. */
.js [role="tabpanel"][hidden] { display: none; }
.tabpanel__label { margin-bottom: var(--s-3); }
.js .tabpanel__label { display: none; }
.no-js-only { display: none; }

/* ---- floor plans --------------------------------------------------------
   Measured on the live site: 360x228 cards, 25px radius, 1px gold border,
   with one shared faded layout drawing behind a centred gold pill. */
.plans-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 360px));
  justify-content: center;
  gap: var(--s-6);
  list-style: none;
  margin-top: var(--s-5);
}
@media (max-width: 1024px) { .plans-grid { grid-template-columns: repeat(2, minmax(0, 360px)); } }
@media (max-width: 767px)  { .plans-grid { grid-template-columns: minmax(0, 360px); } }

.plan-card {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 228px;
  padding: var(--s-4);
  border: 1px solid var(--gold);
  border-radius: 25px;
  background: var(--navy);   /* fallback if the teaser fails to load */
  overflow: hidden;
}
/* One blurred layout per type behind the lock — the same baked-blur files as the
   floor-plans tiles (tools/build_plan_blurs.py). Only the -blur.jpg is
   referenced, so the sharp drawing never ships; "See Plan" opens the enquiry
   form, where the full plan is shared. */
.plan-card__blur {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.06);   /* hides the blur's soft edge against the card */
}
/* Frosted scrim over the teaser so the type label and gold CTA keep contrast. */
.plan-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(160deg, rgb(var(--grey-rgb) / 0.5), rgb(var(--grey-rgb) / 0.28));
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.plan-card__lock {
  position: relative;
  z-index: 2;   /* above the teaser and its scrim */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  text-align: center;
}
.plan-card__type {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: var(--tracking);
  color: var(--gold-ink);
}
.plan-card__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 12px 20px;
  border-radius: var(--radius-sm);
  background: var(--gradient-gold);
  color: var(--navy);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: var(--tracking);
  text-decoration: none;
}
.plan-card__btn:hover { filter: brightness(1.06); }

/* ---- interior pages: breadcrumbs ---------------------------------------- */
.breadcrumbs {
  padding: calc(var(--header-h) + var(--s-4)) var(--section-pad-x) 0;
  background: var(--marble);
  font-size: var(--fs-body);
}
.breadcrumbs ol {
  max-width: var(--container-max);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  list-style: none;
}
.breadcrumbs li + li::before {
  content: "›";
  margin-right: var(--s-2);
  color: var(--gold-ink);
}
.breadcrumbs a { color: var(--gold-ink); }
.breadcrumbs a:hover { text-decoration: underline; }
.breadcrumbs [aria-current="page"] { color: var(--ink-muted); }

/* ---- interior pages: floor plans ---------------------------------------- */
.plans-page .plan-group + .plan-group { margin-top: var(--s-9); }
.plan-group__rooms { margin: var(--s-2) 0 var(--s-4); color: var(--ink-muted); }

.plan-detail-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  list-style: none;
}
@media (max-width: 1024px) { .plan-detail-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 767px)  { .plan-detail-grid { grid-template-columns: 1fr; } }

.plan-detail figure {
  height: 100%;
  display: flex;
  flex-direction: column;
  background: transparent;
  border: 1px solid var(--gold);
  border-radius: var(--radius-card);
  overflow: hidden;
}
/* The plan drawing is never shipped — the media slot is a locked frosted-glass
   panel with a centred "See Plan" gate, so there is nothing in the DOM or
   Network tab to lift. The panel is translucent and blurs the marble texture
   showing through the transparent figure; the white figcaption below keeps the
   type and carpet area (public marketing text). aspect-ratio holds the shape. */
.plan-detail__media {
  position: relative;
  aspect-ratio: 1113 / 759;
  overflow: hidden;
  background: var(--navy);   /* shows only if the teaser fails to load */
}
/* The pre-blurred plan teaser (tools/build_plan_blurs.py). The blur is baked
   into the file, so DevTools has nothing crisp to reveal, and the sharp original
   is never referenced — make_dist ships only this file. The slight scale hides
   the blur's soft edge against the panel. */
.plan-detail__blur {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.06);
}
/* The lock now doubles as a frosted scrim over the drawing: the translucent grey
   and its backdrop blur (a) keep the gold "See Plan" CTA readable over a pale
   plan and (b) add to the baked blur, so the layout reads as "a plan, but
   locked" rather than anything legible. */
.plan-lock {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  background: linear-gradient(160deg, rgb(var(--grey-rgb) / 0.5), rgb(var(--grey-rgb) / 0.28));
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.plan-lock__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 12px 22px;
  border-radius: var(--radius-sm);
  background: var(--gradient-gold);
  color: var(--navy);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: var(--tracking);
}
.plan-lock:hover .plan-lock__btn,
.plan-lock:focus-visible .plan-lock__btn { filter: brightness(1.06); }
.plan-detail figcaption { flex: 1; background: var(--white); padding: var(--s-3) var(--s-4) var(--s-4); }
.plan-detail h3 { margin-bottom: 4px; }
.plan-detail__area { font-size: var(--fs-body); font-weight: 600; color: var(--gold-ink); }
.plan-detail__area span { font-weight: 400; color: var(--ink-muted); }

.disclaimer {
  margin-top: var(--s-5);
  font-size: 13px;
  line-height: 1.6;
  opacity: 0.8;
}

.facts { margin: var(--s-3) 0 var(--s-5); }
.facts > div {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--s-3);
  padding: 10px 0;
  border-bottom: 1px solid var(--hairline);
}
.facts dt { font-weight: 600; }
.facts dd { margin: 0; }
@media (max-width: 767px) {
  .facts > div { grid-template-columns: 1fr; gap: 2px; }
}

/* ---- interior pages: connectivity table --------------------------------- */
.connectivity {
  width: 100%;
  border-collapse: collapse;
  margin-top: var(--s-3);
}
.connectivity caption {
  text-align: left;
  font-family: var(--font-sans);
  font-size: var(--fs-body-lg);
  font-weight: 600;
  color: var(--teal);
  padding-bottom: var(--s-2);
}
.connectivity th, .connectivity td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--hairline);
  font-size: var(--fs-body);
}
.connectivity th { font-weight: 600; }
.connectivity td:last-child { text-align: right; white-space: nowrap; font-weight: 600; color: var(--gold-ink); }
.connectivity-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-6) var(--s-8);
}
@media (max-width: 1024px) { .connectivity-grid { grid-template-columns: 1fr; } }

/* ---- highlights --------------------------------------------------------- */
.highlights-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  list-style: none;
}
@media (max-width: 1024px) { .highlights-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 767px)  { .highlights-grid { grid-template-columns: 1fr; } }

.highlight-card {
  display: flex;
  gap: var(--s-3);
  align-items: center;
  padding: var(--s-3);
  background: var(--white);
  border-radius: var(--radius-card);
}
.highlight-card__icon { flex: 0 0 auto; }
.highlight-card__icon img {
  width: 72px; height: 72px;
  object-fit: cover;
  border-radius: var(--radius-sm);
}
.highlight-card__text {
  font-size: var(--fs-h3);
  font-weight: 600;
  line-height: 1.45;
}

/* ---- about / developer -------------------------------------------------- */
.developer__logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s-8);
  margin-bottom: var(--s-6);
}
.developer__logos img { max-width: 220px; height: auto; }
/* The Kean logo is a wider, shorter mark than Vanraaj's; sized up so the two
   read at the same optical height and its thin gold strokes stay crisp. */
.developer__logos img.dev-logo--kean { max-width: 250px; }
.prose { max-width: 74ch; margin-inline: auto; }
.prose p + p { margin-top: var(--s-3); }

/* ---- enquiry form ------------------------------------------------------- */
.enquiry {
  max-width: 520px;
  margin-inline: auto;
  display: grid;
  gap: var(--s-3);
}
.field { display: grid; gap: 6px; }
.field label { font-size: var(--fs-body); font-weight: 600; }
.field input, .field select {
  width: 100%;
  padding: 12px 14px;
  font: inherit;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
}
.field--invalid input, .field--invalid select { border-color: var(--danger); }
.field__error { min-height: 1em; font-size: 13px; color: var(--danger); }
/* --danger is tuned for light grounds and measures 1.94:1 on navy, so every
   inline error on the homepage band and in the modal was effectively unreadable.
   Same tint .form__status--error already uses: 9.93:1. The modal is not
   .on-dark, hence both selectors. */
.on-dark .field__error,
.modal__body .field__error { color: var(--danger-on-dark); }

/* The error is LIFTED OUT OF FLOW into a band the field already reserved, so a
   message can never grow its own row and shove the rest of the form down. In the
   modal that reflow moved the paired second row, the verify card, the consent
   block and Submit, and grew a scrollbar mid-validation.

   Scoped to the form's DIRECT children on purpose. .otp-step is a .field too, but
   its error sits mid-stack — hint, error, WhatsApp warning, "Change number" — so
   absolute placement there would drop it past the actions. Inside the card the
   error stays in flow and wraps, as before.

   30px is what the field reserved anyway: the 6px .field gap plus one 13px/1.8
   line box (29.4px), held open until now by min-height:1em. So nothing moves —
   the dialog is exactly as tall as it was, which matters on a 720px laptop.
   bottom:0 supplies that 6px of air above the line, because grid `gap` does not
   apply to an absolutely positioned item.

   nowrap + ellipsis is the backstop, not the mechanism: the messages in
   js/form.js are written to fit, but between 768px and ~900px the modal's paired
   columns narrow to ~157-190px, and a wrapped second line would put the layout
   straight back where it started. setError mirrors the text onto `title` so a
   clipped message is still readable; aria-live announces it in full regardless.
   left/right:0 bounds the line to the field, so it can never overflow the dialog
   or trigger horizontal scroll. */
.enquiry > .field { position: relative; padding-bottom: 30px; }
.enquiry > .field > .field__error {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  min-height: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.field__hint { font-size: 13px; color: var(--ink-muted); }
.field--consent { grid-template-columns: auto 1fr; align-items: start; gap: var(--s-2); }
.field--consent label { font-weight: 400; font-size: 13px; }

/* Honeypot: off-screen, not display:none, so bots still fill it. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form__status { min-height: 1.5em; font-size: var(--fs-body); }
.form__status--error { color: var(--danger-on-dark); }

/* ---- verify card ---------------------------------------------------------
   The card is TRANSPARENT: it takes the ground it lands on. There are only two
   of those — flat --white (the .contact__form panel, which fully occludes the
   marble section behind it) and --navy + --bg-texture (the homepage #enquire
   band and the modal, which are visually the same surface).

   So the card declares its palette as local custom properties and the dark
   grounds redefine only those. Every rule below is written once, against the
   tokens; nothing inside the card is duplicated per ground. The modal is NOT
   .on-dark — it sets .modal__body { color: var(--white) } instead — which is why
   the dark selector has to name both.

   The selected pill INVERTS against the ground — white on navy, navy on white —
   while the unselected one stays transparent with a gold edge. One rule, read
   the same way on both grounds, and the strongest signal available without
   reaching for gold: a gold-filled pill would fight Send OTP, and a gold TINT
   was tried first and lost against the unselected pill's own gold edge.
   Gold could not carry the light ground anyway — solid --gold is 2.34:1 on
   white, so no alpha of it ever reaches 3:1.

   [hidden] needs its own rule for the same reason .otp-step does — the class
   sets display:grid, which beats the [hidden] attribute's UA display:none.
   ------------------------------------------------------------------------- */
.otp-card[hidden] { display: none; }
.otp-card {
  --otp-ink:       var(--ink);
  --otp-ink-soft:  var(--ink-soft);
  --otp-accent:    var(--gold-ink);                 /* 4.80:1 on white */
  --otp-danger:    var(--danger);
  --otp-ring:      var(--gold-ink);
  /* The card's own outline is all that delineates it now, so it is deliberately
     stronger than --hairline, which measures 1.27:1 on white and 1.73:1 on navy
     — barely an edge at all. A panel divider, not a control, so 3:1 does not
     apply; it only has to be seen. */
  --otp-edge:      rgb(var(--gold-rgb) / 0.6);
  /* A control boundary, so this one does need 3:1. Measured 3.02:1. */
  --otp-pill-edge: rgb(var(--gold-ink-rgb) / 0.75);
  --otp-sel-bg:    var(--navy);
  --otp-sel-edge:  var(--navy);
  --otp-sel-ink:   var(--white);                    /* 16.87:1 on navy */
  --otp-off-bg:    color-mix(in srgb, var(--gold) 40%, var(--white));
  --otp-off-ink:   var(--ink-soft);                 /* 4.89:1 */
  /* The glyph follows the SURFACE it sits on, not the ground: brand green is
     2.09:1 on white and has to be darkened there, but it is the real mark on
     navy at 8.50:1. So the two swap over when the pill inverts. */
  --otp-wa:        color-mix(in srgb, var(--whatsapp) 78%, var(--black));
  --otp-sel-wa:    var(--whatsapp);

  display: grid;
  gap: var(--s-2);
  padding: var(--s-3);
  background: transparent;
  border: 1px solid var(--otp-edge);
  border-radius: var(--radius-card);
  color: var(--otp-ink);
}
.on-dark .otp-card,
.modal__body .otp-card {
  --otp-ink:       var(--white);
  --otp-ink-soft:  rgb(255 255 255 / 0.75);         /* 9.86:1 on navy */
  --otp-accent:    var(--gold);                     /* 7.19:1 on navy */
  --otp-danger:    var(--danger-on-dark);           /* 9.93:1 */
  --otp-ring:      var(--white);
  --otp-edge:      rgb(var(--gold-rgb) / 0.35);
  --otp-pill-edge: rgb(var(--gold-rgb) / 0.55);     /* 3.01:1 */
  --otp-sel-bg:    var(--white);
  --otp-sel-edge:  var(--white);
  --otp-sel-ink:   var(--navy);                     /* 16.87:1 on white */
  --otp-off-bg:    color-mix(in srgb, var(--gold) 40%, var(--navy));
  --otp-off-ink:   var(--white);                    /* 7.78:1 */
  --otp-wa:        var(--whatsapp);
  --otp-sel-wa:    color-mix(in srgb, var(--whatsapp) 78%, var(--black));
}
/* Labels now inherit --otp-ink, which is already right on both grounds — the
   old .enquiry .otp-card label escape hatch existed only to fight
   .modal__body label { color: white }, and is gone with it.
   The ring is a token too: .on-dark turns :focus-visible white, but the modal is
   not .on-dark, so without this it would fall back to plain --gold there. */
.enquiry .otp-card :focus-visible { outline-color: var(--otp-ring); }

/* Scoped to the card so nothing else on the page shifts. Both globals are tuned
   for light grounds: --ink-muted is invisible on navy, and --danger measures
   1.94:1 there. */
.otp-card .field__hint  { color: var(--otp-ink-soft); }
.otp-card .field__error { color: var(--otp-danger); }

.otp-card__title {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  font-weight: 600;
  font-size: var(--fs-body-lg);
}
.otp-card__shield { display: inline-flex; color: var(--otp-accent); }

/* ---- channel chooser -----------------------------------------------------
   Native radios, visually hidden, styled through their labels: real radiogroup
   semantics, native arrow-key selection and "1 of 2" announcement, and a visual
   state that cannot drift from the real one because it IS :checked. Selection is
   inert by design — arrowing across these must never send a message.
   ------------------------------------------------------------------------- */
.otp-seg { margin: 0; padding: 0; border: 0; }
.otp-seg__row { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.otp-seg__opt {
  display: inline-flex;
  flex: 1 1 140px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 14px;
  font-size: var(--fs-body);
  font-weight: 600;
  color: var(--otp-ink);
  background: transparent;
  border: 1px solid var(--otp-pill-edge);
  border-radius: var(--radius-btn-lg);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.otp-seg__opt:hover { border-color: var(--otp-accent); }
.otp-seg__input:checked + .otp-seg__opt {
  color: var(--otp-sel-ink);
  background: var(--otp-sel-bg);
  border-color: var(--otp-sel-edge);
}
/* Drawn on the label, since the input itself is visually hidden. Nothing in the
   card may set overflow:hidden or this 3px ring at 3px offset gets clipped. */
.otp-seg__input:focus-visible + .otp-seg__opt {
  outline: 3px solid var(--otp-ring);
  outline-offset: 3px;
}
.otp-seg__glyph { display: inline-flex; }
.otp-seg__glyph--wa { color: var(--otp-wa); }
.otp-seg__input:checked + .otp-seg__opt .otp-seg__glyph--wa { color: var(--otp-sel-wa); }

/* ---- destination row ---------------------------------------------------- */
.otp-dest {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding-top: var(--s-1);
}
.otp-dest__glyph[hidden] { display: none; }   /* the span sets display too */
.otp-dest__glyph { display: inline-flex; color: var(--otp-accent); }
.otp-dest__text { flex: 1 1 200px; font-size: 13px; }
.otp-dest__num { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.btn--otp {
  /* Deliberately lighter than a page CTA: this sits beside 13px destination
     text, and the form's real primary action is Submit. */
  padding: 8px 14px;
  font-size: 13px;
  border-radius: var(--radius-btn-lg);
  /* .btn capitalizes every word, which renders the cooldown as "Send OTP In
     60s". Same opt-out .btn--ui already takes. */
  text-transform: none;
  /* Navy on gold is 7.19:1. The site-wide .btn puts white on it at 2.35:1 —
     existing debt, not repeated here. */
  color: var(--navy);
}
/* aria-disabled, not disabled: a disabled button leaves the tab order, so a
   screen-reader user could never find out why the tap did nothing. */
.btn--otp[aria-disabled="true"] {
  color: var(--otp-off-ink);
  background: var(--otp-off-bg);
  cursor: default;
}

/* ---- OTP step ----------------------------------------------------------- */
/* Ships in the markup and is revealed by js/form.js once the code is away.
   NOT the .js …[hidden] pattern used by the nav panel and the blog search: those
   are meant to stay OPEN without JS, whereas an OTP field with no JS to drive it
   is a dead control. So this hides unconditionally, and only JS reveals it.
   The rule is load-bearing rather than belt-and-braces — .field sets
   display:grid, which beats the [hidden] attribute's UA display:none, so without
   it the step renders on every page for every visitor. */
.otp-step[hidden] { display: none; }

.otp-step input {
  font-size: 20px;
  letter-spacing: 6px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.otp-step__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin: 0;
}
/* A button, because it acts on this page rather than going anywhere — styled as
   a link so it does not compete with the real submit. Only used inside the
   verify card today, so it reads that card's palette; the fallbacks keep it
   legible on a light ground if it is ever reused outside one. */
.linkish {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--otp-accent, var(--gold-ink));
  text-decoration: underline;
  cursor: pointer;
}
.linkish:hover { color: var(--otp-ink, var(--ink)); }
.linkish[disabled] {
  color: var(--otp-ink-soft, var(--ink-muted));
  text-decoration: none;
  cursor: default;
}

/* Shown only after a WhatsApp send. [hidden] needs the rule because .field__hint
   does not set display, but the class list may grow — keep it explicit. */
.otp-step__warn[hidden] { display: none; }
/* Wins over .otp-card .field__hint, which it also carries. */
.otp-card .otp-step__warn { color: var(--otp-accent); }

/* Only the phone is locked while a code is outstanding, not the whole form: the
   visitor now asks for the code before filling the rest, so freezing everything
   would make the form impossible to finish. Locking the number stops it drifting
   away from the one the code went to; "Change number" is the way out. */
.enquiry--otp .phone-input input,
.enquiry--otp .cc__btn { opacity: 0.6; }

.phone-input { display: flex; align-items: stretch; }
.phone-input__prefix {
  display: grid;
  place-items: center;
  padding-inline: 12px;
  background: var(--marble);
  border: 1px solid var(--hairline);
  border-right: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  font-weight: 600;
  /* The chip is marble even inside .on-dark sections, where colour would
     otherwise be inherited as white — invisible on its own background. */
  color: var(--ink);
}
.phone-input input { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }

/* ---- contact page --------------------------------------------------------
   Details on the left, form on the right at desktop; stacked on mobile with
   the form first, since that is what most visitors came to do.
   ------------------------------------------------------------------------ */
.contact__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--col-gap);
  align-items: start;
}
@media (max-width: 1024px) {
  .contact__grid { grid-template-columns: 1fr; gap: var(--s-7); }
  .contact__form { order: -1; }
}

.contact__details h2,
.contact__form h2 {
  font-family: var(--font-display);
  font-size: 22px;
  margin-bottom: var(--s-3);
}
.contact__details h2:not(:first-child) { margin-top: var(--s-6); }
.contact__details address { font-style: normal; line-height: var(--lh-body); }
.contact__details p { margin-bottom: var(--s-3); }

.contact__list { list-style: none; }
.contact__list li {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--hairline);
}
.contact__list span { font-weight: 600; color: var(--gold-ink); }
.contact__list a { color: var(--teal); text-decoration: underline; }

.contact__form { background: var(--white); padding: var(--s-5); border-radius: var(--radius-card); }
.contact__form > p { margin-bottom: var(--s-4); font-size: var(--fs-body); }

/* The legal pages carry a revision date under the h1 — a policy with no date
   is not much use to anyone trying to work out which version they agreed to. */
.legal-updated {
  font-size: var(--fs-body);
  color: var(--ink-muted);
  margin-bottom: var(--s-5);
}

/* The floor-plan CTA sat flush against the card grid above it. */
.plans__cta { margin-top: var(--s-6); }

/* ---- gallery carousel chrome --------------------------------------------
   The strip is still a native scroll container — JS only adds arrows and an
   auto-advance on top, so keyboard, touch and no-JS behaviour are unchanged.
   ------------------------------------------------------------------------ */
.gallery__wrap { position: relative; }

/* Gold scrollbar, replacing the default grey one. Firefox gets the two-value
   scrollbar-color; WebKit/Blink get the pseudo-elements. */
.gallery-grid {
  scrollbar-width: thin;
  scrollbar-color: var(--gold) rgb(255 255 255 / 0.15);
}
.gallery-grid::-webkit-scrollbar { height: 6px; }
.gallery-grid::-webkit-scrollbar-track {
  background: rgb(255 255 255 / 0.15);
  border-radius: 999px;
}
.gallery-grid::-webkit-scrollbar-thumb {
  background: var(--gradient-gold);
  border-radius: 999px;
}
.gallery-grid::-webkit-scrollbar-thumb:hover { background: var(--gold); }

.gallery__nav {
  position: absolute;
  top: calc(50% - var(--s-4));
  transform: translateY(-50%);
  z-index: 2;
  display: none;                    /* JS-only control */
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--gold);
  background: rgb(1 31 51 / 0.72);
  backdrop-filter: blur(2px);
  color: var(--gold);
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.js .gallery__nav { display: grid; }
.gallery__nav:hover { background: var(--gold); color: var(--navy); transform: translateY(-50%) scale(1.06); }
.gallery__nav[disabled] { opacity: 0.35; pointer-events: none; }
.gallery__nav svg { width: 18px; height: 18px; fill: currentColor; }
.gallery__nav--prev { left: calc(var(--s-3) * -1); }
.gallery__nav--next { right: calc(var(--s-3) * -1); }

@media (max-width: 767px) {
  .gallery__nav { width: 38px; height: 38px; }
  .gallery__nav--prev { left: 2px; }
  .gallery__nav--next { right: 2px; }
}

/* ---- country dial-code picker -------------------------------------------
   js/phone.js swaps the static "+91" chip for this. Without JS the chip stays
   as it was, so these rules simply never apply.
   ------------------------------------------------------------------------ */
.phone-input--cc { position: relative; }

.cc__btn {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  white-space: nowrap;
}
.cc__flag {
  /* Regional-indicator emoji. They render as real flags on iOS, Android, macOS
     and Firefox; Windows ships no flag glyphs, so Chrome/Edge there fall back
     to the two ISO letters. Boxed at a fixed size so that fallback reads as a
     deliberate country badge rather than looking broken. */
  display: inline-grid;
  place-items: center;
  min-width: 22px;
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  color: var(--ink-muted);
}
.cc__code { font-weight: 600; color: var(--ink); }
.cc__caret {
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--ink-muted);
}
.cc__btn[aria-expanded="true"] .cc__caret { transform: rotate(180deg); }

.cc__pop {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  width: min(320px, 86vw);
  background: var(--white);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  box-shadow: 0 10px 30px rgb(0 0 0 / 0.18);
  overflow: hidden;
}
.cc__search {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--hairline);
  padding: 10px 12px;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  color: var(--ink);          /* never inherit white from .on-dark */
  background: var(--white);
  outline: 0;
}
.cc__list {
  list-style: none;
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
}
.cc__list li {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  align-items: center;
  gap: var(--s-2);
  padding: 9px 12px;
  cursor: pointer;
  font-size: var(--fs-body);
  color: var(--ink);
}
.cc__list li:hover,
.cc__list li:focus { background: rgb(203 165 43 / 0.14); outline: 0; }
.cc__list li[aria-selected="true"] { background: rgb(203 165 43 / 0.22); font-weight: 600; }
.cc__dial { color: var(--ink-muted); }
.cc__empty { color: var(--ink-muted); cursor: default; grid-template-columns: 1fr; }

/* CasaMonde section needed its own call to action. */
.cp__cta { margin-top: var(--s-4); }

/* Social links on the contact page, each in its own brand colour so the logos
   read as the real thing rather than a monochrome set. */
.contact__social { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s-2); }
.contact__social a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px 8px 10px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  font-size: var(--fs-body);
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              background var(--dur) var(--ease);
}
.contact__social svg { width: 20px; height: 20px; flex: 0 0 auto; }
.contact__social--ig svg { fill: var(--brand-instagram); }
.contact__social--fb svg { fill: var(--brand-facebook); }
.contact__social--li svg { fill: var(--brand-linkedin); }
.contact__social--ig:hover { border-color: var(--brand-instagram); color: var(--brand-instagram); }
.contact__social--fb:hover { border-color: var(--brand-facebook); color: var(--brand-facebook); }
.contact__social--li:hover { border-color: var(--brand-linkedin); color: var(--brand-linkedin); }

/* ---- mobile: the fixed CTAs become a bottom bar --------------------------
   Two full-width stacked buttons ate roughly a third of a 390x844 screen and
   sat on top of the floating buttons. On a phone they become one compact row
   pinned to the bottom edge, and the floating buttons move up to clear it.
   ------------------------------------------------------------------------ */
@media (max-width: 767px) {
  .hero__actions:not([style*="static"]) {
    left: 0;
    right: 0;
    bottom: 0;
    gap: 0;
    flex-wrap: nowrap;
    background: rgb(1 31 51 / 0.92);
    backdrop-filter: blur(6px);
    border-top: 1px solid var(--gold);
    padding: 7px 8px calc(7px + env(safe-area-inset-bottom));
  }
  .hero__actions:not([style*="static"]) .btn {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    font-size: 13px;
    padding: 10px 8px;
    border-radius: var(--radius-btn);
    white-space: nowrap;
  }
  .hero__actions:not([style*="static"]) .btn + .btn { margin-left: 7px; }

  /* Section headings and body get a little more room back. */
  .section { padding-block: var(--section-pad-y); }
  .lede { font-size: var(--fs-body-lg); }
}
