/* ==========================================================================
   Keys & Co. — Design System
   Team operating under Right at Home Realty, Brokerage
   Inspired by Apple/Tesla-level restraint, built on Condos.ca / HouseSigma /
   Pine.ca layout conventions.
   ========================================================================== */

:root {
  /* --- Brand palette (client-provided) --- */
  --dark-green: #2F4A3B;
  --deep-green: #1a3a2a;
  --cream-gold: #e8d5b0;
  --light-green-bg: #f4f9f6;
  --soft-border: #c8ddd0;
  --off-white-bg: #f0ede8;
  --muted-green-text: #a8c4b0;

  /* --- Functional aliases --- */
  --bg: var(--light-green-bg);
  --surface: #ffffff;
  --surface-alt: var(--off-white-bg);
  --text-primary: var(--deep-green);
  --text-secondary: #52685c;
  --text-muted: var(--muted-green-text);
  --accent: var(--cream-gold);
  --border: var(--soft-border);
  --danger: #b3543f;
  --warning: #c9962c;
  --success: #3f7a5c;

  /* --- Shape / elevation --- */
  --radius-sm: 12px;
  --radius-md: 16px;
  --radius-lg: 20px;
  --radius-pill: 50px;
  --shadow-soft: 0 2px 12px rgba(0,0,0,0.06);
  --shadow-med: 0 8px 30px rgba(26,58,42,0.10);
  --shadow-lift: 0 16px 40px rgba(26,58,42,0.16);

  /* --- Type --- */
  --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Inter", "Segoe UI", Roboto, sans-serif;
  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
  --space-5: 24px; --space-6: 32px; --space-7: 48px; --space-8: 64px; --space-9: 96px;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* Safety net: no page should ever legitimately need to pan sideways --
   any content that needs its own horizontal scroll (tables, etc.) already
   gets a dedicated .table-scroll-style wrapper with its own overflow-x.
   This has been a recurring whack-a-mole bug (Kip panel, app-topbar,
   listing tables all separately overflowed the viewport at different
   points) -- clipping at the html/body level turns any future stray
   element that's too wide into an invisible clip instead of a scrollable/
   shiftable page, without affecting any intentional inner scroll areas.
   IMPORTANT: this must stay html-only, never `html, body`. Setting
   overflow-x on body pairs with its default overflow-y: visible and gets
   computed up to overflow-y: auto (CSS overflow's "one axis non-visible
   forces the other to auto" rule) -- that silently turns body into its own
   scroll container. The page still visually scrolls the same either way,
   but .app-sidebar's position: sticky (CRM/portal shell, see below) then
   sticks relative to body's box instead of the real viewport, so on any
   page taller than one screen the sidebar just scrolls away instead of
   following (Perry, Sept 2026: "left hand side menu needs to follow along
   when scrolling"). html alone already clips page width the same way. */
html { overflow-x: hidden; }
body {
  margin: 0;
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text-primary);
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font-family: inherit; }

h1, h2, h3, h4 { margin: 0 0 var(--space-3); font-weight: 700; letter-spacing: -0.02em; color: var(--deep-green); }
h1 { font-size: clamp(2rem, 4vw, 3.25rem); line-height: 1.05; }
h2 { font-size: clamp(1.5rem, 2.5vw, 2.25rem); line-height: 1.15; }
h3 { font-size: 1.25rem; }
p { margin: 0 0 var(--space-3); color: var(--text-secondary); }
.eyebrow {
  text-transform: uppercase; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.12em; color: var(--success);
}

.container { max-width: 1240px; margin: 0 auto; padding: 0 var(--space-6); }
@media (max-width: 640px) { .container { padding: 0 var(--space-4); } }

/* --- Buttons (pill-shaped, per brand spec) --- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  border-radius: var(--radius-pill);
  padding: 14px 28px;
  font-size: 0.95rem; font-weight: 600;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform .15s ease, box-shadow .15s ease, background .15s ease;
  white-space: nowrap;
}
.btn:active { transform: scale(0.97); }
.btn-primary { background: var(--deep-green); color: var(--cream-gold); box-shadow: var(--shadow-soft); }
.btn-primary:hover { background: var(--dark-green); box-shadow: var(--shadow-med); }
.btn-gold { background: var(--cream-gold); color: var(--deep-green); box-shadow: var(--shadow-soft); }
.btn-gold:hover { box-shadow: var(--shadow-med); }
.btn-ghost { background: transparent; border-color: var(--border); color: var(--deep-green); }
.btn-ghost:hover { background: var(--surface-alt); }
.btn-sm { padding: 9px 18px; font-size: 0.82rem; }
.btn-block { width: 100%; }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* --- Cards / surfaces --- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
}
.pad-5 { padding: var(--space-5); }
.pad-6 { padding: var(--space-6); }

.pill-tag {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--light-green-bg); border: 1px solid var(--border);
  color: var(--success); font-size: 0.75rem; font-weight: 700;
  padding: 5px 12px; border-radius: var(--radius-pill);
  text-transform: uppercase; letter-spacing: 0.05em;
}
.pill-tag.gold { background: var(--cream-gold); color: var(--deep-green); border-color: transparent; }
.pill-tag.warn { background: #fbeee0; color: var(--warning); border-color: transparent; }
.pill-tag.danger { background: #f6e6e2; color: var(--danger); border-color: transparent; }

/* --- Top nav (marketing site) --- */
.topnav {
  position: sticky; top: 0; z-index: 50;
  background: rgba(244,249,246,0.85);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
}
.topnav-inner { display: flex; align-items: center; justify-content: space-between; height: 76px; }
.brand { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 1.1rem; letter-spacing: 0.01em; color: var(--deep-green); }
.brand img { height: 34px; width: auto; }
.navlinks { display: flex; gap: var(--space-6); align-items: center; }
.navlinks a { font-weight: 600; font-size: 0.92rem; color: var(--text-secondary); }
.navlinks a.active, .navlinks a:hover { color: var(--deep-green); }
.navcta { display: flex; gap: var(--space-3); align-items: center; }
#account-nav-menu {
  display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: 50;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lift); min-width: 180px; padding: 6px;
}
#account-nav-menu.open { display: block; }
#account-nav-menu a { display: block; padding: 8px 10px; border-radius: var(--radius-sm); font-size: 0.88rem; color: var(--text-primary); }
#account-nav-menu a:hover { background: var(--surface-alt); }
.compliance-strip {
  background: var(--deep-green); color: var(--muted-green-text);
  font-size: 0.72rem; text-align: center; padding: 6px var(--space-4);
}
.compliance-strip b { color: var(--cream-gold); }

/* --- Cookie consent banner (JS-injected, see app.js initCookieBanner) --- */
.cookie-banner {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 200;
  background: var(--deep-green); color: var(--muted-green-text);
  padding: var(--space-4) var(--space-5);
  display: flex; align-items: center; justify-content: center; gap: var(--space-4); flex-wrap: wrap;
  box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
}
.cookie-banner p { margin: 0; font-size: 0.85rem; max-width: 640px; }
.cookie-banner a { color: var(--cream-gold); text-decoration: underline; }
@media (max-width: 640px) { .cookie-banner { padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom)); } }

