/* lc-motion.css — the platform's one motion kernel.
   Every keyframe on the site lives here. Verticals opt in with classes; nobody
   defines a private spinner. Companion: lc-motion.js (window.LCMotion), enqueued
   by mu-plugins/lc-motion.php.

   The governing rule, from docs/superpowers/specs/2026-08-02-motion-system-design.md:
   a wait has three lengths and each needs a different answer —
     under 200ms  show nothing (a loader that flashes reads as a glitch, not as speed)
     200ms – 1s   show the SHAPE of what is coming, so nothing jumps on arrival
     over 1s      NAME the thing being fetched, which turns waiting into anticipation
   The mega menu learned this the hard way; see the note at lc-megamenu.css:117. */

/* ---------------------------------------------------------------- tokens -- */
:root {
  --lc-ease:   cubic-bezier(.22,.72,0,1);
  /* one overshoot-and-settle curve, used anywhere something should read as
     having mass — sheets, saved hearts, dropped map pins. */
  --lc-spring: linear(0,.4 8%,.82 17%,1.02 24%,1.06 30%,1.02 40%,.996 62%,1);

  --lc-t-fast: .18s;   /* hover menus — never make a fast mouse wait */
  --lc-t-base: .32s;
  --lc-t-slow: .55s;

  --lc-gate:   200ms;  /* specimen 04 — the wait a loader must survive to paint */
  --lc-stagger-step: 70ms;

  --lc-skel:      #E7E2E1;
  --lc-skel-high: #FFFFFF;   /* was #F3EEED — twelve levels off the base, i.e. invisible */
  --lc-rose:      #CC9999;
}

/* @property lets the shimmer's travel and the roll counter animate as real typed
   values rather than being stuck at their initial state. */
@property --lc-sweep  { syntax: '<length-percentage>'; initial-value: -120%; inherits: false; }
@property --lc-num    { syntax: '<integer>';           initial-value: 0;     inherits: false; }

/* =========================================================== 04 · the gate ==
   .lc-pending paints nothing for the first 200ms. Applied by LCMotion.pending()
   to whatever wrapper is waiting. If the response beats the gate the visitor
   never sees a loading state at all, which is the entire point — fast should
   look instant, not busy. */
