/*
 * The agency's public website.
 *
 * Tokens are MASTER.md's, not this file's — the palette is not the public site's to choose, and
 * the agency's own accent arrives at runtime as --accent. What this file adds is the part MASTER.md
 * leaves to the surface: "the same structural discipline but breathes more — larger imagery, more
 * whitespace".
 *
 * No CDN. FR-227 forbids every third-party request on a public page while the HusmanHagberg review
 * is open, and MASTER.md's own Google Fonts snippet would have been one. The four faces are served
 * from this origin; the two weights the CRM also uses were dropped rather than the budget
 * (SUC-053: under 200 kB before images, and type alone is 96.9 kB of it).
 */

@font-face {
  font-family: 'Geist';
  font-weight: 400;
  font-display: swap;
  src: url('/site-assets/fonts/geist-sans-latin-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Geist';
  font-weight: 500;
  font-display: swap;
  src: url('/site-assets/fonts/geist-sans-latin-500-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Geist Mono';
  font-weight: 500;
  font-display: swap;
  src: url('/site-assets/fonts/geist-mono-latin-500-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Instrument Serif';
  font-weight: 400;
  font-display: swap;
  src: url('/site-assets/fonts/instrument-serif-latin-400-normal.woff2') format('woff2');
}

:root {
  --bg: #ffffff;
  --surface: #f7f8f8;
  --border: #e3e5e8;
  /* A stronger line for form controls. The ordinary border is tuned for a card edge against the
     page; the same value around an empty input sitting on --surface is very nearly invisible, and
     a field that looks like empty space is one nobody fills in. */
  --border-field: #c8cdd4;
  --text: #101317;
  --text-muted: #5c636d;
  --text-subtle: #6b7280;
  --accent: #0e6e63;

  /* The accent when it has to carry text.
     The accent itself is the agency's, injected at runtime, and it is chosen to look right on a
     logo rather than to be legible at 14px on whatever background this theme happens to use. The
     default here measures 5.9:1 on white and 3.19:1 on the dark surface — a real WCAG failure that
     axe found in the dark theme and nobody would have found by eye. color-mix moves it toward the
     page's own text colour by enough to clear 4.5:1, and it does so relative to whatever accent the
     agency supplied rather than to a value hardcoded here.
     Fills, borders and focus rings keep the pure accent: contrast rules for those are 3:1, and the
     brand colour is the point of them. */
  --accent-text: color-mix(in oklab, var(--accent) 82%, #000000);
  /* Not the accent. The accent is the agency's and is injected at runtime, so a refusal drawn in it
     would be pale grey on one site and scarlet on another — an error has to read as an error on
     every agency's website. 4.7:1 on the light background, 5.9:1 on the dark one. */
  --danger: #a3241c;
  --focus-ring: var(--accent);
  --radius: 6px;

  /* The public site breathes: MASTER.md's scale, entered two steps higher than the CRM. */
  --step-0: 16px;
  --step-1: 19px;
  --step-2: 23px;
  --step-3: 28px;
  --step-4: 33px;
  --measure: 68ch;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg: #0b0c0e;
    --surface: #131519;
    --border: #26292f;
    --border-field: #3d434c;
    --text: #f2f4f6;
    --text-muted: #9ba3ae;
    --text-subtle: #828c99;
    --danger: #ff8a7a;
    /* Lightened rather than darkened: the same adjustment, in the direction this theme needs. */
    --accent-text: color-mix(in oklab, var(--accent) 55%, #ffffff);
  }
}

:root[data-theme='dark'] {
  --bg: #0b0c0e;
  --surface: #131519;
  --border: #26292f;
  --border-field: #3d434c;
  --text: #f2f4f6;
  --text-muted: #9ba3ae;
  --text-subtle: #828c99;
  --danger: #ff8a7a;
  /* Lightened rather than darkened: the same adjustment, in the direction this theme needs. */
  --accent-text: color-mix(in oklab, var(--accent) 55%, #ffffff);
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: 'Geist', system-ui, sans-serif;
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1, h2, .display {
  font-family: 'Instrument Serif', Georgia, serif;
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin: 0;
}

h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-2); }

a { color: var(--accent-text); }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.shell { max-width: 1180px; margin: 0 auto; padding: 0 20px; }

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 24px 0;
  border-bottom: 1px solid var(--border);
}
.masthead__name {
  font-family: 'Instrument Serif', Georgia, serif;
  font-size: var(--step-2);
  text-decoration: none;
  color: var(--text);
  /* The header keeps its height whether the logo loads, fails, or was never set (FR-225).
     Declaring width and height on the image itself would distort every agency's logo but one, so
     the reserved space lives on the link that holds it. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.masthead__logo { max-height: 44px; width: auto; display: block; }

/* spec 009c. The right-hand side of the masthead now holds two things rather than one, and they
   wrap together on a phone instead of the offices link being pushed under the agency's name. */
.masthead__right {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 20px;
  justify-content: flex-end;
}

/* A 44px-tall target like the language links beside it (WCAG 2.5.5, F143), at the body size
   rather than the serif: this is navigation, not a heading. */
.masthead__link {
  font-size: 14px;
  text-decoration: none;
  color: var(--text-muted);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.masthead__link:hover { color: var(--text); text-decoration: underline; }

.languages { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; font-size: 14px; }
/* Spec 129: six languages do not fit beside an agency's name at 375 px, so the row wraps rather than
   the 44 px targets shrinking (F143 holds). */
/* F143. Each link is a 44 × 44 touch target (WCAG 2.5.5); the letters stay 14px and the gap
   shrinks, because the target now carries the spacing the gap used to. Shared by the agency site
   and the portal, which is why it is fixed here once. */
.languages a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}
.languages a[aria-current='true'] { color: var(--text); font-weight: 500; }

/*
 * Money. MASTER.md: tabular figures are not a preference — a column of prices that does not align
 * vertically is a defect.
 */
.money { font-family: 'Geist Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; }

/*
 * The signature.
 *
 * Every property portal in Spain makes the asking price the hero. D-121 makes the *total* the
 * legally required disclosure, and a buyer comparing two listings anywhere else is comparing the
 * wrong number. So the total is set at the largest step and the asking price sits under it,
 * smaller — the one place this site deliberately spends its boldness.
 */
.cost { border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; background: var(--surface); }
.cost__total { font-weight: 500; color: var(--text); }
.cost__label { font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
.cost__lines { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 14px; }
.cost__lines th { text-align: left; font-weight: 400; color: var(--text-muted); padding: 6px 0; }
.cost__lines td { text-align: right; padding: 6px 0; }
.cost__basis { display: block; font-size: 12px; color: var(--text-subtle); }
.cost__estimate { margin-top: 12px; font-size: 13px; color: var(--text-subtle); }
.cost__unavailable { color: var(--text-muted); font-size: 14px; }

.grid { display: grid; gap: 28px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); margin: 32px 0; }
.card { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--bg); }
.card img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; display: block; background: var(--surface); }
.card__body { padding: 16px; }
.card__price { font-size: var(--step-1); }

/* Spec 112, D-285. A reserved listing stays on the site until completion, and says so in words —
   never colour alone. Neutral, not a warning: being reserved is the good outcome. */
.reserved-marker {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 8px;
  border: 1px solid var(--border-field);
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  vertical-align: middle;
}
.card__facts { color: var(--text-muted); font-size: 14px; margin: 4px 0 0; }
.card a { text-decoration: none; color: inherit; }
.card a:hover .card__ref { text-decoration: underline; }

.empty { border: 1px dashed var(--border); border-radius: var(--radius); padding: 48px 24px; text-align: center; color: var(--text-muted); }

.notice { border-left: 3px solid var(--accent); padding: 8px 0 8px 16px; margin: 24px 0; color: var(--text-muted); font-size: 14px; }

.footer-text { border-top: 1px solid var(--border); margin-top: 48px; padding: 24px 0 64px; color: var(--text-muted); font-size: 14px; white-space: pre-line; }

.attribution { font-size: 12px; color: var(--text-subtle); }

@media (prefers-reduced-motion: no-preference) {
  a, .card { transition: color 120ms cubic-bezier(0.4, 0, 0.2, 1); }
}

/* Filters. Quiet by design: the cards and the cost figure carry the page, and a filter bar that
   competes with them turns a listing into a database front end. */
.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px 16px;
  margin: 24px 0 8px;
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.filters__field { display: flex; flex-direction: column; gap: 6px; min-width: 0; flex: 1 1 130px; }
.filters label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.filters input,
.filters select {
  font: inherit;
  font-size: 15px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-field);
  border-radius: 6px;
  padding: 8px 10px;
  min-width: 0;
  width: 100%;
  /* An explicit height on both, because a select derives its own from the option font and ends up
     a couple of pixels taller than the inputs beside it — enough to push its label out of line
     with the rest of the row, which is the sort of thing that reads as carelessness. */
  height: 40px;
}
.filters input:focus-visible,
.filters select:focus-visible,
.chip:focus-visible,
.pager a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.filters button {
  font: inherit;
  font-weight: 500;
  height: 40px;
  color: var(--bg);
  background: var(--text);
  border: 1px solid var(--text);
  border-radius: 6px;
  padding: 0 20px;
  cursor: pointer;
  flex: 0 0 auto;
}
.filters button:hover { background: var(--accent); border-color: var(--accent); }

.chips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 16px 0 0; }
.chips__count { font-size: 14px; color: var(--text-muted); margin-right: 4px; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text);
  text-decoration: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 12px;
  background: var(--bg);
}
.chip:hover { border-color: var(--accent); color: var(--accent-text); }
.chip__x { color: var(--text-subtle); }
.chips__clear { font-size: 13px; color: var(--text-muted); }

