/* Property Manager — public stylesheet
 *
 * Not one colour, radius or font is hard-coded here. Everything references the CSS
 * custom properties written by App\Core\Theme from the branding settings, which is
 * what makes the whole site re-skinnable from the admin.
 */

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

/* The hidden attribute must win.
 *
 * Browsers apply [hidden] { display: none } from the user-agent stylesheet, which any
 * author rule setting display outranks -- so `.thumb-toggle { display: block }` quietly
 * made hidden do nothing, and the "Show all photos" button appeared on every listing
 * whether it was needed or not. Setting .hidden in JavaScript did nothing either.
 *
 * admin.css already carried this fix for one class; stating it once here means the next
 * element toggled with hidden does not have to rediscover it. */
[hidden] { display: none !important; }

:root {
  --space: 1rem;
  --shadow: 0 1px 2px rgb(0 0 0 / 0.04), 0 4px 16px rgb(0 0 0 / 0.06);
  --shadow-lg: 0 2px 4px rgb(0 0 0 / 0.05), 0 12px 32px rgb(0 0 0 / 0.10);
}

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font-body), system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--color-text);
  background: var(--color-background);
}

h1, h2, h3, h4 {
  font-family: var(--font-heading), system-ui, sans-serif;
  line-height: 1.2;
  margin: 0 0 0.5em;
  font-weight: 650;
  letter-spacing: -0.015em;
}

h1 { font-size: clamp(1.75rem, 1.2rem + 2.2vw, 2.6rem); }
h2 { font-size: clamp(1.3rem, 1.1rem + 1vw, 1.75rem); }
h3 { font-size: 1.1rem; }

p { margin: 0 0 1em; }
a { color: inherit; }
img { max-width: 100%; height: auto; display: block; }

.muted { color: var(--color-muted); }
.dot { color: var(--color-border); margin: 0 0.35em; }
.link { color: var(--color-primary); text-decoration: none; font-weight: 600; }
.link:hover { text-decoration: underline; }

ul.plain { list-style: none; margin: 0; padding: 0; }
ul.plain li { margin-bottom: 0.35rem; }
ul.plain a { text-decoration: none; }
ul.plain a:hover { text-decoration: underline; }

.container {
  width: min(100% - 2.5rem, var(--container));
  margin-inline: auto;
}

.skip-link {
  position: absolute; left: -9999px;
  background: var(--color-primary); color: var(--color-primary-contrast);
  padding: 0.6rem 1rem; z-index: 100;
}
.skip-link:focus { left: 1rem; top: 1rem; }

/* Visible focus everywhere — the theme must never be able to remove this. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  padding: 0.65rem 1.15rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font: inherit; font-weight: 600; line-height: 1.2;
  text-decoration: none; cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, transform .05s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--color-primary); color: var(--color-primary-contrast); }
.btn-primary:hover { background: var(--color-primary-dark, var(--color-secondary)); }
.btn-ghost { background: transparent; border-color: var(--color-border); color: var(--color-text); }
.btn-ghost:hover { border-color: var(--color-primary); color: var(--color-primary); }
.btn-whatsapp { background: #25D366; color: #06251a; }
.btn-whatsapp:hover { filter: brightness(0.94); }
.btn-block { width: 100%; }

/* ---------- Header ---------- */

.site-header {
  background: var(--color-background);
  border-bottom: 1px solid var(--color-border);
  position: sticky; top: 0; z-index: 20;
}
.header-inner {
  display: flex; align-items: center; gap: 1.5rem;
  min-height: 72px;
}
.brand { display: flex; align-items: center; gap: 0.7rem; text-decoration: none; }
.brand img { max-height: 44px; width: auto; }
.brand-mark {
  width: 40px; height: 40px; flex: none;
  display: grid; place-items: center;
  background: var(--color-primary); color: var(--color-primary-contrast);
  border-radius: var(--radius);
  font-weight: 700; font-size: 1.2rem;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.25; }
.brand-text strong { font-family: var(--font-heading), sans-serif; font-size: 1.05rem; }
.brand-text small { color: var(--color-muted); font-size: 0.78rem; }

.site-nav { display: flex; gap: 1.4rem; margin-left: auto; }
.site-nav a {
  text-decoration: none; font-weight: 550; font-size: 0.95rem;
  padding: 0.35rem 0; border-bottom: 2px solid transparent;
}
.site-nav a:hover { border-bottom-color: var(--color-primary); }

.header-contact { display: flex; gap: 0.6rem; align-items: center; }

/* ---------- Hero ---------- */

