/*
 * listing.css v1.2.0
 *
 * LORE Search Portal — single-listing template (Chunk D)
 *
 * Inquiry success-state rebrand (v1.2.0, ADR-101 follow-up): the post-submit
 * success panel (.lore-inquiry-success p) used an off-palette green
 * (#e8f5e9 bg / #2e7d32 text) carried over from the V1 portal — not a LORE
 * brand color. Swapped to brand tokens: --color-bay-mist background with
 * --color-harbor-blue text, the sanctioned body-copy pairing from the brand
 * book (Color §p.17). Also moved the bare 1.6 line-height to --lh-normal
 * (same value, on-system). Pairs with inquiry-cta.php v1.5.0 (copy neutralized
 * pending final agent wording). CSS-only.
 *
 * Hero location-block tighten (v1.1.9, ADR-100 follow-up): the address line
 * (v1.1.8) and the neighborhood caption were separated by the full hero-inner
 * flex gap (--space-3 mobile / --space-4 tablet+), reading as loose. Pulled the
 * neighborhood up via .lore-listing__address + .lore-listing__neighborhood so
 * the effective gap is --space-1 at both breakpoints — the two read as one
 * location block under the H1. CSS-only.
 *
 * Hero address sub-line (v1.1.8, ADR-100): new .lore-listing__address rule —
 * the full street address now renders on its own line beneath the headline H1
 * (the H1 carries Salesforce Headline__c as of the mapper pivot). Body font,
 * --fs-lg, muted; inherits the hero's flex-column --space-3 gap. Pairs with
 * single-listing.php v1.0.25. CSS-only.
 *
 * Inquiry close-button charcoal fix (v1.1.7): inquiry.js v1.0.5 focuses the
 * close control on open; a programmatic .focus() fires :focus (not
 * :focus-visible), so GeneratePress's button:focus { background:#3f4047 }
 * (0,1,1) was bleeding charcoal onto the X. Restated the resting/hover
 * background on .lore-inquiry-modal__close:focus / :focus:hover at class
 * specificity (0,2,0) per ADR-079. CSS-only.
 *
 * Inquiry modal fold tighten (v1.1.6): the label-above pattern (v1.1.5) added
 * a ~20px label row per field, pushing the submit button just below the fold
 * on mobile. Recovered space without crowding: modal body top/bottom padding
 * space-5→space-4, agent header margin-bottom space-3→space-2, inter-field
 * margin-bottom space-2→space-1 (the reserved label row already separates
 * fields), opt-in margin space-3→space-2. ~40px back; desktop padding (the
 * @media ≥768 override) unchanged.
 *
 * Inquiry field label moved ABOVE the field (v1.1.5): the floating-inside
 * label was unwinnable on iOS (Safari centers input text and ignores padding,
 * so the value could never sit where we wanted without overlapping the
 * label). Switched patterns: the label now lifts into a reserved row ABOVE
 * the field instead of inside it. Field height back to a normal 48px (the
 * extra internal space is gone). The field reserves the label row via
 * padding-top:20px, so the name appearing/disappearing on entry never shifts
 * the layout. At rest the label still reads as a centered placeholder inside
 * the field (so empty fields stay labeled); it lifts on focus/value. Same
 * markup as v1.4.0 (placeholder=" " + input-first <label>) — CSS-only change.
 *
 * Inquiry field alignment — root cause fix (v1.1.4): the prior passes
 * (v1.1.1–v1.1.3) all failed for the same reason — iOS Safari vertically
 * CENTERS input/select text inside a fixed `height` and ignores asymmetric
 * top/bottom padding, so "push the value down with padding" did nothing and
 * a taller field only grew the empty space below the centered value.
 * Fix: drop the fixed height entirely. height:auto + line-height:1.3 lets
 * the field size to its content, so the text anchors at the top padding
 * (24px, clears the floated label) with a small 7px bottom padding — the
 * value sits low, the field is SHORTER (~56px vs 70px), and there's no dead
 * space at the bottom. min-height:50px keeps the tap target. Floated label
 * top 7→6. Same rule for input and select, so they stay consistent.
 *
 * Inquiry field height for true bottom-alignment (v1.1.3): the v1.1.2 nudge
 * was imperceptible — at 62px the value couldn't drop further without
 * re-clipping descenders (the ceiling we hit on the select). Field height
 * 62→70 and padding 34/10 shared by BOTH input and select drops the value
 * into the lower third with the line box fully inside the field (iOS-safe,
 * no clip), so label-top / value-low now reads clearly and consistently on
 * every field — the select included (the v1.1.2 input-only override is gone).
 * Costs ~8px/field of the fold savings; still far tighter than the original
 * stacked-label layout.
 *
 * Inquiry floating-label alignment (v1.1.2): the value read as vertically
 * centered/floating. Floated label pinned higher (top 9→7) and the text
 * inputs drop the value toward the bottom (input padding-top 27→34, bottom
 * 8→6) so it's label-top / value-low. The Role <select> keeps the shared
 * centered padding deliberately — pushing a select's text lower at this
 * height re-clips descenders (the "g"/"y"); it reads fine centered.
 *
 * Inquiry floating-label fit fix (v1.1.1): the v1.1.0 field was too short
 * for the floated-label layout — the lifted label and the entered value
 * crowded each other and the Role select clipped descenders (the "g" in
 * "Agent"). Field height 52→62, top padding 20→27 (label sits clear above
 * the value), floated label top 7→9. Uses the bottom slack the tighten
 * freed up; no overlap, descenders clear.
 *
 * Inquiry modal — floating labels + mobile tighten + charcoal fixes (v1.1.0):
 *   Pairs with inquiry-cta.php v1.4.0 (input-first markup). Form fields now
 *   use floating labels (label centered at rest, lifts on focus/value via
 *   input:not(:placeholder-shown) + label; the --select field floats its
 *   label persistently). Mobile fold tightened: headshot 96→64, body top
 *   padding space-6→space-5, header/subheader/field/opt-in margins trimmed,
 *   sign-in nudge (.lore-inquiry-loginhint) made smaller + muted. Two ADR-079
 *   charcoal fixes: .lore-inquiry-form__submit restates --color-scarlet across
 *   :hover/:focus/:active (GP button:hover/:focus was bleeding #3f4047), and
 *   .lore-inquiry-cta gains a :focus resting state so the bubble doesn't flash
 *   charcoal when the modal minimizes and JS refocuses it.
 *
 * Mobile-first per ADR-014. Apple-clean per ADR-015. Responsive
 * overrides discipline per ADR-017. Parent-respecting layout per
 * ADR-026.
 *
 * Rent Roll table↔card switch — container query (v1.0.48):
 *   The §8 Rent Roll table-structure rules moved out of the viewport
 *   @media (min-width: 768px) block into a new
 *   @container lore-rentroll (min-width: 720px) block, with
 *   container-type: inline-size on .lore-listing__rent-roll-inner. The
 *   table now flips to stacked cards based on the width of its OWN
 *   container, not the viewport — fixing the desktop map-overlay panel,
 *   where the viewport is wide but the panel is narrow, so the old
 *   viewport query kept the 6-column table cramped until the whole
 *   window dropped under 768px. The section's padding rule stays in the
 *   viewport @media (page spacing, not layout-mode). Pairs with
 *   rent-roll.js v1.2.0, which now detects card mode from the dots'
 *   computed visibility (+ a ResizeObserver) instead of a viewport
 *   matchMedia, so the carousel/dot JS stays in sync with the CSS.
 *
 * OpEx gate prompt — right-aligned (v1.0.47):
 *   CSS-only. The "Sign in to view" link inside the Operating Expenses
 *   dropdown was top-left (inline-block); moved it to the right
 *   (display:block; width:fit-content; margin-left:auto) so it lines up
 *   with the right-aligned breakdown values below it AND matches the §8
 *   Rent Roll login link, which already sits right in its header. No
 *   markup change — single-listing.php stays v1.0.24.
 *
 * OpEx toggle — overlay scroll-jump fix (v1.0.46):
 *   CSS-only. In the desktop listings overlay (a fixed/positioned panel),
 *   expanding the OpEx breakdown scrolled the whole page down past the
 *   footer. Cause: the hidden toggle checkbox received focus on label
 *   click and, with no positioned ancestor, its box resolved to document
 *   coordinates, so the browser's scroll-into-view yanked the page. Fix:
 *   made the OpEx label cell position:relative and pinned the checkbox at
 *   top:0/left:0 inside it (opacity:0, 1px, pointer-events:none), so the
 *   focus target is always already in view → scroll-into-view is a no-op.
 *   Replaced the clip/sr-only technique with opacity:0; the input stays in
 *   the a11y tree + tab order. No markup change — single-listing.php
 *   stays v1.0.23.
 *
 * Operating Expenses toggle — scarlet chevron, text dropped (v1.0.45):
 *   Product call: remove the "Expand"/"Collapse" word and keep only the
 *   chevron, recolored scarlet so it stands out against the muted table.
 *   Removed __opex-expand-hint (+ its mobile-first @media block from
 *   v1.0.44) and the __opex-expand-word--expand/--collapse display-swap
 *   rules — with no text there's nothing to wrap, so the mobile row-height
 *   issue v1.0.44 addressed is moot. The chevron rule now sets
 *   color: var(--color-scarlet) (borders use currentColor), bumped to
 *   0.5em with a --space-2 left margin; the :has(:checked) rotation is
 *   unchanged. Pairs with single-listing.php v1.0.23 (word spans removed).
 *
 * Operating Expenses hint — mobile sizing (v1.0.44):
 *   CSS-only. On mobile the "Expand" hint at the desktop size didn't fit
 *   beside "Operating Expenses" in the narrow label cell and wrapped to a
 *   second line, making that row taller than its neighbors (desktop had
 *   room, so it was fine there). Made the hint mobile-first: the base rule
 *   is now a smaller, tighter size (0.625rem, --ls-tight, --space-1 margin,
 *   0.2em gap) that fits on one line on phones; a co-located
 *   @media (min-width: 768px) block restores the original desktop size. The
 *   em-sized chevron scales with the font, so it shrinks on mobile too.
 *   No markup change — single-listing.php stays v1.0.22.
 *
 * Operating Expenses disclosure — column-divider fix (v1.0.43):
 *   v1.0.42 rendered the OpEx line as a single colspan=2 <details> cell,
 *   which dropped the parent-theme's vertical column divider on that row
 *   (one cell = no middle border). Reworked: the summary is now a normal
 *   two-cell row (inherits the divider + separators), and the breakdown
 *   is a separate full-width row toggled by a visually-hidden labeled
 *   checkbox via :has() (ADR-081 technique). Replaced __opex-disclosure /
 *   __opex-summary(::-webkit-details-marker) with __opex-toggle-input
 *   (clip-hidden, focusable) / __opex-summary-label + __opex-value-label
 *   (both <label for>) / __opex-expand-hint (+ Expand/Collapse word toggle
 *   driven by :has(:checked)) / __opex-detail-row + __opex-detail-cell.
 *   Chevron now uses currentColor + :has(:checked) rotation. Focus ring
 *   moves to the label via input:focus-visible + label. Still no JS, still
 *   bfcache-safe. The breakdown stays a single-column group stack.
 *
 * §6/§7 merge — Operating Expenses disclosure (v1.0.42):
 *   The standalone §7 Operating Expenses section was folded into the
 *   §6 Financial Summary waterfall (single-listing.php v1.0.21). The
 *   "Operating Expenses" line is now a native <details> disclosure
 *   inside the summary table. Added: __opex-disclosure / __opex-summary
 *   (flex row mimicking the waterfall label/value columns, native marker
 *   hidden) / __opex-summary-chevron (CSS box-corner, rotates on [open])
 *   / __opex-summary:focus-visible (inset cabernet ring) / __opex-
 *   disclosure-body (hairline-separated, single-column group stack
 *   override). Removed: the dead .lore-listing__opex / -inner / -header
 *   section-root rules and their two responsive padding overrides, plus
 *   the now-unreachable .lore-listing__opex-groups 2-col @media rule
 *   (the breakdown forces single-column inside the disclosure body). The
 *   reused __opex-group(s) / -label / -list / -row / -value / --masked
 *   and __opex-login-link rules are unchanged. The --masked blur stays
 *   the visual-only incentive; the plugin REST filter + template gate
 *   remain the real boundary.
 *
 * §5 Property Summary column-rule rollback (v1.0.41):
 *   User feedback after seeing v1.0.40's two-column flow live: in
 *   practice the two-column layout for the Property Summary body
 *   didn't read as well alongside the other listing-page sections.
 *   Rolling back to the simple-wide treatment originally labeled
 *   Option A in scoping: §5 body fills the inner container width as
 *   a single column on all viewports.
 *
 *   One CSS change:
 *     - Removed the tablet+ rule block on .lore-listing__description-
 *       body that set column-count: 2 + column-gap: var(--space-6).
 *     - The max-width: 65ch removal from v1.0.40 STAYS — that is what
 *       allows §5 to render end-to-end. Without the column rule, the
 *       body is now a single full-width column at all viewports.
 *
 *   §11 Disclaimer is unchanged from v1.0.40 (already simple-wide,
 *   user happy with that treatment).
 *
 *   Trade-off accepted: §5 prose now has long lines on desktop (~150
 *   characters wide at the 1400px container max-width). Typography
 *   best practice prefers ~65-75 character line length for prolonged
 *   reading. Per user direction the visual balance with adjacent
 *   sections outweighs the line-length readability concern for this
 *   surface.
 *
 * §5 Property Summary + §11 Disclaimer width-fill (v1.0.40):
 *   User feedback after v1.0.39 ship: both prose sections (§5 body and
 *   §11 body) felt visually unbalanced compared to §3/§4/§6/§7/§8
 *   which all fill the inner content width via grids or tables. The
 *   65ch line-length caps on both bodies (typographic best practice for
 *   readability) were producing ~50% empty space on the right side of
 *   each section at desktop widths.
 *
 *   Two coordinated changes:
 *
 *     (a) §5 Property Summary — switches to two-column flow on tablet+
 *         to fill the width WHILE preserving readable line length. The
 *         max-width: 65ch cap is dropped; in its place a tablet+
 *         column-count: 2 + column-gap: var(--space-6) rule lets the
 *         body wpautop() output flow naturally into two ~70ch columns
 *         on wide viewports. On mobile (< 768px) no column rule
 *         applies, so the body renders as a single column at the
 *         container's full width — line length is naturally bounded
 *         by the narrow viewport. Browser default column-fill: balance
 *         keeps the two columns roughly equal height when the prose is
 *         shorter than the section. No column-rule (no divider line);
 *         the Apple-clean aesthetic doesn't need it.
 *
 *     (b) §11 Disclaimer — drops the max-width: 65ch cap entirely.
 *         The disclaimer copy is a single sentence of legal
 *         boilerplate; the readability argument that justifies a
 *         line-length cap for substantive prose doesn't apply here.
 *         End-to-end fill renders the disclaimer as ~2 lines on
 *         desktop instead of the previous 3 left-cramped lines.
 *
 *   No HTML/PHP change required for either section. Pure CSS.
 *
 *   Mobile behavior: §5 single-column at viewport width (unchanged
 *   line wrapping at narrow widths). §11 full-width inside the
 *   inner container's existing padding (negligible visual difference
 *   from before at mobile widths since the viewport already
 *   constrained the line length).
 *
 * §11 Disclaimer marker size unification (v1.0.39):
 *   User feedback after v1.0.38 ship: the dagger size was right on §3
 *   CAP Rate / GRM (host is --fs-2xl, marker at 0.6em → comfortable
 *   ~20px visible) but too small on §6 / §7 headings (host --fs-xl,
 *   --heading variant dropped it to 0.45em → small) and on the §8
 *   Market Rent <th> (host --fs-xs, marker at 0.6em → ~7px tiny). The
 *   em-relative-to-host model produced inconsistent visible sizes
 *   across hosts of different scales.
 *
 *   Fix: switch the marker from host-relative em to a root-relative
 *   absolute size that exactly reproduces the §3 CAP/GRM rendered size
 *   regardless of host.
 *
 *     font-size: 0.6em        →   font-size: calc(var(--fs-2xl) * 0.6)
 *
 *   Because --fs-2xl is the token §3 uses for the pricing-metric-value
 *   font, calc(var(--fs-2xl) * 0.6) produces the SAME px value that
 *   0.6em produced inside the §3 context. The marker therefore looks
 *   identical on §3 (no visible change there), and inherits that same
 *   absolute size in every other context. Because --fs-2xl has the
 *   theme's standard responsive overrides at smaller viewports, the
 *   marker continues to scale down on mobile alongside the rest of the
 *   typography — independent of the host's own scaling.
 *
 *   Removed: the `.lore-listing__disclaimer-marker--heading` font-size
 *   override (0.45em) — no longer needed since the marker no longer
 *   inherits its size from the host. The `--heading` and `--th`
 *   modifier classes stay in the HTML and CSS as no-op hooks for any
 *   future per-context tuning, but neither carries an active
 *   declaration now.
 *
 *   Trade-off accepted on §8 <th>: the host is the small uppercase
 *   column-header register (--fs-xs), so the marker will render
 *   visibly LARGER than its host text — a prominent dagger on top of
 *   a small label. Per user direction this is the intended outcome
 *   (the dagger serves as a marker, not a typographic peer to the
 *   label).
 *
 * §11 Disclaimer + estimate-bearing marker pattern (v1.0.38):
 *   New section + new global marker class supporting ADR-034. Adds:
 *
 *     `.lore-listing__disclaimer-marker` — the clickable dagger anchor
 *     placed inline next to estimate-bearing values (§3 CAP Rate /
 *     GRM values, §6 / §7 section headings, §8 Market Rent <th>).
 *     Cabernet color, font-size 0.6em superscript, 8px padding for
 *     >=44x44 hit area per ADR-014, hover/focus-visible treatments
 *     matching the §7/§8 login-link pattern. Variant
 *     `--heading` drops to 0.45em for the larger §6/§7 <h2> hosts so
 *     the dagger reads as a footnote mark rather than a co-equal
 *     element of the heading.
 *
 *     `.lore-listing__disclaimer` — bottom-of-template section, white
 *     background, generous top/bottom padding. The inner element
 *     carries a 1px --color-border-soft hairline along its top edge
 *     to demarcate the disclaimer from §8 above without imposing a
 *     full card frame.
 *
 *     `.lore-listing__disclaimer-heading` — "Disclaimer" eyebrow in
 *     the §4 tile-meta register (uppercase, --fs-xs, --ls-loose,
 *     muted). Deliberately quieter than the Larken display headings
 *     of §5–§8 so the section reads as supporting fine-print rather
 *     than another content surface.
 *
 *     `.lore-listing__disclaimer-body` — body paragraph at --fs-sm,
 *     muted, line-height 1.6, max-width 65ch. Roman not italic
 *     (italic at small sizes hurts legibility; the muted color
 *     already signals fine-print).
 *
 *     `.lore-listing__disclaimer-mark` — the leading dagger span
 *     in front of the body text. aria-hidden in the HTML so screen
 *     readers skip the typographic mark; visually attached to the
 *     first word of the body via small right-margin.
 *
 *   Smooth-scroll: `html { scroll-behavior: smooth }` becomes a
 *   global rule, scoped to the document root. The existing
 *   scroll-behavior: smooth on .lore-listing__rent-roll-tbody is
 *   unaffected (specificity carries; the tbody override stays
 *   targeted). The tablet+ override that previously set
 *   `scroll-behavior: auto` on the tbody continues to apply only
 *   to that tbody.
 *
 *   :target arrival highlight: when the URL hash matches the
 *   disclaimer section's id, a 1.6s keyframe animation flashes
 *   --color-linen on the inner container and fades it back to
 *   transparent. Scoped to the inner element (which has no static
 *   background) so the section's white surface stays uninterrupted
 *   throughout. The animation is fire-once on initial :target
 *   match — subsequent clicks of the same marker in one session
 *   smooth-scroll but won't re-flash, which is acceptable: the
 *   arrival cue matters most for the first navigation.
 *
 *   §8 mobile decorative dagger: no CSS change required. The
 *   existing `.lore-listing__rent-roll-cell::before { content:
 *   attr(data-label) }` rule already reads the data-label
 *   attribute, which now carries the dagger as part of its value
 *   ("Market Rent†"). The dagger inherits the label's
 *   --color-text-muted color and uppercase letter-spacing-loose
 *   styling — visually consistent with the rest of the label.
 *   See ADR-034 for why this surface can't host a clickable
 *   anchor (CSS attr()-generated content cannot contain
 *   interactive elements).
 *
 *   Pure CSS; no JS change accompanying this version.
 *
 * §8 Rent Roll table-frame removal (v1.0.37):
 *   User reports a visible frame around the entire table during
 *   carousel scrolling on mobile — when two cards are mid-
 *   transition you can see card borders plus what looks like an
 *   outer table perimeter, creating extra lines around the
 *   rounded card edges.
 *
 *   The mobile base table rule in v1.0.36 had no explicit border,
 *   but defensive resets are added here to guarantee no visual
 *   boundary leaks through from UA defaults, table-layout side
 *   effects, or inherited properties:
 *
 *     .lore-listing__rent-roll-table (mobile base) gets explicit
 *     `border: 0`, `background: transparent`, `box-shadow: none`,
 *     `padding: 0`, `outline: 0`. Any of these could theoretically
 *     render a frame; explicit zeroing removes ambiguity.
 *
 *     `border-collapse: separate` and `border-spacing: 0` move
 *     into the tablet+ override block where they actually matter
 *     (the tablet+ table form has a 1px outer border that needs
 *     border-collapse: separate to play cleanly with border-radius
 *     clipping — irrelevant at mobile where display: block makes
 *     the table behave as a generic block container).
 *
 *     `.lore-listing__rent-roll-thead` gets `clip-path: inset(50%)`
 *     alongside the deprecated `clip: rect(0,0,0,0)` for
 *     belt-and-suspenders visually-hidden behavior across all
 *     modern browsers (some have known quirks with the deprecated
 *     `clip` property when the parent is `display: block`).
 *
 *   No markup change. JS unchanged.
 *
 * §8 Rent Roll snap-lock (v1.0.36):
 *   `scroll-snap-type: x proximity` flips back to `mandatory` and
 *   `scroll-snap-stop: always` is added to each card. Together they
 *   force the carousel to commit to exactly one card per gesture:
 *
 *     - mandatory: the scroller MUST land at a snap point on release.
 *       Halfway between two cards goes to whichever is >50% visible
 *       (the >50% rule the user described — built into mandatory
 *       browser behavior, no JS).
 *     - scroll-snap-stop: always: a single swipe cannot skip past
 *       the next snap point. Fast flicks land on the immediately
 *       adjacent card, not 2 cards over. Predictable one-card-at-
 *       a-time navigation.
 *
 *   In v1.0.34 I switched to proximity thinking mandatory was the
 *   cause of the choppy desktop trackpad feel. With cards at 100%
 *   width (v1.0.35) there's no peek to fight the snap during
 *   partial swipes, so mandatory no longer has the choppiness it
 *   had at 92% width — and it correctly delivers the "lock to one
 *   card" behavior the user wants.
 *
 *   No markup change. JS unchanged. The dot ↔ scroll sync in
 *   rent-roll.js v1.0.0 works the same with mandatory snap.
 *
 * §8 Rent Roll card-frame fit (v1.0.35):
 *   Two coordinated fixes to make each card fit the frame exactly,
 *   matching the photo gallery pattern (one item per frame, scales
 *   with viewport):
 *
 *     (a) Cards now use `flex: 0 0 100%` (was 88%) so each card
 *         takes the full scroller width. No peek — dots are the
 *         "more to scroll" affordance. Matches the photo gallery
 *         where one photo fills the frame and dots indicate
 *         position. With 100% cards there's no possibility of
 *         right-edge value clipping regardless of label width.
 *
 *     (b) `.lore-listing__rent-roll-table` gets `display: block`
 *         on mobile. The default `display: table` was interfering
 *         with the flex tbody's width calculation — even with
 *         `box-sizing: border-box` set on the row, the table's
 *         intrinsic-width layout was making cards render wider
 *         than their flex-basis. `display: block` on the table
 *         neutralizes the table layout algorithm, letting the
 *         flex tbody size itself cleanly to 100% of its parent.
 *         Tbody also gets an explicit `width: 100%` for the same
 *         defensive reason. Tablet+ reverts both: table back to
 *         `display: table`, tbody back to default (table-row-
 *         group sizing).
 *
 *   No markup change. JS unchanged. The carousel mechanics
 *   (scroll-snap-type: x proximity, scroll-snap-align: start, dot
 *   indicator, no-loop) all carry forward unchanged.
 *
 * §8 Rent Roll carousel polish pass 2 (v1.0.34):
 *   Three coordinated fixes against real-device observation:
 *
 *     (a) Card width: content-box sizing meant the v1.0.33 cards
 *         rendered as `92% + 32px padding + 2px border` ≈ 5–7px
 *         wider than the scroller viewport. The right portion of
 *         each card (where right-aligned values live) was clipped
 *         off-screen. Card now uses box-sizing: border-box and
 *         flex-basis drops from 92% to 88% so the next-card peek
 *         is more pronounced as a "scroll affordance."
 *
 *     (b) Snap behavior: scroll-snap-type: x mandatory was fighting
 *         the trackpad's continuous-scroll input on desktop and
 *         producing a choppy feel. Switched to x proximity — the
 *         scroller still snaps when close to a card, but doesn't
 *         interrupt mid-scroll momentum. Touch swipe behavior on
 *         mobile is unchanged in practice (proximity vs mandatory
 *         is mostly indistinguishable on flick-velocity input).
 *
 *     (c) Dot indicator: full pattern alignment with the gallery
 *         dots (.lore-listing__gallery-dot — convention #22). The
 *         button gains `outline: none` so iOS Safari + Chrome don't
 *         paint a focus-default outline on tap (this was the
 *         "black box around the selected dot" the v1.0.33 dots
 *         exhibited). :focus-visible (keyboard only) keeps the
 *         cabernet ring with outline-offset: -8px placing it INSIDE
 *         the button against the 6px mark. Tap target sizes up
 *         from 32px to 44px to meet ADR-014. Mark sizes down from
 *         8px to 6px to match gallery. Inactive mark color is
 *         --color-border (very light gray) instead of --color-text-
 *         muted at 0.4 opacity. Hover/active preview = cabernet at
 *         0.5 opacity. Active = cabernet, opacity 1, scale 1.3.
 *
 * §8 Rent Roll mobile carousel polish (v1.0.33):
 *   The mobile layout flips from a vertical stack of cards (v1.0.32)
 *   to a horizontal scroll-snap carousel with a dot indicator below.
 *
 *   `<tbody class="lore-listing__rent-roll-tbody">` becomes the
 *   scroll container — display: flex, gap, overflow-x: auto,
 *   scroll-snap-type: x mandatory. Each `<tr>` is a fixed-width
 *   (92% of viewport, ~17px next-card peek with the gap) card
 *   with scroll-snap-align: start. No infinite loop — scroll-snap
 *   naturally stops at the last card; the empty slot to the right
 *   where the next card would otherwise peek IS the end affordance.
 *
 *   Dot indicator (.lore-listing__rent-roll-dots) sits below the
 *   scroller — one button per row, cabernet-active dot mark,
 *   muted inactive. JS in rent-roll.js v1.0.0 keeps the dots in
 *   sync with scroll position via a passive scroll listener and
 *   wires dot-tap to scrollTo({behavior: 'smooth'}).
 *
 *   Tablet+ (>=768px) reverts: tbody → display: table-row-group,
 *   <tr> → display: table-row with no flex/snap, dot row hidden.
 *   The table form already shipped in v1.0.32 is unchanged.
 *
 * §8 Rent Roll (v1.0.32):
 *   New section block for §8 Rent Roll. Responsive-table pattern:
 *   single <table> markup, layout pivots by breakpoint. Mobile
 *   (<768px) renders each row as a stacked card — <thead> hidden,
 *   each <td>'s data-label attribute surfaces the column name via
 *   ::before. Tablet+ (>=768px) reverts to a standard horizontal
 *   table with the column header row visible in uppercase muted
 *   treatment matching the §3/§4 label aesthetic. Card chrome
 *   (1px border, --radius-md, --space-4 padding) reused from §7
 *   so the whole template reads consistently.
 *
 *   Gated state (.lore-listing__rent-roll--gated on the section
 *   root): identical login-link surfacing + --masked value
 *   treatment as §7. Real values never enter the DOM when gated;
 *   the plugin v2.7.0 REST filter is the server-side boundary,
 *   the blur is decoration.
 *
 *   Per-cell zero/blank suppression: empty value <span>s on
 *   mobile collapse the entire <td> via :has(:empty) so the
 *   stacked-card pattern doesn't render 'Current Rent: ' with
 *   nothing after. Desktop keeps the empty cell to preserve
 *   column alignment.
 *
 * §7 Operating Expenses total-row removal (v1.0.31):
 *   Removed the three .lore-listing__opex-total* rules. Section
 *   no longer renders a bottom total row — see single-listing.php
 *   v1.0.16 for rationale.
 *
 * §7 Operating Expenses (v1.0.30):
 *   New section block for §7 OpEx. Four-group responsive grid
 *   (1-col mobile, 2-col tablet+) with each group rendered as a
 *   self-contained card. Group label sits in small-caps muted
 *   type above each card's row list; rows are dt/dd pairs with
 *   the label flowing left and the dollar value right-aligned
 *   in tabular-nums. Section card border + radius matches §3
 *   Pricing Metrics so the whole template reads as one
 *   consistent surface.
 *
 *   Gated state (.lore-listing__opex--gated on the section root):
 *     - 'Log in to view all information' link surfaces top-right
 *       of the section header, scarlet accent + arrow glyph in
 *       the link color treatment used by the existing CTA
 *       buttons.
 *     - Every row renders with a placeholder value ('$—,—' for
 *       dollars, '—%' for the rate). The placeholder span
 *       carries .lore-listing__opex-value--masked which applies
 *       filter: blur(6px) + user-select: none. Real values are
 *       never in the DOM in this state — the blur is decoration,
 *       not security; the plugin v2.7.0 REST filter is what
 *       actually withholds the data server-side.
 *
 *   Total row at the bottom of the section uses a hairline-above
 *   + larger cabernet display font, the same 'this is the
 *   answer' treatment §6 uses for its subtotal rows.
 *
 * §2 Map iframe + external tap-out (v1.0.22) — Step D:
 *   Closes the §2 Photo/Map view toggle workstream by replacing
 *   the v1.0.19 placeholder rules with rules for the real iframe
 *   and an "Open in Google Maps" tap-out pill. Pairs with single-
 *   listing.php v1.0.12 (iframe markup + lat/lng gate) and
 *   functions.php v2.0.16 (cache-bust).
 *
 *   Changes:
 *     - `.lore-listing__gallery-map-pane` gains `position: relative`
 *       so the new external-link pill anchors against the pane.
 *       The pane's `display: none` default and the `[data-view=
 *       "map"]` activation rule from v1.0.19 carry forward
 *       unchanged.
 *     - New `.lore-listing__gallery-map-iframe` rule: `position:
 *       absolute; inset: 0` to fill the pane regardless of how
 *       the pane's intrinsic size is computed (the aspect-ratio-
 *       based height works correctly under absolute children).
 *       `border: 0` because the iframe carries no chrome of its
 *       own, and `display: block` to suppress the default
 *       inline-replaced layout baseline.
 *     - New `.lore-listing__gallery-map-external` rule for the
 *       "Open in Google Maps" tap-out: dark rgba(0,0,0,0.85) pill
 *       anchored bottom-right of the pane at --space-3 inset
 *       (mirrors the bottom-right photo-count pill's positioning
 *       on the photo frame). Inline-flex layout with the text +
 *       a Material Design `open_in_new` icon at --space-1 gap.
 *       Hover/focus/active locked to a fuller alpha + the same
 *       white text per working convention #22 (UA-default :active
 *       suppression). Focus-visible outline in white because the
 *       pill backdrop is dark; outline-offset 3px keeps the ring
 *       legible against the photo content behind the iframe.
 *     - The v1.0.19 `.lore-listing__gallery-map-placeholder` and
 *       `.lore-listing__gallery-map-placeholder-text` rules are
 *       REMOVED — the corresponding markup is gone from single-
 *       listing.php v1.0.12. Keeping the rules around would be
 *       dead code.
 *
 *   The platform's lat/lng-only address policy (ADR-030) is the
 *   reason both the iframe `src` and the external pill `href`
 *   carry only the LAT,LNG pair, never a resolved street address.
 *
 * §2 Photo/Map toggle backdrop refinement (v1.0.21):
 *   Second cosmetic pass on the toggle. v1.0.20 used a single dark
 *   rgba(0,0,0,0.85) pill backdrop across both breakpoints; visual
 *   review showed that pill blends into the photo at desktop (where
 *   it sits on a varying photo background) and over-dominates the
 *   white page at mobile (where it stands alone). v1.0.21 splits
 *   the backdrop by breakpoint so each context gets a treatment
 *   sized to its surface:
 *
 *     - Mobile (<768px): the pill backdrop becomes
 *       rgba(0,0,0,0.04) with a 1px --color-border-soft outline.
 *       Almost invisible against the white page — the pill is
 *       there only to visually group the two icon buttons; the
 *       cabernet active circle is the only saturated element. This
 *       is the iOS-segmented-control aesthetic: light track,
 *       expressive thumb.
 *     - Desktop (>=768px): the pill backdrop becomes
 *       rgba(255,255,255,0.85) with `backdrop-filter: blur(8px)`
 *       (and -webkit-backdrop-filter for Safari < 18). Frosted
 *       glass — the Apple Maps / Control Center pattern. The blur
 *       neutralizes whatever's behind it (sky, foliage, building
 *       edges) into a soft white field so the icons read against
 *       any photo content. Fallback when backdrop-filter is
 *       unsupported: the rgba alone still renders as a slightly-
 *       transparent white pill (no blur), still readable.
 *
 *   Inactive icon color flips from rgba(255,255,255,0.7) (designed
 *   for the old dark backdrop) to --color-text-muted (designed for
 *   the new light backdrops). Hover/focus bumps to --color-text.
 *   The --active modifier stays unchanged at cabernet fill + white
 *   icon — cabernet against either light backdrop has strong
 *   contrast and is the right brand signal.
 *
 *   Focus-visible outline flips from --color-white to
 *   --color-cabernet so the keyboard-focus ring stays visible
 *   against the now-light pill (white outline on light backdrop
 *   would have disappeared).
 *
 *   No PHP markup change. No JS change. The button structure, icon
 *   SVGs, ARIA semantics, and JS wireup from v1.0.11/v1.5.0 carry
 *   forward unchanged.
 *
 * §2 Photo/Map toggle cosmetic polish (v1.0.20):
 *   Cosmetic-pass iteration on the Step C scaffolding shipped in
 *   v1.0.19. Pairs with single-listing.php v1.0.11 (DOM
 *   restructure + icon swap) and functions.php v2.0.14 (cache
 *   bust). gallery.js v1.5.0 unchanged.
 *
 *   Three coordinated changes:
 *     - `.lore-listing__gallery-inner` becomes `position: relative`
 *       so the toggle can absolute-position against it at desktop.
 *       Mobile keeps the toggle in normal flow (the relative
 *       positioning is benign for static children).
 *     - `.lore-listing__gallery-toggle` is rewritten mobile-first:
 *       default state is a static block-level pill below the photo
 *       frame, centered via `width: fit-content` + `margin: 0
 *       auto`. The dark rgba(0,0,0,0.85) backdrop carries across
 *       both breakpoints — at <768px it stands alone on the page
 *       background; at >=768px it overlays the photo. Single
 *       visual identity, two positioning contexts.
 *     - At the >=768px breakpoint (the same one that surfaces the
 *       chevrons), the toggle flips to `position: absolute` with
 *       `top: var(--space-3)` and `right: var(--space-3)`,
 *       mirroring the chevron right-edge inset. This places it in
 *       the top-right corner of the photo frame, matching the live
 *       portal pattern and avoiding collision with the bottom-
 *       right photo-count pill.
 *
 *   Button content + active state:
 *     - `.lore-listing__gallery-toggle-btn` is now a square icon
 *       container (38x38) instead of a text label. The button's
 *       text-style rules from v1.0.19 (font-size, letter-spacing,
 *       text-transform) are dropped — there is no text inside the
 *       buttons anymore, only an 18x18 SVG icon.
 *     - The active modifier `--active` switches to a
 *       --color-cabernet fill with --color-white icon, instead of
 *       the v1.0.19 white-fill + dark-text scheme. Cabernet
 *       carries the active signal cleanly without leaning on text
 *       contrast (now absent) and matches the LORE brand accent.
 *     - Inactive icon color is rgba(255,255,255,0.7) — same as
 *       v1.0.19 inactive text color, kept as a soft contrast
 *       against the dark backdrop. Hover/focus on the inactive
 *       button bumps the icon to full white but keeps the
 *       backdrop transparent (no fill flash on press).
 *
 *   Working convention #22 (UA-default :active suppression):
 *     - Explicit :hover/:focus/:active rules on both the base
 *       button and the --active modifier. The active button's
 *       press state holds the cabernet fill — no flash to a
 *       UA-default browser highlight.
 *
 * §2 Photo/Map view toggle scaffolding (v1.0.19):
 *   New CSS surface for the Step C toggle + map-pane (single-
 *   listing.php v1.0.10). Adds:
 *     - `.lore-listing__gallery-toggle` — segmented pill, top-
 *       center of the photo frame at --space-3 inset. Backdrop
 *       is rgba(0,0,0,0.5), the same family as the photo-count
 *       pill and chevrons. Anchors against the existing
 *       `position: relative` on `.lore-listing__gallery-stage`
 *       (no new positioning context required). Two buttons
 *       inside; layout via inline-flex with --space-1 gap.
 *     - `.lore-listing__gallery-toggle-btn` — inactive segment is
 *       transparent with rgba(255,255,255,0.7) text. The --active
 *       modifier flips to solid white background + dark text.
 *       38px min-height per segment + --space-2 horizontal
 *       padding. UA-default :active background is explicitly
 *       suppressed per working convention #22 (the same
 *       suppression rule is already in place for chevrons and
 *       dot buttons).
 *     - `.lore-listing__gallery-map-pane` — placeholder frame on
 *       a --color-sand background. 3/2 aspect-ratio on
 *       mobile/tablet matching the hero, flipped to 16/9 at the
 *       >=1200px breakpoint to match the desktop hero. Display
 *       defaults to none; the `[data-view="map"]` selector on the
 *       parent section flips display to flex. A centered
 *       "MAP (PLACEHOLDER)" label uses --color-text-muted for
 *       Step C; Step D replaces the placeholder div with an
 *       iframe so the label simply gets unset by being removed
 *       from the markup.
 *     - View-state visibility rules: `[data-view="map"]
 *       .lore-listing__gallery-hero` and `[data-view="map"]
 *       .lore-listing__gallery-chevron` hide via display: none
 *       so the map pane occupies the frame alone; the inverse
 *       `[data-view="photos"] .lore-listing__gallery-map-pane`
 *       hides the map pane. Default behavior (no data-view or
 *       data-view="photos") shows the photo content per the
 *       existing rules — the new selectors are additive, not
 *       refactors of the existing display rules.
 *
 * §6 Financial Summary card-style alignment (v1.0.18):
 *   Brings the financial-summary table in line with the §3 Pricing
 *   Metrics card aesthetic: 1px border, --radius-md rounded corners,
 *   --space-4 interior padding. The table switches from border-
 *   collapse: collapse to border-collapse: separate + border-
 *   spacing: 0 so the outer border-radius clips cleanly (collapsed
 *   borders break radius on tables). Cell padding goes from
 *   --space-2 0 to --space-2 --space-4 so labels and values inset
 *   from the bordered edges instead of sitting flush. First/last
 *   rendered rows get --space-4 padding-top / padding-bottom via
 *   tbody:first-child / tbody:last-child selectors — mirrors the
 *   --space-4 interior padding of §3 metric cards. The empty-group
 *   collapse logic in single-listing.php means tbody:first-child
 *   and tbody:last-child match the first and last RENDERED groups,
 *   not the spec positions, so partial-data tables stay correctly
 *   inset. Group-separator rule (adjacent-sibling tbody) unchanged
 *   — first row of subsequent groups still gets --space-4 padding-
 *   top, matching the card-edge inset of the first row of the
 *   first group.
 *
 * §6 Financial Summary rules (v1.0.17):
 *   New section block: a focused waterfall table with three
 *   <tbody> groups (income build, NOI calc, upside). Group
 *   separators are an adjacent-sibling padding rule rather than
 *   borders, since group ordering is content-driven and borders
 *   on adjacent rows would compete visually with the subtotal
 *   hairline. Subtotal rows (Effective Gross Income, Net
 *   Operating Income) carry a 1px hairline above both cells plus
 *   a Larken cabernet value at --fs-lg — the same "this is the
 *   answer" treatment the §3 metric values use, scaled for an
 *   in-table footnote. Value cells use font-variant-numeric:
 *   tabular-nums + white-space: nowrap so the dollar column
 *   stacks cleanly. Table max-width 560px keeps the financial
 *   block from sprawling at desktop widths; it's a focused
 *   document, not a banner. Operator prefixes ('+', '−', '=')
 *   render as a fixed-slot inline span in the label cell so
 *   labels align regardless of which rows are present.
 *
 * §2 lightbox stage cursor cleanup (v1.0.16):
 *   Dropped `cursor: pointer` from the lightbox stage base rule.
 *   The pointer was signaling "tap to close" — gallery.js v1.4.1
 *   removed that behavior (close is X-button or Escape only), so
 *   the pointer affordance was now misleading. Stage at 1x reverts
 *   to default cursor (arrow on desktop). The v1.0.15 cursor-state
 *   classes — `--zoomed` (grab) and `--grabbing` (grabbing) — are
 *   unchanged.
 *
 * §2 desktop pinch + drag-pan cursor states (v1.0.15):
 *   Two cursor rules added on the lightbox stage to give visible
 *   affordance for desktop interactions introduced in
 *   gallery.js v1.4.0:
 *     - `.lore-lightbox__stage--zoomed { cursor: grab; }`
 *       toggled on when lbZoom > 1 (any zoom path: trackpad
 *       pinch, double-click, touch pinch). Signals "you can drag
 *       this".
 *     - `.lore-lightbox__stage--grabbing { cursor: grabbing; }`
 *       toggled on while a primary mouse button is held during a
 *       drag. Standard grabbing-hand affordance.
 *   The base stage rule keeps `cursor: pointer` for the zoom-1
 *   "tap/click to close" affordance. JS toggles the classes via
 *   updateStageCursor() so the cursor stays in sync with zoom
 *   state from any input source.
 *
 * §2 pinch-zoom lightbox support (v1.0.14):
 *   Two changes, both inside the lightbox:
 *
 *     (a) Dropped `.lore-lightbox__img--zoomed { transform:
 *         scale(2) }`. Pinch-zoom + pan in gallery.js v1.3.0
 *         applies the transform inline (translate + scale)
 *         continuously, so the class-based 2x toggle is no
 *         longer the source of truth for zoom state. The base
 *         `.lore-lightbox__img` rule keeps the
 *         `transition: transform 200ms ease` declaration so
 *         JS-driven transitions (double-tap toggle, snap-back
 *         from elastic below-1x, pan clamp at touchend) animate.
 *         During active pinch/pan gestures JS sets
 *         `transition: none` inline to make the image follow
 *         fingers without delay, then restores it on touchend.
 *
 *     (b) Added `touch-action: none` to `.lore-lightbox__stage`.
 *         Prevents the browser from intercepting pinch as a
 *         native page-zoom gesture and from interpreting
 *         horizontal drags as edge-swipe history navigation on
 *         some mobile browsers. All gesture handling — pinch,
 *         pan, single-finger swipe nav, double-tap — flows
 *         through the JS handlers in gallery.js.
 *
 *   No JS class-marker is needed to indicate "zoomed" state in
 *   v1.3.0 — the inline `style.transform` carries the full
 *   information. The class is removed from JS as well as CSS.
 *
 * §2 dot button polish patch (v1.0.13):
 *   The v1.0.12 polish suppressed the focus outline and tap
 *   highlight on the dot button, but missed a third UA-default
 *   source: browsers paint a default background-color on <button>
 *   elements in :active (and sometimes :hover) state. Even with
 *   `background: transparent` on the base rule, the UA stylesheet
 *   override on press paints a gray fill — visible in the user's
 *   2026-05-17 screenshot as a "gray box" around the tapped dot
 *   while the mouse button is held down.
 *
 *   Fix:
 *     (a) Explicitly re-assert `background: transparent` on the
 *         dot button's :hover, :focus, :active states. Defeats the
 *         UA default; the button visually doesn't change on press,
 *         the only feedback is the dot mark color/scale change
 *         inside it.
 *     (b) Hover preview at 0.5 opacity cabernet on the dot mark.
 *         Indicates "this is what you're about to activate" without
 *         introducing a non-brand color or persistent change. The
 *         existing color transition (background, 150ms ease) makes
 *         this read as a smooth fade — touchdown shows the preview,
 *         touchend commits the click which fires the carousel
 *         navigate, the dot becomes the active one (full cabernet,
 *         1.3x scale) and the previously-active dot returns to the
 *         muted border color.
 *
 *   Same background-suppression extended to the chevron buttons
 *   for consistency — they have the same UA-default issue on
 *   click. Their existing hover treatment (rgba(0,0,0,0.7))
 *   already differentiates them visually, but the UA :active
 *   background was layering on top of that and producing a
 *   slightly darker flash on press. Now :active explicitly inherits
 *   the :hover value so the press visual is consistent with the
 *   hover.
 *
 * Section structure pattern: see v1.0.4+ header comment.
 *
 * Changelog (v1.0.0 through v1.0.12): see v1.0.12 header.
 *   v1.0.13 — Dot button background suppression + hover preview.
 *             Pure CSS, no JS, no PHP change.
 *   v1.0.14 — Lightbox pinch-zoom support: dropped class-based
 *             scale(2) (JS owns transform now), added
 *             `touch-action: none` to stage to claim pinch
 *             gestures away from the browser.
 *   v1.0.15 — Desktop cursor states for the lightbox stage:
 *             grab when zoomed > 1, grabbing while dragging.
 *             Pairs with gallery.js v1.4.0.
 *   v1.0.16 — Dropped `cursor: pointer` from lightbox stage base.
 *             Stage no longer signals "click to close" because
 *             gallery.js v1.4.1 removed the tap-to-close path
 *             (X button + Escape only). Cursor-state classes
 *             (--zoomed, --grabbing) unchanged.
 *
 * See DECISIONS.md ADR-014 / ADR-015 / ADR-017 / ADR-018 / ADR-025 /
 * ADR-026 / ADR-027 / ADR-028.
 */

