/**
 * property.css — /p/:id v2, screen-specs.md §10, visual-system.md §6/§9.7.
 * This is the shared baseline AND Ledger's own template (template-specs.md
 * §D.2's CSS architecture decision) — every other template links this file
 * PLUS one small delta file (public/templates/<template>.css) that overrides
 * only what differs. As of the 2026-07-13 visual-elevation amendment (§2.0),
 * Ledger gets its own (small) delta file too — see templates/ledger.css.
 *
 * INVARIANT (2026-07-13 visual-elevation §0.1, restated here since this is
 * where every template's mood token contract lives): template deltas — this
 * file's own --tpl-* block below AND every public/templates/<name>.css file —
 * must NEVER set --color-accent, --color-accent-hover, --color-accent-active,
 * --color-focus-ring, --color-text-on-accent, or --color-accent-button-fill.
 * orgAccentStyle()/accentOverrideStyle() (lib/render-public.mjs) inject an
 * org's white-label accent as a <style> block scoped to `.property-page` at
 * specificity (0,1,0); a template delta selecting at
 * `.property-page[data-template="x"] .foo` is (0,2,0)+ and would silently win
 * the cascade over the org's color, breaking white-labeling for every org on
 * that template. Template mood colors live only in --tpl-* tokens.
 */

.property-page { padding-bottom: var(--space-12); }

/* --- Template mood token contract (2026-07-13 visual-elevation §2.0) --------
   Every token defaults to the current shipped look (zero visual change until
   a template delta overrides it). --tpl-facts-bg being non-transparent also
   flips the facts-card text via a local semantic-token override
   (--color-text-primary/-secondary/-tertiary) — the same .mkt-band--ink
   technique marketing.css uses, intentional reuse, not two different systems. */
.property-page {
  --tpl-band-bg: transparent;                    /* .property-hero-media wrapper + .reel-stack fallback */
  --tpl-facts-bg: transparent;                    /* .property-facts (dl) card background */
  --tpl-kicker-color: var(--color-text-primary);  /* section h2s: "Property facts", "Photos", "About this home", "Virtual Host", "Schedule a showing" */
  --tpl-caption-color: var(--color-text-secondary); /* OFF-photo static pull-quote captions ONLY (Gallery/Atelier photo-essay/asymmetric full-width figcaption) — NEVER the on-photo "Virtually staged" overlay badge, which stays hardcoded white on the shared dark scrim in every template (§0.3) */
  --tpl-frame: var(--color-border-hairline);      /* hairline weight around facts-card / gallery-grid container edges — NEVER the photo <img> itself (§0.2) */
}

/* --- Owner-only preview banner (T10 + FIX-USER-5) --------------------------
   Shown only when the requester is verified as the listing's owner (either
   viewing an unpublished listing, or using the ?template= preview override) —
   never rendered for a real buyer. Warning-tint, not error/accent, since it's
   informational, not a failure state. */
.property-preview-banner {
  display: inline-block; background: var(--color-warning-tint); color: var(--color-warning-text);
  font: var(--fw-semibold) var(--fs-body-sm) / 1.4 var(--font-sans);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  margin: 0 0 var(--space-4);
}

/* --- 3D walkthrough (FIX-USER-9) -------------------------------------------
   Shared across every template — click-to-load only, never auto-embedded.
   Placement (near the reel/hero) is a per-composer DOM-order decision, not a
   CSS one; this block's own look is identical everywhere it appears. */
