Webdesign · Branding

TIPE

Een nieuwe visuele uitstraling, van positionering tot website.

TIPE
Jaar · 2026Diensten · Merkstrategie · Visuele identiteit · Website · Content
De uitdaging

We startten met het fundament: positionering, doelgroep en merkverhaal. Dat vertaalden we naar een consistente visuele identiteit en tone of voice over alle communicatie heen.

Onze aanpak

Merkstrategie

Positionering en merkverhaal aangescherpt.

Visuele identiteit

Een herkenbare, consistente uitstraling.

Website

Ontworpen en gebouwd vanaf nul.

Guidelines

Richtlijnen voor consistente uitvoering.

1 merk

sterk en herkenbaar

Consistent

intern én extern

Vertrouwen

door herkenning

TIPE 1
TIPE 2
TIPE 3

“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

Maak van jouw verhaal de Final Frame

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