.notice--refused { border-left-color: var(--danger); color: var(--text); }

/* The describe box (spec 036). The one place on the listing page that speaks in a sentence, so its
   prompt is set in the display face; everything under it is the filters' own primitives. */
.describe { margin: 24px 0 0; max-width: 860px; }
.describe__label {
  display: block;
  font-family: 'Instrument Serif', Georgia, serif;
  font-size: var(--step-3);
  line-height: 1.15;
  margin: 0 0 12px;
}
.describe__row { display: flex; gap: 10px; flex-wrap: wrap; }
.describe__row input {
  flex: 1 1 320px;
  min-width: 0;
  font: inherit;
  font-size: var(--step-1);
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-field);
  border-radius: var(--radius);
  padding: 0 14px;
  height: 52px;
}
.describe__row input::placeholder { color: var(--text-subtle); }
.describe__send {
  font: inherit;
  font-weight: 500;
  height: 52px;
  padding: 0 24px;
  /* Ink on paper, like .filters button and .enquiry__send: the site's one primary-button pair, and
     the one that holds 4.5:1 in both themes. Accent-filled failed it in the dark theme (3.19:1). */
  color: var(--bg);
  background: var(--text);
  border: 1px solid var(--text);
  border-radius: var(--radius);
  cursor: pointer;
}
.describe__send:hover { background: var(--accent); border-color: var(--accent); }
.describe__send[aria-busy='true'] { opacity: 0.7; cursor: progress; }
.describe__row input:focus-visible,
.describe__send:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.describe__hint { margin: 8px 0 0; font-size: 13px; color: var(--text-muted); }