.property-tour { margin-bottom: var(--space-9); }
.property-tour h2 { font: var(--fw-semibold) var(--fs-heading-lg) / 1.3 var(--font-sans); margin: 0 0 var(--space-4); }
.tour-stack {
  position: relative; aspect-ratio: 16 / 9; background: var(--color-bg-surface-2);
  border-radius: var(--radius-lg); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.tour-load-btn {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-width: 44px; min-height: 44px; padding: var(--space-3) var(--space-6);
  border-radius: var(--radius-full); border: none;
  background: var(--color-accent-button-fill, var(--color-accent)); color: var(--color-text-on-accent);
  font: var(--fw-semibold) var(--fs-body-md) / 1 var(--font-sans);
  cursor: pointer; transition: background-color var(--duration-fast) var(--ease-standard);
}
.tour-load-btn:hover { background: var(--color-accent-hover); }
.tour-load-btn:active { background: var(--color-accent-active); }
.tour-iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.tour-fallback-note { font-size: var(--fs-body-sm); color: var(--color-text-secondary); margin: var(--space-3) 0 0; }
.tour-linkout-note { font-size: var(--fs-body-sm); color: var(--color-text-secondary); margin: 0 0 var(--space-3); }
.tour-linkout-btn { width: auto; }

/* FIX-6 (a11y review 2026-07-13): the mitigation composeMediaFirst's DOM
   deviation needed (see that function's comment in lib/render-public.mjs) —
   a skip link straight to the detailed facts <dl> (#property-facts) so a
   keyboard/screen-reader user isn't forced through the hero/tour/gallery
   block first. Same visually-hidden-until-focus shape as public.css's own
   .skip-link (not reused directly: that class is the page's "skip to main
   content" link and always renders first in the DOM; this one is scoped to
   .property-page and DOM-adjacent to the intro it follows). */
.skip-to-facts {
  position: fixed; top: -48px; left: var(--space-4); z-index: 100;
  background: var(--color-accent-button-fill, var(--color-accent)); color: var(--color-text-on-accent);
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-md);
  font: var(--fw-semibold) var(--fs-body-sm) / 1 var(--font-sans);
  transition: top var(--duration-base) var(--ease-standard);
}
.skip-to-facts:focus-visible { top: var(--space-4); }

.property-intro { padding-top: var(--space-8); }
.property-intro .hero-headline {
  font: var(--fw-medium) var(--fs-display-lg) / var(--lh-display-lg) var(--font-display);
  color: var(--color-text-primary); margin: 0 0 var(--space-3); max-width: 22ch;
}
@media (min-width: 1024px) { .property-intro .hero-headline { font-size: var(--fs-display-xl); } }
.property-facts-line { font: var(--fw-semibold) var(--fs-data-lg) / 1.3 var(--font-sans); font-feature-settings: "tnum" 1; color: var(--color-text-primary); margin: 0 0 var(--space-2); }
.property-presented-by { display: flex; align-items: center; gap: var(--space-2); color: var(--color-text-secondary); font-size: var(--fs-body-sm); margin: 0 0 var(--space-5); }
.property-presented-by .org-logo { height: 24px; width: auto; border-radius: var(--radius-sm); }
.property-ctas { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-8); }
.property-ctas .btn { width: auto; }

/* FIX-P1-8: was a bordered 4-card grid — the hero photo had near-zero
   presence in the first viewport (measured 0% visible at 375×844) partly
   because this block ate so much vertical space restating facts already on
   the inline summary line above. DOM order is untouched (do-not-touch #1) —
   this is a restyle only, from boxed cards to a slim, unboxed row, so the
   hero photo further down the DOM can rise into view sooner without moving
   a single node. Dropping the summary line's aria-label override (DD-10) is
   safe now that this is one plain-text node away and reads the full words. */
.property-facts { margin-bottom: var(--space-6); }
.property-facts h2 { font: var(--fw-semibold) var(--fs-heading-md) / 1.3 var(--font-sans); margin: 0 0 var(--space-3); }
.property-facts-list {
  display: flex; flex-wrap: wrap; column-gap: var(--space-6); row-gap: var(--space-2);
  margin: 0; padding: var(--space-3) 0; border-block: 1px solid var(--color-border-hairline);
}
.property-facts-list > div { display: flex; align-items: baseline; gap: var(--space-2); }
.property-facts-list dt { font: var(--fw-semibold) var(--fs-label) / 1 var(--font-sans); color: var(--color-text-tertiary); text-transform: uppercase; letter-spacing: var(--tracking-wide); margin: 0; }
.property-facts-list dd { font: var(--fw-semibold) var(--fs-body-lg) / 1.3 var(--font-sans); font-feature-settings: "tnum" 1; color: var(--color-text-primary); margin: 0; }

