*{margin:0;padding:0;box-sizing:border-box}
:root{
  --page-bg:#ffffff; --surface:#ffffff; --surface-2:#f2f3f8; --border:rgba(15,15,20,.07);
  --text:#111114; --text-dim:#5b5b66; --text-faint:#8b8b96;
  --accent:#7c3aed; --accent-dark:#6d28d9; --accent-light:#a78bfa; --accent-wash:rgba(124,58,237,.1);
  --shadow-card:0 2px 10px rgba(20,20,30,.08),0 1px 3px rgba(20,20,30,.06);
}
[data-theme="dark"]{
  --page-bg:#0e0e13; --surface:#0e0e13; --surface-2:#1a1a22; --border:rgba(255,255,255,.08);
  --text:#fafafa; --text-dim:rgba(255,255,255,.55); --text-faint:rgba(255,255,255,.35);
  --accent-wash:rgba(124,58,237,.18);
  --shadow-card:0 2px 12px rgba(0,0,0,.4),0 1px 4px rgba(0,0,0,.3);
}
html,body{width:100%;min-height:100%;background:var(--page-bg);font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;color:var(--text)}
html{overflow-x:hidden}
/* Sticky footer: #viewerPage grows to fill any leftover viewport height so
   #viewerFooter (its next sibling) stays pinned to the bottom instead of
   floating mid-page when content is shorter than the viewport. */
/* align-items:stretch set explicitly -- empirically (see DevTools computed
   style) align-items:normal (the default) was NOT stretching #viewerPage
   to body's own width the way the spec says it should for flex containers,
   leaving #viewerPage sized to fit its widest content (the hero image/
   canvas's intrinsic size) instead of the viewport. This, not any
   min-width:auto shrink-refusal, was the actual cause of the mobile
   horizontal overflow. */
body{min-height:100vh;display:flex;flex-direction:column;align-items:stretch;transition:background .2s ease,color .2s ease}
/* flex-shrink:0 (this element was the ONLY body child with it -- confirmed
   via DevTools) was the actual cause of the mobile overflow: empirically it
   was preventing cross-axis (width) stretch from applying at all, leaving
   this sized to its widest content (the hero image/canvas) instead of the
   viewport, despite align-items:stretch and min-width:0 both being correctly
   applied. Safe to drop -- body uses min-height:100vh (not a fixed height),
   so the flex line's main-size (height) never actually needs to shrink below
   content in practice; flex-shrink:0 was defensive but not load-bearing for
   the sticky-footer behavior (flex-grow:1 is what does that work). */
#viewerPage{flex:1 1 auto;min-width:0}
a{color:inherit}
/* ─ Hotspots (sit on the photo itself — theme-invariant) ─ */
.hs-link-wrap{position:relative;display:flex;flex-direction:column;align-items:center;transform:translate(-50%,calc(-50% - 10px));perspective:280px}
.hs-link-btn{width:52px;height:52px;border-radius:50%;background:rgba(124,58,237,.9);border:2.5px solid rgba(167,139,250,.8);display:flex;align-items:center;justify-content:center;box-shadow:0 3px 16px rgba(124,58,237,.5);cursor:pointer;transform:rotateX(60deg) rotateY(0deg)}
.hs-link-btn svg{width:22px;height:22px}
/* Matches the editor's .hs-info-wrap/.hs-info-btn/.hs-info-popup (viewer.css)
   exactly -- same wrapper/perspective, same 44px amber button, same popup
   layout -- so a hotspot looks the same to buyers as it did to the owner
   designing it. Colors are hardcoded rather than var()'d since this
   stylesheet doesn't share the editor's base.css theme variables. */