/* --- Mobile app-shell nav (bottom tabs) --- */
.app-tabbar {
  display: none;
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 60;
  background: rgba(255,255,255,0.96);
  backdrop-filter: blur(16px);
  border-top: 1px solid var(--border);
  padding: 8px var(--space-3) calc(8px + env(safe-area-inset-bottom));
  justify-content: space-between;
}
.app-tabbar a {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
  font-size: 0.62rem; font-weight: 700; color: var(--text-muted); padding: 4px 0;
}
.app-tabbar a.active { color: var(--deep-green); }
.app-tabbar .tab-icon { font-size: 1.2rem; line-height: 1; }
.nav-icon { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vertical-align: -3px; margin-right: 4px; }
.app-tabbar .tab-icon.nav-icon { width: 20px; height: 20px; vertical-align: -5px; margin-right: 0; display: block; margin: 0 auto 2px; }

@media (max-width: 860px) {
  .navlinks { display: none; }
  .app-tabbar { display: flex; }
  body { padding-bottom: 74px; }
  .hide-mobile { display: none !important; }
}

/* --- Native app shell (Capacitor iOS/Android, consumer pages only) ---
   Set by applyNativeAppShell() in app.js. Never applies inside /portal/ or
   /crm/ (those keep their normal chrome), and never applies to mobile web
   visitors (Safari/Chrome) -- only inside the actual downloaded app. */
html.native-app-consumer .topnav,
html.native-app-consumer .compliance-strip,
html.native-app-consumer #app-download-banner {
  display: none !important;
}
html.native-app-consumer body {
  padding-top: env(safe-area-inset-top);
}
html.native-app-consumer .app-tabbar {
  display: flex; /* the 4-tab app bar always shows in-app, regardless of viewport width */
}
html.native-app-consumer {
  /* Disable the elastic/bounce overscroll effect so the wrapped site feels
     less like a scrolled browser tab and more like a native screen. */
  overscroll-behavior-y: none;
}
@media (min-width: 861px) { .hide-desktop { display: none !important; } }

/* --- Hero --- */
.hero {
  padding: var(--space-9) 0 var(--space-8);
  background: linear-gradient(180deg, var(--light-green-bg) 0%, #ffffff 100%);
}
.hero-search {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-med);
  padding: var(--space-4); display: flex; gap: var(--space-3); flex-wrap: wrap;
  margin-top: var(--space-6);
}
.hero-search .seg { display: flex; gap: 4px; background: var(--surface-alt); padding: 4px; border-radius: var(--radius-pill); }
.hero-search .seg button {
  border: none; background: transparent; padding: 10px 20px; border-radius: var(--radius-pill);
  font-weight: 700; font-size: 0.85rem; color: var(--text-secondary); cursor: pointer;
}
.hero-search .seg button.active { background: var(--deep-green); color: var(--cream-gold); }
.hero-search input[type="text"] {
  flex: 1; min-width: 220px; border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: 12px 20px; font-size: 1rem; background: var(--surface-alt);
  /* Same 16px-minimum reasoning as .field input -- this one was missed in
     the earlier pass, and it now matters more than most: the native app
     opens straight into buy.html, where this search bar is the first input
     on screen, so it's the first thing likely to get tapped on launch. */
}
.hero-search input:focus, .field input:focus, .field select:focus, .field textarea:focus { outline: 2px solid var(--dark-green); outline-offset: 1px; }

/* --- Homepage live search dropdown --- */
.search-live-results {
  position: absolute; top: calc(100% + 8px); left: 0; right: 0;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-med); z-index: 40; max-height: 440px; overflow-y: auto; padding: var(--space-2);
}
.search-live-results .slr-item { display: flex; gap: 12px; align-items: center; padding: 10px; border-radius: var(--radius-sm); text-decoration: none; color: inherit; }
.search-live-results .slr-item:hover { background: var(--surface-alt); }
.search-live-results .slr-thumb { width: 56px; height: 56px; border-radius: var(--radius-sm); background-size: cover; background-position: center; flex-shrink: 0; }
.search-live-results .slr-viewall { display: block; text-align: center; padding: 10px; font-weight: 700; color: var(--deep-green); text-decoration: none; border-top: 1px solid var(--border); margin-top: 4px; }
.search-live-results .slr-empty { padding: 18px; text-align: center; color: var(--text-muted); }

/* --- Native app "home" header (Listings tab) ---
   HouseSigma/Realtor.ca-style search + category row shown only inside the
   wrapped app, above the existing Buy/Rent filters. Hidden everywhere else
   (desktop and mobile web keep the normal buy.html/rent.html layout). */
#app-home-header { display: none; }
html.native-app-consumer #app-home-header { display: block; margin-bottom: var(--space-5); }
.app-category-row { display: flex; gap: var(--space-3); margin-top: var(--space-4); }
.app-category-item {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 8px;
  text-decoration: none; color: var(--text-secondary); font-size: 0.76rem; font-weight: 700;
  background: none; border: none; cursor: pointer; padding: 0; font-family: inherit;
}
.app-category-icon {
  width: 52px; height: 52px; border-radius: 50%; background: var(--light-green-bg);
  display: flex; align-items: center; justify-content: center; font-size: 1.4rem;
}
.app-category-item.active .app-category-icon { background: var(--deep-green); }
.app-category-item.active { color: var(--deep-green); }
/* The market-report blurb and the numbered pagination row on buy.html/
   rent.html are SEO furniture (the pagination links aren't even functional
   -- see the comment in buy.html) -- hide both inside the app. */
html.native-app-consumer #page-market-block,
html.native-app-consumer #listings-pagination { display: none; }

/* Native app footer -- the full 5-column site-wide menu (Explore/Portals/
   Company/Programs) duplicates the app's own tab bar and isn't something
   anyone taps through on a phone; the brokerage disclosure and legal links
   stay (required, and small enough to sit above the tab bar cleanly).
   Same markup on every page (footer isn't templated), so this is one CSS
   change rather than 26 file edits. */
html.native-app-consumer .site-footer { padding: var(--space-6) 0 var(--space-4); margin-top: var(--space-6); }
html.native-app-consumer .site-footer .footer-grid { display: none; }
html.native-app-consumer .footer-brokerage {
  justify-content: center; text-align: center; border-top: none;
  margin-top: 0; padding-top: 0;
}
html.native-app-consumer .footer-legal { text-align: center; }
/* Note: no extra bottom margin needed here -- body already reserves
   padding-bottom for the fixed .app-tabbar via the existing
   @media(max-width:860px) rule, which every in-app screen falls under. */