/* ─── Global: smooth in-page anchor scroll (ADR-034) ──────────────── */
/*
 * Enables smooth-scroll for all in-page anchor navigations (the §11
 * disclaimer markers' href="#disclaimer-target" links, plus any
 * future anchor links). The existing scroll-behavior: smooth on
 * .lore-listing__rent-roll-tbody continues to apply via its own
 * selector — this rule does not conflict.
 *
 * Respects prefers-reduced-motion via the media query below.
 */
html {
  scroll-behavior: smooth;
}

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

/* ─── Defeat GP's right-sidebar layout on listing pages ─────────────── */
body.single-listing .grid-container.container {
  width: 100%;
  max-width: 100%;
  padding-left: 0;
  padding-right: 0;
}

body.single-listing main.lore-listing {
  width: 100%;
  max-width: 100%;
  float: none;
  clear: both;
}

/* ─── Hero container ───────────────────────────────────────────────── */
.lore-listing__hero {
  background: var(--color-white);
  padding: var(--space-6) var(--space-4) var(--space-8);
}

.lore-listing__hero-inner {
  max-width: 1400px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}

/* ─── Status pill ──────────────────────────────────────────────────── */
.lore-listing__status {
  display: inline-block;
  padding: 7px 16px;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  white-space: nowrap;
}