.lc-pending { position: relative; }
.lc-pending > .lc-pending-veil {
  position: absolute; inset: 0; z-index: 6;
  display: flex; flex-direction: column; gap: 10px;
  background: var(--lc-bg, #fff);
  opacity: 0;
  animation: lc-gate-in var(--lc-t-base) var(--lc-ease) var(--lc-gate) both;
}
/* A first load has an EMPTY container, so an absolutely-positioned veil would
   stretch over a zero-height box and show nothing at all — the exact failure the
   skeleton exists to prevent. When there is nothing to cover, the veil sits in
   normal flow instead and gives the container its height. lc-motion.js decides
   which case applies. */
.lc-pending > .lc-pending-veil.is-flow { position: static; min-height: var(--lc-pending-min, 240px); }
/* The label is absolutely positioned, so without this it sits on top of the first
   ghost row instead of above it. */
.lc-pending > .lc-pending-veil.has-label { padding-top: 26px; }
@keyframes lc-gate-in { from { opacity: 0 } to { opacity: 1 } }

/* ======================================================= 01 · content ghost ==
   Grey blocks in the exact shape the real cards will take. Never fake headlines
   and never fake prices — a placeholder that looks like content is a lie the
   visitor notices the moment it resolves into something else. */
.lc-skel-grid {
  display: grid; gap: var(--lc-skel-gap, 18px);
  grid-template-columns: repeat(auto-fill, minmax(var(--lc-skel-min, 240px), 1fr));
}
.lc-skel-card {
  position: relative; overflow: hidden; border-radius: 3px;
  background: var(--lc-card-bg, #fff);
  display: flex; flex-direction: column;
}
.lc-skel-img   { aspect-ratio: 4/3; background: var(--lc-skel); }
.lc-skel-body  { padding: 10px 12px 14px; display: flex; flex-direction: column; gap: 7px; }
.lc-skel-line  { height: 9px; border-radius: 99px; background: var(--lc-skel); }
.lc-skel-line.is-short { width: 54%; }
.lc-skel-line.is-mid   { width: 76%; }

/* Row shape — a thumbnail beside two lines, for the directories whose results
   are a vertical list rather than a card grid (shops, schools, restaurants).
   Matching the real card is not a detail, it IS the feature: a ghost of the
   wrong shape still lets the layout jump on arrival, which is the one thing a
   skeleton exists to prevent. Sizes come from the vertical via custom props so
   the kernel never hardcodes one directory's dimensions. */
.lc-skel-grid.is-rows { grid-template-columns: 1fr; }
.lc-skel-card.is-row {
  display: grid; grid-template-columns: var(--lc-skel-thumb, 132px) 1fr;
  gap: 14px; padding: 12px; align-items: start;
}
.lc-skel-card.is-row .lc-skel-img {
  aspect-ratio: auto; height: var(--lc-skel-thumb-h, 104px); border-radius: 4px;
}
.lc-skel-card.is-row .lc-skel-body { padding: 6px 0 0; }

/* Most directory cards carry a hairline frame. Without it the ghost is a
   borderless slab and a border materialises on arrival — a small jump, but a
   jump, and this whole primitive exists to prevent exactly that. */
.lc-skel-card.has-frame {
  border: 1px solid var(--lc-line, #e3e6e3);
  border-radius: var(--lc-skel-radius, 6px);
}

/* The light pass. Warm rather than grey — a neutral sweep is the tell that says "generic
   template". Travel is a transform so it stays on the compositor.

   The first version was far too polite to register as motion, which defeated the point: on
   a long wait the ONLY job of this surface is to prove the page is not frozen. Wider band,
   full-strength highlight, and a per-row delay so the light walks DOWN the list rather than
   every card flashing in unison. */
.lc-skel-card::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg,
    transparent 20%,
    color-mix(in oklab, var(--lc-skel-high) 88%, var(--lc-rose)) 50%,
    transparent 80%);
  transform: translateX(var(--lc-sweep));
  animation: lc-shine 1.55s cubic-bezier(.5,0,.5,1) infinite;
  animation-delay: calc(var(--lc-i, 0) * 130ms);
  opacity: .95;
}
@keyframes lc-shine { from { --lc-sweep: -135% } to { --lc-sweep: 135% } }

/* The unambiguous one. A crawling line at the top of the pending area: nobody has to
   interpret it, it cannot be mistaken for a static page, and it keeps moving no matter how
   long the wait runs. This is what actually answers "is it stuck?". */
.lc-pending > .lc-pending-veil::after {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: color-mix(in oklab, var(--lc-rose) 26%, transparent);
  overflow: hidden;
}
.lc-pending > .lc-pending-veil::before {
  content: ''; position: absolute; top: 0; height: 2px; width: 34%; z-index: 1;
  background: linear-gradient(90deg, transparent, var(--lc-rose), transparent);
  animation: lc-crawl 1.5s cubic-bezier(.62,0,.38,1) infinite;
}
@keyframes lc-crawl {
  from { left: -34% } to { left: 100% }
}

/* ======================================================== 02 · name the thing ==
   Past a second, silence reads as broken. The label is real: a real noun and a
   real count, written by whoever called LCMotion.pending(). It only appears once
   the wait has run long enough to deserve an explanation. */
.lc-pending-label {
  position: absolute; left: 0; top: 0; z-index: 7;
  padding-left: 20px;
  font: 700 10px/1.6 var(--lc-mono, ui-monospace, Consolas, monospace);
  letter-spacing: .22em; text-transform: uppercase;
  color: currentColor; opacity: 0;
  animation: lc-gate-in var(--lc-t-base) var(--lc-ease) 1s both;
}
.lc-pending-label::before {
  content: ''; position: absolute; left: 0; top: 4px;
  width: 9px; height: 9px; border-radius: 99px; background: var(--lc-rose);
  animation: lc-seek .9s var(--lc-ease) infinite;
}
@keyframes lc-seek {
  0%, 100% { transform: translateX(0) scale(1);      opacity: .25 }
  50%      { transform: translateX(7px) scale(1.35); opacity: 1   }
}

/* ============================================================ 03 · map settle ==
   Markers land over ~400ms instead of all in one frame. LCMotion.stagger() caps
   how many get a delay — 200 pins cannot each wait their turn, so everything past
   the cap arrives immediately with the last delayed batch. */
.lc-pin-in {
  animation: lc-drop var(--lc-t-slow) var(--lc-spring) both;
  animation-delay: calc(var(--lc-i, 0) * 55ms);
}
@keyframes lc-drop {
  from { transform: translateY(-18px) scale(.4); opacity: 0 }
  to   { transform: none;                        opacity: 1 }
}

/* ==================================================== 05 · concierge thinking ==
   The caret belongs to the stream. A blinking caret left on finished text reads
   as broken, so lc-motion.js removes .lc-typing the moment the stream closes. */
.lc-think { display: inline-flex; gap: 5px; align-items: center; }
.lc-think i {
  width: 6px; height: 6px; border-radius: 99px; background: var(--lc-rose);
  animation: lc-breathe 1.25s var(--lc-ease) infinite;
  animation-delay: calc(var(--lc-i, 0) * .16s);
}
@keyframes lc-breathe {
  0%, 100% { opacity: .28; transform: translateY(0)    }
  45%      { opacity: 1;   transform: translateY(-3px) }
}
.lc-typing { border-right: 1.5px solid var(--lc-rose); animation: lc-caret .7s steps(1) infinite; }
@keyframes lc-caret { 50% { border-color: transparent } }

/* ====================================================== 06 · staggered arrival ==
   Cards rise ~70ms apart. Short enough that nobody consciously notices, long
   enough that the eye reads a wave instead of a flash. Only the first row is
   staggered — see the cap in LCMotion.stagger(). */
.lc-arrive > * {
  animation: lc-rise var(--lc-t-slow) var(--lc-ease) both;
  animation-delay: calc(var(--lc-i, 0) * var(--lc-stagger-step));
}
@keyframes lc-rise {
  from { opacity: 0; transform: translateY(14px) }
  to   { opacity: 1; transform: none             }
}

/* @starting-style gives the same entrance to nodes appended later (infinite
   scroll, streamed results) with no JS bookkeeping at all. */
@supports (transition-behavior: allow-discrete) {
  .lc-arrive-auto > * {
    opacity: 1; transform: none;
    transition: opacity var(--lc-t-base) var(--lc-ease),
                transform var(--lc-t-base) var(--lc-ease);
  }
  @starting-style {
    .lc-arrive-auto > * { opacity: 0; transform: translateY(14px) }
  }
}

/* ========================================================== 08 · count roll ==
   Pure CSS: an animated integer read back through counter(). Kept under 350ms —
   a number theatrically counting to 193 is a toy, not feedback.

   Named .lc-roll, NOT .lc-count: every directory already uses .lc-count for its
   result line, and squatting on it would have appended a stray counter to all of
   them. Same reason .lc-press is not .lc-btn and .lc-bloom is not .lc-saved. */
.lc-roll { font-variant-numeric: tabular-nums; counter-reset: lcn var(--lc-num); }
.lc-roll::after { content: counter(lcn); }
.lc-roll.is-rolling { animation: lc-roll-to .3s var(--lc-ease) forwards; }
@keyframes lc-roll-to { from { --lc-num: var(--lc-from, 0) } to { --lc-num: var(--lc-to, 0) } }

/* counter() output is generated content, so the digits are not text anything can be
   relied on to read — the result line announced its noun with no number at all. The
   kernel now marks the rolling node decorative and writes the value into this span
   beside it, which is real text and is read by everything.

   Taken out of flow entirely rather than sized to nothing: a 1px inline box between
   the number and its noun is a word space the layout never asked for. */
.lc-roll-a11y {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ========================================================= 09 · photo bloom ==
   Images resolve out of the placeholder's own tone rather than snapping in one
   frame late. Only applied to images that actually had to travel — a bloom on a
   cached image reads as lag, so lc-motion.js skips anything already complete. */
.lc-photo-in { animation: lc-bloom var(--lc-t-slow) var(--lc-ease) both; }
/* The kernel already declines to bloom a tile, and this is the second lock, for the
   caller that adds the class itself. The bloom ends on `transform: none` and runs
   with `fill: both`, so its retained final frame outranks an inline style forever —
   and Leaflet positions every tile with an inline translate3d. A bloomed tile is
   moved to the pane origin and stays there. */
.leaflet-container .lc-photo-in { animation: none; }
@keyframes lc-bloom {
  from { opacity: 0; filter: saturate(.15) blur(6px); transform: scale(1.04) }
  to   { opacity: 1; filter: none;                    transform: none        }
}

/* ==================================================== 10/11 · view transitions ==
   Cross-document navigation opt-in lives in lc-motion.php so it can be switched
   off per-template. Browsers without support simply navigate — nothing here needs
   a fallback. The root cross-fade is deliberately quick; a slow page fade feels
   like latency rather than polish. */
::view-transition-old(root),
::view-transition-new(root) { animation-duration: var(--lc-t-base); }

/* 11 · a card's photo grows into the hero of the page it opens. Both pages must
   agree on the name — set per vertical, never globally. */
::view-transition-group(lc-hero) { animation-timing-function: var(--lc-ease); }

/* ========================================================= 12 · menu unfurl ==
   For the REBUILT mega menus (owner is mid-rebuild, 2026-08-02) — not a patch on
   the current ones. Wipes down from its own top edge, links following just behind. */
.lc-unfurl { animation: lc-unfurl var(--lc-t-fast) var(--lc-ease) both; }
@keyframes lc-unfurl {
  from { clip-path: inset(0 0 100% 0); transform: translateY(-6px); opacity: .4 }
  to   { clip-path: inset(0 0 0 0);    transform: none;             opacity: 1  }
}

/* ====================================================== 13 · sheet with weight ==
   One change lands on all thirteen directories at once — every mobile filter
   routes through LCBottomBar. The overshoot must never reveal background beneath
   the sheet, hence the padded pseudo-element rather than a taller transform. */
.lc-sheet-in { animation: lc-sheet .62s var(--lc-spring) both; }
.lc-sheet-in::after {
  content: ''; position: absolute; inset: 100% 0 -40px 0;
  background: inherit; pointer-events: none;
}
@keyframes lc-sheet { from { transform: translateY(100%) } to { transform: translateY(0) } }

/* =========================================================== 14 · save bloom ==
   Saving is the most valuable action on the platform — it is the whole basis of
   accounts and return visits — so it should be the most satisfying thing to do.
   Fires optimistically; lc-saves reverses it honestly if the request fails. */
/* position:relative so the ring has something to anchor to. Safe on any button —
   relative with no offsets moves nothing — and necessary because the save control
   is different markup in every vertical. */
.lc-bloom { position: relative; animation: lc-pop .5s var(--lc-spring) both; }
@keyframes lc-pop {
  0%   { transform: scale(1)    }
  40%  { transform: scale(1.32) }
  100% { transform: scale(1)    }
}
.lc-bloom::after {
  content: ''; position: absolute; inset: -2px; border-radius: inherit;
  border: 1.5px solid var(--lc-rose); pointer-events: none;
  animation: lc-ring .7s var(--lc-ease) both;
}
@keyframes lc-ring {
  from { transform: scale(.7);  opacity: .9 }
  to   { transform: scale(2.1); opacity: 0  }
}

/* ====================================================== 15/18 · scroll-driven ==
   Tied to scroll position rather than to a threshold, so it tracks the finger
   exactly and runs off the main thread — it stays smooth even while a directory
   is busy building itself. Replaces scroll listeners; does not add any.
   Chromium-only today: elsewhere both elements render in their resting state and
   nothing is lost. */
@supports (animation-timeline: scroll()) {
  .lc-hdr-condense {
    animation: lc-condense linear both;
    animation-timeline: scroll();
    animation-range: 0 120px;
  }
  @keyframes lc-condense { to { --lc-hdr-scale: .82 } }

  .lc-read-prog {
    position: fixed; inset-inline: 0; top: 0; z-index: 999; height: 2px;
    background: var(--lc-rose); transform-origin: 0 50%; transform: scaleX(0);
    animation: lc-fill linear both;
    animation-timeline: scroll();
  }
  @keyframes lc-fill { to { transform: scaleX(1) } }
}

/* ============================================================= 16 · buttons ==
   These sit on the paths that make money. The depth is real on press; the
   confirmation state is applied only when the server has actually answered. */
.lc-press {
  transition: transform .12s var(--lc-ease), box-shadow .12s var(--lc-ease), background .3s;
  box-shadow: 0 3px 0 var(--lc-btn-shadow, rgba(0,0,0,.28));
}
.lc-press:active { transform: translateY(3px); box-shadow: 0 0 0 var(--lc-btn-shadow, rgba(0,0,0,.28)); }
.lc-press[aria-busy="true"] { cursor: progress; opacity: .72; }

/* ============================================================== 17 · fields ==
   The label lifts instead of vanishing, so the question stays readable while it
   is being answered. Requires placeholder=" " on the input. Stays above 12px —
   shrunk past that it stops being the field's name and becomes decoration. */
.lc-field { position: relative; }
.lc-field > input,
.lc-field > textarea { padding-top: 19px; }
.lc-field > label {
  position: absolute; left: 12px; top: 14px; font-size: 15px;
  transform-origin: 0 0; pointer-events: none; color: var(--lc-muted, #726C6C);
  transition: transform .22s var(--lc-ease), color .22s var(--lc-ease);
}
.lc-field > :is(input, textarea):focus + label,
.lc-field > :is(input, textarea):not(:placeholder-shown) + label {
  transform: translateY(-9px) scale(.8);
}

/* ============================================================= 20 · the tide ==
   The one signature, and it stays special only by being scarce: native app cold
   start and a hard page boot. Two placements maximum. The moment it shows up on
   a filter click it is dead and should be deleted. */
.lc-tide { position: relative; height: 120px; }
.lc-tide > span { position: absolute; inset-inline: 0; height: 1px; background: currentColor; opacity: .18; }
.lc-tide > span:nth-child(1) { top: 52% }
.lc-tide > span:nth-child(2) { top: 58%; opacity: .1 }
.lc-tide::before {
  content: ''; position: absolute; inset-inline: -30%; top: calc(52% - 1px);
  height: 2px; border-radius: 99px;
  background: radial-gradient(closest-side, var(--lc-rose) 0%, transparent 100%);
  animation: lc-swell 9s var(--lc-ease) infinite;
}
@keyframes lc-swell {
  0%, 100% { transform: translateX(-14%) scaleY(1);   opacity: .25 }
  50%      { transform: translateX(14%)  scaleY(3.4); opacity: .9  }
}

/* ====================================================== reduced motion ====== */
/* Nothing here removes meaning: skeletons still hold their shape, labels still
   name what is loading, saved states still read as saved. Only the movement goes. */
@media (prefers-reduced-motion: reduce) {
  .lc-skel-card::after,
  .lc-pin-in, .lc-arrive > *, .lc-arrive-auto > *,
  .lc-photo-in, .lc-unfurl, .lc-sheet-in, .lc-bloom, .lc-bloom::after,
  .lc-think i, .lc-typing, .lc-roll.is-rolling,
  .lc-hdr-condense, .lc-read-prog, .lc-tide::before {
    animation: none !important;
    transition: none !important;
  }
  /* The roll's counter reads --lc-num, which the animation would normally carry
     to its end value. With the animation off it must already BE the end value or
     the count renders 0 — a silent wrong number is worse than no motion. */
  .lc-roll { --lc-num: var(--lc-to, 0); }
  /* The gate and the label's 1s threshold are NOT motion — they are information
     design, and they are the whole three-lengths rule. Killing them here would
     hand reduced-motion visitors a skeleton that flashes on every fast request,
     which is the exact defect specimen 04 exists to remove. So the fade goes and
     the TIMING stays: a hard cut at the same two thresholds.

     A 1ms LINEAR run, not steps() — with steps(1, end) the value the forwards
     fill settles on is ambiguous across engines, and in Chrome it left the label
     invisible permanently. 1ms is imperceptible and unambiguous. */
  .lc-pending > .lc-pending-veil {
    animation: lc-gate-in .001s linear var(--lc-gate) both !important;
  }
  .lc-pending-label {
    animation: lc-gate-in .001s linear 1s both !important;
  }
  .lc-pending-label::before { animation: none !important; }
  .lc-pending > .lc-pending-veil::before { animation: none !important; left: 0; width: 100%; opacity: .5; }
  .lc-arrive > *, .lc-arrive-auto > *, .lc-pin-in, .lc-photo-in {
    opacity: 1; transform: none; filter: none;
  }
  .lc-sheet-in  { transform: translateY(0) }
  .lc-read-prog { display: none }
  .lc-btn:active { transform: none }
  ::view-transition-old(root), ::view-transition-new(root) { animation: none }
}

/* ===================================================== the house hero scrim ==
   Companion to the site's hero formula (image on the SECTION, width expand,
   header_transparent + overlap + height_offset_top). A photograph bright enough
   to be worth using is bright enough to swallow white type — the Boone Hall
   canopy on /weddings/ being the case in point. Add class "lc-hero-scrim" to any
   hero section and the type becomes legible without dimming the picture.

   Vertical and desaturated: a 45-degree saturated gradient is the naive tell, and
   a legibility scrim should disappear rather than be noticed. */
/* Hung on the INNER background element, not the outer section. YOOtheme renders
   the section as a wrapper whose CHILD carries uk-background-cover and the image —
   so a scrim on the wrapper, with the usual `> * { z-index: 1 }` guard, puts that
   image child ABOVE the scrim and it disappears. Ask how the theme actually builds
   the markup before styling it; the class was landing fine all along. */
.lc-hero-scrim > .uk-background-cover { position: relative; }
.lc-hero-scrim > .uk-background-cover::before {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(to top,
    rgba(14,18,16,.82) 0%, rgba(14,18,16,.55) 38%, rgba(14,18,16,.22) 70%, rgba(14,18,16,.08) 100%);
}
.lc-hero-scrim > .uk-background-cover > * { position: relative; z-index: 1; }

/* Listing variant — deliberately heavier, and anchored bottom-LEFT.
   Every other hero on this site uses a picture we chose. A listing hero uses whatever
   the listing agent shot: blown-out noon skies, a dark unlit den, a phone photo of a
   white kitchen. The type has to survive all of them, so this scrim does not try to be
   invisible — it earns its keep. Two stacked gradients rather than one diagonal: a
   vertical one for the bottom edge and a horizontal one for the left, which puts the
   darkest area exactly under the price and address and leaves the top-right of the
   photograph almost untouched. */
.lc-hero-scrim--listing > .uk-background-cover::before {
  background:
    linear-gradient(to top,
      rgba(10,14,12,.90) 0%, rgba(10,14,12,.72) 30%,
      rgba(10,14,12,.42) 58%, rgba(10,14,12,.16) 82%, rgba(10,14,12,.04) 100%),
    linear-gradient(to right,
      rgba(10,14,12,.52) 0%, rgba(10,14,12,.22) 45%, rgba(10,14,12,0) 78%);
}

/* The hero's own type. Sizes come from the theme's heading styles (title_style on the
   headline elements); everything here is contrast and rhythm, which the theme has no
   opinion about because no other page overlays type on a stranger's photograph. */
.lc-listing-hero .lc-listing-hero__price { text-shadow: 0 2px 22px rgba(10,14,12,.55); }
.lc-listing-hero .lc-listing-hero__addr { text-shadow: 0 1px 14px rgba(10,14,12,.6); }
.lc-listing-hero .lc-listing-hero__stats {
  letter-spacing: .04em;
  text-shadow: 0 1px 12px rgba(10,14,12,.6);
}
/* The save control sits on the photo, so it cannot keep the white pill it wears on a
   white page — it would read as a third piece of type. Glass over the scrim instead. */
.lc-listing-hero .lc-listing-save__btn {
  background: rgba(255,255,255,.14);
  border-color: rgba(255,255,255,.45);
  color: #fff;
  backdrop-filter: blur(6px);
}
.lc-listing-hero .lc-listing-save__hint { color: rgba(255,255,255,.82); }

/* No-photo listings. Land, new construction not yet photographed, a pocket listing the
   agent never uploaded — roughly 1 in 10. A hero with no image and light text would be
   white on white, so the hero is ONE section that always renders, dark by default, with
   the photo painted over it when there is one.

   It was briefly two sections switched by a display condition, and that broke the site
   header: the theme decides header transparency from the FIRST section in the template,
   not from whichever sections the conditions leave standing. A no-photo listing got a
   transparent light header over a white page — no logo, no nav, HTTP 200. One section
   cannot drift like that. The navy is the one the mortgage calculator already uses. */
.lc-listing-hero {
  background:
    radial-gradient(120% 100% at 12% 100%, rgba(184,134,59,.14) 0%, rgba(184,134,59,0) 55%),
    linear-gradient(150deg, #33343a 0%, #242427 100%);
}
.lc-listing-hero__note {
  color: rgba(255,255,255,.55);
  font-size: .8rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* ===================================================== the listing gallery ==
   Three faults in one component, all of them the same fault: a listing that carried
   forty-eight photographs was indistinguishable from one that carried two.

   1. The count existed only as the carousel's per-slide aria-label. Correct for a
      screen reader, invisible to everyone else.
   2. The strip that would have implied the count sat under a 620px image which itself
      starts ~510px down the document — roughly 250px below a 900px fold. It was
      reachable, but only by someone who already suspected it was there.
   3. Below 640px it was not rendered at all, and that is the majority of the traffic
      on this surface.

   The photograph is the product on a listing page, so none of this is paid for by
   cropping it. On a wide screen the strip stands the image's full height BESIDE it
   instead of under it: the image keeps its height and both are in the first screen.
   Narrow screens keep the horizontal shelf and float it over the foot of the image,
   where it occupies no layout height at all. */

/* -- The horizontal shelf: the default, and the only layout below 1200px. ----
   UIkit's thumbnav is a wrapping flex row, which is right for the six or eight images a
   normal slideshow carries and wrong for a house: this feed averages forty photos and
   tops out at 197. Left to shrink, forty thumbnails become forty 20px slivers; left to
   wrap, they become five rows of furniture. A single scrollable shelf is what a buyer
   already expects from every portal, so the thumbnails keep their size and the strip
   scrolls, with snap points so it comes to rest on a photo rather than between two. */
.lc-listing-gallery .uk-thumbnav {
  flex-wrap: nowrap;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: 6px;
  -webkit-overflow-scrolling: touch;
}
.lc-listing-gallery .uk-thumbnav > * { flex: 0 0 auto; scroll-snap-align: start; }
.lc-listing-gallery .uk-thumbnav::-webkit-scrollbar { height: 6px; width: 6px; }
.lc-listing-gallery .uk-thumbnav::-webkit-scrollbar-thumb {
  background: rgba(0,0,0,.22);
  border-radius: 3px;
}
/* A shelf of more thumbnails than fit is centred by the element's nav_align, which puts
   the first photo off the left edge at a scroll offset the browser cannot reach. Start
   the row instead; a strip whose first item is unreachable is worse than an off-centre
   one. Short strips look deliberate left-aligned under a left-aligned image anyway. */
.lc-listing-gallery .uk-thumbnav.uk-flex-center { justify-content: flex-start; }

/* Which photograph you are looking at. UIkit ships thumbnav's active and inactive
   states identically here — same opacity, no ring — so the strip showed forty-eight
   equal thumbnails and no position within them.
   The ring carries that on its own and the recede is slight, because these thumbnails
   are the content: dimming forty-seven photographs to point at one makes the strip
   look degraded, which is the opposite of what a gallery is for. */
.lc-listing-gallery .uk-thumbnav > * > a {
  display: block;
  border-radius: 3px;
  opacity: .78;
  transition: opacity .18s ease, box-shadow .18s ease;
}
.lc-listing-gallery .uk-thumbnav > * > a:hover { opacity: 1; }
.lc-listing-gallery .uk-thumbnav > .uk-active > a {
  opacity: 1;
  box-shadow: 0 0 0 2px #2d2e33;
}
.lc-listing-gallery .uk-thumbnav > * > a:focus-visible {
  opacity: 1;
  outline: 2px solid #2d2e33;
  outline-offset: 2px;
}

/* -- The count -----------------------------------------------------------------
   Absolutely positioned, so it adds no layout height and cannot shift anything when
   the script that fills it runs. Pointer-events are off because it shares a layer with
   the carousel's prev/next controls and must never swallow a click. It is deliberately
   not a button: the strip is the mechanism, this is only the fact. The per-slide
   aria-label still carries the same number to assistive tech, so this is additive. */
.lc-gal-count {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: .45em;
  padding: .46em .7em;
  border-radius: 4px;
  background: rgba(14, 18, 16, .58);
  border: 1px solid rgba(255, 255, 255, .16);
  backdrop-filter: blur(9px) saturate(1.1);
  -webkit-backdrop-filter: blur(9px) saturate(1.1);
  color: #fff;
  line-height: 1;
  pointer-events: none;
}
.lc-gal-count svg { display: block; width: 13px; height: 13px; opacity: .8; }
.lc-gal-count b { font-size: 15px; font-weight: 600; }
.lc-gal-count span {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .8);
}

/* -- The vertical rail: wide screens only ---------------------------------------
   Turning the strip ninety degrees is what buys the first screen without touching the
   photograph. The image loses ~130px of width, which at a fixed 16:9 makes its box
   SHORTER than its 620px cap rather than taller, so the rendered height is unchanged
   and the crop is fractionally looser than before.

   Only past four photographs: a rail holding one or two thumbnails is a mostly-empty
   column beside a full-height image, which reads as a rendering fault. Under that the
   shelf below the image is both honest and adequate, because a listing with three
   photographs does not have a discovery problem. The class is set by the script that
   counts the slides. */
@media (min-width: 1200px) {
  .lc-listing-gallery.lc-gal--rail { display: flex; align-items: stretch; gap: 14px; }
  .lc-listing-gallery.lc-gal--rail > .uk-position-relative { flex: 1 1 auto; min-width: 0; }
  /* Stretched to the image's height, with the list absolutely filling it, so a long
     rail scrolls inside a fixed column instead of growing the row past the image. */
  .lc-listing-gallery.lc-gal--rail > .uk-margin-top {
    position: relative;
    flex: 0 0 116px;
    align-self: stretch;
    margin-top: 0 !important;
  }
  .lc-listing-gallery.lc-gal--rail > .uk-margin-top > .uk-thumbnav {
    position: absolute;
    inset: 0;
    margin: 0;
    padding: 0 6px 0 0;
    flex-direction: column;
    justify-content: flex-start;
    gap: 7px;
    overflow-x: hidden;
    overflow-y: auto;
    scroll-snap-type: y proximity;
  }
  .lc-listing-gallery.lc-gal--rail > .uk-margin-top > .uk-thumbnav > * {
    width: 100%;
    padding-left: 0;
  }
  .lc-listing-gallery.lc-gal--rail > .uk-margin-top > .uk-thumbnav img {
    display: block;
    width: 100%;
    height: auto;
  }
  /* A rail ending flush with the image's bottom edge reads as a complete list. Fading
     the last few pixels is what says there is more below. */
  .lc-listing-gallery.lc-gal--rail > .uk-margin-top::after {
    content: '';
    position: absolute;
    left: 0;
    right: 6px;
    bottom: 0;
    height: 40px;
    pointer-events: none;
    background: linear-gradient(to top, #fff, rgba(255, 255, 255, 0));
  }
}
/* The element carries slidenav_outside_breakpoint: xl, which reads as though the
   prev/next controls move out of the image at wide sizes and into the rail's gutter.
   Measured at 1600, 1920 and 2560 they do not — they stay uk-position-center-right,
   inside the image, at every width. So the gutter stays narrow and the rail stays
   visually attached to the photograph it belongs to. */

/* -- Phones ----------------------------------------------------------------------
   The element hides its own nav below 640px, which left the phone with no strip and no
   count. Show it, floated over the foot of the image: on a screen this short the only
   affordable strip is one that costs no height. The image gives up 60px of its minimum
   so the whole thing clears a phone's fold. */
@media (max-width: 639px) {
  .lc-listing-gallery { position: relative; }
  .lc-listing-gallery .uk-slideshow-items { min-height: 320px !important; }

  .lc-listing-gallery > .uk-margin-top.uk-visible\@s {
    display: block !important;
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 2;
    margin: 0 !important;
    padding: 40px 8px 8px;
    border-radius: 0 0 5px 5px;
    /* Deep enough to hold a strip of small photographs against the bottom of a bright
       one. A kitchen floor or a white wall is the common case here, and a scrim tuned
       against a dark photo disappears on those. */
    background: linear-gradient(to top,
      rgba(10, 14, 12, .88) 0%, rgba(10, 14, 12, .66) 40%,
      rgba(10, 14, 12, .28) 74%, rgba(10, 14, 12, 0) 100%);
  }
  .lc-listing-gallery > .uk-margin-top > .uk-thumbnav {
    margin: 0;
    padding-bottom: 0;
    gap: 5px;
    scrollbar-width: none;
  }
  .lc-listing-gallery > .uk-margin-top > .uk-thumbnav > * { padding-left: 0; }
  .lc-listing-gallery > .uk-margin-top > .uk-thumbnav::-webkit-scrollbar { display: none; }
  .lc-listing-gallery > .uk-margin-top > .uk-thumbnav img { display: block; width: 62px; height: auto; }
  /* The ring is dark everywhere else because the shelf sits on the page. Here it sits
     on a photograph. */
  .lc-listing-gallery .uk-thumbnav > .uk-active > a { box-shadow: 0 0 0 2px rgba(255, 255, 255, .95); }

  .lc-gal-count { top: 10px; right: 10px; padding: .4em .6em; }
  .lc-gal-count b { font-size: 14px; }
}

/* Measure. The detail column is two thirds of a wide container, which puts MLS remarks
   — often twenty unbroken paragraphs — on a 900px line. YOOtheme's `maxwidth` emits a
   FIXED uk-width-*, not a maximum, so the constraint has to come from CSS. */
.lc-listing-prose { max-width: 66ch; }
