← back to Ventura Corridor

public/marketplace.html

718 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script>(function(){try{var t=localStorage.getItem('vc-theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(e){document.documentElement.dataset.theme='dark';}})();</script>
<link rel="stylesheet" href="/theme.css">
<script src="/theme.js" defer></script>
<title>Marketplace · Ventura Boulevard</title>
<style>
  :root {
    --noir: #0a0a0c; --noir-rise: #131316; --rule: #2a2724;
    --ink: #f4f1ea; --ink-soft: #d8d2c5; --ink-mute: #8b857a;
    --metal: #b89968; --metal-glow: #d4b683; --metal-deep: #8a7044;
    --serif: "Cormorant Garamond", Georgia, serif;
    --sans: -apple-system, "SF Pro Text", "Inter", system-ui, sans-serif;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Menlo, Consolas, monospace;
    --cols: 4;
  }
  *, *::before, *::after { box-sizing: border-box; }
  html, body { margin: 0; background: var(--noir); color: var(--ink); font-family: var(--sans); font-weight: 300; min-height: 100vh; }

  header {
    position: sticky; top: 0; z-index: 1000;
    padding: 18px 24px;
    background: linear-gradient(180deg, rgba(10,10,12,.96), rgba(10,10,12,.86));
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--rule);
    display: flex; align-items: center; justify-content: space-between; gap: 24px;
  }
  .logo {
    font-family: var(--serif); font-weight: 300; font-size: 22px; letter-spacing: -.005em;
    color: var(--ink); text-decoration: none; line-height: 1;
  }
  .logo em { font-style: italic; color: var(--metal); }
  .logo .pill {
    display: inline-block; margin-left: 10px; padding: 3px 10px;
    font-family: var(--sans); font-size: 9px; letter-spacing: .26em; text-transform: uppercase;
    color: var(--metal); border: 1px solid var(--metal); vertical-align: middle;
  }
  .hamburger {
    display: inline-flex; align-items: center; gap: 8px;
    background: transparent; border: 1px solid var(--rule);
    padding: 8px 14px; color: var(--ink-mute); cursor: pointer;
    font-size: 10px; letter-spacing: .26em; text-transform: uppercase;
    transition: color 160ms ease, border-color 160ms ease;
  }
  .hamburger:hover { color: var(--metal); border-color: var(--metal); }
  .hamburger .bars { display: inline-flex; flex-direction: column; gap: 3px; }
  .hamburger .bars i { display: block; width: 14px; height: 1px; background: currentColor; }

  /* nav drawer (right-side) */
  #drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 320px; max-width: 88vw;
    background: var(--noir-rise); border-left: 1px solid var(--rule); z-index: 1500;
    transform: translateX(100%); transition: transform 320ms cubic-bezier(.16,.84,.3,1);
    overflow-y: auto; padding: 22px 22px 30px;
  }
  #drawer.show { transform: none; }
  #drawer .dx { background: transparent; border: 1px solid var(--rule); color: var(--ink-mute);
    padding: 6px 10px; font-size: 10px; letter-spacing: .18em; text-transform: uppercase; cursor: pointer;
    margin-bottom: 18px;
  }
  #drawer a { display: block; padding: 10px 0; border-bottom: 1px solid var(--rule);
    color: var(--ink-soft); font-size: 12px; letter-spacing: .04em; text-decoration: none;
    font-family: var(--serif); font-style: italic;
  }
  #drawer a:hover { color: var(--metal); }
  #drawer-mask { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 1400;
    opacity: 0; pointer-events: none; transition: opacity 200ms ease;
  }
  #drawer-mask.show { opacity: 1; pointer-events: auto; }

  .lede {
    max-width: 1380px; margin: 36px auto 8px; padding: 0 24px;
  }
  .kicker {
    font-size: 10px; letter-spacing: .3em; text-transform: uppercase;
    color: var(--metal); font-weight: 500; margin-bottom: 12px;
  }
  h1 {
    font-family: var(--serif); font-weight: 300; font-style: italic;
    font-size: clamp(40px, 5.4vw, 72px); line-height: 1.04; margin: 0 0 12px;
    letter-spacing: -.012em;
  }
  h1 em { font-style: normal; color: var(--metal); }
  .deck {
    font-family: var(--serif); font-style: italic; font-size: 18px; line-height: 1.5;
    color: var(--ink-soft); max-width: 720px;
  }
  .deck a { color: var(--metal-glow); border-bottom: 1px dotted var(--metal-glow); text-decoration: none; }
  .deck a:hover { color: var(--ink); border-bottom-color: var(--ink); }

  /* Controls bar */
  .controls {
    max-width: 1380px; margin: 30px auto 0; padding: 0 24px;
    display: grid; grid-template-columns: 1fr auto auto auto; gap: 12px; align-items: end;
  }
  @media (max-width: 880px) {
    .controls { grid-template-columns: 1fr; }
  }
  .ctl-label {
    display: block; font-size: 9px; letter-spacing: .26em; text-transform: uppercase;
    color: var(--ink-mute); margin: 0 0 6px;
  }
  .ctl-input, .ctl-select {
    width: 100%; padding: 12px 16px; background: var(--noir-rise);
    border: 1px solid var(--rule); color: var(--ink); font-family: var(--sans);
    font-size: 13px; font-weight: 300; outline: none; transition: border-color 200ms ease;
  }
  .ctl-input::placeholder { color: var(--ink-mute); font-style: italic; font-family: var(--serif); }
  .ctl-input:focus, .ctl-select:focus { border-color: var(--metal); box-shadow: 0 0 0 3px rgba(184,153,104,0.12); }
  .ctl-density { display: flex; align-items: center; gap: 10px; padding: 12px 14px; background: var(--noir-rise); border: 1px solid var(--rule); }
  .ctl-density input[type=range] { width: 110px; accent-color: var(--metal); }
  .ctl-density .v { font-family: var(--mono); font-size: 10px; color: var(--metal); letter-spacing: .12em; min-width: 26px; text-align: right; }

  .cat-bar {
    max-width: 1380px; margin: 18px auto 0; padding: 0 24px;
    display: flex; gap: 8px; flex-wrap: wrap;
  }
  .cat-chip {
    border: 1px solid var(--rule); background: transparent;
    color: var(--ink-soft); padding: 8px 14px; font-size: 10px; letter-spacing: .2em;
    text-transform: uppercase; cursor: pointer; font-family: var(--sans);
    transition: color 160ms ease, border-color 160ms ease, background 160ms ease;
  }
  .cat-chip:hover { color: var(--metal); border-color: var(--metal); }
  .cat-chip.active { color: var(--noir); background: var(--metal); border-color: var(--metal); }
  .cat-chip .n { font-family: var(--serif); font-style: italic; opacity: .8; margin-left: 6px; font-size: 11px; }

  /* Result count + post-listing CTA */
  .meta-row {
    max-width: 1380px; margin: 20px auto 0; padding: 0 24px;
    display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;
  }
  .count { font-size: 10px; letter-spacing: .3em; text-transform: uppercase; color: var(--ink-mute); }
  .count b { color: var(--metal); font-family: var(--serif); font-style: italic; font-weight: 400; font-size: 15px; letter-spacing: 0; margin-right: 4px; font-variant-numeric: tabular-nums; }
  .post-cta {
    background: transparent; border: 1px solid var(--metal); color: var(--metal-glow);
    padding: 10px 18px; font-size: 10px; letter-spacing: .26em; text-transform: uppercase;
    cursor: pointer; font-family: var(--sans); transition: all 200ms ease;
  }
  .post-cta:hover { background: var(--metal); color: var(--noir); }

  /* Grid */
  .grid {
    max-width: 1380px; margin: 18px auto 60px; padding: 0 24px;
    display: grid; gap: 18px;
    grid-template-columns: repeat(var(--cols), 1fr);
  }
  @media (max-width: 1100px) { :root { --cols: 3; } }
  @media (max-width: 760px)  { :root { --cols: 2; } }
  @media (max-width: 460px)  { :root { --cols: 1; } }

  .card {
    background: var(--noir-rise); border: 1px solid var(--rule);
    display: flex; flex-direction: column; cursor: pointer;
    transition: border-color 200ms ease, transform 200ms ease;
    text-decoration: none; color: inherit;
  }
  .card:hover { border-color: var(--metal); transform: translateY(-2px); }
  .card .img {
    aspect-ratio: 1 / 1; background: linear-gradient(135deg, #1a1815 0%, #2a2520 100%);
    position: relative; overflow: hidden;
  }
  .card .img img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .card .img .placeholder {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    color: var(--metal-deep); font-family: var(--serif); font-style: italic; font-size: 15px;
    padding: 18px; text-align: center; letter-spacing: .02em;
  }
  .card .img .cat-tag {
    position: absolute; top: 10px; left: 10px;
    font-size: 8px; letter-spacing: .26em; text-transform: uppercase;
    color: var(--ink); background: rgba(10,10,12,.78); padding: 4px 8px;
    border: 1px solid var(--metal); backdrop-filter: blur(4px);
  }
  .card .img .source {
    position: absolute; top: 10px; right: 10px;
    font-size: 8px; letter-spacing: .2em; text-transform: uppercase;
    color: var(--metal-glow); background: rgba(10,10,12,.78); padding: 4px 8px;
    border: 1px solid var(--rule); backdrop-filter: blur(4px);
  }
  .card .body { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
  .card .title {
    font-family: var(--serif); font-weight: 400; font-size: 16px; line-height: 1.22;
    color: var(--ink); letter-spacing: -.003em;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  .card .price {
    font-family: var(--serif); font-style: italic; font-size: 18px;
    color: var(--metal-glow); font-variant-numeric: tabular-nums; margin-top: 2px;
  }
  .card .price.free { color: var(--metal); }
  .card .loc {
    font-size: 10px; letter-spacing: .12em; color: var(--ink-mute);
    text-transform: uppercase; margin-top: auto; padding-top: 6px;
    display: flex; justify-content: space-between; align-items: baseline;
  }
  .card .loc .pin { color: var(--metal); font-family: var(--serif); font-style: italic; text-transform: none; font-size: 12px; letter-spacing: 0; }
  .card .loc .date { font-family: var(--mono); font-size: 9px; }

  .empty {
    max-width: 1380px; margin: 80px auto; padding: 0 24px; text-align: center;
    font-family: var(--serif); font-style: italic; font-size: 22px; color: var(--ink-mute);
  }

  /* Modal — detail panel */
  #modal-mask {
    position: fixed; inset: 0; background: rgba(0,0,0,.72); z-index: 2000;
    display: none; align-items: center; justify-content: center; padding: 24px;
    backdrop-filter: blur(6px);
  }
  #modal-mask.show { display: flex; }
  #modal {
    background: var(--noir-rise); border: 1px solid var(--rule);
    max-width: 640px; width: 100%; max-height: 88vh; overflow-y: auto;
    position: relative;
  }
  #modal .mx {
    position: absolute; top: 14px; right: 14px;
    background: transparent; border: 1px solid var(--rule); color: var(--ink-mute);
    padding: 6px 12px; font-size: 10px; letter-spacing: .18em; text-transform: uppercase; cursor: pointer;
  }
  #modal .mx:hover { color: var(--metal); border-color: var(--metal); }
  #modal .img { aspect-ratio: 16 / 10; background: #1a1815; border-bottom: 1px solid var(--rule); position: relative; overflow: hidden; }
  #modal .img img { width: 100%; height: 100%; object-fit: cover; }
  #modal .body { padding: 26px 28px 30px; }
  #modal .mk { font-size: 9px; letter-spacing: .28em; text-transform: uppercase; color: var(--metal); margin: 0 0 10px; font-weight: 500; }
  #modal h2 { font-family: var(--serif); font-weight: 400; font-size: 28px; line-height: 1.18; color: var(--ink); margin: 0 0 8px; letter-spacing: -.005em; }
  #modal .mp { font-family: var(--serif); font-style: italic; font-size: 28px; color: var(--metal-glow); margin: 0 0 18px; }
  #modal .md { font-size: 14px; line-height: 1.6; color: var(--ink-soft); margin: 0 0 18px; white-space: pre-wrap; }
  #modal .mrow { display: flex; justify-content: space-between; padding: 10px 0; border-top: 1px solid var(--rule); font-size: 12px; }
  #modal .mrow .k { color: var(--ink-mute); letter-spacing: .04em; }
  #modal .mrow .v { color: var(--ink); font-family: var(--serif); font-style: italic; font-size: 13px; }
  #modal .cta {
    display: block; margin-top: 22px; padding: 14px 18px;
    border: 1px solid var(--metal); color: var(--metal-glow);
    font-size: 11px; letter-spacing: .22em; text-transform: uppercase; font-weight: 500;
    text-align: center; text-decoration: none;
    transition: background 200ms ease, color 200ms ease;
  }
  #modal .cta:hover { background: var(--metal); color: var(--noir); }

  /* Post-listing form modal */
  #post-mask {
    position: fixed; inset: 0; background: rgba(0,0,0,.78); z-index: 2100;
    display: none; align-items: center; justify-content: center; padding: 24px;
    backdrop-filter: blur(6px);
  }
  #post-mask.show { display: flex; }
  #post {
    background: var(--noir-rise); border: 1px solid var(--rule);
    max-width: 540px; width: 100%; max-height: 88vh; overflow-y: auto; padding: 28px;
    position: relative;
  }
  #post h2 { font-family: var(--serif); font-style: italic; font-size: 28px; margin: 0 0 6px; color: var(--ink); font-weight: 400; }
  #post .sub { font-family: var(--serif); font-style: italic; color: var(--ink-mute); font-size: 13px; margin: 0 0 22px; }
  #post .fld { margin-bottom: 14px; }
  #post .fld label { display: block; font-size: 9px; letter-spacing: .26em; text-transform: uppercase; color: var(--ink-mute); margin-bottom: 6px; }
  #post .fld input, #post .fld select, #post .fld textarea {
    width: 100%; padding: 10px 14px; background: var(--noir); border: 1px solid var(--rule);
    color: var(--ink); font-family: var(--sans); font-size: 13px; outline: none;
  }
  #post .fld input:focus, #post .fld select:focus, #post .fld textarea:focus { border-color: var(--metal); }
  #post .fld textarea { resize: vertical; min-height: 90px; }
  #post .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
  #post .submit {
    width: 100%; padding: 14px; background: var(--metal); color: var(--noir);
    border: none; font-size: 11px; letter-spacing: .26em; text-transform: uppercase;
    font-weight: 500; cursor: pointer; margin-top: 10px;
  }
  #post .submit:hover { background: var(--metal-glow); }
  #post .submit:disabled { opacity: .5; cursor: not-allowed; }
  #post .pclose {
    position: absolute; top: 14px; right: 14px;
    background: transparent; border: 1px solid var(--rule); color: var(--ink-mute);
    padding: 6px 12px; font-size: 10px; letter-spacing: .18em; text-transform: uppercase; cursor: pointer;
  }
  #post .msg { padding: 12px; font-size: 12px; margin-top: 12px; }
  #post .msg.ok  { background: rgba(184,153,104,0.12); border: 1px solid var(--metal); color: var(--metal-glow); }
  #post .msg.err { background: rgba(196,123,123,0.12); border: 1px solid #c47b7b; color: #c47b7b; }

  /* FB Marketplace deeplink footer */
  .fb-deeplink {
    max-width: 1380px; margin: 40px auto; padding: 28px 24px;
    border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
    text-align: center;
  }
  .fb-deeplink .fk { font-size: 9px; letter-spacing: .3em; text-transform: uppercase; color: var(--metal); margin-bottom: 14px; font-weight: 500; }
  .fb-deeplink .fp { font-family: var(--serif); font-style: italic; font-size: 17px; color: var(--ink-soft); margin: 0 0 18px; line-height: 1.5; }
  .fb-deeplink a {
    display: inline-block; margin: 4px; padding: 10px 18px; border: 1px solid var(--rule);
    color: var(--metal); font-size: 10px; letter-spacing: .22em; text-transform: uppercase;
    text-decoration: none; font-family: var(--sans); transition: all 200ms ease;
  }
  .fb-deeplink a:hover { background: var(--metal); color: var(--noir); border-color: var(--metal); }
