/* ============================================================
   Kool Cars of Sussex — landing page
   Brand tokens live in :root. Change them here, nowhere else.
   ============================================================ */

:root {
  /* colour */
  --navy-900:  #08223c;
  --navy-800:  #0d2a45;
  --navy-700:  #123c66;
  --blue-600:  #1b6fc0;
  --blue-500:  #2b83d6;

  /* hero headline gradient (top -> bottom) */
  --hero-grad-top:    #0A3162;
  --hero-grad-bottom: #0090FF;
  --blue-400:  #3f9ae4;
  --sky-300:   #b9d8fb;
  --sky-200:   #cfe3fb;
  --sky-100:   #e4effe;
  --sky-050:   #eff6ff;
  --hairline:  #86bdf2;

  /* type */
  --font-display: "Inter", "Segoe UI", system-ui, sans-serif;

  --font-body: "Bacasime Antique", Georgia, "Times New Roman", serif;

  /* Paragraph copy, per the design spec */
  --body-line-height: 0.844;

  /* Hero type scale. The subheader is exactly half the title, per the design. */
  --hero-title-size: clamp(34px, 6.1vw, 78px);
  --hero-tracking: -0.09em;
  --body-tracking: -0.04em;

  /* shape */
  --r-panel:  44px;
  --r-card:   40px;
  --r-pill:   999px;
  /* suits the stacked lockup (395x214). A wide lockup wants ~150px. */
  --logo-width: 106px;

  /* hero car watermark — raise opacity toward 0.12 if 5% reads too faint */
  --watermark-opacity: 0.05;
  --watermark-blur: 3px;

  /* motion — durations follow the 100/300/500 rule: instant feedback, state
     change, layout change, entrance. Easing is ease-out only; bounce and
     elastic curves draw attention to the animation itself and read as dated. */
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-out-expo:  cubic-bezier(0.16, 1, 0.3, 1);
  --dur-press:    120ms;  /* button press, colour shift */
  --dur-state:    220ms;  /* hover, focus ring */
  --dur-lift:     300ms;  /* card lift */
  --dur-entrance: 620ms;  /* hero entrance, card reveal */

  /* rhythm */
  --gutter: clamp(20px, 5vw, 56px);
  --shell:  1180px;
  /* side inset for contained panels (the hero ignores this and goes full-bleed) */
  --page-inset: clamp(12px, 2.4vw, 28px);
}

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

html {
  -webkit-text-size-adjust: 100%;
  background: var(--sky-200);
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  letter-spacing: var(--body-tracking);
  color: var(--navy-800);
}

/* Paragraph copy: 84.4% line-height per the design spec. Scoped to prose only —
   applying it to labels, buttons or nav would clip their line boxes. */
.hero-lede,
.services-intro,
.booking-lede,
.contact-lede,

.footer-note {
  line-height: var(--body-line-height);
}

/* UI text keeps normal tracking — -4% on labels and buttons hurts legibility */
.nav-links,
.field label,
.section-eyebrow,
.contact-label,
.service-fit,
.service-price-label,
.service-price-value,
.contact-details li,
.form-status,
.field input,
.field select,
.field textarea,
.btn {
  letter-spacing: normal;
}

/* Backdrop as a fixed layer rather than background-attachment: fixed —
   the latter forces a repaint on every scroll and is broken on iOS Safari. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  /* The hero gradient, expanded to back the whole site. Fixed to the viewport
     rather than stretched over the document, so the ramp stays consistent
     however long the page gets. */
  background:
    linear-gradient(180deg, rgb(255 255 255 / 0.42), rgb(255 255 255 / 0.06)),
    linear-gradient(180deg, var(--sky-100), var(--sky-200));
}

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

h1, h2, h3 { margin: 0; font-family: var(--font-display); font-weight: 800; letter-spacing: -0.02em; }
p { margin: 0; }

a { color: inherit; }

:where(a, button, input, select, textarea):focus-visible {
  outline: 3px solid var(--blue-600);
  outline-offset: 3px;
  border-radius: 6px;
}

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