.lore-listing__status--available {
  background: var(--color-harbor-blue);
  color: var(--color-white);
}

.lore-listing__status--pending {
  background: var(--color-sand);
  color: var(--color-text);
}

.lore-listing__status--off_market {
  background: var(--color-bay-mist);
  color: var(--color-cabernet);
}

.lore-listing__status--sold {
  background: var(--color-scarlet);
  color: var(--color-linen);
}

/* ─── Title (H1) ───────────────────────────────────────────────────── */
.lore-listing__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-regular);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--color-text);
}

/* ─── Address sub-line (full street address beneath the headline H1) ── */
.lore-listing__address {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-lg);
  font-weight: var(--fw-regular);
  line-height: var(--lh-snug);
  color: var(--color-text-muted);
}

/* ─── Neighborhood (caption-style) ─────────────────────────────────── */
.lore-listing__neighborhood {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Tighten the address↔neighborhood pair into one location block under the
 * H1 (ADR-100 follow-up). The hero-inner flex gap (--space-3 mobile,
 * --space-4 at tablet+) is too loose between these two related lines; pull
 * the neighborhood up so the effective gap is --space-1 at both breakpoints
 * and the pair reads as a unit, separated from title/price by the full gap. */
.lore-listing__address + .lore-listing__neighborhood {
  margin-top: calc(var(--space-1) - var(--space-3));
}

/* ─── Price ────────────────────────────────────────────────────────── */
.lore-listing__price {
  margin: var(--space-2) 0 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-family: var(--font-display);
  line-height: var(--lh-tight);
}

.lore-listing__price-original {
  font-size: var(--fs-lg);
  font-weight: var(--fw-regular);
  color: var(--color-text-muted);
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
}

.lore-listing__price-asking {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-regular);
  color: var(--color-cabernet);
  letter-spacing: var(--ls-tight);
}