</style>
</head>
<body>

<header>
  <a class="logo" href="/">Ventura <em>Boulevard</em><span class="pill">Marketplace</span></a>
  <button class="hamburger" aria-label="Menu" onclick="document.getElementById('drawer').classList.add('show');document.getElementById('drawer-mask').classList.add('show')">
    <span class="bars"><i></i><i></i><i></i></span>
    <span>Menu</span>
  </button>
</header>

<div id="drawer-mask" onclick="document.getElementById('drawer').classList.remove('show');this.classList.remove('show')"></div>
<aside id="drawer">
  <button class="dx" onclick="document.getElementById('drawer').classList.remove('show');document.getElementById('drawer-mask').classList.remove('show')">close ×</button>
  <a href="/">Corridor map</a>
  <a href="/marketplace.html" style="color:var(--metal)">Marketplace</a>
  <a href="/today.html">Today</a>
  <a href="/issue">The issue</a>
  <a href="/search.html">Search</a>
  <a href="/news.html">News</a>
  <a href="/atlas.html">Atlas</a>
  <a href="/scoreboard.html">Scoreboard</a>
  <a href="/3d.html">3D corridor</a>
  <a href="/wall.html">The wall</a>
  <a href="/timeline.html">Timeline</a>
  <a href="/pantone.html">Pantone</a>
  <a href="/feedback.html">Feedback</a>
  <a href="/about.html">About</a>
