/* ==========================================================================
   Pravasth Developers & Interiors — design system + site styles

   Structure
   01  Design tokens
   02  Base & typography
   03  Reusable section patterns
   04  Buttons & links
   05  Forms
   06  Header / navigation
   07  Hero (home) and page hero (inner pages)
   08  About + read more
   09  Split panels (Developers / Interiors)
   10  Gallery slider
   11  Statistics
   12  Testimonials
   13  Project teaser
   14  Interiors: solutions, services, process, FAQ
   15  Blog
   16  Contact
   17  Enquiry section
   18  Footer
   19  Motion (reveal, hover)
   20  Responsive
   ========================================================================== */


/* 01 ------------------------------------------------------------- tokens */

/* ---------------------------------------------------------------- brand
   The Pravasth palette, exactly as the brand document defines it. Nothing
   below this block invents a brand colour; everything else is a semantic
   name pointing at one of these six.                                    */
:root {
  --brand-gold:     #C29A69;   /* Primary   - Warm Champagne Gold */
  --brand-bronze:   #845C2B;   /* Secondary - Deep Bronze Brown   */
  --brand-charcoal: #24221F;   /* Dark      - Deep Charcoal       */
  --brand-ivory:    #F5F1E9;   /* Light     - Warm Ivory          */
  --brand-stone:    #D8D0C4;   /* Neutral   - Stone Beige         */
  --brand-espresso: #4A382B;   /* Accent    - Espresso Brown      */
}

:root {
  --color-bg:       var(--brand-ivory);      /* the page */
  --color-surface:  var(--brand-stone);      /* alternating bands */
  --color-dark:     var(--brand-charcoal);   /* headings, dark sections */
  --color-dark-2:   var(--brand-bronze);     /* the footer - secondary brand */
  --color-muted:    #64533F;                 /* body copy: see note below */
  --color-white:    #FFFFFF;

  /* --------------------------------------------------------------------
     GOLD IS NOT A TEXT COLOUR ON THE LIGHT PAGE.

     Champagne Gold on Warm Ivory measures 2.29:1. That is below the 4.5:1
     text minimum and below the 3:1 needed for focus rings and control
     borders, so gold is used for fills, icons, dividers and decorative
     rules — which is what the brand document asks of it anyway.

     Where something needs to READ as brand-coloured text on a light
     surface it uses Espresso Brown, which clears both the ivory page
     (9.85:1) and the Stone Beige bands (7.26:1). Deep Bronze Brown was the
     first choice, but it only reaches 3.87:1 on stone and eyebrows sit on
     both surfaces. On a dark background the relationship inverts — espresso
     and bronze both fail on charcoal, gold makes 6.14:1 — so dark sections
     use gold.

       --color-accent          gold    fills, rules, icons, decoration
       --color-accent-ink      espresso  accent TEXT on light surfaces
       --color-accent-on-dark  gold    accent text on dark backgrounds

     --color-muted is the one value here that is not from the palette: the
     brand document specifies no body-copy colour. It is a warm mid-brown
     drawn from the same family, measured at 5.67:1 on Warm Ivory.
     -------------------------------------------------------------------- */
  --color-accent:         var(--brand-gold);
  --color-accent-ink:     var(--brand-espresso);
  --color-accent-on-dark: var(--brand-gold);
  --color-taupe:          var(--brand-stone);

  --color-border:   rgba(36, 34, 31, 0.18);
  --color-border-soft: rgba(36, 34, 31, 0.10);
  --color-border-light: rgba(245, 241, 233, 0.18);

  /* Two families, per the brand typography: Sora carries headings, titles
     and key statements; Manrope carries body copy, navigation, buttons and
     captions. */
  --font-primary: "Manrope", ui-sans-serif, system-ui, sans-serif;
  --font-head:    "Sora", ui-sans-serif, system-ui, sans-serif;

  --container-width: 1320px;
  /* Tightened from clamp(72px, 9vw, 150px) so sections sit closer
     together without the bands running into one another. */
  --section-spacing: clamp(48px, 5.6vw, 96px);
  --gutter: clamp(20px, 5vw, 64px);

  --radius-sm: 2px;    /* buttons, fields, badges - essentially square */
  --radius-md: 8px;

  /* Photography is the exception to the square-corner rule. A soft radius on
     the editorial images reads as a printed plate on the page; the full-bleed
     bands (hero, split panels, project teaser) stay square, because a rounded
     corner against the viewport edge looks like a mistake. */
  --radius-img: 18px;
  --radius-img-sm: 12px;

  --header-h: 150px;

  /* One easing for the whole site: slow-out, no bounce. */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --t-fast: 0.25s var(--ease);
  --t-slow: 0.6s var(--ease);
}


/* 02 -------------------------------------------------------- base & type */

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

html {
  scroll-behavior: smooth;
  /* The header scrolls away now, so an anchor no longer has to clear it —
     reserving 150px above every target just pushed the heading off-screen.
     A small offset keeps the target off the very top edge. */
  scroll-padding-top: 24px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-muted);
  font-family: var(--font-primary);
  font-size: 18.15px;
  font-weight: 400;
  line-height: 1.78;
  letter-spacing: 0.005em;
  -webkit-font-smoothing: antialiased;
  /* Every full-bleed image band is wider than the grid at some sizes; this
     keeps a stray overflow from producing a horizontal scrollbar. */
  overflow-x: hidden;
}

body, p, a, button, input, textarea, select, label, li {
  font-family: var(--font-primary);
}

/* Sora: headings, the wordmark, the statistic numerals and the pull quote -
   everything set large enough for its wider letterforms to read as display
   type rather than as a second body face. */
h1, h2, h3, h4, h5, h6,
.brand, .footer-brand,
.section-title,
.hero__title, .page-hero__title,
.stat-item__value,
.testimonial-card__quote {
  font-family: var(--font-head);
}
/* A link inside a heading keeps the heading face. */
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-family: inherit; }

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 0.6em;
  color: var(--color-dark);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.022em;
}

h1 { font-size: clamp(2.64rem, 5.94vw, 4.84rem); }
h2 { font-size: clamp(2.09rem, 3.74vw, 3.3rem); }
h3 { font-size: clamp(1.43rem, 2.2vw, 1.925rem); }
h4 { font-size: clamp(1.155rem, 1.54vw, 1.375rem); }

p { margin: 0 0 1.15em; }
p:last-child { margin-bottom: 0; }

