/* ==========================================================================
   Ronnie Trammell - site-specific overrides
   --------------------------------------------------------------------------
   Loaded AFTER site.css. Everything here is either (a) a real-estate
   component the shared design system does not have, or (b) a deliberate
   adjustment where this site's photography differs from the shared default.

   Colours all come from the CSS custom properties the layout injects from
   config.php 'theme', so nothing here hardcodes navy or gold.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Hero scrim
   The shared scrim is tuned for dark video footage. This site's hero is a
   bright sunset field, and white text over the lit half of it failed contrast
   badly. These stops darken the left column where the copy sits while leaving
   the right side of the photograph visible.
   Do not lighten without re-checking contrast on the lede paragraph, which is
   the smallest text over the busiest part of the image.
   -------------------------------------------------------------------------- */
.hero--home .hero-scrim {
  background:
    linear-gradient(180deg, rgba(6,17,40,.72) 0%, rgba(6,17,40,.46) 30%,
                            rgba(6,17,40,.46) 60%, rgba(6,17,40,.88) 100%),
    linear-gradient(90deg,  rgba(8,24,52,.96) 0 34%, rgba(8,24,52,.86) 54%,
                            rgba(8,24,52,.48) 80%, rgba(8,24,52,.34) 100%);
}
.hero--home .hero-lede { color: #e9eff8; }

/* --------------------------------------------------------------------------
   Property feed
   The scroller lives in an iframe (see components/property-embed.php). It is
   given room to breathe and NOTHING that could interfere with it: no
   transform, no filter, no opacity animation, no containment. A transform on
   an ancestor re-parents fixed positioning and is a classic way to break a
   working carousel.
   -------------------------------------------------------------------------- */
.section--listings { padding-top: clamp(2rem, 3.6vw, 3rem); }
.section--listings > .wrap { margin-bottom: 1.4rem; }

.property-feed {
  width: 100%;
  background: var(--paper);
}
/* The feed is rendered INLINE (see components/property-embed.php).
   .property-feed and every ancestor of it must never receive transform,
   filter, perspective, contain or will-change: any of those becomes the
   containing block for the lightbox's position:fixed and drops the image
   thousands of pixels down the page. */
.property-feed,
.section--listings {
  transform: none;
  filter: none;
  perspective: none;
  contain: none;
  will-change: auto;
}
.prop-unavailable {
  padding: 2rem 1rem;
  text-align: center;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   Listing cards on community pages
   These are OUR cards built from listings.json, not the scroller's. Fixed
   media aspect ratio so a mixed bag of MLS photography cannot cause layout
   shift as images arrive.
   -------------------------------------------------------------------------- */
.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.card-media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--cream);
}
/* aspect-ratio fallback for older Safari */
@supports not (aspect-ratio: 4 / 3) {
  .card-media { height: 0; padding-bottom: 75%; position: relative; }
  .card-media img { position: absolute; inset: 0; }
}

.price-cur {
  font-weight: 800;
  font-size: 1.15rem;
  color: var(--brand);
  margin: 0 0 .2rem;
  letter-spacing: -.01em;
}

/* --------------------------------------------------------------------------
   Area list on /properties
   -------------------------------------------------------------------------- */
.area-list {
  display: flex;
  flex-wrap: wrap;
  gap: .55rem 1.1rem;
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
}
.area-list li {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: .38rem .95rem;
  font-size: .92rem;
  color: var(--ink);
}
.area-list .dot {
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--accent); flex: none;
}

/* --------------------------------------------------------------------------
   Reduced motion
   The shared sheet already honours this; restated because the property frame
   is third-party markup we do not control and must not be animated by us.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .property-frame,
  .property-feed { transition: none !important; animation: none !important; }
}

/* --------------------------------------------------------------------------
   Header: making the whole bar fit, at every width

   "REALTOR - SOUTH TOWNE REALTORS" is a much longer sub-line than the design
   was sized for, and it was set at 16px with 5.5px of tracking - as wide as
   the 24px name above it. That made .brand 455px. With .nav-desktop at 669px
   and the call button at 129px, the row needed 1280px inside a 1220px bar, so
   the call button hung 68px past the edge and the overflow rule below CLIPPED
   it. The button was not missing; it was cut in half, which is worse, because
   nothing about it looked like a bug worth reporting.

   Three things, in order of importance:

     1. The brand may SHRINK. It was flex:0 0 auto, so it took its full width
        and pushed everything else out. Now it gives way first and the sub-line
        ellipsises, which is the correct thing to lose.
     2. The sub-line is sized like a sub-line. Smaller, less tracking, and it
        steps down again before the bar gets tight.
     3. The call button never shrinks and is never clipped. It is the single
        most valuable control on the page.
   -------------------------------------------------------------------------- */
.scroll-rail {
  max-width: 100%;
  overflow-x: clip;          /* clip, not hidden: does not create a scroll container */
}
@supports not (overflow-x: clip) {
  .scroll-rail { overflow-x: hidden; }
}

/* NOT overflow-clipped. Clipping here is what turned "68px too wide" into a
   call button sliced down the middle, silently, on a common laptop width.
   The bar is now built to fit; if it ever does not, it must be visible. */
.nav-bar { max-width: 100%; }

/* The brand gives way first. */
.brand { flex: 0 1 auto; min-width: 0; }
.brand-words { min-width: 0; }
.brand-name,
.brand-sub {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A sub-line, sized as one. It was 16.3px/5.5px tracking and exactly as wide
   as the name above it. */
.brand-sub {
  font-size: .78rem;
  letter-spacing: .16em;
}

/* The call button holds its size and its place, always. */
.nav-actions { flex: 0 0 auto; }
.nav-cta { flex: none; white-space: nowrap; }

@media (max-width: 1250px) {
  /* Below this the nav links and the brand are competing for the same pixels.
     The sub-line steps down rather than the button being pushed out. */
  .brand-sub { font-size: .72rem; letter-spacing: .1em; }
  .brand-name { font-size: 1.35rem; }
}

@media (max-width: 479.98px) {
  .brand-sub  { font-size: .58rem; letter-spacing: .08em; }
  .brand-name { font-size: 1rem; }
}
@media (max-width: 359.98px) {
  /* Below this the agent line has nowhere to go. The name and the logo still
     identify the site, and the footer carries the full credit. */
  .brand-sub { display: none; }
}

/* Nothing on any page may create sideways scroll. */
html, body { overflow-x: clip; }
@supports not (overflow-x: clip) {
  html, body { overflow-x: hidden; }
}

/* ==========================================================================
   Home hero: rotating cross-fade + centred agent identification
   ========================================================================== */

.hero--rotator { position: relative; isolation: isolate; overflow: hidden; }

.hero-rotator {
  position: absolute;
  inset: 0;
  z-index: -1;
}

/* Every slide occupies the same box; only opacity changes, so nothing
   reflows and the crossfade cannot shift layout. */
.hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.2s ease-in-out;
  will-change: opacity;
}
.hero-slide.is-active { opacity: 1; }

.hero-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Anchor to the TOP. The slides are already cropped from the top, and
     when the viewport is shorter or wider than the 1920x1000 frame the
     browser crops again - centring that second crop is what took the top
     of Ronnie's head off on slides 1 and 6. */
  object-position: 50% 0;
  display: block;
}

/* A full scrim rather than the left-weighted one the inner pages use.
   The copy is centred here and two of the six frames have Ronnie standing
   left of centre, so the darkening has to be even across the whole frame.
   Do not lighten without re-checking contrast on .hero-role, which is the
   smallest text over the busiest part of the photography. */
.hero--rotator .hero-scrim {
  position: absolute;
  inset: 0;
  display: block;
  /* site.css puts .hero-scrim at z-index:-1, which parks it BEHIND the
     slides and leaves white text sitting on raw photography. Inside the
     rotator (itself z-index:-1) the scrim needs to be above the images,
     so this is a deliberate override, not a duplicate. */
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(8,20,44,.74) 0%, rgba(8,20,44,.58) 38%,
                            rgba(8,20,44,.62) 70%, rgba(8,20,44,.86) 100%),
    radial-gradient(120% 90% at 50% 45%, rgba(8,20,44,.34) 0%, rgba(8,20,44,.70) 100%);
}

/* ── Copy ───────────────────────────────────────────────────────────────── */

.hero-copy--centre {
  text-align: center;
  max-width: 46rem;
  margin-inline: auto;
}

.hero-name {
  color: #fff;
  margin: 0 0 .35rem;
  font-size: clamp(2.1rem, 6.2vw, 3.9rem);
  line-height: 1.06;
  letter-spacing: -.02em;
  text-shadow: 0 2px 18px rgba(4, 14, 32, .55);
}

.hero-role {
  margin: 0 0 1.15rem;
  color: var(--accent);
  font-weight: 700;
  font-size: clamp(.92rem, 2.2vw, 1.12rem);
  letter-spacing: .1em;
  text-transform: uppercase;
}