/* A dashed edge says what .empty's dashed edge says: this is not a filter. It narrows nothing; it
   decides the similar section. */
.chip--about { border-style: dashed; }
.chips__source { margin: 16px 0 0; font-size: 13px; color: var(--text-muted); }

.similar { margin: 48px 0 64px; padding-top: 32px; border-top: 1px solid var(--border); }
.similar__title { font-family: 'Instrument Serif', Georgia, serif; font-weight: 400; font-size: var(--step-3); margin: 0; }
.similar__lead { margin: 8px 0 0; color: var(--text-muted); max-width: var(--measure); }
.similar .grid { margin-top: 24px; }
.card__why { margin: 0 0 6px; font-size: 13px; color: var(--accent-text); }

.pager { display: flex; align-items: center; gap: 20px; margin: 8px 0 64px; font-size: 15px; }
.pager__where { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.pager__spent { color: var(--text-subtle); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Page furniture. Everything here was an inline style until the filters landed; centralised because
   .claude/rules/frontend.md says so, and because a margin repeated in three views is a margin that
   drifts in two of them. */
.page-title { margin: 48px 0 8px; }

.empty__headline { margin: 0 0 8px; }
.empty__aside { margin: 0; font-size: 14px; }
.empty__action { margin: 0; }

.refusal { padding: 96px 0; max-width: var(--measure); }
.refusal__body { color: var(--text-muted); margin-top: 16px; }

.property { margin: 48px 0; }
.property__header { max-width: var(--measure); }
.property__facts { color: var(--text-muted); margin: 8px 0 0; font-size: var(--step-1); }
.property__reference { color: var(--text-subtle); margin: 8px 0 0; font-size: 13px; }
.property__description { max-width: var(--measure); white-space: pre-line; }

.gallery { margin-top: 32px; }
/* The frame is declared here rather than taken from the file (FR-262). A photograph is whatever
   shape the agency uploaded, and a page whose height depends on that shifts as each image arrives
   — and looks broken when one of them is square. object-fit crops to the frame instead. */
.gallery__photo {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius);
  background: var(--surface);
}

.cost__heading { font-family: inherit; }
/* D-244. The total closes the table as its sum: a rule above it, the text colour rather than the
   muted one, and no more size or weight than a line needs to read as the answer. */
.cost__sum th, .cost__sum td { border-top: 1px solid var(--border); padding-top: 10px; color: var(--text); }
.cost__note { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border); }
.cost__noteTitle { font-family: inherit; font-size: 14px; font-weight: 500; margin: 0 0 6px; }
.cost__noteBody { font-size: 14px; color: var(--text-muted); white-space: pre-line; margin: 0; }
.cost__unavailable { margin-top: 12px; }

.gone { margin: 64px 0; }
.gone__title { font-size: var(--step-2); margin: 0 0 12px; }