</aside>

<section class="lede">
  <p class="kicker">Local · neighborhood-to-neighborhood</p>
  <h1>The corridor's <em>marketplace</em>.</h1>
  <p class="deck">Furniture, electronics, vehicles, rentals, services, jobs — listed by neighbors along Ventura Boulevard, from Sherman Oaks to Woodland Hills. A local board for the corridor. Looking for a wider net? <a href="https://www.facebook.com/marketplace/?ref=app_tab" target="_blank" rel="noopener noreferrer">Facebook Marketplace ↗</a> covers all of LA.</p>
</section>

<div class="controls">
  <div>
    <label class="ctl-label" for="q">Search</label>
    <input class="ctl-input" id="q" type="search" placeholder="Couch, Tesla, wallpaper installer…" autocomplete="off">
  </div>
  <div>
    <label class="ctl-label" for="sort">Sort</label>
    <select class="ctl-select" id="sort">
      <option value="newest">Newest</option>
      <option value="price_asc">Price ↑</option>
      <option value="price_desc">Price ↓</option>
      <option value="title">Title A→Z</option>
    </select>
  </div>
  <div>
    <label class="ctl-label" for="maxp">Max price</label>
    <select class="ctl-select" id="maxp">
      <option value="0">Any</option>
      <option value="10000">Under $100</option>
      <option value="50000">Under $500</option>
      <option value="100000">Under $1,000</option>
      <option value="500000">Under $5,000</option>
      <option value="2500000">Under $25,000</option>
    </select>
  </div>
  <div>
    <label class="ctl-label" for="density">Density</label>
    <div class="ctl-density">
      <input id="density" type="range" min="2" max="6" step="1" value="4">
      <span class="v" id="density-v">4</span>
    </div>
  </div>
