UI Components Easy
Location Pin Card
Location card with map placeholder, animated pin drop, address details, distance badge, and directions button. Pure CSS.
Open in Lab
MCP
css react tailwind svelte vue
Targets: TS JS HTML React Svelte Vue
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f9fafb;
min-height: 100vh;
padding: 32px 16px;
display: flex;
justify-content: center;
}
.demo {
width: 100%;
max-width: 480px;
}
.section-label {
font-size: 11px;
font-weight: 700;
color: #9ca3af;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 10px;
}
.pin-card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.pin-map {
height: 180px;
position: relative;
overflow: hidden;
}
.map-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(145deg, #e0e7ff 0%, #c7d2fe 100%);
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.map-grid {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(255, 255, 255, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.3) 1px, transparent 1px);
background-size: 32px 32px;
}
.map-pin {
position: relative;
z-index: 2;
animation: pin-drop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.3s both;
}
@keyframes pin-drop {
from {
transform: translateY(-40px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.pin-ring {
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 16px;
height: 6px;
background: rgba(0, 0, 0, 0.15);
border-radius: 50%;
animation: pin-shadow 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.3s both;
}
@keyframes pin-shadow {
from {
transform: translateX(-50%) scaleX(0);
opacity: 0;
}
to {
transform: translateX(-50%) scaleX(1);
opacity: 1;
}
}
.pin-body {
padding: 16px 20px 20px;
}
.pin-name-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
margin-bottom: 4px;
}
.pin-name {
font-size: 16px;
font-weight: 800;
color: #111827;
}
.pin-distance {
font-size: 12px;
font-weight: 700;
color: #6366f1;
background: #eef2ff;
padding: 3px 8px;
border-radius: 20px;
white-space: nowrap;
}
.pin-addr {
font-size: 13px;
color: #6b7280;
line-height: 1.5;
margin-bottom: 10px;
}
.pin-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 14px;
}
.pin-tag {
font-size: 11px;
font-weight: 600;
background: #f3f4f6;
color: #6b7280;
padding: 3px 8px;
border-radius: 6px;
}
.pin-tag--open {
background: #dcfce7;
color: #166534;
}
.pin-actions {
display: flex;
gap: 8px;
}
.pin-btn {
font-size: 13px;
font-weight: 600;
padding: 8px 16px;
border-radius: 8px;
text-decoration: none;
background: #f3f4f6;
color: #374151;
transition: background 0.15s;
}
.pin-btn:hover {
background: #e5e7eb;
}
.pin-btn--primary {
background: #6366f1;
color: #fff;
}
.pin-btn--primary:hover {
background: #5558e3;
}
/* Branch list */
.branch-list {
display: flex;
flex-direction: column;
gap: 1px;
background: #e5e7eb;
border-radius: 14px;
overflow: hidden;
}
.branch-item {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: #fff;
}
.branch-icon {
font-size: 18px;
flex-shrink: 0;
}
.branch-info {
flex: 1;
}
.branch-name {
font-size: 14px;
font-weight: 700;
color: #111827;
margin-bottom: 2px;
}
.branch-addr {
font-size: 12px;
color: #9ca3af;
}
.branch-dist {
font-size: 12px;
font-weight: 700;
color: #6b7280;
flex-shrink: 0;
}// Pure CSS animations — no JavaScript required<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Location Pin Card</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h2 class="section-label">Single location</h2>
<div class="pin-card">
<div class="pin-map">
<div class="map-placeholder">
<div class="map-grid"></div>
<div class="map-pin" id="mapPin">
<svg width="24" height="32" viewBox="0 0 24 32" fill="none">
<path d="M12 0C5.373 0 0 5.373 0 12c0 8 12 20 12 20s12-12 12-20c0-6.627-5.373-12-12-12z" fill="#6366f1"/>
<circle cx="12" cy="12" r="5" fill="#fff"/>
</svg>
<div class="pin-ring"></div>
</div>
</div>
</div>
<div class="pin-body">
<div class="pin-info">
<div class="pin-name-row">
<h3 class="pin-name">Acme Headquarters</h3>
<span class="pin-distance">0.4 km</span>
</div>
<p class="pin-addr">350 5th Avenue, Floor 68<br/>New York, NY 10118</p>
<div class="pin-tags">
<span class="pin-tag pin-tag--open">Open now</span>
<span class="pin-tag">Mon–Fri 9–6</span>
<span class="pin-tag">+1 212 555 0100</span>
</div>
</div>
<div class="pin-actions">
<a href="#" class="pin-btn pin-btn--primary">Directions</a>
<a href="#" class="pin-btn">Save</a>
<a href="#" class="pin-btn">Share</a>
</div>
</div>
</div>
<h2 class="section-label" style="margin-top:28px;">Branch list</h2>
<div class="branch-list">
<div class="branch-item">
<div class="branch-icon">📍</div>
<div class="branch-info">
<p class="branch-name">Acme — Manhattan</p>
<p class="branch-addr">350 5th Ave, New York, NY</p>
</div>
<span class="branch-dist">0.4 km</span>
</div>
<div class="branch-item">
<div class="branch-icon">📍</div>
<div class="branch-info">
<p class="branch-name">Acme — Brooklyn</p>
<p class="branch-addr">1 Court Square, LIC, NY</p>
</div>
<span class="branch-dist">3.2 km</span>
</div>
<div class="branch-item">
<div class="branch-icon">📍</div>
<div class="branch-info">
<p class="branch-name">Acme — Hoboken</p>
<p class="branch-addr">88 River St, Hoboken, NJ</p>
</div>
<span class="branch-dist">5.1 km</span>
</div>
</div>
</div>
</body>
</html>import { useState } from "react";
interface Location {
id: number;
name: string;
address: string;
city: string;
country: string;
distance: string;
rating: number;
reviews: number;
open: boolean;
hours: string;
phone: string;
lat: number;
lng: number;
}
const LOCATIONS: Location[] = [
{
id: 1,
name: "HQ — Main Campus",
address: "1 Hacker Way",
city: "Menlo Park, CA 94025",
country: "United States",
distance: "0.2 mi",
rating: 4.8,
reviews: 2340,
open: true,
hours: "Mon–Fri 8am–8pm",
phone: "+1 (650) 555-0101",
lat: 37.4848,
lng: -122.1487,
},
{
id: 2,
name: "Downtown Office",
address: "181 Fremont St, Floor 32",
city: "San Francisco, CA 94105",
country: "United States",
distance: "1.4 mi",
rating: 4.6,
reviews: 871,
open: true,
hours: "Mon–Fri 9am–6pm",
phone: "+1 (415) 555-0199",
lat: 37.7907,
lng: -122.3958,
},
{
id: 3,
name: "Research Lab East",
address: "77 Massachusetts Ave",
city: "Cambridge, MA 02139",
country: "United States",
distance: "2.8 mi",
rating: 4.9,
reviews: 540,
open: false,
hours: "Mon–Thu 9am–5pm",
phone: "+1 (617) 555-0042",
lat: 42.3601,
lng: -71.0942,
},
];
function MapPlaceholder({ lat, lng, pin }: { lat: number; lng: number; pin: string }) {
return (
<div className="relative h-48 bg-[#0d1117] rounded-xl overflow-hidden border border-[#30363d]">
{/* Grid lines */}
<svg className="absolute inset-0 w-full h-full opacity-20" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="mapgrid" width="24" height="24" patternUnits="userSpaceOnUse">
<path d="M 24 0 L 0 0 0 24" fill="none" stroke="#30363d" strokeWidth="0.5" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#mapgrid)" />
{/* Roads */}
<line x1="0" y1="60%" x2="100%" y2="60%" stroke="#21262d" strokeWidth="3" />
<line x1="0" y1="30%" x2="100%" y2="30%" stroke="#21262d" strokeWidth="2" />
<line x1="30%" y1="0" x2="30%" y2="100%" stroke="#21262d" strokeWidth="2" />
<line x1="65%" y1="0" x2="65%" y2="100%" stroke="#21262d" strokeWidth="3" />
<line x1="80%" y1="0" x2="80%" y2="100%" stroke="#21262d" strokeWidth="1.5" />
<rect x="32%" y="40%" width="15%" height="15%" fill="#1c2128" rx="3" />
<rect x="50%" y="20%" width="12%" height="8%" fill="#1c2128" rx="2" />
</svg>
{/* Animated pin */}
<div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-full"
style={{ animation: "pin-drop 0.6s cubic-bezier(0.34,1.56,0.64,1) both" }}
>
<div className="relative">
<div
className="w-8 h-8 rounded-full rounded-bl-none rotate-45 flex items-center justify-center -rotate-45 shadow-lg"
style={{ background: "#58a6ff" }}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="white"
strokeWidth="2.5"
className="rotate-0"
>
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z" />
<circle cx="12" cy="9" r="2.5" fill="white" stroke="none" />
</svg>
</div>
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-2 h-2 bg-[#58a6ff] rounded-full opacity-40 blur-sm scale-150" />
</div>
</div>
{/* Coords badge */}
<div className="absolute bottom-2 right-2 bg-[#161b22]/80 border border-[#30363d] rounded-lg px-2 py-1 font-mono text-[9px] text-[#484f58]">
{lat.toFixed(4)}, {lng.toFixed(4)}
</div>
<style>{`@keyframes pin-drop{from{transform:translate(-50%,-150%) scale(0.5);opacity:0}to{transform:translate(-50%,-100%) scale(1);opacity:1}}`}</style>
</div>
);
}
function Stars({ rating }: { rating: number }) {
return (
<div className="flex items-center gap-0.5">
{[1, 2, 3, 4, 5].map((s) => (
<svg
key={s}
width="10"
height="10"
viewBox="0 0 24 24"
fill={s <= Math.round(rating) ? "#e3b341" : "none"}
stroke="#e3b341"
strokeWidth="2"
>
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
</svg>
))}
<span className="text-[10px] text-[#8b949e] ml-1">
{rating} ({(rating * 100).toLocaleString()})
</span>
</div>
);
}
export default function LocationPinCardRC() {
const [selected, setSelected] = useState(0);
const loc = LOCATIONS[selected];
return (
<div className="min-h-screen bg-[#0d1117] p-6 flex justify-center">
<div className="w-full max-w-[420px] space-y-4">
{/* Location tabs */}
<div className="flex gap-1 bg-[#161b22] border border-[#30363d] rounded-xl p-1">
{LOCATIONS.map((l, i) => (
<button
key={l.id}
onClick={() => setSelected(i)}
className={`flex-1 px-2 py-1.5 rounded-lg text-[11px] font-semibold transition-colors truncate ${
selected === i
? "bg-[#21262d] text-[#e6edf3]"
: "text-[#8b949e] hover:text-[#e6edf3]"
}`}
>
{i === 0 ? "HQ" : i === 1 ? "Downtown" : "Lab"}
</button>
))}
</div>
{/* Card */}
<div className="bg-[#161b22] border border-[#30363d] rounded-2xl overflow-hidden">
<MapPlaceholder lat={loc.lat} lng={loc.lng} pin={loc.name} />
<div className="p-5 space-y-4">
{/* Name + open badge */}
<div className="flex items-start justify-between gap-2">
<h3 className="text-[15px] font-bold text-[#e6edf3] leading-tight">{loc.name}</h3>
<span
className={`flex-shrink-0 text-[10px] font-bold px-2 py-0.5 rounded-full ${
loc.open
? "bg-green-500/10 text-green-400 border border-green-500/20"
: "bg-red-500/10 text-red-400 border border-red-500/20"
}`}
>
{loc.open ? "Open" : "Closed"}
</span>
</div>
<Stars rating={loc.rating} />
{/* Address */}
<div className="space-y-1.5 text-[12px]">
<div className="flex items-start gap-2 text-[#8b949e]">
<svg
className="flex-shrink-0 mt-0.5"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z" />
</svg>
<div>
<p>{loc.address}</p>
<p>{loc.city}</p>
<p className="text-[#484f58]">{loc.country}</p>
</div>
</div>
<div className="flex items-center gap-2 text-[#8b949e]">
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
<span>{loc.hours}</span>
</div>
<div className="flex items-center gap-2 text-[#8b949e]">
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 9.5a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.62 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z" />
</svg>
<span>{loc.phone}</span>
</div>
</div>
{/* Distance + CTA */}
<div className="flex items-center gap-2 pt-1">
<span className="flex items-center gap-1.5 text-[11px] text-[#8b949e] bg-[#21262d] border border-[#30363d] rounded-lg px-2.5 py-1.5">
<svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<circle cx="12" cy="12" r="10" />
<polyline points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76" />
</svg>
{loc.distance} away
</span>
<button className="flex-1 flex items-center justify-center gap-1.5 py-2 bg-[#58a6ff] rounded-xl text-[12px] font-bold text-white hover:bg-[#79b8ff] transition-colors">
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
>
<polygon points="3 11 22 2 13 21 11 13 3 11" />
</svg>
Get Directions
</button>
</div>
</div>
</div>
</div>
</div>
);
}<script>
const LOCATIONS = [
{
id: 1,
name: "HQ \u2014 Main Campus",
address: "1 Hacker Way",
city: "Menlo Park, CA 94025",
country: "United States",
distance: "0.2 mi",
rating: 4.8,
reviews: 2340,
open: true,
hours: "Mon\u2013Fri 8am\u20138pm",
phone: "+1 (650) 555-0101",
lat: 37.4848,
lng: -122.1487,
},
{
id: 2,
name: "Downtown Office",
address: "181 Fremont St, Floor 32",
city: "San Francisco, CA 94105",
country: "United States",
distance: "1.4 mi",
rating: 4.6,
reviews: 871,
open: true,
hours: "Mon\u2013Fri 9am\u20136pm",
phone: "+1 (415) 555-0199",
lat: 37.7907,
lng: -122.3958,
},
{
id: 3,
name: "Research Lab East",
address: "77 Massachusetts Ave",
city: "Cambridge, MA 02139",
country: "United States",
distance: "2.8 mi",
rating: 4.9,
reviews: 540,
open: false,
hours: "Mon\u2013Thu 9am\u20135pm",
phone: "+1 (617) 555-0042",
lat: 42.3601,
lng: -71.0942,
},
];
let selected = 0;
$: loc = LOCATIONS[selected];
const tabLabels = ["HQ", "Downtown", "Lab"];
</script>
<div class="min-h-screen bg-[#0d1117] p-6 flex justify-center">
<div class="w-full max-w-[420px] space-y-4">
<!-- Location tabs -->
<div class="flex gap-1 bg-[#161b22] border border-[#30363d] rounded-xl p-1">
{#each LOCATIONS as l, i}
<button
on:click={() => (selected = i)}
class="flex-1 px-2 py-1.5 rounded-lg text-[11px] font-semibold transition-colors truncate {selected === i ? 'bg-[#21262d] text-[#e6edf3]' : 'text-[#8b949e]'}"
>
{tabLabels[i]}
</button>
{/each}
</div>
<!-- Card -->
<div class="bg-[#161b22] border border-[#30363d] rounded-2xl overflow-hidden">
<!-- Map placeholder -->
<div class="relative h-48 bg-[#0d1117] rounded-xl overflow-hidden border border-[#30363d]">
<svg class="absolute inset-0 w-full h-full opacity-20" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="mapgrid" width="24" height="24" patternUnits="userSpaceOnUse">
<path d="M 24 0 L 0 0 0 24" fill="none" stroke="#30363d" stroke-width="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#mapgrid)"/>
<line x1="0" y1="60%" x2="100%" y2="60%" stroke="#21262d" stroke-width="3"/>
<line x1="0" y1="30%" x2="100%" y2="30%" stroke="#21262d" stroke-width="2"/>
<line x1="30%" y1="0" x2="30%" y2="100%" stroke="#21262d" stroke-width="2"/>
<line x1="65%" y1="0" x2="65%" y2="100%" stroke="#21262d" stroke-width="3"/>
<line x1="80%" y1="0" x2="80%" y2="100%" stroke="#21262d" stroke-width="1.5"/>
<rect x="32%" y="40%" width="15%" height="15%" fill="#1c2128" rx="3"/>
<rect x="50%" y="20%" width="12%" height="8%" fill="#1c2128" rx="2"/>
</svg>
<!-- Pin -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-full" style="animation: pin-drop 0.6s cubic-bezier(0.34,1.56,0.64,1) both;">
<div class="relative">
<div class="w-8 h-8 rounded-full rounded-bl-none rotate-45 flex items-center justify-center -rotate-45 shadow-lg" style="background: #58a6ff;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/>
<circle cx="12" cy="9" r="2.5" fill="white" stroke="none"/>
</svg>
</div>
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 w-2 h-2 bg-[#58a6ff] rounded-full opacity-40 blur-sm scale-150"></div>
</div>
</div>
<!-- Coords badge -->
<div class="absolute bottom-2 right-2 bg-[#161b22]/80 border border-[#30363d] rounded-lg px-2 py-1 font-mono text-[9px] text-[#484f58]">
{loc.lat.toFixed(4)}, {loc.lng.toFixed(4)}
</div>
</div>
<div class="p-5 space-y-4">
<!-- Name + open badge -->
<div class="flex items-start justify-between gap-2">
<h3 class="text-[15px] font-bold text-[#e6edf3] leading-tight">{loc.name}</h3>
<span
class="flex-shrink-0 text-[10px] font-bold px-2 py-0.5 rounded-full border {loc.open ? 'bg-green-500/10 text-green-400 border-green-500/20' : 'bg-red-500/10 text-red-400 border-red-500/20'}"
>
{loc.open ? "Open" : "Closed"}
</span>
</div>
<!-- Stars -->
<div class="flex items-center gap-0.5">
{#each [1, 2, 3, 4, 5] as star}
<svg width="10" height="10" viewBox="0 0 24 24"
fill={star <= Math.round(loc.rating) ? "#e3b341" : "none"}
stroke="#e3b341" stroke-width="2">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
</svg>
{/each}
<span class="text-[10px] text-[#8b949e] ml-1">{loc.rating} ({(loc.rating * 100).toLocaleString()})</span>
</div>
<!-- Address -->
<div class="space-y-1.5 text-[12px]">
<div class="flex items-start gap-2 text-[#8b949e]">
<svg class="flex-shrink-0 mt-0.5" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/>
</svg>
<div>
<p>{loc.address}</p>
<p>{loc.city}</p>
<p class="text-[#484f58]">{loc.country}</p>
</div>
</div>
<div class="flex items-center gap-2 text-[#8b949e]">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
</svg>
<span>{loc.hours}</span>
</div>
<div class="flex items-center gap-2 text-[#8b949e]">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 9.5a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.62 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/>
</svg>
<span>{loc.phone}</span>
</div>
</div>
<!-- Distance + CTA -->
<div class="flex items-center gap-2 pt-1">
<span class="flex items-center gap-1.5 text-[11px] text-[#8b949e] bg-[#21262d] border border-[#30363d] rounded-lg px-2.5 py-1.5">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>
</svg>
{loc.distance} away
</span>
<button class="flex-1 flex items-center justify-center gap-1.5 py-2 bg-[#58a6ff] rounded-xl text-[12px] font-bold text-white hover:bg-[#79b8ff] transition-colors">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polygon points="3 11 22 2 13 21 11 13 3 11"/>
</svg>
Get Directions
</button>
</div>
</div>
</div>
</div>
</div>
<style>
@keyframes pin-drop {
from { transform: translate(-50%, -150%) scale(0.5); opacity: 0; }
to { transform: translate(-50%, -100%) scale(1); opacity: 1; }
}
</style><script setup>
import { ref, computed } from "vue";
const LOCATIONS = [
{
id: 1,
name: "HQ \u2014 Main Campus",
address: "1 Hacker Way",
city: "Menlo Park, CA 94025",
country: "United States",
distance: "0.2 mi",
rating: 4.8,
reviews: 2340,
open: true,
hours: "Mon\u2013Fri 8am\u20138pm",
phone: "+1 (650) 555-0101",
lat: 37.4848,
lng: -122.1487,
},
{
id: 2,
name: "Downtown Office",
address: "181 Fremont St, Floor 32",
city: "San Francisco, CA 94105",
country: "United States",
distance: "1.4 mi",
rating: 4.6,
reviews: 871,
open: true,
hours: "Mon\u2013Fri 9am\u20136pm",
phone: "+1 (415) 555-0199",
lat: 37.7907,
lng: -122.3958,
},
{
id: 3,
name: "Research Lab East",
address: "77 Massachusetts Ave",
city: "Cambridge, MA 02139",
country: "United States",
distance: "2.8 mi",
rating: 4.9,
reviews: 540,
open: false,
hours: "Mon\u2013Thu 9am\u20135pm",
phone: "+1 (617) 555-0042",
lat: 42.3601,
lng: -71.0942,
},
];
const selected = ref(0);
const loc = computed(() => LOCATIONS[selected.value]);
const tabLabels = ["HQ", "Downtown", "Lab"];
</script>
<template>
<div class="min-h-screen bg-[#0d1117] p-6 flex justify-center">
<div class="w-full max-w-[420px] space-y-4">
<!-- Location tabs -->
<div class="flex gap-1 bg-[#161b22] border border-[#30363d] rounded-xl p-1">
<button
v-for="(l, i) in LOCATIONS"
:key="l.id"
@click="selected = i"
class="flex-1 px-2 py-1.5 rounded-lg text-[11px] font-semibold transition-colors truncate"
:class="selected === i ? 'bg-[#21262d] text-[#e6edf3]' : 'text-[#8b949e] hover:text-[#e6edf3]'"
>
{{ tabLabels[i] }}
</button>
</div>
<!-- Card -->
<div class="bg-[#161b22] border border-[#30363d] rounded-2xl overflow-hidden">
<!-- Map placeholder -->
<div class="relative h-48 bg-[#0d1117] rounded-xl overflow-hidden border border-[#30363d]">
<svg class="absolute inset-0 w-full h-full opacity-20" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="mapgrid" width="24" height="24" patternUnits="userSpaceOnUse">
<path d="M 24 0 L 0 0 0 24" fill="none" stroke="#30363d" stroke-width="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#mapgrid)"/>
<line x1="0" y1="60%" x2="100%" y2="60%" stroke="#21262d" stroke-width="3"/>
<line x1="0" y1="30%" x2="100%" y2="30%" stroke="#21262d" stroke-width="2"/>
<line x1="30%" y1="0" x2="30%" y2="100%" stroke="#21262d" stroke-width="2"/>
<line x1="65%" y1="0" x2="65%" y2="100%" stroke="#21262d" stroke-width="3"/>
<line x1="80%" y1="0" x2="80%" y2="100%" stroke="#21262d" stroke-width="1.5"/>
<rect x="32%" y="40%" width="15%" height="15%" fill="#1c2128" rx="3"/>
<rect x="50%" y="20%" width="12%" height="8%" fill="#1c2128" rx="2"/>
</svg>
<!-- Pin -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-full pin-drop">
<div class="relative">
<div class="w-8 h-8 rounded-full rounded-bl-none rotate-45 flex items-center justify-center -rotate-45 shadow-lg" style="background: #58a6ff;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/>
<circle cx="12" cy="9" r="2.5" fill="white" stroke="none"/>
</svg>
</div>
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 w-2 h-2 bg-[#58a6ff] rounded-full opacity-40 blur-sm scale-150"></div>
</div>
</div>
<!-- Coords badge -->
<div class="absolute bottom-2 right-2 bg-[#161b22]/80 border border-[#30363d] rounded-lg px-2 py-1 font-mono text-[9px] text-[#484f58]">
{{ loc.lat.toFixed(4) }}, {{ loc.lng.toFixed(4) }}
</div>
</div>
<div class="p-5 space-y-4">
<!-- Name + open badge -->
<div class="flex items-start justify-between gap-2">
<h3 class="text-[15px] font-bold text-[#e6edf3] leading-tight">{{ loc.name }}</h3>
<span
class="flex-shrink-0 text-[10px] font-bold px-2 py-0.5 rounded-full border"
:class="loc.open
? 'bg-green-500/10 text-green-400 border-green-500/20'
: 'bg-red-500/10 text-red-400 border-red-500/20'"
>
{{ loc.open ? 'Open' : 'Closed' }}
</span>
</div>
<!-- Stars -->
<div class="flex items-center gap-0.5">
<svg v-for="star in 5" :key="star" width="10" height="10" viewBox="0 0 24 24"
:fill="star <= Math.round(loc.rating) ? '#e3b341' : 'none'"
stroke="#e3b341" stroke-width="2">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
</svg>
<span class="text-[10px] text-[#8b949e] ml-1">{{ loc.rating }} ({{ (loc.rating * 100).toLocaleString() }})</span>
</div>
<!-- Address -->
<div class="space-y-1.5 text-[12px]">
<div class="flex items-start gap-2 text-[#8b949e]">
<svg class="flex-shrink-0 mt-0.5" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/>
</svg>
<div>
<p>{{ loc.address }}</p>
<p>{{ loc.city }}</p>
<p class="text-[#484f58]">{{ loc.country }}</p>
</div>
</div>
<div class="flex items-center gap-2 text-[#8b949e]">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
</svg>
<span>{{ loc.hours }}</span>
</div>
<div class="flex items-center gap-2 text-[#8b949e]">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 9.5a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.62 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/>
</svg>
<span>{{ loc.phone }}</span>
</div>
</div>
<!-- Distance + CTA -->
<div class="flex items-center gap-2 pt-1">
<span class="flex items-center gap-1.5 text-[11px] text-[#8b949e] bg-[#21262d] border border-[#30363d] rounded-lg px-2.5 py-1.5">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>
</svg>
{{ loc.distance }} away
</span>
<button class="flex-1 flex items-center justify-center gap-1.5 py-2 bg-[#58a6ff] rounded-xl text-[12px] font-bold text-white hover:bg-[#79b8ff] transition-colors">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polygon points="3 11 22 2 13 21 11 13 3 11"/>
</svg>
Get Directions
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
@keyframes pin-drop {
from { transform: translate(-50%, -150%) scale(0.5); opacity: 0; }
to { transform: translate(-50%, -100%) scale(1); opacity: 1; }
}
.pin-drop {
animation: pin-drop 0.6s cubic-bezier(0.34,1.56,0.64,1) both;
}
</style>Location card with a CSS map placeholder, animated drop-pin, address block with street/city/country, distance badge, and a directions CTA. Multiple variants — single location, multi-branch list.