.hero {
  background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
  color: #fff;
  padding: clamp(3rem, 8vw, 5.5rem) 0;
}
.hero-inner { text-align: center; }
.hero h1 { color: #fff; margin-bottom: 0.25em; }
.hero-tagline { font-size: 1.15rem; opacity: 0.9; margin-bottom: 0.6rem; }

/* Heading image (Admin -> Design). Replaces the text inside the h1, which is why the
   h1's own type metrics have to stop applying -- line-height on a block containing
   only an image adds a stripe of dead space under it. */
.hero h1.is-image { line-height: 0; margin-bottom: 0.6rem; }
.hero-heading {
  display: block; margin: 0 auto;
  /* max-height caps it; max-width stops a wide wordmark overflowing a phone. Both are
     needed -- whichever binds first wins and the aspect ratio is kept either way. */
  width: auto; height: auto; max-width: 100%;
}
.hero-heading.is-small  { max-height: 72px; }
.hero-heading.is-medium { max-height: 120px; }
.hero-heading.is-large  { max-height: 180px; }

/* The counts sit between the tagline and the search box, so the 2rem gap that used to
   hang off the tagline now hangs off them. The two sibling rules below put it back when
   the counts are absent -- the stock is zero, or the tagline is switched off, or both.
   Adjacent-sibling selectors rather than :has(), and they beat .hero-search's own
   `margin: 0 auto` on specificity. */
.hero-stats { margin-bottom: 2rem; }
.hero-tagline + .hero-search { margin-top: 2rem; }
.hero h1 + .hero-search { margin-top: 2rem; }

.hero-search {
  display: flex; gap: 0.5rem;
  max-width: 620px; margin: 0 auto;
  background: #fff; padding: 0.4rem;
  border-radius: calc(var(--radius) * 1.5);
  box-shadow: var(--shadow-lg);
}
.hero-search input {
  flex: 1; border: 0; padding: 0.7rem 0.9rem;
  font: inherit; background: transparent; color: var(--color-text);
  border-radius: var(--radius);
}
.hero-search input:focus { outline: none; }
.hero-stats { margin-top: 0; opacity: 0.9; font-size: 0.95rem; }

/* ---------- Quick links ---------- */

.quick-links {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  margin-top: -2.5rem; margin-bottom: 3rem;
  position: relative; z-index: 2;
}
.quick-link {
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.15rem 1.25rem;
  text-decoration: none;
  box-shadow: var(--shadow);
  transition: transform .15s ease, box-shadow .15s ease;
}
.quick-link:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.quick-link strong { display: block; font-family: var(--font-heading), sans-serif; }
.quick-link small { color: var(--color-muted); }

/* ---------- Sections ---------- */

.section { margin-block: 3.5rem; }
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; margin-bottom: 1.5rem;
}
.section-head h2 { margin: 0; }

.page-head {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  padding: 2.25rem 0;
  margin-bottom: 2rem;
}
.page-head h1 { margin-bottom: 0.15em; }
.page-head p { margin: 0; }

/* ---------- Cards ---------- */

.card-grid {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(min(290px, 100%), 1fr));
}

.card {
  display: flex; flex-direction: column;
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  transition: transform .15s ease, box-shadow .15s ease;
}
.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.card.is-sold { opacity: 0.75; }

.card-media { position: relative; display: block; aspect-ratio: 4 / 3; background: var(--color-surface); }
.card-media img { width: 100%; height: 100%; object-fit: cover; }

/* Placeholder until the photographs are migrated. Hue varies by category so a grid
   of them still reads as distinct properties rather than a wall of identical boxes. */
