/* ==========================================================================
   Browse v5 -- shared across broker, buyer and public Browse
   --------------------------------------------------------------------------
   Lives here, NOT in the Tailwind bundle, because layouts/explore.html.erb loads
   only the marketing bundle. While these rules sat in components/brokering.css the
   class names simply did not exist on /explore, so no view work there could have
   shown a result.

   Two consequences of being a plain, unlayered stylesheet:

   * It outranks Tailwind utilities instead of losing to them. That is why the market
     row's flex-wrap and padding no longer need !important. It also means a utility
     in a view can no longer override these rules: reach for a modifier class.
   * Tokens are not guaranteed. --base-text, --base-border-tertiary,
     --color-brand-green and --type-* ARE defined on explore, but --base-bg-subtle
     and --ws-coral-* are NOT (they come from themes/light.css and broker-v2.css), so
     every var here carries a literal fallback. Keep it that way.

   A surface opts in with the .browse-v5 wrapper class; nothing is restyled
   implicitly.
   ========================================================================== */

/* ONE accent for every Browse surface. `--color-brand-green` is not the same
   colour in every bundle: it resolves to the dashboard teal (#0EA184) in the app
   bundle and to the client site's v2 forest green (#205E3A) on /explore, so a
   shared partial that consumed it directly rendered two different buttons on two
   pages -- measured on the filters sheet's "Show N listings". The design's green
   is the teal, so Browse pins it here rather than inheriting whichever palette a
   layout happened to load.

   Declared on .browse-v5-sheet too: the sheet is re-parented onto <body>, which
   takes it out of the .browse-v5 subtree and with it any inherited custom property. */
/* Declared on every Browse ROOT, and there are four: the page wrapper, the
   re-parented buyer sheet (moved onto <body>, so it inherits nothing), and broker's
   toolbar and sheet, which render into the shell's page-header slot and are hoisted
   OUT of the wrapper. Miss one and its components fall back to whatever
   --color-brand-green means in that bundle -- which is how buying's practice-type
   pills and range sliders came out forest green while explore's were teal. */
.browse-v5,
.browse-v5-sheet,
.brokering-browse-toolbar,
.brokering-browse-sheet {
  --browse-accent: #0EA184;
  --browse-accent-deep: #0C7C66;
  /* The selection bar's two greens, from the prototype: #063E36 for the count and
     #0A6E5C for the sentence beside it. Tokens rather than literals in the rules
     below so Dark Dimmed / Dark High Contrast can flip them -- deep green on a
     green tint is unreadable there. */
  --browse-ink: #063E36;
  --browse-ink-soft: #0A6E5C;
  /* The card's neutral surface (covers, metric tiles, chips). Pinned for the same
     reason as the accent: --base-bg-subtle is #F9FAFB in the app bundle and #F7F7F7
     on the client site, so the SAME card rendered two different greys on
     /buying/browse and /explore. Measured. */
  --browse-surface: #F7F7F7;
  /* Breathing room BELOW the split, and room for a card's shadow to fall outside the
     card box. Both are VISUAL, and deliberately separate from the shell's height: the
     split is still sized from the measured chrome, it is just now knowingly SHORTER
     than the space available, so the map does not sit flush against the fold.
     Neither number is new. 24px is the `margin-bottom` `.explore-map-layout` has
     always carried (browse_map.css), which is the gap this relationship was already
     expressing on /explore before the dashboards zeroed it. 16px is the reach of the
     canonical card shadow -- --brk-shadow-card / --buying-shadow-card / --shadow-card
     are all `0 2px 16px`, so 16px is exactly how far outside its box it paints. */
  --browse-split-gap: 24px;
  --browse-shadow-gutter: 16px;
}

:is(.dark, .dimmed) :is(.browse-v5, .browse-v5-sheet, .brokering-browse-toolbar, .brokering-browse-sheet) {
  --browse-ink: #DDE2DF;
  --browse-ink-soft: #8FD0A8;
  /* 🚨 --browse-surface had NO dark value, so every surface built on it — the card
     chips, the cover placeholder, the metric tiles, the map "reset" control, the
     active-filter pills — painted #F7F7F7 in Dark Dimmed and Dark High Contrast
     while their text stayed light. Measured on /brokering/practices in Dark Dimmed:
     a chip was rgb(179,189,184) on rgb(247,247,247), about 1.6:1. The token was
     pinned to a literal (see the note above) to stop the same card rendering two
     greys across bundles; that reasoning holds per mode, it just never covered the
     dark modes. #303030 is the dashboard's --base-bg-subtle in both dark ramps and
     the same relationship to the dark page ground that #F7F7F7 has to white. */
  --browse-surface: #303030;
}

/* ==========================================================================
   Browse listings — v5 redesign ("Browse Listings Prototype v5")
   Broker-only. Every rule is scoped to .browse-v5 (or a
   .brokering-browse-* local) so the seller shell and the buyer/public
   marketplace, which share .brokering-practice-card, render unchanged.
   Colours come from tokens, so Light / Dark Dimmed / Dark High Contrast all
   follow without per-mode overrides.
   ========================================================================== */

/* ---------- Toolbar: search · filter · scope · view, one row ---------- */
.brokering-browse-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  width: 100%;
}

