/* Copyright (c) 2026 Datum Investment Solutions, LLC. All rights reserved.
   Proprietary and confidential. See LICENSE at the repository root. */

/* A cool daylight palette. The previous one paired a neutral gray ground
   with amber "gated" accents, which read as brown at card size and made a
   grid of twenty listings look tired. Everything here sits on a light
   blue ground; the gated state is a soft steel blue rather than amber, so
   it reads as calm information rather than a rust-colored warning.

   The gated color stays clearly distinct from the interactive accent by
   being much lighter and grayer -- if "locked" and "clickable" look the
   same, the banner stops meaning anything. */
:root{
  --ink:#0f1c2a; --ink2:#43586b; --mut:#7189a0; --line:#d8e3ee;
  --bg:#f0f5fa; --card:#fff; --accent:#1d6fa8; --accent2:#e4f0f9;
  --gate:#4d6785; --gatebg:#eaf1f8; --gateline:#cddced;
  --ok:#1c6b58; --okbg:#e7f4f0; --heart:#d4374b;
  /* The three flag colors, defined once. Red and amber are the ones
     that mean work; green is only ever shown where somebody has
     actually closed something out. */
  --crit:#a8261d; --critbg:#fbeae8; --critline:#efc4bf;
  --attn:#8a5300; --attnbg:#fbf0dd; --attnline:#ebd6ac;
  --shadow:0 1px 2px rgba(16,42,66,.06),0 6px 18px rgba(16,42,66,.08);
  /* White on a colored ground -- a filled button's label, a badge on the
     accent. It is white in BOTH themes, which is why it cannot be --card:
     the card goes dark and the text on a blue button must not. */
  --onfill:#fff;
  /* The plotted map: its ground, its grid, and the pill a gated listing
     gets. Tokens because the fallback is an inline SVG -- it cannot
     inherit a background, so the colours have to be reachable by class. */
  --mapbg:#eaf1f8; --mapgrid:#dce7f1; --mapring:#7f9dbe;
  /* The score card. A filled panel in the product's own ink, dark in
     BOTH themes on purpose: it is the one thing on the page that states
     an outcome, and a white card among white cards cannot do that
     without either a border loud enough to read as a button or a tint
     that makes it look disabled. Its own four tokens rather than
     borrowed ones, because everything on it sits on that fill and none
     of the page's text colours are legible there. */
  --scard:#14293b; --scink:#eef4fa; --scmut:#9fb6cb; --scline:#33526d;
}

/* ---------- who to talk to ----------
   On the listing, beside the reference, because that is where the
   question comes up. Named people only -- see concierge() in app.js. */
.dmeta{flex:0 0 auto;text-align:right}
/* At the top of the side column, above the photograph and the card. A
   stated minimum: squeezed below about 230px it breaks the address
   across two lines mid-word. No maximum -- it is a column of its own
   now and filling it is what a card at the head of one should do. */
.concierge{display:flex;gap:11px;align-items:flex-start;text-align:left;
  border:1px solid var(--line);border-radius:11px;padding:12px 14px;
  background:var(--card);margin:0;min-width:230px}
.cgav{flex:0 0 auto;width:38px;height:38px;border-radius:50%;
  background:var(--accent2);color:var(--accent);display:flex;
  align-items:center;justify-content:center;font-weight:700;font-size:13.5px}
.cgwho{display:grid;gap:2px;min-width:0}
.cgrole{font-size:10.5px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--mut)}
.cgwho b{font-size:14.5px;line-height:1.25}
.cgway{font-size:12.5px;line-height:1.4;overflow-wrap:break-word}
.cgway a{color:var(--accent);text-decoration:none}
.cgway a:hover{text-decoration:underline}
.cgnote{font-size:11.5px;line-height:1.4;color:var(--mut);margin-top:2px}
@media (max-width:700px){
  .concierge{min-width:0}
}

/* ---------------------------------------------------------------------
   Dark
   ---------------------------------------------------------------------
   The same twenty tokens, and only those. Every screen in this product
   draws its ground, its cards, its lines and its text from them, so a
   second palette is the whole of dark mode -- there is no dark stylesheet
   and nothing is duplicated per component.

   RESOLVED, NOT ASKED. theme.js turns "system" into an explicit
   data-theme="dark" or "light" before the first paint, and listens for the
   machine changing its mind. So this file needs one block rather than one
   block plus a prefers-color-scheme copy of it, and the two can never
   drift apart.

   WHAT MOVED, AND WHY IT IS NOT AN INVERSION. Text lightens and grounds
   darken, but the accent gets LIGHTER rather than darker: #1d6fa8 on a
   near-black ground is a link nobody can read. The semantic three --
   good, attention, critical -- keep their hues and gain lightness for the
   same reason. Their tinted backgrounds go dark, because a pale pink
   banner on a dark page is a hole in the screen. */
:root[data-theme="dark"]{
  --ink:#e3ecf5; --ink2:#a6bcd0; --mut:#7d93a8; --line:#26374a;
  --bg:#0c151e; --card:#15212e; --accent:#57a9e0; --accent2:#17303f;
  --gate:#93aac4; --gatebg:#1a2937; --gateline:#2c4055;
  --ok:#4ec5a3; --okbg:#12302a; --heart:#ff6b7d;
  --crit:#ff8779; --critbg:#37191a; --critline:#5c2b27;
  --attn:#e5ac54; --attnbg:#33280f; --attnline:#584622;
  /* Deeper and wider: the same shadow that reads as a lifted card on a
     pale ground is invisible on a dark one. */
  --shadow:0 1px 2px rgba(0,0,0,.4),0 8px 22px rgba(0,0,0,.45);
  --mapbg:#0f1a24; --mapgrid:#1d2c3b; --mapring:#4a6c8f;
  /* Lifted rather than darkened. On a near-black page the daylight card
     would sink into the ground and stop reading as a panel at all, so it
     goes a step ABOVE --card the way --card sits above --bg. */
  --scard:#1e3446; --scink:#e8f0f8; --scmut:#9db4c9; --scline:#39566f;
}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--ink);
  font:14px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
button,input,select{font:inherit;color:inherit}
h1{font-size:19px;margin:0}

/* ---------- top bar ---------- */
.bar{display:flex;align-items:center;gap:18px;padding:10px 18px;
  background:var(--card);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:900}
/* THE TABS CLAIM THE FREE SPACE, so everything after them is at the right
   end -- the search box on the marketplace, the New record button on the
   entity screens. Stated once here rather than as a `margin-left:auto` on
   each of those: two of them in one bar split the space between them and
   leave a gap in the middle, which is exactly what happened when the
   search box and the identity chip each claimed it. */
.bar > .tabs{margin-right:auto}
.brand{display:flex;align-items:center;gap:9px;white-space:nowrap}
.mark{background:var(--accent);color:#fff;font-weight:700;letter-spacing:.5px;
  padding:4px 8px;border-radius:5px;font-size:13px}
.brandtxt{font-weight:600}
/* The bar's own title, on pages with no tab strip. Not a brand -- the rail
   carries that -- just the name of the page you are on. */
.pagename{font-size:15px;font-weight:600;margin:0;white-space:nowrap}
/* THE SEARCH BOX SITS IN THE TOOLBAR, not across it.
   
   It was `flex:1` up to 640px, which put it immediately after the tabs and
   let it eat every spare pixel -- and at 41px tall it was the thing setting
   the bar's height, so it stood proud of the row it was supposed to be part
   of. `margin-left:auto` moves it and everything after it to the right; the
   padding is trimmed so it matches the tabs rather than towering over them. */