.stat-strip { display: grid; grid-template-columns: repeat(4,1fr); gap: var(--space-4); margin-top: var(--space-7); }
.stat-strip .card { text-align: center; padding: var(--space-5); }
.stat-strip .stat-num { font-size: 1.8rem; font-weight: 800; color: var(--deep-green); }
.stat-strip .stat-label { font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
@media (max-width: 760px) { .stat-strip { grid-template-columns: repeat(2,1fr); } }

/* --- Listing grid --- */
.listing-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-5); transition: opacity .15s ease; }
@media (max-width: 1000px) { .listing-grid { grid-template-columns: repeat(2,1fr); } }
@media (max-width: 640px) { .listing-grid { grid-template-columns: 1fr; } }

/* Sold-page/VOW-gated teaser cards -- blurred placeholder content shown
   until a visitor has registered + agreed to the VOW Terms of Use. Blur is
   now scoped to just the .thumb photo (a decorative stock photo, see
   SOLD_TEASER_STOCK_PHOTOS in app.js) rather than the whole card, so the
   per-card "Sign up to view" CTA layered on top stays sharp and clickable. */
.teaser-blurred { filter: blur(5px); user-select: none; pointer-events: none; }
.teaser-card { position: relative; cursor: pointer; }
.teaser-card-cta {
  position: absolute; inset: 0; bottom: 42%; z-index: 2; display: flex;
  align-items: center; justify-content: center; background: rgba(26,58,42,0.28);
}
.teaser-card .price, .teaser-card .addr { filter: blur(3px); user-select: none; }
.sold-teaser-overlay {
  display: none; position: absolute; inset: 0; z-index: 2;
  align-items: center; justify-content: center; padding: var(--space-5);
  background: linear-gradient(to bottom, rgba(255,255,255,0.35), rgba(255,255,255,0.85) 40%);
}
#sold-grid, #home-sold-grid { position: relative; }

.listing-card { overflow: hidden; transition: box-shadow .2s ease, transform .2s ease; }
.listing-card:hover { box-shadow: var(--shadow-lift); transform: translateY(-2px); }
.listing-card .thumb {
  position: relative; aspect-ratio: 4/3; background: var(--surface-alt);
  display: flex; align-items: center; justify-content: center; color: var(--text-muted); font-size: 0.8rem;
  background-size: cover; background-position: center;
}
.listing-card .thumb .badge { position: absolute; top: 12px; left: 12px; }
.listing-card .thumb .fav { position: absolute; top: 12px; right: 12px; background: rgba(255,255,255,0.9); border-radius: 50%; width: 34px; height: 34px; display: flex; align-items: center; justify-content: center; }
.listing-card .body { padding: var(--space-4); }
.listing-card .price { font-size: 1.3rem; font-weight: 800; color: var(--deep-green); }
.listing-card .addr { font-size: 0.88rem; color: var(--text-secondary); margin-bottom: 6px; }
.listing-card .meta { display: flex; gap: var(--space-4); font-size: 0.8rem; color: var(--text-muted); }

/* --- Filters bar --- */
.filters-bar {
  display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center;
  padding: var(--space-3); background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-soft); margin-bottom: var(--space-5);
}
.filters-bar select { border: 1px solid var(--border); background: var(--surface-alt); border-radius: var(--radius-pill); padding: 9px 16px; font-size: 1rem; font-weight: 600; color: var(--text-secondary); cursor: pointer; }

/* City + Price dropdown filters (Aug 2026) -- a pill button that opens a
   panel underneath, same interaction shape for both: multi-city checkboxes
   for City, preset range chips + custom min/max for Price. Replaced (a) the
   old single <select id="city-select"> (Buy/Rent can now search several
   TRREB cities at once) and (b) two bare number inputs sitting directly in
   the filters bar, which Perry flagged as not very appealing. */
.dropdown-filter { position: relative; }
.dropdown-filter-btn {
  border: 1px solid var(--border); background: var(--surface-alt); border-radius: var(--radius-pill);
  padding: 9px 16px; font-size: 0.82rem; font-weight: 600; color: var(--text-secondary);
  cursor: pointer; font-family: inherit; display: inline-flex; align-items: center; gap: 6px;
}
.dropdown-filter-btn::after { content: '▾'; font-size: 0.7rem; opacity: 0.6; }
.dropdown-filter-btn:hover { background: #fff; border-color: var(--deep-green); }
.dropdown-filter-panel {
  display: none; position: absolute; top: calc(100% + 8px); left: 0; z-index: 20;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lift); padding: var(--space-3); min-width: 240px;
}
.dropdown-filter-panel.open { display: block; }
.dropdown-filter-options { display: flex; flex-direction: column; gap: 2px; max-height: 260px; overflow-y: auto; }
.dropdown-filter-options label {
  display: flex; align-items: center; gap: 8px; font-size: 0.86rem; font-weight: 500;
  padding: 7px 6px; border-radius: 6px; cursor: pointer; color: var(--text-primary);
}
.dropdown-filter-options label:hover { background: var(--surface-alt); }
.dropdown-filter-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }

.price-presets { display: flex; flex-direction: column; gap: 2px; margin-bottom: 10px; }
.price-preset-btn {
  border: none; background: transparent; text-align: left; padding: 7px 6px; border-radius: 6px;
  font-size: 0.86rem; font-weight: 500; color: var(--text-primary); cursor: pointer; font-family: inherit;
}
.price-preset-btn:hover { background: var(--surface-alt); }
.price-custom-row { display: flex; align-items: center; gap: 8px; padding-top: 10px; border-top: 1px solid var(--border); }
.price-custom-row input[type="number"] {
  border: 1px solid var(--border); background: var(--surface-alt); border-radius: var(--radius-pill);
  padding: 8px 12px; font-size: 0.82rem; font-weight: 600; color: var(--text-secondary);
  width: 100%; min-width: 0;
}
.price-custom-row input[type="number"]::placeholder { color: var(--text-muted); font-weight: 500; }
.price-custom-row span { color: var(--text-muted); font-weight: 600; }

/* "Lead active on site" toasts (Aug 2026) -- CRM-only, injected via JS (see
   showLeadActivityToast() in app.js), not present in any page's markup. */
#live-activity-toasts { position: fixed; bottom: 24px; right: 24px; display: flex; flex-direction: column; gap: 10px; z-index: 999; max-width: 320px; }
.live-activity-toast {
  background: var(--deep-green); color: var(--cream-gold); border-radius: var(--radius-md);
  box-shadow: var(--shadow-soft); padding: 14px 16px; font-size: 0.85rem; line-height: 1.4;
  display: flex; align-items: flex-start; gap: 10px; animation: liveActivityIn 0.25s ease;
}
.live-activity-toast a { color: var(--cream-gold); font-weight: 700; }
.live-activity-toast .toast-close { cursor: pointer; opacity: 0.7; font-size: 0.8rem; }
@keyframes liveActivityIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
/* .filter-chip is intentionally NOT scoped to .filters-bar -- it's reused
   standalone on the homepage city-switcher and CRM role-assignment chips, so
   it needs to look right everywhere. (The homepage's old "Explore by city"
   flat chip wall was replaced with .city-card below -- Perry felt the chip
   wall looked low-effort next to the rest of the homepage.) */