/* --- Hero reel/photo ---------------------------------------------------------- */
.property-hero-media { margin-bottom: var(--space-9); }
.reel-stack { position: relative; aspect-ratio: 3 / 2; background: var(--color-bg-surface-2); overflow: hidden; }
.reel-poster, .reel-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.reel-video { opacity: 0; pointer-events: none; }
.reel-stack.is-playing .reel-video { opacity: 1; pointer-events: auto; }
.reel-stack.is-playing .reel-poster { opacity: 0; }
.reel-poster, .reel-video { transition: opacity 200ms var(--ease-standard); }
@media (prefers-reduced-motion: reduce) { .reel-poster, .reel-video { transition: none; } }
.staged-badge {
  position: absolute; bottom: var(--space-3); left: var(--space-3);
  background: rgba(10,10,12,0.65); color: #fff; font-size: var(--fs-caption);
  padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm);
}
.reel-play-btn {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  min-width: 44px; min-height: 44px; padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-full); border: none; background: rgba(255,255,255,0.92);
  /* FIX-T6 item 3 (template-specs.md §A.5): the frosted-white pill sits on a
     photo, not the canvas, so it should never itself go dark theme — but its
     label was wired to --color-text-primary, which becomes ink-100
     (near-white) in dark theme, i.e. near-white text on a near-white pill.
     Hardcoded to --gray-900 (a theme-independent primitive, #17181C) so the
     label stays legible in any theme, matching pickAccentTextColor()'s own
     "dark text" branch in lib/render-public.mjs. */
  color: var(--gray-900); font: var(--fw-semibold) var(--fs-body-md) / 1 var(--font-sans);
  cursor: pointer; transition: transform 90ms var(--ease-in);
}
.reel-play-btn:active { transform: translate(-50%, -50%) scale(0.96); }
.reel-stack.is-playing .reel-play-btn { display: none; }

/* --- Two-column: description | Virtual Host ----------------------------------- */
.property-body { display: grid; gap: var(--space-9); margin-bottom: var(--space-10); grid-template-columns: 1fr; }
@media (min-width: 900px) { .property-body { grid-template-columns: 3fr 2fr; align-items: start; } }
.property-main h2 { font: var(--fw-semibold) var(--fs-heading-lg) / 1.3 var(--font-sans); margin: 0 0 var(--space-4); }
.property-description { font-size: var(--fs-body-lg); line-height: 1.6; color: var(--color-text-primary); max-width: var(--container-reading); margin: 0; }

.property-chat-col { position: relative; }
@media (min-width: 900px) { .property-chat-col { position: sticky; top: var(--space-6); } }
.property-chat-col h2 { font: var(--fw-semibold) var(--fs-heading-lg) / 1.3 var(--font-sans); margin: 0 0 var(--space-4); }
.chat-widget { border: 1px solid var(--color-border-hairline); border-radius: var(--radius-lg); background: var(--color-bg-surface-1); display: flex; flex-direction: column; max-height: 520px; }
.chat-log { flex: 1; overflow-y: auto; padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); min-height: 160px; }
.msg { max-width: 88%; padding: var(--space-3) var(--space-4); border-radius: var(--radius-lg); font-size: var(--fs-body-md); line-height: 1.5; }
.msg.host { align-self: flex-start; background: var(--color-bg-surface-2); border-bottom-left-radius: var(--radius-sm); color: var(--color-text-primary); }
.msg.buyer { align-self: flex-end; background: var(--color-accent-tint-bg); border-bottom-right-radius: var(--radius-sm); color: var(--color-text-primary); }
.msg.err { align-self: stretch; color: var(--color-error-text); background: var(--color-error-tint); }
.msg.gate-form { align-self: stretch; background: var(--color-bg-surface-2); }
.msg.gate-form form { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
/* FIX-P2-5 / DD-15: was 40px — inconsistent with .chat-form input's own 44px
   two rules below, on the very same widget. */
.msg.gate-form input { height: 44px; padding: 0 var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--color-border-interactive); background: var(--color-bg-canvas); color: var(--color-text-primary); }
.msg.gate-form .gate-dismiss { align-self: flex-start; margin-top: var(--space-1); }
.msg.gate-form .gate-disclosure { font-size: var(--fs-caption); color: var(--color-text-tertiary); }
.chat-status { padding: 0 var(--space-4); min-height: 1.2em; font-size: var(--fs-body-sm); color: var(--color-text-secondary); margin: 0; }
.chat-form { display: flex; gap: var(--space-2); padding: var(--space-3); border-top: 1px solid var(--color-border-hairline); }
.chat-form input { flex: 1; height: 44px; padding: 0 var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--color-border-interactive); background: var(--color-bg-canvas); color: var(--color-text-primary); }
.chat-form button { min-width: 44px; min-height: 44px; }

