:root {
  --bg-light: #f4efe8;
  --bg-dark: #14120f;
  --text-on-light: #1c1a17;
  --text-on-dark: #f4efe8;
  --accent: #a8683f;
  --muted: #6b6459;
  --font-display: 'Playfair Display', serif;
  --font-body: 'Inter', sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  background: var(--bg-light);
  color: var(--text-on-light);
  font-family: var(--font-body);
  overflow-x: hidden;
}

/* ===== Loader ===== */
#loader {
  position: fixed; inset: 0; z-index: 9999;
  background: var(--bg-dark);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1.5rem;
  transition: opacity 0.6s ease, visibility 0.6s ease;
}
#loader.loaded { opacity: 0; visibility: hidden; pointer-events: none; }
.loader-brand {
  font-family: var(--font-display);
  font-size: 1.4rem; letter-spacing: 0.3em;
  color: var(--text-on-dark);
  font-weight: 600;
}
.loader-brand span { color: var(--accent); }
#loader-bar-track {
  width: 220px; height: 2px; background: rgba(244,239,232,0.15);
}
#loader-bar { width: 0%; height: 100%; background: var(--accent); transition: width 0.2s ease; }
#loader-percent {
  font-size: 0.75rem; letter-spacing: 0.15em; color: var(--muted);
}

/* ===== Header ===== */
.site-header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 500;
  padding: 1.8rem 5vw;
  mix-blend-mode: difference;
}
.site-header nav { display: flex; justify-content: space-between; align-items: center; }
.logo {
  font-family: var(--font-display); font-weight: 700;
  font-size: 1.1rem; letter-spacing: 0.2em; color: #fff;
}
.logo span { color: var(--accent); }
.nav-links { display: flex; gap: 2.5rem; }
.nav-links a {
  color: #fff; text-decoration: none; font-size: 0.85rem;
  letter-spacing: 0.08em; text-transform: uppercase;
}

/* ===== Hero ===== */
.hero-standalone {
  /* Real feedback, 2026-09-17: first attempt (a dark gradient over the
     photo) still read as basically black — not different enough. Second
     pass: a real, distinct color (deep rust-brown, from the site's own
     terracotta accent family, not a near-black) as the actual base.
     Third pass, 2026-09-18: the interior walkthrough frame was swapped
     for a real exterior aerial housing-estate shot (Pexels, Kindel
     Media, photo 9716237) — Sir asked for an actual estate/exterior
     image, not the interior frame — and the overlay opacity was backed
     off so it's genuinely visible ("just about see it"), not a barely-
     there texture. */
  position: relative; z-index: 10;
  height: 92vh; width: 100%;
  /* 2026-09-18, later: Sir said the color itself (not just the overlay)
     was wrong, and the photo needed to read cleaner. #3d2818 shares the
     site's real accent hue (--accent: #a8683f) but is so dark/desaturated
     it read as muddy near-black rather than an intentional color. Rebuilt
     from the actual accent: #5a3520, same warm-copper family, genuinely
     a color rather than near-black. Overlay backed off further (was
     .72-.82, now .48-.62) so the photo is clearly the dominant layer. */
  background-color: #5a3520;
  background-image: linear-gradient(180deg, rgba(90,53,32,.48) 0%, rgba(90,53,32,.62) 100%),
                     url('../frames/hero_exterior.jpg');
  background-size: cover; background-position: center;
  display: flex; flex-direction: column; justify-content: center;
  padding: 0 6vw;
  color: var(--text-on-dark);
}
.hero-standalone .section-label {
  /* 2026-09-18: the site-wide --accent (#a8683f) and --muted (#6b6459)
     tokens are tuned for light backgrounds elsewhere on the site. Once
     the hero overlay was lightened so the photo reads clean, both
     failed real contrast checks here (measured off actual rendered
     pixels: as low as 1.5:1-2.5:1, WCAG AA needs 4.5:1 for this size).
     Scoped, lighter replacements for hero-only text — doesn't touch the
     shared tokens other sections still rely on. */
  color: #e6b489; font-size: 0.85rem; letter-spacing: 0.2em;
  text-transform: uppercase; margin-bottom: 1.5rem; display: block;
}
.hero-heading {
  font-family: var(--font-display); font-weight: 600;
  font-size: 8.5rem; line-height: 1.02; letter-spacing: -0.01em;
  max-width: 85vw;
}
/* Real bug found 2026-09-17, reproduced in a completely fresh browser
   (not tab-specific): `overflow: hidden` on each word span, at this
   heading's 8.5rem size, was clipping most glyphs down to a faint sliver
   instead of a clean crop — only "Lux" (of "Luxury") painted correctly,
   everything else rendered as near-invisible fragments despite every
   computed style (opacity, color, font) checking out fine via JS. No
   script anywhere animates `.word` (checked directly, no matches) —
   this rule was vestigial, serving no active purpose, so removed
   outright rather than patched around. */
