/* ==========================================================================
   Villa Terranea — Eaubonne — Landing page
   ========================================================================== */

/* --- Fonts -----------------------------------------------------------
   Futura PT is served via Adobe Fonts (Typekit kit ldd4khd), linked in
   index.html's <head>. Adobe hosts and licenses the actual font files
   for this domain — do NOT self-host font files copied from a desktop
   install here, that's a different license (see assets/fonts/README).
   The kit only publishes two static weights for "futura-pt": 400 and
   700. Any other font-weight value in this file resolves to whichever
   of those two is numerically closer (standard CSS font-matching), so
   stick to 400/700 when adding new rules.
   ----------------------------------------------------------------------- */

/* --- Tokens ------------------------------------------------------------ */
:root {
  --terracotta: #C66E4E;
  --terracotta-dark: #a85a3d;
  --sand: #D2B48C;
  --sage: #E5EBDF;
  --black: #000000;
  --white: #FFFFFF;

  --ink: #201c1a;

  --font: "futura-pt", "Jost", "Century Gothic", "Segoe UI", sans-serif;

  --container-max: 1560px;
  --sidebar-width: 452px;
  /* Space kept clear between the content's right edge and the fixed
     contact panel, on top of --gutter. Used together in one place
     (--content-inset below) so the hero image, colour section panels,
     and text always line up — never edit the two independently. */
  --sidebar-gap: 50px;
  --content-inset: calc(var(--sidebar-width) + var(--sidebar-gap) + var(--gutter));
  --gutter: clamp(20px, 4vw, 48px);
  --header-height: 96px;

  --radius-pill: 999px;
  --radius-card: 4px;

  --shadow-panel: 0 24px 60px -20px rgba(0, 0, 0, 0.25);
}

/* --- Reset --------------------------------------------------------------*/
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font);
  font-weight: 400;
  color: var(--ink);
  background: var(--white);
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }
ul { list-style: none; margin: 0; padding: 0; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
h1, h2, h3, h4 { margin: 0; font-weight: 700; }
p { margin: 0; }

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

.sticky-scope { position: relative; }

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

/* --- Full-page loader -----------------------------------------------------
   Shown until window "load" fires (main.js), then faded out once via
   .is-hidden. Sits above everything (z-index), so it must never be the
   thing that scrolls — position:fixed regardless of where it sits in the
   DOM. */
.page-loader {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--terracotta);
  transition: opacity 0.6s ease;
}
/* brightness(0) invert(1) turns the logo (orange mark + black wordmark)
   into a flat white silhouette, regardless of its own colours — no need
   for a second, white-only copy of the asset. */
.page-loader__logo {
  width: 120px;
  height: auto;
  filter: brightness(0) invert(1);
  animation: page-loader-pulse 1.6s ease-in-out infinite;
}
@keyframes page-loader-pulse {
  0%, 100% { opacity: 0.55; transform: scale(0.92); }
  50% { opacity: 1; transform: scale(1.05); }
}
.page-loader.is-hidden {
  opacity: 0;
  pointer-events: none;
}

/* --- Buttons ------------------------------------------------------------*/
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  padding: 0.95em 1.8em;
  border-radius: var(--radius-pill);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  border: 2px solid transparent;
  transition: transform 0.15s ease, background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
  white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--filled {
  background: var(--terracotta);
  color: var(--white);
  border-color: var(--terracotta);
}
.btn--filled:hover { background: var(--terracotta-dark); border-color: var(--terracotta-dark); }

.btn--outline {
  background: var(--white);
  color: var(--ink);
  border-color: var(--terracotta);
}
.btn--outline:hover { background: var(--terracotta); color: var(--white); }

.btn--outline-light {
  background: transparent;
  color: var(--white);
  border-color: var(--white);
}
.btn--outline-light:hover { background: var(--white); color: var(--terracotta); }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: currentColor;
  padding: 0.6em 1.4em;
  font-size: 0.85rem;
}
.btn--ghost:hover { background: var(--ink); color: var(--white); }

.btn--sm { padding: 0.6em 1.3em; font-size: 0.8rem; }
.btn--block { width: 100%; }

/* --- Decorative scallop divider ------------------------------------------*/
.scallop-divider {
  height: clamp(34px, 5.5vw, 72px);
  background-image: url("../images/Villa-Terranea_graphic-line_Eaubonne-VEFA.svg");
  /* Stretched to fill the section's exact width instead of tiled, so it
     never gets cut off mid-shape at the edge. */
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
}
.scallop-divider--sage { background-color: var(--sage); }
.scallop-divider--white { background-color: var(--white); margin-bottom: clamp(32px, 5vw, 56px); }

/* ==========================================================================
   Header
   ========================================================================== */
/* Mobile-only strip above the header — display:none added back for
   desktop in the ≥900px query, same pattern as .nav-toggle/.mobile-nav. */
.mobile-topbar {
  background: var(--terracotta);
  color: var(--white);
  text-align: center;
  font-weight: 700;
  font-size: calc(0.78rem + 6px);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 8px 12px;
}
.site-header {
  background: var(--white);
}
.site-header__inner {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
}
.logo {
  display: block;
  width: 106px;
  height: auto;
  flex-shrink: 0;
}
/* Mobile: a plain stacked row above the nav row. Desktop (≥900px,
   see media query below): pulled out of the flow to overlap the top-left
   corner of the hero image below (see the source mockup) — a white block
   taller than the header row, that the logo sits on top of. Never
   position:fixed/sticky, so it scrolls away with the rest of the page. */
.logo-block {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px 0;
  background: var(--white);
}
.main-nav {
  display: none;
}
.main-nav__list {
  display: flex;
  gap: 100px;
}
.main-nav__list a {
  font-weight: 700;
  font-size: calc(0.85rem + 4px);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.4em 0;
  border-bottom: 2px solid transparent;
  transition: border-color 0.15s ease, color 0.15s ease;
  /* Flex items shrink by default — at narrower desktop widths (common
     laptop windows, not just the widest screens) that let "La résidence"
     and "Les appartements" get squeezed narrower than their own text,
     wrapping it onto a second line. Locked to one line instead. */
  white-space: nowrap;
}
.main-nav__list a:hover {
  color: var(--terracotta);
  border-color: var(--terracotta);
}

