<!-- Dotted Map Nederland · Bron: Magic UI · licentie MIT. Laat deze vermelding staan bij hergebruik. -->
<div class="jouw-container">
<div class="dtm"><svg class="dtm__map" role="img" aria-label="Kaart van Nederland met zes servicepunten"><g class="dtm__dots"></g><g class="dtm__pins"></g></svg><div class="dtm__side"><span class="dtm__eye">Servicepunten</span><b class="dtm__h">6 vestigingen</b><ul class="dtm__list"></ul></div></div>
</div>
/* Dotted Map Nederland · Bron: Magic UI · licentie MIT. Laat deze vermelding staan bij hergebruik. */
.dtm{display:flex;align-items:center;gap:14px;width:94%;height:92%;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a}
.dtm__map{flex:1 1 55%;height:100%;min-width:0}
.dtm__dots circle{fill:#cfc9c0}
.dtm__pin{cursor:pointer}
.dtm__pin .dtm__core{fill:#F5480F;stroke:#fff;stroke-width:1.2}
.dtm__pin .dtm__wave{fill:none;stroke:#F5480F;stroke-width:1.2;transform-box:fill-box;transform-origin:center;animation:dtm-p 2.2s ease-out infinite}
.dtm__pin.is-on .dtm__core{fill:#0a0a0a}
.dtm__pin.is-on .dtm__wave{stroke:#0a0a0a}
@keyframes dtm-p{0%{transform:scale(1);opacity:.9}100%{transform:scale(3.4);opacity:0}}
.dtm__side{flex:1 1 45%;min-width:0}
.dtm__eye{display:block;font:500 9px/1 'JetBrains Mono',monospace;letter-spacing:.14em;text-transform:uppercase;color:#F5480F}
.dtm__h{display:block;font-size:15px;margin:5px 0 8px}
.dtm__list{list-style:none;margin:0;padding:0}
.dtm__list li{display:flex;justify-content:space-between;gap:6px;font-size:12px;padding:3px 0;border-bottom:1px solid rgba(10,10,10,.08);cursor:default;transition:color .2s}
.dtm__list li span{font:500 10px/1.5 'JetBrains Mono',monospace;color:rgba(10,10,10,.5)}
.dtm__list li.is-on{color:#F5480F}
@media(prefers-reduced-motion:reduce){.dtm__pin .dtm__wave{animation:none;opacity:0}}
// Dotted Map Nederland · Bron: Magic UI · licentie MIT. Laat deze vermelding staan bij hergebruik.
// root = het element waarin de HTML staat
function init(root){
var w = root.querySelector('.dtm'); if (!w) return;
var svg = w.querySelector('.dtm__map'), gd = w.querySelector('.dtm__dots'), gp = w.querySelector('.dtm__pins'), ul = w.querySelector('.dtm__list');
var NS = 'http://www.w3.org/2000/svg';
var LAND = [
[[3.37,51.37],[3.44,51.54],[3.7,51.6],[3.85,51.83],[4.12,51.99],[4.27,52.11],[4.43,52.26],[4.57,52.46],[4.65,52.77],[4.74,52.96],[5.08,52.95],[5.42,53.18],[5.6,53.32],[5.9,53.39],[6.2,53.41],[6.7,53.46],[6.85,53.44],[6.93,53.33],[7.2,53.24],[7.21,53.15],[7.05,52.88],[7.06,52.64],[6.73,52.65],[6.71,52.49],[7.05,52.4],[7.05,52.24],[6.73,52.04],[6.83,51.97],[6.4,51.83],[5.97,51.85],[6.17,51.73],[6.22,51.37],[6.08,51.17],[5.87,51.05],[6.02,50.76],[5.69,50.76],[5.77,51.1],[5.56,51.25],[5.24,51.27],[5.05,51.47],[4.8,51.5],[4.55,51.43],[4.25,51.38],[4.24,51.33],[3.92,51.2],[3.53,51.25],[3.37,51.37]],
[[4.71,53.0],[4.73,53.19],[4.9,53.2],[4.86,53.03]],
[[4.95,53.25],[5.55,53.45],[5.62,53.41],[5.05,53.21]],
[[5.63,53.46],[5.96,53.48],[5.96,53.43],[5.63,53.42]],
[[6.1,53.49],[6.34,53.52],[6.33,53.47],[6.1,53.46]]
];
var HOLES = [
[[5.1,52.77],[5.04,52.92],[5.25,53.07],[5.4,52.98],[5.36,52.86],[5.62,52.82],[5.58,52.68],[5.45,52.55],[5.29,52.7]],
[[5.29,52.7],[5.45,52.55],[5.15,52.4],[5.05,52.4],[5.0,52.5],[5.1,52.62]],
[[3.5,51.4],[3.6,51.45],[4.0,51.44],[4.25,51.4],[4.2,51.34],[3.9,51.37],[3.6,51.36]],
[[3.7,51.6],[3.95,51.64],[4.2,51.56],[4.05,51.5],[3.8,51.54]]
];
function inside(p, poly) { var c = false; for (var i = 0, j = poly.length - 1; i < poly.length; j = i++) { var a = poly[i], b = poly[j]; if ((a[1] > p[1]) !== (b[1] > p[1]) && p[0] < (b[0] - a[0]) * (p[1] - a[1]) / (b[1] - a[1]) + a[0]) c = !c; } return c; }
var K = Math.cos(52.2 * Math.PI / 180), X0 = 3.3, Y1 = 53.6, STEP = .072;
function px(lon, lat) { return [(lon - X0) * K / STEP * 10, (Y1 - lat) / STEP * 10]; }
var maxX = 0, maxY = 0, frag = document.createDocumentFragment();
for (var lat = Y1; lat > 50.7; lat -= STEP) {
for (var lon = X0; lon < 7.3; lon += STEP / K) {
var p = [lon, lat], land = LAND.some(function (pl) { return inside(p, pl); }) && !HOLES.some(function (h) { return inside(p, h); });
if (!land) continue;
var q = px(lon, lat), c = document.createElementNS(NS, 'circle');
c.setAttribute('cx', q[0].toFixed(1)); c.setAttribute('cy', q[1].toFixed(1)); c.setAttribute('r', '3.3'); frag.appendChild(c);
maxX = Math.max(maxX, q[0]); maxY = Math.max(maxY, q[1]);
}
}
gd.appendChild(frag);
svg.setAttribute('viewBox', '-6 -6 ' + (maxX + 12).toFixed(0) + ' ' + (maxY + 12).toFixed(0));
var SITES = [['Amsterdam', 4.9, 52.37, '14 monteurs'], ['Rotterdam', 4.48, 51.92, '11 monteurs'], ['Utrecht', 5.12, 52.09, '9 monteurs'], ['Eindhoven', 5.47, 51.44, '7 monteurs'], ['Zwolle', 6.09, 52.51, '5 monteurs'], ['Groningen', 6.57, 53.22, '6 monteurs']];
var pins = [], lis = [];
SITES.forEach(function (s, i) {
var q = px(s[1], s[2]), g = document.createElementNS(NS, 'g'); g.setAttribute('class', 'dtm__pin'); g.setAttribute('transform', 'translate(' + q[0].toFixed(1) + ',' + q[1].toFixed(1) + ')');
var wv = document.createElementNS(NS, 'circle'); wv.setAttribute('r', '6'); wv.setAttribute('class', 'dtm__wave'); wv.style.animationDelay = (i * .35) + 's';
var co = document.createElementNS(NS, 'circle'); co.setAttribute('r', '7'); co.setAttribute('class', 'dtm__core');
var tt = document.createElementNS(NS, 'title'); tt.textContent = s[0] + ', ' + s[3];
g.appendChild(wv); g.appendChild(co); g.appendChild(tt); gp.appendChild(g); pins.push(g);
var li = document.createElement('li'); li.innerHTML = s[0] + '<span>' + s[3].split(' ')[0] + '</span>'; ul.appendChild(li); lis.push(li);
function on() { pins[i].classList.add('is-on'); lis[i].classList.add('is-on'); }
function off() { pins[i].classList.remove('is-on'); lis[i].classList.remove('is-on'); }
g.addEventListener('pointerenter', on); g.addEventListener('pointerleave', off); li.addEventListener('pointerenter', on); li.addEventListener('pointerleave', off);
});
}
init(document.querySelector('.jouw-container'));