.hero-call {
  margin: 0 0 .35rem;
  color: #eaf0fa;
  font-size: clamp(1.05rem, 2.8vw, 1.32rem);
  font-weight: 600;
}
.hero-call a {
  color: #fff;
  font-weight: 800;
  text-decoration: none;
  border-bottom: 2px solid var(--accent);
  padding-bottom: 1px;
  white-space: nowrap;
}
.hero-call a:hover,
.hero-call a:focus-visible { color: var(--accent); }

/* The brokerage line. Ronnie is an agent under South Towne Realtors, not a
   brokerage — this is a licensing statement, not decoration, so it is given
   real weight rather than footnote treatment. */
.hero-brokerage {
  margin: 0 0 1.6rem;
  color: #cfdcef;
  font-size: clamp(.86rem, 2vw, 1rem);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.hero-brokerage::before,
.hero-brokerage::after {
  content: "";
  display: inline-block;
  width: clamp(18px, 6vw, 44px);
  height: 1px;
  background: rgba(255,255,255,.38);
  vertical-align: middle;
  margin: 0 .7rem;
}

.hero--rotator .hero-actions { justify-content: center; }

@media (prefers-reduced-motion: reduce) {
  .hero-slide { transition: none !important; }
}

/* --------------------------------------------------------------------------
   Centring the rotator hero
   site.css lays .hero-inner out as a grid that becomes two columns at 960px
   (copy + shield) and caps .hero-copy at 36rem. This hero has no second
   column and its copy is centred, so the grid is collapsed back to one
   centred track. Scoped to .hero--rotator so the inner-page heroes keep the
   two-column treatment they were designed for.
   -------------------------------------------------------------------------- */
.hero--rotator .hero-inner {
  grid-template-columns: 1fr;
  justify-items: center;
}
.hero--rotator .hero-copy {
  max-width: 48rem;
  width: 100%;
}
/* The h1 size in site.css is tuned for left-aligned copy in a 36rem column.
   Centred over photography it wants to be a touch tighter so the name holds
   one line down to tablet width. */
.hero--rotator .hero-name {
  font-size: clamp(2rem, 5.4vw, 3.6rem);
}
/* Keep the role on one line as long as it can be. "Real Estate Agent ·
   Marshall County, Alabama" breaking after the comma reads as two thoughts. */
.hero-role { text-wrap: balance; }

/* ==========================================================================
   CONTRAST CORRECTIONS (WCAG 2.1 AA)
   --------------------------------------------------------------------------
   Measured in the browser against rendered pixels, not eyeballed. The brand
   gold #c8a832 is a fine DECORATIVE colour and passes comfortably on the navy
   backgrounds (5.57:1 on --brand, 6.43:1 on --brand-deep, 7.30:1 in the
   footer). What it cannot do is carry text on a light background:

       white on gold   2.31:1   (needs 4.5)  - every gold button
       gold on white   2.31:1   (needs 4.5)  - eyebrows, brand sub-line

   Two fixes, both keeping the brand palette:
     1. Gold BUTTONS get dark ink text instead of white          -> 6.82:1
     2. Gold TEXT on light backgrounds uses a darkened gold      -> 4.91:1
        (--accent-text, the brand gold at 66% lightness; also
         clears the cream sections at 4.57:1)

   Do not "restore" white-on-gold or the light gold for text. They were the
   failures.
   ========================================================================== */

:root {
  /* Brand gold, darkened until it clears AA on both white and cream.
     For TEXT on light surfaces only — never as a fill. */
  --accent-text: #846f21;
  /* Text that sits ON the gold fill. */
  --on-accent: #1b2333;
}

/* ── 1. Gold fills carry dark text ──────────────────────────────────────── */
.btn-primary,
.btn-call,
.nav-cta.btn-primary {
  color: var(--on-accent) !important;
}
.btn-primary:hover,
.btn-primary:focus-visible {
  color: var(--on-accent) !important;
}

.skip-link {
  background: var(--accent);
  color: var(--on-accent) !important;
}

/* ── 2. Gold text on light surfaces uses the darkened gold ──────────────── */
.eyebrow,
.brand-sub,
.price-cur {
  color: var(--accent-text);
}

/* A gold-on-white button that was using --accent-dark (3.26:1).
   NOT .btn-ghost on its own: that button also appears on the dark hero and
   the CTA band, where it must stay white. */
.section .btn-ghost,
.info-card .btn-ghost,
.btn-on-light {
  color: var(--accent-text);
}

/* ── Keep the DARK contexts on the bright brand gold ─────────────────────
   Inside the hero, the footer and any brand-coloured band the original gold
   is correct and passes — the darkened one would be muddy there. These
   overrides put it back. */
.hero .eyebrow,
.hero-role,
.hero-badge,
.cta-band .eyebrow,
.section--brand .eyebrow,
footer .eyebrow,
.footer-head,
.tr-menu .eyebrow {
  color: var(--accent);
}
.cta-band .btn-ghost,
.hero .btn-ghost,
.btn-on-dark {
  color: #fff;
}

/* --------------------------------------------------------------------------
   Dark-context components used on light surfaces
   --------------------------------------------------------------------------
   Measured failures, all the same mistake: components the shared design system
   built for the dark footer or the dark hero, reused inside light cards and
   light bands. Each one is corrected where it is misused, rather than by
   changing the component (which would break it where it belongs).
   -------------------------------------------------------------------------- */

/* 1. Breadcrumb inside the dark page hero.
      site.css gives .breadcrumb a cream background for use as a light band
      under the header. Inside .page-hero it inherits white text and keeps the
      cream background -> white on #f4f7fb, 1.07:1. Here it is a hero element,
      so the band is dropped and the type goes light. */
.page-hero .breadcrumb {
  background: transparent;
  border-bottom: 0;
  padding: 0 0 .9rem;
  color: #c3d2e8;
}
.page-hero .breadcrumb a { color: #e8eefa; text-decoration: underline; }
.page-hero .breadcrumb a:hover,
.page-hero .breadcrumb a:focus-visible { color: #fff; }
.page-hero .breadcrumb [aria-current="page"] { color: #fff; font-weight: 600; }

/* 2. Any link inside the dark page hero.
      The default link colour is brand navy, which on brand-deep navy is
      1.16:1 - effectively invisible. */
.page-hero a { color: #fff; }
.page-hero a:hover,
.page-hero a:focus-visible { color: var(--accent); }

/* 3. Hours table and section heads inside a LIGHT info card.
      .hours-table and .footer-head are footer components: pale blue on navy.
      On a cream card that is 1.2:1 and 1.52:1. */
.info-card .hours-table th { color: var(--ink); font-weight: 700; }
.info-card .hours-table td { color: var(--muted); }
.info-card .footer-head,
.info-card h2.footer-head,
.info-card h3 {
  color: var(--brand-deep);
  font-weight: 700;
}
.info-card .info-row strong { color: var(--brand-deep); }
.info-card .info-row,
.info-card p { color: var(--ink); }

/* 4. The 404 code. A 1.26:1 watermark is decoration pretending to be text;
      it carries the page's meaning, so it has to be readable. */
.notfound-code {
  color: var(--brand-soft);
  opacity: 1;
}

/* 5. Gold links on LIGHT surfaces only.
      Scoped to main content. An earlier version of this rule also listed
      .footer-col a and .footer-group a - those sit on the NAVY footer, and
      a class selector (0,1,1) outranked the 'footer a' correction (0,0,2),
      so the darkened gold landed on a dark background at 3.02:1. Specificity
      trap: the fix is to not select them in the first place. */
main a:not(.btn):not(.card-link):not([class]) {
  color: var(--accent-text);
}
main a:not(.btn):not(.card-link):not([class]):hover,
main a:not(.btn):not(.card-link):not([class]):focus-visible { color: var(--brand); }

/* --------------------------------------------------------------------------
   Final two contrast corrections (measured)
   -------------------------------------------------------------------------- */

/* 1. The CURRENT nav item.
      site.css marks it with the brand gold, which is correct on the navy
      utility bar but sits on white in the main nav row: 2.31:1. */
.nav-desktop a[aria-current="page"],
.nav-mobile a[aria-current="page"],
.nav-desktop [aria-current="page"],
.brand-sub {
  color: var(--accent-text);
}

/* 2. Breadcrumb links inside the dark page hero.
      `main a:not(.btn):not(.card-link):not([class])` scores 0,3,2 because each
      :not() contributes its argument's specificity - which outranked the
      0,2,1 breadcrumb rule above and put the darkened gold on navy at 3.02:1.
      Matching that weight rather than reaching for !important. */
.page-hero .breadcrumb a:not(.btn):not(.card-link):not([class]) {
  color: #e8eefa;
}
.page-hero .breadcrumb a:not(.btn):not(.card-link):not([class]):hover,
.page-hero .breadcrumb a:not(.btn):not(.card-link):not([class]):focus-visible {
  color: var(--accent);
}

/* Any link inside the dark page hero, at the same weight as the main-content
   link rule (0,3,2) so it is not outranked. Covers the phone number in a hero
   lede, which was rendering darkened gold on navy at 3.02:1. */
.page-hero a:not(.btn):not(.card-link):not([class]) {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.page-hero a:not(.btn):not(.card-link):not([class]):hover,
.page-hero a:not(.btn):not(.card-link):not([class]):focus-visible {
  color: var(--accent);
}

/* --------------------------------------------------------------------------
   About page portrait
   The source headshot is 2667x2792 and the crop is 900x1000. Left unconstrained
   it rendered 961px tall and swallowed the sidebar column, so it is capped and
   the caption is given real styling rather than browser-default italics.
   -------------------------------------------------------------------------- */
.about-portrait {
  margin: 0 0 1.5rem;
  max-width: 22rem;
}
.about-portrait img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
}
.about-portrait figcaption {
  margin-top: .75rem;
  font-size: .92rem;
  line-height: 1.45;
  color: var(--muted);
}
.about-portrait figcaption strong {
  display: block;
  color: var(--brand-deep);
  font-weight: 700;
  font-size: 1rem;
}
@media (max-width: 959.98px) {
  .about-portrait { max-width: 18rem; margin-inline: auto; }
}

/* ==========================================================================
   Hero entrance: clear photo -> vignette -> logo -> copy
   --------------------------------------------------------------------------
   Sequence (see components/hero-rotator.php):
     0ms    the photograph is on screen, completely clear
     240ms  vignette and contrast scrim fade in from the edges
     520ms  logo
     640ms+ name, role, phone, brokerage, buttons, staggered
   ========================================================================== */

/* The vignette. Radial so the centre - where Ronnie stands - stays open and
   only the edges darken. Starts fully transparent; the .is-revealed class
   brings it in. */
.hero-vignette {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  pointer-events: none;
  opacity: 0;
  transition: opacity .85s ease-out .28s;
  background:
    radial-gradient(115% 88% at 50% 42%,
      rgba(6,16,38,0)    0%,
      rgba(6,16,38,0)   34%,
      rgba(6,16,38,.42) 62%,
      rgba(6,16,38,.82) 88%,
      rgba(6,16,38,.94) 100%);
}

/* The contrast scrim arrives with the vignette, so the copy never appears
   before the surface that makes it readable. */
.hero--rotator .hero-scrim {
  opacity: 0;
  transition: opacity .85s ease-out .28s;
}

.hero--rotator.hero-in .hero-vignette,
.hero--rotator.hero-in .hero-scrim { opacity: 1; }

/* ── Logo ────────────────────────────────────────────────────────────────── */
.hero-logo {
  display: block;
  width: clamp(132px, 30vw, 190px);
  height: auto;
  margin: 0 auto 1.1rem;
  filter: drop-shadow(0 4px 18px rgba(4, 14, 32, .55));
}

/* ── Staged entrance ─────────────────────────────────────────────────────
   Each [data-anim] step fades up. The transform is small on purpose: a long
   slide is the kind of motion that causes trouble, a short lift is not. */
.hero-copy--centre > [data-anim] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .62s ease-out, transform .62s ease-out;
}
.hero--rotator.hero-in .hero-copy--centre > [data-anim] {
  opacity: 1;
  transform: none;
}
.hero--rotator.hero-in [data-anim="1"] { transition-delay: .95s; }
.hero--rotator.hero-in [data-anim="2"] { transition-delay: 1.05s; }
.hero--rotator.hero-in [data-anim="3"] { transition-delay: 1.13s; }
.hero--rotator.hero-in [data-anim="4"] { transition-delay: 1.21s; }
.hero--rotator.hero-in [data-anim="5"] { transition-delay: 1.29s; }
.hero--rotator.hero-in [data-anim="6"] { transition-delay: 1.38s; }

/* Scripting off: nothing should stay invisible. */
.no-js .hero-copy--centre > [data-anim],
.no-js .hero-vignette,
.no-js .hero--rotator .hero-scrim { opacity: 1; transform: none; }

/* ── Mobile ──────────────────────────────────────────────────────────────
   On a phone the hero is the whole first screen, so the logo gets more room
   and the vignette tightens - a narrow frame needs the edges darker sooner
   for the copy to hold up. */
@media (max-width: 719.98px) {
  .hero-logo {
    width: clamp(150px, 44vw, 210px);
    margin-bottom: 1.25rem;
  }
  .hero-vignette {
    background:
      radial-gradient(125% 74% at 50% 38%,
        rgba(6,16,38,0)    0%,
        rgba(6,16,38,0)   26%,
        rgba(6,16,38,.48) 56%,
        rgba(6,16,38,.86) 84%,
        rgba(6,16,38,.96) 100%);
  }
  .hero--rotator .hero-inner { min-height: min(86vh, 40rem); }
  .hero--rotator .hero-actions { flex-direction: column; width: 100%; }
  .hero--rotator .hero-actions .btn { width: 100%; }
}

/* ==========================================================================
   Motion policy
   --------------------------------------------------------------------------
   config.php 'motion' controls this; templates/layout.php puts the result on
   <html> as .motion-on (reduced-motion NOT honoured) and .motion-force-all.

   The shared stylesheets carry blanket `prefers-reduced-motion` blocks that
   switch every transition off. With .motion-on set, the hero entrance is put
   back — it is a brief one-shot fade, which is not the kind of motion that
   causes trouble.

   .motion-force-all goes further and restores CONTINUOUS motion too (the
   background rotation). That one genuinely can affect people with vestibular
   conditions; it is off by default and deliberately separate.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {

  /* Entrance only. Short, non-repeating, and it ends. */
  .motion-on .hero-vignette,
  .motion-on .hero--rotator .hero-scrim {
    transition: opacity .85s ease-out .28s !important;
  }
  .motion-on .hero-copy--centre > [data-anim] {
    transition: opacity .62s ease-out, transform .62s ease-out !important;
  }
  .motion-on .hero--rotator.hero-in .hero-copy--centre > [data-anim] {
    opacity: 1;
    transform: none;
  }

  /* Continuous background rotation stays OFF here unless force_all is set. */
  .motion-on.motion-force-all .hero-slide {
    transition: opacity 1.2s ease-in-out !important;
  }
}

/* With scripting on, the hero starts hidden and the script reveals it.
   .no-js above keeps everything visible when scripting is off. */

/* site.js runs an IntersectionObserver that tags elements [data-reveal] and
   adds .is-revealed. The hero runs its own staged entrance, so it opts out -
   otherwise the two systems fight over opacity and the copy appears before
   the vignette that makes it readable. */
.hero--rotator [data-reveal] {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   Hero legibility, rebalanced (Chad, 11 September 2026)
   --------------------------------------------------------------------------
   The first pass stacked TWO full-frame darkeners — a vignette and a scrim —
   so the middle of the photograph went muddy and you could not tell what you
   were looking at. That is the opposite of the point: the background is a
   property, and a property you cannot see is just texture.

   Rebalanced so the darkening is where it is needed and nowhere else:

     - the vignette keeps the EDGES dark, which is what makes it look framed
       and deliberate, and stays fully transparent across the middle
     - the full-frame scrim is gone; what replaces it is a soft elliptical
       pool sized to the copy block, so the type sits on its own shadow
       instead of on a sheet laid over the whole picture
     - the type itself carries a shadow, which buys contrast without
       darkening a single pixel of the photograph

   If contrast ever needs raising again, raise the TEXT shadow first and the
   copy pool second. Do not reach for a full-frame overlay: that is what made
   it unreadable as a photograph the first time.
   ========================================================================== */

/* Edges only. Transparent until 52%, so the middle of the frame is the
   photograph at full clarity. */
.hero-vignette {
  background:
    radial-gradient(112% 86% at 50% 44%,
      rgba(6,16,38,0)    0%,
      rgba(6,16,38,0)   52%,
      rgba(6,16,38,.28) 72%,
      rgba(6,16,38,.66) 90%,
      rgba(6,16,38,.84) 100%);
}

/* The old full-frame scrim, reduced to a light top-and-bottom wash. It exists
   now only to seat the hero against the header and the section below it. */
.hero--rotator .hero-scrim {
  background:
    linear-gradient(180deg,
      rgba(8,20,44,.46) 0%,
      rgba(8,20,44,.10) 18%,
      rgba(8,20,44,0)   38%,
      rgba(8,20,44,0)   62%,
      rgba(8,20,44,.30) 86%,
      rgba(8,20,44,.58) 100%);
}

/* A soft pool behind the copy only. Elliptical and heavily feathered, so it
   reads as depth rather than as a panel with edges. */
.hero--rotator .hero-copy--centre {
  position: relative;
  isolation: isolate;
}
.hero--rotator .hero-copy--centre::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(150%, 62rem);
  height: 128%;
  background: radial-gradient(closest-side ellipse at 50% 50%,
      rgba(5,14,34,.60) 0%,
      rgba(5,14,34,.44) 48%,
      rgba(5,14,34,.18) 76%,
      rgba(5,14,34,0)  100%);
  pointer-events: none;
}

/* Type carries its own contrast, so the pool can stay gentle. */
.hero-name {
  text-shadow: 0 2px 4px rgba(3,10,26,.72), 0 6px 26px rgba(3,10,26,.55);
}
.hero-role,
.hero-call,
.hero-brokerage {
  text-shadow: 0 1px 3px rgba(3,10,26,.82), 0 3px 14px rgba(3,10,26,.6);
}
.hero-call a { text-shadow: 0 1px 3px rgba(3,10,26,.82); }

/* On a phone the frame is narrow and the copy fills it, so the pool tightens
   rather than the whole image darkening. */
@media (max-width: 719.98px) {
  .hero-vignette {
    background:
      radial-gradient(120% 72% at 50% 40%,
        rgba(6,16,38,0)    0%,
        rgba(6,16,38,0)   44%,
        rgba(6,16,38,.34) 68%,
        rgba(6,16,38,.74) 90%,
        rgba(6,16,38,.9)  100%);
  }
  .hero--rotator .hero-copy--centre::before {
    width: 150%;
    height: 118%;
    background: radial-gradient(closest-side ellipse at 50% 50%,
        rgba(5,14,34,.66) 0%,
        rgba(5,14,34,.5)  52%,
        rgba(5,14,34,.2)  80%,
        rgba(5,14,34,0)  100%);
  }
}

/* --------------------------------------------------------------------------
   Copy pool, strengthened
   --------------------------------------------------------------------------
   Opening the middle up made the photograph read properly and the type stop
   reading at all — over a bright frame (the lake at midday) the role line and
   the brokerage washed out completely.

   So the pool behind the copy does the work instead of a full-frame overlay:
   denser at the centre where the type sits, still feathered to nothing before
   it reaches the edges of the picture. The photograph stays legible either
   side of the text; the text stays legible on every frame in the set.

   This is the lever to pull if contrast needs adjusting again — not the
   vignette, and never a sheet over the whole image.
   -------------------------------------------------------------------------- */
.hero--rotator .hero-copy--centre::before {
  width: min(164%, 68rem);
  height: 138%;
  background: radial-gradient(closest-side ellipse at 50% 50%,
      rgba(4,12,30,.88) 0%,
      rgba(4,12,30,.80) 34%,
      rgba(4,12,30,.62) 58%,
      rgba(4,12,30,.30) 80%,
      rgba(4,12,30,0)  100%);
}

/* A second, tighter core behind the smaller lines, which are the ones that
   fail first on a bright frame. */
.hero--rotator .hero-copy--centre::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(112%, 46rem);
  height: 96%;
  background: radial-gradient(closest-side ellipse at 50% 50%,
      rgba(4,12,30,.52) 0%,
      rgba(4,12,30,.34) 56%,
      rgba(4,12,30,0)  100%);
  pointer-events: none;
}

@media (max-width: 719.98px) {
  .hero--rotator .hero-copy--centre::before {
    width: 168%;
    height: 126%;
    background: radial-gradient(closest-side ellipse at 50% 50%,
        rgba(4,12,30,.9)  0%,
        rgba(4,12,30,.82) 38%,
        rgba(4,12,30,.6)  66%,
        rgba(4,12,30,.26) 86%,
        rgba(4,12,30,0)  100%);
  }
}

/* ==========================================================================
   Listings backdrop — stationary, slow, behind the property scroller
   --------------------------------------------------------------------------
   See templates/components/listings-backdrop.php for why it is a SIBLING of
   .property-feed and never a wrapper: a transform on any ancestor of the feed
   re-parents the MLS lightbox's position:fixed and drops the photograph
   thousands of pixels down the page.
   ========================================================================== */

.section--listings {
  position: relative;
  isolation: isolate;
  background: linear-gradient(175deg, #12264f 0%, #16305f 42%, #0f2146 100%);
  /* Deliberately NO transform / filter / contain / will-change here - any of
     those would re-parent the MLS lightbox's position:fixed.

     And deliberately NO overflow:hidden either. overflow on an ancestor
     disables position:sticky on its descendants, which left the backdrop
     scrolling with the page instead of pinning. The SVG is clipped by
     .listings-backdrop itself instead. */
}

/* The sticky positioning now lives on .pbg, with the animated field it
   belongs to. See the block further down and the component file. */

/* Content sits above the backdrop. */
.section--listings > .wrap,
.section--listings > .property-feed,
.section--listings > .center {
  position: relative;
  z-index: 1;
}

/* The listings themselves keep their own surface; the backdrop is the field
   they float on, not a tint over them. */
.section--listings .property-feed {
  background: transparent;
}

/* Section heading type has to work on navy now. */
.section--listings > .wrap h2 { color: #fff; }
.section--listings > .wrap .lede { color: #c6d5ea; }
.section--listings > .wrap .eyebrow { color: var(--accent); }
.section--listings .area-list li {
  background: rgba(255,255,255,.07);
  border-color: rgba(255,255,255,.16);
  color: #e2eaf6;
}
.section--listings .area-note { color: #b9c8e0; }
.section--listings .area-note a { color: #fff; }

/* ── Drift ───────────────────────────────────────────────────────────────
   Replaced by the animated field further down this file. See
   templates/components/listings-backdrop.php.
   ------------------------------------------------------------------------ */

/* Short viewports: a 100vh sticky layer in a short section can leave a seam,
   so it is allowed to be a little taller than the viewport. */
@media (max-height: 560px) {
  .pbg { height: 120vh; margin-bottom: -120vh; }
}

/* --------------------------------------------------------------------------
   Room for the backdrop to read
   The feed's own cards run nearly edge to edge, which left the field showing
   as a sliver. Padding the feed lets the backdrop frame the listings instead
   of hiding behind them, and gives the cards the look of floating on the field
   rather than tiling it.
   -------------------------------------------------------------------------- */
/* VERTICAL padding only.
   Horizontal padding narrows the listing cards, and the feed's .info-box is
   absolutely positioned at a FIXED right:440px offset from the card. Narrow
   the card and that box slides straight over the address block - the same
   overlap that was fixed this morning, arriving from a different direction.
   The backdrop shows above, below and between the cards instead. */
.section--listings .property-feed {
  padding: clamp(.75rem, 2.4vw, 2.25rem) 0 clamp(1.5rem, 3vw, 3rem);
}

/* The feed's cards get a little lift so they sit ON the field. */
.section--listings .property-feed .listing {
  box-shadow: 0 10px 34px rgba(3, 10, 26, .34), 0 2px 6px rgba(3, 10, 26, .22);
}

/* ==========================================================================
   THE ANIMATED FIELD BEHIND THE LISTINGS
   ==========================================================================
   Markup and the full rationale: templates/components/listings-backdrop.php.

   THE 24 SECOND RULE. Every duration here is 24s, 12s, 6s or 3s, so the
   composite loop is exactly 24 seconds with no seam. A layer on 20s would give
   the whole field an audible beat.

   COLOURS are bare RGB triplets, consumed as rgba(var(--pbg-gold), .3). That
   is the single hook for recolouring: change five numbers and the field
   reskins. Do not hardcode a colour in a rule below.

   ALPHAS ARE THE WHOLE STORY. The grid lines are .075. The scanlines are .016.
   Nothing except the streak cores goes above .5. It is meant to be felt rather
   than looked at — there are thirty white cards on top of it.
   ========================================================================== */

.pbg {
  position: sticky;
  top: 0;
  z-index: 0;
  height: 100vh;
  margin-bottom: -100vh;      /* occupies no layout height */
  pointer-events: none;
  overflow: hidden;
  contain: strict;            /* safe: nothing in here is an ancestor of the feed */

  --pbg-brand: 38, 85, 166;   /* the mid brand blue     */
  --pbg-deep:  11, 26, 58;    /* deep navy              */
  --pbg-gold:  200, 168, 50;  /* brand gold             */
  --pbg-steel: 150, 172, 210; /* pale steel blue        */
  --pbg-ink:   6, 14, 32;     /* the vignette           */
}

/* One class stops everything. See the script in the component. */
.pbg.is-paused *,
.pbg.is-paused *::before,
.pbg.is-paused *::after { animation-play-state: paused !important; }

/* ── 1. The perspective floor, and a fainter mirrored ceiling ─────────────
   The only continuous motion in the field.

   Two things here are load-bearing and were both wrong on the first attempt:

     transform-origin: 50% 100%   With the default origin the plane tips above
                                  y=0 and is clipped away entirely.
     linear timing, travel == one tile (80px)
                                  Any other easing, or any other distance, and
                                  the loop visibly jumps every cycle. */
.pbg-floor {
  position: absolute;
  left: -10%; right: -10%; bottom: -12%;
  height: 72%;
  perspective: 340px;
  perspective-origin: 50% 0%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 46%, #000 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 46%, #000 100%);
}
.pbg-floor--ceil { top: -12%; bottom: auto; transform: scaleY(-1); opacity: .55; }

.pbg-floor__plane {
  position: absolute;
  left: -30%; right: -30%; bottom: 0;
  height: 190%;
  transform-origin: 50% 100%;
  transform: rotateX(74deg);
  background-image:
    linear-gradient(rgba(var(--pbg-steel), .075) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--pbg-steel), .075) 1px, transparent 1px);
  background-size: 80px 80px;
  animation: pbgFloor 24s linear infinite;
}
@keyframes pbgFloor {
  from { transform: rotateX(74deg) translate3d(0, 0, 0); }
  to   { transform: rotateX(74deg) translate3d(0, 80px, 0); }
}

/* A band of light running down the grid, twice per loop. */
.pbg-floor::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(var(--pbg-brand), .16) 46%,
    rgba(var(--pbg-gold),  .12) 50%,
    rgba(var(--pbg-brand), .16) 54%,
    transparent 100%);
  animation: pbgSweep 12s linear infinite;
}
@keyframes pbgSweep {
  0%   { opacity: 0;   transform: translate3d(0, -60%, 0); }
  22%  { opacity: .85; }
  60%  { opacity: .5; }
  100% { opacity: 0;   transform: translate3d(0, 70%, 0); }
}

/* ── 2. Horizon glow — the slow breath ───────────────────────────────────── */
.pbg-horizon {
  position: absolute;
  left: -4%; right: -4%; top: 32%;
  height: 44%;
  background:
    radial-gradient(ellipse 52% 100% at 50% 50%, rgba(var(--pbg-brand), .22) 0%, transparent 70%),
    radial-gradient(ellipse 90% 60% at 50% 62%, rgba(var(--pbg-deep), .55) 0%, transparent 72%);
  animation: pbgBreathe 24s ease-in-out infinite;
}
@keyframes pbgBreathe {
  0%, 100% { opacity: .6; transform: scale(1); }
  34%      { opacity: .95; transform: scale(1.06); }
  68%      { opacity: .72; transform: scale(1.02); }
}

/* ── 3. Light streaks ────────────────────────────────────────────────────
   1px tall, on screen for roughly a second in every twenty-four. They are the
   thing that makes somebody glance up. Any more of them and it is a nuisance. */
.pbg-streak {
  position: absolute;
  height: 1px;
  width: 44%;
  opacity: 0;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(var(--pbg-brand), .5) 22%,
    rgba(255, 255, 255, .7) 50%,
    rgba(var(--pbg-brand), .5) 78%,
    transparent 100%);
  animation: pbgStreak 24s linear infinite;
}
.pbg-streak--1 { top: 24%; animation-delay: -2s; }
.pbg-streak--2 {
  top: 68%;
  width: 34%;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(var(--pbg-gold), .45) 22%,
    rgba(255, 240, 205, .8) 50%,
    rgba(var(--pbg-gold), .45) 78%,
    transparent 100%);
  animation-delay: -13s;
}
@keyframes pbgStreak {
  0%    { opacity: 0; transform: translate3d(-60%, 0, 0); }
  3%    { opacity: .9; }
  8%    { opacity: .75; }
  12%   { opacity: 0; transform: translate3d(230%, 0, 0); }
  100%  { opacity: 0; transform: translate3d(230%, 0, 0); }
}

/* ── 4. Data tracers — thin verticals falling through the frame ──────────── */
.pbg-trace {
  position: absolute;
  top: 0;
  width: 1px;
  height: 16%;
  opacity: 0;
  background: linear-gradient(180deg,
    transparent 0%, rgba(var(--pbg-brand), .42) 50%, transparent 100%);
  animation: pbgTrace 12s linear infinite;
}
.pbg-trace--1 { left: 12%; animation-delay: -3s; }
.pbg-trace--2 {
  left: 87%;
  background: linear-gradient(180deg,
    transparent 0%, rgba(var(--pbg-gold), .34) 50%, transparent 100%);
  animation-delay: -8s;
}
@keyframes pbgTrace {
  0%   { opacity: 0;   transform: translate3d(0, 0, 0); }
  10%  { opacity: .85; }
  70%  { opacity: .4; }
  100% { opacity: 0;   transform: translate3d(0, 460%, 0); }
}

/* ── 5. Motes ────────────────────────────────────────────────────────────
   Six of them. Position and phase come from inline custom properties on each
   element, which is the entire particle system. */
.pbg-dots { position: absolute; inset: 0; }
.pbg-dots i {
  position: absolute;
  left: var(--x); top: var(--y);
  width: 3px; height: 3px;
  border-radius: 50%;
  background: rgba(var(--pbg-steel), .7);
  box-shadow: 0 0 6px rgba(var(--pbg-brand), .5);
  animation: pbgDot 24s ease-in-out infinite;
  animation-delay: var(--d);
}
.pbg-dots i:nth-child(even) {
  background: rgba(var(--pbg-gold), .6);
  box-shadow: 0 0 6px rgba(var(--pbg-gold), .4);
}
@keyframes pbgDot {
  0%, 100% { opacity: 0;   transform: translate3d(0, 0, 0); }
  16%      { opacity: .9; }
  50%      { opacity: .35; transform: translate3d(14px, -30px, 0); }
  84%      { opacity: 0;   transform: translate3d(-8px, -62px, 0); }
}

/* ── 6. Corner brackets ──────────────────────────────────────────────────
   The two diagonals are half a loop apart, so a pair is always arriving as
   the other pair leaves. */
.pbg-brk {
  position: absolute;
  width: 58px; height: 58px;
  opacity: 0;
  animation: pbgBrk 24s ease-in-out infinite;
}
.pbg-brk--tl {
  top: 6%; left: 4%;
  border-top: 2px solid rgba(var(--pbg-gold), .38);
  border-left: 2px solid rgba(var(--pbg-gold), .38);
  animation-delay: 0s;
}
.pbg-brk--br {
  bottom: 8%; right: 4%;
  border-bottom: 2px solid rgba(var(--pbg-gold), .38);
  border-right: 2px solid rgba(var(--pbg-gold), .38);
  animation-delay: -.5s;
}
.pbg-brk--tr {
  top: 6%; right: 4%;
  border-top: 2px solid rgba(var(--pbg-brand), .42);
  border-right: 2px solid rgba(var(--pbg-brand), .42);
  animation-delay: -12s;
}
.pbg-brk--bl {
  bottom: 8%; left: 4%;
  border-bottom: 2px solid rgba(var(--pbg-brand), .42);
  border-left: 2px solid rgba(var(--pbg-brand), .42);
  animation-delay: -12.5s;
}
@keyframes pbgBrk {
  0%       { opacity: 0; transform: translate3d(-14px, -14px, 0); }
  6%       { opacity: .9; transform: translate3d(0, 0, 0); }
  38%      { opacity: .9; transform: translate3d(0, 0, 0); }
  50%      { opacity: 0; transform: translate3d(10px, 10px, 0); }
  100%     { opacity: 0; transform: translate3d(-14px, -14px, 0); }
}

/* ── 7. Scanlines — static texture, no animation ─────────────────────────── */
.pbg-scan {
  position: absolute;
  inset: 0;
  opacity: .5;
  background: repeating-linear-gradient(180deg,
    rgba(255, 255, 255, .016) 0 1px, transparent 1px 3px);
}

/* ── 8. The protected core ───────────────────────────────────────────────
   Pushes every moving layer out to the edges. This is why a dozen animated
   layers read as calm. If the field ever competes with the listings, raise
   these alphas before removing anything. */
.pbg-core {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 52% 60% at 50% 48%,
      rgba(var(--pbg-ink), .6) 0%, rgba(var(--pbg-ink), .32) 44%, transparent 74%),
    linear-gradient(180deg,
      rgba(var(--pbg-ink), .3) 0%, transparent 22%,
      transparent 78%, rgba(var(--pbg-ink), .38) 100%);
}

