From 47bc84ef422db876851f0114ec2dca15e055aaac Mon Sep 17 00:00:00 2001 From: "Till (Cowork)" Date: Mon, 29 Jun 2026 14:33:28 +0000 Subject: [PATCH] =?UTF-8?q?Codex-Rescue:=20karte.astro=20surl-Runtime-Bug?= =?UTF-8?q?=20fix;=20Textwahrheit=20(/=20+=20/karte=20+=20llms-full=20+=20?= =?UTF-8?q?geraete)=20ehrliche=20Live-/Demo-Sprache;=20Offer-Cards=20(Quel?= =?UTF-8?q?le/Preis/Name/Status=20getrennt,=20klickbar)=20auf=20Startseite?= =?UTF-8?q?+Karte;=20klickbare=20Marktnamen=20+=20separater=20Pr=C3=BCflin?= =?UTF-8?q?k;=20escapeHtml=20+=20http(s)-URL-Guard=20f=C3=BCr=20Crawler-Da?= =?UTF-8?q?ten;=20Empty=20States=20Feed/Online?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/geraete/index.astro | 2 +- src/pages/index.astro | 32 ++++++++++------ src/pages/karte.astro | 70 +++++++++++++++++++++-------------- src/pages/llms-full.txt.ts | 2 +- src/styles/global.css | 16 ++++++++ 5 files changed, 81 insertions(+), 41 deletions(-) diff --git a/src/pages/geraete/index.astro b/src/pages/geraete/index.astro index 4921b80..47fa95a 100644 --- a/src/pages/geraete/index.astro +++ b/src/pages/geraete/index.astro @@ -4,7 +4,7 @@ import ProductCard from '../../components/ProductCard.astro'; import { allProducts } from '../../lib/pricing'; const sorted = [...allProducts].sort((a,b)=>b.score-a.score); --- - +

Kuratierter Vergleich

Alle Geräte

diff --git a/src/pages/index.astro b/src/pages/index.astro index 1b260db..09524ba 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -13,7 +13,7 @@ const Bell=' +
@@ -30,7 +30,7 @@ const IMG_PHONE='/img/alarm-phone.jpg'; In meiner Nähe
- Echtzeit-Verfügbarkeit + Live-Preise direkt geprüft 6 Länder Preis-Check gegen Wucher Alarm & Preiswecker @@ -53,7 +53,7 @@ const IMG_PHONE='/img/alarm-phone.jpg';
@@ -109,7 +109,7 @@ const IMG_PHONE='/img/alarm-phone.jpg';
-

So funktioniert's

  1. PLZ eingeben oder Standort nutzen.
  2. Karte zeigt Märkte mit Bestand + Prognose.
  3. Hingehen – oder Alarm/Preiswecker setzen.
+

So funktioniert's

  1. PLZ eingeben oder Standort nutzen.
  2. Live-Preise vergleichen, Standort-Abdeckung + Prognose sehen.
  3. Beim Händler prüfen – oder Alarm/Preiswecker setzen.

Hitzeschutz statt Panik

Hitzewellen nehmen zu. Gute, effiziente Kühlung kann Hitzeschutz sein – mit Quellen statt Alarmismus.

Klima & Hitzeschutz
@@ -129,11 +129,9 @@ const IMG_PHONE='/img/alarm-phone.jpg'; .herofeat span{display:inline-flex;align-items:center;gap:.45rem}.herofeat svg{color:var(--petrol-deep)} .hero-live{display:flex;flex-direction:column;gap:.5rem;padding:1.1rem;justify-self:end;width:100%;max-width:340px;background:rgba(255,255,255,.92);backdrop-filter:blur(4px)} .lc-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem;font-size:.95rem} - .livelist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column} - .livelist li{display:flex;justify-content:space-between;align-items:center;gap:.5rem;padding:.6rem .1rem;border-top:1px solid var(--line);font-size:.88rem} - .livelist li:first-child{border-top:none} - .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);font-weight:600} + .livelist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.45rem} + .livelist li{display:block} + .hero-live .offer-card{box-shadow:none;border-color:var(--line)} .stat-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:.9rem;margin-top:1rem} .stile{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:1rem 1.1rem;box-shadow:var(--shadow-card)} .st-l{font-size:.82rem;color:var(--slate)} @@ -161,6 +159,8 @@ const IMG_PHONE='/img/alarm-phone.jpg'; diff --git a/src/pages/karte.astro b/src/pages/karte.astro index a5c9e92..5380663 100644 --- a/src/pages/karte.astro +++ b/src/pages/karte.astro @@ -4,16 +4,16 @@ 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 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.'}; + description:'Live-Preise von Klimageräten direkt bei Händlern (eigener Crawler) plus Standort-Abdeckung, Prognose und Preis-Check gegen Wucher.'}; --- - +
Live-Preis-Crawler · Standort-Abdeckung (Demo) · 6 Länder