/* --- Gallery ------------------------------------------------------------------ */
.property-gallery { margin-bottom: var(--space-10); }
.property-gallery h2 { font: var(--fw-semibold) var(--fs-heading-lg) / 1.3 var(--font-sans); margin: 0 0 var(--space-5); }
.gallery-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }
@media (min-width: 768px) { .gallery-grid { grid-template-columns: repeat(4, 1fr); } }
.gallery-item { position: relative; margin: 0; aspect-ratio: 4 / 3; border-radius: var(--radius-md); overflow: hidden; background: var(--color-bg-surface-2); }
.gallery-item img { width: 100%; height: 100%; object-fit: cover; }
.gallery-item figcaption { position: absolute; bottom: 0; left: 0; right: 0; background: rgba(10,10,12,0.6); color: #fff; font-size: var(--fs-caption); padding: var(--space-1) var(--space-2); }

/* --- Schedule a showing -------------------------------------------------------- */
.property-schedule { max-width: 560px; }
.property-schedule h2 { font: var(--fw-semibold) var(--fs-heading-lg) / 1.3 var(--font-sans); margin: 0 0 var(--space-5); }
.schedule-form { display: grid; gap: var(--space-4); grid-template-columns: 1fr 1fr; }
.schedule-form .field:nth-child(3), .schedule-form .field:nth-child(4), .schedule-form .field:nth-child(5), .schedule-form .field:nth-child(6) { grid-column: 1 / -1; }
@media (max-width: 480px) { .schedule-form { grid-template-columns: 1fr; } .schedule-form .field { grid-column: 1 / -1; } }
.schedule-form button { grid-column: 1 / -1; }
.schedule-status { min-height: 1.4em; color: var(--color-text-secondary); font-size: var(--fs-body-sm); }

/* --- Mobile Virtual Host launcher: fixed pill + bottom sheet ------------------
   FIX-P1-2 / critic M5 / DD-1, screen-specs §10.2, MOTION-MODAL-1's sheet
   variant. Mobile-only — desktop keeps the sidebar Virtual Host sticky
   (above), which never needs a floating relaunch affordance. Motion is
   CSS-only (transform/opacity transitions); property.js only toggles
   attributes/classes and forces one reflow to make the transition play — no
   rAF, no element.animate(). The universal reduced-motion kill-switch in
   tokens.css already collapses these to instant-correct; the explicit rules
   below match the "belt and suspenders" style already used elsewhere in this
   file (see .reel-poster/.reel-video above). */
@media (max-width: 899px) {
  .property-chat-col { margin-top: var(--space-8); }
}

.vh-launcher-pill { display: none; }
@media (max-width: 899px) {
  .vh-launcher-pill {
    display: inline-flex; align-items: center; gap: var(--space-2);
    position: fixed; z-index: 40;
    right: max(var(--space-5), env(safe-area-inset-right));
    bottom: max(var(--space-5), env(safe-area-inset-bottom));
    height: 56px; padding: 0 var(--space-6);
    border: none; border-radius: var(--radius-full);
    /* FIX-T6 items 1+2 (template-specs.md §A.5): button-fill fallback (same
       reasoning as public.css's .btn-primary/.pub-nav-cta/.skip-link) and a
       semantic shadow token instead of the primitive --shadow-elevation-3 —
       the semantic --shadow-popover redefines itself per theme (dark theme's
       version is a harder black shadow tuned for ink-900, not light theme's
       tight+ambient pair, which would render "muddy" against a dark canvas). */
    background: var(--color-accent-button-fill, var(--color-accent)); color: var(--color-text-on-accent);
    font: var(--fw-semibold) var(--fs-body-md) / 1 var(--font-sans);
    box-shadow: var(--shadow-popover);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-standard);
  }
  .vh-launcher-pill:hover { background: var(--color-accent-hover); }
  .vh-launcher-pill:active { background: var(--color-accent-active); }
}