/* ── Narrow screens ──────────────────────────────────────────────────────
   Fewer layers, a smaller grid tile, and a darker core. A phone is both the
   slowest device and the one where the cards cover almost the whole field. */
@media (max-width: 760px) {
  .pbg-floor--ceil,
  .pbg-trace--2,
  .pbg-streak--2,
  .pbg-dots i:nth-child(n + 4) { display: none; }

  .pbg-floor__plane { background-size: 54px 54px; animation-name: pbgFloorSm; }
  @keyframes pbgFloorSm {
    from { transform: rotateX(74deg) translate3d(0, 0, 0); }
    to   { transform: rotateX(74deg) translate3d(0, 54px, 0); }
  }
  .pbg-brk { width: 34px; height: 34px; }
  .pbg-core {
    background:
      radial-gradient(ellipse 88% 62% at 50% 48%,
        rgba(var(--pbg-ink), .66) 0%, rgba(var(--pbg-ink), .4) 46%, transparent 76%);
  }
}

/* ── Reduced motion ──────────────────────────────────────────────────────
   Two different situations, and they must not be conflated.

   1. THE VISITOR ASKED FOR LESS MOTION and this site has not overridden it.
      Everything stops, and each layer is pinned to a DESIGNED still frame
      rather than a frozen one. Freezing keyframes at 0% would leave half the
      field invisible — every streak, tracer, mote and bracket starts at
      opacity 0, so a plain `animation: none` gives an empty box rather than a
      static composition.

   2. THE SITE OWNER HAS OVERRIDDEN IT — config.php 'motion' puts .motion-on
      on <html>; see the long note in that file about the trade-off. Then the
      field runs IN FULL.

      In full, not in part. An earlier version of this block restored only the
      grid, the horizon and the motes, reasoning that a 1px streak flicking
      across the frame is the exact thing the preference exists to prevent.
      Sound in the abstract, wrong here: this machine reports `reduce`, so the
      "careful" version is what the site owner actually sees, and it is not
      the effect that was asked for. The place to make that decision is the
      config flag, which exists so it is made once, deliberately, in one file.
   ───────────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {

  /* 1. The still frame. */
  :not(.motion-on) .pbg *,
  :not(.motion-on) .pbg *::before,
  :not(.motion-on) .pbg *::after { animation: none !important; }

  :not(.motion-on) .pbg-streak       { opacity: .35; transform: translate3d(60%, 0, 0); }
  :not(.motion-on) .pbg-trace        { opacity: .4;  transform: translate3d(0, 240%, 0); }
  :not(.motion-on) .pbg-dots i       { opacity: .5;  transform: none; }
  :not(.motion-on) .pbg-brk          { opacity: .75; transform: none; }
  :not(.motion-on) .pbg-horizon      { opacity: .8; }
  :not(.motion-on) .pbg-floor::after { opacity: .35; transform: translate3d(0, 10%, 0); }

  /* 2. The override: every layer, at its declared timing. The delays are
        restated because losing them would put all four brackets, both
        streaks and both tracers on the same phase — which reads as a single
        flash rather than as a field. */
  .motion-on .pbg-floor__plane { animation: pbgFloor   24s linear infinite !important; }
  .motion-on .pbg-floor::after { animation: pbgSweep   12s linear infinite !important; }
  .motion-on .pbg-horizon      { animation: pbgBreathe 24s ease-in-out infinite !important; }
  .motion-on .pbg-dots i {
    animation: pbgDot 24s ease-in-out infinite !important;
    animation-delay: var(--d) !important;
  }

  .motion-on .pbg-streak    { animation: pbgStreak 24s linear infinite !important; }
  .motion-on .pbg-streak--1 { animation-delay: -2s  !important; }
  .motion-on .pbg-streak--2 { animation-delay: -13s !important; }

  .motion-on .pbg-trace     { animation: pbgTrace 12s linear infinite !important; }
  .motion-on .pbg-trace--1  { animation-delay: -3s !important; }
  .motion-on .pbg-trace--2  { animation-delay: -8s !important; }

  .motion-on .pbg-brk       { animation: pbgBrk 24s ease-in-out infinite !important; }
  .motion-on .pbg-brk--tl   { animation-delay: 0s     !important; }
  .motion-on .pbg-brk--br   { animation-delay: -.5s   !important; }
  .motion-on .pbg-brk--tr   { animation-delay: -12s   !important; }
  .motion-on .pbg-brk--bl   { animation-delay: -12.5s !important; }
}