.position { margin-top: 40px; }
.position__map {
  display: block;
  width: 100%;
  height: auto;
  max-width: 640px;
  margin-top: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.position__words { margin-top: 12px; font-size: var(--step-1); color: var(--text-muted); }

/* One photograph fills the measure instead of sitting in a third of a grid it does not need. */
.gallery--single { grid-template-columns: minmax(0, 1fr); max-width: 900px; }

/* The asking price, and since D-244 the one figure the panel leads with — with or without a
   breakdown under it, so the page looks the same whether the region's rates are set or not. */
.cost__askingLabel { margin: 16px 0 0; }
.cost__asking { font-family: 'Instrument Serif', Georgia, serif; font-size: var(--step-4); line-height: 1.1; margin: 0; }

/* -----------------------------------------------------------------------------------------------
 * The enquiry form (US2).
 *
 * A third bounded panel, built from the same parts as .cost and .filters: --surface, one --border,
 * one --radius. A fourth visual idiom on a page that already has three would read as a bolt-on, and
 * this is the one thing on the page a visitor is asked to act in — it has to look like it belongs
 * to the site rather than to whoever wrote the form.
 *
 * It borrows the CRM's field metrics deliberately (40px controls, --border-field, the uppercase
 * micro-label). MASTER.md gives the public surface more room, not different controls: a text input
 * that behaves differently here than in the tool is two answers to one question.
 *
 * Nothing here uses the display face. D-121 makes the total the largest thing on the page, and a
 * serif heading over the form would take that back.
 * -------------------------------------------------------------------------------------------- */
.enquiry {
  margin: 56px 0 64px;
  padding: 28px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  max-width: 720px;
}
.enquiry__lead { margin: 12px 0 24px; color: var(--text-muted); font-size: var(--step-1); }

.enquiry__form { display: flex; flex-direction: column; gap: 20px; }

/* Two fields side by side above 560px, stacked below it. minmax(0, 1fr) rather than 1fr, so a long
   validation message wraps instead of widening its column and pushing the other one off. */
.enquiry__row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; }
@media (min-width: 560px) {
  .enquiry__row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

.enquiry__field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.enquiry__field label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.enquiry__field input,
.enquiry__field textarea {
  font: inherit;
  font-size: 15px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-field);
  border-radius: 6px;
  padding: 8px 10px;
  width: 100%;
  min-width: 0;
}
.enquiry__field input { height: 40px; }
.enquiry__field textarea { resize: vertical; min-height: 120px; line-height: 1.5; }
.enquiry__field input:focus-visible,
.enquiry__field textarea:focus-visible,
.enquiry__privacy summary:focus-visible,
.enquiry__send:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The signature: email and telephone are one group under one hint, because FR-266 asks for either
   and two independent inputs each read as optional on their own. The fieldset carries the meaning;
   its default border and padding would only draw a second box inside the panel. */
.enquiry__pair { border: 0; padding: 0; margin: 0; min-width: 0; }
.enquiry__pairLegend {
  padding: 0;
  margin-bottom: 10px;
  font-size: 13px;
  color: var(--text-subtle);
}

.enquiry__problem { margin: 0; font-size: 13px; color: var(--danger); }

/* Off-screen rather than display:none or hidden: a form-filling script skips what a browser will
   not render and fills what it finds in the markup, so the trap has to be in the layout and out of
   sight. aria-hidden and tabindex="-1" on the markup keep it out of the accessibility tree and the
   tab order, so it is invisible to a person by every route a person has. */
.enquiry__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.enquiry__privacy { border-top: 1px solid var(--border); padding-top: 16px; }
.enquiry__privacy summary {
  cursor: pointer;
  font-size: 14px;
  color: var(--accent-text);
}
.enquiry__privacyTitle {
  margin: 16px 0 8px;
  font-family: inherit;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
/* pre-line, exactly as the agency footer text is: the notice is authored prose with paragraph
   breaks in it, and Razor escapes it on the way out. Line breaks get meaning; nothing else does. */
.enquiry__privacyBody {
  white-space: pre-line;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-muted);
}

.enquiry__send {
  font: inherit;
  font-weight: 500;
  height: 44px;
  color: var(--bg);
  background: var(--text);
  border: 1px solid var(--text);
  border-radius: 6px;
  padding: 0 24px;
  cursor: pointer;
  align-self: flex-start;
}
.enquiry__send:hover { background: var(--accent); border-color: var(--accent); }
.enquiry__send[disabled] { opacity: 0.6; cursor: progress; }

.enquiry__thanks { margin: 12px 0 0; }
.enquiry__thanksTitle { margin: 0 0 6px; color: var(--text); font-size: var(--step-1); }
.enquiry__thanksBody { margin: 0; }

/* ---------------------------------------------------------------------------
 * The shared portal (spec 034)
 *
 * Two classes, and only two. Everything else the portal renders — .shell, .grid, .card, .empty,
 * .filters, .pager, .enquiry, .refusal, .money — is this file's already, unchanged. MASTER.md gives
 * the public surfaces one structural discipline, and a second stylesheet for the portal would be a
 * second design language maintained by nobody.
 *
 * What differs is the LAYOUT, not the CSS: the portal's masthead carries no logo and injects no
 * --accent, because it belongs to no agency. The token default is ours.
 * --------------------------------------------------------------------------- */

/* A card whose listing has no photograph. F114: the publish command refuses one, but the database
   does not, so a seed or a restore can produce a published listing with none — and in a portal that
   sits next to a competitor's card with a picture.
   The same aspect-ratio as .card img, so a grid of mixed cards does not stagger. */
.card__noPhoto {
  aspect-ratio: 4 / 3;
  display: grid;
  place-items: center;
  background: var(--surface);
  color: var(--text-subtle);
  font-size: 14px;
  padding: 16px;
  text-align: center;
}