Dein Klimagerät-Kontrollzentrum

-

Live-Bestand in Baumärkten, Elektromärkten, Online & eBay – mit Verfügbarkeits-Prognose und Preis-Check gegen Wucher.

+

Live-Preise direkt bei Händlern (eigener Crawler) plus Standort-Abdeckung – mit Prognose und Preis-Check gegen Wucher. Filialbestände sind noch nicht live verifiziert.

Aktualisiert
lädt …
@@ -34,11 +34,11 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
-
verfügbarMarkt (Cluster)
+
Markt-Standort (Demo-Abdeckung)
@@ -52,7 +52,6 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü
- @@ -119,6 +118,8 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü .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} + .onlinebar .offer-card{flex:1 1 210px;max-width:280px} + @media(max-width:560px){.onlinebar .offer-card{flex:1 1 100%;max-width:none}} .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)} @@ -162,6 +163,8 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü const storeLink=(s)=>{ const q=(state.model!=='all'&&DATA.products[state.model]?DATA.products[state.model].name:'Klimagerät'); const fn=CHAIN_SEARCH[s.c]; return fn?fn(q):('https://www.google.com/search?q='+encodeURIComponent(s.c+' '+s.ci+' '+q)); }; const LIVE_URL=(import.meta.env.PUBLIC_CRAWLER_URL)||'http://vnqwgi4gq520hs1p4bsgi4w6.46.224.49.66.sslip.io'; const fmt=(n,c)=>n==null?'–':new Intl.NumberFormat('de-DE',{style:'currency',currency:c||'EUR',maximumFractionDigits:0}).format(n); + const esc=s=>String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + const safeUrl=u=>{try{const x=new URL(u,location.origin);return (x.protocol==='http:'||x.protocol==='https:')?x.href:'#';}catch(e){return '#';}}; const $=s=>document.querySelector(s),$$=s=>Array.from(document.querySelectorAll(s)); let DATA=null,center=null,shown=120; const state={q:'',model:'all',countries:new Set(),kind:'all',avail:false,chance:false,radius:500}; @@ -208,11 +211,11 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü const tb=$('#rows');tb.innerHTML=''; for(const s of list.slice(0,shown)){ const st=status(s); - const stk = st.a?` live verfügbar`:`beim Händler prüfen ↗`; + const surl=safeUrl(storeLink(s)); + const stk = `beim Händler prüfen ↗`; const distTd=center?`${dist(center[1],center[0],s.la,s.lo).toFixed(0)} km`:''; - const surl=storeLink(s); - tb.insertAdjacentHTML('beforeend',`${s.flag||FLAG[s.cc]||''} ${s.n}${s.kind==='elektro'?'Elektro':'Baumarkt'}
${s.z} ${s.ci} · Stand vor ${s.t||'?'} Min
- ${stk}${st.p?fmt(st.p,s.cur):"–"} + tb.insertAdjacentHTML('beforeend',`${s.flag||FLAG[s.cc]||''} ${esc(s.n)}${s.kind==='elektro'?'Elektro':'Baumarkt'}
${esc(s.z)} ${esc(s.ci)} · Demo-Standort, Filialbestand nicht verifiziert
+ ${stk}– ${st.q||0}%${distTd}`); } $('#rowcount').textContent=`${Math.min(shown,list.length)} von ${list.length} Märkten`; @@ -222,10 +225,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')); - $('#s-wucher').textContent=(DATA.online||[]).filter(o=>o.wucher).length; - $('#h-feednote').textContent='Feed live'; - $('#feed').innerHTML=DATA.feed.slice(0,18).map(f=>`
  • ${FLAG[f.cc]||''} ${f.name}${f.model}
    ${rel(f.at)}
  • `).join(''); - $('#onlinebar').innerHTML='Online & eBay:'+DATA.online.map(o=>`${o.src}${o.wucher?' ⚠':''}${fmt(o.p)}${o.cond?o.cond+' · ':''}${o.a?(o.wucher?'Wucher':'verfügbar'):'nicht verf.'}${stars(o.trust||3)}`).join(''); + $('#s-wucher').textContent=(DATA.online||[]).filter(o=>o.wucher).length||'–'; + $('#h-feednote').textContent='Standort-Abdeckung (Demo)'; + const feed=DATA.feed||[]; + $('#feed').innerHTML = feed.length + ? feed.slice(0,18).map(f=>`
  • ${FLAG[f.cc]||''} ${esc(f.name)}${esc(f.model)}
    ${rel(f.at)}
  • `).join('') + : '
  • Noch keine verifizierten Restock-Ereignisse. Sobald der Crawler eine Wiederverfügbarkeit erkennt, erscheint sie hier.
  • '; + // Online-Leiste wird von loadLive() mit echten Live-Angeboten gefüllt const ccs=[...new Set(DATA.stores.map(s=>s.cc))]; $('#countrychips').innerHTML=ccs.map(c=>``).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();})); @@ -234,20 +240,30 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü loadLive(); } function loadLive(){ + const bar=$('#onlinebar'); fetch(LIVE_URL+'/api/live').then(r=>r.json()).then(d=>{ - if(!d||!d.offers||!d.offers.length)return; - const when=d.generatedAt?new Date(d.generatedAt):null; + const offers=(d&&d.offers)||[]; + const when=d&&d.generatedAt?new Date(d.generatedAt):null; const mins=when?Math.max(1,Math.round((Date.now()-when)/60000)):'?'; - const items=d.offers.filter(o=>o.price||o.availability==='discontinued').map(o=>{ - const av=o.availability==='in_stock'?'Produktseite: verfügbar':(o.availability==='online_available'?'online verfügbar':(o.availability==='discontinued'?'⚠ Seite entfernt (404)':(o.availability==='blocked'?'blockiert':'vergriffen'))); - const cls=(o.availability==='out_of_stock')?'':''; - return `${o.chain} · LIVE${fmt(o.price)}${o.name} · ${av}`; + const liveN=offers.filter(o=>o.price).length; const le=document.getElementById('s-live'); if(le)le.textContent=liveN||'–'; + const shownOffers=offers.filter(o=>o.price||o.availability==='discontinued') + .sort((a,b)=>(a.price||1e9)-(b.price||1e9)); + const items=shownOffers.map(o=>{ + let dot='status-ok',meta='Produktseite geprüft',alert=''; + if(o.availability==='online_available'){meta='Online-Angebot gefunden';} + else if(o.availability==='in_stock'){meta='Produktseite: Angebot vorhanden';} + else if(o.availability==='discontinued'){dot='status-bad';meta='⚠ Produktseite entfernt (404)';alert=' is-alert';} + else if(o.availability==='out_of_stock'){dot='status-mut';meta='derzeit vergriffen';} + const price=o.price?fmt(o.price):'—'; + return `` + +`${esc(o.chain)}${price}` + +`${esc(o.name)}` + +`${meta} · vor ${mins} Min`; }).join(''); - const bar=$('#onlinebar'); - const liveN=d.offers.filter(o=>o.price).length; var le=document.getElementById('s-live'); if(le)le.textContent=liveN; - const note = d.blocked?` · ${d.blocked} Quelle(n) blockiert (Proxy nötig)`:''; - bar.insertAdjacentHTML('afterbegin', ` LIVE gescrapt · Stand vor ${mins} Min${note}`+items); - }).catch(()=>{}); + const note = d&&d.blocked?` · ${d.blocked} Quelle(n) blockiert`:''; + const head=` LIVE-Preise gescrapt · Stand vor ${mins} Min${note}`; + bar.innerHTML = items ? (head+items) : 'Live-Preise gerade nicht erreichbar – bitte gleich erneut schauen.'; + }).catch(()=>{bar.innerHTML='Live-Preise gerade nicht erreichbar.';}); } map.on('load',()=>{ map.addSource('all',{type:'geojson',data:{type:'FeatureCollection',features:[]},cluster:true,clusterRadius:46,clusterMaxZoom:9}); @@ -256,7 +272,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(`${p.n}
    ${p.ci} – verfügbar
    Stand vor ${p.t||'?'} Min`).addTo(map);}); + map.on('click','av',e=>{const p=e.features[0].properties;new maplibregl.Popup().setLngLat(e.lngLat).setHTML(`${p.n}
    ${p.ci} · Demo-Standort
    Filialbestand nicht verifiziert`).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(); }); diff --git a/src/pages/llms-full.txt.ts b/src/pages/llms-full.txt.ts index 8e1b5dc..65a9ee5 100644 --- a/src/pages/llms-full.txt.ts +++ b/src/pages/llms-full.txt.ts @@ -2,7 +2,7 @@ import { allProducts, priceIntel, euro } from '../lib/pricing'; export const GET = () => { const L:string[] = [ '# Kühlfinder – Vollständige Referenz', - '> Kontrollzentrum für Klimagerät-Verfügbarkeit in DE/AT/CH/NL/PL/DK: Live-Bestand in Bau- und Elektromärkten + Online/Amazon/eBay, Verfügbarkeits-Prognose, Preis-Check mit Wucher-Warnung, Custom-Alarm + Preiswecker. Eigener Scraper, keine Drittdaten.', + '> Kontrollzentrum für Klimageräte in DE/AT/CH/NL/PL/DK: Live-Preise direkt bei Händlern (eigener Crawler, ausgewählte Produktseiten) + Standort-Abdeckung, Verfügbarkeits-Prognose, Preis-Check mit Wucher-Warnung, Custom-Alarm + Preiswecker. Filialbestände sind noch nicht live verifiziert.', '', '## Kernfunktionen', '- Karte & Tabelle mit Standort-Abdeckung (Demo) + live geprüften Online-Preisen ausgewählter Produktseiten.', diff --git a/src/styles/global.css b/src/styles/global.css index a506637..d65fd6d 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -118,3 +118,19 @@ header.site .brand{letter-spacing:-.02em} .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}} + +/* --- Offer-Cards (Live-Angebote): Quelle/Preis/Name/Status sauber getrennt, ganze Karte klickbar --- */ +.offer-card{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.2rem .9rem;align-content:start;text-decoration:none;color:var(--ink);border:1px solid var(--line);border-radius:12px;padding:.6rem .8rem;background:var(--card);box-shadow:var(--shadow-card);transition:border-color .15s,transform .1s} +.offer-card:hover{border-color:var(--petrol);transform:translateY(-1px)} +.offer-card:focus-visible{outline:2px solid var(--petrol);outline-offset:2px} +.offer-source{font-weight:600;min-width:0;overflow-wrap:anywhere} +.offer-price{font-family:var(--mono);font-weight:700;white-space:nowrap;text-align:right;color:var(--petrol-deep)} +.offer-title{grid-column:1 / -1;color:var(--ink-2);font-size:.82rem;line-height:1.35;overflow-wrap:anywhere} +.offer-meta{grid-column:1 / -1;color:var(--slate);font-size:.74rem;display:inline-flex;gap:.4rem;align-items:center} +.offer-card:hover .offer-source{text-decoration:underline} +.offer-card.is-alert{border-color:var(--danger)}.offer-card.is-alert .offer-price{color:var(--danger)} +.status-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;display:inline-block} +.status-ok{background:var(--ok-dot)}.status-warn{background:var(--warm)}.status-bad{background:var(--danger)}.status-mut{background:var(--slate)} +.market-link{font-weight:600;font-size:.92rem;color:var(--petrol-deep);text-decoration:none} +.market-link:hover{text-decoration:underline} +.empty-note{color:var(--slate);font-size:.85rem;padding:.6rem .2rem}