.skip-link {
  position: absolute; left: 50%; top: 8px;
  transform: translate(-50%, -160%);
  z-index: 50;
  padding: 10px 22px;
  background: #fff; color: var(--navy-800);
  border-radius: var(--r-pill);
  font-family: var(--font-display); font-weight: 600; font-size: 15px;
  text-decoration: none;
  box-shadow: 0 8px 24px rgb(8 34 60 / 0.25);
  transition: transform 160ms ease;
}
.skip-link:focus { transform: translate(-50%, 0); }

/* ---------- shared buttons ---------- */
.btn {
  --btn-py: 14px;
  --btn-px: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--btn-py) var(--btn-px);
  border: 0;
  border-radius: var(--r-pill);
  /* Inter for every button label, including all the "Book Now"s. Inter has a
     larger x-height than Bacasime, so the size drops slightly to keep the
     pills visually the same weight as before. */
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 17px;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition:
    transform var(--dur-state) var(--ease-out-quart),
    background-color var(--dur-state) var(--ease-out-quart),
    box-shadow var(--dur-state) var(--ease-out-quart),
    border-color var(--dur-state) var(--ease-out-quart);
}

/* frosted pill — used on the light hero and on the dark cards */
.btn-ghost {
  color: var(--navy-800);
  background: rgb(255 255 255 / 0.55);
  border: 1px solid rgb(255 255 255 / 0.9);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.85),
    0 10px 24px -14px rgb(8 34 60 / 0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.btn-ghost:hover {
  background: rgb(255 255 255 / 0.82);
  transform: translateY(-2px);
}
/* Press feedback on every button. Under ~80ms reads as instant; 120ms keeps the
   press legible without feeling laggy. */
.btn:active {
  transform: scale(0.97);
  transition-duration: var(--dur-press);
}

.btn-lg { --btn-py: 17px; --btn-px: 46px; font-size: 19px; }

.btn-solid {
  color: #fff;
  background: linear-gradient(180deg, var(--blue-500), var(--blue-600));
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 17px;
  box-shadow: 0 14px 28px -16px rgb(11 63 112 / 0.9);
}
.btn-solid:hover { transform: translateY(-2px); filter: brightness(1.06); }
.btn-solid:disabled { opacity: 0.6; cursor: progress; transform: none; }

/* ============================================================
   Layout shell
   ============================================================ */
/* minmax(0, 1fr) throughout: an `auto` grid column is sized by its items'
   max-content, which lets long headings push the whole page wider than the
   viewport. Capping the column at 1fr forces them to wrap instead. */
.page {
  max-width: none;
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(20px, 3vw, 34px);
}

/* the sticky nav pill must not cover a section we've just jumped to */
.hero, .services, .booking, .contact { scroll-margin-top: 110px; }

/* ============================================================
   Header
   ============================================================ */
/* The hero is full-bleed, so the header supplies its own inset — otherwise the
   pill would sit flush against the viewport edges. */
.header {
  position: sticky;
  top: clamp(10px, 2vw, 18px);
  z-index: 30;
  padding: clamp(10px, 1.6vw, 16px) var(--gutter);
}

.nav-pill {
  max-width: var(--shell);
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 12px 12px 26px;
  border-radius: var(--r-pill);
  border: 1px solid rgb(255 255 255 / 0.75);
  background: rgb(232 242 255 / 0.72);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  box-shadow: 0 18px 40px -28px rgb(9 40 70 / 0.75);
}

.nav-logo { display: block; flex: 0 0 auto; }

/* The logo PNG carries an opaque white background; multiply drops it out
   against the pill. Replace with a transparent asset and this becomes a no-op. */
.nav-logo img {
  width: var(--logo-width);
  mix-blend-mode: multiply;
}

.nav-links {
  display: flex;
  gap: 26px;
  margin-inline: auto;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
}
.nav-links a {
  position: relative;
  color: var(--navy-700);
  text-decoration: none;
  padding-block: 4px;
}
.nav-links a::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--blue-500);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 200ms ease;
}
.nav-links a:hover::after { transform: scaleX(1); }

.btn-touch {
  --btn-py: 15px;
  --btn-px: 32px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 19px;
  color: var(--navy-700);
  background: rgb(255 255 255 / 0.7);
}

/* ============================================================
   Hero
   ============================================================ */