/* Bottom-aligned like the tabs, so the toolbar reads as one row. Centerd, it
   floated in the upper half of a bar whose height the tabs set, and the two
   controls sat on visibly different lines. */
.ask{flex:0 1 440px;display:flex;align-items:center;gap:7px;max-width:440px;
  align-self:flex-end;margin-bottom:-10px;
  border:1px solid var(--line);border-radius:17px;padding:3px 3px 3px 12px;
  background:#fbfcfd}
.ask:focus-within{border-color:var(--accent);background:var(--card);box-shadow:0 0 0 3px var(--accent2)}
.askicon{color:var(--accent);font-size:12.5px}
.ask input{flex:1;border:0;background:transparent;outline:0;padding:4px 0;
  min-width:0;font-size:13px}
.ask button{background:var(--accent);color:#fff;border:0;border-radius:14px;
  padding:5px 13px;cursor:pointer;font-weight:600;font-size:12.5px}
/* No `margin-left:auto` here any more -- the search box already claimed the
   free space, and two auto margins would split it and leave a gap between
   them. */
.who{display:flex;align-items:center;gap:8px;white-space:nowrap}
/* The bar's own primary: the NEW RECORD button, at the right end of the
   top toolbar on every entity screen. `.who` is already the bar's right
   end -- nav.css hides the chip and the sign-out inside it once the rail
   is drawn, and this is what is left standing there. In app.css because
   three screens carry it and app.css is the one stylesheet all three
   load; in a per-page sheet it would have to be written three times. */
.who .primary.new{padding:7px 13px;font-size:13px;border-radius:8px;border:0;
  background:var(--accent);color:#fff;font-weight:600;cursor:pointer;
  white-space:nowrap}
.who .primary.new:hover{background:#17608f}
.who .primary.new:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ---------------------------------------------------------------------
   The form dialog
   ---------------------------------------------------------------------
   New property, new customer, new agent, upload a workbook. Four forms,
   one shape, and this used to be four copies of these rules -- one per
   page stylesheet -- on the reasoning that the pages shared the markup by
   coincidence rather than being one component. At two copies that was
   arguable. At four it was simply four places to change a border radius,
   so they live here, in the one stylesheet every screen loads.

   A form, not a chain of prompt() boxes: a run of prompts cannot be
   reviewed before submitting, corrected once past, or say which field is
   wrong. */
.ndlg{border:0;border-radius:12px;padding:0;width:min(540px,94vw);
  box-shadow:0 20px 60px rgba(12,18,24,.3);color:var(--ink);background:var(--card)}
.ndlg::backdrop{background:rgba(12,18,24,.42)}
.ndlg form{padding:20px 22px 18px}
.ndlg h2{margin:0 0 6px;font-size:17px;letter-spacing:-.01em}
.ndlg .small{margin:0 0 14px}
.ngrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px 12px}
/* The property form asks for ten things and reads better three across. */
.ngrid.three{grid-template-columns:repeat(3,minmax(0,1fr))}
/* The long one -- an address, an email -- takes the width. */
.nf.n2{grid-column:span 2}
.nf{display:grid;gap:4px;min-width:0}
.nf>span{font-size:11.5px;font-weight:700;letter-spacing:.3px;color:var(--ink2)}
/* `optional` in the label rather than a required marker on the others:
   most of these are required, so marking the exceptions is the shorter
   list and the one worth reading. */
.nf>span i{font-style:normal;font-weight:400;color:var(--mut)}
.nf input,.nf select{width:100%;padding:8px 9px;border:1px solid var(--line);
  border-radius:7px;font:inherit;font-size:13.5px;background:var(--bg);color:var(--ink)}
.nf input:focus,.nf select:focus{outline:2px solid var(--accent);outline-offset:-1px;
  background:var(--card)}
.ndlg .err{margin:12px 0 0;padding:9px 11px;background:var(--critbg);
  border:1px solid var(--critline);border-radius:7px;color:var(--crit);font-size:13px}
.nact{display:flex;justify-content:flex-end;gap:9px;margin-top:16px}

/* A line across the top of a screen, for something that happened
   somewhere the reader cannot see -- a workbook that landed in the
   review queue, which is a different page. Used by Intake review and
   by Properties -> Manage, so it lives here rather than in one of
   their stylesheets. */
.banner{border-radius:9px;padding:11px 13px;margin-bottom:12px;font-size:13px}
.banner.ok  {background:#eef7f1;color:#1f6f4d;border:1px solid #cfe6d8}
.banner.warn{background:#fdf5e7;color:#8a5a1c;border:1px solid #f0e2c8}

@media (max-width:520px){ .ngrid,.ngrid.three{grid-template-columns:repeat(2,minmax(0,1fr))} }


.chip{background:var(--accent2);color:var(--accent);border-radius:20px;
  padding:5px 11px;font-size:12.5px;font-weight:600}
.ghost{background:var(--card);border:1px solid var(--line);border-radius:7px;
  padding:6px 11px;cursor:pointer;text-decoration:none;color:var(--ink2);font-size:13px}
.ghost:hover{border-color:var(--accent);color:var(--accent)}
.ghost[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:#fff}
.primary{background:var(--accent);color:#fff;border:1px solid var(--accent);
  border-radius:7px;padding:6px 12px;cursor:pointer;font-weight:600;font-size:13px}

.parsed{padding:8px 18px;background:var(--accent2);color:var(--accent);
  border-bottom:1px solid #cfe0ec;font-size:13px}
.parsed b{font-weight:700}
/* A CLASS-NAME COLLISION, and it predates the refusal message that made
   it visible. `.note` further down this file is the detail panel's gate
   NOTICE -- a bordered, padded block with its own background and a 14px
   vertical margin. This rule only ever overrode the color, so the run-on
   half of every parse explanation has been rendering as a floating block
   inside the banner since the box was written; with a one-line message it
   read as an odd inline chip and nobody looked twice.
   Reset explicitly rather than relying on the cascade: two unrelated
   things sharing a name is the actual fault, and the reset is what stops
   the next property added to `.note` leaking in here too. */
.parsed .note{color:#4a6f88;font-weight:400;
  background:none;border:0;border-radius:0;padding:0;margin:0;display:inline}

/* ---------- filters ---------- */
.filters{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px;
  padding:11px 18px;background:var(--card);border-bottom:1px solid var(--line);
  position:sticky;top:57px;z-index:800}
/* The `hidden` attribute means hidden, everywhere.
   The browser's own rule is [hidden]{display:none}, and ANY author rule
   that sets display beats it -- so .filters label{display:flex} left a
   "hidden" control on screen, and .lightbox{display:flex} left an
   invisible full-window layer swallowing every click. Both were real bugs
   that looked like nothing was wrong. One declaration ends the class. */
[hidden]{display:none !important}

.filters label{display:flex;flex-direction:column;gap:4px;
  font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--mut);font-weight:600}
.filters input,.filters select{border:1px solid var(--line);border-radius:6px;
  padding:6px 8px;background:var(--card);font-size:13.5px;text-transform:none;letter-spacing:0;color:var(--ink)}
.filters input{width:88px}
.pair{display:flex;align-items:center;gap:4px}
.pair i{color:var(--mut);font-style:normal}
.spacer{flex:1}
.saved{max-width:210px}

.gate{margin:0;padding:9px 18px;background:var(--gatebg);color:var(--gate);
  border-bottom:1px solid var(--gateline);font-size:13px}
.gate.open{background:var(--okbg);color:var(--ok);border-bottom-color:#c9e5dc}

/* ---------- layout ---------- */
/* No map access, no map. The listings take the whole width rather than
   sitting beside an empty panel. */
main.nomap{grid-template-columns:1fr}
.nomapnote{background:var(--gatebg);border-color:var(--gateline);color:var(--gate)}
main.nomap .mapwrap{display:none}

main{display:grid;grid-template-columns:minmax(320px,44%) 1fr;
  height:calc(100vh - 155px);min-height:420px}
.mapwrap{position:relative;border-right:1px solid var(--line);background:var(--mapbg)}
#map,.fallback{position:absolute;inset:0}
.fallback{background:var(--mapbg)}
.legend{position:absolute;left:10px;bottom:10px;z-index:500;background:var(--card);
  border:1px solid var(--line);border-radius:7px;padding:7px 10px;display:flex;gap:14px;
  font-size:12px;color:var(--ink2);box-shadow:var(--shadow)}
.dot{display:inline-block;width:10px;height:10px;border-radius:50%;vertical-align:-1px;margin-right:4px}
.dot.exact{background:var(--accent)}
.dot.approx{background:#7f9dbe;opacity:.7;border:1px dashed var(--gate)}

.results{overflow-y:auto;padding:14px 16px 40px}
.resulthead{display:flex;align-items:baseline;gap:10px;margin-bottom:12px}
.muted{color:var(--mut);font-size:12.5px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(258px,1fr));gap:14px}

/* ---------- grid or list ----------
   ONE SET OF CARDS, TWO SHAPES. `list` restyles the same markup rather
   than rendering something else: a second renderer is a second place for
   the favorite button, the flag and the last note to be got wrong, and
   they were only ever going to be kept in step by remembering to. */
.list{display:grid;grid-template-columns:1fr;gap:9px}
.list .card{display:grid;grid-template-columns:158px minmax(0,1fr)}
/* The photograph fills the row's height instead of setting it, so rows
   stay the same height whether or not a card carries a flag or a note. */
.list .shot{aspect-ratio:auto;height:100%;min-height:104px}
/* Everything keeps its column-one position and the metrics move to the
   right, spanning however many rows the card happens to have. Stated as
   `> *` because the flag and the note are optional -- naming each one
   would put a card without them out of position. */
.list .body{display:grid;grid-template-columns:minmax(0,1fr) max-content;
  align-content:center;column-gap:22px;padding:11px 14px}
.list .body > *{grid-column:1}
.list .metrics{grid-column:2;grid-row:1/-1;align-self:center;
  border-top:0;padding-top:0;margin-top:0;gap:22px;text-align:right}
.list .price{font-size:17px}
.list .addr{min-height:0}
/* A card in a row does not lift: at this width the shadow reads as the row
   coming apart from the ones above and below it. */
.list .card:hover{transform:none;box-shadow:0 2px 5px rgba(17,24,32,.09)}
/* Below the two-column breakpoint the results column is the full width, so
   a list row has room for the photograph and the numbers side by side. It
   does not have room on a phone, where the row becomes a card again. */
@media (max-width:560px){
  .list .card{grid-template-columns:1fr}
  .list .shot{aspect-ratio:4/3}
  .list .body{grid-template-columns:1fr}
  .list .metrics{grid-column:1;grid-row:auto;text-align:left;
    border-top:1px solid var(--line);padding-top:8px;margin-top:8px;gap:12px}
}

/* The switch itself. Pushed to the end of the head row, so it sits against
   the results column's right edge rather than beside the count. */
.viewtog{margin-left:auto;display:flex;border:1px solid var(--line);
  border-radius:8px;overflow:hidden;background:var(--card)}
.viewtog button{display:grid;place-items:center;width:32px;height:28px;
  padding:0;border:0;background:none;cursor:pointer;color:var(--mut)}
.viewtog button + button{border-left:1px solid var(--line)}
.viewtog svg{width:15px;height:15px;fill:currentColor}
.viewtog button:hover{background:var(--accent2);color:var(--ink2)}
/* The pressed one is filled, not merely darker: two icons differing only
   in shade is a state you have to compare to read. */
.viewtog button[aria-pressed="true"]{background:var(--accent);color:#fff}
.viewtog button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* ---------- cards ---------- */
.card{background:var(--card);border:1px solid var(--line);border-radius:10px;
  overflow:hidden;cursor:pointer;box-shadow:var(--shadow);
  transition:transform .12s ease,box-shadow .12s ease}
.card:hover{transform:translateY(-2px);box-shadow:0 3px 6px rgba(17,24,32,.08),0 12px 28px rgba(17,24,32,.12)}
.card.hi{outline:2px solid var(--accent);outline-offset:-2px}
.shot{position:relative;aspect-ratio:4/3;background:#dfe5e9;overflow:hidden}
.shot img{width:100%;height:100%;object-fit:cover;display:block}
.badge{position:absolute;left:9px;top:9px;background:rgba(17,24,32,.82);color:#fff;
  border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;letter-spacing:.4px;text-transform:uppercase}
.heart{position:absolute;right:8px;top:8px;width:32px;height:32px;border-radius:50%;
  border:0;background:rgba(255,255,255,.9);cursor:pointer;font-size:17px;line-height:1;
  color:#5a6672;display:grid;place-items:center;box-shadow:0 1px 4px rgba(0,0,0,.2)}
.heart:hover{color:var(--heart)}
.heart.on{color:var(--heart)}
.body{padding:10px 12px 12px}
.price{font-size:19px;font-weight:700;letter-spacing:-.3px}
.specs{color:var(--ink2);margin:3px 0 5px}
.specs b{font-weight:700}
.addr{font-size:12.5px;color:var(--mut);min-height:18px}
.addr.locked{color:var(--gate)}
.metrics{display:flex;gap:12px;margin-top:8px;padding-top:8px;border-top:1px solid var(--line);
  font-size:12px;color:var(--ink2)}
.metrics b{display:block;font-size:13.5px;color:var(--ink)}

/* ---------- map markers ---------- */
.pin{background:var(--accent);color:#fff;border-radius:14px;padding:3px 8px;
  font-size:12px;font-weight:700;white-space:nowrap;box-shadow:0 1px 4px rgba(0,0,0,.35);
  border:2px solid #fff}
.pin.gated{background:var(--card);color:var(--gate);border:2px dashed #8fadc9}
/* Highlight darkens the background, so it must restate the foreground:
   .pin.gated sets a dark slate on white, and inheriting that color onto
   the dark hover background left the price unreadable. border-color, not
   border, so a gated pin keeps its dashed edge -- that dash is what says
   the position is approximate. */
.pin.hi{background:#0f4a72;color:#fff;border-color:#fff;transform:scale(1.12)}

/* ---------- detail panel ---------- */
.scrim{position:fixed;inset:0;background:rgba(12,18,24,.42);z-index:1000}
.detail{position:fixed;top:0;right:0;bottom:0;width:min(680px,94vw);background:var(--card);
  z-index:1001;overflow-y:auto;box-shadow:-8px 0 40px rgba(12,18,24,.25)}
/* THE FROZEN BAR. Mirrors the panel's own two widths rather than having
   them synced from JavaScript -- there are only two, and CSS already
   knows both. Fixed, not sticky: a sticky first child takes flow space at
   the very top, which is the one place this must not be, since the page
   already shows all of it there.
   Above the panel's own z-index so it covers the content sliding under
   it, and below the close buttons, which stay reachable. */
.dsticky{position:fixed;top:0;right:0;width:min(680px,94vw);z-index:1002;
  display:flex;align-items:center;gap:12px;
  padding:9px 460px 9px 18px;
  background:color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter:blur(10px);
  border-bottom:1px solid var(--line);
  box-shadow:0 6px 18px rgba(12,18,24,.10);
  transform:translateY(-102%);opacity:0;pointer-events:none;
  transition:transform .18s ease, opacity .18s ease}
.detail.full .dsticky{width:100%;left:0;right:0}
/* IN THE SIDE PANEL THE BAR IS 680px WHATEVER THE WINDOW IS, so a
   viewport media query cannot reach this case. Measured in the browser:
   the price, the deposit and the cash flow ran 33px under the pinned
   toolbar. The bar already drops its first two figures on a narrow
   screen, for exactly this reason -- the cash flow is the one that moves
   while you read, and the other two are on the page above.
   The toolbar keeps its words: the control that moves the deal on should
   not be the thing that gets shortened to make room for two numbers you
   have already scrolled past. */
.detail:not(.full) .dstfigs span:not(#dstcf){display:none}
.dsticky.on{transform:none;opacity:1;pointer-events:auto}
/* 460px of right padding is the pinned toolbar at its WIDEST -- a guest
   in full screen: Add to Cart with its words, a basket, a Sign Up chip,
   a heart, share, full screen and close. Measured in the browser, not
   added up: the pill's padding and the font it is set in are not in the
   stylesheet's head, and arithmetic has got this wrong four times now.
   The test reads the measured width and this number and compares them. */

.dstback{flex:0 0 auto;border:0;background:none;color:var(--ink2);
  font:inherit;font-size:18px;line-height:1;cursor:pointer;padding:4px 2px}
.dstback:hover{color:var(--accent)}
.dstthumb{flex:0 0 auto;width:42px;height:42px;border-radius:8px;
  object-fit:cover;display:block}
/* min-width:0, or the address refuses to ellipsis and pushes the figures
   off the end of the bar. A flex item's automatic minimum is its content. */
.dstwhat{min-width:0;display:flex;flex-direction:column;gap:1px;line-height:1.3}
.dstwhat b{font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dstwhat span{font-size:12px;color:var(--ink2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* The figures sit at the right, and are the first thing dropped when the
   bar runs out of room -- on a phone the house and the way back matter
   more than three numbers that are a scroll away. */
.dstfigs{margin-left:auto;display:flex;align-items:center;gap:22px;
  flex:0 0 auto}
.dstfigs span{display:flex;flex-direction:column;gap:1px;line-height:1.25}
.dstfigs i{font-style:normal;font-size:10px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink2)}
.dstfigs b{font-size:14px;font-variant-numeric:tabular-nums}
/* The same two tokens the cash-flow card uses for the same two meanings.
   A second pair invented here would drift from it. */
.dstfigs b.bad{color:var(--crit)}
.dstfigs b.good{color:var(--ok)}
/* The bar's own Add to Cart moved to the pinned toolbar (.dtcart), which
   is on screen from the moment the listing opens rather than only once
   the photograph has scrolled off. Its styles went with it -- a rule kept
   "in case" is a rule the next person has to work out is dead. */

@media (max-width:760px){
  .dsticky{padding-right:118px;gap:9px}
  .dstfigs{gap:14px}
  .dstfigs span:not(#dstcf){display:none}
}
@media (max-width:460px){
  .dstthumb{display:none}
}

/* The floating controls. They sit over a photograph in the side panel and
   over the page itself in full screen, so they are a solid chip from the
   palette rather than a white disc: `rgba(255,255,255,.92)` with no
   colour set inherited --ink, which on a dark page is near-white text on
   a near-white circle -- three blank buttons. Full screen made that
   visible because full screen is now what a click opens. */
/* ---------------------------------------------------------------------
   The panel's pinned controls
   ---------------------------------------------------------------------
   FIXED, NOT ABSOLUTE. These were four buttons absolutely positioned
   inside .detail -- which is a scrolling container, so "absolute" scrolls
   with the content. Three screens into a listing there was no close, no
   full screen, no share and no heart, and the only way to reach any of
   them was to scroll back up. The panel is itself fixed to the right edge
   of the window in both its widths, so fixing these to the window's top
   right corner puts them in the same place in both.

   ABOVE THE FROZEN BAR (1002). They are siblings inside .detail and the
   bar would otherwise cover them -- and these are exactly the controls
   that must not become unreachable when something slides over them.

   ONE ROW, ONE SET OF OFFSETS. Four magic right: values that each had to
   agree with the padding reserved underneath them is how the fourth one
   ended up half under the listing reference. */
.dtools{position:fixed;top:12px;right:12px;z-index:1003;
  display:flex;align-items:center;gap:8px}

/* Add to Cart leads: it is the one control here that moves the deal on,
   so it is a labelled button rather than a glyph anybody has to decode. */
.dtcart{border:1px solid var(--line);background:var(--card);color:var(--ink);
  height:34px;padding:0 14px;border-radius:17px;cursor:pointer;
  font-size:13px;font-weight:600;white-space:nowrap;
  box-shadow:0 1px 6px rgba(0,0,0,.25)}
.dtcart:hover{border-color:var(--accent);color:var(--accent)}
/* In the cart: still a button -- pressing it again updates the terms --
   but it stops asking to be pressed. */
.dtcart.incart{color:var(--ok);border-color:var(--ok)}

/* THE LABEL GOES, THE CONTROL STAYS. On a phone the toolbar has room for
   one word, and "＋" is the word everybody reads as add. AFTER the base
   rule, not before it: these have the same specificity, so the one
   written later is the one that applies -- and written first they did
   nothing at all. */
@media (max-width:760px){
  .dtcart{padding:0 11px;font-size:0}
  .dtcart::before{content:"＋";font-size:15px;font-weight:700}
  .dtcart.incart::before{content:"✓"}
}
/* AND IN THE SIDE PANEL, for the same reason. The row gained a basket and
   -- for a guest -- a Sign In chip, and 680px is 680px whatever the
   window is, so a viewport media query cannot reach this case. The label
   is what gets spent: the control stays exactly where it is and keeps
   working, and "＋" is the word everybody reads as add. */
.detail:not(.full) .dtcart{padding:0 11px;font-size:0}
.detail:not(.full) .dtcart::before{content:"＋";font-size:15px;font-weight:700}
.detail:not(.full) .dtcart.incart::before{content:"✓"}

.close{position:absolute;right:12px;top:12px;z-index:5;width:34px;height:34px;border-radius:50%;
  border:1px solid var(--line);background:var(--card);color:var(--ink);
  font-size:22px;line-height:1;cursor:pointer;
  box-shadow:0 1px 6px rgba(0,0,0,.25)}
.close:hover{background:var(--bg)}
/* Lead image above, every remaining photograph in a strip beneath. The
   strip wraps to however many there are -- the previous two-column layout
   was built around exactly three images and left a hole in the grid the
   moment a listing had four. */
/* var(--line), not #dfe5e9: this colour shows through as the 3px seams
   between the photographs, and a pale literal draws bright lines across
   a dark page. */
.gallery{display:grid;gap:3px;background:var(--line)}
.gallery img{width:100%;height:100%;object-fit:cover;display:block}
.gallery .lead{aspect-ratio:16/10}
.gallery .strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(86px,1fr));gap:3px}
.gallery .strip img{aspect-ratio:4/3}
.close.expand{right:54px;font-size:17px}
/* In the row, flex places them. The offsets above still serve
   #closephotos, which is on its own in the photographs overlay. */
.dtools .close{position:static;right:auto;top:auto;flex:0 0 auto}

/* THE BASKET, and the count that rides on it. `position:relative` rather
   than the static above, because the badge is absolutely positioned
   against it -- nav.css owns .rcount and places it in the corner of
   whatever it sits in. */
.dtools .close.basket{position:relative;display:grid;place-items:center;
  text-decoration:none;color:var(--ink)}
.dtools .close.basket:hover{color:var(--accent);border-color:var(--accent)}
.dtools .close.basket svg{width:19px;height:19px;display:block}

/* SIGN IN. A chip with words in it, like the one in the corner cluster on
   every other screen -- two words dropped into a 34px disc wrap to two
   lines and spill out of it. */
.dtsignin{flex:0 0 auto;height:34px;padding:0 12px;border-radius:8px;
  border:1px solid var(--line);background:var(--card);color:var(--ink);
  display:grid;place-items:center;text-decoration:none;
  font-size:13px;font-weight:600;white-space:nowrap;
  box-shadow:0 1px 6px rgba(0,0,0,.25)}
.dtsignin:hover{color:var(--accent);border-color:var(--accent)}

/* Full screen. The panel is a 680px column by default, which is right for
   skimming numbers beside the map and wrong for looking at a house. Full
   screen is not just a wider column: at that width the photographs take
   the left and the figures the right, so the extra space goes to the
   pictures rather than to a 1600px line of text nobody can read. */
.detail.full{width:100%;right:0;left:0}
/* A GAP AT THE TOP. Full screen put the photograph and the price hard
   against the edge of the window with nothing between them and the
   browser's own chrome, so the page read as though it had been cropped
   rather than laid out. The side panel never needed it -- it has the
   map beside it and a shadow down its edge -- which is why this only
   appeared when full screen became what a click opens. */
.detail.full #detailbody{padding:18px 18px 0}
/* BACK TO LISTINGS. Full screen only: in the 680px side panel the
   results are visible beside the panel, and a back link pointing at
   something already on screen is noise. */
/* In the cart: the action bar's button stops shouting. It is still a
   button -- pressing it again updates the terms -- but it is no longer
   the thing on the page asking to be pressed. */
.dact .incart{box-shadow:inset 0 0 0 1px var(--line)}

.dback{display:none;align-items:center;gap:7px;border:0;background:none;
  color:var(--ink2);font:inherit;font-size:13.5px;font-weight:600;
  cursor:pointer;padding:2px 6px 10px 0}
.dback:hover{color:var(--accent)}
.dback span{font-size:16px;line-height:1}
.detail.full .dback{display:inline-flex}

/* THE RAIL: the price, and the person to ask about it.
   Narrow -- a phone, and the 680px side panel -- the two columns become
   one and the rail comes FIRST, straight under the photographs. A price
   printed after the income table is a price nobody scrolled far enough
   to see. */
/* minmax(0,1fr), and not for the first time -- .pwrap and the photograph
   strip have both had this. A grid with no named column gets an `auto`
   track, which is sized BY its content and will happily grow past the
   screen: the photograph's natural 1800px reached this track through
   .dwrap and pushed the column to 564px inside a 330px panel. Naming the
   column bounds it, and the content wraps inside it instead. */
.dcols{display:grid;grid-template-columns:minmax(0,1fr);gap:4px 26px}
.drail{display:grid;gap:12px;align-content:start;order:-1;
  padding:14px 0 2px}
.drail .dprice{margin:0}
@media (min-width:1000px){
  .detail.full .drail{order:0;padding:6px 0 0}
}
/* And the photograph gets a corner, so the thing sitting in that space
   reads as a panel rather than as a picture that failed to reach the
   edge. */

/* FULL SCREEN: A BAND, NOT A SCREENFUL.
   At full width and 3:2 this was 915px tall on a 1600px screen -- the
   entire first view, of a photograph that on a gated listing is a
   masked placeholder. It is a fixed-height band now with the lead
   beside the rest rather than above them, which is both shorter and
   more of the property visible at once.
   grid-auto-flow:column with a two-track cycle, so a listing with ONE
   photograph gives the whole band to it rather than leaving half the
   row empty -- the strip is not emitted at all when there is nothing
   to put in it. */
/* grid-template-rows:100% is load-bearing, not decoration. With
   grid-auto-flow:column and no explicit row the implicit row is `auto`,
   so `height:100%` on the lead photograph resolves against a track that
   is sized BY the lead photograph -- circular, so the percentage is
   dropped and the image falls back to its natural height. A 1200x800
   mask then drew 912px tall inside a 340px band and the band clipped
   the top third: sky, no house, and the watermark cut off entirely.
   Naming the row makes the track definite and the percentage resolve. */
/* NOTHING IS SHOWN AT THE WRONG SHAPE. The band is a fixed height and
   the panel is as wide as the screen, so on a 1900px monitor it was
   1760x350 -- five to one. object-fit:cover then threw away most of every
   photograph and what was left read as a flattened strip. A hero crop is
   normal; a five-to-one crop is a different picture.
   So the band is capped at 2.2x its own height and centred. That is the
   widest a photograph can be cropped to and still look like a photograph,
   and it holds at every screen width because both sides of it come from
   --band. */
.detail.full .gallery{--band:clamp(280px,42vh,520px);
  border-radius:12px;overflow:hidden}

/* ONE PHOTOGRAPH IS NOT A MOSAIC. With a strip beside it the lead has to
   fill its cell or the tiles do not line up, and a mild crop is the price
   of that. Alone it has no tiles to line up with, so there is nothing to
   buy and no reason to crop at all: the image keeps its own proportions
   and the band shrinks to fit it.
   width:fit-content is what makes the band hug the photograph instead of
   leaving bars beside it -- the grid is a block and would otherwise fill
   the panel. Both caps are on the image, so a tall photograph is limited
   by the height and a wide one by the width, and neither is stretched. */
/* LEFT, not centred. Centring it put a 700px photograph in the middle of
   a 1900px panel with a gap either side and nothing in them, floating
   over the column boundary. Left-aligned it starts where the
   specification, the figures and the table below it start, which is the
   line the eye is already following. */
/* THE MOSAIC: the lead beside its thumbnails, in one band.
   ASKED OF THE COLUMN, NOT THE SCREEN. This gallery lives in the left
   column and that column is about 540px wide on a 1100px screen and
   1240px on a 2000px one -- so a viewport media query says "wide" about
   a column that is not, and the lead came out 323px across and 420 down.
   A sliver, not a photograph. Same fault the score card had, same fix.

   aspect-ratio rather than a fixed height, for the same reason. The lead
   takes 1.5 of 2.5 tracks, so at 2.85:1 for the whole band the lead is
   always about 1.7:1 whatever the column is doing. A fixed height cannot
   hold that: it makes the lead wide on a big screen and narrow on a
   small one, which is exactly what was wrong. --band caps it so the
   photographs do not take the entire first view on a very wide column.

   grid-template-rows:100% is load-bearing. With grid-auto-flow:column
   and no explicit row the implicit row is `auto`, so `height:100%` on
   the lead resolves against a track sized BY the lead -- circular, the
   percentage is dropped, and a 1200x800 mask drew 912px tall inside a
   340px band and was clipped to the top third: sky, no house, and the
   watermark gone. Naming the row makes the track definite.

   Below 720px of column there is no mosaic at all: the gallery keeps its
   ordinary stacked form, the lead at 16/10 above a row of thumbnails. */
.detail.full .dwrap{container-type:inline-size}
@container (min-width:720px){
  .detail.full .gallery:not(.solo){display:grid;
    grid-auto-flow:column;grid-auto-columns:1.5fr 1fr;
    grid-template-rows:100%;
    aspect-ratio:2.85/1;max-height:var(--band)}
  .detail.full .gallery:not(.solo) .lead{aspect-ratio:auto;height:100%}
  /* minmax(0,1fr), not 1fr: a bare 1fr is minmax(auto,1fr) and the auto
     floor is the thumbnail's own aspect-ratio height, so the first row
     refused to shrink and the second hung 20px out of the band. */
  .detail.full .gallery:not(.solo) .strip{
    grid-template-columns:repeat(2,minmax(0,1fr));
    grid-auto-rows:minmax(0,1fr);height:100%}
  .detail.full .gallery:not(.solo) .strip img{aspect-ratio:auto;height:100%}
}

.detail.full .gallery.solo{display:block;height:auto;background:none}
/* THE IMAGE CARRIES BOTH CAPS, and the container is left alone. Sizing
   the container to the photograph instead -- width:fit-content -- reads
   well and is circular: fit-content asks the image how wide it wants to
   be, the image answers `max-width:100% of my container`, and the
   container is the thing being measured. Chromium resolved it against
   the image's natural 1800px and the column grew past the screen on a
   phone. An img with width and height auto under a max-width and a
   max-height keeps its own proportions inside both, with no such loop. */
.detail.full .gallery.solo .lead{display:block;aspect-ratio:auto;
  width:auto;height:auto;max-width:100%;max-height:var(--band)}
/* The container is now the full column, so bottom-right would put this
   button on the empty side. Bottom-LEFT is on the photograph, because
   the photograph starts at the column's left edge. */
.detail.full .gallery.solo .seeall{right:auto;left:14px}
/* The close and full-screen buttons float top-right. In the side panel
   they sit over the photograph; in full screen the figures column starts
   at the top and the listing reference lands underneath them. */
/* ROOM FOR THE TOOLBAR, ON THE PART THAT SITS UNDER IT. Four 34px discs
   and an Add to Cart pill in one pinned row, so what has to be cleared is
   the row's width rather than four separate offsets that each had to
   agree with this number. Reserving it on the whole header took it out of the header's
   content box, which left the price and the specification 329px of a
   549px row and folded "5 bd · 2 ba · 3,350 sqft · Single Family · built
   1970" onto two lines with 140px of empty space beside it. Only the
   reference and status block is anywhere near the buttons. */
.detail.full .dmeta{padding-right:460px}
@media (min-width:1000px){
  /* Two columns from the top of the page: the photographs and the deal on
     the left, a pinned rail on the right. The photographs used to be a
     full-width band above both, which only worked while they were cropped
     to fill it. */
  .detail.full .dcols{display:grid;align-items:start;
    grid-template-columns:minmax(0,1fr) minmax(300px,.4fr);
    column-gap:26px}
  .detail.full .dwrap{padding:0 0 60px}
  .detail.full .drail{position:sticky;top:18px}

}


/* The button sits on the lead image rather than beside it: it is about the
   photographs, and putting it anywhere else means nobody finds it. */
/* THE SAME BUG AS .close ABOVE, MISSED IN THE SAME PASS. A hardcoded
   near-white ground with `color: var(--ink)`: on a dark page --ink is
   near-white too, so the label was white text on a white chip and the
   button read as blank until it was hovered -- at which point the
   background flipped to --card, went dark, and the words appeared. A
   control that is only legible under the cursor is a control nobody
   knows is there.

   Fixed the way .close was: the palette's own card and ink, so the pair
   moves together in both themes, with a border and a shadow to hold it
   off whatever photograph is behind it. Hover lightens the ground by one
   step rather than inverting it. */
.seeall{position:absolute;right:14px;bottom:14px;z-index:3;cursor:pointer;
  border:1px solid var(--line);background:var(--card);color:var(--ink);
  font-size:12.5px;font-weight:600;
  padding:8px 13px;border-radius:7px;box-shadow:0 1px 8px rgba(12,18,24,.3)}
.seeall:hover{background:var(--bg)}
.gallery{position:relative}

/* ---------- all photographs ---------- */
.photos{position:fixed;inset:0;z-index:1200;background:var(--bg);overflow-y:auto}
.phead{position:sticky;top:0;z-index:2;display:flex;justify-content:space-between;
  align-items:center;gap:16px;padding:14px 20px;background:var(--card);
  border-bottom:1px solid var(--line)}
.phead .close{position:static}
.phttl{font-weight:700;font-size:15px}
.phsub{color:var(--ink2);font-size:12.5px}
/* One grid for the whole page, with the headings spanning it. The first
   version gave each section its own grid, so a section holding a single
   photograph rendered one tile and three empty columns -- four rooms
   became four near-empty rows. `display:contents` on the row lifts the
   figures into the page grid, so photographs flow densely and the
   headings still say what they are. */
.phgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));
  gap:8px 12px;padding:18px 22px 60px;max-width:1400px;margin:0 auto;align-items:start}
.phrow{display:contents}
.phsec{grid-column:1/-1;font-size:12px;font-weight:700;letter-spacing:.8px;
  text-transform:uppercase;color:var(--ink2);margin:18px 0 2px}
.phsec:first-child{margin-top:2px}
.phrow figure{margin:0;cursor:zoom-in}
.phrow img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block;border-radius:8px;
  background:var(--accent2)}
.phrow figcaption{font-size:11.5px;color:var(--ink2);margin-top:5px}
.phlock{display:inline-block;font-size:11px;font-weight:700;color:var(--gate);
  background:var(--gatebg);border:1px solid var(--gateline);border-radius:4px;
  padding:1px 6px;margin-left:6px}

/* ---------- one photograph, large ---------- */
.lightbox{position:fixed;inset:0;z-index:1300;background:rgba(10,15,20,.94);
  display:flex;align-items:center;justify-content:center}
.lightbox figure{margin:0;max-width:94vw;max-height:92vh;display:flex;
  flex-direction:column;align-items:center;gap:10px}
.lightbox img{max-width:94vw;max-height:82vh;object-fit:contain;display:block}
.lightbox figcaption{color:#e8eef4;font-size:13px;display:flex;gap:14px;align-items:baseline}
.lbcount{color:#93a4b4;font-variant-numeric:tabular-nums}
.lbnav{position:absolute;top:50%;transform:translateY(-50%);width:48px;height:76px;
  border:0;border-radius:8px;background:rgba(255,255,255,.12);color:#fff;font-size:34px;
  line-height:1;cursor:pointer}
.lbnav:hover{background:rgba(255,255,255,.24)}
.lbnav.prev{left:16px}
.lbnav.next{right:16px}

/* The map-area toggle reads as a control, not as a form field: it changes
   what the result means rather than adding another filter to it. */
/* `.filters label` is one class and one element, `.chk` is one class, so
   the column direction on the label was winning and this rule did nothing:
   the box sat above its own words at every width. Qualified, it applies. */
.filters label.chk,
.chk{flex-direction:row;align-items:center;gap:6px;font-size:12.5px;font-weight:600;
  color:var(--ink2);cursor:pointer;white-space:nowrap;padding-top:14px;
  text-transform:none;letter-spacing:0}
.chk input{width:15px;height:15px;accent-color:var(--accent);cursor:pointer}

/* Public notes on the listing. Attributed and dated, because "the agent
   said the roof was done in 2024" is worth being able to point at. */
/* The last note on a card. Colored when it is internal, because a member
   of staff glancing at a grid should be able to tell at once whether the
   thing they are reading is visible to buyers. */
.cnote{font-size:11.5px;color:var(--ink2);margin-top:5px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.cnote.int{color:var(--gate)}
.cnote b{font-weight:600}

.lnotes{display:grid;gap:9px}
.lnote{background:var(--accent2);border-radius:8px;padding:10px 13px}
.lnbody{font-size:13.5px;line-height:1.5;white-space:pre-wrap}
.lnwho{font-size:11.5px;color:var(--ink2);margin-top:6px}

.dwrap{padding:18px 22px 40px}
/* The header is the price and the specification, with the reference and
   the status in the corner. Nothing else: the concierge card used to be
   the other half of the right-hand side, where its stated minimum width
   came out of the specification and folded "5 bd · 2 ba · 3,350 sqft ·
   Single Family · built 1970" onto four lines. It is a card about a
   person and it sits under the header now, on its own. */
.dhead{display:flex;flex-wrap:wrap;justify-content:space-between;
  align-items:flex-start;gap:8px 16px}
.dhead > :first-child{flex:1 1 300px;min-width:0}
.dprice{font-size:29px;font-weight:700;letter-spacing:-.6px}
.dspec{color:var(--ink2);margin-top:2px}
/* The specification leads the left column now that the price has moved
   into the rail, so it is the size a lead line should be. */
.dspec.big{font-size:17px;color:var(--ink);margin-top:0}
.dhl{font-size:16px;font-weight:600;margin:12px 0 4px}
.ddesc{color:var(--ink2);margin:0 0 16px}
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(118px,1fr));gap:10px;margin:14px 0 18px}
/* var(--bg), not #f7f9fa. The literal was a pale grey behind text that
   takes its colour from the theme, so on a dark page the figures were
   near-white on near-white. */
.kpi{background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:9px 11px}
.kpi span{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--mut);font-weight:600}
.kpi b{font-size:17px;font-weight:700}
h2.sec{font-size:13px;text-transform:uppercase;letter-spacing:.7px;color:var(--mut);
  margin:22px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--line)}
/* BANDS, NOT RULES -- the same treatment as the year table in model.css.
   A line under every row draws a stroke across the panel for each one,
   and on a table of eight or fifteen the eye reads the strokes before it
   reads the figures. An alternating tint separates the rows just as well
   and puts nothing on the page that competes with the numbers.

   The tint is DERIVED rather than picked: color-mix nudges the surface a
   few percent toward the ink, so it lands correctly in both themes
   instead of needing a hand-chosen grey for each. The line above it is
   the light-theme value, for any browser without color-mix.

   The old rule was `border-bottom:1px solid #eef1f3` -- a literal, which
   is why these tables drew near-white hairlines on a dark page. */
table.fin{width:100%;border-collapse:separate;border-spacing:0}
table.fin td{padding:7px 9px;border-bottom:0}
table.fin tbody tr:nth-child(even) td,
table.fin tr:nth-child(even) td{background:#f6f8fa}
table.fin tbody tr:nth-child(even) td,
table.fin tr:nth-child(even) td{background:color-mix(in srgb, var(--card) 94%, var(--ink))}
/* The corners, so a band does not butt against the table edge as a
   rectangle with square shoulders. */
table.fin tr td:first-child{border-radius:5px 0 0 5px}
table.fin tr td:last-child{border-radius:0 5px 5px 0}
table.fin td:last-child{text-align:right;font-variant-numeric:tabular-nums;font-weight:600}
/* The total keeps its rule. It is the one row that is a different KIND
   of thing -- a sum of the rows above it -- and a band cannot say that.
   Its own tint is cleared so the stripe order does not decide whether
   the bottom line of a table looks emphasized. */
table.fin tr.total td{border-top:2px solid var(--line);border-bottom:0;
  padding-top:10px;background:none}
table.fin tr.total td:first-child{font-weight:700}
table.fin .sub{color:var(--mut);font-size:12px;font-weight:400}
.tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}
.tag{background:var(--accent2);color:var(--accent);border-radius:20px;padding:4px 11px;font-size:12.5px}
.note{background:var(--gatebg);border:1px solid var(--gateline);color:var(--gate);
  border-radius:8px;padding:10px 12px;margin:14px 0;font-size:13px}
.note.open{background:var(--okbg);border-color:#c9e5dc;color:var(--ok)}

/* The link out of the gate notice to /how-it-works.html. Underlined
   rather than coloured: the notice is already a tinted band, and an
   accent-coloured link inside a tinted band is the one thing on it that
   is hard to read in one of the two themes. */
.notemore{color:inherit;font-weight:600;text-decoration:underline;
  text-underline-offset:2px;white-space:nowrap}.dact{display:flex;gap:9px;margin-top:16px}
.dact button{flex:1;padding:10px;border-radius:8px;cursor:pointer;font-weight:600}
.empty{padding:50px 20px;text-align:center;color:var(--mut)}

@media (max-width:900px){
  main{grid-template-columns:1fr;height:auto}
  .mapwrap{height:320px;position:relative;border-right:0;border-bottom:1px solid var(--line)}
  .results{overflow:visible}
  /* The search moved to its own row here, but the bar it moved inside was
     still a single non-wrapping flex line -- so `flex-basis:100%` did not
     start a second row, it pushed the form off the end. On a phone that
     made the document 732px wide inside a 390px viewport: the browser
     zoomed the whole page out to fit, which put the layout viewport back
     above the breakpoints below, so none of the narrow rules applied at
     all. One missing `flex-wrap` was the whole of the mobile problem. */
  .bar{flex-wrap:wrap;row-gap:10px}
  .brand{min-width:0}
  .ask{order:3;max-width:none;flex-basis:100%;min-width:0;margin-left:0}
}

@media (max-width:620px){
  /* The rail already carries the mark, and 'Investment Property
     Marketplace' at this width is most of the row. */
  .brandtxt{display:none}
  .bar{padding:10px 12px;gap:10px}
  .ask{padding-left:11px}
  .ask button{padding:5px 11px}
  .filters{gap:8px}
}

/* built-in map, used when the tile CDN is unreachable */
#mapfallback .fb rect{transition:transform .1s ease}
#mapfallback .fb.hi rect{fill:#0f4a72;stroke:#0f4a72}
#mapfallback .fb.hi text{fill:#fff}
#mapfallback .fb.hi circle{fill-opacity:.24}
.nobase{position:absolute;left:0;right:0;bottom:44px;text-align:center;
  font-size:12px;color:var(--mut);pointer-events:none}

/* ---------------------------------------------------------------------
   flags
   A pennant on a pole, drawn rather than typed: ⚑ is missing from enough
   fonts that it turns into a box on the machines least likely to have a
   designer nearby. It takes its color from the text around it.
   --------------------------------------------------------------------- */
.flag{display:inline-block;width:10px;height:11px;background:currentColor;
  vertical-align:-1px;flex:0 0 auto;
  clip-path:polygon(0 0,100% 0,64% 38%,100% 76%,22% 76%,22% 100%,0 100%)}

/* Staff only -- see the comment on staff() in app.js. */
.cflag{display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:700;
  border-radius:6px;padding:3px 8px;margin-top:8px;width:fit-content}
.cflag.critical{color:var(--crit);background:var(--critbg);border:1px solid var(--critline)}
.cflag.attention{color:var(--attn);background:var(--attnbg);border:1px solid var(--attnline)}

/* ---------------------------------------------------------------------
   the share dialog
   --------------------------------------------------------------------- */
.sharewrap{position:fixed;inset:0;z-index:2200;background:rgba(10,22,34,.55);
  display:grid;place-items:center;padding:20px}
.sharebox{width:min(520px,100%);background:var(--card);border-radius:12px;
  padding:22px 22px 18px;box-shadow:var(--shadow);max-height:90vh;overflow:auto}
.sharebox h2{margin:0 0 4px;font-size:17px}
.smut{margin:0 0 16px;font-size:13px;color:var(--ink2);line-height:1.5}
.sfield{display:grid;gap:5px;margin-bottom:16px}
.sfield span{font-size:12px;font-weight:700;color:var(--ink2)}
.sfield input{padding:9px 11px;border:1px solid var(--line);border-radius:8px;
  font:inherit;font-size:14px;background:var(--bg);color:var(--ink)}
.sfield input:focus{outline:2px solid var(--accent);outline-offset:-1px}

.schoice{display:grid;gap:8px}
.sopt{display:flex;gap:10px;align-items:flex-start;padding:11px 13px;cursor:pointer;
  border:1px solid var(--line);border-radius:9px;background:var(--card)}
.sopt span{display:grid;gap:2px}
.sopt b{font-size:13px}
.sopt i{font-style:normal;font-size:12px;color:var(--ink2);line-height:1.45}
.sopt.on{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
/* Shown and disabled rather than removed. "Not available to you" is an
   answer; a control that is simply absent leaves somebody wondering whether
   the feature exists at all and asking an administrator for it. */
.sopt.off{opacity:.55;cursor:not-allowed}
.sopt input{margin-top:2px;accent-color:var(--accent)}

.swarn{margin:12px 0 0;font-size:12px;line-height:1.5;color:var(--crit);
  background:var(--critbg);border:1px solid var(--critline);border-radius:8px;
  padding:9px 11px}
.sact{display:flex;align-items:center;gap:9px;margin-top:18px}
.smsg{margin-right:auto;font-size:12px;color:var(--ink2)}
.dact{display:flex;gap:9px;margin-top:16px;flex-wrap:wrap}
/* Share, beside close and full-screen. Same affordance as its neighbors so
   it reads as part of the panel's controls rather than as content. */
.close.share{right:96px;font-size:16px}

/* THE HEART, fourth in the row and furthest from the close button --
   deliberately, because the two either side of a misjudged click should
   not be "save this" and "throw this away".

   RED ONLY WHEN IT IS ON. Unsaved it is the same quiet outline as its
   three neighbours, so a row of four controls does not have one shouting
   for no reason; saved it fills, and that is the whole signal. */
.close.fav{right:138px;font-size:17px;line-height:1}
.close.fav:hover{color:var(--heart)}
.close.fav[aria-pressed="true"]{color:var(--heart);border-color:var(--heart)}
.smsg.bad{color:var(--crit);font-weight:600}
/* A refused search is not an error and must not look like one. It is a
   considered "no" with a reason, so it reads as the system explaining
   itself rather than failing. */
.parsed.refused{background:var(--attnbg);border-color:var(--attnline);color:var(--attn)}
.parsed.refused .note{color:var(--ink2)}

/* ---------- the theme toggle ---------- */
/* In the top bar, at the right end. A single icon button: it says what it
   will do next in its tooltip rather than carrying a label, because a
   header with "Light/Dark/System" written across it is a preference panel
   in the furniture. The full three-way choice is on the profile. */
/* ROUND, AND THE SAME SIZE as the favorites and cart controls beside it.
   This sits at the right end of the header and the top-right cluster is
   fixed immediately after it, so the two are read as one row whether they
   are one element or not -- and a square among circles reads as something
   that does not belong to the group. */
.themetoggle{display:inline-flex;align-items:center;justify-content:center;
  width:38px;height:38px;flex:0 0 auto;padding:0;cursor:pointer;
  border:1px solid var(--line);border-radius:50%;
  background:var(--card);color:var(--ink2)}
.themetoggle:hover{background:var(--accent2);color:var(--accent);
  border-color:var(--accent2)}
.themetoggle:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.themetoggle svg{width:18px;height:18px;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
/* The moon and the monitor are shapes, not outlines -- an outlined
   crescent at 16px is a smudge. */
:root[data-theme="dark"] .themetoggle svg path:first-child{fill:currentColor;
  stroke:none}

/* The plotted-map fallback. Its colors are here rather than in the SVG
   attributes app.js writes, because an inline SVG cannot inherit a
   background and the palette has to be able to reach it. */
.fbground{fill:var(--mapbg)}
.fbgrid{stroke:var(--mapgrid)}
.fbring{fill:var(--mapring);fill-opacity:.16;stroke:var(--gate)}
.fbpill{fill:var(--accent);stroke:var(--onfill)}
.fbpill.gated{fill:var(--card);stroke:var(--gateline)}
.fbprice{fill:var(--onfill)}
.fbprice.gated{fill:var(--gate)}
.fblabel{fill:var(--mut);opacity:.9}

/* ---------------------------------------------------------------------
   Saved to your cart, keep looking
   ---------------------------------------------------------------------
   A guest who presses Add to Cart is no longer taken to a sign-up form,
   so something has to say the press worked -- an unchanged page after a
   click reads as a dead control. It sits at the bottom, over the listing
   they are still reading rather than pushing it down, and it leaves on
   its own.

   Both colours from the palette, and a border, because it floats over
   whatever is behind it. (See .seeall for what happens when only one of
   the two comes from the palette.) */
.keptbar{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);
  z-index:1100;display:flex;align-items:center;gap:14px;
  max-width:min(680px,calc(100vw - 32px));
  padding:11px 12px 11px 16px;border-radius:10px;
  border:1px solid var(--line);background:var(--card);color:var(--ink);
  box-shadow:var(--shadow);font-size:13.5px;line-height:1.45}
.keptwhat{min-width:0}
.keptgo{flex:0 0 auto;padding:6px 11px;border-radius:7px;
  background:var(--accent);color:var(--onfill);text-decoration:none;
  font-weight:600;white-space:nowrap}
.keptgo:hover{filter:brightness(1.08)}
.keptx{flex:0 0 auto;border:0;background:none;color:var(--mut);
  font-size:19px;line-height:1;cursor:pointer;padding:2px 4px}
.keptx:hover{color:var(--ink)}

/* On a phone it is the width of the screen and the text wraps above the
   button rather than squeezing it to nothing. */
@media (max-width:560px){
  .keptbar{left:16px;right:16px;transform:none;max-width:none;flex-wrap:wrap}
  .keptgo{margin-left:auto}
}