</div>

<div class="cat-bar" id="cat-bar"></div>

<div class="meta-row">
  <div class="count"><b id="count">—</b> listings on the corridor</div>
  <button class="post-cta" onclick="openPost()">+ Post a listing</button>
</div>

<div class="grid" id="grid"></div>
<div class="empty" id="empty" style="display:none">Nothing matches that filter yet.</div>

<section class="fb-deeplink">
  <div class="fk">Wider net · external links</div>
  <p class="fp">The corridor board above is local-only. For all of LA, Facebook Marketplace deeplinks below — opens on facebook.com.</p>
  <a href="https://www.facebook.com/marketplace/?ref=app_tab" target="_blank" rel="noopener noreferrer">All categories</a>
  <a href="https://www.facebook.com/marketplace/category/furniture" target="_blank" rel="noopener noreferrer">Furniture</a>
  <a href="https://www.facebook.com/marketplace/category/electronics" target="_blank" rel="noopener noreferrer">Electronics</a>
  <a href="https://www.facebook.com/marketplace/category/vehicles" target="_blank" rel="noopener noreferrer">Vehicles</a>
  <a href="https://www.facebook.com/marketplace/category/propertyrentals" target="_blank" rel="noopener noreferrer">Rentals</a>
  <a href="https://www.facebook.com/marketplace/category/apparel" target="_blank" rel="noopener noreferrer">Clothing</a>
  <a href="https://www.facebook.com/marketplace/category/free" target="_blank" rel="noopener noreferrer">Free</a>
  <a href="https://www.facebook.com/marketplace/category/jobs" target="_blank" rel="noopener noreferrer">Jobs</a>