/* ---------------------------------------------------------------------------
 * Offices (spec 009c)
 *
 * The overview is a LIST, not a card grid, and that is the one decision on these two pages worth
 * writing down. An agency has two offices or six; three cards across for two of them reads as a
 * template with the content poured in, and the facts an office carries — a name, two lines of
 * address, a telephone number — are a row, not a tile. In a row the numbers also land in a column
 * where they align, which is the only reason this page asks for tabular figures.
 *
 * On the office page the ADDRESS is the hero. The listing page already spends this site's boldness
 * on the total cost (D-121) and nothing here may compete with it, but an office page still has to
 * answer its own question at a glance, and an estate agency's office is a place somebody walks
 * into. So the address is set at display size under the name instead of being shrunk into a
 * label-and-value row beside the opening hours.
 *
 * Every value is a token from the block at the top of this file. MASTER.md's checklist: one
 * hardcoded hex in a component is the difference between a rebrand and a repaint.
 * --------------------------------------------------------------------------- */

.offices { list-style: none; margin: 32px 0 0; padding: 0; border-top: 1px solid var(--border); }

.offices__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 24px;
  padding: 24px 0;
  border-bottom: 1px solid var(--border);
}

.offices__name { font-size: var(--step-2); }
.offices__name a { color: inherit; text-decoration: none; }
.offices__name a:hover { text-decoration: underline; }

.offices__address, .office__address { font-style: normal; }

.offices__address { margin-top: 4px; color: var(--text-muted); font-size: 14px; }
.offices__line, .office__line { display: block; }

.offices__phone { margin: 0; color: var(--text-muted); font-size: 14px; }

.office__address {
  margin: 8px 0 0;
  font-size: var(--step-1);
  line-height: 1.35;
  max-width: var(--measure);
}

.office__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 24px 48px;
  margin: 32px 0 0;
  padding: 24px 0 0;
  border-top: 1px solid var(--border);
}

.office__fact dt { color: var(--text-muted); font-size: 14px; }
.office__fact dd { margin: 4px 0 0; }

/* The agency's own sentence about when they are open, with their line breaks and nothing else. */
.office__hours { white-space: pre-line; }

.office-people { margin: 48px 0 0; }
.office-people__list {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 32px;
}

.office-listings { margin: 48px 0 0; }
.office-listings__all { margin: 0 0 32px; }

/* ---- 113: film, walkthrough and the subject chips -----------------------------------------
   The editorial half of MASTER.md's split: a serif heading like every other section's, 16:9
   frames at the measure, and the chips as pills carrying their own count. Nothing new is invented
   here — the frame reuses --radius and --border, and the pill reuses the accent the agency already
   set. */

.media { margin-top: 40px; }
.media__title { margin: 0 0 16px; }

/* 16:9 rather than the gallery's 4:3: a film is shot wide, and a frame that cropped it would crop
   the only thing on the page nobody can scroll. */
.media__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  margin-bottom: 16px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
}
.media__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.media__external { margin: 0 0 12px; }
.media__hint { margin-left: 8px; font-size: var(--step-1); color: var(--text-muted); }

/* Above the gallery, hidden until PublicScripts.GalleryFilter unhides them. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 32px;
}
.chips[hidden] { display: none; }

.chip {
  font: inherit;
  font-size: 14px;
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  height: 34px;
  padding: 0 14px;
  cursor: pointer;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  /* A pill, which is the one place on this page that is not --radius: a filter you press is not a
     panel you read, and the shape is how a reader tells them apart without a label saying so. */
  border-radius: 999px;
  transition: background-color 120ms cubic-bezier(0.4, 0, 0.2, 1),
              border-color 120ms cubic-bezier(0.4, 0, 0.2, 1);
}
.chip:hover { border-color: var(--text-muted); background: var(--surface); }
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Never colour alone (MASTER.md): the pressed chip is darker AND aria-pressed, which is what a
   screen reader announces and what the test asserts. */
.chip[aria-pressed='true'] {
  color: var(--bg);
  background: var(--text);
  border-color: var(--text);
}

.chip__count { font-size: 13px; opacity: 0.7; }

@media (prefers-reduced-motion: reduce) {
  .chip { transition: none; }
}

/* Spec 112c — who to ring about a listing. The cost card's frame, so the two read as siblings. */
.contact {
  margin: 32px 0 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
  background: var(--surface);
}
.contact__agent { margin: 8px 0 0; font-size: var(--step-1); }
.contact__agency { margin: 4px 0 0; color: var(--text-muted); }
.contact__facts { margin-top: 16px; padding-top: 16px; }

/* -----------------------------------------------------------------------------------------------
 * The property page in HusmanHagberg's structure (spec 129, FR-2940…FR-2948).
 *
 * Patrik's brief decompiled into this file's own tokens rather than his hex values: his card
 * background, border and text colours are within a step of --surface, --border and --text already,
 * and the one colour that is HusmanHagberg's — the forest green — is HH's --accent, set in their
 * branding. Every other agency gets the same structure in its own colour (Johan O, 2026-11-06).
 * Inter is not used: MASTER.md bans it, and Geist is the system's clean sans.
 *
 * The photographs are the hero and carry the page; everything else is quiet. The accent reaches the
 * actions, the price and the badge, nothing more.
 * --------------------------------------------------------------------------------------------- */

