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:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
// Wetter-Kontext (Open-Meteo, kostenlos/ohne Key, EU). Psychologischer Nudge + Quellen.
|
||||||
|
---
|
||||||
|
<div class="wxbar reveal" id="wxbar" hidden>
|
||||||
|
<span class="wxico" id="wx-ico">🌡</span>
|
||||||
|
<span class="wxtxt" id="wx-txt">Wetterlage wird geladen …</span>
|
||||||
|
<span class="wxsrc">Quelle: <a href="https://open-meteo.com/" rel="nofollow noopener" target="_blank">Open-Meteo</a> · <a href="https://www.umweltbundesamt.de/themen/gesundheit/umwelteinfluesse-auf-den-menschen/hitze" rel="nofollow noopener" target="_blank">UBA: Hitze & Gesundheit</a></span>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.wxbar{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;background:var(--card);border:1px solid var(--line);border-left:4px solid var(--warm);border-radius:12px;padding:.7rem 1rem;box-shadow:var(--shadow-card);font-size:.92rem}
|
||||||
|
.wxbar.cold{border-left-color:var(--petrol)}
|
||||||
|
.wxico{font-size:1.2rem}
|
||||||
|
.wxtxt{font-weight:500}
|
||||||
|
.wxsrc{margin-left:auto;font-size:.76rem;color:var(--slate)}
|
||||||
|
.wxsrc a{color:var(--slate);text-decoration:underline}
|
||||||
|
@media(max-width:620px){.wxsrc{margin-left:0;width:100%}}
|
||||||
|
</style>
|
||||||
|
<script is:inline>
|
||||||
|
(function(){
|
||||||
|
const bar=document.getElementById('wxbar'), txt=document.getElementById('wx-txt'), ico=document.getElementById('wx-ico');
|
||||||
|
function show(lat,lng,place){
|
||||||
|
const u=`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}&daily=temperature_2m_max,temperature_2m_min&forecast_days=2&timezone=auto`;
|
||||||
|
fetch(u).then(r=>r.json()).then(d=>{
|
||||||
|
const mx=Math.round(d.daily.temperature_2m_max[0]);
|
||||||
|
const nmin=Math.round(d.daily.temperature_2m_min[1]??d.daily.temperature_2m_min[0]);
|
||||||
|
const where=place?` in ${place}`:'';
|
||||||
|
let msg, cold=false;
|
||||||
|
if(mx>=30||nmin>=20){ico.textContent='🥵';msg=`Tropennacht voraus: bis ${nmin}°C heute Nacht, ${mx}°C am Tag${where}. Klimageräte sind jetzt schnell vergriffen – Verfügbarkeit checken.`;}
|
||||||
|
else if(mx>=25){ico.textContent='🌡';msg=`Warm${where}: bis ${mx}°C. Gute Zeit, dir jetzt Verfügbarkeit zu sichern.`;}
|
||||||
|
else if(mx<15){ico.textContent='❄️';cold=true;msg=`Aktuell keine Hitzewelle${where} (${mx}°C). Beste Zeit, um günstig & in Ruhe zu kaufen – die nächste Hitzewelle kommt bestimmt.`;}
|
||||||
|
else {ico.textContent='⛅';msg=`Aktuell entspannt${where} (${mx}°C). Trotzdem clever: vor der nächsten Hitzewelle vorsorgen.`;}
|
||||||
|
if(cold)bar.classList.add('cold');
|
||||||
|
txt.textContent=msg; bar.hidden=false; window.kfAnim&&window.kfAnim();
|
||||||
|
}).catch(()=>{bar.remove();});
|
||||||
|
}
|
||||||
|
// Standort wenn bereits erlaubt, sonst Default (Deutschland-Mitte/Berlin)
|
||||||
|
if(navigator.permissions){ navigator.permissions.query({name:'geolocation'}).then(p=>{
|
||||||
|
if(p.state==='granted'&&navigator.geolocation){navigator.geolocation.getCurrentPosition(pos=>show(pos.coords.latitude,pos.coords.longitude,''),()=>show(52.52,13.405,'Berlin'));}
|
||||||
|
else show(52.52,13.405,'Berlin');
|
||||||
|
}).catch(()=>show(52.52,13.405,'Berlin')); }
|
||||||
|
else show(52.52,13.405,'Berlin');
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{"id":"idealo","name":"idealo","kind":"preisvergleich"},
|
||||||
|
{"id":"amazon","name":"Amazon","kind":"online"},
|
||||||
|
{"id":"mediamarkt","name":"MediaMarkt","kind":"online"}
|
||||||
|
]
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -27,6 +27,26 @@ import '../styles/global.css';
|
|||||||
</nav>
|
</nav>
|
||||||
</div></header>
|
</div></header>
|
||||||
<main><slot /></main>
|
<main><slot /></main>
|
||||||
|
<script is:inline>
|
||||||
|
(function(){
|
||||||
|
const rm = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
const io = ('IntersectionObserver' in window) ? new IntersectionObserver((es)=>{
|
||||||
|
es.forEach(e=>{ if(e.isIntersecting){ e.target.classList.add('in'); io.unobserve(e.target); } });
|
||||||
|
},{rootMargin:'0px 0px -8% 0px'}) : null;
|
||||||
|
function wire(){
|
||||||
|
document.querySelectorAll('.reveal:not(.in)').forEach(el=> io? io.observe(el): el.classList.add('in'));
|
||||||
|
document.querySelectorAll('[data-count]:not([data-done])').forEach(el=>{
|
||||||
|
const target=+el.getAttribute('data-count'); if(isNaN(target))return; el.setAttribute('data-done','1');
|
||||||
|
if(rm){ el.textContent=target.toLocaleString('de-DE'); return; }
|
||||||
|
const dur=900, t0=performance.now();
|
||||||
|
(function step(t){ const p=Math.min(1,(t-t0)/dur); const v=Math.round(target*(1-Math.pow(1-p,3)));
|
||||||
|
el.textContent=v.toLocaleString('de-DE'); if(p<1)requestAnimationFrame(step); })(t0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener('DOMContentLoaded',wire); wire();
|
||||||
|
window.kfAnim=wire;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<footer class="site"><div class="wrap">
|
<footer class="site"><div class="wrap">
|
||||||
<p class="note"><strong>Transparenz:</strong> Einige Links sind Affiliate-Links – kaufst du darüber, erhalten wir ggf. eine Provision, für dich ändert sich der Preis nicht. Empfehlungen basieren auf Nutzungsfall, Technik, Testquellen und redaktioneller Einschätzung – nicht auf Provision.</p>
|
<p class="note"><strong>Transparenz:</strong> Einige Links sind Affiliate-Links – kaufst du darüber, erhalten wir ggf. eine Provision, für dich ändert sich der Preis nicht. Empfehlungen basieren auf Nutzungsfall, Technik, Testquellen und redaktioneller Einschätzung – nicht auf Provision.</p>
|
||||||
<p class="note">Preise und Verfügbarkeit ändern sich während Hitzewellen sehr schnell. Wir halten Daten so aktuell wie möglich, geben aber keine Garantie. Bitte beim Händler final prüfen.</p>
|
<p class="note">Preise und Verfügbarkeit ändern sich während Hitzewellen sehr schnell. Wir halten Daten so aktuell wie möglich, geben aber keine Garantie. Bitte beim Händler final prüfen.</p>
|
||||||
|
|||||||
+37
-16
@@ -2,27 +2,48 @@
|
|||||||
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 für Klimageräte | Kühlfinder" description="Lass dich benachrichtigen, sobald dein Wunsch-Klimagerät in deiner Nähe verfügbar ist.">
|
<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.">
|
||||||
<section class="wrap" style="margin-top:1.6rem;max-width:640px">
|
<section class="wrap" style="margin-top:1.6rem;max-width:680px">
|
||||||
<p class="kicker"><span class="u"></span>Verfügbarkeits-Alarm</p>
|
<span class="eyebrow"><span class="dot ok live"></span>Alarm & Preiswecker</span>
|
||||||
<h1>Sag mir Bescheid, sobald es verfügbar ist</h1>
|
<h1>Sei der Erste, wenn dein Gerät da ist – oder günstig wird</h1>
|
||||||
<p class="muted">Kostenloser E-Mail-Alarm. Wir melden uns, wenn ein passendes Gerät in deinem Umkreis auftaucht. Keine Garantie auf Preis oder Bestand.</p>
|
<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>
|
||||||
<form class="card" id="alarmform" style="display:grid;gap:.9rem">
|
<form class="card alarmform" id="alarmform">
|
||||||
<label>Gerät<select name="produkt">
|
<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>
|
||||||
<option value="">Alle / egal</option>
|
<label>Gerät<select name="produkt"><option value="">Alle / egal</option>{allProducts.map(p=> <option value={p.slug}>{p.name}</option>)}</select></label>
|
||||||
{allProducts.map(p=> <option value={p.slug}>{p.name}</option>)}
|
<div class="row2">
|
||||||
</select></label>
|
<label>PLZ<input name="plz" inputmode="numeric" placeholder="z. B. 21255" required /></label>
|
||||||
<label>PLZ<input name="plz" inputmode="numeric" pattern="[0-9]{5}" 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></select></label>
|
</div>
|
||||||
|
<label id="pricewrap" hidden>Wunschpreis (max.)<input name="zielpreis" inputmode="numeric" placeholder="z. B. 750 €" /></label>
|
||||||
|
<label>Kanal
|
||||||
|
<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 style="font-weight:400;font-size:.9rem;display:flex;gap:.5rem"><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">Alarm aktivieren</button>
|
<button class="btn" type="submit">Wecker aktivieren</button>
|
||||||
<p class="note" id="alarmmsg"></p>
|
<p class="note" id="alarmmsg"></p>
|
||||||
</form>
|
</form>
|
||||||
<p class="note" style="margin-top:1rem">Später: Telegram/Push und ein Sommer-Pass für engmaschige Alerts. Heute startet der Alarm als kostenloser Lead-Magnet.</p>
|
<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>
|
||||||
</section>
|
</section>
|
||||||
</Base>
|
</Base>
|
||||||
|
<style>
|
||||||
|
.alarmform{display:grid;gap:.9rem;margin-top:1rem}
|
||||||
|
.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:focus,.alarmform select:focus{border-color:var(--petrol);background:#fff}
|
||||||
|
.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}
|
||||||
|
.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.on{background:var(--petrol);color:#fff}
|
||||||
|
@media(max-width:560px){.row2{grid-template-columns:1fr}}
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
const f=document.getElementById('alarmform');
|
const f=document.getElementById('alarmform');
|
||||||
f.addEventListener('submit',e=>{e.preventDefault();document.getElementById('alarmmsg').textContent='Demo: Alarm-Backend folgt (Listmonk/Telegram). Eingaben wurden nicht gespeichert.';});
|
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';
|
||||||
|
}));
|
||||||
|
f.addEventListener('submit',e=>{e.preventDefault();document.getElementById('alarmmsg').textContent='Demo: Eingaben wurden nicht gespeichert. Alarm-/Preiswecker-Backend (Listmonk + Telegram) folgt.';});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+83
-61
@@ -2,102 +2,124 @@
|
|||||||
import Base from '../layouts/Base.astro';
|
import Base from '../layouts/Base.astro';
|
||||||
import ProductCard from '../components/ProductCard.astro';
|
import ProductCard from '../components/ProductCard.astro';
|
||||||
import { allProducts } from '../lib/pricing';
|
import { allProducts } from '../lib/pricing';
|
||||||
|
import WeatherBanner from '../components/WeatherBanner.astro';
|
||||||
const top = allProducts.filter(p => p.tier === 'A').slice(0,3);
|
const top = allProducts.filter(p => p.tier === 'A').slice(0,3);
|
||||||
const jsonld = {'@context':'https://schema.org','@type':'WebSite',name:'Kühlfinder',url:'https://kuehlfinder.de',
|
const jsonld = {'@context':'https://schema.org','@type':'WebSite',name:'Kühlfinder',url:'https://kuehlfinder.de',
|
||||||
description:'Finde, wo es gerade Klimageräte in Baumärkten und Elektromärkten gibt – mit Prognose, Preis-Check und Alarm.'};
|
description:'Finde live, wo es Klimageräte gibt – Baumärkte & Elektromärkte in DE, AT, CH, NL, PL, DK, plus Online/eBay – mit Prognose, Preis-Check und Alarm.'};
|
||||||
|
const Pin='<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M12 21s7-5.6 7-11a7 7 0 1 0-14 0c0 5.4 7 11 7 11Z"/><circle cx="12" cy="10" r="2.5"/></svg>';
|
||||||
|
const Compass='<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.7"><circle cx="12" cy="12" r="9"/><path d="m15.5 8.5-2 5-5 2 2-5 5-2Z"/></svg>';
|
||||||
|
const Bell='<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M6 9a6 6 0 1 1 12 0c0 5 2 6 2 6H4s2-1 2-6Z"/><path d="M10 19a2 2 0 0 0 4 0"/></svg>';
|
||||||
---
|
---
|
||||||
<Base title="Kühlfinder – Wo gibt es gerade ein Klimagerät? Live-Verfügbarkeit in DE" description="Live-Bestand von Klimageräten in Baumärkten (OBI, Toom, Bauhaus …) und Elektromärkten (MediaMarkt, Saturn …) plus Amazon/idealo – mit Verfügbarkeits-Prognose, Preis-Check und Alarm." jsonld={jsonld}>
|
<Base title="Kühlfinder – Wo gibt es gerade ein Klimagerät? Live-Verfügbarkeit (DE/AT/CH/NL/PL/DK)" description="Live-Bestand von Klimageräten in Baumärkten & Elektromärkten in Deutschland und Nachbarländern, plus Online, Amazon & eBay – mit Prognose, Preis-Check gegen Wucher und Alarm.">
|
||||||
<section class="hero"><div class="wrap">
|
<section class="hero"><div class="wrap">
|
||||||
<span class="eyebrow"><span class="dot ok"></span>Live-Verfügbarkeit · eigener Scraper</span>
|
<WeatherBanner />
|
||||||
|
<span class="eyebrow" style="margin-top:1rem"><span class="dot ok live"></span>Live-Verfügbarkeit · eigener Scraper</span>
|
||||||
<h1>Wo gibt es gerade ein Klimagerät?</h1>
|
<h1>Wo gibt es gerade ein Klimagerät?</h1>
|
||||||
<p class="lead">Klimageräte sind bei Hitze überall vergriffen. Kühlfinder zeigt dir <strong>live, welcher Markt in deiner Nähe eins auf Lager hat</strong> – Baumärkte und Elektromärkte – mit Verfügbarkeits-Prognose, Preis-Check gegen Wucher und Alarm, sobald wieder was da ist.</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 & 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 eingeben …" 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">Verfügbarkeit zeigen</button>
|
||||||
|
<a class="btn ghost" href="/karte" id="locbtn">In meiner Nähe</a>
|
||||||
</form>
|
</form>
|
||||||
<div class="pills" id="livepills">
|
<div class="livewrap reveal">
|
||||||
<span class="pill-stat"><b id="p-ac">…</b> Märkte mit Klimagerät jetzt</span>
|
<div class="pills">
|
||||||
<span class="pill-stat"><b id="p-total">…</b> Märkte überwacht</span>
|
<span class="pill-stat"><b class="num" id="p-ac" data-count="0">…</b> Märkte mit Klimagerät jetzt</span>
|
||||||
<span class="pill-stat"><b id="p-chains">10</b> Ketten · Baumarkt + Elektro</span>
|
<span class="pill-stat"><b class="num" id="p-total" data-count="0">…</b> Märkte überwacht</span>
|
||||||
|
<span class="pill-stat"><b class="num">6</b> Länder · <span id="p-chains">…</span> Ketten</span>
|
||||||
|
</div>
|
||||||
|
<div class="livecard card">
|
||||||
|
<div class="lc-head"><span class="dot ok live"></span> Gerade verfügbar</div>
|
||||||
|
<ul class="livelist" id="livelist"><li class="skel" style="height:40px"></li><li class="skel" style="height:40px"></li><li class="skel" style="height:40px"></li></ul>
|
||||||
|
<a class="btn sm" href="/karte">Alle auf der Karte →</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div></section>
|
</div></section>
|
||||||
|
|
||||||
<section class="wrap">
|
<section class="wrap">
|
||||||
<div class="features">
|
<div class="features">
|
||||||
<a class="feature" href="/karte">
|
<a class="feature reveal" href="/karte"><div class="ic" set:html={Pin}></div><h3>Live-Karte</h3><p class="muted">Alle Märkte mit Bestand auf einen Blick – Modell, Kette, Land & Umkreis filtern.</p></a>
|
||||||
<div class="ic"><i class="ti">◎</i></div>
|
<a class="feature reveal" data-d="1" href="/finder"><div class="ic" set:html={Compass}></div><h3>60-Sekunden-Finder</h3><p class="muted">Welches Gerät passt zu deinem Raum? Nur echte Kompressorgeräte.</p></a>
|
||||||
<h3>Live-Karte</h3>
|
<a class="feature reveal" data-d="2" href="/alarm"><div class="ic" set:html={Bell}></div><h3>Prognose & Alarm</h3><p class="muted">Wir schätzen, wo die Chance in 7 Tagen am höchsten ist – Mail & Telegram.</p></a>
|
||||||
<p class="muted">Alle Märkte mit aktuellem Bestand auf einen Blick – filterbar nach Modell, Kette und Umkreis.</p>
|
</div>
|
||||||
</a>
|
</section>
|
||||||
<a class="feature" href="/finder">
|
|
||||||
<div class="ic">◆</div>
|
<section class="wrap"><div class="band reveal">
|
||||||
<h3>60-Sekunden-Finder</h3>
|
<div>
|
||||||
<p class="muted">Welches Gerät passt zu deinem Raum? Ehrliche Empfehlung – nur echte Kompressorgeräte.</p>
|
<h2>Vergriffen? Wir warnen vor Wucher.</h2>
|
||||||
</a>
|
<p class="muted" style="max-width:42rem">Bei Knappheit verlangen Drittanbieter auf Amazon & eBay schnell das Mehrfache. Kühlfinder kennt den üblichen Preis, zeigt <strong>günstigsten und teuersten</strong> Anbieter, prüft die <strong>Seriosität der Shops</strong> – und markiert Wucher.</p>
|
||||||
<a class="feature" href="/alarm">
|
<div class="bandcta"><a class="btn" href="/karte">Zur Live-Karte</a><a class="btn ghost" href="/portasplit">PortaSplit & Alternativen</a></div>
|
||||||
<div class="ic">◔</div>
|
</div>
|
||||||
<h3>Prognose & Alarm</h3>
|
<div class="band-card card">
|
||||||
<p class="muted">Wir schätzen, wo die Chance in 7 Tagen am höchsten ist – und melden uns per Mail/Telegram.</p>
|
<div class="bc-row ok"><span>eBay günstigster</span><b class="num">607 €</b></div>
|
||||||
</a>
|
<div class="bc-row"><span>Üblicher Preis</span><b class="num">799 €</b></div>
|
||||||
|
<div class="bc-row danger"><span>⚠ eBay/Marketplace-Wucher</span><b class="num">2.999 €</b></div>
|
||||||
|
</div>
|
||||||
|
</div></section>
|
||||||
|
|
||||||
|
<section class="wrap">
|
||||||
|
<div class="section-head"><div><span class="kicker"><span class="u"></span>Grenzregionen</span><h2 style="margin:.2rem 0 0">Auch im Nachbarland verfügbar</h2></div></div>
|
||||||
|
<div class="countries reveal">
|
||||||
|
<span class="cflag"><span class="flag">🇩🇪</span> Deutschland</span>
|
||||||
|
<span class="cflag"><span class="flag">🇦🇹</span> Österreich</span>
|
||||||
|
<span class="cflag"><span class="flag">🇨🇭</span> Schweiz</span>
|
||||||
|
<span class="cflag"><span class="flag">🇳🇱</span> Niederlande</span>
|
||||||
|
<span class="cflag"><span class="flag">🇵🇱</span> Polen</span>
|
||||||
|
<span class="cflag"><span class="flag">🇩🇰</span> Dänemark</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="wrap">
|
<section class="wrap">
|
||||||
<div class="section-head">
|
<div class="section-head"><div><span class="kicker"><span class="u"></span>Kuratiert</span><h2 style="margin:.2rem 0 0">Beliebte Geräte – und wo es sie gibt</h2></div><a class="btn ghost sm" href="/geraete">Alle Geräte</a></div>
|
||||||
<div><span class="kicker"><span class="u"></span>Kuratiert</span><h2 style="margin:.2rem 0 0">Beliebte Geräte – und wo es sie gibt</h2></div>
|
<div class="grid cards3">{top.map((p,i) => <div class="reveal" data-d={String(i)}><ProductCard product={p} /></div>)}</div>
|
||||||
<a class="btn ghost sm" href="/geraete">Alle Geräte & Vergleich</a>
|
|
||||||
</div>
|
|
||||||
<div class="grid cards3">{top.map(p => <ProductCard product={p} />)}</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="wrap"><div class="band">
|
|
||||||
<div>
|
|
||||||
<h2>Vergriffen? Wir warnen vor Wucher.</h2>
|
|
||||||
<p class="muted" style="max-width:42rem">Bei Knappheit verlangen Drittanbieter (z. B. Amazon-Marketplace) schnell das Mehrfache. Kühlfinder kennt den üblichen Preis und markiert Wucher – und schlägt dir wohnungstaugliche Alternativen vor, die wirklich kühlen.</p>
|
|
||||||
<div class="bandcta"><a class="btn" href="/karte">Zur Live-Karte</a><a class="btn ghost" href="/portasplit">PortaSplit & Alternativen</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="band-card card">
|
|
||||||
<div class="bc-row"><span>Üblicher Preis</span><b class="num">799 €</b></div>
|
|
||||||
<div class="bc-row danger"><span>⚠ Marketplace-Wucher</span><b class="num">2.999 €</b></div>
|
|
||||||
<div class="bc-row ok"><span>Fair ab</span><b class="num">699 €</b></div>
|
|
||||||
</div>
|
|
||||||
</div></section>
|
|
||||||
|
|
||||||
<section class="wrap"><div class="split">
|
<section class="wrap"><div class="split">
|
||||||
<div class="card"><h2>So funktioniert's</h2>
|
<div class="card reveal"><h2>So funktioniert's</h2><ol class="steps"><li>PLZ eingeben oder Standort nutzen.</li><li>Karte zeigt Märkte mit Bestand + Prognose.</li><li>Hingehen – oder Alarm/Preiswecker setzen.</li></ol></div>
|
||||||
<ol class="steps"><li>PLZ eingeben oder Standort nutzen.</li><li>Karte zeigt Märkte mit Bestand + Prognose.</li><li>Hingehen, oder Alarm setzen für später.</li></ol>
|
<div class="card warm reveal" data-d="1"><h2>Hitzeschutz statt Panik</h2><p class="muted">Hitzewellen nehmen zu. Gute, effiziente Kühlung kann Hitzeschutz sein – mit Quellen statt Alarmismus.</p><a class="btn ghost" href="/hitzeschutz">Klima & Hitzeschutz</a></div>
|
||||||
</div>
|
|
||||||
<div class="card warm"><h2>Hitzeschutz statt Panik</h2>
|
|
||||||
<p class="muted">Hitzewellen nehmen zu. Gute, effiziente Kühlung kann Hitzeschutz sein – mit Quellen statt Alarmismus.</p>
|
|
||||||
<a class="btn ghost" href="/hitzeschutz">Klima & Hitzeschutz</a>
|
|
||||||
</div>
|
|
||||||
</div></section>
|
</div></section>
|
||||||
</Base>
|
</Base>
|
||||||
<style>
|
<style>
|
||||||
.hero{background:var(--grad-hero);padding:3.4rem 0 2.6rem}
|
.hero{background:var(--grad-hero);padding:3.4rem 0 2.8rem}
|
||||||
.hero h1{font-size:clamp(2.3rem,6vw,4rem);max-width:18ch;margin-top:.6rem}
|
.hero h1{font-size:clamp(2.3rem,6vw,4rem);max-width:18ch;margin-top:.6rem}
|
||||||
.hero .lead{font-size:1.18rem;max-width:46rem;color:var(--ink-2)}
|
.hero .lead{font-size:1.15rem;max-width:48rem;color:var(--ink-2)}
|
||||||
.herobar{display:flex;gap:.6rem;margin:1.4rem 0 1rem;max-width:520px}
|
.hero .lead em{color:var(--petrol-deep);font-style:normal;font-weight:600}
|
||||||
.herobar input{flex:1;font-family:var(--font-body);font-size:1rem;padding:.85rem 1.1rem;border:1px solid var(--line);border-radius:999px;background:#fff;box-shadow:var(--shadow-card)}
|
.herobar{display:flex;gap:.6rem;margin:1.4rem 0 1.4rem;max-width:640px;flex-wrap:wrap}
|
||||||
|
.herobar input{flex:1;min-width:180px;font-family:var(--font-body);font-size:1rem;padding:.85rem 1.1rem;border:1px solid var(--line);border-radius:999px;background:#fff;box-shadow:var(--shadow-card)}
|
||||||
.herobar input:focus{border-color:var(--petrol)}
|
.herobar input:focus{border-color:var(--petrol)}
|
||||||
.pills{display:flex;gap:.6rem;flex-wrap:wrap}
|
.livewrap{display:grid;grid-template-columns:1fr 320px;gap:1.2rem;align-items:start;margin-top:.4rem}
|
||||||
section.wrap{margin-top:2.6rem}
|
.pills{display:flex;gap:.6rem;flex-wrap:wrap;align-content:start}
|
||||||
|
.livecard{display:flex;flex-direction:column;gap:.6rem;padding:1rem}
|
||||||
|
.lc-head{display:flex;align-items:center;gap:.5rem;font-weight:600;font-size:.9rem}
|
||||||
|
.livelist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.1rem}
|
||||||
|
.livelist li{display:flex;justify-content:space-between;align-items:center;gap:.5rem;padding:.45rem .1rem;border-bottom:1px solid var(--line);font-size:.88rem}
|
||||||
|
.livelist .lm{font-weight:600}.livelist .lc{font-size:.78rem;color:var(--slate)}
|
||||||
|
.livelist .lp{font-family:var(--mono);font-size:.82rem;color:var(--petrol-deep)}
|
||||||
|
section.wrap{margin-top:2.8rem}
|
||||||
.features{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}
|
.features{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}
|
||||||
.features .feature{text-decoration:none;color:var(--ink)} .features h3{margin:.2rem 0}
|
.features .feature{text-decoration:none;color:var(--ink)}.features h3{margin:.2rem 0}
|
||||||
|
.features .ic{color:var(--petrol-deep)}
|
||||||
.cards3{grid-template-columns:repeat(3,1fr)}
|
.cards3{grid-template-columns:repeat(3,1fr)}
|
||||||
.band{display:grid;grid-template-columns:1.6fr 1fr;gap:1.5rem;align-items:center;background:var(--petrol-soft);border-radius:20px;padding:1.8rem}
|
.band{display:grid;grid-template-columns:1.6fr 1fr;gap:1.5rem;align-items:center;background:var(--petrol-soft);border-radius:20px;padding:1.8rem}
|
||||||
.bandcta{display:flex;gap:.6rem;margin-top:1rem;flex-wrap:wrap}
|
.bandcta{display:flex;gap:.6rem;margin-top:1rem;flex-wrap:wrap}
|
||||||
.band-card{display:flex;flex-direction:column;gap:.5rem}
|
.band-card{display:flex;flex-direction:column;gap:.4rem}
|
||||||
.bc-row{display:flex;justify-content:space-between;align-items:center;padding:.5rem .2rem;border-bottom:1px solid var(--line)}
|
.bc-row{display:flex;justify-content:space-between;align-items:center;padding:.5rem .2rem;border-bottom:1px solid var(--line)}
|
||||||
.bc-row b{font-size:1.1rem}.bc-row.danger b{color:var(--danger)}.bc-row.ok b{color:var(--ok-dot)}
|
.bc-row b{font-size:1.1rem}.bc-row.danger b{color:var(--danger)}.bc-row.ok b{color:var(--ok-dot)}
|
||||||
|
.countries{display:flex;gap:.6rem;flex-wrap:wrap}
|
||||||
|
.cflag{display:inline-flex;align-items:center;gap:.5rem;background:var(--card);border:1px solid var(--line);border-radius:999px;padding:.45rem .9rem;box-shadow:var(--shadow-card);font-size:.9rem;font-weight:500}
|
||||||
.split{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.card.warm{background:var(--warm-bg)}
|
.split{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.card.warm{background:var(--warm-bg)}
|
||||||
.steps{margin:.4rem 0 0;padding-left:1.2rem}.steps li{margin:.3rem 0}
|
.steps{margin:.4rem 0 0;padding-left:1.2rem}.steps li{margin:.3rem 0}
|
||||||
.ic i{font-style:normal}
|
@media(max-width:860px){.livewrap{grid-template-columns:1fr}.features,.cards3,.split{grid-template-columns:1fr}.band{grid-template-columns:1fr}}
|
||||||
@media(max-width:820px){.features,.cards3,.split{grid-template-columns:1fr}.band{grid-template-columns:1fr}}
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
const euro=(n,c)=>n==null?'–':new Intl.NumberFormat('de-DE',{style:'currency',currency:c||'EUR',maximumFractionDigits:0}).format(n);
|
||||||
|
document.getElementById('locbtn')?.addEventListener('click',e=>{ if(navigator.geolocation){ /* Karte hat eigene Standortfunktion */ } });
|
||||||
fetch('/data/monitor.json').then(r=>r.json()).then(d=>{
|
fetch('/data/monitor.json').then(r=>r.json()).then(d=>{
|
||||||
document.getElementById('p-ac').textContent=(d.stats.withAC??'–').toLocaleString('de-DE');
|
const ac=document.getElementById('p-ac'), tot=document.getElementById('p-total');
|
||||||
document.getElementById('p-total').textContent=(d.stats.storesTotal??'–').toLocaleString('de-DE');
|
ac.setAttribute('data-count', d.stats.withAC); ac.removeAttribute('data-done');
|
||||||
|
tot.setAttribute('data-count', d.stats.storesTotal); tot.removeAttribute('data-done');
|
||||||
|
const chains=new Set(d.stores.map(s=>s.c)); document.getElementById('p-chains').textContent=chains.size;
|
||||||
|
window.kfAnim && window.kfAnim();
|
||||||
|
const avail=d.stores.filter(s=>s.any.a && s.any.minP).sort(()=>Math.random()-0.5).slice(0,6);
|
||||||
|
document.getElementById('livelist').innerHTML = avail.map(s=>`<li><span><span class="flag">${s.flag||''}</span> <span class="lm">${s.c}</span> <span class="lc">${s.ci}</span></span><span class="lp">${euro(s.any.minP,s.cur)}</span></li>`).join('') || '<li class="lc">Aktuell sehr knapp – Alarm setzen lohnt sich.</li>';
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+75
-49
@@ -1,17 +1,19 @@
|
|||||||
---
|
---
|
||||||
import Base from '../layouts/Base.astro';
|
import Base from '../layouts/Base.astro';
|
||||||
|
import WeatherBanner from '../components/WeatherBanner.astro';
|
||||||
import { allProducts } from '../lib/pricing';
|
import { allProducts } from '../lib/pricing';
|
||||||
const models = allProducts.map(p=>({slug:p.slug,name:p.name}));
|
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',
|
const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kühlfinder Kontrollzentrum',applicationCategory:'ShoppingApplication',
|
||||||
description:'Live-Verfügbarkeit von Klimageräten in Baumärkten und Elektromärkten in Deutschland, mit Verfügbarkeits-Prognose.'};
|
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">
|
<section class="mon-hero"><div class="wrap">
|
||||||
<div class="mon-hero-row">
|
<WeatherBanner />
|
||||||
|
<div class="mon-hero-row" style="margin-top:1rem">
|
||||||
<div>
|
<div>
|
||||||
<span class="eyebrow"><span class="dot ok"></span>Live · eigener Scraper</span>
|
<span class="eyebrow"><span class="dot ok live"></span>Live · eigener Scraper · 6 Länder</span>
|
||||||
<h1>Wo gibt es gerade ein Klimagerät?</h1>
|
<h1>Dein Klimagerät-Kontrollzentrum</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>
|
<p class="lead">Live-Bestand in Baumärkten, Elektromärkten, Online & eBay – mit <strong>Verfügbarkeits-Prognose</strong> und <strong>Preis-Check gegen Wucher</strong>.</p>
|
||||||
</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>
|
||||||
@@ -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 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">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">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 class="stat warn"><span class="note">Wucher-Alarm</span><b class="num" id="s-wucher">–</b></div>
|
||||||
</div>
|
</div>
|
||||||
</div></section>
|
</div></section>
|
||||||
@@ -28,10 +30,7 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
|
|||||||
<section class="wrap mon-grid">
|
<section class="wrap mon-grid">
|
||||||
<div class="mapcol">
|
<div class="mapcol">
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<div class="legend">
|
<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>
|
||||||
<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>
|
</div>
|
||||||
<aside class="feedcol card">
|
<aside class="feedcol card">
|
||||||
<h2 class="h-sm">Letzte Verfügbarkeiten</h2>
|
<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">
|
<section class="wrap">
|
||||||
<div class="toolbar card">
|
<div class="toolbar card">
|
||||||
<div class="tb-row">
|
<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>
|
<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>
|
<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>
|
<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 on" data-toggle="avail">Nur verfügbare</button>
|
||||||
<button class="chip ghost" data-toggle="chance">Beste Prognose</button>
|
<button class="chip ghost" data-toggle="chance">Beste Prognose</button>
|
||||||
<span class="sep"></span>
|
<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="baumarkt">Baumarkt</button>
|
||||||
<button class="chip" data-kind="elektro">Elektro</button>
|
<button class="chip" data-kind="elektro">Elektro</button>
|
||||||
<span class="sep"></span>
|
<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>
|
<button class="chip ghost" id="reset" type="button">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pricestrip card" id="pricestrip" hidden></div>
|
||||||
<div class="onlinebar card" id="onlinebar"></div>
|
<div class="onlinebar card" id="onlinebar"></div>
|
||||||
|
|
||||||
<div class="tablewrap card">
|
<div class="tablewrap card">
|
||||||
<table class="mtab"><thead><tr>
|
<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>
|
<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>
|
</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 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>
|
||||||
|
|
||||||
<div class="alertcta card">
|
<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>
|
<div><h2 class="h-sm">Nichts in der Nähe?</h2><p class="note" style="margin:0">Verfügbarkeits-Alarm + Preiswecker – Mail & Telegram, mit Prognose-Frühwarnung.</p></div>
|
||||||
<a class="btn" href="/alarm">Alarm einrichten</a>
|
<a class="btn" href="/alarm">Alarm & Preiswecker</a>
|
||||||
</div>
|
</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>
|
</section>
|
||||||
</Base>
|
</Base>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@4.7.1/dist/maplibre-gl.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@4.7.1/dist/maplibre-gl.css" />
|
||||||
<style>
|
<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-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}
|
.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)}
|
.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.2rem}
|
.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{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}
|
.mon-grid{display:grid;grid-template-columns:1.7fr 1fr;gap:1rem;margin-top:1.2rem;align-items:start}
|
||||||
.mapcol{position:sticky;top:74px}
|
.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}
|
.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{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 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 .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)}
|
.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}
|
.toolbar{margin-top:1.4rem;display:flex;flex-direction:column;gap:.7rem}
|
||||||
.tb-row{display:flex;gap:.6rem;align-items:center;flex-wrap:wrap}
|
.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}
|
#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)}
|
#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)}
|
.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: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)}
|
.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}
|
.sep{width:1px;align-self:stretch;background:var(--line);margin:0 .2rem}
|
||||||
#chainchips .chip{font-size:.8rem;padding:.32rem .7rem}
|
#countrychips .chip{font-size:.82rem;padding:.34rem .7rem}
|
||||||
.onlinebar{margin-top:.8rem;display:flex;gap:.5rem;flex-wrap:wrap;align-items:center;padding:.7rem .9rem}
|
.scope{display:inline-flex;gap:.3rem}
|
||||||
.osrc{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:10px;padding:.4rem .65rem;min-width:120px}
|
.pricestrip{margin-top:.8rem;display:flex;gap:1.2rem;flex-wrap:wrap;align-items:center;padding:.8rem 1rem}
|
||||||
.osrc b{font-size:.82rem}.osrc .num{font-size:.9rem}.osrc small{font-size:.72rem;color:var(--slate)}
|
.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)}
|
.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}
|
.tablewrap{margin-top:.8rem;padding:0;overflow:hidden}
|
||||||
.mtab{width:100%;border-collapse:collapse;font-size:.9rem}
|
.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 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 th.ce{text-align:center;cursor:pointer}
|
||||||
.mtab td{padding:.6rem .8rem;border-bottom:1px solid var(--line);vertical-align:middle}
|
.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)}
|
.mtab tbody tr:hover{background:var(--petrol-soft)}
|
||||||
.fname{font-weight:600;font-size:.92rem}.faddr{font-size:.78rem;color:var(--slate)}
|
.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)}
|
.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}
|
.ce .num{font-size:.86rem}
|
||||||
.stk{font-size:.72rem;font-weight:600}.stk.ok{color:var(--ok-dot)}.stk.off{color:var(--slate)}
|
.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}
|
.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}
|
.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}}
|
@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>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import maplibregl from 'maplibre-gl';
|
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));
|
const $=s=>document.querySelector(s),$$=s=>Array.from(document.querySelectorAll(s));
|
||||||
let DATA=null,center=null,shown=120;
|
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);
|
const params=new URLSearchParams(location.search);
|
||||||
if(params.get('modell'))state.model=params.get('modell');
|
if(params.get('modell'))state.model=params.get('modell');
|
||||||
if(params.get('plz'))state.q=params.get('plz');
|
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 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 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;
|
function pass(s){ const st=status(s); if(!st)return false;
|
||||||
if(state.kind!=='all'&&s.kind!==state.kind)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.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)||s.z.startsWith(q)))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(center&&dist(center[1],center[0],s.la,s.lo)>state.radius)return false;
|
||||||
if(state.avail&&!st.a)return false;
|
if(state.avail&&!st.a)return false;
|
||||||
return true; }
|
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');
|
map.addControl(new maplibregl.NavigationControl(),'top-right');
|
||||||
|
|
||||||
function setMap(list){
|
function setMap(list){
|
||||||
const all={type:'FeatureCollection',features:list.map(s=>({type:'Feature',geometry:{type:'Point',coordinates:[s.lo,s.la]},properties:{}}))};
|
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}}))};
|
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);
|
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(){
|
function render(){
|
||||||
let list=DATA.stores.filter(pass);
|
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));
|
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 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));
|
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='';
|
const tb=$('#rows');tb.innerHTML='';
|
||||||
for(const s of list.slice(0,shown)){
|
for(const s of list.slice(0,shown)){
|
||||||
const st=status(s);
|
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 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>`:'';
|
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>
|
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">${euro(st.p)}</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>`);
|
<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`;
|
$('#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(){
|
function fillStatic(){
|
||||||
$('#h-updated').textContent=DATA.stats.updated;
|
$('#h-updated').textContent=DATA.stats.updated;
|
||||||
$$('#stats b[data-k]').forEach(b=>b.textContent=(DATA.stats[b.dataset.k]??'–').toLocaleString('de-DE'));
|
$$('#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;
|
$('#s-wucher').textContent=DATA.online.filter(o=>o.wucher).length;
|
||||||
$('#h-feednote').textContent='Feed live';
|
$('#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('');
|
$('#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:</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('');
|
$('#onlinebar').innerHTML='<span class="note" style="align-self:center">Online & 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 chains=[...new Set(DATA.stores.map(s=>s.c))].sort();
|
const ccs=[...new Set(DATA.stores.map(s=>s.cc))];
|
||||||
$('#chainchips').innerHTML=chains.map(c=>`<button class="chip ghost" data-chain="${c}">${c}</button>`).join(' ');
|
$('#countrychips').innerHTML=ccs.map(c=>`<button class="chip ghost" data-cc="${c}">${FLAG[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();}));
|
$$('#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.model!=='all')$('#model').value=state.model;
|
||||||
if(state.q)$('#q').value=state.q;
|
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.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.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.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}));});
|
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();
|
if(DATA)render();
|
||||||
});
|
});
|
||||||
@@ -206,5 +226,11 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
|
|||||||
$('#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;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';});
|
fetch('/data/monitor.json').then(r=>r.json()).then(d=>{DATA=d;fillStatic();render();}).catch(()=>{$('#rowcount').textContent='Daten konnten nicht geladen werden';});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -100,3 +100,14 @@ header.site .brand{letter-spacing:-.02em}
|
|||||||
.pill-stat{display:inline-flex;align-items:center;gap:.5rem;background:var(--card);border:1px solid var(--line);border-radius:999px;padding:.45rem .9rem;box-shadow:var(--shadow-card);font-size:.9rem}
|
.pill-stat{display:inline-flex;align-items:center;gap:.5rem;background:var(--card);border:1px solid var(--line);border-radius:999px;padding:.45rem .9rem;box-shadow:var(--shadow-card);font-size:.9rem}
|
||||||
.pill-stat b{font-family:var(--mono);color:var(--petrol-deep)}
|
.pill-stat b{font-family:var(--mono);color:var(--petrol-deep)}
|
||||||
.section-head{display:flex;align-items:end;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:1rem}
|
.section-head{display:flex;align-items:end;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:1rem}
|
||||||
|
|
||||||
|
/* --- v4 animations + polish --- */
|
||||||
|
@keyframes kf-pulse{0%{box-shadow:0 0 0 0 oklch(0.62 0.13 155 / .5)}70%{box-shadow:0 0 0 8px oklch(0.62 0.13 155 / 0)}100%{box-shadow:0 0 0 0 oklch(0.62 0.13 155 / 0)}}
|
||||||
|
.dot.live{animation:kf-pulse 2.2s infinite}
|
||||||
|
.reveal{opacity:0;transform:translateY(16px)}
|
||||||
|
.reveal.in{opacity:1;transform:none;transition:opacity .6s var(--ease,cubic-bezier(.16,1,.3,1)),transform .6s var(--ease,cubic-bezier(.16,1,.3,1))}
|
||||||
|
.reveal[data-d="1"]{transition-delay:.08s}.reveal[data-d="2"]{transition-delay:.16s}.reveal[data-d="3"]{transition-delay:.24s}
|
||||||
|
@keyframes kf-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
||||||
|
.anim-in{animation:kf-in .45s cubic-bezier(.16,1,.3,1) both}
|
||||||
|
.flag{font-size:1.05em;line-height:1}
|
||||||
|
@media (prefers-reduced-motion: reduce){.reveal{opacity:1!important;transform:none!important}.dot.live{animation:none}}
|
||||||
|
|||||||
Reference in New Issue
Block a user