/* ─── §2 Photo Gallery ─────────────────────────────────────────────── */
.lore-listing__gallery {
  background: var(--color-white);
  padding: var(--space-6) var(--space-4);
}

.lore-listing__gallery-inner {
  position: relative;
  max-width: 1400px;
  margin: 0 auto;
}

.lore-listing__gallery-stage {
  position: relative;
}

.lore-listing__gallery-hero {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--color-linen);
  border-radius: var(--radius-md);
  overflow: hidden;
  cursor: pointer;
  font: inherit;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
  touch-action: pan-y;
  outline: none;
  transition: none;
}

.lore-listing__gallery-hero:hover,
.lore-listing__gallery-hero:focus,
.lore-listing__gallery-hero:active {
  background: var(--color-linen);
}

.lore-listing__gallery-hero:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: 3px;
}

.lore-listing__gallery-hero-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity 180ms ease;
}

.lore-listing__gallery-hero-img--fading {
  opacity: 0;
}

.lore-listing__gallery-pill {
  position: absolute;
  bottom: var(--space-3);
  right: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  background: rgba(80, 0, 0, 0.85);
  color: var(--color-white);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}

.lore-listing__gallery-pill-icon {
  display: inline-block;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--color-white);
}

.lore-listing__gallery-pill-text {
  display: inline-block;
}