.hero {
  position: relative;
  overflow: hidden;
  padding: clamp(56px, 9vw, 108px) var(--gutter) clamp(64px, 8vw, 96px);
  border-radius: 0;
  border: 0; /* borderless hero — the other panels keep their hairline */
  /* No background of its own: the gradient now lives on body::before and backs
     the whole site, so the hero simply sits on it.

     `isolation: isolate` is deliberately absent. It would make the hero a
     stacking context, and the car watermark's mix-blend-mode: multiply would
     then blend against an empty backdrop instead of the page gradient —
     bringing back the artwork's opaque white background as a solid block. */
  background: none;
}

/* ---- hero intro video ----
   Sits above the watermark and below .hero-inner in DOM order, so when it
   fades the watermark and headline are revealed underneath. Hidden entirely
   unless the .js-hero-video gate is set. */
.hero-video {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 1;
  transition: opacity 900ms var(--ease-out-quart);
}

.js-hero-video .hero-video { display: block; }

.js-hero-video.hero-revealed .hero-video {
  opacity: 0;
  pointer-events: none;
}

/* While the video runs, the headline and badge are held back. Their entrance
   animation is suppressed too, so it plays at the hand-over rather than firing
   invisibly behind the video on page load. */
.js-hero-video .hero-inner,
.js-hero-video .hero-badge {
  opacity: 0;
}

.js-hero-video .hero-eyebrow,
.js-hero-video .hero-title,
.js-hero-video .hero-lede,
.js-hero-video .hero .btn-lg,
.js-hero-video .hero-badge {
  animation: none;
}

.js-hero-video.hero-revealed .hero-inner,
.js-hero-video.hero-revealed .hero-badge {
  opacity: 1;
  transition: opacity var(--dur-lift) var(--ease-out-quart);
}

/* Re-applying the animation here restarts it, giving the same staggered
   entrance the hero has on a normal load — just 25 seconds later. */
.js-hero-video.hero-revealed .hero-eyebrow,
.js-hero-video.hero-revealed .hero-title,
.js-hero-video.hero-revealed .hero-lede,
.js-hero-video.hero-revealed .hero .btn-lg,
.js-hero-video.hero-revealed .hero-badge {
  animation: hero-rise var(--dur-entrance) var(--ease-out-expo) both;
  animation-delay: calc(var(--i, 0) * 80ms);
}

/* ---- hero car watermark ----
   The artwork is a dark car on an opaque white background. multiply keeps the
   dark ink and lets the white drop out to the hero gradient, so no cut-out is
   needed. .hero has `isolation: isolate` to keep the blend inside the panel.
   Dial the look with the two custom properties below. */
/* No z-index here on purpose. `z-index: -1` would make this a stacking context,
   which isolates the children's blending — the cars would then multiply against
   an empty backdrop instead of the hero gradient, and the artwork's white
   background would render as a solid white block. Layering is handled by DOM
   order instead: .hero-inner and .hero-badge are positioned and come later, so
   they paint on top. */
.hero-watermark {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}

.hero-car {
  position: absolute;
  bottom: -2%;
  width: auto;
  height: clamp(240px, 72%, 560px);
  mix-blend-mode: multiply;
  opacity: var(--watermark-opacity);
  filter: blur(var(--watermark-blur));
}

/* Cut edge of the artwork faces outward on both sides so it bleeds off-panel */
.hero-car--left  { left: -3%; transform: scaleX(-1); }
.hero-car--right { right: -3%; }

/* Soft veil over the cars: keeps the headline crisp and stops the blurred
   edges reading as a hard band. */
.hero-watermark::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    58% 55% at 50% 44%,
    rgb(255 255 255 / 0.3) 0%,
    rgb(255 255 255 / 0.1) 55%,
    rgb(255 255 255 / 0) 100%
  );
}

.hero-inner {
  position: relative; /* paints above .hero-watermark, which is unlayered */
  max-width: 1060px;
  margin-inline: auto;
  text-align: center;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: clamp(18px, 2.4vw, 26px);
}

.hero-eyebrow {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: var(--hero-tracking);
  font-size: calc(var(--hero-title-size) / 2);
  color: var(--navy-700);
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--hero-title-size);
  letter-spacing: var(--hero-tracking);
  line-height: 1.05;
  text-wrap: balance;
  /* Fallback for anything without background-clip: text — also the colour that
     shows if the gradient fails to paint for any reason. */
  color: var(--blue-500);
}

/* Gradient fill lives in the "Gradient text fill" section further down — it has
   to come after .section-title is defined, or that rule's own colour would
   override the transparent fill. */