.property { --radius-card: 12px; }

.hero-grid {
  position: relative;
  display: grid;
  gap: 8px;
  grid-template-columns: 2fr 1fr 1fr;
  grid-template-rows: repeat(2, minmax(0, 1fr));
  height: clamp(320px, 42vw, 520px);
  margin: 0 0 32px;
}
.hero-grid__photo {
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: cover;
  display: block;
  background: var(--surface);
  cursor: zoom-in;
}
.hero-grid__photo--main { grid-row: 1 / span 2; border-radius: var(--radius-card) 0 0 var(--radius-card); }
.hero-grid--n5 .hero-grid__photo:nth-child(3) { border-top-right-radius: var(--radius-card); }
.hero-grid--n5 .hero-grid__photo:nth-child(5) { border-bottom-right-radius: var(--radius-card); }

/* Fewer than five: the grid closes up rather than leaving a tile empty (SC-4147). */
.hero-grid--n1 { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }
.hero-grid--n1 .hero-grid__photo--main { grid-row: auto; border-radius: var(--radius-card); }
.hero-grid--n2 { grid-template-columns: 2fr 1fr; }
.hero-grid--n2 .hero-grid__photo:nth-child(2) { grid-row: 1 / span 2; border-radius: 0 var(--radius-card) var(--radius-card) 0; }
.hero-grid--n3 { grid-template-columns: 2fr 1fr; }
.hero-grid--n3 .hero-grid__photo:nth-child(2) { border-top-right-radius: var(--radius-card); }
.hero-grid--n3 .hero-grid__photo:nth-child(3) { border-bottom-right-radius: var(--radius-card); }
.hero-grid--n4 .hero-grid__photo:nth-child(3) { border-top-right-radius: var(--radius-card); }
.hero-grid--n4 .hero-grid__photo:nth-child(4) { grid-column: 2 / span 2; border-bottom-right-radius: var(--radius-card); }

.hero-grid__count {
  position: absolute;
  right: 16px;
  bottom: 16px;
  /* 44 px tall, the site's touch-target floor (F143). */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 999px;
  background: rgb(0 0 0 / 0.62);
  color: #ffffff;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
.hero-grid__count:hover { background: rgb(0 0 0 / 0.78); }
.hero-grid__count:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; }

.stats {
  max-width: none;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px 32px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--border);
}
.stats__lead { min-width: 0; }
.stats__title { margin: 12px 0 0; }
.stats__price {
  margin: 0;
  font-family: 'Geist', system-ui, sans-serif;
  font-weight: 500;
  font-size: var(--step-4);
  line-height: 1.1;
  color: var(--accent-text);
}

.badge {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  color: var(--accent-text);
  background: color-mix(in oklab, var(--accent) 12%, var(--bg));
}
/* Reserved is a warning, not a brand moment: it reads the same on every agency's site. */
.badge--reserved { color: var(--text); background: var(--surface); border: 1px solid var(--border-field); }

.jumpbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 10;
  display: flex;
  justify-content: center;
  gap: 28px;
  padding: 0 20px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.jumpbar[hidden] { display: none; }
.jumpbar a { display: inline-flex; align-items: center; min-height: 44px; color: var(--text-muted); text-decoration: none; }
.jumpbar a:hover { color: var(--text); text-decoration: underline; }
.jumpbar a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.property__layout {
  display: grid;
  gap: 32px;
  grid-template-columns: minmax(0, 68fr) minmax(0, 32fr);
  margin-top: 32px;
  align-items: start;
}
.property__aside {
  position: sticky;
  top: 96px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.property__aside .cost,
.overview,
.agent-card { border-radius: var(--radius-card); }
.property__aside .contact { margin: 0; }
.property__sectionTitle { font-size: var(--step-2); margin: 40px 0 12px; }

.overview {
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 24px;
}
.overview__title { font-size: var(--step-1); margin: 0 0 16px; font-family: inherit; font-weight: 500; }
.overview__facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px 24px;
  margin: 0;
}
.overview__fact dt { color: var(--text-muted); font-size: 13px; }
.overview__fact dd { margin: 4px 0 0; font-weight: 500; font-variant-numeric: tabular-nums; }

.agent-card__person { display: flex; align-items: center; gap: 14px; }
.agent-card__avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: color-mix(in oklab, var(--accent) 14%, var(--bg));
  color: var(--accent-text);
  font-weight: 500;
}
.agent-card__title { margin: 0 0 14px; }
.agent-card__name { font-family: inherit; font-size: var(--step-1); font-weight: 500; margin: 0; }
.agent-card__role { margin: 2px 0 0; color: var(--text-muted); font-size: 14px; }
.agent-card .button { margin-top: 16px; }
.agent-card .button + .button { margin-top: 8px; }