.nav-toggle {
  /* Independent of the logo's own centering below — pinned to the
     vertical centre of the header row and the right edge, so moving the
     logo (size, centering...) never affects it. Harmless on desktop:
     .nav-toggle is display:none there (see the ≥900px query). */
  position: absolute;
  top: 50%;
  right: 40px;
  transform: translateY(-50%);
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 26px;
  height: 40px;
  border: none;
  background: transparent;
  padding: 0;
}
.nav-toggle span {
  display: block;
  height: 2px;
  background: var(--ink);
  border-radius: 2px;
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.mobile-nav {
  position: fixed;
  /* top is set from JS to match the header's actual rendered height,
     since the header is no longer a fixed single-row height on mobile
     (stacked logo row + nav row). */
  inset: 0 0 0 0;
  background: var(--white);
  z-index: 39;
  padding: 32px var(--gutter);
  transform: translateY(-8px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.mobile-nav.is-open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.mobile-nav__logo { display: block; width: 90px; height: auto; margin: 0 auto 28px; }
.mobile-nav__list { display: flex; flex-direction: column; gap: 4px; }
.mobile-nav__list a {
  display: block;
  padding: 16px 4px;
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--terracotta);
}
.mobile-nav__phone {
  margin-top: 28px;
  display: block;
  color: var(--terracotta);
  font-weight: 700;
  font-size: 1.3rem;
}

/* ==========================================================================
   Hero
   ========================================================================== */
.hero {
  padding-top: clamp(0px, 3vw, 10px);
}
.hero__media {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 0;
  overflow: hidden;
  background: var(--sage);
}
.hero__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.hero__slide.is-active { opacity: 1; }
.hero__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Anchored bottom-right, then zoomed 15% past the normal cover-fit
   (transform-origin matches the anchor so the crop stays pinned to the
   same bottom-right corner while zooming in) — the slide box itself is
   untouched, only the image within it grows. */
.hero__slide-img--bottom {
  object-position: right bottom;
  transform: scale(1.08);
  transform-origin: right bottom;
}
.hero__gradient {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0) 55%);
  pointer-events: none;
}
.hero__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: clamp(18px, 3vw, 40px);
  pointer-events: none;
}
.hero__headline {
  text-align: right;
  color: var(--white);
  font-size: clamp(1.05rem, 2.1vw, 1.7rem);
  font-weight: 700;
  text-transform: uppercase;
  line-height: 1.25;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.75), 0 4px 26px rgba(0, 0, 0, 0.6);
  max-width: 26em;
  margin-left: auto;
  margin-top: 20px;
  margin-right: 30px;
}
.hero__foot {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 14px;
  pointer-events: auto;
}
.hero__reserve {
  color: var(--white);
  font-weight: 700;
  text-transform: uppercase;
  font-size: calc(0.85rem + 10px);
  letter-spacing: 0.04em;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}
.hero__offer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}
.hero__offer-plus {
  color: var(--white);
  font-weight: 700;
  font-size: 4.16rem;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}
.hero__badge {
  background: var(--white);
  color: var(--ink);
  border: 6px solid var(--terracotta);
  border-radius: var(--radius-pill);
  padding: 1.13em 1.4em;
  font-weight: 700;
  text-transform: uppercase;
  font-size: calc(1.36rem + 4px);
  line-height: 1.3;
  text-align: center;
}
.hero__badge--filled {
  background: var(--terracotta);
  color: var(--white);
  border-color: var(--white);
  padding-block: 0.5em;
  line-height: 1.15;
}
.hero__badge sup { font-size: 0.6em; }
.hero__badge__lower { text-transform: none; }
.hero__badge__big { font-size: calc(1.36rem + 10px); }

.hero__nav-btn {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.85);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  z-index: 2;
  transition: background-color 0.15s ease;
}
.hero__nav-btn:hover { background: var(--white); }
.hero__nav-btn--prev { left: 50px; }
.hero__nav-btn--next { right: 50px; }
.hero__nav-btn svg { width: 18px; height: 18px; }

.hero__dots {
  position: absolute;
  bottom: 24px;
  left: 50%;
  translate: -50% 0;
  display: flex;
  gap: 8px;
  z-index: 2;
  pointer-events: auto;
}
.hero__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.55);
  padding: 0;
}
.hero__dot.is-active { background: var(--white); }

/* Thin bar under the slider showing the countdown to the next slide —
   driven from JS (see main.js), which restarts the fill animation on
   every slide change, whether triggered by autoplay or a manual click. */
.hero__progress {
  margin-top: 0;
  height: 3px;
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.1);
  overflow: hidden;
}
.hero__progress-bar {
  height: 100%;
  width: 0%;
  background: var(--terracotta);
}
.hero__progress-bar.is-filling {
  animation: hero-progress-fill 5s linear forwards;
}
.hero__progress-bar.is-paused { animation-play-state: paused; }
@keyframes hero-progress-fill {
  from { width: 0%; }
  to { width: 100%; }
}

/* --- Contact panel --------------------------------------------------------*/
.contact-panel {
  background: var(--white);
  border: 5px solid var(--terracotta);
  border-radius: 0;
  box-shadow: var(--shadow-panel);
  padding: clamp(24px, 3vw, 36px);
  margin-top: 24px;
  transition: border-color 0.2s ease, border-width 0.2s ease;
}
.contact-panel:hover {
  border-color: var(--black);
  border-width: 10px;
}
/* Desktop CTAs trigger this to draw attention to the (always-visible,
   fixed) panel instead of navigating away — see main.js. */
.contact-panel.is-blinking {
  animation: panel-blink 0.4s ease-in-out 3;
}
@keyframes panel-blink {
  0%, 100% { border-color: var(--terracotta); border-width: 5px; }
  50% { border-color: var(--black); border-width: 15px; }
}
/* Once the visitor has submitted the form, the panel switches to a
   confirmation state: orange fill, intro copy (kicker/title/subtitle/
   lead) removed, "Merci !" takes over as the panel's headline instead. */
.contact-panel.is-success {
  background: var(--terracotta);
  border-color: var(--white);
}
.contact-panel.is-success .contact-panel__kicker,
.contact-panel.is-success .contact-panel__title,
.contact-panel.is-success .contact-panel__subtitle,
.contact-panel.is-success .contact-panel__lead {
  display: none;
}
.contact-panel.is-success .contact-panel__phone { color: var(--white); }
/* Everything sitting directly on the orange fill turns white; the select/
   input fields keep their own white background, so their text stays as
   it was (already legible there). */
.contact-panel.is-success .refine-form__intro,
.contact-panel.is-success .field--range label,
.contact-panel.is-success .refine-budget__scale,
.contact-panel.is-success .refine-form__done {
  color: var(--white);
}
/* The filled CTA is terracotta-on-white normally, which disappears against
   this same terracotta panel — flip it to white-on-terracotta instead. */
.contact-panel.is-success .refine-form__fields .btn--filled {
  background: var(--white);
  color: var(--terracotta);
  border-color: var(--white);
}
.contact-panel.is-success .refine-form__fields .btn--filled:hover {
  background: var(--sand);
  border-color: var(--sand);
}
/* Third and last step: refine-form submitted too — a distinct beige fill
   so all 3 steps (white form, orange thank-you, beige complete) read as
   clearly separate. Added alongside .is-success (not instead of it), so
   it must come after it here to win the shared selectors below. */