.hero-lede {
  max-width: 30ch;
  font-size: clamp(17px, 1.6vw, 20px);
  color: var(--blue-600);
  text-wrap: pretty;
}

/* The supplied REFCOM artwork is a 455x455 square with the mark sitting in a
   band from y143 to y312 — i.e. vertically centred with large empty margins.
   Cropping to a 455:205 window with object-fit trims the dead space without
   needing the file re-exported. If you swap in a pre-trimmed asset, delete the
   aspect-ratio and object-fit lines. */
.hero-badge {
  position: absolute;
  right: clamp(18px, 3.5vw, 44px);
  bottom: clamp(18px, 3.5vw, 40px);
  width: clamp(96px, 11vw, 150px);
  height: auto;
  aspect-ratio: 455 / 205;
  object-fit: cover;
  object-position: center center;
  opacity: 0.95;
}

/* ============================================================
   Services
   ============================================================ */
.services {
  padding: clamp(48px, 7vw, 84px) var(--gutter) clamp(64px, 9vw, 110px);
  border-radius: 0;
  background: linear-gradient(180deg, var(--sky-100), var(--sky-050));
}

.services-intro {
  max-width: 78ch;
  margin: 0 auto clamp(44px, 6vw, 72px);
  text-align: center;
  font-size: clamp(16px, 1.45vw, 19px);
  color: var(--blue-600);
  text-wrap: pretty;
}

.service-grid {
  display: grid;
  /* min() so a 280px track can still shrink on very narrow phones */
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: clamp(26px, 4vw, 56px);
  max-width: 900px;
  margin-inline: auto;
}

.service-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  align-content: center;
  gap: 20px;
  min-height: 420px;
  padding: clamp(30px, 4vw, 44px) clamp(22px, 3vw, 34px);
  text-align: center;
  color: #fff;
  border-radius: var(--r-card);
  background: linear-gradient(180deg, var(--navy-900) 0%, var(--navy-700) 46%, var(--blue-400) 100%);
  box-shadow: 0 34px 54px -30px rgb(9 40 70 / 0.75);
  transition: transform var(--dur-lift) var(--ease-out-quart), box-shadow var(--dur-lift) var(--ease-out-quart);
}
.service-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 44px 64px -30px rgb(9 40 70 / 0.8);
}

.service-title {
  font-size: clamp(23px, 2.4vw, 30px);
  line-height: 1.22;
}
.service-title span { white-space: nowrap; }

.service-fit {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(14px, 1.2vw, 16px);
  color: rgb(255 255 255 / 0.92);
}

.service-price { display: grid; gap: 4px; }

.service-price-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-style: italic;
  font-size: clamp(15px, 1.3vw, 18px);
}

.service-price-value {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(30px, 3.4vw, 40px);
  letter-spacing: -0.01em;
}

.service-card .btn-ghost {
  margin-top: 8px;
  color: #fff;
  background: rgb(255 255 255 / 0.16);
  border-color: rgb(255 255 255 / 0.5);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.35),
    0 12px 26px -16px rgb(0 0 0 / 0.7);
}
.service-card .btn-ghost:hover { background: rgb(255 255 255 / 0.3); }

/* ============================================================
   Testimonials
   ============================================================ */
/* Flush with the page: no container, border, radius or panel background — the
   site gradient shows straight through and the cards do the work. */
.testimonials {
  padding: clamp(48px, 7vw, 84px) var(--gutter) clamp(52px, 8vw, 92px);
  border-radius: 0;
  background: none;
  scroll-margin-top: 110px;
}

.testimonial-grid {
  display: grid;
  /* Four cards, so the track minimum and max-width are tuned to give a balanced
     2 x 2 rather than a 3 + 1 orphan row: 300px is wide enough that three
     tracks cannot fit in 900px, but narrow enough that two still fit at tablet
     width. Below roughly 665px it drops to a single column. */
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: clamp(20px, 2.6vw, 30px);
  max-width: 900px;
  margin-inline: auto;
}