/* The narrow-screen grid keeps its own 54px travel under the override too,
   or the loop seams on a phone. Separate rule rather than a nested media
   query, which Safari did not support until 16.4. */
@media (prefers-reduced-motion: reduce) and (max-width: 760px) {
  .motion-on .pbg-floor__plane { animation-name: pbgFloorSm !important; }
}

/* ==========================================================================
   Property filter bar
   Sits on the navy backdrop above the listings, so it is styled for a dark
   surface. See components/property-filters.php.
   ========================================================================== */
.prop-filters {
  position: relative;
  z-index: 2;
  margin: 0 auto 1.1rem;
  max-width: 1200px;
  padding: 0 1rem;
}

.prop-filters-inner {
  display: grid;
  gap: 1rem 1.4rem;
  grid-template-columns: 1fr;
  align-items: end;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 12px;
  padding: 1rem 1.15rem;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
@media (min-width: 760px) {
  .prop-filters-inner {
    grid-template-columns: minmax(0, 2.2fr) auto auto minmax(0, 1.1fr);
  }
}

.pf-field label {
  display: block;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: .4rem;
}

.pf-field select {
  width: 100%;
  min-width: 6.5rem;
  padding: .5rem .7rem;
  border-radius: 7px;
  border: 1px solid rgba(255,255,255,.24);
  background: rgba(8, 20, 44, .72);
  color: #fff;
  font-size: .95rem;
}
.pf-field select:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* ── Price ─────────────────────────────────────────────────────────────── */
.pf-price-row {
  display: flex;
  align-items: center;
  gap: .9rem;
}

/* The readable figure sits to the LEFT of the track, and is given a fixed
   width so the slider does not jump sideways as the number changes length. */
.pf-price-out {
  flex: 0 0 auto;
  min-width: 8.5rem;
  font-weight: 700;
  font-size: .98rem;
  color: #fff;
  background: rgba(8, 20, 44, .72);
  border: 1px solid rgba(255,255,255,.22);
  border-radius: 7px;
  padding: .45rem .7rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.pf-price-row input[type="range"] {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--accent);
  height: 1.6rem;
  cursor: pointer;
}
.pf-price-row input[type="range"]:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 4px;
}