.contact-panel.is-complete {
  background: var(--sand);
  border-color: var(--terracotta);
}
.contact-panel.is-complete .contact-panel__success h3,
.contact-panel.is-complete .contact-panel__success p,
.contact-panel.is-complete .contact-panel__phone,
.contact-panel.is-complete .refine-form__done {
  color: var(--ink);
}
.contact-panel__kicker {
  color: var(--terracotta);
  font-weight: 700;
  text-transform: uppercase;
  font-size: calc(1.05rem + 12px);
  letter-spacing: 0.02em;
  text-align: center;
}
.contact-panel__title {
  color: var(--black);
  font-weight: 700;
  /* The loaded font only ships Regular/Bold — push past 700 with a
     text-stroke, since a heavier weight file isn't available. */
  -webkit-text-stroke: 0.6px currentColor;
  font-size: calc(clamp(1.6rem, 3vw, 2rem) + 8px);
  text-transform: uppercase;
  line-height: 1.1;
  letter-spacing: 1px;
  margin: 2px 0 6px;
  text-align: center;
}
.contact-panel__subtitle {
  color: var(--terracotta);
  font-weight: 700;
  text-transform: uppercase;
  font-size: calc(0.95rem + 8px);
  letter-spacing: 0.02em;
  text-align: center;
}
.contact-panel__lead {
  margin-top: 20px;
  font-weight: 700;
  font-size: calc(0.95rem + 2px);
  line-height: calc(1.4em - 3px);
  text-transform: uppercase;
  text-align: center;
}
.contact-panel__lead::before {
  content: "";
  display: block;
  width: 50px;
  height: 3px;
  margin: 0 auto 12px;
  background: var(--terracotta);
}
.contact-form {
  width: 70%;
  margin: 18px auto 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Our own `display: flex` above outranks the UA stylesheet's [hidden]
   rule, so the JS-driven `contactForm.hidden = true` on submit needs an
   explicit override here to actually hide the form. */
.contact-form[hidden] { display: none; }
.field {
  position: relative;
}
.field input,
.field select {
  width: 100%;
  border: 1px solid var(--black);
  border-radius: 0;
  padding: calc(0.85em - 1.5px) 1em;
  font-family: inherit;
  font-size: 0.92rem;
  color: var(--terracotta);
  background: var(--white);
  appearance: none;
}
.field input::placeholder { color: var(--terracotta); opacity: 1; }
.field input:focus,
.field select:focus {
  outline: none;
  border-color: var(--terracotta);
  box-shadow: 0 0 0 3px rgba(198, 110, 78, 0.15);
}
.field--select::after {
  content: "";
  position: absolute;
  right: 1em;
  top: 50%;
  translate: 0 -50%;
  width: 10px;
  height: 10px;
  border-right: 2px solid var(--terracotta);
  border-bottom: 2px solid var(--terracotta);
  transform: rotate(45deg) translateY(-50%);
  pointer-events: none;
}

.consent {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 4px;
}
.consent input {
  margin-top: 3px;
  accent-color: var(--terracotta);
  flex-shrink: 0;
}
.consent label {
  font-size: 0.72rem;
  line-height: 1.45;
  color: #4a4540;
}
.contact-panel__fineprint {
  margin-top: 12px;
  font-size: 0.68rem;
  line-height: 1.5;
  color: #6b655e;
}
.contact-panel__fineprint a { text-decoration: underline; }

.contact-panel__submit { margin-top: 6px; }

.contact-panel__phone {
  display: block;
  margin-top: 10px;
  margin-bottom: -8px;
  color: var(--terracotta);
  font-weight: 700;
  font-size: calc(1.4rem + 8px);
  letter-spacing: 0.03em;
  text-align: center;
}

.contact-panel__success {
  text-align: center;
  padding: 0 0 4px;
}
.contact-panel__success h3 {
  color: var(--white);
  font-size: calc(1.8rem + 8px);
  font-weight: 700;
  text-transform: uppercase;
  margin: 0 0 12px;
}
.contact-panel__success p { color: var(--white); font-size: 0.95rem; line-height: 1.5; }

.refine-form {
  margin-top: 20px;
  padding-top: 20px;
  text-align: left;
}
/* Same thickness/width as the accent line above "Inscrivez-vous..." in
   the main form (.contact-panel__lead::before) — white here since the
   refine-form only ever appears on the orange success fill. */
.refine-form::before {
  content: "";
  display: block;
  width: 50px;
  height: 3px;
  margin: 0 auto 20px;
  background: var(--white);
}
.refine-form__intro {
  font-weight: 700;
  font-size: 0.85rem;
  text-transform: uppercase;
  margin-bottom: 12px;
  text-align: center;
}
.refine-form__fields {
  width: 70%;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.field--range label {
  display: block;
  font-size: 0.85rem;
  font-weight: 700;
  margin-bottom: 8px;
}
/* Track and thumb are both fully custom (appearance: none on both) —
   mixing a styled track with a native thumb is what left the thumb
   riding visibly above/off the line before. The thumb's negative
   margin-top is what centres an 18px circle on a 4px track in WebKit;
   Firefox centres -moz-range-thumb on -moz-range-track on its own. */
.field--range input[type="range"] {
  width: 100%;
  margin: 10px 0;
  -webkit-appearance: none;
  appearance: none;
}
.field--range input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  background: rgba(0, 0, 0, 0.2);
  border-radius: 2px;
}
.field--range input[type="range"]::-moz-range-track {
  height: 4px;
  background: rgba(0, 0, 0, 0.2);
  border-radius: 2px;
}
.field--range input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--terracotta);
  margin-top: -7px;
  cursor: pointer;
}
.field--range input[type="range"]::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: none;
  border-radius: 50%;
  background: var(--terracotta);
  cursor: pointer;
}
.refine-budget__scale {
  display: flex;
  justify-content: space-between;
  font-size: 0.7rem;
  color: #6b655e;
  margin-top: 4px;
}
.refine-form__done {
  font-size: 0.9rem;
  line-height: 1.5;
  text-align: center;
}
.refine-form__fineprint {
  margin-top: 12px;
  font-size: 0.68rem;
  line-height: 1.5;
  text-align: center;
  color: rgba(255, 255, 255, 0.75);
}