.testimonial {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: space-between;
  gap: 22px;
  margin: 0;
  padding: clamp(26px, 3vw, 34px) clamp(22px, 2.6vw, 30px) clamp(22px, 2.6vw, 28px);
  border-radius: 30px;
  border: 1px solid rgb(255 255 255 / 0.9);
  background: rgb(255 255 255 / 0.62);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 26px 50px -34px rgb(9 40 70 / 0.7);
  transition: transform var(--dur-lift) var(--ease-out-quart), box-shadow var(--dur-lift) var(--ease-out-quart);
}
.testimonial:hover {
  transform: translateY(-4px);
  box-shadow: 0 34px 58px -34px rgb(9 40 70 / 0.75);
}

/* decorative opening quote */
.testimonial::before {
  content: "\201C";
  display: block;
  margin-bottom: -18px;
  font-family: var(--font-body);
  font-size: 64px;
  line-height: 1;
  color: var(--sky-300);
}

.testimonial blockquote {
  margin: 0;
}

.testimonial blockquote p {
  font-size: clamp(17px, 1.5vw, 19px);
  line-height: var(--body-line-height);
  color: var(--navy-700);
  text-wrap: pretty;
}

.testimonial-attr {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 3px;
  padding-top: 16px;
  border-top: 1px solid var(--sky-300);
}

.testimonial-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: normal;
  color: var(--navy-800);
}

.testimonial-meta {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: normal;
  color: var(--blue-500);
}

/* ============================================================
   Booking
   ============================================================ */
.booking {
  padding: clamp(44px, 6vw, 76px) var(--gutter);
  border-radius: 0;
  background: linear-gradient(180deg, var(--sky-100), var(--sky-050));
}

.booking-head,
.section-head {
  max-width: 640px;
  margin: 0 auto clamp(28px, 4vw, 44px);
  text-align: center;
}

.booking-lede {
  margin-top: 16px;
  font-size: 18px;
  color: var(--blue-600);
  text-wrap: pretty;
}

/* Booking call to action. Square owns the transaction, so this is a card
   pointing at it rather than an embedded calendar — see index.html for why the
   inline embed could not complete a booking. */
.booking-cta {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: 18px;
  max-width: 640px;
  margin-inline: auto;
  padding: clamp(30px, 5vw, 44px) clamp(24px, 4vw, 36px);
  text-align: center;
  border-radius: 30px;
  border: 1px solid rgb(255 255 255 / 0.9);
  background: rgb(255 255 255 / 0.62);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 26px 50px -34px rgb(9 40 70 / 0.7);
}

.booking-cta-note {
  max-width: 44ch;
  font-size: 18px;
  line-height: var(--body-line-height);
  color: var(--blue-600);
}

.booking-cta .btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.booking-cta-sub {
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: normal;
  color: var(--blue-500);
}

/* ============================================================
   Contact
   ============================================================ */
/* Flush with the page, like the hero and testimonials — no container, border,
   radius or panel background. The form card carries the visual weight. */
.contact {
  padding: clamp(44px, 6vw, 76px) var(--gutter);
  border-radius: 0;
  background: none;
  scroll-margin-top: 110px;
}

.contact-inner {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(34px, 5vw, 64px);
  align-items: start;
  max-width: 1000px;
  margin-inline: auto;
}

.section-eyebrow {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-500);
  margin-bottom: 10px;
}

.section-title {
  font-size: clamp(30px, 4vw, 44px);
  color: var(--navy-700);
  line-height: 1.1;
}

.contact-lede {
  margin-top: 16px;
  font-size: 18px;
  color: var(--blue-600);
  text-wrap: pretty;
}

.contact-details {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
}
.contact-details li {
  overflow-wrap: anywhere;
  font-family: var(--font-display);
  font-weight: 500;
  display: grid;
  gap: 3px;
  font-size: 18px;
  color: var(--navy-700);
}
.contact-details a { text-decoration-color: var(--sky-300); }
.contact-details a:hover { text-decoration-color: currentColor; }

.contact-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-500);
}

/* ---- form ---- */
.contact-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  padding: clamp(24px, 3.4vw, 36px);
  border-radius: 30px;
  border: 1px solid rgb(255 255 255 / 0.9);
  background: rgb(255 255 255 / 0.62);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 26px 50px -34px rgb(9 40 70 / 0.7);
}

.field-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
}

.field { display: grid; grid-template-columns: minmax(0, 1fr); gap: 7px; margin: 0; }

