← back to Commercialrealestate

public/recities.html

157 lines

<!doctype html>
<html lang="en">
<head>
<script>(function(){try{var t=localStorage.getItem('crcp-theme');document.documentElement.setAttribute('data-theme',t==='light'?'light':'dark');}catch(e){document.documentElement.setAttribute('data-theme','dark');}})();</script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CRCP · Cities</title>
<style>
  :root{
    --bg:#0f1419; --panel:#1a2029; --panel2:#232b36; --edge:#2f3946;
    --ink:#e8edf2; --mut:#93a1b0; --accent:#4ea1ff; --accent2:#2b6fd6;
  }
  /* LIGHT mode surface flips — keep the bespoke blue ACCENT, only re-point generic surfaces
     to light so day mode has no dark islands (Cycle-6 lesson). Accents unchanged. */
  html[data-theme="light"]{
    --bg:#f4f7fb; --panel:#ffffff; --panel2:#eef3f9; --edge:#d5dee8;
    --ink:#131a22; --mut:#586472;
  }
  *{box-sizing:border-box}
  body{margin:0;background:var(--bg);color:var(--ink);
       font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;min-height:100vh}
  .wrap{max-width:1080px;margin:0 auto;padding:40px 20px 80px}
  header.top{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;margin-bottom:6px}
  header.top h1{font-size:26px;font-weight:700;margin:0;letter-spacing:.2px}
  header.top .tag{color:var(--mut);font-size:14px}
  .sub{color:var(--mut);margin:0 0 26px}
  .panel{background:var(--panel);border:1px solid var(--edge);border-radius:14px;padding:22px;box-shadow:0 8px 30px rgba(0,0,0,.25)}
  .pickrow{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
  label.lbl{color:var(--mut);font-size:13px;text-transform:uppercase;letter-spacing:.6px;display:block;margin-bottom:8px}
  select,button,input[type=search]{font:inherit}
  select#city{flex:1 1 320px;min-width:240px;background:var(--panel2);color:var(--ink);border:1px solid var(--edge);
    border-radius:10px;padding:13px 14px;appearance:none;
    background-image:linear-gradient(45deg,transparent 50%,var(--mut) 50%),linear-gradient(135deg,var(--mut) 50%,transparent 50%);
    background-position:calc(100% - 20px) 20px,calc(100% - 14px) 20px;background-size:6px 6px,6px 6px;background-repeat:no-repeat}
  button.go{background:var(--accent2);color:#fff;border:1px solid var(--accent);border-radius:10px;padding:13px 22px;cursor:pointer;font-weight:600}
  button.go:hover{background:var(--accent)}
  .row2{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-top:16px}
  .count{color:var(--mut);font-size:13px}
  .toggle{color:var(--mut);font-size:13px;display:flex;gap:7px;align-items:center;cursor:pointer;user-select:none}
  input#filter{width:100%;background:var(--panel2);color:var(--ink);border:1px solid var(--edge);border-radius:10px;padding:12px 14px;margin-top:22px}
  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-top:14px}
  a.city{display:flex;justify-content:space-between;align-items:center;gap:10px;text-decoration:none;color:var(--ink);
    background:var(--panel2);border:1px solid var(--edge);border-radius:10px;padding:12px 14px;transition:.12s}
  a.city:hover{border-color:var(--accent);background:var(--panel);transform:translateY(-1px)}
  a.city.empty{opacity:.5}
  a.city .nm{font-weight:600}
  a.city .host{display:block;color:var(--mut);font-size:12px;margin-top:2px;font-weight:400}
  a.city .n{flex:0 0 auto;font-size:12px;color:var(--mut);background:var(--bg);border:1px solid var(--edge);border-radius:20px;padding:2px 9px}
  a.city .n.has{color:#bfe0ff;border-color:#2f5da3}
  .empty-note{color:var(--mut);padding:20px 4px}
  footer{color:var(--mut);font-size:12px;margin-top:34px}
</style>
  <link rel="stylesheet" href="/crcp-theme.css"><!-- CRCP day/night tokens: load LAST so html[data-theme] wins -->
  <link rel="stylesheet" href="/nav-agent/nav-agent.css"><!-- nav-agent -->
</head>
<body>
<div class="wrap">
  <header class="top">
    <h1>CRCP · Cities</h1>
    <span class="tag">Commercial Real Estate — pick a market</span>
  </header>
  <p class="sub">Markets with active listings. Choose one to open its scoped dashboard.</p>

  <div class="panel">
    <label class="lbl" for="city">Jump to a city</label>
    <div class="pickrow">
      <select id="city" aria-label="Select a city"></select>
      <button class="go" id="go">Open →</button>
    </div>
    <input id="filter" type="search" placeholder="Filter cities…" autocomplete="off">
    <div class="row2">
      <span class="count" id="count"></span>
      <label class="toggle"><input type="checkbox" id="showEmpty"> Show empty markets</label>
    </div>
    <div class="grid" id="grid"></div>
  </div>

  <footer id="foot"></footer>
</div>

<script src="/crcp-cities.js"></script>
<script>
var CITIES = window.CRCP_CITIES.CITIES.slice();       // from the shared cities module
var sameCity = window.CRCP_CITIES.sameCity;
var counts = {};                                      // slug -> listing count (from ranked.json)
var loaded = false;
var urlOf = function (c) { return "https://" + c.host + "/"; };

var sel = document.getElementById('city'), grid = document.getElementById('grid'),
    count = document.getElementById('count'), filter = document.getElementById('filter'),
    showEmpty = document.getElementById('showEmpty'), foot = document.getElementById('foot');

// Count listings per city from the same snapshot the app uses. Best-effort: if it fails, show all.
fetch('/data/ranked.json').then(function (r) { return r.json(); }).then(function (d) {
  var arr = (d && d.ranked) || [];
  CITIES.forEach(function (c) {
    counts[c.slug] = arr.filter(function (p) { return sameCity(p.city, c); }).length;
  });
  loaded = true; render();
}).catch(function () { loaded = true; render(); });     // no counts -> treat all as shown

function visible() {
  var withData = CITIES.filter(function (c) { return (counts[c.slug] || 0) > 0; });
  // Default: hide empty markets (Steve's choice). Toggle reveals all 50. Before counts load, show all.
  var base = (!loaded || showEmpty.checked || withData.length === 0) ? CITIES : withData;
  return base.slice().sort(function (a, b) {
    var na = counts[a.slug] || 0, nb = counts[b.slug] || 0;
    if (nb !== na) return nb - na;                       // most inventory first
    return a.label.localeCompare(b.label);
  });
}

function fillSelect() {
  var list = visible(); sel.innerHTML = '';
  list.forEach(function (c) {
    var o = document.createElement('option'); o.value = c.slug;
    o.textContent = c.label + (loaded ? ' (' + (counts[c.slug] || 0) + ')' : '');
    sel.appendChild(o);
  });
}

function render() {
  fillSelect();
  var q = (filter.value || '').trim().toLowerCase();
  var list = visible().filter(function (c) { return !q || c.label.toLowerCase().indexOf(q) >= 0 || c.slug.indexOf(q) >= 0; });
  grid.innerHTML = '';
  if (!list.length) { grid.innerHTML = '<div class="empty-note">No cities match “' + q + '”.</div>'; }
  list.forEach(function (c) {
    var n = counts[c.slug] || 0;
    var a = document.createElement('a');
    a.className = 'city' + (loaded && n === 0 ? ' empty' : '');
    a.href = urlOf(c);
    a.innerHTML = '<span><span class="nm">' + c.label + '</span><span class="host">' + c.host + '</span></span>' +
                  '<span class="n' + (n ? ' has' : '') + '">' + (loaded ? n : '·') + '</span>';
    grid.appendChild(a);
  });
  var shownCities = visible();
  var withData = CITIES.filter(function (c) { return (counts[c.slug] || 0) > 0; }).length;
  count.textContent = loaded
    ? (shownCities.length + ' markets shown · ' + withData + ' with listings · ' + CITIES.length + ' total')
    : (CITIES.length + ' city markets');
  foot.textContent = 'CRCP · recities.crcp.agentabrams.com';
}

function open() { var c = CITIES.find(function (x) { return x.slug === sel.value; }); if (c) window.location.href = urlOf(c); }
document.getElementById('go').addEventListener('click', open);
sel.addEventListener('keydown', function (e) { if (e.key === 'Enter') open(); });
filter.addEventListener('input', render);
showEmpty.addEventListener('change', render);
render();
</script>
  <script src="/crcp-theme.js" defer></script><!-- CRCP day/night toggle (auto-injects into header) -->
  <script src="/corner-nav.js" defer></script>
  <script src="/nav-agent/nav-agent.js" defer></script><!-- nav-agent -->
</body>
</html>