/* --- "Louer" not offered here: explanatory modal, blurred backdrop ------ */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(32, 28, 26, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 200;
}
.modal-overlay[hidden] { display: none; }
.modal {
  position: relative;
  background: var(--white);
  border-radius: 3px;
  padding: clamp(28px, 4vw, 40px);
  max-width: 480px;
  width: 100%;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.modal__close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 34px;
  height: 34px;
  border: none;
  background: transparent;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink);
}
.modal__title {
  color: var(--terracotta);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 1.3rem;
  line-height: 1.25;
  margin-bottom: 16px;
  padding-right: 24px;
}
.modal__text { font-size: 0.95rem; line-height: 1.55; margin-bottom: 14px; }
.modal__text:last-of-type { margin-bottom: 24px; }
.modal__actions { display: flex; flex-direction: column; gap: 10px; }

/* Desktop: fixed sidebar ---------------------------------------------------
   The contact panel stays pinned to the top-right of the viewport while
   the visitor scrolls through the page (position: fixed), and switches to
   position: absolute — anchored to the bottom of .sticky-scope — once the
   footer is about to appear, so it never covers the footer links. See
   assets/js/main.js for the scroll logic that toggles `.is-bottomed`.
   ----------------------------------------------------------------------- */
@media (min-width: 1180px) {
  .hero__media { aspect-ratio: unset; height: 810px; }

  .hero__nav-btn--next { right: 80px; }

  .container {
    padding-right: var(--content-inset);
  }

  .contact-panel {
    margin-top: 0;
    /* +50px over the base sidebar width, growing only to the right: the
       `right` offset below is pulled in by the same 50px so the panel's
       left edge (and therefore its 30px overlap with the slider/section
       backgrounds) doesn't move — see the width/right pairing. Kept off
       --sidebar-width itself so --content-inset (text/image reservation
       across the rest of the page) is untouched. */
    width: calc(var(--sidebar-width) + 50px);
    /* Overlaps the hero slider (and other section backgrounds) by 30px:
       --sidebar-gap (50px) is the natural breathing room between content
       and the panel, so closing that gap (50px) plus a true 30px overlap
       needs an 80px pull, minus the 50px added to width above. */
    right: calc(max(var(--gutter), calc((100vw - var(--container-max)) / 2 + var(--gutter))) + 30px);
    z-index: 30;
  }
  .contact-panel.is-fixed {
    position: fixed;
    top: calc(var(--header-height) + 24px);
    max-height: calc(100vh - var(--header-height) - 48px);
    overflow-y: auto;
  }
  .contact-panel.is-bottomed {
    position: absolute;
    top: auto;
    bottom: 0;
  }
}

/* Extra inset for text/interactive content specifically — photos (hero,
   showcase image, town gallery) are NOT wrapped in this, so they keep
   using the section's own edge. */
.text-inset { padding-inline: 40px; }

/* ==========================================================================
   Section: Résidence à taille humaine
   ========================================================================== */
/* Colour fills stay inside the grid column (not full-bleed): the
   background lives on the .container, not on the full-width <section>. */
.section > .container { padding-block: clamp(28px, 7vw, 76px); }
/* The frieze must sit flush against the last gallery photo, so this
   section carries no bottom breathing room of its own. */
#eaubonne > .container { padding-bottom: 0; }
.section--terracotta > .container { color: var(--white); }

/* Colour fill lives on its own layer (not the container's background)
   so it can stop exactly at the same right edge as the hero image/
   content, instead of bleeding into the padding reserved for the
   contact panel on desktop. */
.section--sage > .container,
.section--terracotta > .container,
.section--sand > .container {
  position: relative;
  z-index: 0;
}
.section--sage > .container::before,
.section--terracotta > .container::before,
.section--sand > .container::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--gutter);
  /* Below 1180px (see the override below) this must match the same
     --gutter used on the left and by every other element on the page —
     right:0 (the true screen edge) broke that symmetry specifically for
     this colour fill, on both mobile and tablet widths. */
  right: var(--gutter);
  z-index: -1;
}
.section--sage > .container::before { background: var(--sage); }
.section--terracotta > .container::before { background: var(--terracotta); }
.section--sand > .container::before { background: var(--sand); }
@media (min-width: 1180px) {
  .section--sage > .container::before,
  .section--terracotta > .container::before,
  .section--sand > .container::before {
    right: var(--content-inset);
  }
}

.section-title {
  color: var(--terracotta);
  font-size: clamp(1.4rem, 2.6vw, 2rem);
  line-height: 1.25;
  text-transform: uppercase;
  max-width: 32em;
}
.section-title--ink { color: var(--ink); }
.section-title--white { color: var(--white); }
.section--terracotta .section-title,
.section--terracotta h2,
.section--terracotta h3 { color: var(--white); }

.prose { margin-top: 20px; display: flex; flex-direction: column; gap: 12px; max-width: 46em; }
.prose p { line-height: 1.6; font-size: 0.98rem; }

