Interactive Map

Map supporting pan, zoom and selectable markers.

Studio HQ
Riverside Cafe

html

<div class="imap">
  <div class="imap__viewport">
    <div class="imap__layer" style="transform:scale(100%);">
      <details class="imap__marker" style="left:30%;top:38%;">
        <summary class="imap__pin" style="background:#241430;" aria-label="Marker: Studio HQ"></summary>
        <div class="imap__popover">Studio HQ</div>
        
      </details>
      <details class="imap__marker" style="left:64%;top:56%;">
        <summary class="imap__pin" style="background:#241430;" aria-label="Marker: Riverside Cafe"></summary>
        <div class="imap__popover">Riverside Cafe</div>
        
      </details>
    </div>
  </div>
</div>
<style>
.imap { max-width: 360px; font: 12px/1.4 system-ui, sans-serif; color: #241430; }
.imap__viewport { position: relative; aspect-ratio: 4 / 3; overflow: hidden; border-radius: 12px; border: 1px solid #24143022; background: linear-gradient(#D9C8F0, #BFD9F2); cursor: grab; }
.imap__layer { position: absolute; inset: 0; transform-origin: center; background-image: repeating-linear-gradient(0deg, #24143014 0 1px, transparent 1px 40px), repeating-linear-gradient(90deg, #24143014 0 1px, transparent 1px 40px); }
.imap__marker { position: absolute; list-style: none; }
.imap__pin { display: block; width: 18px; height: 18px; border-radius: 50% 50% 50% 0; transform: translate(-50%, -100%) rotate(-45deg); cursor: pointer; border: 2px solid #FAF7F2; list-style: none; }
.imap__pin::marker { content: ""; }
.imap__pin::-webkit-details-marker { display: none; }
.imap__pin:focus-visible { outline: 2px solid #241430; outline-offset: 2px; }
.imap__popover { position: absolute; left: 0; bottom: calc(100% + 6px); padding: 4px 8px; border-radius: 6px; background: #241430; color: #FAF7F2; font-size: 11px; white-space: nowrap; }
.imap__perm-label { position: absolute; top: 2px; left: 22px; font-size: 10px; color: #241430cc; white-space: nowrap; }
@media (prefers-reduced-motion: no-preference) {
  .imap__layer { transition: transform .2s ease; }
}
</style>

tailwind

<div class="imap-tw max-w-[360px] font-sans text-xs text-[#241430]">
  <div class="imap-tw__viewport relative overflow-hidden rounded-xl border border-[#24143022] cursor-grab" style="aspect-ratio:4/3;background:linear-gradient(#D9C8F0,#BFD9F2);">
    <div class="imap-tw__layer absolute inset-0" style="transform:scale(100%);transform-origin:center;background-image:repeating-linear-gradient(0deg,#24143014 0 1px,transparent 1px 40px),repeating-linear-gradient(90deg,#24143014 0 1px,transparent 1px 40px);">
      <details class="imap-tw__marker absolute list-none" style="left:30%;top:38%;">
        <summary class="imap-tw__pin block w-[18px] h-[18px] cursor-pointer border-2 border-[#FAF7F2]" style="background:#241430;border-radius:50% 50% 50% 0;transform:translate(-50%,-100%) rotate(-45deg);" aria-label="Marker: Studio HQ"></summary>
        <div class="absolute left-0 px-2 py-1 rounded-md bg-[#241430] text-[#FAF7F2] text-[11px] whitespace-nowrap" style="bottom:calc(100% + 6px);">Studio HQ</div>
        
      </details>
      <details class="imap-tw__marker absolute list-none" style="left:64%;top:56%;">
        <summary class="imap-tw__pin block w-[18px] h-[18px] cursor-pointer border-2 border-[#FAF7F2]" style="background:#241430;border-radius:50% 50% 50% 0;transform:translate(-50%,-100%) rotate(-45deg);" aria-label="Marker: Riverside Cafe"></summary>
        <div class="absolute left-0 px-2 py-1 rounded-md bg-[#241430] text-[#FAF7F2] text-[11px] whitespace-nowrap" style="bottom:calc(100% + 6px);">Riverside Cafe</div>
        
      </details>
    </div>
  </div>
</div>
<style>
.imap-tw__pin::-webkit-details-marker { display: none; }
.imap-tw__pin::marker { content: ""; }
.imap-tw__pin:focus-visible { outline: 2px solid #241430; outline-offset: 2px; }
</style>

react

export function InteractiveMap({
  zoom = "100%",
  markerLabel = "Studio HQ",
  accent = "#241430",
  showLabels = false,
}) {
  const markers = [
    { left: "30%", top: "38%", label: markerLabel },
    { left: "64%", top: "56%", label: "Riverside Cafe" },
  ];
  return (
    <div style={{ maxWidth: 360, font: "12px/1.4 system-ui, sans-serif", color: "#241430" }}>
      <div style={{ position: "relative", aspectRatio: "4 / 3", overflow: "hidden", borderRadius: 12,
        border: "1px solid #24143022", background: "linear-gradient(#D9C8F0, #BFD9F2)", cursor: "grab" }}>
        <div className="map-content" style={{ position: "absolute", inset: 0, transformOrigin: "center",
          transform: `scale(${zoom})`,
          backgroundImage: "repeating-linear-gradient(0deg, #24143014 0 1px, transparent 1px 40px), repeating-linear-gradient(90deg, #24143014 0 1px, transparent 1px 40px)" }}>
          {markers.map((m) => (
            <details key={m.label} style={{ position: "absolute", left: m.left, top: m.top, listStyle: "none" }}>
              <summary
                style={{ display: "block", width: 18, height: 18, borderRadius: "50% 50% 50% 0",
                  transform: "translate(-50%, -100%) rotate(-45deg)", cursor: "pointer", border: "2px solid #FAF7F2",
                  background: accent, listStyle: "none" }}
                aria-label={`Marker: ${m.label}`}
              />
              <div style={{ position: "absolute", left: 0, bottom: "calc(100% + 6px)", padding: "4px 8px",
                borderRadius: 6, background: "#241430", color: "#FAF7F2", fontSize: 11, whiteSpace: "nowrap" }}>
                {m.label}
              </div>
              {showLabels && (
                <span style={{ position: "absolute", top: 2, left: 22, fontSize: 10, color: "#241430cc", whiteSpace: "nowrap" }}>
                  {m.label}
                </span>
              )}
            </details>
          ))}
        </div>
      </div>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          .map-content { transition: transform .2s ease; }
        }
      `}</style>
    </div>
  );
}