v4 Kontrollzentrum: 6 Länder (DE/AT/CH/NL/PL/DK), Währungs-aware, eBay/Retoure + Shop-Seriosität, Preis-Strip, Wetter-Banner (Open-Meteo)+Quellen, availability-first Home, Animationen, Länder-Scope, Preiswecker

This commit is contained in:
2026-06-28 06:47:30 +00:00
parent 600fcfe97d
commit 0915e0988b
16 changed files with 3655 additions and 129 deletions
+75 -49
View File
@@ -1,17 +1,19 @@
---
import Base from '../layouts/Base.astro';
import WeatherBanner from '../components/WeatherBanner.astro';
import { allProducts } from '../lib/pricing';
const models = allProducts.map(p=>({slug:p.slug,name:p.name}));
const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kühlfinder Monitor',applicationCategory:'ShoppingApplication',
description:'Live-Verfügbarkeit von Klimageräten in Baumärkten und Elektromärkten in Deutschland, mit Verfügbarkeits-Prognose.'};
const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kühlfinder Kontrollzentrum',applicationCategory:'ShoppingApplication',
description:'Live-Verfügbarkeit von Klimageräten in Baumärkten & Elektromärkten in DE/AT/CH/NL/PL/DK plus Online & eBay, mit Prognose und Preis-Check.'};
---
<Base title="Klimageräte-Monitor Live-Verfügbarkeit in Baumärkten & Elektromärkten | Kühlfinder" description="Sieh live, welche Märkte (OBI, Toom, Bauhaus, Hornbach, MediaMarkt, Saturn, expert, Euronics …) gerade Klimageräte auf Lager haben mit Prognose, Preis-Check und Wucher-Warnung." jsonld={jsonld}>
<Base title="Klimagerät-Kontrollzentrum Live-Verfügbarkeit DE/AT/CH/NL/PL/DK | Kühlfinder" description="Live-Bestand von Klimageräten in Baumärkten und Elektromärkten in Deutschland und Nachbarländern, plus Online, Amazon & eBay mit Prognose, Preis-Check und Wucher-Warnung.">
<section class="mon-hero"><div class="wrap">
<div class="mon-hero-row">
<WeatherBanner />
<div class="mon-hero-row" style="margin-top:1rem">
<div>
<span class="eyebrow"><span class="dot ok"></span>Live · eigener Scraper</span>
<h1>Wo gibt es gerade ein Klimagerät?</h1>
<p class="lead">Live-Bestand in Baumärkten <em>und</em> Elektromärkten plus <strong>Verfügbarkeits-Prognose</strong>, wo die Chance in 7 Tagen am höchsten ist.</p>
<span class="eyebrow"><span class="dot ok live"></span>Live · eigener Scraper · 6 Länder</span>
<h1>Dein Klimagerät-Kontrollzentrum</h1>
<p class="lead">Live-Bestand in Baumärkten, Elektromärkten, Online &amp; eBay mit <strong>Verfügbarkeits-Prognose</strong> und <strong>Preis-Check gegen Wucher</strong>.</p>
</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>
@@ -20,7 +22,7 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
<div class="stat ok"><span class="note">mit Klimagerät</span><b class="num" data-k="withAC"></b></div>
<div class="stat"><span class="note">Baumärkte</span><b class="num" data-k="baumarkt"></b></div>
<div class="stat"><span class="note">Elektromärkte</span><b class="num" data-k="elektro"></b></div>
<div class="stat"><span class="note">Restocks 7 Tg.</span><b class="num" id="s-restocks"></b></div>
<div class="stat"><span class="note">Länder</span><b class="num" data-k="countries"></b></div>
<div class="stat warn"><span class="note">Wucher-Alarm</span><b class="num" id="s-wucher"></b></div>
</div>
</div></section>
@@ -28,10 +30,7 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
<section class="wrap mon-grid">
<div class="mapcol">
<div id="map"></div>
<div class="legend">
<span><span class="dot ok"></span>Klimagerät verfügbar</span>
<span><span class="dot" style="background:var(--petrol)"></span>Markt (Cluster)</span>
</div>
<div class="legend"><span><span class="dot ok"></span>verfügbar</span><span><span class="dot" style="background:var(--petrol)"></span>Markt (Cluster)</span></div>
</div>
<aside class="feedcol card">
<h2 class="h-sm">Letzte Verfügbarkeiten</h2>
@@ -43,7 +42,7 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
<section class="wrap">
<div class="toolbar card">
<div class="tb-row">
<input id="q" type="search" placeholder="Markt, Ort oder PLZ suchen …" />
<input id="q" type="search" placeholder="Markt, Ort oder PLZ …" />
<select id="model"><option value="all">Alle Modelle</option>{models.map(m=> <option value={m.slug}>{m.name}</option>)}</select>
<button class="btn sm ghost" id="locate" type="button">Standort</button>
<label class="rad">Umkreis <span class="num" id="r-out"></span><input id="radius" type="range" min="10" max="500" step="10" value="500" disabled /></label>
@@ -52,50 +51,55 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
<button class="chip ghost on" data-toggle="avail">Nur verfügbare</button>
<button class="chip ghost" data-toggle="chance">Beste Prognose</button>
<span class="sep"></span>
<button class="chip on" data-kind="all">Alle</button>
<button class="chip on" data-kind="all">Alle Typen</button>
<button class="chip" data-kind="baumarkt">Baumarkt</button>
<button class="chip" data-kind="elektro">Elektro</button>
<span class="sep"></span>
<span id="chainchips"></span>
<span class="scope" id="scope"><button class="chip" data-scope="de">🇩🇪 Nur DE</button><button class="chip on" data-scope="all">+ Nachbarländer</button></span>
<span id="countrychips"></span>
<button class="chip ghost" id="reset" type="button">Reset</button>
</div>
</div>
<div class="pricestrip card" id="pricestrip" hidden></div>
<div class="onlinebar card" id="onlinebar"></div>
<div class="tablewrap card">
<table class="mtab"><thead><tr>
<th>Markt</th><th class="ce">Klimageräte</th><th class="ce">ab Preis</th><th class="ce" data-sort="q">Prognose 7 Tg.</th><th class="ce" id="distcol" style="display:none">Entfernung</th>
</tr></thead><tbody id="rows"><tr><td colspan="5"><div class="skel" style="height:200px"></div></td></tr></tbody></table>
<div class="more"><button class="btn ghost" id="more" type="button" style="display:none">Mehr Märkte</button><p class="note" id="rowcount"></p></div>
</div>
<div class="alertcta card">
<div><h2 class="h-sm">Nichts in der Nähe?</h2><p class="note" style="margin:0">Wir mailen/telegrammen dir, sobald ein Klimagerät in deinem Umkreis auftaucht inkl. Prognose-Frühwarnung.</p></div>
<a class="btn" href="/alarm">Alarm einrichten</a>
<div><h2 class="h-sm">Nichts in der Nähe?</h2><p class="note" style="margin:0">Verfügbarkeits-Alarm + Preiswecker Mail &amp; Telegram, mit Prognose-Frühwarnung.</p></div>
<a class="btn" href="/alarm">Alarm &amp; Preiswecker</a>
</div>
<p class="note" style="margin-top:.6rem">Datenquelle: <strong>eigener Kühlfinder-Scraper</strong> (Händler-Seiten direkt) keine Drittdaten. Aktuell Seed-Stand; Live-Crawler + echte Historie folgen. Prognose = statistische Schätzung, keine Garantie.</p>
<p class="note" style="margin-top:.6rem">Datenquelle: <strong>eigener Kühlfinder-Scraper</strong> (Händler direkt) keine Drittdaten. Aktuell Seed-Stand; Live-Crawler + echte Historie folgen. Prognose = statistische Schätzung. Quellen: Wetter <a href="https://open-meteo.com/" rel="nofollow">Open-Meteo</a>, Hitze/Gesundheit <a href="https://www.umweltbundesamt.de/themen/gesundheit/umwelteinfluesse-auf-den-menschen/hitze" rel="nofollow">UBA</a>.</p>
</section>
</Base>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@4.7.1/dist/maplibre-gl.css" />
<style>
.mon-hero{background:var(--grad-hero);padding:1.8rem 0 1.4rem}
.mon-hero{background:var(--grad-hero);padding:1.6rem 0 1.4rem}
.mon-hero-row{display:flex;justify-content:space-between;gap:1.2rem;align-items:flex-start}
.mon-hero h1{font-size:clamp(1.8rem,4vw,2.7rem);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}
.upd{min-width:140px;text-align:center}.upd-t{font-size:1.6rem;font-weight:600;color:var(--petrol-deep)}
.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:.6rem;margin-top:1.2rem}
.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}
.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.45rem;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}
.mapcol{position:sticky;top:74px}
#map{height:520px;border-radius:16px;overflow:hidden;border:1px solid var(--line);box-shadow:var(--shadow-card)}
#map{height:540px;border-radius:16px;overflow:hidden;border:1px solid var(--line);box-shadow:var(--shadow-card)}
.legend{display:flex;gap:1rem;flex-wrap:wrap;font-size:.8rem;color:var(--slate);margin-top:.5rem}
.feedcol{max-height:560px;overflow:auto}.h-sm{font-size:1.15rem;margin:.1rem 0 .2rem}
.feedcol{max-height:580px;overflow:auto}.h-sm{font-size:1.15rem;margin:.1rem 0 .2rem}
.feed{list-style:none;margin:.6rem 0 0;padding:0}
.feed li{display:flex;justify-content:space-between;gap:.6rem;padding:.55rem .2rem;border-bottom:1px solid var(--line)}
.feed .fl b{font-weight:600;font-size:.9rem}.feed .fl span{font-size:.8rem;color:var(--slate)}
.feed .ft{font-size:.78rem;color:var(--slate);white-space:nowrap;font-family:var(--mono)}
.toolbar{margin-top:1.4rem;display:flex;flex-direction:column;gap:.7rem}
.tb-row{display:flex;gap:.6rem;align-items:center;flex-wrap:wrap}
#q{flex:1;min-width:180px;font-family:var(--font-body);font-size:.95rem;padding:.6rem .8rem;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}
#q{flex:1;min-width:170px;font-family:var(--font-body);font-size:.95rem;padding:.6rem .8rem;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}
#q:focus{border-color:var(--petrol);background:#fff}
#model{padding:.55rem .7rem;border-radius:10px;border:1px solid var(--line);font-family:var(--font-body)}
.rad{display:flex;align-items:center;gap:.5rem;font-size:.85rem;color:var(--slate)}
@@ -103,68 +107,86 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
.chip:active{transform:scale(.97)}.chip.on{background:var(--petrol);color:#fff;border-color:var(--petrol)}
.chip.ghost.on{background:var(--ok-bg);color:var(--ok-dot);border-color:var(--ok-dot)}
.sep{width:1px;align-self:stretch;background:var(--line);margin:0 .2rem}
#chainchips .chip{font-size:.8rem;padding:.32rem .7rem}
.onlinebar{margin-top:.8rem;display:flex;gap:.5rem;flex-wrap:wrap;align-items:center;padding:.7rem .9rem}
.osrc{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:10px;padding:.4rem .65rem;min-width:120px}
.osrc b{font-size:.82rem}.osrc .num{font-size:.9rem}.osrc small{font-size:.72rem;color:var(--slate)}
#countrychips .chip{font-size:.82rem;padding:.34rem .7rem}
.scope{display:inline-flex;gap:.3rem}
.pricestrip{margin-top:.8rem;display:flex;gap:1.2rem;flex-wrap:wrap;align-items:center;padding:.8rem 1rem}
.pcell{display:flex;flex-direction:column}.pcell .num{font-size:1.05rem;font-weight:600}.pcell small{font-size:.72rem;color:var(--slate)}
.pcell.ok .num{color:var(--ok-dot)}.pcell.bad .num{color:var(--danger)}
.onlinebar{margin-top:.8rem;display:flex;gap:.5rem;flex-wrap:wrap;align-items:stretch;padding:.7rem .9rem}
.osrc{display:flex;flex-direction:column;gap:.1rem;border:1px solid var(--line);border-radius:10px;padding:.45rem .65rem;min-width:128px}
.osrc b{font-size:.82rem}.osrc .num{font-size:.92rem}.osrc small{font-size:.71rem;color:var(--slate)}
.osrc.wucher{border-color:var(--danger);background:var(--danger-bg)}.osrc.wucher .num{color:var(--danger)}
.trust{font-size:.68rem;color:var(--slate)}
.tablewrap{margin-top:.8rem;padding:0;overflow:hidden}
.mtab{width:100%;border-collapse:collapse;font-size:.9rem}
.mtab thead th{text-align:left;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--slate);font-weight:600;padding:.7rem .8rem;border-bottom:1px solid var(--line);background:var(--card);white-space:nowrap}
.mtab th.ce{text-align:center;cursor:pointer}
.mtab td{padding:.6rem .8rem;border-bottom:1px solid var(--line);vertical-align:middle}
.mtab tbody tr{animation:kf-in .3s ease both}
.mtab tbody tr:hover{background:var(--petrol-soft)}
.fname{font-weight:600;font-size:.92rem}.faddr{font-size:.78rem;color:var(--slate)}
.kindb{font-size:.66rem;padding:.05rem .4rem;border-radius:99px;background:var(--surface-2);color:var(--slate);margin-left:.4rem;border:1px solid var(--line)}
.ce .num{font-size:.86rem}
.stk{font-size:.72rem;font-weight:600}.stk.ok{color:var(--ok-dot)}.stk.off{color:var(--slate)}
.prog{display:inline-flex;align-items:center;gap:.4rem}.progbar{width:46px;height:6px;border-radius:99px;background:var(--line);overflow:hidden}.progbar i{display:block;height:100%;background:var(--warm)}
.prog{display:inline-flex;align-items:center;gap:.4rem}.progbar{width:46px;height:6px;border-radius:99px;background:var(--line);overflow:hidden}.progbar i{display:block;height:100%;background:var(--warm);transition:width .5s}
.more{display:flex;justify-content:space-between;align-items:center;padding:.8rem}
.alertcta{margin-top:1rem;display:flex;justify-content:space-between;align-items:center;gap:1rem;background:var(--petrol-soft);border-color:transparent}
@media(max-width:900px){.stats{grid-template-columns:repeat(3,1fr)}.mon-grid{grid-template-columns:1fr}.mapcol{position:static}.mon-hero-row{flex-direction:column}}
</style>
<script>
import maplibregl from 'maplibre-gl';
const euro=n=>n==null?'':new Intl.NumberFormat('de-DE',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(n);
const FLAG={DE:'🇩🇪',AT:'🇦🇹',CH:'🇨🇭',NL:'🇳🇱',PL:'🇵🇱',DK:'🇩🇰'};
const fmt=(n,c)=>n==null?'':new Intl.NumberFormat('de-DE',{style:'currency',currency:c||'EUR',maximumFractionDigits:0}).format(n);
const $=s=>document.querySelector(s),$$=s=>Array.from(document.querySelectorAll(s));
let DATA=null,center=null,shown=120;
const state={q:'',model:'all',chains:new Set(),kind:'all',avail:true,chance:false,radius:500};
const state={q:'',model:'all',countries:new Set(),kind:'all',avail:true,chance:false,radius:500};
const params=new URLSearchParams(location.search);
if(params.get('modell'))state.model=params.get('modell');
if(params.get('plz'))state.q=params.get('plz');
function dist(aLat,aLng,bLat,bLng){const R=6371,dLat=(bLat-aLat)*Math.PI/180,dLng=(bLng-aLng)*Math.PI/180;const x=Math.sin(dLat/2)**2+Math.cos(aLat*Math.PI/180)*Math.cos(bLat*Math.PI/180)*Math.sin(dLng/2)**2;return 2*R*Math.asin(Math.sqrt(x));}
function rel(iso){const d=(Date.now()-new Date(iso))/3600000;if(d<1)return 'vor '+Math.max(1,Math.round(d*60))+' Min';if(d<24)return 'vor '+Math.round(d)+' Std';return 'vor '+Math.round(d/24)+' Tg';}
function status(s){ if(state.model!=='all'){const m=(s.models||[]).find(x=>x.id===state.model); if(!m)return null; return {a:m.a,p:m.a?m.p:null,q:m.q,n:m.a?1:0};} return {a:s.any.a,p:s.any.minP,q:s.any.q,n:s.any.n}; }
function status(s){ if(state.model!=='all'){const m=(s.models||[]).find(x=>x.id===state.model); if(!m)return null; return {a:m.a,p:m.a?m.p:null,q:m.q};} return {a:s.any.a,p:s.any.minP,q:s.any.q,n:s.any.n}; }
function pass(s){ const st=status(s); if(!st)return false;
if(state.kind!=='all'&&s.kind!==state.kind)return false;
if(state.chains.size&&!state.chains.has(s.c))return false;
if(state.q){const q=state.q.toLowerCase();if(!(s.n.toLowerCase().includes(q)||s.ci.toLowerCase().includes(q)||s.z.startsWith(q)))return false;}
if(state.countries.size&&!state.countries.has(s.cc))return false;
if(state.q){const q=state.q.toLowerCase();if(!(s.n.toLowerCase().includes(q)||s.ci.toLowerCase().includes(q)||String(s.z).startsWith(q)))return false;}
if(center&&dist(center[1],center[0],s.la,s.lo)>state.radius)return false;
if(state.avail&&!st.a)return false;
return true; }
const stars=t=>'★'.repeat(t)+'☆'.repeat(5-t);
const map=new maplibregl.Map({container:'map',style:'https://tiles.openfreemap.org/styles/liberty',center:[10.2,51.1],zoom:5});
const map=new maplibregl.Map({container:'map',style:'https://tiles.openfreemap.org/styles/liberty',center:[9.6,50.6],zoom:4.4});
map.addControl(new maplibregl.NavigationControl(),'top-right');
function setMap(list){
const all={type:'FeatureCollection',features:list.map(s=>({type:'Feature',geometry:{type:'Point',coordinates:[s.lo,s.la]},properties:{}}))};
const av={type:'FeatureCollection',features:list.filter(s=>status(s)?.a).map(s=>({type:'Feature',geometry:{type:'Point',coordinates:[s.lo,s.la]},properties:{n:s.n,ci:s.ci}}))};
map.getSource('all').setData(all);map.getSource('av').setData(av);
}
function priceStrip(){
const el=$('#pricestrip');
if(state.model==='all'||!DATA.priceOverview[state.model]){el.hidden=true;return;}
const o=DATA.priceOverview[state.model]; const name=DATA.products[state.model]?.name||'';
el.hidden=false;
el.innerHTML=`<div class="pcell ok"><span class="num">${fmt(o.ebayLow)}</span><small>eBay günstigster</small></div>
<div class="pcell"><span class="num">${fmt(o.avg)}</span><small>üblicher Preis (UVP)</small></div>
<div class="pcell"><span class="num">${fmt(o.refurb)}</span><small>Retoure/refurbished</small></div>
<div class="pcell bad"><span class="num">${fmt(o.ebayHigh)}</span><small>eBay teuerster ⚠</small></div>
<div class="pcell" style="margin-left:auto"><small>Wucher ab</small><span class="num" style="color:var(--danger)">${fmt(o.wucherFrom)}</span></div>`;
}
function render(){
let list=DATA.stores.filter(pass);
if(center)list.sort((a,b)=>dist(center[1],center[0],a.la,a.lo)-dist(center[1],center[0],b.la,b.lo));
else if(state.chance)list.sort((a,b)=>(status(b)?.q||0)-(status(a)?.q||0));
else list.sort((a,b)=>(status(b)?.a?1:0)-(status(a)?.a?1:0)||(status(b)?.q||0)-(status(a)?.q||0));
setMap(list);
setMap(list); priceStrip();
const tb=$('#rows');tb.innerHTML='';
for(const s of list.slice(0,shown)){
const st=status(s);
const stk = st.a?`<span class="stk ok"><span class="dot ok"></span> ${state.model==='all'?st.n+' verfügbar':'verfügbar'}</span>`:`<span class="stk off"><span class="dot off"></span> vergriffen</span>`;
const distTd=center?`<td class="ce num">${dist(center[1],center[0],s.la,s.lo).toFixed(0)} km</td>`:'';
tb.insertAdjacentHTML('beforeend',`<tr><td><span class="fname">${s.n}</span><span class="kindb">${s.kind==='elektro'?'Elektro':'Baumarkt'}</span><div class="faddr">${s.z} ${s.ci}</div></td>
<td class="ce">${stk}</td><td class="ce num">${euro(st.p)}</td>
tb.insertAdjacentHTML('beforeend',`<tr><td><span class="flag">${s.flag||FLAG[s.cc]||''}</span> <span class="fname">${s.n}</span><span class="kindb">${s.kind==='elektro'?'Elektro':'Baumarkt'}</span><div class="faddr">${s.z} ${s.ci}</div></td>
<td class="ce">${stk}</td><td class="ce num">${fmt(st.p,s.cur)}</td>
<td class="ce"><span class="prog"><span class="progbar"><i style="width:${st.q||0}%"></i></span><span class="num">${st.q||0}%</span></span></td>${distTd}</tr>`);
}
$('#rowcount').textContent=`${Math.min(shown,list.length)} von ${list.length} Märkten`;
@@ -174,15 +196,13 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
function fillStatic(){
$('#h-updated').textContent=DATA.stats.updated;
$$('#stats b[data-k]').forEach(b=>b.textContent=(DATA.stats[b.dataset.k]??'').toLocaleString('de-DE'));
const r7=DATA.feed.filter(f=>(Date.now()-new Date(f.at))/86400000<=7).length;
$('#s-restocks').textContent=r7+(r7>=DATA.feed.length?'+':'');
$('#s-wucher').textContent=DATA.online.filter(o=>o.wucher).length;
$('#h-feednote').textContent='Feed live';
$('#feed').innerHTML=DATA.feed.slice(0,18).map(f=>`<li><div class="fl"><b>${f.name}</b><span>${f.model}</span></div><span class="ft">${rel(f.at)}</span></li>`).join('');
$('#onlinebar').innerHTML='<span class="note" style="align-self:center">Online:</span>'+DATA.online.map(o=>`<a class="osrc ${o.wucher?'wucher':''}" href="${o.url}" target="_blank" rel="nofollow noopener"><b>${o.src}${o.wucher?' ⚠':''}</b><span class="num">${euro(o.p)}</span><small>${o.model} · ${o.a?(o.wucher?'Wucher':'verfügbar'):'nicht verf.'}</small></a>`).join('');
const chains=[...new Set(DATA.stores.map(s=>s.c))].sort();
$('#chainchips').innerHTML=chains.map(c=>`<button class="chip ghost" data-chain="${c}">${c}</button>`).join(' ');
$$('#chainchips .chip').forEach(b=>b.addEventListener('click',()=>{const c=b.dataset.chain;state.chains.has(c)?(state.chains.delete(c),b.classList.remove('on')):(state.chains.add(c),b.classList.add('on'));render();}));
$('#feed').innerHTML=DATA.feed.slice(0,18).map(f=>`<li><div class="fl"><b>${FLAG[f.cc]||''} ${f.name}</b><span>${f.model}</span></div><span class="ft">${rel(f.at)}</span></li>`).join('');
$('#onlinebar').innerHTML='<span class="note" style="align-self:center">Online &amp; eBay:</span>'+DATA.online.map(o=>`<a class="osrc ${o.wucher?'wucher':''}" href="${o.url}" target="_blank" rel="nofollow noopener"><b>${o.src}${o.wucher?' ⚠':''}</b><span class="num">${fmt(o.p)}</span><small>${o.cond?o.cond+' · ':''}${o.a?(o.wucher?'Wucher':'verfügbar'):'nicht verf.'}</small><small class="trust" title="Seriosität">${stars(o.trust||3)}</small></a>`).join('');
const ccs=[...new Set(DATA.stores.map(s=>s.cc))];
$('#countrychips').innerHTML=ccs.map(c=>`<button class="chip ghost" data-cc="${c}">${FLAG[c]||''} ${c}</button>`).join(' ');
$$('#countrychips .chip').forEach(b=>b.addEventListener('click',()=>{const c=b.dataset.cc;state.countries.has(c)?(state.countries.delete(c),b.classList.remove('on')):(state.countries.add(c),b.classList.add('on'));render();}));
if(state.model!=='all')$('#model').value=state.model;
if(state.q)$('#q').value=state.q;
}
@@ -193,7 +213,7 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
map.addLayer({id:'pt',type:'circle',source:'all',filter:['!',['has','point_count']],paint:{'circle-color':'#2b6a72','circle-opacity':.6,'circle-radius':4,'circle-stroke-width':1,'circle-stroke-color':'#fff'}});
map.addSource('av',{type:'geojson',data:{type:'FeatureCollection',features:[]}});
map.addLayer({id:'av',type:'circle',source:'av',paint:{'circle-color':'#1d9e75','circle-radius':7,'circle-stroke-width':2,'circle-stroke-color':'#fff'}});
map.on('click','av',e=>{const p=e.features[0].properties;new maplibregl.Popup().setLngLat(e.lngLat).setHTML(`<b>${p.n}</b><br>${p.ci} Klimagerät verfügbar`).addTo(map);});
map.on('click','av',e=>{const p=e.features[0].properties;new maplibregl.Popup().setLngLat(e.lngLat).setHTML(`<b>${p.n}</b><br>${p.ci} verfügbar`).addTo(map);});
map.on('click','cl',e=>{const f=map.queryRenderedFeatures(e.point,{layers:['cl']});map.getSource('all').getClusterExpansionZoom(f[0].properties.cluster_id).then(z=>map.easeTo({center:f[0].geometry.coordinates,zoom:z}));});
if(DATA)render();
});
@@ -206,5 +226,11 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
$('#reset').addEventListener('click',()=>location.reload());
$$('.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();});});
$$('#scope .chip').forEach(b=>b.addEventListener('click',()=>{
$$('#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();}
$$('#countrychips .chip').forEach(c=>c.classList.toggle('on', state.countries.has(c.dataset.cc)));
render();
}));
fetch('/data/monitor.json').then(r=>r.json()).then(d=>{DATA=d;fillStatic();render();}).catch(()=>{$('#rowcount').textContent='Daten konnten nicht geladen werden';});
</script>