/* ── Count and reset ───────────────────────────────────────────────────── */
.pf-field--count {
  display: flex;
  flex-direction: column;
  gap: .35rem;
  align-items: flex-start;
}
@media (min-width: 760px) {
  .pf-field--count { align-items: flex-end; text-align: right; }
}

.pf-count {
  margin: 0;
  font-size: .9rem;
  color: #d4e0f2;
  font-weight: 600;
}

.pf-reset {
  background: transparent;
  border: 1px solid rgba(255,255,255,.3);
  color: #e8eefa;
  border-radius: 100px;
  padding: .26rem .8rem;
  font-size: .8rem;
  cursor: pointer;
}
.pf-reset:hover,
.pf-reset:focus-visible {
  background: rgba(255,255,255,.12);
  border-color: var(--accent);
  color: #fff;
}

/* ==========================================================================
   Hero entrance, rebuilt to feel organic
   --------------------------------------------------------------------------
   The first version faded six things in on a shared 14px lift with linear-ish
   easing. Everything moved the same distance at the same speed, which is what
   made it read as mechanical: real motion does not arrive in identical steps.

   Three things fix that, and none of them add time:

   1. EASING. A single custom cubic-bezier with a slight overshoot at the tail
      (the 1.16 on the third handle) so each element settles rather than stops
      dead. That one curve is what most of the "organic" feeling comes from.

   2. DIFFERENT DISTANCES AND DIRECTIONS. The logo drops in from above and
      scales up slightly; the name rises; the role and phone slide from the
      left; the brokerage rises; the buttons rise last and furthest. Things
      that differ in weight should not travel identically.

   3. OVERLAP. The delays are closer together than the durations, so each
      element starts while the one before it is still settling. A strict
      queue reads as a list being ticked off; an overlap reads as one movement.

   Durations are longer than before (0.62s -> 0.9-1.1s) because a slow ease is
   what makes motion feel weighty rather than twitchy. The whole sequence still
   completes inside ~2.1s.
   ========================================================================== */