/* --- Legal page (mentions-legales.html) --------------------------------- */
.legal-content { max-width: 56em; }
.legal-content h3 {
  color: var(--terracotta);
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-top: 16px;
}
.legal-content h4 {
  font-size: 0.98rem;
  margin-top: 4px;
}
.legal-content ul {
  list-style: disc;
  padding-left: 1.3em;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.legal-content li { line-height: 1.6; font-size: 0.98rem; }
.legal-content a { text-decoration: underline; }
.legal-content strong { font-weight: 700; }
.legal-content__updated {
  font-size: 0.8rem;
  color: #6b655e;
  margin-top: 4px;
}
.legal-content__placeholder {
  background: rgba(198, 110, 78, 0.15);
  color: var(--terracotta-dark);
  padding: 0.1em 0.4em;
  font-weight: 700;
}

/* Standalone pages (no hero for the header's logo to overlap onto) —
   on desktop, .logo-block is pulled out of flow and absolutely
   positioned at 200px tall over whatever follows the 96px-tall header
   row, since on the homepage that's the hero image it's designed to
   sit on. Without a hero, the very next section needs that same 200px
   cleared with real padding instead, or its own heading renders
   half-hidden under the logo. */
@media (min-width: 900px) {
  .standalone-page-section { padding-top: 128px; }
}

.commute {
  margin: clamp(32px, 5vw, 56px) 0;
}
/* Viewport clips the vertical slide between the two tracks and animates
   its own height to match whichever track is currently showing (set in
   JS, see assets/js/main.js). */
.commute__viewport {
  position: relative;
  overflow-x: auto;
  overflow-y: hidden;
  transition: height 0.4s ease;
}
.commute__track {
  display: flex;
  align-items: flex-start;
  gap: 0;
  min-width: 640px;
  width: max-content;
  position: absolute;
  top: 0;
  left: 0;
  transition: transform 0.4s ease, opacity 0.4s ease;
}
.commute__track--walk { transform: translateY(0); opacity: 1; z-index: 2; }
.commute__track--car { transform: translateY(100%); opacity: 0; z-index: 1; }
.commute[data-mode="car"] .commute__track--walk { transform: translateY(-100%); opacity: 0; z-index: 1; }
.commute[data-mode="car"] .commute__track--car { transform: translateY(0); opacity: 1; z-index: 2; }
/* Single continuous line, positioned to cross through the centre of every
   dot. Inserted as the first child so everything else paints above it. */
/* Vertically centred on the walk/car pill graphic (44px wide, ~150px
   tall at its native ratio — so centre sits at ~75px), and starts only
   after the Villa Terranea mark (54px + 18px margin) instead of running
   underneath it. Every element it crosses is `position:relative` so it
   paints below them (see the "paints above the line" comments below). */
.commute__line {
  position: absolute;
  left: 72px;
  right: 0;
  top: 75px;
  height: 2px;
  background: var(--terracotta);
}
/* Reserves the logo's old flex slot inside each sliding track (width +
   gap) without rendering anything — the real logo lives outside the
   tracks now, see .commute__logo below. */
.commute__logo-spacer {
  width: 54px;
  margin-right: 18px;
  flex-shrink: 0;
}
.commute__logo {
  position: absolute;
  left: 0;
  /* Centres it on the same 75px line the pill graphic and dots share
     (75 - half of 74 = 38). */
  top: 38px;
  width: 54px;
  height: 74px;
  z-index: 3;
}
.commute__start {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
}
.commute__start-graphic { width: 44px; height: auto; }

.commute__stop {
  position: relative;
  flex: 1;
  min-width: 92px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-inline: 6px;
  /* Moved from .commute__time's own margin-bottom below: keeps the dot at
     the same 75px line centre while bringing the time label closer to it. */
  padding-top: 20px;
}
.commute__time {
  height: 18px;
  line-height: 18px;
  font-weight: 700;
  font-size: 0.82rem;
  /* Pushes the dot below down to sit at the same 75px line centre as
     the pill graphic (20px stop padding-top + 18px own height + 28px
     margin + 9px half-dot). */
  margin-bottom: 28px;
  white-space: nowrap;
}
.commute__time sup { font-size: 0.65em; }
.commute__point {
  position: relative;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--terracotta);
  margin-bottom: 8px;
}
.commute__label { font-size: 0.72rem; line-height: 1.3; color: var(--ink); text-align: center; }
/* JS pins this element's width to the active track's (which can exceed
   the viewport on narrow screens, since the track scrolls horizontally
   on its own via .commute__viewport) — max-width caps it back to the
   available space so it wraps instead of pushing the whole page wider. */
.commute__note { font-size: 0.68rem; color: #6b655e; text-align: right; margin-top: 8px; max-width: 100%; }

.section__actions { margin-top: clamp(20px, 3vw, 32px); }

/* ==========================================================================
   Showcase image (contained within the grid, not full-bleed)
   ========================================================================== */
#interieur-visuel { margin-top: 40px; }
.showcase-image {
  width: 100%;
  height: 610px;
  overflow: hidden;
}
.showcase-image img { width: 100%; height: 100%; object-fit: cover; }

/* ==========================================================================
   Comfort section (terracotta)
   ========================================================================== */
.comfort {
  display: grid;
  grid-template-columns: 1fr;
  gap: 36px;
}
.comfort__col + .comfort__col {
  border-top: 1px solid var(--white);
  padding-top: 32px;
}
.comfort__intro { line-height: 1.6; margin-bottom: 24px; }
.comfort__list-title { font-size: 1.25rem; text-transform: uppercase; margin-bottom: 16px; }
.comfort__list { display: flex; flex-direction: column; gap: 10px; }
.comfort__list li {
  position: relative;
  padding-left: 20px;
  line-height: 1.5;
  font-size: 0.95rem;
}
.comfort__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.55em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--white);
}
.comfort__badge-graphic { width: 160px; height: auto; }
.comfort__badge-text { margin: 18px 0 22px; line-height: 1.55; font-size: 0.95rem; }

/* ==========================================================================
   Pricing section (sand)
   ========================================================================== */
#appartements { margin-top: 40px; }
.pricing-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  margin-bottom: clamp(24px, 4vw, 44px);
}
.pricing-head__offer {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.pricing-head__offer-text {
  font-weight: 700;
  text-transform: uppercase;
  font-size: calc(0.85rem + 2px);
  line-height: 1.3;
  text-align: right;
}
.pricing-badge {
  background: var(--terracotta);
  color: var(--white);
  border-radius: var(--radius-pill);
  padding: 0.7em 1.5em;
  text-align: center;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 0.85rem;
  line-height: 1.3;
  border: 3px solid var(--white);
  /* Lines up the badge's own right edge with the "Je me renseigne"
     buttons' right edge below (the table's own right padding — see
     .pricing-table td), not just the table's outer edge. */
  margin-right: 12px;
}
.pricing-badge__big {
  display: inline-block;
  font-size: calc(1em + 6px);
  line-height: 1.05;
}

.pricing-table { width: 100%; }
.pricing-table table {
  width: 100%;
  border-collapse: collapse;
}
/* Exact 8px dash / 8px gap, 1px thick — a plain `dotted` border can't
   guarantee precise dash/gap lengths across browsers, so this is drawn
   as a background on the whole row instead of a per-cell border. */
.pricing-table th {
  text-align: left;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 700;
  padding: 0 12px 14px;
  color: var(--white);
  /* Dashed rule lives per-header-cell (not on the whole row) so it can
     stop before the action column instead of running under the CTAs. */
  background-image: repeating-linear-gradient(
    to right,
    var(--white) 0, var(--white) 8px,
    transparent 8px, transparent 16px
  );
  background-size: 100% 1px;
  background-repeat: no-repeat;
  background-position: bottom;
}
.pricing-table th:last-child { background-image: none; }
.pricing-table td {
  padding: 20px 12px;
  border-bottom: 1px solid var(--white);
  font-size: 0.92rem;
  vertical-align: middle;
}
.pricing-table tr:last-child td { border-bottom: none; }
/* The horizontal rule stops before the CTA column instead of running
   under the "Je me renseigne" buttons. */
.pricing-table td.col-cta { border-bottom: none; }
.pricing-table .lot { font-weight: 700; }
/* Shared with .pricing-cards (the mobile layout) — these price classes
   aren't table-specific, so they're not scoped under .pricing-table only
   (that previously left the mobile cards' prices completely unstyled:
   no strikethrough on the old price, no bold/size on the new one). */
.price-old {
  text-decoration: line-through;
  opacity: 0.7;
  white-space: nowrap;
  font-size: 1.38rem;
}
.price-new {
  font-weight: 700;
  font-size: 1.725rem;
  white-space: nowrap;
}
.price-sub { display: block; font-size: 0.68rem; font-weight: 400; opacity: 0.8; }
.pricing-table .col-cta { text-align: right; }
.pricing-table .btn--sm { padding: 0.765em 1.6575em; font-size: 1.02rem; }

.pricing-foot__cta:hover {
  background: var(--white);
  color: var(--terracotta);
  border-color: var(--terracotta);
}
.pricing-foot {
  margin-top: clamp(20px, 3vw, 32px);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 20px;
}
.pricing-foot__legal {
  font-size: 0.68rem;
  line-height: 1.6;
  color: #6b655e;
  max-width: 40em;
}

/* Mobile pricing cards */
.pricing-cards { display: none; flex-direction: column; gap: 16px; }
.pricing-card {
  background: rgba(255, 255, 255, 0.55);
  border-radius: 10px;
  padding: 18px;
}
.pricing-card__top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
.pricing-card__lot { font-weight: 700; font-size: 1.05rem; }
.pricing-card__type { font-size: 0.85rem; }
/* flex-wrap + gap: a long "Extérieurs" value (e.g. "Terrasse + Jardin")
   no longer has room to sit flush right of the label at card width —
   without these it shrinks both items down to zero gap instead of
   wrapping the value onto its own line. */
.pricing-card__row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 2px 12px; font-size: 0.85rem; padding: 4px 0; }
.pricing-card__price { margin: 10px 0 14px; }