a {
  color: var(--color-dark);
  text-decoration: none;
  transition: color var(--t-fast);
}
a:hover { color: var(--color-accent-ink); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

img { max-width: 100%; height: auto; display: block; }

ul, ol { margin: 0 0 1.15em; padding-left: 1.15em; }

::selection { background: var(--brand-charcoal); color: var(--brand-ivory); }

/* A visible, branded focus ring - never removed, only restyled. */
:focus-visible {
  outline: 2px solid var(--color-accent-ink);
  outline-offset: 3px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 2000;
  background: var(--color-dark);
  color: #fff;
  padding: 12px 20px;
  font-size: 14.3px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.skip-link:focus {
  left: 12px;
  top: 12px;
  color: #fff;
}


/* 03 -------------------------------------------------- section patterns */

.container-x {
  width: 100%;
  max-width: var(--container-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section-padding { padding-block: var(--section-spacing); }
.section-padding-sm { padding-block: clamp(52px, 6vw, 96px); }
/* Used where a short section runs straight into the next one. The testimonial
   card is only a quote, a name and two arrows, so a full section rhythm on
   both sides of that seam opened a ~300px void before Let's Connect. */
.section-padding--flush-bottom { padding-bottom: clamp(32px, 4vw, 56px); }

.section-surface { background: var(--color-surface); }
.section-dark { background: var(--color-dark); color: rgba(255, 255, 255, 0.66); }
.section-dark h1, .section-dark h2, .section-dark h3, .section-dark h4 { color: #fff; }

/* The eyebrow: small, tracked, with a short rule before it. */
.section-label {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
  color: var(--color-accent-ink);
  font-size: 12.1px;
  font-weight: 500;
  letter-spacing: 0.22em;
  line-height: 1;
  text-transform: uppercase;
}
/* The mark before a section name. This was a 28px hairline rule, which at
   this size reads as a stray dash in front of the words. A small rotated
   square is a definite mark rather than an ambiguous one, and it picks up
   the architectural geometry of the brand. Gold, because it is decoration
   rather than text and gold is the primary brand colour. */
.section-label::before {
  content: "";
  width: 7px;
  height: 7px;
  flex: none;
  transform: rotate(45deg);
  background: var(--brand-gold);
}
.section-dark .section-label::before,
.section-label--on-dark::before { background: var(--brand-gold); }
.section-label--center { justify-content: center; }
.section-dark .section-label { color: var(--color-taupe); }

.section-title {
  max-width: 19ch;
  margin-bottom: 0;
  font-size: clamp(2.09rem, 3.74vw, 3.3rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.028em;
}
.section-title--wide { max-width: 26ch; }

/* Interiors cross-sell — a centred band between the gallery and the
   testimonials. Centred rather than left-aligned because it is an
   interlude with a single action, not a section of content. */
.crosssell {
  max-width: 860px;
  margin-inline: auto;
  text-align: center;
}
.crosssell .section-label { justify-content: center; }
.crosssell .section-title { max-width: 22ch; margin-inline: auto; }
.crosssell__text {
  max-width: 68ch;
  margin: 24px auto 0;
  /* Same size and leading as .section-description — this was inheriting the
     plain body size and reading a step smaller than every other section. */
  font-size: 1.169rem;
  line-height: 1.8;
}
.crosssell__cta { margin-top: 34px; }

.section-description {
  max-width: 62ch;
  margin-top: 22px;
  font-size: 1.169rem;
  line-height: 1.8;
}

.section-head { margin-bottom: clamp(38px, 4.5vw, 64px); }
.section-head--center { text-align: center; }
.section-head--center .section-title,
.section-head--center .section-description { margin-inline: auto; }

/* Title on one side, a link or short line on the other. */
.section-head--split {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(24px, 4vw, 72px);
}

.rule { height: 1px; background: var(--color-border-soft); border: 0; margin: 0; }

/* The About intro on Interiors. It used to be a step larger and near-black,
   which read as a different kind of text from the rest of the page: every
   other paragraph on Interiors is 1.169rem / 1.8 in --color-muted, whether it
   is a .section-description or a plain <p> in a .section-surface. Matched to
   those exactly, so the About copy sits in the same voice as its neighbours.
   Only the two paragraphs in the Interiors About block use this class. */
.lede {
  font-size: 1.169rem;
  line-height: 1.8;
  color: var(--color-muted);
  font-weight: 400;
}


/* 04 --------------------------------------------------- buttons & links */

.btn-primary-custom,
.btn-outline-custom {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 16px 30px;
  border: 1px solid var(--color-dark);
  border-radius: var(--radius-sm);
  font-size: 12.65px;
  font-weight: 500;
  letter-spacing: 0.18em;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}

.btn-primary-custom {
  background: var(--color-dark);
  color: #fff;
}
.btn-primary-custom:hover,
.btn-primary-custom:focus-visible {
  background: var(--color-accent);
  border-color: var(--color-accent);
  /* Not #fff: white on Champagne Gold is 2.4:1. Charcoal on gold is 6.14:1. */
  color: var(--brand-charcoal);
}

/* On a charcoal band the default fill is the same colour as the background,
   so the button would be an outline of itself. Gold is the brand's CTA
   colour and carries charcoal text at 6.14:1. */
.section-dark .btn-primary-custom {
  background: var(--brand-gold);
  border-color: var(--brand-gold);
  color: var(--brand-charcoal);
}
.section-dark .btn-primary-custom:hover,
.section-dark .btn-primary-custom:focus-visible {
  background: var(--brand-ivory);
  border-color: var(--brand-ivory);
  color: var(--brand-charcoal);
}

.btn-outline-custom {
  background: transparent;
  color: var(--color-dark);
}
.btn-outline-custom:hover,
.btn-outline-custom:focus-visible {
  background: var(--color-dark);
  color: #fff;
}

.section-dark .btn-outline-custom {
  border-color: var(--color-border-light);
  color: #fff;
}
.section-dark .btn-outline-custom:hover { background: #fff; color: var(--color-dark); }

/* Text link with a rule that draws in from the left. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 13.2px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-dark);
}
.link-arrow i { transition: transform var(--t-fast); }
.link-arrow:hover i { transform: translateX(5px); }


/* 05 ---------------------------------------------------------- forms */

.form-field { margin-bottom: 20px; }

.form-field label {
  display: block;
  margin-bottom: 9px;
  color: var(--color-dark);
  font-size: 12.1px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.form-field input,
.form-field select,
.form-field textarea {
  width: 100%;
  padding: 14px 16px;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-dark);
  font-size: 17.05px;
  line-height: 1.5;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.form-field textarea { min-height: 132px; resize: vertical; }

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  outline: none;
  border-color: var(--color-accent);
  background: rgba(255, 255, 255, 0.5);
}

.form-field input::placeholder,
.form-field textarea::placeholder { color: rgba(37, 37, 34, 0.35); }

.form-field select {
  appearance: none;
  /* Chevron drawn in CSS so the form needs no icon font to stay legible. */
  background-image: linear-gradient(45deg, transparent 50%, var(--color-muted) 50%),
                    linear-gradient(135deg, var(--color-muted) 50%, transparent 50%);
  background-position: calc(100% - 20px) 55%, calc(100% - 14px) 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 44px;
}

.form-field .field-error {
  display: none;
  margin-top: 7px;
  color: #9A3412;
  font-size: 13.75px;
  letter-spacing: 0.01em;
}
.form-field.has-error input,
.form-field.has-error select,
.form-field.has-error textarea { border-color: #9A3412; }
.form-field.has-error .field-error { display: block; }

.form-note {
  margin-top: 18px;
  font-size: 14.3px;
  line-height: 1.6;
  color: var(--color-muted);
}

.form-status {
  display: none;
  margin-top: 20px;
  padding: 15px 18px;
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-sm);
  background: rgba(107, 115, 92, 0.08);
  color: var(--color-dark);
  font-size: 15.95px;
}
.form-status.is-visible { display: block; }


/* 06 ------------------------------------------------------------ header */

.site-header {
  /* Absolute, not fixed: the header sits at the top of the page and scrolls
     away with it.

     Not `static` — the home page header is transparent ON TOP of the hero
     image, and taking it out of the overlay would push the hero down by
     150px and leave white type on a white bar. Absolute keeps exactly the
     design that was approved and removes only the following-you-down-the-
     page behaviour. */
  position: absolute;
  inset: 0 0 auto;
  z-index: 1030;
  height: var(--header-h);
  display: flex;
  align-items: center;
  border-bottom: 1px solid transparent;
  transition: background var(--t-fast), border-color var(--t-fast), height var(--t-fast);
}

/* Over the hero image: transparent, white type. */
.site-header--over { background: transparent; }
.site-header--over .brand,
.site-header--over .nav-list a { color: #fff; }
.site-header--over .brand small { color: rgba(255, 255, 255, 0.65); }
.site-header--over .nav-toggle span { background: #fff; }

/* Scrolled, or any page without a transparent hero. */
.site-header.is-solid {
  height: 138px;
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: blur(10px);
  border-bottom-color: var(--color-border-soft);
}
.site-header.is-solid .brand,
.site-header.is-solid .nav-list a { color: var(--color-dark); }
.site-header.is-solid .brand small { color: var(--color-muted); }
.site-header.is-solid .nav-toggle span { background: var(--color-dark); }

.header-inner {
  width: 100%;
  max-width: var(--container-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.brand {
  display: inline-flex;
  flex-direction: column;
  gap: 3px;
  color: var(--color-dark);
  font-size: 20.9px;
  font-weight: 500;
  letter-spacing: 0.24em;
  line-height: 1;
  text-transform: uppercase;
}
.brand small {
  font-size: 9.35px;
  font-weight: 400;
  letter-spacing: 0.3em;
  color: var(--color-muted);
}
.brand:hover { color: inherit; opacity: 0.75; }

/* The supplied logo is a stacked lockup — emblem over PRAVASTH over the
   "Signature Living" line — at 1189x1093, so very nearly square rather than
   the wide wordmark a header usually carries.

   Because it is stacked, the emblem takes the top half and the wordmark and
   tagline share what is left. At the old 92px the tagline rendered about
   five pixels tall and read as a smudge. It becomes legible at around 126px,
   which is what drives both the logo height and the header height below it.

   A horizontal lockup — emblem beside the wordmark — would give a far larger
   wordmark at half this header height. Worth asking the client for one. */
.brand__logo {
  display: block;
  height: 126px;
  width: auto;
  object-fit: contain;
}
.site-header.is-solid .brand__logo { height: 112px; }

/* Over the banner the header sits on the photograph, and the logo is a gold
   lockup on transparency — it needs no plate behind it, only the same slight
   lift the navigation already has. Dropped once the header goes solid, where
   a shadow over white would only look like a printing fault. */
.site-header--over:not(.is-solid) .brand__logo {
  filter: drop-shadow(0 1px 8px rgba(0, 0, 0, 0.35));
}

@media (max-width: 720px) {
  .brand__logo,
  .site-header.is-solid .brand__logo { height: 96px; }
}

.nav-list {
  display: flex;
  align-items: center;
  gap: clamp(20px, 2.6vw, 40px);
  margin: 0;
  padding: 0;
  list-style: none;
}
.nav-list a {
  position: relative;
  display: inline-block;
  padding: 8px 0;
  color: var(--color-dark);
  /* 13.2px + 10%. The letter-spacing below is in em, so it scales with the
     type and the words keep their proportions rather than tightening up. */
  font-size: 14.52px;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

/* Two rules under each item: a hairline that is always there at low opacity,
   and the accent rule that wipes across it from the left on hover. The item
   never changes width or weight, so the row does not shift as you move along
   it - the commonest way an underline hover goes wrong. */
.nav-list a::before,
.nav-list a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
}
.nav-list a::before {
  width: 100%;
  background: currentColor;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.nav-list a::after {
  width: 100%;
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.34s var(--ease);
}
.nav-list a:hover::before { opacity: 0.22; }
.nav-list a:hover::after,
.nav-list a:focus-visible::after { transform: scaleX(1); }

/* The current page keeps the accent rule and a dot in front of it, so it
   reads as "you are here" rather than as a link that happens to be hovered. */
.nav-list a[aria-current="page"]::after { transform: scaleX(1); }

/* ...but only one rule at a time. The current-page rule used to stay put, so
   hovering any other item drew a SECOND line and the bar read as though two
   pages were open at once. While the pointer is in the nav, the current page
   retracts its rule - wiping out to the right, so the line reads as travelling
   towards whatever you are pointing at - and takes it back the moment you
   leave. :not(:hover) so that pointing at the current page itself keeps it.

   Guarded by (hover: hover): on a touch screen :hover latches after a tap and
   the "you are here" mark would vanish for the rest of the visit. */
@media (hover: hover) {
  .nav-list:hover a[aria-current="page"]:not(:hover)::after,
  .nav-list:focus-within a[aria-current="page"]:not(:focus-visible):not(:hover)::after {
    transform: scaleX(0);
    transform-origin: right;
  }
}
.site-header .nav-list a[aria-current="page"] { color: var(--color-accent-on-dark); }

/* Over the hero the whole bar is white, so the current page is marked in
   white rather than olive, which would disappear against the photograph. */
.site-header--over:not(.is-solid) .nav-list a[aria-current="page"] { color: #fff; }
.site-header--over:not(.is-solid) .nav-list a[aria-current="page"]::after { background: #fff; }

.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.nav-toggle span {
  display: block;
  width: 22px;
  height: 1.5px;
  background: var(--color-dark);
  transition: transform var(--t-fast), opacity var(--t-fast);
}
body.nav-open .nav-toggle span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
body.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
body.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }


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

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: var(--color-dark);
}
.hero__media { position: absolute; inset: 0; }
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Slow settle on load - the only movement in the hero. */
  animation: heroSettle 2.4s var(--ease) both;
}
@keyframes heroSettle {
  from { transform: scale(1.07); }
  to   { transform: scale(1); }
}
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
              rgba(20, 20, 18, 0.46) 0%,
              rgba(20, 20, 18, 0.12) 38%,
              rgba(20, 20, 18, 0.72) 100%);
}

.hero__inner {
  position: relative;
  width: 100%;
  max-width: var(--container-width);
  margin-inline: auto;
  padding: 0 var(--gutter) clamp(56px, 8vh, 110px);
  color: #fff;
}
.hero__label {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 26px;
  color: rgba(255, 255, 255, 0.8);
  font-size: 12.1px;
  font-weight: 500;
  letter-spacing: 0.26em;
  text-transform: uppercase;
}
.hero__label::before {
  content: "";
  width: 34px;
  height: 1px;
  background: currentColor;
}
.hero__title {
  margin: 0;
  color: #fff;
  font-size: clamp(2.86rem, 9.02vw, 5.7rem);
  font-weight: 700;
  line-height: 0.98;
  letter-spacing: -0.04em;
}
.hero__status {
  margin-top: 26px;
  padding-top: 24px;
  border-top: 1px solid rgba(255, 255, 255, 0.22);
  display: flex;
  align-items: baseline;
  gap: 16px;
  color: rgba(255, 255, 255, 0.82);
  font-size: 14.3px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  max-width: 560px;
}


/* Inner-page hero: shorter, image behind, sits under the solid header. */
.page-hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: clamp(380px, 56vh, 620px);
  padding-top: var(--header-h);
  background: var(--color-dark);
  overflow: hidden;
}
.page-hero__media { position: absolute; inset: 0; }
.page-hero__media img { width: 100%; height: 100%; object-fit: cover; }
.page-hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20,20,18,0.5) 0%, rgba(20,20,18,0.28) 45%, rgba(20,20,18,0.78) 100%);
}
.page-hero__inner {
  position: relative;
  width: 100%;
  max-width: var(--container-width);
  margin-inline: auto;
  padding: 0 var(--gutter) clamp(44px, 6vw, 76px);
  color: #fff;
}
.page-hero__title {
  margin: 0;
  color: #fff;
  font-size: clamp(2.31rem, 5.72vw, 4.62rem);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.035em;
  max-width: 20ch;
}
.page-hero__sub {
  max-width: 58ch;
  margin-top: 20px;
  color: rgba(255, 255, 255, 0.78);
  font-size: 1.169rem;
  line-height: 1.75;
}

.breadcrumb-x {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
  padding: 0;
  list-style: none;
  font-size: 12.1px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
}
.breadcrumb-x a { color: rgba(255, 255, 255, 0.82); }
.breadcrumb-x a:hover { color: #fff; }
.breadcrumb-x li + li::before { content: "/"; margin-right: 10px; opacity: 0.5; }


/* 08 ------------------------------------------------------------- about */

.about-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(36px, 6vw, 96px);
  /* stretch, not center: the photograph is asked to end exactly where the
     copy ends, so both columns have to take the full row height. */
  align-items: stretch;
}

/* The copy is justified. Justified text without hyphenation opens rivers of
   white space at this measure, so hyphens are on; text-wrap: pretty keeps the
   last line from stranding a single word. */
.about-grid .section-description {
  max-width: none;   /* 62ch would leave the right edge short of the image */
  text-align: justify;
  hyphens: auto;
  text-wrap: pretty;
}

.about-grid__media { position: relative; height: 100%; }

/* The About photograph is a wide, symmetrical arrival-court shot (1672x941).
   Forcing it to the full height of the copy beside it keeps only 23% of its
   width - the trees, the timber screens and the building all crop away. So
   this column keeps the picture whole and centres it against the text
   instead. Swap .about-grid--wide off the section to get the full-height
   crop back. */
/* A wider picture column needs less vertical crop to reach the same height,
   and the copy is short enough now to take the narrower half without
   crowding. 1fr/1fr kept under half the photograph; this keeps noticeably
   more of it for the same matched height. */
.about-grid--wide { grid-template-columns: 0.85fr 1.15fr; }


.about-grid--wide .about-grid__media {
  height: 100%;
  align-self: stretch;
}
.about-grid--wide .about-grid__media img {
  height: 100%;
  min-height: 0;
  aspect-ratio: auto;
  object-fit: cover;
}

/* Between 992 and 1100 the two columns are narrow, so the copy runs tall and
   the matched-height crop got severe — a third of the photograph left. Stack
   instead over that band: a full-width 4/3 crop keeps three quarters of it.
   The height match resumes above 1100, where the columns are wide enough for
   it to cost less. */
@media (max-width: 1099.98px) {
  .about-grid--wide { grid-template-columns: 1fr; }
  .about-grid--wide .about-grid__media { height: auto; align-self: auto; }
  .about-grid--wide .about-grid__media img { height: auto; aspect-ratio: 4 / 3; }
}

/* No fixed aspect ratio here: the frame is as tall as the text beside it and
   the photograph crops to fill it. min-height stops a short intro (the
   Developers page) from squashing the picture into a letterbox. */
.about-grid__media img {
  width: 100%;
  height: 100%;
  min-height: 420px;
  object-fit: cover;
}

/* A thin olive frame offset behind the photograph. */
.about-grid__media::before {
  content: "";
  position: absolute;
  inset: -18px -18px 34px 34px;
  border: 1px solid var(--color-border);
  z-index: -1;
}

/* Animated open/close. The grid 0fr-to-1fr trick is tidier, but the fr unit
   does not resolve while the transition is running in every engine - it sat at
   0px and the panel never opened - so main.js animates an explicit height and
   hands it back to auto once the transition ends. */
.read-more-body {
  height: 0;
  overflow: hidden;
  transition: height var(--t-slow);
}
.read-more-body p:first-child { padding-top: 1.15em; }

.read-more-toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 26px;
  padding: 0;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  padding-bottom: 6px;
  color: var(--color-dark);
  font-size: 12.65px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color var(--t-fast), color var(--t-fast);
}
.read-more-toggle:hover { color: var(--color-accent-ink); border-color: var(--color-accent-ink); }
.read-more-toggle i { font-size: 14.3px; transition: transform var(--t-fast); }
.read-more-toggle[aria-expanded="true"] i { transform: rotate(180deg); }


/* 09 ------------------------------------------------------ split panels */

.split-panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.split-panel {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: clamp(420px, 62vh, 700px);
  padding: clamp(28px, 4vw, 56px);
  overflow: hidden;
  color: #fff;
}
.split-panel__media { position: absolute; inset: 0; }
.split-panel__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.1s var(--ease);
}
.split-panel__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20,20,18,0.14) 0%, rgba(20,20,18,0.72) 100%);
  transition: background var(--t-slow);
}
.split-panel:hover .split-panel__media img { transform: scale(1.06); }
.split-panel:hover .split-panel__media::after {
  background: linear-gradient(180deg, rgba(20,20,18,0.24) 0%, rgba(20,20,18,0.8) 100%);
}
.split-panel__body { position: relative; width: 100%; }
.split-panel__title {
  margin: 0 0 10px;
  color: #fff;
  font-size: clamp(1.76rem, 3.08vw, 2.75rem);
  font-weight: 700;
  letter-spacing: -0.025em;
}
.split-panel__text {
  max-width: 34ch;
  margin: 0;
  color: rgba(255, 255, 255, 0.78);
  font-size: 17.05px;
  line-height: 1.7;
}
.split-panel__cta {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.25);
  color: #fff;
  font-size: 12.1px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.split-panel:hover .split-panel__cta i { transform: translateX(6px); }