.hero-heading .word { display: inline-block; }
.hero-tagline {
  font-size: 1.15rem; color: rgba(244,239,232,0.7);
  max-width: 32ch; margin-top: 2rem;
}
/* Real ask, 2026-09-17: Sir wanted price visible before the video starts,
   not buried after the whole scroll experience — matching how a real
   listing page (e.g. the Iliffe & Iliffe benchmark) shows price
   immediately under the title, before any gallery. */
.hero-price-row {
  display: flex; align-items: baseline; gap: 2.4rem; flex-wrap: wrap;
  margin-top: 1.8rem; padding-top: 1.8rem;
  border-top: 1px solid rgba(244,239,232,0.15);
}
.hero-price { display: flex; flex-direction: column; }
.hero-price-label {
  font-size: 0.75rem; letter-spacing: 0.15em; text-transform: uppercase;
  color: #cbb6a3; margin-bottom: 0.3rem;   /* see hero contrast note above */
}
.hero-price-value {
  font-family: var(--font-display); font-weight: 700; font-size: 2.2rem;
  color: #e6b489;   /* see hero contrast note above */
}
.hero-quickstats {
  display: flex; gap: 1.6rem; color: rgba(244,239,232,0.75); font-size: 1rem;
}
.hero-quickstats strong { color: var(--text-on-dark); font-weight: 600; }
/* 2026-09-18: Sir wanted a real way to skip the scroll-driven video
   walkthrough entirely and land straight on the full listing details —
   not everyone wants to scroll through the whole experience every time.
   Scroll behavior handled by `lenis.scrollTo()` in app.js rather than a
   bare anchor jump, so it animates with the same easing as every other
   scroll on the page instead of an instant, jarring cut.

   2026-09-18, later: real feedback — Sir called the first version
   "kind of bad," both weak and badly placed. It was a thin, low-opacity
   outline floating alone in the top-right corner, over the photo's
   bright hazy-sky area — a subtle ghost button with nothing behind it
   fighting a low-contrast patch of the image. Rebuilt as a solid-fill
   button (contrast checked: dark text on the fill is ~7.4:1, so it
   reads clearly no matter what part of the photo is behind it — it no
   longer depends on the photo at all) and moved out of the floating
   corner into the hero's actual content flow, directly under the price
   row where it reads as a real next action tied to the listing instead
   of a disconnected badge. */
.hero-skip-link {
  display: inline-flex; align-self: flex-start;
  margin-top: 1.4rem;
  background: #e6b489; color: #2a1a0f; text-decoration: none;
  font-size: 0.85rem; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 999px;
  padding: 0.9rem 1.8rem;
  transition: background-color 0.2s ease, transform 0.2s ease;
}
.hero-skip-link:hover {
  background-color: #f0c49b; transform: translateY(-1px);
}
.scroll-indicator {
  /* bottom offset trimmed 3rem -> 1.8rem, 2026-09-18: the new skip
     button pushed the hero's centered content block taller, and this
     is independently bottom-anchored - measured real clearance against
     it rather than guessing (was down to ~4px before this change). */
  position: absolute; bottom: 1.8rem; left: 6vw;
  display: flex; flex-direction: column; align-items: center; gap: 0.6rem;
}
.scroll-indicator span {
  font-size: 0.7rem; letter-spacing: 0.15em; color: #cbb6a3;   /* see hero contrast note above */
  text-transform: uppercase;
}
.scroll-arrow {
  width: 1px; height: 40px; background: #cbb6a3;
  position: relative;
}
.scroll-arrow::after {
  content: ''; position: absolute; bottom: 0; left: -3px;
  width: 7px; height: 7px; border-right: 1px solid #cbb6a3; border-bottom: 1px solid #cbb6a3;
  transform: rotate(45deg);
}