:root {
  /* Settle curve: quick to leave, slow to arrive, a hair past the target. */
  --ease-settle: cubic-bezier(.22, .82, .28, 1.16);
  /* For things that should not overshoot - the vignette and the scrim. */
  --ease-soft:   cubic-bezier(.33, .72, .32, 1);
}

/* Base: no shared transform any more. Each step declares its own. */
.hero-copy--centre > [data-anim] {
  opacity: 0;
  transition:
    opacity   .95s var(--ease-soft),
    transform 1.05s var(--ease-settle);
}
.hero--rotator.hero-in .hero-copy--centre > [data-anim] {
  opacity: 1;
  transform: none;
}

/* 1. Logo — drops from above and grows the last few per cent into place. */
.hero-copy--centre > [data-anim="1"] {
  transform: translate3d(0, -26px, 0) scale(.92);
}

/* 2. Name — rises, and is the only element given a touch of blur on the way
      in, which is what makes it feel like it is arriving rather than sliding. */
.hero-copy--centre > [data-anim="2"] {
  transform: translate3d(0, 26px, 0);
  filter: blur(6px);
  transition:
    opacity   .95s var(--ease-soft),
    transform 1.05s var(--ease-settle),
    filter    .8s  var(--ease-soft);
}
.hero--rotator.hero-in .hero-copy--centre > [data-anim="2"] { filter: none; }

