← back to Costa Rica

public/place.html

245 lines

<!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="alternate" type="application/json" id="alt-json" />
  <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}
    [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} 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:1080px;margin:0 auto;padding:14px 22px;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
    .brand{font-weight:700;font-size:18px;text-decoration:none} .brand .pin{color:var(--accent)}
    .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}
    main{max-width:1080px;margin:0 auto;padding:34px 22px 80px}
    .crumbs{font-size:13px;color:var(--muted);margin-bottom:8px}
    h1{font-size:clamp(28px,4vw,40px);letter-spacing:-.02em;margin:0 0 6px}
    .where{color:var(--muted);margin-bottom:24px;font-size:14px}
    .hero{position:relative;aspect-ratio:21/9;border-radius:14px;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)));margin-bottom:14px;display:flex;align-items:center;justify-content:center;font-size:64px;color:color-mix(in srgb,var(--fg) 30%,transparent)}
    .hero img{width:100%;height:100%;object-fit:cover;display:block}
    .credit{position:absolute;bottom:8px;right:10px;background:rgba(0,0,0,.55);color:#fff;font-size:11px;padding:4px 9px;border-radius:999px;backdrop-filter:blur(4px)}
    .credit a{color:#fff;text-decoration:underline;text-decoration-color:rgba(255,255,255,.5)}
    .grid{display:grid;grid-template-columns:2fr 1fr;gap:32px;margin-top:18px}
    @media(max-width:760px){.grid{grid-template-columns:1fr}}
    .card{background:var(--card);border:1px solid var(--card-line);border-radius:14px;padding:18px 20px;margin-bottom:14px}
    .label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);font-weight:600;margin-bottom:6px}
    .row{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:1px solid var(--line)}
    .row:last-child{border-bottom:0}
    .row .k{color:var(--muted);font-size:13px} .row .v{font-size:14px;text-align:right;max-width:62%;word-break:break-word}
    .badges{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0 6px}
    .badge{font-size:11px;padding:3px 9px;border-radius:999px;background:color-mix(in srgb,var(--fg) 6%,transparent);color:var(--muted)}
    .badge.verified{background:color-mix(in srgb,var(--accent) 18%,transparent);color:var(--accent)}
    .desc{font-size:15px;line-height:1.7;color:color-mix(in srgb,var(--fg) 92%,transparent)}
    .ctas{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
    .cta{display:inline-block;padding:9px 16px;border:1px solid var(--accent);color:var(--accent);border-radius:999px;font-size:13px;text-decoration:none;font-weight:500}
    .cta:hover{background:color-mix(in srgb,var(--accent) 12%,transparent);text-decoration:none}
    .cta.alt{border-color:var(--line);color:var(--muted)}
    #map{aspect-ratio:4/3;border-radius:14px;border:1px solid var(--card-line);overflow:hidden;background:color-mix(in srgb,var(--fg) 4%,transparent)}
    .siblings{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-top:8px}
    @media(max-width:760px){.siblings{grid-template-columns:1fr}}
    .sib{display:flex;gap:10px;align-items:center;padding:8px;border:1px solid var(--card-line);border-radius:10px;text-decoration:none;color:inherit;background:var(--card)}
    .sib:hover{border-color:var(--accent)}
    .sib .pic{width:46px;height:46px;border-radius:8px;background:linear-gradient(135deg, color-mix(in srgb, var(--accent) 35%, var(--card)), color-mix(in srgb, var(--accent-2) 30%, var(--card)));flex-shrink:0;background-size:cover;background-position:center}
    .sib .meta{display:flex;flex-direction:column;font-size:13px}
    .sib .meta .v{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.05em}
    footer{max-width:1080px;margin:0 auto;padding:30px 22px 60px;color:var(--muted);font-size:13px;border-top:1px solid var(--line);margin-top:40px}
    .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>
      <button class="icon-btn ml-auto" 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>
  </header>

  <main>
    <div class="crumbs"><a href="/">Directory</a> · <a href="" id="region-link">Region</a> · <span id="vertical-crumb">…</span></div>
    <h1 id="name">Loading…</h1>
    <div class="where" id="where">—</div>

    <div class="hero" id="hero">⛰<div class="credit" id="credit" style="display:none"></div></div>

    <div class="grid">
      <section>
        <div class="card">
          <div class="label">About</div>
          <p class="desc" id="desc">—</p>
          <div class="badges" id="badges"></div>
          <div class="ctas" id="ctas"></div>
        </div>

        <div class="card">
          <div class="label">Location</div>
          <div id="map"></div>
          <div class="row" style="border-bottom:0;padding-top:12px"><span class="k">Coordinates</span><span class="v" id="d-coords">—</span></div>
        </div>

        <div class="card" id="siblings-card" style="display:none">
          <div class="label">More in <span id="sib-region">this region</span></div>
          <div class="siblings" id="siblings"></div>
        </div>
      </section>

      <aside>
        <div class="card">
          <div class="label">Details</div>
          <div class="row"><span class="k">Vertical</span><span class="v" id="d-vertical">—</span></div>
          <div class="row"><span class="k">Region</span><span class="v" id="d-region">—</span></div>
          <div class="row"><span class="k">Province</span><span class="v" id="d-province">—</span></div>
          <div class="row"><span class="k">Address</span><span class="v" id="d-address">—</span></div>
          <div class="row"><span class="k">Phone</span><span class="v" id="d-phone">—</span></div>
          <div class="row"><span class="k">Email</span><span class="v" id="d-email">—</span></div>
          <div class="row"><span class="k">Website</span><span class="v" id="d-website">—</span></div>
          <div class="row"><span class="k">Cédula jurídica</span><span class="v" id="d-cedula">—</span></div>
          <div class="row"><span class="k">Price range</span><span class="v" id="d-price">—</span></div>
          <div class="row"><span class="k">Rating</span><span class="v" id="d-rating">—</span></div>
          <div class="row"><span class="k">Source</span><span class="v" id="d-source">—</span></div>
          <div class="row"><span class="k">Status</span><span class="v" id="d-status">—</span></div>
          <div class="row"><span class="k">Added</span><span class="v" id="d-added">—</span></div>
        </div>
      </aside>
    </div>
  </main>

  <footer>
    <div>© Costa Rica Directory. Aggregated from public records (MEIC, ICT, municipal patentes) and local web directories. Listing owners can correct or claim this entry at <a href="mailto:info@agentabrams.com">info@agentabrams.com</a>.</div>
  </footer>

  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
  <script>
    document.getElementById('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(/^\/p\//,'');
    const altLink = document.getElementById('alt-json');
    altLink.href = '/api/places/' + encodeURIComponent(slug);

    function setHTML(id, html) { document.getElementById(id).innerHTML = html; }
    function setText(id, v) { document.getElementById(id).textContent = v || '—'; }

    fetch('/api/places/' + encodeURIComponent(slug)).then(r => r.ok ? r.json() : Promise.reject(r)).then(p => {
      document.title = p.name + ' — Costa Rica Directory';
      document.getElementById('name').textContent = p.name;
      const where = (p.region_name ? p.region_name + (p.province ? ', ' + p.province : '') : (p.address || ''));
      document.getElementById('where').textContent = where;
      const vertical = (p.vertical||'').replace(/_/g,' ');
      document.getElementById('vertical-crumb').textContent = vertical;
      const regionLink = document.getElementById('region-link');
      if (p.region_slug){ regionLink.href = '/r/' + p.region_slug; regionLink.textContent = p.region_name; }

      // Hero image (place's own → falls back to region's Wikipedia hero)
      const hero = document.getElementById('hero');
      const credit = document.getElementById('credit');
      const heroImg = p.effective_image_url || p.image_url || p.region_image_url;
      if (heroImg) {
        const imgEl = document.createElement('img');
        imgEl.src = heroImg; imgEl.alt = p.name; imgEl.loading = 'eager';
        hero.firstChild && hero.removeChild(hero.firstChild);
        hero.insertBefore(imgEl, credit);
        const creditText = p.effective_image_credit || p.region_image_credit;
        const creditUrl  = p.effective_image_source_url || p.region_image_source_url;
        if (creditText && creditUrl) {
          credit.innerHTML = '📷 <a href="'+creditUrl+'" target="_blank" rel="noopener nofollow">'+creditText+'</a>';
          credit.style.display = '';
        } else if (creditText) {
          credit.textContent = '📷 ' + creditText;
          credit.style.display = '';
        }
      }

      setText('desc', p.description);

      // Badges
      const badges = document.getElementById('badges');
      const tags = [vertical, p.verified ? 'verified' : null, p.rating ? '★ ' + (+p.rating).toFixed(1) : null].filter(Boolean);
      tags.forEach(t => { const span=document.createElement('span'); span.className='badge'+(t==='verified'?' verified':''); span.textContent=t; badges.appendChild(span); });

      // CTAs
      const ctas = document.getElementById('ctas');
      const ctaList = [];
      if (p.website) ctaList.push({label:'Visit website ↗', href: p.website, alt:false});
      if (p.phone)   ctaList.push({label:'Call ' + p.phone, href:'tel:' + p.phone.replace(/[^+\d]/g,''), alt:false});
      if (p.email)   ctaList.push({label:'Email', href:'mailto:' + p.email, alt:true});
      const lat = p.lat || p.region_lat, lng = p.lng || p.region_lng;
      if (lat && lng) ctaList.push({label:'Open in Google Maps ↗', href:`https://www.google.com/maps/search/?api=1&query=${lat},${lng}`, alt:true});
      if (p.region_slug) ctaList.push({label:'More in ' + p.region_name, href:'/r/' + p.region_slug, alt:true});
      ctaList.forEach(c => { const a=document.createElement('a'); a.className='cta'+(c.alt?' alt':''); a.href=c.href; if(c.href.startsWith('http')){ a.target='_blank'; a.rel='noopener noreferrer'; } a.textContent=c.label; ctas.appendChild(a); });

      // Details panel
      setText('d-vertical', vertical);
      setText('d-region',   p.region_name);
      setText('d-province', p.province);
      setText('d-address',  p.address);
      setText('d-phone',    p.phone);
      setText('d-coords',   (lat && lng) ? (+lat).toFixed(4) + ', ' + (+lng).toFixed(4) : null);
      setHTML('d-email',    p.email   ? '<a href="mailto:'+p.email+'">'+p.email+'</a>' : '—');
      setHTML('d-website',  p.website ? '<a href="'+p.website+'" target="_blank" rel="noopener noreferrer">'+p.website.replace(/^https?:\/\//,'').replace(/\/$/,'')+'</a>' : '—');
      setText('d-cedula',   p.cedula_juridica);
      setText('d-price',    p.price_range);
      setText('d-rating',   p.rating ? (+p.rating).toFixed(1) : null);
      setText('d-status',   p.status);
      setText('d-added',    p.created_at ? new Date(p.created_at).toLocaleDateString() : null);
      const dSrc = document.getElementById('d-source');
      if (p.source_url){ dSrc.innerHTML = '<a href="'+p.source_url+'" target="_blank" rel="noopener nofollow">'+(p.source||'link')+' ↗</a>'; }
      else { dSrc.textContent = p.source || '—'; }

      // Map (Leaflet + OpenStreetMap)
      if (lat && lng){
        const map = L.map('map', { zoomControl: true, scrollWheelZoom: false }).setView([+lat, +lng], p.lat ? 15 : 12);
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' }).addTo(map);
        const marker = L.marker([+lat, +lng]).addTo(map);
        marker.bindPopup('<strong>'+p.name+'</strong><br>'+(p.address||p.region_name||'')).openPopup();
      } else {
        document.getElementById('map').textContent = 'No coordinates on file.';
        document.getElementById('map').style.display = 'flex';
        document.getElementById('map').style.alignItems = 'center';
        document.getElementById('map').style.justifyContent = 'center';
        document.getElementById('map').style.color = 'var(--muted)';
        document.getElementById('map').style.fontSize = '13px';
        document.getElementById('map').style.aspectRatio = 'auto';
        document.getElementById('map').style.padding = '20px';
      }

      // Siblings ("more in region")
      if (p.siblings_in_region && p.siblings_in_region.length) {
        document.getElementById('siblings-card').style.display = '';
        document.getElementById('sib-region').textContent = p.region_name || 'this region';
        const sibs = document.getElementById('siblings');
        p.siblings_in_region.forEach(s => {
          const a = document.createElement('a');
          a.className = 'sib'; a.href = '/p/' + encodeURIComponent(s.slug);
          a.innerHTML = `<div class="pic"${s.image_url ? ' style="background-image:url('+s.image_url+')"' : ''}></div><div class="meta"><span>${s.name}</span><span class="v">${(s.vertical||'').replace(/_/g,' ')}</span></div>`;
          sibs.appendChild(a);
        });
      }

      // JSON-LD LocalBusiness — embed as <script type="application/ld+json">
      fetch('/api/places/' + encodeURIComponent(slug) + '/jsonld').then(r => r.ok ? r.json() : null).then(ld => {
        if (!ld) return;
        const s = document.createElement('script');
        s.type = 'application/ld+json';
        s.textContent = JSON.stringify(ld);
        document.head.appendChild(s);
      }).catch(()=>{});
    }).catch(() => {
      document.getElementById('name').textContent = 'Listing not found';
      document.getElementById('desc').textContent = 'This listing does not exist or was removed.';
    });
  </script>
</body>
</html>