/* ==========================================================================
   Town section
   ========================================================================== */
.town-intro { max-width: 60em; }
.town-intro p:first-child { color: var(--terracotta); }
.town-intro p { line-height: 1.6; margin-top: 10px; font-size: 0.98rem; }
.town-intro p:first-child { margin-top: 20px; }

.town-gallery {
  margin-top: clamp(28px, 4vw, 44px);
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}
.town-photo {
  margin: 0; /* reset the browser's default <figure> margin */
  position: relative;
  overflow: hidden;
  border-radius: 2px;
  aspect-ratio: 4 / 3;
}
.town-photo--wide { aspect-ratio: 16 / 6; }
.town-photo img { width: 100%; height: 100%; object-fit: cover; }
.town-photo__caption {
  position: absolute;
  left: 0; bottom: 0;
  background: var(--terracotta);
  color: var(--white);
  font-size: 0.82rem;
  font-weight: 700;
  padding: 10px 18px;
}

/* ==========================================================================
   Footer
   ========================================================================== */
.site-footer {
  background: var(--terracotta);
  color: var(--white);
}
.site-footer__inner {
  padding-block: 28px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  align-items: center;
  text-align: center;
}
.site-footer__brand {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.75rem;
}
.site-footer__brand img { height: 66.24px; width: auto; }
.site-footer__links {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 0.78rem;
}
@media (min-width: 640px) {
  .site-footer__inner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    /* Reset the mobile-only centering below — text-align inherits, so
       without this it would bleed into tablet/desktop's row layout too. */
    text-align: left;
  }
  .site-footer__links { align-items: flex-end; }
}
.site-footer__links a { text-decoration: underline; }
.site-footer__links a:hover { opacity: 0.8; }
.site-footer__credit { opacity: 0.9; }

/* ==========================================================================
   Mobile floating CTA
   ========================================================================== */
.mobile-cta {
  position: fixed;
  left: 50%;
  translate: -50% 0;
  bottom: 18px;
  z-index: 45;
  box-shadow: 0 10px 30px -6px rgba(0, 0, 0, 0.4);
}

/* ==========================================================================
   Breakpoints
   ========================================================================== */
@media (min-width: 640px) {
  .town-gallery { grid-template-columns: 1fr 1fr; }
  .town-photo--wide { grid-column: 1 / -1; }
  .pricing-table th:nth-child(4),
  .pricing-table td:nth-child(4) { display: table-cell; }
}

@media (min-width: 860px) {
  .comfort { grid-template-columns: 1fr 1fr; gap: 56px; }
  .comfort__col + .comfort__col {
    border-top: none;
    border-left: 1px solid var(--white);
    padding-top: 0;
    padding-left: 56px;
  }
}

@media (min-width: 900px) {
  .site-header__inner { flex-direction: row; align-items: center; height: var(--header-height); }
  .main-nav { display: block; margin-left: 240px; }
  .nav-toggle { display: none; }
  .mobile-nav { display: none; }
  .mobile-cta { display: none; }
  .mobile-topbar { display: none; }
  .pricing-cards { display: none; }
  .pricing-table { display: block; }

  /* The logo overlaps the hero image below the header row (see the
     "logo-block" rule) instead of sitting inline with the nav. Positioned
     with `left: var(--gutter)` relative to .site-header__inner (which is
     itself a .container, so this is the exact same reference edge the
     hero image/content use) — deliberately NOT `100vw`-based, which
     drifts from the real content edge by the scrollbar's width on
     platforms where the scrollbar takes up layout space. It's a square,
     and the logo sits centred within it on both axes. */
  .logo-block {
    position: absolute;
    top: 0;
    left: var(--gutter);
    z-index: 5;
    width: 200px;
    height: 200px;
    padding: 0;
  }
}