@media (prefers-reduced-motion: no-preference) {
  .hero-standalone { will-change: opacity; }
}

/* ===== Canvas ===== */
.canvas-wrap {
  position: fixed; inset: 0; z-index: 1;
  clip-path: circle(0% at 50% 50%);
}
#canvas { width: 100%; height: 100%; display: block; }

/* ===== Dark overlay ===== */
#dark-overlay {
  position: fixed; inset: 0; z-index: 50;
  background: var(--bg-dark);
  opacity: 0; pointer-events: none;
}

/* ===== Marquee ===== */
.marquee-wrap {
  position: fixed; top: 46%; left: 0; z-index: 40;
  width: 200%; pointer-events: none;
  opacity: 0;
}
.marquee-text {
  font-family: var(--font-display); font-weight: 700;
  font-size: 13vw; white-space: nowrap;
  color: rgba(244,239,232,0.08);
  -webkit-text-stroke: 1px rgba(244,239,232,0.18);
}

/* ===== Scroll container ===== */
/* Real feedback, 2026-09-17: with 3x the original video content now
   packed into the same 900vh, everything (including the stats numbers)
   flew past too fast to actually read. 1500vh gives each section real
   dwell time without changing any of the enter/leave percentages. */
/* z-index 60 is deliberate: #dark-overlay sits at 50, and everything
   inside this container (including the stats numbers) was rendering
   BELOW it — the overlay was covering fully-opaque white text with 90%
   black regardless of the text's own opacity, which is the real reason
   the stats read as washed out no matter how dark the overlay got.
   Higher than the overlay so the text actually sits on top of it. */
#scroll-container { position: relative; height: 1500vh; z-index: 60; }

.scroll-section {
  /* Real bug found 2026-09-15, Sir caught it live: this was `position:
     absolute`, positioned relative to #scroll-container — which is 900vh
     tall — so `top: 50%` put every single section at the exact same one
     fixed point in the DOCUMENT (roughly the container's vertical
     center), not spread across the page to match each one's own
     data-enter/data-leave window. Opacity was fading correctly per
     section, but the elements themselves were only ever physically
     inside the viewport during a narrow scroll range near the document's
     midpoint — explains the CTA appearing to vanish entirely at the real
     bottom (it was positioned thousands of pixels above where the page
     actually ends) and the general "flashes then disappears" feel.
     `position: fixed` matches the pattern already used correctly by
     .canvas-wrap/#dark-overlay elsewhere in this file — pinned to the
     real viewport, so scroll purely drives opacity/content, never the
     element's own position. */
  position: fixed; top: 50%; left: 0; width: 100%;
  transform: translateY(-50%);
  opacity: 0;
}
.section-inner { max-width: 40vw; }

.align-left { padding-left: 6vw; padding-right: 55vw; }
.align-right { padding-left: 55vw; padding-right: 6vw; }
.align-left .section-inner, .align-right .section-inner { max-width: 40vw; }

/* Real feedback, 2026-09-17: text sat directly on the video with no
   backing, illegible against busy footage. A solid backdrop card behind
   the text (not the video, not the whole section) fixes it regardless
   of what's playing underneath. */
.section-content .section-inner {
  background: rgba(20, 18, 15, 0.78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: 2.4rem 2.6rem;
  border-radius: 6px;
}
.section-content .section-label {
  display: block; font-size: 0.8rem; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--accent); margin-bottom: 1.2rem;
}
.section-content .section-heading {
  font-family: var(--font-display); font-weight: 600;
  font-size: 4rem; line-height: 1.08; margin-bottom: 1.4rem;
  color: var(--text-on-dark);
}
.section-content .section-body {
  font-size: 1.05rem; line-height: 1.6; color: rgba(244,239,232,0.75);
}

/* ===== Stats ===== */
.section-stats {
  width: 100%; padding: 0 8vw;
}
.stats-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 3vw;
}
.stat { display: flex; flex-direction: column; align-items: flex-start; }
.stat-number {
  font-family: var(--font-display); font-weight: 700;
  font-size: 5vw; color: var(--text-on-dark); line-height: 1;
}
.stat-suffix {
  font-size: 1.1rem; color: var(--accent); margin-top: 0.5rem;
  text-transform: uppercase; letter-spacing: 0.1em;
}
.stat-label {
  font-size: 0.85rem; color: rgba(244,239,232,0.55); margin-top: 0.4rem;
}