.filter-chip {
  display: inline-block; border: 1px solid var(--border); background: var(--surface-alt); border-radius: var(--radius-pill);
  padding: 9px 16px; font-size: 0.82rem; font-weight: 600; color: var(--text-secondary); cursor: pointer;
  text-decoration: none;
}
.filter-chip.active { background: var(--deep-green); color: var(--cream-gold); border-color: var(--deep-green); }

/* --- Homepage "Explore by city" cards (Aug 2026) -- replaces the old flat
   .filter-chip wall with real cards: a gradient header (no stock city
   photography exists yet -- see the Lifestyle Photography Plan task; swap
   these for real photos once shot), a short blurb, a live active-listings
   count pulled from the same market-stats endpoint as Market Pulse, and
   Homes/Rentals links. On-brand gradients only, no random colors -- each
   city gets a different angle/mix of the existing palette. */
.city-card { overflow: hidden; padding: 0; display: flex; flex-direction: column; }
.city-card-photo { height: 120px; display: flex; align-items: flex-end; padding: var(--space-4); }
.city-card-photo h3 { color: #fff; margin: 0; text-shadow: 0 2px 8px rgba(0,0,0,0.25); }
.city-card-body { padding: var(--space-4); display: flex; flex-direction: column; flex: 1; }
.city-card-body p { color: var(--text-secondary); font-size: 0.88rem; margin: 0 0 12px; flex: 1; }
.city-card-stat { font-size: 0.8rem; font-weight: 700; color: var(--deep-green); margin-bottom: 12px; }
.city-card-links { display: flex; gap: 8px; }
.city-card-links a { flex: 1; text-align: center; }
.city-grad-1 { background: linear-gradient(135deg, var(--deep-green), var(--dark-green)); }
.city-grad-2 { background: linear-gradient(135deg, var(--dark-green), #3d5a4a); }
.city-grad-3 { background: linear-gradient(150deg, var(--deep-green), #2a5240); }
.city-grad-4 { background: linear-gradient(120deg, #24443590, var(--deep-green)), linear-gradient(135deg, var(--dark-green), var(--deep-green)); }
.city-grad-5 { background: linear-gradient(160deg, var(--dark-green), var(--deep-green) 70%); }
.city-grad-6 { background: linear-gradient(140deg, var(--deep-green), #345c48); }
.city-grad-7 { background: linear-gradient(125deg, #3d5a4a, var(--deep-green)); }

/* --- Pagination (SEO — real paginated pages, not infinite scroll) --- */
.pagination { display: flex; justify-content: center; gap: 6px; margin: var(--space-7) 0; }
.pagination a, .pagination span {
  width: 38px; height: 38px; display: flex; align-items: center; justify-content: center;
  border-radius: 50%; font-weight: 700; font-size: 0.85rem; color: var(--text-secondary);
  border: 1px solid var(--border);
}
.pagination a.active { background: var(--deep-green); color: var(--cream-gold); border-color: var(--deep-green); }
.pagination a:hover:not(.active) { background: var(--surface-alt); }
/* "Previous"/"Next" are text, not a single digit -- the default circular
   38x38 chip above was squashing "Next →" into an unreadable shape (this is
   the pagination bug Perry flagged). Override to a pill shape sized to its
   content instead of forcing it into a circle. */
.pagination a.pagination-nav { width: auto; border-radius: 999px; padding: 0 16px; white-space: nowrap; }

/* --- Listing map view (buy.html/rent.html), Leaflet-based --- */
.map-price-pin { background: transparent; border: none; }
.map-price-pin span {
  display: inline-block; background: var(--deep-green); color: var(--cream-gold);
  font-weight: 700; font-size: 0.72rem; padding: 4px 10px; border-radius: 999px;
  white-space: nowrap; box-shadow: var(--shadow-lift); transform: translate(-50%, -100%);
  border: 2px solid var(--cream-gold);
}
.map-pin-popup { display: flex; gap: 8px; align-items: center; text-decoration: none; color: inherit; min-width: 180px; }
.map-pin-popup-thumb { width: 64px; height: 48px; border-radius: 6px; background-size: cover; background-position: center; flex-shrink: 0; background-color: var(--surface-alt); }
/* Cluster-bubble popup (Aug 2026) -- clicking a small cluster shows the
   individual listings underneath it, stacked, instead of only zooming the
   map in with nothing to actually click on. */
.map-cluster-popup-list { max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; }
.map-cluster-popup-list .map-pin-popup { padding: 8px 0; border-bottom: 1px solid var(--border); }
.map-cluster-popup-list .map-pin-popup:last-child { border-bottom: none; }
.leaflet-popup-content-wrapper { border-radius: var(--radius-md); }
/* Leaflet's default panes/controls (zoom buttons, popups) carry z-index up
   to 1000 in leaflet.css. Because leaflet.css only sets position:relative
   (not z-index) on .leaflet-container, that container never establishes its
   own stacking context -- so those internal z-index:1000 controls compete
   directly against the sticky top nav (z-index:50) and win, visually
   floating above it while scrolling. Giving the container its own z-index
   creates that missing stacking context, capping everything inside it (no
   matter how high leaflet.css sets it) below the site chrome. */
.leaflet-container { z-index: 1; }

/* Server-side cluster bubbles (Aug 2026, cluster=true) -- a count of
   listings in an area rather than a single price pin. Sized dynamically in
   app.js (fetchAndRenderClusters) based on how many listings the bubble
   represents; click zooms into it, which re-fetches tighter clusters.
   Redesigned (Aug 2026) from a plain numeric circle to a rounded "N units"
   pill, closer to condos.ca's cluster styling -- reads more clearly than a
   bare number at a glance, especially for small counts. */
.map-cluster-bubble { background: transparent; border: none; }
.map-cluster-bubble .cluster-pill {
  display: inline-flex; align-items: center; gap: 4px; padding: 0 13px;
  border-radius: 999px; white-space: nowrap; cursor: pointer;
  background: var(--deep-green); color: var(--cream-gold);
  box-shadow: var(--shadow-lift); border: 2px solid var(--cream-gold);
  transform: translate(-50%, -50%);
}
.map-cluster-bubble .cluster-count { font-weight: 700; font-size: 0.82rem; }
.map-cluster-bubble .cluster-label { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; opacity: 0.85; }

/* --- List / Mixed / Map view toggle (buy.html/rent.html) --- */
.view-toggle { display: inline-flex; margin-left: auto; border: 1px solid var(--border); border-radius: 999px; overflow: hidden; background: var(--surface); }
.view-toggle-btn {
  background: transparent; border: none; padding: 8px 18px; font-size: 0.82rem; font-weight: 700;
  color: var(--text-secondary); cursor: pointer; font-family: inherit;
}
.view-toggle-btn + .view-toggle-btn { border-left: 1px solid var(--border); }
.view-toggle-btn.active { background: var(--deep-green); color: var(--cream-gold); }
.view-toggle-btn:hover:not(.active) { background: var(--surface-alt); }

/* Grid + map live side by side ("Mixed" mode) or map takes over full-width
   ("Map" mode); "List" mode is the original grid-only layout. Toggled by
   adding a mode-* class to #listing-view-container from setListingViewMode()
   in app.js. */
#city-listing-map { display: none; height: 600px; border-radius: var(--radius-lg); overflow: hidden; margin-bottom: var(--space-5); }
.listing-view-container.mode-mixed { display: flex; gap: var(--space-5); align-items: flex-start; }
.listing-view-container.mode-mixed #city-listing-grid {
  display: flex; flex-direction: column; gap: var(--space-5);
  flex: 0 0 44%; max-width: 44%; max-height: 760px; overflow-y: auto; padding-right: 4px;
}
.listing-view-container.mode-mixed .listing-card { display: flex; flex-direction: row; flex-shrink: 0; }
.listing-view-container.mode-mixed .listing-card .thumb { width: 160px; flex-shrink: 0; }
.listing-view-container.mode-mixed .listing-card .body { flex: 1; min-width: 0; }
.listing-view-container.mode-mixed #city-listing-map { display: block; flex: 1; height: 760px; position: sticky; top: 88px; margin-bottom: 0; }
.listing-view-container.mode-map #city-listing-grid { display: none; }
.listing-view-container.mode-map #city-listing-map { display: block; height: 680px; }
@media (max-width: 900px) {
  .listing-view-container.mode-mixed { display: block; }
  .listing-view-container.mode-mixed #city-listing-grid { max-width: none; max-height: none; overflow-y: visible; margin-bottom: var(--space-5); }
  .listing-view-container.mode-mixed #city-listing-map { position: static; height: 420px; }
}

/* --- Section basics --- */
.section { padding: var(--space-8) 0; }
.section-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: var(--space-6); flex-wrap: wrap; gap: var(--space-3); }

footer.site-footer { background: var(--deep-green); color: var(--muted-green-text); padding: var(--space-8) 0 var(--space-6); margin-top: var(--space-8); }
footer.site-footer h4 { color: var(--cream-gold); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: var(--space-4); }
footer.site-footer a { color: var(--muted-green-text); font-size: 0.88rem; display: block; margin-bottom: 10px; }
footer.site-footer a:hover { color: var(--cream-gold); }
.footer-grid { display: grid; grid-template-columns: 2fr repeat(4, 1fr); gap: var(--space-6); }
@media (max-width: 900px) { .footer-grid { grid-template-columns: 1fr 1fr 1fr; } }
@media (max-width: 760px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
.footer-brokerage {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  border-top: 1px solid rgba(255,255,255,0.12);
  margin-top: var(--space-7); padding-top: var(--space-5); padding-bottom: var(--space-3);
}
.footer-brokerage img { height: 34px; width: auto; }
.footer-brokerage span { color: var(--muted-green-text); font-size: 0.82rem; line-height: 1.4; }

.footer-legal { font-size: 0.72rem; line-height: 1.6; }

/* --- Modal (offer / application / general) --- */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(26,58,42,0.45); backdrop-filter: blur(3px);
  display: none; align-items: flex-start; justify-content: center; z-index: 100;
  /* Top padding needs env(safe-area-inset-top) added on top of the normal
     space-6 gap -- otherwise a tall modal (like Book a Showing, six fields)
     that's aligned flex-start can render close enough to the top that its
     heading ends up partly under the Dynamic Island/notch. Resolves to 0 on
     non-notch/desktop, so harmless there. */
  padding: calc(var(--space-6) + env(safe-area-inset-top)) var(--space-3) var(--space-6);
  overflow-y: auto;
}
.modal-overlay.open { display: flex; }
.modal {
  background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow-lift);
  max-width: 560px; width: 100%; padding: var(--space-6); margin: var(--space-6) 0; position: relative;
}
.modal-close { position: absolute; top: var(--space-4); right: var(--space-4); background: var(--surface-alt); border: none; width: 34px; height: 34px; border-radius: 50%; cursor: pointer; font-size: 1rem; }
.modal-steps { display: flex; gap: 6px; margin-bottom: var(--space-5); }
.modal-steps .dot { flex: 1; height: 4px; border-radius: 2px; background: var(--border); }
.modal-steps .dot.done { background: var(--success); }
.modal-steps .dot.current { background: var(--dark-green); }

.field { margin-bottom: var(--space-4); }
.field label { display: block; font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin-bottom: 6px; }
.field input:not([type="radio"]):not([type="checkbox"]), .field select, .field textarea {
  width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 12px 14px; font-size: 1rem; background: var(--surface-alt);
  /* 1rem (16px) is deliberate, not a design choice -- anything smaller makes
     iOS Safari/WKWebView auto-zoom the whole page in when the field is
     focused, and it stays zoomed until manually pinched back out. That's
     what was causing the "have to scroll horizontally" symptom on the
     landlord/tenant login inside the app. */
}
.field input[type="radio"], .field input[type="checkbox"] { width: auto; accent-color: var(--dark-green); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
@media (max-width: 480px) { .field-row { grid-template-columns: 1fr; } }
.field-hint { font-size: 0.74rem; color: var(--text-muted); margin-top: 4px; }
.consent-box { display: flex; gap: 10px; align-items: flex-start; font-size: 0.76rem; color: var(--text-muted); background: var(--surface-alt); padding: var(--space-3); border-radius: var(--radius-sm); margin-top: var(--space-3); }

/* --- Portal / CRM shell layout --- */
/* .app-sidebar used to paint its own green background AND be the sticky/
   scrolling element, forced to a flat 100vh tall. That works fine right up
   until .app-main's content (which is often much taller, e.g. a long CRM
   page) runs out of room to keep the sidebar "stuck" -- past that point it
   scrolls away with the page like any normal element, and everything below
   its 100vh box shows the page's plain background instead of green. Fix:
   paint the green on .app-shell itself via a hard-stop gradient, sized to
   the sidebar's width, for its *entire* height (which flexbox already
   stretches to match .app-main, however tall that is) -- so the colour
   never runs out regardless of content length. .app-sidebar itself goes
   transparent and just handles the sticky nav-links positioning on top of
   that backdrop, capped at 100vh with its own scroll for safety on smaller
   screens with a long nav list. */
.app-shell { display: flex; min-height: 100vh; background: linear-gradient(to right, var(--deep-green) 248px, var(--bg) 248px); }
.app-sidebar {
  width: 248px; flex-shrink: 0; background: transparent; color: var(--muted-green-text);
  padding: var(--space-5) var(--space-4); position: sticky; top: 0; max-height: 100vh; overflow-y: auto;
}
.app-sidebar .brand { color: var(--cream-gold); margin-bottom: var(--space-7); }
.app-sidebar .brand img { height: 30px; }
.side-link {
  display: flex; align-items: center; gap: 10px; padding: 11px 14px; border-radius: var(--radius-sm);
  font-size: 0.88rem; font-weight: 600; margin-bottom: 4px; color: var(--muted-green-text);
}
.side-link.active, .side-link:hover { background: rgba(255,255,255,0.08); color: #fff; }
.side-group-label { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.6; margin: var(--space-5) 0 var(--space-2) 14px; }
.app-main { flex: 1; min-width: 0; background: var(--bg); }
.app-topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-4) var(--space-6); background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 20;
}
.app-content { padding: var(--space-6); max-width: 1400px; }
.sidebar-toggle-btn { display: none; background: none; border: none; padding: 6px; margin-right: 10px; cursor: pointer; color: var(--deep-green); flex-shrink: 0; }
.sidebar-toggle-btn svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.sidebar-backdrop { display: none; }
@media (max-width: 860px) {
  /* Below this width the sidebar (which holds Log Out, Switch Portal, and
     Public Site — the only way out of a portal/CRM view) used to just
     disappear with no way to reopen it. That's fine on desktop where it's
     always visible, but on a phone -- and especially inside the native app,
     which has no browser chrome or back button at all -- it left users
     stuck with no visible way to log out. It's now an off-canvas drawer
     toggled by a hamburger button in the topbar instead of being hidden. */
  .app-sidebar {
    /* Floating drawer here, independent of .app-shell's in-flow gradient
       backdrop -- needs its own solid colour back rather than the
       transparent background it uses on desktop. */
    display: block; position: fixed; top: 0; left: 0; height: 100vh; z-index: 60;
    width: 260px; max-width: 82vw; max-height: none; transform: translateX(-100%);
    background: var(--deep-green);
    transition: transform 0.25s ease; box-shadow: 4px 0 24px rgba(0,0,0,0.3);
  }
  .app-shell.sidebar-open .app-sidebar { transform: translateX(0); }
  .sidebar-toggle-btn { display: inline-flex; align-items: center; justify-content: center; }
  .sidebar-backdrop {
    display: block; position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 50;
    opacity: 0; pointer-events: none; transition: opacity 0.25s ease;
  }
  .app-shell.sidebar-open .sidebar-backdrop { opacity: 1; pointer-events: auto; }
  .app-content { padding: var(--space-4); }
  /* Topbars with more than a stat badge + avatar on the right (the
     landlord dashboard's property-count badge, plan badge, and "Request
     new unit" button all together) had nowhere to go on a narrow screen --
     .app-topbar never wrapped, so that whole right-hand group just
     overlapped the "Landlord Dashboard" title on the left instead of
     dropping to its own line. Wrapping (both the topbar itself and its
     two direct child groups) lets it reflow to a second row instead of
     overlapping; harmless on topbars with less content, which just never
     need the second row. */
  .app-topbar { padding: var(--space-3) var(--space-4); flex-wrap: wrap; row-gap: 10px; }
  .app-topbar > div { flex-wrap: wrap; row-gap: 8px; }
}
/* Portal/CRM pages intentionally never get the .native-app-consumer class
   (see applyNativeAppShell() in app.js -- they keep their own staff/
   landlord/tenant chrome instead of the marketing site's), but every
   safe-area-inset-top rule in this file was scoped to .native-app-consumer
   only. That meant on a physical device, running inside the actual app,
   this fixed/sticky .app-topbar (and the sidebar drawer) rendered flush
   against the very top edge with zero allowance for the iOS status bar --
   the clock/signal/battery visibly overlapped the "Landlord Dashboard"
   title. .native-app (the broader class, added for every app page
   regardless of section) is the correct scope here. */
html.native-app .app-topbar { padding-top: calc(var(--space-3) + env(safe-area-inset-top)); }
html.native-app .app-sidebar { padding-top: calc(var(--space-5) + env(safe-area-inset-top)); }
/* NOTE: this breakpoint is intentionally aligned with the .app-tabbar breakpoint below
   (also 860px) — previously the sidebar hid at 960px while the bottom tab bar only
   appeared below 860px, leaving portal/CRM pages with zero navigation between
   861–960px (a real width for large phones in landscape, e.g. iPhone Pro Max ~932px). */

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--radius-md); }
.table-scroll table.data-table { min-width: 560px; }
/* Narrow 2-column label/value tables (listing "Key facts" / "Additional
   Details") don't need the 560px floor above -- that's sized for wide
   multi-column CRM tables. Forcing it on a 2-column table just causes
   pointless horizontal scroll on phones, since the grid track (see
   .listing-detail-cols) also can't shrink below a min-content child,
   which was cutting the facts section off until you scrolled right. */
.table-scroll table.data-table.facts-table { min-width: 0; }
/* Room Details / Price History / Listing History tables on the listing
   detail page hit this exact same bug and never got the fix above --
   they only render when a given listing actually has that data, which is
   why this looked inconsistent listing-to-listing. Rather than tagging
   every current and future table on this page with .facts-table by hand,
   exempt anything living inside .listing-detail-cols outright: no table on
   this page should ever be allowed to force the grid column (and every
   card sharing it, including unrelated ones like "About this home") wider
   than the screen. Genuinely wide multi-column CRM tables elsewhere keep
   the 560px floor untouched since they're not inside this grid. */
.listing-detail-cols .table-scroll table.data-table { min-width: 0; }

/* listing-buy.html / listing-rent.html's two-column grid (main content +
   the "Interested in this home?" aside card). The aside's .card already has
   position:sticky;top:96px inline, meant to follow along as you scroll past
   the long main column -- but CSS Grid's default align-items:stretch makes
   Safari compute the sticky child's scroll range incorrectly (a long-
   standing WebKit bug with sticky inside a stretched grid item), so it just
   sits still instead of sticking. align-items:start stops the grid from
   stretching either column to match the other's height -- harmless for the
   main column since it's already taller than the aside on its own content,
   and it's what makes the sticky card actually stick in Safari. */
.listing-detail-cols { align-items: start; }

.avatar { width: 38px; height: 38px; border-radius: 50%; background: var(--cream-gold); color: var(--deep-green); display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 0.85rem; }

table.data-table { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
table.data-table th { text-align: left; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); padding: 10px 14px; border-bottom: 1px solid var(--border); }
table.data-table td { padding: 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
table.data-table tr:last-child td { border-bottom: none; }
table.data-table tr:hover td { background: var(--surface-alt); }

.kpi-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--space-4); margin-bottom: var(--space-6); }
@media (max-width: 900px) { .kpi-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
.kpi-card { padding: var(--space-5); display: flex; flex-direction: column; min-width: 0; }
.kpi-card .kpi-label { font-size: 0.76rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; min-height: 2.1em; }
.kpi-card .kpi-value { font-size: clamp(1.15rem, 2.4vw, 1.7rem); font-weight: 800; color: var(--deep-green); overflow-wrap: break-word; word-break: break-word; margin-top: auto; }
.kpi-card .kpi-delta { font-size: 0.78rem; font-weight: 700; margin-top: 4px; }
.kpi-delta.up { color: var(--success); }
.kpi-delta.down { color: var(--danger); }

/* Homepage "Market Pulse" cards (Aug 2026 redesign) -- an original design,
   separate from the generic .kpi-card grid above (which stays exactly as-is
   everywhere else it's used: Market Report, CRM dashboards, portals,
   mortgage page). Each of the 4 stats gets its own accent color (icon chip
   + a "ticket stub" bar along the bottom edge) so the row reads as four
   distinct signals rather than four identical boxes -- same data, same
   ids, purely a visual pass per Perry's request. */
.market-pulse-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--space-4); margin-bottom: var(--space-6); transition: opacity .15s ease; }
@media (max-width: 900px) { .market-pulse-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
.market-pulse-card {
  position: relative; overflow: hidden; min-width: 0;
  padding: var(--space-5) var(--space-5) 20px; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft); transition: transform .18s ease, box-shadow .18s ease;
}
.market-pulse-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lift); }
.market-pulse-card::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 4px; background: var(--pulse-accent);
}
.market-pulse-card .pulse-icon {
  width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--pulse-accent-tint); color: var(--pulse-accent); margin-bottom: 14px; flex-shrink: 0;
}
.market-pulse-card .pulse-icon svg { width: 18px; height: 18px; }
.market-pulse-card .pulse-label { font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; margin-bottom: 6px; min-height: 1.8em; }
.market-pulse-card .pulse-value { font-size: clamp(1.15rem, 2.4vw, 1.7rem); font-weight: 800; color: var(--text-primary); overflow-wrap: break-word; word-break: break-word; margin-top: auto; }
.pulse-accent-listings { --pulse-accent: var(--deep-green); --pulse-accent-tint: rgba(26,58,42,0.10); }
.pulse-accent-price     { --pulse-accent: var(--warning); --pulse-accent-tint: rgba(201,150,44,0.14); }
.pulse-accent-sold      { --pulse-accent: var(--danger); --pulse-accent-tint: rgba(179,84,63,0.13); }
.pulse-accent-dom       { --pulse-accent: var(--text-secondary); --pulse-accent-tint: rgba(82,104,92,0.13); }