@media (max-width: 899px) {
  .pricing-table { display: none; }
  .pricing-cards { display: flex; }
  /* -35% from the base 106px, mobile only — desktop's .logo width is
     untouched (it's inside the absolutely-positioned 200x200 logo-block
     from the ≥900px query, a separate rule this doesn't reach). */
  .logo { width: 68.9px; }

  /* The open mobile-nav shows its own full logo (.mobile-nav__logo) right
     below — without this, the small header logo stays visible above it
     too, reading as the same mark shown twice at once. visibility:hidden
     (not display:none) keeps the header's own height/layout, so the
     close (×) button doesn't shift. */
  .site-header--nav-open .logo { visibility: hidden; }

  /* Fixed height instead of the 4:3 aspect-ratio used below 900px —
     desktop's own ≥1180px height:810px override is untouched. -50px per
     a later request (was 800px). */
  .hero__media { aspect-ratio: unset; height: 750px; }

  /* Full-bleed: cancel out .container's own padding-inline (--gutter) for
     just the slider and its progress bar, without touching the sibling
     .contact-panel, which keeps the normal inset. */
  .hero__media, .hero__progress {
    margin-inline: calc(-1 * var(--gutter));
    width: auto;
  }

  /* Matches the 7s AUTOPLAY_DELAY used on mobile (see main.js: 6s pan +
     1s held pause) — desktop keeps the default 5s set on
     .hero__progress-bar.is-filling above. */
  .hero__progress-bar.is-filling {
    animation-duration: 7s;
  }

  /* The offer badges/plus-sign were sized for the wide desktop hero and
     dwarf a narrow phone screen — scaled down for mobile only. nowrap +
     tighter sizing so both badges and the "+" fit on one line instead of
     wrapping. +10% on the reserve text and both badges (border, padding
     and font-size, so the padding-in-em scales with it), then another
     +10% on top of that per a later request (×1.21 total) — the em-based
     padding/height rules further below key off these font-sizes, so
     they grow proportionally along with it. */
  .hero__reserve { font-size: calc((0.85rem + 2px) * 1.21); }
  .hero__offer { flex-wrap: nowrap; gap: calc(6px * 1.21); }
  .hero__offer-plus { font-size: calc(1.3rem * 1.21); }
  .hero__badge {
    border-width: calc(2px * 1.21);
    padding: 0.5em 0.7em;
    font-size: calc(0.8rem * 1.21);
  }
  .hero__badge--filled { padding-block: 0.4em; }
  .hero__badge__big { font-size: calc(1rem * 1.21); }
  /* "Nouveaux prix" is single-line while the filled badge next to it wraps
     onto two — extra vertical padding brings both pills to the same
     height instead of the shorter single-line one looking mismatched. */
  .hero__badge:not(.hero__badge--filled) { padding-block: 1.05em; }

  /* Shifts the "Seulement pour..."/badges block up off the very bottom
     edge of the slider. */
  .hero__foot { margin-bottom: 30px; }

  /* Desktop keeps this right-aligned (explicit earlier request) — mobile
     only, centred instead. margin-inline:auto centres the box itself
     (still capped by max-width), not just the text within it. +4px on
     top of the base clamp() (which, at mobile widths, is pinned to its
     1.05rem floor) per a later request. */
  .hero__headline {
    text-align: center;
    margin-inline: auto;
    font-size: calc(clamp(1.05rem, 2.1vw, 1.7rem) + 4px);
  }

  /* Closer to the outer edges than the 50px used everywhere else. */
  .hero__nav-btn--prev { left: 40px; }
  .hero__nav-btn--next { right: 40px; }

  /* Slow horizontal "traveling" pan on every slide, desktop untouched.
     object-fit:cover (set in the base rule) already renders these
     landscape originals at full resolution and crops only what doesn't
     fit — animating object-position's X from one edge to the other pans
     across that *entire* original width, instead of the old
     scale()+translateX() approach, which only nudged the view within
     the already-cropped centre slice and never revealed the rest of the
     photo. 6s duration, with the 7s AUTOPLAY_DELAY on mobile (see
     main.js) giving it a 1s held pause once the pan lands, before the
     slide changes. */
  /* Scoped to .is-active (toggled by main.js on every slide change, see
     goTo()) rather than running continuously on every slide from page
     load: each time a slide becomes active this is a fresh application
     of the animation, so it always pans forward from the start and — no
     "infinite", no "alternate", "forwards" holds the end position — it
     stops there instead of reversing back. The inactive slides' own
     animation instance is simply torn down when .is-active is removed,
     invisibly, since they're hidden at that point anyway. */
  .hero__slide.is-active img {
    animation: hero-pan 6s ease-in-out forwards;
  }
  @keyframes hero-pan {
    from { object-position: 0% 50%; }
    to   { object-position: 100% 50%; }
  }
  /* This slide has its own bottom-anchored crop (a separate earlier
     request, kept here) — same full-width pan, just along the bottom
     instead of vertical-centre (img.… beats .hero__slide img on
     specificity, so this wins over the rule right above it). */
  .hero__slide.is-active img.hero__slide-img--bottom {
    animation: hero-pan-bottom 6s ease-in-out forwards;
  }
  @keyframes hero-pan-bottom {
    from { object-position: 0% 100%; }
    to   { object-position: 100% 100%; }
  }

  /* Same full-width pan on the single "perspective intérieure" showcase
     photo, so its entire original width is visible over time instead of
     a static centre crop. Still a genuine back-and-forth (unlike the
     slider's one-way pan above) — this image isn't tied to a slide
     change, so there's nothing to hand off to. 5s each way, with a 2s
     hold at both ends before reversing, baked into one 14s keyframe
     loop (5s pan + 2s pause + 5s pan back + 2s pause) instead of
     "alternate", which would reverse the instant it touches the edge. */
  .showcase-image img {
    animation: showcase-pan 14s ease-in-out infinite;
  }
  @keyframes showcase-pan {
    0%     { object-position: 0% 50%; }
    35.71% { object-position: 100% 50%; }
    50%    { object-position: 100% 50%; }
    85.71% { object-position: 0% 50%; }
    100%   { object-position: 0% 50%; }
  }

  /* The form sat flush against the hero above it (24px, desktop's own
     value) and flush against the scallop frieze below it (no margin at
     all) — harmonized to both use the same clamp() as the space between
     every other section (.section > .container's own padding-block),
     instead of two different, tighter gaps unique to this one panel. */
  .contact-panel { margin-top: clamp(28px, 7vw, 76px); }
  .scallop-divider--sage { margin-top: clamp(28px, 7vw, 76px); }

  /* +20% (was 70%) — mobile fields read as too short. Kept matching
     between the two forms, as before. */
  .contact-form, .refine-form__fields { width: 84%; }

  /* The refine-form (shown after the first form is submitted) was causing
     a page-wide horizontal scroll on mobile: "Budget* : 350 000 €" is one
     unbreakable run (every space in it, including the one inside the
     locale-formatted number, is non-breaking), so at narrow widths it
     could outgrow the label's box instead of wrapping. Stacking the value
     on its own line keeps every run short enough to always fit. */
  .field--range label { display: flex; flex-direction: column; gap: 2px; }

  /* Tighter interline for the legal fineprint under the form — the
     desktop 1.5 reads as too airy at mobile width. */
  .contact-panel__fineprint { line-height: 1.2; }

  /* Shrunk so "Ouverture de l'espace de vente" fits on one line at the
     narrow mobile panel width instead of wrapping to two. */
  .contact-panel__subtitle {
    font-size: 1.05rem;
    letter-spacing: normal;
    white-space: nowrap;
  }

  /* Larger, and a real Villa Terranea mark instead of no logo at all in
     this menu. */
  .mobile-nav__phone { font-size: 1.95rem; }

  /* .btn's own white-space:nowrap forces this long label past the
     button's edge on a narrow screen — let it wrap onto two lines. */
  .modal__actions .btn { white-space: normal; text-align: center; }
}

@media (min-width: 1180px) {
  .mobile-cta { display: none; }
}

@media (max-width: 599px) {
  .pricing-table th:nth-child(4),
  .pricing-table td:nth-child(4) { display: none; }
  .commute__label { font-size: 0.66rem; }
}

/* --- Cookie consent banner -------------------------------------------- */
.cookie-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 300;
  background: var(--white);
  border-top: 3px solid var(--terracotta);
  box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.15);
}
.cookie-banner[hidden] { display: none; }
.cookie-banner__inner {
  max-width: var(--container-max);
  margin-inline: auto;
  padding: 18px var(--gutter);
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
}
.cookie-banner__inner p {
  flex: 1 1 420px;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}