.vh-sheet-backdrop {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(10, 10, 12, 0.55);
  opacity: 0; transition: opacity var(--duration-slow) var(--ease-standard);
}
.vh-sheet-backdrop[hidden] { display: none; }
.vh-sheet-backdrop.is-open { opacity: 1; }

.vh-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 51;
  max-height: min(80vh, 640px);
  display: flex; flex-direction: column;
  background: var(--color-bg-canvas);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  /* FIX-T6 item 2 — semantic --shadow-modal, not the primitive
     --shadow-elevation-4 (see .vh-launcher-pill comment above). */
  box-shadow: var(--shadow-modal);
  padding: var(--space-3) var(--space-4) max(var(--space-4), env(safe-area-inset-bottom));
  transform: translateY(100%);
  transition: transform 260ms var(--ease-standard);
}
.vh-sheet[hidden] { display: none; }
.vh-sheet.is-open { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .vh-sheet, .vh-sheet-backdrop { transition: none; }
}
.vh-sheet-handle {
  width: 36px; height: 4px; border-radius: var(--radius-full);
  background: var(--color-border-hairline); margin: 0 auto var(--space-2); flex-shrink: 0;
}
.vh-sheet-close {
  align-self: flex-end; flex-shrink: 0;
  min-height: 44px; padding: 0 var(--space-3);
  border: none; background: transparent; color: var(--color-text-secondary);
  font: var(--fw-medium) var(--fs-body-md) / 1 var(--font-sans); cursor: pointer;
}
.vh-sheet-close:hover { color: var(--color-text-primary); }
.vh-sheet-body { overflow-y: auto; min-height: 0; }
.vh-sheet-body .property-chat-col { margin-top: 0; position: static; }
.vh-sheet-body .chat-widget { max-height: none; }

@media (min-width: 900px) {
  /* Desktop never opens the sheet (the pill that triggers it is hidden), but
     keep it visually inert too — defense in depth if JS somehow ran anyway. */
  .vh-sheet, .vh-sheet-backdrop { display: none; }
}

/* =============================================================================
 * New shared blocks (2026-07-14-reference-templates.md §2) — the baseline
 * look for the seven new lib/render-public.mjs block renderers + the three
 * new gallery patterns, used by all four new templates (Summit/Estate/Folio/
 * Horizon). Template-specific structural scaffolding (each template's own
 * hero-band/about/offset-photo treatment) lives in that template's own
 * public/templates/<name>.css delta file, not here — this section is only
 * what needs to render sanely regardless of which of the four is active.
 * ========================================================================== */

/* --- Sticky anchor nav ------------------------------------------------------- */
.property-anchor-nav {
  position: sticky; top: 0; z-index: 20;
  display: flex; gap: var(--space-5); align-items: center;
  padding-inline: var(--space-7);
  background: var(--color-bg-canvas);
  border-bottom: 1px solid var(--color-border-hairline);
  overflow-x: auto;
}
@media (max-width: 599px) { .property-anchor-nav { padding-inline: var(--space-5); } }
.property-anchor-nav a {
  display: inline-flex; align-items: center; min-height: 44px; padding-inline: var(--space-2);
  font: var(--fw-medium) var(--fs-body-sm) / 1 var(--font-sans);
  color: var(--color-text-secondary); text-decoration: none; white-space: nowrap;
}
.property-anchor-nav a:hover, .property-anchor-nav a:focus-visible { color: var(--color-text-primary); }

/* --- Big facts (oversized numerals row) ------------------------------------- */
.property-bigfacts { margin: var(--space-8) 0; }
.bigfacts-list { display: flex; flex-wrap: wrap; gap: var(--space-7); margin: 0; padding: 0; }
.bigfacts-item { display: flex; flex-direction: column; gap: var(--space-1); }
.bigfacts-item dd {
  font: var(--fw-semibold) var(--fs-display-md) / 1.1 var(--font-display);
  font-feature-settings: "tnum" 1; color: var(--color-text-primary); margin: 0;
}
@media (min-width: 1024px) { .bigfacts-item dd { font-size: var(--fs-display-lg); } }
.bigfacts-item dt {
  font: var(--fw-semibold) var(--fs-label) / 1 var(--font-sans);
  text-transform: uppercase; letter-spacing: var(--tracking-wide);
  color: var(--color-text-secondary); margin: 0;
}
/* Estate's "icon-facts strip" — bordered top+bottom band, centered. */
.property-bigfacts--bordered { border-block: 1px solid var(--color-border-hairline); padding-block: var(--space-6); }
.property-bigfacts--bordered .bigfacts-list { justify-content: center; text-align: center; }
.property-bigfacts--bordered .bigfacts-item { align-items: center; }

