Umami-Tracking: Loader + track()-Helper, Auto-Tracking ausgehender Affiliate/Partner-Links, data-umami-event auf CTAs, Events für Monitor/Finder/Rechner/Alarm/Pro

This commit is contained in:
2026-06-28 07:19:06 +00:00
parent a78a9ab829
commit 7636a00d9f
8 changed files with 18 additions and 13 deletions
+2 -2
View File
@@ -18,8 +18,8 @@ const typeLabel = product.type === 'mobile_split' ? 'Mobile Split' : 'Monoblock'
<div><span class="note">Score</span><br /><strong class="num">{product.score}/100</strong></div> <div><span class="note">Score</span><br /><strong class="num">{product.score}/100</strong></div>
</div> </div>
<div class="pc-cta"> <div class="pc-cta">
<a class="btn sm" href={`/karte?modell=${product.slug}`}>Verfügbarkeit</a> <a class="btn sm" href={`/karte?modell=${product.slug}`} data-umami-event="card_verfuegbarkeit" data-umami-event-modell={product.slug}>Verfügbarkeit</a>
<a class="btn sm" href={idealo(product.name)} rel={REL} target="_blank">Preis prüfen ↗</a> <a class="btn sm" href={idealo(product.name)} rel={REL} target="_blank" data-umami-event="card_preis_pruefen" data-umami-event-modell={product.slug}>Preis prüfen ↗</a>
<a class="btn sm ghost" href={`/geraete/${product.slug}`}>Details</a> <a class="btn sm ghost" href={`/geraete/${product.slug}`}>Details</a>
</div> </div>
</article> </article>
+7 -2
View File
@@ -15,6 +15,8 @@ import '../styles/global.css';
<meta property="og:description" content={desc} /> <meta property="og:description" content={desc} />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
{jsonld && <script type="application/ld+json" set:html={JSON.stringify(jsonld)} />} {jsonld && <script type="application/ld+json" set:html={JSON.stringify(jsonld)} />}
<!-- Umami Analytics (self-hosted). TODO: data-website-id setzen, Host prüfen. -->
<script defer is:inline src="https://umami.heidrich-digital.de/script.js" data-website-id="REPLACE_WITH_UMAMI_WEBSITE_ID" data-domains="kuehlfinder.de"></script>
</head> </head>
<body> <body>
<header class="site"><div class="wrap bar"> <header class="site"><div class="wrap bar">
@@ -24,9 +26,9 @@ import '../styles/global.css';
<a href="/rechner">Rechner</a> <a href="/rechner">Rechner</a>
<a href="/finder">Finder</a> <a href="/finder">Finder</a>
<a href="/alarm">Alarm</a> <a href="/alarm">Alarm</a>
<a href="/pro">Pro</a> <a href="/pro" data-umami-event="nav_pro">Pro</a>
<a href="/login">Login</a> <a href="/login">Login</a>
<a class="nav-cta" href="/karte">Live-Karte</a> <a class="nav-cta" href="/karte" data-umami-event="nav_live_karte">Live-Karte</a>
</nav> </nav>
</div></header> </div></header>
<main><slot /></main> <main><slot /></main>
@@ -48,6 +50,9 @@ import '../styles/global.css';
} }
document.addEventListener('DOMContentLoaded',wire); wire(); document.addEventListener('DOMContentLoaded',wire); wire();
window.kfAnim=wire; window.kfAnim=wire;
window.track=function(name,data){try{window.umami&&window.umami.track(name,data);}catch(e){}};
// Auto-Tracking ausgehender Affiliate-/Partner-Links
document.addEventListener('click',function(e){var a=e.target.closest&&e.target.closest('a[href^="http"]');if(!a)return;var rel=(a.getAttribute('rel')||'');var host=(a.hostname||'');if(rel.includes('sponsored')||/amazon|ebay|idealo|mediamarkt|saturn|obi|bauhaus|hornbach|toom|hagebau|globus|klempnerei-stoever|tgasolutions/i.test(host)){window.track&&window.track('outbound_click',{host:host,label:(a.textContent||'').trim().slice(0,40)});}},true);
})(); })();
</script> </script>
<footer class="site"><div class="wrap"> <footer class="site"><div class="wrap">
+1 -1
View File
@@ -49,5 +49,5 @@ import { allProducts } from '../lib/pricing';
<script> <script>
const f=document.getElementById('alarmform'), pw=document.getElementById('pricewrap'); const f=document.getElementById('alarmform'), pw=document.getElementById('pricewrap');
document.querySelectorAll('.segbtn').forEach(b=>b.addEventListener('click',()=>{document.querySelectorAll('.segbtn').forEach(x=>x.classList.remove('on'));b.classList.add('on');pw.hidden=b.dataset.mode!=='price';})); document.querySelectorAll('.segbtn').forEach(b=>b.addEventListener('click',()=>{document.querySelectorAll('.segbtn').forEach(x=>x.classList.remove('on'));b.classList.add('on');pw.hidden=b.dataset.mode!=='price';}));
f.addEventListener('submit',e=>{e.preventDefault();const n=f.querySelectorAll('input[name=artikel]:checked').length;document.getElementById('alarmmsg').textContent=`Demo: ${n||'alle'} Wunschartikel vorgemerkt. Backend (Listmonk + Telegram) folgt nichts gespeichert.`;}); f.addEventListener('submit',e=>{e.preventDefault();const n=f.querySelectorAll('input[name=artikel]:checked').length;window.track&&window.track('alarm_submit',{artikel:n});document.getElementById('alarmmsg').textContent=`Demo: ${n||'alle'} Wunschartikel vorgemerkt. Backend (Listmonk + Telegram) folgt nichts gespeichert.`;});
</script> </script>
+1 -1
View File
@@ -42,7 +42,7 @@ const products = allProducts.map(p=>({slug:p.slug,name:p.name,type:p.type,badge:
return {primary:find(primary),alt:find(alt),budget:find(budget),warn}; return {primary:find(primary),alt:find(alt),budget:find(budget),warn};
} }
function result(){ function result(){
const r=pick(); const r=pick(); window.track&&window.track("finder_ergebnis",{modell:r.primary&&r.primary.slug});
const card=p=>`<a class="rc" href="/geraete/${p.slug}"><strong>${p.name}</strong><span class="note">${p.badge}</span></a>`; const card=p=>`<a class="rc" href="/geraete/${p.slug}"><strong>${p.name}</strong><span class="note">${p.badge}</span></a>`;
el.innerHTML=`<h2>Deine Empfehlung</h2> el.innerHTML=`<h2>Deine Empfehlung</h2>
<div class="reco"><span class="badge">Beste Wahl</span>${card(r.primary)}</div> <div class="reco"><span class="badge">Beste Wahl</span>${card(r.primary)}</div>
+2 -2
View File
@@ -18,8 +18,8 @@ const Bell='<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="
<p class="lead">Bei Hitze ist alles vergriffen. Kühlfinder ist dein Kontrollzentrum: <strong>live sehen, welcher Markt in der Nähe eins auf Lager hat</strong> Baumärkte, Elektromärkte, Online &amp; eBay mit Prognose, Preis-Check gegen Wucher und Alarm. <em>Die nächste Hitzewelle kommt bestimmt.</em></p> <p class="lead">Bei Hitze ist alles vergriffen. Kühlfinder ist dein Kontrollzentrum: <strong>live sehen, welcher Markt in der Nähe eins auf Lager hat</strong> Baumärkte, Elektromärkte, Online &amp; eBay mit Prognose, Preis-Check gegen Wucher und Alarm. <em>Die nächste Hitzewelle kommt bestimmt.</em></p>
<form class="herobar" action="/karte" method="get"> <form class="herobar" action="/karte" method="get">
<input name="plz" inputmode="numeric" placeholder="PLZ oder Ort …" aria-label="PLZ oder Ort" /> <input name="plz" inputmode="numeric" placeholder="PLZ oder Ort …" aria-label="PLZ oder Ort" />
<button class="btn" type="submit">Verfügbarkeit zeigen</button> <button class="btn" type="submit" data-umami-event="hero_plz_suche">Verfügbarkeit zeigen</button>
<a class="btn ghost" href="/karte" id="locbtn">In meiner Nähe</a> <a class="btn ghost" href="/karte" id="locbtn" data-umami-event="hero_naehe">In meiner Nähe</a>
</form> </form>
<div class="livewrap reveal"> <div class="livewrap reveal">
<div class="pills"> <div class="pills">
+3 -3
View File
@@ -224,17 +224,17 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
if(DATA)render(); if(DATA)render();
}); });
$('#q').addEventListener('input',e=>{state.q=e.target.value.trim();shown=120;render();}); $('#q').addEventListener('input',e=>{state.q=e.target.value.trim();shown=120;render();});
$('#model').addEventListener('change',e=>{state.model=e.target.value;render();}); $('#model').addEventListener('change',e=>{state.model=e.target.value;window.track&&window.track('monitor_modell',{modell:state.model});render();});
$$('.chip[data-toggle]').forEach(b=>b.addEventListener('click',()=>{state[b.dataset.toggle]=!state[b.dataset.toggle];b.classList.toggle('on');render();})); $$('.chip[data-toggle]').forEach(b=>b.addEventListener('click',()=>{state[b.dataset.toggle]=!state[b.dataset.toggle];b.classList.toggle('on');render();}));
$$('.chip[data-kind]').forEach(b=>b.addEventListener('click',()=>{state.kind=b.dataset.kind;$$('.chip[data-kind]').forEach(x=>x.classList.remove('on'));b.classList.add('on');render();})); $$('.chip[data-kind]').forEach(b=>b.addEventListener('click',()=>{state.kind=b.dataset.kind;$$('.chip[data-kind]').forEach(x=>x.classList.remove('on'));b.classList.add('on');render();}));
$('#radius').addEventListener('input',e=>{state.radius=+e.target.value;$('#r-out').textContent=state.radius+' km';render();}); $('#radius').addEventListener('input',e=>{state.radius=+e.target.value;$('#r-out').textContent=state.radius+' km';render();});
$('#more').addEventListener('click',()=>{shown+=120;render();}); $('#more').addEventListener('click',()=>{shown+=120;render();});
$('#reset').addEventListener('click',()=>location.reload()); $('#reset').addEventListener('click',()=>location.reload());
$$('.mtab th[data-sort]').forEach(th=>th.addEventListener('click',()=>{state.chance=true;render();})); $$('.mtab th[data-sort]').forEach(th=>th.addEventListener('click',()=>{state.chance=true;render();}));
$('#locate').addEventListener('click',()=>{if(!navigator.geolocation)return;navigator.geolocation.getCurrentPosition(p=>{center=[p.coords.longitude,p.coords.latitude];$('#radius').disabled=false;state.radius=100;$('#radius').value=100;$('#r-out').textContent='100 km';new maplibregl.Marker({color:'#185fa5'}).setLngLat(center).addTo(map);map.flyTo({center,zoom:8});render();});}); $('#locate').addEventListener('click',()=>{if(!navigator.geolocation)return;window.track&&window.track('monitor_standort');navigator.geolocation.getCurrentPosition(p=>{center=[p.coords.longitude,p.coords.latitude];$('#radius').disabled=false;state.radius=100;$('#radius').value=100;$('#r-out').textContent='100 km';new maplibregl.Marker({color:'#185fa5'}).setLngLat(center).addTo(map);map.flyTo({center,zoom:8});render();});});
$$('#scope .chip').forEach(b=>b.addEventListener('click',()=>{ $$('#scope .chip').forEach(b=>b.addEventListener('click',()=>{
$$('#scope .chip').forEach(x=>x.classList.remove('on'));b.classList.add('on'); $$('#scope .chip').forEach(x=>x.classList.remove('on'));b.classList.add('on');
if(b.dataset.scope==='de'){state.countries=new Set(['DE']);} else {state.countries=new Set();} window.track&&window.track('monitor_scope',{scope:b.dataset.scope});if(b.dataset.scope==='de'){state.countries=new Set(['DE']);} else {state.countries=new Set();}
$$('#countrychips .chip').forEach(c=>c.classList.toggle('on', state.countries.has(c.dataset.cc))); $$('#countrychips .chip').forEach(c=>c.classList.toggle('on', state.countries.has(c.dataset.cc)));
render(); render();
})); }));
+1 -1
View File
@@ -35,7 +35,7 @@ const jsonld={'@context':'https://schema.org','@type':'FAQPage',mainEntity:[
<li>★ <strong>Preiswecker</strong>: Alarm, wenn Preis unter dein Limit fällt</li> <li>★ <strong>Preiswecker</strong>: Alarm, wenn Preis unter dein Limit fällt</li>
<li>★ Beste-Chancen-Route in deinem Umkreis</li> <li>★ Beste-Chancen-Route in deinem Umkreis</li>
</ul> </ul>
<a class="btn" href="/alarm">Pro-Pass holen</a> <a class="btn" href="/alarm" data-umami-event="pro_pass_cta">Pro-Pass holen</a>
<p class="note" style="margin:.5rem 0 0">Zahlung per Stripe (folgt). Keine automatische Verlängerung.</p> <p class="note" style="margin:.5rem 0 0">Zahlung per Stripe (folgt). Keine automatische Verlängerung.</p>
</div> </div>
</div> </div>
+1 -1
View File
@@ -107,6 +107,6 @@ const products = allProducts.map(p=>({slug:p.slug,name:p.name,type:p.type,btuh:p
} }
$('#m2').addEventListener('input',e=>{S.m2=+e.target.value;$('#m2-out').textContent=S.m2+' m²';calc();}); $('#m2').addEventListener('input',e=>{S.m2=+e.target.value;$('#m2-out').textContent=S.m2+' m²';calc();});
['room','sun','mode'].forEach(g=>$$('#'+g+' .segbtn').forEach(b=>b.addEventListener('click',()=>{ ['room','sun','mode'].forEach(g=>$$('#'+g+' .segbtn').forEach(b=>b.addEventListener('click',()=>{
$$('#'+g+' .segbtn').forEach(x=>x.classList.remove('on'));b.classList.add('on');S[g]=b.dataset.v;calc();}))); $$('#'+g+' .segbtn').forEach(x=>x.classList.remove('on'));b.classList.add('on');S[g]=b.dataset.v;window.track&&window.track('rechner_input',{feld:g,wert:b.dataset.v});calc();})));
calc(); calc();
</script> </script>