.split-panel__cta i { transition: transform var(--t-fast); }
/* The whole panel is the link target; the visible CTA carries the label. */
.split-panel__link {
  position: absolute;
  inset: 0;
  z-index: 2;
}


/* 10 ---------------------------------------------------------- gallery */

.gallery__stage {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--color-surface);
  overflow: hidden;
}
.gallery__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.75s var(--ease), visibility 0.75s;
}
.gallery__slide.is-active { opacity: 1; visibility: visible; }
.gallery__slide img { width: 100%; height: 100%; object-fit: cover; }

.gallery__caption {
  position: absolute;
  left: 0;
  bottom: 0;
  right: 0;
  padding: clamp(20px, 3vw, 40px);
  background: linear-gradient(180deg, transparent, rgba(20,20,18,0.72));
  color: #fff;
}
.gallery__caption strong {
  display: block;
  font-size: clamp(1.155rem, 1.87vw, 1.54rem);
  font-weight: 500;
  letter-spacing: -0.01em;
}
.gallery__caption span {
  display: block;
  margin-top: 5px;
  font-size: 12.1px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
}

.gallery__controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  margin-top: 24px;
}
.gallery__count {
  font-size: 13.2px;
  letter-spacing: 0.18em;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
.gallery__count b { color: var(--color-dark); font-weight: 500; }

.gallery__nav { display: flex; gap: 10px; }
.gallery__btn {
  width: 52px;
  height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-dark);
  font-size: 18.7px;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.gallery__btn:hover {
  background: var(--color-dark);
  border-color: var(--color-dark);
  color: #fff;
}

.gallery__dots { display: flex; gap: 8px; }
.gallery__dot {
  width: 30px;
  height: 2px;
  padding: 0;
  background: var(--color-border);
  border: 0;
  cursor: pointer;
  transition: background var(--t-fast);
}
.gallery__dot.is-active { background: var(--color-dark); }


/* 11 ------------------------------------------------------ statistics */

/* The figures carry themselves; the rules that used to box each one are
   gone. Spacing does the separating now - hence the column gap, and the
   padding-inline drops to 0 so the first figure lines up with the heading
   above it instead of sitting one pad-width in. */
.stats-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(28px, 3.4vw, 56px);
}
.stat-item { padding: clamp(18px, 2.4vw, 30px) 0; }
.stat-item__value {
  display: flex;
  align-items: baseline;
  gap: 4px;
  color: var(--color-dark);
  font-size: clamp(2.53rem, 4.84vw, 4.29rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
}
.stat-item__value small {
  font-size: 0.352em;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-accent-ink);
}
.stat-item__label {
  margin: 16px 0 0;
  font-size: 13.75px;
  line-height: 1.6;
  letter-spacing: 0.06em;
  color: var(--color-muted);
  max-width: 22ch;
}
.section-dark .stat-item__value { color: #fff; }
.section-dark .stat-item__value small { color: var(--color-taupe); }
.section-dark .stat-item__label { color: rgba(255, 255, 255, 0.58); }

/* Five metrics on the Developers page. */
.stats-row--5 { grid-template-columns: repeat(5, 1fr); }


/* 12 --------------------------------------------------- testimonials */

/* The height follows the card on screen, set by main.js.
   The cards differ enormously in length — one runs to about 1,400 characters
   and the others to a third of that — and a flex row is as tall as its
   tallest item, so every slide was padded out to the longest one and the
   prev/next buttons ended up a screen below the short ones. */
.testimonial-viewport {
  overflow: hidden;
  transition: height 0.45s var(--ease);
}
.testimonial-track {
  display: flex;
  /* Cards size to their own content rather than stretching to the row, so a
     card's measured height is its real one. */
  align-items: flex-start;
  transition: transform 0.65s var(--ease);
}
.testimonial-card {
  flex: 0 0 100%;
  padding-right: 0;
}
/* Sized for a real testimonial, not a one-line placeholder. The demo quotes
   this replaced were ~100 characters; the approved ones run from 330 to about
   1,400, and at the old 2.2rem over a 34ch measure even the short one filled
   ten lines. Set as running text rather than as a display pull-quote, which
   is the only way the longest one is readable. */
.testimonial-card__quote {
  max-width: 62ch;
  margin: 0;
  color: var(--color-dark);
  font-size: clamp(0.9375rem, 1.05vw, 1.0625rem);
  font-weight: 300;
  line-height: 1.72;
  letter-spacing: 0;
}
/* The longest testimonial arrives as several paragraphs; keeping them apart
   is most of what makes it readable at this length. */
.testimonial-card__quote p {
  margin: 0 0 0.9em;
}
.testimonial-card__quote p:last-child {
  margin-bottom: 0;
}
.testimonial-card__quote::before {
  content: "\201C";
  display: block;
  margin-bottom: 6px;
  color: var(--color-taupe);
  font-size: 3.74em;
  line-height: 0.6;
}
.testimonial-card__meta {
  margin-top: 30px;
  padding-top: 20px;
  border-top: 1px solid var(--color-border);
  max-width: 46ch;
  display: flex;
  align-items: center;
  gap: 16px;
}
/* The photo is optional: a card whose image is missing (or has not been
   supplied yet) has it hidden by main.js, and the flex gap collapses so the
   name sits where it would have anyway. */
.testimonial-card__photo {
  flex: 0 0 64px;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  /* Clips the scaled image inside to the circle. */
  overflow: hidden;
  display: block;
  background: var(--color-border);
}
/*
 * The photos are phone portraits, not head-shots: the face is small and high
 * in the frame. object-fit alone cannot fix that — on a 1060x1280 photo in a
 * square box only about 17% of the height is ever cropped, so object-position
 * has almost no room to move and every value looks the same. Scaling the
 * image inside a clipping wrapper is what actually crops to the face.
 *
 * Tuned per photo, because each one is framed differently: set --photo-zoom
 * and --photo-y on the wrapper. The defaults suit a half-body portrait.
 */
.testimonial-card__photo img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  transform: scale(var(--photo-zoom, 1.8));
  transform-origin: 50% var(--photo-y, 24%);
}
.testimonial-card__name {
  color: var(--color-dark);
  font-size: 15.95px;
  font-weight: 500;
  letter-spacing: 0.02em;
}
.testimonial-card__role {
  margin-top: 3px;
  font-size: 12.1px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-accent-ink);
}

