[object Object]

← back to Costa Rica

yolo tick 2: dedicated region landing page (/r/:slug) with hero image, headline variety, by-vertical breakdown, nearby-in-province cross-links, region map

cfab471d192b39e705075888b208bd27b753aa96 · 2026-05-08 07:57:20 -0700 · Steve

Files touched

Diff

commit cfab471d192b39e705075888b208bd27b753aa96
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri May 8 07:57:20 2026 -0700

    yolo tick 2: dedicated region landing page (/r/:slug) with hero image, headline variety, by-vertical breakdown, nearby-in-province cross-links, region map
---
 public/region.html | 314 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 server.js          |  43 +++++++-
 2 files changed, 353 insertions(+), 4 deletions(-)

diff --git a/public/region.html b/public/region.html
new file mode 100644
index 0000000..1cf5cac
--- /dev/null
+++ b/public/region.html
@@ -0,0 +1,314 @@
+<!doctype html>
+<html lang="en" data-theme="light">
+<head>
+  <meta charset="utf-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1" />
+  <title>Loading… — Costa Rica Directory</title>
+  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
+  <script>(function(){try{var t=localStorage.getItem('cr-theme');if(t)document.documentElement.setAttribute('data-theme',t);}catch(e){}})();</script>
+  <style>
+    :root{--bg:#fafaf7;--fg:#0e1015;--muted:#5a6270;--line:#e3e0d6;--card:#fff;--card-line:#ebe7da;--accent:#0a7a3a;--accent-2:#c2a85a;--cols:4}
+    [data-theme="dark"]{--bg:#0e1015;--fg:#f3f1ea;--muted:#9aa3b2;--line:#23262d;--card:#15181f;--card-line:#262932;--accent:#3ec07a;--accent-2:#d8c074}
+    *{box-sizing:border-box}
+    body{margin:0;background:var(--bg);color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,'Inter','Segoe UI',Roboto,sans-serif;line-height:1.55}
+    a{color:inherit;text-decoration:none} a:hover{color:var(--accent)}
+    .nav{position:sticky;top:0;z-index:30;background:color-mix(in srgb,var(--bg) 88%,transparent);backdrop-filter:saturate(160%) blur(10px);border-bottom:1px solid var(--line)}
+    .nav-in{max-width:1320px;margin:0 auto;padding:14px 22px;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
+    .brand{font-weight:700;font-size:18px} .brand .pin{color:var(--accent)}
+    .nav-links{display:flex;gap:14px;flex-wrap:wrap;color:var(--muted);font-size:14px}
+    .nav-links a.active{color:var(--fg);font-weight:600}
+    .ml-auto{margin-left:auto} .icon-btn{appearance:none;background:transparent;color:inherit;border:1px solid var(--line);border-radius:999px;width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}
+
+    .hero-wrap{position:relative;width:100%;height:42vh;min-height:340px;max-height:520px;overflow:hidden;background:linear-gradient(135deg, color-mix(in srgb, var(--accent) 35%, var(--card)), color-mix(in srgb, var(--accent-2) 30%, var(--card)))}
+    .hero-wrap img{width:100%;height:100%;object-fit:cover;display:block;filter:saturate(1.05)}
+    .hero-overlay{position:absolute;inset:0;background:linear-gradient(to bottom, rgba(0,0,0,.05) 0%, rgba(0,0,0,.7) 100%);display:flex;align-items:flex-end;padding:36px 22px}
+    .hero-inner{max-width:1320px;margin:0 auto;width:100%;color:#fff}
+    .hero-eyebrow{font-size:12px;text-transform:uppercase;letter-spacing:.12em;opacity:.85;margin-bottom:6px}
+    .hero-title{font-size:clamp(36px,6vw,68px);letter-spacing:-.025em;line-height:1.05;margin:0 0 6px;font-weight:800}
+    .hero-sub{font-size:16px;opacity:.92;max-width:760px}
+    .hero-credit{position:absolute;bottom:8px;right:14px;background:rgba(0,0,0,.5);color:#fff;font-size:11px;padding:4px 10px;border-radius:999px}
+    .hero-credit a{color:#fff;text-decoration:underline;text-decoration-color:rgba(255,255,255,.5)}
+
+    main{max-width:1320px;margin:0 auto;padding:24px 22px 80px}
+    .crumbs{font-size:13px;color:var(--muted);margin-bottom:16px}
+    .stats-row{display:flex;gap:14px;flex-wrap:wrap;margin-bottom:18px}
+    .stat{padding:10px 14px;border:1px solid var(--card-line);border-radius:12px;background:var(--card);font-size:13px;color:var(--muted)}
+    .stat .n{color:var(--fg);font-weight:600;font-size:18px;margin-right:6px;font-variant-numeric:tabular-nums}
+
+    .layout{display:grid;grid-template-columns:1fr 360px;gap:32px;margin-top:8px}
+    @media(max-width:980px){.layout{grid-template-columns:1fr}}
+
+    .filter-bar{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}
+    .pill{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border:1px solid var(--line);border-radius:999px;background:var(--card);color:var(--fg);font-size:13px;cursor:pointer}
+    .pill.active{background:var(--fg);color:var(--bg);border-color:var(--fg)}
+    .pill .n{font-variant-numeric:tabular-nums;color:var(--muted);font-size:12px;margin-left:4px}
+    .pill.active .n{color:color-mix(in srgb,var(--bg) 70%, transparent)}
+
+    .controls{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin:6px 0 14px}
+    .search{flex:1;min-width:240px;display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--line);border-radius:12px;background:var(--card)}
+    .search input{flex:1;border:0;outline:0;background:transparent;color:inherit;font-size:15px}
+    .ctrl{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--muted)}
+    .ctrl select{appearance:none;background:var(--card);color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:6px 10px;font-size:13px}
+    .ctrl input[type=range]{padding:0;width:120px}
+
+    .grid{display:grid;grid-template-columns:repeat(var(--cols),minmax(0,1fr));gap:16px}
+    @media(max-width:760px){.grid{grid-template-columns:repeat(2,1fr)}}
+    .card{background:var(--card);border:1px solid var(--card-line);border-radius:12px;overflow:hidden;display:flex;flex-direction:column;transition:transform .15s,box-shadow .15s}
+    .card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(20,20,20,.08)}
+    .card .img{aspect-ratio:16/10;background:linear-gradient(135deg, color-mix(in srgb, var(--accent) 35%, var(--card)), color-mix(in srgb, var(--accent-2) 30%, var(--card)));display:flex;align-items:center;justify-content:center;font-size:24px;color:color-mix(in srgb,var(--fg) 35%,transparent)}
+    .card .img img{width:100%;height:100%;object-fit:cover;display:block}
+    .card .body{padding:12px 14px 14px}
+    .cat{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--accent);font-weight:600}
+    .name{font-weight:600;letter-spacing:-.01em;margin-top:4px}
+    .where{font-size:12px;color:var(--muted);margin-top:2px}
+
+    .side .card{padding:18px;margin-bottom:14px}
+    .label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);font-weight:600;margin-bottom:10px}
+    #region-map{aspect-ratio:1/1;border-radius:12px;border:1px solid var(--card-line);overflow:hidden;background:color-mix(in srgb,var(--fg) 4%,transparent)}
+    .vert-list{display:flex;flex-direction:column;gap:6px}
+    .vert-list a{display:flex;justify-content:space-between;padding:7px 10px;border-radius:8px;font-size:13px}
+    .vert-list a:hover{background:color-mix(in srgb,var(--fg) 5%,transparent)}
+    .vert-list .n{color:var(--muted);font-variant-numeric:tabular-nums}
+    .nearby{display:grid;grid-template-columns:1fr 1fr;gap:8px}
+    .nearby a{display:flex;align-items:center;gap:8px;padding:6px;border:1px solid var(--card-line);border-radius:10px;font-size:12px}
+    .nearby a:hover{border-color:var(--accent)}
+    .nearby .pic{width:32px;height:32px;border-radius:6px;background:linear-gradient(135deg,color-mix(in srgb,var(--accent) 35%,var(--card)),color-mix(in srgb,var(--accent-2) 30%,var(--card)));background-size:cover;background-position:center;flex-shrink:0}
+    .meta{font-size:13px;color:var(--muted);margin:6px 0 14px}
+    footer{max-width:1320px;margin:0 auto;padding:30px 22px 60px;color:var(--muted);font-size:13px;border-top:1px solid var(--line);margin-top:30px}
+    .leaflet-container{background:color-mix(in srgb,var(--fg) 4%,transparent)!important}
+  </style>
+</head>
+<body>
+  <header class="nav">
+    <div class="nav-in">
+      <a class="brand" href="/"><span class="pin">●</span>&nbsp;Costa&nbsp;Rica&nbsp;Directory</a>
+      <nav class="nav-links">
+        <a href="/?category=tourism">Tourism</a>
+        <a href="/?category=rentals">Rentals</a>
+        <a href="/?category=service">Local services</a>
+      </nav>
+      <div class="ml-auto" style="display:flex;gap:8px;align-items:center">
+        <button class="icon-btn" id="theme-toggle" title="Toggle theme">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
+        </button>
+      </div>
+    </div>
+  </header>
+
+  <section class="hero-wrap" id="hero-wrap">
+    <img id="hero-img" alt="" />
+    <div class="hero-overlay">
+      <div class="hero-inner">
+        <div class="hero-eyebrow" id="hero-eyebrow">Costa Rica</div>
+        <h1 class="hero-title" id="hero-title">Loading…</h1>
+        <p class="hero-sub" id="hero-sub"></p>
+      </div>
+    </div>
+    <div class="hero-credit" id="hero-credit" style="display:none"></div>
+  </section>
+
+  <main>
+    <div class="crumbs"><a href="/">Directory</a> · <span id="crumb-region">Region</span></div>
+
+    <div class="stats-row">
+      <div class="stat"><span class="n" id="s-count">—</span> listings</div>
+      <div class="stat"><span class="n" id="s-tourism">—</span> tourism</div>
+      <div class="stat"><span class="n" id="s-rentals">—</span> rentals</div>
+      <div class="stat"><span class="n" id="s-service">—</span> local services</div>
+    </div>
+
+    <div class="layout">
+      <section>
+        <div class="filter-bar" id="vertical-bar"></div>
+
+        <div class="controls">
+          <div class="search">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
+            <input id="q" placeholder="Search businesses in this region…" autocomplete="off" />
+          </div>
+          <div class="ctrl">
+            <label for="sort">Sort</label>
+            <select id="sort">
+              <option value="newest">Newest</option>
+              <option value="name">Name A→Z</option>
+              <option value="rating">Rating</option>
+              <option value="verified">Verified first</option>
+            </select>
+          </div>
+          <div class="ctrl">
+            <label for="cols">Density</label>
+            <input id="cols" type="range" min="2" max="6" step="1" value="4" />
+          </div>
+        </div>
+
+        <div class="meta" id="meta">Loading places…</div>
+        <div class="grid" id="grid"></div>
+      </section>
+
+      <aside class="side">
+        <div class="card">
+          <div class="label">On the map</div>
+          <div id="region-map"></div>
+        </div>
+
+        <div class="card">
+          <div class="label">By vertical</div>
+          <div class="vert-list" id="vert-list"></div>
+        </div>
+
+        <div class="card" id="nearby-card" style="display:none">
+          <div class="label">Nearby in province</div>
+          <div class="nearby" id="nearby"></div>
+        </div>
+      </aside>
+    </div>
+  </main>
+
+  <footer>© Costa Rica Directory. Aggregated from public records (MEIC, ICT, municipal patentes) and local web directories. Hero photos via Wikipedia/Wikimedia Commons. Map data © OpenStreetMap contributors. Owners can correct or claim a listing at <a href="mailto:info@agentabrams.com">info@agentabrams.com</a>.</footer>
+
+  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
+  <script>
+    const $  = (s,r=document)=>r.querySelector(s);
+    const $$ = (s,r=document)=>[...r.querySelectorAll(s)];
+    document.documentElement.style.setProperty('--cols', localStorage.getItem('cr-cols') || 4);
+
+    $('#theme-toggle').addEventListener('click', () => {
+      const cur = document.documentElement.getAttribute('data-theme') || 'light';
+      const nxt = cur === 'dark' ? 'light' : 'dark';
+      document.documentElement.setAttribute('data-theme', nxt);
+      try { localStorage.setItem('cr-theme', nxt); } catch(e){}
+    });
+
+    const slug = location.pathname.replace(/^\/r\//,'');
+    const state = { vertical: '', q: '', sort: 'newest' };
+
+    function pluralize(n, s) { return n === 1 ? s : s + 's'; }
+    function headline(region, count) {
+      const variants = [
+        `${count.toLocaleString()} ${pluralize(count,'business')} in ${region.name}`,
+        `${region.name} — every business, in one place`,
+        `Find ${pluralize(count,'business')} across ${region.name}`,
+        `The directory of ${region.name}`,
+      ];
+      return variants[Math.abs(region.id) % variants.length] || variants[0];
+    }
+    function subline(region, byVert) {
+      const top = (byVert||[]).slice(0,3).map(v => v.vertical.replace(/_/g,' ')).join(' · ');
+      const where = region.province ? `${region.province} province` : 'Costa Rica';
+      return `${region.region_type === 'city' ? 'City of' : ''} ${region.name}, ${where}. ${top || 'Tourism, rentals, and local services'}.`.replace(/^\s+/,'');
+    }
+
+    Promise.all([
+      fetch('/api/regions/' + encodeURIComponent(slug)).then(r => r.ok ? r.json() : Promise.reject(r)),
+    ]).then(([region]) => {
+      document.title = region.name + ' — Costa Rica Directory';
+      $('#crumb-region').textContent = region.name;
+
+      // Hero
+      if (region.image_url) { $('#hero-img').src = region.image_url; $('#hero-img').alt = region.name; }
+      else { $('#hero-img').style.display = 'none'; }
+      $('#hero-eyebrow').textContent = region.province ? region.province + ' · Costa Rica' : 'Costa Rica';
+      $('#hero-title').textContent = headline(region, region.place_count);
+      $('#hero-sub').textContent = subline(region, region.by_vertical);
+      if (region.image_credit && region.image_source_url) {
+        $('#hero-credit').innerHTML = '📷 <a href="'+region.image_source_url+'" target="_blank" rel="noopener nofollow">'+region.image_credit+'</a>';
+        $('#hero-credit').style.display = '';
+      }
+
+      // Stats
+      const byCat = {};
+      for (const v of (region.by_vertical||[])) {
+        const cat = v.vertical.startsWith('tourism_') ? 'tourism' : v.vertical.startsWith('rentals_') ? 'rentals' : 'service';
+        byCat[cat] = (byCat[cat] || 0) + v.n;
+      }
+      $('#s-count').textContent   = region.place_count.toLocaleString();
+      $('#s-tourism').textContent = (byCat.tourism||0).toLocaleString();
+      $('#s-rentals').textContent = (byCat.rentals||0).toLocaleString();
+      $('#s-service').textContent = (byCat.service||0).toLocaleString();
+
+      // Vertical pill bar (top 8) + sidebar list (all)
+      const vbar = $('#vertical-bar');
+      const allPill = document.createElement('button');
+      allPill.className = 'pill active'; allPill.dataset.vert='';
+      allPill.innerHTML = `All<span class="n">${region.place_count}</span>`;
+      vbar.appendChild(allPill);
+      (region.by_vertical||[]).slice(0,8).forEach(v => {
+        const p = document.createElement('button');
+        p.className = 'pill'; p.dataset.vert = v.vertical;
+        p.innerHTML = `${v.vertical.replace(/_/g,' ')}<span class="n">${v.n}</span>`;
+        vbar.appendChild(p);
+      });
+      vbar.addEventListener('click', e => {
+        const t = e.target.closest('.pill'); if (!t) return;
+        $$('.pill', vbar).forEach(x => x.classList.remove('active'));
+        t.classList.add('active');
+        state.vertical = t.dataset.vert;
+        loadGrid();
+      });
+
+      const vlist = $('#vert-list');
+      (region.by_vertical||[]).forEach(v => {
+        const a = document.createElement('a');
+        a.href = '#'; a.dataset.vert = v.vertical;
+        a.innerHTML = `<span>${v.vertical.replace(/_/g,' ')}</span><span class="n">${v.n}</span>`;
+        a.addEventListener('click', e => { e.preventDefault(); state.vertical=v.vertical; $$('.pill', vbar).forEach(x=>x.classList.remove('active')); loadGrid(); window.scrollTo({top:0,behavior:'smooth'}); });
+        vlist.appendChild(a);
+      });
+
+      // Nearby in province
+      if (region.nearby_in_province && region.nearby_in_province.length) {
+        $('#nearby-card').style.display = '';
+        const wrap = $('#nearby');
+        region.nearby_in_province.forEach(n => {
+          const a = document.createElement('a');
+          a.href = '/r/' + n.slug;
+          a.innerHTML = `<div class="pic"${n.image_url ? ' style="background-image:url('+n.image_url+')"' : ''}></div><div><div>${n.name}</div><div style="color:var(--muted);font-size:11px">${n.n} listings</div></div>`;
+          wrap.appendChild(a);
+        });
+      }
+
+      // Region map (Leaflet) — pin region center, fetch place markers async
+      if (region.lat && region.lng) {
+        const map = L.map('region-map', { zoomControl: true, scrollWheelZoom: false }).setView([+region.lat, +region.lng], 12);
+        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© <a href="https://www.openstreetmap.org/copyright">OSM</a>' }).addTo(map);
+        L.circleMarker([+region.lat, +region.lng], { radius: 8, color: '#0a7a3a', fillColor:'#0a7a3a', fillOpacity:.7 }).addTo(map).bindPopup('<strong>'+region.name+'</strong><br>'+region.place_count+' listings');
+      } else {
+        $('#region-map').textContent = 'No coordinates on file.';
+        $('#region-map').style.cssText = 'aspect-ratio:auto;padding:20px;color:var(--muted);font-size:13px;display:flex;align-items:center;justify-content:center';
+      }
+
+      loadGrid();
+    }).catch(() => {
+      $('#hero-title').textContent = 'Region not found';
+    });
+
+    function card(p){
+      const wrap = document.createElement('a');
+      wrap.className = 'card'; wrap.href = '/p/' + encodeURIComponent(p.slug);
+      const heroSrc = p.effective_image_url || p.image_url;
+      wrap.innerHTML = `${heroSrc ? '<div class="img"><img loading="lazy" src="'+heroSrc+'" alt=""></div>' : '<div class="img">⛰</div>'}<div class="body"><div class="cat">${(p.vertical||'').replace(/_/g,' ')}</div><div class="name">${p.name}</div><div class="where">${p.address || p.region_name || ''}</div></div>`;
+      return wrap;
+    }
+
+    let qDebounce;
+    $('#q').addEventListener('input', e => { clearTimeout(qDebounce); qDebounce=setTimeout(()=>{ state.q=e.target.value.trim(); loadGrid(); },220); });
+    $('#sort').addEventListener('change', e => { state.sort=e.target.value; loadGrid(); });
+    $('#cols').addEventListener('input', e => { document.documentElement.style.setProperty('--cols', e.target.value); try{localStorage.setItem('cr-cols',e.target.value)}catch(e){} });
+
+    function loadGrid() {
+      const params = new URLSearchParams();
+      params.set('region', slug);
+      if (state.vertical) params.set('vertical', state.vertical);
+      if (state.q)        params.set('q', state.q);
+      if (state.sort)     params.set('sort', state.sort);
+      params.set('limit', '120');
+      $('#meta').textContent = 'Loading…';
+      $('#grid').innerHTML = '';
+      fetch('/api/places?' + params).then(r => r.json()).then(j => {
+        $('#meta').textContent = `${(j.total||0).toLocaleString()} matches` + (state.vertical ? ` · ${state.vertical.replace(/_/g,' ')}` : '');
+        (j.places||[]).forEach(p => $('#grid').appendChild(card(p)));
+      });
+    }
+  </script>
+</body>
+</html>
diff --git a/server.js b/server.js
index d01cf37..862bbac 100644
--- a/server.js
+++ b/server.js
@@ -329,15 +329,50 @@ app.get('/api/places/:slug/jsonld', async (req, res) => {
   } catch (e) { res.status(500).json({ error: e.message }); }
 });
 