</section>

<!-- Detail modal -->
<div id="modal-mask" onclick="if(event.target===this)closeModal()">
  <div id="modal">
    <button class="mx" onclick="closeModal()">close ×</button>
    <div id="modal-body"></div>
  </div>
</div>

<!-- Post-listing modal -->
<div id="post-mask" onclick="if(event.target===this)closePost()">
  <div id="post">
    <button class="pclose" onclick="closePost()">close ×</button>
    <h2>Post a listing</h2>
    <p class="sub">Visible to corridor neighbors. No fees, no algorithm.</p>
    <form id="post-form" onsubmit="return submitPost(event)">
      <div class="fld">
        <label for="p-cat">Category *</label>
        <select id="p-cat" required>
          <option value="">— pick one —</option>
          <option>Furniture</option>
          <option>Electronics</option>
          <option>Vehicles</option>
          <option>Home Goods</option>
          <option>Real Estate</option>
          <option>Services</option>
          <option>Jobs</option>
          <option>Clothing</option>
          <option>Free</option>
          <option>Misc</option>
        </select>
      </div>
      <div class="fld">
        <label for="p-title">Title *</label>
        <input id="p-title" type="text" maxlength="200" required placeholder="Mid-century walnut credenza">
      </div>
      <div class="row2">
        <div class="fld">
          <label for="p-price">Price (USD) — blank if free</label>
          <input id="p-price" type="number" min="0" step="1" placeholder="450">
        </div>
        <div class="fld">
          <label for="p-condition">Condition</label>
          <select id="p-condition">
            <option value="">—</option>
            <option value="new">New</option>
            <option value="like-new">Like new</option>
            <option value="good">Good</option>
            <option value="fair">Fair</option>
            <option value="parts">For parts</option>
          </select>
        </div>
      </div>
      <div class="fld">
        <label for="p-desc">Description</label>
        <textarea id="p-desc" maxlength="4000" placeholder="Condition, age, dimensions, why you're selling…"></textarea>
      </div>
      <div class="row2">
        <div class="fld">
          <label for="p-loc">Neighborhood</label>
          <select id="p-loc">
            <option value="">—</option>
            <option>Sherman Oaks</option>
            <option>Studio City</option>
            <option>Tarzana</option>
            <option>Encino</option>
            <option>Woodland Hills</option>
          </select>
        </div>
        <div class="fld">
          <label for="p-name">Your name</label>
          <input id="p-name" type="text" maxlength="80" placeholder="Alex K.">
        </div>
      </div>
      <div class="fld">
        <label for="p-contact">Contact (email or phone)</label>
        <input id="p-contact" type="text" maxlength="120" placeholder="alex@example.com">
      </div>
      <button class="submit" id="p-submit" type="submit">Post listing</button>
      <div id="p-msg" style="display:none"></div>
    </form>
  </div>