/* Chevrons — hidden on mobile, visible at tablet+. v1.0.13 adds
   explicit :active background to lock the press visual identical
   to :hover, defeating any UA default mid-click flash. */
.lore-listing__gallery-chevron {
  position: absolute;
  top: 50%;
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-round);
  background: rgba(0, 0, 0, 0.5);
  color: var(--color-white);
  cursor: pointer;
  transform: translateY(-50%);
  transition: background 150ms ease;
  -webkit-tap-highlight-color: transparent;
  outline: none;
  z-index: 2;
}

.lore-listing__gallery-chevron:hover,
.lore-listing__gallery-chevron:active {
  background: rgba(0, 0, 0, 0.7);
}

.lore-listing__gallery-chevron:focus-visible {
  outline: 2px solid var(--color-white);
  outline-offset: 3px;
}

.lore-listing__gallery-chevron--prev {
  left: var(--space-3);
}

.lore-listing__gallery-chevron--next {
  right: var(--space-3);
}

/* ─── §2 Photo/Map view toggle (v1.0.20) ─────────────────────────────
   Mobile-first: static positioning below the photo frame, centered via
   width: fit-content + margin auto. Desktop (>=768px) override below
   flips to absolute positioning at the top-right of the photo frame,
   anchored against .lore-listing__gallery-inner (which is set
   position: relative). v1.0.21 splits the backdrop by breakpoint:
   - Mobile (this rule): rgba(0,0,0,0.04) + --color-border-soft
     outline. Light and quiet against the white page; the cabernet
     active circle is the only saturated element.
   - Desktop (>=768px override): frosted glass via backdrop-filter
     blur + rgba(255,255,255,0.85). See media query below.
   z-index 2 matches the chevrons. */
.lore-listing__gallery-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  width: fit-content;
  margin: var(--space-3) auto 0;
  padding: 4px;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.04);
  z-index: 2;
}

/* Square icon button. 38x38 keeps the visual height matching the
   text-version from v1.0.19 (same min-height) so the pill silhouette
   is identical; just tighter horizontally now that there's no text. */
.lore-listing__gallery-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  outline: none;
  transition: background 150ms ease, color 150ms ease;
}

.lore-listing__gallery-toggle-icon {
  display: block;
  width: 18px;
  height: 18px;
  color: inherit;
}

/* UA-default :active background suppression — working convention #22.
   Inactive base: hover/focus/active bumps icon from --color-text-muted
   to full --color-text; backdrop stays transparent so the press
   doesn't flash to a UA fill. */
.lore-listing__gallery-toggle-btn:hover,
.lore-listing__gallery-toggle-btn:focus,
.lore-listing__gallery-toggle-btn:active {
  background: transparent;
  color: var(--color-text);
}

/* Active state: cabernet fill, white icon. All four states locked so
   the press visual stays cabernet across hover/focus/active. */
.lore-listing__gallery-toggle-btn--active,
.lore-listing__gallery-toggle-btn--active:hover,
.lore-listing__gallery-toggle-btn--active:focus,
.lore-listing__gallery-toggle-btn--active:active {
  background: var(--color-cabernet);
  color: var(--color-white);
}

/* Cabernet outline (was white in v1.0.20) — keyboard-focus ring needs
   to stay visible against the new light backdrop at mobile and the
   frosted-white backdrop at desktop. */
.lore-listing__gallery-toggle-btn:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: 3px;
}

/* ─── §2 Map pane placeholder (v1.0.19) ──────────────────────────────
   Sibling of the hero button + chevrons inside .gallery-stage. Hidden
   by default; the parent section's data-view="map" attribute flips it
   to display:block (see view-state rules at the end of this block).
   v1.0.22 (Step D): gained position:relative so the iframe and the
   "Open in Google Maps" tap-out anchor cleanly against the pane.
   The --color-sand background carries through as a fallback color
   during iframe load (the user sees sand briefly before the iframe
   tiles paint). */
.lore-listing__gallery-map-pane {
  position: relative;
  display: none;
  width: 100%;
  aspect-ratio: 3 / 2;
  background: var(--color-sand);
  border-radius: var(--radius-md);
  overflow: hidden;
}

/* Iframe fills the pane via position:absolute + inset:0 — robust
   against any future change to how the pane sizes itself. border:0
   suppresses the UA-default iframe border. display:block suppresses
   the inline-replaced layout baseline that would otherwise leave a
   stray pixel of whitespace below the iframe. */
.lore-listing__gallery-map-iframe {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* "Open in Google Maps" tap-out at bottom-right of the map pane.
   Same family as the bottom-right photo-count pill (rgba dark
   backdrop, --fs-xs --fw-medium --ls-loose uppercase) but with
   --space-1 gap to the external-link icon and an anchor element
   instead of a span. */
.lore-listing__gallery-map-external {
  position: absolute;
  bottom: var(--space-3);
  right: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.85);
  color: var(--color-white);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  outline: none;
  z-index: 2;
  transition: background 150ms ease, color 150ms ease;
}

/* UA-default :active background suppression — working convention #22.
   Hover/focus/active lock to a fuller-alpha backdrop with white text.
   The anchor's UA-default underline is already suppressed by
   text-decoration: none on the base rule. */
.lore-listing__gallery-map-external:hover,
.lore-listing__gallery-map-external:focus,
.lore-listing__gallery-map-external:active {
  background: rgba(0, 0, 0, 1);
  color: var(--color-white);
}

.lore-listing__gallery-map-external:focus-visible {
  outline: 2px solid var(--color-white);
  outline-offset: 3px;
}

.lore-listing__gallery-map-external-icon {
  display: inline-block;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: inherit;
}

/* ─── §2 View-state visibility rules (v1.0.19) ──────────────────────
   When data-view="map" on the section root, hide the hero photo and
   chevrons, show the map pane. Default (data-view="photos" or absent)
   inherits the existing visibility — these rules are additive, not
   refactors. */
.lore-listing__gallery[data-view="map"] .lore-listing__gallery-hero {
  display: none;
}

.lore-listing__gallery[data-view="map"] .lore-listing__gallery-chevron {
  display: none;
}

.lore-listing__gallery[data-view="map"] .lore-listing__gallery-map-pane {
  display: block;
}

.lore-listing__gallery[data-view="map"] .lore-listing__gallery-dots {
  display: none;
}

/* ─── §2 dot indicator (windowed) ──────────────────────────────────── */
.lore-listing__gallery-dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* Dot button: 44x44 tap target with a 6px visible mark centered
   inside. v1.0.13 fixes the UA-default :active gray-fill artifact
   by explicitly locking the button background to transparent
   across all interactive states. The visual feedback for press is
   delegated entirely to the dot mark child element (color +
   scale + opacity), not the button itself. */
.lore-listing__gallery-dot {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.lore-listing__gallery-dot:hover,
.lore-listing__gallery-dot:focus,
.lore-listing__gallery-dot:active {
  background: transparent;
}

.lore-listing__gallery-dot:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: -8px;
  border-radius: var(--radius-round);
}

.lore-listing__gallery-dot-mark {
  display: block;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-round);
  background: var(--color-border);
  transition: background 150ms ease, transform 150ms ease, opacity 150ms ease;
}

/* Hover preview: dot mark fades to cabernet at 0.5 opacity, telling
   the user "this is what you're about to activate." Non-active
   inactive dots only — the active dot already has its own full-
   cabernet rule and shouldn't preview anything (it's already the
   selected state). */
.lore-listing__gallery-dot:hover .lore-listing__gallery-dot-mark,
.lore-listing__gallery-dot:active .lore-listing__gallery-dot-mark {
  background: var(--color-cabernet);
  opacity: 0.5;
}

/* Active state takes precedence — when a dot becomes active, the
   :hover and :active rules above are still in scope but the
   --active modifier overrides via specificity + later declaration.
   Active dot is full opacity cabernet at 1.3x scale. */
.lore-listing__gallery-dot--active .lore-listing__gallery-dot-mark {
  background: var(--color-cabernet);
  opacity: 1;
  transform: scale(1.3);
}

/* Edge-of-window dots: scaled down with reduced opacity to suggest
   "more beyond." If the user hovers an edge dot, the hover preview
   still applies (cabernet at 0.5 opacity) — they see the preview
   color but at the smaller scale, communicating "you can click this
   to jump there, and there are more in this direction." */
.lore-listing__gallery-dot--edge .lore-listing__gallery-dot-mark {
  transform: scale(0.65);
  opacity: 0.7;
}

.lore-listing__gallery-dot--out-of-window {
  display: none;
}

.lore-listing__gallery-manifest {
  display: none;
}

/* ─── Section heading (shared §3-§11) ──────────────────────────────── */
.lore-listing__section-heading {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-regular);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--color-text);
}

/* ─── §3 Pricing Metrics ───────────────────────────────────────────── */
.lore-listing__pricing-metrics {
  background: var(--color-white);
  padding: var(--space-6) var(--space-4);
}

.lore-listing__pricing-metrics-inner {
  max-width: 1400px;
  margin: 0 auto;
}

.lore-listing__pricing-metrics-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}

.lore-listing__pricing-metric {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: var(--space-4);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.lore-listing__pricing-metric-value {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-regular);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--color-cabernet);
  margin-bottom: var(--space-2);
}

.lore-listing__pricing-metric-label {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* ─── §4 Property Facts ────────────────────────────────────────────── */
.lore-listing__property-facts {
  background: var(--color-white);
  padding: var(--space-6) var(--space-4);
}

.lore-listing__property-facts-inner {
  max-width: 1400px;
  margin: 0 auto;
}

.lore-listing__property-facts-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.lore-listing__property-fact {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.lore-listing__property-fact-value {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-regular);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--color-cabernet);
  margin-bottom: var(--space-1);
}