.tabs { display: flex; gap: var(--space-2); border-bottom: 1px solid var(--border); margin-bottom: var(--space-5); }
.tabs button { background: none; border: none; padding: 12px 4px; margin-right: var(--space-5); font-weight: 700; font-size: 0.88rem; color: var(--text-muted); cursor: pointer; border-bottom: 2px solid transparent; }
.tabs button.active { color: var(--deep-green); border-color: var(--deep-green); }

.ai-widget { border: 1px dashed var(--muted-green-text); background: linear-gradient(135deg, #fff 0%, var(--light-green-bg) 100%); }
.ai-badge { display: inline-flex; align-items: center; gap: 6px; background: var(--deep-green); color: var(--cream-gold); font-size: 0.68rem; font-weight: 800; padding: 4px 10px; border-radius: var(--radius-pill); text-transform: uppercase; letter-spacing: 0.06em; }

.lead-score { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border-radius: 50%; font-weight: 800; font-size: 0.85rem; }
.lead-score.hot { background: #f6e6e2; color: var(--danger); }
.lead-score.warm { background: #fbeee0; color: var(--warning); }
.lead-score.cold { background: var(--light-green-bg); color: var(--success); }

.timeline { border-left: 2px solid var(--border); padding-left: var(--space-4); margin-left: 6px; }
.timeline-item { position: relative; padding-bottom: var(--space-5); }
.timeline-item::before { content: ''; position: absolute; left: -25px; top: 4px; width: 10px; height: 10px; border-radius: 50%; background: var(--dark-green); }
.timeline-item .t-time { font-size: 0.72rem; color: var(--text-muted); }
/* Sept 2026: the sitewide `a { color: inherit; text-decoration: none; }`
   reset (line 76) means the new clickable links in activity/notes text
   (linkifyText() in app.js) and Website Activity's "Viewed listing"/"Saved
   listing" rows (visitorActivityRowHtml()) rendered completely invisible --
   real <a> tags, but indistinguishable from plain text, which is why Perry
   couldn't "see" them despite the underlying fix working correctly. */
.timeline-item a { color: var(--deep-green); text-decoration: underline; font-weight: 600; }

.empty-state { text-align: center; padding: var(--space-8) var(--space-5); color: var(--text-muted); }
.chatbox { border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.chat-msgs { max-height: 320px; overflow-y: auto; padding: var(--space-4); background: var(--surface-alt); display: flex; flex-direction: column; gap: var(--space-3); }
.chat-msg { max-width: 80%; padding: 10px 14px; border-radius: var(--radius-md); font-size: 0.86rem; }
.chat-msg.bot { background: #fff; border: 1px solid var(--border); align-self: flex-start; }
.chat-msg.user { background: var(--deep-green); color: var(--cream-gold); align-self: flex-end; }
.chat-msg.thinking { opacity: 0.6; font-style: italic; }
.cal-cell { width: 100%; border: none; background: transparent; font: inherit; color: inherit; padding: 4px 2px; }
.cal-cell:focus { outline: 1px solid var(--deep-green); border-radius: 4px; }
.collapse-toggle { width: 28px; height: 28px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface-alt); color: var(--text-primary); font-size: 1rem; line-height: 1; cursor: pointer; flex-shrink: 0; }
.collapse-toggle:hover { background: var(--soft-border); }
.chat-input-row { display: flex; gap: var(--space-2); padding: var(--space-3); background: var(--surface); }
/* Same 16px-minimum reasoning as .field input above -- this had never had
   an explicit font-size at all, so it was sitting at the browser's default
   form-control size (well under 16px), which is exactly what triggers
   iOS's zoom-on-focus. This is the "Message Property Manager" chat input
   used across the landlord/tenant/agent portals -- the zoom-then-can't-
   scroll-back symptom on the landlord dashboard traced back to this input,
   not the header layout issue fixed alongside it. */
.chat-input-row input { flex: 1; border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 10px 16px; font-size: 1rem; }

.skip-link { position: absolute; left: -9999px; top: 0; background: var(--deep-green); color: #fff; padding: 10px 16px; z-index: 200; }
.skip-link:focus { left: 10px; top: 10px; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--soft-border); border-radius: 10px; }

/* --- Kip: site-wide public AI assistant widget (see initKipWidget() in
   app.js) -- floating bubble + expandable panel, public marketing pages
   only (never CRM/portal). Bottom offset bumps up on mobile/native-app to
   clear the fixed .app-tabbar (74px + safe-area) instead of sitting on
   top of it. z-index 90: above the app-tabbar (60) and map layers (1),
   below modals (100) so a modal always wins if both are somehow open. */
#kip-widget { position: fixed; right: var(--space-4); bottom: var(--space-4); z-index: 90; }
@media (max-width: 860px) {
  #kip-widget { bottom: calc(74px + env(safe-area-inset-bottom) + 12px); right: var(--space-3); }
}
html.native-app-consumer #kip-widget { bottom: calc(74px + env(safe-area-inset-bottom) + 12px); }

/* Backdrop shown behind the panel on mobile/native-app only (see centering
   rule below) -- dims the page and gives a big, easy tap target to close,
   instead of the visitor having to land a precise tap on a small ✕ that
   may be tucked in a corner of the screen. */
#kip-backdrop { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 95; }
@media (max-width: 860px) {
  #kip-widget.open #kip-backdrop { display: block; }
}
html.native-app-consumer #kip-widget.open #kip-backdrop { display: block; }

#kip-bubble {
  display: flex; align-items: center; gap: 8px; border: none; cursor: pointer;
  background: var(--deep-green); color: var(--cream-gold); border-radius: var(--radius-pill);
  padding: 12px 18px 12px 14px; box-shadow: var(--shadow-lift); font-weight: 700; font-size: 0.9rem;
  transition: transform .15s ease;
}
#kip-bubble:hover { transform: translateY(-2px); }
#kip-bubble .kip-emoji { font-size: 1.15rem; line-height: 1; }
#kip-widget.open #kip-bubble { display: none; }

#kip-panel {
  display: none; flex-direction: column; position: absolute; bottom: 0; right: 0;
  width: min(360px, calc(100vw - 32px)); height: min(520px, calc(100vh - 120px));
  background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow-lift);
  overflow: hidden; border: 1px solid var(--border); z-index: 96;
}
#kip-widget.open #kip-panel { display: flex; }

/* Anchoring the panel to the bottom-right corner of a small bubble works
   on a roomy desktop viewport, but on a phone -- and especially inside the
   native app's fixed-viewport WKWebView -- it was opening partly off the
   edge of the screen with no visible way to find the close button. First
   fix tried true centering (top/left 50% + transform) -- but iOS shifts
   the visual viewport to keep a focused input clear of the keyboard, and
   a transform-centered element doesn't track that shift, so it visibly
   "jumped" and needed a manual scroll to line back up. Pinning to all
   four edges instead (no transform, no midpoint math) is immune to that:
   each edge just stays put relative to the layout viewport regardless of
   what the keyboard does. */
@media (max-width: 860px) {
  #kip-panel {
    position: fixed;
    top: max(16px, env(safe-area-inset-top)); left: 16px;
    right: 16px; bottom: max(16px, env(safe-area-inset-bottom));
    width: auto; height: auto;
  }
}
html.native-app-consumer #kip-panel {
  position: fixed;
  top: max(16px, env(safe-area-inset-top)); left: 16px;
  right: 16px; bottom: max(16px, env(safe-area-inset-bottom));
  width: auto; height: auto;
}

.kip-panel-header {
  display: flex; align-items: center; gap: 10px; padding: 14px 16px;
  background: var(--deep-green); color: #fff; flex-shrink: 0;
}
.kip-panel-header .kip-emoji { font-size: 1.3rem; }
.kip-panel-header strong { display: block; font-size: 0.92rem; }
.kip-panel-header span { display: block; font-size: 0.7rem; opacity: 0.75; }
.kip-panel-header button { margin-left: auto; background: none; border: none; color: #fff; opacity: 0.8; cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 4px; }
.kip-panel-header button:hover { opacity: 1; }

#kip-messages { flex: 1; overflow-y: auto; padding: var(--space-4); background: var(--surface-alt); display: flex; flex-direction: column; gap: var(--space-3); }
#kip-messages .chat-msg { max-width: 88%; }

.kip-listing-card {
  display: flex; gap: 10px; align-items: center; background: #fff; border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: 8px; text-decoration: none; color: inherit; max-width: 88%;
}
.kip-listing-card img { width: 56px; height: 56px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
.kip-listing-card .kip-listing-price { font-weight: 800; color: var(--deep-green); font-size: 0.86rem; }
.kip-listing-card .kip-listing-addr { font-size: 0.76rem; color: var(--text-muted); }

.kip-lead-form { background: #fff; border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px; max-width: 92%; align-self: flex-start; }
.kip-lead-form input { width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 10px; margin-bottom: 8px; font-size: 1rem; }
.kip-lead-form button { width: 100%; }

#kip-input-row { display: flex; gap: 8px; padding: 10px; background: var(--surface); flex-shrink: 0; border-top: 1px solid var(--border); }
#kip-input-row button { flex-shrink: 0; }

/* Soft ambient glow on Kip's input, in brand colours (gold/deep-green)
   rather than a literal rainbow -- reads as "Kip is listening" the way
   Siri's glow does, without looking like a different product's UI. Idle
   state pulses gently; sending/thinking speeds up and brightens so there's
   a visible "processing" cue while waiting on the API round-trip. */
@keyframes kip-glow-idle {
  0%, 100% { box-shadow: 0 0 0 1.5px rgba(232,213,176,0.55), 0 0 10px 1px rgba(232,213,176,0.35); }
  50% { box-shadow: 0 0 0 1.5px rgba(26,58,42,0.35), 0 0 14px 3px rgba(232,213,176,0.55); }
}
@keyframes kip-glow-active {
  0%, 100% { box-shadow: 0 0 0 2px rgba(232,213,176,0.75), 0 0 16px 3px rgba(232,213,176,0.6); }
  50% { box-shadow: 0 0 0 2px rgba(26,58,42,0.55), 0 0 20px 6px rgba(232,213,176,0.85); }
}
#kip-input-row input {
  flex: 1; border: 1px solid transparent; border-radius: var(--radius-pill); padding: 10px 14px; font-size: 0.86rem;
  background: var(--surface); animation: kip-glow-idle 3.2s ease-in-out infinite;
}
#kip-input-row input:focus { outline: none; animation: kip-glow-active 1.4s ease-in-out infinite; }
#kip-widget.kip-sending #kip-input-row input { animation: kip-glow-active 0.9s ease-in-out infinite; }

/* iOS silently zooms the whole page in on focus for any text input under
   16px -- that's what was "distorting"/zooming the screen the moment you
   tapped into Kip's input, and the zoomed-in state is what let the page
   pan/scroll again despite the scroll lock (pinch-zoom panning happens at
   the browser level, entirely outside the reach of any CSS/JS lock). This
   is the standard fix: keep the input's real font at 16px so iOS never
   triggers the auto-zoom in the first place. */
@media (max-width: 860px) {
  #kip-input-row input { font-size: 16px; }
}
html.native-app-consumer #kip-input-row input { font-size: 16px; }
