<!-- wp:html --> <!-- CAFFEINE COMPASS // FINAL (Clean Slate + Particles) UPDATES: - Map: CartoDB Positron (Clean/Minimal) - Pins: Reinforced visibility logic - HUD: Slimmer (300px), Compressed, Particle Animation - Logic: Fail-proof scoped CSS --> <div id="caffeine-compass-root"> <!-- 1. DEPENDENCIES --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;600&family=Space+Grotesk:wght@300;500;700;800&display=swap" rel="stylesheet"> <!-- 2. SCOPED STYLES --> <style> /* ROOT RESET */ #caffeine-compass-root { all: initial; display: block; position: relative; width: 100%; height: 90vh; background: #fff; overflow: hidden; font-family: 'Manrope', sans-serif; color: #0F172A; /* VARS */ --accent: #10B981; --accent-glow: rgba(16, 185, 129, 0.4); --orange: #FF5722; --orange-glow: rgba(255, 87, 34, 0.4); --glass: rgba(255, 255, 255, 0.85); --border: rgba(255, 255, 255, 0.6); } #caffeine-compass-root * { box-sizing: border-box; } /* TYPOGRAPHY */ #caffeine-compass-root h1, #caffeine-compass-root h2, #caffeine-compass-root h3 { font-family: 'Space Grotesk', sans-serif; margin: 0; line-height: 1.1; } #caffeine-compass-root button { cursor: pointer; border: none; background: none; font-family: inherit; padding: 0; } #caffeine-compass-root input { outline: none; } /* MAP */ #caffeine-compass-root #map-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } #caffeine-compass-root .leaflet-control-attribution { display: none; } /* --- CUSTOM PINS (Fixed) --- */ /* We target the Leaflet pane directly to ensure visibility */ #caffeine-compass-root .caffeine-beacon { position: relative; width: 18px; height: 18px; background: var(--accent); border-radius: 50%; border: 2px solid #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: all 0.3s ease; } #caffeine-compass-root .caffeine-beacon.active { background: var(--orange); transform: scale(1.5); border-width: 3px; z-index: 999; box-shadow: 0 4px 15px var(--orange-glow); } #caffeine-compass-root .caffeine-beacon::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--accent); opacity: 0; animation: cc-ping 2.5s infinite; pointer-events: none; } #caffeine-compass-root .caffeine-beacon.active::after { border-color: var(--orange); } @keyframes cc-ping { 0% { width: 18px; height: 18px; opacity: 0.6; } 100% { width: 60px; height: 60px; opacity: 0; } } /* --- LEFT MONOLITH (Sidebar) --- */ #caffeine-compass-root .cc-sidebar { position: absolute; top: 40px; bottom: 40px; left: 30px; width: 320px; z-index: 10; background: var(--glass); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid var(--border); border-radius: 20px; box-shadow: 0 20px 40px -10px rgba(0,0,0,0.1); display: flex; flex-direction: column; overflow: hidden; } #caffeine-compass-root .cc-header { padding: 20px; background: rgba(255,255,255,0.4); border-bottom: 1px solid rgba(0,0,0,0.05); } #caffeine-compass-root .cc-title { font-size: 28px; font-weight: 800; text-transform: uppercase; background: linear-gradient(135deg, #1F2937 0%, #4B5563 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 8px; } #caffeine-compass-root .cc-tag { display: inline-flex; align-items: center; gap: 6px; background: #fff; padding: 4px 8px; border-radius: 4px; font-size: 10px; font-weight: 700; text-transform: uppercase; color: #64748B; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } /* SEARCH */ #caffeine-compass-root .cc-search-wrap { position: relative; margin-top: 12px; margin-bottom: 10px; } #caffeine-compass-root .cc-search-input { width: 100%; padding: 8px 8px 8px 32px; border-radius: 6px; border: 1px solid rgba(0,0,0,0.1); background: rgba(255,255,255,0.6); font-size: 11px; font-family: inherit; } #caffeine-compass-root .cc-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); font-size: 10px; color: #9CA3AF; } /* FILTERS */ #caffeine-compass-root .cc-filters { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 2px; } #caffeine-compass-root .cc-filters::-webkit-scrollbar, #caffeine-compass-root .cc-list::-webkit-scrollbar { display: none; } #caffeine-compass-root .cc-btn { padding: 5px 10px; border-radius: 5px; background: white; border: 1px solid rgba(0,0,0,0.05); font-size: 9px; font-weight: 700; text-transform: uppercase; color: #64748B; white-space: nowrap; transition: all 0.2s; } #caffeine-compass-root .cc-btn.active { background: var(--accent); color: white; border-color: var(--accent); } /* LIST */ #caffeine-compass-root .cc-list { flex: 1; overflow-y: auto; padding: 10px; } #caffeine-compass-root .cc-card { padding: 12px; margin-bottom: 6px; border-radius: 8px; background: rgba(255,255,255,0.0); border: 1px solid transparent; cursor: pointer; transition: all 0.2s; } #caffeine-compass-root .cc-card:hover { background: rgba(255,255,255,0.5); } #caffeine-compass-root .cc-card.active { background: var(--orange); color: white; box-shadow: 0 6px 15px -3px var(--orange-glow); } #caffeine-compass-root .cc-card.active .cc-card-title { color: white; } #caffeine-compass-root .cc-card.active .cc-card-meta, #caffeine-compass-root .cc-card.active .cc-card-cat { color: rgba(255,255,255,0.9); } #caffeine-compass-root .cc-card-cat { font-size: 8px; font-weight: 700; text-transform: uppercase; color: var(--accent); margin-bottom: 2px; } #caffeine-compass-root .cc-card-title { font-size: 13px; font-weight: 700; margin-bottom: 4px; color: #1F2937; } #caffeine-compass-root .cc-card-meta { font-size: 9px; color: #6B7280; display: flex; gap: 6px; } /* --- HUD (RIGHT PANEL - Slimmer) --- */ #caffeine-compass-root .cc-hud { position: absolute; bottom: 40px; right: 30px; width: 300px; /* Slimmer */ z-index: 20; background: var(--glass); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid var(--border); border-radius: 20px; box-shadow: 0 20px 50px rgba(0,0,0,0.15); overflow: hidden; transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1); opacity: 0; transform: translateY(20px); pointer-events: none; } #caffeine-compass-root .cc-hud.visible { opacity: 1; transform: translateY(0); pointer-events: auto; } /* Minimized */ #caffeine-compass-root .cc-hud.minimized { width: auto; min-width: 200px; height: 50px; border-radius: 25px; background: #fff; cursor: pointer; } #caffeine-compass-root .cc-hud.minimized .cc-hud-content { opacity: 0; pointer-events: none; } #caffeine-compass-root .cc-dock { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; padding: 0 16px 0 6px; opacity: 0; pointer-events: none; } #caffeine-compass-root .cc-hud.minimized .cc-dock { opacity: 1; pointer-events: auto; } /* HUD Content */ #caffeine-compass-root .cc-hud-img-wrap { position: relative; height: 130px; /* Compressed height */ width: 100%; overflow: hidden; } #caffeine-compass-root .cc-hud-img { width: 100%; height: 100%; object-fit: cover; } #caffeine-compass-root .cc-hud-grad { position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,0.9), transparent); } #caffeine-compass-root .cc-hud-title-overlay { position: absolute; bottom: 12px; left: 16px; right: 16px; color: white; z-index: 2; } #caffeine-compass-root .cc-ht-main { font-size: 18px; font-weight: 700; line-height: 1.1; } #caffeine-compass-root .cc-ht-sub { font-size: 9px; text-transform: uppercase; margin-top: 4px; opacity: 0.9; font-weight: 600; } #caffeine-compass-root .cc-hud-body { padding: 16px; background: rgba(255,255,255,0.5); } /* PARTICLE ANIMATION */ #caffeine-compass-root .cc-particles { position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; overflow: hidden; } #caffeine-compass-root .cc-part { position: absolute; bottom: -10px; width: 2px; height: 2px; background: rgba(255,255,255,0.7); border-radius: 50%; animation: floatUp linear infinite; } @keyframes floatUp { 0% { transform: translateY(0) scale(1); opacity: 0; } 20% { opacity: 0.8; } 100% { transform: translateY(-60px) scale(0); opacity: 0; } } /* Stats Grid */ #caffeine-compass-root .cc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px; } #caffeine-compass-root .cc-stat { background: rgba(255,255,255,0.7); border-radius: 6px; padding: 8px; text-align: center; border: 1px solid rgba(0,0,0,0.05); } #caffeine-compass-root .cc-lbl { font-size: 8px; color: #64748B; text-transform: uppercase; letter-spacing: 0.5px; } #caffeine-compass-root .cc-val { font-size: 12px; font-weight: 700; color: #0F172A; margin-top: 2px; } /* Heat Bar */ #caffeine-compass-root .cc-heat { margin-bottom: 12px; } #caffeine-compass-root .cc-heat-top { display: flex; justify-content: space-between; font-size: 9px; font-weight: 700; color: #64748B; margin-bottom: 4px; text-transform: uppercase; } #caffeine-compass-root .cc-track { height: 4px; background: rgba(0,0,0,0.05); border-radius: 2px; overflow: hidden; } #caffeine-compass-root .cc-fill { height: 100%; width: 0; background: linear-gradient(90deg, #FFAB91, #FF5722); transition: width 1s; } /* Pills */ #caffeine-compass-root .cc-pills { display: flex; gap: 4px; flex-wrap: wrap; } #caffeine-compass-root .cc-pill-item { font-size: 8px; font-weight: 700; text-transform: uppercase; padding: 3px 6px; background: #fff; border-radius: 4px; color: #059669; border: 1px solid rgba(0,0,0,0.05); } /* Min Button */ #caffeine-compass-root .cc-min { position: absolute; top: 10px; right: 10px; width: 24px; height: 24px; border-radius: 50%; background: rgba(0,0,0,0.3); color: white; display: flex; align-items: center; justify-content: center; font-size: 9px; z-index: 10; } @media (max-width: 768px) { #caffeine-compass-root .cc-sidebar { top: auto; bottom: 20px; left: 10px; right: 10px; width: auto; height: 40vh; } #caffeine-compass-root .cc-hud { display: none; } #caffeine-compass-root .mobile-hide { display: none; } } </style> <!-- 3. MAP CONTAINER --> <div id="map-canvas"></div> <!-- 4. SIDEBAR --> <div class="cc-sidebar"> <div class="cc-header"> <h1 class="cc-title">Caffeine<br>Compass</h1> <div class="cc-tag"> <img src="https://upload.wikimedia.org/wikipedia/commons/8/83/Flag_of_Cambodia.svg" width="12" height="9" style="border-radius:1px;"> <span>Phnom Penh</span> </div> <div class="cc-search-wrap mobile-hide"> <i class="fa-solid fa-search cc-search-icon"></i> <input type="text" class="cc-search-input" id="cc-search" placeholder="Filter signals..."> </div> <div class="cc-filters" id="cc-filters"> <button class="cc-btn active" onclick="ccFilter('all')">All</button> <button class="cc-btn" onclick="ccFilter('nomad')">Nomad</button> <button class="cc-btn" onclick="ccFilter('meeting')">Meet</button> <button class="cc-btn" onclick="ccFilter('quiet')">Quiet</button> </div> </div> <div class="cc-list" id="cc-list"></div> <div style="height: 20px; background: linear-gradient(to top, rgba(255,255,255,0.8), transparent); position:absolute; bottom:0; left:0; width:100%; pointer-events:none;"></div> </div> <!-- 5. HUD --> <div class="cc-hud" id="cc-hud" onclick="ccExpand()"></div> <!-- 6. LOGIC --> <script> (function() { // DATA const DATA = [ { id: 1, name: "Brown Roastery BKK", lat: 11.5508, lng: 104.9295, cat: "Institution", tags: ["meeting", "social"], price: "$3.50", vibe: "Bustling", heat: 95, wifi: "Fiber", amenities: ["AC", "Garden"], img: "https://images.unsplash.com/photo-1554118811-1e0d58224f24?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80" }, { id: 2, name: "Enso Cafe", lat: 11.5590, lng: 104.9300, cat: "Nomad Haven", tags: ["nomad", "quiet"], price: "$3.00", vibe: "Zen", heat: 85, wifi: "Reliable", amenities: ["AC", "Food"], img: "https://images.unsplash.com/photo-1509042239860-f550ce710b93?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80" }, { id: 3, name: "Temple Coffee St. 51", lat: 11.5555, lng: 104.9280, cat: "Late Night", tags: ["meeting", "social"], price: "$2.75", vibe: "Majestic", heat: 90, wifi: "Fast", amenities: ["24/7", "Sofas"], img: "https://images.unsplash.com/photo-1497935586351-b67a49e012bf?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80" }, { id: 4, name: "Tini Cafe", lat: 11.5400, lng: 104.9150, cat: "Hidden Gem", tags: ["quiet", "artistic"], price: "$3.00", vibe: "Intimate", heat: 75, wifi: "Decent", amenities: ["Cocktails", "Art"], img: "https://images.unsplash.com/photo-1493857671505-72967e2e2760?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80" }, { id: 5, name: "Backyard Cafe", lat: 11.5600, lng: 104.9310, cat: "Healthy", tags: ["nomad", "healthy"], price: "$4.00", vibe: "Tropical", heat: 88, wifi: "Stable", amenities: ["Vegan", "Patio"], img: "https://images.unsplash.com/photo-1600093463592-8e36ae95ef56?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80" } ]; // Add generics const G = ["Tube", "Khéma", "Mere", "Feel Good", "Pleng Chan", "Suzy Time", "Vibe", "Farm to Table", "Amazon", "Koi"]; let c = 11; G.forEach((n, i) => { DATA.push({ id: c++, name: n + " Branch", lat: 11.55 + (Math.random()*0.03 - 0.015), lng: 104.92 + (Math.random()*0.03 - 0.015), cat: "Standard", tags: ["nomad", "meeting", "quiet"][i%3], price: "$2.50", vibe: "Casual", heat: 50 + Math.floor(Math.random()*40), wifi: "Standard", amenities: ["AC", "Wifi"], img: "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80" }); }); let map, markers = {}, activeId = null, filter = 'all', minimized = false; function init() { const el = document.getElementById('map-canvas'); if(!el) return; // CLEAN SLATE MAP map = L.map(el, { zoomControl: false, attributionControl: false }).setView([11.5564, 104.9282], 13); L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { maxZoom: 19, subdomains: 'abcd' }).addTo(map); renderList(); } function renderList() { Object.values(markers).forEach(m => map.removeLayer(m)); markers = {}; const list = document.getElementById('cc-list'); list.innerHTML = ''; const items = filter === 'all' ? DATA : DATA.filter(d => d.tags.includes(filter)); items.forEach(d => { // Sidebar Item const card = document.createElement('div'); card.className = 'cc-card'; card.dataset.id = d.id; card.onclick = () => activate(d.id); card.innerHTML = ` <div class="cc-card-cat">${d.cat}</div> <div class="cc-card-title">${d.name}</div> <div class="cc-card-meta"><span>${d.price}</span>•<span>${d.wifi}</span></div> `; list.appendChild(card); // Marker const icon = L.divIcon({ className: 'cc-marker-wrap', // Wrapper class html: `<div class="caffeine-beacon" id="beacon-${d.id}"></div>`, iconSize: [18, 18], iconAnchor: [9, 9] }); const m = L.marker([d.lat, d.lng], {icon}).addTo(map); m.on('click', () => { document.querySelector(`.cc-card[data-id="${d.id}"]`)?.scrollIntoView({behavior:'smooth', block:'center'}); activate(d.id); }); markers[d.id] = m; }); } function activate(id) { if(activeId === id) return; activeId = id; const d = DATA.find(x => x.id === id); map.flyTo([d.lat, d.lng], 15, {duration: 1.2}); document.querySelectorAll('.cc-card').forEach(e => e.classList.remove('active')); document.querySelector(`.cc-card[data-id="${id}"]`)?.classList.add('active'); document.querySelectorAll('.caffeine-beacon').forEach(e => e.classList.remove('active')); const b = document.getElementById(`beacon-${id}`); if(b) b.classList.add('active'); minimized = false; renderHUD(d); } function renderHUD(d) { const hud = document.getElementById('cc-hud'); hud.className = 'cc-hud visible'; // Particle Generation const particles = Array(15).fill(0).map(() => { const left = Math.random() * 100; const delay = Math.random() * 2; const dur = 2 + Math.random() * 3; return `<div class="cc-part" style="left:${left}%; animation-delay:${delay}s; animation-duration:${dur}s"></div>`; }).join(''); hud.innerHTML = ` <div class="cc-hud-content"> <button class="cc-min" onclick="ccToggle(event)"><i class="fa-solid fa-chevron-down"></i></button> <div class="cc-hud-img-wrap"> <img src="${d.img}" class="cc-hud-img"> <div class="cc-hud-grad"></div> <div class="cc-particles">${particles}</div> <div class="cc-hud-title-overlay"> <div class="cc-ht-main">${d.name}</div> <div class="cc-ht-sub" style="color:#10B981">${d.cat}</div> </div> </div> <div class="cc-hud-body"> <div class="cc-grid"> <div class="cc-stat"><div class="cc-lbl">Vibe</div><div class="cc-val">${d.vibe}</div></div> <div class="cc-stat"><div class="cc-lbl">Price</div><div class="cc-val" style="color:#10B981">${d.price}</div></div> </div> <div class="cc-heat"> <div class="cc-heat-top"><span>Heat</span><span style="color:#FF5722">${d.heat}%</span></div> <div class="cc-track"><div class="cc-fill" style="width:${d.heat}%"></div></div> </div> <div class="cc-pills"> ${d.amenities.map(a => `<span class="cc-pill-item">${a}</span>`).join('')} </div> </div> </div> <div class="cc-dock"> <div style="width:32px; height:32px; border-radius:50%; overflow:hidden; border:2px solid #FF5722; margin-right:10px;"> <img src="${d.img}" style="width:100%; height:100%; object-fit:cover;"> </div> <div> <div style="font-size:11px; font-weight:700;">${d.name}</div> <div style="font-size:9px; color:#FF5722; font-weight:700; text-transform:uppercase;">Active</div> </div> </div> `; } window.ccFilter = (cat) => { filter = cat; const btns = document.getElementById('cc-filters').children; Array.from(btns).forEach(b => b.classList.remove('active')); const map = { 'all':0, 'nomad':1, 'meeting':2, 'quiet':3 }; if(btns[map[cat]]) btns[map[cat]].classList.add('active'); renderList(); }; window.ccToggle = (e) => { e.stopPropagation(); const hud = document.getElementById('cc-hud'); minimized = !minimized; hud.classList.toggle('minimized', minimized); const icon = hud.querySelector('.cc-min i'); if(icon) icon.className = minimized ? 'fa-solid fa-chevron-up' : 'fa-solid fa-chevron-down'; }; window.ccExpand = () => { if(minimized) { minimized = false; document.getElementById('cc-hud').classList.remove('minimized'); } }; if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); })(); </script> </div> <!-- /wp:html -->