/* Anything the client still has to supply is marked, not quietly invented. */
.placeholder-flag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 20px;
  padding: 5px 11px;
  border: 1px dashed var(--brand-bronze);
  border-radius: var(--radius-sm);
  color: var(--color-accent-ink);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  background: rgba(166, 154, 135, 0.1);
}


/* 13 --------------------------------------------------- project teaser */

.project-card {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: clamp(440px, 68vh, 720px);
  padding: clamp(28px, 4vw, 64px);
  overflow: hidden;
  color: #fff;
}
.project-card__media { position: absolute; inset: 0; }
.project-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.4s var(--ease);
}
.project-card:hover .project-card__media img { transform: scale(1.04); }
.project-card__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20,20,18,0.3) 0%, rgba(20,20,18,0.16) 40%, rgba(20,20,18,0.8) 100%);
}
.project-card__body { position: relative; max-width: 46ch; }
.project-card__tag {
  display: inline-block;
  margin-bottom: 18px;
  padding: 7px 14px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: var(--radius-sm);
  font-size: 11.55px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}
.project-card__title {
  margin: 0 0 14px;
  color: #fff;
  font-size: clamp(2.2rem, 4.84vw, 3.74rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.05;
}
.project-card__text {
  margin: 0;
  color: rgba(255, 255, 255, 0.8);
  font-size: 1.1rem;
  letter-spacing: 0.02em;
}


/* 14 -------------------------------------------- interiors: solutions */

.solution-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(32px, 5vw, 84px);
  align-items: center;
}
.solution-row + .solution-row { margin-top: clamp(52px, 7vw, 110px); }
.solution-row--flip .solution-row__media { order: 2; }
.solution-row__media img { width: 100%; aspect-ratio: 5 / 4; object-fit: cover; }
.solution-row__index {
  display: block;
  margin-bottom: 14px;
  /* Was --color-taupe. Taupe is Stone Beige now and these numerals sit on a
     Stone Beige band — same colour, measured at 1.00:1, invisible. */
  color: var(--color-accent-ink);
  font-size: 13.2px;
  font-weight: 500;
  letter-spacing: 0.2em;
  font-variant-numeric: tabular-nums;
}

.solution-tagline {
  margin-top: clamp(48px, 6vw, 88px);
  padding-top: clamp(32px, 4vw, 52px);
  border-top: 1px solid var(--color-border-soft);
  text-align: center;
  color: var(--color-dark);
  font-size: clamp(1.155rem, 2.2vw, 1.705rem);
  font-weight: 300;
  letter-spacing: 0.01em;
}

/* services grid */
.service-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(20px, 2.4vw, 34px);
}
.service-card {
  display: flex;
  flex-direction: column;
  background: transparent;
}
.service-card__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--color-surface);
}
.service-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1s var(--ease);
}
.service-card:hover .service-card__media img { transform: scale(1.055); }
.service-card__num {
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 1;
  padding: 5px 10px;
  background: rgba(20, 20, 18, 0.55);
  color: #fff;
  font-size: 11.55px;
  letter-spacing: 0.16em;
  font-variant-numeric: tabular-nums;
}
.service-card__title {
  margin: 22px 0 10px;
  font-size: 1.32rem;
  font-weight: 700;
  letter-spacing: -0.012em;
}
.service-card__text {
  margin: 0;
  font-size: 16.23px;
  line-height: 1.72;
}


/* process timeline */
.process-track {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(18px, 2vw, 32px);
  position: relative;
}
.process-track::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 13px;
  height: 1px;
  background: var(--color-border);
}
.process-step { position: relative; padding-top: 44px; }
.process-step::before {
  content: "";
  position: absolute;
  top: 8px;
  left: 0;
  width: 11px;
  height: 11px;
  border: 1px solid var(--color-accent);
  background: var(--color-bg);
  border-radius: 50%;
}
.section-surface .process-step::before { background: var(--color-surface); }
.process-step__num {
  display: block;
  margin-bottom: 10px;
  color: var(--color-accent-ink);
  font-size: 13.2px;
  font-weight: 500;
  letter-spacing: 0.2em;
  font-variant-numeric: tabular-nums;
}
.process-step__title {
  margin: 0 0 9px;
  font-size: 1.265rem;
  font-weight: 700;
}
.process-step__text { margin: 0; font-size: 15.95px; line-height: 1.7; }

