Monetarisierung: Affiliate-Deeplinks (Amazon/idealo/eBay)+Zubehör-Upsell, Pro-Pass (Zeit-Tier Echtzeit vs ~10min + Prognose-Frühwarnung/Watchlist/Preiswecker), Custom Multi-Artikel-Watchlist, Partner-Empfehlung Stöver/TGA (UTM, kein Lead-Verkauf), Dashboard (Chart.js) + Login-Stub

This commit is contained in:
2026-06-28 07:16:52 +00:00
parent 331647ff20
commit a78a9ab829
11 changed files with 244 additions and 24 deletions
+3 -1
View File
@@ -1,5 +1,6 @@
--- ---
import { priceIntel, euro, type Product } from '../lib/pricing'; import { priceIntel, euro, type Product } from '../lib/pricing';
import { idealo, REL } from '../lib/affiliate';
const { product, compact } = Astro.props as { product: Product; compact?: boolean }; const { product, compact } = Astro.props as { product: Product; compact?: boolean };
const pi = priceIntel(product); const pi = priceIntel(product);
const typeLabel = product.type === 'mobile_split' ? 'Mobile Split' : 'Monoblock'; const typeLabel = product.type === 'mobile_split' ? 'Mobile Split' : 'Monoblock';
@@ -17,7 +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 prüfen</a> <a class="btn sm" href={`/karte?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 ghost" href={`/geraete/${product.slug}`}>Details</a> <a class="btn sm ghost" href={`/geraete/${product.slug}`}>Details</a>
</div> </div>
</article> </article>
+7
View File
@@ -0,0 +1,7 @@
[
{"id":"fensterabdichtung","name":"Fensterabdichtung (Dreh-Kipp)","why":"Pflicht für Monoblock ohne Abdichtung zieht warme Luft nach und die Kühlleistung sinkt.","for":"monoblock","q":"Fensterabdichtung mobile Klimaanlage Dreh Kipp"},
{"id":"balkontuer-abdichtung","name":"Balkontür-Abdichtung","why":"Für Abluftschlauch an der Balkontür hält Hitze draußen.","for":"monoblock","q":"Klimaanlage Balkontür Abdichtung Abluftschlauch"},
{"id":"smarte-steckdose","name":"Smarte Steckdose mit Verbrauchsmessung","why":"Stromkosten im Blick behalten und per App/Timer steuern.","for":"all","q":"smarte Steckdose Verbrauchsmessung WLAN"},
{"id":"thermo-hygrometer","name":"Thermo-/Hygrometer","why":"Temperatur und Luftfeuchte messen richtig und effizient kühlen.","for":"all","q":"Thermometer Hygrometer innen digital"},
{"id":"thermo-rollo","name":"Hitzeschutz-Rollo / Thermo-Verdunkelung","why":"Außen-/Thermovorhang senkt die Hitze, bevor sie reinkommt wirkt auch ohne Gerät.","for":"all","q":"Hitzeschutz Rollo Thermo Verdunkelung Fenster"}
]
+2
View File
@@ -24,6 +24,8 @@ 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="/login">Login</a>
<a class="nav-cta" href="/karte">Live-Karte</a> <a class="nav-cta" href="/karte">Live-Karte</a>
</nav> </nav>
</div></header> </div></header>
+10
View File
@@ -0,0 +1,10 @@
// Affiliate-/Deeplink-Helfer. Tags sind Platzhalter (TODO: echte Partner-IDs setzen):
// Amazon PartnerNet, idealo Partner, eBay Partner Network (EPN), Awin (OBI/Hagebau/Bauhaus).
const AMZ_TAG = 'kuehlfinder-21'; // TODO PartnerNet
const EBAY_CAMPID = 'TODO_EPN'; // TODO eBay Partner Network
export const q = (s: string) => encodeURIComponent(s);
export const amazon = (term: string) => `https://www.amazon.de/s?k=${q(term)}&tag=${AMZ_TAG}`;
export const idealo = (term: string) => `https://www.idealo.de/preisvergleich/MainSearchProductCategory.html?q=${q(term)}`;
export const ebay = (term: string) => `https://www.ebay.de/sch/i.html?_nkw=${q(term)}&mkcid=1&mkrid=707-53477-19255-0&campid=${EBAY_CAMPID}&toolid=10001`;
export const utm = (url: string) => url + (url.includes('?') ? '&' : '?') + 'utm_source=kuehlfinder&utm_medium=affiliate';
export const REL = 'sponsored nofollow noopener';
+23 -19
View File
@@ -2,48 +2,52 @@
import Base from '../layouts/Base.astro'; import Base from '../layouts/Base.astro';
import { allProducts } from '../lib/pricing'; import { allProducts } from '../lib/pricing';
--- ---
<Base title="Verfügbarkeits-Alarm & Preiswecker für Klimageräte | Kühlfinder" description="Lass dich per E-Mail oder Telegram benachrichtigen, sobald dein Wunsch-Klimagerät verfügbar ist oder unter deinen Wunschpreis fällt."> <Base title="Custom-Alarm & Preiswecker für deine Wunschartikel | Kühlfinder" description="Überwache ein oder mehrere Wunsch-Klimageräte über alle Ketten, Länder, Online & eBay. Verfügbarkeits-Alarm + Preiswecker per E-Mail/Telegram, mit Prognose-Frühwarnung.">
<section class="wrap" style="margin-top:1.6rem;max-width:680px"> <section class="wrap" style="margin-top:1.6rem;max-width:680px">
<span class="eyebrow"><span class="dot ok live"></span>Alarm &amp; Preiswecker</span> <span class="eyebrow"><span class="dot ok live"></span>Custom-Alarm &amp; Preiswecker</span>
<h1>Sei der Erste, wenn dein Gerät da ist oder günstig wird</h1> <h1>Deine Wunschartikel im Blick ein oder mehrere</h1>
<p class="muted">Zwei Wecker in einem: <strong>Verfügbarkeits-Alarm</strong> (Markt in der Nähe hat Bestand) und <strong>Preiswecker</strong> (Preis fällt unter dein Limit). Frühwarnung dank Prognose. E-Mail kostenlos, Telegram im Pro-Pass.</p> <p class="muted">Lege eine <strong>Watchlist</strong> an: Wir überwachen deine Wunschgeräte über alle Ketten, Länder, Online &amp; eBay. <strong>Verfügbarkeits-Alarm</strong> (Markt in der Nähe hat Bestand) und <strong>Preiswecker</strong> (Preis fällt unter dein Limit) mit Prognose-Frühwarnung. E-Mail kostenlos, Telegram &amp; Echtzeit im Pro-Pass.</p>
<form class="card alarmform" id="alarmform"> <form class="card alarmform" id="alarmform">
<div class="seg"><button type="button" class="segbtn on" data-mode="stock">Verfügbarkeit</button><button type="button" class="segbtn" data-mode="price">Preiswecker</button></div> <div class="seg"><button type="button" class="segbtn on" data-mode="stock">Verfügbarkeit</button><button type="button" class="segbtn" data-mode="price">Preiswecker</button></div>
<label>Gerät<select name="produkt"><option value="">Alle / egal</option>{allProducts.map(p=> <option value={p.slug}>{p.name}</option>)}</select></label>
<label>Wunschartikel <span class="note">(ein oder mehrere)</span></label>
<div class="watchlist">
{allProducts.map(p => (
<label class="wl"><input type="checkbox" name="artikel" value={p.slug} /> <span>{p.name}</span></label>
))}
</div>
<div class="row2"> <div class="row2">
<label>PLZ<input name="plz" inputmode="numeric" placeholder="z. B. 21255" required /></label> <label>PLZ<input name="plz" inputmode="numeric" placeholder="z. B. 21255" required /></label>
<label>Umkreis<select name="radius"><option>25 km</option><option selected>50 km</option><option>100 km</option><option>Grenzregion +Ausland</option></select></label> <label>Umkreis<select name="radius"><option>25 km</option><option selected>50 km</option><option>100 km</option><option>Grenzregion +Ausland</option></select></label>
</div> </div>
<label id="pricewrap" hidden>Wunschpreis (max.)<input name="zielpreis" inputmode="numeric" placeholder="z. B. 750 €" /></label> <label id="pricewrap" hidden>Wunschpreis (max.)<input name="zielpreis" inputmode="numeric" placeholder="z. B. 750 €" /></label>
<label>Kanal <label>Kanal<select name="kanal"><option>E-Mail (kostenlos, ~10 Min versetzt)</option><option>E-Mail + Telegram, Echtzeit (Pro)</option></select></label>
<select name="kanal"><option>E-Mail (kostenlos)</option><option>E-Mail + Telegram (Pro)</option></select>
</label>
<label>E-Mail<input name="email" type="email" placeholder="du@example.de" required /></label> <label>E-Mail<input name="email" type="email" placeholder="du@example.de" required /></label>
<label class="chk"><input type="checkbox" required /> Ich akzeptiere die <a href="/datenschutz">Datenschutzhinweise</a>.</label> <label class="chk"><input type="checkbox" required /> Ich akzeptiere die <a href="/datenschutz">Datenschutzhinweise</a>.</label>
<button class="btn" type="submit">Wecker aktivieren</button> <button class="btn" type="submit">Watchlist aktivieren</button>
<p class="note" id="alarmmsg"></p> <p class="note" id="alarmmsg"></p>
</form> </form>
<p class="note" style="margin-top:1rem">Hinweis: Demo Alarm-Backend (Listmonk-Mail + Telegram-Bot) folgt. Keine Garantie auf Preis/Bestand. Datenquelle: eigener Kühlfinder-Scraper.</p> <p class="note" style="margin-top:1rem">Mehr Vorsprung mit <a href="/pro">Pro</a>: Echtzeit statt ~10 Min, Telegram-Push, Prognose-Frühwarnung. Hinweis: Demo Alarm-Backend (Listmonk + Telegram) folgt.</p>
</section> </section>
</Base> </Base>
<style> <style>
.alarmform{display:grid;gap:.9rem;margin-top:1rem} .alarmform{display:grid;gap:.9rem;margin-top:1rem}
.alarmform label{display:block;font-weight:500;font-size:.92rem} .alarmform>label{display:block;font-weight:500;font-size:.92rem}
.alarmform input,.alarmform select{width:100%;margin-top:.3rem;font-family:var(--font-body);font-size:1rem;padding:.6rem .8rem;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)} .alarmform input,.alarmform select{width:100%;margin-top:.3rem;font-family:var(--font-body);font-size:1rem;padding:.6rem .8rem;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}
.alarmform input:focus,.alarmform select:focus{border-color:var(--petrol);background:#fff} .alarmform input:focus,.alarmform select:focus{border-color:var(--petrol);background:#fff}
.watchlist{display:grid;grid-template-columns:1fr 1fr;gap:.4rem;margin-top:.3rem}
.wl{display:flex;align-items:center;gap:.5rem;font-weight:400;font-size:.9rem;background:var(--surface-2);border:1px solid var(--line);border-radius:9px;padding:.45rem .6rem;cursor:pointer}
.wl input{width:auto;margin:0}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:.9rem} .row2{display:grid;grid-template-columns:1fr 1fr;gap:.9rem}
.chk{display:flex;gap:.5rem;align-items:center;font-weight:400;font-size:.9rem}.chk input{width:auto;margin:0} .chk{display:flex;gap:.5rem;align-items:center;font-weight:400;font-size:.9rem}.chk input{width:auto;margin:0}
.seg{display:inline-flex;background:var(--surface-2);border:1px solid var(--line);border-radius:999px;padding:3px;gap:3px;width:max-content} .seg{display:inline-flex;background:var(--surface-2);border:1px solid var(--line);border-radius:999px;padding:3px;gap:3px;width:max-content}
.segbtn{font-family:var(--font-body);font-weight:600;font-size:.88rem;border:0;background:transparent;color:var(--slate);padding:.4rem 1rem;border-radius:999px;cursor:pointer} .segbtn{font-family:var(--font-body);font-weight:600;font-size:.88rem;border:0;background:transparent;color:var(--slate);padding:.4rem 1rem;border-radius:999px;cursor:pointer}
.segbtn.on{background:var(--petrol);color:#fff} .segbtn.on{background:var(--petrol);color:#fff}
@media(max-width:560px){.row2{grid-template-columns:1fr}} @media(max-width:560px){.row2,.watchlist{grid-template-columns:1fr}}
</style> </style>
<script> <script>
const f=document.getElementById('alarmform'); const f=document.getElementById('alarmform'), pw=document.getElementById('pricewrap');
const 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',()=>{ 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.`;});
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();document.getElementById('alarmmsg').textContent='Demo: Eingaben wurden nicht gespeichert. Alarm-/Preiswecker-Backend (Listmonk + Telegram) folgt.';});
</script> </script>
+60
View File
@@ -0,0 +1,60 @@
---
import Base from '../layouts/Base.astro';
import { allProducts } from '../lib/pricing';
const watch = allProducts.filter(p=>p.tier==='A').slice(0,3).map(p=>({slug:p.slug,name:p.name,uvp:p.uvp}));
---
<Base title="Kontrollzentrum (Dashboard) Preis- & Verfügbarkeitsverläufe | Kühlfinder" description="Dein persönliches Kontrollzentrum: Watchlist, Preisverlauf, Verfügbarkeits-Trend und Prognose die Datenbasis hinter den Alerts.">
<section class="wrap" style="margin-top:1.4rem">
<div class="dash-head">
<div><span class="eyebrow"><span class="dot ok live"></span>Kontrollzentrum · Demo</span><h1 style="margin:.4rem 0 0">Willkommen zurück</h1></div>
<a class="btn ghost sm" href="/login">Konto / Logout</a>
</div>
<p class="muted">Das sehen Pro-Nutzer in Echtzeit. Demo-Verläufe mit Live-Crawler werden es echte Systemdaten.</p>
<div class="kpis">
<div class="kpi card"><span class="note">Watchlist</span><b class="num" data-count="3">3</b></div>
<div class="kpi card ok"><span class="note">aktuell verfügbar</span><b class="num" data-count="1">1</b></div>
<div class="kpi card"><span class="note">Ø Prognose 7 Tg.</span><b class="num">38%</b></div>
<div class="kpi card warn"><span class="note">Preis-Alerts offen</span><b class="num" data-count="2">2</b></div>
</div>
<div class="charts">
<div class="card"><h3 class="h-sm">Preisverlauf (30 Tage)</h3><canvas id="priceChart" height="150"></canvas></div>
<div class="card"><h3 class="h-sm">Verfügbare Märkte (Trend)</h3><canvas id="availChart" height="150"></canvas></div>
</div>
<div class="card" style="margin-top:1rem">
<h3 class="h-sm">Deine Watchlist</h3>
<table class="wtab"><thead><tr><th>Modell</th><th class="ce">Status</th><th class="ce">bester Preis</th><th class="ce">Prognose</th><th></th></tr></thead>
<tbody id="wrows"></tbody></table>
</div>
<p class="note" style="margin-top:.8rem">Hinweis: Demo-Daten. Mit Login + Live-Crawler echte, personalisierte Verläufe. Quellen: eigener Scraper, <a href="https://open-meteo.com/" rel="nofollow">Open-Meteo</a>.</p>
</section>
</Base>
<link rel="preconnect" href="https://cdnjs.cloudflare.com" />
<style>
.dash-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:.6rem;margin:1rem 0}
.kpi{padding:.7rem .9rem;display:flex;flex-direction:column;gap:.1rem}.kpi b{font-size:1.6rem}.kpi.ok b{color:var(--ok-dot)}.kpi.warn b{color:var(--danger)}
.charts{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
.h-sm{font-size:1.05rem;margin:.1rem 0 .6rem}
.wtab{width:100%;border-collapse:collapse;font-size:.9rem}
.wtab th{text-align:left;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--slate);padding:.5rem .6rem;border-bottom:1px solid var(--line)}
.wtab th.ce{text-align:center}.wtab td{padding:.55rem .6rem;border-bottom:1px solid var(--line)}
@media(max-width:820px){.kpis{grid-template-columns:repeat(2,1fr)}.charts{grid-template-columns:1fr}}
</style>
<script define:vars={{ watch }}>
const euro=n=>new Intl.NumberFormat('de-DE',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(n);
function series(seed,base,vol,n){let v=base,out=[];for(let i=0;i<n;i++){const r=Math.sin(seed*9.7+i*1.3)*0.5+ (Math.cos(seed*3.1+i*0.7)*0.5);v=Math.max(base*0.7,Math.min(base*1.4, v+(r*vol)));out.push(Math.round(v));}return out;}
const days=Array.from({length:30},(_,i)=>i+1);
const wrows=document.getElementById('wrows');
wrows.innerHTML=watch.map((w,i)=>{const a=i===0; const q=[22,38,54][i%3]; const best=Math.round(w.uvp*(0.9+i*0.03));
return `<tr><td><strong>${w.name}</strong></td><td class="ce">${a?'<span style="color:var(--ok-dot);font-weight:600">verfügbar</span>':'<span style="color:var(--slate)">vergriffen</span>'}</td><td class="ce num">${euro(best)}</td><td class="ce num">${q}%</td><td class="ce"><a class="btn sm ghost" href="/karte?modell=${w.slug}">Karte</a></td></tr>`;}).join('');
function load(cb){const s=document.createElement('script');s.src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js';s.onload=cb;document.head.appendChild(s);}
load(()=>{
const css=getComputedStyle(document.documentElement);
const petrol='#2b6a72', warm='#c97a2b', ok='#1d9e75', grid='rgba(120,120,120,.12)';
new Chart(document.getElementById('priceChart'),{type:'line',data:{labels:days,datasets:watch.map((w,i)=>({label:w.name,data:series(i+1,w.uvp,18,30),borderColor:[petrol,warm,ok][i],backgroundColor:'transparent',tension:.35,pointRadius:0,borderWidth:2}))},options:{plugins:{legend:{labels:{boxWidth:10,font:{size:10}}}},scales:{x:{grid:{color:grid},ticks:{maxTicksLimit:6,font:{size:10}}},y:{grid:{color:grid},ticks:{callback:v=>v+' €',font:{size:10}}}}}});
new Chart(document.getElementById('availChart'),{type:'bar',data:{labels:days,datasets:[{label:'Märkte verfügbar',data:series(7,40,16,30).map(v=>Math.max(0,Math.round(v/4))),backgroundColor:petrol,borderRadius:3}]},options:{plugins:{legend:{display:false}},scales:{x:{grid:{display:false},ticks:{maxTicksLimit:6,font:{size:10}}},y:{grid:{color:grid},ticks:{font:{size:10}}}}}});
});
</script>
+12 -1
View File
@@ -1,12 +1,15 @@
--- ---
import Base from '../../layouts/Base.astro'; import Base from '../../layouts/Base.astro';
import { allProducts, getProduct, priceIntel, euro } from '../../lib/pricing'; import { allProducts, getProduct, priceIntel, euro } from '../../lib/pricing';
import { amazon, idealo, ebay, REL } from '../../lib/affiliate';
import accessories from '../../data/accessories.json';
export function getStaticPaths(){ return allProducts.map(p => ({ params: { slug: p.slug } })); } export function getStaticPaths(){ return allProducts.map(p => ({ params: { slug: p.slug } })); }
const { slug } = Astro.params; const { slug } = Astro.params;
const product = getProduct(slug!)!; const product = getProduct(slug!)!;
const pi = priceIntel(product); const pi = priceIntel(product);
const typeLabel = product.type === 'mobile_split' ? 'Mobile Split-Klimaanlage' : 'Monoblock mit Abluftschlauch'; const typeLabel = product.type === 'mobile_split' ? 'Mobile Split-Klimaanlage' : 'Monoblock mit Abluftschlauch';
const alternatives = allProducts.filter(p => p.id!==product.id && p.type===product.type).slice(0,3); const alternatives = allProducts.filter(p => p.id!==product.id && p.type===product.type).slice(0,3);
const accs = (accessories as any[]).filter(a => a.for==='all' || a.for===product.type);
const jsonld = {'@context':'https://schema.org','@type':'Product',name:product.name,brand:product.brand,category:'Mobile Klimaanlage', const jsonld = {'@context':'https://schema.org','@type':'Product',name:product.name,brand:product.brand,category:'Mobile Klimaanlage',
offers:{'@type':'AggregateOffer',priceCurrency:'EUR',lowPrice:pi.lowestLegit,highPrice:product.uvp}}; offers:{'@type':'AggregateOffer',priceCurrency:'EUR',lowPrice:pi.lowestLegit,highPrice:product.uvp}};
--- ---
@@ -26,8 +29,12 @@ const jsonld = {'@context':'https://schema.org','@type':'Product',name:product.n
</div> </div>
<div class="status-cta"> <div class="status-cta">
<a class="btn sm" href={`/karte?modell=${product.slug}`}>Auf der Karte suchen</a> <a class="btn sm" href={`/karte?modell=${product.slug}`}>Auf der Karte suchen</a>
<a class="btn sm ghost" href={`/alarm?produkt=${product.slug}`}>Alarm setzen</a> <a class="btn sm" href={idealo(product.name)} rel={REL} target="_blank">idealo ↗</a>
<a class="btn sm" href={amazon(product.name)} rel={REL} target="_blank">Amazon ↗</a>
<a class="btn sm" href={ebay(product.name)} rel={REL} target="_blank">eBay ↗</a>
<a class="btn sm ghost" href={`/alarm?produkt=${product.slug}`}>Alarm</a>
</div> </div>
<p class="note" style="margin:.5rem 0 0">Affiliate-Links Preis bleibt für dich gleich. <a href="/transparenz">Warum?</a></p>
</div> </div>
{product.type==='monoblock' && <p class="hint">Wichtig: Monoblock-Geräte brauchen eine gute Fensterabdichtung. Ohne Abdichtung wird ständig warme Luft nachgezogen.</p>} {product.type==='monoblock' && <p class="hint">Wichtig: Monoblock-Geräte brauchen eine gute Fensterabdichtung. Ohne Abdichtung wird ständig warme Luft nachgezogen.</p>}
{product.type==='mobile_split' && <p class="hint">Wichtig: Das Außengerät sicher und rücksichtsvoll platzieren Fensterbank/Balkon, Nachbarn, Mietvertrag prüfen.</p>} {product.type==='mobile_split' && <p class="hint">Wichtig: Das Außengerät sicher und rücksichtsvoll platzieren Fensterbank/Balkon, Nachbarn, Mietvertrag prüfen.</p>}
@@ -39,6 +46,10 @@ const jsonld = {'@context':'https://schema.org','@type':'Product',name:product.n
<div class="card"><h3>Stärken</h3><ul>{product.pros.map(x => <li>{x}</li>)}</ul></div> <div class="card"><h3>Stärken</h3><ul>{product.pros.map(x => <li>{x}</li>)}</ul></div>
<div class="card"><h3>Schwächen</h3><ul>{product.cons.map(x => <li>{x}</li>)}</ul></div> <div class="card"><h3>Schwächen</h3><ul>{product.cons.map(x => <li>{x}</li>)}</ul></div>
</div> </div>
<h2 style="margin-top:1.6rem">Das brauchst du zusätzlich</h2>
<div class="grid stores">
{accs.map(a => <div class="card"><strong>{a.name}</strong><p class="note" style="margin:.3rem 0 .6rem">{a.why}</p><a class="btn sm" href={amazon(a.q)} rel={REL} target="_blank">Zubehör finden ↗</a></div>)}
</div>
<h2 style="margin-top:1.6rem">Alternativen</h2> <h2 style="margin-top:1.6rem">Alternativen</h2>
<div class="grid stores"> <div class="grid stores">
{alternatives.map(a => <a class="card altlink" href={`/geraete/${a.slug}`}><strong>{a.name}</strong><br /><span class="note">{a.badge}</span></a>)} {alternatives.map(a => <a class="card altlink" href={`/geraete/${a.slug}`}><strong>{a.name}</strong><br /><span class="note">{a.badge}</span></a>)}
+7 -1
View File
@@ -17,6 +17,10 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
</div> </div>
<div class="upd card"><span class="note">Aktualisiert</span><div class="num upd-t" id="h-updated"></div><span class="note" id="h-feednote">lädt …</span></div> <div class="upd card"><span class="note">Aktualisiert</span><div class="num upd-t" id="h-updated"></div><span class="note" id="h-feednote">lädt …</span></div>
</div> </div>
<div class="delaybar reveal">
<span class="dot ok live"></span>
<span>Kostenlos siehst du Live-Daten <strong>~10 Min zeitversetzt</strong>. <a href="/pro">Mit Pro in Echtzeit + Push →</a></span>
</div>
<div class="stats" id="stats"> <div class="stats" id="stats">
<div class="stat"><span class="note">Märkte</span><b class="num" data-k="storesTotal"></b></div> <div class="stat"><span class="note">Märkte</span><b class="num" data-k="storesTotal"></b></div>
<div class="stat ok"><span class="note">mit Klimagerät</span><b class="num" data-k="withAC"></b></div> <div class="stat ok"><span class="note">mit Klimagerät</span><b class="num" data-k="withAC"></b></div>
@@ -85,7 +89,9 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
.mon-hero h1{font-size:clamp(1.8rem,4vw,2.6rem);margin:.5rem 0 .3rem} .mon-hero h1{font-size:clamp(1.8rem,4vw,2.6rem);margin:.5rem 0 .3rem}
.mon-hero .lead{max-width:48rem;color:var(--ink-2);font-size:1.05rem} .mon-hero .lead{max-width:48rem;color:var(--ink-2);font-size:1.05rem}
.upd{min-width:140px;text-align:center}.upd-t{font-size:1.5rem;font-weight:600;color:var(--petrol-deep)} .upd{min-width:140px;text-align:center}.upd-t{font-size:1.5rem;font-weight:600;color:var(--petrol-deep)}
.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:.6rem;margin-top:1.1rem} .delaybar{display:flex;align-items:center;gap:.5rem;background:var(--warm-bg);border:1px solid var(--line);border-left:4px solid var(--warm);border-radius:10px;padding:.55rem .8rem;margin-top:1rem;font-size:.9rem}
.delaybar a{font-weight:600}
.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:.6rem;margin-top:.8rem}
.stat{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:.6rem .8rem;display:flex;flex-direction:column;gap:.1rem;box-shadow:var(--shadow-card)} .stat{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:.6rem .8rem;display:flex;flex-direction:column;gap:.1rem;box-shadow:var(--shadow-card)}
.stat b{font-size:1.4rem;line-height:1.1}.stat.ok b{color:var(--ok-dot)}.stat.warn b{color:var(--danger)} .stat b{font-size:1.4rem;line-height:1.1}.stat.ok b{color:var(--ok-dot)}.stat.warn b{color:var(--danger)}
.mon-grid{display:grid;grid-template-columns:1.7fr 1fr;gap:1rem;margin-top:1.2rem;align-items:start} .mon-grid{display:grid;grid-template-columns:1.7fr 1fr;gap:1rem;margin-top:1.2rem;align-items:start}
+31 -2
View File
@@ -42,16 +42,45 @@ const jsonld={'@context':'https://schema.org','@type':'FAQPage',mainEntity:[
<h2>Tipp zum Timing</h2> <h2>Tipp zum Timing</h2>
<p>Mitten in der Hitzewelle sind SHK-Betriebe ausgebucht und Geräte knapp. Wer kann, holt jetzt <strong>Angebote</strong> ein und lässt <strong>außerhalb der Hochsaison</strong> installieren oft günstiger und schneller. Für die akute Notlage hilft der <a href="/karte">Verfügbarkeits-Monitor</a> oder die <a href="/soforthilfe">Hitze-Soforthilfe</a>.</p> <p>Mitten in der Hitzewelle sind SHK-Betriebe ausgebucht und Geräte knapp. Wer kann, holt jetzt <strong>Angebote</strong> ein und lässt <strong>außerhalb der Hochsaison</strong> installieren oft günstiger und schneller. Für die akute Notlage hilft der <a href="/karte">Verfügbarkeits-Monitor</a> oder die <a href="/soforthilfe">Hitze-Soforthilfe</a>.</p>
<div class="card" style="margin-top:1.4rem;border-left:4px solid var(--petrol)"> <div class="card" style="margin-top:1.4rem;border-left:4px solid var(--ok-dot)">
<h3 style="margin-top:0">Fachbetrieb finden unsere Empfehlung</h3>
<p class="muted" style="margin-bottom:.7rem">Feste Anlagen dürfen nur zertifizierte Betriebe einbauen. In unseren Regionen empfehlen wir direkt:</p>
<div class="partners">
<a class="pcard" id="p-stoever" href="https://www.klempnerei-stoever.de/?utm_source=kuehlfinder&utm_medium=referral&utm_campaign=feste-anlage" rel="noopener" target="_blank">
<strong>Bauklempnerei Stöver</strong><span class="note">Tostedt &amp; Umgebung</span><span class="go">Zum Betrieb ↗</span></a>
<a class="pcard" id="p-tga" href="https://www.tgasolutions.de/?utm_source=kuehlfinder&utm_medium=referral&utm_campaign=feste-anlage" rel="noopener" target="_blank">
<strong>TGA Solutions</strong><span class="note">Ahlerstedt · Landkreis Stade</span><span class="go">Zum Betrieb ↗</span></a>
</div>
<form class="plzcheck" id="plzcheck"><label>Deine PLZ<input name="plz" inputmode="numeric" placeholder="z. B. 21255" /></label><button class="btn sm" type="submit">Passenden Betrieb zeigen</button></form>
<p class="note" id="plzmsg" style="margin:.4rem 0 0">Außerhalb der Region: zertifizierten SHK-Fachbetrieb vor Ort wählen, mehrere Angebote einholen. Empfehlungen sind HD-Partner, keine bezahlte Platzierung.</p>
</div>
<div class="card" style="margin-top:1.2rem;border-left:4px solid var(--petrol)">
<h3 style="margin-top:0">Kurz gesagt</h3> <h3 style="margin-top:0">Kurz gesagt</h3>
<p class="muted" style="margin:0">Mobiles Gerät = schnelle Brücke. Fest installiert = die ruhige, effiziente Dauerlösung. Beides hat seine Berechtigung Panikkauf zum Wucherpreis nicht.</p> <p class="muted" style="margin:0">Mobiles Gerät = schnelle Brücke. Fest installiert = die ruhige, effiziente Dauerlösung. Beides hat seine Berechtigung Panikkauf zum Wucherpreis nicht.</p>
</div> </div>
<p class="note" style="margin-top:1.2rem">Quellen: <a href="https://www.adac.de/rund-ums-haus/wohnen/haushalt/klimaanlage-mit-einbau-kosten/" rel="nofollow">ADAC</a>, <a href="https://ratgeber.blauarbeit.de/kosten-preise/klimaanlage-einbauen-kosten" rel="nofollow">Blauarbeit Ratgeber</a>, <a href="https://www.heizcenter.de/blog/klimaanlage-kosten-2026-anschaffung-installation-betrieb" rel="nofollow">HeizCenter</a>. Preise sind Richtwerte mehrere Angebote einholen. Keine Rechtsberatung.</p> <p class="note" style="margin-top:1.2rem">Quellen: <a href="https://www.adac.de/rund-ums-haus/wohnen/haushalt/klimaanlage-mit-einbau-kosten/" rel="nofollow">ADAC</a>, <a href="https://ratgeber.blauarbeit.de/kosten-preise/klimaanlage-einbauen-kosten" rel="nofollow">Blauarbeit Ratgeber</a>, <a href="https://www.heizcenter.de/blog/klimaanlage-kosten-2026-anschaffung-installation-betrieb" rel="nofollow">HeizCenter</a>. Preise sind Richtwerte mehrere Angebote einholen. Keine Rechtsberatung.</p>
</section> </section>
<script>
const f=document.getElementById('plzcheck');
f&&f.addEventListener('submit',e=>{e.preventDefault();const v=(new FormData(f).get('plz')||'').toString().trim();
const st=document.getElementById('p-stoever'),tga=document.getElementById('p-tga'),msg=document.getElementById('plzmsg');
st.classList.remove('hot');tga.classList.remove('hot');
if(/^212(5|0|3)/.test(v)||/^211/.test(v)){st.classList.add('hot');msg.textContent='Empfehlung für deine Region: Bauklempnerei Stöver (Tostedt).';}
else if(/^217/.test(v)||/^215/.test(v)){tga.classList.add('hot');msg.textContent='Empfehlung für deine Region (LK Stade): TGA Solutions.';}
else {msg.textContent='Für deine PLZ haben wir (noch) keinen festen Partner wähle einen zertifizierten SHK-Betrieb vor Ort, mehrere Angebote einholen.';}
});
</script>
</Base> </Base>
<style>.prose .lead{font-size:1.15rem;color:var(--ink-2)}.prose h2{margin-top:1.6rem}.prose ul{padding-left:1.2rem}.prose li{margin:.35rem 0} <style>.prose .lead{font-size:1.15rem;color:var(--ink-2)}.prose h2{margin-top:1.6rem}.prose ul{padding-left:1.2rem}.prose li{margin:.35rem 0}
.cmp{width:100%;border-collapse:collapse;margin:.6rem 0;font-size:.92rem} .cmp{width:100%;border-collapse:collapse;margin:.6rem 0;font-size:.92rem}
.cmp th,.cmp td{border:1px solid var(--line);padding:.55rem .7rem;text-align:left} .cmp th,.cmp td{border:1px solid var(--line);padding:.55rem .7rem;text-align:left}
.cmp thead th{background:var(--petrol-soft);color:var(--petrol-deep);font-weight:600} .cmp thead th{background:var(--petrol-soft);color:var(--petrol-deep);font-weight:600}
.cmp td:first-child{color:var(--slate);font-weight:500}</style> .cmp td:first-child{color:var(--slate);font-weight:500}
.partners{display:grid;grid-template-columns:1fr 1fr;gap:.7rem;margin:.4rem 0 .8rem}
.pcard{display:flex;flex-direction:column;gap:.1rem;border:1px solid var(--line);border-radius:12px;padding:.8rem .9rem;text-decoration:none;color:var(--ink);transition:border-color .15s,transform .08s}
.pcard:hover{border-color:var(--petrol);transform:translateY(-2px)}.pcard.hot{border-color:var(--ok-dot);background:var(--ok-bg)}
.pcard .go{color:var(--petrol-deep);font-weight:600;font-size:.85rem;margin-top:.2rem}
.plzcheck{display:flex;gap:.6rem;align-items:flex-end;flex-wrap:wrap}.plzcheck input{padding:.55rem .8rem;border:1px solid var(--line);border-radius:10px;background:var(--surface-2);font-family:var(--font-body)}
@media(max-width:560px){.partners{grid-template-columns:1fr}}
.shk{display:grid;gap:.7rem}.shk .row2{display:grid;grid-template-columns:1fr 1fr;gap:.7rem}.shk input,.shk select{width:100%;padding:.6rem .8rem;border:1px solid var(--line);border-radius:10px;background:var(--surface-2);font-family:var(--font-body)}.shk .chk{display:flex;gap:.5rem;align-items:center;font-size:.88rem}.shk .chk input{width:auto}@media(max-width:560px){.shk .row2{grid-template-columns:1fr}}</style>
+19
View File
@@ -0,0 +1,19 @@
---
import Base from '../layouts/Base.astro';
---
<Base title="Login / Konto Kühlfinder" description="Melde dich an, um deine Watchlist, Alerts und dein Kontrollzentrum mit Verläufen zu sehen.">
<section class="wrap" style="margin-top:2rem;max-width:420px">
<span class="eyebrow"><span class="dot ok live"></span>Konto</span>
<h1>Anmelden</h1>
<p class="muted">Passwortlos per Magic-Link: Wir senden dir einen Login-Link per E-Mail. So bekommst du dein <a href="/dashboard">Kontrollzentrum</a> mit Watchlist, Alerts und Verläufen.</p>
<form class="card lf" id="lf">
<label>E-Mail<input type="email" name="email" placeholder="du@example.de" required /></label>
<button class="btn" type="submit">Login-Link senden</button>
<p class="note" id="lfmsg"></p>
<p class="note">Noch ohne Konto? Der Link legt automatisch eins an. Demo Auth (Magic-Link/Authentik) folgt.</p>
</form>
<p class="note" style="margin-top:1rem"><a href="/dashboard">Demo-Dashboard ansehen →</a></p>
</section>
</Base>
<style>.lf{display:grid;gap:.8rem;margin-top:1rem}.lf label{font-weight:500;font-size:.92rem}.lf input{width:100%;margin-top:.3rem;padding:.7rem .9rem;border:1px solid var(--line);border-radius:10px;background:var(--surface-2);font-family:var(--font-body)}.lf input:focus{border-color:var(--petrol);background:#fff}</style>
<script>document.getElementById('lf').addEventListener('submit',e=>{e.preventDefault();document.getElementById('lfmsg').textContent='Demo: Magic-Link-Versand folgt (Listmonk/SMTP).';});</script>
+70
View File
@@ -0,0 +1,70 @@
---
import Base from '../layouts/Base.astro';
const jsonld={'@context':'https://schema.org','@type':'FAQPage',mainEntity:[
{'@type':'Question',name:'Was kostet Kühlfinder?',acceptedAnswer:{'@type':'Answer',text:'Die Karte, Tabelle, Prognose und ein E-Mail-Alarm sind kostenlos. Der Pro-Pass für die Saison kostet einmalig 4,90 € (kein Abo) und schaltet unbegrenzte Alerts, Telegram, Prognose-Frühwarnung und Preiswecker frei.'}},
{'@type':'Question',name:'Gibt es ein Abo?',acceptedAnswer:{'@type':'Answer',text:'Nein. Der Pro-Pass ist eine Einmalzahlung für die Saison, keine automatische Verlängerung.'}}]};
---
<Base title="Kühlfinder Pro Sommer-Pass: schneller ans Klimagerät | Kühlfinder" description="Kostenlos: Karte, Prognose, 1 Alarm. Pro-Pass (einmalig 4,90 €, kein Abo): unbegrenzte Alerts, Telegram, Prognose-Frühwarnung und Preiswecker." jsonld={jsonld}>
<section class="wrap" style="margin-top:1.6rem;max-width:920px">
<span class="eyebrow"><span class="dot ok live"></span>Pricing</span>
<h1>Verfügbarkeit zuerst sehen. In Echtzeit.</h1>
<p class="muted" style="max-width:48rem">Bei Knappheit zählt jede Minute. <strong>Pro sieht neue Verfügbarkeiten und Alerts in Echtzeit</strong> kostenlose Nutzer sehen dieselben Live-Daten <strong>ca. 10 Minuten zeitversetzt</strong>. In einem Wettlauf um die letzte PortaSplit entscheidet genau dieser Vorsprung.</p>
<div class="advbar card"><div class="adv"><span class="num big">~10 Min</span><small>Free: Live-Daten zeitversetzt</small></div><div class="vs">vs</div><div class="adv ok"><span class="num big">Echtzeit</span><small>Pro: sofort + Push</small></div></div>
<div class="plans">
<div class="plan card">
<h2>Free</h2><div class="price"><span class="num">0 €</span></div>
<ul class="feat">
<li>✓ Live-Karte &amp; Tabelle (alle Länder)</li>
<li>✓ Live-Daten <strong>~10 Min zeitversetzt</strong></li>
<li>✓ Verfügbarkeits-Prognose (Anzeige)</li>
<li>✓ Preis-Check &amp; Wucher-Warnung</li>
<li>✓ Klima-Rechner &amp; Finder</li>
<li>✓ 1 E-Mail-Alarm</li>
</ul>
<a class="btn ghost" href="/karte">Kostenlos nutzen</a>
</div>
<div class="plan card featured">
<span class="tag">Empfohlen</span>
<h2>Pro · Sommer-Pass</h2><div class="price"><span class="num">4,90 €</span><small>einmalig · kein Abo</small></div>
<ul class="feat">
<li>★ <strong>Echtzeit</strong> statt ~10 Min Verzögerung du bist zuerst da</li>
<li>★ Unbegrenzte Alerts (mehrere Märkte/Modelle)</li>
<li>★ <strong>Telegram</strong> + E-Mail, ~1 Min Vorsprung</li>
<li>★ <strong>Prognose-Frühwarnung</strong>: Alarm, bevor es wieder da ist</li>
<li>★ <strong>Preiswecker</strong>: Alarm, wenn Preis unter dein Limit fällt</li>
<li>★ Beste-Chancen-Route in deinem Umkreis</li>
</ul>
<a class="btn" href="/alarm">Pro-Pass holen</a>
<p class="note" style="margin:.5rem 0 0">Zahlung per Stripe (folgt). Keine automatische Verlängerung.</p>
</div>
</div>
<div class="card" style="margin-top:1.2rem;border-left:4px solid var(--petrol)">
<h3 style="margin-top:0">Mehr als ein simpler Tracker</h3>
<p class="muted" style="margin-bottom:.4rem">Zeitvorsprung allein kann jeder. Pro geht weiter:</p>
<ul class="feat">
<li>★ <strong>Prognose-Frühwarnung</strong>: Alarm, <em>bevor</em> ein Markt typischerweise nachlegt nicht erst, wenn es schon da (und gleich wieder weg) ist.</li>
<li>★ <strong>Custom-Watchlist</strong>: ein oder mehrere Wunschartikel, überwacht über alle Ketten, 6 Länder, Online &amp; eBay.</li>
<li>★ <strong>Preiswecker</strong> + Wucher-Schutz: kauf nie überteuert.</li>
</ul>
</div>
<div class="card" style="margin-top:1.2rem">
<h3 style="margin-top:0">Wie sich Kühlfinder finanziert transparent</h3>
<p class="muted">Die kostenlose Suche finanziert sich über <strong>Affiliate-Links</strong> (Amazon, idealo, eBay, Baumärkte): Kaufst du über unsere „Kaufen/Preis prüfen"-Buttons, erhalten wir ggf. eine Provision <strong>für dich ändert sich der Preis nicht</strong>. Empfehlungen hängen nie von der Provision ab. Mehr: <a href="/transparenz">Transparenz</a>.</p>
</div>
</section>
</Base>
<style>
.advbar{display:flex;align-items:center;gap:1.4rem;justify-content:center;margin:1.2rem 0;padding:1rem}
.adv{display:flex;flex-direction:column;align-items:center}.adv .big{font-size:1.8rem;font-weight:600;color:var(--slate)}.adv.ok .big{color:var(--ok-dot)}.adv small{color:var(--slate);font-size:.8rem}
.vs{color:var(--slate);font-weight:600}
.plans{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-top:1.2rem}
.plan{display:flex;flex-direction:column;gap:.6rem;position:relative}
.plan.featured{border:2px solid var(--petrol)}
.plan .tag{position:absolute;top:-11px;left:1rem;background:var(--petrol);color:#fff;font-size:.72rem;font-weight:600;padding:.2rem .6rem;border-radius:999px}
.plan h2{margin:.2rem 0 0}
.price{display:flex;align-items:baseline;gap:.5rem}.price .num{font-size:2rem;font-weight:600;color:var(--petrol-deep)}.price small{color:var(--slate);font-size:.8rem}
.feat{list-style:none;margin:.4rem 0;padding:0;display:flex;flex-direction:column;gap:.4rem;font-size:.95rem}
@media(max-width:760px){.plans{grid-template-columns:1fr}}
</style>