.button {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 20px;
  border-radius: var(--radius);
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #ffffff;
  font-weight: 500;
  text-decoration: none;
}
.button:hover { background: color-mix(in oklab, var(--accent) 86%, #000000); }
.button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.button--outline { background: transparent; color: var(--accent-text); }
.button--outline:hover { background: color-mix(in oklab, var(--accent) 8%, var(--bg)); }

.viewer {
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: rgb(10 11 12 / 0.96);
  color: #ffffff;
}
.viewer::backdrop { background: rgb(0 0 0 / 0.6); }
.viewer[open] { display: grid; place-items: center; }
.viewer__photo { max-width: 92vw; max-height: 82vh; object-fit: contain; }
.viewer__position { position: absolute; top: 16px; left: 20px; margin: 0; font-variant-numeric: tabular-nums; }
.viewer__button {
  position: absolute;
  font: inherit;
  color: #ffffff;
  background: rgb(255 255 255 / 0.12);
  border: 1px solid rgb(255 255 255 / 0.3);
  border-radius: 999px;
  min-height: 44px;
  padding: 0 18px;
  cursor: pointer;
}
.viewer__button:hover { background: rgb(255 255 255 / 0.22); }
.viewer__button:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; }
.viewer__previous { left: 20px; top: 50%; }
.viewer__next { right: 20px; top: 50%; }
.viewer__close { top: 12px; right: 20px; }

/* Below the two-column width the sidebar dissolves into the page, and the cost breakdown moves up to
   sit right after the overview card (FR-2946): the two questions a buyer on a phone asks first. */
/* Spec 137 — the geographic pages. One new structural idea: the column of counts. A buyer's and a
   crawler's question on a place page is "where is the stock", and the answer is a number per place,
   so the numbers are what the eye lands on — right-aligned, tabular, on hairlines. Deliberately not
   cards: a card per town would make twelve equal objects out of a list whose whole content is that
   the quantities differ. Everything else here is the site's existing scale and palette. */
.trail { margin: 32px 0 0; font-size: 14px; color: var(--text-muted); }
.trail__steps { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
.trail a { color: inherit; }
.trail li + li::before { content: "›"; padding: 0 8px; color: var(--border-field); }

.place { margin: 8px 0 24px; max-width: var(--measure); }
.place__name { margin: 8px 0 4px; font-size: var(--step-4); line-height: 1.15; }
.place__count { margin: 0; color: var(--text-muted); }

.prose { max-width: var(--measure); margin: 0 0 32px; }
.prose p { margin: 0 0 16px; font-size: var(--step-1); line-height: 1.65; }
.prose p:last-child { margin-bottom: 0; }

.within { margin: 0 0 32px; }
.within__title { margin: 0 0 8px; font-size: var(--step-1); }
.within__list {
  list-style: none;
  margin: 0;
  padding: 0;
  columns: 2;
  column-gap: 48px;
  max-width: 760px;
}
.within__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
  break-inside: avoid;
}
.within__count {
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  font-size: 14px;
}

@media (max-width: 1023px) {
  .property__layout { display: flex; flex-direction: column; align-items: stretch; gap: 24px; }
  .property__main, .property__aside { display: contents; }
  .property__main > *, .property__aside > * { order: 4; margin-top: 0; }
  .property__main > .overview { order: 1; }
  .property__aside > .cost { order: 2; }
  .property__aside > .contact { order: 3; }
}
@media (max-width: 767px) {
  .within__list { columns: 1; }
  .place__name { font-size: var(--step-3); }
  .hero-grid { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); height: auto; }
  .hero-grid__photo { display: none; }
  .hero-grid__photo--main { display: block; grid-row: auto; aspect-ratio: 4 / 3; border-radius: var(--radius-card); }
  .overview__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stats__price { font-size: var(--step-3); }
  .jumpbar { gap: 16px; overflow-x: auto; justify-content: flex-start; }
}

/* ---------------------------------------------------------------------------
   Spec 136 — the office hierarchy and agent profiles.
   The place names carry the hub; the agency's colour is spent on one thing,
   the languages line, because for an international buyer it is the fact that
   decides whether to ring.
   --------------------------------------------------------------------------- */
.breadcrumb { margin: 0 0 8px; font-size: 14px; color: var(--text-muted); }
.breadcrumb ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 0; }
.breadcrumb li { line-height: 44px; }
.breadcrumb li + li::before { content: "/"; margin: 0 8px; color: var(--border-field); }
.breadcrumb a { color: inherit; display: inline-block; min-height: 44px; }
.breadcrumb [aria-current] { color: var(--text); }

.coast-group { margin: 40px 0 0; }
.coast-group__name { font-size: var(--step-3); margin: 0 0 8px; }
.coast-group__name a { color: inherit; text-decoration: none; }
.coast-group__name a:hover { text-decoration: underline; }
.coast-group .offices { margin-top: 0; }

.town-offices { display: grid; gap: 32px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.office__name { font-size: var(--step-1); margin: 0 0 8px; }

.people { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 16px 32px;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.person { display: flex; gap: 14px; align-items: flex-start; }
.person__text { min-width: 0; }
.person__name { display: inline-block; min-height: 44px; line-height: 44px; color: var(--text); }
a.person__name { text-decoration: none; }
a.person__name:hover { text-decoration: underline; }
.person__speaks { margin: 2px 0 0; font-size: 14px; color: var(--text-muted); }
.person__speaks--match { color: var(--accent-text); }

.profile { margin: 0 0 16px; }
.profile__head { display: flex; gap: 20px; align-items: center; margin: 0 0 24px; }
.profile__avatar { width: 72px; height: 72px; font-size: 24px; flex: none; }
.profile__name { margin: 0; }
.profile__title { margin: 4px 0 0; color: var(--text-muted); }
.profile__about { margin: 32px 0 0; max-width: var(--measure); }
.profile__about-text { white-space: pre-line; margin: 8px 0 0; }

.site-footer { margin-top: 48px; }
.site-footer .footer-text { margin-top: 0; }
.footer-legal { border-top: 1px solid var(--border); margin: 0; padding: 16px 0 64px; color: var(--text-muted); font-size: 14px; }
.footer-text + .footer-legal { border-top: 0; padding-top: 0; }

@media (max-width: 600px) {
  .profile__head { align-items: flex-start; }
  .profile__avatar { width: 56px; height: 56px; font-size: 20px; }
}

/* ---------------------------------------------------------------------------------------------
 * Spec 138: articles and calculators. The article reads like the profile's about text (one column
 * at --measure); the calculator is the enquiry panel's sibling, and its one loud element is the main
 * figure in Instrument Serif, the same treatment as the property page's asking price.
 * ------------------------------------------------------------------------------------------- */
.article { max-width: var(--measure); margin: 0 0 48px; }
.article__meta { display: flex; flex-wrap: wrap; gap: 4px 20px; margin: 0 0 32px; color: var(--text-muted); font-size: 14px; }
.article__summary { font-size: var(--step-1); color: var(--text-muted); margin: 0 0 32px; }
.article__body { line-height: 1.65; }
.article__body h2 { font-size: var(--step-3); margin: 40px 0 12px; }
.article__body h3 { font-family: inherit; font-size: var(--step-1); font-weight: 500; margin: 32px 0 8px; }
.article__body p, .article__body ul { margin: 0 0 18px; }
.article__body a { color: var(--accent-text); }
.article__body .listings, .article__body .calculator { margin: 32px 0; }

.article-list { list-style: none; margin: 24px 0 0; padding: 0; max-width: var(--measure); }
.article-list__item { padding: 24px 0; border-top: 1px solid var(--border); }
.article-list__title { margin: 0 0 6px; font-size: var(--step-2); }
.article-list__title a { color: var(--text); text-decoration: none; }
.article-list__title a:hover { text-decoration: underline; }
.article-list__summary { margin: 0 0 6px; color: var(--text-muted); }
.article-list__date { margin: 0; font-size: 13px; color: var(--text-subtle); }
.article-pager { display: flex; gap: 24px; margin: 24px 0 64px; }

.tool-list { list-style: none; margin: 24px 0 64px; padding: 0; display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.tool-list__item { border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
.tool-list__item a { color: var(--text); font-size: var(--step-1); }
.tool-list__lead { margin: 8px 0 0; font-size: 14px; color: var(--text-muted); }

.calculator { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); padding: 28px; margin: 32px 0 56px; }
.calculator__title { margin: 0 0 20px; }
.calculator__grid { display: grid; gap: 28px; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 760px) {
  .calculator__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
.calculator__fields { display: flex; flex-direction: column; gap: 16px; }
.calculator__field select {
  font: inherit; font-size: 15px; color: var(--text); background: var(--bg);
  border: 1px solid var(--border-field); border-radius: 6px; padding: 8px 10px; height: 40px; width: 100%;
}
.calculator__field input[aria-invalid='true'] { border-color: var(--danger); }
.calculator__field select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.calculator__result { min-width: 0; }
.calculator__main { font-family: 'Instrument Serif', Georgia, serif; font-size: var(--step-4); line-height: 1.1; margin: 0 0 8px; }
.calculator__empty { color: var(--text-muted); margin: 0; }
.calculator__problem { color: var(--danger); margin: 0; }
.calculator__send { border-top: 1px solid var(--border); margin: 28px 0 0; padding: 24px 0 0; }
.calculator__sendFields { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.calculator__sendTitle { padding: 0; font-size: var(--step-1); font-weight: 500; }
.calculator__sendLead { margin: -8px 0 0; color: var(--text-muted); font-size: 14px; }
.calculator__send .enquiry__send { align-self: flex-start; }

.calculation-sent { max-width: var(--measure); margin: 0 0 64px; }
.calculation-sent .cost__lines { max-width: 480px; }
.article__body .calculator__title { margin-top: 0; }
/* The calculator's table looks like the cost panel's but is not it: a page carries both, and the
   cost breakdown is what assistive-technology checks count (public-site-a11y). */
.calculator__lines { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 14px; }
.calculator__lines th { text-align: left; font-weight: 400; color: var(--text-muted); padding: 6px 0; }
.calculator__lines td { text-align: right; padding: 6px 0; }
.article-shelf { margin: 48px 0; }