/* ===== CTA ===== */
/* Same backdrop-card fix as .section-content — this was actually the
   original 2026-09-15 "too faint against the busy photo" complaint,
   parked at the time; fixed for real now while touching this anyway. */
.section-cta .section-inner {
  background: rgba(20, 18, 15, 0.78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: 2.4rem 2.6rem;
  border-radius: 6px;
}
.section-cta .section-label {
  display: block; font-size: 0.8rem; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--accent); margin-bottom: 1.2rem;
}
.section-cta .section-heading {
  font-family: var(--font-display); font-weight: 600;
  font-size: 4.5rem; line-height: 1.05; margin-bottom: 1.4rem;
  color: var(--text-on-dark);
}
.section-cta .section-body {
  font-size: 1.05rem; line-height: 1.6; color: rgba(244,239,232,0.75); margin-bottom: 2rem;
  max-width: 34ch;
}
.cta-button {
  display: inline-block; padding: 1.1rem 2.6rem;
  background: var(--text-on-light); color: var(--bg-light);
  text-decoration: none; font-size: 0.9rem; letter-spacing: 0.08em;
  text-transform: uppercase; border-radius: 2px;
  transition: background 0.3s ease;
}
.cta-button:hover { background: var(--accent); }

/* ===== Full details section ===== */
/* Sits in normal document flow after the video experience ends — real
   listing content (description + full feature list), not scroll-jacked.
   z-index 90 for the same reason the footer needs 100: every fixed
   video-experience layer (dark-overlay at 50, canvas, marquee) would
   otherwise bleed through underneath a plain position:relative block. */
.details-section {
  position: relative; z-index: 90;
  background: var(--bg-light); color: var(--text-on-light);
  padding: 6rem 6vw;
}
.details-inner { max-width: 1000px; margin: 0 auto; }
.details-header {
  display: flex; justify-content: space-between; align-items: flex-end;
  flex-wrap: wrap; gap: 2rem;
  padding-bottom: 2.4rem; margin-bottom: 2.4rem;
  border-bottom: 1px solid rgba(28,26,23,0.12);
}
.details-heading {
  font-family: var(--font-display); font-weight: 600;
  font-size: 2.6rem; line-height: 1.1; margin: 0.6rem 0 0.4rem;
}
.details-address { color: var(--muted); font-size: 0.95rem; }
.details-price { text-align: right; }
.details-price-label {
  display: block; font-size: 0.75rem; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 0.3rem;
}
.details-price-value {
  font-family: var(--font-display); font-weight: 700; font-size: 2.2rem;
  color: var(--accent);
}
.details-body {
  max-width: 68ch; margin-bottom: 3rem;
}
.details-body p {
  font-size: 1.05rem; line-height: 1.75; color: var(--text-on-light);
  margin-bottom: 1.2rem;
}
.details-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
  background: rgba(28,26,23,0.12);
  border: 1px solid rgba(28,26,23,0.12);
  margin-bottom: 3rem;
}
.detail-item {
  background: var(--bg-light); padding: 1.6rem 1.4rem;
  display: flex; flex-direction: column; gap: 0.4rem;
}
.detail-label {
  font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted);
}
.detail-value {
  font-family: var(--font-display); font-weight: 600; font-size: 1.3rem;
}
.details-cta { display: inline-block; }
/* Real ask, 2026-09-17: Sir compared us against a real competitor listing
   (Iliffe & Iliffe) and asked for the same depth they give — a real
   room-by-room dimension list and a features checklist, not just the
   4-stat grid. */
.details-columns {
  display: grid; grid-template-columns: 1fr 1fr; gap: 3rem;
  margin-bottom: 3rem;
}
.details-subheading {
  font-family: var(--font-display); font-weight: 600; font-size: 1.3rem;
  margin-bottom: 1.2rem;
}
.room-list { list-style: none; }
.room-list li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: 0.7rem 0; border-bottom: 1px solid rgba(28,26,23,0.1);
  font-size: 0.95rem;
}
.room-name { font-weight: 600; }
.room-size { color: var(--muted); }
.details-tenure { margin-top: 1.2rem; color: var(--muted); font-size: 0.9rem; }
.features-list {
  list-style: none; display: grid; grid-template-columns: 1fr 1fr; gap: 0.8rem 1rem;
}
.features-list li {
  font-size: 0.95rem; padding-left: 1.5rem; position: relative;
}
.features-list li::before {
  content: '\2713'; position: absolute; left: 0; color: var(--accent);
  font-weight: 700;
}

