/* ==========================================================================
   Previa — the stylesheet Tailwind compiles.

   Build:  npm run build   →   ../web/static/css/app.css

   Order is the cascade. It is the same order the five <link> tags were in
   before this file existed — fonts, tokens, base, components, layout, pages —
   because that order is what the approved design was verified in, and a
   stylesheet that loads in a different order is a different stylesheet.

   The hand-written sheets are imported plainly rather than wrapped in
   @layer components, and the reason is worth stating so nobody "tidies" it:
   Tailwind tree-shakes anything inside @layer against the content scan, and
   this codebase builds class names in previa.js and in Go handlers, which no
   scanner can see. Unlayered CSS is passed through untouched. Utility classes
   in new markup are still purged normally, which is where purging pays.

   @tailwind utilities comes last so a utility beats a component rule, which is
   the one thing utilities have to be able to do.
   ========================================================================== */

/* Previa self-hosted webfonts.
   Manrope variable (SIL OFL 1.1). One file per unicode subset covers every
   weight the site asks for (400–800), so the homepage no longer downloads
   five separate weight files × two subsets (~126 KB). Latin alone is ~24 KB.

   latin-ext (~14 KB) lives in manrope-latin-ext.css — linked on full pages,
   or injected by previa.boot.js on the 2nd load for lean / LeanKeepCSS so
   first-paint Font stays ~24 KB (Pingdom does not honour unicode-range).

   DM Serif Display is no longer fetched: titles use the system serif stack
   already named in --font-display (Iowan / Georgia). That keeps first-paint
   Font near one small woff2 instead of another 24–34 KB.

   font-display: block — lean homepage preloads Manrope and links full
   app.css render-blocking, so first paint already uses the site font. */

@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 200 800;
  font-display: block;
  src: url('/static/fonts/manrope-latin-wght.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   Previa — design tokens

   Generated by design/build-tokens.js. Do not edit this file: the values come
   from `tokens` in design/tailwind.config.js, and this layout and its prose
   from design/css/tokens.template.css. Change a value there and rebuild.

   Single source of truth for colour, type, space, radius, shadow and motion.
   Every component reads from these variables. Never hardcode a raw value in
   a component stylesheet.
   ========================================================================== */

:root {
  /* --- Brand ------------------------------------------------------------ */
  --navy: #0C2D48;
  --navy-hover: #061F33;
  --teal: #2F6F68;
  --teal-hover: #245852;
  --slate: #4A6274;
  /* neutral supporting blue for secondary text/icons */
  --gold: #C39A5B;
  --gold-hover: #A97D3E;
  /* --- Semantic surfaces (light theme) ---------------------------------- */
  --bg: #F6F3EE;
  /* warm off-white page background */
  --bg-alt: #EEF3F4;
  /* cool alternate band */
  --surface: #FFFFFF;
  /* cards, panels, inputs */
  --surface-2: #FBF9F6;
  /* subtle alternate rows */
  --surface-3: #EFEAE3;
  /* pressed / hovered neutral fills */
  --surface-inverse: #0C2D48;
  /* --- Text ------------------------------------------------------------- */
  --heading: #0A2238;
  --text: #172B3A;
  --text-muted: #687784;
  --text-subtle: #87939D;
  --text-inverse: #FFFFFF;
  --text-on-gold: #2A1D06;
  /* --- Lines ------------------------------------------------------------ */
  --border: #DDE3E7;
  --border-strong: #C4CDD4;
  --border-inverse: #294353;
  /* --- Status ----------------------------------------------------------- */
  --success: #2E7D5B;
  /* For green marks sitting on a dark overlay (card pager, map popup):
     --success is tuned for text on white and goes muddy there. */
  --success-on-dark: #4ED08F;
  --success-bg: #E9F4EF;
  --success-border: #A9D3C1;
  --warning: #C77919;
  --warning-bg: #FCF2E5;
  --warning-border: #EFCB98;
  --error: #C43D3D;
  --error-bg: #FBEDED;
  --error-border: #EAB4B4;
  /* The favourite heart, and only it. The client named the value and named it
     for both themes at once — "make #FF0000 (in day and dark theme the same)" —
     so it is declared here once and never redefined under [data-theme='dark'],
     which is the whole point of it not being --error. */
  --fav: #FF0000;
  --info: #2B6CB0;
  --info-bg: #EAF1F9;
  --info-border: #B0C8E3;
  /* --- Links -----------------------------------------------------------
     The blue the client asked for where a piece of text is a link and has to
     announce itself as one without an underline — the address on a listing is
     the first of them. A token of its own rather than a borrowed --info,
     because that one is tuned for notice panels and moves with them. */
  --link: #1F63B8;
  --link-hover: #144C93;
  /* --- Accent tints ----------------------------------------------------- */
  --navy-tint: #E7EDF2;
  --teal-tint: #E6EFEE;
  --teal-border: #A9C8C4;
  --gold-tint: #F8F1E6;
  --gold-border: #E2CDA9;
  /* --- Focus ------------------------------------------------------------ */
  --focus-ring: 0 0 0 3px rgba(12, 45, 72, 0.20);
  --focus-ring-teal: 0 0 0 3px rgba(47, 111, 104, 0.28);
  --focus-ring-gold: 0 0 0 3px rgba(195, 154, 91, 0.32);
  --focus-color: var(--navy);
  /* A field being typed into.

     "And around these fields, when start to type there, then the bold
     borderline will come. Make this borderline green #008000 and thinner — it
     stays green in day and dark mode."

     The colour is the client's, literally. Thinner is the second half of the
     note and the reason there are two tokens: the frame used to be a 1px
     border plus a 3px halo around it, four pixels of colour, and it is a 1px
     border plus a 1px ring now.

     Deliberately declared once, outside both theme blocks, and deliberately
     not --focus-color: the theme's focus colour used to flip navy → gold between
     day and night (the "golden selector"); dark now uses --text. This one may
     not flip with it. Everything else that takes a
     ring — buttons, checkboxes, cards, links — still uses --focus-ring and
     still follows the theme. */
  --field-focus: #008000;
  --field-focus-ring: 0 0 0 1px #008000;
  /* --- Typography ------------------------------------------------------- */
  --font-display: 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --font-sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
               'Helvetica Neue', Arial, sans-serif;
  /* Fluid type scale. Body stays 16px; headings shrink on small screens. */
  --fs-hero: clamp(2.25rem, 1.30rem + 4.2vw, 4rem);
  /* 36 → 64 */
  --fs-h1: clamp(1.875rem, 1.30rem + 2.6vw, 3rem);
  /* 30 → 48 */
  --fs-page-title: clamp(1.3125rem, 0.91rem + 1.82vw, 2.1rem);
  /* 21 → 34 — --fs-h1 less 30% */
  --fs-h2: clamp(1.5rem, 1.15rem + 1.6vw, 2.25rem);
  /* 24 → 36 */
  --fs-h3: clamp(1.25rem, 1.08rem + 0.75vw, 1.625rem);
  /* 20 → 26 */
  --fs-h4: clamp(1.125rem, 1.04rem + 0.38vw, 1.375rem);
  /* 18 → 22 */
  --fs-lg: 1.125rem;
  /* 18 */
  --fs-base: 1rem;
  /* 16 */
  --fs-sm: 0.875rem;
  /* 14 */
  --fs-xs: 0.8125rem;
  /* 13 */
  --fs-2xs: 0.75rem;
  /* 12 — badges, overlines only */
  --lh-tight: 1.15;
  --lh-snug: 1.3;
  --lh-normal: 1.55;
  --lh-relaxed: 1.7;
  --ls-tight: -0.02em;
  --ls-normal: 0;
  --ls-wide: 0.06em;
  /* --- Spacing (8px system) -------------------------------------------- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-7: 32px;
  --sp-8: 40px;
  --sp-9: 48px;
  --sp-10: 64px;
  --sp-11: 80px;
  --sp-12: 96px;
  --sp-13: 120px;
  /*
    Vertical rhythm between blocks, and — now that a coloured block is an inset
    panel rather than a full-bleed band — the padding inside one.

    Cut twice. First on the client's 18 August notes, made about two different
    pages and meaning the same thing: "reduce the line spacing between all the
    blocks in this page" (About) and "reduce the spacing between these menu
    blocks compat together, so move all a bit up" (homepage). Then again on the
    19 August note about the homepage: "then move it all more a bit up, more
    compact together, the content blocks closer the it's title … then look that
    the distance gaps between the blocks would be the same."

    One step is what makes the gaps equal. Every block on the homepage now
    takes exactly this padding above and below, so the distance between any two
    of them is 2 × --section-y whichever pair you measure; the old ladder of
    section, section--lg and the per-section overrides was what made some gaps
    read wider than others.
  */
  --section-y: clamp(24px, 2.6vw, 40px);
  --section-y-lg: clamp(30px, 3.4vw, 52px);
  /* --- Layout -----------------------------------------------------------
     Two nested limits, and they do different jobs.

     --site-shell-max-width is the outer edge of the website itself: past it
     the page stops growing and centres, so a 27" monitor shows Previa at the
     width it was designed for rather than a stretched band of it. It is a
     CSS pixel value on purpose — a browser cannot measure the physical size
     of a display, so a unit like `14in` would be a guess dressed up as a
     measurement.

     --container was an inner reading limit of 1280 and is now 1440, the same
     as the shell.

     The client, 31 August: "And in a narrower view the same that header and
     page content use the max screen width. The header and page content use the
     max screen width till to the max set limit for the screen size what we have
     set there."

     Two limits that disagreed made the header a different width on different
     pages, which is the mismatch he is describing. The listings and admin
     screens run on .container--wide and reach the shell's edge, so their header
     was cut to 1376 on a 1440 window; every ordinary page ran on this token and
     was cut to 1216. Same header, same window, two widths, and neither of them
     "the max screen width till to the max set limit".

     Setting the two equal is the whole fix: .container now fills the shell, so
     --page-width below resolves to the same number on every page, and the limit
     that stops the growth is the one that was always meant to — the shell's
     1440. Past it the page centres exactly as before.

     --container-narrow is untouched at 820. That is the reading measure for
     prose, and prose is the thing that must not get wider.
     -------------------------------------------------------------------- */
  --site-shell-max-width: 1440px;
  --container: 1440px;
  --container-narrow: 820px;
  --gutter: 32px;
  /* The width of a page's content, as one number.

     The header, the page head and the homepage banner are panels now, at the
     client's request on 18 August — "cut the header the same width as the page
     content, and make the below corners round", said about the double-header
     pages, the listings page and the homepage banner in three consecutive
     messages. A panel needs to know where the content it is matching ends, and
     that is what this carries.

     The formula is the footer's, character for character: the content box of
     a .container, which is --container less its two gutters, clamped so the
     panel keeps its margins on a phone. Pages laid out on the wide container
     override it — see body:has(.container--wide) in layout.css — because their
     content really is wider, and a header that stopped short of the results
     grid under it would be answering the note with the wrong number. */
  --page-width: min(100% - var(--gutter) * 2, calc(var(--container) - var(--gutter) * 2));
  /* Ceiling for the homepage hero's search panel.
     70% of the widest content box — (--container 1280) less its two desktop
     gutters (32 each) is 1216, and 70% of that is 851. A fixed length rather
     than a percentage on purpose: the panel is meant to be at its largest on a
     full screen and to shrink from there, so a ceiling is the only shape of
     rule that cannot let it grow when the window narrows. It deliberately does
     not read --gutter, which drops to 24 and then 16 at the responsive
     breakpoints and would nudge the ceiling upward on smaller screens. */
  --hero-search-max: 851px;
  --card-gap: 24px;
  --header-h: 72px;
  /* Height of the search page's breadcrumb row. A token because two rules in
     two files depend on the same number: the row sets its height from it, and
     the advertising banner beside it reaches back up by exactly that much so
     the two align at the top. */
  --crumb-h: 22px;
  /* Width of one outer gutter once the shell has stopped growing: zero at
     1440px and below, (viewport − 1440) / 2 above it. Fixed-position controls
     that belong to the website rather than to the viewport — the floating menu
     button — offset themselves by this so they stay on the shell. */
  --shell-gutter: max(0px, calc((100vw - var(--site-shell-max-width)) / 2));
  /*
    The band the floating menu control occupies at the top of the viewport once
    the header has scrolled away: its own offset, its 44px height, and a gap
    under it.

    Anything sticky that reaches the page's right edge has to start below this
    or the two land on each other — which is new, and is the cost of the client
    asking for the control to sit inside the page rather than out on the
    monitor's edge: "make that it stays always inside the page, so it is always
    under the header right side." The add-listing progress rail is the one
    panel that does reach that edge.
  */
  --floating-menu-band: calc(max(var(--sp-4), env(safe-area-inset-top)) + 44px + var(--sp-3));
  /* The neutral ground the centred website sits on. Deliberately duller than
     --bg so the shell edge reads as the end of the site, not as a section. */
  --page-surround: #E4DFD7;
  /* --- Radius ----------------------------------------------------------- */
  --r-sm: 6px;
  --r-md: 10px;
  /* buttons, inputs */
  --r-lg: 14px;
  /* cards */
  --r-xl: 20px;
  /* large panels */
  --r-pill: 999px;
  /* --- Elevation ---------------------------------------------------------
     Two-level shadows: a tight contact shadow plus a wider ambient one. That
     reads as real depth without the large blurred haze that makes a page feel
     cheap. */
  --shadow-xs: 0 1px 1px rgba(10, 34, 56, 0.04), 0 1px 3px rgba(10, 34, 56, 0.05);
  --shadow-sm: 0 1px 2px rgba(10, 34, 56, 0.06), 0 2px 6px rgba(10, 34, 56, 0.06);
  --shadow-md: 0 2px 4px rgba(10, 34, 56, 0.07), 0 8px 18px rgba(10, 34, 56, 0.09);
  --shadow-lg: 0 4px 8px rgba(10, 34, 56, 0.08), 0 16px 32px rgba(10, 34, 56, 0.12);
  --shadow-panel: 0 6px 12px rgba(10, 34, 56, 0.10), 0 24px 48px rgba(10, 34, 56, 0.16);
  /* Marker/pin lift — small and directional. */
  --shadow-pin: 0 1px 2px rgba(8, 23, 34, 0.30), 0 4px 8px rgba(8, 23, 34, 0.22);
  /* --- Motion ----------------------------------------------------------- */
  --dur-fast: 150ms;
  --dur: 200ms;
  --dur-slow: 250ms;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  /* --- Control sizing --------------------------------------------------- */
  --control-h: 48px;
  --control-h-sm: 40px;
  --control-px: 22px;
  /* --- Overlay ---------------------------------------------------------- */
  --overlay: rgba(8, 20, 32, 0.55);
  --hero-overlay: linear-gradient(
      to bottom,
      rgba(8, 20, 32, 0.62) 0%,
      rgba(8, 20, 32, 0.48) 45%,
      rgba(8, 20, 32, 0.66) 100%);
  /* The banner area with no artwork in it — see .hero__media--empty. */
  --hero-empty: #23384A;
  --z-header: 100;
  --z-drawer: 200;
  --z-modal: 300;
  --z-toast: 400;
  color-scheme: light;
}

/* ==========================================================================
   Dark theme
   Applied via [data-theme="dark"] on <html>, set before first paint by an
   inline script so the page never flashes.
   ========================================================================== */

[data-theme='dark'] {
  --bg: #081722;
  --bg-alt: #0B1E2B;
  --surface: #102635;
  --surface-2: #133042;
  --surface-3: #173243;
  --surface-inverse: #F6F1E8;
  --hero-empty: #1B3247;
  --heading: #F6F1E8;
  --text: #E8EDF1;
  --text-muted: #AEBBC4;
  --text-subtle: #8B9AA5;
  --text-inverse: #081722;
  --text-on-gold: #1B1301;
  --border: #294353;
  --border-strong: #375667;
  --border-inverse: #375667;
  /* Brand surfaces lift so primary actions stay legible on a dark page. */
  --navy: #E9EFF3;
  /* primary buttons invert to a light surface */
  --navy-hover: #FFFFFF;
  --teal: #5A9D94;
  --teal-hover: #74B3AA;
  --slate: #93A7BC;
  --gold: #D1AA68;
  --gold-hover: #E0BE84;
  --navy-tint: #173243;
  --teal-tint: #12302E;
  --teal-border: #2C5A55;
  --gold-tint: #2A2214;
  --gold-border: #4A3C1E;
  --success: #57BC92;
  --success-on-dark: #4ED08F;
  --success-bg: #102A21;
  --success-border: #235C46;
  --warning: #E0A44F;
  --warning-bg: #2C2314;
  --warning-border: #5A4522;
  --error: #E8827F;
  --error-bg: #301A1A;
  --error-border: #66332F;
  --info: #6FA3DC;
  --info-bg: #11243A;
  --info-border: #2B4C74;
  /* Lighter here: the light-theme blue on this navy reads as a bruise rather
     than as a link. */
  --link: #74AEF0;
  --link-hover: #9BC7F6;
  --focus-ring: 0 0 0 3px rgba(232, 238, 244, 0.28);
  --focus-ring-teal: 0 0 0 3px rgba(90, 157, 148, 0.38);
  /* Was gold — client, 23 September: remove golden selectors everywhere. */
  --focus-color: var(--text);
  --shadow-xs: 0 1px 1px rgba(0, 0, 0, 0.30), 0 1px 3px rgba(0, 0, 0, 0.32);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.36), 0 2px 6px rgba(0, 0, 0, 0.34);
  --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.40), 0 8px 18px rgba(0, 0, 0, 0.42);
  --shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.44), 0 16px 32px rgba(0, 0, 0, 0.50);
  --shadow-panel: 0 6px 12px rgba(0, 0, 0, 0.48), 0 24px 48px rgba(0, 0, 0, 0.58);
  --shadow-pin: 0 1px 2px rgba(0, 0, 0, 0.5), 0 4px 8px rgba(0, 0, 0, 0.4);
  --overlay: rgba(3, 9, 15, 0.70);
  --page-surround: #030A11;
  color-scheme: dark;
}

/* Primary buttons in dark mode use a light surface with dark ink. The
   --on-primary token flips with the theme so button text stays legible. */

:root {
  --on-primary: #FFFFFF;
}

[data-theme='dark'] {
  --on-primary: #081722;
}

/* Teal is the interactive accent: selected filters, map actions, active
   states. It keeps white text in both themes. */

:root {
  --on-teal: #FFFFFF;
}

[data-theme='dark'] {
  --on-teal: #06181A;
}

/* --- Responsive gutters --------------------------------------------------- */

@media (max-width: 1024px) {
  :root {
    --gutter: 24px;
    --card-gap: 20px;
  }
}

@media (max-width: 640px) {
  :root {
    --gutter: 16px;
    --card-gap: 16px;
    --header-h: 64px;
  }
}

/* ==========================================================================
   Previa — reset, typography and utilities
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Offset in-page anchors so the sticky header never covers a heading. */
  scroll-padding-top: calc(var(--header-h) + 16px);
  /* The ground the centred website sits on, seen only past 1440px. */
  background: var(--page-surround);
  /* Keep the column still when a scrollbar appears after the first paint. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /*
    clip, not hidden. overflow-x: hidden makes body a scroll container, and in
    Safari (and sometimes Chrome) that stops position: sticky on the listings
    filter sidebar — the panel scrolls away with the page instead of staying
    up. clip still keeps sideways spill inside the shell and does not create a
    scrollport, so sticky keeps working.
  */
  overflow-x: clip;
}

/* --------------------------------------------------------------------------
   Website shell

   The outer edge of the site. Below 1440px it is simply the viewport; above
   it the website stops growing and centres, leaving equal gutters either
   side. Applied to <body>, which means every band that paints edge to edge —
   header, hero, section colours, footer — stops at the same line, and no
   `100vw` rule inside can reach past it.

   <body> is deliberately not given a transform, filter or contain: those
   would make it the containing block for `position: fixed`, and the overlays
   that are *meant* to cover the whole screen — the map in full-screen mode,
   lightboxes, modal backdrops and the mobile navigation backdrop — would be
   trapped inside the shell.
   -------------------------------------------------------------------------- */

.site-shell,
body.page {
  width: min(100%, var(--site-shell-max-width));
  margin-inline: auto;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-weight: 600;
  line-height: var(--lh-snug);
  color: var(--heading);
}

p {
  margin: 0;
}

ul, ol {
  margin: 0;
  padding: 0;
}

figure, blockquote {
  margin: 0;
}

img, svg, video, canvas {
  display: block;
  max-width: 100%;
}

[x-cloak] {
  display: none !important;
}

.logo__mark {
  flex: none;
  width: 72px;
  height: 72px;
  max-width: none;
}

img {
  height: auto;
}

a {
  color: inherit;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}

button, input, select, textarea {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

table {
  border-collapse: collapse;
  width: 100%;
}

/* --- Icons ----------------------------------------------------------------
   Every icon is a 24x24 Lucide glyph carrying width/height attributes so it
   has intrinsic size even before CSS loads. Actual rendered size is set once
   here per context, so an SVG can never balloon to fill its container.        */

.icon {
  width: 18px;
  height: 18px;
  flex: none;
  /* never squashed by a flex parent */
  vertical-align: middle;
  stroke-width: 2;
}

.icon--sm {
  width: 16px;
  height: 16px;
}

.icon--lg {
  width: 20px;
  height: 20px;
}

.icon--xl {
  width: 24px;
  height: 24px;
}

/* Inline with text: 16px, aligned to the text baseline rather than centre. */

p .icon, li .icon, td .icon, .text-sm .icon, .text-xs .icon {
  width: 16px;
  height: 16px;
}

.brand-mark {
  flex: none;
  vertical-align: middle;
}

/* YouTube play triangle: match the page ground in both themes (never white). */
.brand-mark__yt-play {
  fill: var(--bg);
}

/* --- Focus ---------------------------------------------------------------- */

/* Keyboard users always get a visible ring; pointer users never see one. */

:focus {
  outline: none;
}

:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/*
  Text fields draw their own focus treatment (see .input:focus in
  components.css): the border takes the focus colour and a soft ring sits
  outside it. Letting the rule above add a second, offset 2px outline on top
  stacked two rings into one thick frame around the field — the "selector
  frame" the client reported on the market search box, where the gold focus
  colour of the dark theme made it especially loud.

  Suppressed here for text-entry controls only, which is why this is a narrow
  selector list and not `*:focus { outline: none }`. Every other control —
  links, buttons, checkboxes, cards — keeps the ring above untouched.
*/

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
}

/* --- Display headings ----------------------------------------------------- */

/* Titles use the system serif stack (--font-display). Body UI uses Manrope
   (variable, self-hosted). */

.display {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: var(--lh-tight);
  letter-spacing: -0.015em;
  color: var(--heading);
}

.display--hero {
  font-size: var(--fs-hero);
}

.display--h1 {
  font-size: var(--fs-h1);
}

.display--h2 {
  font-size: var(--fs-h2);
}

.display--h3 {
  font-size: var(--fs-h3);
}

.overline {
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--slate);
}

.lede {
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
  max-width: 62ch;
}

/* --- Text utilities ------------------------------------------------------- */

.muted {
  color: var(--text-muted);
}

.subtle {
  color: var(--text-subtle);
}

.strong {
  font-weight: 600;
}

.text-sm {
  font-size: var(--fs-sm);
}

.text-xs {
  font-size: var(--fs-xs);
}

.text-center {
  text-align: center;
}

.text-gold {
  color: var(--gold);
}

/* Over a limit rather than wrong: the SEO screen colours a title that has run
   past what a search result shows, and the reader is being told, not stopped. */

.text-warning {
  color: var(--warning);
}

.nowrap {
  white-space: nowrap;
}

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.clamp-2,
.clamp-3 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.clamp-2 {
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

.clamp-3 {
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

/* Prices, areas and other numeric facts — tabular figures keep columns even. */

.numeric {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* --- Accessibility helpers ------------------------------------------------ */

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -100px;
  /* Positioned against the viewport, so past 1440px it needs the shell's own
     gutter added or it lands on the bare surround to the left of the site. */
  left: calc(var(--shell-gutter) + var(--sp-4));
  z-index: calc(var(--z-modal) + 10);
  padding: var(--sp-3) var(--sp-5);
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  font-weight: 600;
  transition: top var(--dur) var(--ease);
}

.skip-link:focus {
  top: var(--sp-4);
}

/* --- Reduced motion ------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --- Print ---------------------------------------------------------------- */

@media print {
  .site-header, .site-footer, .no-print {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
  }
}

/* ==========================================================================
   Previa — reusable components
   Buttons, forms, badges, cards, tables, modals, alerts, tabs, skeletons.
   ========================================================================== */

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--control-h);
  padding: 0 var(--control-px);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
}

.btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.btn:active:not(:disabled) {
  transform: translateY(1px);
}

.btn:disabled,
.btn[aria-disabled='true'] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/*
  A button face that is only a label.

  The client, 30 August, about the banner's own button: "at the moment the
  button will not come, if don't enter the url to open - make that button comes
  without url as well. As we might use it as just a label without url as well.
  If there is no url behind it then if move to mouse on it the finger will not
  come. If url is there, then the finger will come."

  So a banner with a label and no link draws a <span> carrying this class
  instead of an anchor. The face is untouched — same block, same colours, same
  size, and the banner's own two colour fields still paint it — and everything
  that promises a click is gone: the pointer stays an arrow, and there is no
  hover or press feedback, because pointer-events is what the disabled state
  above already uses to stop all three at once. It is not disabled, though, and
  must not read as disabled: no dimming, and no aria-disabled on a span that
  was never a control.
*/

.btn--static {
  cursor: default;
  pointer-events: none;
}

/* Primary — the single dominant action per section. */

.btn--primary {
  background: var(--navy);
  color: var(--on-primary);
  border-color: var(--navy);
}

.btn--primary:hover:not(:disabled) {
  background: var(--navy-hover);
  border-color: var(--navy-hover);
}

/* Full backup — green in day and dark, white label and icon. */
.btn--backup {
  background: #008000;
  color: #fff;
  border-color: #008000;
}
.btn--backup:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
  color: #fff;
}
[data-theme='dark'] .btn--backup,
[data-theme='dark'] .btn--backup:hover:not(:disabled) {
  background: #008000;
  color: #fff;
  border-color: #008000;
}
/* Disabled while a backup runs — same green face, not the half-faded .btn:disabled. */
.btn.btn--backup:disabled,
.btn.btn--backup[aria-disabled='true'] {
  opacity: 1;
  background: #008000;
  color: #fff;
  border-color: #008000;
  cursor: not-allowed;
  pointer-events: none;
  /* Same box as idle — no press nudge when the click disables it. */
  transform: none;
}
.btn.btn--backup:active {
  transform: none;
}
.btn--backup .icon {
  stroke: #fff;
  color: #fff;
}

/* Same green as gen. full backup — spinner arc and “creating backup”.
   Always reserves its width so the backup button never shifts. */
.bk-running {
  color: #008000;
  align-items: center;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity var(--dur) var(--ease);
}
.bk-running .spinner {
  border-color: color-mix(in srgb, #008000 28%, transparent);
  border-top-color: #008000;
}
.bk-running--on {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* Google Drive status chip — same width as the reconnect / authorise button. */
.drive-status-col {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: max-content;
  max-width: 100%;
}
.drive-status-col .drive-status {
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  /* Same height as .btn--sm (reconnect drive). */
  min-height: var(--control-h-sm);
  padding: 0 var(--sp-4);
  align-items: center;
  justify-content: center;
  font-size: var(--fs-sm);
  line-height: 1;
}
.drive-status-col .drive-status .alert__title {
  margin: 0;
  font-size: inherit;
  line-height: inherit;
}
.drive-status-col .drive-status svg {
  margin-top: 0;
  width: 16px;
  height: 16px;
}

.btn--backup-settings {
  background: #008000;
  color: #fff;
  border-color: #008000;
}
.btn--backup-settings:hover:not(:disabled),
.btn--backup-settings:focus:not(:disabled),
.btn--backup-settings:focus-visible {
  background: #008000;
  color: #fff;
  border-color: #008000;
  box-shadow: none;
}

/* Compact admin card body/field spacing (Backups settings form). Title size
   for all admin cards is on .admin .card__title (client, 21 September). */
.card--backup-settings .card__header {
  padding: var(--sp-3) var(--sp-4);
}
.card--backup-settings .card__title {
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.25;
}
.card--backup-settings .card__body {
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}
.card--backup-settings .backup-settings {
  gap: 0.55rem;
}
.card--backup-settings .backup-settings .field {
  gap: 0.2rem;
}
.card--backup-settings .backup-settings .field__hint {
  margin: 0;
  line-height: 1.25;
}
.card--backup-settings .backup-settings .row {
  margin-top: 0.15rem;
}

/* Side-by-side fold cards must not stretch: a closed card beside an open one
   used to keep an empty body the height of its neighbour (Google Drive bug). */

.grid.grid--fold-cards {
  align-items: start;
}

/* Users admin — foldable cards; no sideways table scroll. */

.users-admin .user-card > .card__header,
.users-admin .user-card > .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.users-admin .user-card > .card__body {
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}

.users-admin .user-card__head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
  flex: 1;
}

.users-admin .user-card__identity {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.75rem;
  min-width: 0;
}

.users-admin .user-card__name {
  font-weight: 700;
  color: var(--text);
}

.users-admin .user-card__email,
.users-admin .user-card__since {
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

.users-admin .user-card__panels {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}

@media (max-width: 900px) {
  .users-admin .user-card__panels {
    grid-template-columns: minmax(0, 1fr);
  }
}

.users-admin .user-card__label {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.users-admin .user-card__ad-row {
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
}

.users-admin .user-card .expiry-field {
  flex-wrap: wrap;
}

/* deactivate — quiet outline at rest, red on hover (same idea as trash). */

.users-admin .btn.btn--danger-quiet {
  color: var(--text);
  border-color: var(--border);
  background: transparent;
}

.users-admin .btn.btn--danger-quiet:hover:not(:disabled),
.users-admin .btn.btn--danger-quiet:hover:not(:disabled) .icon {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
}

/* Country activate — same ADS market picker, sized to the date row. */

.users-admin .user-card__market {
  flex: none;
  width: 11.5rem;
  max-width: 100%;
}

.users-admin .user-card__market .market-picker--field {
  width: 100%;
}

.users-admin .user-card__market .market-picker__btn {
  height: 34px;
  min-height: 34px;
  padding-inline: var(--sp-2);
}

.users-admin .user-card__market .dropdown {
  width: max(100%, 280px);
  z-index: 30;
}

/* set / activate / deactivate — same 34px height as the date box. */

.users-admin .btn.btn--expiry-set {
  min-height: 34px;
  height: 34px;
  padding: 0 0.55rem;
  line-height: 1;
  box-sizing: border-box;
}

.btn--reset-backup {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 0.15rem;
  padding: 0.45rem 0.9rem;
  background: transparent;
  color: #ffab00;
  border: 1px solid #ffab00;
  border-radius: 0.25rem;
  box-shadow: none;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.25;
  text-transform: none;
  letter-spacing: 0;
  text-decoration: none;
  cursor: pointer;
}
.btn--reset-backup:hover:not(:disabled) {
  background: #ffab00;
  color: #000;
  border-color: #ffab00;
}
.btn--reset-backup:focus,
.btn--reset-backup:focus-visible {
  outline: none;
  box-shadow: none;
}
[data-theme='dark'] .btn--reset-backup {
  color: #ffab00;
  border-color: #ffab00;
  background: transparent;
}
[data-theme='dark'] .btn--reset-backup:hover:not(:disabled) {
  background: #ffab00;
  color: #000;
}

/* Secondary — outlined, same footprint as primary. */

.btn--secondary {
  background: var(--surface);
  color: var(--navy);
  border-color: var(--navy);
}

.btn--secondary:hover:not(:disabled) {
  background: var(--navy-tint);
}

[data-theme='dark'] .btn--secondary {
  background: transparent;
  color: var(--text);
  border-color: var(--border-strong);
}

[data-theme='dark'] .btn--secondary:hover:not(:disabled) {
  background: var(--surface-3);
}

/*
  Advertise — the one violet control on the site.

  "Make a violet #8B008B button 'advertise'." The colour is the client's,
  literally, and the same one the favicon tile and the header logo's tile are
  painted in, so the three read as one brand accent rather than three purples.

  Declared once and never redefined per theme: it is a named colour, not a
  semantic token, and a value picked by eye does not flip at night. White text
  on #8B008B clears AA at this size in both themes.
*/

.btn--advertise {
  background: #8B008B;
  color: #fff;
  border-color: #8B008B;
  font-weight: 600;
}

.btn--advertise:hover:not(:disabled) {
  background: #6F006F;
  border-color: #6F006F;
}

.btn--advertise:focus-visible {
  box-shadow: 0 0 0 3px rgba(139, 0, 139, 0.32);
}

.btn--advertise svg {
  width: 15px;
  height: 15px;
}

/*
  Listing "save" — Previa logo violet ground (#6F006F) in both themes, white
  label, heart #ff0000. Same named colours day and night.
*/

.btn--save-listing {
  background: #6F006F;
  color: #fff;
  border-color: #6F006F;
  font-weight: 600;
  /* Hold width for "saved" so the label swap does not nudge the share button. */
  min-width: 6.25rem;
}

.btn--save-listing:hover:not(:disabled),
.btn--save-listing:focus-visible {
  background: #5A005A;
  border-color: #5A005A;
  color: #fff;
}

.btn--save-listing svg,
.btn.btn--sm.btn--save-listing svg {
  width: 24px;
  height: 24px;
  color: #ff0000;
  stroke: #ff0000;
  fill: none;
  stroke-width: 1.75;
  stroke-linejoin: round;
}

.btn--save-listing[aria-pressed='true'] svg,
.btn.btn--sm.btn--save-listing[aria-pressed='true'] svg {
  fill: #ff0000;
  stroke: #ff0000;
}

/* Listing "share" — #0000CD in both themes, white label. */

.btn--share-listing {
  background: #0000CD;
  color: #fff;
  border-color: #0000CD;
  font-weight: 600;
}

.btn--share-listing:hover:not(:disabled),
.btn--share-listing:focus-visible {
  background: #0000A8;
  border-color: #0000A8;
  color: #fff;
}

/* Share fallback menu (Copy link / Facebook / LinkedIn) when Web Share is absent. */
.share-menu {
  position: relative;
  display: inline-flex;
}

.share-menu > .dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: calc(var(--z-header) + 5);
}

.share-menu .share-menu__panel {
  min-width: 11.5rem;
}

.share-menu .menu__item .brand-mark {
  width: 18px;
  height: 18px;
  flex: none;
}

/* Add-listing publish / draft — logo violet and #008000, no outline. */
.btn.btn--wizard-publish {
  background: #8B008B;
  color: #fff;
  border: none;
  box-shadow: none;
}
.btn.btn--wizard-publish:hover:not(:disabled) {
  background: #6F006F;
  color: #fff;
  border: none;
}
.btn.btn--wizard-draft {
  background: #008000;
  color: #fff;
  border: none;
  box-shadow: none;
}
.btn.btn--wizard-draft:hover:not(:disabled) {
  background: #006400;
  color: #fff;
  border: none;
}
.btn.btn--wizard-pause {
  background: #FF8C00;
  color: #fff;
  border: none;
  box-shadow: none;
}
.btn.btn--wizard-pause:hover:not(:disabled) {
  background: #E67E00;
  color: #fff;
  border: none;
}
.btn.btn--wizard-deactivate {
  background: #FF0000;
  color: #fff;
  border: none;
  box-shadow: none;
}
.btn.btn--wizard-deactivate:hover:not(:disabled) {
  background: #CC0000;
  color: #fff;
  border: none;
}

/* Ghost — low-emphasis actions inside dense UI. */

.btn--ghost {
  background: transparent;
  color: var(--text);
  border-color: transparent;
}

.btn--ghost:hover:not(:disabled) {
  background: var(--surface-3);
}

/* Quiet — bordered neutral, for toolbars and table row actions. */

.btn--quiet {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
}

.btn--quiet:hover:not(:disabled) {
  border-color: var(--border-strong);
  background: var(--surface-2);
}

/* Gold — reserved. One justified premium action per section, never the
   default CTA. Used for package upgrades and featured promotion only. */

.btn--gold {
  background: var(--gold);
  color: var(--text-on-gold);
  border-color: var(--gold);
}

.btn--gold:hover:not(:disabled) {
  background: var(--gold-hover);
  border-color: var(--gold-hover);
}

.btn--gold:focus-visible {
  box-shadow: var(--focus-ring-gold);
}

/*
  Teal — the second filled face, for the button that stands beside the gold one
  on a dark panel.

  The client, 8 September, about "Contact us" at the foot of /about: "this
  contace us buttun background make better color". It was .btn--on-image, which
  is 14% white with a blur behind it — a face designed to sit on a photograph,
  where the picture underneath supplies the contrast. The panel it is actually
  on is a flat colour, so there was nothing underneath it and it read as a grey
  smudge next to the gold button.

  Teal is the site's own second colour, already carried by the broker cards and
  the map, and --on-teal is the label that goes with it: both flip with the
  theme, so the button is legible on the panel in light (navy) and in dark
  (--surface-2) without a second rule here.
*/

.btn--teal {
  background: var(--teal);
  color: var(--on-teal);
  border-color: var(--teal);
}

.btn--teal:hover:not(:disabled) {
  background: var(--teal-hover);
  border-color: var(--teal-hover);
}

/* Danger — destructive confirmations only. */

.btn--danger {
  background: var(--error);
  color: #fff;
  border-color: var(--error);
}

.btn--danger:hover:not(:disabled) {
  filter: brightness(0.92);
}

/* Payments bulk delete — always #ff0000, including when nothing is ticked. */
.btn.btn--danger-ff {
  background: #ff0000;
  color: #fff;
  border-color: #ff0000;
}
.btn.btn--danger-ff:hover:not(:disabled) {
  filter: brightness(0.92);
}
.btn.btn--danger-ff:disabled,
.btn.btn--danger-ff[aria-disabled='true'] {
  opacity: 1;
  background: #ff0000;
  color: #fff;
  border-color: #ff0000;
  cursor: not-allowed;
  pointer-events: none;
}

/*
  Checkout pay / cancel, 16 September.

  "continue to payment" green with white text; "cancel" red #ff0000 with
  white text — both small letters in the markup.
*/

.btn--checkout-pay {
  background: #008000;
  color: #fff;
  border-color: #008000;
}

.btn--checkout-pay:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
  color: #fff;
}

.btn--checkout-cancel {
  background: #ff0000;
  color: #fff;
  border-color: #ff0000;
}

.btn--checkout-cancel:hover:not(:disabled) {
  background: #d40000;
  border-color: #d40000;
  color: #fff;
}

/*
  Payment success tick — rounded rectangle, solid green, white check.
*/

.checkout-success__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  background: #008000;
  color: #fff;
  border-radius: var(--r-md);
}

.checkout-success__icon svg {
  width: 28px;
  height: 28px;
}

/*
  Apply filters, in the search panel's footer.

  "'Apply filters' make in small letters and the area of the button make
  smaller and the background of this button make green #008000 — the same in
  day and dark mode."

  All three, literally: the label is lower case in the markup, the button is
  34px tall with tighter sides and no longer stretches across the footer, and
  the green is the client's own value rather than --success, which is a muted
  teal in light and a mint in dark. It is the same #008000 the field focus ring
  already uses, so the two greens on this panel are one green.
*/

/* Two classes, because .btn--sm sets its own 40px height further down this
   file and would otherwise win the size at equal specificity. */

.btn--sm.btn--apply {
  min-height: 34px;
  padding: 0 10px;
  font-size: var(--fs-xs);
}

.btn--apply {
  background: #8B008B;
  color: #fff;
  border-color: #8B008B;
}

.btn--apply:hover:not(:disabled) {
  background: #6F006F;
  border-color: #6F006F;
}

/* Save search / save this search — green #008000. */
.btn--sm.btn--save-search {
  min-height: 34px;
  padding: 0 10px;
  font-size: var(--fs-xs);
}

.btn--save-search {
  background: #008000;
  color: #fff;
  border-color: #008000;
}

.btn--save-search:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
}

/* Active sessions row — transparent; hover greens text + border only (client, 23 Sep). */
.btn--session-out {
  background: transparent;
  color: var(--text);
  border-color: var(--border);
}
.btn--session-out:hover:not(:disabled) {
  background: transparent;
  border-color: #008000;
  color: #008000;
}

.btn--apply svg {
  width: 15px;
  height: 15px;
}

/* On dark imagery (hero, gallery overlays). */

.btn--on-image {
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.45);
  backdrop-filter: blur(6px);
}

.btn--on-image:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.24);
}

/* Sizes */

.btn--sm {
  min-height: var(--control-h-sm);
  padding: 0 var(--sp-4);
  font-size: var(--fs-sm);
}

.btn--lg {
  min-height: 56px;
  padding: 0 var(--sp-7);
  font-size: var(--fs-lg);
}

.btn--block {
  display: flex;
  width: 100%;
}

/* Icon-only — must always carry an accessible name via aria-label. */

.btn--icon {
  min-height: var(--control-h-sm);
  width: var(--control-h-sm);
  padding: 0;
  border-radius: var(--r-md);
}

.btn--icon.btn--lg {
  min-height: var(--control-h);
  width: var(--control-h);
}

.btn svg {
  flex: none;
  width: 18px;
  height: 18px;
}

.btn--sm svg {
  width: 16px;
  height: 16px;
}

/*
  A brand mark inside a button — the Google Maps icon on the homepage's
  "search on map" action. It is a raster-independent <img>, not a currentColor
  glyph, so it keeps its own colours and needs its own size and alignment
  rather than inheriting the icon rules above.
*/

.btn__brand-icon {
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: 3px;
  align-self: center;
}

/* Holds 18px even in a small button: unlike a line glyph this is a detailed
   mark, and the client asked for it at roughly 16–20px. */

.btn--sm .btn__brand-icon {
  width: 18px;
  height: 18px;
}

/* Loading — the spinner replaces the label so width stays stable. */

.btn.is-loading {
  position: relative;
  color: transparent !important;
  pointer-events: none;
}

.btn.is-loading > * {
  visibility: hidden;
}

.btn.is-loading::after {
  content: '';
  position: absolute;
  width: 18px;
  height: 18px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: var(--on-primary);
  animation: spin 0.7s linear infinite;
  visibility: visible;
}

.btn--secondary.is-loading::after,
.btn--quiet.is-loading::after,
.btn--ghost.is-loading::after {
  color: var(--text);
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Success — transient confirmation after a mock save. */

.btn.is-success {
  background: var(--success) !important;
  border-color: var(--success) !important;
  color: #fff !important;
}

/* ==========================================================================
   Forms
   ========================================================================== */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 0;
}

/* Labels are always visible — placeholders are never the only label. */

.field__label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

.field__label .req {
  color: #ff0000;
}

.field__hint {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* Extra phone block: Telegram / Signal sit tight under their labels (hints gone). */
.settings-extra-links .field {
  gap: var(--sp-1);
}

.field__hint--tight {
  margin-top: 0;
  line-height: 1.2;
}

/* General → Advertising statistics: stepper width on the wrapper (not the
   input), hint pulled up tight under the row (client, 23 September). */
.field--ad-visitor-days {
  gap: var(--sp-1);
}
.field--ad-visitor-days__row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.field--ad-visitor-days .field__hint--tight {
  margin-top: 0;
  line-height: 1.15;
}

/* General → Booking: fee ladder rows. Fee column is 7rem (matches
   .input--rate steppers) so “Previa fee (%)” sits on the field’s left edge. */
.booking-fees__head,
.booking-fees__row {
  display: grid;
  grid-template-columns: max-content 7rem auto;
  gap: var(--sp-2);
  align-items: center;
  margin-bottom: var(--sp-2);
  justify-content: start;
}
.booking-fees__head {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-bottom: var(--sp-1);
}
.booking-fees__head > span {
  text-align: left;
  justify-self: start;
}

.admin .ad-card-fields {
  gap: var(--sp-2);
}

.admin .ad-card-fields .field {
  gap: 2px;
}

.admin .ad-card-fields + .ad-card-fields {
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
}

/* Advertising admin: same tight packing as Frontpage. */
.advertising-admin {
  gap: var(--sp-2);
}

.advertising-admin > .card,
.advertising-admin > .card--fold {
  margin: 0;
}

.advertising-admin .card__header,
.advertising-admin .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.advertising-admin .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.advertising-admin .card__body > .stack,
.advertising-admin .card__body > .stack--sm,
.advertising-admin form.stack,
.advertising-admin form.stack--sm {
  gap: var(--sp-2);
}

.advertising-admin .field {
  gap: 2px;
}

.advertising-admin .grid {
  gap: var(--sp-2);
}

.advertising-admin .textarea {
  min-height: 0;
}

.advertising-admin .field__hint {
  margin-top: 0;
  line-height: 1.25;
}

/*
  The character count under a field.

  The client, 8 September: "for page title, meta description, OG:title,
  OG:description and image alt tag fields make character count as well. As there
  is very important to stay inside limits."

  A hint's size and colour, because that is what it is until the limit is
  passed — the figure sits under the field and stays out of the way. Tabular
  numerals so the count does not jog the line about as it climbs through the
  widths of 8 and 1.

  Over the limit it takes the warning colour and nothing else happens: the
  limits are where the platforms truncate, not rules, and a title of 62
  characters is a decision somebody is allowed to make.
*/

.field__count {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.field__count.is-over {
  color: var(--warning);
  font-weight: 600;
}

/* Count after the label — "Page title (34/80)" — not under the field. */

.field__label > .field__count {
  margin-left: 0.35em;
  font-weight: 400;
}

/*
  A field heading with a control on the right of it.

  The client asked for the advertise button "after the title 'Contry'" and
  again "after title 'Your location on the map'", which is a heading row rather
  than a field row: the button belongs to the section, not to the input under
  it, and putting it inside the input's box would make it look like part of
  what is being edited.
*/

.field__label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 34px;
}

.field__label-row .field__label {
  flex: 1;
  min-width: 0;
}

.field__error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--error);
}

.field__error svg {
  flex: none;
  width: 14px;
  height: 14px;
  margin-top: 2px;
}

.input,
.select,
.textarea {
  width: 100%;
  min-height: var(--control-h);
  padding: 0 var(--sp-4);
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-base);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.input::placeholder,
.textarea::placeholder {
  color: var(--text-subtle);
}

.input:hover:not(:disabled),
.select:hover:not(:disabled),
.textarea:hover:not(:disabled) {
  border-color: var(--border-strong);
}

/*
  The frame on the field you are typing in — green, one theme, one pixel.

  See --field-focus in tokens.css for the client's words and for why the colour
  is fixed rather than themed. Only the three text controls take it: a field is
  what you type into, and it is the frame around a field that was reported.
*/

.input:focus,
.select:focus,
.textarea:focus {
  border-color: var(--field-focus);
  box-shadow: var(--field-focus-ring);
  background: var(--surface);
  background-color: var(--surface);
  color: var(--text);
}

/*
  Chromium/WebKit paint autofill with a bright yellow/blue wash (email fields
  especially). Force the theme surface so focus stays a green frame only.
*/
.input:-webkit-autofill,
.input:-webkit-autofill:hover,
.input:-webkit-autofill:focus,
.input:-webkit-autofill:active,
.textarea:-webkit-autofill,
.textarea:-webkit-autofill:hover,
.textarea:-webkit-autofill:focus,
.textarea:-webkit-autofill:active {
  -webkit-text-fill-color: var(--text);
  caret-color: var(--text);
  border-color: var(--border);
  box-shadow: 0 0 0 1000px var(--surface) inset;
  transition: background-color 99999s ease-out;
}

.input:-webkit-autofill:focus,
.textarea:-webkit-autofill:focus {
  border-color: var(--field-focus);
  box-shadow: 0 0 0 1000px var(--surface) inset, var(--field-focus-ring);
}

.input:disabled,
.select:disabled,
.textarea:disabled {
  background: var(--surface-3);
  color: var(--text-subtle);
  cursor: not-allowed;
}

/*
  A colour swatch is the colour, not a colour inside a box.

  The client, 31 August, of the four colour fields on the Advertising screen:
  "this color system is a great thing, thanks for that, just at the moment the
  rectangle color boxes are inside the rounded rectangle box. Remove Make that
  the color is filled inside the ronded rectangle box and no borderline, so,
  remove the 'double boxing'"

  There were three boxes drawn one inside the other, and only the first is ours:
  .input's own border and radius, then 2px of inline padding, then the swatch
  the browser draws for input[type=color] — which Chromium and WebKit put inside
  a padded wrapper with a grey border of their own, and Firefox draws with a
  border too. Hence a small hard-edged rectangle floating inside a rounded one.

  So the host keeps only the radius and the size, and the UA's wrapper padding
  and both swatch borders are zeroed. What is left is one rounded rectangle
  filled edge to edge with the colour.

  No border at all, which is what "no borderline" asks for. It is safe here
  because this control never stands on its own: it sits in a .row--tight beside
  the text box that holds the code, which carries the frame for the pair.

  appearance: none is required before any of it applies in WebKit, and the
  overflow clip is what stops the square swatch corners showing through the
  radius in the engines that ignore the radius on the pseudo-element.
*/

.swatch {
  flex: none;
  width: 44px;
  min-height: var(--control-h);
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--r-md);
  overflow: hidden;
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

.swatch::-webkit-color-swatch-wrapper {
  padding: 0;
  border: 0;
}

.swatch::-webkit-color-swatch {
  border: 0;
  border-radius: var(--r-md);
}

.swatch::-moz-color-swatch {
  border: 0;
  border-radius: var(--r-md);
}

.swatch:focus-visible {
  box-shadow: var(--field-focus-ring);
}

.textarea {
  min-height: 132px;
  padding: var(--sp-3) var(--sp-4);
  line-height: var(--lh-normal);
  resize: vertical;
}

/* Native select with a custom chevron. */

.select {
  appearance: none;
  padding-right: var(--sp-9);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23667085' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-4) center;
  cursor: pointer;
}

[data-theme='dark'] .select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23B2BBC6' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* Validation states — communicated by border, icon and message, not colour alone. */

.field.is-invalid .input,
.field.is-invalid .select,
.field.is-invalid .textarea {
  border-color: var(--error);
}

.field.is-invalid .input:focus,
.field.is-invalid .select:focus,
.field.is-invalid .textarea:focus {
  box-shadow: 0 0 0 3px rgba(217, 45, 32, 0.18);
}

.field.is-valid .input {
  border-color: var(--success);
}

/*
  A price cell in a rate table: narrow, because it holds a small number, and
  short, because a table row is not a form field.

  The width goes on the .num-stepper wrapper previa.js puts around every number
  field, not on the input: the caret column is pinned to the wrapper's right
  edge, so narrowing only the input would leave the arrows floating in the gap
  beside it.
*/

.num-stepper:has(.input--rate) {
  width: 7rem;
}

.input--rate {
  width: 100%;
  min-height: 34px;
  padding: 0 var(--sp-3);
  text-align: right;
}

/*
  The per-market advertising price list, in the admin panel.

  Two hundred markets is a scroll, so the table gets its own scrolling box with
  the head pinned rather than pushing the rest of the screen down; the search
  field above it hides the rows that do not match.
*/

.rate-table {
  max-height: 420px;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.rate-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-2);
}

.rate-table tr[hidden] {
  display: none;
}

/* Advertising markets country list — half the usual row padding. */
.admin .rate-table--compact .table th,
.admin .rate-table--compact .table td {
  padding: 6px var(--sp-2);
  vertical-align: middle;
}

.admin .rate-table--compact .sort-menu__btn {
  min-height: 28px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-sm);
}

.admin .rate-table--compact .sort-menu__btn svg {
  width: 14px;
  height: 14px;
}

.admin .rate-table--compact .check {
  margin: 0;
}

.admin .rate-table--compact .ad-market-row {
  cursor: pointer;
}

.admin .rate-table--compact .ad-market-row:hover {
  background: var(--surface-2);
}

.admin .rate-table--compact .ad-market-row .sort-menu,
.admin .rate-table--compact .ad-market-row .check {
  cursor: default;
}

/* Input with a leading icon (search, location). */

.input-icon {
  position: relative;
  display: flex;
  align-items: center;
}

.input-icon svg {
  position: absolute;
  left: var(--sp-4);
  width: 18px;
  height: 18px;
  color: var(--text-subtle);
  pointer-events: none;
}

.input-icon .input {
  padding-left: calc(var(--sp-4) * 2 + 18px);
}

/*
  Paired min/max inputs.

  No dash between them any more: "Here in the main search menu remove the
  separator - between these fields." The two boxes are already labelled Min and
  Max and sit under one heading, so the character was carrying no meaning the
  placeholders did not. Two equal columns rather than three, so the pair keeps
  filling the sidebar's width with the middle column gone.
*/

.range-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: var(--sp-2);
}

/*
  Number inputs — our own stepper, ruled off at the field's right edge.

  Two client notes, three months of the same complaint. First: "the up/down
  arrows for the numbers, use the same style as in sexydate, so these arrows are
  on the right corner of the field. At the moment they are centered." Moving the
  unit labels out of the fields fixed the centring. Then, with the arrows in the
  corner: "the up/down arrows need to make better, make the same style as in
  sexydate, that they sit on the right side of the field."

  What is left to fix is the stepper itself, and the native one cannot be it.
  Chrome's is a grey slab with no colour, border or size of its own, and
  Firefox's takes no styling whatsoever — neither can be made to look like the
  reference, which is two thin carets in a ruled column pinned to the right edge
  of the field. So the native stepper is hidden here and previa.js wraps every
  number field in the .num-stepper below.
*/

input[type='number']::-webkit-outer-spin-button,
input[type='number']::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}

input[type='number'] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/*
  The price field on the add-listing form, and the unit it is quoted in.

  "In the add listing price section, if the user has chosen deal type rent or
  short, then the price must be in case of rent 1000 € / month and in case of
  short rent 100 € / day."

  A row rather than a suffix floated inside the input: previa.js pins a stepper
  column to the input's right edge, and text placed under it would either
  collide with the arrows or push a five-digit price out of view. The unit sits
  beside the field instead, on the same baseline, so the two read as one price.
*/

.price-field {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.price-field .num-stepper {
  flex: 1;
  min-width: 0;
}

.price-field__unit {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  white-space: nowrap;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-muted);
}

/* Booking duration packages on add-listing; day picker on the listing page. */
.booking-price-layout {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  align-items: flex-start;
}

.booking-price-layout .booking-cal {
  flex: 0 0 auto;
}

.booking-price-layout .booking-cal-wrap {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  align-self: flex-start;
  width: 16.5rem;
  max-width: 100%;
  height: fit-content;
}

.booking-price-layout .booking-cal-wrap .booking-cal {
  width: 100%;
}

.booking-price-layout .booking-packages {
  flex: 0 0 auto;
  min-width: 0;
}

.booking-price-layout .booking-fee-notice {
  flex: 0 0 auto;
  align-self: flex-start;
}

.booking-packages__head,
.booking-packages__row {
  display: grid;
  /* Match General → Booking .input--rate steppers (7rem). */
  grid-template-columns: 7rem 7rem auto;
  gap: var(--sp-2);
  align-items: center;
  margin-bottom: var(--sp-2);
}

.booking-packages__head {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-bottom: var(--sp-1);
  text-transform: lowercase;
}

/* Delete crosses: transparent at rest, red on hover (client, 23 Sep). */
.booking-packages__row .btn.btn--quiet,
.booking-packages__row .btn.btn--danger-quiet {
  color: var(--text);
  border-color: var(--border);
  background: transparent;
}
.booking-packages__row .btn.btn--quiet:hover:not(:disabled),
.booking-packages__row .btn.btn--danger-quiet:hover:not(:disabled),
.booking-packages__row .btn.btn--quiet:hover:not(:disabled) .icon,
.booking-packages__row .btn.btn--danger-quiet:hover:not(:disabled) .icon,
.booking-packages__row .btn.btn--quiet:hover:not(:disabled) svg,
.booking-packages__row .btn.btn--danger-quiet:hover:not(:disabled) svg {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
}

.booking-fee-notice {
  margin: 0;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md, 8px);
  background: transparent;
  max-width: 14rem;
}

.booking-fee-notice__title {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.booking-fee-notice__list {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-sm);
  line-height: 1.45;
}

.booking-fee-notice__list p {
  margin: 0 0 0.35em;
  padding: 0;
}

.booking-fee-notice__list p:last-child {
  margin-bottom: 0;
}

/* Booking availability / stay calendar (add-listing + detail). */
.booking-cal {
  width: 16.5rem;
  max-width: 100%;
  flex: 0 0 auto;
}

.booking-cal__title {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.booking-cal__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-1);
  margin-bottom: var(--sp-2);
}

.btn.btn--quiet.booking-cal__nav-btn {
  background: transparent !important;
  border-color: var(--border);
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1;
  color: var(--text);
}

.btn.btn--quiet.booking-cal__nav-btn:hover:not(:disabled) {
  background: transparent !important;
  border-color: #008000;
  color: #008000;
}

.booking-cal__marked {
  margin: 0.2rem 0 0;
  color: #008000;
  font-weight: 500;
  line-height: 1.3;
}

.booking-cal__month {
  font-size: var(--fs-sm);
  font-weight: 600;
  font-family: inherit;
  text-align: center;
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.booking-cal__weekdays,
.booking-cal__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  width: 100%;
}

.booking-cal__grid {
  /* Rows follow the month (complete weeks only — no forced sixth blank week). */
  grid-template-rows: none;
}

.booking-cal--owner .booking-cal__grid,
.booking-cal--guest .booking-cal__grid {
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.booking-cal__weekdays {
  margin-bottom: 2px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-align: center;
}

.booking-cal__cell {
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  aspect-ratio: 1;
  border-radius: 6px;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  color: var(--text);
  box-sizing: border-box;
  padding: 0;
  min-width: 0;
}

.booking-cal__cell.is-empty,
.booking-cal__cell:disabled {
  cursor: default;
  color: transparent;
  pointer-events: none;
  border-color: transparent;
}

/* Collapse pad cells so trailing empty weeks do not leave a blank gap
   under the month (Property information was sitting far below). */
.booking-cal__cell.is-empty {
  aspect-ratio: auto;
  height: 0;
  min-height: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
}

.booking-cal__cell.is-past,
.booking-cal__cell.is-blocked {
  color: var(--text-muted);
  opacity: 0.45;
  cursor: default;
  pointer-events: none;
}

.booking-cal--owner .booking-cal__cell.is-past {
  pointer-events: none;
}

.booking-cal--owner .booking-cal__cell:not(.is-empty):not(.is-past):not(:disabled) {
  background: transparent;
  border: 1px solid transparent;
}

/* Selected / available — green rounded outline (add-listing + detail). */
.booking-cal--owner .booking-cal__cell.is-available:not(.is-empty):not(.is-past):not(:disabled),
.booking-cal--owner .booking-cal__cell.is-available,
.booking-cal--guest .booking-cal__cell.is-selected {
  background: transparent;
  border: 2px solid #008000;
  border-radius: 6px;
  color: #008000;
  font-weight: 600;
  box-sizing: border-box;
  box-shadow: none;
}

.booking-cal--guest .booking-cal__cell.is-open {
  background: transparent;
}

.booking-cal--guest .booking-cal__cell.is-edge {
  box-shadow: none;
}

.booking-stay-wrap {
  flex: 1 1 auto;
  min-width: 0;
}

.booking-stay {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  max-width: 16.5rem;
}

.booking-stay--detail {
  display: grid;
  grid-template-columns: 16.5rem minmax(8rem, max-content);
  grid-template-areas:
    "hint meta"
    "cal  aside";
  column-gap: var(--sp-3);
  row-gap: var(--sp-2);
  align-items: start;
  max-width: none;
  width: max-content;
}

.booking-stay--detail .booking-stay__hint,
.booking-stay--detail .booking-stay__meta {
  margin: 0;
  padding: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.3;
  font-family: inherit;
  align-self: start;
}

.booking-stay--detail .booking-stay__hint {
  grid-area: hint;
  color: var(--text);
}

.booking-stay--detail .booking-stay__meta {
  grid-area: meta;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  white-space: nowrap;
  color: #008000;
}

.booking-stay--detail .booking-stay__cal {
  grid-area: cal;
  width: 16.5rem;
}

.booking-stay--detail .booking-stay__aside {
  grid-area: aside;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  min-width: 6.5rem;
  /* Flush with the calendar nav row (‹ month ›), not pushed down. */
  padding-top: 0;
}

.booking-stay--detail .booking-stay__price {
  margin: 0;
}

.booking-stay--detail .booking-stay__unavailable {
  margin: 0;
}

.booking-stay--detail .booking-stay__book {
  margin: 0;
}

.booking-stay--detail .btn.btn--book-stay {
  margin-top: 0;
}

.btn.btn--book-stay:disabled,
.btn.btn--book-stay.is-disabled,
a.btn.btn--book-stay.is-disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* Guest wrap keeps hover so the login tip can show (button itself is inert). */
.booking-stay__book-wrap {
  position: relative;
  display: inline-block;
  cursor: not-allowed;
}

.booking-stay__login-tip {
  display: none;
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%);
  z-index: 40;
  box-sizing: border-box;
  width: max-content;
  max-width: min(16rem, 70vw);
  padding: 6px 10px;
  border: 1px solid #333;
  border-radius: 4px;
  background: #facc15;
  color: #000;
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  white-space: normal;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
  pointer-events: none;
}

.booking-stay__login-tip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  margin-left: -6px;
  border: 6px solid transparent;
  border-top-color: #facc15;
}

.booking-stay__book-wrap:hover .booking-stay__login-tip,
.booking-stay__book-wrap:focus-within .booking-stay__login-tip {
  display: block;
}

.booking-stay__controls {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.booking-stay__days {
  width: 4.5rem;
  text-align: center;
}

.booking-stay__unavailable {
  color: var(--danger, #c44);
  margin: 0;
}

.booking-cal__clear {
  padding: 0.15rem 0.45rem;
  min-height: 0;
  font-size: var(--fs-xs);
  line-height: 1.2;
  background: transparent;
}

.booking-stay__book {
  margin: 0;
}

.btn.btn--book-stay {
  margin-top: var(--sp-1);
  color: #fff;
  border-color: #008000;
  background: #008000;
  text-transform: lowercase;
}

.btn.btn--book-stay:hover:not(:disabled) {
  color: #fff;
  border-color: #006600;
  background: #006600;
}

.pcard__price-from {
  font-weight: 500;
  margin-right: 0.25em;
  text-transform: lowercase;
}

.num-stepper {
  position: relative;
  display: block;
}

/* Room for the arrow column, so a five-digit price never runs under it. */

.num-stepper .input {
  padding-right: 34px;
}

/*
  Pinned to the field's inner edge and inset by the border, so the divider is
  flush with the top and bottom of the field the way the reference draws it.
  Ignores pointer events itself; only the two buttons take clicks, which leaves
  the rest of the field clickable straight through to the text cursor.
*/

.num-stepper__arrows {
  position: absolute;
  top: 1px;
  right: 1px;
  bottom: 1px;
  display: flex;
  flex-direction: column;
  width: 26px;
  border-left: 1px solid var(--border);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  overflow: hidden;
  pointer-events: none;
}

.num-stepper__btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-subtle);
  cursor: pointer;
  pointer-events: auto;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

/* A hairline between the two, matching the column's own rule. */

.num-stepper__btn--up {
  border-bottom: 1px solid var(--border);
}

.num-stepper__btn svg {
  width: 10px;
  height: 6px;
}

.num-stepper__btn:hover {
  background: transparent;
  color: #008000;
}

.num-stepper__btn:active {
  background: transparent;
  color: #008000;
}

[data-theme='dark'] .num-stepper__btn:active {
  color: #008000;
}

/* The field owns the focus ring; the column must not draw a second one. */

.num-stepper:focus-within .num-stepper__arrows {
  border-left-color: var(--border-strong);
}

/* --- Checkbox and radio --------------------------------------------------- */

.check {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  cursor: pointer;
  padding: var(--sp-1) 0;
  min-height: 32px;
}

.check input {
  flex: none;
  appearance: none;
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

.check input[type='radio'] {
  border-radius: 50%;
}

.check input:checked {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
}

/* Dark theme used to invert checked boxes to light navy + black tick
   (because --navy flips to #E9EFF3). Keep the same green + white tick. */
[data-theme='dark'] .check input:checked {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

/* Search filters: checked box is draft-green with a white tick. */

.filter-panel .check input:checked {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

[data-theme='dark'] .filter-panel .check input:checked {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

.check input[type='radio']:checked {
  background-image: none;
  box-shadow: inset 0 0 0 4px var(--surface);
}

/*
  A radio that is drawn as a tickbox.

  The client asked for this pair as tickboxes — "in the user profile in this
  place make two checkboxes 'Broker' and 'Private Seller'" — and in the same
  breath for the behaviour of a radio group: "can not be both at the same time,
  must choose one." Those were built as two checkboxes with the radio behaviour
  written by hand in Alpine, and the hand-written half did not work; see the note
  in account/settings.html. They are real radios now, so the browser guarantees
  the "choose one" half, and this keeps the square box and the checkmark he
  approved.

  The two background-image URLs are repeated from .check input:checked rather
  than inherited, because .check input[type='radio']:checked sets
  background-image: none at the same specificity as the rule that sets it. Same
  weight, so the later rule wins — which is this one, and only if it names the
  image itself.
*/

.check input.check__tick[type='radio'] {
  border-radius: var(--r-sm);
}

.check input.check__tick[type='radio']:checked {
  box-shadow: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

[data-theme='dark'] .check input.check__tick[type='radio']:checked {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

.check input:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* Checkout terms: no gold (or any) focus ring around the box. */
.check--checkout-terms input:focus,
.check--checkout-terms input:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Promotion bump / featured tick: same — no golden selector. */
.promo__check input:focus,
.promo__check input:focus-visible {
  outline: none;
  box-shadow: none;
}

/* SEO admin form: same — no golden selector on the ticks. */
.seo-form .check input:focus,
.seo-form .check input:focus-visible {
  outline: none;
  box-shadow: none;
}

.check input:disabled {
  cursor: not-allowed;
}

/* Shorthand `background` on :disabled was resetting background-repeat to
   `repeat`, so a checked-and-disabled box tiled the tick SVG. Keep unchecked
   disabled muted; keep checked disabled as one green tick. */
.check input:disabled:not(:checked) {
  background: var(--surface-3);
}

.check input:checked:disabled {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
  opacity: 0.85;
}

[data-theme='dark'] .check input:checked:disabled {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
}

.check__text {
  font-size: var(--fs-sm);
  line-height: 1.45;
}

/* --- Segmented control (Buy / Rent) --------------------------------------- */

.segmented {
  display: inline-flex;
  padding: 4px;
  background: var(--surface-3);
  border-radius: var(--r-md);
  gap: 2px;
}

.segmented__item {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 42px;
  padding: 0 var(--sp-5);
  border-radius: 7px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.segmented__item input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.segmented__item:hover {
  color: var(--text);
}

.segmented--full {
  width: 100%;
}

.segmented--full .segmented__item {
  flex: 1;
}

/*
  Deal type carries Sell / Rent / Short rent on the first row. Booking shares
  the second row with Brokers (half width each) so both fit (client, 23 Sep).
*/

.segmented--deal .segmented__item {
  padding: 0 var(--sp-2);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

.segmented--booking-brokers {
  margin-top: var(--sp-2);
}

.segmented--booking-brokers .segmented__item {
  padding: 0 var(--sp-2);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

/* Brokers is half the previous full-width control so Booking fits beside it. */
.segmented--booking-brokers .segmented__item--brokers {
  flex: 0 0 calc(50% - var(--sp-1));
  max-width: calc(50% - var(--sp-1));
}

.segmented--booking-brokers .segmented__item--brokers > span {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.segmented--booking-brokers .segmented__item--brokers svg {
  width: 15px;
  height: 15px;
}

/* Selected: raised white surface + teal ink + weight change, so the state is
   not carried by colour alone. */

.segmented__item:has(input:checked) {
  background: var(--surface);
  color: var(--teal);
  font-weight: 700;
  box-shadow: var(--shadow-sm);
}

[data-theme='dark'] .segmented__item:has(input:checked) {
  color: var(--text);
}

.segmented__item:has(input:focus-visible) {
  box-shadow: var(--focus-ring-teal);
}

/* --- Toggle switch -------------------------------------------------------- */

.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  cursor: pointer;
}

.switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.switch__track {
  flex: none;
  width: 44px;
  height: 26px;
  padding: 3px;
  background: var(--border-strong);
  border-radius: var(--r-pill);
  transition: background-color var(--dur) var(--ease);
}

.switch__thumb {
  width: 20px;
  height: 20px;
  background: #fff;
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease);
}

.switch input:checked + .switch__track {
  background: var(--success);
}

.switch input:checked + .switch__track .switch__thumb {
  transform: translateX(18px);
}

/*
  Cookie preferences — client, 21 September: active toggle #008000; three
  action buttons lowercase green with white type.
*/

.cookies-page .switch input:checked + .switch__track {
  background: #008000;
}

/*
  Prices admin — client, 21 September: active toggle #008000; edit is
  lowercase green outline on transparent; Previews reuse /.price-grid cards.
*/

.packages-admin .switch input:checked + .switch__track {
  background: #008000;
}

/* Languages admin — enabled toggle #008000 (client, 22 September). */

.languages-admin .switch input:checked + .switch__track {
  background: #008000;
}

.languages-admin .table tbody tr:hover {
  background: transparent;
}

.languages-admin .legend__fill {
  display: block;
  background: #008000;
}

/* Search filter: collapse rows instead of display:none so column widths (and
   the page scrollbar gutter) stay put — typing in the search must not shove
   the table sideways (client, 23 September). */
.languages-admin .table-wrap {
  scrollbar-gutter: stable;
}
.languages-admin tr.is-filtered-out {
  visibility: collapse;
}
.languages-admin .table__actions {
  justify-content: flex-start;
  /* Room for strings + edit + trash so a row without Remove does not shrink. */
  min-width: 11.5rem;
}
.languages-admin .table__actions__slot {
  display: inline-flex;
  width: 2rem;
  height: 2rem;
  flex: none;
  visibility: hidden;
  pointer-events: none;
}

/* Add language panel — header height matches Integrations Stripe fold
   (padding + title line). Cancel is compact so it does not stretch the bar
   taller than that fold (client, 22 September). */
.languages-admin__add {
  margin-bottom: 0;
}

.languages-admin__add > .card__header {
  padding: var(--sp-3) var(--sp-4);
  min-height: 0;
  gap: var(--sp-3);
  align-items: center;
}
.languages-admin__add > .card__header .card__title {
  margin: 0;
  line-height: 1.25;
  font-size: var(--fs-base);
  font-weight: 600;
}
.languages-admin__add > .card__header .languages-admin__cancel {
  min-height: 0;
  height: auto;
  padding: 0.1rem 0.55rem;
  font-size: var(--fs-xs);
  line-height: 1.25;
  box-sizing: border-box;
  background: transparent;
}
.languages-admin__add > .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}
.languages-admin__add form.stack--sm {
  gap: 0.55rem;
}
.languages-admin__add-fields {
  gap: var(--sp-2);
}
.languages-admin__add .field {
  gap: 0.2rem;
}
.languages-admin__add .field__hint {
  margin: 0;
  line-height: 1.25;
}

/* English name on top, native name under it (client, 22 September). */
.languages-admin__lang {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.languages-admin__lang-names {
  display: flex;
  flex-direction: column;
  gap: 0;
  line-height: 1.25;
}
.languages-admin__lang-names .strong,
.languages-admin__lang-names .muted {
  margin: 0;
  font-size: var(--fs-sm);
}
.languages-admin__lang-names .muted {
  font-weight: 400;
}

/* Strings admin — language pick uses sort-menu field chrome (not native select). */

.strings-lang-pick {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  position: relative;
}

.strings-lang-pick .sort-menu--field {
  width: auto;
  min-width: 200px;
}

.strings-lang-pick .sort-menu--field .sort-menu__btn {
  width: 100%;
  justify-content: space-between;
  border: 1px solid var(--border);
  background: var(--surface);
  min-height: var(--control-h);
  font-size: var(--fs-base);
  font-weight: 400;
  text-transform: none;
  white-space: nowrap;
  text-align: left;
  padding-inline: 12px;
  gap: 8px;
  box-shadow: none;
}

.strings-lang-pick .sort-menu--field .sort-menu__btn:hover {
  background: var(--surface-2);
}

.strings-lang-pick .sort-menu .dropdown {
  min-width: 100%;
}

/* Strings search — no lupe; clear-cross gets #808000 hover disc. */

.strings-admin__search {
  min-height: 40px;
  width: 280px;
  -webkit-appearance: none;
  appearance: none;
}

.strings-admin__search::-webkit-search-decoration {
  display: none;
}

.strings-admin__search::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  margin-inline-end: 2px;
  border-radius: 50%;
  cursor: pointer;
  background: transparent center / 10px 10px no-repeat
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23c8d0d8' stroke-width='1.6' stroke-linecap='round'><path d='M3 3l6 6M9 3L3 9'/></svg>");
}

.strings-admin__search::-webkit-search-cancel-button:hover {
  background-color: #808000;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23ffffff' stroke-width='1.6' stroke-linecap='round'><path d='M3 3l6 6M9 3L3 9'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 10px 10px;
}

/* Strings table — fixed columns so English (source) does not reflow on refresh.
   Scroll window shows ~20 rows (client, 22 September); data-row-window measures
   the real row height after paint. */
.strings-admin .table-wrap {
  scrollbar-gutter: stable;
}

.strings-admin .table-scroll {
  /* Estimate before fitRowWindow runs: one input row ≈ 3.25rem. */
  max-height: calc(var(--table-rows, 20) * 3.25rem + 2.75rem);
}

.strings-admin .table-scroll .table th {
  background: var(--surface, #0f1724);
}

.strings-admin .table {
  table-layout: fixed;
  width: 100%;
}

/* No row hover tint while editing translations (client, 22 September). */
.strings-admin .table tbody tr:hover {
  background: transparent;
}

.strings-admin__col-section {
  width: 16%;
}

.strings-admin__col-english {
  width: 30%;
}

.strings-admin__col-translation {
  width: 40%;
}

.strings-admin__col-status {
  width: 14%;
}

.strings-admin .table th,
.strings-admin .table td {
  text-align: left;
  vertical-align: middle;
}

.strings-admin__english {
  overflow-wrap: anywhere;
  word-break: break-word;
}

.strings-admin .table th:last-child,
.strings-admin .table td:last-child {
  white-space: nowrap;
}

.strings-admin__translation {
  min-width: 0;
}

.strings-admin__value {
  width: 100%;
  min-height: 48px;
  font-size: var(--fs-base);
  padding: 10px 14px;
  box-sizing: border-box;
}

.strings-admin .btn.btn--danger-quiet {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}

.strings-admin .btn.btn--danger-quiet:hover:not(:disabled),
.strings-admin .btn.btn--danger-quiet:hover:not(:disabled) .icon,
.strings-admin .btn.btn--danger-quiet:hover:not(:disabled) svg {
  color: #FF0000;
  border-color: transparent;
  background: transparent;
}

/* Import translations panel — same compact header as Add language
   (client, 22 September). */
.strings-admin__import {
  margin-top: var(--sp-3);
}
.strings-admin__import > .card__header {
  padding: var(--sp-3) var(--sp-4);
  min-height: 0;
  gap: var(--sp-3);
  align-items: center;
}
.strings-admin__import > .card__header .card__title {
  margin: 0;
  line-height: 1.25;
  font-size: var(--fs-base);
  font-weight: 600;
}
.strings-admin__import > .card__header .strings-admin__cancel {
  min-height: 0;
  height: auto;
  padding: 0.1rem 0.55rem;
  font-size: var(--fs-xs);
  line-height: 1.25;
  box-sizing: border-box;
  background: transparent;
  text-transform: lowercase;
}
.strings-admin__import > .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}
.strings-admin__import form.stack--sm {
  gap: 0.55rem;
}
.strings-admin__import .field {
  gap: 0.2rem;
}
.strings-admin__import .field__hint {
  margin: 0;
  line-height: 1.25;
}

.packages-admin .btn--package-edit {
  background: transparent;
  color: #008000;
  border: 1px solid #008000;
  text-transform: lowercase;
  box-shadow: none;
}

.packages-admin .btn--package-edit:hover:not(:disabled) {
  background: transparent;
  color: #006400;
  border-color: #006400;
}

.packages-admin .btn--package-edit svg {
  color: inherit;
  stroke: currentColor;
}

/* Broker ad rates — tight body + compact market rows; fold is wired in the template. */

.packages-admin .packages-rate-card > .card__header,
.packages-admin .packages-rate-card > .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.packages-admin .packages-rate-card > .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.packages-admin .packages-rate-card form.stack,
.packages-admin .packages-rate-card form.stack--sm {
  gap: var(--sp-2);
}

.packages-admin .packages-rate-card .field {
  gap: 2px;
}

.packages-admin .packages-rate-card__fields {
  gap: var(--sp-2);
}

.packages-admin .packages-rate-card .field__hint {
  margin-top: 0;
  line-height: 1.25;
}

.packages-admin .packages-rate-card .rate-table {
  max-height: 320px;
}

.packages-admin .packages-rate-card .input--rate {
  min-height: 28px;
  padding-block: 2px;
}

/* Broker on the map — compact ladder; header cells match the card. */

.packages-admin .packages-ladder-card > .card__header,
.packages-admin .packages-ladder-card > .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.packages-admin .packages-ladder-card > .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.packages-admin .packages-ladder-card > .card__body.stack,
.packages-admin .packages-ladder-card > .card__body.stack--sm,
.packages-admin .packages-ladder-card form.stack,
.packages-admin .packages-ladder-card form.stack--sm {
  gap: var(--sp-2);
}

.packages-admin .packages-ladder-card .field {
  gap: 2px;
}

.packages-admin .packages-ladder-card__add {
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-2);
}

.packages-admin .packages-ladder-card .table th,
.packages-admin .packages-ladder-card .table td {
  padding: 6px var(--sp-2);
  vertical-align: middle;
}

.packages-admin .packages-ladder-card .table th {
  background: transparent;
}

.packages-admin .packages-ladder-card .input--rate {
  min-height: 28px;
  padding-block: 2px;
}

.packages-admin .packages-ladder-card .row-grip__move .btn {
  min-height: 24px;
  padding: 0 var(--sp-1);
}

.packages-admin .packages-ladder-card .btn.ladder-trash:hover:not(:disabled),
.packages-admin .packages-ladder-card .btn.ladder-trash:hover:not(:disabled) .icon,
.packages-admin .packages-ladder-card .btn.ladder-trash:hover:not(:disabled) svg {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
}

/*
  Cookie page layout — client, 21 September: settings card centred between
  header and footer; privacy note tight under the card, all lowercase.
*/

.page:has(.cookies-page) .page__main {
  display: flex;
  flex-direction: column;
}

.cookies-page {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.cookies-page > .page-head {
  flex: none;
}

.cookies-page__stage {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: var(--sp-4);
}

.cookies-page__stage > .container {
  width: 100%;
}

.cookies-page__note {
  margin: var(--sp-2) 0 0;
  text-transform: lowercase;
}

.btn--cookie {
  background: #008000;
  color: #fff;
  border-color: #008000;
  text-transform: lowercase;
}

.btn--cookie:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
  color: #fff;
}

[data-theme='dark'] .btn--cookie,
[data-theme='dark'] .btn--cookie:hover:not(:disabled) {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

[data-theme='dark'] .btn--cookie:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
}

/*
  No golden focus frame on the pill. Dark mode's --focus-ring is gold; after a
  click the track kept that ring painted around the toggle. Keyboard focus
  still moves; it just does not draw the selector.
*/

.switch input:focus,
.switch input:focus-visible + .switch__track {
  outline: none;
  box-shadow: none;
}

/* ==========================================================================
   Badges, chips, pills
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--r-sm);
  font-size: var(--fs-2xs);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.badge svg {
  width: 12px;
  height: 12px;
  flex: none;
}

/* Sale, rent and short rent share one chip colour so deal type is read from
   the wording, not from three near-greys fighting each other on the photo. */
.badge--sale,
.badge--rent {
  background: var(--slate);
  color: #fff;
}

[data-theme='dark'] .badge--sale,
[data-theme='dark'] .badge--rent {
  color: var(--text-inverse);
}

.badge--featured {
  background: var(--gold);
  color: var(--text-on-gold);
}

/*
  Search bump — paid placement, same height as .badge--owner.

  Violet Previa logo ground (#6F006F), lime #00FF00 copy and sparkle
  ("magic sign"), so a bumped listing is visibly advertising rather than
  looking like the closest or newest organic result.
*/

.badge--bumped {
  background: #6F006F;
  color: #00FF00;
  border: 1px solid #6F006F;
}

.badge--bumped svg {
  width: 13px;
  height: 13px;
  color: #00FF00;
  stroke: #00FF00;
}

.badge--neutral {
  background: var(--surface-3);
  color: var(--text-muted);
}

/* Banner placement chip (home / search) in the ADS table.
   The client, 16 September: green #008000 background, white text. */
.badge--placement {
  background: #008000;
  color: #fff;
}

.badge--success {
  background: var(--success-bg);
  color: var(--success);
  border: 1px solid var(--success-border);
}

.badge--warning {
  background: var(--warning-bg);
  color: var(--warning);
  border: 1px solid var(--warning-border);
}

.badge--error {
  background: var(--error-bg);
  color: var(--error);
  border: 1px solid var(--error-border);
}

.badge--info {
  background: var(--info-bg);
  color: var(--info);
  border: 1px solid var(--info-border);
}

.badge--outline {
  background: var(--surface);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

/* Active filter chip with a remove control. */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: 34px;
  padding: 0 var(--sp-2) 0 var(--sp-3);
  background: var(--navy-tint);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text);
}

.chip__remove {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  color: var(--text-muted);
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

/*
  The drawn control stays 22px so the chip row keeps its density, but the area
  a finger has to hit is 32px. Property type can now put several chips in a
  row, so these are pressed far more often than when there was one.
*/

.chip__remove::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32px;
  height: 32px;
  transform: translate(-50%, -50%);
}

.chip__remove:hover {
  background: var(--surface);
  color: #FF0000;
}

/*
  A tag with no remove control — the last remaining deal type, which cannot be
  dropped because "no deal type" is not a state the search has. Padded evenly,
  since there is no button to leave room for on the right, and quieter than its
  removable neighbours so the missing × reads as deliberate.
*/

.chip--fixed {
  padding: 0 var(--sp-3);
  background: var(--surface-3);
  color: var(--text-muted);
}

/* The way back into the filter drawer, sitting at the end of the tag bar so
   adding a tag and removing one are in the same place. */

.chip--add {
  padding: 0 var(--sp-3);
  background: none;
  border-style: dashed;
  color: var(--text-muted);
}

.chip--add:hover {
  border-color: var(--border-strong);
  color: var(--text);
  background: var(--surface-3);
}

.chip--add svg {
  width: 14px;
  height: 14px;
}

.chip__remove svg {
  width: 12px;
  height: 12px;
}

/* The filter panel's save confirmation: one line, in the success green, under
   the footer's three controls. */

.save-search-line {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--success);
}

.save-search-line svg {
  width: 14px;
  height: 14px;
  flex: none;
}

.save-search-line a {
  color: inherit;
  text-decoration: underline;
}

/*
  Clear-all, in the active-filter bar.

  "Just the 'Clear all' button replace with red cross (#FF0000) inside rounded
  rectangle." So: the rectangle is the button, the cross is its whole content,
  and the red is the client's literal #FF0000 in both themes rather than
  --error, which is a muted brick in light and a salmon in dark.

  Sized to the 34px chips beside it so the bar stays one row of equal-height
  objects, and square, because a rounded rectangle around a cross with nothing
  else in it should not be wider than it is tall.
*/

.clear-filters {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: #FF0000;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.clear-filters:hover {
  background: var(--surface-3);
  border-color: #FF0000;
}

.clear-filters svg {
  width: 16px;
  height: 16px;
  stroke-width: 2.2;
}

/* ==========================================================================
   Cards
   ========================================================================== */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}

/* Table flush to the card edges — clip row backgrounds to the card radius. */
.card--flush {
  overflow: hidden;
}

.card__body {
  padding: var(--sp-5);
}

.card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border-bottom: 1px solid var(--border);
}

.card__title {
  font-size: var(--fs-h4);
  font-weight: 600;
}

.card__footer {
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/*
  A card that folds, the way an FAQ question folds.

  The client, 7 September, about /admin/integrations: "make the same system as
  in the FAQ that can close this section. So that by default only the upper
  line with the configured / not configured is to see. And if click then this
  menu section opens."

  The header becomes the button, so the whole line is the hit area rather than
  a chevron somebody has to aim at. Two details are what make it look like one
  line instead of a card with an empty body:

  - the rule under the header is drawn only while the card is open. A closed
    card would otherwise show a border immediately above its own bottom border,
    which reads as a section that failed to render.
  - the chevron turns, and it is the same 18px mark and the same rotation the
    public FAQ uses, because the client is comparing the two screens.

  padding is restated: button carries none from the reset in base.css, and the
  shorthand there wins over .card__header's own.
*/

.card__fold {
  width: 100%;
  padding: var(--sp-5);
  text-align: left;
  border-bottom-color: transparent;
}

.card--fold.is-open > .card__fold {
  border-bottom-color: var(--border);
}

.card__fold .card__title {
  font-weight: 600;
}

.card__fold svg {
  width: 18px;
  height: 18px;
  color: var(--text-muted);
  flex: none;
  transition: transform var(--dur) var(--ease);
}

.card--fold.is-open > .card__fold svg {
  transform: rotate(180deg);
}

/* A divider between two halves of one card's body — the same hairline the
   card's own header and footer draw, without borrowing a class named after the
   screen that happened to need one first. */

.card__rule {
  height: 1px;
  margin: 0;
  background: var(--border);
  border: 0;
}

/* --- Property card -------------------------------------------------------- */

.pcard {
  position: relative;
  /*
    The card's own layers stay inside the card.

    Badges, hearts, paging zones and the pager all carry z-index values of their
    own (2 to 4, see the stacking note further down). A positioned element with
    z-index: auto is not a stacking context, so before this line those numbers
    were competing with everything else on the page: a heart at 4 painted over
    an open market menu, and the badges climbed over the homepage's property-type
    panel. The client reported both as menus "staying behind" the cards.

    isolation: isolate makes the card a stacking context without changing where
    the card itself paints, so the internal order is preserved and the whole card
    now sits below any panel that opens over it. It also matches what already
    happened on hover, where the lift transform created a context anyway — which
    is why the bug looked intermittent.
  */
  isolation: isolate;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}

.pcard:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--border-strong);
}

/* No gold/themed focus frame when a card (or the add-listing preview) is
   clicked or tabbed into — client, 21 September. Pager still shows via the
   separate .pcard:focus-within .pcard__pager rule. */

.pcard__media {
  position: relative;
  aspect-ratio: 4 / 3;
  /* consistent ratio prevents distortion and layout shift */
  overflow: hidden;
  background: var(--surface-3);
}

.pcard__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* never stretch */
  transition: transform var(--dur-slow) var(--ease);
}

.pcard:hover .pcard__media img {
  transform: scale(1.045);
}

.pcard__badges {
  position: absolute;
  top: var(--sp-1);
  left: var(--sp-1);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex-wrap: nowrap;
  gap: var(--sp-2);
  max-width: calc(100% - 76px);
  /* Labels, not controls. Without this they sit above the paging zones and
     the card link and swallow clicks aimed at the photograph beneath them. */
  pointer-events: none;
}

/* Deal chip only: hug the wording. Featured / owner keep the shared .badge pad
   so the star and key still have room; their left edge stays with this stack. */
.pcard__badges .badge--sale,
.pcard__badges .badge--rent {
  padding: 1px 6px;
}

/*
  Favourite control, Airbnb style.

  Two states, exactly as the client described them on 17 August:

    not saved  nothing but the red outline. The heart's body was a translucent
               grey, which on a photograph read as a grey plate sitting behind
               the glyph — "make it's background transparent, the borderline red
               and background transparent … so that only the red heart
               bordereline on the image and that's it". The fill is now none, so
               the photograph shows through the middle of the heart
    saved      red throughout, and no outline of a second colour — the glyph is
               one solid red heart

  The red is --fav, a literal #FF0000 that does not change between themes:
  "this featured heart borderline make #FF0000 (in day and dark theme the same)
  and if clicked then the whole heart color the same #FF0000". It was --error,
  which is a muted brick in the light theme and a salmon in the dark one — the
  pink outline the client was looking at.

  Both states use one drawing and one colour, so the change is the fill turning
  on rather than a second icon.

  What is not a background: the drop shadow. It is cast by the stroke itself,
  costs no area of its own, and is the only thing keeping a thin red line
  legible over a red-brick wall or a sunset. Without it an unsaved heart
  disappears on perhaps one photograph in ten.

  The button keeps a 36px box for the tap target even though only the ~22px
  glyph is drawn.
*/

.pcard__fav {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--fav);
  transition: color var(--dur) var(--ease), transform var(--dur-fast) var(--ease);
}

/*
  The outline is the whole drawing. 1.75 rather than 2: a heavier stroke on a
  22px glyph starts to close the gap in the notch at the top and the heart
  reads as filled when it is not.
*/

.pcard__fav svg {
  width: 22px;
  height: 22px;
  fill: none;
  /* transparent — the photograph shows through */
  stroke: var(--fav);
  /* the red outline, and nothing else */
  stroke-width: 1.75;
  stroke-linejoin: round;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45));
}

.pcard__fav:hover {
  transform: scale(1.08);
}

/* Saved: the same red fills the body, so the glyph reads as one solid heart. */

.pcard__fav[aria-pressed='true'] svg {
  fill: var(--fav);
}

.pcard__fav:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  border-radius: 50%;
}

.pcard__count {
  position: absolute;
  bottom: var(--sp-3);
  right: var(--sp-3);
  pointer-events: none;
  /* decorative, same reasoning as the badges */
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 9px;
  background: rgba(8, 20, 32, 0.72);
  color: #fff;
  border-radius: var(--r-sm);
  font-size: var(--fs-2xs);
  font-weight: 500;
}

.pcard__count svg {
  width: 13px;
  height: 13px;
}

/*
  Card text block.

  20 September: price tight under the image, €/m² tight under the price, then
  title / location / facts / seller / distance / messengers packed up. Image
  size unchanged.
*/

.pcard__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px var(--sp-4) var(--sp-3);
  flex: 1;
}

.pcard__price {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  font-family: var(--font-sans);
  /* prices always use Inter */
  font-size: var(--fs-h4);
  font-weight: 700;
  letter-spacing: var(--ls-tight);
  color: var(--text);
  line-height: 1.1;
  margin: 0;
}

.pcard__ppm {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: 1.1;
  color: var(--text-muted);
}

.pcard__price-period {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text-muted);
}

.pcard__price-old {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text-subtle);
  text-decoration: line-through;
}

/*
  A two-line title sets as one block: "Panelised house in / Grunewald" should
  read as a single name, and at 1.4 the two lines drifted apart far enough to
  look like separate fields. 1.18 is as close as the ascenders and descenders
  allow without them touching, which is what the client asked for.
*/

.pcard__title {
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.18;
  margin: 0;
}

.pcard__title a::after {
  /* Full-card click target without nesting interactive elements. */
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

.pcard__title a,
.pcard__title a:hover,
.pcard__title a:focus,
.pcard__title a:focus-visible {
  color: var(--text);
}

/* Same treatment as the title: a wrapped address — "Grunewald, Berlin, /
   Germany" — is one place, so it sets as one block. */

.pcard__location {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  font-size: var(--fs-sm);
  line-height: 1.18;
  color: var(--text-muted);
}

/* The pin sits on the first line's cap height, which moves with line-height. */

.pcard__location svg {
  flex: none;
  width: 15px;
  height: 15px;
  margin-top: 1px;
  /* Same violet as the Previa logo tile (client, listings page). */
  color: #8B008B;
}

.pcard__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding-top: var(--sp-2);
  margin-top: var(--sp-1);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.pcard__fact {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.pcard__fact svg {
  width: 15px;
  height: 15px;
  color: var(--text-subtle);
}

.pcard__fact strong {
  color: var(--text);
  font-weight: 600;
}

.pcard__seller {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--sp-3);
  font-size: var(--fs-xs);
  line-height: 1.2;
  color: var(--text-muted);
}

/*
  Controls that must stay above the media overlays.

  The heart has to clear the paging zones as well as the link, and those sit at
  z-index 2, so 4 is the floor for it — see the stacking note in the carousel
  section. The badges are labels with pointer-events: none, so 2 is enough to
  keep them drawn over the photograph.
*/

.pcard__fav,
.pcard__actions {
  z-index: 4;
}

.pcard__badges {
  z-index: 2;
}

/* --- Horizontal list card ------------------------------------------------- */

.pcard--row {
  flex-direction: row;
}

.pcard--row .pcard__media {
  width: 300px;
  flex: none;
  aspect-ratio: 4 / 3;
}

.pcard--row .pcard__body {
  padding: var(--sp-5) var(--sp-6);
}

@media (max-width: 720px) {
  .pcard--row {
    flex-direction: column;
  }

  .pcard--row .pcard__media {
    width: 100%;
  }

  .pcard--row .pcard__body {
    padding: var(--sp-4);
  }
}

/* --- Compact card (map sidebar, dense grids) ------------------------------ */

/*
  Listings grid uses this variant. Do not re-open the body top pad — the base
  .pcard__body already sits the price tight under the photograph (client:
  reduce image→price distance ~80%, leave preview image size alone).
*/
.pcard--compact .pcard__body {
  padding: 2px var(--sp-4) var(--sp-3);
  gap: 2px;
}

.pcard--compact .pcard__price {
  font-size: var(--fs-lg);
}

.pcard--compact .pcard__title {
  font-size: var(--fs-sm);
}

.pcard--compact .pcard__facts {
  font-size: var(--fs-xs);
  gap: var(--sp-2);
  padding-top: var(--sp-2);
}

.pcard--compact .pcard__fav {
  width: 32px;
  height: 32px;
}

/*
  The grid card's seller line is two rows, each on one line. The client, 14
  September: "this average m2 price is on two rows, which is bad UI. Make that
  the 'cottage - broker listing' is in one row, and the 1830 / m2 under it in
  one row." Side by side in a ~280px card both halves wrapped, and the price
  broke at the space inside "€1 830".
*/

.pcard--compact .pcard__seller {
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 2px;
}

.pcard--compact .pcard__seller > span {
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Map-sidebar card: horizontal, tight, highlights on marker hover. */

.pcard--map {
  flex-direction: row;
  border-radius: var(--r-md);
}

.pcard--map .pcard__media {
  width: 132px;
  flex: none;
  aspect-ratio: 4 / 3;
}

.pcard--map .pcard__body {
  padding: var(--sp-3) var(--sp-4);
  gap: var(--sp-1);
}

.pcard--map .pcard__price {
  font-size: var(--fs-base);
}

.pcard--map .pcard__title {
  font-size: var(--fs-xs);
  font-weight: 500;
}

.pcard--map .pcard__facts {
  border: 0;
  padding-top: var(--sp-1);
  font-size: var(--fs-xs);
}

.pcard--map .pcard__fav {
  /* Same size as map grid hearts; tight to the photo's top-right. */
  width: 32px;
  height: 32px;
  top: 2px;
  right: 2px;
}

.pcard--map .pcard__fav svg {
  width: 18px;
  height: 18px;
}

.pcard--map.is-active,
.pcard--map:hover {
  /* Selector frame: green #008000 (was gold). Lift matches .pcard:hover so a
     map-label hover moves the sidebar card the same way as hovering the card. */
  transform: translateY(-4px);
  border-color: #008000;
  box-shadow: 0 0 0 1px #008000, var(--shadow-md);
}

.bcard--map.is-active,
.bcard--map:hover {
  transform: translateY(-4px);
  border-color: #008000;
  box-shadow: 0 0 0 1px #008000, var(--shadow-md);
}

/* --- Featured card -------------------------------------------------------- */

/*
  And the same gold behind a featured listing.

  The client, 5 September, in the message that made the premium package gold on
  both themes: "And then the featured ads as well do with this golden
  background." A featured card had the gold border and the gold badge but the
  ordinary card surface behind them, so in a grid of eight it was a hairline's
  worth of difference — which is little to show for what the placement costs.

  --gold-tint again, the same token the premium package takes, so a featured ad
  and the package that buys it are the same colour in daylight and after dark.
  The photograph covers the top of the card either way; what this tints is the
  block of text under it.
*/

.pcard--featured {
  border-color: var(--gold-border);
  background: var(--gold-tint);
}

.pcard--featured::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-lg);
  box-shadow: inset 0 0 0 1px var(--gold-border);
  pointer-events: none;
  z-index: 3;
}

/* ==========================================================================
   Broker / agency / development cards
   ========================================================================== */

/* Broker card.
   A grid row must look even regardless of how long each broker's title or
   agency name is, so the card is a flex column with a fixed-height identity
   block and the action section pushed to the bottom by `margin-top:auto`
   rather than by hand-tuned margins. */

.bcard {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  /* Hug content — grid stretch left empty air under the chevron. */
  height: auto;
  align-self: start;
  /* No padding at the top: the photograph is full-bleed to the card's edges,
     which is what the kinnisvara24 reference does and what makes it read as a
     paid placement rather than as a directory row.
     Bottom padding matches the hairline→chevron gap so the border sits as
     close under the arrow as the arrow sits under the separator. */
  padding: 0 var(--sp-5) 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  text-align: center;
  box-shadow: var(--shadow-xs);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}

.bcard:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--border-strong);
}

/* No focus-within ring: opening the contact fold left a bold selector under
   the ad. Keyboard focus stays on the controls themselves. */

/*
  The photograph.

  The client's note was about size: "make the broker profile picture the same
  size (only with rounded corners) as in kinnisvara24". Their reference fills
  the top of the card with a 4:5 portrait rather than showing an 88px medallion,
  so this is the card's full width, negative-margined out through the padding
  either side to sit flush with the border.

  4:5 rather than a square: these are portraits, and a square crop takes the top
  of the head or the bottom of the chin off half of them. `object-position:
  center top` keeps the face in frame whatever the source ratio.

  Only the top corners are rounded — the bottom two are square because the card
  carries on underneath. The radius is the card's less its 1px border, so the
  photograph follows the border rather than leaving a sliver of surface showing
  in the curve.
*/

.bcard__media {
  position: relative;
  align-self: stretch;
  margin: 0 calc(var(--sp-5) * -1) 0;
  overflow: hidden;
  border-radius: calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
  background: var(--surface-3);
}

.bcard__photo {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center top;
}

/* The listing count, over the photograph's top-left corner as the reference
   has it. Squared off on the outer two corners so it reads as a flag pinned to
   the edge rather than a pill floating on the picture. */

.bcard__count {
  position: absolute;
  top: 0;
  left: 0;
  padding: 5px 10px;
  border-radius: 0 0 var(--r-sm) 0;
  /* Same #8B008B as the Previa logo tile. */
  background: #8B008B;
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
}

[data-theme='dark'] .bcard__count {
  background: #8B008B;
  color: #fff;
}

/* Fixed identity block: names line up across the row even when the role or
   agency wraps to two lines. */

.bcard__identity {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-height: 0;
  justify-content: flex-start;
  width: 100%;
  /* ~40% of the previous gap under the photo (reduce distance by 60%). */
  margin-top: 0.2rem;
}

.bcard__name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 0;
  font-size: var(--fs-base);
  font-weight: 700;
  line-height: 1.15;
}

.bcard__name a:hover {
  color: var(--teal);
}

.bcard__place {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-subtle);
}

.bcard__place .icon {
  width: 13px;
  height: 13px;
  /* Same violet as the frontpage banner location pin. */
  color: #8B008B;
}

.bcard__role {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  line-height: 1.2;
}

.bcard__agency {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text);
  font-weight: 600;
  line-height: 1.2;
}

.bcard__location {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-size: var(--fs-xs);
  color: var(--text-subtle);
}

.bcard__location .icon {
  width: 13px;
  height: 13px;
  color: #8B008B;
}

/* Contact sits directly under the identity — no empty stretch above the rule. */

.bcard__foot {
  margin-top: 0.15rem;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Collapsed contact / Active in / languages — chevron under the hairline. */

.bcard__more {
  width: 100%;
}

.bcard__more-toggle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  width: 100%;
  margin: 0;
  /* Equal air above the icon (under the rule) and below it (to the card edge). */
  padding: 0.1rem 0 0.1rem;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.bcard__more-toggle:hover,
.bcard__more-toggle:focus,
.bcard__more-toggle:focus-visible {
  color: var(--text);
  outline: none;
  box-shadow: none;
}

.bcard__more-rule {
  display: block;
  width: 100%;
  border-top: 1px solid var(--border);
}

.bcard__more-toggle .icon {
  width: 18px;
  height: 18px;
  transition: transform var(--dur) var(--ease);
}

.bcard__more.is-open .bcard__more-toggle .icon {
  transform: rotate(180deg);
}

.bcard__more.is-open .bcard__more-toggle {
  padding-bottom: 0;
}

.bcard__more-panel {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  width: 100%;
  /* Same hairline gap as separator→arrow — email sits tight under the chevron. */
  padding-top: 0.1rem;
  padding-bottom: 0.1rem;
}

/* The row that used to sit here carried a star rating and a paid-placement
   label; the client removed both on 19 August. */

.bcard__contact {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-xs);
}

.bcard__more-panel .bcard__contact {
  padding-top: 0;
  border-top: 0;
}

.bcard__contact a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  max-width: 100%;
  color: var(--text-muted);
}

.bcard__contact a:hover {
  color: #00008B;
}

[data-theme='dark'] .bcard__contact a:hover {
  color: #0000FF;
}

/* Agency card: email / phone hover is darkgoldenrod in both themes. */
.bcard--agency .bcard__contact a:hover {
  color: #B8860B;
}

[data-theme='dark'] .bcard--agency .bcard__contact a:hover {
  color: #B8860B;
}

.bcard__contact .icon {
  width: 14px;
  height: 14px;
}

.bcard__contact .truncate {
  max-width: 100%;
}

/* The apps ticked under each telephone, centred under the number they reach. */

.bcard__contact .msg-links {
  justify-content: center;
}

.bcard__contact .msg-links--sm {
  margin-top: 0;
}

/* Both actions share the row and keep equal height. */

.bcard__actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-2);
}

.bcard__actions .btn {
  width: 100%;
}

@media (max-width: 380px) {
  .bcard__actions {
    grid-template-columns: 1fr;
  }
}

/* One action on its own takes the whole row rather than half of it — an agency
   with no page to open, or none to write to, would otherwise get a button
   stranded in the left-hand column. */

.bcard__actions .btn:only-child {
  grid-column: 1 / -1;
}

/*
  Active in, then languages of communication — where the broker ad's Contact
  button was. Same blocks as the listing seller box (seller-active-in /
  seller-languages), centred in the card foot like the rest of that column.
*/

.bcard__foot .seller-active {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 100%;
  margin-top: 0;
}

.bcard__foot .seller-active__title {
  margin: 0;
  font-size: var(--fs-2xs);
  font-weight: 600;
  line-height: 1.3;
  color: var(--text-subtle);
  text-align: center;
}

.bcard__foot .seller-active__list {
  justify-content: center;
}

/*
  Agency card — the homepage Agencies strip.

  The client, 31 August: "under the brokers section in the frontpage make the
  same agencies section." So it is the broker card, and everything above applies
  to it unchanged: the same cell, the same identity block, the same foot.

  One thing had to differ. A broker card leads with a 4:5 portrait; a company's
  mark is a wordmark, usually wide and usually on its own background, and a
  portrait frame would crop it — which is the same objection the client raised
  about the company logo in the profile, "the logo's box is the larger and the
  wider of the two". So the media band is a short landscape panel and the mark
  is contained inside it, never cropped and never stretched.

  The client, 14 September: "the agency banner make bigger and remove the white
  background from there. For the agency banner use the whole room from the left
  edge of the window till to the right, so that it barely touches the borders,
  but the rounded corners are still there."

  17 September: stretch edge to edge — no inset padding — and let the media
  band's own top radius (overflow:hidden) keep the rounded corners visible.
*/

.bcard__media--logo {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 9;
  padding: 0;
}

.bcard__logo {
  display: block;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  border-radius: 0;
  object-fit: cover;
  object-position: center;
}

/* No mark uploaded. Band is brand violet; the centre tile is green with a
   white house — so an empty company slot still reads as a placement, not as a
   broken image. */

.bcard__media--logo:has(.bcard__logo--empty) {
  background: #8B008B;
}

.bcard__logo--empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--r-md);
  background: #008000;
  color: #8B008B;
}

.bcard__logo--empty svg {
  width: 26px;
  height: 26px;
}

/*
  Agency ads sit in the same .bcard shell as brokers, but they usually have
  only a name under the banner — the 76px identity block and margin-top:auto
  foot were sized for a broker portrait with role, agency and city. That left
  a tall empty band between the company name and the e-mail line.

  Side padding must stay var(--sp-5) so the media's negative margin
  (calc(var(--sp-5) * -1)) cancels it exactly. A tighter side padding left the
  banner and the listings badge hanging outside the card.
*/
.bcard--agency {
  height: auto;
  gap: var(--sp-2);
  padding: 0 var(--sp-5) var(--sp-3);
}
.bcard--agency:hover {
  transform: none;
}
.bcard--agency .bcard__media {
  margin-bottom: 0;
}
.bcard--agency .bcard__identity {
  min-height: 0;
  gap: 2px;
  padding: var(--sp-2) 0 0;
}
.bcard--agency .bcard__name {
  line-height: 1.2;
}
.bcard--agency .bcard__role,
.bcard--agency .bcard__location {
  line-height: 1.25;
}
.bcard--agency .bcard__foot {
  margin-top: var(--sp-2);
  gap: var(--sp-2);
}
.bcard--agency .bcard__contact {
  padding-top: var(--sp-2);
  gap: 4px;
}

/* The company placement's dialog is rendered outside the profile form and holds
   nothing but a modal, so its root draws no box of its own. See
   components/agency-ad.html for why it cannot live inside that form. */

.agency-ad {
  display: block;
  flex: none;
}

/* Development card — wider cover, price-from emphasis.
   Photograph and body ground run edge to edge: no border, no inset frame. */

.dcard {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Day: light grey face under the photo / title / Kalamaja block. */
  background: #F5F5F5;
  border: 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: none;
  padding: 0;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

[data-theme='dark'] .dcard {
  background: var(--surface);
}

.dcard:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
}

.dcard__media {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  margin: 0;
  padding: 0;
  border-radius: 0;
  background: var(--surface-3);
}

.dcard__media .pcard__track {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.dcard__media > img,
.dcard__media .pcard__slide {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
  transition: transform var(--dur-slow) var(--ease);
}

.dcard:hover .dcard__media > img {
  transform: scale(1.045);
}

/* Same carousel as property cards — do not zoom the sliding track. */
.dcard:hover .pcard__track .pcard__slide {
  transform: none;
}

/* Pager sits on the bottom edge; lift the quarter ribbon clear of it. */
.dcard__media--gallery .dcard__ribbon {
  bottom: 44px;
}

.dcard__ribbon {
  position: absolute;
  left: 0;
  bottom: var(--sp-4);
  z-index: 3;
  padding: 6px var(--sp-4);
  background: var(--gold);
  color: var(--text-on-gold);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}

.dcard__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
  flex: 1;
}

.dcard__place {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.dcard__place .pcard__location svg {
  color: #8B008B;
}

.dcard__by {
  margin: 0;
}

.dcard__name {
  font-size: var(--fs-h4);
  font-weight: 600;
}

.dcard__name a::after {
  content: '';
  position: absolute;
  inset: 0;
}

.dcard__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin-top: auto;
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ==========================================================================
   Alerts
   ========================================================================== */

.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid;
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

.alert svg {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 1px;
}

.alert__title {
  font-weight: 600;
  margin-bottom: 2px;
}

.alert--success {
  background: var(--success-bg);
  border-color: var(--success-border);
  color: var(--success);
}

.alert--warning {
  background: var(--warning-bg);
  border-color: var(--warning-border);
  color: var(--warning);
}

.alert--error {
  background: var(--error-bg);
  border-color: var(--error-border);
  color: var(--error);
}

.alert--info {
  background: var(--info-bg);
  border-color: var(--info-border);
  color: var(--info);
}

.alert--neutral {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-muted);
}

/*
  Checkout invoices note, 16 September.

  Green #008000 box with white copy throughout — no link on "billing".
*/

.alert--checkout-invoices {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

.alert p {
  color: inherit;
}

/* Add-listing preview: fields still need attention. */

.alert--listing-attention {
  align-items: stretch;
  background: #FF4500;
  border-color: #FF4500;
  color: #fff;
}

.alert--listing-attention__glyph {
  display: block;
  flex: none;
  align-self: stretch;
  aspect-ratio: 1;
}

.alert--listing-attention__glyph svg {
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
}

.alert--listing-attention .linklike {
  text-decoration: none;
}

/* ==========================================================================
   Tables
   ========================================================================== */

/* Wide tables scroll inside their own container — the page never scrolls sideways. */

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  -webkit-overflow-scrolling: touch;
}

/* Inside .card--flush: no second border or radius; the card clips the corners. */
.table-wrap--flush {
  border: 0;
  border-radius: 0;
  background: transparent;
}

.table {
  font-size: var(--fs-sm);
  min-width: 640px;
}

/* More columns than the panel is wide: the table keeps its width and scrolls
   sideways inside its .table-wrap. /admin/users since 14 September — "you can
   make this user section to scroll left-right like it is with payments". */

.table--wide {
  min-width: 1320px;
}

.table th {
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.table td {
  padding: var(--sp-4);
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.table tbody tr {
  transition: background-color var(--dur-fast) var(--ease);
}

.table tbody tr:hover {
  background: var(--surface-2);
}

/*
  Row actions.
  
  Six of them on a listing row — statistics, promote, edit, clone, activate,
  view, delete — which is what the client's lifecycle needs and more than fits
  on one line in a narrow column. They wrap rather than pushing the row wider
  than the table, and the gap is tighter than the page default because these are
  icon buttons sitting shoulder to shoulder, not a row of labelled controls.
*/

.table__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: flex-end;
  align-items: center;
}

.table__actions--start {
  justify-content: flex-start;
}

.table__actions .btn--sm {
  padding-inline: 8px;
}

.table__actions > form {
  display: inline-flex;
  align-items: center;
  margin: 0;
}

/*
  Admin Developments (and any row that opts in): same quiet outline look as
  My listings — transparent fill, shared resting colour, trash red on hover.
*/

.table__actions--quiet .btn.btn--quiet {
  background: transparent;
}

.table__actions--quiet .btn.btn--danger-quiet {
  color: var(--text);
  border-color: var(--border);
  background: transparent;
}

.table__actions--quiet .btn.btn--danger-quiet:hover:not(:disabled),
.table__actions--quiet .btn.btn--danger-quiet:hover:not(:disabled) .icon {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
}

.btn--backup-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.35rem;
  min-height: 2.15rem;
  padding: 0.35rem 0.55rem;
  background: #b30000 !important;
  color: #fff !important;
  border: 0;
  border-radius: 0.25rem;
}
.btn--backup-delete:hover:not(:disabled) {
  background: #b30000 !important;
  color: #fff !important;
  filter: none;
}
.btn--backup-delete svg {
  width: 1.05rem;
  height: 1.05rem;
  stroke: currentColor;
}

/*
  Edit twin of the red recycle bin on banner (and similar) rows.

  The client, 16 September: green #008000, white icon, same size as the delete
  button beside it, and centred on the up/down scale of the row.
*/
.btn--row-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.35rem;
  min-height: 2.15rem;
  padding: 0.35rem 0.55rem;
  background: #008000 !important;
  color: #fff !important;
  border: 0;
  border-radius: 0.25rem;
}
.btn--row-edit:hover:not(:disabled) {
  background: #008000 !important;
  color: #fff !important;
  filter: none;
}
.btn--row-edit svg {
  width: 1.05rem;
  height: 1.05rem;
  stroke: currentColor;
}

/* Sponsored banners table: tall preview, upload icon in Actions. */

#sponsored-banners .banner-row > td {
  vertical-align: middle;
}

#sponsored-banners .banner-row-market {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  line-height: 1;
}

#sponsored-banners .banner-row-market .flag {
  display: block;
  flex: none;
}

#sponsored-banners .banner-row-market__code {
  font-size: var(--fs-sm);
  line-height: 1;
  position: relative;
  top: 1px;
}

#sponsored-banners td.banner-row-art {
  padding-block: 0;
  width: 1%;
  white-space: nowrap;
}

#sponsored-banners .banner-row-thumb,
#sponsored-banners [data-banner-row] img {
  display: block;
  width: auto;
  height: 72px;
  max-width: 200px;
  object-fit: cover;
  border-radius: var(--r-sm);
}

#sponsored-banners .banner-row-preview {
  display: block;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  line-height: 0;
}

#sponsored-banners .banner-row-preview:focus-visible {
  outline: 2px solid #008000;
  outline-offset: 2px;
}

button.admin-image-preview {
  display: inline-block;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  line-height: 0;
  flex: none;
}

button.admin-image-preview:focus-visible {
  outline: 2px solid #008000;
  outline-offset: 2px;
}

#banner-lightbox[hidden] {
  display: none !important;
}

#sponsored-banners .banner-row-upload {
  display: inline-flex;
  margin: 0;
}

#sponsored-banners .banner-row-upload .btn--row-edit {
  cursor: pointer;
}

#sponsored-banners .table tbody tr:hover,
#sponsored-banners .table tbody tr.banner-row:hover {
  background: transparent;
}

/*
  A table in a window of its own.

  The client, 14 September, on the payment tables: "make there pls a scrollbar
  window, where they are inside. So that 10 rows would be to see and rest is
  scrolling", and fifty rows in the back office. previaRowWindow sets the exact
  height from the rows as drawn, because a package name that wraps makes one row
  taller than the next; the calc here is only the estimate before it has run.
  The column headings stay at the top of the window while the rows move.
*/

.table-scroll {
  overflow: auto;
  max-height: calc(var(--table-rows, 10) * 4.75rem + 3rem);
}

.table-scroll .table th {
  position: sticky;
  top: 0;
  z-index: 1;
  box-shadow: inset 0 -1px 0 var(--border);
}

/* The tick-box column on the left of a table rows can be selected in. */

.table th.table__pick,
.table td.table__pick {
  width: 1%;
  padding-right: 0;
}

.table__pick .check {
  min-height: 0;
  padding: 0;
}

.check input:indeterminate {
  background-color: #B8860B;
  border-color: #B8860B;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
}

[data-theme='dark'] .check input:indeterminate {
  background-color: #B8860B;
  border-color: #B8860B;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
}

/* Payments ledger select-all (partial): always #B8860B, never the light navy. */
#payment-ledger .table__pick .check input:indeterminate,
#payment-ledger .table__pick .check input.is-partial {
  -webkit-appearance: none;
  appearance: none;
  background-color: #B8860B !important;
  border-color: #B8860B !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
}

/* The line above a table: a count on the left, what to do with it on the right. */

.table-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

/*
  The actions column takes the width it needs and the columns of prose beside it
  give way, rather than the buttons wrapping into a second row inside a cell
  that is already tall enough. `width: 1%` with nowrap is the shrink-to-fit
  idiom: a table gives such a column its minimum content width and distributes
  what is left among the others.

  Wrapping stays as the fallback: below 700px the table restacks into cards and
  the row has the full width to play with, so the two never fight.
*/

@media (min-width: 701px) {
  .table td[data-label='Actions'],
  .table th:last-child {
    width: 1%;
    white-space: nowrap;
  }

  /*
    Any actions cell, not only one that labelled itself.

    data-label is what the phone-width stacking layout reads, and the admin
    tables do not carry it — so on those the buttons went on wrapping into a
    column inside a cell that had plenty of room beside it. Selecting on the
    thing itself is what the rule was always describing.
  */

  .table td[data-label='Actions'] .table__actions,
  .table td .table__actions {
    flex-wrap: nowrap;
  }

  .table td:last-child:has(.table__actions) {
    width: 1%;
    white-space: nowrap;
  }
}

/* --------------------------------------------------------------------------
   A table whose rows can be dragged into order

   The client, 10 September: "with the price packages make that can drag the
   order of them, so that the order change applies for the frontend as well."

   The grip is the affordance and the cursor is the promise: grab over the
   handle, grabbing while a row is in the air, and the row it came from left at
   40% so the gap it will land in is readable. The same three the gallery tiles
   use — see .photo-thumb.is-dragging — because it is the same gesture.

   The arrows beside the grip are the keyboard and touch version of it, and are
   what make this reorderable without a pointer at all.
   -------------------------------------------------------------------------- */

[data-sortable-rows] > tr {
  cursor: grab;
}

[data-sortable-rows] > tr.is-dragging {
  opacity: 0.4;
  cursor: grabbing;
}

/* A price ladder puts the handle on the grip instead of on the row, because
   every rung has a price box in it and a draggable ancestor takes the pointer
   away from the text inside an input. The cursor has to say the same thing:
   the row is not the handle there, the grip is. */

[data-sortable-rows] > tr:not([draggable]) {
  cursor: auto;
}

.row-grip[draggable="true"] {
  cursor: grab;
}

.row-grip[draggable="true"]:active {
  cursor: grabbing;
}

.row-grip__cell {
  width: 1%;
  white-space: nowrap;
  padding-right: 0;
}

.row-grip {
  display: inline-flex;
  color: var(--text-subtle, var(--text-muted));
  vertical-align: middle;
}

.row-grip .icon {
  width: 18px;
  height: 18px;
}

.row-grip__move {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  vertical-align: middle;
  margin-left: 2px;
}

.row-grip__move .btn {
  padding: 2px 4px;
  min-height: 0;
  line-height: 1;
}

.row-grip__move .icon {
  width: 14px;
  height: 14px;
}

.table--fixed {
  min-width: 0;
}

/* --------------------------------------------------------------------------
   Stacking table

   A table that needs seven columns has nowhere to go on a phone. Its wrapper
   scrolls, but a scroller the width of a phone shows a column and a half and
   slices the next heading down the middle, and the page itself cannot scroll
   sideways to reach the rest of it.

   Below 700px a table marked table--stack leaves table layout altogether and
   each row becomes a card: the head is hidden, every cell takes its own line,
   and the label that was in the column heading is drawn from the cell's
   data-label attribute. Above 700px none of this applies and the table is
   exactly as it was — which is why this sits after the base table rules rather
   than before them, so it is these declarations that win inside the query.
   -------------------------------------------------------------------------- */

@media (max-width: 700px) {
  .table-wrap--stack {
    overflow-x: visible;
    /* nothing overflows any more, so nothing scrolls */
    border: 0;
    background: none;
  }

  .table--stack {
    display: block;
    min-width: 0;
    width: 100%;
    border-collapse: separate;
  }

  .table--stack thead {
    display: none;
  }

  .table--stack tbody {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }

  .table--stack tr {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 2px var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
  }

  .table--stack tbody tr:hover {
    background: var(--surface);
  }

  .table--stack td {
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    align-items: center;
    justify-items: start;
    /* a badge or a button keeps its own width */
    padding: 3px 0;
    border: 0;
  }

  .table--stack tbody tr td:last-child {
    border-bottom: 0;
  }

  /* The label that used to be the column heading. */

  .table--stack td::before {
    content: attr(data-label);
    font-size: var(--fs-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-subtle);
    white-space: nowrap;
  }

  /* The property leads the card at full width and unlabelled — the photograph
     and the title already say what it is. */

  .table--stack td:first-child {
    grid-template-columns: minmax(0, 1fr);
    justify-items: stretch;
    padding-bottom: var(--sp-3);
    margin-bottom: var(--sp-1);
    border-bottom: 1px solid var(--border);
  }

  .table--stack td:first-child::before {
    content: none;
  }

  /* Actions close the card, pushed to the end of their row. */

  .table--stack td:last-child {
    justify-items: end;
    padding-top: var(--sp-2);
  }

  .table--stack .mini-row__text {
    min-width: 0;
  }
}

/* ==========================================================================
   Pagination
   ========================================================================== */

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.pagination__item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text);
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.pagination__item:hover:not([aria-disabled='true']) {
  background: var(--surface-3);
  border-color: var(--border-strong);
}

.pagination__item[aria-current='page'] {
  background: #008000;
  border-color: #008000;
  color: #fff;
  font-weight: 600;
}

.pagination__item[aria-disabled='true'] {
  opacity: 0.45;
  pointer-events: none;
}

.pagination__gap {
  color: var(--text-subtle);
  padding: 0 var(--sp-1);
}

/* ==========================================================================
   Tabs
   ========================================================================== */

.tabs {
  display: flex;
  gap: var(--sp-1);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
}

.tabs::-webkit-scrollbar {
  display: none;
}

.tab {
  position: relative;
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.tab:hover {
  color: var(--text);
}

/* Active tab: green #008000 title + underline, day and night
   (client, 23 September — Settings Profile / Preferences / Security / Account). */
.tab[aria-selected='true'],
.tab.is-active {
  color: #008000;
  border-bottom-color: #008000;
}

.tab:focus,
.tab:focus-visible {
  outline: none;
  box-shadow: none;
}

.tab__count {
  margin-left: 6px;
  padding: 1px 7px;
  background: var(--surface-3);
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

/* ==========================================================================
   Modal / dialog
   ========================================================================== */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
  background: var(--overlay);
  backdrop-filter: blur(2px);
}

.modal {
  width: 100%;
  max-width: 520px;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-panel);
}

.modal--lg {
  max-width: 780px;
}

.modal--sm {
  max-width: 420px;
}

.modal__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-6) var(--sp-6) var(--sp-4);
}

.modal__title {
  font-size: var(--fs-h4);
  font-weight: 600;
}

.modal__body {
  padding: 0 var(--sp-6) var(--sp-5);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: 1.6;
}

.modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-6);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 0 0 var(--r-xl) var(--r-xl);
}

@media (max-width: 520px) {
  .modal__footer {
    flex-direction: column-reverse;
  }

  .modal__footer .btn {
    width: 100%;
  }
}

/* Sign-out dialog — client, 21 September: solid green cancel / red sign-out;
   no gold focus ring on the close mark (x-trap used to land there). */
.modal--logout .modal--logout__close:focus,
.modal--logout .modal--logout__close:focus-visible,
.modal--logout .modal--logout__close:focus-visible:hover {
  outline: none !important;
  box-shadow: none !important;
  border-color: transparent !important;
}

.modal--logout .btn--logout-cancel {
  background: #008000 !important;
  border-color: #008000 !important;
  color: #fff !important;
  text-transform: lowercase;
  min-height: var(--control-h);
  padding: 0 var(--control-px);
}

.modal--logout .btn--logout-cancel:hover:not(:disabled) {
  background: #006400 !important;
  border-color: #006400 !important;
  color: #fff !important;
}

.modal--logout .btn--logout-cancel:focus-visible {
  outline: none !important;
  box-shadow: none !important;
}

.modal--logout .btn--logout-confirm {
  background: #B00000 !important;
  border-color: #B00000 !important;
  color: #fff !important;
  text-transform: lowercase;
  min-height: var(--control-h);
  padding: 0 var(--control-px);
}

.modal--logout .btn--logout-confirm:hover:not(:disabled) {
  background: #8B0000 !important;
  border-color: #8B0000 !important;
  color: #fff !important;
  filter: none;
}

.modal--logout .btn--logout-confirm:focus-visible {
  outline: none !important;
  box-shadow: none !important;
}

.modal--logout .btn--logout-confirm .icon {
  color: #fff !important;
  stroke: #fff !important;
}

/* ==========================================================================
   Dropdown menu
   ========================================================================== */

.menu {
  min-width: 200px;
  padding: var(--sp-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}

.menu__item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  color: var(--text);
  text-align: left;
  transition: background-color var(--dur-fast) var(--ease);
}

.menu__item:hover {
  background: var(--surface-3);
}

.menu__item svg {
  width: 16px;
  height: 16px;
  color: var(--text-muted);
  flex: none;
}

.menu__item.is-active {
  background: var(--navy-tint);
  font-weight: 600;
}

.menu__divider {
  height: 1px;
  margin: var(--sp-2) 0;
  background: var(--border);
}

.menu__label {
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
}

/* ==========================================================================
   Tooltip
   ========================================================================== */

.tooltip {
  position: absolute;
  z-index: var(--z-toast);
  padding: 6px 10px;
  background: var(--surface-inverse);
  color: var(--text-inverse);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  line-height: 1.4;
  white-space: nowrap;
  box-shadow: var(--shadow-md);
  pointer-events: none;
}

/* ==========================================================================
   Skeletons and loading
   ========================================================================== */

.skeleton {
  background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 37%, var(--surface-3) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
  border-radius: var(--r-sm);
}

@keyframes shimmer {
  0% {
    background-position: 100% 50%;
  }

  100% {
    background-position: 0 50%;
  }
}

.skeleton--text {
  height: 12px;
  margin-bottom: 8px;
}

.skeleton--title {
  height: 18px;
  width: 70%;
  margin-bottom: 12px;
}

.skeleton--media {
  aspect-ratio: 4 / 3;
  border-radius: 0;
}

.skeleton-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.skeleton-card__body {
  padding: var(--sp-5);
}

/* ==========================================================================
   Integrations — the credential fields
   ==========================================================================

   There used to be two environments per provider here, drawn so that the set in
   force was readable at a glance and the standing-by set was dimmed. Both sets
   are gone — "yes exactly, remove all sandbox and make all real", 3 September —
   so there is one column of ordinary fields and nothing to distinguish.

   .cred-field and its modifiers went with them rather than being left as dead
   rules: a class nothing renders is a class somebody re-uses for something else
   next year and cannot understand.
   -------------------------------------------------------------------------- */

/* ==========================================================================
   Back-office flash banner
   ==========================================================================

   "In SEO if click the button save then nothing says to the user it is done,
   the green banner comes on the very top of the page where noone sees it. Make
   that if click the button then the button reacts and this green banner comes
   to the top of the viewport by overlaying other things … this in everywhere
   that this green success banner comes always on top of the viewport overlaying
   other things."

   Every administrative form posts to /admin/action and swaps the answer into
   #admin-result, which is a div near the top of the page. On the SEO screen the
   Save override button is about 1400px below it, so the banner landed off
   screen and the change looked like it had done nothing.

   The container is left where it is — that is what makes a new banner replace
   the previous one — and the banner inside it is taken out of flow and pinned
   to the top of the viewport instead. Nothing on the page moves when one
   arrives or leaves.
   -------------------------------------------------------------------------- */

/*
  The swap target itself takes no space.

  It sits inside a .stack on most screens, and an empty div in a flex column
  still collects a gap on each side — so before this the page had a 24px hole
  in it waiting for a banner that is not in flow anyway. display: contents
  removes the box without removing the element, so HTMX still has something to
  swap into and Alpine still has something to walk.
*/

#admin-result {
  display: contents;
}

.admin-flash {
  position: fixed;
  top: calc(var(--sp-4) + 68px);
  left: 1.25rem;
  z-index: 90;
  width: min(36rem, calc(100vw - 2.5rem));
}
html:not(.admin-nav-collapsed) .admin-flash {
  left: calc(15.5rem + 1.25rem);
  width: min(36rem, calc(100vw - 15.5rem - 2.5rem));
}

.admin-flash .alert {
  align-items: flex-start;
  box-shadow: var(--shadow-lg);
  background-color: var(--surface);
  background-image: linear-gradient(var(--flash-tint), var(--flash-tint));
}

.admin-flash .alert--success {
  --flash-tint: transparent;
  background: #006400;
  background-image: none;
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1.35;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);
  align-items: center;
  cursor: pointer;
}
.admin-flash .alert--success,
.admin-flash .alert--success p {
  color: #fff;
  font-weight: 700;
}

.admin-flash .alert--warning {
  --flash-tint: var(--warning-bg);
}

.admin-flash .alert--info    {
  --flash-tint: var(--info-bg);
}

.admin-flash__close {
  flex: none;
  margin-left: auto;
  padding: 2px;
  border: 0;
  background: none;
  color: inherit;
  opacity: 0.7;
  cursor: pointer;
  border-radius: var(--r-sm);
}

.admin-flash__close:hover {
  opacity: 1;
}

.admin-flash__close svg {
  width: 16px;
  height: 16px;
  margin-top: 0;
}

/* Drops in from just above and fades out where it stands. */

.admin-flash--in {
  transition: opacity 180ms var(--ease), transform 180ms var(--ease);
}

.admin-flash--in-start {
  opacity: 0;
  transform: translateY(-8px);
}

.admin-flash--out {
  transition: opacity 200ms var(--ease);
}

.admin-flash--out-end {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .admin-flash--in, .admin-flash--out {
    transition: none;
  }

  .admin-flash--in-start {
    transform: none;
  }
}

@media (max-width: 700px) {
  .admin-flash {
    top: calc(var(--sp-3) + 56px);
  }
}

/* HTMX request indicator */

.htmx-indicator {
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  opacity: 1;
}

/*
  A button that is doing something says so.

  The other half of the same note: "make that if click the button then the
  button reacts". HTMX puts .htmx-request on the element carrying the hx-post
  for the life of the request, which on every administrative screen is the form
  around the button. So the button dims, stops taking clicks — which also stops
  the double submit that a slow save invites — and grows a spinner where its
  own text ends.

  Applied to the button inside a posting form and to a button that posts on its
  own account, because the panel has both.
*/

.htmx-request button[type='submit'],
button.htmx-request {
  opacity: 0.65;
  pointer-events: none;
  position: relative;
}

.htmx-request button[type='submit']::after,
button.htmx-request::after {
  content: '';
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-left: var(--sp-2);
  vertical-align: -1px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

/* Full backup keeps its own “creating backup” row — no in-button spinner
   (that would grow the green face) and no dim while the POST is in flight. */
.htmx-request button.btn--backup[type='submit'],
button.btn--backup.htmx-request {
  opacity: 1;
}
.htmx-request button.btn--backup[type='submit']::after,
button.btn--backup.htmx-request::after {
  content: none;
  display: none;
  width: 0;
  height: 0;
  margin: 0;
  border: none;
  animation: none;
}

/* Listing save: no spinner and no width jump while the favourite POST runs. */
.htmx-request button.btn--save-listing,
button.btn--save-listing.htmx-request {
  opacity: 1;
}
.htmx-request button.btn--save-listing::after,
button.btn--save-listing.htmx-request::after {
  content: none;
  display: none;
  width: 0;
  height: 0;
  margin: 0;
  border: none;
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .htmx-request button[type='submit']::after,
  button.htmx-request::after {
    animation: none;
  }
}

/* Dim results while a filter request is in flight. */

.htmx-request .results-fade {
  opacity: 0.45;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}

.spinner {
  display: inline-block;
  width: 20px;
  height: 20px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--navy);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

/* ==========================================================================
   Empty state
   ========================================================================== */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-11) var(--sp-6);
  text-align: center;
}

.empty__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  background: var(--surface-3);
  border-radius: 50%;
  color: var(--text-subtle);
}

.empty__icon svg {
  width: 28px;
  height: 28px;
}

.empty__title {
  font-size: var(--fs-h4);
  font-weight: 600;
  margin: 0;
  line-height: var(--lh-snug);
}

.empty__text {
  max-width: 44ch;
  color: var(--text-muted);
  font-size: var(--fs-sm);
  margin: 0;
  line-height: var(--lh-normal);
}

.notifications-empty .empty__title {
  color: #008000;
  text-transform: lowercase;
}

.notifications-empty__icon {
  background: #8B008B;
  border-radius: var(--r-md);
  color: #00FF00;
}

.notifications-empty__action {
  background: #008000;
  border-color: #008000;
  color: #fff;
  text-transform: lowercase;
}

.notifications-empty__action:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
  color: #fff;
}

/* Favourites: violet tile + solid red heart; green browse CTA. */

.favourites-empty__icon {
  background: #6F006F;
  border-radius: var(--r-md);
  color: #ff0000;
}

.favourites-empty__icon svg {
  fill: #ff0000;
  stroke: #ff0000;
  color: #ff0000;
}

.favourites-empty__action {
  background: #008000;
  border-color: #008000;
  color: #fff;
  text-transform: lowercase;
}

.favourites-empty__action:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
  color: #fff;
}

/* Billing details: green save CTA. */

.billing-save {
  background: #008000;
  border-color: #008000;
  color: #fff;
  text-transform: lowercase;
  font-weight: 600;
}

.billing-save:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
  color: #fff;
}

/* Compact billing cards (payment methods + details) — client, 21 September.
   Same upper section on both: tight header padding, no rule under the title. */
.card--billing-details .card__header {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: none;
}

.card--billing-details .card__body {
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}

.card--billing-details .billing-details {
  gap: 0.5rem;
}

.card--billing-details .billing-details .field {
  gap: 0.2rem;
}

.card--billing-details .billing-details .field__label {
  margin: 0;
  line-height: 1.2;
}

.card--billing-details .billing-details .billing-save {
  margin-top: 0.15rem;
}

.card--billing-details .billing-details__note {
  margin: 0.15rem 0 0;
  line-height: 1.3;
}

/* Payment history: one continuous rule per row.

   Per-cell border-bottom under border-collapse left short vertical "ticks" on
   the right end of every separator (the collapse corner). A single inset
   shadow on the row draws one clean line edge to edge. Download stays quiet
   until hover. */

#payment-history.card {
  overflow: visible;
}

/* Compact title band + half the earlier scroll window — client, 21 September. */
#payment-history .card__header {
  padding: var(--sp-2) var(--sp-4);
  border-bottom: none;
}

#payment-history .card__title {
  font-size: var(--fs-h4);
  line-height: 1.2;
}

/* ~half the default .table row height — client, 21 September. */
#payment-history .table {
  width: 100%;
  table-layout: fixed;
}

#payment-history .table th {
  padding: 0.35rem 0.5rem;
}

#payment-history .table td {
  border-bottom: 0;
  padding: 0.3rem 0.5rem;
  line-height: 1.25;
}

/* Column widths: Property absorbs leftover; Method / Amount / Status / Date
   stay content-tight and packed left — no gap after Method (client, 21 September). */
#payment-history .table th:nth-child(1),
#payment-history .table td:nth-child(1) {
  width: 6.4rem;
  max-width: 6.4rem;
  overflow: hidden;
  text-overflow: clip;
  white-space: nowrap;
}

#payment-history .table th:nth-child(2),
#payment-history .table td:nth-child(2) {
  width: 11.2rem;
  max-width: 11.2rem;
}

#payment-history .table th:nth-child(3),
#payment-history .table td:nth-child(3) {
  /* Flexible — only column without a fixed width under table-layout:fixed. */
  overflow: hidden;
  white-space: nowrap;
}

#payment-history .table th:nth-child(4),
#payment-history .table td:nth-child(4) {
  width: 7.25rem;
  max-width: 7.25rem;
  white-space: nowrap;
}

#payment-history .table th:nth-child(5),
#payment-history .table td:nth-child(5),
#payment-history .table th:nth-child(6),
#payment-history .table td:nth-child(6),
#payment-history .table th:nth-child(7),
#payment-history .table td:nth-child(7) {
  text-align: left;
}

#payment-history .table th:nth-child(5),
#payment-history .table td:nth-child(5) {
  width: 5.5rem;
  max-width: 5.5rem;
  text-align: center;
  white-space: nowrap;
}

#payment-history .table th:nth-child(6),
#payment-history .table td:nth-child(6) {
  width: 5.5rem;
  max-width: 5.5rem;
  white-space: nowrap;
}

#payment-history .table th:nth-child(7),
#payment-history .table td:nth-child(7),
#payment-history .billing-date {
  width: 4.25rem;
  max-width: 4.25rem;
  overflow: hidden;
  text-overflow: clip;
  white-space: nowrap;
}

#payment-history .table th:nth-child(8),
#payment-history .table td:nth-child(8),
#payment-history .table__actions {
  width: 2.75rem;
  max-width: 2.75rem;
  white-space: nowrap;
  padding-inline: var(--sp-2);
}

#payment-history .table .badge {
  padding: 0.1rem 0.45rem;
  line-height: 1.2;
}

#payment-history .billing-invoice-dl.btn--sm {
  min-height: 1.75rem;
  padding: 0.2rem 0.45rem;
}

#payment-history .table-scroll {
  max-height: calc(var(--table-rows, 10) * 2.35rem + 2.25rem);
  overflow-x: auto;
}

#payment-history .table tbody tr {
  box-shadow: inset 0 -1px 0 var(--border);
}

#payment-history .table tbody tr:last-child {
  box-shadow: none;
}

#payment-history .billing-invoice-dl {
  background: transparent;
  border-color: transparent;
  color: var(--text);
  box-shadow: none;
}

/* Green fill only on hover. :focus-visible used to keep the same fill after
   opening the PDF in a new tab (target=_blank), so the first row looked
   permanently selected — client, 21 September. */
#payment-history .billing-invoice-dl:hover:not(:disabled) {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

#payment-history .billing-invoice-dl:hover:not(:disabled) svg {
  color: #fff;
  stroke: #fff;
}

#payment-history .billing-invoice-dl:focus-visible {
  outline: 2px solid #008000;
  outline-offset: 2px;
}

/* Package / Property: hard cut (no …). The yellow full-text tip is painted
   fixed on <body> by previa.extra.js so a scroll window or overflow:hidden
   cell cannot clip it (admin ledger + billing history + listings fold titles). */
#payment-history .billing-clip,
#payment-ledger .billing-clip,
.admin-listings .billing-clip,
.seller-listings .billing-clip {
  position: relative;
  max-width: 100%;
  overflow: visible;
}

#payment-history .billing-clip__cut,
#payment-ledger .billing-clip__cut,
.admin-listings .billing-clip__cut,
.seller-listings .billing-clip__cut {
  display: block;
  overflow: hidden;
  text-overflow: clip;
  white-space: nowrap;
}

/* Kept in the markup as the tip's text source; hover display is the float tip. */
#payment-history .billing-clip__tip,
#payment-ledger .billing-clip__tip,
.admin-listings .billing-clip__tip,
.seller-listings .billing-clip__tip {
  display: none !important;
}

.billing-clip__float-tip {
  display: none;
  position: fixed;
  z-index: 400;
  max-width: min(36rem, 70vw);
  box-sizing: border-box;
  padding: 6px 10px;
  border: 1px solid #333;
  border-radius: 4px;
  background: #facc15;
  color: #000;
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.35;
  white-space: normal;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
  pointer-events: none;
}

/* Admin ledger: same tight one-line row style as /billing Payment history.
   Columns stay as they are (pick, invoice, buyer, package, property, …). */
#payment-ledger .table {
  width: 100%;
  table-layout: fixed;
}

#payment-ledger .table th {
  padding: 0.35rem 0.5rem;
}

#payment-ledger .table td {
  border-bottom: 0;
  padding: 0.3rem 0.5rem;
  line-height: 1.25;
  white-space: nowrap;
  vertical-align: middle;
}

#payment-ledger .table tbody tr {
  box-shadow: inset 0 -1px 0 var(--border);
}

#payment-ledger .table tbody tr:last-child {
  box-shadow: none;
}

#payment-ledger .table .badge {
  padding: 0.1rem 0.45rem;
  line-height: 1.2;
}

#payment-ledger .table-scroll {
  max-height: calc(var(--table-rows, 50) * 2.35rem + 2.25rem);
  overflow-x: auto;
  overflow-y: auto;
}

#payment-ledger .table th:nth-child(1),
#payment-ledger .table td:nth-child(1),
#payment-ledger .table__pick {
  width: 2.5rem;
  max-width: 2.5rem;
  padding-inline: var(--sp-2);
}

#payment-ledger .table th:nth-child(2),
#payment-ledger .table td:nth-child(2) {
  width: 6.4rem;
  max-width: 6.4rem;
}

#payment-ledger .table th:nth-child(3),
#payment-ledger .table td:nth-child(3) {
  width: 11rem;
  max-width: 11rem;
}

#payment-ledger .table th:nth-child(4),
#payment-ledger .table td:nth-child(4) {
  width: 8.5rem;
  max-width: 8.5rem;
}

#payment-ledger .table th:nth-child(6),
#payment-ledger .table td:nth-child(6) {
  width: 5.5rem;
  max-width: 5.5rem;
}

#payment-ledger .table th:nth-child(7),
#payment-ledger .table td:nth-child(7) {
  width: 4.5rem;
  max-width: 4.5rem;
  text-align: center;
}

#payment-ledger .table th:nth-child(8),
#payment-ledger .table td:nth-child(8) {
  width: 5.5rem;
  max-width: 5.5rem;
}

#payment-ledger .table th:nth-child(9),
#payment-ledger .table td:nth-child(9),
#payment-ledger .billing-date {
  width: 4.25rem;
  max-width: 4.25rem;
  overflow: hidden;
  text-overflow: clip;
}

#payment-ledger .table th:nth-child(10),
#payment-ledger .table td:nth-child(10),
#payment-ledger .table__actions {
  width: 5.25rem;
  max-width: 5.25rem;
  padding-inline: var(--sp-2);
  flex-wrap: nowrap;
  justify-content: flex-start;
  gap: 2px;
}

#payment-ledger .table__actions .btn--sm {
  min-height: 1.75rem;
  padding: 0.2rem 0.45rem;
}

#payment-ledger .billing-invoice-dl {
  background: transparent;
  border-color: transparent;
  color: var(--text);
  box-shadow: none;
}

#payment-ledger .billing-invoice-dl:hover:not(:disabled) {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

#payment-ledger .billing-invoice-dl:hover:not(:disabled) svg {
  color: #fff;
  stroke: #fff;
}

#payment-ledger .billing-invoice-dl:focus-visible {
  outline: 2px solid #008000;
  outline-offset: 2px;
}

/*
  Payments ledger spacing (client, 22 September): count + delete share one
  toolbar row flush on the table; empty #admin-result must not invent a gap
  (whitespace inside the div means :empty never matches).
*/
.admin #admin-result:not(:has(*)),
.admin .admin-result:not(:has(*)) {
  display: none;
}

.payment-ledger {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.payment-ledger__toolbar {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: 0;
  padding: 0;
}

.payment-ledger__count {
  margin: 0;
  padding: 0 0 2px;
  line-height: 1.2;
}

/*
  Same face as Listings delete: transparent quiet outline at rest, #FF0000 on
  hover. Always show the finger cursor on hover — never HTML disabled (that
  sets pointer-events:none and kills hover).
*/
.payment-ledger__toolbar .btn.btn--quiet.btn--danger-quiet,
.payment-ledger__toolbar .btn.btn--quiet.btn--danger-quiet:disabled,
.payment-ledger__toolbar .btn.btn--quiet.btn--danger-quiet[aria-disabled='true'] {
  flex: none;
  align-self: flex-end;
  color: var(--text);
  border-color: var(--border);
  background: transparent;
  opacity: 1;
  cursor: pointer;
  pointer-events: auto;
}

.payment-ledger__toolbar .btn.btn--quiet.btn--danger-quiet:hover,
.payment-ledger__toolbar .btn.btn--quiet.btn--danger-quiet:hover .icon,
.payment-ledger__toolbar .btn.btn--quiet.btn--danger-quiet:hover svg {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
  cursor: pointer;
}

.payment-ledger > .table-wrap {
  margin-top: 0;
}

/* Search results: nothing matches. */

.search-empty__icon {
  background: #008000;
  border-radius: var(--r-md);
  color: #fff;
}

.search-empty__action {
  text-transform: lowercase;
}

/* Map column: nothing in this area. */

.map-empty__icon {
  background: #008000;
  border-radius: var(--r-md);
  color: #6F006F;
}

.map-empty__icon svg {
  color: #6F006F;
  stroke: #6F006F;
}

.map-empty__action {
  background: #008000;
  border-color: #008000;
  color: #fff;
  text-transform: lowercase;
}

.map-empty__action:hover:not(:disabled) {
  background: #006400;
  border-color: #006400;
  color: #fff;
}

/* ==========================================================================
   Progress
   ========================================================================== */

.progress {
  height: 8px;
  background: var(--surface-3);
  border-radius: var(--r-pill);
  overflow: hidden;
}

.progress__bar {
  height: 100%;
  background: #008000;
  border-radius: var(--r-pill);
  transition: width var(--dur-slow) var(--ease);
}

.progress__bar--gold {
  background: var(--gold);
}

.progress__bar--success {
  background: var(--success);
}

.progress__bar--warning {
  background: var(--warning);
}

[data-theme='dark'] .progress__bar {
  background: #008000;
}

/* ==========================================================================
   Avatar
   ========================================================================== */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--navy-tint);
  color: var(--slate);
  font-size: var(--fs-sm);
  font-weight: 600;
  overflow: hidden;
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar--sm {
  width: 32px;
  height: 32px;
  font-size: var(--fs-xs);
}

.avatar--lg {
  width: 64px;
  height: 64px;
  font-size: var(--fs-lg);
}

.avatar--xl {
  width: 96px;
  height: 96px;
  font-size: var(--fs-h3);
}

/* ==========================================================================
   Toast
   ========================================================================== */

.toast-stack {
  position: fixed;
  /* Same reasoning as the floating menu button: fixed to the viewport, but
     offset so it appears at the website's edge rather than the monitor's. */
  right: calc(var(--shell-gutter) + var(--sp-5));
  bottom: var(--sp-5);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  max-width: min(380px, calc(100vw - 32px));
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--navy);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm);
}

.toast--success {
  border-left-color: var(--success);
}

.toast--error {
  border-left-color: var(--error);
}

.toast--warning {
  border-left-color: var(--warning);
}

.toast svg {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 1px;
}

/* --------------------------------------------------------------------------
   Card image carousel

   Three ways to change picture, as asked: the pager arrows, a dot, or a click
   on the left/right third of the photograph. A click in the middle falls
   through to the card link and opens the listing.
   -------------------------------------------------------------------------- */

/* The track slides; the media box clips it.

   220ms is the client's "fast, smooth" brief (he asked for 180–250ms). The
   easing decelerates, so the picture arrives rather than stopping dead. */

.pcard__track {
  display: flex;
  width: 100%;
  height: 100%;
  will-change: transform;
  transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.pcard__slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The zoom-on-hover belongs to single-image cards; on a track it would fight
   the slide transform. */

.pcard:hover .pcard__track .pcard__slide {
  transform: none;
}

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

/* --- Click zones ----------------------------------------------------------

   Stacking order over the photograph, lowest first:
     1  .pcard__title a::after  the card-wide link, so the middle opens the listing
     2  .pcard__zone            the outer thirds, paging instead
     3  .pcard__pager           arrows and dots
     4  .pcard__fav             the heart, which must stay clickable everywhere

   The heart sits inside the right-hand paging zone, so without a higher layer
   than the zone it cannot be pressed at all — which is exactly what happened
   before it was raised to 4.
   -------------------------------------------------------------------------- */

.pcard__zone {
  position: absolute;
  top: 0;
  bottom: 34px;
  /* clear of the pager, so it stays clickable */
  width: 30%;
  z-index: 2;
  border: none;
  background: none;
}

/* Directional cursors, so the outer thirds read as paging rather than as more
   of the link underneath them. */

.pcard__zone--prev {
  left: 0;
  cursor: w-resize;
}

.pcard__zone--next {
  right: 0;
  cursor: e-resize;
}

/* --- Pager capsule -------------------------------------------------------- */

.pcard__pager {
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform: translateX(-50%);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px 4px;
  border-radius: 999px;
  background: rgba(8, 20, 32, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.pcard:hover .pcard__pager,
.pcard:focus-within .pcard__pager {
  opacity: 1;
}

/* Development cards use .dcard, not .pcard — keep the green-dot pager visible
   on the photograph the same way it always was on this strip. */
.dcard .pcard__pager {
  opacity: 1;
}

/* No hover on touch: show it rather than never showing it. */

@media (hover: none) {
  .pcard__pager {
    opacity: 1;
  }
}

/*
  Arrows and dots are green, as asked, and the capsule behind them is dark, so
  the green keeps its contrast over a bright photograph as well as a dark one.
  --success-on-dark is a lightened green defined for exactly this: the base
  --success is tuned for text on white and goes muddy on the capsule.
*/

.pcard__pager-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--success-on-dark);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.pcard__pager-arrow svg {
  width: 14px;
  height: 14px;
  stroke-width: 2.4;
}

.pcard__pager-arrow:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
}

.pcard__pager-arrow:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 1px;
}

/*
  Dot strip.

  `touch-action: pan-y` is the load-bearing line: it tells the browser it may
  still scroll the page vertically from here, but that horizontal movement is
  ours to handle. Without it a horizontal drag on a phone would either be
  swallowed by the scroller or, with `none`, would cost the page its vertical
  scrolling — the gesture is only taken once previaCardGallery has confirmed
  the movement is sideways.
*/

.pcard__pager-dots {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 2px;
  margin: -4px 0;
  /* the extra grab height costs the capsule nothing */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
  cursor: grab;
}

.pcard__pager-dots.is-holding {
  cursor: grabbing;
}

.pcard__dot {
  width: 6px;
  height: 6px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  cursor: inherit;
  transition: background-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.pcard__dot:hover {
  background: rgba(255, 255, 255, 0.85);
}

.pcard__dot.is-on {
  background: var(--success-on-dark);
  transform: scale(1.25);
}

.pcard__dot:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/*
  Held. The active dot swells while a press is in progress and settles back on
  release — transform only, so it costs no layout and the dots either side do
  not move. The capsule is pinned open for the duration, because a drag can
  easily carry the pointer off the card and the hover rule above would
  otherwise fade the control away mid-gesture.
*/

.pcard__pager-dots.is-holding .pcard__dot.is-on {
  transform: scale(2.1);
}

.pcard__pager:has(.pcard__pager-dots.is-holding) {
  opacity: 1;
}

/* Reduced motion: the dot still grows — that is the feedback that the gesture
   has been picked up — it simply arrives without the animation. */

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

.pcard__pager-count {
  padding: 0 4px;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
}

/* A map card in List mode is 132px of photograph beside three lines of text.
   The track and the swipe work there, but a pager capsule and two paging zones
   do not fit on a thumbnail that size, so they are held back until the card is
   drawn full width. .map-shell.is-grid-mode in pages.css turns them on. */

.pcard--map .pcard__pager,
.pcard--map .pcard__zone,
.pcard--map .pcard__count {
  display: none;
}

/* --------------------------------------------------------------------------
   Map-list locate control
   Top-right of each card in the map sidebar: a button that centres the map on
   this listing, plus the market it belongs to.
   -------------------------------------------------------------------------- */

.pcard__locate {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 6px 3px 3px;
  border-radius: var(--r-pill, 999px);
  /* Always the dark-theme chip — same on day and night so the flag/code
     stays legible on light photographs (client, map listing previews). */
  background: color-mix(in srgb, #102635 88%, transparent);
  border: 1px solid #294353;
  box-shadow: var(--shadow-xs);
}

@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .pcard__locate {
    background: #102635;
  }
}

.pcard__locate-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: none;
  border-radius: 50%;
  background: transparent;
  /* Same violet as the Previa logo tile. */
  color: #8B008B;
  transition: background-color var(--dur-fast) var(--ease);
}

.pcard__locate-btn svg {
  width: 16px;
  height: 16px;
  stroke-width: 2.2;
}

.pcard__locate-btn:hover {
  background: color-mix(in srgb, #8B008B 14%, transparent);
}

.pcard__market {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  /* Locked to dark-theme text so day mode stays readable on the dark chip. */
  color: #E8EDF1;
}

/*
  On map list cards the locate chip sits on the card (body side), so the
  heart can keep the photo's top-right. In grid mode the photo is full-width
  and locate owns that corner — drop the heart just below it.
*/

.pcard--map .pcard__fav {
  top: 2px;
  right: 2px;
  left: auto;
}

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__fav {
  /* Just under the country/locate pill — was 44px with a visible gap. */
  top: 34px;
  right: 6px;
  width: 32px;
  height: 32px;
}

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__fav svg {
  width: 18px;
  height: 18px;
}

/* --------------------------------------------------------------------------
   Flags

   One rule for every flag in the interface, so the language menu, the market
   picker, the card badges and the drawer pills all draw the same shape. The
   client asked for the frame around them removed and for the rounded corners
   used in the supplied reference; the SVG itself no longer strokes an outline,
   and the radius is applied here. `overflow: hidden` is already the initial
   value for an outermost <svg>, so the radius clips the stripes inside.

   The selector is doubled so this beats the generic `.menu__item svg` and
   `.btn svg` rules, which would otherwise square the flag off at 16x16 and
   throw away its 7:5 proportions.
   -------------------------------------------------------------------------- */

.flag.flag {
  display: inline-block;
  flex: none;
  width: 22px;
  height: 16px;
  border-radius: 3px;
  vertical-align: middle;
  /*
    The source flags are 4:3 and the slot is 11:8 — a 3% difference. `cover`
    fills the slot by trimming a quarter-pixel off the top and bottom rather
    than stretching the flag, so proportions stay true. Without this the <img>
    would squash to fit.
  */
  object-fit: cover;
  /* A hair of contrast so a white-edged flag (Japan, Poland, Finland) still
     reads as a shape against a white surface. Not a frame — no gold, no rule. */
  box-shadow: inset 0 0 0 1px rgba(10, 34, 56, 0.10);
}

[data-theme='dark'] .flag.flag {
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
}

/* Placeholder for a code with no flag file. Should never appear — every market
   country ships one — but it keeps an unknown code from breaking the layout. */

.flag--unknown {
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Language menu

   Flag plus English name with the native name under it (same size), search at
   the top for a growing list, and tight row spacing so many languages fit.
   -------------------------------------------------------------------------- */

.lang-menu {
  min-width: 220px;
  padding: var(--sp-1);
}

.lang-menu .menu__label {
  padding: 2px var(--sp-2) 4px;
}

.lang-menu__search {
  position: relative;
  display: flex;
  align-items: center;
  padding: 0 var(--sp-1) var(--sp-1);
}

.lang-menu__input {
  width: 100%;
  min-height: 34px;
  padding-left: var(--sp-2);
  padding-right: var(--sp-7);
  font-size: var(--fs-sm);
}

.lang-menu__input::-webkit-search-cancel-button {
  display: none;
}

.lang-menu__clear {
  position: absolute;
  right: calc(var(--sp-1) + 4px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.lang-menu__clear:hover {
  color: var(--text);
}

.lang-menu__clear svg {
  width: 14px;
  height: 14px;
}

.lang-menu .menu__item {
  gap: var(--sp-2);
  padding: 5px var(--sp-2);
  align-items: center;
}

.lang-menu .menu__item > span.spacer {
  flex: 1;
}

.lang-menu__names {
  display: flex;
  flex-direction: column;
  gap: 0;
  line-height: 1.2;
  min-width: 0;
}

.lang-menu__name,
.lang-menu__native {
  font-size: var(--fs-sm);
  white-space: nowrap;
}

.lang-menu__native {
  color: var(--text-muted);
  font-weight: 400;
}

.lang-menu .menu__item.is-active .lang-menu__name {
  font-weight: 600;
}

.lang-menu--drawer {
  min-width: 0;
  padding: var(--sp-1) 0;
  background: transparent;
  border: 0;
  box-shadow: none;
}

.lang-menu--drawer .lang-menu__search {
  padding: var(--sp-1) var(--sp-3);
}

.lang-menu--drawer a {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 6px var(--sp-3);
}

/* --------------------------------------------------------------------------
   Location field

   One box in place of the three the sidebar used to carry. The suggestion list
   is absolutely positioned so it overlays whatever follows rather than pushing
   the form around as it opens and closes.
   -------------------------------------------------------------------------- */

/*
  One set of numbers for the whole control, so every variant of it — the hero
  panel, the search sidebar, add-listing, the broker directory — insets its pin
  and its text by the same steps. A variant that needs a tighter control
  overrides a token here rather than restating a padding, which is how the hero
  panel's flat 12px gutter came to leave a long value running under the clear
  button.
*/

.location-field {
  position: relative;
  --loc-icon-inset: var(--sp-3);
  /* pin to the field's left edge */
  --loc-icon-size: 18px;
  /* matches .input-icon svg */
  --loc-gap: var(--sp-2);
  /* pin (and clear button) to the text */
  --loc-clear-size: 22px;
  /* matches .location-field__clear */
  --loc-clear-inset: var(--sp-2);
  /* clear button to the right edge */
}

/*
  The control is a block, not the flex row `.input-icon` normally makes, because
  the suggestion list is a sibling that has to span the full width. That means
  `.input-icon`'s `align-items: center` no longer reaches the pin, so the icon
  is centred explicitly here — without this it fell to the top of the line box
  and sat above the text rather than beside it.
*/

.location-field__control {
  position: relative;
  display: block;
}

.location-field__control > svg {
  top: 50%;
  transform: translateY(-50%);
  left: var(--loc-icon-inset);
  /* Same violet as the homepage "search properties" button. */
  color: #8B008B;
  opacity: 1;
}

/*
  Room for the text.

  The pin and the clear button were reserving 98px of a ~276px field in the
  sidebar, which left barely enough for "Kalamaja, Tallinn, Estonia" — the
  value was clipped mid-word on a phone. Both gutters are now sized to what the
  control actually occupies plus one step of breathing room.
*/

.location-field__control .input {
  /* 38px each side at the default token values. */
  padding-left: calc(var(--loc-icon-inset) + var(--loc-icon-size) + var(--loc-gap));
  padding-right: calc(var(--loc-clear-inset) + var(--loc-clear-size) + var(--loc-gap));
  /* Cut at the clear button — no ellipsis. Same as the listings filter (14 Sep)
     and add-listing (21 Sep): "the text goes as far as possible and is then
     just cut off." */
  text-overflow: clip;
}

.filter-panel .location-field__control .input {
  text-overflow: clip;
}

.location-field__clear {
  position: absolute;
  top: 50%;
  right: var(--loc-clear-inset);
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--loc-clear-size);
  height: var(--loc-clear-size);
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--text-subtle);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.location-field__clear:hover {
  background: var(--surface-3);
  color: var(--text);
}

.location-field__clear:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 1px;
}

/*
  Sexydate yellow hover label — full value when the box cuts the address short.
*/

.location-field__tip {
  display: none;
  position: absolute;
  left: 0;
  bottom: calc(100% + 6px);
  z-index: 40;
  max-width: min(36rem, 92vw);
  box-sizing: border-box;
  padding: 6px 10px;
  border: 1px solid #333;
  border-radius: 4px;
  background: #facc15;
  color: #000;
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.35;
  white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
  pointer-events: none;
}

.location-field.is-overflowing:not(.is-open):hover .location-field__tip {
  display: block;
}

/*
  position: static, and it matters.

  This svg is a descendant of .input-icon, whose own rule absolutely positions
  the icon it is written for — the pin — at `left: var(--sp-4)`. That rule
  reached in here too, so the cross was taken out of the button's flex centring
  and pinned 16px from the left edge of a 22px button: it hung 7px past the
  button's right side, missed the hover circle entirely, and drifted over the
  field's border. Putting it back in flow re-centres it in the button.

  colour with it: .input-icon svg also sets its own colour, which overrode the
  button's, so the cross never brightened on hover.
*/

.location-field__clear svg {
  position: static;
  width: 13px;
  height: 13px;
  color: inherit;
}

/* The drawn button stays small; the area a finger has to find is 32px. */

.location-field__clear::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32px;
  height: 32px;
  transform: translate(-50%, -50%);
}

.location-field__list {
  position: absolute;
  z-index: 50;
  /* over the fields below it, under the header and drawers */
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 292px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-1);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}

.location-field__option {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  border: none;
  border-radius: var(--r-sm);
  background: none;
  text-align: left;
  cursor: pointer;
}

.location-field__option[hidden] {
  display: none;
}

.location-field__option:hover,
.location-field__option.is-active {
  background: var(--surface-3);
}

.location-field__option:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: -2px;
}

.location-field__kind {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-muted);
}

.location-field__kind svg {
  width: 15px;
  height: 15px;
}

/* A country reads as the broadest match, so it carries the accent. */

.location-field__kind--country {
  background: var(--navy-tint);
  color: var(--navy);
}

[data-theme='dark'] .location-field__kind--country {
  background: var(--surface-3);
  color: var(--gold);
}

.location-field__label {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-sm);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.location-field__type {
  flex: none;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-subtle);
}

.location-field__empty,
.location-field__searching {
  padding: var(--sp-4) var(--sp-3);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/*
  A Places result, told apart from one of Previa's own.

  The site's catalogue leads somewhere that has listings in it; a Google address
  is a point on the map and nothing more. The rule above the first one is what
  separates the two lists, and it is only drawn when there is a Previa result
  above it to separate from.
*/

.location-field__option--remote + .location-field__option--remote,
.location-field__option:not(.location-field__option--remote) + .location-field__option--remote {
  border-top: 1px solid var(--border);
}

.location-field__option--remote .location-field__type {
  color: var(--text-subtle);
}

/*
  Google's places alone — the filter panel's box.

  The client, 13 September: "there must be the googlemaps locations displayed
  like in the sexydate". Drawn the way that list is: a pin, the place's name
  with the typed letters in bold, the region and country after it in smaller
  type, a rule between rows, and "powered by Google" at the foot.
*/

.location-field__option--google {
  gap: var(--sp-2);
}

.location-field__option--google + .location-field__option--google {
  border-top: 1px solid var(--border);
}

.location-field__pin {
  display: inline-flex;
  flex: none;
  color: var(--text-muted);
}

.location-field__pin svg {
  width: 16px;
  height: 16px;
}

.location-field__hit {
  font-weight: 700;
}

.location-field__secondary {
  margin-left: 0.4em;
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-muted);
}

.location-field__powered {
  margin: 0;
  padding: var(--sp-1) var(--sp-3) 0;
  text-align: right;
  font-size: var(--fs-2xs);
  color: var(--text-subtle);
}

.location-field__google {
  font-weight: 600;
}

.location-field__google b {
  font-weight: inherit;
}

.location-field__google b:nth-child(1),
.location-field__google b:nth-child(4) {
  color: #4285F4;
}

.location-field__google b:nth-child(2),
.location-field__google b:nth-child(6) {
  color: #EA4335;
}

.location-field__google b:nth-child(3) {
  color: #FBBC05;
}

.location-field__google b:nth-child(5) {
  color: #34A853;
}

/*
  A listing's description, the way the seller typed it.

  The client, 13 September: "Make that the text in the frontend would be the
  same layout as in the backend, so empty rows are there. And if user puts a
  link there ... it is displayed in frontend as a blue #0000FF link (no
  underline) and if user clicks on it, then it opens in a new window."

  pre-wrap draws every newline the textarea kept, blank rows included; anywhere
  lets a long address break rather than push the column wider than the page.
  The link colour is the client's literal, in both themes, as he gave it.
*/

.listing-description {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.prose .listing-description__link,
.prose .listing-description__link:visited,
.prose .listing-description__link:hover {
  color: #0000FF;
  text-decoration: none;
}

.prose .listing-description__link:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 2px;
  border-radius: 2px;
}

.my-articles-block {
  max-width: 68ch;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.my-articles {
  max-width: 68ch;
  font-size: var(--fs-base);
  line-height: 1.2;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding-top: var(--sp-3);
  padding-bottom: var(--sp-3);
}

.my-articles__link,
.my-articles__link:visited,
.my-articles__link:hover {
  color: #0000FF;
  text-decoration: none;
}

.my-articles__link:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 2px;
  border-radius: 2px;
}

/* The preview under the text keeps a video's own proportions without taking
   the full width of the column, as a link preview under a post does. */

.yt-video--description {
  max-width: 640px;
}

/* --------------------------------------------------------------------------
   Payment method options
   -------------------------------------------------------------------------- */

/*
  Two columns — "the payment methods make there in 2 columns".

  Two, stated as two: an auto-fit track list reads the client's instruction as a
  minimum and gives four columns on a wide screen, which is not what was asked
  for. Rows stretch to the tallest tile in them, so a row with a line of
  explanation stays level with one that has only a name.

  One column below 520px, where the page is a single column anyway and two
  tiles would be about 155px each — narrower than the words "Credit card" set
  on one line.
*/

.pay-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3);
}

@media (max-width: 520px) {
  .pay-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.pay-option__name {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
}

.pay-logo {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
}

.pay-logo__svg {
  width: 40px;
  height: 26px;
  border-radius: 4px;
}

/* A provider that is present in the design but not yet connected. */

.pay-option.is-disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.pay-option.is-disabled input {
  cursor: not-allowed;
}

/* Mock processing: the button reports the request is in flight rather than
   leaving the page apparently idle. */

.pay-option.is-processing {
  border-color: var(--navy);
}

/* The switch's text label, next to its track — used in the admin tables where
   the state has to be readable as a word as well as a position. */

.switch__label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ==========================================================================
   Messenger apps — WhatsApp, Telegram, Viber, Signal, Teams
   ==========================================================================

   Two faces of the same set of marks: .msg-picker is the row of toggles in the
   add-listing contact section, .msg-links is the row of live chat links on a
   listing and on a card.

   The tiles carry their own brand colours. The one thing that does change with
   the theme is the ink of the glyph on them: the client supplied a day and a
   night drawing of each app, identical but for white glyphs on the day set and
   black on the night one. That is what these rules switch — see the note in
   components/brand-icons.html.
   -------------------------------------------------------------------------- */

.msg-mark {
  flex: none;
  display: block;
  width: 100%;
  height: 100%;
  color: #fff;
  /* the day drawings' ink */
}

[data-theme='dark'] .msg-mark {
  color: #000;
}

/* the night drawings' ink */

/* Telegram's plane is three-toned: the fold under the wing and the shadow it
   throws are their own values in both drawings, not tints of the main ink. */

.msg-mark__fold {
  fill: #c8daea;
}

.msg-mark__shade {
  fill: #a9c9dd;
}

[data-theme='dark'] .msg-mark__fold {
  fill: #4a4a4a;
}

[data-theme='dark'] .msg-mark__shade {
  fill: #2a2a2a;
}

/* The day plane is a warm white rather than pure white, as supplied. */

.msg-mark--telegram {
  color: #f6fbfe;
}

/* --- The picker in the add-listing form ----------------------------------
   Laid out to the reference's own proportions: a 20px tickbox, a 40px brand
   tile, 10px between the two halves of a pair and 16px between pairs. Pairs
   wrap as a unit, so a box never ends a line orphaned from its icon. */

.msg-picker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) 16px;
  margin-top: var(--sp-3);
}

.msg-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
}

.msg-toggle input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

/*
  The tickbox. Checked state is green #008000 with a white tick — same as the
  other form checkboxes on the site.
*/

.msg-toggle__box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 20px;
  height: 20px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: transparent;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}

.msg-toggle__box svg {
  width: 13px;
  height: 13px;
}

.msg-toggle:hover .msg-toggle__box {
  border-color: #008000;
}

[data-theme='dark'] .msg-toggle:hover .msg-toggle__box {
  border-color: #008000;
}

.msg-toggle input:checked + .msg-toggle__box {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

[data-theme='dark'] .msg-toggle input:checked + .msg-toggle__box {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

.msg-toggle input:focus-visible + .msg-toggle__box {
  box-shadow: var(--focus-ring);
}

/*
  The brand tile. Unticked it dims rather than greys out — the reference keeps
  every icon in full colour, and the tickbox beside it already carries the
  state, so desaturating as well would say the same thing twice and make the
  row harder to read at a glance.
*/

.msg-toggle__mark {
  display: block;
  flex: none;
  width: 40px;
  height: 40px;
  opacity: 0.45;
  transition: opacity var(--dur-fast) var(--ease);
}

.msg-toggle input:checked ~ .msg-toggle__mark {
  opacity: 1;
}

.msg-toggle:hover .msg-toggle__mark {
  opacity: 1;
}

/* --- Live chat links on a listing ----------------------------------------
   On the reference these sit tight against each other, immediately after the
   contact line, so the run reads as one strip of apps rather than as five
   separate buttons. */

.msg-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 3px;
}

.msg-link {
  display: inline-flex;
  width: 26px;
  height: 26px;
  border-radius: var(--r-sm);
  line-height: 0;
  text-decoration: none;
}

/* Wired chat links only (.msg-link is skipped when no href can be built) —
   same hover heartbeat as SDW's a.social-link-active. */
.msg-link:hover .msg-mark,
.msg-link:focus-visible .msg-mark {
  animation: msg-link-heartbeat 1.15s ease-in-out infinite;
}

.msg-link:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 2px;
}

@keyframes msg-link-heartbeat {
  0%,
  100% {
    transform: scale(1);
  }

  14% {
    transform: scale(1.08);
  }

  28% {
    transform: scale(1);
  }

  42% {
    transform: scale(1.05);
  }

  70% {
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .msg-link:hover .msg-mark,
  .msg-link:focus-visible .msg-mark {
    animation: none;
  }
}

/* Compact row on a property card. */

.msg-links--sm {
  margin-top: var(--sp-2);
}

.msg-links--sm .msg-link {
  width: 22px;
  height: 22px;
}

/*
  The messenger row under a phone number.

  It carried a "Or message on" line until 8 September; the client asked for the
  text to go and the icons to sit under the phone. Gap to the green phone above
  and the blue mail below is the same stack gap — no negative pull toward the
  phone, which used to leave the icons too high.
*/

.msg-block {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* ==========================================================================
   Paid promotion picker
   ==========================================================================

   A checkbox that opens a price list. Ticked, the row gets a tinted panel so
   it is obvious that something chargeable has been switched on and that the
   day options belong to it rather than to the row below.
   -------------------------------------------------------------------------- */

.promo-picker {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.promo {
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.promo.is-on {
  border-color: var(--gold);
  background: var(--gold-tint);
}

/*
  ...except the bump, which is gold for nothing.

  The client, 7 September: "the background color of 'Bump on top of search
  results' make normal color as the bumping will not add this featured color to
  the ad, so it is confusing if this thing here has this featured color."

  He is right, and it is the same rule the gold is following everywhere else on
  the site: gold means featured. A featured listing gets a golden frame on its
  card, a golden pin on the map and a golden preview — so a golden panel behind
  the box that buys featuring is telling the truth, and the same panel behind
  the box that buys a bump is promising a frame the bump does not come with.

  Ticked, the bump row keeps a ground and an edge — it still has to read as
  switched on, and the day options under it still have to belong to it — in the
  neutral pair the rest of the site uses for exactly that.
*/

.promo--bump.is-on {
  border-color: var(--border-strong);
  background: var(--surface-2);
}

.promo__check {
  align-items: flex-start;
}

/* These labels are full sentences, so they wrap — line-height has to suit
   running text rather than a one-word checkbox. */

.promo__check .check__text {
  line-height: 1.5;
}

.promo__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding-top: var(--sp-3);
}

.promo__info {
  display: flex;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: 1.5;
}

.promo__info svg {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  color: var(--text-subtle);
}

.promo__tiers {
  border: 0;
  padding: 0;
  margin: 0;
}

.promo__tiers > legend {
  padding: 0 0 var(--sp-2);
}

.promo__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--sp-2);
}

/* One day option: days above, price below, the whole tile clickable. */

.promo__tier {
  position: relative;
  cursor: pointer;
}

.promo__tier input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.promo__tier-face {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--sp-2) var(--sp-1);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.promo__tier:hover .promo__tier-face {
  border-color: var(--border-strong);
}

.promo__tier input:checked + .promo__tier-face {
  border-color: var(--navy);
  box-shadow: inset 0 0 0 1px var(--navy);
}

[data-theme='dark'] .promo__tier input:checked + .promo__tier-face {
  border-color: var(--gold);
  box-shadow: inset 0 0 0 1px var(--gold);
}

.promo__tier input:focus-visible + .promo__tier-face {
  box-shadow: var(--focus-ring);
}

.promo__tier-days {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.promo__tier-price {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
}

.promo__total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.promo__total strong {
  font-size: var(--fs-base);
  color: var(--text);
}

/* Checkout Promotion card: half the vertical chrome of a normal card. */

.card--checkout-promo .checkout-promo {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 14px 12px;
}

.card--checkout-promo .checkout-promo__title {
  margin: 0;
  font-size: var(--fs-base);
  line-height: 1.2;
}

.card--checkout-promo .checkout-promo__lede {
  margin: 0;
  line-height: 1.25;
  max-width: none;
}

.card--checkout-promo .promo-picker {
  gap: 6px;
}

.card--checkout-promo .promo {
  padding: 6px 10px;
}

.card--checkout-promo .promo__check .check__text {
  line-height: 1.3;
}

.card--checkout-promo .promo__body {
  gap: 6px;
  padding-top: 6px;
}

/*
  The broker's two paid placements, on their profile.

  Both are a violet button in a field's heading row and a dialog behind it —
  see components/broker-ad.html — so the component itself is only a wrapper
  around the button plus whatever is running now. It is display:contents so the
  wrapper does not become a box in the middle of the label row: the button is
  what the row lays out, and the dialog is fixed to the viewport anyway.
*/

.broker-ad {
  display: inline-flex;
  flex: none;
}

/*
  Title + plan blurb in the dialog header.

  The client, 17 September: the explanation sits straight under the title with
  tight line spacing, not as the first body paragraph with a stack gap above.
*/
.broker-ad__heading {
  min-width: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.broker-ad__intro {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--text-muted);
  line-height: 1.3;
}

/* What is running now, listed under the field the button sits on rather than
   inside its heading row — see broker-ad-runs in components/broker-ad.html.

   Two lists on the profile now: the markets a person bought and the markets
   their company bought. Same treatment, two class names, because two lists that
   shared one would be two lists nothing could tell apart. */

.broker-ad__runs,
.agency-ad__runs {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  list-style: none;
}

.broker-ad__status {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: var(--gold-tint);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.broker-ad__status svg {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  /* Same violet as the advertise button (client, 23 September). */
  color: #8B008B;
}

.broker-ad__status strong {
  color: var(--text);
}

/*
  Run length: a number the broker types.

  A free number rather than a ladder of fixed runs, because the price is per
  day per market now — "the broker choosess the Germany and Poland for 10 days"
  — so there is no tier for the length to have to land on. The chips beside it
  are shortcuts to the common answers.

  The unit used to sit inside the box, so the field read "10 days" rather than
  as a bare integer. It came out on 19 August — "remove the small 'days' text
  from the field" — and with it the right padding that had been reserved for it:
  3.25rem of clearance for a word that is no longer there would have left the
  number floating in an oddly empty box. The box keeps its width, so nothing
  around it moves.
*/

.days-field {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.days-field .input {
  width: 7.5rem;
}

/* A chip that is a button rather than a removable filter. */

.chip--action {
  cursor: pointer;
  background: var(--surface-2);
}

.chip--action:hover {
  border-color: var(--border-strong);
}

.chip--action.is-on {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--on-primary);
}

[data-theme='dark'] .chip--action.is-on {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--text-on-gold);
}

/*
  The bill.

  "So the system will calculate a bill: Germany 3 € per day x 10 = 30 €; for
  Estonia 1 € per day x 10 = 10 €; total: 40 €." One line per market, each
  showing its own rate and its own arithmetic, then the total — which is the
  client's layout, not a summary of it. A single "2 markets × 10 days = €40"
  would hide the one number the whole per-country price list exists to show.
*/

.ad-bill {
  padding: var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.ad-bill__lines {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.ad-bill__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.ad-bill__line strong {
  color: var(--text);
}

.ad-bill__total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.ad-bill__total strong {
  font-size: var(--fs-h4);
  color: var(--text);
}

/*
  Touch targets for the card pager.

  The dot is 6px and the arrow 20px by design — the client asked for small
  controls. A transparent pseudo-element gives each one a finger-sized hit area
  without changing what is drawn, so the pager stays discreet and still stays
  usable on a phone.
*/

.pcard__dot,
.pcard__pager-arrow,
.map-popup__dot,
.map-popup__arrow {
  position: relative;
}

.pcard__dot::after,
.pcard__pager-arrow::after,
.map-popup__dot::after,
.map-popup__arrow::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32px;
  height: 32px;
  transform: translate(-50%, -50%);
}

/*
  Dots are the exception: they sit 10px apart, so a 32px-wide target on each
  one covered its neighbours as well, and whichever dot came last in the row
  won every click — aiming at the third dot selected the fourth. The targets
  are tiled to the spacing instead, which keeps each click on the dot it was
  aimed at, and the height carries the finger reach. Dragging sideways along
  the strip is the coarse control now; the dots are the precise one.
*/

.pcard__dot::after,
.map-popup__dot::after {
  width: 10px;
  /* 6px dot + the 4px gap beside it — no overlap, no gaps */
  height: 30px;
}

/* --------------------------------------------------------------------------
   Payment method detail panels

   What the chosen method actually involves, shown under the list. Credit card
   draws a disabled card form (entered here, processed by Stripe); Stripe draws
   a sketch of the hosted page the payer would be sent to. Both are
   demonstrations — see the note in each panel.
   -------------------------------------------------------------------------- */

.pay-detail {
  margin-top: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.pay-detail__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.25;
  color: var(--text);
}

.pay-detail__head svg {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--text-muted);
}

.pay-detail .field__hint {
  margin: 2px 0 0;
  line-height: 1.3;
}

/* A sketch of Stripe's hosted page — deliberately abstract, not a facsimile. */

.pay-detail__hosted {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
  background: var(--surface);
}

.pay-detail__bar {
  height: 10px;
  width: 100%;
  border-radius: var(--r-pill);
  background: var(--surface-3);
}

.pay-detail__bar--short {
  width: 55%;
}

.pay-detail__btn {
  align-self: flex-start;
  margin-top: var(--sp-2);
  padding: 6px var(--sp-4);
  border-radius: var(--r-sm);
  background: #635BFF;
  /* Stripe's own violet, for recognition */
  color: #fff;
  font-size: var(--fs-xs);
  font-weight: 700;
}

/* ==========================================================================
   Profile picture and company logo
   ==========================================================================

   The client's 17 August notes, in order:

     "this avatar, what at the moment is round, make rectangle with rounded
      corners"

     "make 'your company logo' where the brokers can upload their compay logo,
      it is rectangle with rounded corners and a bit bigger"

   So both are rounded rectangles and the logo's box is the larger and, more to
   the point, the wider: a company logo is a wordmark more often than a
   monogram, and a square frame crops one. Two fields side by side, because the
   two are not alternatives — a broker has both a face and a brand — and either
   may be left empty.
   ========================================================================== */

.upload-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--sp-5);
}

.upload-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/*
  The picture beside its controls — until neither fits.

  A company logo is as wide as its artwork, and a wordmark is wide: at 375px the
  tile took 221 of the 343 the card has, which left 122 for a column whose
  narrowest possible line is the "Upload logo" button at 137. A flex item cannot
  shrink below that, so the row ran off the side of the screen and the page went
  with it.

  Wrapping is the whole fix: the controls drop under the picture on a screen
  that cannot hold both, and stay beside it on one that can. min-width: 0 lets
  the column below the button — the hint line — reflow rather than setting the
  row's width from the longest word in it.

  Measured in Chrome at 360, 375, 390, 412 and 430: no page overflow at any of
  them, and the two-across layout is unchanged from 480px up.
*/

.upload-field__body {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-4);
}

.upload-field__body > .stack {
  min-width: 0;
}

.upload-field .btn {
  align-self: flex-start;
}

/*
  Profile / logo upload row: both greens are .btn--save-search --sm (34px). The
  bin matches that height as a square. Resting colours are locked to day-mode
  charcoal on white in both themes — dark mode used to invert them to a white
  outline, which the client rejected (20 September). Hover stays #FF0000 via
  .btn--danger-quiet.
*/

.upload-field__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.upload-field__actions .btn--quiet {
  box-sizing: border-box;
  min-height: 34px;
  height: 34px;
  width: 34px;
  padding: 0;
  color: #008000;
  border-color: #008000;
  background: transparent;
}

.upload-field__actions .btn--quiet:hover:not(:disabled) {
  min-height: 34px;
  height: 34px;
}

/*
  Your details: the empty #profile-error slot was still a flex child of
  stack--lg, so a blank gap sat between the header rule and Profile picture /
  Your company logo / advertise. Hide it when unused and tighten the body top.

  23 September: narrow violet header + logo-violet frame — same bar as the
  Account menu, listings Filters, and checkout Resumen.
*/

#profile-error:empty {
  display: none;
}

.card--settings-details {
  border-color: #6F006F;
  overflow: hidden;
}

.card--settings-details > .card__header {
  padding: 2px var(--sp-4);
  min-height: 0;
  background: #6F006F;
  border-bottom-color: #6F006F;
}

.card--settings-details > .card__header .card__title {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.1;
  color: #fff;
}

.card--settings-details > .card__body {
  padding-top: var(--sp-3);
}

/*
  Your details body: pull labels onto their fields, seller-kind ticks under
  Name, and the whole form up — less stack / field / check air
  (client, 23 September).
*/
.card--settings-details .settings-details-form {
  gap: var(--sp-3);
}

.card--settings-details .settings-details-form .field {
  gap: 2px;
}

.card--settings-details .settings-details-form .field__label {
  margin: 0;
  line-height: 1.2;
}

.card--settings-details .settings-details-form .field__hint {
  margin: 0;
  line-height: 1.25;
}

.card--settings-details .settings-details-form .check {
  padding: 0;
  min-height: 0;
  gap: var(--sp-2);
}

.card--settings-details .settings-details-form .msg-picker {
  margin-top: 4px;
  gap: var(--sp-2) 12px;
}

.card--settings-details .settings-details-form .upload-row {
  gap: var(--sp-3);
}

/*
  Preferences / Security / Account: same short header bar height as Your details
  and the Account menu — without violet paint or a violet frame
  (client, 23 September).
*/
.card--settings-compact > .card__header {
  padding: 2px var(--sp-4);
  min-height: 0;
}

.card--settings-compact > .card__header .card__title {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.1;
}

/*
  Preferences + Security: green title bar + green frame.
  Account (delete): red title bar + red frame.
  Same short header as compact; day and night (client, 23 September).
*/
.card--settings-green {
  border-color: #008000;
  overflow: hidden;
}

.card--settings-green > .card__header {
  background: #008000;
  border-bottom-color: #008000;
}

.card--settings-green > .card__header .card__title {
  color: #fff;
}

/*
  Preferences body: pull content up under the green header — less stack /
  check / legend air (client, 23 September).
*/
.card--settings-green > .card__body {
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}

#preferences-error:empty {
  display: none;
}

.settings-prefs-form {
  gap: var(--sp-3);
}

.settings-prefs-form__legend {
  margin: 0 0 4px;
  line-height: 1.2;
  padding: 0;
}

.settings-prefs-form .check {
  padding: 0;
  min-height: 0;
  gap: var(--sp-2);
}

.settings-prefs-form .check + .check {
  margin-top: 2px;
}

.card--settings-red {
  border-color: #FF0000;
  overflow: hidden;
}

.card--settings-red > .card__header {
  background: #FF0000;
  border-bottom-color: #FF0000;
}

.card--settings-red > .card__header .card__title {
  color: #fff;
}

/* --------------------------------------------------------------------------
   The same upload control, in the back office

   The client, 31 August: "in the admin panel where is possible to upload
   images, there make the same upload button what is under the USER PROFILE.
   At the moment there is 'Choose file / No file chosen' - this remove."

   What he photographed is the browser's own <input type="file">: a wide
   bordered rectangle with a grey system button in it and the words "No file
   chosen" beside it, in the platform's font, at the platform's size, unstyled
   because a file input is one of the two controls a stylesheet cannot reach
   inside. Seven of them, on three screens, next to buttons that were all
   Previa's.

   So the back office now does what the profile does: the input keeps every
   attribute it had — its name, its accept list, its htmx post, its indicator —
   and is put inside a <label> that is drawn as an ordinary secondary button
   with .visually-hidden on the control itself. Clicking the label opens the
   picker, because that is what a label does; nothing is hidden from a screen
   reader, and nothing is hidden by a rule that reaches every file input on the
   site — each one opts in where it stands.

   The name of the chosen file is the one thing the native control gave that
   this has to give back. The profile does not need it: it posts on change and
   the new picture appears. Two of the seven — the favicon and the share image
   on the SEO screen — are chosen first and uploaded with a second button, so
   without this they would say nothing at all between the two presses.
   -------------------------------------------------------------------------- */

.upload-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
  max-width: 100%;
}

.upload-btn > .btn {
  flex: none;
}

/* One line, ellipsised: a phone camera's file name is longer than the column
   it is standing in, and a wrapped one would move the button under it. */

.upload-btn__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* ADS placeholder remove — quiet at rest, red on hover. */

.btn.ads-remove:hover:not(:disabled),
.btn.ads-remove:hover:not(:disabled) .icon,
.btn.ads-remove:hover:not(:disabled) svg {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
}

/* ADS New banner editor — tight packing. */

.banner-editor {
  gap: var(--sp-2);
}

.banner-editor .field {
  gap: 2px;
}

.banner-editor .field__label {
  margin: 0;
  line-height: 1.2;
}

.banner-editor .textarea {
  min-height: 0;
}

.banner-editor .grid {
  gap: var(--sp-2);
}

/* Placement pick matches market-picker field height (40px). */
#banner-editor .field > .sort-menu--field .sort-menu__btn {
  height: 40px;
  min-height: 40px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-sm);
  font-weight: 500;
  box-sizing: border-box;
}

/*
  Banner button up/down scale: filled left #008000, remainder #DAA520, no border.
  Thin track (colours on ::before); input stays transparent for the thumb.
*/

.banner-cta-range-wrap {
  --range-pct: 25%;
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  height: 18px;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
}

.banner-cta-range-wrap::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 6px;
  margin-top: -3px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(
    to right,
    #008000 0%,
    #008000 var(--range-pct),
    #DAA520 var(--range-pct),
    #DAA520 100%
  );
  pointer-events: none;
}

.banner-cta-range {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 18px;
  margin: 0;
  padding: 0;
  background: transparent !important;
  border: 0 !important;
  border-radius: 0;
  box-shadow: none !important;
  outline: none !important;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  accent-color: #008000;
}

.banner-cta-range:focus,
.banner-cta-range:focus-visible {
  outline: none !important;
  box-shadow: none !important;
  border: 0 !important;
}

.banner-cta-range::-webkit-slider-runnable-track {
  height: 6px;
  border: 0 !important;
  border-radius: 999px;
  background: transparent !important;
}

.banner-cta-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -4px;
  border: 0 !important;
  border-radius: 50%;
  background: #008000 !important;
  box-shadow: none !important;
  cursor: pointer;
}

.banner-cta-range::-moz-range-track {
  height: 6px;
  border: 0 !important;
  border-radius: 999px;
  background: transparent !important;
}

.banner-cta-range::-moz-range-progress {
  height: 6px;
  border: 0 !important;
  border-radius: 999px;
  background: transparent !important;
}

.banner-cta-range::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: 0 !important;
  border-radius: 50%;
  background: #008000 !important;
  box-shadow: none !important;
  cursor: pointer;
}

#banner-editor .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

#banner-editor .card__header,
#banner-editor .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

/* ==========================================================================
   Upload progress

   The client, 29 August: "there is no green progress bar for the user what says
   that process is ongoing, create it." He had decided the artwork upload was
   broken and stuck — it was neither, it was a two-megabyte file being decoded
   and written into three derivatives with nothing on screen to say so.

   Shown by htmx, which puts .htmx-request on the element named in hx-indicator
   for exactly the life of the request. Green, because he asked for green and
   because that is this interface's "working, nothing wrong" colour.

   Two states in one bar. Where the browser reports upload progress,
   previa.core.js sets --upload-progress and the fill is that fraction of the
   track. Where it does not — and after the bytes are sent, while the server is
   still scaling — the fill runs its own stripe across the track, because a bar
   frozen at 100% for four seconds reads as a hang.
   ========================================================================== */

.uploading {
  display: none;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

/* htmx adds this class for the life of the request and removes it however the
   request ends, success or failure — so the bar cannot be left running. */

.uploading.htmx-request {
  display: flex;
}

.uploading__track {
  position: relative;
  overflow: hidden;
  height: 6px;
  border-radius: 999px;
  background: var(--surface-3);
}

/*
  Indeterminate by default — which covers both the browser that reports no
  upload progress and, more usefully, the stretch after the last byte is sent
  while the server is still decoding and writing three derivatives. A bar parked
  at 100% through that reads as a hang, which is the thing this exists to stop.
*/

.uploading__bar {
  height: 100%;
  width: 40%;
  border-radius: 999px;
  background: var(--success);
  animation: uploading-sweep 1.1s ease-in-out infinite;
}

/* Bytes going up, and the browser is counting them: a real fraction of the
   track. previa.core.js sets both the attribute and the number. */

.uploading__bar[data-upload-state='sending'] {
  width: calc(var(--upload-progress, 0) * 1%);
  margin-left: 0;
  animation: none;
  transition: width 120ms linear;
}

@keyframes uploading-sweep {
  0%   {
    margin-left: -40%;
  }

  100% {
    margin-left: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .uploading__bar {
    animation: none;
    width: 100%;
    margin-left: 0;
  }
}

.uploading__label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* ==========================================================================
   Back-office image gallery

   An article's further pictures, on the editor that writes them. The client,
   28 August: "for the articles there is no place to upload images, make there
   a place to upload, and for multiple images, in the articles we do gallery
   with more images."

   Each tile carries a hidden field, so what is on the screen is exactly what
   the save will store: removing a tile removes the field with it. The grid
   collapses to nothing when it is empty, which is why the row above it does
   not reserve space it may never use.
   ========================================================================== */

.admin-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--sp-3);
}

.admin-gallery:empty {
  display: none;
}

.admin-gallery__tile {
  position: relative;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface-2);
}

.admin-gallery__tile img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* Top-right of its own tile, on a disc dark enough to be seen over whatever
   the photograph has in that corner. */

.admin-gallery__remove {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
}

.admin-gallery__remove:hover {
  background: rgba(0, 0, 0, 0.78);
}

.admin-gallery__remove svg {
  width: 15px;
  height: 15px;
}

/* --------------------------------------------------------------------------
   The same grid, put in order by hand.

   The client, 3 September: "Make only one 'choose images' button and if images
   are uploaded, then can drag them to reorder, and the first is preview
   image." So the developments editor's grid carries --sortable, and three
   things come with it: the tile is grabbable, the tile being dragged is
   faded out of the way, and the tile that happens to be first says what being
   first means.

   The modifier rather than .admin-gallery itself, because the article editor
   uses the same grid and the client did not ask for it there — an article has
   no cover chosen by position.

   "Preview" is drawn by :first-child rather than by a class the script keeps in
   step. The order lives in the DOM, so the browser already knows which tile is
   first and cannot disagree with itself about it halfway through a drag.
   -------------------------------------------------------------------------- */

.admin-gallery--sortable .admin-gallery__tile {
  cursor: grab;
}

.admin-gallery--sortable .admin-gallery__tile.is-dragging {
  opacity: 0.4;
  cursor: grabbing;
}

.admin-gallery__badge {
  position: absolute;
  left: var(--sp-2);
  top: var(--sp-2);
  padding: 2px 7px;
  border-radius: var(--r-sm);
  background: var(--gold);
  color: var(--text-on-gold);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Every tile carries the badge and only the first one shows it, so nothing has
   to be added or taken away as the order changes. */

.admin-gallery__tile .admin-gallery__badge {
  display: none;
}

.admin-gallery--sortable .admin-gallery__tile:first-child .admin-gallery__badge {
  display: block;
}

/* The arrows: reordering for a keyboard and for a touch screen, which has no
   drag-and-drop to offer. Always reachable, drawn at rest only when the tile is
   pointed at or something inside it has focus — the same treatment the
   publishing form's photographs take. */

.admin-gallery__move {
  position: absolute;
  left: var(--sp-2);
  bottom: var(--sp-2);
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.admin-gallery__tile:hover .admin-gallery__move,
.admin-gallery__tile:focus-within .admin-gallery__move {
  opacity: 1;
}

@media (hover: none) {
  .admin-gallery__move {
    opacity: 1;
  }
}

.admin-gallery__move button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
}

.admin-gallery__move button:hover {
  background: rgba(0, 0, 0, 0.78);
}

.admin-gallery__move button svg {
  width: 14px;
  height: 14px;
}

/* The profile picture. Square, so a portrait is not asked to crop to a circle,
   with the same corner radius the rest of the interface uses on a small tile. */

.avatar-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 88px;
  height: 88px;
  border-radius: var(--r-md);
  object-fit: cover;
  object-position: center top;
  background: var(--navy-tint);
  color: var(--slate);
  font-size: var(--fs-h3);
  font-weight: 600;
  overflow: hidden;
}

.avatar-tile--empty {
  border: 1px dashed var(--border-strong);
}

/* No photo yet on the listing seller card — same fill as the card, no glyph. */
.avatar-tile--blank {
  background: var(--surface-2);
  border: none;
  color: transparent;
}

.property-detail #seller-contact .avatar-tile {
  background: var(--surface-2);
}

.property-detail #seller-contact .seller-logo {
  background: var(--surface-2);
}

.property-detail #seller-contact .seller-logo img[data-previa-defer] {
  opacity: 0;
}

/* The company logo. Wider than tall and larger than the avatar, sized to the
   200 × 56 the seller box and the broker profile both reserve for one. */

/*
  An uploaded company logo, and nothing around it.

  The client, 25 August: "if the user uploads the logo then remove the white
  borders there, make it nice, all the box is filled with banner only!"

  It was a fixed 160x88 box with 12px of padding and object-fit: contain. A logo
  is a wide shape — the field asks for 400x120 — so contain fitted it to the
  width and left a white band above and below it, inside a white box that had
  already added a white margin of its own. Two sources of white border for one
  picture.

  The tile is the picture now: no padding, and a height with the width left to
  the artwork's own aspect ratio. The pipeline never crops or pads a logo — see
  IngestLogo — so what is stored is the logo's own shape, and drawing it at its
  own shape is what leaves no border to remove. max-width keeps an unusually
  wide one from pushing the upload button off the row.

  The white background stays. A logo drawn as dark artwork on transparency
  disappears against the dark theme, and it is somebody else's mark to be shown
  as they drew it — but it is now exactly the size of the mark.
*/

.logo-tile {
  flex: none;
  display: block;
  width: auto;
  max-width: 220px;
  height: 56px;
  border-radius: var(--r-md);
  object-fit: contain;
  /* never crop somebody's logo */
  background: #fff;
  /* a logo is drawn for white, in either theme */
  overflow: hidden;
}

/* The empty state has no artwork to take its shape from, so it keeps a box. */

.logo-tile--empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 160px;
  background: var(--surface-2);
  border: 1px dashed var(--border-strong);
  color: var(--text-subtle);
}

.logo-tile--empty svg {
  width: 28px;
  height: 28px;
}

/* ==========================================================================
   Tag picker
   ==========================================================================

   The market picker's menu, choosing several instead of one — see
   components/tag-picker.html. Used for the languages of communication and for
   the countries a seller is active in.
   ========================================================================== */

.tagpick {
  position: relative;
}

/*
  The box and its menu, and nothing else.

  The menu is placed with `top: calc(100% + 4px)`, and a percentage top is a
  percentage of the *containing block's* height — so while this wrapper did not
  exist the 100% was the height of the whole field, hint included, and the menu
  opened that far below the button instead of under it. On the settings screen
  that dropped it clear of the card, which is what the client saw: "these
  dropdown menus open in wrong place, make them to be opened in the same place
  where you click them".
*/

.tagpick__anchor {
  position: relative;
}

/* Chosen values and the button that opens the menu, in one box, so the control
   reads as a single field however many tags are in it. */

.tagpick__box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: 7px var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
}

/*
  "Any language" while nothing is ticked — the resting state of the two search
  filters, and the whole control at that point.

  Drawn as the field's own text with a chevron rather than as a tag or a dashed
  pill, because it is not a choice that can be removed: it is what the field
  says before a choice is made, and the first tag replaces it. Fills the box so
  the whole field opens the menu, the way clicking a select anywhere opens it.
*/

.tagpick__any {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-subtle);
  font: inherit;
  font-size: var(--fs-sm);
  text-align: left;
  cursor: pointer;
}

.tagpick__any svg {
  width: 16px;
  height: 16px;
  flex: none;
}

.tagpick__any:hover {
  color: var(--text);
}

/*
  No golden selector. Dark mode's :focus-visible ring is --focus-color (gold);
  after a click the "any language" control kept that frame painted around the
  box. Keyboard focus still moves; it just does not draw the ring.
*/
.tagpick__any:focus,
.tagpick__any:focus-visible,
.tagpick__add:focus,
.tagpick__add:focus-visible {
  outline: none;
  box-shadow: none;
}

.tagpick__tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 4px 4px 8px;
  background: var(--surface-3);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
}

/*
  Read-only twin of .tagpick__tag: same flag + name chip the settings picker
  uses, without the remove control — so the right padding matches the left
  instead of leaving room for a cross that is not there.
*/
.tagpick__tag--static {
  padding: 4px 8px;
}

/*
  Active in on the listing's seller / broker box.

  Under the company banner when one is present, and in the same foot of the
  card when it is not. Title wording matches the settings hint's "Active in".
*/
.seller-active {
  margin-top: var(--sp-2);
}

.seller-active__title {
  margin: 0 0 6px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.02em;
}

.seller-active__list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tagpick__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.tagpick__remove svg {
  width: 12px;
  height: 12px;
}

.tagpick__remove:hover {
  background: var(--error-bg);
  color: var(--error);
}

.tagpick__remove:focus-visible {
  box-shadow: var(--focus-ring);
}

.tagpick__add {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px var(--sp-3);
  border: 1px dotted #008000;
  border-radius: var(--r-pill);
  background: none;
  color: #008000;
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.tagpick__add svg {
  width: 13px;
  height: 13px;
}

.tagpick__add:hover {
  border-color: #006600;
  color: #006600;
}

[data-theme='dark'] .tagpick__add:hover {
  border-color: #00a000;
  color: #00a000;
}

/*
  The menu itself. Anchored to the bottom of the box it opens from, and capped
  so a long list scrolls inside it instead of running off the card.

  This element does the positioning, not the .menu inside it: two stacked
  offsets — a relative one here and an absolute one on .menu — were how the
  original ended up placing the menu twice and landing it in neither place.

  Selected through the anchor rather than on its own, because .dropdown and
  .dropdown > .menu are declared in pages.css, which loads after this file. At
  equal specificity those would win and the element would stay `relative` — the
  same trap the map popup's close button once fell into. Two classes beat one
  whatever the load order.
*/

.tagpick__anchor > .tagpick__dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: auto;
  z-index: calc(var(--z-header) + 5);
  width: min(360px, 100%);
}

.tagpick__anchor > .tagpick__dropdown > .menu {
  position: static;
  margin-top: 4px;
}

/* Flipped above the box, set by previaTagPicker.place() when what is below is
   not tall enough to hold the menu. */

.tagpick__anchor.is-up > .tagpick__dropdown {
  top: auto;
  bottom: 100%;
}

.tagpick__anchor.is-up > .tagpick__dropdown > .menu {
  margin-top: 0;
  margin-bottom: 4px;
}

/*
  Shorter than the market menu's own list, so a picker opening low on a form
  still shows its menu rather than running under the footer. Scoped through the
  anchor for the pages.css load-order reason above — .market-menu__list sets
  min(46vh, 320px) there and would otherwise win.
*/

.tagpick__anchor .tagpick__list {
  max-height: min(260px, 38vh);
}

/*
  An option is a label wrapping a real checkbox, so the whole row is a hit
  target and the set of checkboxes *is* what the form submits — nothing is
  mirrored into hidden inputs. The checkbox itself is hidden and the tick on the
  right is what reports the state, the same arrangement the market menu uses for
  its current market.
*/

.tagpick__option {
  cursor: pointer;
}

/*
  Language / country rows — same tighter spacing as the market country menu.
  Font size and the menu window height stay put; only the empty space between
  rows shrinks so more languages fit in the same window.
*/

.tagpick__option.market-menu__item {
  padding-block: 3.6px;
}

.tagpick__input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.tagpick__name {
  flex: 1;
  min-width: 0;
}

.tagpick__native {
  color: var(--text-subtle);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

.tagpick__tick {
  display: none;
  color: var(--success);
}

.tagpick__tick svg {
  width: 16px;
  height: 16px;
}

.tagpick__input:checked ~ .tagpick__tick {
  display: inline-flex;
}

.tagpick__input:checked ~ .tagpick__native {
  display: none;
}

.tagpick__option:has(.tagpick__input:checked) {
  background: var(--surface-3);
}

.tagpick__option:has(.tagpick__input:focus-visible) {
  box-shadow: var(--focus-ring);
}

/* The seller's picture on a listing: the same rounded rectangle as the profile
   screen it is uploaded on, at the 64px that box gives it. */

.avatar-tile--sm {
  width: 64px;
  height: 64px;
  font-size: var(--fs-lg);
  border-radius: var(--r-sm);
}

/* The same tile at profile-header size. */

.avatar-tile--lg {
  width: 96px;
  height: 96px;
}

/*
  The seller's picture and name, as one link to their page.

  A block link rather than two, so the picture and the name cannot end up
  pointing at different places, and only the name takes the link colour on
  hover: the role and the company inside the anchor are describing the person,
  not offering a second destination.
*/

.seller-identity {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  border-radius: var(--r-md);
}

.seller-identity__name {
  color: var(--text);
}

/*
  Seller "About you" under the face on a listing. Closer to the identity row
  (half the .stack gap) and a tighter line-height than body muted text.
*/

.seller-bio {
  margin: 0;
  margin-top: calc(var(--sp-4) / -2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: 1.25;
}

/*
  A piece of text that is a link and has to say so without an underline — the
  address at the top of a listing, which opens Google Maps navigation. The
  client asked for blue and for no rule under it, in both themes.
*/

/*
  The listing's address, which opens Google Maps directions.

  #0000FF exactly, in both themes, at the client's word on 18 August: "in the
  single ad page this link works perfectly, just make the color of it #0000FF".
  It was --link, a softer blue picked per theme for contrast. Hover lightens
  rather than darkens, since pure blue has nowhere darker to go that still
  reads as the same colour.
*/

.address-link {
  color: #0000FF;
  text-decoration: none;
}

.address-link:hover {
  color: #3333FF;
  text-decoration: none;
}

/*
  The seller's own line about where they are, under their identity in the
  listing's contact box. A pin, then their text — and the pin is 16px so it
  reads as a marker beside a line of type rather than as an icon in its own
  right.
*/

.seller-place {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.seller-place svg {
  width: 16px;
  height: 16px;
  flex: none;
  margin-top: 1px;
  color: var(--text-subtle);
}

/*
  The seller's YouTube video on a listing, and the channel card beside it.

  The client, 31 August: "so the user can enter youtube video link there and
  this link youtube preview will be displayed in single ad page under he's
  profile."

  The preview is the video's own poster with YouTube's play button over it, and
  pressing it puts the player in the same box. The box keeps 16/9 whichever of
  the two is in it, so the card does not jump when the video starts.

  .yt-channel had never had a rule of its own. It was written with four classes
  and none of them existed in the stylesheet, so a seller who saved a channel
  address got an unstyled row of an SVG and two lines of text at the foot of an
  otherwise finished card. Both are here now, and they are deliberately alike:
  the same border, radius and hover as .seller-logo under them.
*/

.yt-video {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
}

/*
  One box, and the poster and the player both inside it and out of the flow.

  Siblings in the flex column would reflow it every time one replaced the other,
  and that reflow is heard by the ResizeObserver the map provider watches its
  own pane with — which called Google's resize mid-initialisation and threw
  inside Google's script on most presses. The stage has one height whatever is
  in it, so pressing play changes no geometry outside these four lines.
*/

.yt-video__stage {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: var(--r-md);
  overflow: hidden;
  background: #000;
}

.yt-video__poster,
.yt-video__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
}

.yt-video__poster {
  display: block;
  padding: 0;
  overflow: hidden;
  cursor: pointer;
}

/*
  hqdefault is 480x360: the 480x270 widescreen frame with a 45px black bar above
  and below it. object-fit: cover in a 16:9 box crops exactly those two bars and
  nothing else — 45/480 is 0.09375 of the width, and the overflow cover produces
  is 0.1875 of it, half above and half below. So the arithmetic does the framing
  and no zoom is needed; a transform on top of this was cropping the picture a
  second time and eating the video's own title.
*/

.yt-video__poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.yt-video__play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.yt-video__play svg {
  width: 68px;
  height: 48px;
}

.yt-video__play-bg {
  fill: #212121;
  fill-opacity: 0.8;
  transition: fill-opacity var(--dur-fast) var(--ease);
}

.yt-video__poster:hover .yt-video__play-bg {
  fill: #f00;
  fill-opacity: 1;
}

.yt-video__link,
.yt-channel {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.yt-video__link:hover,
.yt-channel:hover {
  border-color: var(--border-strong);
  background: var(--surface-2);
}

/* Border matches the external-link icon: day #000080, dark #0000CD.
   Same colours on the seller's YouTube channel card in the contact column. */
a.yt-video__link,
a.yt-channel {
  margin-top: 0;
  border: 1px solid #000080;
}

[data-theme='dark'] a.yt-video__link,
[data-theme='dark'] a.yt-channel {
  border-color: #0000CD;
}

a.yt-video__link:hover,
a.yt-video__link:focus-visible,
a.yt-channel:hover,
a.yt-channel:focus-visible {
  border-color: #000080;
  background: transparent;
}

[data-theme='dark'] a.yt-video__link:hover,
[data-theme='dark'] a.yt-video__link:focus-visible,
[data-theme='dark'] a.yt-channel:hover,
[data-theme='dark'] a.yt-channel:focus-visible {
  border-color: #0000CD;
}

.yt-video__link > span {
  flex: 1;
  font-size: var(--fs-sm);
  font-weight: 600;
}

.yt-video__link svg,
.yt-channel svg {
  flex: none;
}

.yt-video__link .brand-mark,
.yt-channel .brand-mark {
  width: 22px;
  height: 22px;
}

.yt-video__link > .icon,
.yt-channel > .icon {
  color: #000080;
}

[data-theme='dark'] .yt-video__link > .icon,
[data-theme='dark'] .yt-channel > .icon {
  color: #0000CD;
}

.yt-channel__text {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

.yt-channel__title {
  font-size: var(--fs-sm);
  font-weight: 600;
}

.yt-channel__handle {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
  The company logo at the foot of the seller box.

  A logo is somebody else's artwork: it is never cropped, never tinted and never
  put on a coloured ground, so the frame is white in both themes and the image
  is contained rather than covered. The separating rule above it is what marks
  it as the company behind the person rather than a second control.
*/

.seller-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-3);
  margin-top: var(--sp-1);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition: border-color var(--dur-fast) var(--ease);
}

.seller-logo:hover {
  border-color: var(--border-strong);
}

.seller-logo img {
  max-width: 100%;
  height: auto;
  max-height: 56px;
  object-fit: contain;
}

/*
  On a listing, the company banner is stretched to its field rather than sat in
  the middle of one.

  The client, 31 August: "in the single ad page this broker company banner, what
  is this?? Make it to fill the space now normally, make there that it always
  stretches the whole area, so the system fits this banner into this field."

  What he was looking at is the rule directly above: --sp-3 of padding all round,
  a 56px ceiling on the height and object-fit: contain. A 2650x750 company
  banner arriving in a 230px column was therefore drawn about 200px wide inside a
  white box a good deal larger than itself — a small picture floating in a frame,
  which is exactly what "what is this??" is asking about.

  Stretched, not fitted. He named the behaviour he wanted twice in one sentence
  ("always stretches the whole area", "the system fits this banner into this
  field"), and it is the same one he chose for site banners on 30 August and
  which the Advertising screen calls "Stretched to the area - nothing cut off,
  the shape changes". object-fit: fill is that setting.

  aspect-ratio gives the field a shape of its own, so every company gets the same
  strip whatever the proportions of the file they uploaded — 200/56 is the size
  the <img> has always declared, so nothing moves for a banner already drawn at
  it. The white ground stays: a transparent PNG of a dark wordmark still needs
  something to be dark against.

  :not(.profile-head__logo) keeps this to the listing page. The broker and agency
  profile headers use the same class with a fixed 200px width and their own
  padding, and nobody has asked for those to change.
*/

.seller-logo:not(.profile-head__logo) {
  padding: 0;
  overflow: hidden;
  aspect-ratio: 200 / 56;
  /* No grey frame: the banner fills the same window edge-to-edge. The client,
     16 September: remove the frame completely and extend the logo into the
     space the border used to take — the field itself does not grow. */
  border: none;
  background: transparent;
}

.seller-logo:not(.profile-head__logo):hover {
  border-color: transparent;
}

.seller-logo:not(.profile-head__logo) img {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: fill;
  display: block;
}

/*
  The seller's email is its own label, so a long one has to be able to give way.

  The address is the button's text, matching "revealed-contact" on the broker
  branch, which has printed the email that way since it was written. An address
  is arbitrary in length, though, and a sidebar is not: ellipsis rather than a
  wrap keeps the control one line tall, and the href carries the whole address
  whatever is drawn. Measured at 222px inside a 316px button on a 390px phone,
  so this is a guard rather than something in force today.
*/

.seller-contact__phone {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

.seller-contact__phone:hover:not(:disabled),
.seller-contact__phone:focus-visible {
  background: #006600;
  border-color: #006600;
  color: #fff;
}

.seller-contact__mail {
  min-width: 0;
  background: #0000ff;
  border-color: #0000ff;
  color: #fff;
}

.seller-contact__mail:hover:not(:disabled),
.seller-contact__mail:focus-visible {
  background: #0000cc;
  border-color: #0000cc;
  color: #fff;
}

.seller-contact__mail > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The agency's logo in place of a monogram on its profile header. Wider than
   the tile it replaces, and flush with the heading beside it. */

.profile-head__logo {
  flex: none;
  width: 200px;
  padding: var(--sp-3) var(--sp-4);
}

/* ==========================================================================
   Direct from the owner
   ==========================================================================

   "add more one thing 'direct from the owner' and for this in a frontend there
   is a special label. As sometimes it is important to note that the property
   owner itself is selling it."

   Its own colour, not a reuse of one already on the card. Gold is a paid
   placement and green is something Previa has verified; this is neither — it is
   a fact about who is on the other end of the enquiry, and it has to be
   tellable apart from both at a glance on a card that may carry all three.
   Teal is the third accent the palette already holds, so the badge is new
   without introducing a new hue.
   ========================================================================== */

.badge--owner {
  background: var(--teal-tint);
  color: var(--teal);
  border: 1px solid var(--teal);
}

[data-theme='dark'] .badge--owner {
  background: var(--teal-tint);
  color: var(--teal);
  border-color: var(--teal);
}

.badge--owner svg {
  width: 13px;
  height: 13px;
}

/* A hint under a checkbox, for a control whose effect is not obvious from its
   own label — "Direct from the owner" being the case it was added for. */

.check__text:has(.check__hint) {
  display: flex;
  flex-direction: column;
  gap: 1px;
  line-height: 1.25;
}

.check__hint {
  display: block;
  margin-top: 0;
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text-muted);
  line-height: 1.2;
}

/* ==========================================================================
   Listing status, and the statistics panel
   ========================================================================== */

/*
  A quiet destructive action. Resting face matches other quiet outline buttons
  (view / deactivate). Hover is literal #FF0000 — client, 22 September.
*/

.btn.btn--danger-quiet {
  color: var(--text);
  border-color: var(--border);
  background: transparent;
}

.btn.btn--danger-quiet:hover:not(:disabled),
.btn.btn--danger-quiet:hover:not(:disabled) .icon {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
}

.btn.btn--danger-quiet:disabled,
.btn.btn--danger-quiet[aria-disabled='true'] {
  opacity: 0.45;
}

/* Settings: remove extra phone — neutral at rest, red trash + label on hover. */

.btn.settings-phone-remove {
  color: var(--text);
  border-color: var(--border);
  background: transparent;
}

.btn.settings-phone-remove:hover:not(:disabled),
.btn.settings-phone-remove:hover:not(:disabled) .icon {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
}

/* Same pattern for admin view / deactivate / publish / set → #008000.
   Transparent at rest (like danger-quiet) so featured gold cards do not show
   a solid surface chip behind the label. */

.btn.btn--ok-quiet {
  color: var(--text);
  border-color: var(--border);
  background: transparent;
}

.btn.btn--ok-quiet:hover:not(:disabled),
.btn.btn--ok-quiet:hover:not(:disabled) .icon {
  color: #008000;
  border-color: #008000;
  background: transparent;
}

/* The four totals above the chart. */

.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface-2);
  border-radius: var(--r-md);
}

.stat-strip__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.stat-strip__value {
  font-size: var(--fs-h4);
  font-weight: 700;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.stat-strip__label {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

/*
  The chart itself.

  Fourteen bars in a dialog is tighter than the admin dashboard's six, so the
  columns lose their gap and the labels turn on their side rather than
  overlapping. Everything else is the shared .bars component — one chart
  treatment on the site, and still no charting library.
*/

.stats-chart {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Statistics modal — three blocks: all opens, unique/day, viewport. */
.stats-modal {
  gap: var(--sp-5);
}

.stats-modal__head {
  align-items: flex-start;
}

.stats-modal__head-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}

.stats-modal__title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
}

.stats-modal__title-row .modal__title {
  margin: 0;
}

.stats-modal__heading {
  color: #6F006F;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.stats-modal__saves {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  margin: 0;
  margin-left: auto;
  font-size: var(--fs-lg);
  font-weight: 700;
  line-height: 1.2;
  color: #6F006F;
}

.stats-modal__saves-heart {
  display: inline-flex;
  color: #ff0000;
}

.stats-modal__saves-heart svg {
  width: 1.15em;
  height: 1.15em;
  fill: #ff0000;
  stroke: #ff0000;
  stroke-width: 2;
}

.stats-modal__saves-label {
  font-weight: 700;
}

.stats-modal__saves-count {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.stats-modal__listing {
  margin: 0;
  font-size: var(--fs-md);
  line-height: 1.3;
}

.stats-block {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.stats-block__head {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.stats-block__title {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.1;
  color: var(--heading);
}

.stats-block__lede {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.05;
  color: var(--text-muted);
}

.stats-chart .bars--dense {
  gap: 6px;
  height: 190px;
  padding-top: var(--sp-2);
  overflow-x: auto;
  /* a very narrow dialog scrolls rather than crushing */
}

.bars--dense .bars__col {
  min-width: 26px;
  gap: 4px;
}

.bars--dense .bars__bar {
  max-width: none;
  border-radius: 3px;
}

.bars--dense .bars__track {
  width: 100%;
}

.bars--dense .bars__label {
  font-size: 10px;
  white-space: nowrap;
  writing-mode: vertical-rl;
  line-height: 1;
}

/* The count above each bar, so the chart is readable as numbers too. */

.bars__value {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   Sort by — the one menu, on the listings page and on the map
   ==========================================================================
   "In the maps page there is 'All locations · Newest first' there make the same
   'sort by' menu as in listing page. And sort by in both places write with
   small letter."

   One rule for one component, because the client's point was that the two
   screens should offer the same control rather than two that look alike. The
   label sits on the same baseline as the field and never wraps: it is two short
   words, and a "sort" / "by" break would read as two labels.

   Lower-case is set here rather than left to the markup so it survives a
   translation pass that capitalises its strings: .field__label is used across
   the site with sentence case, and this is the one place that opts out. */

.sort-field {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  position: relative;
}

.sort-field__label {
  white-space: nowrap;
  margin: 0;
  text-transform: lowercase;
  /* Same green in day and night (client, listings / map sort). */
  color: #008000;
}

.sort-field__select {
  min-height: 40px;
  width: auto;
}

.sort-menu {
  position: relative;
}

.sort-menu__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 40px;
  padding: 0 var(--sp-4);
  background: var(--surface);
  border: none;
  border-radius: var(--r-md);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 500;
  text-transform: lowercase;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: none;
  outline: none;
}

.sort-menu__btn:hover {
  background: var(--surface-2);
}

.sort-menu__btn:focus,
.sort-menu__btn:focus-visible {
  outline: none;
  box-shadow: none;
}

.sort-menu__btn svg {
  width: 16px;
  height: 16px;
  color: var(--text-muted);
  flex: none;
}

.sort-menu__btn[aria-expanded='true'] svg {
  transform: rotate(180deg);
}

.sort-menu .dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: calc(var(--z-header) + 5);
  min-width: 100%;
}

.sort-menu__list {
  min-width: 220px;
  padding: 6px;
  list-style: none;
  margin: 0;
}

.sort-menu__list .menu__item {
  justify-content: space-between;
  padding: 8px 10px;
  text-transform: lowercase;
  font-weight: 500;
}

.sort-menu__list .menu__item svg {
  opacity: 0;
  color: #008000;
}

.sort-menu__list .menu__item.is-active {
  background: var(--navy-tint);
  font-weight: 600;
}

.sort-menu__list .menu__item.is-active svg {
  opacity: 1;
}

.sort-menu__list .menu__item:hover {
  background: var(--surface-3);
}

.sort-menu__list .menu__item.is-active,
.sort-menu__list .menu__item.is-active:hover {
  background: var(--navy-tint);
  font-weight: 600;
}

/* Full-width sort-style picker inside a form field (ADS placement). */

.field > .sort-menu--field {
  width: 100%;
}

/* Login page Photograph fit only — 60% narrower than a full-width field. */
.field > .sort-menu--field.login-fit-menu {
  width: 40%;
  max-width: 40%;
}

.field > .sort-menu--field.login-fit-menu .sort-menu__btn,
.field > .sort-menu--field.login-fit-menu .sort-menu__list .menu__item {
  font-size: 0.75rem;
  line-height: 1.25;
  white-space: nowrap;
}

.field > .sort-menu--field.login-fit-menu .sort-menu__btn {
  padding-inline: 8px;
  gap: 4px;
}

.field > .sort-menu--field.login-fit-menu .sort-menu__list .menu__item {
  padding: 6px 8px;
}

.field > .sort-menu--field .sort-menu__btn {
  width: 100%;
  justify-content: space-between;
  /* Same frame as .input / Artwork address — not the borderless sort-by chip. */
  border: 1px solid var(--border);
  background: var(--surface);
  min-height: var(--control-h);
  font-size: var(--fs-base);
  font-weight: 400;
  text-transform: lowercase;
  white-space: normal;
  text-align: left;
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.field > .sort-menu--field .sort-menu__btn:hover {
  border-color: var(--border-strong);
  background: var(--surface);
}

.field > .sort-menu--field .sort-menu__btn:focus,
.field > .sort-menu--field .sort-menu__btn:focus-visible,
.field > .sort-menu--field .sort-menu__btn[aria-expanded='true'] {
  border: 1px solid var(--border);
  border-color: var(--field-focus);
  box-shadow: var(--field-focus-ring);
}

.field > .sort-menu--field .dropdown {
  left: 0;
  right: 0;
}

.field > .sort-menu--field .sort-menu__list {
  min-width: 100%;
}

.field > .sort-menu--field .sort-menu__search {
  padding: 6px 6px 2px;
}

.field > .sort-menu--field .sort-menu__search .input {
  width: 100%;
  min-height: 36px;
}

.field > .sort-menu--field .sort-menu__list--scroll {
  max-height: min(360px, 50vh);
  overflow-y: auto;
}

/* Market country rows: keep "Germany (de)" — name as written, code lower. */
.field > .sort-menu--field .sort-menu__list--scroll .menu__item {
  text-transform: none;
}

.field > .sort-menu--field .sort-menu__empty {
  margin: 0;
  padding: 8px 12px 10px;
}

/* ==========================================================================
   Distance
   ==========================================================================
   "So on every broker profile now come the 'distance' like it is in sexydate
   page", and for listings: "all the ads displayed have on bottom the distance
   and distance number is red."

   The reference the client sent puts the word in ordinary ink and the figure in
   red, and that is the whole design: the label is furniture, the number is what
   one card is compared with the next on.

   The red is the literal #FF0000 in both themes, matching the hearts and the
   clear-all cross — every red the client has specified on this site is that
   one, and --error is a muted brick that does not read as the same instruction.

   One block for both cards, because it is the same fact in both places. */

.distance-line {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  margin-top: 2px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* Directory / homepage broker cards are centred; keep the distance with them. */
.bcard:not(.bcard--map):not(.bcard--row) .distance-line {
  justify-content: center;
}

.distance-line__value {
  color: #FF0000;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Broker cards beside the map: the narrow column's variant.

   The directory's card is a portrait photograph over a fixed identity block —
   right for a four-across grid, far too tall for a 420px column that also has
   to hold listings. So the map variant lays the same content along a row, the
   way .pcard--map does, and drops the parts that need width: the e-mail
   address, the second action and the rating's review count.

   Same component and the same class names, so nothing about it can drift from
   the card a reader met on the homepage — but three of the base card's rules
   exist only to make the photograph full-bleed at the top of a tall card, and
   all three have to be undone here: the negative margins that push the media
   out through the padding, the 4:5 portrait ratio, and the centred text. */

.bcard--map {
  /* A positioning context for the locate button. The tall card never needed
     one, because it has no absolutely positioned child of its own. */
  position: relative;
  display: grid;
  /* Same thumbnail column as .pcard--map — 132px flush to the left edge. */
  grid-template-columns: 132px minmax(0, 1fr);
  grid-template-areas:
    'media identity'
    'media foot';
  gap: 0;
  align-items: stretch;
  justify-items: stretch;
  align-content: start;
  width: 100%;
  height: auto;
  padding: 0;
  text-align: left;
  overflow: hidden;
}

.bcard--map .bcard__media {
  grid-area: media;
  position: relative;
  align-self: stretch;
  justify-self: stretch;
  width: 132px;
  min-height: calc(132px * 3 / 4);
  /* Edge-to-edge like .pcard--map — no padded inset tile. */
  margin: 0;
  border-radius: 0;
  overflow: hidden;
  background: var(--surface-3);
}

.bcard--map .bcard__photo {
  /* Absolute fill: height:100% does not resolve against a parent whose height
     comes from aspect-ratio, and the base 4:5 portrait then letterboxes. */
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  border-radius: 0;
  object-fit: cover;
  object-position: center 20%;
}

.bcard--map .bcard__count {
  display: none;
}

.bcard--map .bcard__identity {
  grid-area: identity;
  min-height: 0;
  gap: 2px;
  padding: var(--sp-3) var(--sp-3) 0;
  align-self: start;
}

.bcard--map .bcard__location {
  justify-content: flex-start;
}

.bcard--map .bcard__name {
  justify-content: flex-start;
}

.bcard--map .bcard__foot {
  grid-area: foot;
  margin-top: 0;
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  align-self: end;
}

.bcard--map .bcard__actions {
  grid-template-columns: 1fr;
}

.bcard--map .distance-line {
  justify-content: flex-start;
}

/* Same locate + market flag pill as the listing card beside it. */
.bcard__locate {
  top: var(--sp-3);
  right: var(--sp-3);
}

/*
  Search list view: the same horizontal listing row — photograph left, copy
  right — so a broker ad in this view is not a leftover portrait from the grid.
*/

.bcard--row {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  grid-template-areas:
    'media identity'
    'media foot';
  align-items: stretch;
  gap: 0;
  height: auto;
  padding: 0;
  text-align: left;
}

.bcard--row .bcard__media {
  grid-area: media;
  align-self: stretch;
  margin: 0;
  border-radius: calc(var(--r-lg) - 1px) 0 0 calc(var(--r-lg) - 1px);
}

.bcard--row .bcard__photo {
  height: 100%;
  aspect-ratio: 4 / 3;
  object-position: center 20%;
  border-radius: calc(var(--r-lg) - 1px) 0 0 calc(var(--r-lg) - 1px);
}

.bcard--row .bcard__identity {
  grid-area: identity;
  min-height: 0;
  padding: var(--sp-5) var(--sp-6) var(--sp-3);
}

.bcard--row .bcard__location,
.bcard--row .distance-line {
  justify-content: flex-start;
}

.bcard--row .bcard__name {
  justify-content: flex-start;
}

.bcard--row .bcard__foot {
  grid-area: foot;
  margin-top: 0;
  padding: 0 var(--sp-6) var(--sp-5);
}

.bcard--row .bcard__contact {
  align-items: flex-start;
  padding-top: var(--sp-3);
}

.bcard--row .bcard__contact a {
  justify-content: flex-start;
}

.bcard--row .bcard__contact .msg-links {
  justify-content: flex-start;
}

.bcard--row .bcard__foot .seller-active {
  align-items: flex-start;
}

.bcard--row .bcard__foot .seller-active__title {
  text-align: left;
}

.bcard--row .bcard__foot .seller-active__list {
  justify-content: flex-start;
}

.bcard--row .bcard__actions {
  grid-template-columns: max-content;
  justify-content: start;
}

.bcard--row .bcard__actions .btn {
  width: auto;
}

@media (max-width: 720px) {
  .bcard--row {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      'media'
      'identity'
      'foot';
  }

  .bcard--row .bcard__media,
  .bcard--row .bcard__photo {
    border-radius: calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
  }

  .bcard--row .bcard__identity {
    padding: var(--sp-4) var(--sp-4) var(--sp-2);
  }

  .bcard--row .bcard__foot {
    padding: 0 var(--sp-4) var(--sp-4);
  }
}

/* The broker strip inside the search results.

   Advertising, above the listings and marked as a block of its own, because a
   paid card shuffled between two listings reads as a listing until it is too
   late. */

.results-brokers {
  margin-bottom: var(--sp-2);
}

.results-brokers__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-sm);
  color: #008000;
}

/* Mixed into the listing grid: only the “N brokers here” line — no extra
   gap under the heading before the cards. */
.results-brokers:not(:has(.grid)) .results-brokers__head {
  margin-bottom: 0;
}

.results-brokers__head svg {
  width: 16px;
  height: 16px;
  flex: none;
  color: #008000;
}

.results-brokers__head strong {
  color: #008000;
}

/* Brokers sat under the deal types; it now shares a row with Booking
   (.segmented--booking-brokers). Keep the old class harmless if anything
   still references it. */

.segmented--brokers {
  margin-top: var(--sp-2);
}

/* The icon sits beside the word, not above it: .icon is display:block across
   the site, and inside a plain text span that stacks the two. */

.segmented--brokers .segmented__item > span {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.segmented--brokers .segmented__item svg {
  width: 15px;
  height: 15px;
}

/* The market runs on the profile: one line per market, each with its own
   countdown, because each was a separate purchase. The company's runs are the
   same list under a different button. */

.broker-ad__runs,
.agency-ad__runs {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-3);
  padding: 0;
  list-style: none;
}

.broker-ad__live {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
}

.broker-ad__live svg {
  width: 14px;
  height: 14px;
  flex: none;
  margin-top: 2px;
}

.broker-map-ad {
  margin-top: var(--sp-5);
}

/*
  A settings row: one field, and the button that saves it.

  Used across the back office wherever a screen stores one key per form rather
  than one form for a whole screen — saving the title separator must not be able
  to blank the site name, and one form per key is what guarantees that. The
  button aligns to the field's control rather than to its label, so a row with a
  hint under it does not drag the button down with it.
*/

.field-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}

.field-row .field {
  flex: 1;
  min-width: 0;
}

.field-row > .btn {
  margin-top: 1.7rem;
  flex: none;
}

@media (max-width: 640px) {
  .field-row {
    flex-direction: column;
    align-items: stretch;
  }

  .field-row > .btn {
    margin-top: 0;
  }
}

/*
  SEO → Structured data (JSON-LD). Compact rows; quiet save matches upload
  height (btn--sm) and paints green on hover.
*/

.seo-jsonld > .card__header {
  padding: var(--sp-3) var(--sp-4);
}

.seo-jsonld > .card__body {
  padding: var(--sp-3) var(--sp-4);
  gap: var(--sp-2);
}

.seo-jsonld .field {
  gap: 2px;
}

.seo-jsonld .field-row {
  gap: var(--sp-3);
}

.seo-jsonld .field-row > .btn {
  margin-top: 1.35rem;
}

.seo-jsonld .textarea {
  min-height: 0;
}

.seo-jsonld__logo .strong {
  margin: 0;
  font-size: var(--fs-sm);
}

/*
  SEO page — compact packing (labels, fields, hints tight; cards close).
*/

.seo-admin {
  gap: var(--sp-2);
}

/*
  Clip flush tables (and SEO section bodies) to the card radius — otherwise the
  last row paints square corners past the rounded border (client, 22 September).
  Scoped to SEO + flush-table cards so pick menus on other admin forms still
  open past the card edge.
*/
.seo-admin .card,
.admin .shell--nav .card:has(.table-wrap--flush) {
  overflow: hidden;
}

.admin .shell--nav .card .table-wrap--flush {
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

.seo-admin .seo-edit-path {
  font-family: inherit;
  font-size: inherit;
  font-weight: 400;
  color: #0000CD;
}

.seo-admin .seo-lang-filter {
  width: max-content;
  max-width: 100%;
}

.seo-admin .seo-lang-filter .sort-menu__btn {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.15rem 0;
  min-height: 0;
  justify-content: flex-start;
  gap: 0;
}

.seo-admin .seo-lang-filter .sort-menu__btn svg {
  display: none;
}

/* Language column hugs the badges; Title/Description start immediately after. */
.seo-admin .table th:nth-child(2),
.seo-admin .table td:nth-child(2) {
  width: 1%;
  white-space: nowrap;
  padding-inline-end: var(--sp-3);
}

.seo-admin .table th:nth-child(1),
.seo-admin .table td:nth-child(1) {
  width: 1%;
  white-space: nowrap;
}

.seo-admin #seo-form > .card__fold,
.seo-admin #seo-defaults > .card__fold,
.seo-admin #seo-jsonld > .card__fold {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.seo-admin #seo-form > .card__fold .card__title,
.seo-admin #seo-defaults > .card__fold .card__title,
.seo-admin #seo-jsonld > .card__fold .card__title {
  flex: 1;
  min-width: 0;
}

.seo-admin #seo-form .seo-form-autosave,
.seo-admin #seo-defaults .seo-form-autosave,
.seo-admin #seo-jsonld .seo-form-autosave {
  flex: none;
  margin-left: auto;
  white-space: nowrap;
}

.seo-admin .seo-col-clip {
  max-width: 18rem;
  vertical-align: top;
}

.seo-admin .seo-col-clip__text {
  margin: 0;
  overflow: hidden;
  text-overflow: clip;
  white-space: normal;
  /* Two lines, hard clip — no "…" (line-clamp always paints dots). */
  line-height: 1.25;
  max-height: calc(1.25em * 2);
  word-break: break-word;
}

.seo-admin .seo-col-clip__count {
  margin: 0;
  line-height: 1.1;
}

.seo-admin > .stack.stack--lg,
.seo-admin .shell--nav > .stack.stack--lg {
  gap: var(--sp-2);
}

.seo-admin .card__header,
.seo-admin .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.seo-admin .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.seo-admin .card__body.stack,
.seo-admin .card__body > .stack,
.seo-admin form.stack,
.seo-admin form.stack--sm {
  gap: var(--sp-2);
}

.seo-admin .field {
  gap: 1px;
}

.seo-admin .grid {
  gap: var(--sp-2);
}

.seo-admin .seo-form {
  gap: var(--sp-2);
}

.seo-admin .seo-form > .strong {
  margin: var(--sp-1) 0 0;
}

.seo-admin .field__hint {
  display: block;
  margin: 0;
  padding: 0;
  line-height: 1.2;
}

.seo-admin .field-row {
  gap: var(--sp-3);
}

.seo-admin .field-row > .btn {
  margin-top: 1.2rem;
}

.seo-admin .seo-form .check {
  margin-top: 2px;
}

.seo-admin .textarea {
  min-height: 0;
}

.seo-admin #seo-form .card__body > .btn {
  margin-bottom: 0;
}

/* Integrations — provider list starts under the title; notes sit at the foot.
   Same 8px card gaps as General / SEO; bodies pulled tight (client, 22 September). */
.integrations-admin .shell--nav > .stack,
.integrations-admin .shell--nav > .stack > .stack {
  gap: 8px;
}

.integrations-admin .card__header,
.integrations-admin .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.integrations-admin .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.integrations-admin .card__body.stack,
.integrations-admin .card__body > .stack,
.integrations-admin .card__body > .stack--sm {
  gap: var(--sp-2);
}

.integrations-admin .field {
  gap: 1px;
}

.integrations-admin .grid {
  gap: var(--sp-2);
}

.integrations-admin .field__hint,
.integrations-admin .text-xs.muted,
.integrations-admin .text-sm.muted {
  display: block;
  margin: 0;
  padding: 0;
  line-height: 1.2;
}

.integrations-admin .alert {
  margin: 0;
}

.integrations-admin .alert p {
  margin: 0;
  line-height: 1.25;
}

.integrations-admin .card__body > .btn {
  margin-top: var(--sp-1);
}

/* General settings — tight gaps under the page title; remembered folds. */
.settings-admin .shell--nav > .stack,
.settings-admin #system.stack {
  gap: 8px;
}

.settings-admin .card__body.stack {
  gap: var(--sp-2);
}

.settings-admin .admin-restart-wrap--inline {
  margin-top: var(--sp-1);
}

/* General → Video uploads: label on one line; pick menu under it, compact. */

.field--video-edge {
  width: auto;
  max-width: 100%;
}

.field--video-edge > .field__label {
  white-space: nowrap;
}

.field--video-edge > .sort-menu--field {
  width: 14rem;
  max-width: 100%;
}

/*
  Upload state on a photograph tile, while the file is on its way to the server.
*/

.photo-thumb__state {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgb(0 0 0 / 0.45);
  color: #fff;
  border-radius: inherit;
}

.photo-thumb__state .spinner {
  width: 28px;
  height: 28px;
  border-width: 4px;
  border-color: color-mix(in srgb, #008000 30%, transparent);
  border-top-color: #008000;
}

.photo-thumb__state--failed {
  background: var(--error);
  inset: auto var(--sp-2) var(--sp-2) auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  font-weight: 700;
}

/*
  The preview card before a photograph has been chosen. A card with an empty
  media box reads as broken; one that says what is missing does not.
*/

.pcard__media-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  height: 100%;
  min-height: 180px;
  background: var(--surface-2);
  color: var(--text-subtle);
}

/*
  The sign-out block at the foot of the administration rail.

  A form, because signing out is a POST — so the control inside it has to be a
  button that reads as one of the rail's links rather than as a button. The
  reset here is what makes the two indistinguishable.
*/

.side-nav__group--foot {
  margin-top: 0;
  padding-top: var(--sp-1);
  padding-bottom: var(--sp-1);
}

.side-nav__group--foot .side-nav__label {
  padding-block: 0;
  margin: 0;
}

.side-nav__who {
  padding: 0 var(--sp-3) 2px;
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text-subtle);
  overflow-wrap: anywhere;
}

.side-nav__group--foot .side-nav__link {
  padding-block: 2px;
}

.side-nav__link--button {
  width: 100%;
  font: inherit;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
}

/* ==========================================================================
   Empty section
   ==========================================================================

   The placeholder inside a homepage panel with nothing to list. Its whole job
   is to be the same size every time: the four panels on the homepage were
   different heights precisely because an empty one shrank to the height of its
   own heading, and a heading with an overline is taller than one without.

   min-height is therefore fixed rather than derived — it is what makes four
   empty panels four identical panels. It is close to the height of one row of
   property cards, so a section does not jump when its first listing appears.
   ========================================================================== */

.section-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  min-height: 220px;
  padding: var(--sp-8) var(--sp-5);
  text-align: center;
}

.section-empty__icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  color: var(--text-muted);
}

.section-empty__icon .icon {
  width: 22px;
  height: 22px;
}

.section-empty__title {
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--text);
}

.section-empty__body {
  max-width: 34rem;
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-muted);
  /* Two lines, reserved. One panel's sentence wraps and another's does not, and
     the difference was visible as two panels of different heights rather than
     as two sentences of different lengths. */
  min-height: calc(2 * 1.6em);
}

/* Three on a narrow screen, where the same sentences wrap once more and the
   panels came apart again — 277 beside 299. The reserve has to match the widest
   the copy actually gets, and that is a function of the column, not of the
   sentence. */

@media (max-width: 40rem) {
  .section-empty__body {
    min-height: calc(3 * 1.6em);
  }
}

/* The action row is always present and always the height of the button that may
   or may not be in it, so a panel with nothing to offer is not shorter than the
   one above it. 2.5rem is what .btn--sm actually measures — 2.25 left the one
   panel with no button four pixels short of the five that have one, which is
   the same bug in miniature. */

.section-empty__action {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.5rem;
}

/* The blank panel: the height the filled one reserves, with nothing in it —
   its padding, the 48px icon, three gaps, the title's line, the two (three on a
   narrow screen) reserved lines of the sentence and the action row. Featured
   properties, 13 September: "remove this text from there completely, there will
   stay just empty space. The dimensions of the featured section do not change." */

.section-empty--blank {
  min-height: calc(2 * var(--sp-8) + 48px + 3 * var(--sp-3)
                   + var(--fs-lg) * var(--lh-normal)
                   + 2 * 1.6 * var(--fs-sm) + 2.5rem);
}

@media (max-width: 40rem) {
  .section-empty--blank {
    min-height: calc(2 * var(--sp-8) + 48px + 3 * var(--sp-3)
                     + var(--fs-lg) * var(--lh-normal)
                     + 3 * 1.6 * var(--fs-sm) + 2.5rem);
  }
}

/* Inside a navy panel the muted greys disappear; both lines take the panel's
   own ink instead. */

.section--navy .section-empty__title,
.section--navy .section-empty__body {
  color: inherit;
}

.section--navy .section-empty__icon {
  background: rgba(255, 255, 255, 0.1);
  color: inherit;
}

/* Markup goes in these two SEO fields, so they are set in the face that shows a
   stray angle bracket. Same box as .textarea otherwise.

   A long row wraps rather than running off the side. The client, 8 September:
   "And in backend make that it will not stretch out of the viewport, that it
   will go in the box to the next line."

   pre-wrap, not the plain wrapping the other textareas have: the rows the
   administrator types are the rows the page draws — one field row is one page
   row, and blank rows survive the trip — so the newlines have to be kept
   exactly as they are while the long ones fold. anywhere rather than
   break-word so a pasted URL with no spaces in it folds too, which is the one
   thing that could still push the field wider than the panel. */

.textarea--code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow-x: hidden;
}

/* A grid of checkboxes for a small fixed catalogue — land type, today.
   Two columns in a filter rail, three where there is room. Distinct from
   .type-check-grid, which draws the property-type tiles with their icons. */

.check-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-2) var(--sp-3);
}

@media (min-width: 60rem) {
  .listing-section .check-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    /* Fill down each column so catalogue order matches what the eye reads
       top-to-bottom (Residential, then Commercial under it, …). */
    grid-template-rows: repeat(3, auto);
    grid-auto-flow: column;
  }

  /* Land type: 10 options, column 2 is one taller (Real estate → Timberland). */
  .listing-section .check-grid--land {
    grid-template-rows: repeat(4, auto);
    grid-auto-flow: dense;
  }
  .listing-section .check-grid--land .check:nth-child(1)  { grid-column: 1; grid-row: 1; }
  .listing-section .check-grid--land .check:nth-child(2)  { grid-column: 1; grid-row: 2; }
  .listing-section .check-grid--land .check:nth-child(3)  { grid-column: 1; grid-row: 3; }
  .listing-section .check-grid--land .check:nth-child(4)  { grid-column: 2; grid-row: 1; }
  .listing-section .check-grid--land .check:nth-child(5)  { grid-column: 2; grid-row: 2; }
  .listing-section .check-grid--land .check:nth-child(6)  { grid-column: 2; grid-row: 3; }
  .listing-section .check-grid--land .check:nth-child(7)  { grid-column: 2; grid-row: 4; }
  .listing-section .check-grid--land .check:nth-child(8)  { grid-column: 3; grid-row: 1; }
  .listing-section .check-grid--land .check:nth-child(9)  { grid-column: 3; grid-row: 2; }
  .listing-section .check-grid--land .check:nth-child(10) { grid-column: 3; grid-row: 3; }
}

.check-grid .check {
  margin: 0;
}

/* ==========================================================================
   Previa — layout
   Containers, grids, header, mobile drawer, footer, filter sidebar.
   ========================================================================== */

/* ==========================================================================
   Containers and sections
   ========================================================================== */

.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.container--narrow {
  max-width: var(--container-narrow);
}

.container--wide {
  max-width: 1600px;
}

/* Map and search screens may use the whole viewport. */

.container--fluid {
  max-width: none;
}

/*
  A page laid out on the wide container is wider than --page-width's default.

  --page-width is what the header, the page head and the homepage banner are
  cut to (see tokens.css). Its default is the ordinary container's content box,
  which is right for every page that uses one — and wrong for the two families
  that do not: the listings screen and the administration screens both run on
  .container--wide, and on those the results grid and the admin shell reach one
  gutter from the shell edge. Matching "the same width as the page content"
  there means matching *their* content, so the panels above them read as the
  top of the same block rather than as a narrower thing sitting on it.

  :has() on <body> because the header is rendered before the page it belongs
  to: nothing in the markup above the content can know which layout follows it.
  The same mechanism already decides whether the header draws its separator.
*/

body:has(.container--wide) {
  --page-width: calc(100% - var(--gutter) * 2);
}

/*
  The map screens run on the listings page's width, since 19 August.

  They used to be the one exception, edge to edge: there was no content column
  on them to match, so a panel-width bar over a full-bleed map would have been
  the mismatch the client asked to remove, drawn the other way round. The
  answer now is to inset the map itself — "on the maps page make the header the
  same width as on the listing page, add the rounded corners like on the
  listing page, and make the main content the same width as on the listing
  page" — so the two agree at the panel width instead of at the window's.

  This is the same value .container--wide pages take, which is what the listings
  page is, so the header, the shell under it and the footer below all land on
  the same two edges. --gutter changes at the breakpoints and this follows it.
*/

body:has(.map-shell) {
  --page-width: calc(100% - var(--gutter) * 2);
}

/*
  On a narrow screen the page shell runs to the edge of it.

  The client, 30 August, with three photographs of the site on his phone: "And
  the header is ugly! It must be till to the screen edge and rounded corners if
  screen is narrower. Make it to work in the phone and in desktop small
  browser."

  --page-width is a margin measured in --gutter, and a gutter is room to breathe
  on a monitor. On a 360px screen the same idea spends 32 of those pixels — nine
  per cent of the width — on two stripes of background either side of a bar that
  has a logo, two buttons and nothing else in it, and the panel it leaves reads
  as a card that failed to size itself rather than as the top of the page. That
  is what he was looking at.

  One token, not a rule per element. The header, the page head, the homepage
  banner, the footer, the drawer and the floating menu button are all cut to
  this width precisely so they agree with each other; widening only the header
  would put its edges a gutter outside every block below it, which is the
  mismatch he asked to have removed on three earlier screens. They go together
  or not at all.

  The rounded corners stay — he asked for them in the same sentence — and they
  are drawn where they always were: on the header's bottom edge, on the hero
  photograph that closes the block on the homepage, on the results toolbar on
  the map screens. Nothing inside touches the glass either: .container keeps its
  own --gutter of padding, so the logo and the text below it stand exactly where
  they stood.

  The two overrides above are repeated rather than left to cascade: they are
  written against <body> and would outrank a :root declaration made here however
  late it comes.

  It turned at 720px until 3 September and turns with the header now: the
  client's condition for the whole shell is "when the header switched to phone
  mode", and the header switches at 1200px or earlier. See the note below.
*/

/*
  The phone shell, as four tokens.

  The client, 3 September, with the homepage photographed on his phone: "you
  see it looks much better if in narrow screen the content stretched over the
  whole viewport, at the moment you made it only with one section, make it with
  all of them. and make it with all phone views, so when the header switched to
  phone mode (the header sections move to phone sidebar menu) then all the
  website content stretches till to the end of screen."

  Two separate things were wrong, and each has a token here.

  "Only with one section" was Featured properties, and the reason it was the
  only one is that the rule below used to name .grid--properties and three
  other blocks by hand. Every other section on the page — the developments, the
  articles, the cities, the brokers, the testimonials — kept its gutter because
  nobody had added its class to a list. --shell-bleed replaces the list: any
  card block sitting directly in a section's container reads the token, so a
  section added next month is full width on a phone without anyone remembering
  this file.

  The banded sections had a second inset on top of that: their panel is drawn
  one gutter in from the page and their content two, so Recently added stood a
  gutter narrower than Featured properties above it even where both grids
  bleed. --band-width, --band-radius and --band-pad flatten the panel into the
  page at phone widths — full width, square, one gutter of room inside — so a
  banded section and a plain one are the same width and only their colour
  differs.

  "When the header switched to phone mode" is the condition, and it is taken
  literally: the same 1200px the header collapses at, and the same is-cramped
  class previaHeader sets when a longer language collapses it earlier. The
  header, the footer, the page head and the page's panels then all change at
  one moment rather than at two — which is the mismatch he has written about
  at both ends of the page before.

  The text does not move. The header's rule is that the panel runs to the glass
  and the things inside it keep their gutter, and that is what these tokens do:
  .container keeps its padding, so headings, subtitles and the breadcrumb stand
  exactly where they stood, and it is the cards that reach the edge.
*/

:root {
  --shell-bleed: 0px;
  --band-width: min(100% - var(--gutter) * 2, calc(var(--container) - var(--gutter) * 2));
  --band-radius: var(--r-xl);
  --band-pad: calc(var(--gutter) * 2);
}

@media (max-width: 1200px) {
  /*
    :root on a line of its own, and not in the list below it.

    A selector list is dropped whole by any engine that cannot parse one of its
    parts, so writing :root beside two :has() selectors made the plain phone
    rule — the one every page depends on to reach the edge of the screen —
    conditional on :has() being understood. Samsung Internet did not understand
    it until version 20, and on those the homepage kept a 16px gutter it was
    never meant to have. The client, 31 August: "need to make that the website
    works with all browsers."
  */

  :root {
    --page-width: 100%;
    --shell-bleed: calc(var(--gutter) * -1);
    --band-width: 100%;
    --band-radius: 0px;
    --band-pad: var(--gutter);
  }

  body:has(.container--wide),
  body:has(.map-shell) {
    --page-width: 100%;
  }
}

/*
  And the same shell when the header collapses above 1200px.

  previaHeader measures the row it actually drew and puts .is-cramped on the
  header the moment the navigation is short of room — a German header at 1290px
  is in phone mode while the media query above still thinks it is a desktop.
  The client's condition is the header's state, not a number, so the state
  carries the tokens too.

  :has() on <body> because the header is rendered before the page it belongs
  to. Written after the query so it also wins under 1200px, where it changes
  nothing: both set the same five values. Specificity is what settles it
  against body:has(.container--wide) above — .site-header.is-cramped is two
  classes, .container--wide is one — so a listings or admin screen whose header
  is cramped reaches the edge with everything else.
*/

body:has(.site-header.is-cramped) {
  --page-width: 100%;
  --shell-bleed: calc(var(--gutter) * -1);
  --band-width: 100%;
  --band-radius: 0px;
  --band-pad: var(--gutter);
}

/*
  And the content panels reach the same two edges the header does.

  The client, 31 August, having just approved the header at this width: "now in
  narrow view the header stretches from one screen edge to another, this is
  good ... And the rest of the website content do the same way as the header to
  stretch from one edge to another."

  "The same way as the header" is the instruction, and the header is precise
  about it: the panel runs to the glass, and the things inside the panel keep
  their gutter — the logo does not touch the edge, the burger does not touch the
  edge. So it is the panels that move here, not the text. The breadcrumb, the
  result count, the sort row and the filter chips are bare rows on the page
  rather than panels, and they keep the gutter they have; zeroing .container's
  padding instead would have put every one of them hard against the glass and
  clipped "Save this search" off the right of the screen.

  A negative margin rather than a width, because these boxes sit two and three
  levels inside .container (.search-layout > .search-results-col > .search-banner)
  and neither level in between adds padding of its own. --gutter is read rather
  than written so the bleed stays exactly one gutter if that token moves.

  What is matched, and what is deliberately not:

  A block that holds cards and sits *directly* in a section's container. That
  is the shape of every section the client photographed — .section > .container
  on the homepage, and the .container.section single element the inner pages
  use — and it is what keeps form layouts out of it. Form rows are .grid--2 and
  .grid--3 too, but they live three and four levels down inside a .card, a
  .stack or a .listing-section, so a direct-child selector never reaches them:
  running a text input to the edge of a phone is the bug this rule exists to
  avoid.

  .trust-grid is not in the list on purpose. Its four items are an icon, a
  heading and a paragraph with no card behind them, and a paragraph against the
  glass is not "content stretched over the viewport", it is text nobody can
  read. The same goes for .container--narrow, which is a reading measure.
*/

/* And the "more like this" grids that close a detail page.

   A development page is still .container > section > .grid. A property page
   wraps Similar properties and All listings from this seller in
   .property-detail__rails first, so the grid sits one level deeper — same
   cards as Featured / Recently added on the homepage, same phone bleed. */

.container:not(.container--narrow) > section > .grid,
.container:not(.container--narrow) > .property-detail__rails > section > .grid,
.section > .container:not(.container--narrow) > .grid,
.section > .container:not(.container--narrow) > .broker-strip,
.section > .container:not(.container--narrow) > .loc-grid,
.section > .container:not(.container--narrow) > .price-grid,
.section > .container:not(.container--narrow) > .stat-grid,
.section > .container:not(.container--narrow) > .section-empty,
.container.section:not(.container--narrow) > .grid,
.container.section:not(.container--narrow) > .broker-strip,
.container.section:not(.container--narrow) > .loc-grid,
.container.section:not(.container--narrow) > .price-grid,
.container.section:not(.container--narrow) > .stat-grid,
.container.section:not(.container--narrow) > .section-empty {
  margin-inline: var(--shell-bleed);
}

/*
  The results screen keeps its own breakpoint.

  These three sit inside .search-layout, which is two columns — filters beside
  results — until 1024px. Bleeding a grid that still has a sidebar next to it
  would pull the cards under the filters, so this follows the layout's own
  collapse rather than the header's: below 1024 there is one column and the
  bleed is the same bleed as everywhere else.
*/

@media (max-width: 1024px) {
  .search-banner,
  .grid--results,
  .grid--list {
    margin-inline: calc(var(--gutter) * -1);
  }
}

/*
  Vertical rhythm only — padding-block, never the shorthand.

  The shorthand was `padding: var(--section-y) 0`, and an element carrying both
  classes (`class="container section"`, which is most content pages) had its
  gutters zeroed by it: .section is declared after .container, so its inline 0
  won. Agencies, help, the account pages and the broker directory therefore ran
  32px wider on each side than the footer and than the homepage, which is the
  same complaint the client made three times over — "make it narrower with the
  agencies", "make the page content narrower", "make the footer wider". One
  property, not three page-by-page tweaks.
*/

.section {
  padding-block: var(--section-y);
}

.section--lg {
  padding-block: var(--section-y-lg);
}

.section--tight {
  padding-block: calc(var(--section-y) * 0.6);
}

/*
  Coloured blocks are panels now, not bands.

  "All the areas on the screen (except the header part, this like that) what
  stretch from one screen side to another, make inside rounded corners
  rectangle with limited size — so the same width as the footer", and again on
  the About page: "this area restrict to a rounded corners rectangle with the
  left-right border limits the same as the 'work with us' below it is."

  The width formula is the footer's, character for character, which is also the
  width of a .container's content box — so a panel's edges line up with the
  cards, the footer and the cta-panel on every page and at every breakpoint.

  Drawn as a pseudo-element rather than by moving the colour onto .container:
  .container's own box is --container wide *including* its gutters, so painting
  it would run 64px wider than the footer. Behind the content at z-index -1,
  with `isolation` keeping that negative layer inside the section instead of
  sliding under the page.

  No hairline between them: the client's rule is that a separator earns its
  place only where two blocks share a colour, and a panel already differs from
  what sits above and below it.
*/

.section--band,
.section--navy {
  position: relative;
  isolation: isolate;
}

.section--band::before,
.section--navy::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset-block: 0;
  left: 50%;
  transform: translateX(-50%);
  /* The desktop value is the footer's formula, character for character; the
     phone value is the whole screen. Both live on --band-width at the top of
     this file, where the client's 3 September note explains why. */
  width: var(--band-width);
  border-radius: var(--band-radius);
}

/*
  Room inside the panel.

  The panel's edges are the footer's, which is also exactly where a .container's
  content box ends — so without this the heading, the cards and the panel's own
  edge all landed on the same line and the text sat flush against it. The
  content is inset by one more gutter, which is the same step the footer's links
  and legal row sit at inside their panel, so a panel and the footer are laid
  out the same way at every breakpoint.

  The consequence is deliberate: a card grid inside a panel is one gutter
  narrower on each side than the same grid in an uncoloured section above it.
  Those are the only two widths available once the client has fixed the panel's
  outer edge to the footer's — "the same width as the footer" — and content
  touching that edge is the one the client rejected.
*/

/*
  Two panels in a row are two panels.

  The panel is drawn on ::before across the section's whole block, padding
  included, so consecutive banded sections met edge to edge and read as one
  tall block with a hairline across it — which is what made "one menu block
  bigger than the others" hard to see as four equal ones. A gap between them
  says where each ends.
*/

.section--band + .section--band,
.section--band + .section--navy,
.section--navy + .section--band {
  margin-top: var(--sp-6);
}

.section--band > .container,
.section--navy > .container {
  /* Two gutters beside a panel that is inset by one, one gutter beside a panel
     that is the screen — so a banded section and a plain one put their content
     on the same line at every width. See --band-pad. */
  padding-inline: var(--band-pad);
}

/*
  A panel whose content sits close to its own top edge.

  "In related reading move the content more up inside it's menu block, and
  reduce the distance between title and content." The first half is this: a
  band's padding is its inset panel's padding, so the heading was starting a
  full section step below the panel's rounded corner. The second half is
  .section__head-text--tight on the heading itself.
*/

.section--band-tight {
  padding-block: calc(var(--section-y) * 0.7);
}

/* Pull the title up inside the panel, and bring the cards closer under it. */
.section--band-up {
  padding-top: calc(var(--section-y) * 0.4);
  padding-bottom: calc(var(--section-y) * 0.7);
}

.section--band-up > .container > .section__head {
  margin-bottom: var(--sp-3);
}

.section--band::before {
  background: var(--surface-2);
}

/*
  About Previa stats — client, 21 September: same width as Work with us (70%);
  tiles gold like "advertising options". Green panel removed (same day):
  the four tiles stay in place with no outer box.
*/

.about-stats {
  width: 70%;
  max-width: 70%;
  margin-inline: auto;
  padding: 0;
  background: none;
  border-radius: 0;
}

.about-stats__grid.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-2);
}

.about-stats .stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 10px;
  background: var(--gold);
  border: none;
  border-radius: var(--r-md);
  color: var(--text-on-gold);
}

.about-stats .stat__label,
.about-stats .stat__foot,
.about-stats .stat__value {
  color: var(--text-on-gold);
}

.about-stats .stat__label {
  font-size: var(--fs-xs);
  line-height: 1.2;
}

.about-stats .stat__value {
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: var(--ls-tight);
  line-height: 1.15;
  margin: 0;
}

.about-stats .stat__foot {
  font-size: 0.6875rem;
  line-height: 1.25;
  margin: 0;
}

@media (max-width: 860px) {
  .about-stats {
    width: 100%;
    max-width: 100%;
  }

  .about-stats__grid.stat-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/*
  About Previa body copy — client, 21 September: green rounded panel around
  the three paragraphs under the title.
*/

.about-body {
  padding: 14px clamp(18px, 3vw, 28px);
  background: #008000;
  border-radius: var(--r-xl);
  color: #fff;
}

.about-body .prose,
.about-body .prose p {
  color: #fff;
}

.about-body .prose {
  gap: var(--sp-3);
}

/*
  About page — client, 21 September: pull every block up; tight even gaps
  between title, prose, stats, Work with us and the footer.
*/

.about-page > .page-head {
  padding-bottom: 0;
}

.about-page > .about-page__block,
.about-page > .section,
.about-page > .container.section {
  padding-block: 8px;
}

.about-page > .about-page__block:last-child,
.about-page > .section:last-child {
  padding-bottom: 8px;
  margin-bottom: 0;
}

.about-page {
  margin-bottom: 0;
}

/*
  Gap above New developments matches the gap Popular locations keeps to the
  panel above it (.section--band + .section--band → --sp-6). The advertise
  strip's own bottom padding would otherwise stack on top of that step.
*/

.section--ads:has(+ .section--band) {
  padding-bottom: 0;
}

.section--ads + .section--band {
  margin-top: var(--sp-6);
}

/*
  Frontpage New developments: Previa-violet panel, lime title.
*/

.section--home-developments::before {
  background: #8B008B;
}

.section--home-developments .section__title {
  color: #00FF00;
}

/*
  Frontpage Popular locations: green panel, Previa-ground violet title.
*/

.section--home-locations::before {
  background: #008000;
}

.section--home-locations .section__title {
  color: var(--bg);
}

/*
  Why people search on Previa: half the previous step under Popular locations,
  and the same half-step down to the SEO box when it follows.
*/

.section--home-why {
  padding-top: calc(var(--section-y) * 0.5);
  padding-bottom: calc(var(--section-y) * 0.5);
}

.section--band + .section--home-why {
  margin-top: calc(var(--sp-6) / 2);
}

.section--home-why + .container > .seo-box--below {
  margin-top: calc(var(--sp-6) / 2);
}

/*
  A section that keeps its name but not its panel.

  The page alternates panel / no panel down its whole length, and --navy now
  paints the same surface as --band, so a --navy section next to a --band one
  reads as one long block. This suppresses the panel without renaming the
  section — see home.html for the client's note.
*/

.section--flat::before {
  content: none;
}

/*
  The panel goes; the page's own margins do not.

  The client, 31 August, with a photograph of "Why people search on Previa":
  "in the frontpage below one section is out of website dimensions, align it
  with other sections." It was: this rule said `padding-inline: 0`, which is
  not "no panel padding" but "no padding at all", so the one section carrying
  the class had its heading, its subtitle and its four columns standing on the
  left edge of the window while every other section on the page began a gutter
  in. The panelled sections start at two gutters and the plain ones at one; this
  started at none, which is a width the page does not have anywhere else.

  --gutter is what .container declares for itself, so this now says exactly
  "undo the two-gutter panel inset above, and be an ordinary section" — which
  puts its edges on the same line as Featured properties, Brokers to work with
  and Articles & real-estate advice, at every breakpoint, since the token moves
  with them.
*/

.section--flat > .container {
  padding-inline: var(--gutter);
}

/*
  "Why people search on Previa" — a light panel in the light theme.

  It used to paint --navy with inverted text, which is a dark-theme surface
  wherever it is met: "in day mode this section still looks like dark mode, fix
  it so it uses the proper light design; keep the dark mode as it is." The
  colour was the only thing making it a dark block, and the dark theme already
  painted --surface-2 here, so both themes now take the same panel as every
  other band on the page and the text inherits --text in both.

  The class keeps its name because it is what the section is called throughout
  the homepage and the tests; what it means is now "the panel that closes the
  page", not "the navy one".
*/

.section--navy::before {
  background: var(--surface-2);
}

/*
  And a gap above it.

  "Add a small gap between this section and the section above it." The cities
  panel directly above is a panel too, and two panels whose padding is inside
  them meet with nothing between: the rounded corners of one sat on the rounded
  corners of the other and the pair read as a single shape. The same --sp-6 the
  footer takes above itself, for the same reason and so the page's three panel
  seams are all one distance.
*/

.section--band + .section--navy {
  margin-top: var(--sp-6);
}

/*
  Heading block, then the content it introduces.

  20px, not 40. "The content blocks closer the it's title" — the gap was wide
  enough that a heading read as belonging to the whitespace under it rather
  than to the cards it names. One figure for every section on the site, so the
  distance from a title to its content is the same wherever you look.
*/

.section__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-5);
  margin-bottom: var(--sp-5);
}

/* Broker / agency ads and listing preview rows sit tight under their title —
   Featured and Recently added match the brokers strip gap. */
.section__head:has(+ .broker-strip),
.section__head:has(+ .grid--properties),
.section__head:has(+ .section-empty),
.section--ads > .container > .section__head {
  margin-bottom: var(--sp-1);
}

/*
  And a heading with no panel under it starts on that same line.

  The client, 9 September, with a photograph of Featured properties standing
  above Recently added: "in the frontpage there are two kind of section, the
  ones on the plain background and the ones on the box ... in wide screen (not
  mobile view) their title is more left than the titles in the boxes. Align them
  equal with the titles in the boxes, so move them a bit more right. Only the
  titles, with the preview windows all is fine."

  A panel holds its content at --band-pad; a plain section holds its content at
  the gutter .container declares for itself. One gutter of difference, and the
  place it shows is the heading — Handpicked, Fresh to market, Buy new are read
  straight down the page, so two kinds of section starting on two lines reads as
  a mistake even though both are where they were drawn to be.

  Only the head moves. The grids keep the widths they have always had — "with
  the preview windows all is fine" — which are also the two widths the client
  settled on when the panels were fixed to the footer's edge, so nothing below a
  title changes on any page.

  Written as the difference between the two insets rather than as a gutter, so
  it is one gutter on a wide screen and exactly nothing the moment --band-pad
  drops to --gutter: at 1200px and in the header's cramped state the panels
  flatten into the page and the two kinds of section already agree there. That
  is what keeps the phone view untouched, which is what was asked.

  .section--flat is named beside the new class rather than given it in the
  markup: a flat section is a panelled section with its panel taken off — it is
  drawn on the plain background like the rest of these — and its opening tag is
  pinned character for character by the two tests that keep the trust block on
  the page. It reads the rule by what it already is.

  Written after .section__head's own rule, not before it, and that is not a
  matter of taste: the CSS tests find a rule by the first occurrence of its
  selector text, and this selector ends in the same .section__head they look
  for.
*/

.section--head-inset > .container > .section__head,
.section--flat > .container > .section__head {
  padding-inline: calc(var(--band-pad) - var(--gutter));
}

/*
  And the why-Previa features move with their heading.

  The client, 9 September, 23:31, on that section: "the title 'Search the map,
  not a list' move a bit right and align with section title, this is now
  preview window so this needs to be aligned with title."

  The rule above deliberately left every grid where it was — "with the preview
  windows all is fine" — and that still holds for the card grids: a card has an
  edge you can see, and Featured properties reads as a row of panels held at
  the width the client settled on. This block has no panels in it. It is four
  icons with headings and paragraphs drawn straight onto the page, so there is
  no edge of its own to read and the only line it can be measured against is
  the title above it — which is exactly what the client is reading it against.

  Same expression as the heading's, for the same reason: one gutter on a wide
  screen and nothing at all once --band-pad has stepped down to --gutter, so
  phone view is untouched here too.

  Both sides, not the left alone. The heading is inset on both, and taking only
  the left would widen the four columns while moving them, which shifts every
  title on the row rather than lining them up.
*/

.section--head-inset > .container > .trust-grid,
.section--flat > .container > .trust-grid {
  padding-inline: calc(var(--band-pad) - var(--gutter));
}

.section__head-text {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 0;
}

.section__title {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 400;
  letter-spacing: var(--ls-tight);
}

.section__subtitle {
  color: var(--text-muted);
  font-size: var(--fs-base);
  max-width: 60ch;
}

.section__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--navy);
  white-space: nowrap;
}

[data-theme='dark'] .section__link {
  color: var(--gold);
}

.section__link svg {
  width: 16px;
  height: 16px;
}

@media (max-width: 640px) {
  .section__head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-3);
    margin-bottom: var(--sp-4);
  }
  .section--ads > .container > .section__head,
  .section__head:has(+ .broker-strip),
  .section__head:has(+ .grid--properties),
  .section__head:has(+ .section-empty) {
    margin-bottom: var(--sp-1);
  }
}

/* ==========================================================================
   Grids
   ========================================================================== */

.grid {
  display: grid;
  gap: var(--card-gap);
}

/*
  display:grid is also on the named grids: a stray brace once dropped the
  bare .grid rule from the CSSOM and every homepage card went full-bleed.
*/
.grid--3,
.grid--4,
.grid--2,
.grid--results,
.grid--properties,
.grid--articles,
.grid--list {
  display: grid;
}

/* Premium homepage density: 3 → 2 → 1. */

.grid--3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

@media (max-width: 1024px) {
  .grid--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px)  {
  .grid--3 {
    grid-template-columns: 1fr;
  }
}

.grid--4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 1180px) {
  .grid--4 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 860px)  {
  .grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 520px)  {
  .grid--4 {
    grid-template-columns: 1fr;
  }
}

.grid--2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 768px) {
  .grid--2 {
    grid-template-columns: 1fr;
  }
}

/*
  Property grids.

  The client asked for smaller previews, five to a row on a large screen. At
  the 1280px container that means roughly 220px a card, so the auto-fill
  minimum drops accordingly and the count falls naturally as the viewport
  narrows rather than through a stack of breakpoints.
*/

.grid--results {
  grid-template-columns: repeat(auto-fill, minmax(214px, 1fr));
}

/* Listings column: tighter card grid under the toolbar / chips. */
.search-results-col .grid--results,
.search-results-col .grid--list {
  gap: var(--sp-4);
}

@media (max-width: 600px) {
  .grid--results {
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  }
}

@media (max-width: 400px) {
  .grid--results {
    grid-template-columns: 1fr;
  }
}

/*
  Curated property rows (homepage "Featured properties" and similar). Same
  density as the results grid, expressed in fixed steps because these sections
  show a known number of cards and should not leave a ragged final row.
*/

.grid--properties {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

@media (max-width: 1280px) {
  .grid--properties {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 1024px) {
  .grid--properties {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px)  {
  .grid--properties {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 480px)  {
  .grid--properties {
    grid-template-columns: 1fr;
  }
}

/*
  The article index. Five across on a wide screen, on the client's note that the
  previews were too big — the card takes its width from the column, so the
  column count is the size control. Same ladder as the property rows above, so
  an article row and a property row break at the same widths.
*/

.grid--articles {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

@media (max-width: 1280px) {
  .grid--articles {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 1024px) {
  .grid--articles {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px)  {
  .grid--articles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 480px)  {
  .grid--articles {
    grid-template-columns: 1fr;
  }
}

.grid--list {
  grid-template-columns: 1fr;
  gap: var(--sp-4);
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.stack--sm {
  gap: var(--sp-2);
}

.stack--lg {
  gap: var(--sp-6);
}

.row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

.row--between {
  justify-content: space-between;
}

.row--end {
  justify-content: flex-end;
}

.row--tight {
  gap: var(--sp-2);
}

.spacer {
  flex: 1;
}

/* ==========================================================================
   Header
   ========================================================================== */

/*
  The header scrolls away with the page rather than pinning to the top: it was
  taking a permanent bite out of the viewport for no benefit. The floating menu
  button below takes over once it is gone, so the menu is never unreachable.
*/

/*
  The header is a panel too, as of 18 August.

  It was the last block on the page still painting from one screen edge to the
  other, and the client asked for it cut on three screens in three messages —
  the double-header pages, the listings page and the homepage. "Cut the header
  the same width as the page content, and make the below corners round."

  Width and centring are the footer's, by way of --page-width, so the header
  and the footer are the same shape at opposite ends of the page and both line
  up with what is between them. The rounding is the footer's mirrored: the
  bottom two corners here, the top two there.

  Which element carries the rounding depends on what follows the header, and
  the two rules below take it away again when something else continues the
  block downwards — a page head sharing this surface colour, or the homepage
  banner running up underneath it.
*/

.site-header {
  position: relative;
  z-index: var(--z-header);
  width: var(--page-width);
  margin-inline: auto;
  /*
    Day-mode snow on every page — the client, 17 September: the same #FFFAFA
    as the map page header, everywhere. Dark keeps --surface.
  */
  background: #FFFAFA;
  border-bottom: 1px solid var(--border);
  border-radius: 0 0 var(--r-xl) var(--r-xl);
}

[data-theme='dark'] .site-header {
  background: var(--surface);
}

/*
  Room inside the panel, at the same step as the footer's.

  The inner .container fills the panel rather than keeping its own 1280px
  ceiling, which on the wide pages would have centred it inside a wider panel
  and left the logo floating two gutters in. One gutter, everywhere — the
  distance the footer's links sit from their panel's edge.
*/

.site-header > .container,
.page-head > .container {
  max-width: none;
}

/* A page head continues the header's surface downwards, so the corners belong
   to whichever of them the block actually ends on. */

body:has(.page-head) .site-header {
  border-radius: 0;
}

/*
  The map shell continues it too.

  The client, 25 August, twice: "the header there on the left side is still not
  fixed." The bar and the shell beneath it are the same width and land on the
  same two edges — that part was right — but the bar still curved inwards at
  the bottom while the shell under it started square. The two met along a line
  with a bite out of each end, and the page background showed through the gap:
  a notch at the header's bottom-left corner, which is what he was pointing at.

  Same rule as .page-head above, for the same reason: the corners belong to
  whichever element the block actually ends on, and on these screens that is
  the results toolbar, which draws them at its own bottom edge — see
  .map-results__head in pages.css.
*/

/*
  Map page header: like the frontpage bar over the banner, but only the
  bottom-right corner is cut round so the Google Map fills that notch. Tops
  stay square. Bottom-left stays square so the results column continues the
  bar without a gap. The map shell pulls up under the bar — see .map-shell.
*/

body:has(.map-shell) .site-header {
  border-bottom: 0;
  box-shadow: none;
  border-radius: 0 0 var(--r-xl) 0;
}

/*
  Header above a flush hero — no separator.

  On the homepage the header sits directly on the hero photograph, and the
  1px rule under it drew a hairline across the top of the image. The client
  asked for it gone. Removing the border also removes the pixel it occupied,
  so the hero moves up by exactly that much and closes the gap on its own —
  the hero's -(--header-h) margin is measured from the header's bottom edge,
  which is now one pixel higher.

  Scoped to pages that actually carry a flush hero. Everywhere else the rule
  still separates the header from a page head that shares its surface colour.
  The two keep distinct backgrounds here regardless: an opaque --surface bar
  over photography.

  The client, 17 September: keep the header's own rounded bottom corners on
  the homepage (the first-paint look). The hero still rounds its media; the
  bar no longer goes square when app.css arrives.
*/

body:has(.hero--flush) .site-header {
  border-bottom: 0;
  box-shadow: none;
  border-radius: 0 0 var(--r-xl) var(--r-xl);
}

/* Nothing may reintroduce the line as a pseudo-element or a spacer strip. */

body:has(.hero--flush) .site-header::after,
body:has(.hero--flush) .site-header::before {
  content: none;
}

/*
  The same reasoning, generalised, at the client's request on 17 August: the
  separator under the header exists to divide the header from a block that
  shares its colour, and a page whose next block is a different colour does not
  need it — "in the main listing page where the header and menu block under it
  are different colors, then remove this separator line under header".

  A page head is the one block that carries the header's --surface, so it is
  also the one case that keeps the line. Everywhere else — the search results,
  a listing, the map screens, the sign-in pages — the page opens on --bg and
  the colour change is the division.
*/

body:not(:has(.page-head)) .site-header {
  border-bottom: 0;
}

body:has(.hero--flush) .hero--flush {
  margin-block-start: calc(var(--header-h) * -1);
}

/*
  The row tightens before it collapses.

  The client, 2 September, with the header photographed at about 1270px: "now
  still this add listing button moves out of header, it should never happen, so
  fix it. Make that if narrow the screen down, then the header sections -
  Listings, Map, Articles - at first will move closer to each other and can move
  more left closer to logo, and when too close, then it switched to mobile
  view."

  Three requests, and each has its own mechanism here:

  1. It must never happen. The actions are flex: none and the navigation is the
     only item in the row allowed to give ground, so a row that runs out of room
     takes the pixels out of the navigation. There is no arrangement of labels,
     languages or account states in which "add listing" is the thing that is
     pushed over the edge.

  2. Closer to each other, and closer to the logo. Every gap between the mark
     and the last nav link — the row's own gap, the nav's offset from the logo,
     the space between two links and the padding inside each one — is a clamp
     that runs from its full value at 1440px down to a tight one at 1200px, so
     narrowing the window closes them continuously instead of in one jump. The
     four together are 112px of room recovered before anything is hidden.

  3. When too close, mobile. 1200px is where the clamps bottom out and the
     media query further down takes the row into the drawer. That number is a
     default rather than the whole answer: previaHeader measures the row it
     actually drew and collapses earlier when the labels are longer than the
     English ones — see .site-header.is-cramped below it.
*/

.header__inner {
  display: flex;
  align-items: center;
  gap: clamp(var(--sp-3), calc(var(--sp-3) + (100vw - 1200px) / 30), var(--sp-5));
  height: var(--header-h);
  min-width: 0;
}

.header__logo {
  flex: none;
  display: inline-flex;
  align-items: center;
  height: var(--header-h);
}

.header__nav {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: clamp(0px, (100vw - 1200px) / 60, var(--sp-1));
  margin-left: clamp(0px, (100vw - 1200px) / 15, var(--sp-4));
  /* The one flexible item in the row, and the one the measurement above
     reads: the pixels the nav is missing are the pixels the row is short of.
     Clipping is what makes that reading possible — and what guarantees the
     overflow can never reach the button on the other side of the row.

     Full header height, so the clipping is horizontal in practice: the links
     are centred in 72px and their focus ring, which is drawn 2px outside them,
     has room above and below instead of being cut off by this rule. */
  align-self: stretch;
  min-width: 0;
  overflow: hidden;
}

.header__link {
  position: relative;
  flex: none;
  padding-block: var(--sp-2);
  padding-inline: clamp(7px, calc(7px + (100vw - 1200px) / 48), var(--sp-3));
  border-radius: var(--r-sm);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  color: var(--text-muted);
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.header__link:hover {
  color: var(--text);
  background: var(--surface-3);
}

/*
  One selection cue, not two. The active section used to carry both an
  underline and (on hover) a box; the underline is gone and the box now marks
  the current section as well as the hovered one.
*/

.header__link[aria-current='page'] {
  color: var(--navy);
  font-weight: 600;
  background: var(--surface-3);
}

[data-theme='dark'] .header__link[aria-current='page'] {
  color: var(--gold);
}

/* Never shrunk and never pushed: see the note above .header__inner. */

.header__actions {
  display: flex;
  align-items: center;
  flex: none;
  gap: var(--sp-2);
  margin-left: auto;
}

/*
  Language / account menu hosts.

  These wrappers must be real boxes (not display:contents). With contents, the
  open .dropdown became a flex sibling in .header__actions — the trigger slid
  left and the menu appeared beside it instead of under it.
*/

.header__menu.header__desktop-only {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex: none;
}

.header__menu .dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: calc(var(--z-header) + 5);
}

.header__menu .dropdown--right {
  left: auto;
  right: 0;
}

.header__menu .dropdown > .menu {
  top: 8px;
}

/* No gold focus frame on the language chip — the client asked it gone. */
.icon-btn--lang:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Small square control used by theme, language and country triggers. */

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 40px;
  min-width: 40px;
  padding: 0 var(--sp-2);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: 1;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.icon-btn:hover {
  background: var(--surface-3);
  color: var(--text);
}

.icon-btn svg {
  width: 18px;
  height: 18px;
}

/* Same #8B008B as the header add listing button (.header__cta). */
.icon-btn--notify,
.icon-btn--notify:hover {
  color: #8B008B;
}

html[data-theme='dark'] .theme-icon--moon,
html:not([data-theme='dark']) .theme-icon--sun {
  display: none;
}

.icon-btn__flag {
  font-size: 16px;
  line-height: 1;
}

/*
  The language chip's two-letter code.

  The client, 6 September: "make there abbreviation: EN, RU, ES", matching the
  market chip on the banner beside it. The catalogue stores a language code
  lowercase — "ru" is the spelling the /ru/ prefix and the previa_lang cookie
  are matched on — so the capitals are drawn here rather than written into the
  data, and the code stays comparable everywhere else.

  Weight and tracking are the market chip's, so the two read as a pair: 600 and
  a hair of letter-spacing, which two capitals need and a word does not.
*/

.lang-code {
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1;
  display: inline-block;
}

/*
  Navigation menu trigger.

  One class for every control that opens the drawer — the header's and the
  floating one that takes over after the header scrolls away — so they cannot
  drift apart.

  Transparent, at the client's request: no green disc, no coloured fill, no
  border, no gradient, no shadow behind the glyph. The icon carries itself in
  currentColor, which means it inherits the right ink in both themes without a
  second rule.

  The glyph is 26px inside a 44px box: comfortably tappable and comfortably
  above the 44px accessibility floor, while the drawn mark stays modest.
*/

.menu-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--r-md);
  /*
    Previa logo violet ground, lime icon — day and dark, same pair the
    advertise "Featured listings" mark uses.
  */
  background: #6F006F;
  color: #00FF00;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}

.menu-trigger svg {
  width: 26px;
  height: 26px;
  stroke-width: 1.9;
  color: #00FF00;
  stroke: #00FF00;
}

.menu-trigger:hover {
  background: #5a005a;
  color: #00FF00;
}

.menu-trigger:active {
  transform: translateY(1px);
}

/* No gold (or any) focus ring — the violet face is the only frame. */
.menu-trigger:focus,
.menu-trigger:focus-visible,
.floating-menu:focus,
.floating-menu:focus-visible {
  outline: none;
  box-shadow: none;
}

[data-theme='dark'] .menu-trigger,
[data-theme='dark'] .menu-trigger:hover {
  background: #6F006F;
  color: #00FF00;
}

[data-theme='dark'] .menu-trigger:hover {
  background: #5a005a;
}

.header__burger {
  display: none;
}

/*
  Floating menu button — appears only after the header has scrolled out of
  view. Pinned to the top of the viewport, above page content but below the
  drawer it opens, and clear of the safe area on notched phones.

  The shell below is what keeps it inside the page.

  "The sidebar menu on the right, if start to scroll page down, it moves too
  far away, make that it stays always inside the page, so it is always under
  the header right side. So that this menu's right side is aligned to header
  right side."

  It used to be offset from the *viewport* edge by --shell-gutter, which is 0
  below 1440px — so on any ordinary monitor it sat against the window rather
  than against the website, a whole gutter outside the page panel it belongs
  to. Arithmetic on 100vw could not fix that: 100vw includes the scrollbar and
  --page-width does not, so the two disagree by however wide the scrollbar is.

  This does it by layout instead. The shell is the header's box — same
  --page-width, same centring, same --gutter of inside room — pinned to the
  top of the viewport, and the button is pushed to its inline end. Its right
  edge is therefore the same pixel as the "add listing" button's in the header
  above it, at every width and on every page, including the wide-container and
  full-bleed map layouts that redefine --page-width for themselves.

  It is inert except where the button is, so the page underneath stays
  clickable across the rest of the strip.
*/

.floating-menu-shell {
  position: fixed;
  top: max(var(--sp-4), env(safe-area-inset-top));
  inset-inline: 0;
  z-index: calc(var(--z-header) - 1);
  width: var(--page-width);
  margin-inline: auto;
  padding-inline: max(var(--gutter), env(safe-area-inset-right));
  display: flex;
  justify-content: flex-end;
  pointer-events: none;
}

/*
  A plain surface behind the icon.

  It used to be transparent — it only appeared once the header had scrolled
  away, and what was behind it then was the page's own background rather than
  anything to read against. That stopped being true when the control moved
  inside the page: it now sits over listing cards, article covers and panels,
  and a bare stroked icon on a photograph is not legible.

  No colour and no border, which is the least that does the job: this is still
  the header's control, moved, and it should not read as a new button.
*/

.floating-menu {
  pointer-events: auto;
  background: #6F006F;
  color: #00FF00;
  box-shadow: var(--shadow-sm);
}

[data-theme='dark'] .floating-menu {
  background: #6F006F;
  color: #00FF00;
}

/* The map screen used to need this rule of its own; it is the default now. */

/*
  The nav collapses into the drawer at 1200px, not at 1080px.

  The client, 31 August, with a photograph of the header at about 1100px: "in
  this screen size look the add listing button in the header, so it must switch
  to phone header a bit earlier."

  He was right, and the number is measurable rather than a matter of taste.
  Signed in — which is the state he photographed, and the widest the row ever
  gets, because it carries the language menu, the bell and the account avatar
  on top of the five nav links — .header__actions is 1009px of content in a
  972px inner at 1100px wide. The "add listing" button is the last thing in the
  row, so the overflow lands on it: it hung 53px past the header's right edge at
  1100px, 33px at 1120px and 13px at 1140px. The row first fits exactly at
  1160px.

  1160 is where it stops overflowing, so 1160 is the wrong place to switch: a
  breakpoint with no slack is one longer translated label away from the bug
  coming back, and this header is drawn in every language the site sells in.
  1200px keeps roughly 100px of room at the narrowest width that still shows the
  desktop row, which is a label's worth.
*/

@media (max-width: 1200px) {
  .header__nav,
  .header__desktop-only,
  .header__menu.header__desktop-only {
    display: none;
  }

  .header__burger {
    display: inline-flex;
  }
}

/*
  The same collapse, decided by measurement instead of by a number.

  1200px is right for the English row and wrong for a longer one — the German
  navigation ends in "Neubauprojekte", and the client's own screenshot was of a
  header a hundred pixels wider than the breakpoint that was meant to protect
  it. A media query cannot know which language it was drawn in, how long the
  signed-in visitor's initials are, or that a sixth section was added to the bar
  in August.

  So previaHeader watches the row it actually rendered and puts this class on
  the header the moment the navigation is short of room, whatever the width.
  What the class does is exactly what the breakpoint above does — the drawer is
  the same drawer, and it already carries every one of these links.
*/

.site-header.is-cramped .header__nav,
.site-header.is-cramped .header__desktop-only,
.site-header.is-cramped .header__menu.header__desktop-only {
  display: none;
}

.site-header.is-cramped .header__burger {
  display: inline-flex;
}

/*
  A label that gives way to its icon, on its own breakpoint.

  The Grid / List / Map words in the results toolbar used to borrow
  .header__desktop-only, which was harmless while that class turned at 1080 and
  wrong the moment the header's own switch moved to 1200: the toolbar would have
  dropped to icons across a 120px band nobody asked about, on a screen the
  client was not writing about. The header's breakpoint is the header's, so
  these keep the number they have always had.
*/

@media (max-width: 1080px) {
  .desktop-only {
    display: none;
  }
}

/* ==========================================================================
   Mobile off-canvas drawer
   Overlays the page — content never shifts sideways.
   ========================================================================== */

.drawer-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  background: var(--overlay);
}

/*
  Navigation sidebar.

  The design is the one the client approved; what changed is its size. It no
  longer runs the full height of the screen — it is a panel attached to the
  right edge, as tall as its own contents, with the two left corners rounded
  and the two against the screen edge left square. Roughly half the height it
  used to be on a normal desktop screen, because every row, gap and divider is
  tighter and the list itself is shorter.

  Height is adaptive rather than fixed: content decides it, a safe maximum
  caps it on short screens, and only the middle scrolls — the header carrying
  the close control and the footer carrying `add listing` are always in view.
*/

/*
  The box the panel is positioned in: the header's box, pinned under it.

  "Fix the right side menu — when scrolling down it should stay properly below
  the header, it should not move too far away, and its right edge should be
  aligned with the header's right edge."

  Same device as .floating-menu-shell and for the same reason. The panel used
  to be fixed to the viewport, which put its right edge on the monitor rather
  than on the website — --shell-gutter is zero on anything narrower than 1440px,
  so on an ordinary screen it hung a full outer margin outside the page — and
  its top edge 12px from the top of the window, over the header instead of
  under it. Neither can be corrected with arithmetic on 100vw, because 100vw
  includes the scrollbar and --page-width does not.

  So the shell is the header's own box — same --page-width, same centring —
  fixed at --floating-menu-band, the strip directly beneath the floating menu
  button. The panel inside is pinned to its inline end, which makes its right
  edge the header's right edge at every width, and its top edge the header's
  bottom edge at the top of the page and the line just under the floating
  button once the header has gone. Layouts that redefine --page-width for
  themselves — the wide listings shell, the full-bleed map — are carried along
  with no rule of their own.

  Inert, so the backdrop underneath still takes the click that closes the menu.
*/

.drawer-shell {
  position: fixed;
  top: var(--floating-menu-band);
  bottom: 0;
  inset-inline: 0;
  z-index: calc(var(--z-drawer) + 1);
  width: var(--page-width);
  margin-inline: auto;
  pointer-events: none;
}

.drawer {
  /* One number drives every row in the panel. */
  --drawer-row-h: 28px;
  position: absolute;
  top: 0;
  right: 0;
  bottom: auto;
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  width: min(360px, 88vw);
  height: auto;
  /* content-driven */
  /* Never past the bottom of the window: the shell starts a band down from the
     top, so that band comes off the height, and --sp-4 keeps it off the edge. */
  max-height: calc(100dvh - var(--floating-menu-band) - var(--sp-4));
  background: var(--surface);
  border: 1px solid var(--border);
  /*
    Rounded on all four corners now, and bordered on all four sides.

    The two right corners were square and the right border absent because the
    panel was attached to the edge of the screen — there was nothing on that
    side for an edge to separate it from. Now that it stops at the website's
    edge it is a panel among panels, like the header above it, and a squared-off
    right side hanging in the middle of the page would read as a clipping bug.
  */
  border-radius: 16px;
  box-shadow: var(--shadow-panel);
  overflow: hidden;
  /* so the rounded corners clip their content */
  overscroll-behavior: contain;
}

.drawer--left {
  right: auto;
  left: 0;
}

.drawer__header {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: 5px var(--sp-2) 5px var(--sp-4);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.drawer__title {
  font-size: var(--fs-base);
  font-weight: 600;
}

/*
  Close control, floating in the corner rather than sitting in a header row of
  its own. Absolute inside the panel — which is `position: fixed`, so it is the
  containing block — which keeps it in view while the list below scrolls.
*/

.drawer__close {
  position: absolute;
  top: 6px;
  right: 8px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--r-sm);
  color: var(--text-muted);
  background: var(--surface);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.drawer__close:hover {
  background: var(--surface-3);
  color: var(--text);
}

.drawer__close svg {
  width: 17px;
  height: 17px;
}

/*
  The focus trap lands on this close control when the menu opens, so the
  global :focus-visible ring (gold in dark mode) painted a golden frame
  around the X. Keyboard focus stays; the ring does not.
*/

.drawer__close:focus,
.drawer__close:focus-visible {
  outline: none;
  box-shadow: none;
}

/* The only scrolling region, and only when the content needs it. */

.drawer__body {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--sp-2) var(--sp-2) var(--sp-2);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.drawer__footer {
  flex: none;
  display: flex;
  gap: var(--sp-3);
  padding: 6px var(--sp-3);
  border-top: 1px solid var(--border);
  background: var(--surface);
}

.drawer__nav {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.drawer__link {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 3px var(--sp-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text);
  min-height: var(--drawer-row-h);
}

.drawer__link:hover {
  background: var(--surface-3);
}

.drawer__link[aria-current='page'] {
  background: var(--navy-tint);
  font-weight: 600;
}

.drawer__link svg {
  width: 17px;
  height: 17px;
  color: var(--text-muted);
}

.drawer__divider {
  height: 1px;
  margin: 4px 0;
  background: var(--border);
}

.drawer__label {
  /* Right padding clears the floating close button, which shares this line. */
  padding: 2px 38px 3px var(--sp-3);
  font-size: var(--fs-2xs);
  font-weight: 700;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
}

.drawer__footer .btn {
  min-height: 36px;
}

/* Same #8B008B as the header add listing button and the logo tile. */
.drawer__footer .btn--primary {
  background: #8B008B;
  border-color: #8B008B;
  color: #fff;
}
.drawer__footer .btn--primary:hover:not(:disabled) {
  background: #6F006F;
  border-color: #6F006F;
  color: #fff;
}

/* --------------------------------------------------------------------------
   Sidebar preference rows — website language, day/dark mode, notifications.

   The same height and rhythm as the navigation rows above them, so the panel
   reads as one list rather than as a list with a settings block bolted on.
   -------------------------------------------------------------------------- */

.drawer__row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: var(--drawer-row-h);
  padding: 3px var(--sp-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text);
  text-align: left;
}

.drawer__row > svg {
  width: 17px;
  height: 17px;
  flex: none;
  color: var(--text-muted);
}

.drawer__row-label {
  flex: 1;
  min-width: 0;
}

.drawer__row-value {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
}

button.drawer__row:hover {
  background: var(--surface-3);
}

/*
  The theme button, borrowed from the header so both copies of the control look
  identical. It is inert markup inside the row button, so it must not eat the
  pointer or paint its own hover box on top of the row's — only its colour
  lifts, in step with the row.
*/

.drawer__row-toggle {
  height: 28px;
  min-width: 28px;
  padding: 0;
  pointer-events: none;
}

.drawer__row-toggle:hover {
  background: none;
}

button.drawer__row:hover .drawer__row-toggle {
  color: var(--text);
}

/* Language options, revealed under their row rather than in a second overlay. */

.drawer__sublist {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 2px 0 2px var(--sp-8);
}

.drawer__sublist a {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 30px;
  padding: 4px var(--sp-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.drawer__sublist a:hover {
  background: var(--surface-3);
  color: var(--text);
}

.drawer__sublist a.is-active {
  color: var(--text);
  font-weight: 700;
}

/* Prevent the page behind an open overlay from scrolling. */

body.is-locked {
  overflow: hidden;
}

/* ==========================================================================
   Footer
   ========================================================================== */

/*
  The footer is a panel, not a band.

  It used to paint edge to edge across the shell while every other block on the
  page stopped at the content width, so on a wide screen it read as a strip cut
  off past where the page ends. The client asked for it "the same wide as the
  page content", with the top corners rounded: the width below is the container
  less its two gutters, which is exactly the box the cards and headings above it
  sit in, so the footer's edges line up with theirs at every breakpoint. The
  gutter subtraction is inside the min() on both sides, which is what keeps the
  panel off the viewport edges on a phone as well.

  --gutter changes at the responsive breakpoints, so both halves of the min()
  read it rather than hard-coding 32px.
*/

.site-footer {
  width: min(100% - var(--gutter) * 2, calc(var(--container) - var(--gutter) * 2));
  margin-inline: auto;
  background: var(--navy);
  color: #C7D3E0;
  font-size: var(--fs-sm);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
}

[data-theme='dark'] .site-footer {
  background: var(--surface);
  color: var(--text-muted);
  border-top: 1px solid var(--border);
}

/*
  The same exception the header takes, for the same reason.

  The rule above is "the footer stops where the page content stops", and on the
  two families laid out on the wide container — the listings screen and the
  administration screens — the content does not stop there. Left alone, the
  header cut to the results grid on 18 August would have sat 145px wider than
  the footer under the same grid, which is the mismatch the client has now
  asked to close twice, once at each end of the page. The width is
  --page-width's wide value; the base rule keeps the literal formula because
  every panel on the site is matched against it.
*/

body:has(.container--wide) .site-footer {
  width: var(--page-width);
}

/*
  And on a narrow screen the footer goes to the edge with everything else.

  The rule above reads --page-width, so the listings and admin screens follow
  the narrow-screen change near the top of this file on their own. The base rule
  cannot: it spells the formula out rather than reading the token — deliberately,
  because every panel on the site is matched against it — so the token moving
  leaves it behind. Left alone, an ordinary page on a phone had a header running
  to both edges and a footer inset from them by a gutter, which is the header
  and the footer disagreeing about where the page ends: the exact mismatch the
  client has now asked to close twice, once at each end.

  The rounded top corners stay, mirroring the header's, which is what the two of
  them have always done.

  The width the two of them turn at moved with the shell on 3 September: the
  header collapses at 1200px, so the footer reaches the edge there too, and
  .is-cramped carries the pair together above it.
*/

@media (max-width: 1200px) {
  .site-footer {
    width: 100%;
  }
}

body:has(.site-header.is-cramped) .site-footer {
  width: 100%;
}

/*
  One link row and a legal row — the whole footer. The brand block, the Explore
  column, the popular-searches matrix and, later, the two column headings and
  every link the client did not name were all removed at their request.

  The five survivors sit on a single line spread evenly across the container
  rather than stacked in a column, which is what keeps the footer short: five
  links tall became five links wide. Equal columns, not space-between, so the
  gaps stay even instead of being set by the longest label. Below 720px the row
  wraps into a centred cluster — at that width five columns would each be too
  narrow to hold their label on one line.
*/

/*
  The mark on the left, everything else to the right of it.

  "The same logo insert into footer left side as well. So the footer text
  'Listing packages' and '© 2026 Previa, all rights reserved!' move to the
  right and place the logo there, in the same place as it is in the header."

  Two columns and two rows: the logo spans both rows in the first column, the
  link row takes the second column's first row and the legal strip its second,
  so the horizontal rule between them still runs the width of the text block
  rather than under the mark. The column is sized from the lockup itself so it
  tracks the responsive logo sizes without a second set of breakpoints.
*/

.footer__grid {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--sp-8);
  align-items: start;
}

/*
  The wordmark reads --navy, and in the light theme the footer's ground *is*
  --navy: the mark would have been a magenta tile with nothing beside it. So the
  word takes the footer's own text colour, the way every other word down here
  does, rather than the header's.
*/

.footer__logo .logo__word {
  color: var(--text-inverse);
}

[data-theme='dark'] .footer__logo .logo__word {
  color: var(--text);
}

/*
  The mark alone, as tall as the footer.

  "Remove the text Previa from the footer, keep only the logo, and make the logo
  as large as possible inside the current footer. Do not make the footer
  higher." Those last two pull against each other in a grid: an item spanning
  both rows contributes its height to them, so simply setting a big square here
  would push the rows apart and take the footer with them.

  So the box contributes no height at all. It stretches to whatever the link row
  and the legal strip beside it come to, and the lockup inside is taken out of
  flow and pinned to its four edges — an absolutely positioned child has no
  intrinsic contribution, so the rows are still sized by column two alone and
  the footer's height is exactly what it was. Whatever that height turns out to
  be at a given breakpoint, the mark is that tall.

  The width is the one fixed number, because a column cannot be sized from a
  height that is not known until the rows resolve. 132px is the desktop footer's
  own height (70 + 61 + the hairline), so the square comes out square. The SVG
  keeps preserveAspectRatio's default, so on any breakpoint where the rows come
  out taller or shorter than that the drawing scales to fit and centres rather
  than distorting.

  The old padding-top, which held the mark level with the header's, goes with
  the word: the instruction now is to fill the footer, and a 24px inset at the
  top is 24px of the fill given away.
*/

.footer__logo {
  grid-row: 1 / span 2;
  align-self: stretch;
  position: relative;
  width: 132px;
}

.footer__logo .logo {
  position: absolute;
  inset: 0;
  gap: 0;
  height: auto;
  width: auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

.footer__logo .logo__mark {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
}

/*
  Narrow, the footer keeps its shape.

  It used to collapse to one column here: the mark went onto a row of its own,
  centred, with a step of padding above it, and the footer grew by the height of
  that row. The client's note is both halves of the fix — "the website footer in
  narrow view is too big, make it the same height as it is in wider view, the
  logo will not go to up-center, it stays on the left always, if needed, will
  just go smaller."

  So the two columns stay, and the only thing that changes with the viewport is
  how wide the first one is. The mark is still stretched to the height of the
  two rows beside it and still contributes none of its own, exactly as it does
  on a wide screen, so the footer is the height of its content at every width.
*/

@media (max-width: 900px) {
  .footer__grid {
    column-gap: var(--sp-5);
  }

  .footer__logo {
    width: 96px;
  }
}

@media (max-width: 600px) {
  .footer__grid {
    column-gap: var(--sp-4);
  }

  .footer__logo {
    width: 64px;
  }
}

@media (max-width: 420px) {
  .footer__logo {
    width: 48px;
  }
}

/*
  The link row, packed against the right-hand edge.

  The client, 5 September: "in the footer this upper row (Prices, FAQ &
  contact) align to right, at the moment there is too big gap between the
  single section, align it all to right and make compact together."

  It was four equal columns spread across the whole width, so the gap between
  two labels was whatever was left over after the longest one — on a wide
  screen, most of the footer. Four links do not need a twelve-hundred-pixel
  ruler.

  So the row is a flex line now: each link is its own width, one step between
  them, and the run is pushed to the end of the column — which puts the last
  label on the same edge the legal strip's Sitemap already ends on, and the
  two rows finally line up on the right the way they do on the left. The
  narrow-screen rules below already used flex and are unchanged; they inherit
  the alignment from here.
*/

.footer__main {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-3) var(--sp-6);
  padding: var(--sp-6) 0;
}

.footer__main a {
  color: #C7D3E0;
}

.footer__main a:hover {
  color: #8B008B;
}

[data-theme='dark'] .footer__main a {
  color: var(--text-muted);
}

[data-theme='dark'] .footer__main a:hover {
  color: #8B008B;
}

.footer__bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-4);
  flex-wrap: wrap;
  padding: var(--sp-4) 0;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  font-size: var(--fs-xs);
  color: #9FB0C2;
}

[data-theme='dark'] .footer__bottom {
  border-top-color: var(--border);
  color: var(--text-subtle);
}

.footer__legal {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
}

.footer__legal a:hover {
  color: #8B008B;
}

/*
  Email and account, moved down from the removed brand block. Sized as icons
  rather than as the 38px bordered buttons they used to be — this row is a
  compact legal strip, not a section — and separated from the word links by a
  hairline so the two kinds of link do not read as one run-on list.
*/

.footer__legal-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-right: calc(var(--sp-2) * -1);
  border-radius: var(--r-sm);
  color: inherit;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.footer__legal-icon + .footer__legal-icon {
  margin-right: var(--sp-1);
}

.footer__legal-icon:hover {
  background: rgba(255, 255, 255, 0.1);
  color: #8B008B;
}

[data-theme='dark'] .footer__legal-icon:hover {
  background: var(--surface-3);
}

.footer__legal-icon svg {
  width: 16px;
  height: 16px;
}

.footer__legal-icon + a:not(.footer__legal-icon) {
  padding-left: var(--sp-4);
  border-left: 1px solid rgba(255, 255, 255, 0.18);
}

[data-theme='dark'] .footer__legal-icon + a:not(.footer__legal-icon) {
  border-left-color: var(--border);
}

/*
  Narrow, the footer keeps close to its desktop height.

  "The website footer in narrow view is too big, make it the same height as it
  is in wider view." Once the logo stopped taking a centred row of its own, what
  was left of the growth was the two rows of text beside it wrapping: five links
  onto three lines, and the legal strip onto two or three. Both are tightened
  here rather than restructured — the links pack into whatever rows the column
  can hold, and the type, the gaps and the vertical padding step down with the
  viewport.

  A wrap rather than a fixed column count: two columns would have forced three
  rows of links at every width, including the ones where three fit across.

  These rules sit after the base ones deliberately. They carry no extra
  specificity, so source order is what decides them, and an earlier media query
  would have lost to the very declarations it is meant to override.
*/

@media (max-width: 900px) {
  .footer__main {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-4) 0;
    font-size: var(--fs-sm);
  }

  .footer__bottom {
    gap: var(--sp-1) var(--sp-3);
    padding: var(--sp-3) 0;
  }

  .footer__legal {
    gap: var(--sp-3);
  }
}

@media (max-width: 700px) {
  .footer__main {
    gap: 2px var(--sp-3);
    padding: var(--sp-3) 0;
    font-size: var(--fs-xs);
  }

  .footer__bottom {
    gap: 0 var(--sp-2);
    padding: var(--sp-2) 0;
    font-size: 11px;
  }

  .footer__legal {
    gap: var(--sp-2);
  }

  /* The two icons are the first thing to go: they duplicate links that are
     already in this row, and in the header above it. */

  .footer__legal-icon {
    display: none;
  }

  .footer__legal-icon + a:not(.footer__legal-icon) {
    padding-left: 0;
    border-left: 0;
  }
}

/* ==========================================================================
   Page shell
   ========================================================================== */

.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.page__main {
  flex: 1;
}

/*
  The ground the footer stands on: the page colour, on every page.

  It used to take the colour of whatever the last section painted, so a page
  closing on a band carried that band down behind the footer. That was right
  while bands were full-bleed and wrong the moment they became inset panels —
  "remove this block around the footer and footer stays into the same
  background as in other pages. So only the header part stays at the moment to
  a big area from left to right."
*/

/* A gap above the footer panel, because it is a panel among panels now: with
   the last section's colour no longer running behind it, its rounded top edge
   would otherwise sit flush against the block above and the two would read as
   one shape. */

.page__foot {
  margin-top: auto;
  padding-top: var(--sp-6);
  background: var(--bg);
}

/*
  The same gap above the footer on every page — the homepage's.

  "The footer distance to the upper menu block make in every page like it is in
  frontpage: so smaller." The homepage closes on a coloured panel whose own
  padding is *inside* it, so the only space between that panel's bottom edge and
  the footer is the 24px above. Everywhere else the last section added its full
  --section-y on top of that, which is where the extra 60-odd pixels the client
  measured came from: a page ended, and then went on ending.

  So the last block on the page stops padding itself at the bottom and the 24px
  is all that is left, on every screen. Panels are the exception, and have to
  be: their padding is the room inside the shape, not space under the page, and
  taking it away would push their content against their own bottom edge.
*/

.page__main > :last-child:not(.section--band):not(.section--navy) {
  padding-bottom: 0;
}

/*
  The page head: breadcrumb, title, lede, on the header's own surface colour.

  Two things the client asked for on 17 August are in these three declarations.

  The top padding was 48px, which left the breadcrumb floating in the middle of
  a gap and pushed the title a long way down the screen. It is 16px now — the
  same figure the search page uses above its breadcrumb — so the breadcrumb
  starts just under the header and sits in the same place on every page of the
  site, and the title follows it up. "The navigation bar home - developments move
  quite up next to the header, it is always there in the same place, like in the
  main listings page."

  The hairline along the bottom is gone: the block below this one is a different
  colour, and the client asked for no separator wherever that is already true.
  The rule under the header itself stays, because there the two blocks share
  this surface and the line is the only thing dividing them.
*/

/*
  Cut to the page width and rounded off at the bottom, 18 August: "so now the
  header, in these pages where we have the 'double-header', cut the header the
  same width as the page content, and make the below corners round."

  The double header is this block and the bar above it: two elements, one
  colour, read as one header. So they are cut to the same width and only the
  lower one is rounded — the header's own bottom corners are squared off by the
  body:has(.page-head) rule above, which is what keeps the two reading as one
  shape rather than as two stacked cards with a notch between them.

  The 1px rule between them stays. It is the one place on the site where two
  blocks share a colour, which is the client's own test for when a separator
  has earned its place.
*/

/*
  Tightened on 26 August, on every page that carries one.

  "This below header we make smaller and compact, and this in all the pages.
  So in all the pages this navigation bar move more up, quite close to
  upper-header, then this title SEO font make 30 % smaller and so this title
  move up us well, and the text below it make the same size as the navigation
  bar, and move more up."

  Four changes, and they are all here rather than on the admin screens alone,
  because the block is one component and the note says "in all the pages":
  2px of air above the breadcrumb (flush under the separator), 4px under the
  title for the rounded edge, the title at --fs-page-title (see tokens:
  --fs-h1 less 30%), and 2px between breadcrumb and title.
*/

.page-head {
  width: var(--page-width);
  margin-inline: auto;
  padding: 2px 0 4px;
  /* Same day-mode snow as .site-header — the page head continues that block. */
  background: #FFFAFA;
  border-radius: 0 0 var(--r-xl) var(--r-xl);
}

[data-theme='dark'] .page-head {
  background: var(--surface);
}

/*
  8px between the title and the line under it, not 12.

  "In the single article page the article title is in two rows, reduce the line
  spacing. And the small text under it move more up as well and reduce line
  spacing." Both halves are here: the gap below, and a tighter line-height on
  the title itself.
*/

.page-head__inner {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.page-head nav {
  margin: 0;
}

.page-head__title {
  font-family: var(--font-display);
  font-size: var(--fs-page-title);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.015em;
  margin: 0;
}

/*
  And the line under it, at the breadcrumb's own size — "the text below it make
  the same size as the navigation bar" — and at --lh-snug rather than the
  relaxed 1.7 it takes in running prose: it is a standfirst here, not a
  paragraph.
*/

.page-head__inner .lede {
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
}

/*
  Compact variant, used by every signed-in account screen.

  The client's note was that the account pages open with too much air: the
  breadcrumb sat a long way below the header, the welcome heading was display
  sized, and a full section gap followed before the first panel. All three are
  tightened here — the breadcrumb starts just under the header, the heading
  drops from --fs-h1 to --fs-h3, and .section--after-head below replaces the
  48–96px section top padding with a fixed 24px.
*/

/*
  The compact variant is now barely a variant: the block above already opens
  under the header and closes on the title. What it still does is drop the title
  to --fs-h3, which on an account screen is a welcome line rather than a page
  title.
*/

.page-head--compact {
  padding: 2px 0 4px;
}

.page-head--compact .page-head__inner {
  gap: 2px;
}

.page-head--compact .page-head__title {
  font-size: var(--fs-h3);
}

.section--after-head {
  padding-top: var(--sp-6);
}

/* Account screens (sidebar shell): same tight step under the page head as
   Billing — client, 21 September. */
.section--after-head:has(.shell--nav) {
  padding-top: var(--sp-2);
}

/* Checkout: short step under compact page-head so packages sit up under the title. */
.section--after-head.checkout-page {
  padding-top: var(--sp-3);
  padding-bottom: var(--sp-6);
}
.checkout-page .checkout-grid {
  gap: var(--sp-5);
}
.checkout-page .checkout-plans {
  gap: var(--sp-4);
}

/*
  Checkout packages: tighter than /prices so the choose button sits up with the
  feature list instead of leaving a tall empty band under the price.
*/
.checkout-plans .plan {
  gap: var(--sp-2);
  padding: var(--sp-4);
}
.checkout-plans .plan__flag {
  top: -10px;
  left: var(--sp-4);
}
.checkout-plans .plan__amount {
  font-size: 28px;
  line-height: 1.1;
}
.checkout-plans .plan__features {
  gap: 4px;
  line-height: 1.25;
}
.checkout-plans .plan__features svg {
  margin-top: 1px;
}
.checkout-plans .plan__choose {
  margin-top: auto;
  padding: 8px var(--sp-3);
}

.breadcrumbs {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  list-style: none;
}

.breadcrumbs li {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.breadcrumbs a:hover {
  color: var(--navy);
}

[data-theme='dark'] .breadcrumbs a:hover {
  color: var(--gold);
}

.breadcrumbs svg {
  width: 13px;
  height: 13px;
  color: var(--text-subtle);
}

.breadcrumbs [aria-current='page'] {
  color: var(--text);
  font-weight: 500;
}

/* ==========================================================================
   Search layout with collapsible filter sidebar
   ========================================================================== */

/*
  Listings page: the "Listings › For sale" trail scrolls away with the page.
  The Filters sidebar stays pinned to the top of the viewport once you scroll.

  No top padding — the trail sits flush under the header (same place it had
  when it was position:fixed at --header-h). Extra padding had pushed the
  crumb and the banner down.
*/

.search-page {
  padding-top: 0;
}

.search-layout {
  display: grid;
  grid-template-columns: var(--filter-w, 320px) minmax(0, 1fr);
  gap: var(--sp-7);
  align-items: start;
  transition: grid-template-columns var(--dur-slow) var(--ease);
}

/* Collapsed: the results area expands, a thin rail stays to reopen. */

.search-layout.is-collapsed {
  grid-template-columns: 0 minmax(0, 1fr);
  gap: 0;
}

/*
  Keep the filters column as tall as the page main, so sticky has room to
  travel for the whole results scroll — not only the short stretch of a
  handful of cards, after which the panel used to drop away with the layout.
*/

.page:has(.search-layout) .page__main {
  display: flex;
  flex-direction: column;
}

.page:has(.search-layout) .page__main > .container.container--wide {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.page:has(.search-layout) .search-layout {
  flex: 1 1 auto;
  min-height: 0;
}

/*
  The breadcrumb row — in normal flow so it scrolls out of the viewport.
  Fixed height, because the advertising banner below reaches up past it by
  exactly this much (see .search-banner in pages.css).

  padding-left: one gutter so the trail lines up with the header logo / the
  tabs on Articles. The wide container's content edge sits a gutter left of
  the header's inner padding; without this the crumb reads as shifted left.
*/

.search-crumb {
  height: var(--crumb-h);
  margin: 0 0 var(--sp-2);
  padding-left: var(--gutter);
  background: var(--bg);
}

/*
  The trail on this page is longer than "Home > Search" was: since the client's
  20 August note it names every deal type the panel has ticked and the place
  they are ticked in — "in the navigation bar the user sees always precisely
  which options he has chosen in the main search panel".

  Which brings it up against the banner that reaches up into this row from the
  results column. Above the breakpoint where the banner does that, the row is
  capped at the sidebar's width — the banner's left edge — and stays on one
  line: the row's height is fixed, so a second line would be drawn outside it.
  The last entry is the one that gives way, with an ellipsis; the entries before
  it are the search itself and are what the note is about.
*/

.search-crumb .breadcrumbs {
  flex-wrap: nowrap;
}

/* Only the last entry gives way. Left shrinkable, the entries before it lose
   their separators and their spacing before it loses a single character. */

.search-crumb .breadcrumbs li {
  flex: none;
  white-space: nowrap;
}

.search-crumb .breadcrumbs li:last-child {
  flex: 0 1 auto;
  min-width: 0;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (min-width: 1025px) {
  .search-crumb {
    max-width: var(--filter-w, 320px);
  }
}

/*
  Filters stay in the viewport while results scroll (sticky). Desktop keeps
  sticky — fixed + JS pin caused a heavy jump on reload-while-scrolled.
*/
.filter-panel {
  position: sticky;
  top: 2px;
  align-self: start;
  max-height: calc(100dvh - 2px);
  /* overflow-y:auto alone forces overflow-x:auto too — pick menus then
     invent a sideways scroll and leave blank "empty room" beside the fields. */
  overflow-x: clip;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid #6F006F;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
  z-index: 5;
  /* Thumb only — no gray track / corridor when the panel scrolls.
     Thumb is Previa logo violet #6F006F. */
  scrollbar-width: thin;
  scrollbar-color: #6F006F transparent;
}

.filter-panel::-webkit-scrollbar {
  width: 8px;
}

.filter-panel::-webkit-scrollbar-track {
  background: transparent;
  border: none;
  box-shadow: none;
}

.filter-panel::-webkit-scrollbar-thumb {
  background: #6F006F;
  border-radius: 999px;
  border: none;
  box-shadow: none;
}

.filter-panel::-webkit-scrollbar-thumb:hover {
  background: #6F006F;
}

.filter-panel::-webkit-scrollbar-button,
.filter-panel::-webkit-scrollbar-corner {
  display: none;
  width: 0;
  height: 0;
}

.search-layout.is-collapsed .filter-panel {
  display: none;
}

@media (min-width: 1025px) {
  .search-layout:not(.is-collapsed) > .filter-panel {
    /* Sticky, not fixed: reload-while-scrolled keeps the same pin with no
       JS correction jump. Pull up by the crumb's bottom margin so the panel
       sits 2px under the crumb (same as the old fixed pin) — not a step
       lower under the margin. */
    position: sticky;
    top: 2px;
    width: var(--filter-w, 320px);
    max-height: calc(100dvh - 2px);
    margin-top: calc(2px - var(--sp-2));
  }

  .search-layout:not(.is-collapsed) > .search-results-col {
    grid-column: 2;
  }
}

/*
  Vertical rhythm inside the panel, tightened on the client's 18 August note.

  "The main search menu is very good, just make it more compact, so a bit
  smaller. The first title 'Filters' occupies in up-down scale too much space,
  so move it more up and it's below borderline more up as well. In up-down
  scale move all the titles a bit more up and reduce their line spacing. In
  left-right scale no changes, the width of the menu stays like is."

  So every figure that changes below is a *block* one — padding-block, row gaps,
  the space between a label and its control. Nothing horizontal moves: the side
  paddings stay at --sp-5 and the panel keeps its column width, which is what
  the last sentence asks for.

  20 September: header band half as tall again (8→4), then another 40%
  (4→2). Previa logo violet #6F006F behind "Filters" and on the panel frame.
  Body sections sit tighter under the header. Property-type tiles select with
  a #008000 frame only — no fill change.
*/

.filter-panel__header {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 2px var(--sp-5);
  min-height: 0;
  background: #6F006F;
  color: #fff;
  border-bottom: 1px solid #6F006F;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}

.filter-panel__header .card__title {
  color: #fff;
  line-height: 1.1;
  margin: 0;
}

.filter-panel__header .filter-panel__collapse,
.filter-panel__header .filter-panel__close {
  color: #fff;
}

.filter-panel__header .filter-panel__collapse svg,
.filter-panel__header .filter-panel__close svg {
  color: #fff;
}

.filter-panel__body {
  padding: var(--sp-2) var(--sp-5) var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Label to control, inside this panel only: 8px on every field down a column
   this long adds up to a screenful. Other forms on the site keep --sp-2. */

.filter-panel .field {
  gap: 6px;
}

/*
  The footer holds three controls now — the clear cross, apply, and save search
  — and, under them, whatever the save answered. A column, so the confirmation
  appears where the click was rather than at the top of a panel the reader has
  scrolled away from.
*/

.filter-panel__footer {
  position: sticky;
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* The row itself. Save search takes the space apply gave up when it stopped
   stretching, so the two together still fill the panel's width. */

.filter-panel__actions {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
}

/* Sized to fit all three across a 320px panel; wrapping is the safety net for
   a narrower one or a longer translation. */

.filter-panel__actions .btn--quiet {
  flex: 1;
  min-height: 34px;
  padding: 0 6px;
  font-size: var(--fs-xs);
}

.filter-panel__actions .btn--quiet svg {
  width: 15px;
  height: 15px;
}

/* Empty until something has been saved, and then a line rather than a panel:
   this sits in a footer that is 40px tall the rest of the time. */

#filter-save-slot:empty {
  display: none;
}

/* Collapsible group inside the filter panel. Same compaction as above: the
   group titles move up against what they open, and the rule under each group
   follows its content up. */

.filter-group {
  border-bottom: 1px solid var(--border);
  padding-bottom: var(--sp-2);
}

.filter-group:last-of-type {
  border-bottom: 0;
  padding-bottom: 0;
}

.filter-group__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0 0 4px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}

.filter-group__toggle svg {
  width: 16px;
  height: 16px;
  color: var(--text-muted);
  transition: transform var(--dur) var(--ease);
}

.filter-group__toggle[aria-expanded='false'] svg {
  transform: rotate(-90deg);
}

/*
  No gold (dark --focus-color) keyboard frame on accordion rows or other
  chrome inside the Filters panel — the client asked those selectors gone.
*/

.filter-panel :focus-visible {
  outline: none;
}

.filter-group__content {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* A group with no fold — one field, its own label, no header above it. */

.filter-group--flat {
  padding-top: 0;
}

/*
  Land type ticks — tight rows so a two-line label like "Commercial land"
  barely stacks, and the checkbox sits vertically centred on the text.
*/

.filter-panel .check-grid {
  gap: 1px var(--sp-2);
}

.filter-panel .check-grid .check {
  align-items: center;
  gap: var(--sp-2);
  padding: 0;
  min-height: 0;
}

.filter-panel .check-grid .check input {
  margin: 0;
}

.filter-panel .check-grid .check__text {
  line-height: 1.05;
}

/* Condition + listing flag ticks: same compact stack. */

.filter-condition {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.filter-condition > .field__label {
  margin-bottom: 2px;
}

.filter-condition .check,
.filter-listing-flags .check {
  align-items: center;
  gap: var(--sp-2);
  padding: 0;
  min-height: 0;
}

.filter-condition .check input,
.filter-listing-flags .check input {
  margin: 0;
}

.filter-listing-flags {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/*
  Rooms / bedrooms / bathrooms (and Listed by): same bordered frame as a
  .select — not the borderless results "sort by" chip. Dropdown stays a
  compact fixed panel (content-sized), not stretched to the field width.
*/

.filter-panel .filter-pick {
  display: block;
  width: 100%;
}

.filter-panel .filter-pick .sort-menu__btn {
  width: 100%;
  justify-content: space-between;
  border: 1px solid var(--border);
  background: var(--surface);
  min-height: var(--control-h);
  font-size: var(--fs-base);
  font-weight: 400;
  text-transform: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.filter-panel .filter-pick .sort-menu__btn:hover {
  border-color: var(--border-strong);
  background: var(--surface);
}

.filter-panel .filter-pick .sort-menu__btn:focus,
.filter-panel .filter-pick .sort-menu__btn:focus-visible,
.filter-panel .filter-pick .sort-menu__btn[aria-expanded='true'] {
  border-color: var(--field-focus);
  box-shadow: var(--field-focus-ring);
}

.filter-panel .filter-pick .dropdown {
  /* Same width as the field — long labels ("private sellers only") stay on
     one line. Never wider than the sidebar column. */
  left: 0;
  right: 0;
  min-width: 0;
  width: auto;
  max-width: none;
}

.filter-panel .filter-pick .sort-menu__list {
  min-width: 0;
  width: 100%;
  padding: 2px;
}

.filter-panel .filter-pick .sort-menu__list .menu__item {
  padding: 2px 8px;
  line-height: 1.15;
  min-height: 0;
  white-space: nowrap;
  gap: 6px;
  /* Keep "class B" — do not force the letter to "class b". */
  text-transform: none;
}

/* Add-listing Condition / Energy / Ownership — same compact panel as the
   listings filter energy menu (tight rows, keep "class A" capitalisation). */
.listing-sections .filter-pick {
  display: block;
  width: 100%;
}
.listing-sections .filter-pick .sort-menu__btn {
  text-transform: none;
}
.listing-sections .filter-pick .sort-menu__list {
  padding: 2px;
}
.listing-sections .filter-pick .sort-menu__list .menu__item {
  padding: 2px 8px;
  line-height: 1.15;
  min-height: 0;
  white-space: nowrap;
  gap: 6px;
  text-transform: none;
}

/*
  Building year from/to: no gold focus halo (dark --focus-color), no stepper
  column — plain green field frame only when typing.
*/

.filter-panel .range-pair input[data-stepper='off']:focus,
.filter-panel .range-pair input[data-stepper='off']:focus-visible {
  outline: none;
  border-color: var(--field-focus);
  box-shadow: var(--field-focus-ring);
}

/* --------------------------------------------------------------------------
   Location and radius, on one row

   The client asked for the radius "next to the googlemaps field", which is a
   layout instruction as much as a feature one: under it, the number reads as a
   separate question rather than as the reach of the place above it.

   Grid rather than flex, because the two are not equal — the place takes
   whatever is left and the number takes exactly the width four digits and a
   pair of arrows need. `min-width: 0` on the first column is what stops the
   Location box, which has a placeholder longer than the column, from pushing
   the radius off the edge of a 320px drawer. Below that width there is no room
   for both, and they stack.
   -------------------------------------------------------------------------- */

.filter-place {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 104px;
  gap: var(--sp-2);
  /* Start, not end: the under-field restricted line must not drag Radius down. */
  align-items: start;
}

.filter-place > .location-field {
  min-width: 0;
}

.filter-place__radius .input {
  text-align: center;
}

/* Under-field restricted refusal — sits under Location, pushes Property type down. */
.location-field__restricted {
  margin: var(--sp-2) 0 0;
  max-width: 100%;
}

@media (max-width: 380px) {
  .filter-place {
    grid-template-columns: 1fr;
  }
}

/* Rail button that reopens a collapsed panel. */

/* --------------------------------------------------------------------------
   Features and amenities, in the filter sidebar

   Thirty-four ticks in five titled groups, and a search field above them,
   because "there are quite many of them". The list is capped and scrolls
   inside itself rather than pushing the rest of the sidebar's groups a screen
   and a half down; the search box stays put above it so typing never scrolls
   away from what it is filtering.
   -------------------------------------------------------------------------- */

.amenity-search {
  position: relative;
  display: flex;
  align-items: center;
  margin-bottom: var(--sp-2);
}

.amenity-search > svg {
  position: absolute;
  left: var(--sp-3);
  width: 16px;
  height: 16px;
  color: var(--text-subtle);
  pointer-events: none;
}

.amenity-search__input {
  padding-left: calc(var(--sp-3) * 2 + 16px);
}

/* The platform's own clear button would sit on top of this one. */

.amenity-search__input::-webkit-search-cancel-button {
  display: none;
}

.amenity-search__clear {
  position: absolute;
  right: var(--sp-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--text-muted);
}

.amenity-search__clear:hover {
  background: var(--surface-3);
  color: var(--text);
}

.amenity-search__clear svg {
  width: 14px;
  height: 14px;
}

.amenity-filter {
  max-height: 320px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: var(--sp-2);
}

.amenity-filter__group {
  border: 0;
  padding: 0;
  margin: 0;
}

/* Subtitle (THE PROPERTY, …) sits straight under the search field; groups pack. */

.amenity-filter__group + .amenity-filter__group {
  margin-top: var(--sp-2);
}

.amenity-filter__group[hidden] {
  display: none;
}

.amenity-filter .check[hidden] {
  display: none;
}

.filter-panel .amenity-group__title {
  margin-bottom: 2px;
  line-height: 1.1;
}

.filter-panel .amenity-filter .check {
  align-items: center;
  gap: var(--sp-2);
  padding: 0;
  min-height: 0;
}

.filter-panel .amenity-filter .check input {
  margin: 0;
}

.filter-panel .amenity-filter .check__text {
  line-height: 1.15;
}

.filter-rail {
  display: none;
  position: sticky;
  top: var(--floating-menu-band);
  align-self: start;
  z-index: 5;
}

.search-layout.is-collapsed .filter-rail {
  display: block;
}

/*
  Right-hinged rail, used by the add-listing progress panel. Its grid column
  collapses to zero width, so the button is pinned to that column's right edge
  and allowed to extend leftwards — the mirror of the filter rail, which simply
  overflows rightwards out of its own zero-width column.

  Deliberately no `display` here: the base .filter-rail is display:none and the
  is-collapsed rule turns it on, so setting one on the modifier would leave the
  rail permanently visible.
*/

.filter-rail--right .filter-reopen {
  position: absolute;
  right: 0;
  white-space: nowrap;
}

.filter-reopen {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: 44px;
  padding: 0 var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}

.filter-reopen:hover {
  border-color: var(--border-strong);
  background: var(--surface-2);
}

.filter-reopen svg {
  width: 16px;
  height: 16px;
}

.filter-reopen__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  background: var(--gold);
  color: var(--text-on-gold);
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
}

/* Floating edge tab, always reachable while scrolling on small screens.
   Listings Filters: logo violet + #00FF00 — same pairing as the floating menu
   and the Previa mark — not theme-flipped --navy, which went light-grey in dark
   mode and read as a plain chip. The active-filter count lives on the tag bar
   above the results, not on this button. */

.filter-fab {
  display: none;
  position: fixed;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: calc(var(--z-header) - 1);
  align-items: center;
  gap: var(--sp-2);
  height: 48px;
  padding: 0 var(--sp-4) 0 var(--sp-3);
  background: #6F006F;
  color: #00FF00;
  border-radius: 0 var(--r-md) var(--r-md) 0;
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.filter-fab:hover {
  background: #6F006F;
  color: #00FF00;
}

.filter-fab svg {
  width: 18px;
  height: 18px;
  color: #00FF00;
}

/*
  Map results toolbar "Filters" — same violet / #00FF00 as the edge tab, day
  and dark. No hover repaint, no gold focus ring (dark --focus-color).
*/
.btn--map-filters {
  background: #6F006F;
  border-color: #6F006F;
  color: #00FF00;
  box-shadow: none;
}
.btn--map-filters:hover:not(:disabled),
.btn--map-filters:focus,
.btn--map-filters:focus-visible,
.btn--map-filters:active {
  background: #6F006F;
  border-color: #6F006F;
  color: #00FF00;
  box-shadow: none;
  outline: none;
}
.btn--map-filters svg {
  color: #00FF00;
  stroke: #00FF00;
}
[data-theme='dark'] .btn--map-filters,
[data-theme='dark'] .btn--map-filters:hover:not(:disabled),
[data-theme='dark'] .btn--map-filters:focus,
[data-theme='dark'] .btn--map-filters:focus-visible {
  background: #6F006F;
  border-color: #6F006F;
  color: #00FF00;
  box-shadow: none;
}

/* Collapsed reopen control on the listings rail — same brand colours. */

.search-layout .filter-reopen {
  background: #6F006F;
  border-color: #6F006F;
  color: #00FF00;
  box-shadow: none;
}

.search-layout .filter-reopen:hover {
  background: #6F006F;
  border-color: #6F006F;
  color: #00FF00;
}

.search-layout .filter-reopen svg {
  color: #00FF00;
}

/* Mirrored edge tab for panels that slide off the right-hand side. */

.filter-fab--right {
  left: auto;
  right: 0;
  padding: 0 var(--sp-3) 0 var(--sp-4);
  border-radius: var(--r-md) 0 0 var(--r-md);
}

.filter-panel__close {
  display: none;
}

.filter-backdrop {
  display: none;
}

/* Below ~1024px (roughly the client's 8–11" range) the same panel becomes an
   overlay drawer: closed by default, opened from the fixed edge tab, and laid
   over the page so content never shifts sideways. The form is not re-rendered,
   so every selected filter survives the transition. */

@media (max-width: 1024px) {
  .search-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .search-layout .filter-rail {
    display: none;
  }

  .filter-fab {
    display: inline-flex;
  }

  .filter-panel {
    position: fixed;
    /* Under the crumb trail — same closeness as crumb under header. */
    top: calc(var(--header-h) + var(--crumb-h) + 2px);
    left: 0;
    bottom: 0;
    z-index: calc(var(--z-drawer) + 1);
    width: min(360px, 88vw);
    max-height: calc(100dvh - var(--header-h) - var(--crumb-h) - 2px);
    /* Rounded on the right, top and bottom, at the client's request: the two
       corners that meet the page. The left pair stay square — they are flush
       against the edge of the screen, where a radius would only show a sliver
       of the page behind them. */
    border-radius: 0 var(--r-lg) var(--r-lg) 0;
    border-left: 0;
    box-shadow: var(--shadow-panel);
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--dur-slow) var(--ease), visibility var(--dur-slow);
  }

  .filter-panel.is-open {
    transform: translateX(0);
    visibility: visible;
  }

  /* The sticky header and footer are clipped by the panel's own corners, so
     they follow its radius on the side that has one. */

  .filter-panel__header {
    border-radius: 0 var(--r-lg) 0 0;
  }

  .filter-panel__footer {
    border-radius: 0 0 var(--r-lg) 0;
  }

  .filter-panel__collapse {
    display: none;
  }

  .filter-panel__close {
    display: inline-flex;
  }

  .filter-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: var(--z-drawer);
    background: var(--overlay);
  }
}

/* Property-type radio tiles inside the filter panel. Exactly one is active. */

.filter-fieldset {
  border: 0;
  padding: 0;
  margin: 0;
}

.filter-fieldset > legend {
  padding: 0;
}

.type-check-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-2);
}

.type-check {
  position: relative;
  cursor: pointer;
}

.type-check input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.type-check__box {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  /* One fixed height for every tile — two-line labels tighten into it;
     single-line tiles do not grow. */
  height: 68px;
  padding: 6px 4px;
  box-sizing: border-box;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-2xs);
  font-weight: 500;
  text-align: center;
  line-height: 1.1;
  color: var(--text-muted);
  /* Keep overflow visible so the corner tick stays a full circle. */
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.type-check__box .icon {
  width: 20px;
  height: 20px;
  flex: none;
}

/* No hover brightening — resting colours stay put. */

/* The check badge is the non-colour signal that an option is selected. */

.type-check__tick {
  position: absolute;
  top: -6px;
  right: -6px;
  display: none;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  background: #008000;
  color: #fff;
  border: 2px solid var(--surface);
  border-radius: 50%;
}

.type-check__tick .icon {
  width: 10px;
  height: 10px;
  stroke-width: 3.5;
}

.type-check input:checked + .type-check__box {
  /* Frame only — no fill change. #008000 day and dark. */
  border-color: #008000;
  background: var(--surface);
  color: var(--text-muted);
  font-weight: 500;
  box-shadow: none;
  transform: none;
}

.type-check input:checked + .type-check__box .type-check__tick {
  display: inline-flex;
}

[data-theme='dark'] .type-check input:checked + .type-check__box {
  background: var(--surface);
  color: var(--text-muted);
}

.type-check input:focus-visible + .type-check__box {
  box-shadow: var(--field-focus-ring);
}

.results-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-bottom: var(--sp-2);
}

.results-count {
  font-size: var(--fs-sm);
  color: #008000;
}

.results-count strong {
  color: #008000;
  font-weight: 700;
  font-size: var(--fs-base);
}

.results-count .subtle {
  color: #008000;
}

.view-switch {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--surface-3);
  border-radius: var(--r-md);
}

.view-switch__btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 var(--sp-3);
  border-radius: 7px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.view-switch__btn:hover {
  color: var(--text);
}

.view-switch__btn[aria-current='true'] {
  background: var(--surface);
  color: var(--navy);
  box-shadow: var(--shadow-xs);
}

[data-theme='dark'] .view-switch__btn[aria-current='true'] {
  color: var(--text);
}

.view-switch__btn svg {
  width: 15px;
  height: 15px;
}

.active-filters {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-bottom: var(--sp-3);
}

.active-filters__label {
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: lowercase;
  color: #008000;
}

[data-theme='dark'] .active-filters__label {
  color: #008000;
}

/* ==========================================================================
   Dashboard / admin shell
   ========================================================================== */

.shell {
  display: grid;
  grid-template-columns: 264px minmax(0, 1fr);
  align-items: start;
  gap: var(--sp-7);
}

@media (max-width: 1080px) {
  .shell {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-5);
  }
}

.side-nav {
  position: sticky;
  top: var(--floating-menu-band);
  align-self: start;
  max-height: calc(100dvh - var(--floating-menu-band) - var(--sp-4));
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  z-index: 2;
}

/* Account menu: violet header flush to the top, logo-violet frame.
   .shell--nav .side-nav beats the generic .side-nav border (var(--border)). */
.shell--nav .side-nav:has(> .side-nav__header) {
  padding-top: 0;
  border: 1px solid #6F006F;
}

/*
  Admin rail: sticky under a 12px gap — stays in the grid track so a refresh
  cannot jump left/width when Alpine later measures a fixed pin.
*/

@media (min-width: 1081px) {
  .admin .shell--nav > .side-nav {
    position: sticky;
    top: 12px;
    max-height: calc(100dvh - 12px);
    z-index: 5;
  }
}

@media (max-width: 1080px) {
  .side-nav {
    position: static;
    max-height: none;
    display: flex;
    gap: var(--sp-2);
    overflow-x: auto;
    scrollbar-width: none;
  }

  .side-nav::-webkit-scrollbar {
    display: none;
  }

  .side-nav__group {
    display: flex;
    gap: var(--sp-2);
    flex: none;
  }

  .side-nav__label {
    display: none;
  }

  .side-nav__link {
    white-space: nowrap;
  }
}

/* --------------------------------------------------------------------------
   Collapsible account menu — .shell--nav

   The same two-state behaviour as the search page's filter panel, which is
   what the client asked for: pushed off to the left with a rail left behind on
   wide screens, an overlay drawer on narrow ones. It is opt-in through the
   modifier so the admin shell, which shares .shell and .side-nav, is untouched.

   The rules below win over the generic .side-nav block above by specificity,
   including inside its 1080px media query — .shell--nav .side-nav is two
   classes to one, and both media queries end at the same breakpoint.
   -------------------------------------------------------------------------- */

.shell--nav {
  transition: none;
}

.shell--nav.is-collapsed {
  grid-template-columns: 0 minmax(0, 1fr);
  gap: 0;
}

.shell--nav.is-collapsed .side-nav {
  display: none;
}

/* The panel's own header, carrying the collapse and close buttons. Sticky, so
   the way out stays on screen however far the menu is scrolled. */

.side-nav__header {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  /* Span the side padding only — top padding is already 0 when this header
     is present, so the violet band meets the panel's top corners. */
  margin: 0 calc(var(--sp-4) * -1);
  padding: 0 var(--sp-4);
  min-height: 0;
  background: #6F006F;
  color: #fff;
  border-bottom: 1px solid #6F006F;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}

.side-nav__title {
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.1;
  margin: 0;
  color: #fff;
}

.side-nav__header .filter-panel__collapse,
.side-nav__header .filter-panel__close {
  height: 28px;
  min-width: 28px;
  padding: 0;
  color: #fff;
}

.side-nav__header .filter-panel__collapse:hover,
.side-nav__header .filter-panel__close:hover {
  background: color-mix(in srgb, #fff 14%, transparent);
  color: #fff;
}

.side-nav__header .filter-panel__collapse svg,
.side-nav__header .filter-panel__close svg {
  width: 16px;
  height: 16px;
  color: #fff;
}

/*
  The way out, with no bar under it.

  The administration rail lost its "Admin" heading on 31 August, and what
  stayed behind was the empty bar it had stood on. The client, 2 September:
  "you removed this 'Admin' text but the pointless menu overlay stayd there,
  you see the menu text is hiding under it, remove this thing from there. All
  this menu field belongs to the section titles there from top till down. Just
  the closing arrow stays there to close the menu."

  Both halves of that: the row keeps no height, no background, no rule under it
  — the first section title starts at the top of the panel — and the button
  alone is left, pinned to the top right corner. The account rail is untouched
  and keeps its own header, because it still has a title to put on one.
*/

.side-nav__toggle {
  /* Sticky at the top of the panel's content, which is where the row already
     sits unscrolled: the button never jumps on the first scroll, it simply
     stops moving. */
  position: sticky;
  top: var(--sp-4);
  z-index: 2;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  /* No height and no margins. The row is a hook for one button, not a band
     across the panel, and the groups below lay out as though it were absent —
     the button hangs out of it. */
  height: 0;
  /* Only the button takes the clicks: the empty width beside it is menu, and a
     full-width invisible row lying across the first entry would swallow it. */
  pointer-events: none;
}

.side-nav__toggle .icon-btn {
  pointer-events: auto;
  /* Opaque, because entries now scroll underneath rather than under a bar: a
     transparent button would show the menu text through its own glyph. */
  background: var(--surface);
}

.shell--nav .filter-rail {
  display: none;
}

.shell--nav.is-collapsed .filter-rail {
  display: block;
}

/* Account Menu reopen — same logo violet / #00FF00 as listings Filters. */
.shell--nav .filter-reopen {
  background: #6F006F;
  border-color: #6F006F;
  color: #00FF00;
  box-shadow: none;
}

.shell--nav .filter-reopen:hover {
  background: #6F006F;
  border-color: #6F006F;
  color: #00FF00;
}

.shell--nav .filter-reopen svg {
  color: #00FF00;
}

@media (max-width: 1080px) {
  .shell--nav {
    grid-template-columns: minmax(0, 1fr);
  }

  .shell--nav .filter-rail {
    display: none;
  }

  .shell--nav .filter-fab {
    display: inline-flex;
  }

  /* Back to a vertical list: below this width the menu is an overlay panel,
     not a strip above the content, so the horizontal-scroll treatment that the
     generic .side-nav takes here does not apply. */

  .shell--nav .side-nav {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: calc(var(--z-drawer) + 1);
    display: block;
    width: min(320px, 86vw);
    max-height: none;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: 0 var(--r-lg) var(--r-lg) 0;
    border-left: 0;
    box-shadow: var(--shadow-panel);
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--dur-slow) var(--ease), visibility var(--dur-slow);
  }

  .shell--nav .side-nav:has(> .side-nav__header) {
    border: 1px solid #6F006F;
    border-left: 0;
  }

  .shell--nav .side-nav.is-open {
    transform: translateX(0);
    visibility: visible;
  }

  /* The shared backdrop only switches on below 1024px for the filter panel;
     this menu overlays from 1080px down, so it needs the extra 56px band. */

  .shell--nav .filter-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: var(--z-drawer);
    background: var(--overlay);
  }

  .shell--nav .side-nav__group {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  .shell--nav .side-nav__label {
    display: block;
  }

  .shell--nav .side-nav__header {
    border-radius: 0 var(--r-lg) 0 0;
  }

  .shell--nav .filter-panel__collapse {
    display: none;
  }

  .shell--nav .filter-panel__close {
    display: inline-flex;
  }
}

/*
  Group rhythm. The client asked for the sections to sit closer together — the
  32px that used to separate them made a nine-item menu scroll on a laptop.
*/

.side-nav__group {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.side-nav__group + .side-nav__group {
  margin-top: var(--sp-3);
}

.side-nav__label {
  padding: var(--sp-2) var(--sp-3) 2px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
}

.side-nav__link {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text-muted);
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.side-nav__link:hover {
  background: var(--surface-3);
  color: var(--text);
}

.side-nav__link[aria-current='page'] {
  background: var(--navy-tint);
  color: var(--navy);
  font-weight: 600;
}

[data-theme='dark'] .side-nav__link[aria-current='page'] {
  color: var(--text);
  background: var(--surface-3);
}

.side-nav__link svg {
  width: 17px;
  height: 17px;
  flex: none;
}

.side-nav__badge {
  margin-left: auto;
  padding: 1px 7px;
  background: var(--gold);
  color: var(--text-on-gold);
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   Section directly under the hero

   The hero already ends in generous space, so a full --section-y on top of it
   left an empty band before the first listing. Roughly half brings the
   featured block up without letting it crowd the search panel.
   -------------------------------------------------------------------------- */

.section--after-hero {
  padding-top: calc(var(--section-y) * 0.5);
  /* Half-step under the cards so Featured → Recently added matches
     banner → Featured (0.25 here + 0.25 on the follower = 0.5). */
  padding-bottom: calc(var(--section-y) * 0.25);
}

/* Homepage strips that follow another strip (Recent, Brokers, Agencies). */
.section--home-follow {
  padding-top: calc(var(--section-y) * 0.25);
  padding-bottom: calc(var(--section-y) * 0.25);
}

/* No heading override any more. The default is 20px for every section on the
   page, which is what "the distance gaps between the blocks would be the same"
   asks for — a first block tucked closer to its cards than the eight below it
   is the mismatch, not the fix. */

/*
  Compact heading block. The default gap suits headings that stand alone; an
  eyebrow, title and one line of subtitle read better tucked together, and the
  title's own line-height already provides most of the separation.
*/

.section__head-text--tight {
  gap: 0;
}

.section__head-text--tight .section__title {
  line-height: 1.15;
  margin-top: 2px;
}

.section__head-text--tight .section__subtitle {
  margin-top: 2px;
  line-height: var(--lh-tight);
}

.section__head-text--tight .overline {
  margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   Account button

   Rounded rectangle, initials only. It shares the 40px height of the icon
   buttons either side of it so the header row stays on one baseline, and it
   drops the chevron the client asked to remove — aria-expanded still tells a
   screen reader that it opens a menu.
   -------------------------------------------------------------------------- */

.user-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  min-width: 44px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

.user-btn:hover {
  background: var(--surface-3);
  border-color: var(--border-strong);
}

.user-btn:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 2px;
}

.user-btn__initials {
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
}

/* ==========================================================================
   The back office rail, made compact
   ==========================================================================

   The client, 26 August: "the admin menu, reduce the line spacing there and
   make it more compact together."

   Nineteen entries in four groups over two headings' worth of vertical space
   meant the System group was below the fold on a laptop and the two operations
   at the foot could not be seen at all. Only the rail is tightened — the
   account navigation on the public side uses the same classes and is a much
   shorter list, so it is scoped to .admin.
   ========================================================================== */

/*
  The collapsed rail keeps a column of its own here.

  .shell--nav.is-collapsed takes its sidebar column to zero and lets the reopen
  button overflow rightwards, which is what the search page and the account
  screens do. It works there because what follows is a toolbar with room in it.
  It does not work on a back-office screen: the first thing in the content
  column is a count or a card heading, and the button lands on top of the words
  — "16 banners configured" reading as "…nfigured" behind a floating chip.

  auto rather than a fixed width, so the column is exactly as wide as the button
  and nothing has to be kept in step with its padding. Scoped to .admin for the
  same reason the rail's compaction below is: the other two layouts were not
  asked to change and are not changed.
*/

.admin .shell--nav.is-collapsed {
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-5);
}

.admin .side-nav__link {
  padding-block: 2px;
  gap: var(--sp-2);
  line-height: 1.15;
}

.admin .side-nav__link svg {
  width: 15px;
  height: 15px;
}

/*
  No golden focus frame on admin rail links. File manager / DB manager open in
  a new tab and leave :focus-visible painted in gold on the row behind.
*/

.admin .side-nav__link:focus,
.admin .side-nav__link:focus-visible {
  outline: none;
  box-shadow: none;
}

.admin .side-nav__label {
  padding-block: var(--sp-1) 0;
}

.admin .side-nav__group + .side-nav__group {
  margin-top: var(--sp-1);
}

.admin .side-nav__fold {
  padding-top: var(--sp-1);
}

.admin .side-nav__group--ops {
  gap: var(--sp-1);
  padding-top: var(--sp-2);
  padding-bottom: var(--sp-1);
  margin-top: var(--sp-1);
}

/* Cache and Web app restart: full-width, stacked, at the foot of the rail. */

.side-nav__group--ops {
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-2);
  margin-top: var(--sp-2);
}

.side-nav__group--ops form {
  display: contents;
}

.side-nav__op {
  width: 100%;
  justify-content: center;
  gap: var(--sp-2);
  background: #b30000 !important;
  color: #ffffff !important;
  border-color: #b30000 !important;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 700;
}
.side-nav__op svg {
  width: 15px;
  height: 15px;
  flex: none;
}
.side-nav__op:hover:not(:disabled),
.side-nav__op:focus {
  background: #b30000 !important;
  color: #ffffff !important;
}
[data-theme='dark'] .side-nav__op,
[data-theme='dark'] .side-nav__op:hover:not(:disabled) {
  color: #d7d7d7 !important;
}
.side-nav__op:disabled {
  opacity: 0.65;
  cursor: wait;
}

.admin-restart-wrap {
  display: block;
  width: 100%;
  position: relative;
  overflow: visible;
}
.admin-restart-wrap--inline {
  width: auto;
  display: inline-block;
  align-self: flex-start;
}
/* Yellow build stamp — hidden until the pointer is on WEB APP RESTART. */
.admin-restart-build-tip {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 8px);
  z-index: 40;
  width: 100%;
  box-sizing: border-box;
  padding: 0.4rem 0.35rem;
  border-radius: 4px;
  background: #facc15;
  color: #000;
  font-size: 0.62rem;
  font-weight: 900;
  -webkit-text-stroke: 0.45px #000;
  paint-order: stroke fill;
  line-height: 1.3;
  letter-spacing: 0.01em;
  text-transform: none;
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}
.admin-restart-build-tip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  margin-left: -6px;
  border: 6px solid transparent;
  border-top-color: #facc15;
}
.admin-restart-wrap:hover .admin-restart-build-tip,
.js-admin-restart:hover + .admin-restart-build-tip,
.js-admin-restart:focus + .admin-restart-build-tip {
  display: block;
}
.admin-restart-wrap--inline .admin-restart-build-tip {
  min-width: 11rem;
  white-space: nowrap;
}
.admin-restart-progress {
  display: block;
  width: 100%;
  margin: 0.4rem 0 0;
}
.admin-restart-progress[hidden] {
  display: none !important;
}
.admin-restart-progress__track {
  display: block;
  width: 100%;
  height: 6px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.12);
  overflow: hidden;
}
[data-theme='dark'] .admin-restart-progress__track {
  background: rgba(255, 255, 255, 0.12);
}
.admin-restart-progress__bar {
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: #008000 !important;
  transition: width 0.35s linear;
}
.admin-restart-progress__label {
  display: block;
  width: 100%;
  margin-top: 0.28rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: lowercase;
  color: #008000 !important;
  line-height: 1.2;
  text-align: center;
}

/* SweetAlert2's show/hide (swal2-show 0.3s, swal2-hide 0.15s, i-mark 0.8s). */
@keyframes previa-confirm-show {
  0% { transform: scale(0.7); }
  45% { transform: scale(1.05); }
  80% { transform: scale(0.95); }
  100% { transform: scale(1); }
}
@keyframes previa-confirm-hide {
  0% { transform: scale(1); opacity: 1; }
  100% { transform: scale(0.5); opacity: 0; }
}
@keyframes previa-confirm-bang {
  0% { transform: rotateZ(45deg); opacity: 0; }
  25% { transform: rotateZ(-25deg); opacity: 0.4; }
  50% { transform: rotateZ(15deg); opacity: 0.8; }
  75% { transform: rotateZ(-5deg); opacity: 1; }
  100% { transform: rotateX(0deg); opacity: 1; }
}

.previa-confirm {
  position: fixed;
  inset: 0;
  z-index: 2147483000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.625em;
  background: rgba(0, 0, 0, 0);
  transition: background-color 0.15s;
}
.previa-confirm.previa-confirm--in {
  background: rgba(0, 0, 0, 0.4);
}
.previa-confirm[hidden] {
  display: none !important;
}
.previa-confirm__box {
  width: min(32em, 100%);
  background: #f5f5f5;
  color: #1f2937;
  border-radius: 0.3125rem;
  padding: 0 0 1.25em;
  text-align: center;
  box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
}
.previa-confirm--in .previa-confirm__box {
  animation: previa-confirm-show 0.3s;
}
.previa-confirm--out .previa-confirm__box {
  animation: previa-confirm-hide 0.15s forwards;
}
[data-theme='dark'] .previa-confirm__box {
  background: #191c24;
  color: #fff;
}
.previa-confirm__icon {
  position: relative;
  box-sizing: content-box;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 5em;
  height: 5em;
  margin: 2.5em auto 0.6em;
  border: 0.25em solid #ffc107;
  border-radius: 50%;
  color: #ffc107;
  line-height: 5em;
  background: transparent;
  user-select: none;
}
.previa-confirm__bang {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 3.75em;
  font-weight: 400;
  line-height: 1;
  font-family: inherit;
}
.previa-confirm--in .previa-confirm__bang {
  animation: previa-confirm-bang 0.8s;
}
@media (prefers-reduced-motion: reduce) {
  .previa-confirm {
    transition: none;
  }
  .previa-confirm--in .previa-confirm__box,
  .previa-confirm--out .previa-confirm__box,
  .previa-confirm--in .previa-confirm__bang {
    animation: none;
  }
}
.previa-confirm__title {
  margin: 0;
  padding: 0.8em 1em 0;
  font-size: 1.875em;
  font-weight: 600;
  line-height: 1.2;
  color: inherit;
}
.previa-confirm__text {
  margin: 0;
  padding: 1em 1.6em 0.3em;
  font-size: 1.125em;
  font-weight: 400;
  line-height: 1.5;
}
.previa-confirm__actions {
  display: flex;
  justify-content: center;
  gap: 0.625em;
  padding: 1.25em 1em 0;
}
.previa-confirm__no,
.previa-confirm__yes {
  min-width: 5.5rem;
  padding: 0.625em 1.75em;
  border: 0;
  border-radius: 0.25rem;
  font-size: 1em;
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  color: #fff;
}
.previa-confirm__no {
  background: #dc3545;
}
.previa-confirm__yes {
  background: #28a745;
}

/*
  Sections that fold to their title.

  The client, 15 September: "can you pls do that can close these menus so that
  only the title is to see and the content comes when clik the title?"

  The chevron's button is stretched over the whole title row, so the words are
  the control and not only the arrow. On the first section the rail's own
  collapse arrow hangs over the right end of that row, and keeps its clicks:
  .side-nav__toggle sits above it. A folded section keeps its title and loses
  its entries; the chevron turns to point at where they went.
*/

.side-nav__head {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

/* The title carries more padding above its words than below, so the chevron is
   centred on the words rather than on the row: it takes the row's height and
   the same difference as padding on top. */

.side-nav__fold {
  display: inline-flex;
  align-self: stretch;
  align-items: center;
  justify-content: center;
  padding-top: calc(var(--sp-2) - 1px);
  color: var(--text-subtle);
  border-radius: var(--r-sm);
}

.side-nav__fold::after {
  content: '';
  position: absolute;
  inset: 0;
}

.side-nav__fold svg {
  width: 12px;
  height: 12px;
  transition: transform var(--dur-fast) var(--ease);
}

.side-nav__fold:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 2px;
}

/* Admin rail + dashboard Website content: no golden focus pill on fold arrows. */
.admin .side-nav__fold:focus,
.admin .side-nav__fold:focus-visible,
.card .side-nav__fold:focus,
.card .side-nav__fold:focus-visible {
  outline: none;
  box-shadow: none;
}

.side-nav__head:hover .side-nav__label,
.side-nav__head:hover .side-nav__fold {
  color: var(--text);
}

.side-nav__items {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.side-nav__group.is-folded .side-nav__fold svg {
  transform: rotate(-90deg);
}

.side-nav__group.is-folded .side-nav__items {
  display: none;
}

html.admin-fold-overview .side-nav__group[data-fold="overview"] .side-nav__items,
html.admin-fold-content .side-nav__group[data-fold="content"] .side-nav__items,
html.admin-fold-people .side-nav__group[data-fold="people"] .side-nav__items,
html.admin-fold-commerce .side-nav__group[data-fold="commerce"] .side-nav__items,
html.admin-fold-settings .side-nav__group[data-fold="settings"] .side-nav__items,
html.admin-fold-system .side-nav__group[data-fold="system"] .side-nav__items {
  display: none;
}
html.admin-fold-overview .side-nav__group[data-fold="overview"] .side-nav__fold svg,
html.admin-fold-content .side-nav__group[data-fold="content"] .side-nav__fold svg,
html.admin-fold-people .side-nav__group[data-fold="people"] .side-nav__fold svg,
html.admin-fold-commerce .side-nav__group[data-fold="commerce"] .side-nav__fold svg,
html.admin-fold-settings .side-nav__group[data-fold="settings"] .side-nav__fold svg,
html.admin-fold-system .side-nav__group[data-fold="system"] .side-nav__fold svg {
  transform: rotate(-90deg);
}

html.admin-nav-collapsed .admin .shell--nav {
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-5);
  transition: none;
}
html.admin-nav-collapsed .admin .shell--nav .side-nav {
  display: none;
}
html.admin-nav-collapsed .admin .shell--nav .filter-rail {
  display: block;
}

/* ==========================================================================
   Previa — page and feature styles
   Logo, hero, homepage sections, map screens, wizard, dashboard, admin.
   ========================================================================== */

[x-cloak] {
  display: none !important;
}

/* ==========================================================================
   Logo
   ========================================================================== */

/*
  Lockup sizes.

  A fifth again on top of the 50% granted the day before: "then make this logo
  more 20 % bigger. The header part do not make bigger any more, the header
  size stays. But inside the current header there is more room, so can make the
  logo a bit bigger."

  So the header keeps --header-h and only the lockup grows: 60→72 in the
  header, 51→61 compact, 78→94 large, and each wordmark by the same factor, the
  whole lockup scaling together the way it has each time.

  72px is exactly the header's height, which sounds like an overflow and is
  not: the drawing occupies about 70% of its viewBox — the discs overhang the
  planet, so the mark is inset from the box on every side — leaving some 50px
  of ink inside a 72px row. That is the room the client could see going spare.
*/

.logo {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  height: var(--header-h);
}

.logo__mark {
  flex: none;
  width: 72px;
  height: 72px;
  max-width: none;
}

.logo__word {
  font-family: var(--font-display);
  font-size: 44px;
  font-weight: 400;
  letter-spacing: -0.015em;
  color: var(--navy);
  line-height: 1;
}

[data-theme='dark'] .logo__word {
  color: var(--text);
}

/*
  The mark carries its own lighting and its own colours — see the note in
  components/logo.html. It is one lit object, so it does not repaint per theme:
  the yellow sea and green land hold their own contrast, which is what lets the
  same drawing sit on a white header, on the dark theme's surface and over
  photography. Only the wordmark below follows the theme.
*/

.logo--sm .logo__mark {
  width: 61px;
  height: 61px;
  max-width: none;
}

.logo--sm .logo__word {
  font-size: 37px;
}

.logo--lg .logo__mark {
  width: 94px;
  height: 94px;
  max-width: none;
}

.logo--lg .logo__word {
  font-size: 58px;
}

/* Over photography (the auth screens): only the wordmark needs to change, and
   it stays white in both themes because what is behind it is a photograph, not
   a themed surface. */

.logo--mono .logo__word {
  color: #fff;
}

/*
  Below 480px the lockup shares the bar with a burger and the account button and
  cannot take the full increase — but it still grows, by as much as the row has
  to give: 44→53 and 27→32, against a header that drops to 64px there.
*/

@media (max-width: 480px) {
  .logo__word {
    font-size: 32px;
  }

  .logo__mark {
    width: 53px;
    height: 53px;
    max-width: none;
  }
}

/* ==========================================================================
   Header extras
   ========================================================================== */

.dropdown {
  position: relative;
}

.dropdown > .menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: calc(var(--z-header) + 5);
  max-height: 70vh;
  overflow-y: auto;
}

.dropdown--right > .menu {
  left: auto;
  right: 0;
}

/*
  Homepage: hide the footer until the below-banner second load finishes, so
  first paint is truly header + hero (SDW-style). #home-below drops aria-busy
  when /home/below has been injected. body.home-below-ready restores display
  in case lean critical CSS left an older unconditional hide in place.
*/
body:has(#home-below[aria-busy="true"]) .page__foot {
  display: none;
}
body.home-below-ready .page__foot {
  display: block;
}

.icon-btn {
  position: relative;
}

.icon-btn__dot {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 8px;
  height: 8px;
  background: var(--gold);
  border: 2px solid var(--surface);
  border-radius: 50%;
}

.header__cta svg {
  width: 16px;
  height: 16px;
}

/* Same #8B008B as the header logo tile. */
.header__logo .logo__word {
  color: #8B008B;
}
[data-theme='dark'] .header__logo .logo__word {
  color: #8B008B;
}

/* Same #8B008B as the header logo tile (logo.html / favicon). */
.header__cta {
  background: #8B008B;
  border-color: #8B008B;
  color: #fff;
}
.header__cta:hover:not(:disabled) {
  background: #6F006F;
  border-color: #6F006F;
  color: #fff;
}

/*
  Below 420px the CTA used to hide its own label, which left a 26px button
  containing nothing at all — invisible to a screen reader and meaningless to
  everyone else. It is dropped entirely instead: the drawer's footer carries
  "add listing", and the burger that opens it is immediately beside this.
*/

@media (max-width: 420px) {
  .header__cta {
    display: none;
  }
}

.drawer__countries {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.country-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 38px;
  padding: 0 var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 500;
}

.country-pill.is-active {
  background: var(--navy-tint);
  border-color: var(--slate);
  font-weight: 600;
}

/* Drawer transition classes (used by x-transition:enter/leave) */

.drawer-enter, .drawer-leave {
  transition: transform var(--dur-slow) var(--ease);
}

.drawer-enter-start, .drawer-leave-end {
  transform: translateX(100%);
}

.drawer--left.drawer-enter-start, .drawer--left.drawer-leave-end {
  transform: translateX(-100%);
}

.footer__contact-line {
  margin-top: var(--sp-5);
  line-height: 1.8;
  font-size: var(--fs-xs);
}

.footer__contact-line a:hover {
  color: #8B008B;
}

/* ==========================================================================
   Country banner
   ========================================================================== */

.cbanner {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
  background: var(--surface);
}

.cbanner--navy {
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
}

.cbanner--slate {
  background: var(--navy-tint);
  border-color: var(--border);
}

.cbanner--gold {
  background: var(--gold-tint);
  border-color: var(--gold-border);
}

[data-theme='dark'] .cbanner--navy {
  background: var(--surface-3);
  border-color: var(--border);
  color: var(--text);
}

.cbanner__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, 0.14);
}

.cbanner--slate .cbanner__icon,
.cbanner--gold .cbanner__icon {
  background: rgba(16, 42, 67, 0.08);
}

.cbanner__icon svg {
  width: 22px;
  height: 22px;
}

.cbanner__text {
  flex: 1;
  min-width: 0;
}

.cbanner__headline {
  font-size: var(--fs-base);
  font-weight: 600;
  margin-bottom: 2px;
}

.cbanner__body {
  font-size: var(--fs-sm);
  opacity: 0.86;
}

.cbanner__sponsor {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.7;
  margin-bottom: 4px;
}

/*
  The advertiser's button, on its own line under the copy.

  The client, 31 August: "the button comes to wrong place, make it left side,
  from the center line a bit down. The text and button location in listing page
  banner make the same as in frontpage banner."

  It was a sibling of .cbanner__text in a centred flex row, so it sat at the far
  right of the strip on the same line as the sponsor. It is inside the column
  now, and this is the only rule it needs: the same margin .hero__cta uses, so
  the gap between the copy and the button is the one he already approved on the
  front page.
*/

.cbanner__cta {
  margin-top: var(--sp-5);
}

.cbanner--navy .btn--secondary {
  background: transparent;
  color: #fff;
  border-color: rgba(255,255,255,0.5);
}

.cbanner--navy .btn--secondary:hover {
  background: rgba(255,255,255,0.12);
}

/*
  A banner that is a picture.

  The client, 28 August, on the listings page: "the banner in the listings page,
  for this the same system as for the frontpage banner" — and for the front page,
  "there is no filter, the image will be displayed like it is". So the strip
  drops its surface and its border and becomes the artwork, scaled the way a
  listing photograph is scaled and with nothing laid over it.

  The theme classes are not applied alongside this one: a colour underneath a
  picture that covers the box is a colour nobody sees, and the two backgrounds
  fighting is what made the first attempt at this look like a bordered card with
  a photograph inside it.
*/

.cbanner--art {
  position: relative;
  overflow: hidden;
  background: none;
  border-color: transparent;
  /* The shadow that used to be on these letters has gone with the hero's — see
     .hero__media--art above. Same complaint, same answer: the banner's own
     colour field, which arrives as --banner-ink and falls back to the white
     this has always drawn. */
  color: var(--banner-ink, #fff);
}

/*
  The whole picture, never a crop of it.

  The client, 30 August: "make that the entered banner always fits into this
  banner area. So if the banner is too big, then it will be made smaller to fit
  there, so that all the banner is to see, no cutting."

  contain is that sentence. The box is a fixed size — one height for every
  market, which he asked for on 29 August — so an upload whose shape is not the
  box's shape has to give somewhere, and it now gives at the edges of the box
  instead of at the edges of the artwork.

  What fills the edges is the same picture, blown up and blurred behind the one
  that fits. A flat band would be the other answer and is the wrong one here:
  "if the user uploads the logo then remove the white borders there, make it
  nice, all the box is filled with banner only!" — 25 August, the same
  objection. Nothing is cut from what a sponsor uploaded, and the box is still
  filled. At the recommended dimensions the two shapes agree and the backdrop
  is never seen.
*/

.cbanner__art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/*
  The three ways a banner may draw its picture, chosen on the banner itself.

  The client, 31 August: "in each banner make the options 'image will be fitted
  into the field' (this is what we have now) and 'image will not be fitted into
  field' (this is what we had earlier). As in some cases the earlier version was
  better." And an hour later: "and third option that the system makes the image
  to fit with the banner field, so if needed, stretched it bigger, if needed
  then makes it smaller."

  Three answers to one question — what gives when the picture's shape and the
  area's shape disagree — and he is right that none of them is correct for every
  upload. A photograph chosen to be cropped wants fill; a logo or a banner with
  writing on it wants fit; something drawn to a shape close to the area's wants
  stretch.

  On the picture rather than on the box around it, because the homepage hero and
  this strip are two completely different boxes drawing the same three choices,
  and the picture is what they have in common. models.Banner.FitClass writes the
  class; the selectors below are doubled where they have to beat a box's own
  rule on the same element.
*/

.cbanner__art.banner-fit--fill {
  object-fit: cover;
}

.cbanner__art.banner-fit--fit {
  object-fit: contain;
}

.cbanner__art.banner-fit--stretch {
  object-fit: fill;
}

/* Behind the fitted picture, and it carries the .cbanner__art class as well so
   that the rule below leaves it alone — it is artwork, not text on top of it.
   The scale is what keeps a blurred edge from showing as a seam. */

.cbanner__art--fill {
  object-fit: cover;
  filter: blur(28px) saturate(1.15) brightness(0.62);
  transform: scale(1.12);
}

/* Everything that is not the picture sits on it. */

.cbanner--art > :not(.cbanner__art) {
  position: relative;
  z-index: 1;
}

.cbanner--art .btn--secondary {
  background: rgba(0,0,0,0.35);
  color: #fff;
  border-color: rgba(255,255,255,0.6);
}

.cbanner--art .btn--secondary:hover {
  background: rgba(0,0,0,0.5);
}

@media (max-width: 780px) {
  .cbanner {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-3);
  }

  .cbanner .btn {
    width: 100%;
  }
}

/* ==========================================================================
   Hero
   ========================================================================== */

/*
  isolation keeps the hero's own layers — the photograph at -2, the market
  picker at 2 — inside it. z-index is what puts the whole hero above the
  sections that follow it in the document.

  Without that number the hero was a stacking context with z-index: auto, which
  paints in document order alongside the positioned cards below it: an open
  property-type or deal-type panel hangs over the Featured row, and every card
  in that row painted over it. The client reported the panel "staying hidden"
  behind the listings. 3 clears the sections below while staying well under the
  header at 100.
*/

.hero {
  position: relative;
  isolation: isolate;
  z-index: 3;
}

/*
  Flush hero.

  The header is no longer sticky, so the hero can simply start where the
  header ends — but the client wanted the image to run *under* the bar rather
  than beginning below it. Pulling it up by the header height and padding the
  content back down does that without the header having to become transparent
  on every page.
*/

.hero--flush {
  margin-top: calc(var(--header-h) * -1);
  padding-top: var(--header-h);
  /* Cut to the page width, like the header it runs up under: "in the frontpage
     make the width of the headet and banner the same as the content in the
     listings page, and banner down corners cut round". The two are the same
     --page-width, so the bar's edges and the photograph's are one line.

     No overflow: hidden here, deliberately. The rounding is drawn on
     .hero__media below, which is the layer that actually shows a colour;
     clipping the hero itself would also clip the deal-type and property-type
     panels that open downwards out of the search box. */
  width: var(--page-width);
  margin-inline: auto;
}

/* Market picker sits top-right of the hero, above the search panel. */

.hero__market {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: flex-end;
  padding-top: var(--sp-4);
}

/* The sponsored strip overlays the hero rather than displacing it. */

.hero__banner-wrap {
  position: relative;
  z-index: 2;
  padding-top: var(--sp-4);
}

/* With a banner present the picker tucks under it instead of adding a second
   block of vertical space. */

.hero__banner-wrap + .hero__market {
  padding-top: var(--sp-3);
}

/* The photograph, and with it the banner's bottom corners: "banner down corners
   cut round". It already clips its own image, so the radius is all that is
   needed. The top corners stay square — the header sits on them. */

/*
  The box is sized twice over, on purpose.

  The client, 31 August, with two photographs of the same phone: "in chrome
  browser all good, but in samsung browser the frontpage banner is not there,
  so need to make that the website works with all browsers."

  This rule is why. The box had no width and no height of its own — it was
  sized entirely by `inset: 0`, and both of its children are absolutely
  positioned too, so they lend it nothing to shrink-to-fit around. On an engine
  that does not know the `inset` shorthand (it is Chromium 87, so Samsung
  Internet 14) all four offsets stay `auto`, the box computes to 0x0, and
  `overflow: hidden` clips the artwork away without a trace: what is left is
  the page's own cream showing through a hero that still has its height from
  .hero__inner below. Exactly the screenshot he sent.

  So: the long-hand offsets, which have worked since CSS 2.1, then the
  shorthand for anyone reading it, then an explicit width and height that do
  not depend on either. Three ways to say the same thing, because the failure
  mode of saying it once is a blank front page on a browser we do not test on.
  The same treatment is applied to every layer inside it below.

  The listings banner never had this bug: .cbanner__art carries its own
  width:100% and height:100%, which is what this now does too.
*/

.hero__media {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  overflow: hidden;
  border-radius: 0 0 var(--r-xl) var(--r-xl);
}

.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
  A sponsor's banner fits; Previa's own photograph fills.

  Same client note as .cbanner__art above — "so that all the banner is to see,
  no cutting" — and the same pair of layers: the artwork drawn whole, and a
  blurred copy of it behind to fill whatever the shapes leave over. The stock
  photograph above keeps cover: it is scenery chosen to be cropped, with the
  site's own headline over it, and nobody paid for its corners.
*/

.hero__media--art img {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  inset: 0;
  width: 100%;
  height: 100%;
}

.hero__media--art .hero__media-art {
  object-fit: contain;
}

/* The banner's own choice of the three — see .banner-fit--* above. Written
   against .hero__media--art as well, because the rule it has to beat is. */

.hero__media--art .hero__media-art.banner-fit--fill {
  object-fit: cover;
}

.hero__media--art .hero__media-art.banner-fit--fit {
  object-fit: contain;
}

.hero__media--art .hero__media-art.banner-fit--stretch {
  object-fit: fill;
}

.hero__media--art .hero__media-fill {
  object-fit: cover;
  filter: blur(36px) saturate(1.15) brightness(0.62);
  transform: scale(1.12);
}

/*
  The tint that keeps the hero's white text readable over a photograph.

  Only over a photograph. The client, 25 August: "in the frontpage this banner
  borderline remove, just make that the banner area if empty, is different
  color, like it is in day mode. In day mode as well the banner borderline
  remove."

  There was never a border property anywhere on the hero — what he was pointing
  at was this rectangle. Laid over an empty area it tinted the page background
  by a few per cent and drew a seam along all four edges: a borderline made of
  nothing but a slightly darker copy of what was behind it. With no artwork the
  overlay is gone and the area carries a colour of its own instead, below.

  Gone from uploaded artwork as well, since 28 August: "in the frontpage banner
  there is some darkening filter what makes image darker, this remove, there is
  no filter, the image will be displayed like it is." So the tint is now scoped
  to the stock photograph — the one picture Previa chose, under the one headline
  Previa wrote. A sponsor's banner is shown as it arrived.
*/

/* Written without :has(). The stock branch of the template is an <img> and
   nothing else, so the test was always true, and asking a question an older
   engine cannot parse only meant the tint disappeared on that engine. */

.hero__media--stock::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  inset: 0;
  background: var(--hero-overlay);
}

/*
  The banner's own text, over an untinted picture.

  There used to be a shadow here — two of them, a tight drop and a wide glow —
  put on the letters to keep a white headline legible over a pale banner now
  that nothing is tinting the picture behind it. The client, 30 August: "And
  behind the text remove the shadow, it makes it foggy, unclear to see."

  He is right, and the replacement is in the same message: "the white colored
  text is not always good to see, so make that in each banner can set the text
  color differently." A glow is a guess at contrast that has to work over every
  picture anybody might upload, and over his stone wall it worked over none of
  them — it smeared the letters without darkening the wall. Letting the person
  who chose the picture choose the ink is the fix the glow was standing in for.

  So the only thing left here is full white for the two small lines, which is
  what they need over artwork of any kind, and it is a fallback: --banner-ink,
  set from the banner's own colour field, wins wherever one has been entered.
*/

.hero__media--art ~ .container .hero__sponsor,
.hero__media--art ~ .container .hero__sub {
  color: var(--banner-ink, #fff);
}

/*
  An empty banner area.

  A deliberate block rather than the page showing through: --hero-empty is a
  distinctly different tone from --bg in both themes, so an area waiting for
  artwork reads as a slot rather than as a rendering fault. The hero's own text
  sits on it, so it is dark enough for white type in either mode — the same
  requirement the overlay above exists to meet.
*/

.hero__media--empty {
  background: var(--hero-empty);
}

/*
  One height, whatever is on the banner.

  The client, 29 August: "The frontpage banner changes it's size according to
  uploaded image and added text, fix it always to be one size."

  He is right that it moved, and the cause was this box: the hero is as tall as
  whatever is inside it, and what is inside it is now a sponsor's copy, which
  differs per market. A market with three lines of headline pushed the banner a
  hundred and fifty pixels taller than a market with none, so the same page
  jumped as the visitor changed market.

  Two halves to holding it still. A floor, below — one number for every market,
  so an empty banner is the same height as a full one. And a ceiling: the copy
  is clamped to two lines a piece, so no amount of typing in the back office can
  push past the floor and start the box growing again.

  justify-content anchors the stack to the bottom, so the search panel — the one
  control most visitors came for — sits in the same place on every market rather
  than sliding up and down with the length of an advertisement.
*/

.hero__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--sp-7);
  /* 700, because the fullest this box can be — sponsor, two clamped lines of
     headline, two of body, the button, the counts and the search panel —
     measures 698 at every width from 1280 up, and less below it. One number
     above the maximum is what makes the height a constant instead of a
     consequence. Anything that would push past it is clamped, not accommodated. */
  min-height: 700px;
  padding: clamp(40px, 6vw, 72px) 0 clamp(40px, 5vw, 64px);
}

/* A phone stacks the search panel, so the maximum there is different and lower:
   652. Same rule, its own number, so a phone is not given fifty pixels of empty
   banner to scroll past. */

@media (max-width: 720px) {
  .hero__inner {
    min-height: 660px;
  }

  /*
    On a phone the banner goes to the top of the area, not the middle of it.

    The client, 30 August: "what is this in the phone view?? No banners at all
    not in frontpage nor in listing page!"

    This area is a fixed 660 pixels tall — his own instruction of 29 August, so
    that the search box does not move as the market changes — and on a monitor
    it is also wider than it is tall, so a banner fitted whole into it very
    nearly fills it. Turned on its side it does not: a 390-wide phone fits the
    same picture into 241 pixels of a 776-pixel box and centres it, which put
    the advertisement a third of the way down the page behind two hundred
    pixels of its own blurred backdrop. Scrolling past a banner without seeing
    it is the same thing as not having one.

    So in fit mode the picture is pinned to the top of the area, immediately
    under the header, and the backdrop takes the space underneath — which is
    where the headline, the figures and the search panel already sit. Nothing
    about which pixels are shown changes: the whole banner is still drawn, whole
    and uncut, which is what fit means.

    Only fit. Fill and stretch both cover the area completely, so there is no
    space to move the picture into and object-position would only decide which
    part of a photograph gets cropped — a decision the person who chose fill
    made when they chose it.
  */

  .hero__media--art .hero__media-art.banner-fit--fit {
    object-position: center top;
  }
}

.hero__copy {
  max-width: 46rem;
  color: #fff;
}

/*
  The company that bought the placement. "in the banner fiels thers is field
  'Sponsor' So this should be the companys name? This is not wired now."

  Above the headline and deliberately quiet — it is a disclosure, not the
  advertisement. Same treatment the strip on the listings page gives it, so the
  two placements read as one system.
*/

/*
  --banner-ink, here and on the two lines below it.

  One custom property set once on .hero__copy, from the colour field on the
  banner being drawn — see models.Banner.TextStyle. It has to be a variable
  rather than a colour set directly on the container: each of these three rules
  states its own colour at a different opacity, so an inherited value would
  reach none of them. The fallback in every var() is the value that was there
  before, so a banner with an empty colour field is pixel-for-pixel unchanged.
*/

.hero__sponsor {
  margin-bottom: var(--sp-2);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--banner-ink, rgba(255, 255, 255, 0.82));
}

/* The advertiser's own button. Its own line, so it never collides with the
   headline above it however long that runs. */

.hero__cta {
  margin-top: var(--sp-5);
}

.hero__title {
  font-family: var(--font-display);
  font-size: var(--fs-hero);
  font-weight: 400;
  line-height: 1.06;
  letter-spacing: var(--ls-tight);
  color: var(--banner-ink, #fff);
  text-wrap: balance;
  /* Two lines, so a long banner headline cannot make the hero taller — see
     .hero__inner. Everything still reaches a screen reader; only the drawing is
     cut. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/*
  A sponsor's headline, set a size down from Previa's own.

  The site's line is six words; the ones being sold are sentences — "Spain
  realestate market waiting: place your ad here!" At the full hero size that
  runs to three lines, and the banner is held to two so that its height stays
  the one number the client asked for. Smaller type is what lets the sentence
  finish rather than being cut, and it still reads as a headline.

  After .hero__title, not before it: the two carry the same specificity, so the
  one written later is the one that wins. Placed above, this rule was in the
  stylesheet, matched the element, and did nothing.
*/

.hero__title--banner {
  font-size: clamp(1.75rem, 1.05rem + 3vw, 3rem);
}

.hero__sub {
  margin-top: var(--sp-4);
  font-size: clamp(1rem, 0.95rem + 0.3vw, 1.1875rem);
  line-height: 1.6;
  color: var(--banner-ink, rgba(255, 255, 255, 0.9));
  max-width: 44rem;
  /* Two lines, for the reason the headline above is two. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/*
  The three site figures, in their own colour.

  --banner-stats-ink rather than --banner-ink: the client asked for these
  separately from the headline — "make there on each banner a color field for
  'website numbers' so that can enter the color code to change this text color
  as well" — and they sit on a different part of the picture, so the colour
  that saves the headline is often not the one that saves them. Whether they
  are drawn at all is the banner's own switch, decided in the template.
*/

.hero__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
  margin-top: var(--sp-6);
  color: var(--banner-stats-ink, rgba(255, 255, 255, 0.92));
}

.hero__stat strong {
  display: block;
  font-size: var(--fs-h3);
  font-weight: 700;
}

.hero__stat span {
  font-size: var(--fs-sm);
  opacity: 0.82;
}

/* --- Hero search panel ---------------------------------------------------- */

.searchbox {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
  /* Not `overflow: hidden`: the Location field's suggestion list is absolutely
     positioned and has to escape the panel. The rounded corners are kept by
     clipping the two end rows instead of the whole panel. */
}

.searchbox__body--three {
  border-radius: var(--r-xl) var(--r-xl) 0 0;
}

.searchbox__footer {
  border-radius: 0 0 var(--r-xl) var(--r-xl);
}

.searchbox__tabs {
  display: flex;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4) 0;
}

/*
  Panel metrics live on the panel itself, so the whole thing resizes from four
  numbers rather than from a scattering of rules. Every reduction is real
  geometry — padding, gaps, control heights, type sizes. Nothing is scaled
  with a transform: that would soften the text and leave the hit areas at
  their old size.

  The client's brief: the homepage panel is a signpost saying "Previa is a
  property-search site", not the search experience itself — that lives on
  /search, and every field here hands its value over intact. So it is sized to
  sit *on* the hero photograph instead of covering it.
*/

.searchbox {
  --sb-pad: 14px;
  --sb-gap: var(--sp-3);
  /* 12 */
  --sb-control-h: 42px;
  /* inside the 40–44px band the client asked for */
  --sb-label: var(--fs-xs);
  /* 13 — compact, still readable */
}

/*
  Panel width — a ceiling, not a percentage.

  The panel stops short of the container's full width, at the client's request,
  so it reads as a signpost sitting on the photograph rather than as a bar
  stretched across it. It stays flush left, under the headline it belongs to.

  That used to be a 70-per-cent max-width above 1200px and nothing below, which
  made the panel *grow* as the window narrowed: at 1400px it was 70% of the 1216px
  content box (851px), and at 1150px the rule stopped applying and it sprang out
  to the full ~1085px content width. The client saw a compact panel on a full
  screen turn huge the moment they dragged the window in.

  So the limit is now an absolute one, and it applies at every width: the panel
  looks exactly as it does today on a large screen, never exceeds that on any
  smaller one, and once the container itself drops below the ceiling the panel
  simply shrinks with it. --hero-search-max carries the number and why it is
  what it is.
*/

.hero__inner .searchbox {
  max-width: min(100%, var(--hero-search-max));
}

.searchbox__body {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1.2fr 0.9fr auto;
  gap: var(--sb-gap);
  align-items: end;
  padding: calc(var(--sb-pad) - 2px) var(--sb-pad);
}

/*
  Three fields plus the button, since price and bedrooms moved to the sidebar.
  Location gets the widest column: it takes the longest values.
*/

.searchbox__body--three {
  grid-template-columns: 0.9fr 1.6fr 1fr auto;
}

/* Controls and labels are shorter here than in the page forms, which keep the
   full 48px. Scoped to the panel so nothing else moves. The label-to-control
   step is the sidebar's 6px rather than 4: the panel stays compact, and a
   label 4px off its field reads as sitting on it. */

.searchbox .field {
  gap: 6px;
}

.searchbox .field__label {
  font-size: var(--sb-label);
  line-height: 1.2;
}

.searchbox .input,
.searchbox .select,
.searchbox .textarea {
  min-height: var(--sb-control-h);
  padding-inline: var(--sp-3);
  font-size: var(--fs-sm);
}

.searchbox .select {
  padding-right: var(--sp-8);
  background-position: right var(--sp-3) center;
}

/* The location field's leading pin sits closer in too, so its text starts at
   the same inset as the other controls. */

.searchbox .input-icon svg {
  left: var(--sp-3);
}

.searchbox .input-icon .input {
  padding-left: calc(var(--sp-3) + 18px + var(--sp-2));
}

/*
  The location control keeps its own two gutters inside this panel.

  `.searchbox .input` above sets a flat 12px padding-inline, which is right for
  a plain field and wrong for this one: it lands after the component's own rule
  in the cascade, so the right-hand gutter collapsed to 12px while the clear
  button still sat 8px in at 22px wide — a typed value ran under the cross by
  18px. Restating both sides from the same tokens the component uses keeps the
  pin-to-text and text-to-cross steps identical here and in the sidebar.
*/

.searchbox .location-field__control .input {
  padding-left: calc(var(--loc-icon-inset) + var(--loc-icon-size) + var(--loc-gap));
  padding-right: calc(var(--loc-clear-inset) + var(--loc-clear-size) + var(--loc-gap));
}

.searchbox__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding: 5px var(--sb-pad) 6px;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

.searchbox__footer .btn {
  min-height: 32px;
  padding-inline: var(--sp-3);
}

/*
  Search properties.

  Sized by its own content, in an `auto` grid column — never stretched across
  the panel. Height matches the fields either side of it, and the existing
  loading state swaps the label for a centred spinner without changing the
  box, so pressing it does not shift the row.
*/

.searchbox__submit {
  min-height: var(--sb-control-h);
  padding-inline: var(--sp-5);
  font-size: var(--fs-sm);
  white-space: nowrap;
  background: #8B008B;
  border-color: #8B008B;
  color: #fff;
}

.searchbox__submit:hover:not(:disabled) {
  background: #6F006F;
  border-color: #6F006F;
  color: #fff;
}

/*
  Tablet. The single row survives well below the old 1080px switch — at 720px
  the four columns are still comfortable — which is most of the height saving
  on an 8–11" screen.
*/

@media (max-width: 1023.98px) {
  .searchbox {
    --sb-pad: var(--sp-3);
    --sb-gap: var(--sp-2);
    --sb-control-h: 40px;
    --sb-label: var(--fs-2xs);
  }
}

/*
  Phones. Two columns: the short selects pair up on one row and Location takes
  the row below at full width, where its longest values still fit. The button
  keeps its own row but only its own width, aligned to the end — the panel-wide
  slab it used to be is what made this panel so tall.
*/

/*
  Phones — two rows, not four.

  The panel used to stack every field and then hand a full-width slab of a
  button its own row, which is most of why it was 480px tall. Here the two
  short selects share the first row and Location takes the second with the
  submit beside it, the way a search bar is normally built. That removes two
  rows outright and is what takes the panel to roughly 190px.

  Areas rather than `order`, because the two rows want different column splits:
  the selects divide the width evenly, while Location wants nearly all of it
  and leaves the button only what it needs. The DOM order is untouched, so the
  tab sequence and the submitted field order still match the desktop layout.
*/

/*
  The three fields are addressed by the class that identifies each one, not by
  the classes they happen to share.

  This is the bug the client photographed: the deal picker's element is
  `field type-picker deal-picker` and the property picker's is `field
  type-picker`, so a rule for `> .field` claimed both of them and the later rule
  for `> .type-picker` then claimed both of them back. Both fields ended up
  assigned to the same grid cell, drawn one on top of the other — which is why
  the panel read "Deapltyrpe type" with two controls stacked underneath.

  The empty cell above the submit button is deliberate. Letting the property
  picker span into it would make the two pickers unequal by the width of the
  button, and they are a matched pair.
*/

@media (max-width: 700px) {
  .searchbox__body,
  .searchbox__body--three {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
    grid-template-areas:
      'deal type .'
      'loc  loc  go';
  }

  .searchbox__body--three > .deal-picker {
    grid-area: deal;
  }

  .searchbox__body--three > .type-picker:not(.deal-picker) {
    grid-area: type;
  }

  .searchbox__body--three > .location-field {
    grid-area: loc;
  }

  .searchbox__submit {
    grid-area: go;
  }

  /*
    Both pickers open a panel wider than the field that triggers them, so each
    one hangs off the side it is nearest and is capped at what is left of the
    screen. The general rule below takes the panel down to the field's own
    width at this size, which for the property grid is about 140px — three
    words per line and a scroll bar.
  */

  .searchbox .type-picker__panel {
    left: 0;
    right: auto;
    width: min(420px, calc(100vw - var(--gutter) * 2 - var(--sb-pad) * 2));
  }

  .searchbox__body--three > .type-picker:not(.deal-picker) .type-picker__panel {
    left: auto;
    right: 0;
  }

  /*
    The button keeps the magnifier and drops the words. Beside a location field
    that is exactly what a search button looks like, it is square rather than
    the panel-wide slab the client objected to, and it stays a 44px target with
    its name intact through aria-label.
  */

  .searchbox__submit {
    width: 44px;
    min-width: 44px;
    padding: 0;
  }

  .searchbox__submit-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}

/* Narrow phones: the action row stays on one line rather than wrapping into a
   second strip of height. */

@media (max-width: 430px) {
  .searchbox__footer {
    gap: var(--sp-2);
  }

  .searchbox__footer .btn {
    padding-inline: var(--sp-2);
    font-size: var(--fs-2xs);
  }

  .searchbox__footer .btn svg {
    width: 14px;
    height: 14px;
  }
}

/* ==========================================================================
   Homepage sections
   ========================================================================== */

/* The .type-grid / .type-tile block that styled the homepage's browse-by-type
   section was removed with the section itself. The search controls use
   .type-check-grid, which is a different component and is untouched. */

/* Popular locations */

/*
  The tiles sit on --card-gap like every other card grid on the site rather
  than on a gap of their own. The 16px they used to carry read as a tighter
  block than the property, development and article rows above and below them,
  and the difference was plainest inside a panel, where the grid's gutters and
  the panel's own 32px inset are visible in the same glance.
*/

.loc-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--card-gap);
}

@media (max-width: 900px) {
  .loc-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 480px) {
  .loc-grid {
    grid-template-columns: 1fr;
  }
}

.loc-tile {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-lg);
  overflow: hidden;
  isolation: isolate;
}

.loc-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease);
}

.loc-tile:hover img {
  transform: scale(1.05);
}

/* City and count sit on the same inset from all three edges they touch, so the
   caption reads as padded rather than as text dropped near a corner. */

.loc-tile__text {
  position: absolute;
  left: var(--sp-5);
  right: var(--sp-5);
  bottom: var(--sp-5);
  z-index: 1;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55), 0 0 12px rgba(0, 0, 0, 0.35);
}

.loc-tile__city {
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-weight: 400;
}

.loc-tile__count {
  font-size: var(--fs-sm);
  opacity: 0.88;
}

/* Trust / why Previa */

/*
  Four columns on --card-gap, which is the gap every other grid on the page
  uses and which steps down with the breakpoints on its own — so the 960px
  rule below only changes the column count now.

  The three rows are declared on the grid and each column spans them as a
  subgrid, which is what keeps the icons, the titles and the descriptions on
  the same lines across the four columns: before this a title that wrapped to
  two lines pushed its own description down and nothing else's, and the block
  read as four independent columns rather than one row of them. Browsers
  without subgrid drop the one declaration and fall back to the plain
  three-row column this always was.
*/

.trust-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: auto auto auto;
  gap: var(--card-gap);
}

/*
  Once the four columns wrap, one feature sits under another and the gap
  between them is doing a different job from the gap beside them: it has to
  read as larger than the 12px inside a feature, which --card-gap no longer is
  by the time it steps down to 16. So the row gap holds at 24 while the column
  gap keeps following the breakpoints.
*/

@media (max-width: 960px) {
  .trust-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: var(--sp-6);
  }
}

@media (max-width: 520px) {
  .trust-grid {
    grid-template-columns: 1fr;
  }
}

.trust-item {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  row-gap: var(--sp-3);
}

/*
  The tile's two colours are read through --trust-tile and --trust-ink, and
  every rule below writes its own value as those variables' fallback rather
  than as the property.

  That is what lets a single mark carry its own colours. The three rules that
  follow are a theme rule and a section rule, both of which sit above a plain
  modifier class in specificity, so a .trust-item__icon--bell that simply said
  `background: #008000` would win on the light theme and lose on the dark one.
  A variable is set on the element itself and read wherever the cascade lands,
  so the modifier needs no weight at all — see the four marks further down.
*/

.trust-item__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-md);
  background: var(--trust-tile, var(--gold-tint));
  color: var(--trust-ink, var(--gold-hover));
}

[data-theme='dark'] .trust-item__icon {
  background: var(--trust-tile, var(--surface-3));
  color: var(--trust-ink, var(--gold));
}

.trust-item__icon svg {
  width: 24px;
  height: 24px;
}

/* balance, because these four titles sit in a row and one of them is a word
   longer than its column: without it "Search the map, not a list" leaves the
   word "list" alone on the second line, which reads as a mistake beside three
   single-line neighbours. */

.trust-item__title {
  font-size: var(--fs-h4);
  font-weight: 600;
  text-wrap: balance;
}

.trust-item__text {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  /* Was 1.65; half the leading above 1 → 1.325 so wrapped lines sit closer
     without moving the block up under the title. */
  line-height: 1.325;
}

/*
  The panel's own type used to be inverted — white titles on rgba white text —
  because the panel was navy in the light theme, and a second set of rules put
  it back to the ordinary colours in the dark one. The panel is a light surface
  in both themes now (see .section--navy::before in layout.css), so the type is
  the page's ordinary type in both and neither set is needed: the titles and the
  body text inherit --text and --text-muted like every other section's.

  The icon tile keeps its own colours, which were already the same in both
  themes: a gold wash behind a gold glyph, which is what it renders as on the
  dark theme's --surface-2 today and reads the same way on the light theme's.
*/

.section--navy .trust-item__icon {
  background: var(--trust-tile, rgba(201,154,61,0.16));
  color: var(--trust-ink, var(--gold));
}

/*
  The four marks, to the client's 9 September note on this section:

    "the map icon replace with googlemaps logo, but make it inline svg there.
     Then the sellers you can check icon background make green #008000 and this
     image there in the middle the same violet as Previa logo background. Then
     Genuinly international logo make there the Previa logo with violet
     background. And first to know logo backround make green #008000 and the
     bell-icon there in the middle white."

  Two greens, one violet, and the Google Maps mark on no tile at all — see the
  rule under these two.

  The violet is #8B008B, which is not a guess and not a near miss: it is the
  literal behind the Previa mark in web/templates/components/logo.html and in
  the favicon, so "the same violet as Previa logo background" is the same
  number, not a colour matched by eye. The greens are his #008000 exactly.

  The bell was white for a day. The client, 10 September, 19:52, looking at it
  on the live site: "the white bell on the right make the same violet as the
  verification icon" — the verification icon being the shield two tiles left,
  which is that same #8B008B. So the two line icons are now one declaration:
  they are the same tile and the same ink, and writing them apart would only
  invite one of them to drift.

  Written as variables, so each one is one line and none of them has to outrank
  the theme rule above — see the note on .trust-item__icon.
*/

.trust-item__icon--shield,
.trust-item__icon--bell {
  --trust-tile: #008000;
  --trust-ink: #8B008B;
}

/*
  And Google's mark is given the whole tile, with nothing behind it.

  The client, 10 September: "the googlemaps logo on the left make in full size,
  so the background box remove completely and the map is there in this size."

  Two declarations do it. The wash goes by setting --trust-tile to transparent
  rather than by taking the background off — the theme rule and the section
  rule both write that property, so a `background: none` here would have to
  outrank two selectors, while the variable is simply what they read.

  The mark then grows from 24px to the tile's own 48. It fits exactly and needs
  no re-centring: the artwork already runs corner to corner in its viewBox, the
  sheet from 0 to 43.4 and the pin overhanging it to 47.95.

  The 48x48 box itself stays. It is what holds the four icons on one line and
  the four headings under them on the next — .trust-item takes its rows from
  the grid as a subgrid — so removing the element rather than its background
  would lift this column's heading above the other three.
*/

.trust-item__icon--googlemaps {
  --trust-tile: transparent;
}

.trust-item__icon--googlemaps svg {
  width: 48px;
  height: 48px;
}

/*
  And the Previa tile is the logo itself on the logo's own violet.

  The mark is the component every other Previa logo on the site is, rather than
  a copy of its drawing: the geometry is generated (docs/logo_gen.py) and the
  file says to regenerate the mark and the favicon together, so a third copy
  here would be the one that goes stale.

  It is drawn larger than the tile and clipped back to it, which is the whole
  trick. The component's violet rectangle is inset in its own 64-unit box — it
  runs 5.5 to 58.5 — so a 48px mark would leave a ring of tile showing round a
  39px rectangle. 64/53 of 48 is 58, the flexbox centres the overflow evenly on
  all four sides, and the tile's own radius clips the corners: the result is a
  48px violet square with the planet at full size, which is the favicon. The
  tile is given the same violet so the clipped corners have nothing to reveal.
*/

.trust-item__icon--previa {
  --trust-tile: #8B008B;
  overflow: hidden;
}

.trust-item__icon--previa .logo {
  flex: none;
}

.trust-item__icon--previa .logo__mark {
  width: 58px;
  height: 58px;
}

/*
  The same tile on /advertising, in the two colours the client named for the
  three placement cards.

  The client, 11 September: "in the frontpage the 'Why people search on Previa'
  section icons are perfect. Now the same in 'advertise in Previa' page. The
  'Homepage banner' and 'Promotion for brokers' icons make green #008000
  background and the same violet icon there in the middle. The 'Featured
  listings' icon make vice versa: the violet background (same as Previa logo)
  and green #008000 icon in the middle."

  So the first two are the shield and bell pair above, to the number, and the
  third is those two swapped. Written as the same two variables and for the same
  reason: the tile these cards use is .trust-item__icon, which a theme rule
  repaints, and a plain modifier saying `background: #008000` would win on one
  theme and lose on the other.

  Which card gets which is models.adCardTones, by position — see the note there.

  The two lines under them are for a card whose mark is one of the two brand
  marks rather than a line icon: an administrator can type "previa" or
  "googlemaps" into a card's Mark box, and the Previa lockup is drawn at 72px,
  which without these would stand outside a 48px tile.
*/

.trust-item__icon--ad-green  {
  --trust-tile: #008000;
  --trust-ink: #8B008B;
}

.trust-item__icon--ad-violet {
  --trust-tile: #8B008B;
  --trust-ink: #00FF00;
}

.trust-item__icon--ad-brown {
  --trust-tile: #A52A2A;
  --trust-ink: #8B4513;
}

.trust-item__icon--ad-purple {
  --trust-tile: #000080;
  --trust-ink: #FFD700;
}

.trust-item__icon--ad-green,
.trust-item__icon--ad-violet,
.trust-item__icon--ad-brown,
.trust-item__icon--ad-purple {
  overflow: hidden;
}

.trust-item__icon--ad-green .logo__mark,
.trust-item__icon--ad-violet .logo__mark,
.trust-item__icon--ad-brown .logo__mark,
.trust-item__icon--ad-purple .logo__mark {
  flex: none;
  width: 58px;
  height: 58px;
}

/* Advertise page placement cards — fill by position, border matches fill. */

.ad-card {
  box-shadow: none;
  color: #fff;
}

.ad-card .card__title,
.ad-card .text-sm,
.ad-card .muted,
.ad-card .strong {
  color: #fff;
}

.ad-card--banner {
  background: #B8860B;
  border-color: #B8860B;
}

.ad-card--brokers {
  background: #8B008B;
  border-color: #8B008B;
}

.ad-card--featured {
  background: #008000;
  border-color: #008000;
}

.ad-card--developments {
  background: #8B0000;
  border-color: #8B0000;
}

/* New developments mark: navy tile #000080, building #FFD700. */
.ad-card--developments .trust-item__icon {
  --trust-tile: #000080;
  --trust-ink: #FFD700;
  background: #000080;
  color: #FFD700;
}

[data-theme='dark'] .ad-card--developments .trust-item__icon {
  background: #000080;
  color: #FFD700;
}

/* /advertising market table — enquire is the available badge chip, in violet. */

a.badge.ad-enquire {
  background: #8B008B;
  border: 1px solid #8B008B;
  color: #00FF00;
  text-transform: lowercase;
  text-decoration: none;
}

a.badge.ad-enquire:hover {
  background: #6F006F;
  border-color: #6F006F;
  color: #00FF00;
}

/* /advertising — markets heading + narrower centred table.
   Margin under the placement cards matches the gap under the page head
   (client, 21 September). */

.ad-markets {
  margin-top: var(--section-y);
}

.ad-markets__title {
  color: #8B008B;
  text-transform: lowercase;
  text-align: center;
}

.ad-markets__table {
  width: 70%;
  max-width: 70%;
  margin-inline: auto;
}

.ad-markets__country {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  white-space: nowrap;
}

.ad-markets__country .flag.flag {
  display: block;
  flex: none;
}

/* ADS sponsored-banners range: "showing 1-10 of 13". */

.banner-count {
  color: #008000;
  text-transform: lowercase;
}

/*
  Sponsored banners country search — no green focus frame.

  The client, 16 September: "the sponsored banner search field has green
  selector over it, remove it, here no selector." Pick menus keep the green
  ring; this plain search box does not.
*/

#banner-q:focus,
#banner-q:focus-visible {
  border-color: var(--border);
  box-shadow: none;
  outline: none;
}

/* ADS Live column — on-state matches the site's green, not --success mint. */

#banner-results .switch input:checked + .switch__track {
  background: #008000;
}

/* Admin listings Featured toggle — same #008000 when on. */

.admin-listing__feature .switch input:checked + .switch__track {
  background: #008000;
}

/* Saved searches Alerts — on-state #008000, not --success mint. */

.saved-search .switch input:checked + .switch__track {
  background: #008000;
}

/* Violet bump twin under Featured — Previa logo #6F006F when on. */

.admin-listing__bump .switch input:checked + .switch__track {
  background: #6F006F;
}

/* General → Video uploads: same ffmpeg switch as sexydate.world options. */

.options-ffmpeg-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 0 0.35rem;
}

.options-ffmpeg-label {
  font-size: 14px;
  line-height: 1.2;
  white-space: nowrap;
  color: #4b5563;
}

[data-theme='dark'] .options-ffmpeg-label {
  color: #d7d7d7;
}

.options-ffmpeg-switch {
  position: relative;
  display: inline-block;
  width: 52px;
  height: 28px;
  flex: 0 0 auto;
  margin: 0;
}

.options-ffmpeg-switch input {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
}

.options-ffmpeg-slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: #ff0000;
  border-radius: 28px;
  transition: background-color 0.2s ease;
}

.options-ffmpeg-slider:before {
  position: absolute;
  content: "";
  height: 22px;
  width: 22px;
  left: 3px;
  bottom: 3px;
  background: #00008B;
  border-radius: 50%;
  transition: transform 0.2s ease;
  transform: translateX(24px);
}

.options-ffmpeg-switch input:checked + .options-ffmpeg-slider {
  background: #008000;
}

.options-ffmpeg-switch input:checked + .options-ffmpeg-slider:before {
  transform: translateX(0);
}

.options-ffmpeg-toggle:has(input:checked) .options-ffmpeg-label--on {
  color: #008000;
}

.options-ffmpeg-toggle:not(:has(input:checked)) .options-ffmpeg-label--off {
  color: #ff0000;
}

/* ADS banner editor checkboxes — same green tick as listing filters. */

#banner-editor .check input:checked {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

[data-theme='dark'] #banner-editor .check input:checked {
  background-color: #008000;
  border-color: #008000;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

/* Artwork upload — size hints, normal weight, tight lines. */
#banner-editor .banner-art-sizes {
  display: block;
  margin-top: 2px;
  color: var(--text-muted);
  font-weight: 400;
  font-size: var(--fs-xs);
  line-height: 1.25;
}

/* Button up/down — same small face as the size hints, pulled up under Button label. */
#banner-editor .banner-cta-stack {
  gap: 4px;
}

#banner-editor .field--banner-cta-offset {
  gap: 2px;
  margin-top: 0;
}

#banner-editor .banner-cta-offset-label {
  display: block;
  margin: 0;
  color: var(--text-muted);
  font-weight: 400;
  font-size: var(--fs-xs);
  line-height: 1.25;
  cursor: default;
}

#banner-editor .field--banner-fit > .sort-menu--field.banner-fit-menu {
  width: 50%;
  max-width: 50%;
}

/* Placeholder banners: titles share one row (aligned), content tight under them. */

.placeholder-banners-card > .card__header,
.placeholder-banners-card > .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.placeholder-banners-card > .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.placeholder-banners {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--sp-4);
  row-gap: var(--sp-2);
  align-items: start;
}

.placeholder-banners__title {
  margin: 0;
  font-weight: 700;
  line-height: 1.2;
}

.placeholder-banners__col {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 0;
}

@media (max-width: 768px) {
  .placeholder-banners {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Testimonials */

.tcard {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.tcard__stars {
  display: flex;
  gap: 2px;
  color: var(--gold);
}

.tcard__stars svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
}

.tcard__quote {
  font-size: var(--fs-base);
  line-height: 1.65;
}

.tcard__author {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-top: auto;
}

.tcard__name {
  font-size: var(--fs-sm);
  font-weight: 600;
}

.tcard__role {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* Promoted brokers strip */

/*
  The way in for a broker who wants one of the placements above. Quiet — it is
  addressed to the few readers who sell rather than the many who buy — and it
  sits under the strip so it reads as a note about what they just looked at.
*/

.broker-strip__sell {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.broker-strip + .broker-strip__sell,
.section-empty + .broker-strip__sell {
  margin-top: var(--sp-1);
}

.broker-strip__sell svg {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  color: var(--gold);
}

.broker-strip__sell a {
  color: var(--gold);
  font-weight: 600;
}

.broker-strip__sell a:hover {
  color: var(--gold);
}

.broker-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--card-gap);
}

@media (max-width: 1024px) {
  .broker-strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 520px)  {
  .broker-strip {
    grid-template-columns: 1fr;
  }
}

/* Final CTA */

.cta-panel {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-8);
  padding: clamp(32px, 5vw, 64px);
  background: var(--navy);
  border-radius: var(--r-xl);
  color: #fff;
}

[data-theme='dark'] .cta-panel {
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
}

.cta-panel__title {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 400;
  color: inherit;
}

.cta-panel__text {
  margin-top: var(--sp-3);
  color: rgba(255,255,255,0.8);
  max-width: 52ch;
}

[data-theme='dark'] .cta-panel__text {
  color: var(--text-muted);
}

.cta-panel__actions {
  display: flex;
  gap: var(--sp-3);
  flex: none;
  flex-wrap: wrap;
}

@media (max-width: 860px) {
  .cta-panel {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-6);
  }

  .cta-panel__actions {
    width: 100%;
  }

  .cta-panel__actions .btn {
    flex: 1;
  }
}

/* About page — Work with us (client, 21 September): Previa violet, lime copy,
   70% width centred, tight up/down. */
#about-work-with-us.cta-panel,
.cta-panel--about {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 70% !important;
  max-width: 70% !important;
  margin-inline: auto !important;
  gap: var(--sp-4) !important;
  padding: 12px clamp(22px, 3.5vw, 40px) !important;
  background: #8B008B !important;
  border: none !important;
  border-radius: var(--r-xl);
  color: #00FF00 !important;
}

html[data-theme='dark'] #about-work-with-us.cta-panel,
html[data-theme='dark'] .cta-panel--about,
[data-theme='dark'] #about-work-with-us.cta-panel,
[data-theme='dark'] .cta-panel--about {
  background: #8B008B !important;
  border: none !important;
  color: #00FF00 !important;
}

#about-work-with-us .cta-panel__title,
#about-work-with-us .cta-panel__text,
.cta-panel--about .cta-panel__title,
.cta-panel--about .cta-panel__text {
  color: #00FF00 !important;
}

#about-work-with-us .cta-panel__title,
.cta-panel--about .cta-panel__title {
  margin: 0 !important;
  line-height: 1.1 !important;
}

#about-work-with-us .cta-panel__text,
.cta-panel--about .cta-panel__text {
  margin: 0 !important;
  margin-top: 0 !important;
  padding-top: 0 !important;
  line-height: 1.1 !important;
}

#about-work-with-us.cta-panel > div:first-child,
.cta-panel--about > div:first-child {
  display: flex;
  flex-direction: column;
  gap: 0;
}

@media (max-width: 860px) {
  #about-work-with-us.cta-panel,
  .cta-panel--about {
    width: 100% !important;
    max-width: 100% !important;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-4) !important;
  }
}

/* Pricing page — Ready to publish? (client, 21 September): logo violet, lime
   copy, half width centred, ~30% shorter, pulled up under the packages.
   #pricing-ready + !important: beats [data-theme=dark] .cta-panel and any
   cached partials that still load the generic panel rules. */
#pricing-ready.cta-panel,
.cta-panel--pricing {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 50% !important;
  max-width: 50% !important;
  margin-inline: auto !important;
  margin-top: var(--sp-5);
  gap: var(--sp-4) !important;
  /* Tight up/down — client, 21 September: too much empty room in the box. */
  padding: 12px clamp(22px, 3.5vw, 40px) !important;
  background: #8B008B !important;
  border: none !important;
  border-radius: var(--r-xl);
  color: #00FF00 !important;
}

html[data-theme='dark'] #pricing-ready.cta-panel,
html[data-theme='dark'] .cta-panel--pricing,
[data-theme='dark'] #pricing-ready.cta-panel,
[data-theme='dark'] .cta-panel--pricing {
  background: #8B008B !important;
  border: none !important;
  color: #00FF00 !important;
}

#pricing-ready .cta-panel__title,
#pricing-ready .cta-panel__text,
.cta-panel--pricing .cta-panel__title,
.cta-panel--pricing .cta-panel__text {
  color: #00FF00 !important;
}

#pricing-ready .cta-panel__title,
.cta-panel--pricing .cta-panel__title {
  margin: 0 !important;
  line-height: 1.1 !important;
}

#pricing-ready .cta-panel__text,
.cta-panel--pricing .cta-panel__text {
  margin: 0 !important;
  margin-top: 0 !important;
  padding-top: 0 !important;
  line-height: 1.1 !important;
}

#pricing-ready.cta-panel > div:first-child,
.cta-panel--pricing > div:first-child {
  display: flex;
  flex-direction: column;
  gap: 0;
}

@media (max-width: 860px) {
  #pricing-ready.cta-panel,
  .cta-panel--pricing {
    width: 100% !important;
    max-width: 100% !important;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-4) !important;
  }
}

/* Article card */

.acard {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.acard:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--border-strong);
}

.acard__media {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--surface-3);
}

.acard__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease);
}

.acard:hover .acard__media img {
  transform: scale(1.045);
}

.acard__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-5);
  flex: 1;
}

.acard__title {
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.35;
}

.acard__title a:hover {
  color: var(--slate);
}

.acard__excerpt {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: 1.6;
}

.acard__meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: auto;
  padding-top: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--text-subtle);
}

/*
  The index's five-across cards are about 224px wide — the same width the
  property cards live at — so they take the property card's density rather than
  the homepage row's. Scoped to the grid, because the homepage still shows three
  wide cards and the client asked to shrink the index, not that section.
*/

.grid--articles .acard__body {
  padding: var(--sp-4);
  gap: 6px;
}

.grid--articles .acard__title {
  font-size: var(--fs-base);
  line-height: 1.3;
}

.grid--articles .acard__excerpt {
  font-size: var(--fs-xs);
  line-height: 1.55;
}

.grid--articles .acard__meta {
  padding-top: var(--sp-2);
}

/* ==========================================================================
   Map screens
   ========================================================================== */

.map-shell {
  display: grid;
  grid-template-columns: minmax(360px, 42%) minmax(0, 1fr);
  /* An explicit row track is required: with an auto row the results list would
     size the row to its own content and stretch the map pane thousands of
     pixels tall, pushing markers out of view. */
  grid-template-rows: minmax(0, 1fr);
  /*
    100dvh, not 100vh, with vh kept as the fallback for anything that does not
    know the unit.

    Nothing on this screen scrolls the page — the shell is exactly the height
    of what is left below the header and the results column scrolls inside it.
    On a phone, 100vh is the height with the browser chrome *retracted*, so the
    shell was taller than the visible area by the height of the address bar and
    the last part of the list could not be reached however far it was scrolled.
    dvh measures what is actually on screen.
  */
  /*
    Full viewport height and pulled up under the header — same idea as
    .hero--flush on the frontpage. The bar sits on top; its bottom-right
    radius lets the map show through. The results column pads itself back
    down so its toolbar still starts below the bar.
  */
  height: 100vh;
  height: 100dvh;
  margin-top: calc(var(--header-h) * -1);
  /*
    The page's width, not the window's, since 19 August: "make the main content
    the same width as on the listing page". --page-width is redefined for these
    screens in layout.css, so this is the same box the header above it and the
    footer on every other page sit in.
  */
  width: var(--page-width);
  margin-inline: auto;
  /*
    Bottom-right only is rounded — the outer curve of the map column against
    the page surround. Tops stay square under the header.
  */
  border-radius: 0 0 var(--r-xl) 0;
  overflow: hidden;
}

.map-shell > * {
  min-height: 0;
}

.map-shell.is-panel-collapsed {
  grid-template-columns: 0 minmax(0, 1fr);
}

.map-shell--full {
  grid-template-columns: minmax(0, 1fr);
}

/*
  Grid mode gives the results column more room.

  The client asked for "3 or 4 ads in one row" beside the map. At 42% of the
  window a card would be about 150px wide at three across — narrower than the
  price it has to carry — so the column widens when the grid is chosen and
  returns to its reading width when the list is. Nothing re-requests: the class
  follows the same listMode the layout does.
*/

@media (min-width: 901px) {
  .map-shell.is-grid-mode {
    grid-template-columns: minmax(420px, 56%) minmax(0, 1fr);
  }
}

.map-results {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  /* Shell is tucked under the header; push the panel content back below it. */
  padding-top: var(--header-h);
  border-right: 1px solid var(--border);
  background: var(--bg);
  /*
    The results column is what the card grid inside it measures against, not
    the window. It is a share of the window on a desktop, the whole window on a
    phone and nothing at all when collapsed, so a viewport media query would be
    answering the wrong question in two of those three states.
  */
  container-type: inline-size;
  container-name: map-results;
}

.map-shell.is-panel-collapsed .map-results {
  display: none;
}

/* The head and the tag bar together, as one out-of-band swap target. Fixed
   height by content: only the results list below it scrolls. */

/*
  The toolbar at the top of the results column.

  No rule under it: "remove the separator line below the header" and "remove
  the border line above the map". It was the one horizontal hairline on this
  screen, drawn immediately beneath the header bar and along the top of the
  block the map sits in, and the tag bar below it already changes colour, which
  divides the two without a line.
*/

/*
  The toolbar and the tag bar are one panel, and it is the panel that closes.

  The surface and the downward corners used to be on the toolbar alone. That
  left the tag row underneath it on the column's own background, so the left
  column read as the site header, then a second narrower rounded header, then a
  row of tags floating on nothing — which is the "header issue" the client
  pointed at on 25 August.

  Moving both onto #map-results-top makes the counts, the sort control, the
  filter buttons and the tags one block that carries the header's surface down
  and closes it once, with the same two bottom corners at the same radius as
  every other page's .page-head.
*/

#map-results-top {
  /* Sized by its content: only the results list below it scrolls. */
  flex: none;
  /* Same day-mode snow as the map page's site header — see that rule. */
  background: #FFFAFA;
  border-radius: 0 0 var(--r-xl) var(--r-xl);
  /* Visible so the sort-by menu can open over the cards below. overflow:hidden
     was clipping the absolute .dropdown into the toolbar's box. The panel's own
     background still follows border-radius without clipping descendants. */
  overflow: visible;
  position: relative;
  z-index: 2;
}

[data-theme='dark'] #map-results-top {
  background: var(--surface);
}

.map-results__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
}

/*
  On a phone the row wraps rather than overflowing.

  The column lost a gutter on each side when the shell was cut to the page
  width, and this row had about twelve pixels to spare before that: the Filters
  button and the view switch ended up past the panel's edge, where the shell's
  overflow:hidden clipped them. Scoped to the single-column breakpoint, because
  above it there is room and letting the row wrap there costs it a third line
  it does not need.
*/

@media (max-width: 900px) {
  .map-results__head {
    flex-wrap: wrap;
  }
}

/*
  Tag bar under the head. It carried these numbers as an inline style before it
  became a shared block; the padding is unchanged, and it is scoped by ancestor
  rather than by a second class so the element's own class stays exactly
  `active-filters`, which is how the rest of the site addresses a tag bar.

  It is rendered even with no tags in it, so a filter change always has an
  element to swap into, and collapses to nothing when empty — the template trims
  its whitespace so :empty genuinely matches.
*/

/* Inside the panel now, so it needs a bottom inset of its own rather than
   borrowing the column's. */

#map-results-top .active-filters {
  padding: 0 var(--sp-5) var(--sp-4);
  margin: 0;
}

#map-results-top .active-filters:empty {
  display: none;
}

.map-results__list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

/* Cards must keep their intrinsic height — inside a height-constrained flex
   column they would otherwise shrink to slivers. */

.map-results__list > * {
  flex: 0 0 auto;
}

/*
  The fixed Filters edge tab belongs to the /search grid, where the filter rail
  is hidden below 1024px and nothing else opens the drawer. The map screens
  carry their own Filters button — in the results toolbar on the split view, in
  the floating toolbar on the full-screen one — so between 900 and 1024px the
  tab was a second control for the same drawer, pinned over the left-hand
  column of result cards.

  It stays when the results panel is not on screen: on a phone showing the map,
  and on a desktop with the panel collapsed, the toolbar button goes with it and
  the tab is the only way back to the filters.
*/

body:has(.map-shell:not(.is-map-visible):not(.is-panel-collapsed)) .filter-fab {
  display: none;
}

.map-pane {
  position: relative;
  min-width: 0;
  background: var(--surface-3);
}

.map-canvas {
  position: absolute;
  inset: 0;
}

.map-toggle {
  position: absolute;
  top: var(--sp-4);
  left: var(--sp-4);
  z-index: 4;
}

/* ---------------------------------------------------------------------------
   Google Maps surface
   Google's own chrome is restyled to match the Previa system rather than
   sitting on top of it as a foreign widget.
   --------------------------------------------------------------------------- */

.map-canvas {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--bg-alt);
}

.map-canvas:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: -2px;
}

/* The map's own root sits under everything Previa draws. */

.gm-style {
  font-family: var(--font-sans);
  background: var(--bg-alt);
}

/* Dark mode: cool and dim the tiles so the map does not glare out of a dark
   page. The filter is on the tile pane alone, so it does not reach the markers
   and popups Previa draws above it — those already have dark-theme rules of
   their own and inverting them twice would undo both. */

[data-theme='dark'] .gm-style > div > div > div:first-child > div > div:first-child {
  filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.88) saturate(0.7);
}

/*
  ...except that the selector above is a shape, not a name, and Google owns the
  shape. Measured on the live site in dark mode, the three elements it matches
  are an empty pane wrapper, the attribution strip and .map-popup-shell — and
  not one of them contains a tile. So the rule never dimmed the map, and it did
  invert Previa's own popup: the photograph came out as a negative that had then
  been pulled to 0.92 brightness, 0.88 contrast and 0.7 saturation, which is the
  faded, soft picture the client screenshotted beside a sharp one from the
  property page. The card read as light in a dark theme for the same reason —
  its dark surface was being inverted to near-white.

  The popup is Previa's overlay, not Google's chrome, so it says so by name
  here. !important rather than a heavier selector because the arithmetic leaves
  no honest way to win: the rule above carries four class-level components — the
  attribute, .gm-style and two :first-child, pseudo-classes counting the same as
  classes — against this one's three, and no amount of naming the popup more
  precisely closes that. It is the same reason the attribution rules below use
  it. Nothing about the tile rule itself changes, so the map, the markers, the
  clustering and the light theme all render exactly as they did.
*/

[data-theme='dark'] .gm-style .map-popup-shell {
  filter: none !important;
}

/* Attribution and terms — Google requires them visible, so they are styled to
   be legible rather than hidden. */

.gm-style-cc,
.gm-style .gmnoprint:last-child {
  font-size: 11px !important;
}

.gm-style-cc span,
.gm-style-cc a {
  color: var(--text-muted) !important;
}

[data-theme='dark'] .gm-style-cc a {
  color: var(--gold) !important;
}

/* --- Price markers ---------------------------------------------------------
   HTML rather than an image pin: it carries the price, matches the button
   system, and lifts on hover like the cards do. This is why the markers are
   AdvancedMarkerElement — the legacy marker takes an image and could not draw
   any of it.

   The wrapper is deliberately zero-size. Google puts the bottom centre of a
   marker's content on the coordinate, and a box with no width and no height has
   its bottom centre at its own origin — so the pill below keeps the
   translate(-50%, -100%) it has always used, and lands exactly where it did. */

.map-pin-wrap,
.map-broker-wrap {
  position: relative;
  width: 0;
  height: 0;
}

.map-pin {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transform: translate(-50%, -100%);
  height: 30px;
  padding: 0 11px;
  /*
    Day-mode navy and white in both themes.

    The client, 17 September: on the map page the ad labels are blue in day
    mode and white in dark mode — keep the same blue labels after dark.
    --navy / --on-primary invert under [data-theme='dark'] for primary
    buttons; the price pills must not follow that flip.
  */
  background: #0C2D48;
  color: #FFFFFF;
/*
    The ring stays transparent in both themes.

    In daylight the map tiles are pale and the pill is already navy against
    them, so a white collar was pure noise — the client called the markers out
    for it. After dark the pill is the same navy (locked above), so the same
    rule applies: no white borderline, labels look like day mode.
  */
  border: 2px solid transparent;
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-pin);
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.map-pin::after {
  /* Small stem so the pill reads as anchored to a point. */
  content: '';
  position: absolute;
  bottom: -5px;
  left: 50%;
  width: 8px;
  height: 8px;
  background: inherit;
  border-right: 2px solid transparent;
  border-bottom: 2px solid transparent;
  transform: translateX(-50%) rotate(45deg);
  border-radius: 0 0 2px 0;
}

/* Dark theme used to paint a surface collar when the pill inverted to white.
   With navy locked for both themes, that ring is gone — same look as day. */

[data-theme='dark'] .map-pin {
  border-color: transparent;
}

[data-theme='dark'] .map-pin::after {
  border-right-color: transparent;
  border-bottom-color: transparent;
}

.map-pin:hover,
.map-pin.is-active {
  /* Client, 17 September: hover green #008000. */
  background: #008000;
  color: #FFFFFF;
  transform: translate(-50%, -100%) scale(1.1);
  box-shadow: 0 2px 4px rgba(8, 23, 34, 0.32), 0 8px 16px rgba(8, 23, 34, 0.28);
}

/*
  A featured listing's marker is gold, not merely ringed in it.

  The client, 6 September: "if the ad is featured, then on the map as well make
  this background golden like in other places." The ring alone was the whole of
  it, and against pale map tiles a 2px outline on a navy pill is not what a
  featured card, a featured badge or the featured package look like anywhere
  else on the site.

  --gold with --text-on-gold is the badge's pairing rather than the card's
  --gold-tint: a 30px pill has to hold its own against Google's tiles, and the
  tint that sits behind a block of card text would disappear into them. Both
  tokens are theme-aware, so the marker is gold in daylight and gold after dark
  without a second rule.

  The stem needs nothing: .map-pin::after takes `background: inherit`.

  Hover and active have to be restated here. .map-pin:hover / .is-active paint
  the pill green #008000, and this block already outranks it on source order,
  so a featured pin would otherwise sit frozen under the pointer with no
  feedback at all. Hover gets the gold-hover token; marked / active still
  takes the same green as a basic pin.
*/
.map-pin.is-featured {
  background: var(--gold);
  color: var(--text-on-gold);
  box-shadow: 0 0 0 2px var(--gold), var(--shadow-pin);
}

/*
  Hover stays a darker gold. Marked / active must match a basic pin: green
  #008000 — the featured gold-hover was winning and only darkened the pill
  (client, 20 September).
*/
.map-pin.is-featured:hover {
  background: var(--gold-hover);
  color: var(--text-on-gold);
  box-shadow: 0 0 0 2px var(--gold-hover),
              0 2px 4px rgba(8, 23, 34, 0.32), 0 8px 16px rgba(8, 23, 34, 0.28);
}

.map-pin.is-featured.is-active {
  background: #008000;
  /* Price ink stays the featured dark tone — not white (client, 20 September). */
  color: var(--text-on-gold);
  box-shadow: 0 0 0 2px #008000,
              0 2px 4px rgba(8, 23, 34, 0.32), 0 8px 16px rgba(8, 23, 34, 0.28);
}

/*
  ...and the collar the dark theme draws round it, in daylight too.

  The client, 6 September: "in the dark mode there is the blue borderline, in
  day mode it is not there - make it in day mode there as well."

  The border is transparent in the light theme on purpose — see the note on
  .map-pin — because a white collar round a navy pill was noise on pale tiles.
  A gold pill is the other case: it has no dark edge of its own, and against
  Google's greens and blues it needs one, which is exactly what the reader is
  seeing after dark. So the featured marker alone takes it back, and it takes
  the navy rather than --surface: --surface in daylight is the white that was
  removed, and navy is what the dark theme's own collar reads as.

  The dark theme keeps its own. [data-theme='dark'] .map-pin and this rule
  weigh exactly the same — an attribute and a class against two classes — so
  source order alone would decide it, and this block is lower in the file. That
  is not a thing to leave to source order when it is the night collar the
  client likes, so the night collar is restated here by name.
*/

.map-pin.is-featured {
  border-color: var(--navy);
}

.map-pin.is-featured::after {
  border-right-color: var(--navy);
  border-bottom-color: var(--navy);
}

[data-theme='dark'] .map-pin.is-featured {
  border-color: var(--surface);
}

[data-theme='dark'] .map-pin.is-featured::after {
  border-right-color: var(--surface);
  border-bottom-color: var(--surface);
}

/* No golden focus frame on map labels — click/hover already turn them green. */
gmp-advanced-marker:focus-visible .map-pin,
gmp-advanced-marker:focus .map-pin {
  outline: none;
}

/*
  The cluster bubble.

  Above the threshold the administration screen advertises — "Enabled above 40
  markers" — pins sharing a patch of screen are replaced by one of these
  carrying the count, and clicking it zooms in on what it stands for.

  Deliberately the price pill's ground and geometry with a round shape and no
  stem: it is the same family of object, but it points at a neighbourhood
  rather than at a coordinate, and a stem would claim a precision it does not
  have.
*/

.map-cluster {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transform: translate(-50%, -50%);
  min-width: 38px;
  height: 38px;
  padding: 0 8px;
  /* Same locked day-mode navy as .map-pin — clusters are the same family. */
  background: #0C2D48;
  color: #FFFFFF;
  /*
    No white collar — the client, 17 September: remove the white borderline
    on the blue circle in day mode, and keep the same look after dark.
    Transparent rather than `border: 0` so size and hit area stay with .map-pin.
  */
  border: 2px solid transparent;
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-pin);
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.map-cluster:hover {
  background: #008000;
  color: #FFFFFF;
  transform: translate(-50%, -50%) scale(1.1);
}

/*
  The pin on a one-point map — the seller profile's location.

  A teardrop rather than the price pill, because there is no price to put in it
  and nothing to click through to: it marks a place. It reuses .map-pin's
  positioning and stem, and only replaces the pill's dimensions with a circle.
*/

/*
  Violet, and a point that is actually pointed.

  The client, 31 August, of the pin on the seller's map and on the publishing
  form's: "the pin icon make in both places with sharper pointer and the
  background color (white at the moment) make the same as Previa logo violet
  background."

  Both halves are one rule because one pin serves both screens: buildPinMap sets
  pin: true for the account map and the add-listing map alike, and previa.map.js
  draws .map-pin--drop for both.

  White, because the fill was var(--navy), and --navy is theme-flipped — #0C2D48
  in the light theme and #E9EFF3 in the dark one, where primary buttons invert to
  a light surface. He was reading the site in the dark theme, so his pin was very
  nearly white. #8B008B is not a semantic token and is not themed: it is the
  Previa violet, written literally in exactly three other places — the logo tile,
  the favicon and .btn--advertise — and this is the fourth.

  --pin-ink so the head and the point cannot drift apart: the base .map-pin::after
  takes `background: inherit`, which a triangle drawn out of borders cannot use.

  The point was a 7px square rotated 45 degrees with `border-radius: 0 0 2px 0`
  rounding the very tip — a stub under a circle rather than a point. This is a
  real triangle: no radius to round off, and 9px tall against 10px wide, so it
  reads as a drop rather than as an arrowhead. bottom is one pixel inside the
  head so no seam shows between them at any zoom.

  The hover rules keep the same colour rather than reverting to --navy, which is
  what made the pin change colour under the pointer.
*/

.map-pin--drop {
  --pin-ink: #8B008B;
  width: 22px;
  height: 22px;
  min-width: 0;
  padding: 0;
  border-radius: 50%;
  cursor: default;
  background: var(--pin-ink);
}

.map-pin--drop::after {
  bottom: -8px;
  width: 0;
  height: 0;
  background: none;
  border: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 9px solid var(--pin-ink);
  border-radius: 0;
  transform: translateX(-50%);
}

.map-pin--drop:hover {
  background: var(--pin-ink);
  transform: translate(-50%, -100%);
  box-shadow: var(--shadow-pin);
}

[data-theme='dark'] .map-pin--drop:hover {
  background: var(--pin-ink);
}

/*
  The dark theme's collar is for the price pin, not for this point.

  [data-theme='dark'] .map-pin::after paints border-right and border-bottom in
  --surface, which draws two edges of a collar around a stub that is a rotated
  square. Against a triangle made of borders it does something quite different:
  the right border is one whole half of the shape, so the point came out as a
  violet left half and a solid navy right half with a hard seam down the middle.

  It outranks the rule above on specificity — two class-level selectors against
  one — so being written later is not enough; this has to name the theme too.
*/

[data-theme='dark'] .map-pin--drop::after {
  border-right-color: transparent;
  border-bottom-color: transparent;
}

/* --- Popup ---------------------------------------------------------------- */

/*
  No outline around the popup: the client asked for the frame gone. The shadow
  and the surface colour are enough to lift it off the map, and losing the
  1px rule stops the card reading as a boxed dialog.
*/

/*
  The shell is positioned by the provider in the map's own pixel space: it sets
  left/top to the marker's point, and the transform here lifts the card clear of
  the marker standing there — 42px for a price pill, more for the taller broker
  marker. Keeping the offset in CSS rather than in the script is what lets the
  marker's height change without a code change.
*/

.map-popup-shell {
  position: absolute;
  width: 280px;
  transform: translate(-50%, calc(-100% - 42px));
  z-index: 5;
}

.map-popup-shell--broker {
  transform: translate(-50%, calc(-100% - 54px));
}

.map-popup-shell__card {
  background: var(--surface);
  border: 0;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

/* The tip, drawn on the shell rather than the card: the card clips its own
   overflow so the gallery corners stay rounded, and a tip inside it would be
   clipped with them. */

.map-popup-shell::after {
  content: '';
  position: absolute;
  bottom: -6px;
  left: 50%;
  width: 12px;
  height: 12px;
  background: var(--surface);
  transform: translateX(-50%) rotate(45deg);
  border-radius: 0 0 3px 0;
}

/*
  A featured listing's preview is gold, like everything else about it.

  The client, 6 September: "if open the preview window of this ad, then this
  make golden background as well, if it is featured ad." The marker had just
  been made gold and the card that opened from it was still plain surface, so
  the one place the reader looks longest was the one place the promotion did
  not show.

  --gold-tint over --gold-border, which is the pairing .plan--premium and
  .pcard--featured already use: the tint is the ground a block of text sits on,
  where the marker's solid --gold is what a 30px pill needs to hold its own
  against map tiles. Both tokens are defined in either theme, so this is one
  rule rather than two.

  The tip under the card is painted here as well — it is drawn on the shell,
  not on the card, so it does not inherit the card's ground.
*/

.map-popup-shell--featured .map-popup-shell__card {
  background: var(--gold-tint);
  box-shadow: inset 0 0 0 1px var(--gold-border), var(--shadow-lg);
}

.map-popup-shell--featured::after {
  background: var(--gold-tint);
}

/*
  Close button.

  Two things were wrong with it, both reported by the client: it did not close
  the popup, and it did not look like a button.

  It did not close because .map-popup__media-link — the invisible link over the
  photograph that opens the listing — carries z-index: 1 and the close button
  carries none, so the link painted over it and took the click. A press on the
  cross opened the property instead of dismissing the card. It sits at 3 now,
  clear of both the link and the pager at 2.

  It did not look like a button because the map library supplied its own rule
  for its own close button, one part more specific than ours and loaded after
  it, so its transparent background and its 24px box quietly won.

  That cannot happen again: the popup is Previa's own overlay now, not the
  provider's, and this is Previa's own button inside it. No vendor stylesheet
  has anything to say about it, which is also the reason the preview was built
  as an OverlayView rather than as a Google InfoWindow — an InfoWindow brings
  its chrome and its close button back, and this exact element with it.

  The shape is the rounded rectangle the client asked for, and the fill changes
  on hover so it reads as pressable rather than as a mark printed on the photo.
*/

.map-popup-shell .map-popup__close {
  z-index: 3;
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  top: 8px;
  right: 8px;
  width: 28px;
  height: 28px;
  margin: 0;
  padding: 0;
  border: 0;
  cursor: pointer;
  font: 600 18px/1 var(--font-sans);
  color: var(--text);
  background: rgba(255, 255, 255, 0.92);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  transition: background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}

.map-popup-shell .map-popup__close:hover,
.map-popup-shell .map-popup__close:focus-visible {
  background: var(--navy);
  color: var(--on-primary);
}

[data-theme='dark'] .map-popup-shell .map-popup__close {
  background: rgba(16, 38, 53, 0.92);
}

[data-theme='dark'] .map-popup-shell .map-popup__close:hover,
[data-theme='dark'] .map-popup-shell .map-popup__close:focus-visible {
  background: var(--gold);
  color: var(--text-on-gold);
}

.map-popup__card {
  display: flex;
  flex-direction: column;
}

/* The photograph keeps the size it had — only the text below it was tightened. */

.map-popup__media {
  position: relative;
  display: block;
  aspect-ratio: 16 / 10;
  background: var(--surface-3);
  overflow: hidden;
}

.map-popup__track {
  display: flex;
  width: 100%;
  height: 100%;
  transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

.map-popup__slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Opens the listing from the photograph, under the zones and the pager so the
   controls win the outer thirds and the middle stays the link. */

.map-popup__media-link {
  position: absolute;
  inset: 0;
  z-index: 1;
}

/*
  Paging zones, the property card's .pcard__zone at popup scale.

  The client's note was that the whole photograph was a link — "at the moment
  there is only finger and no matter where click this ad will open in single
  page". These take the outer thirds back for paging and leave the middle to
  the link, and each carries a directional cursor so the behaviour is visible
  before anything is clicked. They stop above the pager so the capsule below
  them stays reachable.
*/

.map-popup__zone {
  position: absolute;
  top: 0;
  bottom: 34px;
  width: 30%;
  z-index: 2;
  padding: 0;
  border: 0;
  background: none;
}

.map-popup__zone--prev {
  left: 0;
  cursor: w-resize;
}

.map-popup__zone--next {
  right: 0;
  cursor: e-resize;
}

/*
  Same pager as the property card: green arrows either side of green dots, and
  the same fade — it appears when the pointer is over the photograph rather than
  sitting on it permanently. That is the "if move the mouse to the right side of
  the image then these left-right arrows will come" the client described.
*/

.map-popup__pager {
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform: translateX(-50%);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px 4px;
  border-radius: 999px;
  background: rgba(8, 20, 32, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.map-popup__media:hover .map-popup__pager,
.map-popup__media:focus-within .map-popup__pager {
  opacity: 1;
}

/* No hover on touch: show it rather than never showing it. */

@media (hover: none) {
  .map-popup__pager {
    opacity: 1;
  }
}

.map-popup__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--success-on-dark);
  cursor: pointer;
}

.map-popup__arrow svg {
  width: 14px;
  height: 14px;
}

.map-popup__arrow:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
}

.map-popup__arrow:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 1px;
}

/*
  Dot strip. `touch-action: pan-y` is the load-bearing line, as on the card: the
  browser may still scroll vertically from here, but sideways movement is ours,
  and it is only claimed once the gesture has been confirmed horizontal.
*/

.map-popup__dots {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 2px;
  margin: -4px 0;
  /* the extra grab height costs the capsule nothing */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
  cursor: grab;
}

.map-popup__dots.is-holding {
  cursor: grabbing;
}

.map-popup__dot {
  width: 6px;
  height: 6px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  cursor: inherit;
  transition: background-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.map-popup__dot:hover {
  background: rgba(255, 255, 255, 0.85);
}

.map-popup__dot.is-on {
  background: var(--success-on-dark);
  transform: scale(1.25);
}

.map-popup__dot:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* Held: the active dot swells while the press lasts and settles back on
   release, and the capsule is pinned open — a scrub easily carries the pointer
   off the photograph, and the hover rule would otherwise fade the control away
   in the middle of the gesture. */

.map-popup__dots.is-holding .map-popup__dot.is-on {
  transform: scale(2.1);
}

.map-popup__media:has(.map-popup__dots.is-holding) .map-popup__pager {
  opacity: 1;
}

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

/*
  Compact text block. The gaps between price, title and location were doing the
  work of separating three lines that belong together, and made the popup tall
  enough to cover a useful part of the map. They are now tucked up, with the
  only real breathing space above the button.
*/

.map-popup__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}

/*
  The old provider's stylesheet set an 18px margin on every paragraph inside a
  popup, and those 36px per line were most of the popup's height. That rule is
  gone with it, but the reset stays: the browser's own paragraph margins would
  do the same thing on a smaller scale, and the column gap above is the only
  spacing this block should have.
*/

.map-popup-shell .map-popup__body > * {
  margin: 0;
}

/* One exception: the button wants air above it. */

.map-popup-shell .map-popup__body > .map-popup__facts {
  margin: 4px 0 var(--sp-3);
}

/*
  A map library that paints every anchor inside itself with its own link colour
  left "View property" in mid-blue on navy — well under the contrast floor.
  Google styles anchors inside .gm-style too, so the button keeps saying which
  ink it wants rather than trusting whichever provider is underneath.
*/

.map-popup-shell .map-popup__body .btn--primary {
  color: var(--on-primary);
}

.map-popup__price {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  margin: 0;
}

.map-popup__title {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text) !important;
  /* Wrapped titles sit tight — two rows without the air of body line-height. */
  line-height: 1.1;
  text-decoration: none !important;
  /* Two lines at most, so a long title cannot stretch the popup down the map. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

a.map-popup__title:hover {
  color: var(--teal) !important;
}

.map-popup__meta {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin: 0;
}

.map-popup__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 10px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin: 0;
}

/* --- Previa's own map controls -------------------------------------------- */

.map-controls {
  position: absolute;
  right: var(--sp-4);
  bottom: var(--sp-8);
  z-index: 500;
  /* above the map's own panes, below drawers */
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.map-ctrl {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--text);
  box-shadow: var(--shadow-sm);
  transition: background-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.map-ctrl:hover {
  background: var(--surface-2);
}

.map-ctrl:active {
  transform: translateY(1px);
}

.map-ctrl:focus-visible {
  box-shadow: var(--focus-ring);
}

.map-edge-btn {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
  z-index: 6;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 48px;
  padding: 0 var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 0;
  border-radius: 0 var(--r-md) var(--r-md) 0;
  box-shadow: var(--shadow-md);
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* Below the split breakpoint the map becomes a toggled full-width view. */

@media (max-width: 900px) {
  .map-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .map-shell .map-pane {
    display: none;
  }

  .map-shell.is-map-visible .map-results {
    display: none;
  }

  .map-shell.is-map-visible .map-pane {
    display: block;
  }

  .map-results {
    border-right: 0;
  }
}

.map-state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  padding: var(--sp-7);
  text-align: center;
  background: var(--surface-2);
  z-index: 8;
}

/* Development note on the mock map — outside the interactive surface. */

.mockmap__note {
  position: absolute;
  left: var(--sp-4);
  bottom: var(--sp-3);
  padding: 5px 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  box-shadow: var(--shadow-xs);
}

/* Floating toolbar over the full-screen map. */

.map-toolbar {
  position: absolute;
  top: var(--sp-4);
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  max-width: calc(100% - 32px);
  overflow-x: auto;
  scrollbar-width: none;
}

.map-toolbar::-webkit-scrollbar {
  display: none;
}

.map-toolbar__count {
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 0 var(--sp-2);
  white-space: nowrap;
}

.map-back-btn {
  position: absolute;
  top: var(--sp-4);
  left: 50%;
  transform: translateX(-50%);
  z-index: 7;
  box-shadow: var(--shadow-lg);
}

/* The overlay variant is used on the map screens, where a permanently docked
   filter column would create a third competing panel. */

.filter-panel.filter-panel--overlay {
  position: fixed;
  /* From the viewport top (~2mm inset), not under the site header. */
  top: 2mm;
  left: 0;
  bottom: auto;
  /* Above the dismiss backdrop and above the map pane (map stays clickable). */
  z-index: calc(var(--z-drawer) + 2);
  width: min(380px, 90vw);
  /* Explicit height so overflow-y scrolls like the listings filter panel. */
  height: calc(100dvh - 4mm);
  max-height: calc(100dvh - 4mm);
  /* Rounded on the right, top and bottom — the two corners that meet the page,
     as the client asked. The left pair are flush with the screen edge. */
  border-radius: 0 var(--r-lg) var(--r-lg) 0;
  border-left: 1px solid #6F006F;
  box-shadow: var(--shadow-panel);
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform var(--dur-slow) var(--ease), visibility var(--dur-slow);
  overflow-x: clip;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: #6F006F transparent;
}

.filter-panel.filter-panel--overlay.is-open {
  transform: translateX(0);
  visibility: visible;
}

.filter-panel.filter-panel--overlay .filter-panel__header {
  border-radius: 0 var(--r-lg) 0 0;
}

.filter-panel.filter-panel--overlay .filter-panel__footer {
  border-radius: 0 0 var(--r-lg) 0;
}

/*
  Map Filters: dismiss on a click anywhere except the Google map.
  Backdrop covers the page at every width (listings only show it under 1024px).
  The map pane sits above the backdrop so pan/zoom/pin clicks keep the drawer
  open; the filter panel sits above both.
*/
body:has(.filter-panel--overlay.is-open) .filter-backdrop {
  display: block;
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  background: var(--overlay);
}

body:has(.filter-panel--overlay.is-open) .map-pane {
  z-index: calc(var(--z-drawer) + 1);
}

.map-results__mobile-bar {
  display: none;
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--border);
  background: var(--surface);
}

@media (max-width: 900px) {
  .map-results__mobile-bar {
    display: block;
  }

  .map-results__collapse {
    display: none;
  }
}

@media (min-width: 901px) {
  .map-back-btn {
    display: none !important;
  }
}

/* ==========================================================================
   Property detail
   ========================================================================== */

.gallery {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--sp-2);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.gallery__main {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--surface-3);
}

.gallery__main img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gallery--empty {
  grid-template-columns: 1fr;
}

.gallery--empty .gallery__main img {
  object-fit: contain;
}

.gallery__side {
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: var(--sp-2);
}

.gallery__thumb {
  position: relative;
  overflow: hidden;
  background: var(--surface-3);
}

.gallery__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease);
}

.gallery__thumb:hover img {
  transform: scale(1.05);
}

.gallery__more {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: rgba(8,20,32,0.62);
  color: #fff;
  font-weight: 600;
  font-size: var(--fs-sm);
}

/* Without an explicit size the inline SVG fills the whole overlay. */

.gallery__more svg {
  width: 18px;
  height: 18px;
  flex: none;
}

@media (max-width: 780px) {
  .gallery {
    grid-template-columns: 1fr;
  }

  .gallery__side {
    grid-template-rows: none;
    grid-template-columns: 1fr 1fr;
  }
}

/*
  The gallery's "5 photos" button.

  "The background color of this button is bad, make it better. Make it the same
  half-way transparent like the choose your market button in the frontpage
  banner." So it takes that chip's treatment exactly — a dark navy at 55%, a
  white hairline, white lettering and the same blur — instead of .btn--on-image,
  whose 14% white wash turned milky over a bright photograph. Written here
  rather than on .btn--on-image because that class is also used on buttons that
  sit on a panel rather than on a photograph.
*/

.gallery__open {
  position: absolute;
  right: var(--sp-4);
  bottom: var(--sp-4);
  background: rgba(12, 45, 72, 0.55);
  border: none;
  color: #fff;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.gallery__open:hover:not(:disabled) {
  background: rgba(12, 45, 72, 0.72);
  color: #fff;
}

.detail-price {
  font-size: clamp(1.75rem, 1.4rem + 1.4vw, 2.25rem);
  font-weight: 700;
  letter-spacing: var(--ls-tight);
  line-height: 1.1;
}

/* The detail-page map is a fixed-height panel rather than a full screen. */

.detail-map {
  position: relative;
  height: 380px;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.detail-map .map-pane {
  position: absolute;
  inset: 0;
}

/* Lightbox */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-8) var(--sp-9);
  background: rgba(6, 14, 22, 0.94);
}

.lightbox__stage {
  max-width: 100%;
  max-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox__stage img {
  max-width: 100%;
  max-height: calc(100vh - 128px);
  object-fit: contain;
  border-radius: var(--r-md);
}

.lightbox__close {
  position: absolute;
  top: var(--sp-5);
  right: var(--sp-5);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  color: #fff;
}

.lightbox__close:hover {
  background: rgba(255, 255, 255, 0.22);
}

.lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  color: #fff;
}

.lightbox__nav:hover {
  background: rgba(255, 255, 255, 0.22);
}

.lightbox__nav--prev {
  left: var(--sp-5);
}

.lightbox__nav--next {
  right: var(--sp-5);
}

.lightbox__counter {
  position: absolute;
  bottom: var(--sp-5);
  color: rgba(255,255,255,0.86);
  font-size: var(--fs-sm);
}

@media (max-width: 640px) {
  .lightbox {
    padding: var(--sp-6) var(--sp-3);
  }

  .lightbox__nav {
    width: 40px;
    height: 40px;
  }

  .lightbox__nav--prev {
    left: var(--sp-2);
  }

  .lightbox__nav--next {
    right: var(--sp-2);
  }
}

.detail-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: var(--sp-4);
  align-items: start;
}

@media (max-width: 1080px) {
  .detail-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

.detail-aside {
  position: sticky;
  top: calc(var(--header-h) + var(--sp-3));
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/*
  Single ad: seller card sticks to the top of the page once you scroll, and the
  floating menu button sits on its top-right corner (overlays it) instead of
  floating in the empty strip above it.
*/
@media (min-width: 1081px) {
  .property-detail .detail-aside {
    top: max(var(--sp-4), env(safe-area-inset-top));
    z-index: 1;
  }

  body:has(.property-detail) .floating-menu-shell {
    z-index: calc(var(--z-header) + 1);
  }
}

@media (max-width: 1080px) {
  .detail-layout > .detail-aside {
    position: static;
    width: 320px;
    max-width: calc(100% - 64px);
    justify-self: center;
  }
}

.fact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--sp-2);
}

.property-detail .display--h2,
.property-detail .display--h3 {
  margin: 0;
  line-height: 1.15;
}

.property-detail .prose {
  gap: var(--sp-2);
  line-height: 1.35;
}

.property-detail .listing-description {
  line-height: 1.2;
}

.property-detail .feature-list {
  gap: var(--sp-1);
}

.property-detail #seller-contact.card {
  background: var(--surface-2);
}

.property-detail #seller-contact.card .card__body {
  padding: var(--sp-3);
  gap: var(--sp-2);
}

/*
  Active in + languages of communication: pull up under the company banner and
  pack the two blocks tight (client, 22 September).
*/
.property-detail #seller-contact .seller-active {
  margin-top: calc(-1 * var(--sp-2) + 4px);
}

.property-detail #seller-contact .seller-active + .seller-active {
  margin-top: calc(-1 * var(--sp-2) + 2px);
}

.property-detail #seller-contact .seller-active__title {
  margin: 0 0 2px;
  line-height: 1.1;
}

.property-detail #seller-contact .seller-active__list {
  gap: 2px 4px;
}

.property-detail #seller-contact .seller-active .tagpick__tag,
.property-detail #seller-contact .seller-active .tagpick__tag--static {
  padding: 2px 6px;
  gap: 4px;
  line-height: 1.15;
  font-size: var(--fs-2xs);
}

.property-detail .detail-aside .card .card__body.stack,
.property-detail .detail-aside .stack {
  gap: var(--sp-2);
}

.property-detail .pcard__location {
  margin: 0;
}

.property-detail .pcard__location > svg {
  color: #6F006F;
  stroke: #6F006F;
}

/* Crumbs flush under the header; gallery tight under the trail.

   Extra left gutter: on a regular .container the trail sits one gutter left of
   the header logo (see --page-width vs container padding). Listings avoid that
   with .search-crumb's padding-left; match that inset here so the trail lines
   up with "Previa" the same way. */
.property-detail__crumbs {
  padding-top: var(--sp-1);
  padding-bottom: var(--sp-1);
  padding-left: calc(var(--gutter) * 2);
}

.property-detail__gallery {
  padding-top: 0;
}

.property-detail__main.stack.stack--sm {
  gap: var(--sp-2);
}

.property-detail__main > header.stack {
  gap: var(--sp-2);
  margin-bottom: 0;
}

.property-detail__facts {
  margin-top: 0;
  padding-top: 0;
}

.property-detail__type {
  text-transform: lowercase;
}

.fact {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.fact__label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 6px;
}

.fact__label svg {
  width: 14px;
  height: 14px;
}

.fact__value {
  font-size: var(--fs-lg);
  font-weight: 600;
}

.feature-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--sp-3);
  list-style: none;
}

.feature-list li {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
}

.feature-list svg {
  width: 16px;
  height: 16px;
  color: #008000;
  flex: none;
}

.prose {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

.prose p {
  max-width: 68ch;
}

.prose--article {
  font-size: var(--fs-lg);
}

.prose--article p {
  max-width: 66ch;
}

/* ==========================================================================
   Add-listing wizard
   ========================================================================== */

/*
  The wizard and its waypoint rail.

  The rail is the mirror image of the search page's filter panel: the same two
  states, the same component in previa.js, the same rail and edge-tab classes —
  only hinged on the right. The client asked for exactly that, because the rail
  used to compress against the right edge as the window narrowed instead of
  getting out of the way.
*/

.wizard {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--sp-8);
  align-items: start;
  transition: grid-template-columns var(--dur-slow) var(--ease);
}

.wizard.is-collapsed {
  grid-template-columns: minmax(0, 1fr) 0;
  gap: 0;
}

.wizard.is-collapsed .stepper {
  display: none;
}

/*
  Cut down on the client's 19 August note: "in the add listing page this right
  side progress bar, at first make this title 'your progress' more up and cut
  it's section together, there is too much empty room at the moment."

  Two figures did it. The panel's own padding drops a step, which takes the
  title up to the panel's top edge, and the twelve waypoints below lose the
  7px of air each was carrying above and below its 25px marker — see
  .stepper__item. Nothing is smaller: the type, the markers and the connector
  are what they were, and only the empty room between them is gone.

  21 September: the header itself is halved again (tight padding, short drop
  under the rule) and painted Previa logo violet #6F006F with white copy —
  same bar the search filter panel uses.
*/

.stepper {
  position: sticky;
  top: calc(var(--header-h) + var(--sp-5));
  max-height: calc(100vh - var(--header-h) - var(--sp-9));
  overflow-x: clip;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* No top pad — violet header sits on the panel's top edge (filter panel pattern). */
  padding: 0 var(--sp-4) var(--sp-4);
  background: var(--surface);
  border: 1px solid #6F006F;
  border-radius: var(--r-lg);
}

/* Sticky, so the way out of the panel stays on screen however far the list is
   scrolled. Negative side margins let it span the panel's full width. */

.stepper__header {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: 0 calc(var(--sp-4) * -1) var(--sp-2);
  padding: 4px var(--sp-4);
  min-height: 0;
  background: #6F006F;
  color: #fff;
  border-bottom: 1px solid #6F006F;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}

.stepper__header .overline {
  margin: 0;
  color: #fff;
}

.stepper__header .filter-panel__collapse,
.stepper__header .filter-panel__close {
  color: #fff;
}

.stepper__header .filter-panel__collapse svg,
.stepper__header .filter-panel__close svg {
  color: #fff;
}

.wizard .filter-rail {
  display: none;
}

.wizard.is-collapsed .filter-rail {
  display: block;
}

/* Same clearance as the panel it replaces. */

.wizard .filter-rail--right {
  top: var(--floating-menu-band);
}

@media (max-width: 1024px) {
  .wizard {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-5);
  }

  .wizard .filter-rail {
    display: none;
  }

  .wizard .filter-fab {
    display: inline-flex;
  }

  /* Off-canvas on the right. The list keeps its vertical form here — it is a
     full-height panel, not a strip above the form, so there is no reason to
     flatten it into a horizontal scroller as it used to be. */

  .wizard .stepper {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: calc(var(--z-drawer) + 1);
    width: min(320px, 86vw);
    max-height: none;
    border-radius: var(--r-lg) 0 0 var(--r-lg);
    border-right: 0;
    box-shadow: var(--shadow-panel);
    transform: translateX(100%);
    visibility: hidden;
    transition: transform var(--dur-slow) var(--ease), visibility var(--dur-slow);
  }

  .wizard .stepper.is-open {
    transform: translateX(0);
    visibility: visible;
  }

  .wizard .stepper__header {
    border-radius: var(--r-lg) 0 0 0;
  }

  .wizard .filter-panel__collapse {
    display: none;
  }

  .wizard .filter-panel__close {
    display: inline-flex;
  }

  .wizard .filter-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: var(--z-drawer);
    background: var(--overlay);
  }
}

.stepper__list {
  display: flex;
  flex-direction: column;
  list-style: none;
}

.stepper__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 3px 0;
}

/* connector line between compact step markers */

.stepper__item::after {
  content: '';
  position: absolute;
  left: 12px;
  top: 28px;
  bottom: -3px;
  width: 1px;
  background: var(--border);
}

.stepper__item:last-child::after {
  display: none;
}

/*
  "Then the green there make #008000."

  A token of its own rather than a change to --success: the client named this
  colour for this rail, and --success is the site's status green — it paints
  the paid badge, the saved-listing notice and the published state, none of
  which were asked to move. Declared once, outside both theme blocks, because
  a value the client picked by eye does not flip at night.
*/

.stepper {
  --stepper-done: #008000;
}

/* "And if smt is red there, the red make #ff0000." Scoped to the rail like the
   green above it: --error paints form messages and destructive buttons, none
   of which were named. */

.stepper {
  --stepper-error: #ff0000;
}

/* Current waypoint: theme navy in light; in dark --navy flips to near-white,
   so the client asked for gold #DAA520 instead (21 September). */
.stepper {
  --stepper-current: var(--navy);
}

[data-theme='dark'] .stepper {
  --stepper-current: #DAA520;
}

.stepper__item.is-done::after {
  background: var(--stepper-done);
}

.stepper__marker {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 25px;
  height: 25px;
  /* deliberately smaller than the reference */
  border: 1.5px solid var(--border-strong);
  border-radius: 50%;
  background: var(--surface);
  font-size: var(--fs-2xs);
  font-weight: 700;
  color: var(--text-muted);
}

.stepper__marker svg {
  width: 13px;
  height: 13px;
}

.stepper__item.is-done .stepper__marker {
  background: var(--stepper-done);
  border-color: var(--stepper-done);
  color: #fff;
}

.stepper__item.is-current .stepper__marker {
  background: var(--stepper-current);
  border-color: var(--stepper-current);
  color: var(--on-primary);
}

.stepper__item.is-error .stepper__marker {
  background: var(--stepper-error);
  border-color: var(--stepper-error);
  color: #fff;
}

.stepper__label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.stepper__item.is-current .stepper__label {
  color: var(--text);
  font-weight: 600;
}

.stepper__item.is-error .stepper__label {
  color: var(--stepper-error);
}

.autosave {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: #008000;
}

.autosave svg {
  width: 14px;
  height: 14px;
}

.autosave.is-saving {
  color: #8B008B;
}

.autosave.is-saved {
  color: #008000;
}

.autosave.is-error {
  color: var(--error);
}

/* Add-listing: no page title / section counter / top progress bar — only the
   save row under the site header, then the form (client, 21 September). */
.listing-compose {
  padding-block: var(--sp-3) 0;
}

.listing-compose__actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  /* Content box is one --gutter wider than the header inner — same pull-in as
     .my-listings-bar__search so save & exit lines up with header add listing. */
  margin-right: var(--gutter);
}

.wizard-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.wizard-card__head {
  padding: var(--sp-4) var(--sp-5) 0;
}

.wizard-card__body {
  padding: var(--sp-4) var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: 0;
}

.wizard-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-6);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

@media (max-width: 560px) {
  .wizard-card__foot {
    flex-direction: column-reverse;
  }

  .wizard-card__foot .btn {
    width: 100%;
  }
}

/* Selectable option tiles (deal type, property category) */

.option-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-3);
}

/*
  Deal type / property category tiles — same selection language as the search
  menu's .type-check: green frame + corner tick, no fill change on hover or
  when chosen.
*/

.option-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-5) var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  text-align: center;
  cursor: pointer;
  color: var(--text-muted);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.option-tile:hover {
  border-color: var(--border-strong);
  background: var(--surface);
}

.option-tile.is-disabled {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

.option-tile input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.option-tile:has(input:checked) {
  border-color: #008000;
  background: var(--surface);
  box-shadow: none;
  color: var(--text-muted);
}

[data-theme='dark'] .option-tile:has(input:checked) {
  border-color: #008000;
  background: var(--surface);
  box-shadow: none;
  color: var(--text-muted);
}

.option-tile:has(input:focus-visible) {
  box-shadow: var(--focus-ring);
}

.option-tile svg {
  width: 26px;
  height: 26px;
  color: var(--slate);
}

.option-tile:has(input:checked) > svg {
  color: var(--slate);
}

[data-theme='dark'] .option-tile:has(input:checked) > svg {
  color: var(--slate);
}

.option-tile__label {
  font-size: var(--fs-sm);
  font-weight: 600;
}

.option-tile__hint {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.option-tile__tick {
  position: absolute;
  top: -6px;
  right: -6px;
  display: none;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  background: #008000;
  color: #fff;
  border: 2px solid var(--surface);
  border-radius: 50%;
  z-index: 1;
}

.option-tile__tick .icon,
.option-tile__tick svg {
  width: 10px;
  height: 10px;
  color: #fff;
  stroke-width: 3.5;
}

.option-tile:has(input:checked) .option-tile__tick {
  display: inline-flex;
}

.option-tile:has(input:checked) .option-tile__tick svg {
  color: #fff;
}

.stepper__foot {
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}

.stepper__foot p {
  color: #008000;
}

.stepper__foot a,
.stepper__drafts {
  color: #8B008B;
  font-weight: 600;
}

.stepper__foot a:hover,
.stepper__drafts:hover {
  color: #6F006F;
}

@media (max-width: 1024px) {
  .stepper__foot {
    display: none;
  }
}

/* Radio rows used for location precision and package choice */

.option-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.option-row:hover {
  border-color: var(--border-strong);
  background: var(--surface-2);
}

/* Payment methods: background stays put on hover and when chosen. */
.option-row.pay-option:hover {
  background: var(--surface);
}

.option-row input {
  flex: none;
  appearance: none;
  width: 20px;
  height: 20px;
  margin-top: 2px;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: var(--surface);
}

.option-row input:checked {
  background: #008000;
  border-color: #008000;
  box-shadow: inset 0 0 0 4px var(--surface);
}

.option-row input:focus-visible {
  box-shadow: var(--focus-ring);
}

.option-row:has(input:checked) {
  border-color: #008000;
  background: var(--navy-tint);
  box-shadow: 0 0 0 1px #008000;
}

.option-row.pay-option:has(input:checked) {
  background: var(--surface);
}

[data-theme='dark'] .option-row:has(input:checked) {
  border-color: #008000;
  background: var(--surface-3);
  box-shadow: 0 0 0 1px #008000;
}

[data-theme='dark'] .option-row.pay-option:has(input:checked) {
  background: var(--surface);
}

.option-row > span {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* Title + lede under "Preview of your listing", tight like a field hint. */

.listing-preview-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.listing-preview-lede {
  margin: 0;
  line-height: 1.25;
}

.preview-frame {
  padding: var(--sp-5);
  background: var(--bg);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-lg);
}

.publish-done {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4) var(--sp-4);
  text-align: center;
}

/* Green rounded tile (draft button) with violet check-circle (publish button). */

.publish-done__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  background: #008000;
  border-radius: var(--r-md);
  color: #8B008B;
}

.publish-done__mark svg {
  width: 28px;
  height: 28px;
}

/* Photo uploader mock */

.uploader {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-9) var(--sp-5);
  border: 2px dashed var(--border-strong);
  border-radius: var(--r-lg);
  background: var(--surface-2);
  text-align: center;
}

/* Upload mark: rounded rectangle, green tile, white arrow. */
.uploader .empty__icon {
  background: #008000;
  border-radius: var(--r-md);
  color: #fff;
}

.uploader .empty__icon svg {
  color: #fff;
  stroke: #fff;
}

.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--sp-3);
}

.photo-thumb {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface-3);
}

.photo-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo-thumb__badge {
  position: absolute;
  left: 6px;
  top: 6px;
  padding: 2px 7px;
  background: var(--gold);
  color: var(--text-on-gold);
  border-radius: var(--r-sm);
  font-size: 10px;
  font-weight: 700;
}

/*
  Tile controls — remove, and the two reorder arrows below.

  Both sit on top of a photograph, so neither can rely on the page background
  for contrast. They share one treatment: an opaque disc with a hairline ring
  and a shadow, which separates the control from whatever is behind it, plus a
  clear hover state. The client's note was that in dark mode the pale
  translucent discs all but disappeared against a bright photo and gave no
  feedback on hover, so the dark palette is defined outright rather than left
  to shine through from the image.
*/

.photo-thumb__remove,
.photo-thumb__move button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid rgba(8, 20, 32, 0.16);
  border-radius: 50%;
  background: #FFFFFF;
  box-shadow: 0 1px 3px rgba(8, 20, 32, 0.35);
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

[data-theme='dark'] .photo-thumb__remove,
[data-theme='dark'] .photo-thumb__move button {
  border-color: rgba(255, 255, 255, 0.28);
  background: #16232F;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
}

.photo-thumb__remove {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 20px;
  height: 20px;
  border: 0;
  color: var(--error);
}

.photo-thumb__remove:hover {
  background: #FF0000;
  border-color: transparent;
  color: #FFFFFF;
}

[data-theme='dark'] .photo-thumb__remove {
  color: #FF8A80;
  border: 0;
}

[data-theme='dark'] .photo-thumb__remove:hover {
  background: #FF0000;
  border-color: transparent;
  color: #FFFFFF;
}

.photo-thumb__remove svg {
  width: 12px;
  height: 12px;
}

/* ==========================================================================
   Dashboard / stats
   ========================================================================== */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--card-gap);
}

.stat {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.stat__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

.stat__label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.stat__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  background: var(--navy-tint);
  border-radius: var(--r-md);
  color: var(--slate);
}

[data-theme='dark'] .stat__icon {
  background: var(--surface-3);
}

.stat__icon svg {
  width: 18px;
  height: 18px;
}

.stat__value {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: var(--ls-tight);
}

.stat__foot {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.stat__delta {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-weight: 600;
}

.stat__delta--up {
  color: var(--success);
}

.stat__delta--down {
  color: var(--error);
}

/* No change against the previous period is neither good news nor bad, so it
   takes the muted ink rather than borrowing one of the two colours. */

.stat__delta--flat {
  color: var(--text-muted);
}

/*
  Admin payments tiles: ~half the up/down size, text packed tight —
  client, 22 September.
*/
.stat-grid--compact {
  /* Four tracks so three tiles keep the old four-across width. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-3);
  align-items: stretch;
}

.stat-grid--compact .stat__status-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  font-size: var(--fs-xs);
  line-height: 1.15;
  color: var(--text-muted);
}

.stat-grid--compact .stat__status-list li {
  margin: 0;
}

.stat-grid--compact .stat--ledger-status {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-self: stretch;
}

.stat-grid--compact .stat--ledger-status .stat__status-list {
  margin-block: auto;
}

.stat-grid--compact .stat {
  gap: 2px;
  padding: calc(var(--sp-5) / 2) var(--sp-4);
}

.stat-grid--compact .stat__head {
  gap: var(--sp-2);
  margin: 0;
}

.stat-grid--compact .stat__label {
  font-size: var(--fs-xs);
  line-height: 1.2;
}

.stat-grid--compact .stat__icon {
  width: 22px;
  height: 22px;
  border-radius: var(--r-sm);
}

.stat-grid--compact .stat__icon svg {
  width: 12px;
  height: 12px;
}

.stat-grid--compact .stat__value {
  font-size: 1.125rem;
  line-height: 1.15;
  margin: 0;
}

.stat-grid--compact .stat__foot {
  margin: 0;
  gap: var(--sp-1);
  line-height: 1.25;
}

/* Simple CSS charts — no charting library needed for mock data. */

.bars {
  display: flex;
  align-items: stretch;
  gap: var(--sp-3);
  height: 180px;
  padding-top: var(--sp-4);
}

.bars__col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  height: 100%;
  min-height: 0;
}

.bars__track {
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
}

.bars__bar {
  width: 100%;
  max-width: 46px;
  background: var(--navy);
  border-radius: var(--r-sm);
  min-height: 4px;
}

[data-theme='dark'] .bars__bar {
  background: var(--slate);
}

.bars__bar--gold {
  background: var(--gold);
}

.bars__label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.legend {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.legend__row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
}

.legend__swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  flex: none;
}

.legend__bar {
  flex: 1;
  height: 8px;
  background: var(--surface-3);
  border-radius: var(--r-pill);
  overflow: hidden;
}

.legend__fill {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
}

.legend__value {
  font-size: var(--fs-sm);
  font-weight: 600;
  min-width: 48px;
  text-align: right;
}

.activity {
  display: flex;
  flex-direction: column;
}

.activity--scroll {
  max-height: calc(15 * (1.15em + 2 * var(--sp-1)));
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: var(--sp-2);
}

.activity--compact .activity__row {
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) 0;
  line-height: 1.2;
}

.activity--compact .activity__text {
  font-size: var(--fs-xs);
  line-height: 1.2;
}

.activity--compact .activity__time {
  line-height: 1.2;
}

.card__body--flush-top {
  padding-top: 0;
}

.activity__row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--border);
}

.activity__row:last-child {
  border-bottom: 0;
}

.activity__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-muted);
}

.activity__icon svg {
  width: 15px;
  height: 15px;
}

.activity__icon--approve {
  background: var(--success-bg);
  color: var(--success);
}

.activity__icon--reject, .activity__icon--delete {
  background: var(--error-bg);
  color: var(--error);
}

.activity__icon--create {
  background: var(--info-bg);
  color: var(--info);
}

.activity__text {
  flex: 1;
  font-size: var(--fs-sm);
}

.activity__time {
  font-size: var(--fs-xs);
  color: var(--text-subtle);
  white-space: nowrap;
}

/* ==========================================================================
   Account area
   ========================================================================== */

/* Compact horizontal row used in dashboards and tables. */

.mini-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.mini-row__img {
  width: 72px;
  height: 54px;
  flex: none;
  object-fit: cover;
  border-radius: var(--r-sm);
  background: var(--surface-3);
}

.mini-row__text {
  flex: 1;
  min-width: 0;
}

.mini-row__text a:hover {
  color: var(--slate);
}

.draft-row__img {
  width: 120px;
  height: 90px;
  flex: none;
  object-fit: cover;
  border-radius: var(--r-md);
  background: var(--surface-3);
}

.draft-row__img--empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-subtle);
  border: 1px dashed var(--border-strong);
}

.draft-row__img--empty svg {
  width: 24px;
  height: 24px;
}

/* Notifications */

.notif-list {
  display: flex;
  flex-direction: column;
}

.notif {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
  transition: background-color var(--dur-fast) var(--ease);
}

.notif:last-child {
  border-bottom: 0;
}

.notif:hover {
  background: var(--surface-2);
}

.notif.is-unread {
  background: var(--navy-tint);
}

[data-theme='dark'] .notif.is-unread {
  background: var(--surface-3);
}

/* One notification and the tick that reads it.
   The button cannot live inside the link — a button nested in an anchor is not
   markup a browser will build — so the row is the flex container and the rule,
   the hover and the unread tint move up onto it. */

.notif-row {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--border);
  transition: background-color var(--dur-fast) var(--ease);
}

.notif-row:last-child {
  border-bottom: 0;
}

.notif-row:hover {
  background: var(--surface-2);
}

.notif-row.is-unread {
  background: var(--navy-tint);
}

[data-theme='dark'] .notif-row.is-unread {
  background: var(--surface-3);
}

.notif-row .notif,
.notif-row .notif:hover,
.notif-row .notif.is-unread {
  flex: 1;
  min-width: 0;
  border-bottom: 0;
  background: transparent;
}

.notif-row__read {
  flex: none;
  margin-inline-end: var(--sp-4);
}

.notif__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--slate);
}

.notif__icon svg {
  width: 17px;
  height: 17px;
}

.notif__icon--match {
  background: var(--info-bg);
  color: var(--info);
}

.notif__icon--payment {
  background: var(--success-bg);
  color: var(--success);
}

.notif__icon--listing {
  background: var(--gold-tint);
  color: var(--gold-hover);
}

.notif__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.notif__title {
  font-size: var(--fs-sm);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.notif__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--gold);
  flex: none;
}

.notif__text {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  line-height: 1.5;
}

.notif__time {
  font-size: var(--fs-xs);
  color: var(--text-subtle);
  white-space: nowrap;
  flex: none;
}

/* Settings and cookie toggle rows */

.cookie-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  padding-bottom: var(--sp-5);
  border-bottom: 1px solid var(--border);
}

.cookie-row:last-of-type {
  border-bottom: 0;
  padding-bottom: 0;
}

@media (max-width: 560px) {
  .cookie-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-3);
  }
}

/* Billing */

.pay-method {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.pay-method__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: var(--surface-3);
  border-radius: var(--r-sm);
  color: var(--slate);
}

/* Checkout */

.checkout-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--sp-7);
  align-items: start;
}

@media (max-width: 900px) {
  .checkout-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/*
  The summary stays on screen.

  The client, 7 September: "make that the order summary on the right side, that
  it stays there on top right even if user scrolls down, so that he always sees
  what he has there."

  It was already position: sticky and it had never once stuck, for a reason
  that is not visible in the rule itself: the declaration was on the card, and
  the card's parent — the <aside> — is a grid item under align-items: start, so
  it is exactly as tall as the card inside it. A sticky element cannot travel
  past its own parent's box, and there was no box to travel through.

  So the aside is the sticky element now. Its containing block is the grid,
  the grid is as tall as the form column beside it, and that is the distance
  the card can hold itself still for. Below 900px the two columns become one
  and the summary goes back to standing where it falls, which is right when it
  is under the form rather than beside it.
*/

.checkout-aside {
  position: sticky;
  top: calc(var(--header-h) + var(--sp-5));
}

@media (max-width: 900px) {
  .checkout-aside {
    position: static;
  }
}

/*
  Order summary (Resumen): narrow violet header + logo-violet frame —
  same bar the listings Filters panel and add-listing stepper use
  (client, 22 September).
*/
.checkout-summary {
  border-color: #6F006F;
  overflow: hidden;
}
.checkout-summary > .card__header {
  padding: 2px var(--sp-4);
  min-height: 0;
  background: #6F006F;
  border-bottom-color: #6F006F;
}
.checkout-summary > .card__header .card__title {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.1;
  color: #fff;
}

/* Choose package: same short violet header bar as Resumen.
   overflow clips the bar to the card's rounded corners. */
.checkout-packages {
  border-color: #6F006F;
  overflow: hidden;
}
.checkout-packages > .card__header {
  padding: 2px var(--sp-4);
  min-height: 0;
  background: #6F006F;
  border-bottom-color: #6F006F;
}
.checkout-packages > .card__header .card__title {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.1;
  color: #fff;
}

/* A promotion the buyer has added, between the package rows and the total. */

.checkout-extra {
  font-size: var(--fs-sm);
}

.checkout-total {
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-lg);
}

/*
  The package cards on the checkout screen.

  The client, 7 September: "in the checkout page the price packages are not
  good, there is not to see what this package contains, so replace these
  packages with the ones in the 'Prices' page … Then in checkout page make this
  'choose' button on bottom of each package where it was."

  So the cards are the Prices page's .plan — everything above is shared and
  none of it is touched here. What this block adds is only what a checkout
  needs and a price list does not: the grid the cards sit in inside a card body
  narrower than a full page, the radio that carries the choice, the button at
  the foot, and the ring that says which one is selected.

  The card is a <label>, so its contents are spans rather than headings and
  list items — see the note in the template. The two rules below give those
  spans the block layout .plan's own children have.
*/

.checkout-plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(216px, 1fr));
  gap: var(--card-gap);
  align-items: stretch;
}

/* Compact checkout cards — do not change /prices .plan spacing. */
.checkout-plans .plan {
  gap: var(--sp-2);
  padding: var(--sp-4);
}

.checkout-plans .plan__flag {
  top: -10px;
  left: var(--sp-4);
}

.checkout-plans .plan__amount {
  font-size: 28px;
  line-height: 1.1;
}

.checkout-plans .plan__features {
  gap: 4px;
  line-height: 1.25;
}

.checkout-plans .plan__features svg {
  margin-top: 1px;
}

.checkout-plans .plan__choose {
  margin-top: auto;
  padding: 8px var(--sp-3);
}

.plan--pick {
  cursor: pointer;
}

.plan--pick:focus,
.plan--pick:focus-visible {
  outline: none;
}

.plan--pick.is-chosen:focus,
.plan--pick.is-chosen:focus-visible,
.plan--pick:has(.plan__radio:checked):focus,
.plan--pick:has(.plan__radio:checked):focus-visible {
  outline: none;
  border-color: #008000;
  box-shadow: 0 0 0 1px #008000;
}

.plan--pick .plan__head {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.plan--pick .plan__name {
  display: block;
}

.plan--pick .plan__tagline {
  display: block;
}

.plan--pick .plan__features > span {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
}

/* The radio is the state; the card is the control. Hidden rather than
   display:none so it keeps its place in the tab order and can be reached and
   changed from a keyboard. */

.plan__radio {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.plan__radio:focus-visible + * {
  outline: none;
}

/* The button at the foot of each card. A span painted as a button: a real
   <button> inside the form would submit it. */

.plan__choose {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: auto;
  padding: 10px var(--sp-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 600;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}

.plan__chosen {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.plan__chosen svg {
  width: 16px;
  height: 16px;
}

.plan--pick:hover .plan__choose {
  border-color: #008000;
}

/*
  Premium (gold) and agency (violet) cards: no hairline on the choose button —
  the tinted ground already frames it (client, 22 September).
*/
.plan--premium .plan__choose,
.plan--agency .plan__choose {
  border-color: transparent;
}

.plan--premium.plan--pick:hover .plan__choose,
.plan--agency.plan--pick:hover .plan__choose {
  border-color: transparent;
}

/* The chosen card's button: green #008000, matching border, white label
   and check — "choose" / "chosen" are lower case in the markup. */

.plan--pick.is-chosen,
.plan--pick:has(.plan__radio:checked) {
  border-color: #008000;
  box-shadow: 0 0 0 1px #008000;
}

.plan--pick.is-chosen .plan__choose,
.plan--pick:has(.plan__radio:checked) .plan__choose {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

.plan--pick.is-chosen .plan__choose svg,
.plan--pick:has(.plan__radio:checked) .plan__choose svg {
  color: #fff;
  stroke: #fff;
}

.receipt {
  max-width: 380px;
  margin: var(--sp-6) auto 0;
  padding: var(--sp-5);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  text-align: left;
}

/* Profile headers on broker/agency pages */

.profile-head {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  margin-top: var(--sp-5);
  flex-wrap: wrap;
}

.profile-head__text {
  flex: 1;
  min-width: 240px;
}

/*
  A column now: the two buttons in a row, and under them the chat apps this
  broker can be reached on — "now add them under the seller's phone as well".
  Right-aligned, so the marks sit under the phone button rather than under the
  left edge of the pair.
*/

.profile-head__actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-3);
}

.profile-head__buttons {
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Bigger than the row on a card: this is the page's own contact block. */

.profile-head__actions .msg-links {
  gap: var(--sp-2);
}

.profile-head__actions .msg-link {
  width: 32px;
  height: 32px;
}

@media (max-width: 640px) {
  .profile-head {
    gap: var(--sp-4);
  }

  .profile-head__actions {
    width: 100%;
    align-items: stretch;
  }

  .profile-head__buttons {
    justify-content: stretch;
  }

  .profile-head__actions .btn {
    flex: 1;
  }
}

.agency-card .card__title a:hover {
  color: var(--slate);
}

.article-cover {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--r-lg);
}

@media (max-width: 640px) {
  .article-cover {
    border-radius: var(--r-md);
  }
}

/*
  The gallery under an article's prose.

  The client, 28 August: "in the articles we do gallery with more images." Two
  columns of the article's own reading width, which is narrower than the page —
  a picture beside a paragraph of the same measure reads as part of the piece,
  where a full-bleed strip of them reads as an advertisement. One column on a
  phone, because two of anything at that width is two of nothing.
*/

.article-gallery {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-4);
  margin-top: var(--sp-8);
}

.article-gallery img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--r-md);
  display: block;
}

@media (max-width: 640px) {
  .article-gallery {
    grid-template-columns: 1fr;
  }
}

/*
  The byline in the article's page head: who wrote it on the left, when it was
  published on the right.

  The picture is .avatar-tile, the rounded rectangle the seller box, the broker
  profile and the settings screen all use — "the author image is rectangle with
  rounded corners like it is everywhere". The date sits at the far end rather
  than beside the name because it belongs to the article, not to the author.
*/

.article-byline {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
}

.article-byline__who {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.article-byline__when {
  margin-left: auto;
  white-space: nowrap;
}

/* Below 560px the date drops under the author rather than squeezing the name. */

@media (max-width: 560px) {
  .article-byline {
    flex-wrap: wrap;
    gap: var(--sp-3);
  }

  .article-byline__when {
    margin-left: 0;
    flex-basis: 100%;
  }
}

.legal-layout {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: var(--sp-8);
  align-items: start;
}

.legal-layout .toc {
  position: sticky;
  top: calc(var(--header-h) + var(--sp-5));
}

@media (max-width: 860px) {
  .legal-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .legal-layout .toc {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--sp-3);
  }
}

/*
  One section of a legal document on its editor, /admin/terms and /admin/privacy.

  The client, 10 September: "The current structure, that on the left are titles
  and on the right the content, is good. We keep it, so make that can edit/add
  the titles on the left, and the content on the right."

  So the row is the page's own two columns, in the page's own proportions: the
  contents panel on /terms-of-use is 200px against the document's remaining
  width, and a title box as wide as the paragraph beside it would have said the
  two were the same size of thing. Wider than the panel it feeds, because this
  one is a box being typed into rather than a line being read.

  It stacks at the same width .legal-layout does. A 260px input and a textarea
  side by side stop being two columns some way before the viewport does, and
  matching the page means the editor and the document reflow together.
*/

.legal-row {
  display: grid;
  gap: 2px;
  padding: 4px 10px 8px;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface-2);
}

.legal-row__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-1);
  min-height: 0;
}

/* The overline carries the section number and nothing else, so its own bottom
   margin would push the row's two columns away from their own heading. */

.legal-row__head .overline {
  margin: 0;
  line-height: 1.2;
}

.legal-row__head .icon-btn {
  height: 28px;
  min-width: 28px;
  padding: 0;
}

.legal-row__head .icon-btn svg {
  width: 16px;
  height: 16px;
}

.legal-row__body {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: var(--sp-2);
  align-items: start;
}

.legal-row__body .field {
  gap: 2px;
}

.legal-row__body .field__label {
  margin: 0;
  line-height: 1.2;
}

.legal-row .icon-btn--danger:hover:not(:disabled),
.legal-row .icon-btn--danger:hover:not(:disabled) .icon,
.legal-row .icon-btn--danger:hover:not(:disabled) svg {
  color: #FF0000;
  background: transparent;
}

.legal-admin {
  gap: var(--sp-2);
}

.legal-admin > .card {
  margin: 0;
}

.legal-admin .card__header {
  padding: var(--sp-3) var(--sp-4);
}

.legal-admin .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.legal-admin .card__body > .stack,
.legal-admin .card__body > .stack--sm {
  gap: var(--sp-2);
}

.legal-admin .field {
  gap: 2px;
}

.legal-admin .textarea {
  min-height: 0;
}

@media (max-width: 860px) {
  .legal-row__body {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Horizontal filter bar used on the broker directory and the restricted-country
   admin form. Collapses to a stack once the fields stop fitting. */

.filter-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-4);
  align-items: end;
}

.filter-row .btn {
  min-height: var(--control-h);
}

/*
  Restricted-countries add row on /admin/maps.

  Top-aligned so Country name and Reason sit with Country code (the ISO hint
  under the code field no longer pulls the other labels down). Explicit
  columns keep the green Add button compact rather than stretching.
*/
.filter-row--restrict {
  align-items: start;
  /* Code +30% (7rem→9.1rem); name gives that width back (1fr→0.7fr, min 10→7.7). */
  grid-template-columns: 9.1rem minmax(7.7rem, 0.7fr) minmax(12rem, 1.4fr) auto;
}

.filter-row--restrict .field--action .btn {
  width: auto;
  justify-self: start;
  text-transform: none;
  letter-spacing: 0;
}

@media (max-width: 640px) {
  .filter-row {
    grid-template-columns: 1fr;
  }

  .filter-row--restrict {
    grid-template-columns: 1fr;
  }
}

/*
  The broker directory's search: a place, how far from it, the languages the
  broker deals in, and free text.

  Explicit columns rather than auto-fit, because these four are not
  interchangeable — the location box holds the longest values and the radius
  the shortest, and auto-fit gave them the same width. The radius now holds a
  typed number and its arrow column rather than a select, so it is sized to
  four digits plus the 26px column and no wider.
*/

.broker-search__row {
  grid-template-columns: minmax(220px, 1.5fr) 118px minmax(200px, 1.2fr) minmax(180px, 1fr) auto;
}

/*
  Brokers directory vertical rhythm.

  Title → search is half the usual .section--after-head gap; search → count and
  count → cards use the same step so the three gaps match.
*/

.section--after-head:has(.broker-search) {
  padding-top: var(--sp-3);
}

.broker-search {
  margin-bottom: var(--sp-3);
}

.broker-search__radius .input {
  text-align: center;
}

.broker-results {
  margin-bottom: var(--sp-3);
}

/* Agencies directory — same vertical rhythm as /brokers. */

.section--after-head:has(.agency-results) {
  padding-top: var(--sp-3);
}

.agency-results {
  margin-bottom: var(--sp-3);
}

/* Developments directory — same vertical rhythm as /agencies. */

.section--after-head:has(.development-results) {
  padding-top: var(--sp-3);
}

.development-results {
  margin-bottom: var(--sp-3);
}

/*
  .filter-row bottom-aligns its children, which is what keeps the controls on
  one line while the language picker grows upward as tags are added to it.
*/

@media (max-width: 1080px) {
  .broker-search__row {
    grid-template-columns: minmax(200px, 1.4fr) 124px minmax(200px, 1fr);
  }

  .broker-search__row .btn {
    grid-column: 1 / -1;
  }
}

@media (max-width: 640px) {
  .broker-search__row {
    grid-template-columns: 1fr;
  }
}

/* What the count counted, when a place and radius narrowed it. */

.results-count__where {
  color: var(--text-subtle);
}

/*
  The seller profile's map location. Shorter than the add-listing map — this is
  a confirmation that the pin is in the right town, not a working surface — and
  the field stacks its parts with the form's own rhythm.
*/

.profile-location {
  gap: var(--sp-3);
}

.profile-location__map {
  height: 300px;
}

/* Map click hint: violet pin + copy, lowercase (client, 23 September). */
.profile-location__hint,
.profile-location__hint svg {
  color: #8B008B;
}

@media (max-width: 640px) {
  .profile-location__map {
    height: 220px;
  }
}

/*
  The same field in the back office, where the development editor places a
  new-build project on the map. Sized like the profile's rather than like the
  detail page's 380px panel: it sits inside a form the administrator is
  scrolling through, and the job here is to confirm a pin, not to explore.
*/

.admin-location {
  gap: var(--sp-2);
}

/* Map search: 60% narrower (40% width), left-aligned. */
.admin-location > .location-field {
  width: 40%;
  max-width: 100%;
  align-self: start;
}

.admin-location .detail-map {
  height: 260px;
}

@media (max-width: 640px) {
  .admin-location .detail-map {
    height: 200px;
  }
}

#development-editor .development-editor-form {
  gap: var(--sp-2);
}

#development-editor .development-editor-form .field {
  gap: 2px;
}

/*
  Search ↔ map must match Address ↔ search (form --sp-2). This rule sits
  after the compact .field { gap: 2px } so it wins — otherwise the search
  sits flush on the map.
*/
#development-editor .development-editor-form > .admin-location {
  gap: var(--sp-2);
}

#development-editor .development-editor-form .field__hint,
#development-editor .development-editor-form .map-hint {
  margin-top: 0;
  line-height: 1.25;
}

#development-editor .development-editor-form .overline {
  margin-bottom: 0;
}

#article-editor .article-editor-form {
  gap: var(--sp-2);
}

#article-editor .article-editor-form .field {
  gap: 2px;
}

.about-admin .about-editor-form {
  gap: var(--sp-2);
}

.about-admin .about-editor-form .field {
  gap: 2px;
}

.help-admin .help-editor-form {
  gap: var(--sp-2);
}

.help-admin .help-editor-form .field {
  gap: 2px;
}

.login-admin .login-editor-form {
  gap: var(--sp-2);
}

.login-admin .login-editor-form .field {
  gap: 2px;
}

/* No golden focus frame on fold headers (admin + My listings + everywhere). */
.card__fold:focus,
.card__fold:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Admin: no golden focus frame on pick-menu rows or field triggers. */
.admin .menu__item:focus,
.admin .menu__item:focus-visible,
.admin .sort-menu__btn:focus,
.admin .sort-menu__btn:focus-visible,
.admin .select:focus,
.admin .select:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Frontpage admin: tight fields, no air between labels and inputs. */
.frontpage-admin {
  gap: var(--sp-2);
}

.frontpage-admin > .card,
.frontpage-admin > .card--fold {
  margin: 0;
}

.frontpage-admin .card__header,
.frontpage-admin .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.frontpage-admin .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.frontpage-admin .card__body > .stack,
.frontpage-admin .card__body > .stack--sm,
.frontpage-admin form.stack,
.frontpage-admin form.stack--sm {
  gap: var(--sp-2);
}

.frontpage-admin .field {
  gap: 2px;
}

.frontpage-admin .field + .field,
.frontpage-admin .check + .grid,
.frontpage-admin .check + .field {
  margin-top: 0;
}

.frontpage-admin .grid {
  gap: var(--sp-2);
}

.frontpage-admin .textarea {
  min-height: 0;
}

.help-tile {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 500;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.help-tile:hover {
  border-color: var(--border-strong);
  background: var(--surface-2);
}

.help-tile__icon {
  display: inline-flex;
  color: var(--slate);
  flex: none;
}

.help-tile > svg:last-child {
  margin-left: auto;
  width: 16px;
  height: 16px;
  color: var(--text-subtle);
  flex: none;
}

.error-card {
  display: block;
  color: #fff;
  box-shadow: none;
}

.error-card:hover,
.error-card:focus,
.error-card:focus-visible {
  transform: none;
  box-shadow: none;
  outline: none;
  color: #fff;
}

.error-page__actions {
  justify-content: center;
  margin-top: var(--sp-3);
  flex-wrap: wrap;
  gap: var(--sp-3);
}

.error-page__cards {
  margin-top: var(--sp-5);
  text-align: left;
  width: 100%;
}

.error-card__title {
  margin-top: var(--sp-3);
  color: #fff;
}

.error-card__text {
  color: rgba(255, 255, 255, 0.88);
}

/* Same fills as /advertising placement cards — no contrasting border. */
.error-card--sale,
.error-card--sale:hover,
.error-card--sale:focus,
.error-card--sale:focus-visible {
  background: #8B008B;
  border-color: #8B008B;
}

.error-card--rent,
.error-card--rent:hover,
.error-card--rent:focus,
.error-card--rent:focus-visible {
  background: #B8860B;
  border-color: #B8860B;
}

.error-card--brokers,
.error-card--brokers:hover,
.error-card--brokers:focus,
.error-card--brokers:focus-visible {
  background: #008000;
  border-color: #008000;
}

/* FAQ accordion */

/*
  The list keeps a reading measure but takes it from the page's left edge
  rather than from its centre, so the questions line up under the title and the
  breadcrumb — the client's "align to left like everywhere", which is about the
  page rather than the heading alone. Narrower than the container it sits in
  because a question and its answer are prose.
*/

.faq-list {
  max-width: var(--container-narrow);
}

/* FAQ / help contact card — same compact header as billing & admin cards
   (client: make the "contact" header narrower). */
.card--contact-details .card__header {
  padding: var(--sp-3) var(--sp-4);
}
.card--contact-details .card__title {
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.25;
}
.card--contact-details .card__body {
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}

.faq {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-base);
  font-weight: 600;
  text-align: left;
}

.faq__q svg {
  width: 18px;
  height: 18px;
  color: var(--text-muted);
  flex: none;
  transition: transform var(--dur) var(--ease);
}

.faq.is-open .faq__q svg {
  transform: rotate(180deg);
}

.faq__a {
  padding: 0 var(--sp-5) var(--sp-5);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  line-height: 1.65;
}

/* ==========================================================================
   Pricing
   ========================================================================== */

.price-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--card-gap);
  align-items: start;
}

@media (max-width: 1080px) {
  .price-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px)  {
  .price-grid {
    grid-template-columns: 1fr;
  }
}

.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  height: 100%;
}

.plan--popular {
  border-color: #6F006F;
  box-shadow: 0 0 0 1px #6F006F;
}

.plan--popular.plan--pick.is-chosen,
.plan--popular.plan--pick:has(.plan__radio:checked) {
  border-color: #008000;
  box-shadow: 0 0 0 1px #008000;
}

/*
  The featured package is gold on both themes.

  The client, 5 September: "this featured package has in day mode golden
  background, the same do in dark mode."

  It was --gold-tint in daylight and plain --surface-2 after dark, which is the
  same navy as the three cards beside it — so the card the whole page is built
  to sell was the only one with nothing to distinguish it but a border. The
  dark override is gone and the token carries it: --gold-tint is defined in
  both themes, cream on white and a warm brown-gold on navy, so the card reads
  as the same decision in either.
*/

.plan--premium {
  border-color: var(--gold-border);
  background: var(--gold-tint);
}

/*
  Agency package: brand violet, white copy.

  Same #6F006F as the PRevia logo mark, on /prices and /checkout so the
  broker tier reads as the branded card rather than another white panel.
*/

.plan--agency {
  border-color: #6F006F;
  background: #6F006F;
  color: #fff;
}

.plan--agency .plan__name,
.plan--agency .plan__tagline,
.plan--agency .plan__amount,
.plan--agency .plan__period,
.plan--agency .plan__features {
  color: #fff;
}

.plan--agency .plan__features svg {
  color: #fff;
}

/* Choose button matches every other package (surface + border); only the
   chosen state stays green #008000 below. */

.plan--agency.plan--pick.is-chosen,
.plan--agency.plan--pick:has(.plan__radio:checked) {
  border-color: #008000;
  box-shadow: 0 0 0 1px #008000;
}

.plan--agency.plan--pick.is-chosen .plan__choose,
.plan--agency.plan--pick:has(.plan__radio:checked) .plan__choose {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

.plan--agency.plan--pick.is-chosen .plan__choose svg,
.plan--agency.plan--pick:has(.plan__radio:checked) .plan__choose svg {
  color: #fff;
  stroke: #fff;
}

.plan__flag {
  position: absolute;
  top: -11px;
  left: var(--sp-6);
  padding: 3px 10px;
  border-radius: var(--r-sm);
  font-size: var(--fs-2xs);
  font-weight: 700;
  background: #6F006F;
  color: #fff;
}

.plan--premium .plan__flag {
  background: var(--gold);
  color: var(--text-on-gold);
}

.plan__name {
  font-size: var(--fs-h4);
  font-weight: 600;
}

.plan__tagline {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.plan__price {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.plan__amount {
  font-size: 36px;
  font-weight: 700;
  letter-spacing: var(--ls-tight);
}

.plan__period {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.plan__features {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  list-style: none;
  font-size: var(--fs-sm);
  flex: 1;
}

.plan__features li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
}

.plan__features svg {
  width: 16px;
  height: 16px;
  color: var(--success);
  flex: none;
  margin-top: 3px;
}

/* ==========================================================================
   Auth
   ========================================================================== */

/* Form on the left, photography on the right. The image column is dropped
   entirely below 960px and replaced by a short banner, so a phone shows the
   form immediately instead of scrolling past a full-height picture. */

/*
  The block is a panel, at the client's request on 26 August: "this banner on
  the right make it the same width as the header, so it stays under the header
  … and the corners of this banner make round and make that it is not
  touching the header."

  It ran the full width of the window before this, so the photograph reached
  the right-hand screen edge while the header above it stopped one gutter
  short, and the two met along a line that did not agree. --page-width is the
  number the header, the footer and every other panel are cut to, so the sign-in
  screen is now cut to it as well and all three land on the same two edges.

  The top margin is the gap he asked for. The bottom one keeps the panel off
  the footer, which the full-bleed version had no need of because it had no
  edges to keep clear.

  Both gaps are the same step since 5 September: "from the footer the same
  distance as from the header."

  They were not, and the reason is that only one of them was written here. The
  20px above was this margin; the space below was this block's 32px *plus* the
  24px .page__foot holds above the footer panel on every page of the site — 56
  against 20, and on the login screen more still, because the panel stopped
  short of the row and left the difference under itself as well.

  So the block hands the bottom gap to the rule that already owns it: nothing
  of its own underneath, and --sp-6 above, which is the very token .page__foot
  spends below. The same number twice, from the same place, and neither can
  drift from the other. The panel takes back everything the mismatch was
  costing it, which is the "a bit bigger" in the same sentence.
*/

.auth {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-6);
  width: var(--page-width);
  margin-inline: auto;
  margin-block: var(--sp-6) 0;
  min-height: calc(100vh - var(--header-h) - var(--sp-6) * 2);
}

@media (max-width: 960px) {
  .auth {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-3);
    margin-block: var(--sp-3) 0;
  }
}

.auth__form-side {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-9) var(--gutter);
}

.auth__form {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.auth__back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-muted);
}

.auth__back:hover {
  color: var(--navy);
}

[data-theme='dark'] .auth__back:hover {
  color: var(--gold);
}

.auth__head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.auth__row {
  gap: var(--sp-3);
}

.auth__link {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--teal);
}

.auth__link:hover {
  color: var(--teal-hover);
  text-decoration: underline;
}

[data-theme='dark'] .auth__link {
  color: var(--teal);
}

.auth__foot {
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.auth__status-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  margin-bottom: var(--sp-2);
  background: var(--navy-tint);
  color: var(--navy);
  border-radius: var(--r-lg);
}

.auth__status-icon .icon {
  width: 24px;
  height: 24px;
}

.auth__status-icon--ok {
  background: var(--success-bg);
  color: var(--success);
}

[data-theme='dark'] .auth__status-icon {
  background: var(--surface-3);
  color: var(--gold);
}

/* --- Image side ----------------------------------------------------------- */

/*
  One screen tall at most, and it does not move.

  It used to be a grid cell with no height of its own, so it grew to whatever
  the form beside it needed — and the registration form is the longest on the
  site. The headline sits in the middle of this panel, which meant the headline
  sat halfway down a panel one and a half screens deep: "this text there
  'publish property …' rise more up so that it would be to see, like it was
  earlier." Capping the panel at one viewport puts the whole of it back on the
  first screen.

  That cap was a sticky box a viewport tall, and both halves of it were wrong.
  The client, 5 September: "this login page image, you made it to move now, it
  was earlier nicely fixed, now it moves in it's place, make it like it was, so
  a bit bigger, from the footer the same distance as from the header, and
  fixed."

  Sticky was the movement. An element offset from the top of the *viewport*, on
  a page whose header scrolls away with everything else, slides upwards under
  that header for the first 72px of any scroll and only then pins — the picture
  crept and stopped, which is what "it moves in it's place" describes.

  The viewport height was the uneven gap. A panel clamped to one screen inside
  a row the form makes taller stops short of the row's bottom edge, so the space
  under the picture was the .auth margin plus whatever the clamp had left over
  — 44px against 20 above it, on the screen he photographed.

  So the panel is an ordinary grid cell again: stretched to the row like every
  other panel on the site, still on the page, and ending exactly where the form
  beside it ends. Its two gaps are the two .auth sets, which are now the same
  one.

  What the viewport still bounds is the writing on it — see .auth__aside-inner.
  That is what the one-screen cap was for: "this text there 'publish property …'
  rise more up so that it would be to see". Bounding the text costs the picture
  nothing, and the register page's headline stays on the first screen.

  position: relative rather than static: the photograph and the text column
  inside are positioned against this box.
*/

.auth__aside {
  position: relative;
  align-self: stretch;
  min-height: 420px;
  overflow: hidden;
  background: var(--navy);
  border-radius: var(--r-xl);
}

@media (max-width: 960px) {
  .auth__aside {
    display: none;
  }
}

.auth__aside-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
  The three ways the sign-in photograph may meet its panel.

  The client, 7 September: "in the login page setup ... make there the same
  'how the picture is shown' options as in the ADS page." The ADS screen's own
  three, and deliberately the same class names — .banner-fit--* is already what
  models.Banner.FitClass writes, and models.LoginPage.FitClass writes the same
  three words, so the stylesheet has one vocabulary for one idea rather than an
  auth-flavoured copy of it.

  The selectors are doubled on the element because .auth__aside-img above sets
  object-fit on the same element and would otherwise win on source order.

  cover is the default and the untouched state: this panel has cropped its
  photograph since the screen was built, and a site nobody has opened the field
  on looks exactly as it did.
*/

.auth__aside-img.banner-fit--fill {
  object-fit: cover;
}

.auth__aside-img.banner-fit--fit {
  object-fit: contain;
}

.auth__aside-img.banner-fit--stretch {
  object-fit: fill;
}

/*
  Behind a fitted photograph, filling what it does not reach.

  The same answer the banners give — .cbanner__art--fill, and the note above it
  about why a flat band is the wrong one. The scale is what keeps the blurred
  edge from showing as a seam against the panel's corner radius, and it is
  drawn from the same address as the picture in front of it, so it costs a
  paint and not a download.
*/

.auth__aside-img--under {
  object-fit: cover;
  filter: blur(28px) saturate(1.15) brightness(0.62);
  transform: scale(1.12);
}

/* No veil over the photograph.

   The client, 3 September: "I changed the image of the login page and there is
   some foggy/darkening filter, this remove. If need to make the image darker
   then this can do before upload. In upload no filter."

   There used to be a navy gradient here, drawn over the picture so the white
   headline would sit on something dark whatever was uploaded. It is gone: the
   picture is shown as it was uploaded, and how dark it is is now a decision
   made in the photograph rather than in the stylesheet.

   What stays is a shadow on the glyphs themselves, below. It tints no pixel of
   the image - it only stops the headline disappearing into a pale sky. */

.auth__aside-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  height: 100%;
  /* The writing is what has to fit on the first screen, not the photograph.
     The panel is as tall as the form beside it — on the registration page, a
     screen and a half — and a headline centred in that lands below the fold,
     which is the "rise more up" note of 26 August. Capping the column rather
     than the panel keeps the logo at the top, the headline in the middle of
     the *visible* part, and the picture free to fill its cell. */
  max-height: calc(100vh - var(--header-h) - var(--sp-6) * 2);
  padding: clamp(32px, 4vw, 56px);
  color: #fff;
  /* Legibility without a filter: the shadow is cast by the letters, so it
     darkens the few pixels behind a glyph and nothing else. On the dark
     photographs the client uploads it is invisible; on a bright one it is the
     difference between a headline and a white smudge.

     Two shadows rather than one. The tight one draws the edge of the letter,
     which is what carries the small text in the list; the wide soft one sits
     under the whole word and is what holds it together over a white pool deck,
     which is the default photograph and the worst case there is. Neither tints
     a pixel the letters do not already stand on. */
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.62),
    0 2px 14px rgba(0, 0, 0, 0.45);
}

/* Same reason as .auth__banner-logo: the mark is an SVG and needs its own. */

.auth__aside-logo {
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.55));
}

/*
  The word "Previa" in the colour an administrator typed.

  The client, 6 September: "make there additional color field 'Previa color' so
  that can change the Previa text color as well." .logo--mono paints the
  wordmark #fff outright, so the colour set on the link would never reach it;
  this hands it back to the link, and only on the banner that carries a colour.
  Without the field filled in there is no class here and the mark is white.
*/

.auth__aside-logo--tinted .logo__word {
  color: inherit;
}

.auth__aside-headline {
  color: #fff;
  max-width: 18ch;
}

/* Logo at the top, everything else centred in what is left — rather than
   pushed to the bottom edge, which is where "rise more up" was aimed. */

.auth__aside-text {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  margin-block: auto;
}

.auth__points {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  list-style: none;
}

.auth__points li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: var(--fs-base);
  /* Full white, not 0.9. The nine-tenths was a hierarchy note written against
     a navy gradient; with the gradient gone it is a tenth of contrast given
     away on the smallest text on the panel. */
  color: #fff;
  /* And a shadow of its own, tighter and denser than the column's. These are
     16px letters, so the wide soft glow that carries the headline does almost
     nothing for them - what a small glyph needs is a dark edge right against
     it. */
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.85),
    0 0 6px rgba(0, 0, 0, 0.6);
}

.auth__point-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 24px;
  height: 24px;
  margin-top: 1px;
  background: rgba(255, 255, 255, 0.16);
  border-radius: 50%;
  /*
    The tick takes the line's own colour.

    The client, 6 September: "just need to make that the ticks in front of the
    page would be the same as 'Ticked lines colour'." The chip used to say #fff
    outright, so a green list on /login kept four white ticks in front of it.
    The li above carries the colour — its own white when nobody has typed one,
    and the administrator's when they have — and inheriting it is what makes
    the tick and the sentence one thing in both cases. The chip behind it stays
    the same translucent white on every screen: it is what lifts the mark off a
    pale photograph, and tinting it would be a second colour nobody asked for.
  */
  color: inherit;
  /* The chip is white on white now that no gradient sits under it, so it needs
     an outline of its own. A shadow rather than a border: it reads as a rim on
     a pale photograph and disappears entirely on a dark one, which is what a
     border would not do. */
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

.auth__point-icon .icon {
  width: 14px;
  height: 14px;
  stroke-width: 3;
  /* The tick is a stroked SVG; text-shadow does not reach it. */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55));
}

/* --- Mobile banner -------------------------------------------------------- */

.auth__banner {
  display: none;
  position: relative;
  height: 152px;
  overflow: hidden;
  border-radius: var(--r-xl);
}

.auth__banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The phone banner draws the same choice — see .auth__aside-img above. It is
   its own rule rather than a shared selector because the two boxes position
   their picture differently: the aside's is absolute, this one is in flow. */

.auth__banner img.banner-fit--fill {
  object-fit: cover;
}

.auth__banner img.banner-fit--fit {
  object-fit: contain;
}

.auth__banner img.banner-fit--stretch {
  object-fit: fill;
}

/* Qualified with the tag so it outranks the .auth__banner img rule below,
   which would otherwise pull this back into flow. */

.auth__banner img.auth__banner-under {
  position: absolute;
  inset: 0;
  object-fit: cover;
  filter: blur(28px) saturate(1.15) brightness(0.62);
  transform: scale(1.12);
}

/* And the photograph in front of it has to be positioned to stay in front:
   a positioned element paints over in-flow content whatever the document order
   says, so an absolute backdrop would cover a static picture. Both are
   positioned now, both have z-index auto, and they paint in the order they are
   written. The logo above them keeps its own z-index: 1. */

.auth__banner img {
  position: relative;
}

/* There is no .auth__banner::after here either, and that is the point: the
   phone banner carried its own copy of the gradient, so removing only the
   desktop one would have left the filter in place on exactly the screens the
   client did not screenshot. */

.auth__banner-logo {
  position: absolute;
  left: var(--gutter);
  bottom: var(--sp-4);
  z-index: 1;
  /* drop-shadow rather than text-shadow: the logo is an SVG, and a shadow on
     the text node would leave the mark itself unshaded. */
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.55));
}

@media (max-width: 960px) {
  .auth__banner {
    display: block;
    /* Shorter strip — logo left the banner, so it need not hold as much height. */
    height: 112px;
  }

  /* The panel carries the side margins now, so the form no longer needs to
     hold its own gutters inside it. Compact on phone (client, 21 September):
     pull content up, tighter gaps between banner, title and fields. */

  .auth__form-side {
    padding: var(--sp-3) 0 var(--sp-5);
    align-items: flex-start;
  }

  .auth__form {
    gap: var(--sp-3);
  }

  .auth__form .stack {
    gap: var(--sp-3);
  }

  .auth__head {
    gap: 2px;
  }

  .auth__head .display--h2 {
    line-height: 1.15;
  }

  .auth__form .field {
    gap: 0.25rem;
  }

  .auth {
    min-height: 0;
  }
}

/* --- Account-type choice -------------------------------------------------- */

.auth__roles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
}

@media (max-width: 420px) {
  .auth__roles {
    grid-template-columns: 1fr;
  }
}

.auth__role {
  position: relative;
  cursor: pointer;
}

.auth__role input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.auth__role-box {
  display: flex;
  flex-direction: column;
  gap: 4px;
  height: 100%;
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.auth__role-box .icon {
  color: var(--slate);
}

.auth__role-title {
  font-size: var(--fs-sm);
  font-weight: 700;
}

.auth__role-hint {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  line-height: 1.4;
}

.auth__role:hover .auth__role-box {
  border-color: var(--border-strong);
  background: var(--surface-2);
}

.auth__role input:checked + .auth__role-box {
  border-color: var(--teal);
  background: var(--teal-tint);
  box-shadow: inset 0 0 0 1px var(--teal);
}

.auth__role input:checked + .auth__role-box .icon {
  color: var(--teal);
}

.auth__role input:focus-visible + .auth__role-box {
  box-shadow: var(--focus-ring-teal);
}

/* --- Password strength ---------------------------------------------------- */

.pw-meter {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.pw-meter__track {
  height: 5px;
  background: var(--surface-3);
  border-radius: var(--r-pill);
  overflow: hidden;
}

.pw-meter__bar {
  height: 100%;
  border-radius: var(--r-pill);
  background: #ff0000;
  transition: width var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.pw-meter__bar.is-fair {
  background: #FFFF00;
}

.pw-meter__bar.is-good,
.pw-meter__bar.is-strong {
  background: #008000;
}

.pw-meter__label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.pw-meter__label.is-weak {
  color: #ff0000;
}

.pw-meter__label.is-fair {
  color: #FFFF00;
}

.pw-meter__label.is-good,
.pw-meter__label.is-strong {
  color: #008000;
}

/* --- Field with leading icon and trailing action -------------------------- */

.input-affix {
  position: relative;
  display: flex;
  align-items: center;
}

.input-affix__lead {
  position: absolute;
  left: var(--sp-4);
  display: inline-flex;
  color: var(--text-subtle);
  pointer-events: none;
}

.input--affixed {
  padding-left: 46px;
}

.input-affix:has(.input-affix__action) .input--affixed {
  padding-right: 48px;
}

/* The same room on the right for a field that has the eye and no lead icon —
   the admin and account password boxes, which keep their own plain label. */

.input--revealable {
  padding-right: 48px;
}

.input-affix__action {
  position: absolute;
  right: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: var(--r-sm);
  color: var(--text-muted);
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.input-affix__action:hover {
  background: var(--surface-3);
  color: var(--text);
}

.input-affix__action:focus-visible {
  box-shadow: var(--focus-ring);
}

.divider-text {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  color: var(--text-subtle);
  font-size: var(--fs-xs);
}

.divider-text::before, .divider-text::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* The two social sign-in buttons. Links rather than buttons since they
   navigate, so the reset that makes an <a> sit level with a real button
   matters — without it the mark and the label sit on different baselines. */

.btn--google,
.btn--facebook {
  gap: var(--sp-3);
  text-decoration: none;
}

.btn.btn--google:hover:not(:disabled),
.btn.btn--facebook:hover:not(:disabled) {
  background: #008000;
  border-color: #008000;
  color: #fff;
}

.btn.btn--google:hover:not(:disabled) .icon,
.btn.btn--facebook:hover:not(:disabled) .icon {
  color: #fff;
}

.social-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
}

@media (max-width: 420px) {
  .social-row {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   Misc page furniture
   ========================================================================== */

.notice-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
}

.kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-3) var(--sp-6);
  font-size: var(--fs-sm);
}

.kv dt {
  color: var(--text-muted);
}

.kv dd {
  margin: 0;
  font-weight: 500;
  text-align: right;
}

.kv--inline {
  display: block;
  line-height: 1.45;
  font-size: var(--fs-sm);
}

.kv--inline dt,
.kv--inline dd {
  display: inline;
  text-align: left;
  color: var(--text);
  font-weight: 500;
}

.kv--inline dt::after {
  content: ": ";
}

.kv--inline dd::after {
  content: "\A";
  white-space: pre;
}

.kv--inline dd:last-child::after {
  content: none;
}

.property-detail__box {
  background: var(--surface-2);
}

.property-detail__box > .card__body {
  padding: var(--sp-2) var(--sp-5);
}

.property-detail__box .kv {
  margin: 0;
}

.property-detail__box .prose {
  gap: 0;
}

.property-detail__box .listing-description {
  margin: 0;
}

.property-detail__rails {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  margin-top: var(--sp-5);
}

.property-detail__rail > .section__head {
  margin-bottom: var(--sp-2);
}

@media (min-width: 641px) {
  .property-detail__rail > .section__head {
    align-items: flex-end;
  }
}

.property-detail__rail .section__subtitle {
  margin: 0;
  line-height: 1.2;
}

.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-6) var(--gutter) var(--sp-5);
  text-align: center;
}

/* Keep the footer under the cards — do not stretch empty main space on 404. */
.page:has(.error-page) .page__main {
  flex: 0 0 auto;
}

.page:has(.error-page) .page__foot {
  margin-top: 0;
  padding-top: var(--sp-5);
}

.error-page__code {
  font-family: var(--font-display);
  font-size: clamp(72px, 14vw, 140px);
  font-weight: 700;
  line-height: 1;
  color: var(--navy);
  opacity: 0.16;
}

[data-theme='dark'] .error-page__code {
  color: var(--gold);
  opacity: 0.24;
}

.toc {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
}

.toc a {
  color: var(--text-muted);
  padding: 4px 0;
}

.toc a:hover {
  color: var(--navy);
}

[data-theme='dark'] .toc a:hover {
  color: var(--gold);
}

/* Admin-only tweaks: denser tables, no display face anywhere. */

/* Pull admin panels up under the page head — ~70% less empty space than a
   normal .section step (which was leaving a tall dark band under Backups,
   Dashboard, etc.). */
.admin > .page-head {
  padding-bottom: 0;
}

.admin > .container.section,
.admin > .container.section.section--after-head {
  padding-top: 8px !important;
  padding-block-start: 8px !important;
}

/*
  Same 8px between every content card as under the page title — client,
  21 September: move the blocks up; keep the gap even on every admin screen.
  Only the column beside the nav, so form stacks inside a card stay as they are.
*/
.admin .shell--nav > .stack,
.admin .shell--nav > .stack--sm,
.admin .shell--nav > .stack--lg {
  gap: 8px;
}

.admin .page-head__title,
.admin .section__title,
.admin .card__title {
  font-family: var(--font-sans);
  letter-spacing: 0;
}

/* Card block titles match Backup settings across the whole admin
   (Generate full backup, maps, ads, … — client, 21 September). */
.admin .card__header {
  padding: var(--sp-3) var(--sp-4);
}
.admin .card__title {
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.25;
}

/*
  And the page title at the size the signed-in screens use.

  The client, 31 August, looking at "Advertising" over the ADS screen: the
  heading is to be the size of the one on his own account pages. Those run
  .page-head--compact, which drops --fs-page-title to --fs-h3 — 26px against 34
  at the top of the clamp — so this is that same step, taken once for the whole
  back office rather than per screen. A back-office title is a label on a tool,
  not the headline of a page somebody came to read.
*/

.admin .page-head__title {
  font-size: var(--fs-h3);
}

.admin .table td {
  padding: var(--sp-3) var(--sp-4);
}

/* --------------------------------------------------------------------------
   Admin listings — a card per listing, not a row in a nine-column table
   --------------------------------------------------------------------------

   "Then the ad title - look how it looks like?? At the same time you have in
   the ad window lot of free place. Make that the ad title and text go to the
   below part of the ad window, then separator line, and then the other buttons
   are there."

   The title was breaking one word per line because Property was one of nine
   columns and the other eight held a word each. Three bands instead: the
   photograph and the facts across the top, the title and its text with the
   full width below them, a rule, and the buttons under that.
   -------------------------------------------------------------------------- */

/*
  The seller's own list takes the same three bands.

  The client, 31 August: "under my profile my listings the listing previews
  make the listing title and text the same way as in admin listings." Same
  rules, two names — .seller-listing on /my-listings, .admin-listing on
  /admin/listings — rather than one name on both, because the two markups are
  not the same markup: the seller's cards carry no Deactivate, no featured
  switch and no editable expiry, and a shared class would imply they could.
  What is shared is the presentation, which is the whole of what he asked for.
*/

.admin-listings,
.seller-listings {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* Closed fold headers sit closer than the old open-card spacing. */
.admin-listings,
.seller-listings {
  gap: var(--sp-2);
}

.admin-listings-toolbar,
.my-listings-toolbar {
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
}

.admin-listings-sort .sort-menu__btn {
  max-width: min(28rem, 70vw);
  /* No chip behind the value; green + tight to the label — same on admin and
     My listings (client, 22 September). */
  background: transparent !important;
  color: #008000 !important;
  padding-inline: 0;
  gap: 2px;
}

.admin-listings-sort {
  gap: 4px;
  position: relative;
}

/* Dropdown lines up with "sort by:", not with "publishing date" (client, 22 September). */
.admin-listings-sort .sort-menu {
  position: static;
}

.admin-listings-sort .sort-menu .dropdown {
  left: 0;
}

.admin-listings-sort .sort-menu__btn:hover,
.admin-listings-sort .sort-menu__btn:focus,
.admin-listings-sort .sort-menu__btn:focus-visible,
.admin-listings-sort .sort-menu__btn[aria-expanded='true'] {
  background: transparent !important;
  color: #008000 !important;
}

.admin-listings-sort .sort-menu__btn svg {
  color: #008000 !important;
}

.admin-listings-sort .sort-menu__btn > span {
  overflow: hidden;
  text-overflow: ellipsis;
  color: #008000;
}

/* Admin / My listings — foldable; closed header carries title (+ seller on admin) / dates. */

.admin-listings .admin-listing.card,
.seller-listings .seller-listing.card {
  padding: 0;
  gap: 0;
}

.admin-listings .admin-listing > .card__header,
.admin-listings .admin-listing > .card__fold,
.seller-listings .seller-listing > .card__header,
.seller-listings .seller-listing > .card__fold {
  padding: var(--sp-3) var(--sp-4);
}

.admin-listings .admin-listing > .card__body,
.seller-listings .seller-listing > .card__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}

.admin-listings .listing-card__head,
.seller-listings .listing-card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1rem;
  min-width: 0;
  flex: 1;
  text-align: left;
}

.admin-listings .listing-card__title,
.seller-listings .listing-card__title {
  flex: 1 1 14rem;
  min-width: 0;
  font-weight: 700;
  color: var(--text);
}

.admin-listings .listing-card__title .billing-clip__cut,
.seller-listings .listing-card__title .billing-clip__cut {
  display: block;
  overflow: hidden;
  white-space: nowrap;
}

.admin-listings .listing-card__seller {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.75rem;
  min-width: 0;
}

.admin-listings .listing-card__seller-name {
  font-weight: 600;
  color: var(--text);
}

.admin-listings .listing-card__seller-email,
.admin-listings .listing-card__dates,
.seller-listings .listing-card__dates {
  color: var(--text-muted);
  font-size: var(--fs-sm);
  white-space: nowrap;
}

.admin-listings .listing-card__dates,
.seller-listings .listing-card__dates {
  margin-left: auto;
}

.admin-listing,
.seller-listing {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

/*
  A promoted listing, on the seller's own list and in the back office.

  The client, 12 September: "make that a featured listing in user my ads panel
  has the sme golden background as in frontpage." And 13 September, of
  /admin/listings: "Now the same in admin page, that if ad is featured, then it
  is with golden background."

  --gold-tint over --gold-border, which is exactly what .pcard--featured and
  .plan--premium take, so the gold a seller pays for is one colour across the
  site and follows the theme in the dark with them. The card is a block of text
  rather than a photograph with text under it, so the tint is the whole of it
  and there is no inset ring to draw: the border is already visible at this
  size, which is the thing the property card's ::before exists to fix.
*/

.admin-listing--featured,
.seller-listing--featured {
  border-color: var(--gold-border);
  background: var(--gold-tint);
}

.admin-listing__head,
.seller-listing__head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}

.admin-listing__img,
.seller-listing__img {
  width: 120px;
  height: 90px;
  flex: none;
  object-fit: cover;
  border-radius: var(--r-md);
  background: var(--surface-3);
}

/* The facts wrap instead of each taking a column of its own, so a narrow
   window reflows them rather than squeezing every one of them. */

.admin-listing__facts,
.seller-listing__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-6);
  flex: 1;
  min-width: 0;
  margin: 0;
}

.admin-listing__facts > div,
.seller-listing__facts > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.admin-listing__facts dt,
.seller-listing__facts dt {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-subtle);
}

.admin-listing__facts dd,
.seller-listing__facts dd {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text);
}

/* Tight, because the client asked for it tight: "now just this ad text,
   reduce the line spacing there, and move it more up towards the title."
   The three lines here are a title, its reference line and its description,
   and they are one block about one listing rather than three paragraphs. */

.admin-listing__body,
.seller-listing__body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

/* One line at this width, which is the whole point — and an ellipsis rather
   than a wrap if a title really is longer than the card. */

.admin-listing__title,
.seller-listing__title {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-listing__title:hover,
.seller-listing__title:hover {
  color: var(--teal);
}

.admin-listing__lede,
.seller-listing__lede {
  margin-top: 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: 1.35;
  /* Two full rows, hard cut — no ellipsis (client, 22 September). */
  max-height: calc(1.35em * 2);
  overflow: hidden;
  text-overflow: clip;
  word-break: break-word;
}

.admin-listing__rule,
.seller-listing__rule {
  height: 1px;
  margin: 0;
  background: var(--border);
  border: 0;
}

.admin-listing__actions,
.seller-listing__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}

.admin-listing__actions form,
.seller-listing__actions form {
  display: flex;
}

/*
  My listings / admin listing under-bar.

  The client, 17 September: remove the golden selectors. Hover colour stays
  on btn--ok-quiet / btn--danger-quiet (outline, not a solid fill).
  Delete matches view/deactivate at rest; red only on hover (22 September).
*/

.seller-listing__actions .btn:focus,
.seller-listing__actions .btn:focus-visible,
.admin-listing__actions .btn:focus,
.admin-listing__actions .btn:focus-visible {
  outline: none;
  box-shadow: none;
}

/* --------------------------------------------------------------------------
   How long this listing stays online, beside the button that puts it there.

   The client, 28 August: "next to the activate/deactivate button make small
   field '28-08-2026 14:53' (no calender, such small text field). So that I can
   set the time for how long this ad stays activated."

   No calendar, so it is a text box and not <input type="datetime-local"> —
   which is the same field with a picker welded to it and a layout no
   stylesheet controls. Narrow enough to read as the small field it is, and
   the numbers are tabular so a column of them lines up.

   Not named for the admin screen, because it is on two: "the same date/time
   field is to see in the user my-ads page as well ... but he can not edit it",
   and one rule serving both is what makes the two boxes look identical.
   -------------------------------------------------------------------------- */

.expiry-field {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: none;
}

.expiry-field .input {
  /*
    Hug "17-09-26 09:30" (14 tabular chars) with equal 8px pad each side.
    Same fixed box for "no end date". Beats .input { width:100% }.
    Height matches .btn--sm (view / deactivate / set / delete).
  */
  width: calc(14ch + 16px) !important;
  max-width: calc(14ch + 16px) !important;
  min-width: calc(14ch + 16px) !important;
  box-sizing: border-box !important;
  flex: none;
  min-height: var(--control-h-sm);
  height: var(--control-h-sm);
  padding: 0 8px !important;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  background: transparent;
}

/* set — same height as the date box, width hugs the label. */

.expiry-field .btn--expiry-set {
  min-height: var(--control-h-sm);
  height: var(--control-h-sm);
  padding: 0 0.55rem;
  flex: none;
  line-height: 1;
  box-sizing: border-box;
}

/* The seller's copy, and a draft's. Read-only rather than absent: the box has
   to be visible in order to be the same box. Transparent like the outline
   buttons beside it (client, 22 September). */

.expiry-field .input[readonly] {
  background: transparent;
  color: var(--text-muted);
  cursor: default;
}

.expiry-field .input[readonly]:hover {
  border-color: var(--border);
}

/* Admin listing action row: view / deactivate / delete match the date field. */

.admin-listing__actions > .btn.btn--sm,
.admin-listing__actions > a.btn.btn--sm,
.admin-listing__actions > form > .btn.btn--sm {
  min-height: var(--control-h-sm);
  height: var(--control-h-sm);
  box-sizing: border-box;
}

/* Featured + bump: packed to the right, toggle flush against its expiry box. */

.admin-listing__promo {
  margin-left: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-2);
}

.admin-listing__promo-row {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-2);
}

.admin-listing__feature,
.admin-listing__bump {
  display: flex;
  flex: none;
  min-width: 0;
}

.admin-listing__promo-row .expiry-field {
  flex: none;
}

@media (max-width: 720px) {
  .admin-listing__head,
  .seller-listing__head {
    flex-direction: column;
  }

  .admin-listing__promo {
    margin-left: 0;
  }

  .admin-listing__title,
  .seller-listing__title {
    white-space: normal;
  }
}

/* --------------------------------------------------------------------------
   Admin FAQ — a card per question
   --------------------------------------------------------------------------

   "in the backend there is no place to edit FAQ - create it under 'content'."

   A card rather than a table row, for the reason the listings screen became
   cards: a question is a sentence and an answer is a paragraph, and neither
   survives being one column of six. The position sits in its own gutter on the
   left so the eye can run down the order without reading the questions, which
   is what somebody rearranging them is doing.
   -------------------------------------------------------------------------- */

.admin-faqs {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.admin-faq {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

/* A hidden question is still listed — this screen is the only place one can be
   found again — but it should not read as though it were on the page. */

.admin-faq--off {
  opacity: 0.72;
  border-style: dashed;
}

.admin-faq__head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}

.admin-faq__pos {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-muted);
  background: var(--surface-3);
  border-radius: var(--r-md);
}

.admin-faq__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.admin-faq__q {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--text);
  line-height: 1.25;
  margin: 0;
}

.admin-faq__a {
  font-size: var(--fs-sm);
  line-height: 1.35;
  color: var(--text-muted);
}

.admin-faq__a em {
  font-style: italic;
}

.admin-faq__a strong {
  font-weight: 700;
}

.admin-faq__rule {
  height: 1px;
  margin: 0;
  background: var(--border);
  border: 0;
}

.admin-faq__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

.admin-faq__actions form {
  display: flex;
}

/* Trash matches edit/hide at rest; hover is #FF0000 (not the ok-quiet green). */

.admin-faq__actions .btn.faq-trash:hover:not(:disabled),
.admin-faq__actions .btn.faq-trash:hover:not(:disabled) .icon,
.admin-faq__actions .btn.faq-trash:hover:not(:disabled) svg {
  color: #FF0000;
  border-color: #FF0000;
  background: transparent;
}

/* The two arrows read as one control, so they sit together with no gap
   between them and a gap after them. */

.admin-faq__actions form:nth-of-type(2) {
  margin-left: calc(var(--sp-2) * -1 + 2px);
}

/* Nothing to move at the ends of the list. Disabled rather than missing, so
   the row of buttons is the same shape on every card. */

.admin-faq__actions button[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.faq-admin {
  gap: var(--sp-2);
}

.faq-admin > .card,
.faq-admin > .stack > .card {
  margin: 0;
}

.faq-admin .card__header {
  padding: var(--sp-3) var(--sp-4);
}

.faq-admin .card__body {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}

.faq-admin .card__body > .stack,
.faq-admin .card__body > .stack--sm,
.faq-admin form.stack,
.faq-admin form.stack--sm {
  gap: var(--sp-2);
}

.faq-admin .field {
  gap: 2px;
}

.faq-admin .field__label {
  line-height: 1.2;
  margin: 0;
}

.faq-admin .textarea {
  min-height: 0;
}

.faq-admin .results-count {
  margin: 0;
  line-height: 1.3;
}

@media (max-width: 720px) {
  .admin-faq__head {
    flex-direction: column;
  }
}

.danger-zone {
  border-color: var(--error-border);
}

/* Settings Account tab: literal #FF0000 frame (beats the muted error token). */
.danger-zone.card--settings-red {
  border-color: #FF0000;
}

.danger-zone .card__header,
.danger-zone.card--fold.is-open > .card__fold {
  border-bottom-color: var(--error-border);
}

.danger-zone.card--settings-red > .card__header {
  border-bottom-color: #FF0000;
}

/* Closed fold: no red rule under the title — that looked like a double border
   on Web application (client, 22 September). */
.danger-zone.card--fold:not(.is-open) > .card__fold {
  border-bottom-color: transparent;
}

/*
  Deleting an account is the one red button on the screen, and the client named
  its colour: "under user's account this 'delete account' make with small letter
  and the background of this red button #FF0000."

  The literal value in both themes, like the favourite heart and the clear-all
  cross before it — --error is a brick in light and a salmon in dark, and this
  is the button that should look the same wherever it is met. Scoped to this
  block, so the administration panel's own destructive buttons keep the muted
  red they were reviewed with.

  The confirmation dialog's button is inside .danger-zone too, so the two reds
  cannot drift apart.
*/

.danger-zone .btn--danger {
  background: #FF0000;
  border-color: #FF0000;
  color: #fff;
}

.danger-zone .btn--danger:hover:not(:disabled) {
  background: #D90000;
  border-color: #D90000;
}

/* --------------------------------------------------------------------------
   My listings — the status bar above the table
   --------------------------------------------------------------------------

   "Move the All / Draft menu a bit upwards so its top is aligned with the top
   edge of the left menu block. Move the My Ads block upwards and make the gap
   smaller. And move the add listing button a bit to the left, so it is aligned
   with the add listing button in the header."

   Three separate things, one row.

   Up: the row's box already started on the same pixel as the account panel
   beside it, but a tab carries 12px of padding above its label, so the first
   ink in the column sat that far below the panel's top edge — which is the
   line the eye actually aligns against. The row is lifted by that padding, so
   the tab labels and the panel's edge start together.

   Left: the page's content box is one --gutter wider on each side than the
   header's, because the header's inner container is inset inside the header
   panel. The button therefore overhung the one in the bar above it by exactly
   that gutter. Pulling it in by --gutter puts the two on the same right edge at
   every breakpoint, since both read the same token.
   -------------------------------------------------------------------------- */

.my-listings-bar,
.admin-listings-bar {
  margin-top: calc(var(--sp-3) * -1);
}

/* Same 8px column as admin listings: toolbar → ads (client, 22 September). */
.shell--nav > .stack:has(> .my-listings-bar) {
  gap: 8px;
}

.my-listings-bar__search,
.admin-listings-bar__search {
  /* Sit optically between the tabs band and the first listing card — tabs
     stay put (client, 21–22 September). */
  transform: translateY(0.4rem);
}

.my-listings-bar__search {
  margin-right: var(--gutter);
}

/*
  And the gap below it.

  The clone confirmation lands in a slot between this row and the table. Empty —
  which is every page load — it was still a flex item, so the column's 24px gap
  was paid twice and the table sat 48px below the tabs instead of 24. Hiding the
  slot until it has something in it closes the half of the gap that was never
  meant to be there, which is the "reduce the vertical gap" half of the note.
*/

#clone-slot:empty {
  display: none;
}

/* ==========================================================================
   Market picker
   Moved off the header onto the page banner, so it cannot be mistaken for the
   language control. Two tones: default sits on a surface, --light sits on
   photography and needs its own colours in both themes.
   ========================================================================== */

.market-picker {
  position: relative;
}

/* Form Field mode — same menu as the banner, sized like a .select. */
.market-picker--field {
  width: 100%;
}

.market-picker--field .market-picker__btn {
  width: 100%;
  justify-content: flex-start;
  font-weight: 500;
}

.market-picker--field .market-picker__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.market-picker--field .dropdown {
  left: 0;
  right: 0;
  min-width: 100%;
  width: max(100%, 280px);
}

.market-picker__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 600;
  transition: background-color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

.market-picker__btn:hover {
  background: var(--surface-3);
}

.market-picker__btn svg {
  width: 16px;
  height: 16px;
}

/*
  No gold focus frame on the chip.

  Dark mode's --focus-color is gold, and the global :focus-visible outline
  drew a double golden rectangle around the LV/EE control on the hero —
  the "golden selector" the client asked to remove. Keyboard focus still
  works; it just does not paint a ring on this chip.
*/
.market-picker__btn:focus,
.market-picker__btn:focus-visible,
.market-picker--light .market-picker__btn:focus,
.market-picker--light .market-picker__btn:focus-visible,
.hero__market .market-picker__btn:focus-visible,
.search-banner__aside .market-picker__btn:focus-visible {
  outline: none;
  box-shadow: none;
}

/*
  The chip gets smaller as the screen does.

  The client, 31 August: "just when the screen gets smaller, this button goes
  bit smaller as well." It is what pays for the chip keeping the banner's corner
  on a phone rather than stacking out of it — at a fixed 40px tall and ~96px
  wide it took a quarter of a 360px strip, which is why it was moved out in the
  first place.

  Below 1024px only, so the approved desktop chip is untouched: above that
  breakpoint every value here is already at its ceiling anyway, but writing the
  query means no desktop pixel depends on a clamp being read the way I read it.

  Every clamp is the same straight line — the small value at 360px, the current
  value at 1024px, and neither exceeded outside that range. 360 is the narrowest
  phone the site is drawn for; 1024 is where the results column stops being one
  column. The ceilings are exactly today's numbers, so this can only ever make
  the chip smaller, never larger than it has been.

  The flag needs the doubled class to beat `.flag.flag`, and keeps its 11:8
  proportions while it shrinks — a flag squashed to a square is the one thing
  .flag.flag exists to prevent.
*/

@media (max-width: 1024px) {
  .market-picker__btn {
    height: clamp(30px, 1.51vw + 24.6px, 40px);
    gap: clamp(6px, 0.3vw + 4.9px, 8px);
    padding: 0 clamp(8px, 0.6vw + 5.8px, 12px);
    font-size: clamp(12px, 0.3vw + 10.9px, 14px);
  }

  .market-picker__btn svg {
    width: clamp(14px, 0.3vw + 12.9px, 16px);
    height: clamp(14px, 0.3vw + 12.9px, 16px);
  }

  .market-picker__btn .flag.flag {
    width: clamp(19px, 0.45vw + 17.4px, 22px);
    height: clamp(14px, 0.3vw + 12.9px, 16px);
  }
}

/*
  On photography the control needs a fixed light treatment in both themes —
  the light theme's dark-on-white would vanish against the hero image.
*/

.market-picker--light .market-picker__btn {
  background: rgba(12, 45, 72, 0.55);
  border-color: transparent;
  color: #fff;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.market-picker--light .market-picker__btn:hover {
  background: rgba(12, 45, 72, 0.72);
  border-color: transparent;
}

/*
  The search banner uses the --light treatment above, unchanged.

  It had a variant of its own that drew the chip out of the banner's own ink at
  low alpha. The client compared the two side by side and preferred the hero's,
  so the banner now asks for "light" and there is one treatment to maintain
  instead of two.
*/

/* --------------------------------------------------------------------------
   Market menu

   The list is the whole world, so the panel is a fixed-height column: the
   search field and the locate button are pinned, and only the countries
   between them scroll. Without that the field scrolls away the moment you
   start looking through the list.
   -------------------------------------------------------------------------- */

.market-menu {
  display: flex;
  flex-direction: column;
  min-width: 268px;
  max-width: 92vw;
}

.market-menu__search {
  position: relative;
  display: flex;
  align-items: center;
  padding: 0 var(--sp-2) var(--sp-2);
}

.market-menu__input {
  min-height: 38px;
  padding-left: var(--sp-3);
  padding-right: var(--sp-8);
  font-size: var(--fs-sm);
}

/* The platform's own clear affordance is suppressed in favour of the button
   below, so there is exactly one on every platform. */

.market-menu__input::-webkit-search-cancel-button {
  display: none;
}

/*
  Focus — green field frame, day and dark.

  Opening this panel focuses the search field straight away, so the frame is
  visible as soon as the menu appears. The client wants that frame in #008000
  (same as other typed fields), not the bright theme focus colour / gold halo.
*/

.market-menu .market-menu__input:focus,
.market-menu .market-menu__input:focus-visible {
  outline: none;
  border-color: #008000;
  box-shadow: var(--field-focus-ring);
}

.market-menu__clear {
  position: absolute;
  right: calc(var(--sp-2) + 6px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-muted);
  cursor: pointer;
}

.market-menu__clear:hover {
  color: var(--text);
}

.market-menu__clear svg {
  width: 13px;
  height: 13px;
}

.market-menu__list {
  overflow-y: auto;
  overscroll-behavior: contain;
  max-height: min(46vh, 320px);
}

/*
  Country rows, 70% closer together.

  The client, 2 September: "in the frontpage and listing page banner the
  country menu, reduce there the line spacing by 70% so that enlisted countries
  are closer to each other. The font size do not change, only reduce spacing."

  So the font is untouched and so is the row's inside gutter — only the empty
  space above and below each name moves. A row was two var(--sp-3) paddings
  around a 14px name: 24px of space between one country and the next. 30% of
  that is 7.2px, which is 3.6px a side, and the row goes from 45.7px to 28.9px
  without the name changing size by a pixel.

  The captions above the two groups keep their own spacing: they are what
  separates the groups, and tightening them as well would have run the two
  lists into one another.

  Scoped to .market-picker, which is the country menu and only the country
  menu. The tag picker borrows this menu's search field and list — the
  restricted-markets and languages controls in the back office, and the
  language filter on the brokers page — and those rows are checkboxes on a
  form the client did not write about.
*/

.market-picker .market-menu__item {
  padding-block: 3.6px;
}

/* Check is always in the row so JS can move is-active without rebuilding icons.
   Only the trailing check (after .spacer) is toggled — not the unknown-flag svg. */
.market-menu__item > .spacer,
.market-menu__item > .spacer + svg {
  visibility: hidden;
}
.market-menu__item.is-active > .spacer,
.market-menu__item.is-active > .spacer + svg {
  visibility: visible;
}

.market-menu__item[hidden] {
  display: none;
}

.market-menu__group {
  padding: var(--sp-3) var(--sp-3) var(--sp-1);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-subtle);
}

.market-menu__group[hidden] {
  display: none;
}

.market-menu__empty {
  padding: var(--sp-5) var(--sp-3);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ==========================================================================
   Banner variants
   ========================================================================== */

/* Overlay banner — laid on the hero image, so it carries its own elevation. */

.cbanner--overlay {
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/*
  Search-results strip. Shorter than the homepage banner and paired with the
  market picker on its right, matching the reference the client sent.
*/

/*
  Listing-page advertising strip.

  It occupies the whole of the search layout's header row: the full width of the
  results column, and the height of that row, which reaches up to the top of the
  breadcrumb beside it. The client asked for both — the strip used to be one
  line tall, inset from the right by the width of the market picker, and started
  well down the page.

  "Choose your market" now rides on the banner, top-right, the way it rides on
  the homepage hero, rather than sitting outside it. That is what frees the
  width; the padding-right below is what keeps the headline clear of it.
*/

.search-banner {
  position: relative;
  display: flex;
  /* Taller at the client's request — the strip reached only just past the
     height of its own button, which made a sponsored placement read as a
     notification bar. It now has room to sit as a banner.

     Doubled again on 29 August: "now this listing page banner make twice bigger
     in up-down axis, so stretch it out downwards." The top edge is pinned to
     the breadcrumb by the negative margin below, so height added here can only
     go downwards, which is what he asked for. */
  min-height: 280px;
  /* Up past the breadcrumb row, so the banner's top edge and the breadcrumb's
     start on the same line. The filter sidebar beside it is unaffected: it
     still begins under the breadcrumb, which is where it moved up to when the
     "Property for sale" heading was removed. */
  margin-top: calc((var(--crumb-h) + var(--sp-2) - 5px) * -1);
  /* Same residual as the top pull (the 5px in the calc above): Grid/List sit
     as close under the banner as the banner sits under the header/crumb. */
  margin-bottom: 5px;
}

.search-banner .cbanner {
  flex: 1;
  min-width: 0;
  align-items: center;
  /* Right padding clears the market picker floating over this corner. */
  padding: var(--sp-6) clamp(160px, 18%, 210px) var(--sp-6) var(--sp-5);
}

.search-banner .cbanner__body {
  display: none;
}

/* shorter: headline carries it */

/*
  Except on artwork, where both lines an administrator typed are drawn.

  The client asked on 28 August for this strip to work "the same system as for
  the frontpage banner", where the banner's two text fields are what is shown
  and an empty one shows nothing. Hiding the second line would make a field he
  had filled in do nothing here and something there. The themed strip above is
  left as it was — it is one line tall by design and nobody asked for that to
  change.
*/

.search-banner .cbanner--art .cbanner__body {
  display: block;
}

/*
  The advertiser's button, under the words it belongs to.

  The client, 31 August: "in the listing page banner the button is in the wrong
  place, place the button and text there like in the frontpage banner."

  On the front page the button is its own line under the headline and the second
  line, at the same left edge — .hero__cta, one rule, one margin. This is that
  rule. The markup moved with it: the button used to be a sibling of
  .cbanner__text and therefore a flex item of the strip itself, which is what
  pinned it to the right-hand edge and centred it vertically no matter how the
  words above were set. Inside the text block it simply follows the last
  paragraph, exactly as it does on the hero.

  Nothing else about the strip changes. The picture, the picker in the top-right
  corner, the sponsor line and the two typed lines are all where they were, and
  the strip is still one flex row: an icon or an image, and a column of words
  beside it.
*/

.search-banner .cbanner__cta {
  margin-top: var(--sp-4);
}

/*
  On the banner rather than beside it, in the same corner the homepage uses.

  z-index is well clear of the results below because this is a stacking
  context: everything inside it, the open country menu included, is clamped to
  whatever number is written here no matter how high the menu's own z-index is.
  At 2 the menu opened *behind* the cards — a card's badges and heart sit at 2
  and 4 of their own, and a card is not a stacking context unless it is being
  hovered, so those numbers were competing with this one directly. The cards now
  isolate themselves (see .pcard), and this sits an order of magnitude above
  them so nothing on the results column can reach over an open menu.
*/

.search-banner__aside {
  position: absolute;
  top: var(--sp-3);
  right: var(--gutter);
  z-index: 40;
}

/* With no active banner the row still has to hold the market picker, so the
   spacer keeps the row's height and gives the picker something to sit on. */

.search-banner__spacer {
  flex: 1;
  min-width: 0;
}

/* Below 1024px the layout is a single column and the banner spans the whole of
   it, so reaching up alongside the breadcrumb would run over its text. */

@media (max-width: 1024px) {
  .search-banner {
    margin-top: 0;
  }
}

@media (max-width: 720px) {
  /*
    The picker stays on the banner at every width, and shrinks instead.

    The client, 31 August, looking at the narrow view: "look the choose your
    market button, it moves out of the banner. Make it to stay always on the
    banner where it must be, just when the screen gets smaller, this button goes
    bit smaller as well."

    It moved out because this block used to answer the phone by giving up the
    corner: position: static here destroyed the containing block the aside is
    absolutely placed against, and column-reverse then drew the picker *above*
    the artwork, since the DOM order is banner-then-picker. That was a
    deliberate choice at the time — a 40px chip pinned to a corner of a strip
    only a little taller than itself, with the headline wrapping underneath — and
    the client has now asked for the other answer. The chip getting smaller is
    what makes it fit, so that is what happens instead. See .market-picker__btn
    below for the sizes.

    position: relative is therefore kept rather than dropped, so the corner is
    still there to pin to.
  */

  .search-banner {
    align-items: stretch;
    min-height: 0;
    /* One column here, so there is no breadcrumb row to reach up alongside. */
    margin-top: 0;
    gap: var(--sp-3);
  }

  /*
    Room for the chip in the corner it now keeps.

    A flat --sp-4 on all four sides ran the headline straight under it. The
    right side is a proportion of the strip rather than a number, because the
    chip is itself a proportion of the viewport at this width; the floor holds
    the smallest chip plus its inset on a 320px screen, and the ceiling stops a
    720px strip reserving more than the chip could ever use.

    Only the copy is affected. The artwork is two absolutely positioned images
    on inset: 0, which no padding here can reach.
  */

  .search-banner .cbanner {
    padding: var(--sp-4);
    padding-right: clamp(92px, 26%, 140px);
  }

  /*
    A banner on a phone has to be a shape, not a consequence.

    The client, 30 August: "what is this in the phone view?? No banners at all
    not in frontpage nor in listing page!"

    He was right, and this is where the listings half of it was. min-height: 0
    above is correct for a strip whose height is its text — but the artwork is
    two absolutely positioned images, which contribute no height at all, so a
    banner whose message is written into the picture (which is every banner he
    has uploaded) had nothing left to stand on. It collapsed to its own padding:
    34 pixels of blur, measured on his own site, with a 2650 x 750 photograph
    fitted into it. Not a small banner — an invisible one.

    So the strip keeps the shape it is drawn at on a monitor, 1024 x 280, and
    takes its height from its width. It is the same shape the Advertising screen
    asks for uploads in, so a banner uploaded at the recommended 2048 x 560 fills
    it exactly on a phone as well, with no blurred band around it in fit mode and
    nothing cut off in fill.

    aspect-ratio rather than a fixed height, because a phone is not one width:
    a 320px screen and a 430px one both get the banner's own proportions instead
    of a number chosen for one of them. And it is a preferred height, not a
    ceiling — a banner carrying two lines of text and a button still grows to
    hold them rather than clipping them.
  */

  .search-banner .cbanner--art {
    /*
      The strip's own shape, in a form that content can still beat.

      1024 x 280 is what it is drawn at on a monitor and the shape the
      Advertising screen asks uploads to be in, so a banner uploaded at the
      recommended 2048 x 560 lands on all four edges here too: nothing cut off
      in fill, no blurred band in fit.

      min-height and not aspect-ratio, though aspect-ratio says it far better.
      A box given one takes that height and lets whatever will not fit spill out
      of it, and .cbanner--art clips its overflow — so a banner carrying a
      headline, a second line and a button had all three drawn on top of each
      other inside 98 pixels and the last of them cut in half. A floor is what
      was wanted: this shape when the picture is the whole message, and taller
      the moment there is copy that needs it.

      The width is arithmetic rather than a percentage because min-height has no
      percentage to resolve against here. It is the width this box actually has
      at this breakpoint, and since 31 August that is the whole viewport: the
      strip is bled out through the page's two gutters so it reaches both edges
      of the phone, the way the header above it does — see the note on the bleed
      in layout.css. It used to subtract --gutter twice, which is now two
      gutters short and would letterbox a correctly-sized banner. In a desktop
      window narrowed below this breakpoint 100vw also counts the scrollbar,
      which makes the floor a few pixels deeper than the shape; in fit mode that
      is a hairline of blur, and in the other two it is nothing at all.
    */
    min-height: calc(100vw * 280 / 1024);
  }

  /*
    And the box has to be allowed to use it.

    The base rule gives this strip flex: 1, which is right beside the market
    picker on a monitor and wrong stacked under it: flex-basis: 0 makes the
    box's own preferred height count for nothing. flex: none hands the height
    back to the content and to the floor above. Width is untouched —
    align-items: stretch on the column above still fills it.
  */

  /*
    flex: 1 from the base rule stands, because this is still a row.

    There was a `flex: none` here, and it was right for the column this used to
    become: flex-basis: 0 in a column makes the box's own preferred height count
    for nothing, so the min-height floor below did nothing. In a row flex-basis
    is a *width*, and shrink-to-fit is what a banner must never be — the strip
    took its width from its headline and pushed the page 148px wider than the
    phone it was drawn on. The floor below works normally in a row, so nothing
    is lost by letting the base rule stand.
  */

  /*
    With no banner running there is nothing to ride, so the picker stacks.

    This is the half of the old phone behaviour that was right and stays. The
    spacer is rendered only when .HasBanner is false (search.html), so :has is
    reading the same condition the template branched on — and in that state the
    picker is already a plain surface button rather than the glass one, because
    the template passes no tone. Nothing is pinned to a corner that is not
    drawn.
  */

  .search-banner:has(.search-banner__spacer) {
    position: static;
    flex-direction: column-reverse;
  }

  .search-banner:has(.search-banner__spacer) .search-banner__aside {
    position: static;
    display: flex;
    justify-content: flex-end;
  }

  .search-banner__spacer {
    display: none;
  }

  .hero__market {
    padding-top: var(--sp-3);
  }

  /*
    The glass chip is kept at this width, because the chip is on the banner.

    This block used to swap it back to a plain surface button, and that was the
    right call for a picker that had stacked out onto the page background: a
    half-transparent dark pill on a cream page is a dark pill for no reason.
    Since it now stays in the banner's corner on a phone as well, the hero
    treatment is again the one that belongs — it is sitting on photography.

    The bannerless case above keeps the plain button, and gets it the way it
    always did: the template passes no tone, so .market-picker--light is never
    put on it in the first place.
  */
}

/* --------------------------------------------------------------------------
   Lightbox thumbnails
   Sits under the main image so the whole set is visible at a glance and any
   photograph is one click away rather than several arrow presses.
   -------------------------------------------------------------------------- */

.lightbox__thumbs {
  position: absolute;
  left: 50%;
  bottom: calc(var(--sp-8) + 28px);
  transform: translateX(-50%);
  display: flex;
  gap: var(--sp-2);
  max-width: min(92vw, 900px);
  padding: var(--sp-2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}

.lightbox__thumb {
  flex: none;
  width: 96px;
  height: 64px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--r-sm);
  overflow: hidden;
  opacity: 0.55;
  transition: opacity var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.lightbox__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.lightbox__thumb:hover {
  opacity: 0.85;
}

.lightbox__thumb.is-current {
  opacity: 1;
  border-color: #fff;
}

@media (max-width: 640px) {
  .lightbox__thumb {
    width: 68px;
    height: 48px;
  }

  .lightbox__thumbs {
    bottom: calc(var(--sp-6) + 26px);
  }
}

/* --------------------------------------------------------------------------
   Map results: List / Grid

   Two layouts over the same cards, switched by a class so nothing re-renders
   and the map keeps its viewport.

   List  — the default. One card per row, image beside the text, with the
           location and the facts line visible.
   Grid  — three or four to a row, image on top, price and title only. Denser,
           so more of the result set is in view at once.

   Both keep the country flag, the country code and the locate control, which
   is what ties a card to its pin.
   -------------------------------------------------------------------------- */

/*
  A real grid, sized by container queries.

  The client's two notes here were "grid view is broken, there should be 3 or 4
  ads in one row" and "make that it would always work and with every screen size
  the layout would be fine", so the column count is a function of how wide the
  results column actually is, not of how wide the window is — the two differ by
  the map on a desktop, by nothing on a phone, and by the collapsed state in
  between.

  Grid rather than the wrapped flex row this used to be. The flex version had to
  hard-code a percentage per line, which meant one number could only ever
  describe one column count; `minmax(0, 1fr)` tracks let every card in a row
  take the same width and, crucially, gives each one a definite width for the
  photograph's aspect ratio to resolve against. That was the reason the media
  box collapsed here before and had to be pinned to a fixed 132px.

  The breakpoints are in card terms: a card stops being useful below about
  170px, which is where a six-figure price starts to wrap.
*/

.map-shell.is-grid-mode .map-results__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-content: start;
  gap: var(--sp-3);
}

@container map-results (min-width: 560px) {
  .map-shell.is-grid-mode .map-results__list {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@container map-results (min-width: 800px) {
  .map-shell.is-grid-mode .map-results__list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@container map-results (max-width: 330px) {
  /* One per line below this. Two cards fit comfortably at 390px — the narrowest
     phone worth designing for — at about 173px each; it is only below roughly
     330px of column that a price and a two-line title run out of room. */

  .map-shell.is-grid-mode .map-results__list {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A grid item's default min-width is its content, which is what let a long
   title push a card wider than its track and out of the panel. */

.map-shell.is-grid-mode .map-results__list > * {
  min-width: 0;
}

/*
  The card clips at the photograph, not at the card.

  `.pcard` sets `overflow: hidden` so the image cannot square off the rounded
  top corners. That makes every card a scroll container, and Chromium sizes an
  auto grid row against a scroll-container item as though its contents were not
  there: every row came out 73px tall and every card was sliced off just below
  its photograph. Moving the clip down onto `.pcard__media` — which needs it
  anyway and is the only child that can overflow — leaves the card an ordinary
  box that a row can measure. The corners are unchanged.
*/

.map-shell.is-grid-mode .map-results__list .pcard--map {
  flex-direction: column;
  overflow: visible;
}

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__media {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  border-radius: calc(var(--r-md) - 1px) calc(var(--r-md) - 1px) 0 0;
}

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__body {
  padding: var(--sp-3);
  gap: 2px;
  min-width: 0;
}

/* The gallery, in the half of this screen that has room for it.

   The client, 31 August: "and in the grid view, make the image gallery to work
   in preview mode like in the listing page." A map card in Grid mode is drawn
   at the full width of the column with a 4:3 photograph — the same shape a
   search card has — so it gets the same controls a search card has: the two
   paging zones over the outer thirds, the pager capsule, and the photo count.
   In List mode the card is 132px of thumbnail and these stay hidden. */

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__pager,
.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__count {
  display: inline-flex;
}

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__zone {
  display: block;
}

/* .pcard__media is the positioning context every one of those is placed
   against, and the grid rules above hand it a radius but no corner clipping of
   its own. Without this the outermost slide's square corner sits proud of the
   card's rounded one. */

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__media {
  overflow: hidden;
}

/* Fewer details, as asked: location and facts drop out of the compact card. */

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__location,
.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__facts {
  display: none;
}

/* Titles are clamped so a row of cards stays level whatever the wording, and
   both lines are allowed to break mid-word rather than push the card open. */

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__price,
.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__title {
  overflow-wrap: anywhere;
}

/* The locate control and market badge stay in both layouts — they are how a
   card is matched to its pin. */

.map-shell.is-grid-mode .map-results__list .pcard--map .pcard__locate {
  top: 6px;
  right: 6px;
}

/*
  Broker cards in the same grid.

  In list mode a broker card is a row — photograph left, details right — which
  is what fits a 420px column. A grid cell here is around 185px wide, and a
  96px photograph beside anything leaves 70px for a name, an agency and a
  distance: the client's screenshots of that state show "Kadaka Kinnisvara"
  broken over three lines and "0.51 KM" split across two.

  So in grid mode the broker card stacks, exactly as the listing card beside it
  does, and drops the same kind of detail the listing card drops.
*/

.map-shell.is-grid-mode .map-results__list .bcard--map {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    'media'
    'identity'
    'meta'
    'foot';
  gap: 0;
  padding: 0;
  width: 100%;
}

.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__media {
  /* Full-bleed 4:3 window — same as .pcard--map in Grid. Absolute photo fill
     below beats the base 4:5 portrait that was letterboxing in this box. */
  position: relative;
  width: 100%;
  min-width: 0;
  max-width: none;
  height: auto;
  min-height: 0;
  margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: 0;
  overflow: hidden;
}

.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  border-radius: 0;
  object-fit: cover;
  object-position: center 20%;
}

.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__identity {
  padding: var(--sp-3) var(--sp-3) 0;
}

.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__foot {
  margin-top: 0;
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
}

/* Fewer details, as on the listing card: the role and the office go, leaving
   the name, the agency and — when there is one — the distance. */

.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__role,
.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__location {
  display: none;
}

.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__name,
.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__agency {
  overflow-wrap: anywhere;
}

.map-shell.is-grid-mode .map-results__list .bcard--map .bcard__locate {
  top: 6px;
  right: 6px;
}

/* --- The switch itself ---------------------------------------------------- */

.segmented--sm {
  padding: 3px;
}

.segmented--sm .segmented__item {
  min-height: 30px;
  padding: 0 var(--sp-3);
  gap: 6px;
  font-size: var(--fs-xs);
  border: 0;
  background: none;
  cursor: pointer;
}

.segmented--sm .segmented__item svg {
  width: 14px;
  height: 14px;
}

/* Selected: raised surface, teal ink and heavier type — three cues, so the
   state does not depend on colour alone. */

.view-switch .segmented__item.is-on {
  background: var(--surface);
  color: var(--teal);
  font-weight: 700;
  box-shadow: var(--shadow-sm);
}

[data-theme='dark'] .view-switch .segmented__item.is-on {
  color: var(--text);
}

.view-switch .segmented__item:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 1px;
}

@media (max-width: 720px) {
  /* Labels drop on a narrow toolbar; the icons and aria-pressed carry it. */

  .view-switch .segmented__item span {
    display: none;
  }
}

/* ==========================================================================
   Add a listing — continuous form
   ========================================================================== */

/*
  Sections are separated by a rule rather than by page breaks, so the form
  reads as one document while each part still has an edge.
*/

.listing-sections {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.listing-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
  /* Anchor offset, so a scrolled-to heading is not tight against the top. */
  scroll-margin-top: var(--sp-6);
}

.listing-section:first-child {
  padding-top: 0;
}

#ls-deal {
  border-bottom: 0;
}

#ls-media {
  border-bottom: 0;
}

.listing-section:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.listing-section--publish {
  padding-top: 10px;
}

/* The heading takes focus when a waypoint is clicked — no gold frame. */

.listing-section h2:focus,
.listing-section h2:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Compact field rows across the publishing form. */

.listing-sections .listing-fields-grid,
.listing-sections .listing-details-grid {
  gap: 8px 12px;
}

/* Rooms: eight number boxes in 4×2 — narrower than a half-width pair. */

.listing-rooms-grid {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 860px) {
  .listing-rooms-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 520px) {
  .listing-rooms-grid {
    grid-template-columns: 1fr;
  }
}

.listing-sections .listing-fields-grid--amenities {
  gap: 0 12px;
}

.listing-sections .field {
  gap: 2px;
}

.listing-sections .check-grid {
  gap: 0 12px;
}

.listing-sections .option-grid {
  gap: 8px;
}

.listing-sections .option-tile {
  padding: 12px 10px;
  gap: 6px;
}

/* Features: no lede, groups and ticks packed tight under the title. */

.listing-section--features {
  gap: 4px;
  padding-top: 8px;
  padding-bottom: 8px;
}

.listing-amenities {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.listing-sections .amenity-group + .amenity-group {
  margin-top: 0;
}

.listing-sections .amenity-group__title {
  margin-bottom: 2px;
  line-height: 1.2;
}

/* Land type + Features: same tight tick rows (drop the 32px check min-height). */

.listing-section--features .check,
.listing-sections .check-grid .check {
  min-height: 0;
  padding: 0;
  gap: 6px;
  align-items: center;
}

.listing-section--features .check__text,
.listing-sections .check-grid .check__text {
  line-height: 1.2;
}

.listing-currency-info {
  margin: 0;
  display: flex;
  align-items: center;
  cursor: default;
  color: var(--text-muted);
  background: var(--surface-2);
  border-style: dashed;
}

/*
  Amenity groups.

  Thirty-four ticks after the client's 19 August list, which is too many to
  read as one undivided pair of columns: a seller looking for "Dishwasher"
  should be able to find the kitchen first and then read seven items, not scan
  thirty-four. A <fieldset> rather than a heading and a div, because that is
  what a named group of checkboxes is — the legend names the group for a screen
  reader as well as for a reader.

  The legend carries .overline's face rather than a border or a panel: these are
  divisions inside one section, and anything heavier would compete with the
  section headings above them.
*/

.amenity-group {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.amenity-group__title {
  padding: 0;
  margin-bottom: var(--sp-2);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--slate);
}

[data-theme='dark'] .amenity-group__title {
  color: var(--text-muted);
}

/*
  The waypoint rail follows the page rather than scrolling away with it, and
  starts below the floating menu control: this panel reaches the page's right
  edge, which is where that control now sits.
*/

.stepper {
  position: sticky;
  top: var(--floating-menu-band);
  align-self: start;
}

/* Waypoints are buttons now, so they need the anchor styling back. */

.stepper__marker,
.stepper__label {
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* The "Jump to section" picker that used to stand in for the rail below 1024px
   was removed along with its markup: the rail is an overlay drawer at that
   width now, so there is nothing for a substitute control to substitute for. */

/* --- Location section ----------------------------------------------------- */

.listing-map {
  height: 340px;
}

/* Title + pin hint as one block, tight under "Location". */

.listing-loc-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.listing-loc-head .map-hint {
  margin: 0;
}

/* Search box left-aligned at ~30% width — clear-X sits next to short labels. */

#ls-location > .location-field {
  width: 30%;
  max-width: 30%;
}

@media (max-width: 640px) {
  #ls-location > .location-field {
    width: 100%;
    max-width: none;
  }
}

/* Public location under the map — ~1mm gap so the green box is not flush. */

.listing-loc-follow {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
}

.listing-loc-follow .map-hint {
  margin: 0;
}

.listing-loc-follow .overline {
  margin: 0;
}

.listing-loc-follow .readonly-grid {
  gap: 6px 12px;
}

.listing-loc-follow .field {
  gap: 2px;
}

.listing-loc-follow .field__hint {
  margin: 0;
  line-height: 1.3;
}

/*
  Read-only, because these come from the map pin. Given a distinctly flatter
  treatment than an editable input so the difference is visible rather than
  only discoverable by clicking.
*/

.readonly-grid .input[readonly] {
  background: var(--surface-2);
  color: var(--text-muted);
  border-style: dashed;
  cursor: default;
}

.readonly-grid .input[readonly]:hover {
  border-color: var(--border);
}

/*
  The one editable location field. Green, at the client's request, so it stands
  out from the read-only block above it as the line the seller controls.
*/

/*
  One weight of border, all the way round.

  It used to carry a 3px accent down the left-hand side — "here in the add
  listing page where can enter your location the green bow, make the borderline
  there equal, so on the left remove this bolder part". The accent was doing a
  job the green label, the green ground and the green field border already do
  three times over, and on a rounded box a thick edge on one side only reads as
  a drawing mistake rather than as emphasis.
*/

.field--editable-location {
  margin-top: 0;
  padding: 8px 12px;
  border: 1px solid var(--success-border);
  border-radius: var(--r-md);
  background: var(--success-bg);
}

.field--editable-location .field__label {
  color: var(--success);
}

.field--editable-location .field__label svg {
  width: 15px;
  height: 15px;
}

.field--editable-location .input {
  background: var(--surface);
  border-color: var(--success-border);
}

/* No green focus frame while typing — client, 21 September: "selector-frame
   will come over this field, this remove, here no selector." */
.field--editable-location .input:focus,
.field--editable-location .input:focus-visible {
  border-color: var(--success-border);
  box-shadow: none;
  outline: none;
}

.field--editable-location .field__hint {
  color: var(--text-muted);
}

/* Empty public line — no location chosen (rail + this box both go red). */
.field--editable-location.is-missing {
  border-color: var(--error-border);
  background: var(--error-bg);
}

.field--editable-location.is-missing .field__label {
  color: var(--error);
}

.field--editable-location.is-missing .input {
  border-color: var(--error-border);
}

.field--editable-location.is-missing .input:focus {
  border-color: var(--error);
  box-shadow: none;
}

/* --- The seller's videos on a listing page --------------------------------- */

/*
  One column, at the width of the text beside it, and never taller than a
  comfortable half-screen: a portrait clip filmed on a telephone is 9:16, and
  left to itself it would push the description a page and a half down.
  object-fit keeps the whole frame visible inside that box rather than cropping
  it — it is the seller's shot of their own property, and a crop is an edit.
*/

.video-list {
  display: grid;
  gap: var(--sp-4);
}

.video-player {
  width: 100%;
  height: auto;
  max-height: 70vh;
  object-fit: contain;
  border-radius: var(--r-lg);
  background: #0b1620;
  border: 1px solid var(--border);
}

/* --- Media tiles ---------------------------------------------------------- */

.uploader.is-dragover {
  border-color: var(--success);
  background: var(--success-bg);
}

.photo-thumb {
  cursor: grab;
}

.photo-thumb.is-dragging {
  opacity: 0.4;
  cursor: grabbing;
}

/*
  A video's tile.

  The preview is the film itself (.photo-thumb__film) — preload=metadata paints
  a real frame of the clip. The play badge sits on top; the named grey box is
  only while the upload / convert is still running.
*/

.photo-thumb__film {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #0b1620;
  pointer-events: none;
}

.photo-thumb__play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  color: #fff;
}

.photo-thumb__play svg {
  width: 34px;
  height: 34px;
  padding: 6px;
  border-radius: 50%;
  background: rgba(8, 20, 32, 0.55);
  filter: drop-shadow(0 1px 3px rgba(8, 20, 32, 0.45));
}

/* Before the poster arrives, the tile names the file instead. */

.photo-thumb__video {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  width: 100%;
  height: 100%;
  padding: var(--sp-3);
  background: var(--surface-3);
  color: var(--text-muted);
  text-align: center;
}

.photo-thumb__video svg {
  width: 22px;
  height: 22px;
}

.photo-thumb__name {
  font-size: var(--fs-2xs);
  word-break: break-word;
  line-height: 1.3;
}

.photo-thumb__kind {
  position: absolute;
  left: 6px;
  bottom: 6px;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: #ff0000;
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
}

/* Keyboard reordering. Always present for touch and keyboard; on a pointer
   device they fade in with the tile so the grid stays calm at rest. */

.photo-thumb__move {
  position: absolute;
  left: 6px;
  top: 6px;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.photo-thumb:hover .photo-thumb__move,
.photo-thumb:focus-within .photo-thumb__move {
  opacity: 1;
}

@media (hover: none) {
  .photo-thumb__move {
    opacity: 1;
  }
}

/* Shape and colours come from the shared rule beside .photo-thumb__remove. */

.photo-thumb__move button {
  color: var(--navy);
  cursor: pointer;
}

.photo-thumb__move button:hover:not(:disabled) {
  background: var(--navy);
  border-color: var(--navy);
  color: #FFFFFF;
}

[data-theme='dark'] .photo-thumb__move button {
  color: var(--text);
}

[data-theme='dark'] .photo-thumb__move button:hover:not(:disabled) {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--text-on-gold);
}

.photo-thumb__move button svg {
  width: 13px;
  height: 13px;
}

.photo-thumb__move button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.photo-thumb__move button:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 1px;
}

/* A button that has to read as a link inside running text. */

.linklike {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}

/* Icon and text on one line, rather than the SVG sitting on its own row. */

.map-hint {
  display: flex;
  align-items: center;
  gap: 6px;
}

.map-hint svg {
  flex: none;
  width: 14px;
  height: 14px;
}

/*
  Restricted-market refusal under the add-listing map. Bold #ff0000; stays
  until the seller places the pin somewhere the site covers. The matching
  top flash is .restricted-flash.
*/
.map-hint--blocked,
.field__error.map-hint.map-hint--blocked {
  color: #ff0000;
  font-weight: 700;
}

/*
  Restricted-country flash — same shape and place as the admin cache-cleared
  banner, red #ff0000 / white. Shown by previaRestrictedRefuse; dismissed by
  a click on the banner or anywhere else on the page.
*/
.restricted-flash {
  position: fixed;
  top: calc(var(--sp-4) + 68px);
  left: 1.25rem;
  /* Above the fixed Filters panel (and the mobile drawer), the header, and
     the map — the refusal must read on top of the page, not under the sidebar. */
  z-index: var(--z-toast);
  width: max-content;
  max-width: calc(100vw - 2.5rem);
  cursor: pointer;
}

.restricted-flash__banner {
  background: #ff0000;
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1.35;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);
  white-space: nowrap;
}

.restricted-flash__text {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --------------------------------------------------------------------------
   Homepage property-type picker

   The shared checkbox grid, folded into a dropdown so it occupies one field in
   the hero. The panel escapes the search panel the same way the Location
   suggestions do.
   -------------------------------------------------------------------------- */

.type-picker {
  position: relative;
}

.type-picker__btn {
  display: flex;
  align-items: center;
  text-align: left;
  cursor: pointer;
}

.type-picker__panel {
  position: absolute;
  z-index: 50;
  top: calc(100% + 4px);
  left: 0;
  width: min(420px, 78vw);
  max-height: 60vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}

/* Right-align the panel when the field sits near the right edge, so it cannot
   push the page wider than the viewport. */

@media (max-width: 1080px) {
  .type-picker__panel {
    left: auto;
    right: 0;
  }
}

@media (max-width: 560px) {
  .type-picker__panel {
    width: 100%;
  }
}

/*
  Deal type uses the same dropdown over the sidebar's segmented control. Its
  field is the narrowest of the three, and the segments inside are one line
  each, so the panel is sized to the control rather than to the trigger — at the
  trigger's width "Short rent" wrapped and the row grew a line taller than its
  neighbours.

  It opens from the left edge at every width: this is the leftmost field, so
  there is no right edge to run past, and the 1080px rule above would otherwise
  hang it off the panel's own left side.
*/

.deal-picker__panel {
  width: min(340px, 84vw);
  padding: var(--sp-2);
}

@media (max-width: 1080px) {
  .deal-picker__panel {
    left: 0;
    right: auto;
  }
}

/* ==========================================================================
   Brokers on the map
   ==========================================================================
   "For this in the maps his rounded rectangle profile image will be displayed
   with his name - small icon, and if user clicks on it, then the bigger user
   profile will open, just like the real estate preview window, and if click
   there then will redirect to this broker's single page."

   So the marker is a photograph and a name, and it has to be distinguishable
   from a price bubble at a glance or a map with both switched on becomes
   unreadable. What separates them is the shape (a rounded rectangle, not a
   pill) and the photograph itself — the navy ground matches the price labels
   so day and dark look the same.

   It shares .map-pin's anchoring exactly — translate(-50%, -100%) plus a stem —
   so a broker's pin points at its own coordinates with the same precision a
   listing's does, and the two sit at the same height when they overlap. */

.map-broker {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transform: translate(-50%, -100%);
  max-width: 190px;
  height: 42px;
  padding: 0 10px 0 4px;
  /*
    Same locked day-mode navy as .map-pin, both themes.

    The client, 17 September: in dark mode the white borderline goes; day mode
    matches that look; hover turns green #008000 like the price labels.
    --navy / --surface invert under [data-theme='dark'], which is what painted
    the white collar and the pale day-mode card — lock the colours instead.
  */
  background: #0C2D48;
  color: #FFFFFF;
  border: 2px solid transparent;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-pin);
  font-size: var(--fs-xs);
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.map-broker__photo {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 7px;
  object-fit: cover;
  background: var(--surface-3);
}

.map-broker__name {
  overflow: hidden;
  text-overflow: ellipsis;
}

.map-broker::after {
  /* The same stem the price pill draws, so both marker kinds point at a
     coordinate rather than hovering near one. */
  content: '';
  position: absolute;
  bottom: -6px;
  left: 50%;
  width: 9px;
  height: 9px;
  background: inherit;
  border-right: 2px solid transparent;
  border-bottom: 2px solid transparent;
  transform: translateX(-50%) rotate(45deg);
  border-radius: 0 0 2px 0;
}

.map-broker:hover,
.map-broker.is-active {
  background: #008000;
  color: #FFFFFF;
  transform: translate(-50%, -100%) scale(1.06);
  box-shadow: 0 2px 4px rgba(8, 23, 34, 0.32), 0 8px 16px rgba(8, 23, 34, 0.28);
}

/* No golden focus frame — hover / is-active already turn the label green. */
gmp-advanced-marker:focus-visible .map-broker,
gmp-advanced-marker:focus .map-broker {
  outline: none;
}

/* The preview a broker marker opens — the listing popup's shape with a
   broker's content in it, which is what "just like the real estate preview
   window" asks for. */

.map-popup__broker-media {
  display: block;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--r-md) var(--r-md) 0 0;
}

.map-popup__broker-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 22%;
}

.map-popup__agency {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text);
}

/* Name + pin + city on one line; agency and listings tucked under with
   almost no air between — the client, 17 September. */
.map-popup__card--broker .map-popup__title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  line-height: 1.2;
  overflow: visible;
  -webkit-line-clamp: unset;
}

/* The name line is identity, not a link affordance — photo / View profile
   open the page. Do not turn teal on hover. */
.map-popup__card--broker .map-popup__title:hover,
.map-popup__card--broker a.map-popup__title:hover {
  color: var(--text) !important;
}

.map-popup__place {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-muted);
}

.map-popup__place .icon {
  width: 14px;
  height: 14px;
  flex: none;
  /* Same violet as the Previa logo tile. */
  color: #8B008B;
}

.map-popup__card--broker .map-popup__meta,
.map-popup__card--broker .map-popup__agency,
.map-popup__card--broker .map-popup__facts {
  line-height: 1.15;
}

.map-popup-shell .map-popup__card--broker .map-popup__body > .map-popup__facts {
  margin: 0 0 var(--sp-3);
}

.map-popup__card--broker .distance-line {
  margin-top: var(--sp-1);
}

/* The advertiser's telephones, the apps under each, and the e-mail address —
   13 September: "the broker ad on the map has no contact details". The anchors
   say which ink they want for the reason the button above does: the map
   library paints links inside itself. */

.map-popup-shell .map-popup__body > .map-popup__contacts {
  margin: 0 0 var(--sp-3);
}

/* Active in under the contacts — same tags as the listing seller card. */
.map-popup-shell .map-popup__body > .map-popup__active {
  margin: 0;
}

.map-popup-shell .map-popup__body > .map-popup__active + .map-popup__active {
  margin-top: var(--sp-2);
}

.map-popup__active .seller-active__title {
  color: var(--text-muted);
}

.map-popup__contacts {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.map-popup__contact {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  font-size: var(--fs-xs);
  color: var(--text) !important;
  text-decoration: none !important;
}

.map-popup__contact:hover {
  color: #008000 !important;
}

.map-popup__contact .icon {
  flex: none;
  width: 14px;
  height: 14px;
}

.map-popup__contacts .msg-links--sm {
  margin-top: 0;
}

/* The map header's second line: where, and in what order.

   It was one sentence of text — "All locations · Newest first" — and the second
   half of it is a control now. The two stay on one line, with the separator the
   old sentence used kept as a hairline between them, so the line still reads as
   one statement about the search rather than as a stray menu under the count. */

.map-results__meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: 3px;
  flex-wrap: wrap;
}

/* No separator between the two.

   The old sentence joined them with a middot — "All locations · Newest first" —
   and that only reads as one statement while they share a line. The results
   column is around 580px at an ordinary window width, which is not enough for
   a place name, a label and a menu, so the menu wraps underneath and a middot
   would be left hanging at the end of the line above it. Two lines, no
   punctuation between them. */

.map-results__head .sort-field__label {
  font-size: var(--fs-xs);
}

.map-results__head .sort-field__select,
.map-results__head .sort-menu__btn {
  min-height: 32px;
  padding-block: 0;
  font-size: var(--fs-xs);
}

/*
  "Nothing in view", over the map rather than across it.

  The empty state on the map pane used to be a .map-state — an opaque panel
  filling the pane edge to edge. On a market with no listings that panel was the
  whole right-hand column, so the map appeared to be missing rather than empty,
  which is exactly how the client read it.

  A card, bottom-centre, over a map that is still drawn and still pannable. It
  does not take pointer events: reaching a place that does have something means
  dragging the map, and the message must not be in the way of doing that.
*/

.map-note {
  position: absolute;
  left: 50%;
  bottom: var(--sp-6);
  transform: translateX(-50%);
  z-index: 8;
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  width: min(420px, calc(100% - var(--sp-8)));
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-panel);
  pointer-events: none;
}

.map-note__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-subtle);
}

.map-note__icon svg {
  width: 18px;
  height: 18px;
}

/* ==========================================================================
   The back office's sign-in page
   ========================================================================== */

/*
  A single card on the page's own ground, with none of the site's sign-in
  furniture around it.

  The client asked for the two logins to be strictly separate — "for
  previa.estate/admin must be separate login page, not the same as
  previa.estate/login" — so this screen shares the design system and nothing
  else: no split hero, no aside, no social buttons, no route back into the
  account area.
*/

.admin-gate {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - 240px);
  padding: var(--sp-8) var(--gutter);
  position: relative;
}

.admin-gate__theme {
  position: absolute;
  top: var(--sp-4);
  right: var(--gutter);
}

.admin-gate__card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  width: 100%;
  max-width: 420px;
  padding: var(--sp-7);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-panel);
}

.admin-gate__head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.admin-gate__foot {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}

@media (max-width: 480px) {
  .admin-gate {
    padding: var(--sp-5) var(--gutter);
    min-height: auto;
  }

  .admin-gate__card {
    padding: var(--sp-5);
  }
}

/* ---------------------------------------------------------------------------
   The SEO box
   ---------------------------------------------------------------------------
   Administrator-written copy, rendered before and after a public page's own
   content — the client, 26 August: "'SEO box text (above)' and 'SEO box text
   (below)' this is the organic text with H1 H2 H3 and <p> for the SEO
   purposes."

   A tinted panel, the width of the column it is placed in.

   The client, 8 September: "wire in the frontend the same-style SEO boxes as in
   sexer.ee, in Previa colors ofcourse, not pink." On sexer.ee the copy sits in
   one filled, rounded block that runs the full width of the results column, and
   that is what this is: --navy-tint in place of that site's pink, the site's own
   radius, and the site's border so the block still has an edge where the tint
   and the page background are close together.

   It ran as bare prose down the middle of the page until now, 72ch wide and
   centred, which is why it never read as a box at all.

   Headings inside it step down from the page's own. An SEO box that opens with
   an <h1> the size of the hero title would read as a second page start.
   --------------------------------------------------------------------------- */

.seo-box {
  /* Same #8B008B as the Previa logo tile. */
  background: #8B008B;
  border: 1px solid #8B008B;
  border-radius: var(--r-lg);
  /* Tighter than sexer.ee's 10×14 — especially the top edge. */
  padding: 2px 8px 4px;
  color: #fff;
  /* One row typed into the field is one row drawn on the page.

     The client, 8 September: "just the linespacing is too wide, make it
     closer, no empty rows. Make it in frontend the same as in backend."

     The wide spacing was not the headings' margins — those are the rules
     below, and they are the small half of it. The box carries .prose, which is
     a flex column with a var(--sp-4) gap between every child, and a flex gap
     is added on top of whatever margins the rows have. So the gap goes here
     and the margins go below, and what is left between two rows is the height
     of the rows themselves, exactly as in the textarea they were typed in. */
  gap: 0;
}

/* Full width inside the box: the reference is one block of copy, not a column
   of it. .prose caps a paragraph at 68ch, which is right on a page of text and
   wrong in a panel whose whole shape is the point. */

.seo-box p {
  max-width: none;
}

.seo-box--above {
  margin-block: var(--sp-5) var(--sp-2);
}

.seo-box--below {
  margin-block: var(--sp-8) var(--sp-6);
}

/*
  Where a page places the upper box itself.

  On the listings page it goes "between header and ad banner, the same width as
  the ad banner", which is inside the results column: it takes the advertising
  strip's place at the top of that column, rides up level with the breadcrumb
  the way the strip does, and the strip follows it down. Without the second rule
  the banner's pull-up would slide it over the box.
*/

.search-results-col > .seo-box--inline {
  margin-top: calc((var(--crumb-h) + var(--sp-2) - 5px) * -1);
  margin-bottom: var(--sp-5);
}

.search-results-col > .seo-box--inline + .search-banner {
  margin-top: 0;
}

@media (max-width: 640px) {
  .seo-box {
    padding: 2px 6px 4px;
  }
}

.seo-box h1,
.seo-box h2,
.seo-box h3,
.seo-box h4,
.seo-box h5,
.seo-box h6 {
  color: #fff;
  font-family: var(--font-display);
  line-height: var(--lh-tight);
  margin-block: 0;
}

.seo-box h1 {
  font-size: var(--fs-h2);
}

.seo-box h2 {
  font-size: var(--fs-h3);
}

.seo-box h3 {
  font-size: var(--fs-h4);
}

.seo-box h4,
.seo-box h5,
.seo-box h6 {
  font-size: var(--fs-lg);
}

/* The leading inside a paragraph, not the space between rows.

   The client, 8 September: "in frontpage in SEO box the paragraph text line
   spacing make as tight as possible."

   The gap between rows was closed the day before — see gap: 0 above. What was
   left is what he is looking at now: a paragraph long enough to wrap set at
   --lh-relaxed (1.7), so its two lines sit far enough apart to read as two
   paragraphs. --lh-tight is the tightest leading the type scale has, and it is
   the one the box's own headings already use, so the whole box is now set to
   one rhythm rather than two. */

.seo-box p,
.seo-box li {
  font-size: var(--fs-base);
  line-height: var(--lh-tight);
  color: #fff;
}

.seo-box p {
  margin-block: 0;
}

.seo-box ul,
.seo-box ol {
  margin-block: 0;
  padding-inline-start: var(--sp-6);
}

.seo-box li {
  margin-block-end: 0;
}

.seo-box a {
  color: #fff;
  text-decoration: underline;
}

.seo-box a:hover {
  color: #fff;
}

.seo-box strong, .seo-box b {
  color: #fff;
}

.seo-box blockquote {
  margin-block: 0;
  margin-inline: 0;
  padding-inline-start: var(--sp-4);
  border-inline-start: 2px solid color-mix(in srgb, #fff 35%, transparent);
  color: color-mix(in srgb, #fff 85%, transparent);
}

.seo-box hr {
  border: 0;
  border-block-start: 1px solid color-mix(in srgb, #fff 28%, transparent);
  margin-block: 0;
}

/*
  A blank row in the field is a blank row on the page.

  "If in backend no empty row, then in frontend no empty row. If in backend
  empty row, then in frontend empty row." HTML folds a blank line in the source
  away to nothing, so an empty row cannot survive the trip on its own: the
  handler writes one of these where the administrator left a row empty (see
  handlers.seoBoxRows) and this is what gives it a height.

  One row of body copy, whatever the rows on either side of it are. The client
  is separating blocks of copy, not tuning a gap, and a blank row that changed
  size with its neighbours would read as an accident.
*/

.seo-box__gap {
  display: block;
  height: calc(var(--fs-base) * var(--lh-relaxed));
}

*, ::before, ::after {
  --tw-border-spacing-x: 0;
  --tw-border-spacing-y: 0;
  --tw-translate-x: 0;
  --tw-translate-y: 0;
  --tw-rotate: 0;
  --tw-skew-x: 0;
  --tw-skew-y: 0;
  --tw-scale-x: 1;
  --tw-scale-y: 1;
  --tw-pan-x:  ;
  --tw-pan-y:  ;
  --tw-pinch-zoom:  ;
  --tw-scroll-snap-strictness: proximity;
  --tw-gradient-from-position:  ;
  --tw-gradient-via-position:  ;
  --tw-gradient-to-position:  ;
  --tw-ordinal:  ;
  --tw-slashed-zero:  ;
  --tw-numeric-figure:  ;
  --tw-numeric-spacing:  ;
  --tw-numeric-fraction:  ;
  --tw-ring-inset:  ;
  --tw-ring-offset-width: 0px;
  --tw-ring-offset-color: #fff;
  --tw-ring-color: rgb(59 130 246 / 0.5);
  --tw-ring-offset-shadow: 0 0 #0000;
  --tw-ring-shadow: 0 0 #0000;
  --tw-shadow: 0 0 #0000;
  --tw-shadow-colored: 0 0 #0000;
  --tw-blur:  ;
  --tw-brightness:  ;
  --tw-contrast:  ;
  --tw-grayscale:  ;
  --tw-hue-rotate:  ;
  --tw-invert:  ;
  --tw-saturate:  ;
  --tw-sepia:  ;
  --tw-drop-shadow:  ;
  --tw-backdrop-blur:  ;
  --tw-backdrop-brightness:  ;
  --tw-backdrop-contrast:  ;
  --tw-backdrop-grayscale:  ;
  --tw-backdrop-hue-rotate:  ;
  --tw-backdrop-invert:  ;
  --tw-backdrop-opacity:  ;
  --tw-backdrop-saturate:  ;
  --tw-backdrop-sepia:  ;
  --tw-contain-size:  ;
  --tw-contain-layout:  ;
  --tw-contain-paint:  ;
  --tw-contain-style:  ;
}

::backdrop {
  --tw-border-spacing-x: 0;
  --tw-border-spacing-y: 0;
  --tw-translate-x: 0;
  --tw-translate-y: 0;
  --tw-rotate: 0;
  --tw-skew-x: 0;
  --tw-skew-y: 0;
  --tw-scale-x: 1;
  --tw-scale-y: 1;
  --tw-pan-x:  ;
  --tw-pan-y:  ;
  --tw-pinch-zoom:  ;
  --tw-scroll-snap-strictness: proximity;
  --tw-gradient-from-position:  ;
  --tw-gradient-via-position:  ;
  --tw-gradient-to-position:  ;
  --tw-ordinal:  ;
  --tw-slashed-zero:  ;
  --tw-numeric-figure:  ;
  --tw-numeric-spacing:  ;
  --tw-numeric-fraction:  ;
  --tw-ring-inset:  ;
  --tw-ring-offset-width: 0px;
  --tw-ring-offset-color: #fff;
  --tw-ring-color: rgb(59 130 246 / 0.5);
  --tw-ring-offset-shadow: 0 0 #0000;
  --tw-ring-shadow: 0 0 #0000;
  --tw-shadow: 0 0 #0000;
  --tw-shadow-colored: 0 0 #0000;
  --tw-blur:  ;
  --tw-brightness:  ;
  --tw-contrast:  ;
  --tw-grayscale:  ;
  --tw-hue-rotate:  ;
  --tw-invert:  ;
  --tw-saturate:  ;
  --tw-sepia:  ;
  --tw-drop-shadow:  ;
  --tw-backdrop-blur:  ;
  --tw-backdrop-brightness:  ;
  --tw-backdrop-contrast:  ;
  --tw-backdrop-grayscale:  ;
  --tw-backdrop-hue-rotate:  ;
  --tw-backdrop-invert:  ;
  --tw-backdrop-opacity:  ;
  --tw-backdrop-saturate:  ;
  --tw-backdrop-sepia:  ;
  --tw-contain-size:  ;
  --tw-contain-layout:  ;
  --tw-contain-paint:  ;
  --tw-contain-style:  ;
}
