Ballenbak met klussen New
Jobs as balls in a ball pit: throw them into the basket with mouse or finger, or tap one (Enter works too) and it flies in or back out by itself. The button counts along. A playful intake on the homepage of a handyman or painter instead of a dull checkbox list. With reduced motion the balls move into or out of the basket without a flight.
Wat moet er gebeuren?
Offerte voor 0 klussenGooi je klussen in de mand. Tik of sleep.
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.
<!-- Ballenbak met klussen · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie -->
<div class="jouw-container">
<div class="fyb">
<div class="fyb__tekst"><div class="fyb__rij"><h3 class="fyb__kop">Wat moet er gebeuren?</h3><a class="fyb__cta" href="#fy-ballenbak">Offerte voor 0 klussen</a></div>
<p class="fyb__st">Gooi je klussen in de mand. Tik of sleep.</p></div>
<div class="fyb__mand" aria-hidden="true"></div>
<div class="fyb__ballen" role="group" aria-label="Klussen, tik om in of uit de mand te gooien">
<button class="fyb__bal" type="button" aria-pressed="false">verven</button>
<button class="fyb__bal" type="button" aria-pressed="false">goot</button>
<button class="fyb__bal" type="button" aria-pressed="false">tegels</button>
<button class="fyb__bal" type="button" aria-pressed="false">kozijn</button>
<button class="fyb__bal" type="button" aria-pressed="false">lekkage</button>
<button class="fyb__bal" type="button" aria-pressed="false">behang</button>
</div>
</div>
</div>/* Ballenbak met klussen · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie */
.fyb{position:absolute;inset:0;overflow:hidden;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',sans-serif;text-align:left}
.fyb *{box-sizing:border-box}
.fyb__tekst{position:absolute;left:14px;top:12px;right:12px;display:flex;flex-direction:column;gap:4px;z-index:3;pointer-events:none}
.fyb__rij{display:flex;align-items:center;justify-content:space-between;gap:10px}
.fyb__kop{margin:0;font:700 17px/1.15 'Switzer',sans-serif;letter-spacing:-.01em;z-index:2;pointer-events:none}
.fyb__st{margin:0;font:400 13px/1.35 'Switzer',sans-serif;color:#3d3b37;z-index:2;pointer-events:none}
.fyb__cta{flex:0 0 auto;pointer-events:auto;padding:9px 12px;background:#0a0a0a;color:#fff;border-radius:4px;font:600 13px/1 'Switzer',sans-serif;text-decoration:none;transition:background-color .16s}
.fyb__cta:hover{background:#F5480F;color:#0a0a0a}
.fyb__cta:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.fyb__mand{position:absolute;right:0;bottom:0;left:52%;top:50%;border-left:4px solid #0a0a0a;background:repeating-linear-gradient(90deg,rgba(10,10,10,.06) 0 2px,transparent 2px 14px)}
.fyb__ballen{position:absolute;inset:0}
.fyb__bal{position:absolute;left:0;top:0;width:56px;height:56px;margin:0;padding:0;border:0;border-radius:50%;background:#fff;color:#0a0a0a;font:600 12px/1 'Switzer',sans-serif;cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none;box-shadow:inset 0 -4px 0 rgba(10,10,10,.12);will-change:transform;z-index:1}
.fyb__bal:nth-child(3n+1){background:#F5480F;color:#0a0a0a}
.fyb__bal:nth-child(3n+2){background:#0a0a0a;color:#fff;box-shadow:inset 0 -4px 0 rgba(255,255,255,.14)}
.fyb__bal:active{cursor:grabbing}
.fyb__bal:focus-visible{outline:3px solid #0a0a0a;outline-offset:2px}
.fyb__bal:nth-child(3n+2):focus-visible{outline-color:#F5480F}// Ballenbak met klussen · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie
// root = het element waarin de HTML staat
function init(root){
var el=root.querySelector('.fyb');if(!el)return;
var RM=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var mand=el.querySelector('.fyb__mand'),st=el.querySelector('.fyb__st'),cta=el.querySelector('.fyb__cta');
var B=[].slice.call(el.querySelectorAll('.fyb__bal')).map(function(n){return{n:n,x:0,y:0,vx:0,vy:0,m:false,drag:false,moved:0,dragged:false,lt:0,ox:0,oy:0};});
var W=1,H=1,R=28,wx=0,rim=0,WT=4,G=1600,raf=0,vis=true,last=0,klaar=false;
function size(){var r=el.getBoundingClientRect();if(!r.width)return;W=r.width;H=r.height;R=Math.max(25,Math.min(36,W*.075));wx=Math.round(W*.52);rim=Math.round(H*.5);mand.style.left=wx+'px';mand.style.top=rim+'px';
B.forEach(function(b){b.n.style.width=b.n.style.height=(2*R)+'px';b.x=Math.min(Math.max(b.x,R),W-R);b.y=Math.min(Math.max(b.y,R),H-R);});}
function draw(){B.forEach(function(b){b.n.style.transform='translate('+(b.x-R).toFixed(1)+'px,'+(b.y-R).toFixed(1)+'px)';});}
function upd(){var nm=[];B.forEach(function(b){var m=b.x>wx+WT&&b.y>rim-R*.2;if(m!==b.m){b.m=m;b.n.setAttribute('aria-pressed',m?'true':'false');}if(b.m)nm.push(b.n.textContent);});
var t=nm.length?'In de mand: '+nm.join(', ')+'.':'Gooi je klussen in de mand. Tik of sleep.';if(st.textContent!==t)st.textContent=t;
var c='Offerte voor '+nm.length+(nm.length===1?' klus':' klussen');if(cta.textContent!==c)cta.textContent=c;}
function layout(){var oi=0,ii=0,d=2*R+4;B.forEach(function(b){if(b.m){var p=Math.max(1,Math.floor((W-wx-WT)/d));b.x=wx+WT+R+2+(ii%p)*d;b.y=H-R-Math.floor(ii/p)*2*R;ii++;}else{var q=Math.max(1,Math.floor((wx-4)/d));b.x=R+4+(oi%q)*d;b.y=H-R-Math.floor(oi/q)*2*R;oi++;}b.vx=b.vy=0;});draw();}
function step(dt){var i,j,b;
for(i=0;i<B.length;i++){b=B[i];if(b.drag)continue;b.vy+=G*dt;b.vx*=.999;b.x+=b.vx*dt;b.y+=b.vy*dt;}
for(var it=0;it<3;it++){
for(i=0;i<B.length;i++)for(j=i+1;j<B.length;j++){var a=B[i],c=B[j],dx=c.x-a.x,dy=c.y-a.y,d2=dx*dx+dy*dy,mn=2*R;if(d2>=mn*mn||d2===0)continue;if((a.vlucht&&a.vy<0)||(c.vlucht&&c.vy<0))continue;
var d=Math.sqrt(d2),nx=dx/d,ny=dy/d,ov=mn-d,wa=a.drag?0:(c.drag?1:.5),wc=c.drag?0:(a.drag?1:.5);
a.x-=nx*ov*wa;a.y-=ny*ov*wa;c.x+=nx*ov*wc;c.y+=ny*ov*wc;var rv=(c.vx-a.vx)*nx+(c.vy-a.vy)*ny;if(rv<0){var jj=-1.3*rv;a.vx-=nx*jj*wa;a.vy-=ny*jj*wa;c.vx+=nx*jj*wc;c.vy+=ny*jj*wc;}}
for(i=0;i<B.length;i++){b=B[i];if(b.drag)continue;
if(b.x<R){b.x=R;b.vx=Math.abs(b.vx)*.5;}if(b.x>W-R){b.x=W-R;b.vx=-Math.abs(b.vx)*.5;}if(b.y<R){b.y=R;b.vy=Math.abs(b.vy)*.4;}
if(b.y>H-R){b.y=H-R;if(b.vy>0)b.vy=-b.vy*.32;if(Math.abs(b.vy)<45)b.vy=0;b.vx*=.97;if(Math.abs(b.vx)<6)b.vx=0;}
if(b.vlucht&&(performance.now()>b.vlucht||b.y>=H-R-1))b.vlucht=0;
if(!(b.vlucht&&b.y<rim+R*.6)){var cx=Math.max(wx,Math.min(b.x,wx+WT)),cy=Math.max(rim,Math.min(b.y,H)),ex=b.x-cx,ey=b.y-cy,e2=ex*ex+ey*ey;
if(e2<R*R){var nx2,ny2,pen;if(e2<1e-6){nx2=b.x<wx+WT/2?-1:1;ny2=0;pen=R;}else{var e=Math.sqrt(e2);nx2=ex/e;ny2=ey/e;pen=R-e;}b.x+=nx2*pen;b.y+=ny2*pen;var vn=b.vx*nx2+b.vy*ny2;if(vn<0){b.vx-=1.45*vn*nx2;b.vy-=1.45*vn*ny2;}}}}}}
function tick(t){if(!vis){raf=0;return;}var dt=Math.min(.033,(t-last)/1000);last=t;for(var k=0;k<3;k++)step(dt/3);draw();upd();raf=requestAnimationFrame(tick);}
function kick(){if(RM||raf)return;last=performance.now();raf=requestAnimationFrame(tick);}
function toss(b){if(RM){b.m=!b.m;layout();upd();return;}
var top=Math.max(R+2,rim-R-16),h=Math.max(30,b.y-top),vy0=-Math.sqrt(2*G*h),ta=-vy0/G,
tx=b.m?wx-R*.6-Math.random()*R:wx+WT+R*.6+Math.random()*R*.8;
b.vy=vy0;b.vx=(tx-b.x)/ta;b.vlucht=performance.now()+1600;kick();}
function pos(e,b){var r=el.getBoundingClientRect();return{x:Math.min(Math.max(e.clientX-r.left-b.ox,R),W-R),y:Math.min(Math.max(e.clientY-r.top-b.oy,R),H-R)};}
B.forEach(function(b){var n=b.n;
n.addEventListener('pointerdown',function(e){if(RM||e.button>0)return;var r=el.getBoundingClientRect();b.ox=e.clientX-r.left-b.x;b.oy=e.clientY-r.top-b.y;b.drag=true;b.moved=0;b.vx=b.vy=0;b.lt=performance.now();try{n.setPointerCapture(e.pointerId);}catch(_){}n.style.zIndex=2;kick();});
n.addEventListener('pointermove',function(e){if(!b.drag)return;var p=pos(e,b),now=performance.now(),dt=Math.max(.008,(now-b.lt)/1000);b.moved+=Math.abs(p.x-b.x)+Math.abs(p.y-b.y);b.vx=b.vx*.4+(p.x-b.x)/dt*.6;b.vy=b.vy*.4+(p.y-b.y)/dt*.6;b.x=p.x;b.y=p.y;b.lt=now;});
function up(){if(!b.drag)return;b.drag=false;n.style.zIndex='';if(b.moved>6){b.dragged=true;if(performance.now()-b.lt>90){b.vx=b.vy=0;}var s=Math.hypot(b.vx,b.vy);if(s>2400){b.vx*=2400/s;b.vy*=2400/s;}}else{b.vx=b.vy=0;}}
n.addEventListener('pointerup',up);n.addEventListener('pointercancel',up);
n.addEventListener('click',function(){if(b.dragged){b.dragged=false;return;}toss(b);});});
function start(){size();if(klaar)return;klaar=true;
if(RM){layout();upd();return;}
B.forEach(function(b,i){b.x=R+8+(i%3)*(2*R+12);b.y=R+8+Math.floor(i/3)*(2*R+4);b.vx=(Math.random()-.5)*120;b.vy=0;});draw();kick();}
if('ResizeObserver' in window)new ResizeObserver(function(){var w=W;size();if(RM&&klaar)layout();else if(w!==W)draw();}).observe(el);
if('IntersectionObserver' in window)new IntersectionObserver(function(es){vis=es[0].isIntersecting;if(vis)kick();}).observe(el);
start();
}
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 →