.placeholder {
  width: 100%; height: 100%;
  display: grid; place-content: center; gap: 0.25rem; text-align: center;
  color: #fff; font-weight: 600; letter-spacing: 0.02em;
  background: linear-gradient(140deg, #4a5d52, #2f3d36);
}
.placeholder small { font-weight: 400; opacity: 0.75; font-size: 0.75rem; }
.placeholder[data-set="farm"]       { background: linear-gradient(140deg, #6b7f4e, #3c4a2c); }
.placeholder[data-set="tourism"]    { background: linear-gradient(140deg, #b07f42, #5c3f1e); }
.placeholder[data-set="commercial"] { background: linear-gradient(140deg, #55697d, #2c3945); }
.placeholder[data-set="land"]       { background: linear-gradient(140deg, #8a8c6a, #4a4c38); }
.placeholder[data-set="residential"]{ background: linear-gradient(140deg, #7a6b83, #403748); }
.placeholder-hero { aspect-ratio: 21 / 9; border-radius: var(--radius); }

.badge {
  position: absolute; top: 0.75rem;
  padding: 0.25rem 0.6rem;
  border-radius: calc(var(--radius) / 1.5);
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
}
.badge-featured { left: 0.75rem; background: var(--color-accent); color: #1a1a1a; }
.badge-status   { left: 0.75rem; background: var(--color-danger); color: #fff; }
.badge-count    { right: 0.75rem; background: rgb(0 0 0 / 0.6); color: #fff; text-transform: none; }

.card-body { padding: 1.1rem 1.15rem 1.25rem; display: flex; flex-direction: column; flex: 1; }
.card-meta { font-size: 0.8rem; color: var(--color-muted); margin-bottom: 0.4rem; }
.card-title { font-size: 1.05rem; margin-bottom: 0.4rem; }
.card-title a { text-decoration: none; }
.card-title a:hover { color: var(--color-primary); }

.card-price {
  font-family: var(--font-heading), sans-serif;
  font-size: 1.25rem; font-weight: 700; color: var(--color-primary);
  margin-bottom: 0.6rem;
}
.card-price small { font-size: 0.8rem; font-weight: 500; color: var(--color-muted); }

.card-specs {
  list-style: none; margin: 0 0 0.75rem; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.4rem 0.75rem;
  font-size: 0.85rem; color: var(--color-muted);
}
.card-specs li { display: flex; gap: 0.3rem; }
.card-specs strong { color: var(--color-text); }

.card-excerpt { font-size: 0.88rem; color: var(--color-muted); margin-bottom: 0.75rem; }
.card-ref { font-size: 0.75rem; margin: auto 0 0; }

/* ---------- Listing index layout ---------- */

.layout-with-sidebar {
  display: grid; gap: 2rem; align-items: start;
  grid-template-columns: 280px 1fr;
  margin-bottom: 4rem;
}
@media (max-width: 860px) {
  .layout-with-sidebar { grid-template-columns: 1fr; }
}

.filters {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.25rem;
  position: sticky; top: 92px;
}
.filter-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 1rem; }
.filter-head h2 { font-size: 1.05rem; margin: 0; }
.filter-head .clear { font-size: 0.82rem; color: var(--color-primary); }

.field { margin-bottom: 1.1rem; border: 0; padding: 0; }
.field label, .field legend {
  display: block; font-size: 0.8rem; font-weight: 650;
  margin-bottom: 0.35rem; color: var(--color-text);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.field input, .field select {
  width: 100%; padding: 0.55rem 0.65rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font: inherit; background: var(--color-background); color: var(--color-text);
}
.field-pair { display: flex; align-items: center; gap: 0.4rem; }
.field-pair span { color: var(--color-muted); }
.hint { font-size: 0.75rem; color: var(--color-muted); margin: 0.4rem 0 0; }

.results-bar {
  display: flex; justify-content: flex-end; align-items: center;
  margin-bottom: 1.25rem;
}
.sort-form { display: flex; align-items: center; gap: 0.5rem; }
.sort-form label { font-size: 0.85rem; color: var(--color-muted); }
.sort-form select {
  padding: 0.45rem 0.6rem; font: inherit;
  border: 1px solid var(--color-border); border-radius: var(--radius);
  background: var(--color-background); color: var(--color-text);
}

.empty {
  text-align: center; padding: 3.5rem 1.5rem;
  background: var(--color-surface);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius);
}

.pagination {
  display: flex; gap: 0.4rem; justify-content: center; flex-wrap: wrap;
  margin-top: 2.5rem;
}
.pagination a, .pagination .current {
  padding: 0.5rem 0.85rem; border-radius: var(--radius);
  text-decoration: none; border: 1px solid var(--color-border);
  font-size: 0.9rem;
}
.pagination a:hover { border-color: var(--color-primary); color: var(--color-primary); }
.pagination .current { background: var(--color-primary); color: var(--color-primary-contrast); border-color: var(--color-primary); }

/* ---------- Listing detail ---------- */

.breadcrumb { font-size: 0.85rem; color: var(--color-muted); padding: 1.25rem 0 0.75rem; }
.breadcrumb a { text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }
.breadcrumb span { margin: 0 0.4rem; }

.listing-hero { margin-bottom: 2rem; }
/* Gallery.
   The frame keeps one shape so nothing below it moves when you click a thumbnail, and
   the photograph is contained inside it rather than cropped to fill it. The old frame
   was 21/9 with object-fit: cover, which suited 8 of the 649 photographs and cut the
   top and bottom off the rest. 3/2 is the shape most of this library actually is. */
.gallery-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 2;
  max-height: 72vh;          /* a tall photo must not fill the whole screen */
  overflow: hidden;
  border-radius: var(--radius);
  background: #14150f;       /* seen only for a fraction of a second while loading */
}

/* A blurred copy of the same photograph behind it, instead of empty bars. It is
   decorative, so it is hidden from screen readers -- the real image carries the alt. */
.gallery-blur {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(30px) saturate(1.15) brightness(0.55);
  /* Blur samples past the element's edge and would otherwise leave a pale rim. */
  transform: scale(1.15);
}

.gallery-stage > picture { position: absolute; inset: 0; display: block; }

.gallery-main {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;       /* the whole photograph, always */
}

@media (max-width: 640px) {
  /* Slightly taller on a phone: the photo gets more of a narrow screen, and there is
     less width for the blurred surround to be worth showing. */
  .gallery-stage { aspect-ratio: 4 / 3; }
  .gallery-blur { filter: blur(20px) brightness(0.6); }
}

/* Motion-free by nature, but the blur is expensive to composite on low-end phones;
   this drops it to a flat tone for anyone who has asked for reduced effects. */
@media (prefers-reduced-motion: reduce) {
  .gallery-blur { filter: brightness(0.45) saturate(0.4); transform: none; }
}

.layout-with-aside {
  display: grid; gap: 2.5rem; align-items: start;
  grid-template-columns: 1fr 340px;
  margin-bottom: 4rem;
}
@media (max-width: 900px) {
  .layout-with-aside { grid-template-columns: 1fr; }
}

/* Sits above the gallery now, at full container width rather than in the article
   column. The title and price are what a buyer checks first, so they should not be
   below a picture that fills the screen. */
.listing-head { margin-bottom: 1.25rem; }
.listing-head h1 { margin: 0 0 0.25rem; }
.listing-meta {
  display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center;
  font-size: 0.88rem; color: var(--color-muted); margin-bottom: 0.75rem;
}
.pill {
  background: var(--color-primary-soft, var(--color-surface));
  color: var(--color-primary);
  padding: 0.2rem 0.65rem; border-radius: 999px;
  font-size: 0.78rem; font-weight: 650;
}
.pill-status { background: var(--color-danger); color: #fff; }

.listing-price {
  font-family: var(--font-heading), sans-serif;
  font-size: clamp(1.6rem, 1.2rem + 1.5vw, 2.1rem);
  font-weight: 700; color: var(--color-primary);
  margin: 0.5rem 0 0.25rem;
}
.listing-price small { font-size: 0.9rem; font-weight: 500; color: var(--color-muted); }

.panel {
  border-top: 1px solid var(--color-border);
  padding-top: 1.75rem; margin-bottom: 1.75rem;
}
/* The article used to open with the title block; now it opens with a panel, and a rule
   immediately under the gallery reads as a stray line rather than a separator. */
.listing-main > .panel:first-child { border-top: 0; padding-top: 0; }
.panel h2 { font-size: 1.2rem; }

/* The spec grid is the point of the rebuild — every value here used to be a
   sentence buried in a paragraph. */
.spec-grid {
  display: grid; gap: 1px; margin: 0;
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
}
.spec { background: var(--color-background); padding: 0.9rem 1rem; }
.spec dt {
  font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--color-muted); margin-bottom: 0.2rem;
}
.spec dd { margin: 0; font-weight: 650; font-size: 1rem; }

.prose { color: var(--color-text); }
.feature-group {
  font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--color-muted); margin: 1.25rem 0 0.5rem;
}
.feature-list {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 0.4rem 1rem;
  grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 1fr));
}
.feature-list li { position: relative; padding-left: 1.35rem; font-size: 0.92rem; }
.feature-list li::before {
  content: "✓"; position: absolute; left: 0;
  color: var(--color-success); font-weight: 700;
}

.map-placeholder {
  aspect-ratio: 16/7; border-radius: var(--radius);
  background: repeating-linear-gradient(45deg, var(--color-surface), var(--color-surface) 12px, var(--color-background) 12px, var(--color-background) 24px);
  border: 1px solid var(--color-border);
  display: grid; place-content: center; gap: 0.25rem; text-align: center;
  color: var(--color-muted);
}
.map-placeholder span { font-weight: 650; color: var(--color-text); }

/* ---------- Contact card ---------- */

.contact-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.4rem;
  position: sticky; top: 92px;
}
.contact-card h2 { font-size: 1.1rem; }

.agent { display: flex; gap: 0.8rem; align-items: center; margin: 1.1rem 0; }
.agent-avatar {
  width: 46px; height: 46px; flex: none;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--color-primary); color: var(--color-primary-contrast);
  font-weight: 700; font-size: 1.15rem;
}
/* A photograph in the same circle the initial occupies, so the card does not reflow
   depending on whether an agent has uploaded one. */
.agent-photo { object-fit: cover; background: var(--color-surface); }
.agent div { display: flex; flex-direction: column; line-height: 1.35; }

/* With a real photograph the block stacks and the picture gets the card's width, up to
   140px. One number to change if it wants to be bigger or smaller. */
.agent.has-photo {
  flex-direction: column; align-items: center; text-align: center;
  gap: 0.6rem; margin: 1.25rem 0;
}
.agent.has-photo .agent-avatar {
  width: 140px; height: 140px;
  max-width: 100%;   /* a narrow card must shrink it rather than overflow */
}
.agent small { font-size: 0.78rem; }

.contact-actions { display: grid; gap: 0.55rem; margin-top: 1.1rem; }

.similar { margin-bottom: 4rem; }

/* ---------- Region grid ---------- */

.region-grid {
  display: grid; gap: 0.85rem;
  grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
}
.region {
  display: flex; flex-direction: column;
  padding: 0.9rem 1.05rem; text-decoration: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.region:hover { border-color: var(--color-primary); }
.region small { color: var(--color-muted); font-size: 0.8rem; }

/* ---------- Footer ---------- */

.site-footer {
  background: var(--color-secondary); color: #fff;
  margin-top: 4rem; padding-top: 3rem;
}
.site-footer a { color: #fff; opacity: 0.85; }
.site-footer a:hover { opacity: 1; }
.site-footer .muted { color: rgb(255 255 255 / 0.6); }
.footer-inner {
  display: grid; gap: 2rem; padding-bottom: 2.5rem;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
}
.footer-col h4 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.7; }

/* Footer logo (Admin -> Design). Takes the place of the company name, so it carries the
   gap that the <strong> line used to leave above the tagline. */
.footer-logo {
  display: block; width: auto; height: auto; max-width: 100%;
  margin-bottom: 0.6rem;
}
.footer-logo.is-small  { max-height: 36px; }
.footer-logo.is-medium { max-height: 56px; }
.footer-logo.is-large  { max-height: 80px; }

/* Social profiles (Admin -> Settings -> Contact details). A row, not a stacked list.
   Selectors are written as ul.social-links deliberately: this element also carries
   .plain, and `ul.plain { margin: 0 }` / `ul.plain li` outweigh a bare class -- a
   plain .social-links rule here would lose and the row would sit flush and stacked. */
ul.social-links { display: flex; gap: 0.5rem; margin-top: 1rem; }
ul.social-links li { margin-bottom: 0; }
.social-link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: var(--radius);
  background: rgb(255 255 255 / 0.12);
}
ul.social-links a,
ul.social-links a:hover { text-decoration: none; }
.social-link:hover { background: rgb(255 255 255 / 0.24); }
.social-link svg { width: 18px; height: 18px; display: block; fill: currentColor; }
.footer-bottom {
  border-top: 1px solid rgb(255 255 255 / 0.12);
  padding: 1.25rem 0; font-size: 0.85rem;
}

@media (max-width: 720px) {
  .site-nav { display: none; }
  .header-contact .btn-ghost { display: none; }
  .filters { position: static; }
  .contact-card { position: static; }
}

/* ---------- Gallery ---------- */

.gallery picture { display: block; }
/* Thumbnails wrap onto rows rather than scrolling sideways.
   They used to be one long flex row with a native scrollbar under it -- a 6px drag
   target, which is a poor way to reach photo 30 of 46. Nothing here scrolls now: a
   listing with the median 10 photographs shows all of them in two rows and needs no
   interaction at all. */
.thumb-strip {
  --thumb-h: 76px;
  --thumb-w: 100px;
  --thumb-gap: 6px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--thumb-gap);
  margin-top: 0.6rem;
}

/* Applied by script, never by default -- see the note in the template. */
.thumb-strip.is-clamped {
  max-height: calc(var(--thumb-h) * 2 + var(--thumb-gap));
  overflow: hidden;
}

/* Fixed width rather than a stretching grid track. An auto-fill grid left a part-full
   row hard against the left edge with the leftover space piled up on the right; fixed
   widths let flexbox centre every row, so four photographs sit under the middle of the
   picture instead of hanging off one side. */
.thumb {
  width: var(--thumb-w); height: var(--thumb-h);
  flex: 0 0 auto; box-sizing: border-box;
  padding: 0; border: 2px solid transparent;
  border-radius: var(--radius); overflow: hidden; cursor: pointer;
  background: none; line-height: 0;
}

.thumb-toggle {
  display: block; width: 100%; margin-top: var(--thumb-gap);
  padding: 0.5rem 1rem; font: inherit; font-size: 0.85rem; font-weight: 600;
  color: var(--color-primary); background: var(--color-surface);
  border: 1px solid var(--color-border); border-radius: var(--radius);
  cursor: pointer;
}
.thumb-toggle:hover { border-color: var(--color-primary); }

@media (max-width: 640px) {
  .thumb-strip {
    --thumb-h: 62px;
    --thumb-w: 78px;
  }
}
.thumb.is-active { border-color: var(--color-primary); }
.thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }

.listing-map {
  aspect-ratio: 16/8; width: 100%;
  border: 1px solid var(--color-border); border-radius: var(--radius);
  background: var(--color-surface); z-index: 0;
}
.leaflet-container { border-radius: var(--radius); font: inherit; }

/* Dark header — for brands whose logo is drawn in light colours. */
.site-header.is-dark {
  background: var(--color-secondary);
  border-bottom-color: rgb(255 255 255 / 0.12);
}
.site-header.is-dark .brand-text strong,
.site-header.is-dark .site-nav a { color: #fff; }
.site-header.is-dark .brand-text small { color: rgb(255 255 255 / 0.65); }
.site-header.is-dark .site-nav a:hover { border-bottom-color: var(--color-accent); }
.site-header.is-dark .btn-ghost { border-color: rgb(255 255 255 / 0.35); color: #fff; }
.site-header.is-dark .btn-ghost:hover { background: rgb(255 255 255 / 0.12); color: #fff; }

/* Hero photograph, set from Design settings. The scrim keeps white text readable over
   whatever image the customer chooses; its strength is adjustable there too. */
.hero.has-image {
  background-image: linear-gradient(var(--hero-scrim, rgb(0 0 0 / .55)), var(--hero-scrim, rgb(0 0 0 / .55))), var(--hero-image);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.hero.has-image h1 { text-shadow: 0 1px 12px rgb(0 0 0 / 0.35); }
/* text-shadow does nothing to an <img>; this is the same lift for the image heading,
   which needs it more -- it sits over an arbitrary photograph. */
.hero.has-image .hero-heading { filter: drop-shadow(0 1px 12px rgb(0 0 0 / 0.45)); }
.hero.has-image .hero-tagline,
.hero.has-image .hero-stats { text-shadow: 0 1px 8px rgb(0 0 0 / 0.4); }

/* Breadcrumb: a real ordered list, separators drawn in CSS so screen readers don't
   announce a slash between every crumb. */
.breadcrumb ol {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.15rem 0;
}
.breadcrumb li { display: flex; align-items: center; min-width: 0; }
.breadcrumb li + li::before {
  content: "›"; margin: 0 0.5rem; color: var(--color-border); flex: none;
}
.breadcrumb li:last-child span {
  color: var(--color-text); font-weight: 550;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 560px) {
  /* On a narrow screen the category crumb is the first thing worth dropping. */
  .breadcrumb li:nth-child(2) { display: none; }
  .breadcrumb li:nth-child(3)::before { content: "›"; }
}

@media (max-width: 420px) {
  /* Search field and button stack rather than squeezing the input to nothing. */
  .hero-search { flex-direction: column; }
  .hero-search input { text-align: center; }
  .results-bar { justify-content: stretch; }
  .sort-form { width: 100%; }
  .sort-form select { flex: 1; }
}

/* ---------- Property map (/map) ---------- */

.map-head { margin-bottom: 1rem; }
.map-head h1 { margin-bottom: 0.25rem; }

.map-page {
  width: 100%;
  height: clamp(420px, 68vh, 760px);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  z-index: 0;   /* keep Leaflet's panes under the sticky site header */
}

.map-key { display: flex; flex-wrap: wrap; gap: 1.25rem; margin: 0.75rem 0 0; font-size: 0.85rem; }
.map-key-item { display: inline-flex; align-items: center; gap: 0.45rem; color: var(--color-muted); }
.map-key-pin {
  width: 12px; height: 18px; border-radius: 50% 50% 50% 50% / 40% 40% 70% 70%;
  background: #3388ff; box-shadow: inset 0 -3px 0 rgb(0 0 0 / 0.15);
}
.map-key-circle {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}
/* A district, not a circle: the legend swatch should look like the thing on the map. */
.map-key-area {
  width: 20px; height: 14px;
  border: 2px solid var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 14%, transparent);
  clip-path: polygon(0 20%, 35% 0, 100% 12%, 92% 100%, 25% 88%);
}

/* The count sitting in the middle of a shaded district. Leaflet's tooltip chrome is
   removed -- this is a label on the map, not a bubble pointing at something. */
.map-district-label,
.leaflet-tooltip.map-district-label {
  background: transparent; border: 0; box-shadow: none; padding: 0;
  color: var(--color-primary);
  font-weight: 700; font-size: 1.15rem;
  text-shadow: 0 0 4px #fff, 0 0 8px #fff;   /* legible over any tile */
}
.map-district-label::before { display: none; }   /* the tooltip's pointer arrow */

/* Clicking a district lists its properties to choose from. The header stays put while
   the list scrolls, so you can still see which district you opened. */
/* No max-height or flex here: Leaflet's own maxHeight option puts the scroll on
   .leaflet-popup-content, and a second height limit inside it let the list render
   outside the popup's white box and over the map. The header sticks instead, so the
   district name stays visible while the list scrolls under it. */
.map-district-pop { display: block; }
.map-district-head {
  position: sticky; top: 0; z-index: 1;
  background: var(--color-background, #fff);
  padding-bottom: 0.4rem; margin-bottom: 0.35rem;
  border-bottom: 1px solid var(--color-border);
}
.map-district-head strong { display: block; font-size: 0.95rem; }
.map-district-sub { display: block; color: var(--color-muted); font-size: 0.75rem; }

.map-district-list { list-style: none; margin: 0; padding: 0; }
.map-district-list li + li { border-top: 1px solid var(--color-border); }
.map-district-item {
  display: flex; gap: 0.55rem; align-items: flex-start;
  padding: 0.45rem 0; text-decoration: none; color: inherit;
}
.map-district-item:hover { background: var(--color-surface); text-decoration: none; }
.map-district-item img {
  width: 54px; height: 40px; object-fit: cover; flex: none;
  border-radius: calc(var(--radius) / 1.5);
}
.map-district-text { display: flex; flex-direction: column; min-width: 0; }
.map-district-name {
  font-weight: 600; font-size: 0.82rem; line-height: 1.25;
  /* Titles here run to 60+ characters; two lines then ellipsis keeps rows even. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.map-district-where,
.map-district-facts { color: var(--color-muted); font-size: 0.78rem; }
.map-district-price { font-weight: 700; font-size: 0.8rem; color: var(--color-primary); }
.map-district-status { color: var(--color-warning); font-size: 0.78rem; font-weight: 600; }

/* The same properties without the map, for print, screen readers and no-JavaScript. */
.district-tally { margin: 0.75rem 0 0; font-size: 0.9rem; }
.district-group { border-bottom: 1px solid var(--color-border); }
.district-group:last-child { border-bottom: 0; }
.district-group > summary { padding: 0.45rem 0; cursor: pointer; }
.district-group-list { list-style: none; margin: 0 0 0.6rem; padding: 0 0 0 1.1rem; }
.district-group-list li { padding: 0.28rem 0; display: flex; gap: 0.6rem; flex-wrap: wrap; }
.district-group-list .muted { font-size: 0.82rem; }

/* Popups. The whole card is one link -- on a phone, hunting for a small "view" link
   inside a popup is worse than tapping anywhere on it. */
.map-pop { display: flex; gap: 0.6rem; text-decoration: none; color: inherit; }
.map-pop-img {
  width: 72px; height: 72px; object-fit: cover; border-radius: var(--radius); flex: none;
}
.map-pop-body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.map-pop-title { font-weight: 650; line-height: 1.25; }
.map-pop-where, .map-pop-facts { font-size: 0.78rem; color: var(--color-muted); }
.map-pop-price { font-weight: 700; color: var(--color-primary); margin-top: 0.15rem; }
.map-pop-status {
  align-self: flex-start; margin-top: 0.15rem;
  background: var(--color-warning); color: #fff;
  padding: 0.05rem 0.4rem; border-radius: 999px; font-size: 0.68rem; font-weight: 650;
}
.map-pop-go { font-size: 0.78rem; font-weight: 650; color: var(--color-primary); margin-top: 0.25rem; }
.map-pop:hover .map-pop-go { text-decoration: underline; }

/* Leaflet sets its own popup padding; this keeps our card flush inside it. */
.leaflet-popup-content { margin: 0.7rem 0.8rem; }
.leaflet-popup-content p { margin: 0; }

.map-unplaced { margin-top: 2rem; }
.map-unplaced-list { list-style: none; padding: 0; margin: 0.5rem 0 0; }
.map-unplaced-list li { padding: 0.35rem 0; display: flex; gap: 0.6rem; flex-wrap: wrap; }

@media (max-width: 640px) {
  .map-page { height: clamp(360px, 60vh, 520px); }
}

/* ---------- Maintenance mode ----------
 *
 * The whole public site, replaced by one page (Admin -> Settings -> Maintenance).
 * It is a standalone document -- no site header, no footer -- because every link in
 * both of those leads somewhere the switch has just closed.
 *
 * Everything here is still a theme token, so a customer's maintenance page is their
 * colours and their type without a line of this file changing. */

body.maintenance-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  /* Two washes of the brand colour over the page background. color-mix rather than a
     hard-coded tint: the primary is whatever the customer set it to. */
  background:
    radial-gradient(60rem 32rem at 50% -12%, color-mix(in srgb, var(--color-primary) 14%, transparent), transparent 70%),
    radial-gradient(46rem 28rem at 88% 108%, color-mix(in srgb, var(--color-accent, var(--color-primary)) 10%, transparent), transparent 70%),
    var(--color-background);
}

.maintenance-card {
  width: 100%;
  max-width: 640px;
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) * 1.5);
  box-shadow: var(--shadow-lg);
  padding: clamp(1.75rem, 1rem + 3vw, 3rem);
  text-align: center;
}

.maintenance-brand { margin-bottom: 1.5rem; }
.maintenance-logo { display: inline-block; }
.maintenance-logo img { max-height: 68px; max-width: 260px; width: auto; margin: 0 auto; }
/* Light-ink artwork is the only artwork on many installs, and it would vanish here. */
.maintenance-logo.on-dark {
  background: var(--color-secondary);
  padding: 0.9rem 1.3rem;
  border-radius: var(--radius);
}
.maintenance-brand .brand-mark { margin: 0 auto 0.6rem; width: 52px; height: 52px; font-size: 1.4rem; }
.maintenance-name {
  display: block;
  font-family: var(--font-heading), system-ui, sans-serif;
  font-size: 1.15rem;
}

.maintenance-pill {
  display: inline-flex; align-items: center; gap: 0.5rem;
  margin: 0 0 1.1rem;
  padding: 0.3rem 0.85rem 0.3rem 0.7rem;
  border-radius: 999px;
  background: var(--color-primary-soft, var(--color-surface));
  color: var(--color-primary);
  font-size: 0.78rem; font-weight: 650;
  letter-spacing: 0.02em; text-transform: uppercase;
}
.maintenance-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: currentColor; flex: none;
  animation: maintenance-pulse 2.4s ease-in-out infinite;
}
@keyframes maintenance-pulse { 50% { opacity: 0.25; } }
/* A page somebody lands on unexpectedly is the wrong place to insist on motion. */
@media (prefers-reduced-motion: reduce) {
  .maintenance-dot { animation: none; }
}

.maintenance-card h1 { margin-bottom: 0.4em; }
.maintenance-lead {
  color: var(--color-muted);
  font-size: 1.05rem;
  max-width: 46ch;
  margin-inline: auto;
}
.maintenance-when {
  display: inline-block;
  margin: 0.25rem 0 0;
  padding: 0.5rem 1rem;
  border: 1px dashed var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  font-size: 0.92rem;
}

.maintenance-contact {
  margin-top: 2rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--color-border);
}
.maintenance-contact h2 { font-size: 1.05rem; margin-bottom: 1rem; }

.maintenance-actions {
  display: flex; flex-wrap: wrap; gap: 0.6rem; justify-content: center;
}

.maintenance-details {
  display: flex; flex-wrap: wrap; gap: 1.25rem 2.5rem; justify-content: center;
  margin: 1.5rem 0 0; text-align: left;
}
.maintenance-details > div { min-width: 0; }
.maintenance-details dt {
  font-size: 0.78rem; font-weight: 650; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--color-muted);
}
.maintenance-details dd { margin: 0.15rem 0 0; }

/* The footer's glyph plate is a white tint for a dark ground; on this card it would be
   invisible, so the same partial gets a surface plate instead. */
.maintenance-card ul.social-links { justify-content: center; margin-top: 1.5rem; }
.maintenance-card .social-link { background: var(--color-surface); color: var(--color-muted); }
.maintenance-card .social-link:hover { background: var(--color-primary-soft, var(--color-surface)); color: var(--color-primary); }

.maintenance-foot { margin: 1.75rem 0 0; font-size: 0.82rem; }

@media (max-width: 480px) {
  /* One per line beats two half-width buttons with the number wrapping inside them. */
  .maintenance-actions { flex-direction: column; }
  .maintenance-details { gap: 1rem; }
  .maintenance-details > div { width: 100%; }
}

/* The bar shown to signed-in staff while maintenance is on, so nobody browsing the
   real site forgets the public cannot see any of it. */
.maintenance-notice {
  background: var(--color-warning, #b45309);
  color: #fff;
  text-align: center;
  /* p carries `margin: 0 0 1em` here, which would open a gap above the header. */
  margin: 0;
  padding: 0.5rem 1rem;
  font-size: 0.85rem;
  font-weight: 600;
}
.maintenance-notice a { color: inherit; }


/* ---------------------------------------------------------------------------
   Touch targets and the small-type floor.

   Measured rather than guessed: a headless run at 390px and 360px (the two
   widths this catalogue's buyers actually arrive on) found the footer contact
   links 22px tall, the breadcrumb 22px, "Clear all" 21px, the district fallback
   list 23px, "View all" 26px, the gallery toggle 40px and the social plates
   36px. Google's mobile-friendly report wants 48px and a fingertip is about
   44px, so every one of those was a miss -- and indexing is mobile-first, so it
   is the phone layout that is ranked.

   Grown at every width rather than inside a media query: a larger hit area is
   no worse with a mouse, and a second copy of the rule under a breakpoint is a
   second copy to drift. Only the box grows -- no type size, colour or position
   changes anywhere here.

   The 0.72rem -> 0.78rem swap above is the same measurement: 11.52px is under
   the 12px floor the mobile report uses, 12.48px clears it and reads the same.

   Not fixed, deliberately: Leaflet's own "Leaflet" attribution link is 14px.
   It is required by the licence, it is conventionally small, and resizing a
   third party's attribution to satisfy a tap-target rule is not worth the
   divergence from their stylesheet. -------------------------------------- */
.site-footer ul.plain a,
.breadcrumb a,
.filter-head .clear,
.district-group-list li a,
.link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

.social-link { width: 44px; height: 44px; }

.thumb-toggle { min-height: 44px; }

/* Leaflet ships 30px zoom controls. Its stylesheet is self-hosted and kept pristine so
   it can be replaced wholesale, so the override belongs here -- but leaflet.css is
   linked AFTER this file (only on pages with a map), so at equal specificity Leaflet
   wins and the override does nothing. Its rule is `.leaflet-touch .leaflet-bar a`, so
   these carry one class more. Both the touch and pointer forms are listed because
   Leaflet picks between them at runtime and a headless browser does not always land on
   the same one as a phone.

   The first attempt at this deployed cleanly, linted, and changed nothing on the page;
   re-measuring the live site is what found it still sitting at 30px. */
.leaflet-bar a.leaflet-control-zoom-in,
.leaflet-bar a.leaflet-control-zoom-out,
.leaflet-touch .leaflet-bar a.leaflet-control-zoom-in,
.leaflet-touch .leaflet-bar a.leaflet-control-zoom-out {
  width: 40px; height: 40px; line-height: 40px; font-size: 1.25rem;
}

/* A region's written introduction, above its listings. Measured against the body text
   rather than the page head: it is prose to be read, not a caption. */
.region-intro {
  max-width: 68ch;
  margin: 1.25rem 0 0.5rem;
  color: var(--color-text);
  line-height: 1.65;
}
.region-intro p { margin: 0 0 0.85rem; }
.region-intro p:last-child { margin-bottom: 0; }

/* ---- Catalogue page (/catalogue) ------------------------------------------------
   The page staff send customers to. Unlinked from the rest of the site and noindex;
   it exists to hand over the PDF, so the download button is the first thing on it. */
.catalogue-page { padding-block: 2rem 3rem; display: grid; gap: 1.5rem; }
.catalogue-hero {
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 2.5rem;
  align-items: center;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) * 1.5); padding: clamp(1.25rem, 3vw, 2.5rem);
}
.catalogue-eyebrow {
  margin: 0 0 0.35rem; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--color-primary);
}
.catalogue-hero h1 { margin: 0 0 0.5rem; }
.catalogue-lead { font-size: 1.15rem; line-height: 1.5; margin: 0 0 0.75rem; }
.catalogue-status { margin: 0 0 1.25rem; min-height: 1.5em; }
.catalogue-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1rem; }
.btn-lg { padding: 0.85rem 1.4rem; font-size: 1.05rem; min-height: 48px; display: inline-flex; align-items: center; gap: 0.5rem; }
.catalogue-note { font-size: 0.9rem; margin: 0; }

.catalogue-hero.is-preparing [data-catalogue-action] { opacity: 0.55; cursor: progress; }
.catalogue-progress { margin: -0.5rem 0 1.25rem; }
.catalogue-progress[hidden] { display: none; }
.catalogue-progress-label { margin: 0 0 0.4rem; font-weight: 600; }
.catalogue-bar {
  position: relative; height: 8px; overflow: hidden;
  background: var(--color-border); border-radius: 999px;
}
.catalogue-bar span {
  position: absolute; inset: 0 auto 0 0; width: 35%;
  background: var(--color-primary); border-radius: inherit;
  animation: catalogue-sweep 1.1s ease-in-out infinite;
}
.catalogue-hero.is-done .catalogue-bar span { animation: none; left: 0; width: 100%; transition: width 0.35s ease-out; }
@keyframes catalogue-sweep { 0% { left: -35%; } 100% { left: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .catalogue-bar span { animation: none; width: 100%; opacity: 0.5; }
}

.catalogue-mosaic { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; }
.catalogue-mosaic img {
  width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--radius); display: block;
}
.catalogue-mosaic.count-1 { grid-template-columns: 1fr; }
.catalogue-mosaic.count-2, .catalogue-mosaic.count-4 { grid-template-columns: repeat(2, 1fr); }

.catalogue-regions {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); gap: 0 2rem;
}
.catalogue-regions li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: 0.6rem 0; border-bottom: 1px solid var(--color-border);
}
.catalogue-regions li span:last-child { white-space: nowrap; }
.catalogue-contact { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem; }
.catalogue-contact h2 { margin: 0 0 0.25rem; }
.catalogue-contact p { margin: 0; }
.catalogue-contact-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.catalogue-contact-actions .btn { min-height: 44px; display: inline-flex; align-items: center; }

@media (max-width: 760px) {
  .catalogue-hero { grid-template-columns: 1fr; gap: 1.5rem; }
  .catalogue-mosaic { order: -1; }
  .catalogue-actions .btn { flex: 1 1 100%; justify-content: center; }
}