/* --- Feature list (dots | chips | outline) ---------------------------------- */
.property-features { margin: var(--space-8) 0; }
.feature-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
.feature-list--dots li {
  font: var(--fw-regular) var(--fs-body-md) / 1.4 var(--font-sans);
  color: var(--color-text-primary); display: inline-flex; align-items: center;
}
/* gap (2026-07-29 fix round): without it consecutive items collided with the
   separator dot glued to the next word ("Corner lot ·Heated floors") on every
   dots-variant template (caught on Azure, also affected Meridian). */
.feature-list--dots { gap: var(--space-2) var(--space-2); }
.feature-list--dots li:not(:last-child)::after { content: "·"; margin-left: var(--space-2); color: var(--color-text-tertiary); }
.feature-list--chips, .feature-list--outline { gap: var(--space-2); }
.feature-list--chips li {
  background: var(--color-bg-surface-2); color: var(--color-text-primary);
  border-radius: var(--radius-full); padding: var(--space-2) var(--space-4);
  font: var(--fw-medium) var(--fs-body-sm) / 1 var(--font-sans);
}
.feature-list--outline li {
  border: 1px solid var(--color-border-hairline); border-radius: var(--radius-sm);
  color: var(--color-text-primary); padding: var(--space-2) var(--space-4);
  font: var(--fw-medium) var(--fs-body-sm) / 1 var(--font-sans);
}

/* --- Media explorer (Video · 3D tour) --------------------------------------- */
.property-media-explorer { margin: var(--space-9) 0; }
.media-explorer-nav { margin: var(--space-2) 0 var(--space-4); font: var(--fw-medium) var(--fs-body-sm) / 1 var(--font-sans); color: var(--color-text-secondary); }
.media-explorer-nav a { color: var(--color-accent); text-decoration: none; }
.media-explorer-nav a:hover { text-decoration: underline; }
.media-explorer-note { color: var(--color-text-secondary); font-size: var(--fs-body-sm); margin: 0 0 var(--space-4); }
/* The 3D-tour block nests its own <section id="tour"> inside this one
   (renderTourBlock's markup is reused verbatim, unmodified) — reset its
   redundant .pub-container width/padding so it doesn't double-indent. */
.property-media-explorer .property-tour.pub-container { max-width: none; padding-inline: 0; }

/* --- Agent contact card + the reused scheduler form ------------------------- */
.property-agent-contact { margin: var(--space-10) 0; }
.agent-contact-grid { display: grid; gap: var(--space-7); grid-template-columns: 1fr; }
@media (min-width: 700px) { .agent-contact-grid { grid-template-columns: minmax(220px, 1fr) 2fr; align-items: start; } }
.agent-contact-card { display: flex; gap: var(--space-4); align-items: flex-start; }
.agent-contact-avatar {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 56px; height: 56px; border-radius: var(--radius-full);
  background: var(--color-accent-tint-bg); color: var(--color-accent);
  font: var(--fw-semibold) var(--fs-heading-md) / 1 var(--font-sans);
}
.agent-contact-info { display: flex; flex-direction: column; gap: var(--space-1); }
.agent-contact-name { font: var(--fw-semibold) var(--fs-heading-sm) / 1.3 var(--font-sans); color: var(--color-text-primary); margin: 0; }
.agent-contact-brokerage, .agent-contact-dre { font-size: var(--fs-body-sm); color: var(--color-text-secondary); margin: 0; }
.agent-contact-org-logo { height: 28px; width: auto; margin-top: var(--space-2); border-radius: var(--radius-sm); }
/* The reused scheduler form sits inside this grid's own .pub-container
   parent already — reset its redundant width/padding so it doesn't
   double-indent (§0.6 unaffected: every input still sets its own
   background:var(--color-bg-canvas) regardless of this reset). */
.agent-contact-grid .property-schedule.pub-container { max-width: none; padding-inline: 0; }