.lore-listing__property-fact-label {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* ─── §5 Description ───────────────────────────────────────────────── */
.lore-listing__description {
  background: var(--color-white);
  padding: var(--space-6) var(--space-4);
}

.lore-listing__description-inner {
  max-width: 1400px;
  margin: 0 auto;
}

.lore-listing__description-body {
  font-family: var(--font-body);
  color: var(--color-text);
  line-height: var(--lh-normal);
}

.lore-listing__description-body p {
  margin: 0 0 var(--space-3);
}

.lore-listing__description-body p:last-child {
  margin-bottom: 0;
}

/* ─── §6 Financial Summary ─────────────────────────────────────────── */
.lore-listing__financial-summary {
  background: var(--color-white);
  padding: var(--space-6) var(--space-4);
}

.lore-listing__financial-summary-inner {
  max-width: 1400px;
  margin: 0 auto;
}

/* Focused-table footprint: max-width 560px keeps the waterfall from
 * sprawling on wide viewports. Margin: 0 keeps it flush-left under the
 * heading rather than centered — the heading anchors the left edge and
 * the table reads as a continuation of it. v1.0.18 wraps the table in
 * the §3-card aesthetic: 1px border + --radius-md rounded corners.
 * border-collapse: separate + border-spacing: 0 is required for the
 * radius to clip; the alternative (border-collapse: collapse) merges
 * cell borders with the table border in a way that breaks the radius.
 */
.lore-listing__financial-summary-table {
  width: 100%;
  max-width: 560px;
  margin: 0;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  color: var(--color-text);
}

/* Cell padding: --space-4 horizontal so labels and values inset from
 * the bordered table edges; --space-2 vertical for row-to-row rhythm.
 * Group separators and subtotal hairlines override padding-top
 * separately below.
 */
.lore-listing__financial-row td {
  padding: var(--space-2) var(--space-4);
  vertical-align: baseline;
  line-height: var(--lh-normal);
}

/* First/last rendered rows get --space-4 padding-top / padding-bottom
 * so content sits the same --space-4 inset from the rounded edges as
 * it does from the left/right edges — matches §3 metric card interior
 * padding. The empty-group collapse logic in the template means
 * tbody:first-child / tbody:last-child match the first/last RENDERED
 * groups, not the spec positions, so a partial-data table (e.g., only
 * group 2 and 3 populated) still gets the correct top/bottom inset.
 */
.lore-listing__financial-summary-table tbody:first-child .lore-listing__financial-row:first-child td {
  padding-top: var(--space-4);
}

.lore-listing__financial-summary-table tbody:last-child .lore-listing__financial-row:last-child td {
  padding-bottom: var(--space-4);
}

/* Group separator: extra top padding on the first row of any tbody
 * that follows another tbody. Empty groups don't render <tbody> at
 * all, so the adjacent-sibling selector fires only between groups
 * that actually have content.
 */
.lore-listing__financial-group + .lore-listing__financial-group
  .lore-listing__financial-row:first-child td {
  padding-top: var(--space-4);
}

.lore-listing__financial-label {
  text-align: left;
  padding-right: var(--space-4);
}

/* Op prefix: fixed-slot inline span so labels align regardless of
 * which rows render. 1.25em width gives a single-glyph operator
 * (+, −, =) a consistent gutter without crowding the label.
 */
.lore-listing__financial-op {
  display: inline-block;
  width: 1.25em;
  color: var(--color-text-muted);
}

.lore-listing__financial-value {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Subtotal rows (EGI, NOI): hairline above both cells signals
 * "this is the calculation result." Value cell adopts the display
 * font + cabernet color treatment that §3 metric values use — the
 * established "this is the answer" recipe, scaled to in-table size.
 */
.lore-listing__financial-row--subtotal .lore-listing__financial-label,
.lore-listing__financial-row--subtotal .lore-listing__financial-value {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3);
}

.lore-listing__financial-row--subtotal .lore-listing__financial-label {
  font-weight: var(--fw-medium);
}

.lore-listing__financial-row--subtotal .lore-listing__financial-value {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  color: var(--color-cabernet);
  letter-spacing: var(--ls-tight);
}

/* ─── Operating Expenses — inline disclosure under §6 ────────────────
   v1.0.43: column-divider fix. The summary is now a NORMAL two-cell
   waterfall row (label <td> + value <td>) so it inherits the parent-
   theme's column divider + row separators exactly like every other row —
   the prior colspan=2 <details> cell had a single cell and so dropped the
   vertical middle divider. The expandable breakdown is a SEPARATE full-
   width row toggled by a visually-hidden, labeled checkbox via :has()
   (ADR-081 technique; no JS, bfcache-safe). A visible "Expand"/"Collapse"
   hint + chevron makes the affordance explicit. The reused group/row/
   value/--masked and __opex-login-link rules below are unchanged.
*/

/* The OpEx summary label cell is the positioning context for the hidden
   toggle. Pinning the checkbox at the cell's top-left (top:0/left:0) means
   that when it receives focus — on label click or Tab — its box resolves
   INSIDE this already-visible cell, so the browser's scroll-into-view is a
   no-op. Without this, inside the desktop overlay (a fixed/positioned
   panel) the focus target resolved to document coordinates and the browser
   scrolled the page down past the footer (v1.0.46 fix). */
.lore-listing__financial-row--opex .lore-listing__financial-label {
  position: relative;
}

/* The toggle: visually hidden (opacity:0, 1px) but kept in the a11y tree
   and tab order so keyboard users can Tab to it and Space to toggle.
   pointer-events:none so it never intercepts clicks — the <label>s do. */
.lore-listing__opex-toggle-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

/* Both the label-cell content and the value are <label for> the toggle,
   so a click anywhere on the summary row expands/collapses. */
.lore-listing__opex-summary-label,
.lore-listing__opex-value-label {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Summary label flows inline exactly like a normal row's label content
   (op gutter + text), then the hint + chevron. */
.lore-listing__opex-summary-label {
  display: inline;
}

.lore-listing__opex-value-label {
  display: block; /* fills the value cell; inherits the cell's right align */
}

/* Toggle affordance: a scarlet chevron only — no text label. The arrow
   alone signals expandability (per product call), and scarlet makes it
   stand out against the muted table. CSS box-corner pointing down when
   closed, flips up when the toggle is checked. Em-sized so it tracks the
   label font; scarlet via currentColor on the borders. (v1.0.45 dropped
   the former "Expand"/"Collapse" word + its mobile-sizing @media block —
   no text means nothing to wrap, so the mobile row-height issue is moot.) */
.lore-listing__opex-summary-chevron {
  display: inline-block;
  width: 0.5em;
  height: 0.5em;
  margin-left: var(--space-2);
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  color: var(--color-scarlet);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
  position: relative;
  top: -0.15em;
}

.lore-listing__financial-group:has(.lore-listing__opex-toggle-input:checked)
  .lore-listing__opex-summary-chevron {
  transform: rotate(225deg);
  top: 0.05em;
}

/* Keyboard focus: ring the visible label when the hidden checkbox has
   focus (cabernet, matching the login link's focus treatment). */
.lore-listing__opex-toggle-input:focus-visible + .lore-listing__opex-summary-label {
  outline: 2px solid var(--color-cabernet);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Detail row: hidden until the toggle is checked (:has() on the group). */
.lore-listing__opex-detail-row {
  display: none;
}

.lore-listing__financial-group:has(.lore-listing__opex-toggle-input:checked)
  .lore-listing__opex-detail-row {
  display: table-row;
}

/* Detail cell: full-width breakdown panel. Class selector beats the
   parent-theme's td-element padding without !important. */
.lore-listing__opex-detail-cell {
  padding: var(--space-3) var(--space-4) var(--space-4);
}

.lore-listing__opex-detail-cell .lore-listing__opex-login-link {
  display: block;
  width: fit-content;
  margin-left: auto; /* right-align the prompt — consistent with the right-
                        aligned breakdown values below it and the §8 Rent
                        Roll login link (which sits right in its header) */
  margin-bottom: var(--space-3);
}

/* Single-column group stack inside the focused 560px table reads cleaner
   than the 2-col grid (overrides the global grid for this context). */
.lore-listing__opex-detail-cell .lore-listing__opex-groups {
  grid-template-columns: 1fr;
}


/* Login link surfaces only when .lore-listing__opex--gated is on the
   root. Scarlet to match the CTA bubble's brand-accent role — it is
   the same conversion incentive surface as the inquiry CTA, so it
   inherits the same color signal. */
.lore-listing__opex-login-link {
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--color-scarlet);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

.lore-listing__opex-login-link:hover,
.lore-listing__opex-login-link:focus {
  text-decoration: underline;
  color: var(--color-scarlet);
}

.lore-listing__opex-login-link:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* Group grid: mobile is single column (groups stack), tablet+ flips
   to 2-col. Card border + radius matches §3 Pricing Metrics. */
.lore-listing__opex-groups {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

.lore-listing__opex-group {
  padding: var(--space-4);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

/* Group label: small-caps muted type, sits above the row list.
   Mirrors the §3/§4 label aesthetic (uppercase, --fs-xs, muted)
   so the whole template's typography is unified. */
.lore-listing__opex-group-label {
  margin: 0 0 var(--space-3);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Row list: definition-list semantics (dt/dd) — labels and values
   are inherently paired. Each .opex-row wraps the dt+dd into a
   flex pair so they read horizontally with the value right-aligned. */
.lore-listing__opex-list {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.lore-listing__opex-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.lore-listing__opex-label {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  color: var(--color-text);
  line-height: var(--lh-normal);
}

/* Value cells use tabular-nums + nowrap so the dollar column stacks
   cleanly within each group. */
.lore-listing__opex-value {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  color: var(--color-text);
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  line-height: var(--lh-normal);
}

/* Masked placeholder: blur + suppress text selection so copy-paste
   yields visually-obscured text. The plugin v2.7.0 REST filter is
   the real security boundary — this is purely a visual incentive
   to log in. The blur radius is tuned so individual glyph shapes
   are unreadable but the overall row footprint is preserved. */
.lore-listing__opex-value--masked {
  filter: blur(6px);
  -webkit-user-select: none;
  user-select: none;
  pointer-events: none;
  color: var(--color-text-muted);
}

/* ─── §8 Rent Roll ────────────────────────────────────────────────────
   Responsive-table pattern (table-becomes-cards). Single <table>
   markup; mobile (<768px) styles <tr> as a stacked card with each
   <td>'s data-label rendered as ::before; tablet+ reverts to a
   horizontal table. Card chrome matches §3/§7: 1px border,
   --radius-md, --space-4 padding. Cell value spans carry the
   --masked modifier when gated; per-cell empty values collapse
   their <td> on mobile via :has(:empty).
*/
.lore-listing__rent-roll {
  background: var(--color-white);
  padding: var(--space-6) var(--space-4);
}

.lore-listing__rent-roll-inner {
  max-width: 1400px;
  margin: 0 auto;
  /* Query container for the table<->card switch (v1.0.48). The Rent Roll
     flips on the width of THIS element, not the viewport — so it switches
     correctly inside the narrow desktop map-overlay panel (where the
     viewport is wide but the panel is not) as well as on the full-width
     single-listing page. See the @container block below. */
  container-type: inline-size;
  container-name: lore-rentroll;
}

.lore-listing__rent-roll-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

/* Login link — same scarlet treatment as §7. Surfaces only when
   .lore-listing__rent-roll--gated is on the root. */
.lore-listing__rent-roll-login-link {
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--color-scarlet);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

.lore-listing__rent-roll-login-link:hover,
.lore-listing__rent-roll-login-link:focus {
  text-decoration: underline;
  color: var(--color-scarlet);
}

.lore-listing__rent-roll-login-link:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* Table reset: kill UA defaults that would compete with our
   borders + radius. border-collapse: separate so the outer
   border-radius clips cleanly on tablet+ (collapsed borders
   break radius on tables; same trick used by §6).

   display: block on mobile (v1.0.35) neutralizes the table-layout
   algorithm so the flex tbody can size cleanly to 100% of its
   parent — the default `display: table` was making cards render
   wider than their flex-basis. Tablet+ reverts to display: table
   for the standard table form. */
.lore-listing__rent-roll-table {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  outline: 0;
}

/* Mobile (<768px) — horizontal scroll-snap carousel. <tbody> becomes
   the scroll container; each <tr> is a fixed-width card. <thead>
   stays clipped (column labels are rendered by ::before on each cell
   instead). Scroll-snap stops naturally at the last card — no
   infinite loop. The empty slot to the right of the last card (where
   the next-card peek would normally sit) is the end affordance. */
.lore-listing__rent-roll-thead {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.lore-listing__rent-roll-tbody {
  display: flex;
  flex-direction: row;
  width: 100%;
  gap: var(--space-3);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.lore-listing__rent-roll-tbody::-webkit-scrollbar {
  display: none;
}

.lore-listing__rent-roll-row {
  flex: 0 0 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  box-sizing: border-box;
  display: block;
  padding: var(--space-4);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.lore-listing__rent-roll-cell {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  border: 0;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  color: var(--color-text);
  line-height: var(--lh-normal);
}

/* data-label renders as the in-cell label on mobile. Mirrors the
   §3/§4 label aesthetic (uppercase, --fs-xs, muted, letter-spacing
   loose) so the per-unit cards feel consistent with the rest of
   the template. */
.lore-listing__rent-roll-cell::before {
  content: attr(data-label);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  color: var(--color-text-muted);
  flex-shrink: 0;
}

/* Numeric cells (Unit Size, Current Rent, Market Rent) get
   tabular-nums + nowrap on the value side. */
.lore-listing__rent-roll-cell--num .lore-listing__rent-roll-value {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.lore-listing__rent-roll-value {
  text-align: right;
}

/* Per-cell zero/blank suppression: a <td> whose value <span> is
   empty collapses entirely on mobile so the card doesn't render
   'Current Rent: ' with nothing after. Desktop unsets this in
   the @media block below so empty cells stay in column alignment. */
.lore-listing__rent-roll-cell:has(.lore-listing__rent-roll-value:empty) {
  display: none;
}

/* Masked placeholder: identical blur treatment to §7. The plugin
   v2.7.0 REST filter is the real security boundary; the blur is
   decoration. */
.lore-listing__rent-roll-value--masked {
  display: inline-block;
  filter: blur(6px);
  -webkit-user-select: none;
  user-select: none;
  pointer-events: none;
  color: var(--color-text-muted);
}

/* Dot indicator (mobile only) — one button per row, rendered after
   the table. JS in rent-roll.js v1.0.0 keeps the active state in
   sync with scroll position and wires dot-tap → scrollTo. Pattern
   mirrors .lore-listing__gallery-dot verbatim (convention #22):
   `outline: none` on base prevents the UA-default tap outline (the
   "black box" on iOS Safari/Chrome); :focus-visible (keyboard only)
   keeps the cabernet ring with outline-offset: -8px placing it
   INSIDE the 44×44 button against the 6px mark. Hidden on tablet+
   via the media block below. */
.lore-listing__rent-roll-dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-4);
}

.lore-listing__rent-roll-dot {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.lore-listing__rent-roll-dot:hover,
.lore-listing__rent-roll-dot:focus,
.lore-listing__rent-roll-dot:active {
  background: transparent;
}

.lore-listing__rent-roll-dot:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: -8px;
  border-radius: var(--radius-round);
}

.lore-listing__rent-roll-dot-mark {
  display: block;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-round);
  background: var(--color-border);
  transition: background 150ms ease, transform 150ms ease, opacity 150ms ease;
}

/* Hover/active preview: cabernet at 0.5 opacity — same "this is
   what you're about to activate" signal as the gallery dots. The
   --active modifier below overrides via specificity + later rule. */
.lore-listing__rent-roll-dot:hover .lore-listing__rent-roll-dot-mark,
.lore-listing__rent-roll-dot:active .lore-listing__rent-roll-dot-mark {
  background: var(--color-cabernet);
  opacity: 0.5;
}

.lore-listing__rent-roll-dot--active .lore-listing__rent-roll-dot-mark {
  background: var(--color-cabernet);
  opacity: 1;
  transform: scale(1.3);
}

/* ─── Lightbox ─────────────────────────────────────────────────────── */
.lore-lightbox {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: none;
  opacity: 0;
  transition: opacity 200ms ease;
}

.lore-lightbox--open {
  display: block;
  opacity: 1;
}

.lore-lightbox__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.92);
}

.lore-lightbox__stage {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  touch-action: none;
}

/* v1.0.15 — cursor affordance for desktop pinch + drag-pan.
   Classes toggled by gallery.js v1.4.0+ updateStageCursor(). */
.lore-lightbox__stage--zoomed {
  cursor: grab;
}

.lore-lightbox__stage--grabbing {
  cursor: grabbing;
}

.lore-lightbox--no-strip .lore-lightbox__stage {
  bottom: 0;
}

.lore-lightbox__img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
  transition: transform 200ms ease;
  transform-origin: center center;
}

/* v1.0.14 — class-based scale(2) rule removed. Lightbox image
   transform is applied inline by gallery.js v1.3.0+ (translate +
   scale) for both the discrete double-tap toggle and the
   continuous pinch gesture. The base transition above handles
   snap-back animations when JS clears `style.transition`. */

/* Lightbox controls — v1.0.13 adds :active to the hover rule for
   consistent press visual (same fix pattern as the listing-page
   chevrons). */
.lore-lightbox__close,
.lore-lightbox__prev,
.lore-lightbox__next {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-round);
  background: rgba(255, 255, 255, 0.12);
  color: var(--color-white);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 150ms ease;
  outline: none;
}

.lore-lightbox__close:hover,
.lore-lightbox__close:active,
.lore-lightbox__prev:hover,
.lore-lightbox__prev:active,
.lore-lightbox__next:hover,
.lore-lightbox__next:active {
  background: rgba(255, 255, 255, 0.22);
}

.lore-lightbox__close:focus-visible,
.lore-lightbox__prev:focus-visible,
.lore-lightbox__next:focus-visible {
  outline: 2px solid var(--color-white);
  outline-offset: 3px;
}

.lore-lightbox__close {
  top: var(--space-4);
  right: var(--space-4);
}

.lore-lightbox__prev {
  top: calc(50% - 50px);
  left: var(--space-4);
  transform: translateY(-50%);
  display: none;
}

.lore-lightbox__next {
  top: calc(50% - 50px);
  right: var(--space-4);
  transform: translateY(-50%);
  display: none;
}

.lore-lightbox--no-strip .lore-lightbox__prev,
.lore-lightbox--no-strip .lore-lightbox__next {
  top: 50%;
}

.lore-lightbox__counter {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.12);
  color: var(--color-white);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-loose);
}

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

.lore-lightbox__thumbs {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 100px;
  background: rgba(0, 0, 0, 0.6);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
  touch-action: pan-x;
  -webkit-overflow-scrolling: touch;
}

.lore-lightbox__thumbs::-webkit-scrollbar {
  height: 6px;
}

.lore-lightbox__thumbs::-webkit-scrollbar-track {
  background: transparent;
}

.lore-lightbox__thumbs::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.3);
  border-radius: 3px;
}