</div>

<script>
const $ = (id) => document.getElementById(id);
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));

// ─── State ─────────────────────────────────────────────────────────────
let CURRENT_CAT = 'all';
let LISTINGS = [];
let CATEGORIES = [];

// localStorage-persisted UI prefs
const PREF_KEY = 'vc-mp-prefs';
function loadPrefs() {
  try { return JSON.parse(localStorage.getItem(PREF_KEY) || '{}'); } catch { return {}; }
}
function savePrefs(p) {
  try { localStorage.setItem(PREF_KEY, JSON.stringify(p)); } catch {}
}

const prefs = loadPrefs();
if (prefs.sort)    $('sort').value    = prefs.sort;
if (prefs.maxp)    $('maxp').value    = prefs.maxp;
if (prefs.density) { $('density').value = prefs.density; document.documentElement.style.setProperty('--cols', prefs.density); $('density-v').textContent = prefs.density; }
if (prefs.cat)     CURRENT_CAT = prefs.cat;

function persist() {
  savePrefs({
    sort: $('sort').value, maxp: $('maxp').value,
    density: $('density').value, cat: CURRENT_CAT,
  });
}

// ─── Currency formatting ───────────────────────────────────────────────
function fmtPrice(cents) {
  if (cents == null) return { text: 'Free', cls: 'free' };
  const dollars = cents / 100;
  if (dollars >= 1000) return { text: '$' + Math.round(dollars).toLocaleString(), cls: '' };
  return { text: '$' + dollars.toFixed(dollars % 1 ? 2 : 0), cls: '' };
}