/* FAQ accordion - Bootstrap's, restyled square and quiet */
.accordion-x .accordion-item {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--color-border-soft);
  border-radius: 0;
}
.accordion-x .accordion-button {
  padding: 26px 0;
  background: transparent;
  border: 0;
  box-shadow: none;
  color: var(--color-dark);
  font-size: clamp(1.1rem, 1.65vw, 1.32rem);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.4;
}
.accordion-x .accordion-button:not(.collapsed) { color: var(--color-accent-ink); background: transparent; }
.accordion-x .accordion-button:focus { box-shadow: none; }
.accordion-x .accordion-button::after {
  width: 14px;
  height: 14px;
  background-size: 14px;
  /* A thin chevron in the brand olive, rather than Bootstrap's blue. */
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236B735C' stroke-width='1.6'%3e%3cpath d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
}
.accordion-x .accordion-body {
  padding: 0 0 28px;
  max-width: 78ch;
  font-size: 17.05px;
  line-height: 1.8;
  color: var(--color-muted);
}


/* 15 -------------------------------------------------------------- blog */

.blog-feature {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: clamp(28px, 4vw, 64px);
  align-items: center;
  padding-bottom: clamp(44px, 5vw, 78px);
  border-bottom: 1px solid var(--color-border-soft);
}
.blog-feature__media { display: block; overflow: hidden; }
.blog-feature__media img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; transition: transform 1s var(--ease); }
.blog-feature:hover .blog-feature__media img { transform: scale(1.04); }

.post-meta {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-bottom: 14px;
  font-size: 12.1px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.post-meta .post-cat { color: var(--color-accent-ink); }
.post-meta span + span::before { content: "—"; margin-right: 14px; opacity: 0.5; }

.post-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 3vw, 44px);
  margin-top: clamp(44px, 5vw, 78px);
}
.post-card__media { display: block; overflow: hidden; aspect-ratio: 3 / 2; background: var(--color-surface); }
.post-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform 1s var(--ease); }
.post-card:hover .post-card__media img { transform: scale(1.05); }
.post-card__title a { color: inherit; }
.post-card:hover .post-card__title a { color: var(--color-accent-ink); }
.post-card__title {
  margin: 18px 0 10px;
  font-size: 1.32rem;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: -0.012em;
}
.post-card__text { margin: 0 0 18px; font-size: 16.23px; line-height: 1.7; }


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

.contact-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(36px, 5vw, 88px);
}
.contact-list { margin: 0; padding: 0; list-style: none; }
.contact-list li {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  padding: 22px 0;
  border-bottom: 1px solid var(--color-border-soft);
}
.contact-list li:first-child { padding-top: 0; }
.c-icon {
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-accent-ink);
  font-size: 17.6px;
}
.contact-list dt,
.contact-list .c-label {
  margin-bottom: 7px;
  color: var(--color-accent-ink);
  font-size: 11.55px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.contact-list .c-value {
  color: var(--color-dark);
  font-size: 1.169rem;
  line-height: 1.6;
}
.contact-list .c-value a { color: var(--color-dark); }
.contact-list .c-value a:hover { color: var(--color-accent-ink); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

.map-frame {
  position: relative;
  aspect-ratio: 21 / 9;
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
}
.map-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.map-frame__note { max-width: 44ch; font-size: 15.4px; color: var(--color-muted); }

/* .social-row / .social-link were replaced by .social-tiles above. */


/* 17 ---------------------------------------------------- enquiry block */

.enquiry-section { position: relative; overflow: hidden; }
.enquiry-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(36px, 5vw, 90px);
  align-items: center;
}
.enquiry-section__media { position: relative; height: 100%; min-height: 320px; }
.enquiry-section__media img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 5; }

.form-row-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 20px;
}


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

/* The footer carries the secondary brand colour, Deep Bronze Brown.

   That is a much lighter surface than the charcoal it replaced, so the text
   opacities had to come up with it: white at 60% over bronze measures
   3.26:1, which is below the 4.5:1 body-text minimum. Warm Ivory at full
   strength makes 5.26:1 and belongs to the palette. */
.site-footer {
  background: var(--color-dark-2);
  color: rgba(245, 241, 233, 0.92);
  padding-top: clamp(56px, 7vw, 96px);
  border-top: 1px solid var(--color-border-light);
}
.site-footer h4, .site-footer .brand { color: var(--brand-ivory); }
.site-footer a { color: rgba(245, 241, 233, 0.92); }
.site-footer a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

.footer-grid {
  display: grid;
  /* Three columns: brand, Company, Get in Touch.

     There is a fourth child in the markup — .footer-social-col, hidden rather
     than deleted. Declaring a fourth track here is what left 270px dead at
     the right edge: an explicit track is created whether or not anything
     occupies it, and a display:none child occupies nothing. Three tracks for
     the three columns that show.

     Restoring Follow Us? Add a fourth track back (1.6fr 1fr 1.2fr 1fr) and
     drop .section-hidden from .footer-social-col on every page. */
  grid-template-columns: 2fr 1fr 1.2fr;
  gap: clamp(30px, 4vw, 70px);
  padding-bottom: clamp(44px, 5vw, 72px);
}
.footer-title {
  margin: 0 0 22px;
  color: var(--brand-ivory);
  font-size: 12.1px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
/* The footer used to set the name as type. It now carries the logo, which
   already contains the wordmark and "Signature Living" — so the <small>
   line that used to sit under it would be saying the same thing twice. */
.footer-brand {
  display: inline-block;
  margin-bottom: 20px;
}
.footer-logo {
  display: block;
  /* Taller than the 126px header mark would be here: the footer column is
     wide and the logo is the only thing identifying the company on a page
     someone may have scrolled to directly. */
  height: 104px;
  width: auto;
  object-fit: contain;
}
/* The mark is gold and the footer is bronze, so it starts out muddy against
   its own background. Measuring the rasterised logo's mean luminance against
   the footer colour: untouched it is 1.83:1, and the ivory wordmark it
   replaces was 5.27:1 — which is why the plain image read as washed out.

   brightness(1.9) brings it to 4.09:1, close to the type it sits beside,
   while keeping the metallic gradient. filter:invert() would score higher
   still (5.92:1) but flattens the mark into a white silhouette and throws
   the gold away. */
.footer-logo { filter: brightness(1.9) saturate(0.9); }

/* The footer link rule underlines on hover; on an image that just draws a
   line under the logo. */
.site-footer a.footer-brand:hover { text-decoration: none; }
.footer-about { max-width: 42ch; font-size: 16.23px; line-height: 1.8; }

/* The Company column is centred, heading and links together, so it reads as
   a deliberate choice rather than a stray indent. It goes back to the left
   with the other columns once the grid stacks on a phone — a centred column
   in a single-column stack just looks like a mistake. */
.footer-col--center { text-align: center; }
@media (max-width: 767.98px) {
  .footer-col--center { text-align: left; }
}

.footer-links { margin: 0; padding: 0; list-style: none; }
.footer-links li { margin-bottom: 12px; }
.footer-links a { font-size: 15.95px; }

.footer-contact { margin: 0; padding: 0; list-style: none; font-size: 15.95px; line-height: 1.7; }
.footer-contact li {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  margin-bottom: 18px;
}
.footer-contact .f-label {
  display: block;
  margin-bottom: 2px;
  color: var(--brand-ivory);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* A thin square chip rather than a bare glyph: it gives the three rows a
   common left edge and keeps the icons from floating in the text. */
.f-icon {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  margin-top: 2px;
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-sm);
  color: var(--brand-ivory);
  font-size: 15.4px;
}

/* Social: icon-only tiles. Hover wipes a fill upward behind the glyph. */
.social-tiles { display: flex; flex-wrap: wrap; gap: 8px; }
.social-tiles .social-tile {
  position: relative;
  overflow: hidden;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-sm);
  color: rgba(255, 255, 255, 0.72);
  font-size: 17.6px;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.social-tiles .social-tile::before {
  content: "";
  position: absolute;
  inset: 0;
  background: #fff;
  transform: translateY(101%);
  transition: transform 0.34s var(--ease);
}
.social-tiles .social-tile i { position: relative; z-index: 1; }
.social-tiles .social-tile:hover,
.social-tiles .social-tile:focus-visible { color: var(--color-dark); border-color: #fff; }
.social-tiles .social-tile:hover::before,
.social-tiles .social-tile:focus-visible::before { transform: translateY(0); }

/* Same tile on a light background - the fill goes dark instead of white. */
.social-tiles--light .social-tile {
  border-color: var(--color-border);
  color: var(--color-dark);
}
.social-tiles--light .social-tile::before { background: var(--color-dark); }
.social-tiles--light .social-tile:hover,
.social-tiles--light .social-tile:focus-visible { color: #fff; border-color: var(--color-dark); }

.footer-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding-block: 26px;
  border-top: 1px solid var(--color-border-light);
  font-size: 14.3px;
}
.footer-bottom p { margin: 0; }


/* 19 ------------------------------------------------------------ motion */

/* Scroll reveal: applied by main.js so content is never hidden when JS is off. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.85s var(--ease), transform 0.85s var(--ease);
}
.js [data-reveal].is-visible { opacity: 1; transform: none; }
.js [data-reveal][data-reveal-delay="1"] { transition-delay: 0.09s; }
.js [data-reveal][data-reveal-delay="2"] { transition-delay: 0.18s; }
.js [data-reveal][data-reveal-delay="3"] { transition-delay: 0.27s; }

/* Image reveal: the photograph uncovers itself rather than fading in. */
.image-reveal { overflow: hidden; }
.js .image-reveal img {
  clip-path: inset(0 0 100% 0);
  transform: scale(1.08);
  transition: clip-path 1.1s var(--ease), transform 1.4s var(--ease);
}
.js .image-reveal.is-visible img { clip-path: inset(0 0 0 0); transform: scale(1); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .js [data-reveal] { opacity: 1; transform: none; }
  .js .image-reveal img { clip-path: none; transform: none; }
}


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

@media (max-width: 1199.98px) {
  .footer-grid { grid-template-columns: 1.4fr 1fr 1.2fr; }
  .footer-social-col { grid-column: 2 / -1; }
  .service-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 991.98px) {
  :root { --header-h: 120px; }

  /* Navigation becomes a full-height panel. */
  .nav-toggle { display: inline-flex; }
  .site-nav {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(380px, 86vw);
    padding: calc(var(--header-h) + 28px) clamp(24px, 6vw, 44px) 40px;
    background: var(--color-bg);
    border-left: 1px solid var(--color-border-soft);
    transform: translateX(100%);
    transition: transform 0.42s var(--ease);
    overflow-y: auto;
  }
  body.nav-open .site-nav { transform: translateX(0); }
  .nav-list {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    counter-reset: navitem;
  }
  .nav-list li {
    width: 100%;
    border-bottom: 1px solid var(--color-border-soft);
    /* Each row slides in a beat after the one above it. The delay is on the
       open state only, so closing the panel is immediate. */
    opacity: 0;
    transform: translateX(16px);
    transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
  }
  body.nav-open .nav-list li { opacity: 1; transform: none; }
  body.nav-open .nav-list li:nth-child(1) { transition-delay: 0.10s; }
  body.nav-open .nav-list li:nth-child(2) { transition-delay: 0.16s; }
  body.nav-open .nav-list li:nth-child(3) { transition-delay: 0.22s; }
  body.nav-open .nav-list li:nth-child(4) { transition-delay: 0.28s; }
  body.nav-open .nav-list li:nth-child(5) { transition-delay: 0.34s; }

  .nav-list a {
    display: flex;
    align-items: baseline;
    gap: 16px;
    width: 100%;
    padding: 20px 0;
    color: var(--color-dark);
    font-size: 16.5px;
    letter-spacing: 0.12em;
  }
  /* 01, 02, 03 … in the accent colour, the way the process steps are numbered
     elsewhere on the site. */
  .nav-list a::before {
    counter-increment: navitem;
    content: counter(navitem, decimal-leading-zero);
    position: static;
    width: auto;
    height: auto;
    background: none;
    opacity: 1;
    color: var(--color-muted);
    font-size: 11.55px;
    letter-spacing: 0.16em;
    font-variant-numeric: tabular-nums;
  }
  /* The sliding rule belongs to the desktop row; on a full-width tap target it
     would stretch across the whole panel. */
  .nav-list a::after { display: none; }
  .nav-list a[aria-current="page"] { color: var(--color-accent-ink); }
  .nav-list a[aria-current="page"]::before { color: var(--color-accent-ink); }
  .site-header--over .nav-list a,
  .site-header.is-solid .nav-list a { color: var(--color-dark); }
  .site-header--over .nav-list a[aria-current="page"],
  .site-header.is-solid .nav-list a[aria-current="page"] { color: var(--color-accent-ink); }
  .nav-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(20, 20, 18, 0.45);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--t-fast), visibility var(--t-fast);
  }
  body.nav-open .nav-backdrop { opacity: 1; visibility: visible; }
  body.nav-open { overflow: hidden; }

  .about-grid,
  .enquiry-grid,
  .solution-row,
  .blog-feature,
  .contact-grid { grid-template-columns: 1fr; }

  .solution-row--flip .solution-row__media { order: 0; }
  .about-grid__media { order: -1; }
  .about-grid__media::before { display: none; }
  .about-grid__media img { aspect-ratio: 4 / 3; height: auto; min-height: 0; }

  /* The --wide rules set height:100% for the desktop height match. Stacked,
     the row has no height of its own to fill, so that resolved to 0 and the
     photograph vanished. Give the ratio back below the breakpoint. */
  .about-grid--wide { grid-template-columns: 1fr; }
  .about-grid--wide .about-grid__media { height: auto; align-self: auto; }
  .about-grid--wide .about-grid__media img { height: auto; aspect-ratio: 4 / 3; }
  /* Justified copy needs a wide measure; at phone widths it produces gaps. */
  .about-grid .section-description { text-align: left; hyphens: manual; }
  .enquiry-section__media { order: -1; }
  .enquiry-section__media img { aspect-ratio: 16 / 9; }

  .split-panels { grid-template-columns: 1fr; }
  .split-panel { min-height: 62vh; }

  .stats-row { grid-template-columns: repeat(2, 1fr); }
  .stats-row--5 { grid-template-columns: repeat(2, 1fr); }

  /* Timeline turns vertical: the rule runs down the left of the steps. */
  .process-track { grid-template-columns: 1fr; gap: 0; }
  .process-track::before { top: 0; bottom: 0; left: 5px; right: auto; width: 1px; height: auto; }
  .process-step { padding: 0 0 34px 34px; }
  .process-step:last-child { padding-bottom: 0; }
  .process-step::before { top: 5px; }

  .post-grid { grid-template-columns: repeat(2, 1fr); }

  .section-head--split { flex-direction: column; align-items: flex-start; }

  .footer-grid { grid-template-columns: 1fr 1fr; }
  .footer-brand-col { grid-column: 1 / -1; }
  .footer-social-col { grid-column: auto; }
}