/* ===== Attachments / Map / Calculator / Contact ===== */
/* Real ask, 2026-09-18: Sir compared us against the Iliffe & Iliffe
   benchmark again and asked for the four sections they have below the
   room list that we didn't — attachments, map, mortgage calculator,
   contact form. Each stacked as its own .details-section so they share
   the same light background/typography as the existing Full Details
   block instead of introducing a new visual language. */
.stacked-section { border-top: 1px solid rgba(28,26,23,0.12); }
.section-heading-md {
  font-family: var(--font-display); font-weight: 600;
  font-size: 2rem; line-height: 1.15; margin: 0.6rem 0 2rem;
}

.attachments-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem;
}
.attachment-item {
  display: flex; align-items: center; gap: 1rem;
  padding: 1.4rem 1.6rem; border: 1px solid rgba(28,26,23,0.12);
  text-decoration: none; color: var(--text-on-light);
  transition: border-color 0.2s ease, background 0.2s ease;
}
.attachment-item:hover { border-color: var(--accent); background: rgba(168,104,63,0.05); }
.attachment-icon { color: var(--accent); flex-shrink: 0; display: flex; }
.attachment-name { font-weight: 600; font-size: 0.95rem; }

.floorplan-note { color: var(--muted); font-size: 0.85rem; margin-bottom: 1.6rem; }
.floorplan-wrap {
  border: 1px solid rgba(28,26,23,0.12); padding: 2rem; background: #fff;
}
.floorplan-svg { width: 100%; height: auto; display: block; }
.fp-room { fill: none; stroke: var(--text-on-light); stroke-width: 1.5; }
.fp-room-name {
  font-family: var(--font-display); font-weight: 600; font-size: 15px;
  fill: var(--text-on-light); text-anchor: middle;
}
.fp-dim { font-family: var(--font-body); font-size: 12px; fill: var(--muted); text-anchor: middle; }
.fp-room-name-vert {
  font-family: var(--font-display); font-weight: 600; font-size: 13px;
  fill: var(--muted); text-anchor: middle;
}
.fp-caption {
  font-family: var(--font-body); font-weight: 600; font-size: 12px;
  letter-spacing: 0.1em; fill: var(--accent); text-anchor: middle; text-transform: uppercase;
}

.property-map {
  width: 100%; height: 420px; border: 1px solid rgba(28,26,23,0.12);
  filter: grayscale(0.15);
}

.calculator-grid {
  display: grid; grid-template-columns: 1.1fr 0.9fr; gap: 4rem; align-items: center;
}
.calc-label {
  display: flex; justify-content: space-between; font-size: 0.85rem;
  letter-spacing: 0.04em; color: var(--muted); margin: 1.4rem 0 0.6rem;
}
.calc-label:first-child { margin-top: 0; }
.calc-label span { color: var(--text-on-light); font-weight: 600; }
.calc-select {
  width: 100%; padding: 0.8rem 1rem; font-family: var(--font-body); font-size: 0.95rem;
  border: 1px solid rgba(28,26,23,0.2); background: var(--bg-light); color: var(--text-on-light);
}
.calc-slider { width: 100%; accent-color: var(--accent); }
.calculator-result {
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.calc-circle {
  width: 190px; height: 190px; border-radius: 50%;
  border: 10px solid var(--bg-dark);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  margin-bottom: 1.4rem;
}
.calc-monthly { font-family: var(--font-display); font-weight: 700; font-size: 1.9rem; }
.calc-monthly-label { font-size: 0.8rem; color: var(--muted); margin-top: 0.2rem; }
.calc-summary { font-size: 0.95rem; margin-bottom: 0.6rem; }
.calc-breakdown { font-size: 0.9rem; color: var(--muted); display: flex; align-items: center; gap: 0.5rem; }
.calc-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); display: inline-block; }