function fmtDate(iso) {
  try {
    const d = new Date(iso);
    const days = (Date.now() - d.getTime()) / 86400000;
    if (days < 1)  return 'today';
    if (days < 2)  return 'yesterday';
    if (days < 30) return Math.floor(days) + 'd ago';
    return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
  } catch { return ''; }
}

// ─── Data fetching ─────────────────────────────────────────────────────
async function loadCategories() {
  try {
    const r = await fetch('/api/marketplace/categories').then(r => r.json());
    CATEGORIES = r.rows || [];
    renderCategories(r.total || 0);
  } catch (e) { console.error(e); }
}

function renderCategories(total) {
  const bar = $('cat-bar');
  const chips = [
    `<button class="cat-chip ${CURRENT_CAT === 'all' ? 'active' : ''}" data-cat="all">All<span class="n">${total}</span></button>`,
    ...CATEGORIES.map(c => `<button class="cat-chip ${CURRENT_CAT === c.category ? 'active' : ''}" data-cat="${esc(c.category)}">${esc(c.category)}<span class="n">${c.n}</span></button>`),
  ];
  bar.innerHTML = chips.join('');
  bar.querySelectorAll('.cat-chip').forEach(el => {
    el.addEventListener('click', () => {
      CURRENT_CAT = el.dataset.cat;
      persist();
      bar.querySelectorAll('.cat-chip').forEach(x => x.classList.toggle('active', x.dataset.cat === CURRENT_CAT));
      load();
    });
  });
}

async function load() {
  const params = new URLSearchParams();
  if (CURRENT_CAT && CURRENT_CAT !== 'all') params.set('category', CURRENT_CAT);
  if ($('q').value.trim())    params.set('q', $('q').value.trim());
  if ($('sort').value)        params.set('sort', $('sort').value);
  if ($('maxp').value && $('maxp').value !== '0') params.set('max_price', $('maxp').value);
  try {
    const r = await fetch('/api/marketplace?' + params.toString()).then(r => r.json());
    LISTINGS = r.rows || [];
    render();
  } catch (e) { console.error(e); }
}

function render() {
  $('count').textContent = LISTINGS.length.toLocaleString();
  const grid = $('grid');
  const empty = $('empty');
  if (!LISTINGS.length) {
    grid.innerHTML = '';
    empty.style.display = '';
    return;
  }
  empty.style.display = 'none';
  grid.innerHTML = LISTINGS.map(l => {
    const price = fmtPrice(l.price_cents);
    const img = l.image_url
      ? `<img src="${esc(l.image_url)}" alt="${esc(l.title)}" loading="lazy" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="placeholder" style="display:none">${esc(l.title)}</div>`
      : `<div class="placeholder">${esc(l.title)}</div>`;
    const src = l.source_platform && l.source_platform !== 'local' ? `<span class="source">${esc(l.source_platform)}</span>` : '';
    return `
      <a class="card" data-id="${l.id}" href="#">
        <div class="img">
          ${img}
          <span class="cat-tag">${esc(l.category)}</span>
          ${src}
        </div>
        <div class="body">
          <div class="title">${esc(l.title)}</div>
          <div class="price ${price.cls}">${price.text}</div>
          <div class="loc">
            <span class="pin">${esc(l.location_label || '—')}</span>
            <span class="date">${fmtDate(l.posted_at)}</span>
          </div>
        </div>
      </a>`;
  }).join('');
  grid.querySelectorAll('.card').forEach(el => {
    el.addEventListener('click', (e) => {
      e.preventDefault();
      openModal(parseInt(el.dataset.id, 10));
    });
  });
}