/* --- Map card (decorative topo-dot pattern, honest outbound link) ---------- */
.property-map { margin: var(--space-9) 0; }
.map-card {
  position: relative; overflow: hidden; text-align: center;
  border-radius: var(--radius-lg); border: 1px solid var(--color-border-hairline);
  padding: var(--space-8) var(--space-6);
  background-image: radial-gradient(var(--color-border-hairline) 1px, transparent 1px);
  background-size: 16px 16px;
}
.map-card-pattern { display: flex; justify-content: center; margin-bottom: var(--space-4); }
.map-card-pin { display: inline-block; width: 28px; height: 28px; background: var(--color-text-secondary); border-radius: 50% 50% 50% 0; transform: rotate(-45deg); }
.map-card-address { font: var(--fw-semibold) var(--fs-body-lg) / 1.4 var(--font-sans); color: var(--color-text-primary); margin: 0; }
.map-card-citystate { font-size: var(--fs-body-sm); color: var(--color-text-secondary); margin: var(--space-1) 0 var(--space-4); }
.map-card-link { margin-top: var(--space-4); }

/* --- Full-bleed closing address band ---------------------------------------- */
.property-address-band { position: relative; margin-top: var(--space-10); overflow: hidden; }
.address-band-photo { width: 100%; height: 320px; object-fit: cover; display: block; }
.address-band-overlay {
  position: absolute; inset: 0; background: rgba(10, 12, 14, 0.78);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: var(--space-6); text-align: center;
}
.address-band-address { font: var(--fw-medium) var(--fs-display-md) / 1.2 var(--font-display); color: #fff; margin: 0; }
.address-band-citystate {
  font: var(--fw-semibold) var(--fs-label) / 1 var(--font-sans);
  text-transform: uppercase; letter-spacing: var(--tracking-wide); color: #fff; margin: var(--space-3) 0 0;
}

/* --- Gallery: "View all N photos" collage caption --------------------------- */
.gallery-viewall-note { margin: var(--space-3) 0 0; font-size: var(--fs-body-sm); color: var(--color-text-secondary); text-align: center; }

/* --- Gallery pattern: collage — varied spans, no absolute positioning (DOM
   order still matches visual/reading order for keyboard + screen-reader
   navigation). Single column below 640px — the same "asymmetry is a
   desktop-scale gesture" rule Gallery/Atelier's existing patterns use. */
@media (min-width: 640px) {
  .gallery-grid--collage { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 140px; gap: var(--space-3); }
  .gallery-grid--collage .gallery-item:nth-child(6n+1) { grid-column: span 2; grid-row: span 2; }
  .gallery-grid--collage .gallery-item:nth-child(6n+4) { grid-column: span 2; }
}
@media (min-width: 1024px) { .gallery-grid--collage { grid-auto-rows: 180px; } }

/* --- Gallery pattern: mosaic — edge-to-edge tight grid, square crops, 2px gaps. */
.gallery-grid--mosaic { grid-template-columns: repeat(2, 1fr); gap: 2px; }
@media (min-width: 768px) { .gallery-grid--mosaic { grid-template-columns: repeat(4, 1fr); } }
.gallery-grid--mosaic .gallery-item { aspect-ratio: 1 / 1; border-radius: 0; }

/* --- Gallery pattern: framed — hairline frame + generous padding per photo,
   floating on a dotted-pattern section background (painted on
   .property-gallery by whichever template uses this pattern — Horizon). */
.gallery-grid--framed { gap: var(--space-5); }
.gallery-grid--framed .gallery-item {
  border: 1px solid var(--tpl-frame); background: var(--color-bg-canvas);
  padding: var(--space-3); border-radius: 0;
}
.gallery-grid--framed .gallery-item img { border-radius: var(--radius-sm); }

/* --- Floor plan (Task H, nine-improvements plan 2026-07-14) — renders only
   when a floor plan was uploaded (lib/render-public.mjs renderFloorPlan). --- */
.property-floorplan { margin-top: var(--space-8); }
.property-floorplan .floorplan-img {
  max-width: 100%; height: auto; display: block;
  border: 1px solid var(--color-border, rgba(0, 0, 0, 0.12)); border-radius: var(--radius-md);
  background: #fff;
}