@media (max-width: 767.98px) {
  body { font-size: 17.6px; }
  .testimonial-card__quote { max-width: none; }
  .testimonial-card__meta { max-width: none; }
  .form-row-2 { grid-template-columns: 1fr; }
  .gallery__stage { aspect-ratio: 4 / 3; }
}

@media (max-width: 575.98px) {
  :root { --gutter: 20px; }

  .service-grid,
  .post-grid { grid-template-columns: 1fr; }
  .service-grid .service-card__media { aspect-ratio: 4 / 3; }

  .stats-row { grid-template-columns: 1fr; gap: clamp(20px, 5vw, 32px); }

  .footer-grid { grid-template-columns: 1fr; }
  .footer-bottom { flex-direction: column; align-items: flex-start; gap: 10px; }

  .btn-primary-custom,
  .btn-outline-custom { width: 100%; justify-content: center; }

  .gallery__controls { flex-direction: column-reverse; align-items: flex-start; gap: 18px; }
  .gallery__stage { aspect-ratio: 3 / 4; }
  .hero { min-height: 92svh; }
  .split-panel { min-height: 56vh; }
}

/* 320px: nothing may overflow, and the tracked capitals are the first thing
   that does if they are left at their desktop letter-spacing. */
@media (max-width: 359.98px) {
  .brand { font-size: 17.6px; letter-spacing: 0.18em; }
  .brand small { letter-spacing: 0.22em; }
  .hero__label,
  .section-label { letter-spacing: 0.16em; }
  .btn-primary-custom,
  .btn-outline-custom { padding-inline: 18px; letter-spacing: 0.12em; }
}

/* 21 ------------------------------------------------- placeholder images
   The sand-coloured placeholder is nearly the page background, so inside a
   dark band it flattens the overlay and the white type loses its contrast.
   Dropping it back lets the dark base carry the text until the real
   photograph - which is what these bands are designed around - arrives. */
.hero__media img.is-placeholder,
.page-hero__media img.is-placeholder,
.split-panel__media img.is-placeholder,
.project-card__media img.is-placeholder {
  opacity: 0.28;
}

/* 22 ----------------------------------------------- Bootstrap row gutters
   On a phone a `g-5` row spends 24px of column padding on each side, on top
   of the 20px page gutter - so body copy sat 44px from the screen edge, and
   the row's negative margins pushed its box past the viewport (masked only
   by overflow-x on <body>). Below 768px the columns stack, so the horizontal
   gutter has nothing left to separate: drop it and keep the vertical one. */
@media (max-width: 767.98px) {
  .container-x > .row,
  .container-x .row {
    --bs-gutter-x: 0;
    --bs-gutter-y: clamp(32px, 7vw, 48px);
  }
}

/* 23 ------------------------------------------------------- map on mobile
   21:9 across a phone leaves a ~140px strip that is too shallow to read or
   pan; the map takes a taller frame once the column narrows. */
@media (max-width: 767.98px) {
  .map-frame { aspect-ratio: 4 / 3; }
}

/* 24 -------------------------------------------------- floating actions
   WhatsApp (always there) and back to top (after the first screen), stacked
   in the bottom-right corner. The WhatsApp button keeps its own green and its
   round shape: it is a recognised mark, and restyling it into a square olive
   tile would cost more in recognition than it gains in consistency. Back to
   top follows the site instead - square corners, charcoal, thin border. */

.floating-actions {
  position: fixed;
  right: clamp(16px, 2.2vw, 28px);
  /* env() keeps it clear of the iOS home indicator; the fallback is the 0 in
     the first argument on every other browser. */
  bottom: calc(clamp(16px, 2.2vw, 28px) + env(safe-area-inset-bottom, 0px));
  z-index: 1040;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
}

.fab {
  width: 52px;
  height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  padding: 0;
  font-size: 24.2px;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  transition: transform var(--t-fast), background var(--t-fast),
              opacity var(--t-fast), visibility var(--t-fast), color var(--t-fast);
}
.fab:hover { transform: translateY(-3px); }
.fab:active { transform: translateY(-1px); }

