← 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> Costa Rica 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>