/**
 * domovita-sold.css - sold-prices chart + partial styles (Excel-bar upgrade).
 *
 * ASSET NAME: com_domovita.sold. Every _shared/sold_* partial registers this
 * file ITSELF (guarded, idempotent registerAndUseStyle at the top of each
 * partial), so any page that renders a sold_* block gets it head-loaded with
 * no template involvement. Templates that render dv-sd-* markup of their own
 * (stats strip, sortable headers) on a page with no partial may register it
 * too - same name + version is a no-op:
 *
 *   $wa = $this->document->getWebAssetManager();
 *   $wa->registerAndUseStyle(
 *       'com_domovita.sold',
 *       'components/com_domovita/assets/css/domovita-sold.css',
 *       ['version' => '3.7.5']
 *   );
 *
 * registerAndUseStyle ONLY - a bare useStyle() on a components-copy asset name
 * throws UnknownAssetException and 500s the page (see tmpl/solddistrict/
 * default.php header comment). Version in arg 3 (options), attrs arg 4.
 * Bump the version HERE and in every partial's block together.
 *
 * WHY THIS FILE EXISTS (perf): these rules used to live in
 * tmpl/solddistrict/_styles.php, which the templates include at the FOOT of
 * the body. Foot CSS that reshapes earlier content is layout shift by
 * definition - the district page measured CLS 0.108 because chart frames,
 * aspect-ratio boxes and visually-hidden fallback tables all snapped to their
 * final geometry only when the foot styles arrived. This file is HEAD-loaded,
 * so every rule a sold_* partial depends on applies on first layout. Nothing
 * a partial renders may depend on foot CSS.
 *
 * SECTION 1 duplicates the handful of PRE-EXISTING _styles.php rules the
 * partials rely on (aspect ratios, hidden-fallback clipping, table wrap,
 * section/table basics). Values are byte-identical on purpose: the foot copy
 * re-applies the same geometry, which is a no-op, so pages that still load
 * _styles.php render identically. Keep the two in lockstep until _styles.php
 * is retired; if a value changes, change it in both.
 *
 * SECTION 2 is the former "dv-excel-upgrade" block, moved here wholesale.
 * SECTION 3 is new wave-3 work (band-share chart, share-over-time lines,
 * street volume panel, indexed frame note).
 * SECTION 6 is the wave-5 filter-scope declaration (the "not filtered" chip
 * that every non-recomputing figure carries while a data filter is active).
 *
 * Ownership: wave C2 (partials + assets). The JS contract comments reference
 * sold-charts.js / design C-E.
 */

/* ==========================================================================
   1. Head-critical duplicates of pre-existing _styles.php rules.
   Keep byte-identical with tmpl/solddistrict/_styles.php.
   ========================================================================== */

.dv-sd-section { margin-bottom: 36px; }
.dv-sd-section h2 { font-size: 1.4rem; color: var(--dv-navy); margin: 0 0 6px; font-weight: 500; }
.dv-sd-section h3 { font-size: 1.15rem; color: var(--dv-navy); margin: 0 0 12px; font-weight: 500; }
.dv-sd-section-intro, .dv-sd-pagination-info { font-size: 0.9rem; color: var(--dv-ink-2); margin: 0 0 14px; }

/* Table wrap: the mobile horizontal-scroll container. Head-loading this rule
   is the real fix for the round-1 "right-hand columns unreachable at 390px"
   finding - before the foot styles arrived, a 700px+ table inside the wrap
   could stretch the layout because overflow-x:auto had not applied yet. */
.dv-sd-table-wrap { overflow-x: auto; border: 1px solid var(--dv-hairline); border-radius: var(--dv-radius); background: var(--dv-surface); }
.dv-sd-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.dv-sd-table th { background: var(--dv-surface-1); padding: 10px 12px; text-align: left; font-weight: 500; color: var(--dv-ink); border-bottom: 1px solid var(--dv-hairline); white-space: nowrap; }
.dv-sd-table td { padding: 10px 12px; border-bottom: 1px solid var(--dv-hairline); vertical-align: top; }
.dv-sd-table tr:last-child td { border-bottom: none; }
.dv-sd-table tr:hover td { background: var(--dv-blue-tint); }
.dv-sd-table .dv-sd-num { text-align: right; white-space: nowrap; font-feature-settings: "tnum" 1; }
.dv-sd-tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 500; margin-left: 6px; vertical-align: middle; }
.dv-sd-tag-new { background: var(--dv-good-bg); color: var(--dv-good); }
.dv-sd-delta-up { color: var(--dv-good); }
.dv-sd-delta-down { color: var(--dv-bad); }

/* Chart surface. aspect-ratio MUST match each SVG's viewBox exactly - an SVG
   with only a viewBox has no intrinsic height, so without a matching
   aspect-ratio everything below it jumps on first paint (the CLS 0.615
   lesson). Base = 720/320; panel modifiers below. */
.dv-sd-chart { margin: 8px 0 24px; }
.dv-sd-chart-svg { width: 100%; height: auto; aspect-ratio: 720 / 320; display: block; border: 1px solid var(--dv-hairline); border-radius: 8px; background: var(--dv-surface); }
.dv-sd-chart-frame { min-height: 0; contain: layout; }
.dv-sd-chart-cap { font-size: .8rem; color: var(--dv-ink-2); margin-top: 6px; }

/* Apply to a WRAPPER element only - never directly to a <table>. CSS table
   layout floors the used width at min-content, so width:1px is silently
   ignored on a table (this broke /house-prices/{town} until 2026-07-29).
   Wrap the table in <div class="dv-visually-hidden">. */
.dv-visually-hidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }

/* Type mix base grid */
.dv-sd-typemix-grid { display: flex; flex-direction: column; gap: 8px; }
.dv-sd-typemix-row { display: grid; grid-template-columns: 130px 1fr 44px auto; gap: 10px; align-items: center; font-size: .95rem; }
.dv-sd-typemix-bar { background: var(--dv-surface-1); border-radius: 4px; height: 14px; overflow: hidden; }
.dv-sd-typemix-bar > span { display: block; height: 100%; background: var(--dv-blue); }
.dv-sd-typemix-pct { color: var(--dv-navy); font-weight: 500; }
.dv-sd-typemix-avg { color: var(--dv-ink-2); font-size: .85rem; }
.dv-sd-typemix-note { margin-top: 12px; color: var(--dv-ink-2); }