/* 3 and 4. Role and phone — come in from the left, a small distance, so the
   middle of the block has a different grain from the top and bottom. */
.hero-copy--centre > [data-anim="3"] { transform: translate3d(-22px, 6px, 0); }
.hero-copy--centre > [data-anim="4"] { transform: translate3d(-14px, 8px, 0); }

/* 5. Brokerage — rises, and its two rules draw outward from the centre. */
.hero-copy--centre > [data-anim="5"] { transform: translate3d(0, 14px, 0); }
.hero-brokerage::before,
.hero-brokerage::after {
  transform: scaleX(0);
  transition: transform 1.1s var(--ease-settle);
}
.hero-brokerage::before { transform-origin: right center; }
.hero-brokerage::after  { transform-origin: left center; }
.hero--rotator.hero-in .hero-brokerage::before,
.hero--rotator.hero-in .hero-brokerage::after { transform: scaleX(1); }

/* 6. Buttons — furthest and last, so the sequence lands on the thing you are
      meant to press. */
.hero-copy--centre > [data-anim="6"] { transform: translate3d(0, 32px, 0); }

/* Overlapping delays: each starts before the previous has settled. */
.hero--rotator.hero-in [data-anim="1"] { transition-delay: .62s; }
.hero--rotator.hero-in [data-anim="2"] { transition-delay: .76s; }
.hero--rotator.hero-in [data-anim="3"] { transition-delay: .88s; }
.hero--rotator.hero-in [data-anim="4"] { transition-delay: .97s; }
.hero--rotator.hero-in [data-anim="5"] { transition-delay: 1.05s; }
.hero--rotator.hero-in .hero-brokerage::before,
.hero--rotator.hero-in .hero-brokerage::after { transition-delay: 1.12s; }
.hero--rotator.hero-in [data-anim="6"] { transition-delay: 1.16s; }

/* The vignette leads, and eases without overshoot - a scrim that bounces
   would be obvious and wrong. */
.hero-vignette,
.hero--rotator .hero-scrim {
  transition: opacity 1.1s var(--ease-soft) .22s;
}

/* The active slide crossfade gets the soft curve too, so a rotation reads as
   the same family of movement as the entrance. */
.hero-slide { transition: opacity 1.4s var(--ease-soft); }

@media (prefers-reduced-motion: reduce) {
  /* Restore the entrance under the site-wide motion override.

     Uses the LONGHAND properties, not the `transition` shorthand. The
     shorthand resets transition-delay to 0s, and with !important on it that
     reset cannot be overridden - which flattened the staggered delays and
     made all six elements arrive at once, exactly the mechanical feel this
     rewrite was meant to remove. */
  .motion-on .hero-copy--centre > [data-anim] {
    transition-property: opacity, transform, filter !important;
    transition-duration: .95s, 1.05s, .8s !important;
    transition-timing-function: var(--ease-soft), var(--ease-settle), var(--ease-soft) !important;
  }
  .motion-on .hero--rotator.hero-in [data-anim="1"] { transition-delay: .62s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="2"] { transition-delay: .76s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="3"] { transition-delay: .88s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="4"] { transition-delay: .97s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="5"] { transition-delay: 1.05s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="6"] { transition-delay: 1.16s !important; }
  .motion-on .hero--rotator.hero-in .hero-brokerage::before,
  .motion-on .hero--rotator.hero-in .hero-brokerage::after {
    transition: transform 1.1s var(--ease-settle) 1.12s !important;
  }
  .motion-on .hero-vignette,
  .motion-on .hero--rotator .hero-scrim {
    transition: opacity 1.1s var(--ease-soft) .22s !important;
  }
  .motion-on .hero-slide { transition: opacity 1.4s var(--ease-soft) !important; }
}

/* ==========================================================================
   THE AGENT'S TELEPHONE NUMBER ON EACH LISTING CARD
   ==========================================================================
   The feed writes it as <p style="color:#2655a6;font-size:12px">, which put
   the single most valuable element on a property card at twelve pixels,
   underneath a bold green price, looking like a caption.

   It measured 6.80:1 on the card, so this is not a contrast failure - it is a
   PROMINENCE one. The fix is size, weight and the deep brand navy (14.09:1),
   plus the gold rule underneath that the rest of the site uses to mark
   something as an action rather than a label.

   !important is not decoration here: the colour and size are INLINE on the
   element, written by the scraper, and nothing else beats that. Property.php
   wraps the number in a tel: link on the way out, so this styles both.
   ========================================================================== */