.contact-grid { display: grid; grid-template-columns: 1.3fr 0.7fr; gap: 4rem; }
.contact-form { display: flex; flex-direction: column; }
.contact-input, .contact-textarea {
  width: 100%; padding: 0.9rem 1rem; font-family: var(--font-body); font-size: 0.95rem;
  border: 1px solid rgba(28,26,23,0.2); background: var(--bg-light); color: var(--text-on-light);
}
.contact-textarea { min-height: 130px; resize: vertical; }
.contact-consent {
  display: flex; align-items: flex-start; gap: 0.7rem; margin: 1.6rem 0;
  font-size: 0.85rem; color: var(--muted);
}
.contact-consent input { margin-top: 0.2rem; accent-color: var(--accent); }
.contact-form .cta-button { border: none; cursor: pointer; align-self: flex-start; }
.contact-form-note { margin-top: 1rem; font-size: 0.9rem; color: var(--accent); min-height: 1.2em; }
.contact-info { display: flex; flex-direction: column; gap: 1.6rem; justify-content: center; }
.contact-info p { font-size: 0.95rem; line-height: 1.6; }
.contact-info a { color: var(--text-on-light); text-decoration: none; }
.contact-info a:hover { color: var(--accent); }

/* ===== Footer ===== */
.site-footer {
  /* z-index 100 is deliberate, not arbitrary: every .scroll-section is
     `position: fixed` so the persisted CTA text stays glued to the
     viewport even once real scroll has moved past #scroll-container
     entirely — without a z-index higher than the highest fixed layer
     (#dark-overlay at 50), the footer's own solid background scrolls
     in underneath the still-visible CTA text instead of covering it,
     producing real overlapping/illegible text on mobile. */
  position: relative; z-index: 100;
  background: var(--bg-dark); color: var(--text-on-dark);
  padding: 5rem 6vw 2rem;
}
.footer-inner {
  display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr;
  gap: 3rem; max-width: 1200px; margin: 0 auto;
  padding-bottom: 3rem; border-bottom: 1px solid rgba(244,239,232,0.12);
}
.footer-brand .logo { font-size: 1.2rem; }
.footer-tagline {
  margin-top: 0.9rem; color: var(--muted); font-size: 0.9rem; max-width: 22ch;
}
.footer-col { display: flex; flex-direction: column; gap: 0.7rem; }
.footer-col-title {
  font-size: 0.75rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 0.3rem;
}
.footer-col a {
  color: var(--text-on-dark); text-decoration: none; font-size: 0.9rem;
  opacity: 0.85; transition: opacity 0.2s ease;
}
.footer-col a:hover { opacity: 1; }
.footer-bottom {
  max-width: 1200px; margin: 0 auto; padding-top: 1.6rem;
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.6rem;
  font-size: 0.78rem; color: var(--muted);
}

/* ===== Mobile ===== */
@media (max-width: 768px) {
  .hero-heading { font-size: 3.2rem; }
  .hero-price-row { flex-direction: column; align-items: flex-start; gap: 1rem; }
  .align-left, .align-right { padding-left: 6vw; padding-right: 6vw; }
  .align-left .section-inner, .align-right .section-inner { max-width: 100%; }
  .section-content .section-heading { font-size: 2.3rem; }
  .stats-grid { grid-template-columns: repeat(2, 1fr); gap: 6vw 4vw; }
  .stat-number { font-size: 10vw; }
  .section-cta .section-heading { font-size: 2.6rem; }
  #scroll-container { height: 900vh; }
  .marquee-text { font-size: 20vw; }
  .nav-links { display: none; }
  .details-grid { grid-template-columns: 1fr 1fr; }
  .details-columns { grid-template-columns: 1fr; gap: 2rem; }
  .features-list { grid-template-columns: 1fr; }
  .details-header { flex-direction: column; align-items: flex-start; }
  .details-price { text-align: left; }
  .details-heading { font-size: 2rem; }
  .footer-inner { grid-template-columns: 1fr 1fr; gap: 2.4rem; }
  .footer-brand { grid-column: 1 / -1; }
  .footer-bottom { flex-direction: column; }
  .attachments-grid { grid-template-columns: 1fr; }
  .floorplan-wrap { padding: 1rem; }
  .property-map { height: 280px; }
  .calculator-grid { grid-template-columns: 1fr; gap: 2.4rem; }
  .contact-grid { grid-template-columns: 1fr; gap: 2.4rem; }
}