.cookie-banner__actions {
  display: flex;
  gap: 10px;
  flex-shrink: 0;
}
@media (max-width: 599px) {
  .cookie-banner__inner {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
    padding: 14px var(--gutter);
  }
  /* Indispensable : la règle de base pose `flex: 1 1 420px` sur ce paragraphe.
     Cette base de 420px s'applique le long de l'axe principal — une largeur en
     desktop, mais une HAUTEUR ici, puisque le conteneur passe en colonne. Le
     paragraphe était donc forcé à 420px de haut pour moins de 200px de texte,
     d'où la bannière qui occupait presque tout l'écran avec un grand vide
     blanc. On lui rend sa hauteur de contenu. */
  .cookie-banner__inner p { flex: 0 0 auto; }
  .cookie-banner__actions { justify-content: flex-end; }
}

/* --- Lead API: honeypot, error message, reCAPTCHA notice ------------------ */
.field--trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.form-error {
  margin: 4px 0 0;
  font-size: 0.78rem;
  line-height: 1.4;
  font-weight: 700;
  color: #a3321f;
}
.btn:disabled {
  opacity: 0.6;
  cursor: wait;
  transform: none;
}
/* The badge is hidden; the required Google notice sits in each form's fineprint. */
.grecaptcha-badge { visibility: hidden; }
.refine-form__fineprint a { text-decoration: underline; }

/* ==========================================================================
   Squeezed desktop range (1180–1560px): common laptop windows, not just
   the widest screens. Placed at the very end of the file, after every
   base rule these selectors also appear in — equal-specificity source
   order means an earlier copy of these overrides was silently losing to
   the later base declarations (.pricing-table td, .commute__stop, etc.),
   even though its media query correctly matched.
   ========================================================================== */

/* Between 1180px (sidebar goes fixed, .container loses --content-inset
   worth of width) and roughly --container-max (where the reservation
   stops mattering because the container is centred with slack on both
   sides), the pricing table's row content (nowrap prices + CTA button)
   no longer fits its narrower column and was overflowing, invisibly
   under the fixed panel — never clipped, so it just looked cut off.
   Shrinking the row's text/button here removes the overflow at every
   common laptop width (1280/1366/1440/1536px) without touching the
   ≥1560px or <1180px cases, which already had room to spare. */
@media (min-width: 1180px) and (max-width: 1560px) {
  .pricing-table th,
  .pricing-table td { padding-inline: 6px; }
  .pricing-table .price-old { font-size: 1.05rem; }
  .pricing-table .price-new { font-size: 1.3rem; }
  .pricing-table .btn--sm { padding: 0.55em 0.9em; font-size: 0.82rem; }

  /* Same squeezed range, same cause: the hero offer badges were sized
     for a full-width ≥1560px hero and wrap onto two lines (flex-wrap:
     wrap, see the base rule) once the hero itself is narrowed by the
     fixed sidebar's reservation at these common laptop widths. */
  .hero__offer-plus { font-size: 3.1rem; }
  .hero__badge {
    padding: 0.85em 1.1em;
    font-size: calc(1.1rem + 4px);
  }

  /* The commute timeline's 7 stops (place names, not just numbers) need
     more raw width than they get once the sidebar reservation narrows
     the section — it was already horizontally scrollable by design
     (.commute__viewport's overflow-x:auto, relied on the same way on
     mobile), but at these widths even the full desktop-sized timeline
     needed scrolling just to see the last stop or two. Trimming each
     stop's footprint here shrinks how much of it (if any) needs that
     scroll on ordinary laptop screens. */
  .commute__stop { min-width: 62px; padding-inline: 2px; }
  .commute__label { font-size: 0.6rem; }
  .commute__time { font-size: 0.7rem; }
}
/* Defensive safety net regardless of width: if this row's content ever
   doesn't fit anyway, it scrolls within its own table instead of
   silently overflowing past the section and disappearing under the
   fixed contact panel again. */
.pricing-table { overflow-x: auto; }

/* The fixed contact panel (.is-fixed, ≥1180px) caps itself to the
   viewport's height and scrolls internally once its content is taller
   — by design, for genuinely short windows. But the panel's content has
   grown (the reCAPTCHA disclosure paragraph is new) to the point where
   it now needs that internal scroll on perfectly ordinary laptop screen
   heights (768/800/900px), not just unusually short ones. Compacting
   the whole panel here keeps it scroll-free on those common heights
   instead; ≥1050px-tall windows are untouched. */
@media (min-width: 1180px) and (max-height: 1050px) {
  .contact-panel { padding-inline: 18px; padding-block: 48px; }
  .contact-panel__kicker { font-size: calc(1.05rem + 10px); }
  .contact-panel__title { font-size: calc(1.2rem + 14px); margin: 6px 0; }
  .contact-panel__subtitle { font-size: calc(0.85rem + 2px); margin-top: 4px; }
  .contact-panel__lead {
    margin-top: 8px;
    font-size: calc(0.8rem + 1px);
    line-height: 1.2;
  }
  .contact-panel__lead::before { margin-bottom: 6px; }
  .contact-form { margin-top: 8px; gap: 6px; }
  .field input,
  .field select { padding: 0.4em 0.8em; font-size: 0.85rem; }
  .consent { margin-top: 2px; gap: 6px; }
  .consent label { font-size: 0.66rem; line-height: 1.25; }
  .contact-panel__submit { margin-top: 2px; }
  .contact-panel__fineprint { margin-top: 4px; font-size: 0.6rem; line-height: 1.2; }
  .contact-panel__phone { margin-top: 4px; font-size: calc(1.1rem + 12px); }

  /* Same fix, second step: once the first form is submitted, the panel
     swaps in the "Merci !" + refine-form content (cloned from a
     <template>, see main.js) — which wasn't covered by the compaction
     above and had the exact same problem, cut off at the bottom by the
     same reCAPTCHA fineprint paragraph. */
  .contact-panel__success { padding-bottom: 2px; }
  .contact-panel__success h3 { font-size: calc(1.3rem + 4px); margin: 0 0 6px; }
  .contact-panel__success p { font-size: 0.82rem; line-height: 1.3; }
  .refine-form { margin-top: 8px; padding-top: 8px; }
  .refine-form::before { margin-bottom: 8px; }
  .refine-form__intro { margin-bottom: 6px; }
  .refine-form__fields { gap: 8px; }
  .field--range label { margin-bottom: 4px; }
  .field--range input[type="range"] { margin: 6px 0; }
  .refine-form__fineprint { margin-top: 4px; font-size: 0.6rem; line-height: 1.2; }
}