.field label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 13px;
  color: var(--navy-700);
}
.optional { font-weight: 500; color: var(--blue-500); }

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 13px 15px;
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--navy-800);
  background: #fff;
  border: 1.5px solid var(--sky-300);
  border-radius: 14px;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.field textarea { resize: vertical; min-height: 96px; }

.field input:hover,
.field select:hover,
.field textarea:hover { border-color: var(--blue-400); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--blue-500);
  box-shadow: 0 0 0 4px rgb(43 131 214 / 0.16);
  outline: none;
}

.field input::placeholder,
.field textarea::placeholder { color: #8fa9c4; }

/* only flag invalid fields once the user has tried to submit */
.contact-form.is-validated :is(input, select, textarea):invalid {
  border-color: #d2544c;
}

.hp { position: absolute; left: -9999px; }

.form-status {
  min-height: 1.2em;
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--blue-600);
}
.form-status[data-state="error"] { color: #b93a32; }
.form-status[data-state="ok"]    { color: #1a7a4c; }

/* ============================================================
   Footer
   ============================================================ */
.footer {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: 12px;
  padding: clamp(30px, 4vw, 46px) var(--gutter) clamp(20px, 3vw, 30px);
  text-align: center;
  color: var(--blue-600);
}
.footer-logo { width: 132px; opacity: 0.9; mix-blend-mode: multiply; }
.footer-note { font-size: 17px; }
.footer-legal { font-size: 14px; color: var(--navy-700); opacity: 0.65; }

/* ============================================================
   Contained panels

   The hero is full-bleed: it spans the viewport with no radius, border or
   shadow. Every other section sits in a rounded container, inset from the edges
   and capped at --shell.

   This block must stay below those sections' own rules — each sets
   `border-radius: 0`, and at equal specificity the later declaration wins.
   ============================================================ */
/* .hero, .testimonials and .contact are deliberately absent — they run flush
   with the background, and their cards carry the visual weight instead. */
.services,
.booking,
.find-us {
  width: calc(100% - 2 * var(--page-inset));
  max-width: var(--shell);
  margin-inline: auto;
  border-radius: var(--r-panel);
  border: 1.5px solid var(--hairline);
  box-shadow: 0 30px 60px -40px rgb(9 40 70 / 0.55);
}

/* ============================================================
   Scroll reveal

   Progressive enhancement: the hidden state is scoped to .js-reveal, which an
   inline script in <head> adds before first paint. With JavaScript off the
   class is never added, so every section stays visible — content is never
   hidden behind an animation that might not run.
   ============================================================ */
/* Applied to CARDS, not whole sections. A grid of siblings arriving in sequence
   is list rhythm and reads as intentional; a whole panel fading in on every
   scroll is the saturated default and reads as templated. Sections now appear
   without motion — the choreography is spent on the hero and on these grids.

   No `will-change` here: it is worth setting only for known-expensive animation
   on a small number of elements, never standing on every animated node. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity var(--dur-entrance) var(--ease-out-expo),
    transform var(--dur-entrance) var(--ease-out-expo);
  transition-delay: calc(var(--i, 0) * 70ms);
}

.js-reveal [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

/* Stagger, capped: 4 cards x 70ms = 210ms total, so the last card never feels
   like it is lagging behind the first. */
.service-grid > :nth-child(2),
.testimonial-grid > :nth-child(2) { --i: 1; }
.testimonial-grid > :nth-child(3) { --i: 2; }
.testimonial-grid > :nth-child(4) { --i: 3; }

/* ---- hero entrance: the one orchestrated moment on the page ----
   Runs on load, no JS gate needed. `both` holds the start frame during the
   delay and the end frame after, so nothing flashes. If the animation never
   runs the elements simply sit at their natural opacity. */
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

.hero-eyebrow,
.hero-title,
.hero-lede,
.hero .btn-lg,
.error-actions,
.hero-badge {
  animation: hero-rise var(--dur-entrance) var(--ease-out-expo) both;
  animation-delay: calc(var(--i, 0) * 80ms);
}
.hero-eyebrow   { --i: 0; }
.hero-title     { --i: 1; }
.hero-lede      { --i: 2; }
.hero .btn-lg   { --i: 3; }
.error-actions  { --i: 3; }
.hero-badge     { --i: 4; }

/* The 404 animates the button row as one unit, so cancel the individual
   animation the .btn-lg rule above would otherwise give each button — two
   overlapping entrances on the same element read as a stutter. */
.error-actions .btn-lg { animation: none; }

/* Never animate for anyone who has asked the OS for reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal [data-reveal],
  .js-reveal [data-reveal].is-visible {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ============================================================
   Gradient text fill
   Applies to the hero headline and both section titles ("Pick a time that
   suits you", "Questions before you book?").

   This block MUST stay below .hero-title and .section-title: those rules set
   their own `color`, and with equal specificity the later declaration wins.
   Placed any earlier, the fill would be overridden back to opaque and the
   gradient would silently do nothing.
   ============================================================ */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .hero-title,
  .section-title {
    background-image: linear-gradient(
      180deg,
      var(--hero-grad-top) 0%,
      var(--hero-grad-bottom) 100%
    );
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;

    /* line-height 1.05 is tighter than Inter's own line box, so ascenders and
       descenders spill outside the element. background-clip: text only paints
       within that box, so the overhang gets no gradient and renders invisible —
       tops and tails of letters simply vanish. Measured at 78px: 6.7px above,
       5.4px below.

       The same happens horizontally. `.hero-inner` uses justify-items: center,
       so the h1 is shrink-to-fit and its box is exactly the text's *advance*
       width — but a glyph's ink can extend past its advance. Inter's "g"
       overhangs by ~0.053em, so the final g of "Servicing" lost its fill. The
       negative letter-spacing tightens the box further, making it worse.

       Enlarge the paint area on both axes and pull it back with an equal
       negative margin, so the gradient covers every glyph while layout stays
       exactly where it was. In em so it scales with the font size. */
    padding: 0.14em;
    margin: -0.14em;
  }
}

/* Windows High Contrast / forced-colors strips background images, which would
   leave the transparent fill invisible. Put a real colour back. */
@media (forced-colors: active) {
  .hero-title,
  .section-title {
    background-image: none;
    color: CanvasText;
    -webkit-text-fill-color: CanvasText;
  }
}

/* ============================================================
   Find us / map
   ============================================================ */
.find-us {
  padding: clamp(44px, 6vw, 76px) var(--gutter);
  /* Explicit radius: this rule sits AFTER the "Contained panels" block, so at
     equal specificity it wins. `border-radius: 0` here previously squared the
     corners while every other panel stayed rounded. */
  border-radius: var(--r-panel);
  /* Inverted panel — the same dark gradient as the service cards, so the map
     reads as a feature rather than another pale box. */
  background: linear-gradient(
    180deg,
    var(--navy-900) 0%,
    var(--navy-700) 46%,
    var(--blue-400) 100%
  );
  border-color: rgb(255 255 255 / 0.18);
  scroll-margin-top: 110px;
}

/* ---- type inverted for the dark panel ---- */
.find-us .section-eyebrow    { color: var(--sky-300); }
.find-us .contact-label      { color: var(--blue-400); }
.find-us .contact-details li { color: #fff; }
.find-us .contact-details a  { text-decoration-color: rgb(255 255 255 / 0.45); }
.find-us .contact-details a:hover { text-decoration-color: currentColor; }

/* The shared heading gradient runs #0A3162 -> #0090FF. Its dark end would be
   invisible against this panel, so flip it to run light. The solid colour is
   the fallback where background-clip: text is unsupported. */
.find-us .section-title { color: #fff; }

@supports (background-clip: text) or (-webkit-background-clip: text) {
  .find-us .section-title {
    background-image: linear-gradient(180deg, #ffffff 0%, #7fc4ff 100%);
  }
}

.find-inner {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(28px, 4vw, 52px);
  align-items: start;
  max-width: 1000px;
  margin-inline: auto;
}

.find-details {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: start;
  gap: 26px;
}

/* Map panel. aspect-ratio rather than a fixed height so it scales with the
   column; min-height stops it collapsing to a sliver on narrow screens. */
.map-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  min-height: 320px;
  overflow: hidden;
  border-radius: 30px;
  border: 1px solid rgb(255 255 255 / 0.9);
  /* Dark, so there is no pale flash on the dark panel before the map paints. */
  background: var(--navy-700);
  box-shadow: 0 26px 50px -34px rgb(0 0 0 / 0.65);
}

.map-iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* Transparent hit area covering the whole map. The iframe underneath would
   otherwise swallow every click, so this is what makes "click the map" work. */
.map-overlay {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: end;
  justify-items: end;
  padding: 16px;
  text-decoration: none;
  background: transparent;
  transition: background-color var(--dur-state) var(--ease-out-quart);
}
.map-overlay:hover,
.map-overlay:focus-visible {
  background: rgb(10 49 98 / 0.12);
}
.map-overlay:focus-visible {
  outline: 3px solid var(--blue-600);
  outline-offset: -3px;
}

.map-overlay-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 18px;
  border-radius: var(--r-pill);
  background: #fff;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: normal;
  color: var(--navy-800);
  box-shadow: 0 10px 26px -12px rgb(8 34 60 / 0.6);
  transition: transform var(--dur-state) var(--ease-out-quart);
}
.map-overlay:hover .map-overlay-label,
.map-overlay:focus-visible .map-overlay-label {
  transform: translateY(-2px);
}

@media (max-width: 860px) {
  .find-inner { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   Confirmation modal
   ============================================================ */
.modal {
  /* A <dialog> is display:none until opened, and centring is handled by the
     browser's top-layer placement, so only appearance is set here. */
  max-width: min(440px, calc(100vw - 2 * var(--page-inset)));
  padding: 0;
  border: 0;
  border-radius: 30px;
  background: transparent;
  overflow: visible;
}

.modal::backdrop {
  background: rgb(6 26 46 / 0.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.modal-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: 16px;
  padding: clamp(30px, 5vw, 42px) clamp(24px, 4vw, 36px);
  text-align: center;
  border-radius: 30px;
  border: 1px solid rgb(255 255 255 / 0.9);
  background: linear-gradient(180deg, #ffffff, var(--sky-050));
  box-shadow: 0 40px 80px -40px rgb(6 26 46 / 0.7);
}

.modal-icon {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  color: #fff;
  background: linear-gradient(180deg, var(--blue-500), var(--blue-600));
  box-shadow: 0 12px 26px -12px rgb(11 63 112 / 0.9);
}

.modal-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(24px, 3vw, 30px);
  letter-spacing: -0.02em;
  color: var(--navy-700);
}

.modal-body {
  max-width: 34ch;
  font-size: 18px;
  line-height: var(--body-line-height);
  color: var(--blue-600);
}

.modal .btn { margin-top: 6px; }

/* Entrance. Only on [open], so it plays each time the dialog is shown. */
@keyframes modal-in {
  from { opacity: 0; transform: translateY(12px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}
@keyframes backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.modal[open] {
  animation: modal-in var(--dur-lift) var(--ease-out-expo) both;
}
.modal[open]::backdrop {
  animation: backdrop-in var(--dur-state) var(--ease-out-quart) both;
}

/* ============================================================
   404 page

   Reuses the hero's type, watermark and entrance animation so the error page
   is unmistakably the same site. Only the layout differs: the page fills the
   viewport with the message centred, so the footer sits at the bottom instead
   of floating halfway up a short page.
   ============================================================ */
.page--error {
  min-height: 100vh;
  grid-template-rows: auto 1fr auto;
}

.error-hero {
  display: grid;
  align-content: center;
  min-height: clamp(380px, 52vh, 560px);
}

.error-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 860px) {
  .contact-inner { grid-template-columns: 1fr; }
}

@media (max-width: 720px) {
  :root { --r-panel: 32px; --r-card: 32px; --logo-width: 108px; }

  .nav-links { display: none; }
  .nav-pill { padding-left: 18px; gap: 12px; justify-content: space-between; }
  .btn-touch { --btn-px: 22px; font-size: 16px; }

  .service-card { min-height: 0; }
  .field-row { grid-template-columns: minmax(0, 1fr); }
  .hero-badge { position: static; margin: 34px auto 0; }
}

@media (max-width: 420px) {
  .btn { --btn-px: 26px; font-size: 17px; }
  .btn-lg { --btn-px: 34px; font-size: 19px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    /* Delays must go too. animation-fill-mode: both holds the *start* frame
       during the delay, so a staggered entrance would leave the hero blank for
       up to 320ms even with the duration zeroed out. */
    animation-delay: 0ms !important;
    transition-delay: 0ms !important;
  }
  .service-card:hover, .btn:hover { transform: none; }
}
