Prijzen

Pakketten

Pakketten die meegroeien met jouw ambitie — van merkfundament tot een volledig geïntegreerde groeimotor. Maandelijks, opzegbaar wanneer je wilt.

Brand Foundation
✓StrategieMerk- en positioneringscheck, doelgroep & kernboodschap aanscherpen, contentplanning.
✓BrandingBrandguidelines (25 pagina's), tone of voice & visuele richtlijnen, templates.
✓Social contentContent op basis van bestaande visuals.
✓Mailcampagnes1 campagne per maand — concept, opmaak en verzending.
✓LeadgeneratieKanaal- en website-inrichting voor maximale conversie.
✓EvaluatieMaandelijkse check & optimalisatie.

Voor merken die een professioneel fundament willen leggen en consistent willen groeien.

€2.500/maand

Brand Foundation
Growth PartnerPopular
✓StrategieVolledige merkstrategie, funnel (awareness → conversie), contentstrategie.
✓BrandingUitgebreide brandguidelines (50 pagina's), doorlopende merkbewaking.
✓Social contentCustom beeldbank, 1 video/fotografie-shoot per kwartaal.
✓WebsiteOnderhoud & optimalisatie, basis SEO, blogs.
✓LeadgeneratieProactieve sales-machine met dedicated outreach + retargeting.
✓EvaluatieTweewekelijkse check & optimalisatie.

Voor ambitieuze merken die een complete funnel en doorlopende groei willen.

€4.000/maand

Growth Partner
Scale Partner
✓StrategieDirectie-sparring, volledige merkstrategie en groeiplan.
✓BrandingVolledige merkbewaking, brandguidelines naar alle touchpoints.
✓WebsiteVolledig redesign, onderhoud en continue optimalisatie.
✓Social contentCustom beeldbank, 2 video/fotoshoots per kwartaal.
✓Sales & materiaalBrochures, presentaties en advertenties.
✓EvaluatieTweewekelijkse operationele check + kwartaalstrategie.

Voor merken die schalen — directie-sparring en een geïntegreerde groeimotor.

€6.000/maand

Scale Partner

Wat klanten zeggen

Testimonial

“Samenwerken met Final Frame was prettig. Ze reageren snel, denken met je mee en leveren werk waar we trots op zijn.”

Hugo van Benthem — Founder, TIPE

Not sure which plan fits?

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); } }); })();