Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.
<!-- Free Map Embed Card widget by haynes lab — https://hayneslab.dev/tools/widgets/map-embed-card -->
<script data-hl-lat="35.2271" data-hl-lon="-80.8431" data-hl-height="340" data-hl-title="Visit us in Uptown Charlotte" data-hl-caption="Tryon Street, Charlotte, NC — street parking and the 7th St light rail stop are a block away.">
/*!
* haynes lab — Map Embed Card widget (free)
* Docs & embed code: https://hayneslab.dev/tools/widgets/map-embed-card
*/
(function () {
'use strict';
var PREFIX = 'hlw-map-embed-card';
var STYLE_ID = 'hlw-map-embed-card-styles';
function findScript() {
return document.currentScript ||
document.querySelector('script[src*="widgets/map-embed-card.js"]');
}
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
var css =
'.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
'.' + PREFIX + ' { position: relative; max-width: 720px; margin: 0 auto; overflow: hidden;' +
' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
' font-size: 16px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
' border: 1px solid #e5e7eb; border-radius: 12px; }' +
'.' + PREFIX + '__title { padding: 16px 20px 12px; font-weight: 600; font-size: 17px;' +
' color: #111827; border-bottom: 1px solid #f3f4f6; }' +
'.' + PREFIX + '__frame { display: block; width: 100%; border: 0; }' +
'.' + PREFIX + '__caption { padding: 12px 20px 4px; font-size: 14px; color: #6b7280; }' +
'.' + PREFIX + '__footer { display: flex; align-items: center; justify-content: space-between;' +
' gap: 12px; padding: 10px 14px 12px; }' +
'.' + PREFIX + '__link { font-size: 13px; color: #4b5563; text-decoration: none; }' +
'.' + PREFIX + '__link:hover { color: #111827; text-decoration: underline; }' +
'.' + PREFIX + '__link:focus-visible { outline: 2px solid #f97316; outline-offset: 2px; }' +
'.' + PREFIX + '__credit { font-size: 11px; display: inline-flex; align-items: center; gap: 4px;' +
' color: #9ca3af; text-decoration: none; white-space: nowrap; }' +
'.' + PREFIX + '__credit svg { display: block; }' +
'.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }' +
'@media (max-width: 560px) { .' + PREFIX + '__footer { flex-direction: column; align-items: flex-start; gap: 6px; } }';
var style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = css;
document.head.appendChild(style);
}
function el(tag, className, text) {
var node = document.createElement(tag);
if (className) node.className = className;
if (text != null) node.textContent = text;
return node;
}
function logoSvg() {
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('viewBox', '0 0 32 32');
svg.setAttribute('width', '13');
svg.setAttribute('height', '13');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
function circle(cx, cy, r, attrs) {
var c = document.createElementNS(svgNS, 'circle');
c.setAttribute('cx', cx);
c.setAttribute('cy', cy);
c.setAttribute('r', r);
for (var key in attrs) c.setAttribute(key, attrs[key]);
svg.appendChild(c);
}
circle(16, 16, 16, { fill: '#0f0a0a' });
circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
circle(16, 16, 3, { fill: '#06b6d4' });
circle(24.5, 7.5, 2, { fill: '#f97316' });
circle(7, 16, 1.8, { fill: '#f97316' });
circle(20, 20, 1.5, { fill: '#f97316' });
return svg;
}
function clamp(n, min, max) {
return Math.min(max, Math.max(min, n));
}
function build(script) {
var lat = parseFloat(script.getAttribute('data-hl-lat'));
var lon = parseFloat(script.getAttribute('data-hl-lon'));
if (isNaN(lat) || isNaN(lon) || lat < -90 || lat > 90 || lon < -180 || lon > 180) {
console.warn('[haynes lab map-embed-card] data-hl-lat and data-hl-lon are required and must be valid coordinates. Nothing rendered.');
return;
}
var zoom = parseInt(script.getAttribute('data-hl-zoom'), 10);
if (isNaN(zoom)) zoom = 14;
zoom = clamp(zoom, 2, 19);
var height = parseInt(script.getAttribute('data-hl-height'), 10);
if (isNaN(height)) height = 360;
height = clamp(height, 200, 1200);
var title = script.getAttribute('data-hl-title');
var caption = script.getAttribute('data-hl-caption');
// Approximate the visible lat/lon span for a web-mercator map at this zoom.
var scale = 360 / (256 * Math.pow(2, zoom)); // degrees per pixel
var deltaLon = scale * 640;
var deltaLat = scale * height * Math.cos(lat * Math.PI / 180);
var bbox = [
clamp(lon - deltaLon / 2, -180, 180),
clamp(lat - deltaLat / 2, -85, 85),
clamp(lon + deltaLon / 2, -180, 180),
clamp(lat + deltaLat / 2, -85, 85),
].map(function (n) { return n.toFixed(6); }).join(',');
injectStyles();
var root = el('div', PREFIX);
if (title) root.appendChild(el('div', PREFIX + '__title', title));
var frame = el('iframe', PREFIX + '__frame');
frame.src = 'https://www.openstreetmap.org/export/embed.html?bbox=' +
encodeURIComponent(bbox) + '&layer=mapnik&marker=' +
encodeURIComponent(lat + ',' + lon);
frame.style.height = height + 'px';
frame.setAttribute('loading', 'lazy');
frame.setAttribute('title', title ? 'Map of ' + title : 'Map');
root.appendChild(frame);
if (caption) root.appendChild(el('p', PREFIX + '__caption', caption));
var footer = el('div', PREFIX + '__footer');
var link = el('a', PREFIX + '__link', 'View larger map');
link.href = 'https://www.openstreetmap.org/?mlat=' + encodeURIComponent(lat) +
'&mlon=' + encodeURIComponent(lon) + '#map=' + zoom + '/' + lat + '/' + lon;
link.target = '_blank';
link.rel = 'noopener';
footer.appendChild(link);
var credit = el('a', PREFIX + '__credit');
credit.appendChild(logoSvg());
credit.appendChild(document.createTextNode('Powered by haynes lab'));
credit.href = 'https://hayneslab.dev/tools/widgets/map-embed-card';
credit.target = '_blank';
credit.rel = 'noopener';
footer.appendChild(credit);
root.appendChild(footer);
if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
script.insertAdjacentElement('afterend', root);
} else {
document.body.appendChild(root);
}
}
function init() {
var script = findScript();
if (!script) {
console.warn('[haynes lab map-embed-card] Could not locate its own script tag. Nothing rendered.');
return;
}
build(script);
}
if (document.body) {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();
</script>