.lore-lightbox__thumbs-track {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  height: 100%;
}

.lore-lightbox__thumb {
  position: relative;
  flex: 0 0 auto;
  width: 60px;
  height: 60px;
  margin: 0;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: pointer;
  overflow: hidden;
  opacity: 0.6;
  transition: opacity 150ms ease, transform 150ms ease, border-color 150ms ease;
  scroll-snap-align: center;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.lore-lightbox__thumb:hover,
.lore-lightbox__thumb:active {
  opacity: 0.85;
  background: transparent;
}

.lore-lightbox__thumb:focus-visible {
  outline: 2px solid var(--color-white);
  outline-offset: 2px;
}

.lore-lightbox__thumb--active {
  opacity: 1;
  border-color: var(--color-cabernet);
  transform: scale(1.05);
}

.lore-lightbox__thumb-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

body.lore-lightbox-active {
  overflow: hidden;
}

/* ─── §11 Disclaimer (ADR-034) ─────────────────────────────────────── */

/*
 * Marker class: small superscript dagger anchor used inline next to
 * estimate-bearing values. Hit area expanded to >=44x44 per ADR-014
 * via 8px padding on the anchor; the visible glyph stays small via
 * font-size scaling, and the padding is invisible because the host
 * context has no background.
 */
.lore-listing__disclaimer-marker {
  color: var(--color-cabernet);
  font-size: calc(var(--fs-2xl) * 0.6);
  vertical-align: super;
  line-height: 0;
  margin-left: 2px;
  padding: 8px;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.lore-listing__disclaimer-marker:hover {
  text-decoration: underline;
}

.lore-listing__disclaimer-marker:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
  text-decoration: underline;
}

/*
 * Modifier classes `--heading` (§6/§7 <h2> hosts) and `--th` (§8
 * Market Rent <th> host) stay in the HTML and CSS as no-op hooks
 * for any future per-context tuning. As of v1.0.39 the base font-
 * size rule above (calc(var(--fs-2xl) * 0.6)) renders an identical
 * marker across all five clickable surfaces, so neither modifier
 * carries an active declaration.
 */

/* §11 section frame. White background matches the surrounding template
 * sections (§5–§8 also white). The hairline lives on the inner
 * container, not the outer section, so the linen arrival highlight
 * (below) can be applied cleanly to the inner without affecting the
 * page-level surface. */
.lore-listing__disclaimer {
  background: var(--color-white);
  padding: var(--space-8) var(--space-4) var(--space-6);
}

.lore-listing__disclaimer-inner {
  max-width: 1400px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-4) var(--space-4);
  border-top: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
}

/*
 * :target arrival highlight. When the URL hash matches the disclaimer
 * section's id (i.e. the user just clicked a marker), the inner
 * container flashes --color-linen and fades back to transparent over
 * 1.6s. Scoped to the inner element (which has no static background)
 * so the outer section's white surface stays uninterrupted. Fires
 * once on initial :target match; subsequent same-session clicks of
 * the same marker smooth-scroll but won't re-flash (CSS animation
 * limitation when :target was already set — acceptable trade-off,
 * the arrival cue matters most for the first navigation).
 */
.lore-listing__disclaimer:target .lore-listing__disclaimer-inner {
  animation: lore-disclaimer-arrival 1.6s ease-out;
}

@keyframes lore-disclaimer-arrival {
  0%   { background-color: var(--color-linen); }
  100% { background-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .lore-listing__disclaimer:target .lore-listing__disclaimer-inner {
    animation: none;
  }
}

/*
 * "Disclaimer" eyebrow heading. Same typographic register as the §4
 * Key Information tile meta-labels: uppercase, --fs-xs, --ls-loose,
 * muted. Deliberately quieter than the Larken display headings of
 * §5–§8 so the section reads as supporting fine-print rather than
 * another sibling content section to engage with.
 */
.lore-listing__disclaimer-heading {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-loose);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-3) 0;
}

/*
 * Body paragraph. Roman not italic — italic at small sizes hurts
 * legibility, and the muted color already signals fine-print. 65ch
 * cap keeps line-length comfortable on wide viewports.
 */
.lore-listing__disclaimer-body {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-regular);
  line-height: 1.6;
  color: var(--color-text-muted);
  margin: 0;
}

/*
 * Leading dagger span. aria-hidden in HTML so screen readers go
 * straight to the disclaimer language. Small right-margin attaches
 * it visually to the first word of the body.
 */
.lore-listing__disclaimer-mark {
  margin-right: 3px;
}

/* ─── Tablet (>=768px) ─────────────────────────────────────────────── */
@media (min-width: 768px) {
  .lore-listing__hero {
    padding: var(--space-8) var(--space-6) var(--space-10);
  }

  .lore-listing__hero-inner {
    gap: var(--space-4);
  }

  /* keep the address↔neighborhood pair tight where the base gap is --space-4 */
  .lore-listing__address + .lore-listing__neighborhood {
    margin-top: calc(var(--space-1) - var(--space-4));
  }

  .lore-listing__price-asking {
    font-size: var(--fs-3xl);
  }

  .lore-listing__price-original {
    font-size: var(--fs-xl);
  }

  .lore-listing__section-heading {
    margin-bottom: var(--space-4);
    font-size: var(--fs-2xl);
  }

  .lore-listing__gallery {
    padding: var(--space-8) var(--space-6);
  }

  .lore-listing__gallery--carousel .lore-listing__gallery-chevron {
    display: inline-flex;
  }

  /* Toggle flips from mobile (static, below photo, centered) to
     desktop (absolute, top-right inside photo frame). The chevron
     breakpoint and the toggle breakpoint are intentionally identical
     — at >=768px the inside-frame control surface (chevrons + toggle)
     comes online; at <768px both live outside the frame.
     Backdrop also flips: mobile's quiet rgba(0,0,0,0.04) + border
     becomes a frosted-glass rgba(255,255,255,0.85) + backdrop-filter
     blur. The border is dropped because the frosted backdrop already
     provides edge definition against the photo. */
  .lore-listing__gallery-toggle {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    width: auto;
    margin: 0;
    border: 0;
    background: rgba(255, 255, 255, 0.85);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
  }

  .lore-listing__pricing-metrics {
    padding: var(--space-8) var(--space-6);
  }

  .lore-listing__pricing-metrics-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-4);
  }

  .lore-listing__pricing-metric-value {
    font-size: var(--fs-3xl);
  }

  .lore-listing__property-facts {
    padding: var(--space-8) var(--space-6);
  }

  .lore-listing__property-facts-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-6);
  }

  .lore-listing__property-fact-value {
    font-size: var(--fs-2xl);
  }

  .lore-listing__description {
    padding: var(--space-8) var(--space-6);
  }

  .lore-listing__financial-summary {
    padding: var(--space-8) var(--space-6);
  }

  /* §8 Rent Roll — flip from stacked cards to horizontal table.
     <thead> comes out of clipped-hide and shows column headers;
     <tr> reverts to table-row layout; <td>::before is suppressed
     (the header row carries the labels now); cells get vertical
     borders + alignment for the table presentation. */
  .lore-listing__rent-roll {
    padding: var(--space-8) var(--space-6);
  }

  .lore-lightbox__stage {
    bottom: 120px;
  }

  .lore-lightbox__prev {
    top: calc(50% - 60px);
    display: inline-flex;
  }

  .lore-lightbox__next {
    top: calc(50% - 60px);
    display: inline-flex;
  }

  .lore-lightbox__thumbs {
    height: 120px;
  }

  .lore-lightbox__thumb {
    width: 80px;
    height: 80px;
  }
}

/* §8 Rent Roll table form — CONTAINER query (v1.0.48). Flips the stacked
   mobile cards to the horizontal table when the Rent Roll's own container
   (.lore-listing__rent-roll-inner) is at least 720px wide — independent of
   the viewport, so it switches correctly inside the narrow desktop map-
   overlay panel (wide viewport, narrow panel) as well as on the full-width
   single-listing page. 720px ~matches the prior viewport breakpoint at the
   page content width; tune if needed. Placed after the base card rules so
   it wins on the cascade when the query matches. */