.fab--top {
  border-radius: var(--radius-sm);
  background: var(--color-dark);
  color: #fff;
  border: 1px solid var(--color-dark);
  font-size: 19.8px;
  /* Hidden until the page has been scrolled. visibility (not just opacity)
     keeps it out of the tab order while it is invisible. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
}
.fab--top.is-visible { opacity: 1; visibility: visible; transform: none; }
.fab--top.is-visible:hover { transform: translateY(-3px); }
.fab--top:hover,
.fab--top:focus-visible { background: var(--color-accent); border-color: var(--color-accent); }

/* The off-canvas menu owns the screen while it is open. */
body.nav-open .floating-actions { opacity: 0; visibility: hidden; }

@media (max-width: 575.98px) {
  .fab { width: 46px; height: 46px; font-size: 20.9px; }
  .fab--top { font-size: 17.6px; }
}

@media (prefers-reduced-motion: reduce) {
  .fab:hover, .fab:active, .fab--top.is-visible:hover { transform: none; }
}

/* 25 ------------------------------------------------- delayed enquiry popup
   Sits above the floating buttons (1040) and the header (1030). The dialog is
   a two-panel card - photograph beside the form - collapsing to the form
   alone on a phone, where a decorative image would push the fields off the
   screen behind the keyboard. */

.popup {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: grid;
  place-items: center;
  padding: clamp(16px, 4vw, 40px);
  opacity: 0;
  transition: opacity 0.3s var(--ease);
}
.popup.is-open { opacity: 1; }
.popup[hidden] { display: none; }

.popup__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 20, 18, 0.62);
  backdrop-filter: blur(3px);
}

.popup__dialog {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 0.8fr 1fr;
  width: min(880px, 100%);
  max-height: calc(100vh - clamp(32px, 8vw, 80px));
  overflow: auto;
  background: var(--color-bg);
  border-radius: var(--radius-sm);
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.45);
  transform: translateY(14px);
  transition: transform 0.36s var(--ease);
}
.popup.is-open .popup__dialog { transform: none; }

.popup__media { position: relative; background: var(--color-surface); }
.popup__media img { width: 100%; height: 100%; object-fit: cover; }

.popup__body { padding: clamp(26px, 3.4vw, 44px); }
.popup__title {
  margin: 0 0 10px;
  font-size: clamp(1.54rem, 2.42vw, 2.09rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.028em;
}
.popup__text { margin: 0 0 26px; font-size: 16.5px; line-height: 1.7; }
.popup__body .form-field { margin-bottom: 16px; }
.popup__body .btn-primary-custom { width: 100%; justify-content: center; }

.popup__close {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-dark);
  font-size: 15.4px;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.popup__close:hover,
.popup__close:focus-visible {
  background: var(--color-dark);
  border-color: var(--color-dark);
  color: #fff;
}

body.popup-open { overflow: hidden; }

@media (max-width: 767.98px) {
  .popup__dialog { grid-template-columns: 1fr; }
  /* The photograph is decorative; on a phone the fields matter more than it. */
  .popup__media { display: none; }
  .popup__body .form-row-2 { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .popup, .popup__dialog { transition: none; }
  .popup__dialog { transform: none; }
}

/* 26 --------------------------------------------------------- action bar
   WhatsApp and Book a Visit in one pill, sitting under the back-to-top
   button. The pill is the only rounded shape on the site: it reads as a
   floating control rather than as part of the page, which is the point. */

/* Two oval buttons, icon only, at the bottom-right of the viewport.

   The positioning belongs to .floating-actions, which is already fixed
   bottom-right and already stacks the back-to-top button. Repeating
   position:fixed here took this out of that stack and dropped it on top of
   the back-to-top — so this stays in flow and only changes its own shape.

   The markup is unchanged: this was a horizontal pill with an icon and a
   label in each half. The labels are hidden rather than removed, so a
   screen reader still reads a name, backed by the aria-label already on
   each control. */
.action-bar {
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: transparent;
  border-radius: 0;
  overflow: visible;
  box-shadow: none;
}

.action-bar__item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  width: 56px;
  height: 56px;
  padding: 0;
  border-radius: 999px;
  background: var(--brand-charcoal);
  border: 0;
  color: var(--brand-gold);
  box-shadow: 0 10px 26px -10px rgba(36, 34, 31, 0.55);
  font-size: 12.65px;
  font-weight: 500;
  letter-spacing: 0.14em;
  line-height: 1;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.action-bar__item i { font-size: 24.2px; line-height: 1; }

/* Symbols only. The label stays in the DOM and stays available to assistive
   technology; it is simply not painted. */
.action-bar__item span {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* WhatsApp in WhatsApp's own green, not the site's gold. The mark is
   recognised by its colour, which is worth more here than palette
   consistency — the same reasoning as the note at the top of this section,
   which the gold had quietly drifted away from.

   #25D366 is the brand green. White on it measures 1.99:1, so the glyph is
   carried by its shape and the button's size rather than by contrast; the
   darker brand green #128C7E is used on hover and would be the swap to make
   if the icon needs to read harder (that pairing measures 4.14:1). */
.action-bar__item--whatsapp {
  background: #25D366;
  color: #FFFFFF;
}

/* A hairline between the two, drawn on the second item so it never appears
   at either end of the pill. */
/* The hairline that separated the two halves of the old pill has no job
   now that they are separate buttons. */
.action-bar__item + .action-bar__item { box-shadow: 0 10px 26px -10px rgba(36, 34, 31, 0.55); }

.action-bar__item:hover,
.action-bar__item:focus-visible { background: var(--brand-espresso); color: var(--brand-gold); }
.action-bar__item--whatsapp:hover,
.action-bar__item--whatsapp:focus-visible { background: #128C7E; color: #FFFFFF; }

@media (max-width: 575.98px) {
  .action-bar__item { width: 50px; height: 50px; }
  .action-bar__item i { font-size: 22px; }
}

/* Below 360px the two labels no longer fit beside the icons, so the labels go
   and the items become two tap targets of equal size. Each keeps its name for
   assistive tech through the title/aria-label on the element. */
@media (max-width: 359.98px) {
  .action-bar__item { width: 46px; height: 46px; }
}

/* 27 ------------------------------------------------------ filtered gallery
   Pill filters over a three-up grid. The pills are rounded like the action
   bar - both are controls rather than content, and that is the distinction
   the radius is carrying on this site. */

.gallery-filter {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-bottom: clamp(30px, 4vw, 52px);
}

.gallery-filter__btn {
  padding: 12px 24px;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--color-dark);
  font-size: 12.65px;
  font-weight: 500;
  letter-spacing: 0.14em;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.gallery-filter__btn:hover { border-color: var(--color-dark); }
.gallery-filter__btn.is-active {
  background: var(--color-dark);
  border-color: var(--color-dark);
  color: #fff;
}

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(12px, 1.6vw, 20px);
}

.gallery-item {
  position: relative;
  margin: 0;
  overflow: hidden;
  /* Portrait, because the supplied interiors are: 32 of the 44 are taller than
     they are wide and the median aspect is 0.73, almost exactly 3:4. In the
     4:3 landscape frame this used to be, only 64% of the average image
     survived the crop and the worst lost 58% of itself. At 3:4 that rises to
     82% average, and most images sit in the frame almost untouched. */
  aspect-ratio: 3 / 4;
  background: var(--color-surface);
  border-radius: var(--radius-sm);
}
.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1s var(--ease);
}
.gallery-item:hover img { transform: scale(1.05); }
.gallery-item.is-hidden { display: none; }

/* Sections switched off for now. The template rule is that nothing is
   deleted, only hidden - this is the switch. !important because the sections
   it lands on set their own display. */
.section-hidden { display: none !important; }

/* The badge names the category, so a filtered view still says what you are
   looking at once the pills scroll out of sight. */
.gallery-item__tag {
  position: absolute;
  left: 12px;
  bottom: 12px;
  padding: 6px 12px;
  background: rgba(20, 20, 18, 0.62);
  backdrop-filter: blur(3px);
  border-radius: var(--radius-sm);
  color: #fff;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.gallery-count {
  margin-top: 22px;
  text-align: center;
  font-size: 13.2px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-muted);
}

@media (max-width: 900px) {
  .gallery-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 575.98px) {
  .gallery-grid { grid-template-columns: 1fr; }
  .gallery-filter__btn { padding: 11px 18px; font-size: 12.1px; letter-spacing: 0.1em; }
}

/* 28 ------------------------------------------------- image corner radius
   Applied to the WRAPPER, not the <img>: every one of these clips a hover
   scale, and a radius on the image alone would let the corners square off
   the moment it grows. */

.about-grid__media,
.solution-row__media,
.enquiry-section__media,
.service-card__media,
.post-card__media,
.blog-feature__media,
.gallery-item {
  border-radius: var(--radius-img);
  overflow: hidden;
}

/* The smaller tiles take a tighter radius - the same 18px on a 280px tile
   eats far more of the picture than it does on a 600px one. */
.service-card__media,
.post-card__media,
.gallery-item { border-radius: var(--radius-img-sm); }

/* Images inside those wrappers must not carry their own corners, or the two
   radii fight at the edges. */
.about-grid__media img,
.solution-row__media img,
.enquiry-section__media img { border-radius: inherit; }

/* The offset frame behind the About photograph follows the same curve, set a
   little wider so the two arcs stay concentric. */
.about-grid__media::before { border-radius: calc(var(--radius-img) + 8px); }

/* A whisper of lift under the large editorial plates. Not a card shadow -
   at 6% it reads as the image sitting on the page rather than floating. */
.about-grid__media,
.solution-row__media,
.enquiry-section__media {
  box-shadow: 0 18px 40px -24px rgba(37, 37, 34, 0.35);
}

.map-frame { border-radius: var(--radius-img-sm); overflow: hidden; }

@media (max-width: 575.98px) {
  /* Tighter on a phone, where the image is nearly edge to edge. */
  :root { --radius-img: 12px; --radius-img-sm: 10px; }
}

/* 29 ------------------------------------------------- connect / lead form
   A short capture form above the footer on every page. The band is white and
   the card is the sand grey, rather than the other way round: several pages
   end on a grey section, and a grey band under a grey band loses the edge.

   Fields here take a softer radius than the rest of the site's inputs. It is
   a deliberate local variation - the card reads as one modern object, and it
   now matches the rounded photography.
   ======================================================================== */

.connect {
  /* Top trimmed to close the seam with the testimonials above it; the bottom
     keeps the full rhythm, because the map section follows and needs the air. */
  padding-block: clamp(44px, 5vw, 72px) var(--section-spacing);
  /* Stone, not ivory: the testimonials above are ivory and the two ran
     together as one long band. The map below moves to ivory to keep the
     alternation rather than push the seam one section down. */
  background: var(--color-surface);
}

.connect__card {
  max-width: 720px;
  margin-inline: auto;
  padding: clamp(24px, 4vw, 52px);
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-img);
}