-// Region landing page — same shell, deep-link a region pre-selected
+// Region landing page — dedicated layout with hero image + region map.
 app.get('/r/:slug', async (req, res) => {
   try {
-    const { rows } = await pool.query('SELECT slug, name FROM regions WHERE slug = $1', [req.params.slug]);
-    if (!rows.length) return res.status(404).sendFile(path.join(__dirname, 'public', 'index.html'));
-    res.sendFile(path.join(__dirname, 'public', 'index.html'));
+    const { rows } = await pool.query('SELECT slug FROM regions WHERE slug = $1', [req.params.slug]);
+    if (!rows.length) return res.status(404).sendFile(path.join(__dirname, 'public', '404.html'));
+    res.sendFile(path.join(__dirname, 'public', 'region.html'));
   } catch (e) { res.status(500).json({ error: e.message }); }
 });
 
+// Region details endpoint (one region + count + sibling regions in same province)
+app.get('/api/regions/:slug', async (req, res) => {
+  try {
+    const { rows } = await pool.query(`
+      SELECT r.id, r.slug, r.name, r.province, r.region_type, r.lat, r.lng,
+             r.image_url, r.image_credit, r.image_license, r.image_source_url,
+             r.description,
+             COUNT(p.id)::int AS place_count
+        FROM regions r
+   LEFT JOIN places p ON p.region_id = r.id AND p.status = 'active'
+       WHERE r.slug = $1
+    GROUP BY r.id`, [req.params.slug]);
+    if (!rows.length) return res.status(404).json({ error: 'not_found' });
+    const region = rows[0];
+
+    const { rows: byVert } = await pool.query(
+      `SELECT vertical, COUNT(*)::int AS n FROM places WHERE region_id=$1 AND status='active' GROUP BY vertical ORDER BY n DESC`,
+      [region.id]
+    );
+    const { rows: provincial } = await pool.query(
+      `SELECT r.slug, r.name, r.image_url, COUNT(p.id)::int AS n
+         FROM regions r LEFT JOIN places p ON p.region_id=r.id AND p.status='active'
+        WHERE r.province = $1 AND r.id != $2 AND r.region_type IN ('city','town','canton')
+     GROUP BY r.id ORDER BY n DESC NULLS LAST, r.name LIMIT 12`,
+      [region.province, region.id]
+    );
+
+    region.by_vertical = byVert;
+    region.nearby_in_province = provincial;
+    res.json(region);
+  } catch (e) {
+    res.status(500).json({ error: e.message });
+  }
+});
+
 // Place detail page
 app.get('/p/:slug', async (req, res) => {
   try {

← 1670949 VCL features + image attribution: per-region Wikipedia hero  ·  back to Costa Rica  ·  yolo tick 3: /stats dashboard with KPIs, by-vertical/categor 72b08a1 →