.property-feed .listing .address-content p:has(> .tr-listing-phone),
.property-feed .listing .address-content .tr-listing-phone {
  color: var(--brand-deep, #12264f) !important;
  font-size: 1rem !important;
  font-weight: 800 !important;
  letter-spacing: .01em;
}
.property-feed .listing .address-content .tr-listing-phone {
  display: inline-block;
  padding: .15rem .1rem;
  border-bottom: 2px solid var(--accent, #c8a832);
  /* Measures 32px tall in the card, which clears the 24px WCAG 2.5.8 (AA)
     target size. It is not taken to 44px because that is a spacing rule for
     buttons, and padding a line of text inside a dense listing card out to
     44px pushes the rest of the card around for no gain in tappability. */
  min-height: 24px;
}
.property-feed .listing .address-content .tr-listing-phone:hover,
.property-feed .listing .address-content .tr-listing-phone:focus-visible {
  color: var(--brand, #1a3060) !important;
  background: rgba(200, 168, 50, .16);
}
.property-feed .listing .address-content .tr-listing-phone:focus-visible {
  outline: 3px solid var(--brand-deep, #12264f);
  outline-offset: 2px;
}

/* :has() is unsupported on older Safari and older Firefox ESR. Those browsers
   skip the rule above, so the anchor carries the same declarations on its own
   and the number is still large, bold and navy - only the parent paragraph's
   own 12px is left behind, which nothing visible depends on. */

/* ==========================================================================
   RIGHT-CLICK MENU
   ==========================================================================
   The menu itself is in templates/layout.php and its behaviour is
   contextActions() in assets/js/site.js. This is the brand pass over the
   shared styling, plus the rows added for this site.

   The shared sheet coloured the icons #ff8ace, which is the pink from the
   business the design system was built for. On navy that is not subtle.

   NOTE ON THE FOOTER LINE: a site that replaces the browser's context menu
   owes the visitor a visible way back to the real one. The line is small, but
   removing it makes the menu feel like a trap rather than a shortcut.
   ========================================================================== */

.tr-menu {
  min-width: 17.5rem;
  border-color: rgba(200, 168, 50, .34);
  border-radius: 13px;
  background: rgba(12, 26, 56, .84);
}
@supports ((backdrop-filter: blur(4px)) or (-webkit-backdrop-filter: blur(4px))) {
  .tr-menu { background: rgba(12, 26, 56, .66); }
}

.tr-menu-head {
  letter-spacing: .12em;
  color: var(--accent, #c8a832);   /* 7.4:1 on the card */
}

/* Brand gold icons, in a soft tile so they read as a set rather than as
   loose glyphs at different weights. */
.tr-menu a .ic {
  width: 16px;
  height: 16px;
  fill: var(--accent, #c8a832);
}
.tr-menu a > svg.ic,
.tr-menu a > .ic {
  box-sizing: content-box;
  padding: 7px;
  border-radius: 8px;
  background: rgba(255, 255, 255, .09);
}
.tr-menu a small { color: #b9c8e2; }   /* 7.0:1, was #a9d2e8 */
.tr-menu a:hover,
.tr-menu a:focus-visible { background: rgba(255, 255, 255, .13); }

/* ── The one action this business most wants pressed ──────────────────── */
.tr-menu a.is-primary {
  background: var(--accent, #c8a832);
  color: var(--on-accent, #1b2333);
  margin-bottom: .18rem;
}
.tr-menu a.is-primary:hover,
.tr-menu a.is-primary:focus-visible { background: #d8b93f; }
.tr-menu a.is-primary .ic {
  fill: var(--on-accent, #1b2333);
  background: rgba(0, 0, 0, .14);
}
.tr-menu a.is-primary small { color: #4a3f16; }   /* 5.6:1 on the gold */

.tr-menu-sep {
  height: 1px;
  margin: .32rem .5rem;
  background: rgba(255, 255, 255, .13);
  list-style: none;
}

.tr-menu-foot {
  margin: .3rem 0 0;
  padding: .42rem .65rem .28rem;
  border-top: 1px solid rgba(255, 255, 255, .13);
  font-size: .7rem;
  color: #a7b8d6;            /* 6.1:1 */
  list-style: none;
}

/* "Copy link to this page" reports in its own row. */
.tr-menu a.is-done b { color: #8ff0bb; }
.tr-menu a.is-warn b { color: #ffd98a; }

/* ==========================================================================
   HERO ENTRANCE — v3: the photograph first, then the overlay
   ==========================================================================
   The brief: show the background first, and about a second later let the
   overlay fade and animate in, with a good quality techy feel.

   The previous pass started darkening the scrim at 0.22s and had the logo
   moving by 0.62s, so the photograph was never actually seen — the overlay
   began before the eye had settled on the picture. Everything here is pushed
   back behind a deliberate hold.

   THE SHAPE OF IT
     0.00 - 0.80   the photograph, clear. Nothing moves.
     0.80 - 1.95   the scrim and vignette ease in underneath
     1.15 onward   the copy arrives, one element at a time, overlapping

   THE "TECHY" PART is three specific things, not a pile of effects:

     1. WIPES, not slides. Each element is revealed by a clip-path travelling
        across it, so the copy is uncovered rather than flown in. That is the
        difference between a HUD element materialising and a slide transition.
     2. The NAME settles out of wide tracking and blur. Letter-spacing
        contracting to its final value is the most recognisable title-sequence
        move there is, and it costs one property.
     3. A gold SCAN LINE crosses the name once, at the moment it lands.

   Durations stay long (0.9-1.2s) and the curves stay soft. Fast techy reads
   as cheap; slow techy reads as expensive.

   This block is LAST in the file and deliberately supersedes the delays in
   the v2 block above. The two are kept apart rather than merged so the
   previous timing stays legible beside the current one.
   ========================================================================== */

/* ── The hold ──────────────────────────────────────────────────────────────
   The scrim and vignette are what turn the photograph into a background, so
   nothing may darken until the photograph has had its moment. */
.hero-vignette,
.hero--rotator .hero-scrim {
  transition: opacity 1.15s var(--ease-soft) .8s;
}

/* ── Base state for every copy element ────────────────────────────────────
   clip-path is on the transition list for all of them; each step below sets
   its own starting shape. A browser without clip-path support still gets the
   fade and the transform, which is a complete animation on its own. */
.hero-copy--centre > [data-anim] {
  opacity: 0;
  clip-path: inset(0 100% 0 0);
  transition:
    opacity   1.05s var(--ease-soft),
    transform 1.15s var(--ease-settle),
    clip-path 1.05s var(--ease-soft);
}
.hero--rotator.hero-in .hero-copy--centre > [data-anim] {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 0 0);
}

/* 1. Logo — uncovers from the centre outwards and settles the last few per
      cent of its scale. It is the brokerage mark, so it resolves rather than
      arrives. */
.hero-copy--centre > [data-anim="1"] {
  transform: translate3d(0, -14px, 0) scale(.94);
  clip-path: inset(0 50% 0 50%);
}

/* 2. Name — the title settle: wide tracking and blur contracting to nothing,
      uncovered downward like a readout filling in. */
.hero-copy--centre > [data-anim="2"] {
  transform: translate3d(0, 18px, 0);
  filter: blur(9px);
  letter-spacing: .34em;
  clip-path: inset(0 0 110% 0);
  transition:
    opacity        1.05s var(--ease-soft),
    transform      1.15s var(--ease-settle),
    filter          .9s  var(--ease-soft),
    letter-spacing 1.2s  var(--ease-settle),
    clip-path      1.1s  var(--ease-soft);
}
.hero--rotator.hero-in .hero-copy--centre > [data-anim="2"] {
  filter: none;
  letter-spacing: normal;
}

/* The scan line. One pass, straight across the name, timed to arrive with it.
   position:relative on the h1 is safe: the hero is not an ancestor of the
   property feed, which is the element that must never gain a containing
   block. See the note at the top of this file. */
.hero-name { position: relative; }
.hero-name::after {
  content: "";
  position: absolute;
  inset: -.18em -6% -.18em -6%;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    transparent 44%,
    rgba(200, 168, 50, .5) 49%,
    rgba(255, 240, 190, .85) 50.5%,
    rgba(200, 168, 50, .5) 52%,
    transparent 57%);
  opacity: 0;
  transform: translateX(-115%);
}
.hero--rotator.hero-in .hero-name::after {
  opacity: 1;
  transform: translateX(115%);
  transition:
    transform 1.05s cubic-bezier(.3, .05, .2, 1) 1.42s,
    opacity    .001s linear 1.42s;
}

/* 3 and 4. Role and phone — wiped in from opposite sides, so the middle of
      the block reads differently from the top and the bottom. */
.hero-copy--centre > [data-anim="3"] {
  transform: translate3d(-16px, 4px, 0);
  clip-path: inset(0 100% 0 0);
}
.hero-copy--centre > [data-anim="4"] {
  transform: translate3d(16px, 6px, 0);
  clip-path: inset(0 0 0 100%);
}

/* 5. Brokerage — rises, uncovering from the centre, with its two rules
      drawing outward behind it. */
.hero-copy--centre > [data-anim="5"] {
  transform: translate3d(0, 12px, 0);
  clip-path: inset(0 50% 0 50%);
}
.hero-brokerage::before,
.hero-brokerage::after {
  transform: scaleX(0);
  transition: transform 1.15s var(--ease-settle);
}
.hero-brokerage::before { transform-origin: right center; }
.hero-brokerage::after  { transform-origin: left center; }
.hero--rotator.hero-in .hero-brokerage::before,
.hero--rotator.hero-in .hero-brokerage::after { transform: scaleX(1); }

/* 6. Buttons — last and furthest, so the sequence lands on the thing you are
      meant to press. No wipe: a button that uncovers reads as not-yet-ready,
      and this one is the point of the page. */
.hero-copy--centre > [data-anim="6"] {
  transform: translate3d(0, 26px, 0);
  clip-path: none;
}

/* ── The stagger ─────────────────────────────────────────────────────────── */
.hero--rotator.hero-in [data-anim="1"] { transition-delay: 1.15s; }
.hero--rotator.hero-in [data-anim="2"] { transition-delay: 1.32s; }
.hero--rotator.hero-in [data-anim="3"] { transition-delay: 1.50s; }
.hero--rotator.hero-in [data-anim="4"] { transition-delay: 1.60s; }
.hero--rotator.hero-in [data-anim="5"] { transition-delay: 1.70s; }
.hero--rotator.hero-in .hero-brokerage::before,
.hero--rotator.hero-in .hero-brokerage::after { transition-delay: 1.80s; }
.hero--rotator.hero-in [data-anim="6"] { transition-delay: 1.88s; }

/* ── Under the site-wide motion override ─────────────────────────────────
   Longhand, never the `transition` shorthand: the shorthand resets
   transition-delay to 0s, and with !important that reset cannot be undone —
   which is what once flattened the whole stagger into a single pop. */
@media (prefers-reduced-motion: reduce) {
  .motion-on .hero-copy--centre > [data-anim] {
    transition-property: opacity, transform, filter, letter-spacing, clip-path !important;
    transition-duration: 1.05s, 1.15s, .9s, 1.2s, 1.1s !important;
    transition-timing-function:
      var(--ease-soft), var(--ease-settle), var(--ease-soft),
      var(--ease-settle), var(--ease-soft) !important;
  }
  .motion-on .hero--rotator.hero-in [data-anim="1"] { transition-delay: 1.15s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="2"] { transition-delay: 1.32s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="3"] { transition-delay: 1.50s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="4"] { transition-delay: 1.60s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="5"] { transition-delay: 1.70s !important; }
  .motion-on .hero--rotator.hero-in [data-anim="6"] { transition-delay: 1.88s !important; }
  .motion-on .hero--rotator.hero-in .hero-brokerage::before,
  .motion-on .hero--rotator.hero-in .hero-brokerage::after {
    transition: transform 1.15s var(--ease-settle) 1.8s !important;
  }
  .motion-on .hero-vignette,
  .motion-on .hero--rotator .hero-scrim {
    transition: opacity 1.15s var(--ease-soft) .8s !important;
  }
  .motion-on .hero--rotator.hero-in .hero-name::after {
    transition:
      transform 1.05s cubic-bezier(.3, .05, .2, 1) 1.42s,
      opacity    .001s linear 1.42s !important;
  }
}

/* Scripting off: nothing may stay hidden or clipped. */
.no-js .hero-copy--centre > [data-anim] {
  opacity: 1;
  transform: none;
  clip-path: none;
  filter: none;
  letter-spacing: normal;
}
.no-js .hero-name::after { display: none; }