.hs-info-wrap{position:relative;display:flex;flex-direction:column;align-items:center;transform:translate(-50%,calc(-50% - 10px));perspective:280px}
.hs-info-btn{width:44px;height:44px;border-radius:50%;background:rgba(245,158,11,.95);border:2.5px solid #fff;display:flex;align-items:center;justify-content:center;font-weight:800;color:#fff;font-size:20px;box-shadow:0 3px 12px rgba(0,0,0,.4);cursor:pointer}
.hs-info-popup{display:none;position:absolute;bottom:56px;left:50%;transform:translateX(-50%);background:#16161f;border-radius:16px;padding:12px 16px;min-width:160px;max-width:210px;box-shadow:0 16px 40px rgba(0,0,0,.7),0 4px 12px rgba(0,0,0,.4);z-index:20;border:1px solid rgba(255,255,255,.09)}
.hs-info-popup.show{display:block}
.hs-info-popup-title{font-size:13px;font-weight:700;color:#fafafa;margin-bottom:4px}
.hs-info-popup-text{font-size:12px;color:#a1a1aa;line-height:1.5}
/* ─ Scene strip (filmstrip inside the 360° Tour tab) ─ */
#strip{position:static;display:none;gap:10px;padding:12px 4px 4px;overflow-x:auto;background:transparent;scrollbar-width:none}
#strip::-webkit-scrollbar{display:none}
.strip-thumb-wrap{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer}
.strip-thumb{width:76px;height:56px;border-radius:10px;object-fit:cover;border:2px solid transparent;opacity:0.65;transition:opacity .2s ease,border-color .2s ease,transform .2s cubic-bezier(.2,.8,.2,1);box-shadow:0 2px 8px rgba(0,0,0,.12)}
.strip-thumb-wrap:hover .strip-thumb{opacity:.9;transform:translateY(-2px)}
.strip-thumb-wrap.active .strip-thumb{border-color:var(--accent-light);opacity:1;box-shadow:0 0 0 3px var(--accent-wash)}
.strip-thumb-name{font-size:10.5px;color:var(--text-faint);max-width:76px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.strip-thumb-wrap.active .strip-thumb-name{color:var(--accent)}
/* ─ Viewer header ─ */
#viewerHeader{position:sticky;top:0;left:0;right:0;height:56px;z-index:220;background:var(--surface);border-bottom:1px solid var(--border)}
.vh-inner{max-width:2200px;margin:0 auto;height:100%;display:flex;align-items:center;gap:12px;padding:0 16px}
@media (min-width:640px){.vh-inner{padding:0 32px}}
.vh-brand{display:flex;align-items:center;gap:8px;text-decoration:none;flex-shrink:0;transition:opacity .15s;color:var(--text)}
.vh-brand:hover{opacity:.8}
.vh-brand-logo{height:40px;width:auto;display:block}
.vh-brand-logo-dark{display:none}
[data-theme="dark"] .vh-brand-logo-light{display:none}
[data-theme="dark"] .vh-brand-logo-dark{display:block}
.vh-actions{margin-left:auto;display:flex;align-items:center;gap:8px}
/* Brand + auth button are fixed-size (can't shrink) -- their combined
   natural width can exceed a narrow phone's viewport, which was overflowing
   the whole page horizontally even with the html overflow-x:hidden safety
   net (that only stops the scrollbar, it doesn't stop content from being
   clipped). Shrink the row's own footprint on narrow screens instead of
   relying on flex-shrink. */
@media (max-width:400px){
  .vh-inner{gap:8px}
  .vh-actions{gap:5px}
  /* #viewerHeader ancestor bumps specificity above auth.css's own
     .hdr-auth-btn/.hdr-avatar rules, which load after this file and would
     otherwise win the cascade tie at equal specificity regardless of this
     media query. Avatar max-width also needs shrinking here -- a long
     signed-in display name filling its full 140px would still overflow
     a narrow phone even with the icon/brand reductions above. */
  #viewerHeader .hdr-auth-btn{padding:5px 10px;font-size:11px}
  #viewerHeader .hdr-avatar{max-width:70px}
}
/* ─ Floating theme toggle (moved out of the header) ─ */
.vh-theme-float{position:fixed;right:20px;bottom:20px;z-index:300;width:44px;height:44px;border-radius:50%;background:var(--surface);border:1px solid var(--border);color:var(--text-dim);display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:var(--shadow-card);transition:background .15s ease,color .15s ease,bottom .2s ease}
.vh-theme-float:hover{background:var(--accent-wash);color:var(--accent)}
/* Sits above the tour/header (z-index 210-220) but below full-page
   takeovers (paywall/planPopup/loader at 400+) so it never floats over a
   blocking modal. Hidden entirely during fullscreen tour viewing, same as
   the header/footer/banners above -- there's no page chrome then, just the
   tour itself. */
body.tour-fullscreen .vh-theme-float{display:none}
/* ppBanner/planUpgradeBanner (full-width, bottom:0) add this class to body
   while shown -- lift the button clear of them instead of overlapping. */
body.pp-banner-on .vh-theme-float{bottom:64px}
.vh-icon-moon{display:none}
[data-theme="dark"] .vh-icon-sun{display:none}
[data-theme="dark"] .vh-icon-moon{display:block}
/* ─ Trial banner ─ */
/* position:fixed, not sticky -- sticky only pins within its own containing
   block for as long as that block is in view, so on this long, scrollable
   page these upgrade CTAs could scroll away with page content well before
   reaching the actual bottom of the screen instead of staying pinned to the
   viewport like a real footer. .pp-banner-on's bottom padding (below)
   prevents page content from sitting underneath the now out-of-flow banner. */
#ppBanner,#planUpgradeBanner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:190;background:linear-gradient(90deg,rgba(124,58,237,.92),rgba(139,92,246,.92));backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:#fff;padding:8px 16px;align-items:center;justify-content:space-between;gap:12px;font-size:13px;font-weight:600;box-shadow:0 -4px 20px rgba(124,58,237,.25)}
body.pp-banner-on{padding-bottom:calc(52px + env(safe-area-inset-bottom, 0px))}
#trialExpiredBanner{position:sticky;top:0;left:0;right:0;z-index:300;display:none;align-items:center;justify-content:center;background:rgba(30,10,10,0.85);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-bottom:1px solid rgba(239,68,68,0.3);padding:9px 16px;gap:8px}
.teb-text{font-size:12px;color:rgba(255,255,255,0.7);text-align:center}
/* ─ Paywall / plan popup / loader / auth overlay: intentionally theme-invariant
   (dark glass modals — transient overlays, not persistent page chrome) ─ */
#paywall{display:none;position:fixed;inset:0;z-index:500;background:rgba(0,0,0,0.75);backdrop-filter:blur(20px) saturate(1.3);-webkit-backdrop-filter:blur(20px) saturate(1.3);flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:32px;color:#fff}
#paywall.show{display:flex}
.pw-icon{font-size:34px;margin-bottom:18px;width:72px;height:72px;display:flex;align-items:center;justify-content:center;border-radius:50%;background:rgba(139,92,246,.15);border:1px solid rgba(139,92,246,.3)}
.pw-title{font-size:22px;font-weight:800;letter-spacing:-.3px;margin-bottom:10px}
.pw-sub{color:rgba(255,255,255,0.55);font-size:14px;line-height:1.6;margin-bottom:28px;max-width:320px}
.pw-code{margin-top:16px;font-size:12px;color:rgba(255,255,255,0.35)}
/* ─ Plan popup (owner) ─ */
#planPopup{display:none;position:fixed;inset:0;z-index:500;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:20px 16px 48px;background:rgba(0,0,0,0.88);backdrop-filter:blur(8px)}
#planPopup.show{display:block}
.pp-sheet{max-width:400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}
/* Header area */
.pp-hdr-area{display:flex;flex-direction:column;align-items:center;gap:6px;padding:16px 0 6px;text-align:center}
.pp-offer-crown{font-size:17px;font-weight:800;color:#fff;letter-spacing:-.01em}
.pp-countdown-lbl{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:#555}
.pp-timer{font-size:34px;font-weight:800;letter-spacing:4px;color:#8b5cf6;font-variant-numeric:tabular-nums;font-family:'SF Mono','Roboto Mono',monospace}
.pp-offer-saves{font-size:12px;font-weight:600;color:#a78bfa}
.pp-restore-msg{font-size:12px;color:#4b5563;line-height:1.6;max-width:280px}
.pp-ended-msg{font-size:14px;font-weight:700;color:#f87171}
/* Cards */
.pp-vlist{display:flex;flex-direction:column;gap:14px}
.pp-vcard{background:#111118;border:1.5px solid rgba(255,255,255,.09);border-radius:16px;padding:20px 16px 16px;position:relative;display:flex;flex-direction:column;gap:12px}
.pp-vcard--pop{border-color:#8b5cf6;box-shadow:0 0 24px rgba(139,92,246,.12)}
.pp-vcard--best{border-color:#f59e0b;box-shadow:0 0 28px rgba(245,158,11,.15)}
.pp-vcard-top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}
.pp-badge{position:absolute;top:-11px;left:50%;transform:translateX(-50%);background:#8b5cf6;color:#fff;font-size:10px;font-weight:700;letter-spacing:.5px;padding:3px 12px;border-radius:20px;white-space:nowrap}
.pp-badge--gold{background:linear-gradient(135deg,#f59e0b,#fbbf24);color:#1c0a00}
.pp-name{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:#666;padding-top:4px}
.pp-prow{display:flex;align-items:baseline;gap:4px}
.pp-price{font-size:28px;font-weight:800;color:#fff;letter-spacing:-1px}
.pp-price.v{color:#8b5cf6}
.pp-period{font-size:11px;color:#555}
.pp-save-tag{display:inline-block;font-size:11px;font-weight:700;color:#34d399;background:rgba(52,211,153,.12);border:1px solid rgba(52,211,153,.25);padding:2px 8px;border-radius:20px;margin-top:3px}
.pp-save-tag--gold{color:#fbbf24;background:rgba(251,191,36,.12);border-color:rgba(251,191,36,.25)}
.pp-feats{display:flex;flex-direction:column;gap:8px}
.pp-feat.y{font-size:13px;color:rgba(255,255,255,.72)}
/* Buttons */
.pp-btn{width:100%;padding:13px;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;border:none;transition:all .15s;min-height:44px}
.pp-btn.pri{background:#8b5cf6;color:#fff}
.pp-btn.pri:hover{background:#7c3aed}
.pp-btn--gold{background:linear-gradient(135deg,#f59e0b,#fbbf24);color:#fff;font-weight:800}
.pp-btn--gold:hover{background:linear-gradient(135deg,#d97706,#f59e0b)}
/* Basic link + decide later */
.pp-tab-toggle{display:flex;gap:4px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.09);border-radius:10px;padding:3px}
.pp-tab-btn{flex:1;padding:7px 12px;border-radius:8px;border:none;font-size:13px;font-weight:600;cursor:pointer;background:transparent;color:rgba(255,255,255,.4);transition:background .15s,color .15s}
.pp-tab-btn--active{background:rgba(139,92,246,.22);color:#c4b5fd;box-shadow:0 1px 4px rgba(0,0,0,.3)}
.pp-bulk-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.pp-bulk-label{font-size:12px;font-weight:600;color:rgba(255,255,255,.4)}
.pp-bulk-toggle{display:flex;gap:3px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);border-radius:9px;padding:3px}
.pp-bct-btn{padding:5px 14px;border-radius:6px;border:none;font-size:13px;font-weight:700;cursor:pointer;background:transparent;color:rgba(255,255,255,.35);transition:background .15s,color .15s}
.pp-bct-btn--active{background:rgba(139,92,246,.25);color:#c4b5fd;box-shadow:0 1px 4px rgba(0,0,0,.3)}
.pp-basic-link{background:none;border:none;color:#4b5563;font-size:13px;cursor:pointer;padding:10px;width:100%;text-align:center;text-decoration:underline;text-underline-offset:3px;transition:color .15s;min-height:40px}
.pp-basic-link:hover{color:rgba(255,255,255,.45)}
.pp-later{text-align:center;font-size:12px;color:#374151;cursor:pointer;padding:8px;display:flex;align-items:center;justify-content:center;transition:color .15s}
.pp-later:hover{color:#6b7280}
.ppb-time{font-weight:800;font-variant-numeric:tabular-nums}
.ppb-cta{background:rgba(255,255,255,.18);border:none;color:#fff;border-radius:6px;padding:5px 12px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;min-height:30px}
#loader{position:fixed;inset:0;z-index:600;background:var(--page-bg);display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-dim);gap:16px;font-size:14px;opacity:1;transition:opacity .35s ease;pointer-events:none}
#loader.hide{opacity:0}
.spin{width:40px;height:40px;border:3px solid rgba(139,92,246,.15);border-top-color:#8b5cf6;border-right-color:#a78bfa;border-radius:50%;animation:loader-spin .8s cubic-bezier(.5,0,.5,1) infinite}
@keyframes loader-spin{to{transform:rotate(360deg)}}
/* ─ 2D Walkthrough view (contained by default; fullscreen when expanded) ─ */
#walkView{display:none;position:absolute;inset:0;background:#000;align-items:center;justify-content:center;overflow:hidden}
.nav2d-arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:20;width:40px;height:40px;display:flex;align-items:center;justify-content:center;background:rgba(20,20,26,.45);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,.16);border-radius:50%;color:#fff;cursor:pointer;box-shadow:0 4px 20px rgba(0,0,0,.25);transition:background .2s ease,border-color .2s ease,transform .2s cubic-bezier(.2,.8,.2,1)}
.nav2d-arrow:hover{background:rgba(139,92,246,.5);border-color:rgba(167,139,250,.6);transform:translateY(-50%) scale(1.08)}
.nav2d-arrow:active{transform:translateY(-50%) scale(.94)}
.nav2d-arrow--prev{left:12px}
.nav2d-arrow--next{right:12px}
#photoCounter{display:none;position:absolute;top:14px;left:50%;transform:translateX(-50%);z-index:20;background:rgba(20,20,26,.5);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,.16);border-radius:20px;padding:5px 14px;color:#fff;font-size:12px;font-weight:600;letter-spacing:.3px;font-variant-numeric:tabular-nums}
.photo2d-wrap{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden}
#photo2d{width:100%;height:100%;object-fit:contain;display:block;user-select:none;opacity:0;transition:opacity .5s ease}
#photo2d.loaded{opacity:1}
#spot2dOverlay{position:absolute;inset:0;pointer-events:none}
#measure2dCanvas{position:absolute;inset:0;pointer-events:none;display:none}
/* ─ Flat-photo spots (nav/info/link) — reuse the same .hs-link-wrap/
   .hs-info-wrap classes the 360 hotspots use above, matching the editor's
   renderFlatSpots (ui.js) exactly, instead of the separate .vspot-* pill
   design this used to be. #spot2dOverlay is pointer-events:none (so drags/
   swipes on the photo pass through where there's no spot); each anchor opts
   back in. */
.std-spot-anchor{pointer-events:all}

/* ════════════════════════════════════════
   PAGE SHELL
   ════════════════════════════════════════ */
/* margin:0 auto (for centering under max-width:2200px on wide screens) is
   scoped to the same breakpoint as that centering actually matters at --
   auto margins on the CROSS axis of a flex item (this is a direct child of
   body, flex-direction:column, so left/right IS the cross axis) disable
   stretch entirely regardless of align-items/align-self, by spec. Having it
   unconditionally applied was the actual, root cause of the mobile
   horizontal overflow this whole thread was chasing -- every align-items/
   flex-shrink/min-width fix was correct but irrelevant, since auto cross-
   margins override all of them. */
#viewerPage{max-width:2200px;margin:0;padding:0 16px 24px}
@media (min-width:640px){#viewerPage{padding:0 32px 24px}}
@media (min-width:2200px){#viewerPage{margin:0 auto}}
#heroSection{order:1;min-width:0}
/* min-width:0 throughout this chain: flex/block items default to
   min-width:auto, which refuses to shrink below a descendant's INTRINSIC
   size -- #heroImg is a real photo (often several thousand px wide) and
   the Marzipano canvas inside #tourContainer has its own pixel-buffer
   width/height attributes, either of which can leak upward through
   min-content calculations and force every ancestor (#heroStage,
   #heroSection, #bodyGrid, #viewerPage) to size up to match, well past
   the actual viewport -- even though the final RENDERED size is correctly
   scaled down by width:100%. That mismatch is what was causing the mobile
   horizontal overflow: every ancestor "refusing" to shrink below the
   photo's/canvas's native pixel size. */
#heroStage{position:relative;width:100%;min-width:0;aspect-ratio:16/10;border-radius:18px;overflow:hidden;background:var(--surface-2);box-shadow:var(--shadow-card)}
#heroImg{width:100%;height:100%;min-width:0;object-fit:cover;display:block;cursor:pointer}
/* Shown instead of #heroImg when a property has no photos yet (e.g. a
   listing published at the "minimal data" stage) -- buildHero() used to just
   set #heroSection to display:none in that case, leaving the page open with
   no cover visual at all and jumping straight to the title/price below it. */
#heroEmptyState{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:var(--text-faint)}
#heroEmptyState svg{opacity:.55}
#heroEmptyState p{font-size:14px;font-weight:600;margin:0}
.hero-badges{position:absolute;top:14px;left:14px;display:flex;gap:8px;z-index:5}
.hero-badge{display:flex;align-items:center;gap:6px;background:rgba(20,20,26,.6);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:#fff;font-size:12px;font-weight:700;padding:6px 12px;border-radius:20px;border:1px solid rgba(255,255,255,.15)}
/* ── Hero play overlay (pre-activation): tap the cover photo to bring the
   live 360°/photo tour up in place, replacing the static image ── */
#heroPlayOverlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;cursor:pointer;background:rgba(0,0,0,.18);transition:background .2s ease}
#heroPlayOverlay:hover{background:rgba(0,0,0,.28)}
.hero-play-btn{width:60px;height:60px;border-radius:50%;background:rgba(255,255,255,.95);color:#111;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 20px rgba(0,0,0,.35);transition:transform .2s ease}
#heroPlayOverlay:hover .hero-play-btn{transform:scale(1.08)}
#heroPlayLabel{color:#fff;font-weight:700;font-size:14px;text-shadow:0 1px 4px rgba(0,0,0,.5)}
#heroSection.tour-live #heroImg,#heroSection.tour-live #heroPlayOverlay,#heroSection.tour-live #heroNavPrev,#heroSection.tour-live #heroNavNext{display:none}
#heroSection.tour-live #strip{display:flex}

/* ─ Body grid ─
   Mobile: #bodyGrid is a flex column; #bodyMain AND #bodySidebar are both
   display:contents so their children become direct flex items, individually
   orderable (hero → title/CTA + map cards → tabs → contact card, matching
   the mobile mockup, which wants the contact card at the very bottom).
   Desktop: #bodyMain becomes a real block (hero stacked above tabs, normal
   flow) and #bodySidebar becomes a real sticky column again — deliberately
   NOT using grid-template-areas row-spanning here, since a tall sidebar
   spanning two auto rows inflates both rows and leaves dead space. */
#bodyGrid{display:flex;flex-direction:column;gap:20px;margin-top:20px;min-width:0;align-items:stretch}
#bodyMain{display:contents}
#bodyLeft{order:3;min-width:0}
#bodySidebar{display:contents}
#bodySidebar > .sb-card:first-child{order:2}
#bodySidebar > .sb-map-card{order:2}
#bodySidebar > .sb-qr-card{order:4}
.sb-card{background:var(--surface);border:1px solid var(--border);border-radius:18px;padding:20px;box-shadow:var(--shadow-card)}
/* At wide desktop the title card sits next to the taller QR/contact card in
   a flex row, which stretches it to match -- without this, the extra height
   shows up as dead space below Schedule Visit/Share. Flexing this card in a
   column and pushing the CTA group down with margin-top:auto turns that
   same extra space into breathing room above the 360° tour button instead. */
#bodySidebar > .sb-card:first-child{display:flex;flex-direction:column}
#bodySidebar > .sb-card:first-child .sb-cta-col{margin-top:auto}

/* ─ Tab bar -- desktop only below 960px it's replaced by stacked sections,
   see the mobile block near the end of this section ─ */
#tabBar{display:flex;gap:4px;overflow-x:auto;border-bottom:1px solid var(--border);scrollbar-width:none;margin-bottom:20px}
#tabBar::-webkit-scrollbar{display:none}
.tab-btn{flex-shrink:0;padding:12px 16px;border:none;background:none;font-size:15px;font-weight:600;color:var(--text-faint);cursor:pointer;border-bottom:2.5px solid transparent;transition:color .15s ease,border-color .15s ease;font-family:inherit;white-space:nowrap}
.tab-btn:hover{color:var(--text-dim)}
.tab-btn--active{color:var(--accent);border-color:var(--accent)}
.tab-btn[data-tab="amenities"]{display:none}
@media (min-width:960px){.tab-btn[data-tab="amenities"]{display:inline-block}}

/* ─ Tab panels ─ */
#tabPanels{position:relative}
.tab-panel{display:none}
.tp-sec-title{font-size:18px;font-weight:700;margin-bottom:12px;letter-spacing:-.2px}
/* Panel headings (Overview/Specifications/etc.) are redundant with the tab
   bar's own label on desktop, so they stay hidden there -- shown only in
   the mobile stacked-sections layout below, where there's no tab bar left
   to label anything. */
.tp-panel-heading{display:none}
.tp-desc{font-size:14.5px;color:var(--text-dim);line-height:1.7;white-space:pre-line}
.tp-viewall-link{background:none;border:none;color:var(--accent);font-size:13px;font-weight:600;cursor:pointer;padding:12px 0 0;font-family:inherit;text-align:left}
#galleryGrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px}
.gallery-thumb{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:12px;cursor:pointer;transition:opacity .15s ease,transform .15s ease;box-shadow:var(--shadow-card)}
.gallery-thumb:hover{opacity:.85;transform:translateY(-2px)}

/* ─ Mobile: stacked vertical sections instead of tabs -- Overview/
   Specifications/Photos/Map & Location/Amenities/Nearby Places all show at
   once, each under its own heading, rather than switching one panel at a
   time via #tabBar (hidden here) or the old mobileQuickLinks -> full-page
   subpage shortcut (removed; Map & Location and Amenities are now just two
   more stacked sections, #tabMap/#tabAmenities, same as the rest). ─ */
@media (max-width:959px){
  #tabBar{display:none}
  .tab-panel{display:block!important;padding-bottom:24px;margin-bottom:24px;border-bottom:1px solid var(--border)}
  .tab-panel:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0}
  .tab-panel.tp-hidden{display:none!important}
  .tp-panel-heading{display:block}
  /* #bodySidebar is display:contents below 960px (see its own rule), so its
     children -- including this compact map preview card -- render inline in
     the page flow same as everything else, duplicating the new #tabMap
     section above. #tabMap is the one canonical map on mobile; this card
     stays as the sidebar's own compact preview on desktop only. */
  #sidebarMapCard{display:none!important}
}

/* ─ Live 360°/photo viewer, filling the hero once activated ─ */
#tourContainer{position:absolute;inset:0;border-radius:inherit;overflow:hidden;background:#000;visibility:hidden;pointer-events:none}
/* Defensive: Marzipano sizes its <canvas> via JS (inline style/attributes,
   from the WebGL pixel buffer dimensions), which beats any stylesheet rule
   without !important. Even though #tourContainer is position:absolute (out
   of normal flow, so it shouldn't itself force ancestor sizing), pin the
   canvas explicitly so it can never render/report wider than its container. */
#pano{width:100%;height:100%;min-width:0}
#pano canvas{max-width:100%!important;max-height:100%!important}
#measureOverlay{position:absolute;inset:0;pointer-events:none}
#heroSection.tour-live #tourContainer{visibility:visible;pointer-events:auto}
#tourContainer #pano,#tourContainer #walkView{position:absolute;inset:0}
.tour-expand-btn,.tour-collapse-btn{position:absolute;top:12px;right:12px;z-index:25;width:38px;height:38px;border-radius:50%;background:rgba(20,20,26,.5);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.16);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s ease}
.tour-expand-btn:hover,.tour-collapse-btn:hover{background:rgba(139,92,246,.55)}
.tour-collapse-btn{display:none}

/* ─ Sidebar contents (badges/name/price/stats/cta) ─ */
.ds-badges{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}
.ds-badge{padding:4px 10px;border-radius:20px;font-size:12px;font-weight:600;letter-spacing:.2px}
.ds-badge-type{background:var(--accent-wash);color:var(--accent-dark);border:1px solid rgba(139,92,246,.25)}
[data-theme="dark"] .ds-badge-type{color:var(--accent-light)}
.ds-badge-status{background:rgba(34,197,94,.14);color:#16a34a;border:1px solid rgba(34,197,94,.25)}
[data-theme="dark"] .ds-badge-status{color:#4ade80}
.ds-name{font-size:24px;font-weight:700;letter-spacing:-.4px;line-height:1.25;margin:0 0 10px}
.ds-price-row{display:flex;align-items:baseline;gap:8px;margin-bottom:14px}
.ds-price{font-size:28px;font-weight:800;background:linear-gradient(135deg,var(--accent-dark),var(--accent-light));-webkit-background-clip:text;background-clip:text;color:transparent;letter-spacing:-.5px}
.ds-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(70px,1fr));gap:8px;margin-bottom:16px}
.ds-stat{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:12px 8px;text-align:center}
.ds-stat-icon{font-size:17px;margin-bottom:4px}
.ds-stat-val{font-size:16px;font-weight:700;line-height:1.2}
.ds-stat-key{font-size:11px;color:var(--text-faint);letter-spacing:.1px;margin-top:2px}
.ds-owner-card{display:flex;flex-direction:column;align-items:center;gap:4px;margin-bottom:6px}
.ds-owner-avatar{width:64px;height:64px;border-radius:50%;background:linear-gradient(135deg,var(--accent-dark),#5b21b6);display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;color:#fff;flex-shrink:0}
.ds-owner-name{font-size:15px;font-weight:700}
.ds-owner-unverified{font-size:11.5px;font-weight:600;color:#eab308;display:flex;align-items:center;gap:4px}
.sb-cta-col{display:flex;flex-direction:column;gap:9px}
.sb-btn-row{display:flex;gap:9px}
.sb-btn-row .sb-btn{flex:1}
.sb-btn{display:flex;align-items:center;justify-content:center;gap:8px;border-radius:12px;padding:13px;font-size:14.5px;font-weight:600;text-decoration:none;cursor:pointer;border:none;font-family:inherit;transition:transform .15s ease,box-shadow .15s ease,background .15s ease;min-height:46px}
.sb-btn--call{background:var(--accent);color:#fff;box-shadow:0 2px 12px rgba(109,40,217,.4)}
.sb-btn--call:hover{background:var(--accent-dark);box-shadow:0 4px 16px rgba(109,40,217,.5)}
.sb-btn--wa{background:var(--surface);border:1.5px solid #25d366;color:#25d366}
.sb-btn--wa:hover{background:rgba(37,211,102,.08)}
.sb-btn--ghost{background:var(--surface-2);border:1px solid var(--border);color:var(--text-dim)}
.sb-btn--ghost:hover{background:var(--accent-wash);color:var(--accent)}
.sb-btn--ghost.saved{background:var(--accent-wash);color:var(--accent);border-color:transparent}
.sb-map-card{padding:12px}
.sb-map-thumb{width:100%;height:180px;border-radius:12px;overflow:hidden;position:relative;cursor:pointer;background:var(--surface-2)}
.sb-map-thumb iframe{width:100%;height:100%;pointer-events:none}
.sb-qr-card{display:flex;flex-direction:column;gap:10px;text-align:center}
.sb-contact-title{font-size:17px;font-weight:700}
.sb-qr-title{font-size:13px;font-weight:600;color:var(--text-dim)}
.sb-qr-wrap{align-self:center;padding:10px;background:#fff;border-radius:12px;border:1px solid var(--border)}
.sb-qr-code{font-size:10.5px;color:var(--text-faint)}

/* ─ Location / amenities detail content (reused by tabs + mobile sub-page) ─ */
.ds-loc-row{display:flex;align-items:flex-start;gap:8px;color:var(--text-dim);font-size:14px;line-height:1.4;margin-bottom:12px}
.ds-maps-link{margin-left:auto;font-size:12px;color:var(--accent);text-decoration:none;flex-shrink:0;padding:2px 0}
.ds-map-wrap{border-radius:14px;overflow:hidden;border:1px solid var(--border);display:flex}
.ds-map-wrap iframe{width:100%;flex:1;min-height:0;display:block}
.ds-details-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
.ds-detail-item{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:10px 12px}
.ds-detail-label{font-size:10px;color:var(--text-faint);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}
.ds-detail-val{font-size:13px;font-weight:600;line-height:1.3}
.ds-amenities{display:flex;flex-wrap:wrap;gap:8px}
.ds-amenity-chip{background:var(--accent-wash);border:1px solid rgba(139,92,246,.25);border-radius:20px;padding:5px 12px;font-size:12px;color:var(--accent-dark);font-weight:500}
[data-theme="dark"] .ds-amenity-chip{color:var(--accent-light)}
.ds-amenity-more-btn{background:none;border:1px solid var(--border);border-radius:20px;padding:5px 12px;font-size:12px;color:var(--accent);font-weight:600;cursor:pointer;transition:background .15s ease}
.ds-amenity-more-btn:hover{background:var(--accent-wash)}

/* ─ Footer ─ */
#viewerFooter{margin-top:8px;border-top:1px solid var(--border);font-size:11.5px;color:var(--text-faint)}
.vf-inner{max-width:2200px;margin:0 auto;padding:16px 16px 24px;display:flex;flex-direction:column;align-items:flex-start;gap:6px}
.vf-code,.vf-brand{white-space:nowrap}
@media (min-width:640px){
  .vf-inner{flex-direction:row;align-items:center;justify-content:space-between;gap:12px;padding:20px 32px 32px}
}

/* ─ Mobile sub-page (slide-in, back-navigable) ─ */
#mobileDetailPage{position:fixed;inset:0;z-index:400;background:var(--page-bg);transform:translateX(100%);transition:transform .3s cubic-bezier(.32,.72,0,1);display:flex;flex-direction:column}
#mobileDetailPage.show{transform:translateX(0)}
.mdp-topbar{display:flex;align-items:center;gap:14px;padding:14px 20px;border-bottom:1px solid var(--border);flex-shrink:0;background:var(--surface)}
.mdp-back{background:var(--surface-2);border:1px solid var(--border);color:var(--text-dim);width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0}
.mdp-title{font-size:15px;font-weight:700}
.mdp-scroll{flex:1;overflow-y:auto;padding:12px 20px 14px;-webkit-overflow-scrolling:touch;display:flex;flex-direction:column}
.mdp-section{margin-bottom:20px}
/* Map section fills all remaining height in the panel instead of a fixed
   size, so the embedded map is as large as the available screen allows. */
#mdpMapSection{display:flex;flex-direction:column;flex:1;min-height:0;margin-bottom:0}
#mdpMapSection .tp-sec-title{margin-bottom:6px;flex-shrink:0}
#mdpMapSection .ds-loc-row{margin-bottom:8px;flex-shrink:0}
#mdpMapSection .ds-map-wrap{flex:1;min-height:0}
/* #tabMap (the inline mobile-stacked-sections version of the map, see
   view.html) isn't inside a fixed-height flex ancestor the way
   #mdpMapSection is (that one fills the full-screen subpage) -- .ds-map-wrap
   itself has no height without one, and its iframe's min-height:0 lets it
   collapse to ~0 instead of falling back to any intrinsic size. Give it an
   explicit height here instead. */
#tabMap .ds-map-wrap{height:280px}
.mdp-cta-bar{display:flex;gap:10px;padding:14px 20px;border-top:1px solid var(--border);background:var(--surface);flex-shrink:0}
.mdp-cta-bar .sb-btn{flex:1}
.nearby-list{display:flex;flex-direction:column;gap:7px}
.nearby-item{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12.5px;color:var(--text-dim)}
.nearby-name{display:flex;align-items:center;gap:6px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.nearby-dist{flex-shrink:0;font-weight:600;color:var(--text-faint)}

.doc-list{display:flex;flex-direction:column;gap:7px}
.doc-item{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12.5px;color:var(--text-dim)}
.doc-name{display:flex;align-items:center;gap:6px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.doc-dl-link{flex-shrink:0;font-weight:600;color:var(--accent-light);text-decoration:none}
.doc-dl-link:hover{text-decoration:underline}

/* ════════════════════════════════════════
   DESKTOP (≥ 960px)
   ════════════════════════════════════════ */
@media (min-width:960px){
  #bodyGrid{display:grid;grid-template-columns:1fr 380px;align-items:start;gap:24px}
  #bodyMain{display:block}
  #bodyLeft{order:initial}
  #bodySidebar{display:flex;flex-direction:column;gap:16px;order:initial;position:sticky;top:76px}
  #bodySidebar > .sb-card:first-child{order:initial}
  #bodySidebar > .sb-map-card{order:initial}
  #bodySidebar > .sb-qr-card{order:initial}
  #heroStage{aspect-ratio:21/9}
}

/* ── Wide desktop: sidebar always splits into two side-by-side cards
   (title/CTA + QR/contact), giving a photo/tabs | card | card three-column
   layout matching viewer4 — not conditional on which tab is active. ── */
@media (min-width:1180px){
  /* Reference proportions: photo/tabs ≈ 52%, title card ≈ 29%, contact card ≈ 18% */
  #bodyGrid{grid-template-columns:1.1fr 1fr;max-width:1800px;margin-left:auto;margin-right:auto}
  #bodySidebar{flex-direction:row;flex-wrap:wrap}
  #bodySidebar > .sb-card{flex:1 1 260px;min-width:0}
  #bodySidebar > .sb-card:first-child{flex:1.7 1 300px}
  #bodySidebar > .sb-qr-card{flex:1 1 220px}
  #bodySidebar > .sb-map-card{flex-basis:100%}
}

/* ════════════════════════════════════════
   FULLSCREEN TOUR MODE (expand toggle)
   ════════════════════════════════════════ */
body.tour-fullscreen #viewerHeader,
body.tour-fullscreen #ppBanner,
body.tour-fullscreen #planUpgradeBanner,
body.tour-fullscreen #trialExpiredBanner,
body.tour-fullscreen #tabBar,
body.tour-fullscreen #tabOverview,
body.tour-fullscreen #tabSpecs,
body.tour-fullscreen #tabPhotos,
body.tour-fullscreen #tabMap,
body.tour-fullscreen #tabAmenities,
body.tour-fullscreen #bodySidebar,
body.tour-fullscreen #viewerFooter{display:none!important}
/* #heroSection itself stays present (it hosts #tourContainer), but its own
   static-preview chrome is hidden — #tourContainer escapes via position:fixed
   below regardless of the hero's own layout box. */
body.tour-fullscreen #heroImg,
body.tour-fullscreen #heroPlayOverlay,
body.tour-fullscreen #heroNavPrev,
body.tour-fullscreen #heroNavNext,
body.tour-fullscreen .hero-badges{display:none!important}
body.tour-fullscreen{overflow:hidden}
body.tour-fullscreen #viewerPage{max-width:none;padding:0;margin:0}
body.tour-fullscreen #tourContainer{position:fixed;inset:0;z-index:210;border-radius:0;visibility:visible!important;pointer-events:auto!important}
body.tour-fullscreen #strip{position:fixed;bottom:0;left:0;right:0;z-index:211;background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.85));padding:14px 16px}
body.tour-fullscreen .tour-expand-btn{display:none}
body.tour-fullscreen .tour-collapse-btn{display:flex}

/* ════════════════════════════════════════
   EMBED TOUR MODE -- view-app.js calls enterTourFullscreen() at boot for any
   EMB-prefixed code, so everything above already applies; this just removes
   the expand/collapse toggle, since there's no "page" to collapse back into
   when the tour IS the whole embed (permanent, not a user-toggled state).
   ════════════════════════════════════════ */
html.embed-view .tour-expand-btn,
html.embed-view .tour-collapse-btn{display:none!important}

/* Optional per-embed opt-out: <iframe src=".../view/EMB123?gallery=0"> --
   see view-app.js for where embed-hide-gallery gets added. */
html.embed-hide-gallery #strip{display:none!important}

.embed-center-badge{display:none}
html.embed-view .embed-center-badge{
  display:flex;flex-direction:column;align-items:center;gap:6px;
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  z-index:15;pointer-events:none;color:#fff;
  text-shadow:0 2px 8px rgba(0,0,0,.6);
  animation:embedBadgePulse 2.4s ease-in-out infinite;
  opacity:1;transition:opacity .35s ease;
}
/* Added once the visitor starts dragging (see view-app.js) -- the hint has
   done its job once they've actually found the interaction, no need to keep
   it sitting over the view. */
html.embed-view .embed-center-badge.embed-badge-hide{
  opacity:0;animation:none;
}
.embed-center-badge svg{filter:drop-shadow(0 2px 8px rgba(0,0,0,.6))}
.embed-center-badge span{font-size:13px;font-weight:700;letter-spacing:.04em}
@keyframes embedBadgePulse{0%,100%{opacity:.55}50%{opacity:1}}