/* Buttons the distribution CTA uses (subset of _styles.php originals) */
.dv-sd-btn { display: inline-block; padding: 11px 22px; border-radius: var(--dv-radius); font-weight: 500; font-size: 0.95rem; text-decoration: none; transition: background 120ms ease; }
.dv-sd-btn-secondary { background: rgba(13, 33, 61, 0.55); color: #fff !important; border: 1.5px solid rgba(255, 255, 255, 0.55); }
.dv-sd-btn-secondary:hover { background: rgba(255, 255, 255, 0.25); color: #fff; text-decoration: none; }

@media (max-width: 600px) {
    .dv-sd-typemix-row { grid-template-columns: 96px 1fr 38px; }
    .dv-sd-typemix-avg { display: none; }
}

/* ==========================================================================
   2. dv-excel-upgrade block (moved verbatim from tmpl/solddistrict/
   _styles.php, wave 3). Covers the multi-panel trend chart, the per-type
   trend, the street scatter, repeat-sales, legends, tooltips, the stats
   strip and sticky/sortable table headers.
   ========================================================================== */

/* Panel aspect ratios. MUST match each SVG's viewBox exactly - the base
   .dv-sd-chart-svg rule pins 720/320 for the price/type-trend panels; these
   modifiers pin the other viewBox sizes (volume/yoy 120, distribution 300,
   scatter 360). Update viewBox + class together. */
.dv-sd-svg-120 { aspect-ratio: 720 / 120; }
.dv-sd-svg-300 { aspect-ratio: 720 / 300; }
.dv-sd-svg-360 { aspect-ratio: 720 / 360; }

/* Layout containment for every chart surface (the design rule pairs each
   aspect-ratio box with contain:layout). It sits on the chart svg itself - and
   on the typemix bar grid, the one chart drawn in HTML - NOT on the <figure>
   wrappers: contain:layout makes an element the containing block for
   FIXED-position descendants, and the FigureNoteHelper popovers inside each
   figcaption/heading are position:fixed, JS-placed in viewport coordinates -
   containing the figure would strand them. The trend figure's range frames
   keep their own contain:layout from the base block above. */
.dv-sd-chart-svg, .dv-sd-typemix-grid { contain: layout; }

/* Aligned-panel stack inside the trend figure */
.dv-sd-panel { margin: 0 0 10px; }
.dv-sd-panel-t { margin: 0 0 4px; font-size: .78rem; font-weight: 500; color: var(--dv-ink-2); text-transform: uppercase; letter-spacing: .04em; }

/* Chart header row: filtered chip + legend + range toggle share one line */
.dv-sd-chart-head { display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap; margin: 0 0 10px; }
.dv-sd-chart-head .dv-sd-chart-toolbar { margin: 0 0 0 auto; }

/* Range toggle */
.dv-sd-chart-toolbar { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }
.dv-sd-range-btn { appearance: none; -webkit-appearance: none; border: 1px solid #d9dee5; background: #fff; color: #4a5568; font: 500 12px/1.4 inherit; padding: 4px 12px; border-radius: 999px; cursor: pointer; }
.dv-sd-range-btn:hover { border-color: #2F6FCC; color: #2F6FCC; }
.dv-sd-range-btn[aria-pressed="true"] { background: #2F6FCC; border-color: #2F6FCC; color: #fff; }
/* Client-built ranges (5y/10y) ship disabled; sold-charts.js builds their
   frames from the figure's embedded JSON, then removes the attribute. */
.dv-sd-range-btn[disabled] { opacity: .45; cursor: default; }
.dv-sd-chart-frame { display: none; }
.dv-sd-chart-frame.is-active { display: block; }

/* Legend (C-E contract: <button data-dv-series> inside [data-dv-legend-for];
   sold-charts.js toggles .is-dimmed on the other series' <g> groups and keeps
   aria-pressed in sync) */
.dv-sd-legend { display: flex; align-items: center; gap: 4px 8px; flex-wrap: wrap; }
.dv-sd-legend-btn { appearance: none; -webkit-appearance: none; display: inline-flex; align-items: center; gap: 6px; border: 1px solid transparent; background: transparent; color: var(--dv-ink-2); font: 500 12px/1.4 inherit; padding: 3px 8px; border-radius: 6px; cursor: pointer; }
.dv-sd-legend-btn:hover { background: var(--dv-surface-1); color: var(--dv-navy); }
.dv-sd-legend-btn[aria-pressed="true"] { background: var(--dv-blue-tint); border-color: var(--dv-blue); color: var(--dv-navy); }
.dv-sd-legend-btn:focus-visible { outline: 2px solid var(--dv-blue); outline-offset: 2px; }
.dv-sd-legend-note { font-size: .74rem; color: var(--dv-ink-3); }
/* Series keys mirror the mark: line for lines, dot for points, dash for the
   dashed secondary, square for stacked-band swatches. Colour is set inline
   per series (identity, never rank). */
.dv-sd-key { display: inline-block; flex: none; }
.dv-sd-key-line { width: 14px; height: 3px; border-radius: 2px; }
.dv-sd-key-dash { width: 14px; height: 0; border-top: 3px dashed currentColor; }
.dv-sd-key-dot { width: 9px; height: 9px; border-radius: 50%; }
/* Legend isolate: sold-charts.js adds .is-dimmed to the non-isolated series
   groups (svg g[data-dv-series]) - marks fade, hit targets stay live. */
[data-dv-series].is-dimmed { opacity: .15; transition: opacity 120ms ease; }

/* "Filtered" chip on the trend chart when it renders a filtered subset */
.dv-sd-chip-filtered { display: inline-block; padding: 3px 10px; border-radius: 999px; background: var(--dv-blue-tint); border: 1px solid var(--dv-blue); color: var(--dv-navy); font-size: .74rem; font-weight: 600; }

/* Type mix v2: name | share bar | % | price bar | price. The head row and the
   data rows share one column template so the two bar panels stay aligned. */
.dv-sd-typemix-head { display: grid; grid-template-columns: 150px 1fr 44px 1fr auto; gap: 10px; font-size: .7rem; font-weight: 500; color: var(--dv-ink-3); text-transform: uppercase; letter-spacing: .05em; margin: 0 0 6px; }
.dv-sd-typemix-fig .dv-sd-typemix-row { grid-template-columns: 150px 1fr 44px 1fr auto; }
.dv-sd-typemix-name { display: inline-flex; align-items: center; gap: 7px; }
.dv-sd-typemix-fig { margin: 8px 0 12px; }
.dv-sd-typemix-colhead { min-width: 0; }
.dv-sd-typedetail { margin: 12px 0 0; }
.dv-sd-typedetail summary, .dv-sd-pivot summary, .dv-sd-repeat-more summary { cursor: pointer; font-size: .9rem; font-weight: 500; color: var(--dv-blue); padding: 6px 0; }
.dv-sd-typedetail > .dv-sd-table-wrap, .dv-sd-pivot > .dv-sd-table-wrap, .dv-sd-repeat-more > .dv-sd-table-wrap { margin-top: 8px; }
.dv-sd-pivot { margin: 14px 0 0; }
.dv-sd-pivot td small { color: var(--dv-ink-3); font-size: .72rem; }

/* Street scatter */
.dv-sd-scatter { margin: 8px 0 28px; }
.dv-sd-scatter h2 { font-size: 1.4rem; color: var(--dv-navy); margin: 0 0 10px; font-weight: 500; }

/* Repeat sales (S2) */
.dv-sd-repeat-table th[scope="rowgroup"] { font-weight: 500; color: var(--dv-navy); vertical-align: top; background: var(--dv-surface); border-bottom: 1px solid var(--dv-hairline); }
.dv-sd-repeat-table tbody { border-top: 2px solid var(--dv-hairline-strong, #d3d9e0); }
.dv-sd-repeat-pc { display: block; font-size: .75rem; color: var(--dv-ink-3); font-weight: 400; margin-top: 2px; }
.dv-sd-repeat-span { display: block; font-size: .72rem; color: var(--dv-ink-3); }
.dv-sd-repeat-first { color: var(--dv-ink-3); font-size: .82rem; }
.dv-sd-repeat-more { margin-top: 10px; }

/* Stats strip (T-3). The template renders #dv-sd-statsbar with one
   .dv-sd-statitem per figure, each holding a .dv-sd-statlabel and a
   <span data-dv-stat="count|median|mean|min|max">; sold-charts.js rewrites
   the values on filter change. */
#dv-sd-statsbar { display: flex; flex-wrap: wrap; gap: 6px 26px; padding: 12px 16px; margin: 0 0 16px; background: var(--dv-surface-1); border: 1px solid var(--dv-hairline); border-radius: 10px; }
#dv-sd-statsbar .dv-sd-statitem { display: flex; flex-direction: column; gap: 1px; }
#dv-sd-statsbar .dv-sd-statlabel { font-size: .68rem; font-weight: 500; color: var(--dv-ink-3); text-transform: uppercase; letter-spacing: .05em; }
#dv-sd-statsbar [data-dv-stat] { font-size: 1.05rem; font-weight: 500; color: var(--dv-navy); font-feature-settings: "tnum" 1; }
#dv-sd-statsbar.is-loading [data-dv-stat] { opacity: .45; }

/* Sortable headers (T-1). The th text sits in
   <button class="dv-sd-sortbtn" data-dv-sort-field="price|date">; the th
   carries aria-sort, which draws the direction arrow. */
.dv-sd-sortbtn { appearance: none; -webkit-appearance: none; border: 0; background: transparent; padding: 0; font: inherit; font-weight: 500; color: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; }
.dv-sd-sortbtn:hover { color: var(--dv-blue); }
.dv-sd-sortbtn:focus-visible { outline: 2px solid var(--dv-blue); outline-offset: 2px; border-radius: 3px; }
.dv-sd-sortbtn::after { content: "\2195"; font-size: .72em; opacity: .45; }
th[aria-sort="ascending"] .dv-sd-sortbtn::after { content: "\2191"; opacity: 1; color: var(--dv-blue); }
th[aria-sort="descending"] .dv-sd-sortbtn::after { content: "\2193"; opacity: 1; color: var(--dv-blue); }

/* Sticky header (T-7). The template adds --sticky to the table wrap.
   overflow-x becomes clip (not auto) because a scroll container swallows
   position:sticky - the sales tables already hide their overflow-prone
   columns on mobile, so horizontal scrolling is not load-bearing where this
   modifier is applied. */
.dv-sd-table-wrap--sticky { overflow-x: clip; }
.dv-sd-table-wrap--sticky th { position: sticky; top: 0; z-index: 2; }

/* Shared tooltip singleton (C-E). sold-charts.js creates ONE .dv-sd-tip div,
   absolutely positioned, aria-hidden, pointer-events none, and fills it from
   any element's data-dv-tip JSON: {"t":"Title","rows":[["Label","Value"],..]}.
   Values lead, labels follow (.dv-sd-tip-v is the strong element). */
.dv-sd-tip { position: fixed; top: 0; left: 0; z-index: 2147483000; max-width: min(320px, 88vw); padding: 10px 12px; border-radius: 8px; background: var(--dv-navy, #1F3A5F); color: #fff; font-size: .8rem; line-height: 1.45; box-shadow: 0 6px 22px rgba(0,0,0,.28); pointer-events: none; }
.dv-sd-tip[aria-hidden="true"] { display: none; }
.dv-sd-tip-t { display: block; font-weight: 600; margin-bottom: 4px; }
.dv-sd-tip-row { display: flex; justify-content: space-between; gap: 14px; }
.dv-sd-tip-row .dv-sd-tip-l { color: rgba(255,255,255,.75); }
.dv-sd-tip-row .dv-sd-tip-v { font-weight: 600; font-feature-settings: "tnum" 1; }

/* Export link (T-5). Rendered beside the filter bar; keeps working as a
   plain link without JS. */
a.dv-sd-export { display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border: 1.5px solid var(--dv-blue); border-radius: var(--dv-radius, 6px); color: var(--dv-blue) !important; font-size: .88rem; font-weight: 500; text-decoration: none; background: transparent; }
a.dv-sd-export:hover { background: var(--dv-blue-tint); text-decoration: none; }

/* Drill-through affordance: clickable marks get a pointer + focus ring */
[data-dv-drill-year], [data-dv-drill-min], [data-dv-drill-max] { cursor: pointer; }

/* Mobile */
@media (max-width: 700px) {
    .dv-sd-chart-head { gap: 6px 10px; }
    .dv-sd-chart-head .dv-sd-chart-toolbar { margin-left: 0; }
}
@media (max-width: 600px) {
    /* The base 600px rule collapses .dv-sd-typemix-row to 3 columns (name, bar,
       %); hide the price bar to match and keep the head row in step. */
    .dv-sd-typemix-fig .dv-sd-typemix-row, .dv-sd-typemix-head { grid-template-columns: 96px 1fr 38px; }
    .dv-sd-typemix-pricebar, .dv-sd-typemix-head .dv-sd-typemix-colhead:nth-child(4) { display: none; }
    .dv-sd-repeat-table th[scope="rowgroup"] { font-size: .8rem; }
}

/* Distribution figure CTA (moved from the partial's former inline <style>) */
.dv-sd-dist .dv-sd-dist-cta { margin: 14px 0 0; }
.dv-sd-dist .dv-sd-dist-cta a { display: inline-block; }

/* ==========================================================================
   3. Wave-3 additions: band-share chart, share-over-time lines, street
   volume panel, indexed frame.
   ========================================================================== */

/* Square legend swatch (price-band stacked columns) */
.dv-sd-key-sq { width: 10px; height: 10px; border-radius: 2px; }

/* Small explanatory note rendered INSIDE a chart frame (the indexed frame
   states its base year and why indexing is offered) */
.dv-sd-frame-note { margin: 4px 0 8px; font-size: .78rem; color: var(--dv-ink-2); }

/* Share-over-time figure (new-build / leasehold share lines) */
.dv-sd-extras { margin: 18px 0 0; }

/* Compact overflow rows in the repeat-sales details (one row per address,
   the sale history as text in a single cell) */
.dv-sd-repeat-compact th[scope="row"] { font-weight: 500; color: var(--dv-navy); vertical-align: top; background: var(--dv-surface); }
.dv-sd-repeat-compact .dv-sd-repeat-hist { font-size: .85rem; line-height: 1.5; }

/* ==========================================================================
   4. Wave-3b integrator additions: median-first hero answer, histogram
   window toggle, hub district league table.
   ========================================================================== */

/* Secondary (mean) sentence inside the hero answer paragraph - present but
   visibly subordinate to the median headline above it */
.dv-sd-answer-secondary { display: block; margin-top: 6px; font-size: .88em; color: var(--dv-ink-2); font-weight: 400; }
/* CONTRAST FIX 2026-08-22. `.dv-sd-answer-secondary` lives inside `.dv-sd-hero`,
   which paints #1F3A5F - but it was coloured with --dv-ink-2 (#5F5E5A), a token
   meant for LIGHT surfaces. Measured on live: 1.77:1 against a 4.5:1 minimum,
   i.e. effectively unreadable. It carries the mean-vs-median caveat, which is
   exactly the sort of qualifier a reader has to be able to see (YMYL).
   Pre-existing, and present on soldstreet, soldtown AND solddistrict - the whole
   sold-price family, ~93% of organic traffic.
   Scoped to the hero so the base rule above still governs any future use on a
   light surface. The descendant selector also catches the FigureNoteHelper tip
   glyph, which inherited the same failing colour. Now 7.49:1. */
.dv-sd-hero .dv-sd-answer-secondary,
.dv-sd-hero .dv-sd-answer-secondary * { color: var(--dv-on-navy-mute, #B5D4F4); }

/* Histogram window toggle (plain GET links, outside the filter form) */
.dv-sd-months-toggle { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 18px 0 0; min-height: 30px; font-size: .85rem; }
.dv-sd-months-toggle .dv-sd-months-label { color: var(--dv-ink-2); }
.dv-sd-months-toggle a,
.dv-sd-months-toggle strong { display: inline-flex; align-items: center; padding: 4px 10px; border: 1px solid var(--dv-hairline); border-radius: 4px; background: var(--dv-surface-1); color: var(--dv-navy); text-decoration: none; font-weight: 400; }
.dv-sd-months-toggle a:hover { background: var(--dv-blue-tint); }
.dv-sd-months-toggle a:focus-visible { outline: 2px solid var(--dv-blue); outline-offset: 2px; }
.dv-sd-months-toggle strong[aria-current] { background: var(--dv-navy); color: var(--dv-on-navy); border-color: var(--dv-navy); }

/* Hub district league table (N1). The wrap adds VERTICAL scroll (Excel
   freeze-panes feel) on top of the base .dv-sd-table-wrap horizontal
   overflow; the sticky header needs the scroll container, not the page. */
.dv-sd-league-wrap { max-height: 70vh; overflow-y: auto; }
.dv-sd-league-wrap th { position: sticky; top: 0; z-index: 2; }
.dv-sd-league th .dv-sd-idxsort-btn { background: transparent; border: none; padding: 0; font: inherit; font-weight: 500; color: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; }
.dv-sd-league th .dv-sd-idxsort-btn:focus-visible { outline: 2px solid var(--dv-blue); outline-offset: 2px; }
.dv-sd-league th .dv-sd-idxsort-btn.is-active { color: var(--dv-blue); }
.dv-sd-league th .dv-sd-idxsort-btn.is-active[data-dv-idx-dir="asc"]::after { content: "\25B2"; font-size: .7em; }
.dv-sd-league th .dv-sd-idxsort-btn.is-active[data-dv-idx-dir="desc"]::after { content: "\25BC"; font-size: .7em; }
.dv-sd-league .dv-sd-league-thin { color: var(--dv-ink-3); font-size: .78em; white-space: nowrap; }

/* ==========================================================================
   5. Wave-4 additions: head-critical hero duplicates, growth mix-shift note,
   town sub-area bars (moved from the soldtown inline <style>), league CSV
   button and league-extras figures.
   ========================================================================== */

/* Head-critical duplicates of the _styles.php HERO rules (source:
   tmpl/solddistrict/_styles.php "Hero - navy data panel" block). Byte-identical
   on purpose - the foot copy re-applies the same values, a no-op - so the
   median-first hero answer never depends on foot CSS for its first paint.
   Keep the two copies in lockstep until _styles.php is retired; if a value
   changes, change it in both. */
.dv-sd-hero { position: relative; background: var(--dv-navy); border-radius: var(--dv-radius-card); padding: 26px 28px 24px; margin-bottom: 14px; }
.dv-sd-hero::before { content: "Sold prices"; display: block; font-size: 12px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--dv-gold); margin-bottom: 8px; }
.dv-sd-hero h1 { font-size: 2rem; color: var(--dv-on-navy); margin: 0 0 8px; line-height: 1.2; font-weight: 500; }
.dv-sd-hero-sub { color: var(--dv-on-navy-mute); margin: 0; font-size: 1.05rem; line-height: 1.5; max-width: 720px; }
.dv-sd-hero-sub strong { color: var(--dv-on-navy); font-weight: 500; }
.dv-sd-hero-sub a, .dv-sd-answer a { color: var(--dv-on-navy) !important; text-decoration: underline; text-underline-offset: 2px; }
.dv-sd-hero-sub a:hover, .dv-sd-answer a:hover { color: #fff !important; }
.dv-sd-answer { color: var(--dv-on-navy); margin: 0 0 10px; font-size: 1.1rem; line-height: 1.55; max-width: 720px; }

/* Growth-badge mix-shift note (honesty rule 9): plain-words caveat under the
   median-on-median growth line, linking to the by-type chart where present. */
.dv-sd-growth-note { display: block; flex-basis: 100%; font-size: .8rem; color: var(--dv-ink-2); }
.dv-sd-growth-note a { color: var(--dv-blue); }

/* T1 sub-area bars (soldtown). MOVED VERBATIM from the template's inline
   <style> block (wave-3b) into this head-loaded file - the shared stylesheet
   is registered by the template and every partial, so the rules now apply on
   first layout instead of at the template's own inline block. */
.dv-sd-subarea-rows { list-style: none; margin: 0; padding: 0; }
.dv-sd-subarea-rows li { display: grid; grid-template-columns: 4.5em minmax(60px, 1fr) minmax(11em, auto); gap: 4px 10px; align-items: center; padding: 5px 0; border-bottom: 1px solid #eef1f4; }
.dv-sd-subarea-d { font-weight: 600; }
.dv-sd-subarea-track { display: block; height: 14px; background: #f1f4f8; border-radius: 3px; overflow: hidden; }
.dv-sd-subarea-bar { display: block; height: 100%; background: #2F6FCC; border-radius: 3px 0 0 3px; }
.dv-sd-subarea-val { font-variant-numeric: tabular-nums; white-space: nowrap; font-size: .92em; }
.dv-sd-subarea-n { color: #667; font-size: .85em; }
.dv-sd-subarea-none { color: #667; font-size: .85em; white-space: normal; }
.dv-sd-subarea-more summary { cursor: pointer; margin: 8px 0; color: #1F3A5F; }
@media (max-width: 560px) {
    .dv-sd-subarea-rows li { grid-template-columns: 4em 1fr; }
    .dv-sd-subarea-rows li .dv-sd-subarea-val { grid-column: 2; }
}

/* League CSV download (index scope, wave 4). Button (not <a>): the CSV is
   built client-side from the embedded league dataset, so it ships [hidden]
   and is revealed only when JS + a parsed dataset exist - a dead control is
   worse than none. Styled to match a.dv-sd-export. */
.dv-sd-idxcsv { display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border: 1.5px solid var(--dv-blue); border-radius: var(--dv-radius, 6px); background: transparent; color: var(--dv-blue); font-size: .88rem; font-weight: 500; cursor: pointer; }
.dv-sd-idxcsv:hover { background: var(--dv-blue-tint); }
.dv-sd-idxcsv:focus-visible { outline: 2px solid var(--dv-blue); outline-offset: 2px; }
.dv-sd-league-actions { margin: 10px 0 0; }

/* League-extras figures (N2 uses per-panel inline aspect-ratio because the
   bar-panel height follows its row count; N3/N4 reuse the fixed
   .dv-sd-svg-300 / .dv-sd-svg-360 boxes - viewBox and aspect-ratio MUST stay
   matched, per the CLS rule at the top of section 1). */

/* ==========================================================================
   6. Filter-scope declarations (round-4 defect 1).
   Two charts on a filtered district/town page recompute and badge themselves
   "Filtered results"; the rest cannot recompute without a per-request query on
   a 24h edge-cached page. Silence next to a badge reads as "this one follows
   the filters too", so every non-recomputing figure now carries the OPPOSITE
   declaration in the SAME slot. The two chips must never be mistaken for one
   another, so they differ in colour AND shape: the filtered chip is a blue
   pill, this one is an amber square-cornered tag with a heavy left rule.
   ========================================================================== */
.dv-sd-chip-unfiltered { display: inline-block; padding: 3px 10px 3px 8px; border-radius: 4px; background: #fdf1dc; border: 1px solid #c47f00; border-left: 5px solid #c47f00; color: #6b4400; font-size: .74rem; font-weight: 700; }

/* Wrapper for blocks that are not <figure>s (movers, the town sub-area bars):
   the chip sits immediately above the block's own heading, same reading slot. */
.dv-sd-scopeflag { margin: 18px 0 -6px; }

/* The caption clause that says, in words, what the chip says as a badge. */
.dv-sd-scope-note { display: block; margin-top: 4px; color: #6b4400; font-weight: 600; }

/* The declaration is pre-rendered in BOTH states and toggled by the [hidden]
   attribute, so an AJAX filter (district/town, no page reload) can reveal it
   the moment the recomputing charts gain their "Filtered results" chip. These
   elements all set an explicit display, which beats the UA's [hidden] rule -
   so restate it here or "hidden" would be decorative. */
.dv-sd-chip-unfiltered[hidden],
.dv-sd-scope-note[hidden],
.dv-sd-scopeflag[hidden],
.dv-sd-chart-head[hidden] { display: none !important; }

/* A figure that could not be drawn has no results to badge. When the histogram
   or the trend chart falls through to its filtered empty state (a gate refusal
   on a large scope, e.g. type=D over a whole town), the partial emits the blue
   "Filtered results" pill above a .dv-card-missing message that already names
   the filters - so the figure reads "here are your filtered results" at a
   glance while showing no results at all. The AJAX path already drops the chip
   in that state (sold-charts.js emptyChartState() clears the figure before
   writing the message), so this only makes the server render agree with it.
   Presentational suppression on purpose: the sentence inside the card carries
   the filter fact, and a browser without :has() keeps the current behaviour. */
.dv-sd-chart:has(> .dv-card-missing) > .dv-sd-chip-filtered { display: none; }

/* Stats-strip label hint marker (2026-08-14): distinguishes the all-years
   median in this strip from the recent-months median in the hero tile. */
.dv-sd-stat-hint {
    display: inline-flex; align-items: center; justify-content: center;
    width: 13px; height: 13px; margin-left: 4px; border-radius: 50%;
    background: #e2e6ea; color: #555; font-size: 9px; font-style: normal;
    font-weight: 600; cursor: help; vertical-align: text-top;
}

/* Light-surface variant of the secondary button (2026-08-14 bug fix).
   .dv-sd-btn-secondary is styled for the NAVY CTA panel: white text on a
   translucent dark fill, hover LIGHTENS the fill. On a white card that hover
   turns the button white-on-white and the label vanishes - reported on the
   "Wondering what yours has done?" and "Where would yours sit?" CTAs. This
   variant inverts the logic for light surfaces: dark ink, hairline border,
   hover darkens. Keep in lockstep with the copy in solddistrict/_styles.php. */
.dv-sd-btn-onlight {
    background: var(--dv-surface, #fff);
    color: var(--dv-navy, #1F3A5F) !important;
    border: 1.5px solid var(--dv-hairline-strong, #C7CDD4);
}
.dv-sd-btn-onlight:hover,
.dv-sd-btn-onlight:focus-visible {
    background: var(--dv-navy, #1F3A5F);
    color: #fff !important;
    border-color: var(--dv-navy, #1F3A5F);
    text-decoration: none;
}

/* ==========================================================================
   7. Consolidated capture card (_shared/sold_capture.php, 2026-08-14).
   Replaces the two stacked email asks the sold detail pages used to carry
   (sold_alert_capture.php + area_report_cta.php) with one card and a radio
   group. Both of those partials shipped their own foot-of-body <style> block;
   these rules live here instead because the card renders ABOVE the sales table
   and reshaping it after the fact is the CLS defect this file exists to fix.

   The radios are the load-bearing part. Each choice is a full-width clickable
   label with a >=44px hit area (WCAG 2.5.8 / Apple's 44pt guidance) so it is
   usable with a thumb, the whole row highlights on hover and when checked, and
   :focus-within puts a real ring on the row rather than a 13px dot the eye
   cannot find. Keyboard, pointer and touch all get the same target.
   ========================================================================== */
.dv-cap { margin: 28px 0; }
.dv-cap__inner {
    background: #f6f9fd; border: 1px solid #dbe3ef; border-radius: 12px;
    padding: 22px 24px;
}
.dv-cap__heading { margin: 0 0 6px; font-size: 1.2rem; color: #1F3A5F; }
.dv-cap__lede { margin: 0 0 14px; font-size: .95rem; color: #445b7a; line-height: 1.45; }

/* Choice group. A fieldset carries the grouping for assistive tech; its
   default border/padding are cleared because the card already frames it. */
.dv-cap__choices { border: 0; margin: 0 0 14px; padding: 0; min-width: 0; }
.dv-cap__legend {
    padding: 0; margin: 0 0 8px; font-size: .85rem; font-weight: 700;
    color: #1F3A5F; letter-spacing: .01em;
}
.dv-cap__choice {
    display: flex; align-items: flex-start; gap: 12px;
    min-height: 44px; padding: 11px 14px; margin: 0 0 8px;
    background: #ffffff; border: 1px solid #c5d0e0; border-radius: 8px;
    cursor: pointer; line-height: 1.4;
    transition: border-color .12s, background .12s, box-shadow .12s;
}
.dv-cap__choice:last-child { margin-bottom: 0; }
.dv-cap__choice:hover { border-color: #2F6FCC; background: #fbfdff; }
.dv-cap__choice input[type="radio"] {
    flex: 0 0 auto; width: 20px; height: 20px; margin: 1px 0 0;
    accent-color: #2F6FCC; cursor: pointer;
}
/* The ring goes on the ROW, not the 20px dot - a focus indicator you have to
   hunt for is not a focus indicator. Keyboard focus and :has() checked state
   are styled separately so a mouse click does not leave a ring behind.
   The control keeps an explicit outline of its own as well: the row ring is
   the nice-to-have, the outline is the guarantee, and a keyboard user must
   never be left hunting for the caret if one of the two is unsupported. */
.dv-cap__choice:focus-within {
    border-color: #2F6FCC; box-shadow: 0 0 0 3px rgba(47,111,204,.28);
}
.dv-cap__choice input[type="radio"]:focus-visible,
.dv-cap__optin input[type="checkbox"]:focus-visible {
    outline: 3px solid #1E5BB8; outline-offset: 2px;
}
.dv-cap__choice:has(input[type="radio"]:checked) {
    border-color: #2F6FCC; background: #eef4fd; box-shadow: inset 0 0 0 1px #2F6FCC;
}
.dv-cap__choice-text { display: block; }
.dv-cap__choice-title { display: block; font-size: .95rem; color: #1F3A5F; font-weight: 600; }
.dv-cap__choice-note { display: block; margin-top: 3px; font-size: .82rem; color: #5a6f8c; }

/* Email + submit. Mirrors the row the alert capture used, so the visual weight
   of the ask is unchanged from the block it replaces. */
.dv-cap__row { display: flex; gap: 10px; flex-wrap: wrap; }
.dv-cap__field { flex: 1 1 220px; margin: 0; }
.dv-cap__field input[type="email"] {
    /* min-height matches the 44px touch target the radios and submit hold;
       without it this input measured 38px at 390px (round-2 critic). */
    width: 100%; box-sizing: border-box; padding: 12px 14px; min-height: 44px;
    border: 1px solid #c5d0e0; border-radius: 8px; font-size: 1rem;
    background: #ffffff; color: #1F3A5F;
}
.dv-cap__field input[type="email"]:focus {
    outline: none; border-color: #2F6FCC; box-shadow: 0 0 0 3px rgba(47,111,204,.15);
}
.dv-cap__submit {
    min-height: 44px; padding: 12px 22px; background: #2F6FCC; color: #ffffff;
    border: 0; border-radius: 8px; font-weight: 500; font-size: 1rem;
    cursor: pointer; transition: background .15s;
}
.dv-cap__submit:hover { background: #1E5BB8; }
.dv-cap__submit:focus-visible { outline: 3px solid #1E5BB8; outline-offset: 2px; }

/* Optional, never pre-ticked marketing opt-in. Visually quieter than the
   choices on purpose - it is not what the visitor came for. */
.dv-cap__optin {
    display: flex; align-items: flex-start; gap: 9px; margin: 0 0 10px;
    min-height: 44px; padding: 2px 0;
    font-size: .85rem; color: #445b7a; line-height: 1.4; cursor: pointer;
}
.dv-cap__optin input { width: 18px; height: 18px; margin-top: 2px; flex-shrink: 0; accent-color: #2F6FCC; }
.dv-cap__privacy { margin: 10px 0 0; font-size: .8rem; color: #5a6f8c; line-height: 1.4; }
.dv-cap__privacy a { color: #2F6FCC; }

.dv-cap__error { margin: 0 0 12px; color: #a4262c; font-size: .9rem; font-weight: 600; }
.dv-cap__success {
    background: #e8f5ea; border: 1px solid #a8d5af; color: #2b6f38;
    padding: 14px 18px; border-radius: 8px; font-weight: 500;
}

/* Honeypot + visually-hidden label. Same recipe the partials this replaces
   used, so bots see no change in shape. */
.dv-cap__hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.dv-cap__sr {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Compact variant: the second, slimmer appearance lower down the page (where
   the area-report CTA used to sit). Smaller heading, tighter rows, no lede -
   the full card above has already made the case. Touch targets are NOT
   shrunk; only the chrome around them is. */
.dv-cap--compact { margin: 22px 0; }
.dv-cap--compact .dv-cap__inner { padding: 16px 18px; }
.dv-cap--compact .dv-cap__heading { font-size: 1.02rem; margin-bottom: 10px; }
.dv-cap--compact .dv-cap__legend { font-size: .8rem; margin-bottom: 6px; }
.dv-cap--compact .dv-cap__choice { padding: 9px 12px; margin-bottom: 6px; }
.dv-cap--compact .dv-cap__choice-title { font-size: .9rem; }
/* The compact card drops the descriptions to stay slim - but NOT all of them.
   The valuation note is the only thing that explains why an email field sits
   under an option that does not use one, so it is marked --keep and stays;
   hiding it left the compact card asking for an address it would never send
   anything to, with no explanation. */
.dv-cap--compact .dv-cap__choice-note { display: none; }
.dv-cap--compact .dv-cap__choice-note--keep { display: block; font-size: .78rem; margin-top: 2px; }
.dv-cap--compact .dv-cap__privacy { font-size: .76rem; }

@media (max-width: 600px) {
    .dv-cap__inner { padding: 18px 16px; }
    .dv-cap__row { flex-direction: column; }
    /* flex-basis follows the MAIN AXIS: once the row becomes a column the
       220px basis above is a HEIGHT, which left a ~190px void between the
       email box and the button on every phone. Reset it here. */
    .dv-cap__field { flex: 0 0 auto; }
    .dv-cap__submit { width: 100%; }
}

/* ====================================================================
   SOLD-PAGE LAYOUT (moved here 2026-08-14 from tmpl/solddistrict/_styles.php).
   These rules style the whole sold page - breadcrumb, hero, tiles, tables.
   They used to ship as a 25KB inline <style> included at the FOOT of every
   sold template. That was survivable while the pages were ~570KB; once they
   grew past 600KB the block began arriving after first paint and the entire
   content column reflowed - live CLS 0 -> 0.144 (district) and 0.174
   (street) on 2026-08-14. Head-loading them is the fix. _styles.php remains
   at its old path and include sites, but now only registers this asset.
   ==================================================================== */
/* Sold-prices shared styles - district / town / street / index views.
   Design-direction 2026-07-14 (Phase 3a): navy data-panel header, metric tiles
   on a dark surface, gold conversion strip, hairlines + tokens throughout. */
.dv-sd-page, .dv-sd-index { max-width: 1100px; margin: 0 auto; padding: 24px 16px; color: var(--dv-ink); }

/* Breadcrumb */
.dv-sd-breadcrumb { font-size: 0.85rem; color: var(--dv-ink-3); margin-bottom: 14px; }
.dv-sd-breadcrumb a { color: var(--dv-blue); text-decoration: none; }
.dv-sd-breadcrumb a:hover { text-decoration: underline; }
.dv-sd-breadcrumb span { margin: 0 6px; color: var(--dv-ink-3); }
.dv-sd-breadcrumb strong { color: var(--dv-ink); font-weight: 500; }

/* Hero - navy data panel with a gold eyebrow */
.dv-sd-hero { position: relative; background: var(--dv-navy); border-radius: var(--dv-radius-card); padding: 26px 28px 24px; margin-bottom: 14px; }
.dv-sd-hero::before { content: "Sold prices"; display: block; font-size: 12px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--dv-gold); margin-bottom: 8px; }
.dv-sd-hero h1 { font-size: 2rem; color: var(--dv-on-navy); margin: 0 0 8px; line-height: 1.2; font-weight: 500; }
.dv-sd-hero-sub { color: var(--dv-on-navy-mute); margin: 0; font-size: 1.05rem; line-height: 1.5; max-width: 720px; }
.dv-sd-hero-sub strong { color: var(--dv-on-navy); font-weight: 500; }
/* 2026-07-20: links inside the navy hero had NO rule, so they inherited the global
   link colour (--dv-navy) and rendered navy-on-navy - invisible. On the CB20 street
   page that left a literal blank gap mid-sentence: "recorded on West Road in the
   [   ] postcode district". Same class as the 2026-07-14 invisible-CTA fix below,
   which patched the button but not the hero copy. Underlined so it still reads as a
   link against surrounding body text.
   color !important for the same reason as .dv-sd-cta-owner below: the Helix rule
   body.com-domovita #sp-component > .sp-column a:not(.btn) carries an ID selector and
   outranks a two-class rule, forcing link text back to --dv-navy. Verified in-browser:
   without !important the underline applied but the colour did not. */
.dv-sd-hero-sub a, .dv-sd-answer a { color: var(--dv-on-navy) !important; text-decoration: underline; text-underline-offset: 2px; }
.dv-sd-hero-sub a:hover, .dv-sd-answer a:hover { color: #fff !important; }
.dv-sd-answer { color: var(--dv-on-navy); margin: 0 0 10px; font-size: 1.1rem; line-height: 1.55; max-width: 720px; }

/* Metric strip - tiles on a navy-raised surface, split by 1px light rules */
.dv-sd-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 0; margin: 0 0 32px; border-radius: var(--dv-radius-card); overflow: hidden; background: var(--dv-navy-raised); }
.dv-sd-stat { background: var(--dv-navy-raised); padding: 18px 20px; border-left: 1px solid rgba(255,255,255,0.10); }
.dv-sd-stat:first-child { border-left: none; }
.dv-sd-stat-label { font-size: 0.72rem; text-transform: uppercase; color: var(--dv-on-navy-mute); font-weight: 500; letter-spacing: 0.06em; }
.dv-sd-stat-value { font-size: 1.75rem; color: var(--dv-on-navy); font-weight: 500; margin-top: 6px; line-height: 1.1; font-feature-settings: "tnum" 1; }
.dv-sd-stat-note { font-size: 0.75rem; color: var(--dv-on-navy-faint); margin-top: 4px; }

/* Sections */
.dv-sd-section { margin-bottom: 36px; }
.dv-sd-section h2 { font-size: 1.4rem; color: var(--dv-navy); margin: 0 0 6px; font-weight: 500; }
.dv-sd-section h3 { font-size: 1.15rem; color: var(--dv-navy); margin: 0 0 12px; font-weight: 500; }
.dv-sd-section-intro, .dv-sd-pagination-info { font-size: 0.9rem; color: var(--dv-ink-2); margin: 0 0 14px; }

/* Table - hairline framed, no shadow */
.dv-sd-table-wrap { overflow-x: auto; border: 1px solid var(--dv-hairline); border-radius: var(--dv-radius); background: var(--dv-surface); }
.dv-sd-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.dv-sd-table th { background: var(--dv-surface-1); padding: 10px 12px; text-align: left; font-weight: 500; color: var(--dv-ink); border-bottom: 1px solid var(--dv-hairline); white-space: nowrap; }
.dv-sd-table td { padding: 10px 12px; border-bottom: 1px solid var(--dv-hairline); vertical-align: top; }
.dv-sd-table tr:last-child td { border-bottom: none; }
.dv-sd-table tr:hover td { background: var(--dv-blue-tint); }
/* Sale rows link through to the pre-filled buyer report. The blue hover above now
   signals a real destination (previously a false affordance -> dead clicks). */
.dv-sd-table tr[data-href] { cursor: pointer; }
.dv-sd-row-link { color: var(--dv-navy); text-decoration: none; }
.dv-sd-table tr:hover .dv-sd-row-link { color: var(--dv-blue) !important; text-decoration: underline; }
.dv-sd-row-link:focus-visible { outline: 2px solid var(--dv-blue); outline-offset: 2px; border-radius: 2px; }
/* Row destination hint (2026-07-19). The whole row navigates to the free property
   report, but nothing said so - GA4 showed 94% of report-page traffic arriving from
   these rows against ~1 click on the page's own CTA button, i.e. the clicks were
   mostly accidental. Naming the destination makes the click intentional. Muted by
   default, brightens with the row hover that already exists. */
/* 2026-08-01: was color:--dv-ink-3 at 0.85 opacity, turning blue only on
   `tr:hover`. Mobile has no hover, so on the touch traffic that dominates
   this page family the highest-frequency onward link in the site (94% of
   /property-report arrivals come from these rows) rendered permanently as
   grey metadata and read as another data column - flagged independently by
   two UX critics, and measured at 3.61:1 by the contrast scanner. Blue by
   default: 4.85:1 on white, passes AA, and the affordance no longer depends
   on a pointer that half the audience does not have. */
.dv-sd-row-cta { display: inline-block; margin-left: 8px; font-size: 0.72rem; font-weight: 600; color: var(--dv-blue, #2F6FCC); white-space: nowrap; opacity: 1; }
.dv-sd-table tr:hover .dv-sd-row-cta { color: var(--dv-blue, #2F6FCC); opacity: 1; text-decoration: underline; }
@media (max-width: 640px) { .dv-sd-row-cta { display: block; margin-left: 0; margin-top: 2px; } }
/* Row-click loading overlay (2026-07-19). The value-first report is built during
   the navigation away from this page (~8.7s cold, ~4s warm), so the only place
   feedback can live is here. Raised on click, carried away by the navigation. */
.dv-sold-loading { position: fixed; inset: 0; z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(19, 39, 66, 0.92); }
.dv-sold-loading[hidden] { display: none; }
.dv-sold-loading-card { display: flex; flex-direction: column; align-items: center; gap: 10px; background: #fff; border-radius: 14px; padding: 28px 26px; max-width: 340px; text-align: center; }
.dv-sold-loading-title { font-size: 1.05rem; color: #132742; }
.dv-sold-loading-stage { font-size: 0.9rem; color: #2F6FCC; font-weight: 500; min-height: 1.3em; }
.dv-sold-loading-note { font-size: 0.8rem; color: #64748b; }
.dv-sold-loading-spinner { width: 34px; height: 34px; border-radius: 50%; border: 3px solid #dbe5f1; border-top-color: #2F6FCC; animation: dvSoldSpin 0.9s linear infinite; }
@keyframes dvSoldSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .dv-sold-loading-spinner { animation-duration: 2.4s; } }
.dv-sd-table .dv-sd-num { text-align: right; white-space: nowrap; font-feature-settings: "tnum" 1; }
.dv-sd-town { font-size: 0.78rem; color: var(--dv-ink-3); }
/* Year sub-label under a price in the "sold again" movers table. Matches
   .dv-sd-delta below (same block sub-label shape) rather than inventing tokens. */
.dv-sd-movers .dv-sd-movers-yr { display: block; font-size: 0.75rem; color: var(--dv-ink-3); line-height: 1.2; font-feature-settings: "tnum" 1; }
.dv-sd-tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 500; margin-left: 6px; vertical-align: middle; }
.dv-sd-tag-new { background: var(--dv-good-bg); color: var(--dv-good); }
/* Previous-sale delta (Phase 3c) - green up / red down, muted "first sale" */
.dv-sd-delta { display: block; font-size: 0.75rem; margin-top: 2px; font-feature-settings: "tnum" 1; }
.dv-sd-delta-up { color: var(--dv-good); }
.dv-sd-delta-down { color: var(--dv-bad); }
.dv-sd-delta-first { color: var(--dv-ink-3); }

/* Pagination */
.dv-sd-pagination { display: flex; align-items: center; justify-content: center; gap: 18px; margin-top: 18px; font-size: 0.95rem; flex-wrap: wrap; }
.dv-sd-pagination a { color: var(--dv-blue); text-decoration: none; padding: 8px 14px; border: 1px solid var(--dv-hairline); border-radius: 6px; transition: background 120ms ease; }
.dv-sd-pagination a:hover { background: var(--dv-blue-tint); }
.dv-sd-page-current { color: var(--dv-ink-2); font-weight: 500; }

/* Streets list */
.dv-sd-streets { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
.dv-sd-street { display: block; background: var(--dv-surface-1); border: 1px solid var(--dv-hairline); border-radius: 8px; padding: 12px 14px; text-decoration: none; transition: border-color .15s; }
a.dv-sd-street:hover { border-color: var(--dv-blue); }
.dv-sd-street strong { display: block; color: var(--dv-navy); font-size: 0.95rem; margin-bottom: 2px; font-weight: 500; }
.dv-sd-street span { font-size: 0.8rem; color: var(--dv-ink-2); }

/* CTA (generic blue) */
.dv-sd-cta {
    background: linear-gradient(135deg, var(--dv-blue) 0%, var(--dv-btn-hover) 100%);
    color: #fff; border-radius: var(--dv-radius-card); padding: 24px 28px; margin: 32px 0;
    display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
}
.dv-sd-cta-text { flex: 1; min-width: 240px; }
.dv-sd-cta h3 { color: #fff; margin: 0 0 6px; font-size: 1.25rem; font-weight: 500; }
.dv-sd-cta p { margin: 0; color: rgba(255, 255, 255, 0.92); line-height: 1.5; }
.dv-sd-cta-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.dv-sd-btn { display: inline-block; padding: 11px 22px; border-radius: var(--dv-radius); font-weight: 500; font-size: 0.95rem; text-decoration: none; transition: background 120ms ease; }
.dv-sd-btn-primary { background: #fff; color: var(--dv-blue); }
.dv-sd-btn-primary:hover { background: var(--dv-blue-tint); color: var(--dv-blue); text-decoration: none; }
.dv-sd-btn-secondary { background: rgba(13, 33, 61, 0.55); color: #fff !important; border: 1.5px solid rgba(255, 255, 255, 0.55); }
.dv-sd-btn-secondary:hover { background: rgba(255, 255, 255, 0.25); color: #fff; text-decoration: none; }

/* Owner valuation CTA - the ONE gold element on the page */
.dv-sd-cta-owner { background: var(--dv-gold); margin: 0 0 36px; }
.dv-sd-cta-owner h3 { color: var(--dv-gold-ink); }
.dv-sd-cta-owner p { color: var(--dv-gold-ink); opacity: 0.85; }
/* color !important: the Helix high-specificity anchor rule
   (body.com-domovita #sp-component > .sp-column a:not(.btn)) forces link text to
   --dv-navy, which on this navy button rendered navy-on-navy (invisible). Force
   white to win, as .dv-sd-btn-secondary already does above. */
.dv-sd-cta-owner .dv-sd-btn-primary { background: var(--dv-navy); color: var(--dv-on-navy) !important; }
.dv-sd-cta-owner .dv-sd-btn-primary:hover { background: var(--dv-blue); color: var(--dv-on-navy) !important; }

/* Other districts */
.dv-sd-other-districts { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.dv-sd-other-districts a {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 8px 14px; background: var(--dv-surface-1); border-radius: 6px;
    color: var(--dv-blue); text-decoration: none; font-weight: 500;
}
.dv-sd-other-districts a:hover { background: var(--dv-blue-tint); }
.dv-sd-other-districts a strong { color: var(--dv-navy); font-weight: 500; }
.dv-sd-other-districts a span { font-size: 0.78rem; color: var(--dv-ink-3); font-weight: 500; }
.dv-sd-view-all { background: transparent !important; color: var(--dv-blue) !important; font-weight: 500 !important; }

/* Attribution */
.dv-sd-attribution { font-size: 0.78rem; color: var(--dv-ink-3); margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--dv-hairline); line-height: 1.6; }
.dv-sd-attribution a { color: var(--dv-blue); }

/* Index page */
.dv-sd-top-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
.dv-sd-top-card {
    display: flex; flex-direction: column; padding: 14px 18px; background: var(--dv-surface-1); border: 1px solid var(--dv-hairline);
    border-radius: 8px; text-decoration: none; color: var(--dv-navy); transition: background 120ms ease, border-color 120ms ease;
}
.dv-sd-top-card:hover { background: var(--dv-blue-tint); border-color: var(--dv-blue); text-decoration: none; }
.dv-sd-top-card strong { font-size: 1.1rem; color: var(--dv-navy); font-weight: 500; }
.dv-sd-top-card span { font-size: 0.78rem; color: var(--dv-ink-2); margin-top: 2px; }

.dv-sd-area-block { margin-bottom: 24px; }
.dv-sd-area-block h3 {
    background: var(--dv-surface-1); padding: 8px 14px; border-radius: 6px;
    font-size: 1rem; color: var(--dv-navy); margin: 0 0 10px;
    display: inline-block; font-weight: 500;
}
.dv-sd-area-list { display: flex; flex-wrap: wrap; gap: 6px; }
.dv-sd-area-list a {
    padding: 5px 10px; background: var(--dv-surface); border: 1px solid var(--dv-hairline); border-radius: 4px;
    color: var(--dv-blue); text-decoration: none; font-size: 0.85rem; font-weight: 500;
    transition: background 120ms ease;
}
.dv-sd-area-list a:hover { background: var(--dv-blue-tint); }

/* Growth badge */
.dv-sd-growth { display:flex; flex-wrap:wrap; gap:8px 16px; margin:8px 0 20px; align-items:baseline; }
.dv-sd-growth-main { font-size:1.15rem; color:var(--dv-navy); }
.dv-sd-growth-sub { font-size:.95rem; color:var(--dv-ink-2); }

/* Price trend chart */
.dv-sd-chart { margin:8px 0 24px; }
/* 2026-07-20 CLS fix. Lighthouse measured mobile CLS 0.615 on this template
   (Google's "poor" threshold is 0.25; desktop was 0.01), and the UX critique
   independently flagged the same shift from screenshots. Cause: the SVG carries a
   viewBox but NO width/height attributes, so with height:auto the browser has no
   intrinsic height to reserve at first paint - everything below the chart
   (property-type bars, CTA, filter form, the whole sold-price table) is pushed down
   once it resolves. aspect-ratio pins the box from the very first layout pass.
   Must match the viewBox (720x320) - update both together if the chart is resized. */
.dv-sd-chart-svg { width:100%; height:auto; aspect-ratio:720 / 320; display:block; border:1px solid var(--dv-hairline); border-radius:8px; background:var(--dv-surface); }
.dv-sd-chart-frame { min-height:0; contain:layout; }
.dv-sd-chart-cap { font-size:.8rem; color:var(--dv-ink-2); margin-top:6px; }
/* Apply to a WRAPPER element only - never directly to a <table>. CSS table layout
   floors the used width at min-content, so width:1px is silently ignored on a
   table and clip:/clip-path: affect paint only, leaving the full content width in
   the document's scroll area (this broke /house-prices/{town} - 753px doc inside a
   390px viewport - until 2026-07-29). Wrap the table in <div class="dv-visually-hidden">. */
.dv-visually-hidden { position:absolute!important; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; }

/* Type mix */
.dv-sd-typemix-grid { display:flex; flex-direction:column; gap:8px; }
.dv-sd-typemix-row { display:grid; grid-template-columns:130px 1fr 44px auto; gap:10px; align-items:center; font-size:.95rem; }
.dv-sd-typemix-bar { background:var(--dv-surface-1); border-radius:4px; height:14px; overflow:hidden; }
.dv-sd-typemix-bar > span { display:block; height:100%; background:var(--dv-blue); }
.dv-sd-typemix-pct { color:var(--dv-navy); font-weight:500; }
.dv-sd-typemix-avg { color:var(--dv-ink-2); font-size:.85rem; }
.dv-sd-typemix-note { margin-top:12px; color:var(--dv-ink-2); }

/* Priced streets */
.dv-sd-priced-streets { display:grid; grid-template-columns:1fr 1fr; gap:24px; }
.dv-sd-priced-col ul { list-style:none; padding:0; margin:0; }
.dv-sd-priced-col li { display:flex; justify-content:space-between; padding:4px 0; border-bottom:1px solid var(--dv-hairline); }
.dv-sd-priced-col li span { color:var(--dv-ink-2); }

/* Currency stamp */
.dv-sd-currency { font-size:.85rem; color:var(--dv-ink-2); margin-top:16px; }

/* Filter bar */
form.dv-sd-filterbar {
    background: var(--dv-surface-1);
    border: 1px solid var(--dv-hairline);
    border-radius: 10px;
    padding: 16px 18px;
    margin-bottom: 24px;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 14px 18px;
}
form.dv-sd-filterbar fieldset.dv-sd-fb-group {
    border: none; margin: 0; padding: 0;
    display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px 8px;
}
form.dv-sd-filterbar legend {
    font-size: .72rem; font-weight: 500; color: var(--dv-ink-3);
    text-transform: uppercase; letter-spacing: .05em;
    margin-bottom: 4px; padding: 0; flex: 0 0 100%;
}
form.dv-sd-filterbar fieldset.dv-sd-fb-group > label { font-size: .82rem; color: var(--dv-ink-2); }
form.dv-sd-filterbar div.dv-sd-fb-group { display: flex; flex-direction: column; gap: 4px; }
form.dv-sd-filterbar div.dv-sd-fb-group > label {
    font-size: .72rem; font-weight: 500; color: var(--dv-ink-3);
    text-transform: uppercase; letter-spacing: .05em; white-space: nowrap;
}
.dv-sd-filterbar input[type="number"],
.dv-sd-filterbar input[type="text"],
.dv-sd-filterbar select {
    height: 36px; padding: 0 10px; border: 1px solid var(--dv-hairline-strong); border-radius: 6px;
    font-size: .88rem; color: var(--dv-navy); background: var(--dv-surface); box-sizing: border-box;
    font-family: inherit;
}
.dv-sd-filterbar input[type="number"]:focus,
.dv-sd-filterbar input[type="text"]:focus,
.dv-sd-filterbar select:focus {
    outline: none; border-color: var(--dv-blue); box-shadow: 0 0 0 3px rgba(47,111,204,.4);
}
.dv-sd-filterbar input[type="number"] { width: 92px; }
.dv-sd-filterbar input[type="text"]   { width: 160px; }
.dv-sd-filterbar select               { min-width: 124px; }
.dv-sd-fb-sort select                 { min-width: 140px; }
label.dv-sd-fb-check {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 5px 11px; border: 1.5px solid var(--dv-hairline-strong); border-radius: 20px;
    font-size: .83rem; cursor: pointer; background: var(--dv-surface); color: var(--dv-navy);
    user-select: none; white-space: nowrap;
    transition: border-color .12s ease, background .12s ease, color .12s ease;
}
label.dv-sd-fb-check:hover { border-color: var(--dv-blue); color: var(--dv-blue); }
label.dv-sd-fb-check:has(input:checked) { background: var(--dv-blue); border-color: var(--dv-blue); color: #fff; }
label.dv-sd-fb-check input[type="checkbox"] {
    accent-color: var(--dv-blue); width: 14px; height: 14px; margin: 0; cursor: pointer;
}
/* On a checked (blue) chip, flip the checkbox to white so it stays visible against the blue fill. */
label.dv-sd-fb-check:has(input:checked) input[type="checkbox"] { accent-color: #fff; }
.dv-sd-fb-actions { display: flex; align-items: center; gap: 10px; }
form.dv-sd-filterbar button.dv-sd-btn {
    height: 36px; padding: 0 20px; font-size: .9rem; font-weight: 500;
    border: none; cursor: pointer; font-family: inherit;
}
form.dv-sd-filterbar .dv-sd-btn-primary { background: var(--dv-blue); color: #fff; }
form.dv-sd-filterbar .dv-sd-btn-primary:hover { background: var(--dv-btn-hover); color: #fff; text-decoration: none; }
form.dv-sd-filterbar a.dv-sd-btn-secondary {
    height: 36px; line-height: 34px; padding: 0 16px;
    font-size: .88rem; background: transparent; color: var(--dv-ink-2) !important;
    border: 1.5px solid var(--dv-hairline-strong);
}
form.dv-sd-filterbar a.dv-sd-btn-secondary:hover {
    background: var(--dv-surface-1); border-color: var(--dv-ink-3); color: var(--dv-navy) !important; text-decoration: none;
}
.dv-sd-filter-count { flex: 0 0 100%; margin: 6px 0 0; font-size: .82rem; color: var(--dv-ink-3); }
/* Empty state (no results after filtering) */
.dv-sd-empty { text-align: center; padding: 48px 20px; color: var(--dv-ink-3); font-size: .95rem; }
.dv-sd-empty a { color: var(--dv-blue); }
/* Load-more button (JS-injected into #dv-sd-sales, shown on white background) */
.dv-sd-btn.dv-sd-load-more {
    display: block; margin: 20px auto; text-align: center;
    background: transparent; color: var(--dv-blue) !important; border: 1.5px solid var(--dv-blue);
}
.dv-sd-btn.dv-sd-load-more:hover { background: var(--dv-blue-tint); text-decoration: none; }

/* Mobile */
@media (max-width: 700px) {
    .dv-sd-hero h1 { font-size: 1.45rem; }
    .dv-sd-stats { grid-template-columns: 1fr 1fr; }
    .dv-sd-stat:nth-child(odd) { border-left: none; }
    .dv-sd-table th, .dv-sd-table td { padding: 8px; font-size: 0.82rem; }
    /* XC10 (2026-06-12): the per-sale street table (.dv-sd-table--sales: Address,
       Postcode, Type, Tenure, Price, Date) crushed Price + Date off-screen at
       390px because Postcode/Type/Tenure ate the width. Hide those three on
       mobile so the columns that matter - Address, Price, Date - are readable.
       Scoped to --sales so the district summary tables are untouched. */
    .dv-sd-table--sales th:nth-child(2), .dv-sd-table--sales td:nth-child(2),
    .dv-sd-table--sales th:nth-child(3), .dv-sd-table--sales td:nth-child(3),
    .dv-sd-table--sales th:nth-child(4), .dv-sd-table--sales td:nth-child(4) { display: none; }
    .dv-sd-cta { flex-direction: column; align-items: stretch; text-align: center; }
    .dv-sd-cta-actions { justify-content: center; }
    .dv-sd-top-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
}
@media (max-width: 600px) {
    .dv-sd-typemix-row { grid-template-columns:96px 1fr 38px; }
    .dv-sd-typemix-avg { display:none; }
    .dv-sd-priced-streets { grid-template-columns:1fr; }
    /* Filter bar mobile - stack groups full-width */
    form.dv-sd-filterbar { gap: 12px; }
    form.dv-sd-filterbar > fieldset.dv-sd-fb-group,
    form.dv-sd-filterbar > div.dv-sd-fb-group,
    .dv-sd-fb-actions { flex: 0 0 100%; width: 100%; }
    form.dv-sd-filterbar fieldset.dv-sd-fb-group { flex-direction: column; align-items: stretch; }
    form.dv-sd-filterbar fieldset.dv-sd-fb-group:has(label.dv-sd-fb-check) { flex-direction: row; flex-wrap: wrap; }
    .dv-sd-filterbar input[type="number"],
    .dv-sd-filterbar input[type="text"],
    .dv-sd-filterbar select { width: 100%; }
    label.dv-sd-fb-check { flex: 1 1 auto; justify-content: center; }
    form.dv-sd-filterbar .dv-sd-btn { flex: 1; text-align: center; }
}

/* dv-excel-upgrade: MOVED (wave 3, 2026-08-13) to
   site/assets/css/domovita-sold.css - asset name com_domovita.sold.
   Every _shared/sold_* partial HEAD-loads it ITSELF (guarded, idempotent
   registerAndUseStyle at the top of each partial - never a bare useStyle;
   components-copy asset names 500 the page), so any template that renders
   a sold_* block gets the stylesheet without doing anything. Templates may
   ALSO register it (same name/version = no-op) for template-owned dv-sd-*
   markup on pages that include no partial. This foot-of-body style block
   was re-shaping the charts, range frames and hidden fallback tables after
   first paint, which is where the district page's CLS 0.108 came from.
   domovita-sold.css also carries head-critical duplicates of a few
   pre-existing rules below (chart aspect ratios, .dv-visually-hidden,
   .dv-sd-table-wrap, section/table basics) - keep those in lockstep. */


/* Light-surface variant of the secondary button (2026-08-14 bug fix).
   .dv-sd-btn-secondary is styled for the NAVY CTA panel: white text on a
   translucent dark fill, hover LIGHTENS the fill. On a white card that hover
   turns the button white-on-white and the label vanishes - reported on the
   "Wondering what yours has done?" and "Where would yours sit?" CTAs. This
   variant inverts the logic for light surfaces: dark ink, hairline border,
   hover darkens. Keep in lockstep with the copy in solddistrict/_styles.php. */
.dv-sd-btn-onlight {
    background: var(--dv-surface, #fff);
    color: var(--dv-navy, #1F3A5F) !important;
    border: 1.5px solid var(--dv-hairline-strong, #C7CDD4);
}
.dv-sd-btn-onlight:hover,
.dv-sd-btn-onlight:focus-visible {
    background: var(--dv-navy, #1F3A5F);
    color: #fff !important;
    border-color: var(--dv-navy, #1F3A5F);
    text-decoration: none;
}

/* Area-guide cross-link (2026-08-17). Deliberately UNDERSTATED - it sits below
   the capture card and must read as an onward path, not a second CTA competing
   with the one the page exists for. No gold, no gradient, no button fill. */
.dv-area-guide-link {
    margin: 20px 0 32px;
    padding: 16px 20px;
    border-left: 3px solid var(--dv-blue, #2C5AA0);
    background: var(--dv-blue-tint, #F4F7FC);
    border-radius: 0 var(--dv-radius, 6px) var(--dv-radius, 6px) 0;
}
.dv-area-guide-link__text {
    margin: 0 0 4px;
    color: var(--dv-navy, #1F3A5F);
    font-size: 0.95rem;
    line-height: 1.5;
}
.dv-area-guide-link__cta {
    color: var(--dv-blue, #2C5AA0);
    font-weight: 500;
    font-size: 0.95rem;
    text-decoration: underline;
}
.dv-area-guide-link__cta:hover,
.dv-area-guide-link__cta:focus-visible {
    color: var(--dv-navy, #1F3A5F);
    text-decoration: underline;
}

/* ---------------------------------------------------------------------------
   Masthead identity strip (design review P4, 2026-08-22)

   One quiet line above the h1 on sold-price pages saying who publishes this
   data and that reading it is free. It sits in the eyebrow register - small,
   tracked, muted - specifically so it cannot compete with the h1 beneath it or
   the instant-estimate widget below that. If this ever starts reading as a
   banner, it has been over-styled.
   --------------------------------------------------------------------------- */
/* CORRECTED same evening, before anyone saw it in the wild but AFTER it went
   live for ~10 minutes. The first version styled this for a LIGHT surface -
   --dv-ink-3 body, --dv-deep-navy strong. `.dv-sd-hero` paints #1F3A5F, so the
   strong rendered navy-on-navy at 1.00:1 (the brand name was literally
   invisible) and the body text sat at 2.12:1. Both fail WCAG AA outright.

   domovita.css warns about exactly this next to --dv-ink-3: "none is text on a
   navy/dark surface (on #1F3A5F the new value is 2.12:1, WORSE, so that would
   have been the trap)". Measure the surface a component actually lands on -
   the on-navy token family exists for this. */
.dv-sd-masthead {
    margin: 0 0 var(--dv-space-sm, 8px);
    font-size: var(--dv-fs-caption, 0.8125rem);
    line-height: 1.5;
    color: var(--dv-on-navy-mute, #B5D4F4);   /* 7.49:1 on #1F3A5F */
    max-width: 68ch;
}

.dv-sd-masthead strong {
    color: var(--dv-on-navy, #FFFFFF);        /* 11.48:1 on #1F3A5F */
    font-weight: 600;
}

/* 3.6.0 - briefing layout (2026-09-19). Hero lives in a paper band now. */
.dv-pg-page .dv-sd-hero { background: transparent; padding: 0; margin: 0 0 1.25rem; }
/* (0,3,1) on purpose (2026-09-20): domovita-pages.css carries a heading
   backstop `.dv-pg-page.dv-pg-page h1 { font-family: var(--dv-pg-font) }` at
   (0,2,1), loaded AFTER this sheet, so the brief's `.dv-pg-page .dv-sd-hero h1`
   (also 0,2,1) lost the tie and the h1 rendered system sans - confirmed via
   getComputedStyle on staging. Both classes sit on the same wrapper element. */
.dv-pg-page.dv-sd-page .dv-sd-hero h1 { font-family: var(--dv-serif); font-weight: 400; font-size: var(--dv-fs-display-mid); line-height: 1.06; letter-spacing: -0.01em; }
/* Fix round 1 (2026-09-20, controller finding 3): the same (0,2,1) heading
   backstop beats every h2/h3 on the page, not just the hero's h1 - every
   section heading rendered system sans (confirmed getComputedStyle on
   staging: h2/h3 reported the sans stack, h1 reported Georgia once the rule
   above landed). Brand rule is Georgia serif for all headings
   (BRAND-STYLE-GUIDE.md).
   FIRST ATTEMPT WAS ALSO (0,2,1) - `.dv-pg-page.dv-sd-page h2` is two class
   selectors + one type selector, the exact same weight as the backstop's
   `.dv-pg-page.dv-pg-page h2`, so it tied and lost on source order (pages.css
   loads after this sheet). Caught by getComputedStyle on re-check, not by
   eye. Repeating `.dv-pg-page` a second time (harmless - same element, class
   selectors don't need to be distinct) is the same (0,3,1) trick the h1 rule
   above already uses, and wins outright regardless of load order. */
.dv-pg-page.dv-pg-page.dv-sd-page h2, .dv-pg-page.dv-pg-page.dv-sd-page h3 { font-family: var(--dv-serif); font-weight: 400; }
.dv-pg-page .dv-sd-stats { margin-bottom: 1rem; }
.dv-pg-page .dv-sd-hero-grid { margin-top: 1rem; }
.dv-pg-page .dv-sd-breadcrumb { font-family: var(--dv-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; }
/* Task 5 polish (2026-09-21): the mock uses a dot separator; the captured
   markup (soldstreet/default.php $dvBlkHeroTop) is off limits this task, so
   the literal "›" text is zeroed out with font-size:0 and a CSS dot is
   drawn with ::before instead - the DOM text is untouched. */
.dv-pg-page .dv-sd-breadcrumb span[aria-hidden="true"] { font-size: 0; }
.dv-pg-page .dv-sd-breadcrumb span[aria-hidden="true"]::before { content: "\00B7"; font-size: 11px; color: inherit; }
/* Re-ink the captured hero for the paper surface (2026-09-20, found on the Task 7
   screenshot). Every text rule above for .dv-sd-hero and its children was written
   for the navy card: the --dv-on-navy family (#FFFFFF / #B5D4F4) and the gold
   eyebrow. With the background now transparent over --dv-paper (#F4EDE0) those
   measure 1.16:1, 1.32:1 and 1.61:1 - the h1 and the answer paragraph were white
   on cream. The .dv-sd-stats tiles keep their own navy fill and are untouched.
   Figures below are contrast against #F4EDE0. --dv-blue is 4.23:1 here, under
   AA for body text, so hero links go navy (they are underlined already); the
   !important matches the on-navy link rule it has to beat. */
.dv-pg-page .dv-sd-hero::before { display: none; }                                          /* Task 5 polish 2026-09-21: mock has no eyebrow line between breadcrumb and h1 (task-1 report, deferred). display:none only - the ::before content stays in the CSS/HTML for a future revert. */
.dv-pg-page .dv-sd-hero h1 { color: var(--dv-navy); }                                         /* 9.86:1 */
.dv-pg-page .dv-sd-masthead { color: var(--dv-ink-2); }                                       /* 5.57:1 */
.dv-pg-page .dv-sd-masthead strong { color: var(--dv-ink); }                                  /* 14.80:1 */
.dv-pg-page .dv-sd-answer { color: var(--dv-ink); }                                           /* 14.80:1 */
.dv-pg-page .dv-sd-hero .dv-sd-answer-secondary,
.dv-pg-page .dv-sd-hero .dv-sd-answer-secondary * { color: var(--dv-ink-2); }                 /* 5.57:1 */
.dv-pg-page .dv-sd-hero-sub { color: var(--dv-ink-2); }                                       /* 5.57:1 */
.dv-pg-page .dv-sd-hero-sub strong { color: var(--dv-ink); }                                  /* 14.80:1 */
.dv-pg-page .dv-sd-hero-sub a, .dv-pg-page .dv-sd-answer a { color: var(--dv-navy) !important; }
.dv-pg-page .dv-sd-hero-sub a:hover, .dv-pg-page .dv-sd-answer a:hover { color: var(--dv-ink) !important; }

/* 3.7.0 - slice 1b Task 1 (2026-09-21). The paper band now holds only
   breadcrumb, h1, answer paragraph, lookup pill and the one stat grid (the
   mock's hero). The masthead line, growth badge, old tile row and basis line
   moved into the analysis band under a "The full figures for {street}" h2
   ($dvBlkMasthead, $dvBlkHeroRest) - see default.php. The answer paragraph
   gets the shared sub-heading size so it reads as the mock's 2-line sub. */
.dv-pg-page.dv-pg-page.dv-sd-page .dv-sd-answer { font-size: var(--dv-pg-t-sub, 17px); max-width: 640px; }
.dv-pg-page .dv-sd-hero-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 420px;
    gap: 2rem;
    align-items: end;
    margin-top: 1rem;
}
/* The row is a 2-track grid (mock's left/right split) but carries only one
   child - the lookup pill - so it is placed explicitly in the right track
   rather than relying on auto-placement filling the first (left) one. */
.dv-pg-page .dv-sd-hero-row .dv-sd-lookup { grid-column: 2; }
.dv-pg-page .dv-sd-lookup {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    background: #fff;
    border: 1px solid var(--dv-pg-rule-strong, #d8d0bd);
    border-radius: 999px;
    padding: 6px 6px 6px 18px;
}
.dv-pg-page .dv-sd-lookup input {
    flex: 1 1 auto;
    min-width: 0;
    border: none;
    background: transparent;
    font-family: inherit;
    font-size: var(--dv-pg-t-body, 1rem);
    color: var(--dv-pg-ink, var(--dv-ink));
}
.dv-pg-page .dv-sd-lookup input:focus { outline: none; }
.dv-pg-page .dv-sd-lookup input::placeholder { color: var(--dv-ink-2); }
/* "Navy button inside" (brief wording) - the pill's own Look up action is not
   the page's money CTA (that stays gold, valuation widget), so it borrows the
   .dv-pg-btn-primary 48px/pill base and re-inks it navy-filled rather than
   reusing the gold fill a second time in the same viewport. */
.dv-pg-page .dv-sd-lookup .dv-pg-btn-primary {
    flex: 0 0 auto;
    min-height: 48px;
    background: var(--dv-navy, #1F3A5F);
    border-color: var(--dv-navy, #1F3A5F);
    --dv-pg-btn-ink: var(--dv-on-navy, #FFFFFF);
}
.dv-pg-page .dv-sd-lookup .dv-pg-btn-primary:hover,
.dv-pg-page .dv-sd-lookup .dv-pg-btn-primary:focus {
    background: var(--dv-deep-navy, #16283F);
    border-color: var(--dv-deep-navy, #16283F);
    --dv-pg-btn-ink: var(--dv-on-navy, #FFFFFF);
}
@media (max-width: 700px) {
    /* Phone: single column, lookup pill drops under the sub (brief wording). */
    .dv-pg-page .dv-sd-hero-row { grid-template-columns: 1fr; gap: 0.75rem; }
    .dv-pg-page .dv-sd-hero-row .dv-sd-lookup { grid-column: 1; }
}

/* Task 9 (2026-09-19): sales table, repeat-sales card, briefing grid, rail cards.
   Step 1 (Sales table): .dv-pg-table added to the sales table (see default.php)
   picks up the shared table skin (domovita-pages.css, loads after this sheet)
   at (0,2,1) - stronger than this file's own .dv-sd-table th/td at (0,1,1) - so
   headers/rows re-ink automatically; no extra rule needed for that. The brief's
   optional chip rule is NOT added: grep found no bare .dv-sd-chip class anywhere
   in the tree (only .dv-sd-chip-filtered, already border-radius:999px, and
   .dv-sd-chip-unfiltered, a deliberately square warning tag, left alone), and
   neither lives inside .dv-sd-block-sales .dv-sd-filterbar - that bar has no
   chips at all (its "chips" are the price/stats charts elsewhere on the page).
   Checked the rendered page per the brief's own instruction before skipping. */
.dv-pg-page .dv-sd-table-wrap--sticky .dv-pg-table th { background: var(--dv-surface, #fff); }
/* ^ Without this, .dv-pg-table th's background:transparent (0,2,1) beats this
   file's .dv-sd-table th background:var(--dv-surface-1) (0,1,1) regardless of
   load order, so the sticky header would go see-through and scrolling rows
   would show behind the frozen header row. Own finding, not in the brief -
   confirmed on the wellington-road screenshot (many sales -> table scrolls). */

/* Step 2 (Repeat-sales card): the partial renders a <table> with one <tbody>
   per resold address (rowspanned), not per-address <div> cards - there is no
   class="dv-sd-rs-*" anywhere in sold_repeat_sales.php (grep confirmed empty).
   The brief's own fallback instruction ("use the partial's real class names -
   read the partial") is followed literally: dv-pg-card is applied to the ONE
   card-shaped unit that exists, the section itself (.dv-sd-repeat, both the
   populated and the empty-state branch - see sold_repeat_sales.php), framing
   the whole block the same way .dv-cap and the widget are framed below. The
   "latest price" hero figure (dv-sd-rs-latest, 1.5rem serif) has no real
   target - every price is a plain .dv-sd-num table cell, not a standalone
   figure, and forcing a 1.5rem serif figure into one arbitrary cell would
   break the table's row alignment for no benefit - so it is NOT added. The
   "negative change" colour is already correct without a new rule: every
   negative pair change already carries .dv-sd-delta-down, styled
   color: var(--dv-bad) at lines 73 and 700 of this file - adding a scoped
   dv-sd-rs-neg duplicate would just be a second rule for the same result. */
.dv-pg-page .dv-sd-block-repeat .dv-pg-card { border-radius: 6px; padding: 1rem 1.1rem; }

/* Step 4 (Rail cards). .dv-cap already carries its own inner box
   (.dv-cap__inner: background #f6f9fd, border, radius 12px, padding 22px 24px)
   one level in - this outer treatment frames the whole capture block the same
   way the widget is framed below, matching the brief's exact selector. */
.dv-pg-page .dv-pg-shell-rail .dv-cap { border-radius: 6px; padding: 1rem; }

/* Step 4 (Widget), SUPERSEDED by Slice 1b Task 3 below (2026-09-21). This
   block used to force the widget's own light boxes (#f6f9fd fields, white
   inputs) back over the navy fill dv-pg-lead-navy provides - correct for the
   PREVIOUS design (a light widget nested inside a navy frame). Only the
   no-JS fallback override survives: .dv-sd-cta-owner is a GOLD box (not the
   navy card), so its own heading still needs the gold/dark-ink pairing, and
   its button keeps its separate !important rule two screens up this file
   (".dv-sd-cta-owner .dv-sd-btn-primary") untouched. Everything else this
   block used to force light is now styled navy-on-navy instead - see the
   Task 3 block below, which is the mock's mock 2d rail card. */
.dv-pg-page .dv-pg-lead-navy .dv-sd-cta-owner h3 { color: var(--dv-gold-ink, #412402); }

/* ---------------------------------------------------------------------------
   Slice 1b Task 3 (2026-09-21). Navy estimate card, rail mock (2d/3a). The
   JS-revealed body (.dv-ew__widget) drops its own light box entirely and
   goes navy-on-navy like the mock: translucent-white chips/pills/inputs,
   a gold submit, a hairline translucent result panel with a white serif
   figure. Everything below is scoped `.dv-pg-page .dv-ew` (a unique class on
   the <section>, stronger than `.dv-pg-lead-navy` alone) so it wins outright
   over sold_estimate_widget.php's own inline <style> block (all
   (0,1,0)-(0,2,1)) regardless of load order - the same trick used throughout
   this file. Headings (h2/h3/h4), the generic input/select/textarea skin and
   placeholders are already handled for free by the SITEWIDE
   `.dv-pg-page .dv-pg-lead-navy` rule (domovita-pages.css, translucent
   rgba(255,255,255,.08) fields, #fff headings) - this block only adds what
   that generic rule does not reach: chips, pills, the submit button, the
   result/near/success panels and every plain paragraph (labels, lede, hedge,
   confidence line, card copy), which the widget's own <style> block still
   paints in its old light-surface blue-greys (#445b7a etc).

   Contrast pairs actually used below, measured against #1F3A5F (--dv-navy,
   the resolved background - confirmed via getComputedStyle on staging):
     #FFFFFF (--dv-on-navy)              11.48:1  headings, range figure,
                                                    active-pill ink's inverse,
                                                    success text
     #B5D4F4 (--dv-on-navy-mute)          7.49:1  lede, labels, result label,
                                                    confidence/hedge text,
                                                    card copy
     #85B7EB (--dv-on-navy-faint)         5.30:1  "(optional)" phone label
     var(--dv-gold) #F5B041 bg /
       var(--dv-gold-ink) #412402 ink     7.56:1  submit button (computed:
                                                    L 0.5086 vs 0.0239)
     #FFC9C9 error red on navy            7.90:1  replaces the light-surface
                                                    error red #a4262c, which
                                                    is 1.56:1 on navy - an
                                                    outright AA fail, not a
                                                    borderline one
     #1F3A5F navy ink on solid #FFFFFF    11.48:1 (symmetric) active chip/pill
   --------------------------------------------------------------------------- */
.dv-pg-page .dv-ew .dv-ew__widget { background: transparent; border: 0; padding: 0; }
.dv-pg-page .dv-ew .dv-ew__heading {
    font-family: var(--dv-serif); font-weight: 400; font-size: 22px;
    color: var(--dv-on-navy, #fff);
}
.dv-pg-page .dv-ew .dv-ew__lede { color: var(--dv-on-navy-mute, #B5D4F4); }
.dv-pg-page .dv-ew .dv-ew__field label,
.dv-pg-page .dv-ew .dv-ew__label { color: var(--dv-on-navy-mute, #B5D4F4); }
.dv-pg-page .dv-ew .dv-ew__optional { color: var(--dv-on-navy-faint, #85B7EB); }

/* Postcode chips + bedroom pills: translucent pill, active = solid white. */
.dv-pg-page .dv-ew .dv-ew__chip,
.dv-pg-page .dv-ew .dv-ew__pill {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.22);
    color: var(--dv-on-navy, #fff);
}
.dv-pg-page .dv-ew .dv-ew__chip:hover,
.dv-pg-page .dv-ew .dv-ew__pill:hover { border-color: var(--dv-on-navy, #fff); }
.dv-pg-page .dv-ew .dv-ew__chip.is-active,
.dv-pg-page .dv-ew .dv-ew__pill.is-active {
    background: var(--dv-on-navy, #fff);
    border-color: var(--dv-on-navy, #fff);
    color: var(--dv-navy, #1F3A5F);
}

/* Type select + text/email/tel inputs. !important IS REQUIRED here, not a
   defensive habit: staging showed every field rendering as a BLANK WHITE box
   (screenshot task3-rail-crop.png, first pass) even though this file's own
   rule and the generic sitewide `.dv-pg-lead-navy` rule (domovita-pages.css)
   both matched. Traced via `el.matches()` against every stylesheet on the
   page (not by eye): the actual winner was
   `templates/shaper_helixultimate/css/dv-helix-overrides.css`
   (`#sp-main-body input[type="text"]:not(.dv-hero-ai-input):not(.dv-ai-input),
   ... #sp-main-body select:not(...):not(...) { background-color:
   var(--dv-aurum-field-bg), which resolves to #fff, ... }`, the sitewide "Aurum field" skin
   applied to every plain text-like input and select on every com_domovita
   page) - an ID-rooted selector, so it beats ANY class-only selector in this
   file or domovita-pages.css regardless of how many classes are stacked; a
   file this task does not own and must not edit. That stylesheet carries no
   `!important` anywhere in the rule (confirmed by reading it), so matching
   it here is the correct, lowest-blast-radius fix - already the established
   pattern in this file (".dv-sd-cta-owner .dv-sd-btn-primary", the hero-link
   "!important" overrides above) for exactly this situation. */
.dv-pg-page .dv-ew .dv-ew__field input[type="text"],
.dv-pg-page .dv-ew .dv-ew__field input[type="email"],
.dv-pg-page .dv-ew .dv-ew__field input[type="tel"],
.dv-pg-page .dv-ew .dv-ew__field select {
    background: rgba(255, 255, 255, 0.08) !important;
    border: 1px solid rgba(255, 255, 255, 0.22) !important;
    box-shadow: none !important;
    color: var(--dv-on-navy, #fff) !important;
}
.dv-pg-page .dv-ew .dv-ew__field input::placeholder { color: rgba(255, 255, 255, 0.55) !important; }
.dv-pg-page .dv-ew .dv-ew__field input:hover,
.dv-pg-page .dv-ew .dv-ew__field select:hover,
.dv-pg-page .dv-ew .dv-ew__field input:focus,
.dv-pg-page .dv-ew .dv-ew__field select:focus {
    border-color: var(--dv-on-navy, #fff) !important;
    background: rgba(255, 255, 255, 0.08) !important;
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.22) !important;
}
/* The <option> list is OS-rendered on a white system surface regardless of
   the select's own colours - keep it legible there too. */
.dv-pg-page .dv-ew .dv-ew__field select option { color: #1F3A5F; background: #fff; }

/* Submit: gold, matching .dv-pg-btn-gold (domovita-pages.css section 8).
   Restated here rather than adding the class to the partial's markup - the
   brief allows class hooks only if strictly needed, and this needed none.
   Covers every .dv-ew__submit button in the card (estimate, email-me,
   agent-form) - all three sit on the same navy fill. */
.dv-pg-page .dv-ew .dv-ew__submit {
    background: var(--dv-gold, #F5B041);
    border-color: var(--dv-gold, #F5B041);
    color: var(--dv-gold-ink, #412402);
    min-height: 46px;
}
.dv-pg-page .dv-ew .dv-ew__submit:hover,
.dv-pg-page .dv-ew .dv-ew__submit:focus {
    background: var(--dv-gold-hover, #E0992E);
    border-color: var(--dv-gold-hover, #E0992E);
    color: var(--dv-gold-ink, #412402);
}
.dv-pg-page .dv-ew .dv-ew__error { color: #FFC9C9; }
.dv-pg-page .dv-ew .dv-ew__error a { color: #FFC9C9; text-decoration-color: #FFC9C9; }

/* Result box: hairline translucent panel, white serif figure. */
.dv-pg-page .dv-ew .dv-ew__result {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-left: 4px solid var(--dv-gold, #F5B041);
}
.dv-pg-page .dv-ew .dv-ew__result-label { color: var(--dv-on-navy-mute, #B5D4F4); }
.dv-pg-page .dv-ew .dv-ew__range {
    font-family: var(--dv-serif); font-weight: 400; color: var(--dv-on-navy, #fff);
}
.dv-pg-page .dv-ew .dv-ew__conf { color: var(--dv-on-navy-mute, #B5D4F4); }
.dv-pg-page .dv-ew .dv-ew__hedge { color: var(--dv-on-navy-mute, #B5D4F4); }
.dv-pg-page .dv-ew .dv-ew__near {
    background: rgba(255, 255, 255, 0.08);
    border-left-color: var(--dv-on-navy, #fff);
    color: var(--dv-on-navy, #fff);
}

/* "Email me" card and the agent-form card: same hairline translucent panel. */
.dv-pg-page .dv-ew .dv-ew__card,
.dv-pg-page .dv-ew .dv-ew__agent form {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.22);
}
.dv-pg-page .dv-ew .dv-ew__card h4,
.dv-pg-page .dv-ew .dv-ew__agent h4 { color: var(--dv-on-navy, #fff); }
.dv-pg-page .dv-ew .dv-ew__card > p,
.dv-pg-page .dv-ew .dv-ew__agent > p { color: var(--dv-on-navy-mute, #B5D4F4); }
.dv-pg-page .dv-ew .dv-ew__consent { color: var(--dv-on-navy-mute, #B5D4F4); }
.dv-pg-page .dv-ew .dv-ew__soft-toggle { color: var(--dv-on-navy, #fff); }

.dv-pg-page .dv-ew .dv-ew__success {
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: var(--dv-on-navy, #fff);
}
.dv-pg-page .dv-ew .dv-ew__success strong { color: var(--dv-on-navy, #fff); }

/* ---------------------------------------------------------------------------
   Slice 1b Task 3 (2026-09-21), continued. Paper capture card, rail mock
   (2d/3a "Hear when {street} sells"). Copy and DOM order are untouched -
   sold_capture.php owns the text and this is a surface change only: the
   light-blue box (#f6f9fd) this partial paints everywhere else becomes the
   paper card these two pages already use for every other band. Scoped
   `.dv-pg-page .dv-cap` only - town/district pages carry no `.dv-pg-page`
   wrapper yet, so the light-blue box stays exactly as it was there (the
   brief's own render-fine-there-too check, confirmed on the town page).

   Contrast (against --dv-paper #F4EDE0, established elsewhere in this file
   at 9.86:1 for --dv-navy text - the "hero re-ink" comment above measures
   the same surface):
     var(--dv-navy) #1F3A5F heading/choice-title/legend/submit  9.86:1  (unchanged)
     #445b7a lede/optin text (already used here, computed)       5.96:1  (unchanged)
     var(--dv-ink-2) #5F5E5A choice-note/privacy (was #5a6f8c,
       computed 4.41:1 - an AA FAIL by 0.09, replaced)            5.57:1
     var(--dv-navy) privacy link (was --dv-blue #2F6FCC, the
       SAME 4.23:1 AA fail already documented above for the hero
       links, and fixed here the same way)                        9.86:1
   --------------------------------------------------------------------------- */
.dv-pg-page .dv-cap__inner {
    background: var(--dv-paper, #F4EDE0);
    border-color: var(--dv-paper-rule, #D6CCB8);
}
.dv-pg-page .dv-cap__heading { font-family: var(--dv-serif); font-weight: 400; font-size: 18px; }
.dv-pg-page .dv-cap__lede { font-size: 13px; }
.dv-pg-page .dv-cap__choice-note,
.dv-pg-page .dv-cap__privacy { color: var(--dv-ink-2, #5F5E5A); }
.dv-pg-page .dv-cap__privacy a { color: var(--dv-navy, #1F3A5F); }  /* Task 5 polish: !important removed - .dv-pg-page .dv-cap__privacy a (0,2,1) already beats the base .dv-cap__privacy a (0,1,1) */

/* Choices as a compact row (flex-wrap), not full-width stacked blocks - the
   copy is unchanged, only the box each choice sits in shrinks and wraps. */
.dv-pg-page .dv-cap__choices { display: flex; flex-wrap: wrap; gap: 8px; }
.dv-pg-page .dv-cap__choice {
    flex: 1 1 220px;
    background: #fff;
    border-color: var(--dv-paper-rule, #D6CCB8);
}
.dv-pg-page .dv-cap__choice-title { font-size: .85rem; }
.dv-pg-page .dv-cap__choice-note { font-size: .72rem; }
.dv-pg-page .dv-cap__choice:hover { border-color: var(--dv-navy, #1F3A5F); background: #fff; }
.dv-pg-page .dv-cap__choice:has(input[type="radio"]:checked) {
    border-color: var(--dv-navy, #1F3A5F);
    background: #fff;
    box-shadow: inset 0 0 0 1px var(--dv-navy, #1F3A5F);
}
.dv-pg-page .dv-cap__choice input[type="radio"] { accent-color: var(--dv-navy, #1F3A5F); }

/* Email + submit row (already one row on desktop via the base .dv-cap__row
   flex rule - domovita-sold.css line ~519 - so only the surface changes):
   the email field sits on white over paper, the submit becomes the mock's
   outline "Alert me"-style button instead of a solid blue fill.
   !important on the input: the same sitewide Aurum field skin documented
   above (templates/shaper_helixultimate/css/dv-helix-overrides.css,
   `#sp-main-body input[type="email"]:not(...):not(...)`, ID-rooted) matches
   this element too and would otherwise win the border colour (its bg is
   already #fff, so only the border and inset shadow actually differed). */
.dv-pg-page .dv-cap__field input[type="email"] {
    background: #fff !important;
    border: 1px solid var(--dv-paper-rule, #D6CCB8) !important;
    box-shadow: none !important;
}
.dv-pg-page .dv-cap__field input[type="email"]:focus {
    border-color: var(--dv-navy, #1F3A5F) !important;
    box-shadow: 0 0 0 3px rgba(31, 58, 95, 0.15) !important;
}
.dv-pg-page .dv-cap__submit {
    background: transparent;
    border: 1px solid var(--dv-navy, #1F3A5F);
    color: var(--dv-navy, #1F3A5F);
}
.dv-pg-page .dv-cap__submit:hover {
    background: var(--dv-navy, #1F3A5F);
    color: var(--dv-on-navy, #fff);
}

/* ---------------------------------------------------------------------------
   Slice 1b Task 2 (2026-09-21). Compact sales table, repeat-sale cards and
   link line on the street page (mock 2d "Every recorded sale" / "What the
   same homes did"). See default.php for the markup - $dvBlkSalesTable
   (compact table) and $dvBlkRepeatCards (three cards).
   --------------------------------------------------------------------------- */

/* --- Compact table (.dv-pg-table already gives the hairline/mono-caps-header
   skin - domovita-pages.css, loads after this sheet, at (0,2,1) beats this
   file's own .dv-sd-table rules). Only the bits the shared skin does not
   cover: section rhythm, the "No." link weight/hover, and the foot row. --- */
.dv-pg-page .dv-sd-recent { margin-bottom: 1.5rem; }
.dv-pg-page .dv-sd-recent h2 { margin-bottom: 0.25rem; }
.dv-pg-page .dv-sd-recent .dv-pg-meta { margin: 0 0 0.9rem; }
/* Sort pill header row (item 3, final fix wave 2026-09-21) - mock 2d shows the
   sort control top-right of "Every recorded sale". A native <select> keeps it
   keyboard-operable with zero extra JS/ARIA; the two rules below only strip the
   native chrome and re-skin it as the existing .dv-pg-pill outline. */
.dv-pg-page .dv-sd-recent-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem 1rem;
    flex-wrap: wrap;
}
.dv-pg-page .dv-sd-recent-sort {
    /* The Helix template's own compiled Bootstrap-reset CSS carries
       `select:not(.form-select):not(.form-control) { display:block; width:100%; }`
       (shaper_helixultimate's cached template stylesheet) at specificity
       (0,2,1) - one type selector (select) plus two :not() pseudo-classes.
       This class-only selector is (0,2,0): two classes, no type, so it LOSES
       that tie-break regardless of file load order (verified live via a full
       styleSheets walk - the Helix rule was the actual match, not anything in
       this file). `!important` on width alone is the targeted fix, not a
       broader override of a template rule we do not own (final fix wave item
       3, 2026-09-21). 190px fits the longest option ("By house number") plus
       the chevron with room to spare. */
    width: 190px !important;
    flex: 0 0 auto;
    align-self: flex-start;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    padding-right: 1.9rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23667080' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.65rem center;
    background-size: 10px 6px;
    cursor: pointer;
}
.dv-pg-page .dv-sd-recent-sort:focus-visible {
    outline: 2px solid var(--dv-blue);
    outline-offset: 2px;
}
.dv-pg-page .dv-sd-recent-table td:first-child { white-space: nowrap; }
.dv-pg-page .dv-sd-recent-table .dv-sd-row-link { font-weight: 600; }
.dv-pg-page .dv-sd-recent-table tr:hover .dv-sd-row-link { color: var(--dv-blue); text-decoration: underline; }
.dv-pg-page .dv-sd-recent-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.75rem 1.25rem;
    margin-top: 0.75rem;
    font-size: var(--dv-pg-t-caption);
    color: var(--dv-pg-ink-3);
}
.dv-pg-page .dv-sd-recent-foot a.dv-sd-correct-link {
    color: var(--dv-navy, #1F3A5F);
    text-decoration: underline;
}
.dv-pg-page .dv-sd-recent-foot a.dv-sd-correct-link:hover,
.dv-pg-page .dv-sd-recent-foot a.dv-sd-correct-link:focus-visible { color: var(--dv-blue); }
/* Link row under the table - the three CTA links the removed CTA box used to
   carry as buttons (h3/paragraph allow-listed; see tools/ops/briefing-slice1-urls.json). */
.dv-pg-page .dv-sd-linkrow {
    margin: 0.9rem 0 0;
    font-family: var(--dv-mono);
    font-size: 12px;
    color: var(--dv-pg-ink-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dv-pg-page .dv-sd-linkrow a { color: var(--dv-pg-ink-3); text-decoration: none; }
.dv-pg-page .dv-sd-linkrow a:hover,
.dv-pg-page .dv-sd-linkrow a:focus-visible { color: var(--dv-navy, #1F3A5F); text-decoration: underline; }

/* --- Repeat-sale cards ("What the same homes did"). Own 3-up grid rather
   than .dv-pg-grid-3's auto-fit (min column 290px): the valuation page spec
   flags the same auto-fit dropping to 2 columns inside a ~666px main column
   ("fix the wrap") - the street page's main column is the same width class,
   so three cards need an explicit equal-thirds track, not auto-fit. Card
   radius/padding is already set by the existing Task 9 (slice 1) rule
   `.dv-pg-page .dv-sd-block-repeat .dv-pg-card { border-radius: 6px; padding:
   1rem 1.1rem; }` a few screens up this file - these cards render inside that
   same .dv-sd-block-repeat wrapper, so no radius/padding rule is repeated
   here. */
.dv-pg-page .dv-sd-repeat-cards { margin-bottom: 1.5rem; }
.dv-pg-page .dv-sd-repeat-cards h2 { margin-bottom: 0.25rem; }
.dv-pg-page .dv-sd-repeat-cards .dv-pg-meta { margin: 0 0 0.9rem; }
.dv-pg-page .dv-sd-repeat-cardgrid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
}
.dv-pg-page .dv-sd-repeat-card-meta {
    margin: 0 0 0.5rem;
    font-size: 12px;
    color: var(--dv-pg-ink-3);
}
.dv-pg-page .dv-sd-repeat-card-price {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0 0 0.4rem;
}
.dv-pg-page .dv-sd-repeat-card-prev {
    font-size: var(--dv-pg-t-caption);
    color: var(--dv-pg-ink-3);
}
.dv-pg-page .dv-sd-repeat-card-now {
    font-family: var(--dv-serif);
    font-weight: 400;
    font-size: 22px;
    color: var(--dv-pg-ink);
}
.dv-pg-page .dv-sd-repeat-card-change {
    margin: 0;
    font-size: var(--dv-pg-t-caption);
}

@media (max-width: 767.98px) {
    .dv-pg-page .dv-sd-repeat-cardgrid { grid-template-columns: 1fr; }

    /* Phone: six-row compact table becomes two-line rows (brief Step 4 - the
       "display:grid per tr" option, chosen over the horizontal-scroll
       fallback). Column order in the markup is No./Type/Sold for/Date/
       Previous sale/Change (1-6); grid-template-areas re-flows them into two
       lines - No.+Type / price on line 1, Date+Previous sale / Change on
       line 2 - WITHOUT merging any cell's text (each <td>'s own content is
       untouched, so a screen reader still gets six distinct cells; only a
       decorative " · " between the paired cells is CSS-generated content).
       This does not fight .dv-pg-table's desktop skin: that skin only sets
       table/th/td display implicitly via normal table layout, which this
       block overrides just for the one table, just under the breakpoint. */
    .dv-pg-page .dv-sd-recent-table thead { display: none; }
    .dv-pg-page .dv-sd-recent-table,
    .dv-pg-page .dv-sd-recent-table tbody { display: block; width: 100%; }
    .dv-pg-page .dv-sd-recent-table tr {
        display: grid;
        grid-template-columns: auto 1fr auto;
        grid-template-areas: "no type price" "date prev change";
        column-gap: 0.4rem;
        row-gap: 2px;
        padding: 0.65rem 0;
        border-bottom: 1px solid var(--dv-pg-rule);
    }
    .dv-pg-page .dv-sd-recent-table tr:last-child { border-bottom: 0; }
    .dv-pg-page .dv-sd-recent-table td { display: block; padding: 0; border: 0; }
    .dv-pg-page .dv-sd-recent-table td:nth-child(1) { grid-area: no; }
    .dv-pg-page .dv-sd-recent-table td:nth-child(2) {
        grid-area: type;
        align-self: center;
        font-size: var(--dv-pg-t-caption);
        color: var(--dv-pg-ink-2);
    }
    .dv-pg-page .dv-sd-recent-table td:nth-child(2)::before { content: "\00b7\0020"; }
    .dv-pg-page .dv-sd-recent-table td:nth-child(3) { grid-area: price; justify-self: end; text-align: right; }
    .dv-pg-page .dv-sd-recent-table td:nth-child(4) {
        grid-area: date;
        font-size: var(--dv-pg-t-caption);
        color: var(--dv-pg-ink-3);
    }
    .dv-pg-page .dv-sd-recent-table td:nth-child(5) {
        grid-area: prev;
        font-size: var(--dv-pg-t-caption);
        color: var(--dv-pg-ink-3);
    }
    .dv-pg-page .dv-sd-recent-table td:nth-child(5)::before { content: "\00b7\0020"; }
    /* Item 1 (final fix wave, 2026-09-21): rows with no previous sale render an
       empty <td class="dv-sd-recent-noprev"> (see soldstreet/default.php) rather
       than a literal "-", so this cell must not also grow the phone separator
       above - the visible row would otherwise read "· -" for a first sale. */
    .dv-pg-page .dv-sd-recent-table td.dv-sd-recent-noprev::before { content: none; }
    .dv-pg-page .dv-sd-recent-table td:nth-child(6) { grid-area: change; justify-self: end; text-align: right; }

    .dv-pg-page .dv-sd-recent-foot { gap: 0.6rem 1rem; }

    /* !important needed here too: the desktop rule above is itself !important
       (it has to be, to beat the Helix template's select reset - see its own
       comment), and !important always outranks a plain declaration regardless
       of media query or source order, so without this the phone-width select
       stayed pinned at 190px instead of going full width (caught live at
       393px: selWidth was 190, not 100%, before this was added). */
    .dv-pg-page .dv-sd-recent-sort { width: 100% !important; }
}

/* Item 1 (final fix wave, 2026-09-21): desktop keeps the visible dash for "no
   previous sale" via CSS rather than a literal "-" in the markup, so the
   phone-only decorative separator above (td:nth-child(5)::before) never fires
   on this cell. The visible desktop table is unchanged from before this fix. */
@media (min-width: 768px) {
    .dv-pg-page .dv-sd-recent-table td.dv-sd-recent-noprev::after { content: "-"; color: var(--dv-pg-ink-3); }
}

/* Task 5 polish (2026-09-21, briefing layout slice 1b): phone rail order.
   Task 3 flagged that on phone the capture card + area guide link + upsell
   land between the navy valuation card and the repeat cards, because
   .dv-pg-shell-rail (domovita-pages.css) is one flex column that carries a
   single order:1 for all its children - it sits as one block right after the
   dv-pg-rail-first sales table (order:0) and before every default order:2
   item in .dv-pg-shell-main (repeat cards, the analysis-band <details>,
   living grid, other streets). Spec order (2026-09-21-briefing-layout-
   slice-1b-design.md): hero, stat grid, sales table, navy valuation card,
   repeat cards, band trigger, living, other streets - capture/area-guide/
   upsell are not in that list, so they go last (after everything, which is
   also "after the band" as the brief asks).

   Fix: make .dv-pg-shell-rail itself display:contents (street page only,
   scoped .dv-sd-page - do not touch the shared rule, the valuation page
   uses the same shell and its rail order is untouched/correct) so its
   children join the SAME grid flow as .dv-pg-shell-main's (also contents)
   children, each carrying its own explicit order instead of inheriting one
   shared order from the aside. */
@media (max-width: 1023.98px) {
    .dv-pg-page.dv-sd-page .dv-pg-shell-rail { display: contents; }
    /* .dv-ew (section, sold_estimate_widget.php) - right after the sales table. */
    .dv-pg-page.dv-sd-page .dv-pg-shell-rail > .dv-ew { order: 1; margin-bottom: 14px; }
    /* .dv-cap (sold_capture.php), .dv-area-guide-link, .dv-upsell-block
       (UpsellHelper) - after the analysis band and everything below it. */
    .dv-pg-page.dv-sd-page .dv-pg-shell-rail > .dv-cap,
    .dv-pg-page.dv-sd-page .dv-pg-shell-rail > .dv-area-guide-link,
    .dv-pg-page.dv-sd-page .dv-pg-shell-rail > .dv-upsell-block { order: 3; margin-bottom: 14px; }
    .dv-pg-page.dv-sd-page .dv-pg-shell-rail > .dv-upsell-block:last-child { margin-bottom: 0; }
}