@container lore-rentroll (min-width: 720px) {
  .lore-listing__rent-roll-table {
    display: table;
    border-collapse: separate;
    border-spacing: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    overflow: hidden;
  }

  .lore-listing__rent-roll-thead {
    position: static;
    width: auto;
    height: auto;
    padding: 0;
    margin: 0;
    overflow: visible;
    clip: auto;
    clip-path: none;
    white-space: normal;
    border: 0;
    display: table-header-group;
  }

  .lore-listing__rent-roll-thead tr {
    background: var(--color-surface-muted);
  }

  /* Reset the mobile scroll-snap carousel — tbody back to its native
     table-row-group, no flex, no overflow, no snap. */
  .lore-listing__rent-roll-tbody {
    display: table-row-group;
    flex-direction: initial;
    width: auto;
    gap: 0;
    overflow: visible;
    scroll-snap-type: none;
    overscroll-behavior-x: auto;
    scroll-behavior: auto;
  }

  /* Dot indicator hidden — the table form is back, dots would be
     redundant + visually noisy. */
  .lore-listing__rent-roll-dots {
    display: none;
  }

  .lore-listing__rent-roll-th {
    padding: var(--space-3) var(--space-4);
    font-family: var(--font-body);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-loose);
    text-transform: uppercase;
    color: var(--color-text-muted);
    text-align: left;
    border-bottom: 1px solid var(--color-border);
  }

  .lore-listing__rent-roll-th--num {
    text-align: right;
  }

  .lore-listing__rent-roll-row {
    flex: initial;
    scroll-snap-align: none;
    scroll-snap-stop: normal;
    display: table-row;
    padding: 0;
    margin: 0;
    background: var(--color-white);
    border: 0;
    border-radius: 0;
  }

  .lore-listing__rent-roll-row:last-child .lore-listing__rent-roll-cell {
    border-bottom: 0;
  }

  .lore-listing__rent-roll-cell {
    display: table-cell;
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--color-border-soft);
    text-align: left;
    vertical-align: middle;
  }

  .lore-listing__rent-roll-cell::before {
    content: none;
  }

  .lore-listing__rent-roll-cell--num {
    text-align: right;
  }

  .lore-listing__rent-roll-value {
    text-align: inherit;
  }

  /* Override the mobile :has(:empty) collapse so empty cells stay
     in column alignment on desktop. */
  .lore-listing__rent-roll-cell:has(.lore-listing__rent-roll-value:empty) {
    display: table-cell;
  }
}

/* ─── Desktop (>=1200px) ───────────────────────────────────────────── */
@media (min-width: 1200px) {
  .lore-listing__hero {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }

  .lore-listing__gallery {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }

  .lore-listing__gallery-hero {
    aspect-ratio: 16 / 9;
  }

  .lore-listing__gallery-map-pane {
    aspect-ratio: 16 / 9;
  }

  .lore-listing__pricing-metrics {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }

  .lore-listing__property-facts {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }

  .lore-listing__description {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }

  .lore-listing__financial-summary {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }

  .lore-listing__rent-roll {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }

  .lore-listing__disclaimer {
    padding-left: var(--space-8);
    padding-right: var(--space-8);
  }
}

/* ============================================================================
   §10 — Inquiry CTA + Modal (ADR-031, supersedes original §10 Agent Card)
   ============================================================================
   v1.0.23 — Added 2026-05-18 as ADR-031 sub-step 4. Pairs with:
     - template-parts/inquiry-cta.php v1.0.0
     - assets/js/inquiry.js v1.0.0
     - functions.php v2.0.17 (lore-inquiry enqueue + cache-bust)

   Modal auto-opens on every listing page load. User dismisses with
   X or Escape; the scarlet chat-bubble CTA stays visible at bottom-
   right as a re-entry point. Mobile-first per ADR-014. Apple-clean
   per ADR-015. Modal dismissal: explicit controls only per ADR-029.
   ============================================================================ */

.lore-inquiry-cta {
  position: fixed;
  bottom: var(--space-6);
  right: var(--space-6);
  width: 56px;
  height: 56px;
  border-radius: var(--radius-round);
  background: var(--color-scarlet);
  color: var(--color-white);
  border: none;
  cursor: pointer;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
  -webkit-tap-highlight-color: transparent;
}

.lore-inquiry-cta:hover {
  transform: scale(1.05);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.22);
  background: var(--color-scarlet);
  color: var(--color-white);
}

.lore-inquiry-cta:active {
  transform: scale(0.98);
  background: var(--color-scarlet);
  color: var(--color-white);
}

.lore-inquiry-cta:focus {
  background: var(--color-scarlet);
  color: var(--color-white);
}

.lore-inquiry-cta:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: 4px;
}

.lore-inquiry-cta--hidden {
  opacity: 0;
  pointer-events: none;
  transform: scale(0.8);
}

.lore-inquiry-cta__icon {
  pointer-events: none;
}

@media (min-width: 768px) {
  .lore-inquiry-cta {
    width: 64px;
    height: 64px;
  }
  .lore-inquiry-cta__icon {
    width: 32px;
    height: 32px;
  }
}

/* Modal container */

.lore-inquiry-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  opacity: 0;
  transition: opacity 0.2s ease-out;
}

.lore-inquiry-modal[hidden] {
  display: none;
}

.lore-inquiry-modal--open {
  opacity: 1;
}

.lore-inquiry-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1;
  cursor: default;
}

.lore-inquiry-modal__panel {
  position: relative;
  z-index: 2;
  background: var(--color-white);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-popup);
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
  cursor: default;
}

.lore-inquiry-modal__close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  width: 44px;
  height: 44px;
  border: none;
  background: var(--color-border-soft);
  cursor: pointer;
  color: var(--color-text);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-round);
  -webkit-tap-highlight-color: transparent;
  z-index: 3;
  transition: color 0.15s ease, background 0.15s ease;
}

.lore-inquiry-modal__close:hover {
  background: var(--color-border);
}

.lore-inquiry-modal__close:active {
  background: var(--color-border);
}

/* ADR-079 / convention #90: GeneratePress ships button:focus { background:#3f4047 }
   at (0,1,1). inquiry.js v1.0.5 focuses this close control on open, and a
   programmatic .focus() fires :focus (NOT :focus-visible) — so restate the
   resting/hover background at class specificity (0,2,0) to keep the X from
   flashing charcoal on open (and on keyboard focus). */
.lore-inquiry-modal__close:focus {
  background: var(--color-border-soft);
}

.lore-inquiry-modal__close:focus:hover {
  background: var(--color-border);
}

.lore-inquiry-modal__close:focus-visible {
  outline: 2px solid var(--color-cabernet);
  outline-offset: 2px;
}

.lore-inquiry-modal__close svg {
  display: none !important;
}

.lore-inquiry-modal__close::before,
.lore-inquiry-modal__close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 22px;
  height: 2px;
  background: var(--color-text);
  border-radius: 1px;
}

.lore-inquiry-modal__close::before {
  transform: translate(-50%, -50%) rotate(45deg);
}

.lore-inquiry-modal__close::after {
  transform: translate(-50%, -50%) rotate(-45deg);
}

.lore-inquiry-modal__body {
  padding: var(--space-4) var(--space-5) var(--space-4);
}

@media (min-width: 768px) {
  .lore-inquiry-modal__body {
    padding: var(--space-8) var(--space-6) var(--space-6);
  }
}

/* Header: agent identity */

.lore-inquiry-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-2);
}

.lore-inquiry-header__photo {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--color-border-soft);
  display: block;
}

@media (min-width: 768px) {
  .lore-inquiry-header__photo {
    width: 112px;
    height: 112px;
  }
}

.lore-inquiry-header__photo--initial {
  background: var(--color-cabernet);
  color: var(--color-white);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  line-height: 1;
}

.lore-inquiry-header__identity {
  flex: 1;
  min-width: 0;
}

.lore-inquiry-header__name {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--color-text);
  line-height: var(--lh-tight);
}

.lore-inquiry-header__role {
  font-size: var(--fs-base);
  color: var(--color-text-muted);
  margin-top: var(--space-1);
}

.lore-inquiry-header__license {
  font-size: var(--fs-base);
  color: var(--color-text-muted);
}

/* Sub-header */

.lore-inquiry-subheader {
  font-size: var(--fs-base);
  color: var(--color-text);
  line-height: var(--lh-normal);
  margin: 0 0 var(--space-2);
}

/* Logged-out sign-in nudge: shares the subheader element but reads as a
   quieter helper — a notch smaller, muted, sitting just under the header. */
.lore-inquiry-loginhint {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-4);
}

.lore-inquiry-loginhint__link {
  color: var(--color-cabernet);
  font-weight: var(--fw-medium);
}

/* Form */

.lore-inquiry-form__honeypot {
  position: absolute !important;
  left: -9999px !important;
  width: 1px !important;
  height: 1px !important;
  opacity: 0 !important;
  pointer-events: none !important;
}

.lore-inquiry-form__field {
  position: relative;
  padding-top: 20px;              /* reserved row for the label ABOVE the field */
  margin-bottom: var(--space-1);
}

.lore-inquiry-form__field input,
.lore-inquiry-form__field select {
  width: 100%;
  height: 48px;                   /* normal field height (label is now outside) */
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md) !important;
  font-size: var(--fs-lg);
  font-family: var(--font-body);
  background: var(--color-white);
  color: var(--color-text);
  -webkit-appearance: none;
  appearance: none;
  box-sizing: border-box;
}

/* Label-above pattern (v1.1.5). At rest the label sits centered inside the
   field like a placeholder; once the field has a value (or is focused) it
   lifts into the reserved row ABOVE the field, smaller. Because the field
   reserves that row with padding-top, the lift never shifts layout — the row
   is always present whether or not the name is showing. Markup is input-first
   so the label is the input's next sibling; placeholder=" " lets
   :placeholder-shown track emptiness. The --select label stays lifted since a
   <select> always carries a value. */
.lore-inquiry-form__field label {
  position: absolute;
  left: calc(var(--space-3) + 1px);
  top: 44px;                      /* vertical center of the 48px input (20 + 24) */
  transform: translateY(-50%);
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
  pointer-events: none;
  transition: top 0.14s ease, transform 0.14s ease, font-size 0.14s ease;
}

.lore-inquiry-form__field input:focus + label,
.lore-inquiry-form__field input:not(:placeholder-shown) + label,
.lore-inquiry-form__field--select label {
  top: 2px;                       /* into the reserved row above the field */
  transform: translateY(0);
  font-size: var(--fs-xs);
}

.lore-inquiry-form__req {
  color: var(--color-scarlet);
  margin-left: 2px;
}

.lore-inquiry-form__field select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%23696969' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  padding-right: var(--space-7);
}

.lore-inquiry-form__field input:focus,
.lore-inquiry-form__field select:focus {
  outline: none;
  border-color: var(--color-cabernet);
  box-shadow: 0 0 0 2px rgba(80, 0, 0, 0.12);
}

.lore-inquiry-form__optin {
  margin: var(--space-2) 0;
}

.lore-inquiry-form__optin label {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  line-height: var(--lh-normal);
  cursor: pointer;
}

.lore-inquiry-form__optin input[type="checkbox"] {
  margin-top: 3px;
  flex-shrink: 0;
  cursor: pointer;
}

.lore-inquiry-form__error {
  color: var(--color-scarlet);
  font-size: var(--fs-sm);
  padding: var(--space-2) var(--space-3);
  background: rgba(231, 57, 13, 0.08);
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-3);
  line-height: var(--lh-normal);
}

.lore-inquiry-form__submit {
  width: 100%;
  min-height: 48px;
  background: var(--color-scarlet);
  color: var(--color-white);
  border: none;
  border-radius: var(--radius-pill);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  font-family: var(--font-body);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 0.15s ease;
}

.lore-inquiry-form__submit:hover,
.lore-inquiry-form__submit:focus,
.lore-inquiry-form__submit:active {
  background: var(--color-scarlet);
  color: var(--color-white);
}

.lore-inquiry-form__submit:hover {
  opacity: 0.92;
}

.lore-inquiry-form__submit:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Success state */

.lore-inquiry-success p {
  padding: var(--space-5);
  background: var(--color-bay-mist, #c1ece3);
  color: var(--color-harbor-blue, #1a5b60);
  border-radius: var(--radius-md);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  margin: 0;
  text-align: left;
}

/* Body scroll lock when modal is open */

body.lore-inquiry-modal-open {
  overflow: hidden;
}
