Optimized by Noetis(function(){
var nav=document.getElementById('on-nav');
if(nav) window.addEventListener('scroll',function(){nav.classList.toggle('scrolled',window.scrollY>40)},{passive:true});
if(window.self!==window.top){
document.querySelectorAll('.on-rv').forEach(function(el){el.classList.add('in')});
var builderNav=document.getElementById('on-nav');
if(builderNav){builderNav.style.position='relative';builderNav.style.top='auto';builderNav.style.left='auto';builderNav.style.right='auto'}
document.querySelectorAll('.on-page').forEach(function(el){el.style.paddingTop='0'});
}else{
var io=new IntersectionObserver(function(entries){
entries.forEach(function(e){
if(!e.isIntersecting) return;
e.target.classList.remove('on-will');
e.target.classList.add('in');
io.unobserve(e.target);
});
},{threshold:.08});
// Perf: only HIDE (.on-will) elements below the fold — above-the-fold content
// stays painted so it can't delay LCP waiting on JS.
var vhR=window.innerHeight||document.documentElement.clientHeight||800;
document.querySelectorAll('.on-rv').forEach(function(el){
var r=el.getBoundingClientRect();
if(r.top-40){el.classList.add('in');}
else{el.classList.add('on-will');io.observe(el);}
});
}
// Build the mobile drawer + overlay LAZILY on first open (post-hydration)
// instead of at HTML-parse time — appending to before React hydrates
// the /s/ render path caused a hydration mismatch (React #418). The guard
// makes this idempotent if the block is injected/executed twice.
function onEnsureMobDrawer(){
if(document.getElementById('on-mob-drawer')) return;
var over=document.createElement('div');
over.className='on-mob-overlay';over.id='on-mob-over';
var drw=document.createElement('nav');
drw.id='on-mob-drawer';
drw.innerHTML=[['Home','/'],['Werk','/work'],['Diensten','/services'],['Prijzen','/pricing'],['Studio','/studio'],['Contact','/contact']].map(function(p){
return ''+p[0]+'';
}).join('');
document.body.appendChild(over);document.body.appendChild(drw);
}
window.onMenu=function(){
onEnsureMobDrawer();
var btn=document.getElementById('on-mob-btn');
var drw=document.getElementById('on-mob-drawer');
var ov=document.getElementById('on-mob-over');
if(!drw||!ov) return;
var opening=!drw.classList.contains('open');
drw.classList.toggle('open',opening);
ov.classList.toggle('open',opening);
if(btn) btn.classList.toggle('open',opening);
document.body.style.overflow=opening?'hidden':'';
};
// Countup — HTML carries the FINAL value (renderer parity + no-JS safe);
// the live site animates from 0 up to it. Skipped in the builder iframe.
if(window.self===window.top&&typeof IntersectionObserver!=='undefined'){
var cio=new IntersectionObserver(function(entries){
entries.forEach(function(en){
if(!en.isIntersecting) return;
cio.unobserve(en.target);
var el=en.target,fin=el.getAttribute('data-on-count')||el.textContent;
var num=parseInt(fin,10)||0,t0=null;
function step(ts){
if(!t0)t0=ts;
var p=Math.min(1,(ts-t0)/1000);p=1-Math.pow(1-p,3);
el.textContent=String(Math.round(num*p));
if(p<1)requestAnimationFrame(step);else el.textContent=fin;
}
requestAnimationFrame(step);
});
},{threshold:.4});
document.querySelectorAll('[data-on-count]').forEach(function(el){cio.observe(el)});
}
// Work filter tabs — hide via display none on the wrapper (never opacity)
function onFilter(cat){
document.querySelectorAll('[data-on-cat]').forEach(function(w){
w.style.display=(cat==='all'||w.getAttribute('data-on-cat')===cat)?'':'none';
});
}
// Delegated clicks — inline onclick attrs are stripped on canvas conversion
document.addEventListener('click',function(e){
if(!e.target||!e.target.closest) return;
var el;
el=e.target.closest('#on-mob-btn'); if(el){onMenu();return;}
el=e.target.closest('#on-mob-over'); if(el){onMenu();return;}
el=e.target.closest('[data-on-tab]');if(el){
document.querySelectorAll('[data-on-tab]').forEach(function(t){t.classList.remove('act')});
el.classList.add('act');
onFilter(el.getAttribute('data-on-tab'));
return;
}
});
// Self-hosted background videos: force muted (SSR can drop the attribute) and
// call play() — Safari reliably autoplays muted HTML5 video this way.
function ffPlay(v){ try{ v.muted=true; v.setAttribute('muted',''); var p=v.play(); if(p&&p.catch)p.catch(function(){}); }catch(e){} }
document.querySelectorAll('video[autoplay]').forEach(function(v){
ffPlay(v);
v.addEventListener('canplay',function(){ffPlay(v)},{once:true});
if('IntersectionObserver' in window){
new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting)ffPlay(e.target)})},{threshold:.1}).observe(v);
}
});
})();(function(){
var nav=document.getElementById('on-nav');
if(nav) window.addEventListener('scroll',function(){nav.classList.toggle('scrolled',window.scrollY>40)},{passive:true});
if(window.self!==window.top){
document.querySelectorAll('.on-rv').forEach(function(el){el.classList.add('in')});
var builderNav=document.getElementById('on-nav');
if(builderNav){builderNav.style.position='relative';builderNav.style.top='auto';builderNav.style.left='auto';builderNav.style.right='auto'}
document.querySelectorAll('.on-page').forEach(function(el){el.style.paddingTop='0'});
}else{
document.querySelectorAll('.on-rv').forEach(function(el){el.classList.add('on-will')});
var io=new IntersectionObserver(function(entries){
entries.forEach(function(e){
if(!e.isIntersecting) return;
e.target.classList.remove('on-will');
e.target.classList.add('in');
io.unobserve(e.target);
});
},{threshold:.08});
document.querySelectorAll('.on-rv').forEach(function(el){io.observe(el)});
}
(function(){
var over=document.createElement('div');
over.className='on-mob-overlay';over.id='on-mob-over';
var drw=document.createElement('nav');
drw.id='on-mob-drawer';
drw.innerHTML=[['Home','/'],['Werk','/work'],['Diensten','/services'],['Prijzen','/pricing'],['Studio','/studio'],['Contact','/contact']].map(function(p){
return ''+p[0]+'';
}).join('');
document.body.appendChild(over);document.body.appendChild(drw);
})();
window.onMenu=function(){
var btn=document.getElementById('on-mob-btn');
var drw=document.getElementById('on-mob-drawer');
var ov=document.getElementById('on-mob-over');
if(!drw||!ov) return;
var opening=!drw.classList.contains('open');
drw.classList.toggle('open',opening);
ov.classList.toggle('open',opening);
if(btn) btn.classList.toggle('open',opening);
document.body.style.overflow=opening?'hidden':'';
};
// Countup — HTML carries the FINAL value (renderer parity + no-JS safe);
// the live site animates from 0 up to it. Skipped in the builder iframe.
if(window.self===window.top&&typeof IntersectionObserver!=='undefined'){
var cio=new IntersectionObserver(function(entries){
entries.forEach(function(en){
if(!en.isIntersecting) return;
cio.unobserve(en.target);
var el=en.target,fin=el.getAttribute('data-on-count')||el.textContent;
var num=parseInt(fin,10)||0,t0=null;
function step(ts){
if(!t0)t0=ts;
var p=Math.min(1,(ts-t0)/1000);p=1-Math.pow(1-p,3);
el.textContent=String(Math.round(num*p));
if(p<1)requestAnimationFrame(step);else el.textContent=fin;
}
requestAnimationFrame(step);
});
},{threshold:.4});
document.querySelectorAll('[data-on-count]').forEach(function(el){cio.observe(el)});
}
// Work filter tabs — hide via display none on the wrapper (never opacity)
function onFilter(cat){
document.querySelectorAll('[data-on-cat]').forEach(function(w){
w.style.display=(cat==='all'||w.getAttribute('data-on-cat')===cat)?'':'none';
});
}
// Delegated clicks — inline onclick attrs are stripped on canvas conversion
document.addEventListener('click',function(e){
if(!e.target||!e.target.closest) return;
var el;
el=e.target.closest('#on-mob-btn'); if(el){onMenu();return;}
el=e.target.closest('#on-mob-over'); if(el){onMenu();return;}
el=e.target.closest('[data-on-tab]');if(el){
document.querySelectorAll('[data-on-tab]').forEach(function(t){t.classList.remove('act')});
el.classList.add('act');
onFilter(el.getAttribute('data-on-tab'));
return;
}
});
// Repaired 2026-08-09. The handler that stood here cancelled the native submit, revealed
// the #on-contact-done panel and fired the 'lead' conversion — while sending nothing at
// all. The strict lead-v1 contract now owns this submit: form[data-sp-form] is bound by
// the _FORM runtime, which POSTs to /api/adaptive/forms/submit and reveals
// [data-sp-success] only once the server has answered ok. We record the conversion at
// that point and not before. 'sp:submit-success' is dispatched without bubbles, so this
// listens in the CAPTURE phase, which reaches document for any event.
document.addEventListener('sp:submit-success',function(e){
var form=e.target;
if(!form||form.id!=='on-contact-form') return;
if(window.noetis&&window.noetis.track) window.noetis.track('lead');
},true);
// Self-hosted background videos: force muted (SSR can drop the attribute) and
// call play() — Safari reliably autoplays muted HTML5 video this way.
function ffPlay(v){ try{ v.muted=true; v.setAttribute('muted',''); var p=v.play(); if(p&&p.catch)p.catch(function(){}); }catch(e){} }
document.querySelectorAll('video[autoplay]').forEach(function(v){
ffPlay(v);
v.addEventListener('canplay',function(){ffPlay(v)},{once:true});
if('IntersectionObserver' in window){
new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting)ffPlay(e.target)})},{threshold:.1}).observe(v);
}
});
})();