// ─── Detail modal ──────────────────────────────────────────────────────
function openModal(id) {
  const l = LISTINGS.find(x => x.id === id);
  if (!l) return;
  const price = fmtPrice(l.price_cents);
  const cond  = l.condition ? l.condition.replace(/-/g, ' ') : null;
  const imgHtml = l.image_url
    ? `<div class="img"><img src="${esc(l.image_url)}" alt="${esc(l.title)}"></div>`
    : `<div class="img" style="display:flex;align-items:center;justify-content:center;color:var(--metal-deep);font-family:var(--serif);font-style:italic;font-size:22px;padding:24px;text-align:center">${esc(l.title)}</div>`;
  const ctaHref = l.source_url || '#';
  const ctaText = l.source_platform === 'facebook' ? 'Open on Facebook Marketplace ↗'
                : l.source_platform === 'craigslist' ? 'Open on Craigslist ↗'
                : l.source_platform === 'offerup'    ? 'Open on OfferUp ↗'
                : l.source_platform === 'nextdoor'   ? 'Open on Nextdoor ↗'
                : l.seller_name ? `Contact ${esc(l.seller_name)} ↗`
                : 'View listing ↗';
  $('modal-body').innerHTML = `
    ${imgHtml}
    <div class="body">
      <p class="mk">${esc(l.category)} · ${esc(l.location_label || 'Corridor')}</p>
      <h2>${esc(l.title)}</h2>
      <div class="mp ${price.cls}">${price.text}</div>
      ${l.description ? `<div class="md">${esc(l.description)}</div>` : ''}
      ${cond ? `<div class="mrow"><span class="k">Condition</span><span class="v">${esc(cond)}</span></div>` : ''}
      ${l.seller_name ? `<div class="mrow"><span class="k">Seller</span><span class="v">${esc(l.seller_name)}</span></div>` : ''}
      <div class="mrow"><span class="k">Posted</span><span class="v">${fmtDate(l.posted_at)}</span></div>
      <div class="mrow"><span class="k">Source</span><span class="v">${esc(l.source_platform)}</span></div>
      ${l.source_url ? `<a class="cta" href="${esc(l.source_url)}" target="_blank" rel="noopener noreferrer">${ctaText}</a>` : ''}
    </div>`;
  $('modal-mask').classList.add('show');
  document.body.style.overflow = 'hidden';
}
function closeModal() {
  $('modal-mask').classList.remove('show');
  document.body.style.overflow = '';
}

// ─── Post-listing modal ────────────────────────────────────────────────
function openPost() {
  $('post-mask').classList.add('show');
  document.body.style.overflow = 'hidden';
}
function closePost() {
  $('post-mask').classList.remove('show');
  document.body.style.overflow = '';
  $('p-msg').style.display = 'none';
}
async function submitPost(e) {
  e.preventDefault();
  const btn = $('p-submit');
  const msg = $('p-msg');
  btn.disabled = true;
  btn.textContent = 'Posting…';
  msg.style.display = 'none';
  const priceVal = $('p-price').value.trim();
  const body = {
    category:       $('p-cat').value,
    title:          $('p-title').value.trim(),
    description:    $('p-desc').value.trim(),
    price_cents:    priceVal ? Math.round(parseFloat(priceVal) * 100) : null,
    condition:      $('p-condition').value || null,
    location_label: $('p-loc').value || null,
    seller_name:    $('p-name').value.trim() || null,
    seller_contact: $('p-contact').value.trim() || null,
  };
  try {
    const r = await fetch('/api/marketplace/post', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
    const j = await r.json();
    if (!r.ok || j.error) throw new Error(j.error || 'Failed');
    msg.className = 'msg ok';
    msg.textContent = `Listing #${j.id} posted. Refreshing the grid…`;
    msg.style.display = '';
    setTimeout(() => {
      closePost();
      $('post-form').reset();
      loadCategories();
      load();
    }, 1200);
  } catch (err) {
    msg.className = 'msg err';
    msg.textContent = err.message || 'Failed to post. Try again.';
    msg.style.display = '';
  } finally {
    btn.disabled = false;
    btn.textContent = 'Post listing';
  }
  return false;
}

// ─── Wire up controls ──────────────────────────────────────────────────
let qDeb;
$('q').addEventListener('input', () => { clearTimeout(qDeb); qDeb = setTimeout(load, 180); });
$('sort').addEventListener('change', () => { persist(); load(); });
$('maxp').addEventListener('change', () => { persist(); load(); });
$('density').addEventListener('input', e => {
  const v = e.target.value;
  document.documentElement.style.setProperty('--cols', v);
  $('density-v').textContent = v;
  persist();
});

document.addEventListener('keydown', e => {
  if (e.key === 'Escape') { closeModal(); closePost(); }
});

loadCategories().then(load);
</script>

</body>
</html>