.brokering-browse-search {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1 1 320px;
  min-width: 0;
  height: 48px;
  padding: 0 8px 0 16px;
  border-radius: var(--radius-xl, 12px);
  border: 1px solid var(--base-border-tertiary);
  background: var(--base-bg-low, #fff);
  transition: box-shadow 150ms ease-out, border-color 150ms ease-out;
}

.brokering-browse-search:focus-within {
  border-color: var(--browse-accent, #0EA184);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--browse-accent, #0EA184) 18%, transparent);
}

.brokering-browse-search__icon {
  flex: none;
  color: var(--base-text-tertiary);
}

/* The search pill IS the focus affordance (:focus-within below), so the input must
   contribute no ring of its own. THE REAL FIX IS NOT HERE: the wrapper carries
   `.field-group`, the canonical "this wrapper owns the focus ring" marker that the
   dashboard-wide input safety net in themes/broker-v2.css excludes structurally.
   Read the DUAL-BORDER RULE note there before touching this. That safety net scores
   about (0,5,1), so no amount of specificity here would have won; `box-shadow` is
   the property that bites (it drew a 3px ring which, with the inherited 12px radius,
   became a second rounded outline nested inside the pill), and clearing border and
   outline alone does nothing about it. These stay as belt and braces, like their
   neighbours in workspace.css, because they also beat @tailwindcss/forms. */
.brokering-browse-search input,
.brokering-browse-search input:focus,
.brokering-browse-search input:focus-visible {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  box-shadow: none;
  border-radius: 0;
  background: transparent;
  padding: 0;
  color: var(--base-text);
  font: var(--type-body) var(--font-sans);
  /* 🚨 Stretch, do not sit at content height. The input's box was 20px tall around a
     19.5px line box, and browsers apply `overflow: clip` to text inputs, so every
     DESCENDER was shaved: "ppppp" rendered as "opppp" because the first p lost its
     stem. Filling the 52px field gives the line box room. */
  align-self: stretch;
  height: auto;
  line-height: normal;
}

.brokering-browse-search input::placeholder { color: var(--base-text-tertiary); }

.brokering-browse-filterbtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
  height: 36px;
  padding: 0 15px;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  background: var(--base-bg-subtle, #F7F7F7);
  color: var(--base-text-secondary, var(--base-text));
  transition: background-color 120ms linear;
}

/* 🚨 NOT --base-bg-hover. CLAUDE.md bans it as a background outright: in light mode
   it is a warm beige-grey that reads as "disabled" against every white card in the
   app. The neutral this button already rests on is the right hover surface. */
.brokering-browse-filterbtn:hover { background: var(--status-neutral-bg, #F4F4F4); }

.brokering-browse-filterbtn__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--browse-accent, #0EA184);
  color: #fff;
  font: var(--type-caption) var(--font-sans);
  font-weight: var(--fw-bold, 700);
  line-height: 1;
}

/* ---------- Applied filter chips ---------- */
.brokering-browse-chips {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

/* ---------- Split layout: cards left, rounded map right ----------
   🚨 The height is the space left UNDER the page chrome, and it has to be MEASURED.
   This read `var(--brokering-browse-chrome, 172px)`, and nothing in the repo ever
   set that variable, so 172px was a hardcoded height wearing a variable's clothes.
   It happened to be right until the "Matching your lead preferences" banner was
   added above the split: the real chrome grew ~66px, the constant did not, the
   shell's bottom fell below the fold, and the DOCUMENT began scrolling -- carrying
   the map up and out of view with a blank region beneath it.
   `--browse-split-chrome` is published by browse_split_controller from the split's
   own `getBoundingClientRect().top`, so the invariant
   `splitTop + splitHeight === viewportHeight` holds whatever sits above it. The
   literal is a no-JS fallback ONLY; it is not a design value. */
.browse-v5 .brokering-listings-map-layout {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0;
  /* The SHELL fills the space under the chrome exactly:
     `splitTop + splitHeight === viewportHeight`, so the document does not scroll.
     🚨 The gap below the map is NOT taken out here. It belongs to the MAP COLUMN
     alone (see `.brokering-browse-split__map` below). Subtracting it from the shell
     insets both columns, which stops the listings scroller a gap short of the fold --
     and a scrolling list that ends above the bottom edge reads as "the list is
     finished" when it is not. In the reference the cards run into the viewport edge
     and only the map is inset. */
  height: calc(100dvh - var(--browse-split-chrome, 172px));
  /* A floor, so a very short window cannot shrink the map to something useless. It
     is the one case that still lets the document scroll a little, deliberately. */
  min-height: 420px;
}

/* The page body's 60px bottom padding is the last thing standing between the split
   and a document that does not scroll at all. Both dashboard shells carry the same
   `padding: 14px 24px 60px`, and measured on /buying/browse it was the entire
   difference between a 745px viewport and an 805px document.
   Zeroed in MAP VIEW only: grid view is an ordinary scrolling page and wants its
   footer breathing room. Broker needs `:has()` because there the modifier sits on an
   inner wrapper; on the buyer surface it is on the padded element itself.
   🚨 DESKTOP ONLY. Below 900px the split stops being a viewport shell and stacks
   (broker) or becomes a bottom sheet (explore), the page scrolls normally, and that
   60px is the last card's breathing room above the viewport edge. */
@media (min-width: 900px) {
  .brokering-page-body:has(.browse-v5.is-map-view),
  .buying-page-body.browse-v5.is-map-view {
    padding-bottom: 0;
  }
}

/* ---------- Shadow gutter ----------
   The scroller is `overflow-y: auto`, and that makes it a CLIP box on BOTH axes
   (an `overflow-y: auto` box computes `overflow-x: auto` too). The cards sit flush
   against its left edge, so the canonical card shadow -- 0 2px 16px, i.e. 16px of
   reach -- was sliced flat on the left while the 20px right and 24px bottom insets
   let it through. A shadow present on only two sides of a box reads as a hard
   rectangle, which is what "boxy shadows" and "shadows cut on the left" both were.
   Measured on /explore before this: first card left 40, scroller clip edge 40.

   🚨 The negative margin is NOT a nudge and must stay paired with the padding.
   Together they move the CLIP outward by the gutter and leave the card boxes exactly
   where they were, so the cards stay flush with the search field above them. There
   is no alternative: `overflow-clip-margin` has no effect on a scroll container, and
   a scroll container must clip. Both clipping boxes in the chain need it -- the
   column is `overflow: hidden` and the scroller `auto`, and they clip at the same x.
   Safe because both boxes are fully transparent here (no background, border, radius
   or shadow -- measured) and every ancestor up to <body> is `overflow: visible`, with
   the page gutter for the 16px to bleed into.

   The TOP is deliberately left as it was. Room above the first card is the very same
   space cards scroll up into, so it cannot be opened without also showing scrolled
   cards beside the results head -- and it costs only the FIRST card at scroll-top,
   every other one having the row gap above it. Left, right and bottom are what every
   card carries all the time. */
.browse-v5 .brokering-browse-split__list {
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding-left: var(--browse-shadow-gutter);
  margin-left: calc(-1 * var(--browse-shadow-gutter));
}

.browse-v5 .brokering-browse-split__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* No LEFT inset on the CARDS: the design has them flush with the search field
     above, and 2px here left them visibly off it. The gutter is not an inset -- the
     negative margin cancels it, so the cards stay flush and only the clip moves.
     TOP is a plain inset and needs no negative margin: the clip edge there is the
     head's bottom margin edge, which does not move, so this only lowers the FIRST
     card -- scrolled cards could always reach the clip and still stop in the same
     place. It buys the first card a full top shadow and lands closer to the design,
     which shows ~35px between the count row and the first card against the 16px this
     had. 20px right stays: room for the scrollbar. */
  padding: var(--browse-shadow-gutter) 20px 24px var(--browse-shadow-gutter);
  margin-left: calc(-1 * var(--browse-shadow-gutter));
}

.browse-v5 .brokering-browse-split__head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  /* Flush left, like the cards below and the search field above. The TOP padding is
     breathing room under the search field: the sort control is 34px sitting under a
     52px field that carries a shadow, and at 14px of clearance it read as tucked
     under it (Shahnoor, review of #251). Measured -- nothing actually overlaps it. */
  padding: 10px 20px 14px 0;
}

.browse-v5 .brokering-browse-split__map {
  position: relative;
  min-height: 0;
  border-radius: 20px;
  overflow: hidden;
  background: var(--base-bg-subtle, #F2F4F3);
}

/* Breathing room under the MAP, and only the map. A grid item stretches to the row,
   so a bottom margin shortens it without touching the shell's height or the listings
   column beside it: the map floats clear of the fold while the cards run into it.
   Desktop only -- below 900px the map is a 46dvh band above the stacked list (broker)
   or a bottom sheet (explore), and the page scrolls normally. */
@media (min-width: 900px) {
  .browse-v5 .brokering-browse-split__map {
    margin-bottom: var(--browse-split-gap);
  }
}

/* The panel that fills this frame is pinned by shared/browse_map.css (search
   ".brokering-browse-split__map"): that file is unlayered and therefore beats
   anything in this bundle's @layer, so the override has to live there. */

/* ---------- Card grid ---------- */
.browse-v5 .brokering-browse-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(228px, 1fr));
  gap: 28px 20px;
}

/* Grid view is three fixed columns in the design, not auto-fit. */
.browse-v5 .brokering-listings-grid.brokering-browse-grid--wide {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 30px 22px;
}

/* ---------- Card ---------- */
.browse-v5 .brokering-browse-card {
  position: relative;
  border: 0;
  border-radius: 16px;
  background: var(--base-bg-low, #fff);
  padding: 14px 16px 16px;
  transition: box-shadow 150ms cubic-bezier(0, 0, .2, 1), transform 150ms cubic-bezier(0, 0, .2, 1);
}

.browse-v5 .brokering-browse-card:hover,
.browse-v5 .brokering-browse-card.is-map-hover {
  transform: translateY(-2px);
}

/* 🚨 ONE card shadow, both classes, rest AND hover, in one place.
   This was two rules that disagreed. Broker's card had
   `box-shadow: 0 0 0 1px var(--base-border-tertiary)` -- a 1px RING -- while
   `.explore-card` was pinned to the soft drop shadow under a comment asserting that
   "broker's card wears the dashboard's soft drop shadow". It did not. The claim held
   in light mode by accident of a later override and broke in dark, where broker
   measured `rgba(255,255,255,0.08) 0 0 0 1px` against the buyer's
   `rgba(0,0,0,0.07) 0 2px 16px`.
   On hover the two disagreed again, and the buyer additionally inherited
   `border-color: #c7ddd0` from `.explore-card:hover` in the marketing bundle -- a
   hardcoded pale GREEN border on every buyer card hover, which broker never had.
   Literals, per this file's rule about bare vars, so no bundle and no mode can
   reinterpret them. */
.browse-v5 .brokering-browse-card,
.browse-v5 .explore-card {
  /* The canonical card elevation, by name. All four names the design system uses for
     it hold the SAME value -- --brk-shadow-card (broker-v2.css), --buying-shadow-card
     (buyer-v2.css, which also aliases --brk-*), --shadow-card (marketing) and
     --ref-shadow (referrals.css) -- so this is a rename, not a restyle: there is no
     softer canonical shadow to move to, and the boxy look was the clip above, not
     this value. Named so a future change to the design system's elevation reaches
     Browse. The literal tail is this file's standing rule: a bundle that does not
     define the token would otherwise drop the whole declaration. */
  box-shadow: var(--brk-shadow-card, var(--shadow-card, 0 2px 16px rgba(0, 0, 0, 0.07)));
  border-color: transparent;
}

/* The lift is the shells' own two-layer one. Both `--brk-shadow-lift` (broker-v2) and
   `--buying-shadow-lift` (buying.css) are defined as exactly
   `0 8px 24px rgba(0,0,0,.10), 0 2px 8px rgba(0,0,0,.06)`, and broker's card already
   renders it in light mode from a rule this one does not out-specify. Measured:
   broker two-layer against the buyer's single `0 12px 28px`. Written as the literal so
   both surfaces land on the value broker already had. */
.browse-v5 .brokering-browse-card:hover,
.browse-v5 .brokering-browse-card.is-map-hover,
.browse-v5 .explore-card:hover,
.browse-v5 .explore-card.is-map-hover {
  box-shadow: var(--brk-shadow-lift, var(--shadow-lift, 0 8px 24px rgba(0, 0, 0, 0.10), 0 2px 8px rgba(0, 0, 0, 0.06)));
  border-color: transparent;
}

.browse-v5 .brokering-browse-card__link { display: block; }

.browse-v5 .brokering-browse-card__cover {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: 12px;
  overflow: hidden;
  background: var(--base-bg-subtle, #F4F6F6);
  margin-bottom: 13px;
}

/* 🚨 ONE row of chips, always. Chips wrap, and a listing carrying four of them
   ("General · Anonymous · Draft · In your leads") ran to three rows where its
   neighbour ran to one -- measured at 21px, 48px and 76px on the same page. Every
   attempt to absorb that difference elsewhere either misaligned the rows below or left
   a visible hole in the shorter card. A fixed single row removes the difference at
   source: overflow hidden drops whole chips rather than slicing one, and the markup
   puts the identifying chips first, so what falls off the end is the least important.
   Anything genuinely load-bearing (match quality, "Broker accepted") also renders in
   the card body, not only here. */
.browse-v5 .brokering-browse-card__chips {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  min-width: 0;
  /* ONE line, always, so the blocks below line up across every card in a row. The
     chip ORDER in explore/_practice_card is chosen around this: the chip that
     survives the clip is the one carrying state, not the one nearly every listing
     carries. */
  height: 21px;
  overflow: hidden;
  margin-bottom: 10px;
}

/* 🚨 ...but the match chip's popover is an OVERLAY, and the clip above ate it.
   `.explore-match-chip__popover` is `position: absolute; top: 100%`, and its
   containing block (`.explore-match-chip`) sits INSIDE this clip, so a box with
   `overflow: hidden` between the two clips it. Measured on /explore at 1440:
   elementFromPoint at the popover's centre returned
   `.brokering-browse-card__where`; lifting only this clip returned
   `.explore-match-chip__row`. That popover is the only place a buyer is shown WHY a
   listing matched, so the clip was silently removing the feature.

   Lifted only while the popover is actually shown, so the clip keeps doing its job
   the rest of the time. The height is deliberately NOT touched: leaving it at 21px
   means nothing below the row moves, so opening the popover costs no layout shift.
   A card whose chips wrap can show a peek of the second line beside the popover
   while it is open; that is a transient hover artifact, against a feature that was
   invisible permanently.

   Kept as a state rule rather than moving the popover out of the row in the markup,
   because the popover lives INSIDE the chip on purpose: that is what makes hovering
   down onto it keep it open, and what lets its caret sit flush under the trigger. */
.browse-v5 .brokering-browse-card__chips:has(.explore-match-chip:hover),
.browse-v5 .brokering-browse-card__chips:has(.explore-match-chip.is-open) {
  overflow: visible;
}

/* 🚨 There used to be `padding-right: 62px` here, commented "room for the
   absolutely-positioned action cluster". There is nothing to make room FOR:
   `.brokering-browse-card__actions` is `position: absolute; top: 24px`, and the card's
   14px padding puts the cover's top edge at 14px, so the cluster sits ON THE COVER,
   a full row above these chips. The gutter reserved 22% of a 307px card's chip row
   for nothing, which is what pushed a third short chip ("General · Anonymous ·
   Draft" — 214px of content in 275px of space) onto a second line. */

/* Consistent geometry for a long label and a short one, which is the whole point of
   a chip: it may never break its own text into two lines, and it may never out-run
   the card. The longest labels the system can produce are "Multi Specialty" (the
   longest of Practice.practice_types), "Financials pending" and "My active
   contract"; each fits on one line at every card width, and the ROW wraps between
   chips. No `text-overflow: ellipsis` — the chip is an inline-flex box, so ellipsis
   would not apply to it; `overflow: hidden` is the honest guard for a label longer
   than a whole card, which nothing produces today. */
.browse-v5 .brokering-browse-chip {
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
}

/* 🚨 STATED, not inherited. The chips row and the footer declare no weight of their
   own, and the shells' ancestor chains do not agree: measured 500 on broker against
   400 on the buyer for both. Setting it on the card was not enough -- something above
   the card in broker's shell still won -- so the two elements say it themselves. */
.browse-v5 .brokering-browse-card__chips,
.browse-v5 .brokering-browse-card__footer {
  font-weight: var(--fw-regular, 400);
}

.browse-v5 .brokering-browse-card__title {
  font: var(--type-card-title) var(--font-display, var(--font-sans));
  letter-spacing: -0.015em;
  color: var(--base-text);
  margin: 0 0 3px;
}

.browse-v5 .brokering-browse-card__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: var(--text-xs);
  font-weight: var(--fw-medium, 500);
  color: var(--base-text-tertiary);
  margin: 0 0 12px;
}

.browse-v5 .brokering-browse-card__price-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0 0 14px;
}

.browse-v5 .brokering-browse-card__price {
  font: var(--type-stat) var(--font-display, var(--font-sans));
  letter-spacing: -0.03em;
  color: var(--base-text);
}

.browse-v5 .brokering-browse-card__price-caption {
  font-size: var(--text-xs);
  font-weight: var(--fw-medium, 500);
  color: var(--base-text-tertiary);
}

/* 🚨 The outreach chip rides the trailing edge of this row (shared/_browse_facts's
   `price_aside`) instead of taking a line of its own under the tiles. A line only some
   cards have is a line that makes two cards in a grid disagree about where everything
   below it sits. `align-self: center` because the row aligns on the price's baseline and
   a chip is a box with no baseline worth aligning to; `margin-left: auto` puts it on the
   far edge, and flex-wrap drops it to its own line only when the card is too narrow to
   hold it beside the number. */
.browse-v5 .brokering-browse-card__price-aside {
  margin-left: auto;
  align-self: center;
  min-width: 0;
}

.browse-v5 .brokering-browse-card__tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.browse-v5 .brokering-browse-tile {
  background: var(--base-bg-subtle, #F7F7F7);
  border-radius: 10px;
  padding: 10px 12px;
  min-width: 0;
}

.browse-v5 .brokering-browse-tile__label {
  display: block;
  font: var(--type-caption) var(--font-sans);
  color: var(--base-text-tertiary);
  margin-bottom: 2px;
}

.browse-v5 .brokering-browse-tile__value {
  display: block;
  font: var(--type-ws-money-sm) var(--font-display, var(--font-sans));
  letter-spacing: -0.01em;
  color: var(--base-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 🚨 The bottom stack starts at the TILES, and the auto margin lives there -- exactly
   one auto margin per flex column, or the free space is split between them and neither
   pins. Anchoring the tiles (rather than just the footer) is what makes the
   Collections / EBITDA / Multiple row sit on the same line across every card in a row:
   the cards are equal height, the stack below the tiles is fixed height, so the tiles
   land at the same y no matter how many chip or meta lines sit above them. */
/* 🚨 The card's LINK wrapper has to be the flex column, or the auto margin below has
   nothing to push against. On broker the facts render inside
   `.brokering-browse-card__link`, which is a plain block `<a>`: `margin-top: auto`
   inside a block box does nothing, so nothing pinned there and a card whose chips wrap
   to one row instead of two pulled its title, price, tiles and footer 27px up while
   its neighbour's stayed. (The buyer card already had a flex `.explore-card__body`,
   which is why it pinned and broker did not.) */
.browse-v5 .brokering-browse-card__link {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* The metric strip is the bottom anchor: with every row above it now a fixed height,
   the tiles and the footer land on the same line across a row without a gap opening
   anywhere in the middle of the shorter card. */
.browse-v5 .brokering-browse-card__tiles {
  margin-top: auto;
}

/* Rendered even when a listing has no ops/patients line, so the row exists on every
   card and the price below it cannot ride up on one and not the other. */
.browse-v5 .brokering-browse-card__meta {
  min-height: 17px;
}

.browse-v5 .brokering-browse-card__footer {
  font-size: var(--text-xs);
  color: var(--base-text-tertiary);
  margin: 14px 0 0;
  min-height: 1em;
}

/* ---------- Triage footer (Catch up queue only) ----------
   Two decisions on the card, under a hairline, because judging a lead means reading the
   photo and the financial tiles directly above them. Browse never renders this: its card
   is for shortlisting, and its controls are the save heart and the bulk checkbox.

   Equal width, so neither answer is the default. "Yes" carries the mint, "no" stays
   neutral: mint on this surface means the move to make, and two mint buttons side by side
   would make declining look recommended. Both are real buttons because both POST. */
.browse-v5 .brokering-browse-card__triage {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--base-border-tertiary);
}

/* 🚨 THE TRIAGE CARDS IN A ROW MUST END ON THE SAME LINE. Each card was as tall as
   its own content, so a card carrying one extra line put its hairline and its two
   buttons ~30px below its neighbour's -- read as "the right one has more padding at the
   bottom". Stretching the card to the grid row and letting the LINK block absorb the
   slack pins the footer to the bottom edge on every card in the row, without adding
   space between the facts and the tiles.

   :has(), so this reaches only the card that has a triage footer. Browse and explore
   render the same card to be shortlisted rather than answered; they have no footer to
   align and keep their intrinsic heights. */
.browse-v5 .brokering-browse-card:has(.brokering-browse-card__triage) {
  display: flex;
  flex-direction: column;
  align-self: stretch;
  height: 100%;
}

.browse-v5 .brokering-browse-card:has(.brokering-browse-card__triage) .brokering-browse-card__link {
  flex: 1 1 auto;
}

.browse-v5 .brokering-browse-card__triage-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  padding: 8px 10px;
  border-radius: 10px;
  font: var(--type-caption) var(--font-sans);
  font-weight: 600;
  border: 1px solid var(--base-border-tertiary);
  background: transparent;
  color: var(--base-text-secondary);
  transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease;
}

/* The tint IS the selected/active language in this product, never a grey. */
.browse-v5 .brokering-browse-card__triage-btn.is-yes {
  border-color: transparent;
  background: var(--color-brand-green-75);
  color: var(--color-brand-green-deep);
}

.browse-v5 .brokering-browse-card__triage-btn.is-yes:hover {
  background: var(--color-brand-green-100, var(--color-brand-green-75));
}

/* 🚨 A BORDER STEP for the neutral hover, not a fill. A fill has to know which surface it
   is sitting on, and --base-bg-hover / --color-warm-100 is banned outright (CLAUDE.md).
   --base-border-tertiary -> --base-border-secondary works on every surface. */
.browse-v5 .brokering-browse-card__triage-btn.is-no:hover {
  border-color: var(--base-border-secondary);
  color: var(--base-text-primary);
}

.browse-v5 .brokering-browse-card__actions {
  position: absolute;
  /* Inset INTO the cover, not flush with its corner. The card's 14px padding puts
     the cover's top edge at 14px, so the row sat exactly on it and the control (or
     the wider "In your leads" chip that replaces it) hugged the edge. */
  top: 24px;
  right: 26px;
  z-index: 2;
  display: flex;
  align-items: center;
  /* 2px left the checkbox touching the save heart. */
  gap: 8px;
}

/* Match the heart's 32px box so the two read as a pair. The tick itself stays 24px
   and centres inside it: as a bare 24px item next to a 32px circle it sat 3px high
   of the heart's centre even under align-items: center, which is the "a bit up"
   misalignment. Scoped to the browse card so the deals table's copy of
   .brokering-match-select keeps its own metrics. */
.browse-v5 .brokering-browse-card__actions .brokering-match-select {
  width: 32px;
  height: 32px;
  align-items: center;
  justify-content: center;
}

/* Pin the row and BOTH controls to the same 32px box. The row was resolving to 37px
   (the save heart sits in a <turbo-frame>, inline by default, and its line box paid
   for the extra), so centring put the frame at 294 while the checkbox label sat at
   292 -- the 2px "the checkbox is a bit up" misalignment. Sizing all three removes
   the arithmetic instead of correcting for it. */
.browse-v5 .brokering-browse-card__actions {
  height: 32px;
}

.browse-v5 .brokering-browse-card__actions > * {
  height: 32px;
  align-self: center;
  /* A margin on either control reintroduces the offset even at equal heights. */
  margin: 0;
}

.browse-v5 .brokering-browse-card__actions turbo-frame {
  display: flex;
  align-items: center;
}

/* The cover sits under the action cluster, so lift the actions onto the photo with
   their own surface rather than reserving space above it.

   OPAQUE, with a shadow — same rule as the slideover hero's controls above. A 92%
   white wash disappeared on any bright cover photo, and the save heart is the one
   control on the card that is not the card's own link, so it has to be findable.
   The shadow is what separates it from the photo; without it an opaque white pill
   on a pale photo reads as a blown-out patch of the image. */
.browse-v5 .brokering-browse-card__actions > * {
  border-radius: 999px;
  background: var(--base-bg-low, #fff);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.20);
}

/* 🚨 ONE control box in this row, whichever partial built it. The two Browse cards
   compose their own actions -- broker renders practices/_save_button (a
   Tailwind-utility button inside a <turbo-frame>), the buyer and public card render
   `.explore-card__save-btn` -- and both arrived carrying geometry from before this
   row existed. Measured on the same listing at 1440px:

                     size      top / right from the card's own corner
       broker save  32 x 32              24 / 26
       buyer save   30 x 32              35 / 37

   The buyer's heart is 11px down and 11px left of broker's, and that is not a
   rounding difference: `.explore-card__save-btn` still declares `position: absolute;
   top: 11px; right: 11px; width: 30px` from when it was pinned to the CARD, and its
   containing block is now this row -- which is itself absolutely positioned and, with
   its only child taken out of flow, ZERO pixels wide. So the button was offset from
   a box with no width, and its placement was an accident that happened to land near
   the right spot. The row owns placement; the button is a plain item inside it. */
.browse-v5 .brokering-browse-card__actions > .explore-card__save-btn {
  position: static;
  width: 32px;
  height: 32px;
  border: 0;
}

/* Broker's drew a second ring INSIDE the pill: the turbo-frame takes the white
   surface and the shadow from the rule above, and the button within it added its own
   1px border over a #FAFAFA fill. The pill is the affordance, and one ring is enough.
   Colour is deliberately left alone -- broker's saved heart is brand green and the
   buyer's is red, which is role context rather than drift. */
.browse-v5 .brokering-browse-card__actions > turbo-frame form,
.browse-v5 .brokering-browse-card__actions > turbo-frame button {
  width: 32px;
  height: 32px;
}

.browse-v5 .brokering-browse-card__actions > turbo-frame button {
  border: 0;
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}

/* One hover for every control in the row. The buyer's heart had none that could
   land: its own `:hover` rule sets `box-shadow: var(--shadow-card)` at (0,2,0) and
   the resting shadow above scores (0,3,0), so the hover state was silently
   outranked and the control did not respond to the pointer at all. Deepening the
   pill's own shadow works for both compositions -- hovering broker's button also
   hovers the frame that carries the pill. */
.browse-v5 .brokering-browse-card__actions > *:hover {
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.24);
}

/* ---------- Filters sheet ---------- */
.brokering-browse-sheet[hidden] { display: none; }

.brokering-browse-sheet {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(34, 34, 34, 0.32);
  padding: 24px;
}

/* 🚨 The panel no longer scrolls: the FIELDS do. Under staging the footer carries the
   commit and its count, and a panel-level scroll takes both off screen on a short
   viewport, which is the one thing that cannot happen on a surface whose whole point is
   the commit. */
.brokering-browse-sheet__panel {
  width: 100%;
  max-width: 520px;
  max-height: 84dvh;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--base-bg-low, #fff);
  border-radius: 24px;
  /* Design's exact panel padding. */
  padding: 28px 30px 24px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14), 0 4px 12px rgba(0, 0, 0, 0.08);
}

/* The form is the flex child that has to give, or the fields region cannot shrink. */
.brokering-browse-sheet__panel > form {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}

.brokering-browse-sheet__title {
  flex: none;
  font: var(--type-ws-panel-title) var(--font-display, var(--font-sans));
  color: var(--base-text);
  margin: 0 0 22px;
}

/* Same gap, now the scroller. Negative inline margins with matching padding so a focus
   ring on a 44px field is not clipped by the new overflow. */
.brokering-browse-sheet__fields {
  display: flex;
  flex-direction: column;
  gap: 22px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin: 0 -4px;
  padding: 2px 4px;
  scrollbar-width: thin;
  scrollbar-color: var(--ws-scroll-thumb, rgba(34, 34, 34, .12)) transparent;
}
.brokering-browse-sheet__fields::-webkit-scrollbar { width: 10px; }
.brokering-browse-sheet__fields::-webkit-scrollbar-track { background: transparent; }
.brokering-browse-sheet__fields::-webkit-scrollbar-thumb {
  border: 3px solid var(--base-bg-low, #fff);
  border-radius: 8px;
  background: var(--ws-scroll-thumb, rgba(34, 34, 34, .12));
}
.brokering-browse-sheet__fields::-webkit-scrollbar-thumb:hover {
  background: var(--ws-scroll-thumb-hover, rgba(34, 34, 34, .24));
}

.brokering-browse-sheet__label {
  display: block;
  font: var(--type-overline) var(--font-sans);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--base-text-tertiary);
  margin-bottom: 9px;
}

/* Left of the spacer is EDITING, right of it is LEAVING. min-height so none of the four
   footer shapes changes the panel's height: the swap is opacity only, never layout. */
.brokering-browse-sheet__foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
  min-height: 36px;
  margin-top: 26px;
}

.brokering-browse-sheet__foot > .brokering-browse-sheet__spacer { flex: 1; }
/* [hidden] beats the flex display the children would otherwise take. */
.brokering-browse-sheet__foot > [hidden] { display: none; }
/* A content change inside an already-open surface: opacity, and NEVER a height. */
.brokering-browse-sheet__foot > * { transition: opacity 150ms ease-out; }

/* System status, not a promise. Sits immediately left of the exits so it reads as one
   statement with the button it qualifies: "428 listings match  [ Apply 3 filters ]".
   No pill, no icon, no colour: the same tertiary caption the headline uses.
   🚨 It never animates its number. The design system bans counting numbers, and here it
   would also read as progress on something that is not progressing. */
.brokering-browse-sheet__count {
  flex: none;
  margin: 0;
  font: var(--type-caption) var(--font-sans);
  color: var(--base-text-tertiary);
  white-space: nowrap;
}

/* The scrim is a dismiss target, not a control. */
.brokering-browse-sheet { cursor: default; }

@media (prefers-reduced-motion: reduce) {
  .brokering-browse-sheet__foot > * { transition-duration: 1ms; }
}

/* ---------- Responsive: stack the split, keep the map usable ---------- */
@media (max-width: 1100px) {
  .browse-v5 .brokering-browse-grid {
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  }
}

@media (max-width: 900px) {
  .browse-v5 .brokering-listings-map-layout {
    grid-template-columns: minmax(0, 1fr);
    height: auto;
    min-height: 0;
  }

  .browse-v5 .brokering-browse-split__map {
    order: -1;
    height: 46dvh;
    min-height: 300px;
  }

  .browse-v5 .brokering-browse-split__list,
  .browse-v5 .brokering-browse-split__scroll {
    overflow: visible;
    height: auto;
  }

  .browse-v5 .brokering-browse-split__scroll { padding-right: 2px; }
}

@media (max-width: 640px) {
  .brokering-browse-toolbar { gap: 8px; }
  .brokering-browse-search { flex-basis: 100%; height: 44px; }
  .browse-v5 .brokering-browse-grid { grid-template-columns: minmax(0, 1fr); }
  .brokering-browse-sheet { padding: 0; align-items: flex-end; }
  .brokering-browse-sheet__panel {
    max-width: none;
    border-radius: 20px 20px 0 0;
    max-height: 88dvh;
    padding: 22px 20px 0;
  }

  /* 🚨 The footer PINS. The fields scroll above it, and the commit plus its count stay
     on screen: on a staging surface they are the point of the surface.
     Three rows, not one squeezed line. A top shadow rather than a rule line, because
     this file's surfaces separate with elevation. */
  .brokering-browse-sheet__foot {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: 8px;
    margin: 0 -20px;
    padding: 12px 20px calc(18px + env(safe-area-inset-bottom, 0px));
    background: var(--base-bg-low, #fff);
    box-shadow: 0 -8px 16px -12px rgba(12, 62, 38, .28);
  }
  .brokering-browse-sheet__foot > .brokering-browse-sheet__spacer { display: none; }
  .brokering-browse-sheet__count {
    grid-column: 1 / -1;
    grid-row: 1;
    text-align: center;
    white-space: normal;
  }
  .brokering-browse-sheet__foot .btn-primary {
    grid-column: 1 / -1;
    grid-row: 2;
    justify-content: center;
    height: 44px;
  }
  /* Clear all left, the exit right, on the row under the commit. */
  .brokering-browse-sheet__foot [data-browse-filters-target="clear"] { grid-column: 1; grid-row: 3; justify-self: start; }
  .brokering-browse-sheet__foot [data-browse-filters-target="exit"] { grid-column: 2; grid-row: 3; justify-self: end; }
  .brokering-browse-sheet__foot .btn-ghost { height: 36px; }

  /* The bottom sheet slides; the desktop modal fades. Composited property only. */
  .brokering-browse-sheet:not([hidden]) .brokering-browse-sheet__panel {
    animation: brokeringSheetIn 300ms ease-out both;
  }
  @keyframes brokeringSheetIn {
    from { transform: translateY(100%); }
    to { transform: none; }
  }
  @media (prefers-reduced-motion: reduce) {
    .brokering-browse-sheet:not([hidden]) .brokering-browse-sheet__panel { animation-duration: 1ms; }
  }
}

/* Practice-type options in the filters sheet: a checkbox styled as a pill.
   :has(:checked) keeps the selected look in sync client-side, so toggling one
   doesn't need a round trip to look right. */
.brokering-browse-typeopt {
  display: inline-flex;
  align-items: center;
  height: 36px;
  padding: 0 14px;
  border-radius: 999px;
  cursor: pointer;
  background: var(--base-bg-subtle, #F7F7F7);
  color: var(--base-text-secondary, var(--base-text));
  font-size: var(--text-sm);
  font-weight: var(--fw-medium, 500);
  transition: background-color 120ms linear, color 120ms linear;
}

.brokering-browse-typeopt:hover { background: var(--base-bg-hover, #F0F0F0); }

.brokering-browse-typeopt:has(:checked),
.brokering-browse-typeopt.is-active {
  background: var(--browse-accent, #0EA184);
  color: #fff;
  font-weight: var(--fw-semibold, 600);
}

/* 🚨 A SELECTED type chip does not change on hover. It is selected, not
   pressable-looking, and darkening it reads as pressed on a control that has not been
   pressed. Stated defensively because the unselected chip's :hover sits right above. */
.brokering-browse-typeopt:has(:checked):hover,
.brokering-browse-typeopt.is-active:hover {
  background: var(--browse-accent, #0EA184);
  color: #fff;
}
.brokering-browse-typeopt:has(:checked):active,
.brokering-browse-typeopt.is-active:active {
  background: var(--browse-accent-deep, #0C7C66);
  transition: background-color 80ms linear;
}
.brokering-browse-typeopt:not(:has(:checked)):active {
  background: var(--status-neutral-strong, #EBEBEB);
  transition: background-color 80ms linear;
}

.brokering-browse-typeopt:has(:focus-visible) {
  outline: 2px solid var(--browse-accent, #0EA184);
  outline-offset: 2px;
}

body.brokering-browse-sheet-open { overflow: hidden; }

/* ---------- Browse listing slideover (v5) ---------- */
.brokering-browse-slideover__hero {
  position: relative;
  height: 200px;
  flex: none;
  overflow: hidden;
  background: var(--base-bg-subtle, #F0F2F3);
}

/* 🚨 ANYTHING DRAWN ON A PHOTO NEEDS ITS OWN CONTRAST — the photo cannot supply it.
   The close button and the type pill were both a 94% white wash on an arbitrary
   listing photo: white-on-white on a bright hero (a clinic exterior in daylight,
   which is most of them), so the only way out of the panel was invisible.

   Two halves to the fix, and both are needed:
     1. the hero carries a scrim under each control band, which is what gives the
        overlay depth rather than the "sticker pasted on a photo" look; and
     2. the controls themselves are OPAQUE with a shadow, so they still read on a
        photo whose top strip happens to be dark.

   `:has()` so the scrims only paint over a REAL photo. Over the grey "Photo not
   provided" placeholder a dark gradient reads as dirt, not depth. */
.brokering-browse-slideover__hero:has(.brokering-browse-cover__img)::before,
.brokering-browse-slideover__hero:has(.brokering-browse-cover__img)::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  height: 88px;
  pointer-events: none;
  z-index: 1;
}

.brokering-browse-slideover__hero:has(.brokering-browse-cover__img)::before {
  top: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.34), rgba(0, 0, 0, 0));
}

.brokering-browse-slideover__hero:has(.brokering-browse-cover__img)::after {
  bottom: 0;
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.30), rgba(0, 0, 0, 0));
}

.brokering-browse-slideover__close {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  background: var(--base-bg-low, #fff);
  color: var(--base-text);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.24);
  transition: background-color 150ms ease-out, transform 150ms ease-out;
}

/* Hover is a fill step, not a border step, because this control sits on a photo
   rather than on a surface: there is no neighbouring border for a border step to
   read against. --base-bg-subtle, never --base-bg-hover (the banned warm grey).

   Scoped through the panel class for SPECIFICITY, not for reach. The button also
   carries `.slideover-close-button`, whose own `:hover` sets exactly that banned
   `--base-bg-hover` and scores the same (0,2,0) — so which one won came down to
   stylesheet order. Three classes settles it. */
.brokering-browse-slideover .brokering-browse-slideover__close:hover {
  background: var(--base-bg-subtle, #F1F3F3);
}

.brokering-browse-slideover__close:active {
  transform: scale(0.94);
}

.brokering-browse-slideover__close:focus-visible {
  outline: 2px solid var(--browse-accent, #0EA184);
  outline-offset: 2px;
}

.brokering-browse-slideover__hero-pill {
  position: absolute;
  bottom: 16px;
  left: 22px;
  z-index: 2;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--base-bg-low, #fff);
  color: var(--base-text);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.22);
  font: var(--type-caption) var(--font-sans);
  font-weight: var(--fw-semibold, 600);
  /* Worst case in Practice.practice_types is "Multi Specialty". It fits on one line
     at every panel width, and this keeps any future longer label from wrapping into
     a two-line block sitting on the photo. */
  white-space: nowrap;
  max-width: calc(100% - 44px);
  overflow: hidden;
  text-overflow: ellipsis;
}

.brokering-browse-slideover__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 22px 24px 0;
}

.brokering-browse-slideover__title {
  font: var(--type-title) var(--font-display, var(--font-sans));
  letter-spacing: -0.02em;
  color: var(--base-text);
  margin: 0 0 4px;
}

.brokering-browse-slideover__where {
  font: var(--type-body-strong) var(--font-sans);
  color: var(--base-text-secondary, var(--base-text));
  margin: 0 0 2px;
}

.brokering-browse-slideover__sub {
  font: var(--type-secondary) var(--font-sans);
  color: var(--base-text-tertiary);
  margin: 0;
}

.brokering-browse-slideover__pricecol {
  text-align: right;
  flex: none;
}

.brokering-browse-slideover__price {
  font: var(--type-stat) var(--font-display, var(--font-sans));
  letter-spacing: -0.02em;
  color: var(--base-text);
}

.brokering-browse-slideover__price-caption {
  font: var(--type-caption) var(--font-sans);
  color: var(--base-text-tertiary);
}

.brokering-browse-slideover__chips {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 14px 24px 0;
}

.brokering-browse-slideover__chips:empty { display: none; }

.brokering-browse-slideover__verified {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 18px 24px 0;
  padding: 12px 16px;
  border-radius: 12px;
  background: var(--color-brand-green-75, #E7F7F1);
  color: var(--color-brand-green-deep, #063E36);
  font: var(--type-secondary) var(--font-sans);
}

.brokering-browse-slideover__metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.brokering-browse-slideover__metric {
  background: var(--base-bg-subtle, #F7F7F7);
  border-radius: 12px;
  padding: 16px 18px;
  min-width: 0;
}

/* practice_listing_metrics returns however many figures a listing actually has, so
   an odd count left a half-empty last row with one tile floating beside a hole. A
   lone trailing tile takes the full width instead, which reads as the end of the
   grid rather than as a missing tile. */
.brokering-browse-slideover__metric:last-child:nth-child(odd) {
  grid-column: 1 / -1;
}

.brokering-browse-slideover__metric-label {
  display: block;
  font: var(--type-overline) var(--font-sans);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--base-text-tertiary);
  margin-bottom: 5px;
}

.brokering-browse-slideover__metric-value {
  display: block;
  font: var(--type-card-title) var(--font-display, var(--font-sans));
  color: var(--base-text);
}

/* Location and seller notes. Was a <details> disclosure with a summary row and a
   chevron; Colin asked for it open with the panel scrolling, so the disclosure went
   and this is the plain block. It keeps the hairline that separated it from the metric
   grid above -- that rule was doing the visual separating, not the summary. */
.brokering-browse-slideover__notes {
  display: flex;
  flex-direction: column;
  gap: 18px;
  border-top: 1px solid var(--base-border-tertiary);
  padding-top: 16px;
}

/* width + align-self, not inherited stretch: the shared .slideover-footer resolves
   align-items: center, so this row shrank to its content (304px) and read as
   centered no matter what flex the primary carried. Both properties are set
   explicitly so the row does not depend on the footer's align-items at all. */
.brokering-browse-slideover__actions {
  display: flex;
  align-items: stretch;
  align-self: stretch;
  width: 100%;
  gap: 10px;
}

/* The primary CTA takes the free width; Save stays at content width beside it.
   Together the two fill the footer, so both rows share one rail from edge to edge.

   This REVERSES an earlier note here that said "buttons are CONTENT width,
   left-aligned, with trailing space — do not re-add flex on these", read off the
   prototype's render. Measured at a 576px panel that gave a 186px primary + an 88px
   Save inside a 524px row: 240px of dead space on the right, under which sat a
   second row of three ghost buttons on a different rail. The two rows read as
   leftovers rather than as a footer. Content width is right when a footer holds one
   button; it is not right for a two-row action block. */
.brokering-browse-slideover__actions { justify-content: flex-start; }

.brokering-browse-slideover__actions > *:not(.brokering-browse-slideover__save) {
  flex: 1 1 auto;
  justify-content: center;
  min-width: 0;
}

.brokering-browse-slideover__save { flex: 0 0 auto; }

/* Secondary rail: three equal ghost buttons filling the same width as the primary
   row, wrapping centred rather than clumping at the left.
   No `margin-left: -12px` here any more. That hack existed to pull the ghost
   buttons' GLYPHS onto the content-width primary's left edge; with the primary now
   stretched, the rails already agree and the negative margin only pushed the row
   12px past the footer's own padding. */
.brokering-browse-slideover .brokering-listing-slideover-footer-secondary {
  align-self: stretch;
  width: 100%;
  justify-content: center;
  gap: 6px;
}

.brokering-browse-slideover .brokering-listing-slideover-footer-secondary > * {
  flex: 1 1 auto;
  justify-content: center;
  min-width: 0;
  /* Between the 32px btn-sm they inherit and the 48px primary above: comfortably
     hittable, still visibly the lesser row. */
  min-height: 40px;
}

/* The two pills in the action row are the SAME height and centred on each other.
   Save came from the shared save_button partial at `btn-sm` (32px) next to a
   48px `btn-lg` primary, top-aligned by `align-items: stretch`, so it floated
   above the primary's baseline. Scoped to this slideover: the same partial is a
   32px pill on the cards, which is correct there. */
.brokering-browse-slideover__actions { align-items: center; }

/* `button`, not `.btn`: without variant: :footer the shared save_button renders its
   inline-Tailwind pill (px-3 py-1.5 rounded-full), so a `.btn` selector matches
   nothing here. Height is set rather than padding because the primary's 48px comes
   from btn-lg. */
.brokering-browse-slideover__save button {
  height: 48px;
  border-radius: 999px;
  padding-inline: 18px;
  font-size: var(--text-sm);
}

@media (max-width: 640px) {
  .brokering-browse-slideover__hero { height: 150px; }
  .brokering-browse-slideover__head { flex-direction: column; gap: 8px; }
  .brokering-browse-slideover__pricecol { text-align: left; }
  .brokering-browse-slideover__metrics { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Listing cover: photo, or the explicit missing state ---------- */
.brokering-browse-cover__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.brokering-browse-cover__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 100%;
  height: 100%;
  background: var(--base-bg-subtle, #F4F6F6);
  color: var(--base-text-tertiary);
}

.brokering-browse-cover__empty span {
  font: var(--type-caption) var(--font-sans);
  font-weight: var(--fw-medium, 500);
}

/* 🚨 THE COMPACT STATE HAS NO LABEL TO SHRINK -- the partial does not render one. Two
   callers used to try `font-size: 0` on the wrapper to hide the words at thumbnail size,
   and the rule directly above defeats both of them: a `font` shorthand on the span sets an
   absolute size, so it never inherits the 0. The words are gone from the markup now and
   the state is on the element's `title`/`aria-label`. Do not reintroduce the size hack. */
.brokering-browse-cover__empty.is-compact {
  gap: 0;
}

.brokering-browse-cover__empty.is-compact svg {
  width: 18px;
  height: 18px;
  opacity: 0.75;
}

/* ---------- Card: title row, location, meta, chips, match reason (v5) ---------- */
.browse-v5 .brokering-browse-card__titlerow {
  display: flex;
  align-items: baseline;
  gap: 7px;
  flex-wrap: wrap;
  margin-bottom: 3px;
}

.browse-v5 .brokering-browse-card__titlerow .brokering-browse-card__title {
  margin: 0;
}

.browse-v5 .brokering-browse-card__verified {
  display: inline-flex;
  align-items: center;
  flex: none;
  color: var(--browse-accent, #0EA184);
}

/* Location line carries an "approx." qualifier: the address is deliberately
   fuzzed on Browse, so the card says so rather than implying precision. */
.browse-v5 .brokering-browse-card__where {
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold, 600);
  color: var(--base-text-secondary, var(--base-text));
  margin: 0 0 3px;
}

.browse-v5 .brokering-browse-card__where span {
  font-weight: var(--fw-medium, 500);
  color: var(--base-text-tertiary);
}

/* --ws-coral-* is the design's coral-50/coral-600 pair (#FFF1E8 / #D5622F), and
   light.css reserves it for "new/urgent ONLY -- the New pill". --status-warning-*
   is amber (#fef3c7 / #92400e) and rendered this chip in the wrong hue entirely.
   The coral tokens also carry dark twins, so all three modes follow. */
.browse-v5 .brokering-browse-chip.is-new {
  background: var(--ws-coral-surface, #FFF1E8);
  color: var(--ws-coral-text, #D5622F);
}

.browse-v5 .brokering-browse-chip.is-pending {
  background: var(--base-bg-subtle, #F4F4F4);
  color: var(--base-text-tertiary);
}

.browse-v5 .brokering-browse-card__reason {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--text-xs);
  font-weight: var(--fw-medium, 500);
  color: var(--color-brand-green-deep, #0A6E5C);
  margin: 14px 0 0;
}

/* ---------- Applied chip counts ---------- */
.brokering-browse-chips .brokering-filter-pill__count {
  font-weight: var(--fw-semibold, 600);
  color: var(--base-text-tertiary);
}

/* A criterion matching nothing stays on screen, dimmed and dashed, so it is
   visible which one killed the result set (design: zero-count pill). */
.brokering-browse-chips .brokering-filter-pill.is-zero {
  background: var(--base-bg-low, #fff);
  border: 1px dashed var(--base-border-secondary, rgba(0, 0, 0, 0.16));
  color: var(--base-text-tertiary);
}

/* ---------- Results headline ---------- */
.brokering-browse-headline {
  font-size: var(--text-sm);
  color: var(--base-text-tertiary);
  margin: 0;
}

.brokering-browse-headline strong {
  font-family: var(--font-display, var(--font-sans));
  font-weight: var(--fw-bold, 700);
  font-size: var(--text-lg, 0.9375rem);
  letter-spacing: -0.01em;
  color: var(--base-text);
}

/* Slideover location line: the address is fuzzed on Browse, so it says so. */
.brokering-browse-slideover__where span {
  font-weight: var(--fw-medium, 500);
  color: var(--base-text-tertiary);
}

/* ---------- Filters sheet: paired fields and the two money ranges (v5) ---------- */
.brokering-browse-sheet__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.brokering-browse-sheet__rangehead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 9px;
}

/* The label already carries its own bottom margin for the stacked case; inside a
   range head the row owns the spacing. */
.brokering-browse-sheet__rangehead .brokering-browse-sheet__label { margin-bottom: 0; }

.brokering-browse-sheet__rangeval {
  font-family: var(--font-display, var(--font-sans));
  font-weight: var(--fw-bold, 700);
  font-size: var(--text-lg, 0.9375rem);
  color: var(--base-text);
}

.brokering-browse-range {
  width: 100%;
  accent-color: var(--browse-accent, #0EA184);
}

.brokering-browse-range:focus-visible {
  outline: 2px solid var(--color-brand-green, #0EA184);
  outline-offset: 3px;
}

@media (max-width: 640px) {
  .brokering-browse-sheet__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Card chips: the design's neutral pill ---------- */
/* The design's chips are borderless #F4F4F4 on #484848 at 4px 10px. The legacy
   .brokering-practice-card-chip they inherit from carries a hairline border, and
   .is-anon carries a green tint that made "Anonymous" read as a status badge
   rather than one of the neutral chips. Coral "New this week" is the only chip in
   the design that takes a colour. */
.browse-v5 .brokering-browse-chip {
  background: var(--browse-surface, #F7F7F7);
  color: var(--base-text-secondary, #484848);
}

.browse-v5 .brokering-browse-chip.is-anon,
.dark .browse-v5 .brokering-browse-chip.is-anon {
  background: var(--base-bg-subtle, #F7F7F7);
  color: var(--base-text-secondary);
  border: 0;
}

/* Every field in the sheet is 44px with a 12px radius, matching the design's
   hint-size="100%,44px". They come from three different components -- a
   form-control input, the minimal-select trigger and the location-search box --
   which shipped 44 / 49 / 40px, and that mismatch is what read as bad padding. */
.brokering-browse-sheet .form-control,
.brokering-browse-sheet .pp-minimal-select__btn {
  height: 44px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 12px;
}

/* The market box holds a chip per selected market plus the input. Left to wrap it
   became a 78px two-row block next to a 44px select, which is what read as broken.
   The design has a single market, so this lays out on ONE 44px row: chip inline with
   the input, and horizontal scroll in the rare case a broker arrives from an alert
   covering several zones (Brokering::DealsHelper#alert_filtered_browse_path
   pre-selects one market per zone, and the chip row above the results is where
   several are managed).

   It stays the partial's `multiple: true` mode despite showing one: single mode
   NAVIGATES on pick with a scalar ?city=, which would drop every other pending
   filter in this sheet, and the partial's own note warns that a scalar `city=`
   alongside the array `city[]=` the query reads is a Rack parse error. */
.brokering-browse-sheet .location-search-box {
  height: 44px;
  min-height: 44px;
  padding: 0 10px;
  border-radius: 12px;
}

/* !important on padding only, and deliberately: the inner row carries Tailwind's
   `px-3 py-1` utilities, and utilities sit in a LATER layer than components, so no
   amount of selector specificity here can beat them. (The reverse of the
   unlayered-beats-layered problem documented elsewhere in this file.) Editing the
   shared partial's markup instead would change four other views that use it. */
.brokering-browse-sheet .location-search-box > div {
  /* BOTH !importants are required and neither is laziness: this row carries
     Tailwind's `flex-wrap` and `px-3 py-1`, and utilities sit in a LATER layer than
     components, so selector specificity here cannot reach them. Left wrapping, the
     row grew past the 44px box, whose `overflow-hidden` utility then clipped the
     chip -- which is the "the chip slides up" and "a stray cross appears" bug: the
     cross was the clipped second row peeking out, not a second market. */
  flex-wrap: nowrap;
  padding: 0;
  overflow-x: auto;
  gap: 6px;
  scrollbar-width: none;
}

.brokering-browse-sheet .location-search-box > div::-webkit-scrollbar { display: none; }

.brokering-browse-sheet .location-search-chip { flex: none; }

.brokering-browse-sheet [data-controller~="places-autocomplete"] input[type="text"] {
  height: auto;
  min-height: 0;
  padding: 0;
  min-width: 5rem;
}

/* Applied filter chips follow the design: neutral #F4F4F4 on #222222, borderless,
   32px, with the count in a lighter grey. The legacy .brokering-filter-pill is
   hardcoded mint green (#e8f6ee / #205E3A / #b6dfc4 border), which made the chip
   row read as a set of status badges instead of removable filters. Scoped to this
   row so the green pill stays wherever else it is still used.
   The border stays present-but-transparent so the zero-count variant can turn it
   dashed without shifting the row by a pixel. */
.brokering-browse-chips .brokering-filter-pill {
  height: 32px;
  padding: 0 12px;
  background: var(--base-bg-subtle, #F7F7F7);
  color: var(--base-text);
  border: 1px solid transparent;
}

.brokering-browse-chips .brokering-filter-pill:hover {
  background: var(--base-bg-hover, var(--base-bg-subtle, #F7F7F7));
}


/* Make the bulk-select box a peer of the save heart: same 32px circle, same
   hairline, no drop shadow. It was a 24px 7px-radius square with a shadow (the
   shared control's styling, tuned for sitting on a photo), which beside a 32px
   bordered circle read as two unrelated controls rather than a pair.
   Scoped to the browse card so the deals table keeps the original box. */
/* 🚨 The checkbox and the heart must read as a PAIR (there is a test on it), and the
   pair moved. This used to copy `practices/_save_button`'s utilities
   (border-base-300 / bg-base-50) because that is what the heart wore. The heart no
   longer wears them: unifying the save control put the white pill and its shadow on
   the ROW's direct child and made the control inside it transparent and borderless,
   so one ring shows instead of a ring inside a pill. Copying the old values here left
   the checkbox with a border and fill the heart had lost -- my regression, caught by
   `the bulk-select checkbox is aligned with the save heart` in light mode.
   Both are now transparent and borderless, stated identically so the comparison is
   on values rather than on two rules agreeing by luck. The LABEL is the row's direct
   child, so it carries the pill; this span is the glyph's box, exactly as the heart's
   button sits inside its turbo-frame. */
.browse-v5 .brokering-browse-card__actions .brokering-match-select span[aria-hidden] {
  width: 32px;
  height: 32px;
  border-radius: 999px;
  border: 0;
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}

/* Re-assert the checked fill: the selector above is MORE specific than the shared
   `input:checked + span` rule, so without this the box would stay white when ticked. */
.browse-v5 .brokering-browse-card__actions .brokering-match-select input:checked + span[aria-hidden],
.dark .browse-v5 .brokering-browse-card__actions .brokering-match-select input:checked + span[aria-hidden] {
  background: var(--color-brand-green);
  border-color: var(--color-brand-green);
}

/* Centre the tick with a transform rather than hand-tuned offsets, so it stays put
   whatever the box size. The extra -58% lifts it onto the optical centre: a rotated
   checkmark hangs low of a geometric one. */
.browse-v5 .brokering-browse-card__actions .brokering-match-select span[aria-hidden]::after {
  left: 50%;
  top: 50%;
  transform: translate(-50%, -58%) rotate(45deg);
}

/* ---------- Buyer/public card: shared cover inside the existing banner ---------- */
/* The banner keeps its badges and save button positioned over the top, so the cover
   is a filling layer behind them rather than a replacement for the banner. */
.explore-card__banner-cover {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

.explore-card__banner > *:not(.explore-card__banner-cover) {
  position: relative;
  z-index: 1;
}

/* ==========================================================================
   /explore + /buying/browse map view on the v5 split
   --------------------------------------------------------------------------
   Opt-in via .browse-v5 on the page wrapper, so nothing here fires until the
   surface asks for it.

   Desktop only, on purpose. Below 900px explore keeps its own layout, because the
   sidebar there is a DRAGGABLE BOTTOM SHEET over the map (browse-sheet controller,
   peek/half/full detents, drag handle). Broker's split just stacks. Forcing broker's
   behaviour onto a public touch surface would delete a real feature to satisfy a
   dashboard design, so the two agree where you compare them and diverge where the
   platform genuinely differs.
   ========================================================================== */
@media (min-width: 900px) {
  /* Browse spans the viewport, like broker's shell. explore's page body is a 1240px
     `.wrap`, and the same `minmax(228px)` grid inside it gave 265px cards against
     ~340px on broker -- narrow enough that "$1,250,000 est. value" wrapped.
     🚨 This used to be three children ESCAPING the wrap with
     `width: calc(100% + (100vw - 100%))` plus half-negative margins. That calc IS
     100vw, and 100vw INCLUDES the classic scrollbar, so on every platform whose
     scrollbars are not overlays (CI's Linux Chrome, Windows) each of them stuck
     ~8px past the content box and the page scrolled sideways -- invisible on macOS,
     a red CI job. A comment right here claimed the opposite. Widening the CONTAINER
     needs no viewport units, so there is nothing for a scrollbar to skew. */
  .browse-v5 .wrap.explore-main {
    max-width: none;
    padding-inline: 0;
  }

  /* The marketing CTA at the foot of the page is not part of Browse and keeps the
     1240px column it was written for. */
  .browse-v5 .explore-main > .explore-match-cta {
    max-width: var(--wrap-max, 1240px);
    margin-inline: auto;
    /* The wrap's own gutter is gone, so carry the same rail as the blocks above
       rather than touching the edges below 1240px. */
    padding-inline: var(--browse-gutter);
    box-sizing: border-box;
  }

  /* 🚨 ONE rail for the whole Browse area, and it is broker's. Measured before this:
     broker sat every block at 64px (its shell's inset, with the map inset 64 on the
     right too); explore ran the lot at 24px, and buying was worse than either --
     the toolbar and cards at 56 (shell 32 + the filters card's own 24) while the
     banners and the results row, being direct children of the shell, sat at 32.
     Three blocks, three rails, on one page.

     So the gutter is a variable, the shell's own inset is zeroed under it, and every
     block reads the variable: TRANSPARENT blocks take it as padding, TINTED blocks
     (the banners, whose box edge is what the eye follows) take it as margin. */
  /* Broker's shell inset, measured: 40px from 900 to 1279, 64px at 1280 and up. The
     gutter follows it exactly rather than picking a number, so the three surfaces
     stay level at every width instead of only at desktop. */
  .browse-v5 { --browse-gutter: 40px; }
  .buying-page-body.browse-v5 { padding-inline: 0; }

  /* 🚨 Neutralise the portrait-tablet ORDER values. browse_map.css stacks the layout
     map-over-list from 768 to 1023 with `order: 1` on the panel and `order: 2` on the
     sidebar; this rule overrides its `display` (grid, two columns) at 900 but not its
     order, so between 900 and 1023 the buyer surfaces put the MAP in the left column
     and the list in the right -- the mirror image of broker, which keeps the list
     left at every width it splits. Measured at 940: broker list@40 map@470, buyer
     map@46 list@470. */
  .browse-v5 .explore-map-layout > .explore-map-sidebar { order: 0; }
  .browse-v5 .explore-map-layout > .explore-map-panel { order: 1; }

  /* Equal halves at EVERY split width, which is what broker does. browse_map.css
     narrows the list to `flex: 0 0 40%; max-width: 440px` between 1024 and 1279 --
     a flex rule that still sizes the grid track here -- so the buyer list ran 440px
     against broker's 560px at the same window. */
  .browse-v5 .explore-map-layout > .explore-map-sidebar {
    flex: 1 1 auto;
    max-width: none;
  }

  /* 🚨 BUYER SHELLS ONLY, and this one is subtle: broker renders the SAME
     `explore/_map_panel` partial, so `.explore-map-panel` exists there too. On broker
     the panel sits inside its own `.brokering-browse-split__map` wrapper, where the
     6px reads as an inset from the column edge and is wanted. On the buyer surfaces
     the panel IS the grid child, so the same 6px became a second gap on top of the
     20px the list already provides and left the columns unequal -- 636px of list
     against 630px of map. Zeroing it globally moved broker's map chrome 6px, which
     is exactly the kind of drive-by change broker must never get. */
  :is(body.explore-page, .buying-shell) .browse-v5 .explore-map-layout > .explore-map-panel {
    margin-left: 0;
  }

  .browse-v5 .explore-map-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0;
    /* Public /explore keeps the constant it has always had. Measured height is for
       the DASHBOARD shells only (rule below) -- see the note there. */
    height: calc(100dvh - 220px);
    /* 420px, matching broker's floor, not the 480px this carried for no stated
       reason. The floor is the ONE case that still lets the document scroll: below
       `floor + chrome` of viewport height something has to give, and a map squeezed
       under 420px is not worth having. Measured chrome is 247px on /buying/browse, so
       480 needed a 727px viewport and 420 needs 667px -- the difference between a
       720p laptop scrolling a little and not. Broker and buyer now yield at the same
       point instead of two different ones. */
    min-height: 420px;
    padding-inline: var(--browse-gutter);
    box-sizing: border-box;
  }

  /* 🚨 BUYER DASHBOARD ONLY, and the scoping is the whole point.
     Everything above the split on /buying/browse is PERSISTENT chrome: the sticky
     topnav, the preferences banner, the search row, the results line. The document
     must not scroll there (that is what carried the map out of view), so the split
     gets exactly the space left under that chrome, measured -- the same rule broker's
     split gets, so the two dashboards cannot drift.

     Public /explore is a different page and deliberately excluded: a tall marketing
     hero sits above its split (measured 469px at 1440x900) and is meant to scroll
     away, followed by a CTA and the site footer. Feeding that hero into the same
     subtraction squeezed the map to the 480px floor -- a shorter map than before, on
     a page nobody asked us to change. */
  .buying-shell .browse-v5 .explore-map-layout {
    height: calc(100dvh - var(--browse-split-chrome, 220px));
  }

  /* 🚨 No margin under the SPLIT on the buyer shell: it would make the document
     taller than the viewport and start it scrolling, which is the bug the measured
     shell exists to prevent. The design's breathing room under the map is the MAP
     COLUMN's own bottom margin instead (`.explore-map-panel` below), so the listings
     column still runs to the fold. On public /explore the layout keeps the 24px it
     has always had in browse_map.css: a CTA and the site footer follow the split
     there, so that page legitimately scrolls past it. */
  .buying-shell .browse-v5.is-map-view .explore-map-layout {
    margin-bottom: 0;
  }

  /* Tinted blocks: the pref bar, the "browsing all N" nudge, the first-run panel,
     the partial-match note and the saved-tab header. Margin, not padding -- their
     background IS the box, so padding would leave the tint hanging off the rail. */
  /* 🚨 BUYER SHELLS ONLY. `.explore-pref-bar` is shared -- broker's Matches tab uses
     the very same partial -- and broker's shell already insets the page, so a gutter
     margin here indented broker's banner a second time. Nothing in this file may
     put a margin or padding on a shared class under a bare `.browse-v5`: broker
     carries that class too. */
  :is(body.explore-page, .buying-shell) .browse-v5 .explore-pref-bar,
  :is(body.explore-page, .buying-shell) .browse-v5 .explore-nudge,
  :is(body.explore-page, .buying-shell) .browse-v5 .explore-first-run,
  :is(body.explore-page, .buying-shell) .browse-v5 .browse-nudge,
  :is(body.explore-page, .buying-shell) .browse-v5 .explore-saved-header,
  :is(body.explore-page, .buying-shell) .browse-v5 .explore-cross-link,
  :is(body.explore-page, .buying-shell) .browse-v5 .explore-new-pill-row {
    margin-inline: var(--browse-gutter);
  }

  /* The sidebar stops being a 320px rail and becomes the left half.
     🚨 NO right padding. The ROWS carry 20px on the right (broker's number, room for
     the scrollbar); a second 20px here left the column 596px of usable width against
     broker's 616, so its cards came out 288px wide instead of 298 and every ratio
     inside them -- the cover especially -- shrank to match. */
  .browse-v5 .explore-map-sidebar {
    flex: initial;
    min-width: 0;
    border: 0;
    background: transparent;
    box-shadow: none;
    padding-right: 0;
    /* Shadow gutter -- see the note beside `.brokering-browse-split__scroll`. Same
       clip chain as broker's: this box is `overflow: hidden` and the scroller inside
       it is `auto`, so both have to move outward or the inner one still cuts. */
    padding-left: var(--browse-shadow-gutter);
    margin-left: calc(-1 * var(--browse-shadow-gutter));
  }

  .browse-v5 .explore-map-sidebar__scroll {
    padding-left: var(--browse-shadow-gutter);
    margin-left: calc(-1 * var(--browse-shadow-gutter));
  }

  /* Rows become the v5 card grid. auto-fit + 228px is the design's own metric, so a
     narrow left half still gets one column and a wide one gets two. */
  /* grid-auto-rows: max-content and align-self: start are BOTH required, and neither
     is belt-and-braces. This container has a fixed height (the sidebar's), so `auto`
     rows split the free space between them: two rows resolved to 276px each. Grid
     items then stretch to that, and .explore-card is a flex COLUMN with
     overflow: hidden, so the card clipped its own content AND the cover -- a
     shrinkable flex child (flex: 0 1 auto) with only an aspect-ratio -- collapsed
     from ~161px to 7px. On screen that read as "cards are cut off and have no
     photo". Size the rows to content, stop the items stretching, and stop the cover
     shrinking. */
  .browse-v5 .explore-map-sidebar__rows {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(228px, 1fr));
    grid-auto-rows: max-content;
    gap: 28px 20px;
    align-content: start;
  }

  /* 🚨 STRETCH, not start. `grid-auto-rows: max-content` is what stops the rows
     splitting the scrollport's height and clipping the cards (see above, and do not
     remove it) -- but `align-self: start` on top of it also stopped the cards filling
     the row they are in, so a card carrying one extra chip line left its neighbour
     short and the row read ragged. Broker's grid stretches: every card in a row is as
     tall as the tallest. `max-content` already sized the row to content, so stretching
     into it cannot clip anything. */
  .browse-v5 .explore-map-sidebar__rows > * {
    align-self: stretch;
  }

  .browse-v5 .brokering-browse-card__cover {
    flex: none;
  }

  /* The map is a rounded panel, as on broker Browse. The 6px is the column
     separation between the listing column and the map.
     🚨 Scoped to `.explore-map-layout`, which is the buyer/explore grid where the
     panel is a GRID ITEM and a left margin genuinely makes a gap. On broker the same
     panel is `position: absolute; inset: 0` inside `.brokering-browse-split__map`
     (browse_map.css), so the margin could not make a gap -- it shifted the panel off
     its own frame and left a 6px strip of the frame's `--base-bg-subtle` grey down
     the map's inner edge, with the panel's `.card` box-shadow painting a hairline
     into it. That strip is the "vertical border along the side of the map". Measured
     on /brokering/practices at 1440 AND 1100: frame left 720 / 550, panel left
     726 / 556, and 0px on all three other sides. Broker's column separation is the
     grid's own `gap`, which is where it belongs. */
  .browse-v5 .explore-map-layout .explore-map-panel {
    border-radius: 20px;
    /* 6px left is the column separation; the bottom is the breathing room under the
       map, matching broker's `.brokering-browse-split__map`. Both on the panel
       because it is the grid ITEM here, and a grid item's margin shortens it without
       touching the shell or the listings column. */
    margin: 0 0 var(--browse-split-gap) 6px;
  }
}

/* Desktop step, matching the shell. Declared outside the 900px block so it applies
   on top of it rather than duplicating every rule that reads the variable. */
@media (min-width: 1280px) {
  .browse-v5 { --browse-gutter: 64px; }
}

/* ==========================================================================
   /explore toolbar: promote the existing mobile bar to the v5 toolbar
   --------------------------------------------------------------------------
   Explore already ships .explore-mbar -- a search input plus a "Filters" button
   with a count badge -- and hides it above 900px in favour of a six-field inline
   card. Structurally that bar IS the v5 toolbar, and its proxy machinery
   (explore-filter-sheet copies the bar's values into the real, CSS-hidden fields
   and submits) already works. So this promotes the bar to every width and hides
   the inline row, rather than rewriting a public page's filter form.

   The sheet it opens is explore's own bottom sheet, which is why the filters
   themselves keep working untouched, including Min/Max price.
   ========================================================================== */
@media (min-width: 900px) {
  /* The card stops being a card: the toolbar is the only chrome. It also full-bleeds
     to the same edges as the split below it, or the toolbar sits inside .wrap while
     the list starts 76px further left. */
  .browse-v5 .explore-filters-card {
    border: 0;
    background: transparent;
    box-shadow: none;
    /* !important because marketing/pages/explore.css sets `padding: 16px !important`
       on this card, which otherwise wins and leaves the search field 8px left of the
       cards and the results row. Measured. */
    padding-inline: var(--browse-gutter) !important;
    padding-block: 0 !important;
    box-sizing: border-box;
  }

  /* And the GRID, which never got this: the toolbar and the split full-bleed while
     the grid stayed inside .wrap, so in grid view the cards started 116px right of
     the search field on /explore and 16px right on /buying/browse. Measured. */
  .browse-v5 #explore-grid,
  .browse-v5 .explore-grid {
    padding-inline: var(--browse-gutter);
    box-sizing: border-box;
  }

  /* Same rail for the results row, so count and sort line up with the cards. */
  .browse-v5 .explore-result-bar {
    padding-inline: var(--browse-gutter);
    box-sizing: border-box;
  }

  /* The six inline fields are the thing being replaced. They stay in the DOM as
     the form's source of truth (the sheet's proxies write into them). */
  .browse-v5 .explore-filters-card__row {
    display: none;
  }

  .browse-v5 .explore-mbar {
    display: flex;
    align-items: center;
    gap: 12px;
  }

  /* Search pill, matching .brokering-browse-search. */
  .browse-v5 .explore-mbar__search {
    flex: 1 1 320px;
    min-width: 0;
    height: 48px;
    padding: 0 16px;
    border-radius: 12px;
    border: 1px solid var(--base-border-tertiary, #e5e5e5);
    background: var(--base-bg-low, #fff);
    font: var(--type-body) var(--font-sans);
    color: var(--base-text, #222);
  }

  /* Superseded by `.explore-mbar__field:focus-within` below, which is where the
     chrome now lives. Kept as a no-op guard so a bare input (any surface that has
     not adopted the wrapper) still shows nothing green of its own. */
  .browse-v5 .explore-mbar__search:focus {
    outline: none;
    border-color: transparent;
    box-shadow: none;
  }

  /* Filter button, matching .brokering-browse-filterbtn. */
  .browse-v5 .explore-mbar__btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: none;
    height: 36px;
    padding: 0 15px;
    border: 0;
    border-radius: 999px;
    cursor: pointer;
    background: var(--base-bg-subtle, #F7F7F7);
    color: var(--base-text-secondary, #484848);
  }

  .browse-v5 .explore-mbar__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 17px;
    height: 17px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--color-brand-green, #0EA184);
    color: #fff;
    font-size: var(--text-2xs, 0.625rem);
    font-weight: 700;
    line-height: 1;
  }
}

@media (min-width: 900px) {
  /* Explore's filters sheet is a phone bottom sheet (slides up, drag handle). Above
     900px it becomes the v5 centred modal, matching the broker sheet, while keeping
     explore's own controller and proxy machinery underneath. */
  .browse-v5 .xfs-root,
  .xfs-root.browse-v5-sheet {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
  }

  .browse-v5 .xfs-sheet,
  .xfs-root.browse-v5-sheet .xfs-sheet {
    /* relative + z-index, NOT static: the scrim is absolutely positioned and would
       otherwise paint over the sheet, making every control unclickable while looking
       perfectly fine in a screenshot. */
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 520px;
    max-height: 84dvh;
    overflow-y: auto;
    border-radius: 24px;
    padding: 28px 30px 24px;
    box-shadow: 0 12px 32px rgb(0 0 0 / 0.14), 0 4px 12px rgb(0 0 0 / 0.08);
    transform: none;
  }

  /* The drag handle is a touch affordance with nothing to drag on a desktop modal. */
  .browse-v5 .xfs-handle,
  .xfs-root.browse-v5-sheet .xfs-handle { display: none; }

  .browse-v5 .xfs-title,
  .xfs-root.browse-v5-sheet .xfs-title {
    font: var(--type-ws-panel-title) var(--font-display, var(--font-sans));
    color: var(--base-text, #222);
    margin: 0 0 22px;
  }
}

@media (min-width: 900px) {
  /* Results row: count left, sort right, matching broker's headline row. */
  .browse-v5 .explore-result-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
  }

  /* Sort pinned to the row's right edge, level with the count, which is where
     broker's results row puts its own sort menu. */
  .browse-v5 .explore-result-bar__sort { margin-left: auto; }


  /* Broker wraps its sort in a fixed w-56 so the label never truncates as it
     changes; this is the same idea without the utility class. */

  /* The view toggle now sits in the toolbar rather than the results row. */
  .browse-v5 .explore-mbar .explore-view-toggle { flex: none; }
}

/* ==========================================================================
   Segmented switch + minimal-select, copied for the surfaces that need them
   --------------------------------------------------------------------------
   Broker's view switch (.segmented) and sort control (.pp-minimal-select) live in
   the Tailwind bundle, which the explore layout does not load -- so using those
   class names there rendered nothing. The rules are duplicated here rather than
   left unreachable, with a literal fallback on every var() because explore's
   palette defines fewer tokens and a bare var() drops the whole declaration.

   The `minimal-select` Stimulus controller itself is fine: the explore layout does
   load javascript_importmap_tags.

   🚨 If you change either control in the Tailwind bundle, change it here too.
   ========================================================================== */

.segmented {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--status-neutral-bg, #F4F4F4);
  border: 1px solid var(--base-border-tertiary, #e5e5e5);
  border-radius: 9px;
}

.segmented__btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: 7px;
  font-size: var(--text-sm, 13px);
  font-weight: var(--fw-medium, 500);
  background: transparent;
  color: var(--base-text-tertiary, #767676);
  border: none;
  cursor: pointer;
  text-decoration: none;
  transition: background 120ms ease, color 120ms ease;
}

.segmented__btn:hover:not(:disabled):not(.is-active):not(:has(:checked)) {
  color: var(--base-text, #222);
}

/* 🚨 TWO FLAVOURS OF THE SAME CONTROL, DEFINED TOGETHER ON PURPOSE. `.is-active` is
   the button/link flavour (a view toggle, where JS or the server marks the winner);
   `:has(:checked)` is the RADIO flavour, where the track wraps sr-only radios and the
   form itself holds the state (the preference editor's National/Regional scope).

   Both have to live HERE, in this unlayered stylesheet, and not in
   components/brokering.css. That file is imported into `@layer components`, and an
   unlayered rule beats a layered one at ANY specificity — so a `:has(:checked)` rule
   written there loses to the `background: transparent` above and the selected segment
   renders with no fill at all. Measured exactly that: the box-shadow applied (nothing
   unlayered sets it) while the background and colour did not, which is the tell.

   Keep the two selectors' declarations identical. */
.segmented__btn.is-active,
.segmented__btn:has(:checked) {
  background: var(--base-bg-low, #fff);
  color: var(--base-text, #222);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}

.segmented__btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.segmented__btn .tab-count {
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold, 600);
  padding: 1px 7px;
  border-radius: 9999px;
  background: var(--color-gray-100);
  color: var(--base-text-tertiary, #767676);
}

.segmented__btn.is-active .tab-count,
.segmented__btn:has(:checked) .tab-count {
  background: var(--color-gray-200, #e5e5e5);
  color: var(--base-text, #222);
}

.segmented__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  margin-left: 6px;
  border-radius: 9999px;
  background: var(--color-brand-green-75);
  color: var(--text-primary);
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold, 600);
  line-height: 1;
}

.pp-minimal-select { position: relative; }

.pp-minimal-select__btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 13px 14px;
  border: 1px solid var(--color-gray-200, #e5e5e5);
  border-radius: 10px;
  background: var(--base-bg-low, #fff);
  font-size: var(--text-md, 14.5px);   /* was 14.5px → Callout (input) */
  font-family: inherit;
  color: var(--base-text-tertiary, #737373);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.pp-minimal-select--compact .pp-minimal-select__btn {
  height: 34px;
  padding: 0 12px;
  font-size: var(--text-base, 14px);
  border-radius: 8px;
}

.pp-minimal-select__btn.is-filled { color: var(--base-text, #171717); }

.pp-minimal-select__btn:disabled { opacity: 0.55; cursor: not-allowed; }

.pp-minimal-select.is-open .pp-minimal-select__btn {
  outline: none;
  border-color: var(--color-brand-green, #205E3A);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-brand-green, #205E3A), white 86%);
}

.pp-minimal-select__label {
  flex: 1;
  min-width: 0;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pp-minimal-select__triggercheck {
  display: none;
  flex-shrink: 0;
  color: var(--color-brand-green, #205E3A);
}

.pp-minimal-select__btn.is-filled .pp-minimal-select__triggercheck { display: inline-flex; }

.pp-minimal-select__chevron {
  flex-shrink: 0;
  color: var(--base-text-tertiary, #737373);
  transition: transform 0.15s ease;
}

.pp-minimal-select.is-open .pp-minimal-select__chevron { transform: rotate(180deg); }

.pp-minimal-select__menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 10;
  padding: 5px;
  max-height: 280px;
  overflow-y: auto;
  background: var(--base-bg-low, #fff);
  border: 1px solid var(--base-border-tertiary, #e5e5e5);
  border-radius: 12px;
  box-shadow: 0 4px 6px -2px rgba(15, 23, 42, 0.06), 0 18px 36px -12px rgba(15, 23, 42, 0.18);
}

.pp-minimal-select__menu[hidden] { display: none; }

/* 🚨 THIS RULE HAS TO EXIST IN BOTH BUNDLES, and the twin in
   app/assets/tailwind/components/profile_forms.css is not enough on its own. This file is
   imported UNLAYERED while the Tailwind components live in a layer, so the `.pp-minimal-select__menu`
   block above beats its layered namesake whatever the specificity -- which means a flip rule
   written only in the layered copy applies its `bottom` and LOSES its `top: auto`. Both then
   resolve, the menu is stretched between a top and a bottom 55px apart on a 49px box, and it
   renders 12 pixels tall: a hairline with five options scrolling inside it.

   Set by minimal_select_controller#placeInline when the menu would be clipped by a scrolling
   ancestor's bottom edge and there is room above the trigger instead. Absolute menus only;
   the portal variant does its own flipping with inline styles. */
.pp-minimal-select.is-above .pp-minimal-select__menu {
  top: auto;
  bottom: calc(100% + 6px);
}

.pp-minimal-select__opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  font-size: var(--text-base, 14px);   /* was 13.5px → Body */
  font-family: inherit;
  color: var(--base-text, #171717);
  text-align: left;
  cursor: pointer;
}

/* Menu rows: the POINTER gets the fill, the current VALUE gets weight and the tick.
   🚨 This copy of the component had no `:hover` at all, and because this file is
   UNLAYERED it beat the one `profile_forms.css` does define (in a Tailwind
   @layer components, which any unlayered rule outranks). So the resting
   `background: transparent` above won on every surface that loads this file, and the
   menus had no hover state anywhere -- measured on broker, /explore and
   /buying/browse: "hover rules in loaded CSS: NONE" on all three.

   The two states are also SEPARATED here, which the base component conflates: it
   gives the selected row the same grey fill as hover, so a menu with a pointer in it
   showed two identical highlights and neither said which was which. The design uses
   fill for "the pointer is here" and weight plus tick for "this is the current
   value". Measured before: every row weight 400, selected row filled
   `oklch(0.985 0 none)` on the dashboards and `#F7F7F7` on /explore -- and the fill
   was the only divergence left between them, which removing it also settles.

   `--status-neutral-bg`, never `--base-bg-hover`: that token is the warm beige-grey
   CLAUDE.md hard-bans as a background. */
.pp-minimal-select__opt:hover,
.pp-minimal-select__opt:focus-visible {
  background: var(--status-neutral-bg, #F4F4F4);
}

.pp-minimal-select__opt.is-selected {
  background: transparent;
  font-weight: var(--fw-semibold, 600);
}

/* The selected row still takes the pointer fill. Needs its own rule to outscore
   `.is-selected`'s transparent above, which ties with a bare `:hover` on
   specificity and would win on source order. */
.pp-minimal-select__opt.is-selected:hover,
.pp-minimal-select__opt.is-selected:focus-visible {
  background: var(--status-neutral-bg, #F4F4F4);
}

.pp-minimal-select__opt .pp-minimal-select__optcheck { color: var(--color-brand-green, #205E3A); opacity: 0; }

.pp-minimal-select__opt.is-selected .pp-minimal-select__optcheck { opacity: 1; }

/* Pinned, because the tick is an `icon :check, class: "w-3.5 h-3.5"` and `w-3.5` is a
   Tailwind utility the marketing bundle does not carry: the SVG fell back to em
   sizing and came out 13px on /explore against 14px on both dashboards. */
.pp-minimal-select__optcheck svg {
  width: 14px;
  height: 14px;
}

/* ---------- Explore filters sheet: broker's field layout ---------- */
.xfs-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

/* Field labels match the broker sheet's uppercase eyebrow. */
.xfs-row__input {
  width: 100%;
  height: 44px;
  padding: 0 14px;
  border: 1px solid var(--base-border-tertiary, #e5e5e5);
  border-radius: 12px;
  background: var(--base-bg-low, #fff);
  color: var(--base-text, #222);
  font: var(--fw-medium, 500) var(--text-md, 0.875rem)/var(--lh-body, 1.5) var(--font-sans);
}

.xfs-row__input:focus {
  outline: none;
  border-color: var(--color-brand-green, #0EA184);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-brand-green, #0EA184) 18%, transparent);
}

.xfs-rows .pp-minimal-select__btn { height: 44px; border-radius: 12px; }

/* Footer: Clear all, spacer, Cancel, primary -- broker's three-button row. */
.xfs-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 26px;
}

.xfs-foot__spacer { flex: 1; }

.xfs-clear,
.xfs-cancel {
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 0 8px;
  height: 36px;
  font-size: var(--text-base, 0.8125rem);
  font-weight: 500;
  color: var(--base-text-tertiary, #767676);
  text-decoration: none;
}

.xfs-clear:hover,
.xfs-cancel:hover { color: var(--base-text, #222); }

/* The apply button stops being full-width now that it shares the row. */
/* The footer's primary. Fully specified HERE rather than inherited from
   marketing/pages/explore.css, because that rule reads
   `background: var(--color-brand-green, #205e3a)` and --color-brand-green is
   undefined in the APP bundle: /explore got the brand green and /buying/browse
   got the dark fallback, from one shared partial. Everything in this file carries
   a literal fallback for exactly this reason. */
.xfs-foot .xfs-apply {
  width: auto;
  height: auto;
  margin: 0;
  flex: none;
  padding: 11px 18px;
  border: 0;
  border-radius: 10px;
  background: var(--browse-accent, #0EA184);
  color: #fff;
  font: var(--type-body-strong) var(--font-sans);
  cursor: pointer;
}

.xfs-foot .xfs-apply:hover { background: var(--browse-accent-deep, #0C7C66); }

/* Broker's sheet spaces its field groups 22px apart; explore's default is tighter, so
   the price labels sat right under the selects above them. */
.xfs-sheet .xfs-rows {
  display: flex;
  flex-direction: column;
  gap: 22px;
}

/* The pill ROW only. The pills themselves are .brokering-browse-typeopt, shared
   with broker's sheet; this exists because broker lays its row out with Tailwind
   utilities (flex gap-2 flex-wrap) and the marketing bundle has no utilities. */
.xfs-pills { display: flex; flex-wrap: wrap; gap: 8px; }

/* System status beside the commit, not a promise. Same treatment as broker's
   .brokering-browse-sheet__count: the tertiary caption, no pill, no icon, no colour,
   and it never animates its number. */
.xfs-count {
  flex: none;
  margin: 0;
  font: var(--type-caption) var(--font-sans);
  color: var(--base-text-tertiary);
  white-space: nowrap;
}
.xfs-foot > [hidden] { display: none; }

/* Explore's own badges inside a v5 card. Same call as broker's .is-anon rule above:
   in this card the type and anonymity chips are two of the design's NEUTRAL chips, so
   the green tint on --anon (and the white --type, which vanished against the cover)
   are dropped here. Outside .browse-v5 they keep their old look. */
.browse-v5 .explore-badge--type,
.browse-v5 .explore-badge--anon {
  background: var(--browse-surface, #F7F7F7);
  color: var(--base-text-secondary, #484848);
}

/* ---------- Selection bar: "N selected / Add to leads" ----------------------
   Replaces the old .brokering-matches-bar, which stood there permanently
   explaining matches mode with a dead, disabled button. The design has no
   standing banner: the bar EXISTS only while something is ticked, so
   `hidden` is toggled by bulk_leads_controller#refresh and the results sit
   directly under the toolbar the rest of the time.

   Tinted with --bg-success-secondary / --text-on-success-secondary, the same
   pair the near-miss prompt uses for warning, so Light / Dark Dimmed / Dark
   High Contrast all resolve without a hardcoded surface. Evenly rounded with
   no left accent stripe. */
.brokering-selectbar {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  /* 10px, not the 18px this first shipped with: the buttons are already 40px tall, so
     18px made the bar 76px against a 48px toolbar and it dominated the page. 10px
     lands it at 60px -- taller than the toolbar, which it should be while it is the
     thing you are acting on, without pushing the results down a row. */
  padding: 10px 18px;
  border-radius: 14px;
  /* --color-brand-green-75 is the mint tint (#E8F6EE light, a green alpha in dark).
     --bg-success-secondary resolves to --color-brand-50 (#F4FAF6), which is so pale
     the bar read as plain white against the page. */
  background: var(--color-brand-green-75, #E8F6EE);
}

.brokering-selectbar[hidden] { display: none; }

.brokering-selectbar__copy {
  min-width: 0;
  flex: 1 1 260px;
  margin: 0;
  font: var(--type-body) var(--font-sans);
  /* The design's helper sentence is mid-green, not grey: --text-primary is the brand
     green in every mode (#205E3A light, #8FD0A8 dark). */
  color: var(--text-primary, #205E3A);
}

.brokering-selectbar__copy strong {
  font: var(--type-body-strong) var(--font-sans);
  color: var(--base-text, #171717);
}

.brokering-selectbar__hint { margin-left: 6px; }

.brokering-selectbar__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* Cancel is btn-ghost and the CTA is btn-primary -- theme components, no bespoke
   button CSS here. The design gives the bar ONE emphasis and it belongs to the CTA. */

@media (max-width: 640px) {
  /* Copy owns the first line, the two buttons the second, and they split it
     rather than one being squeezed to an unreadable width. Two stacked rows need
     their own breathing room back. */
  .brokering-selectbar { padding: 12px 14px; }
  .brokering-selectbar__copy { flex: 1 0 100%; }
  .brokering-selectbar__actions { flex: 1 0 100%; }
  .brokering-selectbar__actions form,
  .brokering-selectbar__actions form button { width: 100%; }
  .brokering-selectbar__actions > .btn { flex-shrink: 0; }
}


/* ==========================================================================
   Toolbar controls, measured off the prototype
   ==========================================================================
   The scope switch (Matches / All) and the view switch were rounded RECTANGLES
   here -- a 9px track around 7px buttons -- where the design's are PILLS: a 2px
   #F4F4F4 track with an inset hairline shadow, 999px on the track AND the
   buttons. Scoped because `.segmented` is a shared theme component the rest of
   the dashboard uses at its own radius -- and scoped TWICE: the buyer toolbars sit
   inside the .browse-v5 wrapper, but broker's renders into the shell's
   `brokerage_page_header` slot, which is hoisted OUT of that wrapper, so a
   .browse-v5-only selector silently missed the surface the design came from. */
.browse-v5 .segmented,
.brokering-browse-toolbar .segmented {
  gap: 2px;
  padding: 2px;
  /* Explicit, because the track inherits its size from the page body otherwise --
     16px on the marketing bundle, 13px in the app. Its buttons set their own, so
     this is only about anything unstyled inside it (a count badge, an icon). */
  font-size: var(--text-base, 0.8125rem);
  border: 0;
  border-radius: 999px;
  background: var(--status-neutral-bg, #F4F4F4);
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.05);
}

.browse-v5 .segmented__btn,
.brokering-browse-toolbar .segmented__btn {
  height: 30px;
  padding: 0 13px;
  border-radius: 999px;
}

/* The Filter button is a 34px pill on the same neutral. The explicit font-size
   matters: explore's sized itself in a unit that resolves off the marketing body,
   so the same button measured 13.33px there and 13px on the other two. */
.brokering-browse-toolbar .brokering-browse-filterbtn,
.browse-v5 .explore-mbar__btn {
  height: 34px;
  border-radius: 999px;
  background: var(--status-neutral-bg, #F4F4F4);
  font-size: var(--text-base, 0.8125rem);
}

/* The search field is a SHADOWED white field in the design, not a bordered one,
   and the focus ring is a brand halo rather than a heavier border.
   Both surfaces are now a field GROUP -- broker's form wrapper and the buyer/public
   `.explore-mbar__field` -- so the chrome and the ring live on the wrapper and light
   on `:focus-within`. They used to differ structurally: broker lit a wrapper holding
   an icon, the buyer lit a bare input with no icon at all. */
.brokering-browse-toolbar .brokering-browse-search,
.browse-v5 .explore-mbar__field {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1 1 320px;
  min-width: 0;
  height: 52px;
  padding: 0 8px 0 16px;
  border: 0;
  border-radius: 12px;
  background: #fff;
  /* Stated: the wrapper inherits its size from the page body, which is 16px on the
     marketing bundle and 13px on the dashboards. */
  font-size: var(--text-base, 0.8125rem);
  box-shadow: 0 2px 16px rgb(0 0 0 / 0.08);
  transition: box-shadow 150ms ease-out;
}

/* One padding on both, INCLUDING the right. Broker's radius pill sits in that 8px;
   the buyer has no pill and simply leaves it empty. Keeping broker on `0 8px 0 16px`
   and the buyer on `0 16px` would put the caret at the same place but the field's
   right-hand breathing room 8px apart, which is the sort of difference that gets
   noticed and reported later. */

.brokering-browse-toolbar .brokering-browse-search:focus-within,
.browse-v5 .explore-mbar__field:focus-within {
  box-shadow: 0 0 0 1.5px var(--browse-accent, #0EA184),
              0 0 0 5px rgb(14 161 132 / 0.12),
              0 2px 16px rgb(0 0 0 / 0.08);
}

/* 🚨 STATE the ink. Broker's toolbar is hoisted into the shell's page-header slot,
   outside the wrapper that sets `color`, so it inherited the UA default pure BLACK
   while the buyer's resolved `--base-text` (#262626). Invisible on the controls that
   set their own colour, visible on anything that inherits. */
.brokering-browse-toolbar .brokering-browse-search,
.browse-v5 .explore-mbar__field,
.brokering-browse-toolbar .segmented,
.browse-v5 .explore-mbar .segmented {
  color: var(--base-text);
}

/* The icon is broker's, same size and same tertiary ink. Broker's own rule leaves it
   `display: block`, so both are stated here rather than one inheriting a default. */
.brokering-browse-toolbar .brokering-browse-search__icon,
.browse-v5 .explore-mbar__icon {
  flex: none;
  display: inline-flex;
  color: var(--base-text-tertiary, #767676);
}

/* The input inside the group carries no chrome of its own -- the group has it all.
   Height 100% so the whole 52px field is a click target, as broker's is.
   🚨 `:focus` and `:focus-visible` too. The dashboard input safety net in
   themes/broker-v2.css is scoped `html:not(.dark)` and to
   `body.brokering-body-broker, body.brokering-body-seller` -- the buyer's
   `body.buying-body` is not in that list, and dark mode is excluded outright -- so
   the exclusion `field-group` buys on broker does NOT reach here. Measured with a
   real focused window: the buyer's input drew
   `rgba(52,184,122,0.25) 0 0 0 3px` plus a green border INSIDE the wrapper's own
   halo, the dual-border bug, while broker's drew nothing. Broker states the same
   three selectors for the same reason (see `.brokering-browse-search input` above);
   this is that rule's twin, not a new idea.
   The `font` shorthand is broker's, so both inputs share a line box: the buyer's
   resolved 19.5px against broker's `normal`. */
/* BOTH inputs, at a specificity that actually wins. Broker states the same thing at
   `.brokering-browse-search input` (0,1,1) and loses the radius and the line box to
   @tailwindcss/forms; measured 12px radius and `line-height: normal` on broker
   against 0px and 19.5px on the buyer. The line box is the one that shows: it moves
   the caret and the placeholder inside a 52px field, and it differed because
   `var(--type-body)` carries a line-height in one shell's bundle and not the other's.
   Stated explicitly here for both, per this file's rule about bare vars. */
/* 🚨 NO `.browse-v5` on broker's half. Broker's toolbar renders into the shell's
   page-header slot and is hoisted OUT of the page wrapper, so it is not a descendant
   of `.browse-v5` -- which is why the rest of this file writes broker toolbar
   selectors bare. A prefixed selector here matched nothing and the values stayed
   split. */
.brokering-browse-toolbar .brokering-browse-search input,
.brokering-browse-toolbar .brokering-browse-search input:focus,
.brokering-browse-toolbar .brokering-browse-search input:focus-visible,
.browse-v5 .explore-mbar__field .explore-mbar__search,
.browse-v5 .explore-mbar__field .explore-mbar__search:focus,
.browse-v5 .explore-mbar__field .explore-mbar__search:focus-visible {
  line-height: 1.5;
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: 0;
  /* 12px, matching the wrapper, not 0. Something in the app bundle gives broker's
     input a 12px radius at a specificity this rule does not beat, and the value is
     unpaintable either way (transparent background, no border, no shadow) -- so both
     say 12px and agree, rather than one saying 0 and losing. */
  border-radius: 12px;
  background: transparent;
  box-shadow: none;
  outline: none;
  color: var(--base-text);
  font: var(--type-body) var(--font-sans);
}

/* ---------- Selection bar, measured off the prototype ---------- */
/* radius 12 (not 14), padding 12/18, 14px count, 13px sentence, 36px buttons. */
.brokering-selectbar {
  gap: 14px;
  padding: 12px 18px;
  border-radius: 12px;
}

.brokering-selectbar__copy {
  font-size: var(--text-base, 0.8125rem);
  color: var(--browse-ink-soft, #0A6E5C);
}

.brokering-selectbar__copy strong {
  font-size: var(--text-md, 0.875rem);
  font-weight: var(--fw-semibold, 600);
  color: var(--browse-ink, #063E36);
}

/* Both buttons are 36px pills. Cancel is a WHITE surface on the tint, not the
   transparent ghost it inherited -- on a mint band a transparent ghost reads as
   plain text rather than a control. */
.brokering-selectbar .btn {
  height: 36px;
  min-height: 36px;
  padding: 0 16px;
  border-radius: 999px;
  font-size: var(--text-base, 0.8125rem);
  font-weight: var(--fw-semibold, 600);
}

.brokering-selectbar .btn-ghost {
  background: #fff;
  border-color: transparent;
  color: var(--base-text, #171717);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
}

.brokering-selectbar .btn-ghost:hover {
  background: #fff;
  box-shadow: 0 2px 6px rgb(0 0 0 / 0.10);
}

.brokering-selectbar .btn-primary {
  background: var(--browse-accent, #0EA184);
  border-color: transparent;
}

.brokering-selectbar .btn-primary:hover {
  background: var(--browse-accent-deep, #0C7C66);
  border-color: transparent;
}

/* Distance inside the search field: the design's grey pill, sitting on the right
   of the field rather than as a bordered select. It is inert until a market is
   picked, because a radius needs a point to measure from. */
.brokering-browse-search__radius { flex: none; }

/* minimal_select's trigger is .pp-minimal-select__btn -- not __trigger, which does
   not exist, so the first version of this rule matched nothing. */
.brokering-browse-search__radius .pp-minimal-select__btn {
  height: 36px;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: var(--status-neutral-bg, #F4F4F4);
  color: var(--base-text-secondary, #5E5E5E);
  font-size: var(--text-sm, 0.75rem);
  font-weight: var(--fw-medium, 500);
}

.brokering-browse-search__radius.is-inert {
  opacity: 0.5;
  pointer-events: none;
}


/* ==========================================================================
   Cross-surface consistency (measured, not assumed)
   ==========================================================================
   Broker Browse, /explore and /buying/browse render the same card and the same
   toolbar. An audit comparing computed styles on all three found five places
   where they still disagreed; these rules are that audit's fix. A system test
   re-measures the three and fails if they drift apart again.

   The recurring cause is a bare `var()`: the app and marketing bundles do not
   define the same token set, and a declaration whose var is undefined is DROPPED
   rather than falling back. Everything here carries a literal. */

/* 1. Card chips were a pill on broker and explore and a SQUARE, BORDERED box on
      buying, because `.explore-badge`'s `border-radius: var(--radius-pill)` has no
      value in the app bundle. Geometry is unified for every badge; colour only for
      the two neutral ones, so the coral "New this week" and the match chip keep
      theirs.

   🚨 NORMALISED BY THE ROW, NOT BY A LIST OF CLASS NAMES. This named
   `.brokering-browse-chip` and `.explore-badge` only, and
   `.explore-match-chip__trigger` -- the "Good match" / "Partial match" pill -- was
   left out, so it kept `padding: 4px 9px`, a stray `letter-spacing: 0.01em`, and
   `border-radius: var(--radius-pill)`. `--radius-pill` is declared ONLY in
   marketing/colors_and_type.css, which the buyer dashboard does not load, so the
   same chip rendered a PILL on /explore and a SQUARE BOX on /buying/browse: exactly
   the undefined-token bug the paragraph above says was fixed, on the one class the
   list forgot.
   A hand-maintained list of chip classes will always be one class behind. The chip
   ROW is the contract instead, so anything dropped into it is normalised whatever it
   is called. `:not(.explore-match-chip)` excludes the match chip's positioning
   shell, whose inner `__trigger` is the pill and is named separately. */
.browse-v5 .brokering-browse-card__chips > *:not(.explore-match-chip),
.browse-v5 .brokering-browse-card__chips .explore-match-chip__trigger,
.browse-v5 .brokering-browse-chip,
.browse-v5 .explore-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border: 0;
  border-radius: 999px;
  font-size: var(--text-xs, 0.6875rem);
  font-weight: var(--fw-semibold, 600);
  line-height: 1.2;
  /* The match chip shipped 0.01em; nothing else in the row tracks its letters. */
  letter-spacing: normal;
}

/* 2. One neutral surface for covers and metric tiles, and one weight for the
      cover's "Photo not provided" placeholder -- it inherited 500 on broker and 400
      on the buyer surfaces. */
.browse-v5 .brokering-browse-card__cover,
.browse-v5 .brokering-browse-tile {
  background: var(--browse-surface, #F7F7F7);
}

/* Both inherit a weight from their card partial otherwise -- 500 on broker's, 400
   on the buyer one -- which showed up on the cover's placeholder text and on the
   metric tiles' labels. */
.browse-v5 .brokering-browse-card__cover,
.browse-v5 .brokering-browse-tile {
  font-weight: var(--fw-medium, 500);
}

/* 3. The card sets its own base size, so the three surfaces inherit the same one
      (explore inherited 16px from the marketing body, the others 13px). */
/* 🚨 WEIGHT as well as size. The card set only `font-size`, so everything that
   inherits rather than declaring its own -- the chips row and the footer -- came out
   500 on broker and 400 on the buyer. Measured on both dashboards. */
.browse-v5 .brokering-browse-card,
.browse-v5 .explore-card {
  font-size: var(--text-base, 0.8125rem);
  font-weight: var(--fw-regular, 400);
}

/* 4. The sort control is a 999px pill with NO border in the design. Broker had a
      filled 8px rect; the buyer surfaces had a white bordered one. */
/* All three build it from `minimal_select`, in different wrappers: the buyer
   TOOLBAR row and broker's split/grid head. */
.browse-v5 .browse-toolbar__sort .pp-minimal-select__btn,
.browse-v5 .brokering-browse-split__head .pp-minimal-select__btn,
.brokering-browse-split__head .pp-minimal-select__btn {
  height: 34px;
  border: 0;
  border-radius: 999px;
  font-weight: var(--fw-medium, 500);
  /* NO background here: the rule below owns it (transparent until hover/open). Two
     rules setting it meant the surface whose selector happened to be one class more
     specific won, which is how broker ended up filled and explore transparent from
     the same pair of rules. */
}

/* 5. The results headline's bold half is one size on all three (15px, the nearest
      rung to the design's 16px). The buyer surfaces were rendering it at 13px. */
.browse-v5 .explore-result-bar__count strong,
.browse-v5 .explore-map-sidebar__count strong {
  font-size: var(--text-lg, 0.9375rem);
}

/* ...and the SUFFIX beside it is the quiet half, as in broker's
   `.brokering-browse-headline`: bold number, muted "in this map area". The base
   `.explore-map-sidebar__count` (browse_map.css) is bold and near-black for the whole
   span, which was right while it held a bare number and wrong the moment it grew a
   suffix -- it made the sidebar header read as two headlines. */
.browse-v5 .explore-map-sidebar__count {
  font-weight: var(--fw-regular, 400);
  color: var(--base-text-tertiary, #737373);
}

/* Icon-only switch buttons are a 32x30 square in the design, not a text button's
   horizontal padding. Both surfaces' view switches carry the modifier. */
.browse-v5 .segmented__btn--icon,
.brokering-browse-toolbar .segmented__btn--icon {
  width: 32px;
  padding: 0;
  justify-content: center;
}

/* The sort control is TRANSPARENT until hovered or open in the design -- a filled
   pill made it the loudest thing on the results row. minimal_select also stamps a
   tick on its trigger; the design puts the tick only on the open menu's active row,
   which is where it means something. */
.browse-v5 .browse-toolbar__sort .pp-minimal-select__btn,
.browse-v5 .brokering-browse-split__head .pp-minimal-select__btn,
.brokering-browse-split__head .pp-minimal-select__btn {
  background: transparent;
  color: var(--base-text-secondary, #5E5E5E);
}

.browse-v5 .browse-toolbar__sort .pp-minimal-select__btn:hover,
.browse-v5 .brokering-browse-split__head .pp-minimal-select__btn:hover,
.brokering-browse-split__head .pp-minimal-select__btn:hover,
.browse-v5 .browse-toolbar__sort .pp-minimal-select__btn[aria-expanded="true"],
.browse-v5 .brokering-browse-split__head .pp-minimal-select__btn[aria-expanded="true"],
.brokering-browse-split__head .pp-minimal-select__btn[aria-expanded="true"] {
  background: var(--status-neutral-bg, #F4F4F4);
}

/* 🚨 NO GREEN RING ON ANY BROWSE CONTROL, IN ANY STATE.
   Two rules put one there. profile_forms.css styles `.pp-minimal-select__btn:focus`
   -- a plain `:focus`, which is why a MOUSE click lit it and not just the keyboard --
   and browse_v5's own `.pp-minimal-select.is-open` rule above; both set a
   `border-color` plus a 3px green glow. The Browse-scoped overrides only ever set
   `background`, so the glow survived on broker, buyer and marketing alike. On
   /explore the marketing bundle piles on its own global
   `button:focus-visible { outline: 2px solid var(--brand-500) }`
   (marketing/site.css), which is why the ring was greener there than anywhere else.

   Scoped to Browse deliberately: the same trigger is used by deal-import, user
   settings and the filters sheet, which keep their focus treatment.

   This removes the keyboard focus indicator on these controls too. That is the
   explicit call (Shahnoor, 2026-09-01) -- do not quietly reinstate one. */
.browse-v5 .browse-toolbar__sort .pp-minimal-select__btn:focus,
.browse-v5 .browse-toolbar__sort .pp-minimal-select__btn:focus-visible,
.browse-v5 .browse-toolbar__sort .pp-minimal-select.is-open .pp-minimal-select__btn,
.browse-v5 .brokering-browse-sortbox .pp-minimal-select__btn:focus,
.browse-v5 .brokering-browse-sortbox .pp-minimal-select__btn:focus-visible,
.browse-v5 .brokering-browse-sortbox .pp-minimal-select.is-open .pp-minimal-select__btn,
.brokering-browse-split__head .pp-minimal-select__btn:focus,
.brokering-browse-split__head .pp-minimal-select__btn:focus-visible,
.brokering-browse-split__head .pp-minimal-select.is-open .pp-minimal-select__btn,
.brokering-browse-toolbar .brokering-browse-filterbtn:focus,
.brokering-browse-toolbar .brokering-browse-filterbtn:focus-visible,
.browse-v5 .explore-mbar__btn:focus,
.browse-v5 .explore-mbar__btn:focus-visible,
.browse-v5 .segmented__btn:focus,
.browse-v5 .segmented__btn:focus-visible,
.brokering-browse-toolbar .segmented__btn:focus,
.brokering-browse-toolbar .segmented__btn:focus-visible {
  outline: none;
  border-color: transparent;
  box-shadow: none;
}

/* No tick on the TRIGGER anywhere in Browse. The design puts a tick only on the open
   menu's active row, where it means "this is the one you picked"; on the trigger it
   just sits beside the label it already agrees with.
   Three classes deep because `.pp-minimal-select__btn.is-filled
   .pp-minimal-select__triggercheck` re-shows it at (0,3,0) as soon as a value is
   selected -- which is always true for sort -- so a two-class selector loses. */
.browse-v5 .browse-toolbar__sort .pp-minimal-select__triggercheck,
.browse-v5 .brokering-browse-sortbox .pp-minimal-select__triggercheck,
.browse-v5 .brokering-browse-split__head .pp-minimal-select__triggercheck,
.brokering-browse-sortbox .pp-minimal-select__btn .pp-minimal-select__triggercheck,
.brokering-browse-split__head .pp-minimal-select__btn .pp-minimal-select__triggercheck {
  display: none;
}

/* The design's trigger is CONTENT-WIDTH with a 6px gap: label, then chevron right
   beside it. minimal_select's base is `width: 100%` + `space-between`, which pushed
   the chevron to the far edge of a 224px box -- the wide gap that read as broken. */
.browse-v5 .browse-toolbar__sort,
.brokering-browse-sortbox {
  flex: none;
}

.browse-v5 .browse-toolbar__sort .pp-minimal-select,
.brokering-browse-sortbox .pp-minimal-select { width: auto; }

.browse-v5 .browse-toolbar__sort .pp-minimal-select__btn,
.browse-v5 .brokering-browse-sortbox .pp-minimal-select__btn,
.brokering-browse-sortbox .pp-minimal-select__btn {
  width: auto;
  justify-content: flex-start;
  gap: 6px;
  padding: 0 12px;
}

/* ---------- Pager (shared: broker Browse + both buyer surfaces) ----------
   The design's: a label on the left, 30px CIRCLES on the right, the current page
   filled dark. Replaces three copies that were each a row of rounded rects. */
.browse-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 20px 2px 0;
}

.browse-pager__label {
  font-size: var(--text-base, 0.8125rem);
  color: var(--base-text-tertiary, #767676);
}

.browse-pager__pages {
  display: flex;
  align-items: center;
  gap: 6px;
}

.browse-pager__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: 999px;
  background: var(--browse-surface, #F7F7F7);
  color: var(--base-text-secondary, #484848);
  font-size: var(--text-base, 0.8125rem);
  font-weight: var(--fw-medium, 500);
  text-decoration: none;
  transition: background-color 120ms linear;
}

.browse-pager__btn:hover { background: var(--base-bg-hover, #EFEFEF); }

.browse-pager__btn.is-active {
  background: #222222;
  color: #fff;
  font-weight: var(--fw-semibold, 600);
}

.browse-pager__gap {
  color: var(--base-text-tertiary, #A8A8A8);
  font-size: var(--text-base, 0.8125rem);
  padding: 0 2px;
}

/* ---------- Preferences bar and filter chips ----------
   Both looked "boxy and bad" on /buying/browse for the same reason as the card
   chips did: their rules in marketing/pages/explore.css set
   `border-radius: var(--radius-input)` / `var(--radius-pill)` and
   `background: var(--brand-50)`, none of which exist in the APP bundle, so those
   declarations were DROPPED and the pill became a square white box with a border.
   Restated here with literals, in the design's shapes. */

/* "Matched to your alert preferences … Edit": a tinted band, evenly rounded, no
   border. Per the styling rules a tinted block already reads as its own region, so
   it needs no outline (and never a coloured edge stripe). */
/* 🚨 The FULL component lives here, not just the v5 overrides. Its base rule was in
   marketing/pages/explore.css, which only the public layout loads -- so rendering
   `.explore-pref-bar` on broker Browse produced an unstyled block: no flex, no
   padding, no background. Every property the bar needs is stated below so the same
   partial renders identically from all three layouts. */
.browse-v5 .explore-pref-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 16px;
  margin-bottom: 12px;
  padding: 12px 16px;
  border: 0;
  border-radius: 12px;
  background: var(--color-brand-green-75, #E8F6EE);
}

.browse-v5 .explore-pref-bar__hint {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--browse-ink-soft, #0A6E5C);
}
.browse-v5 .explore-pref-bar__hint strong {
  font-size: var(--text-sm);
  font-weight: var(--fw-bold);
  color: var(--browse-ink, #063E36);
}
.browse-v5 .explore-pref-bar__hint svg {
  flex: 0 0 auto;
  width: 17px;
  height: 17px;
  color: var(--browse-accent, #0EA184);
}

/* The summary itself wraps rather than truncating: a broker with three zones and
   four specialties has a long one, and a clipped filter statement is worse than a
   two-line one. */
.browse-v5 .explore-pref-bar__hint span { min-width: 0; }

.browse-v5 .explore-pref-bar__edit {
  display: inline-flex;
  align-items: center;
  /* Stays on the right edge even when the summary wraps it onto its own line, which
     is what a long summary does on a phone. */
  margin-left: auto;
  gap: 5px;
  font-size: var(--text-sm);
  font-weight: var(--fw-bold);
  text-decoration: none;
  white-space: nowrap;
}

.browse-v5 .explore-pref-bar__edit {
  padding: 7px 12px;
  border-radius: 999px;
  background: #fff;
  color: var(--browse-ink, #063E36);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
}

.browse-v5 .explore-pref-bar__edit:hover { box-shadow: 0 2px 6px rgb(0 0 0 / 0.10); }

/* Active filter chips: the design's removable pill, and NEUTRAL like broker's row
   (#F7F7F7 on near-black, borderless, 32px). They were mint green with a green
   hairline, which read as status badges rather than filters you can take off -- the
   same call already made on `.brokering-browse-chips .brokering-filter-pill`. */
.browse-v5 .explore-active-filter {
  height: 32px;
  padding: 0 6px 0 12px;
  gap: 7px;
  border-radius: 999px;
  background: var(--browse-surface, #F7F7F7);
  border: 1px solid transparent;
  font-size: var(--text-sm, 0.75rem);
  font-weight: var(--fw-medium, 500);
  color: var(--base-text, #171717);
}

.browse-v5 .explore-active-filter:hover {
  background: var(--base-bg-hover, #EFEFEF);
}

/* The "Location:" prefix is the quiet half of the chip. */
.browse-v5 .explore-active-filter__label {
  opacity: 1;
  color: var(--base-text-tertiary, #767676);
}

/* The × was a bordered square box; the design's is a bare glyph that darkens. */
.browse-v5 .explore-active-filter__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--base-text, #171717);
  opacity: 0.55;
  transition: opacity 120ms linear, background-color 120ms linear;
}

.browse-v5 .explore-active-filter__remove:hover {
  opacity: 1;
  background: rgb(255 255 255 / 0.7);
}

/* No RULE above the applied-filter chips. `.explore-active-filters` carries
   `border-top: 1px solid var(--base-border-tertiary)` plus the 12px padding that
   goes with it (marketing/pages/explore.css:503), which drew a full-width hairline
   between the search field and the chips. Broker has no such line: it renders its own
   `.brokering-browse-chips`, which is why this only ever showed on the two surfaces
   that share this partial, and why removing it here brings all three into line rather
   than making buyer the odd one out. The 14px margin stays as the separation; the
   12px was the line's own breathing room and goes with it.

   Safe under a bare `.browse-v5` only because broker does not render this partial at
   all -- grepped. Any OTHER shared class needs a shell in the selector (see the note
   on `.explore-pref-bar`). */
.browse-v5 .explore-active-filters {
  padding-top: 0;
  border-top: 0;
}

.browse-v5 .explore-active-filters__clear {
  padding: 0 8px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  font-size: var(--text-sm, 0.75rem);
  font-weight: var(--fw-semibold, 600);
  color: var(--browse-ink-soft, #0A6E5C);
  text-decoration: none;
}


/* The sort menu has to clear the MAP's chrome. Its base z-index is 10 and the map's
   floating controls are 30+ in the same stacking context (.explore-map-panel is
   position: relative with z-index auto, so it traps nothing), which is why the open
   menu rendered UNDER the map's top-right controls on /explore. The map's highest
   chrome is the tooltip at 1200 (pointer-events: none, so it cannot swallow a
   click); sheets and modals sit at 800+, so 700 slots between them. */
/* 🚨 A LITERAL border, both modes. The menu takes
   `border: 1px solid var(--base-border-tertiary)`, and that token is
   `rgba(255,255,255,0.08)` on the broker shell and `rgba(255,255,255,0.1)` on the
   buyer's in dark mode, so the same menu wore two different hairlines. Measured. */
.browse-v5 .browse-toolbar__sort .pp-minimal-select__menu,
.browse-v5 .brokering-browse-sortbox .pp-minimal-select__menu,
.brokering-browse-split__head .pp-minimal-select__menu {
  border-color: #E5E5E5;
}

:is(.dark, .dimmed) :is(.browse-v5 .browse-toolbar__sort,
                        .browse-v5 .brokering-browse-sortbox,
                        .brokering-browse-split__head) .pp-minimal-select__menu {
  border-color: rgba(255, 255, 255, 0.08);
}

.browse-v5 .browse-toolbar__sort .pp-minimal-select__menu,
.brokering-browse-split__head .pp-minimal-select__menu {
  z-index: 700;
  /* Every option on ONE line. The menu inherits the trigger's width (116px, since the
     trigger is content-width), so "Best match" and "Request sent" wrapped to two
     lines and the list read as ragged. Sized to the longest label rather than a round
     number, and nowrap so a new, longer option widens the menu instead of wrapping. */
  min-width: 176px;
}

.browse-v5 .browse-toolbar__sort .pp-minimal-select__opt,
.brokering-browse-split__head .pp-minimal-select__opt,
.brokering-browse-sortbox .pp-minimal-select__opt {
  white-space: nowrap;
}

/* Same for the filter row's popovers, which sit above the map on the buyer split. */
.browse-v5 .explore-filters-card .filter-ms-menu,
.browse-v5 .explore-filters-card .pp-minimal-select__menu {
  z-index: 700;
}

/* Hovering a pin lifts its card. `.is-map-hover` was already defined for
   exactly this (see the card's hover rule above) and NOTHING ever applied it -- the
   controller set `.is-highlighted`, which is only styled for the legacy
   `.explore-map-listing-row`, so on the v5 card surfaces hovering a pin lifted
   nothing. The controller applies both classes now; this adds the buyer card, which
   had no lift rule of its own. */
.browse-v5 .explore-card.is-map-hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 24px rgb(0 0 0 / 0.12);
}

.browse-v5 .explore-card {
  transition: transform 150ms cubic-bezier(0, 0, 0.2, 1), box-shadow 150ms linear;
}

/* ---------- Filter button: the ACTIVE state the design has ----------
   With filters applied the design tints the whole pill mint and turns its label
   green (`filterBtnBg: hasFilters ? "#E7F7F1" : "#F7F7F7"`, `filterBtnColor:
   "#0A6E5C"`, weight 600). Ours stayed neutral grey whatever was applied, so the
   only hint that three filters were narrowing the results was a small badge.

   Keyed off the badge's presence rather than a server-rendered class: every surface
   already renders the count only when there IS one, so `:has()` needs no new markup
   and cannot drift out of step with the count. */
.brokering-browse-toolbar .brokering-browse-filterbtn:has(.brokering-browse-filterbtn__count),
.browse-v5 .explore-mbar__btn:has(.explore-mbar__badge) {
  background: var(--color-brand-green-75, #E7F7F1);
  color: var(--browse-ink-soft, #0A6E5C);
  font-weight: var(--fw-semibold, 600);
}

.brokering-browse-toolbar .brokering-browse-filterbtn:has(.brokering-browse-filterbtn__count) svg,
.browse-v5 .explore-mbar__btn:has(.explore-mbar__badge) svg {
  color: var(--browse-ink-soft, #0A6E5C);
}

/* One badge on all three: the design's 17px accent dot, not a white-on-green pill
   (which is what the marketing bundle gave the buyer surfaces). */
.brokering-browse-toolbar .brokering-browse-filterbtn__count,
.browse-v5 .explore-mbar__badge {
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--browse-accent, #0EA184);
  color: #fff;
  font-size: var(--text-2xs, 0.625rem);
  font-weight: var(--fw-bold, 700);
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ---------- Listing slideover: white, not minty ----------
   The v5 slideover inherits the legacy listing panel, which paints its body
   `#F1F7F5` (a mint wash) over a `#F7F6F3` cream shell. The design's panel is plain
   WHITE with grey metric tiles, so the tint made the whole lower half read green and
   the tiles nearly vanish into it.
   Scoped to the browse slideover, so the legacy listing panel elsewhere keeps its
   own look, and only under .browse-v5 surfaces' frame class. */
.brokering-listing-slideover.brokering-browse-slideover,
.brokering-listing-slideover.brokering-browse-slideover .brokering-listing-slideover-body {
  background: #fff;
}

/* The hero sits above the title on a white panel, so its own grey stays -- it is the
   photo's placeholder, not a tint. */
.brokering-listing-slideover.brokering-browse-slideover .brokering-browse-slideover__hero {
  background: var(--browse-surface, #F7F7F7);
}

/* 🚨 The dark value has to be repeated here, it does not come from the
   --browse-surface override at the top of this file. That override is declared on
   `.browse-v5` and friends, and this panel is NOT inside any of them: `shared/_slideover`
   mounts `dialog#slideover-listing_slideover` at the bottom of the page, outside the
   `.browse-v5` wrapper, so the var falls through to the literal light fallback and the
   hero painted a white band across the top of a dark panel. */
:is(.dark, .dimmed) .brokering-listing-slideover.brokering-browse-slideover .brokering-browse-slideover__hero {
  background: #303030;
}

/* Dark modes keep the theme surface rather than the hardcoded white above.
   --base-bg-highest since 2026-09-11, not --background: the buyer's listing drawer now reads
   as the same lifted sheet as every broker and seller drawer instead of sitting a step lower
   at page level. The body rides along on the panel rather than painting itself. */
:is(.dark, .dimmed) .brokering-listing-slideover.brokering-browse-slideover {
  background: var(--base-bg-highest);
}
:is(.dark, .dimmed) .brokering-listing-slideover.brokering-browse-slideover .brokering-listing-slideover-body {
  background: transparent;
}

/* Map-driven highlight. Hovering or clicking a pin marks its card with an accent
   ring on top of the ordinary hover lift, so the answer to "which one is this pin?"
   is visible without the list scrolling itself. Both card classes carry a 150ms
   box-shadow transition, so the ring fades in rather than snapping.
   `.brokering-practice-card` is in the broker selector for SPECIFICITY, not for
   scoping: the dashboard sets that card's shadow through
   `html:not(.dark) :is(body.brokering-body-broker, body.brokering-body-seller)
   .brokering-practice-card` -- three classes AND two elements, so a plain
   `.browse-v5 .brokering-browse-card.is-map-hover` loses to it and the ring never
   paints. A measured test would have caught it; looking at the file would not. */
.browse-v5 .brokering-browse-card.brokering-practice-card.is-map-hover,
.browse-v5 .explore-card.is-map-hover {
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.10), inset 0 0 0 2px var(--browse-accent, #0EA184);
  z-index: 2;
}

/* ══════════════════════════════════════════════════════════════════════════
   The buyer map column, made to match broker's (Shahnoor's review, Aug 2026:
   "the brokering side is aligned on the left but the buyer's browse isn't, and
   the scroll cards look like they're scrolling in a box section")

   Measured at 1400px before the fix — left edge of each element:

                search   results row   first card
     broker       64          64            64      ← one rail
     explore      24           2            33      ← card 9px in from its own rail
     buying       56          34            65      ← same

   Two causes, both structural rather than cosmetic:
     · `.explore-map-sidebar` is a white PANEL (1px border, 14px radius,
       overflow: hidden) that the cards scroll inside. Broker's split has no
       panel at all: the head sits on the page and the scroller is transparent.
       That panel is what reads as "a box section".
     · `.explore-map-sidebar__rows` carries `padding: 9px`, so every card sits
       9px right of the header and the search field above it, and the scrollbar
       runs down the middle of the gutter instead of the column's edge.

   Desktop only. Below 900px the sidebar IS the draggable bottom sheet, where
   the border and radius are the sheet's own chrome and must stay.
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 900px) {
  .browse-v5 .explore-map-sidebar {
    background: transparent;
    border: 0;
    border-radius: 0;
  }

  /* Flush left, like `.brokering-browse-split__head`. The bottom hairline goes
     with the panel: it was drawing the panel's internal divider, and broker's
     head has none.
     🚨 ALL FOUR SIDES of broker's `10px 20px 14px 0`, not three of them. This
     shipped as `0 20px 14px 0` under a comment claiming it copied broker, and the
     missing 10px is the one broker has *because the sort control read as tucked
     under the search field* (your review of #251). Measured on /buying/browse before
     the fix: header padding-top 0, sort trigger top 247, sidebar top 247 -- zero
     clearance, inside a container that is `overflow: hidden`, so the pill looked cut
     off at the top. */
  .browse-v5 .explore-map-sidebar__header {
    padding: 10px 20px 14px 0;
    border-bottom: 0;
  }

  /* Broker's exact scroller padding: no LEFT inset so cards line up with the
     header and the search field, 20px on the right for the scrollbar, 24px at
     the bottom so the last card clears the pager. The top is the shadow gutter --
     see the note beside `.brokering-browse-split__scroll`, whose padding this
     mirrors; the LEFT gutter lives on the scroller itself here, because that is the
     box that clips. */
  .browse-v5 .explore-map-sidebar__rows {
    padding: var(--browse-shadow-gutter) 20px 24px 0;
  }

  /* The "N more in this area" line spans the grid rather than taking a cell. */
  .browse-v5 .explore-map-sidebar__more { grid-column: 1 / -1; }

  .browse-v5 .explore-map-sidebar__pager {
    padding-left: 0;
    padding-right: 20px;
  }
}

/* The "Browsing all N listings · 0 match your preferences" banner is the same kind of
   statement as the Matches tab's "Matched to your alert preferences", so it wears the
   same skin. It came from the marketing bundle as a grey-green #EEF4F1 box with a
   hairline, square corners and 12px text, next to a 13px mint pill with no border --
   two banners, one job, on two tabs of the same page. Values copied from
   `.explore-pref-bar` above rather than re-picked, and a test measures them equal. */
:is(body.explore-page, .buying-shell) .browse-v5 .explore-cross-link {
  padding: 12px 16px;
  border: 0;
  border-radius: 12px;
  background: var(--color-brand-green-75, #E7F7F1);
  /* --text-base, not --text-sm: the Matches banner renders at 13px (its container
     inherits), and `--text-sm` is 12px in the marketing bundle. Same trap as every
     other token that "means" a size -- match the rendered value, not the name. */
  font-size: var(--text-base, 0.8125rem);
  color: var(--browse-ink-soft, #0A6E5C);
}

:is(body.explore-page, .buying-shell) .browse-v5 .explore-cross-link svg {
  color: var(--browse-accent, #0EA184);
}

:is(body.explore-page, .buying-shell) .browse-v5 .explore-cross-link__link {
  color: var(--browse-ink, #063E36);
  font-weight: var(--fw-bold);
}

/* 🚨 The buyer card pads TWICE. `.explore-card` already carries broker's card padding
   (14px 16px 16px), and `.explore-card__body` adds another 16px 18px 18px inside it,
   so the content box was 230px wide in a 298px card against broker's 266 -- every
   metric tile 12px narrower, and the card taller for the same content. The body keeps
   its column layout and gap; the padding belongs to the card. */
/* The body must GROW to fill the card, or the footer's `margin-top: auto` only
   reaches the bottom of the body and the last row still floats inside a stretched
   card. flex: 1 is the other half of pinning that row. */
.browse-v5 .explore-card__body {
  padding: 0;
  flex: 1 1 auto;
}

/* One card treatment on all three surfaces. `.explore-card`'s shadow came from
   `var(--shadow-card)`, which is a 1px #DDD ring in the marketing bundle and a
   1px #E8E6E1 (warm grey) ring in the buying bundle, while broker's card wears
   the dashboard's soft drop shadow. Three looks for one component, from one
   token — the same bundle trap as the map pin. Stated as a literal so no bundle
   can reinterpret it. */
/* The results row inherits `margin: 12px 2px 16px` from the marketing bundle, and
   that 2px pushed the count line ("N in this map area") 2px right of the rail every
   other element sits on. Small, but it is the line directly above the cards. */
.browse-v5 .explore-result-bar {
  margin-inline: 0;
}

/* Shadow and hover now come from the shared pair near the card block above, so this
   only has to drop the marketing bundle's border. */
.browse-v5 .explore-card {
  border: 0;
}

/* ── Nudge banner (amber) ─────────────────────────────────────────────────────
   The "your filters are too tight, here is the fix" block. Broker has had one for
   a while (.brokering-matches-nearmiss: near-miss listings just outside the zones);
   the buyer's equivalent was a single-line amber note with no title and no action,
   so the same message read as two different components. These values ARE broker's,
   copied deliberately rather than shared, because broker's live in the Tailwind
   bundle and this sheet also loads on /explore, which never loads that bundle.
   The warning tokens exist in every bundle, so the literals are only a fallback. */
.browse-nudge {
  display: flex;
  align-items: center;
  gap: 13px;
  flex-wrap: wrap;
  padding: 14px 18px;
  margin: 0 0 12px;
  border: 1px solid color-mix(in oklab, var(--text-on-warning-secondary, #A16207) 20%, transparent);
  border-radius: 14px;
  background: var(--bg-warning-secondary, #FEFCE8);
}

.browse-nudge > div { flex: 1 1 240px; }

.browse-nudge__icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--text-on-warning-secondary, #A16207);
}

.browse-nudge__title {
  margin: 0;
  font-size: var(--text-base, 0.8125rem);
  font-weight: var(--fw-semibold, 600);
  color: var(--text-on-warning-secondary, #A16207);
}

.browse-nudge__hint {
  margin: 2px 0 0;
  font-size: var(--text-sm, 0.75rem);
  line-height: 1.5;
  color: var(--text-on-warning-secondary, #A16207);
  opacity: 0.85;
}

/* Broker's .brokering-deals-btn--secondary, which is what sits in its banner. Note
   the pill radius: that class declares 8px and computes to 999px, because a later
   rule in the Tailwind bundle rounds every deals button. Measured, not read. */
.browse-nudge__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 36px;
  padding: 0 14px;
  border: 1px solid var(--border-secondary, #D4D4D4);
  border-radius: 999px;
  background: var(--base-bg-low, #fff);
  color: var(--base-text, #1F1F1F);
  font-size: var(--text-md, 0.875rem);
  font-weight: 600;
  font-family: inherit;
  white-space: nowrap;
  text-decoration: none;
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  transition: background 120ms ease, border-color 120ms ease;
}
.browse-nudge__cta:hover { border-color: var(--base-border-secondary, #BDBDBD); }