.connect__card .form-field input,
.connect__card .form-field select {
  padding: 15px 16px;
  background: var(--color-bg);
  border-color: var(--color-border-soft);
  border-radius: 10px;
}
.connect__card .form-field input:focus,
.connect__card .form-field select:focus {
  border-color: var(--color-accent);
  background: var(--color-bg);
}

/* Dialling code and number read as one control, so the seam between them is
   a single border rather than two sitting side by side. */
.phone-group { display: flex; }
.phone-group > * { min-width: 0; }
.connect__card .phone-group select {
  flex: 0 0 auto;
  width: 116px;
  padding-right: 34px;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
  border-right: 0;
  background-position: calc(100% - 17px) 55%, calc(100% - 11px) 55%;
  font-variant-numeric: tabular-nums;
}
.connect__card .phone-group input {
  flex: 1;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
/* Focusing either half lifts that half above the other, so the shared seam
   never shows half a focus colour. */
.connect__card .phone-group select:focus,
.connect__card .phone-group input:focus { position: relative; z-index: 1; }

.connect__submit {
  width: 100%;
  justify-content: center;
  margin-top: 10px;
  padding-block: 18px;
  border-radius: 10px;
}

.connect__consent {
  margin: 14px 0 0;
  text-align: center;
  font-size: 13.75px;
  line-height: 1.6;
  color: var(--color-muted);
}

.connect .form-status { text-align: center; }

@media (max-width: 575.98px) {
  .connect__card .phone-group select { width: 104px; font-size: 15.4px; }
}


/* ==========================================================================
   Banner video
   --------------------------------------------------------------------------
   Same box as the still it replaces, and it inherits the gradient scrim from
   .hero__media::after so the headline keeps its contrast whichever is showing.
   The poster carries the frame until the file downloads, and on any device
   that blocks autoplay the poster is simply what stays.
   ========================================================================== */

.hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ==========================================================================
   Glimpse row — three across, sliding sideways
   --------------------------------------------------------------------------
   Replaces the one-at-a-time fader. Built on overflow + scroll-snap rather
   than a slide index, which buys three things the old carousel did not have:
   it swipes natively on a phone, it scrolls with the keyboard, and it still
   works as a scrollable row with JavaScript off. The arrows only scroll it.
   ========================================================================== */

.glimpse { position: relative; }

.glimpse__track {
  display: grid;
  grid-auto-flow: column;
  /* Three full items across, with the fourth just breaking the edge so it is
     obvious the row continues. */
  grid-auto-columns: calc((100% - (2 * var(--glimpse-gap))) / 3);
  gap: var(--glimpse-gap);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-bottom: 6px;
  --glimpse-gap: 18px;

  /* The scrollbar is replaced by the arrows and the cut-off edge. */
  scrollbar-width: none;
}
.glimpse__track::-webkit-scrollbar { display: none; }
.glimpse__track:focus-visible { outline: 2px solid var(--color-accent-ink); outline-offset: 6px; }

.glimpse__item {
  scroll-snap-align: start;
  margin: 0;
  overflow: hidden;
  background: var(--color-surface);
  /* Matched to the supplied photography, which is 16:9 throughout. A portrait
     frame would have cropped 55% off the width of every one of these — the
     tower, the courtyard and the plaza shots are all composed wide. */
  aspect-ratio: 16 / 9;
}
.glimpse__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.7s var(--ease);
}
@media (hover: hover) {
  .glimpse__item:hover img { transform: scale(1.04); }
}

.glimpse__nav {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 22px;
}
.glimpse__btn {
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border: 1px solid var(--color-border-soft);
  background: transparent;
  color: var(--color-dark);
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.glimpse__btn:hover {
  background: var(--color-dark);
  border-color: var(--color-dark);
  color: #fff;
}
/* Nothing further to scroll in that direction. */
.glimpse__btn[disabled] { opacity: 0.3; cursor: default; }
.glimpse__btn[disabled]:hover {
  background: transparent;
  border-color: var(--color-border-soft);
  color: var(--color-dark);
}

/* Two across on a tablet, one and a bit on a phone — a third of a phone
   screen is too small for any of this photography to read. */
@media (max-width: 1024px) {
  .glimpse__track { grid-auto-columns: calc((100% - var(--glimpse-gap)) / 2); }
}
@media (max-width: 640px) {
  .glimpse__track { grid-auto-columns: 82%; --glimpse-gap: 12px; }
  .glimpse__nav { justify-content: center; }
}

/* ==========================================================================
   Map
   ========================================================================== */

/* Address on the left, map on the right.

   This was a full-bleed 460px-tall embed running the whole width of the page
   with the address stacked above it, which gave a map more visual weight
   than the content around it. Two columns inside the normal container reads
   as a standard contact block instead. */
.map-section {
  background: var(--color-bg);
  padding-top: var(--section-spacing);
  padding-bottom: var(--section-spacing);
}
.map-section__grid {
  display: grid;
  gap: clamp(28px, 4vw, 60px);
  align-items: center;
}
@media (min-width: 900px) {
  .map-section__grid { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
}
.map-section__address {
  font-style: normal;
  color: var(--color-muted);
  line-height: 1.75;
  max-width: 40ch;
  margin-top: 14px;
}
.map-section__directions { margin-top: 26px; }

.map-section__frame {
  line-height: 0;          /* kills the descender gap under the iframe */
  overflow: hidden;
  border-radius: var(--radius-img-sm);
  filter: grayscale(1);
  transition: filter 0.5s var(--ease);
}
/* Desaturated so it sits with the rest of the page, and restored on interaction
   so it reads as a real map the moment someone reaches for it. */
.map-section__frame:hover,
.map-section__frame:focus-within { filter: grayscale(0); }

/* The height comes from the aspect ratio rather than a fixed pixel value, so
   the map scales with its column instead of staying 460px tall at every
   width. */
.map-section__frame iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  border: 0;
}
@media (max-width: 899.98px) {
  .map-section__frame iframe { aspect-ratio: 16 / 9; }
}
@media (max-width: 640px) {
  .map-section__frame iframe { aspect-ratio: 4 / 3; }
}


/* 30 ----------------------------------------------------- quick look grid
   Nine images, three rows of three. Replaced the scroll-snap carousel that
   used to sit here; that markup is still in the page behind .section-hidden.

   The depth is built from two shadows rather than one: a tight, dark contact
   shadow that keeps the tile anchored to the page, and a wide, soft ambient
   shadow that gives it the lift. A single shadow doing both jobs either
   floats (too soft) or looks pasted on (too tight). Both are tinted with the
   brand charcoal, not black - a neutral-black shadow reads cold against the
   warm ivory page. */

.quicklook {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(16px, 1.9vw, 28px);
}

.quicklook__item {
  margin: 0;
  border-radius: var(--radius-img);
  overflow: hidden;           /* clips the hover scale to the rounded corner */
  background: var(--color-dark);  /* fills the frame until the image decodes */
  box-shadow:
    0 2px 6px -2px rgba(36, 34, 31, 0.28),
    0 18px 38px -16px rgba(36, 34, 31, 0.42);
  transition: transform 0.45s var(--ease), box-shadow 0.45s var(--ease);
  will-change: transform;
}

.quicklook__item img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  transition: transform 0.9s var(--ease);
}

/* Lift on hover: the tile rises, and the shadow grows and softens to match.
   A shadow that stays put while the element moves is what makes a hover lift
   look like a sticker sliding rather than an object rising. */
@media (hover: hover) {
  .quicklook__item:hover,
  .quicklook__item:focus-within {
    transform: translateY(-6px);
    box-shadow:
      0 4px 10px -3px rgba(36, 34, 31, 0.3),
      0 30px 60px -20px rgba(36, 34, 31, 0.52);
  }
  .quicklook__item:hover img,
  .quicklook__item:focus-within img { transform: scale(1.06); }
}

/* On the stone band the ambient shadow needs a touch more weight, or it
   disappears into a surface that is already mid-tone. */
.section-surface .quicklook__item {
  box-shadow:
    0 2px 6px -2px rgba(36, 34, 31, 0.32),
    0 18px 38px -16px rgba(36, 34, 31, 0.5);
}

/* Two across below the desktop breakpoint. Three columns were still
   applying at 768-991, which put the tiles at 215px wide - too small to read
   a building in. */
@media (max-width: 991.98px) {
  .quicklook { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 767.98px) {
  .quicklook__item { border-radius: var(--radius-img-sm); }
}

/* One across only on a phone: two 150px tiles show nothing. */
@media (max-width: 479.98px) {
  .quicklook { grid-template-columns: 1fr; gap: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .quicklook__item,
  .quicklook__item img { transition: none; }
  .quicklook__item:hover { transform: none; }
  .quicklook__item:hover img { transform: none; }
}
