Shop de look New
Points on a living room mood photo; a click opens a small product card next to the point with price and an add-to-basket button, a bar at the bottom keeps count and adds the whole look in one go. Arrow keys jump between points, Esc closes. The photo covers the tile and the points follow, so they stay on the sofa and the lamp. For home, fashion and garden webshops; the wide explainer version for technical products is product-hotspots.
How to use it
- Paste the HTML where the components should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Shop de look · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="sdl">
<div class="sdl__pic">
<img class="sdl__img" src="https://neddev.com/lab-media/shop-de-look-1.webp" alt="Lichte woonkamer met grijze bank, ronde eiken salontafel, messing vloerlamp en een vijgenboom" width="1200" height="655">
<button class="sdl__pt" type="button" style="--x:37%;--y:57%" data-i="0" aria-expanded="false" aria-label="Bank Lund">
</button><button class="sdl__pt" type="button" style="--x:53%;--y:76%" data-i="1" aria-expanded="false" aria-label="Salontafel Ronde Eik">
</button><button class="sdl__pt" type="button" style="--x:72%;--y:38%" data-i="2" aria-expanded="false" aria-label="Vloerlamp Mira">
</button><button class="sdl__pt" type="button" style="--x:27%;--y:44%" data-i="3" aria-expanded="false" aria-label="Vijgenboom in pot"></button>
</div>
<div class="sdl__card" role="dialog" aria-label="Product" hidden>
<b class="sdl__nm"></b><span class="sdl__me"></span>
<div class="sdl__row"><span class="sdl__pr"></span><button class="sdl__add" type="button" aria-pressed="false">In mandje</button></div>
</div>
<div class="sdl__bar"><span class="sdl__cnt" aria-live="polite">0 van 4 in je mandje</span><button class="sdl__all" type="button">Hele look · € 2.016</button></div>
</div>
</div>/* Shop de look · NedDev · eigen werk. */
.sdl{position:absolute;inset:0;overflow:hidden;background:#0a0a0a;color:#0a0a0a;font-family:'Switzer',sans-serif;text-align:left;container-type:size}
.sdl__pic{position:absolute;left:50%;top:44%;width:max(100cqw,calc(100cqh * 1.832));aspect-ratio:1200/655;transform:translate(-50%,-50%)}
.sdl__img{display:block;width:100%;height:100%}
.sdl__pt{position:absolute;left:var(--x);top:var(--y);width:26px;height:26px;margin:-13px 0 0 -13px;padding:0;border:2px solid #fff;border-radius:50%;background:rgba(10,10,10,.35);cursor:pointer;display:grid;place-items:center}
.sdl__pt::before{content:"";width:10px;height:10px;border-radius:50%;background:#fff;transition:background .2s,transform .2s}
.sdl__pt::after{content:"";position:absolute;inset:-2px;border:2px solid #fff;border-radius:50%;animation:sdl-ring 2.4s ease-out infinite;pointer-events:none}
.sdl__pt:hover::before,.sdl__pt[aria-expanded="true"]::before{background:#F5480F;transform:scale(1.2)}
.sdl__pt.is-in::before{background:#F5480F}
.sdl__pt:focus-visible{outline:2px solid #F5480F;outline-offset:3px}
@keyframes sdl-ring{0%{transform:scale(1);opacity:.8}80%,100%{transform:scale(1.9);opacity:0}}
.sdl__card{position:absolute;width:172px;padding:10px 12px;background:#fff;border-radius:8px;box-shadow:0 8px 24px rgba(10,10,10,.18);display:flex;flex-direction:column;gap:2px}
.sdl__card[hidden]{display:none}
.sdl__nm{font:600 14px/1.25 'Switzer',sans-serif}
.sdl__me{font:400 12px/1.35 'Switzer',sans-serif;color:#5c5a57}
.sdl__row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:6px}
.sdl__pr{font:600 14px/1 'Switzer',sans-serif;font-variant-numeric:tabular-nums}
.sdl__add{min-height:28px;padding:0 10px;border:0;border-radius:4px;background:#0a0a0a;color:#fff;font:500 12px/1 'Switzer',sans-serif;cursor:pointer;white-space:nowrap}
.sdl__add[aria-pressed="true"]{background:#F5480F}
.sdl__add:focus-visible,.sdl__all:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.sdl__bar{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px 8px 14px;background:#fff;border-top:1px solid rgba(10,10,10,.08)}
.sdl__cnt{font:500 13px/1.2 'Switzer',sans-serif}
.sdl__all{min-height:30px;padding:0 12px;border:0;border-radius:4px;background:#F5480F;color:#fff;font:600 13px/1 'Switzer',sans-serif;cursor:pointer;white-space:nowrap}
.sdl__all:disabled{background:#0a0a0a;cursor:default}
@container (max-width:360px){.sdl__cnt{font-size:12px}.sdl__all{padding:0 10px;font-size:12px}.sdl__card{width:156px}}
@media (prefers-reduced-motion:reduce){.sdl__pt::after{animation:none;opacity:0}.sdl__pt::before{transition:none}}// Shop de look · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.sdl');if(!w)return;
var pts=[].slice.call(w.querySelectorAll('.sdl__pt')),card=w.querySelector('.sdl__card'),nm=w.querySelector('.sdl__nm'),me=w.querySelector('.sdl__me'),pr=w.querySelector('.sdl__pr'),add=w.querySelector('.sdl__add'),cnt=w.querySelector('.sdl__cnt'),all=w.querySelector('.sdl__all'),bar=w.querySelector('.sdl__bar');
var P=[['Bank Lund','3-zits, lichtgrijs, 214 cm breed',1299],['Salontafel Ronde Eik','Massief eiken, doorsnede 90 cm',449],['Vloerlamp Mira','Messing, hoogte 158 cm',189],['Vijgenboom in pot','Ficus lyrata, 120 cm, met pot',79]];
var inB=[false,false,false,false],open=-1;
function eur(n){return '€ '+String(n).replace(/\B(?=(\d{3})+(?!\d))/g,'.');}
function count(){var n=inB.filter(Boolean).length,rest=0;P.forEach(function(p,i){if(!inB[i])rest+=p[2];});cnt.textContent=n+' van 4 in je mandje';
all.disabled=n===4;all.textContent=n===4?'Hele look in je mandje':(n?'Rest toevoegen · '+eur(rest):'Hele look · '+eur(rest));pts.forEach(function(b,i){b.classList.toggle('is-in',inB[i]);});}
function place(i){var wr=w.getBoundingClientRect(),br=pts[i].getBoundingClientRect(),cw=card.offsetWidth,ch=card.offsetHeight,bh=bar.offsetHeight;
var x=br.left-wr.left+br.width/2,y=br.top-wr.top+br.height/2,left=x>wr.width*.5?x-cw-18:x+18;left=Math.max(8,Math.min(wr.width-cw-8,left));
var top=Math.max(8,Math.min(wr.height-bh-ch-8,y-ch/2));card.style.left=left+'px';card.style.top=top+'px';}
function show(i){open=i;var p=P[i];nm.textContent=p[0];me.textContent=p[1];pr.textContent=eur(p[2]);card.setAttribute('aria-label',p[0]);
add.setAttribute('aria-pressed',inB[i]?'true':'false');add.textContent=inB[i]?'In je mandje':'In mandje';card.hidden=false;
pts.forEach(function(b,k){b.setAttribute('aria-expanded',k===i?'true':'false');});place(i);}
function hide(focusBack){if(open<0)return;var i=open;open=-1;card.hidden=true;pts.forEach(function(b){b.setAttribute('aria-expanded','false');});if(focusBack)pts[i].focus();}
pts.forEach(function(b,i){b.addEventListener('click',function(e){e.stopPropagation();if(open===i)hide(false);else show(i);});
b.addEventListener('keydown',function(e){var k=e.key,n=-1;if(k==='ArrowRight'||k==='ArrowDown')n=(i+1)%pts.length;else if(k==='ArrowLeft'||k==='ArrowUp')n=(i-1+pts.length)%pts.length;if(n<0)return;e.preventDefault();pts[n].focus();show(n);});});
add.addEventListener('click',function(){if(open<0)return;inB[open]=!inB[open];show(open);count();});
all.addEventListener('click',function(){inB=[true,true,true,true];count();if(open>=0)show(open);});
w.addEventListener('keydown',function(e){if(e.key==='Escape'&&open>=0){e.preventDefault();hide(true);}});
w.addEventListener('click',function(e){if(open>=0&&!card.contains(e.target)&&!e.target.closest('.sdl__pt'))hide(false);});
if('ResizeObserver' in window)new ResizeObserver(function(){if(open>=0)place(open);}).observe(w);
count();
}
init(document.querySelector('.jouw-container'));
More components
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →