Monitor v2: 1167 echte Filialen (braucheklima-Daten + Geocoding), GPU-Karte, Live-Feed, sortierbare Tabelle, Verfügbarkeits-Prognose, Online-Wucher-Zeilen

This commit is contained in:
till committed 2026-06-27 17:25:15 +00:00
1 parent f89db4ee8f
commit 67f25ffd10
8 files changed
+251 -127

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"generatedAt": "2026-06-27T16:54:00", "stats": {"storesTotal": 1167, "withPortaSplit": 39, "withPortaSplitCool": 28, "withHalterung": 67, "restocks24h": 0, "updated": "16:54"}, "offers": [{"storeId": "bauhaus-aachen-425ab7", "productId": "portasplit", "chain": "Bauhaus", "price": 749, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "bauhaus-aachen-425ab7", "productId": "portasplit-cool", "chain": "Bauhaus", "price": 599, "availability": "out_of_stock", "lastSeenAvailable": "2026-05-30T11:41:00"}, {"storeId": "bauhaus-aachen-425ab7", "productId": "universalhalterung", "chain": "Bauhaus", "price": 69, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-13T11:37:00"}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "portasplit", "chain": "OBI", "price": 799.99, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "portasplit-cool", "chain": "OBI", "price": 749.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-25T14:49:00"}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "universalhalterung", "chain": "OBI", "price": 69, "availability": "in_stock", "lastSeenAvailable": "2026-06-23T08:19:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "portasplit", "chain": "OBI", "price": 799.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-08T14:06:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "portasplit-cool", "chain": "OBI", "price": 749.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-16T08:51:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "universalhalterung", "chain": "OBI", "price": 69, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-21T09:03:00"}, {"storeId": "toom-aalen-2bc1a2", "productId": "portasplit", "chain": "Toom", "price": 799, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-21T10:01:00"}, {"storeId": "toom-aalen-2bc1a2", "productId": "portasplit-cool", "chain": "Toom", "price": 729, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-aalen-2bc1a2", "productId": "universalhalterung", "chain": "Toom", "price": 69.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-01T10:27:00"}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "portasplit", "chain": "Bauhaus", "price": 749, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "portasplit-cool", "chain": "Bauhaus", "price": 599, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "universalhalterung", "chain": "Bauhaus", "price": 69, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-21T10:08:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "portasplit", "chain": "OBI", "price": 799.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-21T09:23:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "portasplit-cool", "chain": "OBI", "price": 749.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-26T14:49:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "universalhalterung", "chain": "OBI", "price": 69, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-15T08:13:00"}, {"storeId": "globus-baumarkt-achern-ebfdde", "productId": "portasplit", "chain": "Globus", "price": 799, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "globus-baumarkt-achern-ebfdde", "productId": "portasplit-cool", "chain": "Globus", "price": 759, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-23T09:34:00"}, {"storeId": "hagebau-ahlbeck-590f2e", "productId": "portasplit", "chain": "Hagebau", "price": 849, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-09T08:44:00"}, {"storeId": "toom-ahlen-bfb695", "productId": "portasplit", "chain": "Toom", "price": 799, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-ahlen-bfb695", "productId": "portasplit-cool", "chain": "Toom", "price": 729, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-ahlen-bfb695", "productId": "universalhalterung", "chain": "Toom", "price": 69.99, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-ahrensburg-c563fd", "productId": "portasplit", "chain": "Toom", "price": 799, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-12T14:29:00"}, {"storeId": "toom-ahrensburg-c563fd", "productId": "portasplit-cool", "chain": "Toom", "price": 729, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-ahrensburg-c563fd", "productId": "universalhalterung", "chain": "Toom", "price": 69.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-08T11:05:00"}, {"storeId": "obi-aichach-4fec7b", "productId": "portasplit", "chain": "OBI", "price": 799.99, "availaLine truncated
+1
View File
@@ -0,0 +1 @@
{"generatedAt":"2026-06-27T16:54:00","stats":{"storesTotal":1167,"withPortaSplit":39,"withPortaSplitCool":28,"withHalterung":67,"restocks24h":0,"updated":"16:54"},"feed":[{"storeId":"obi-düren-c371bd","productId":"universalhalterung","chain":"OBI","name":"OBI Düren","at":"2026-06-26T14:56:00"},{"storeId":"obi-schwarzenbruck-cde34d","productId":"universalhalterung","chain":"OBI","name":"OBI Schwarzenbruck","at":"2026-06-26T14:53:00"},{"storeId":"obi-singen-2f83c1","productId":"universalhalterung","chain":"OBI","name":"OBI Singen","at":"2026-06-26T14:53:00"},{"storeId":"globus-baumarkt-losheim-5389a2","productId":"portasplit","chain":"Globus","name":"Globus Baumarkt Losheim","at":"2026-06-26T14:52:00"},{"storeId":"bauhaus-schwentinental-raisdorf-83d83c","productId":"universalhalterung","chain":"Bauhaus","name":"Bauhaus Schwentinental-Raisdorf","at":"2026-06-26T14:51:00"},{"storeId":"obi-abensberg-09f3b6","productId":"portasplit-cool","chain":"OBI","name":"OBI Abensberg","at":"2026-06-26T14:49:00"},{"storeId":"obi-miltenberg-3607c1","productId":"portasplit-cool","chain":"OBI","name":"OBI Miltenberg","at":"2026-06-26T14:49:00"},{"storeId":"toom-greifswald-0bd98e","productId":"portasplit-cool","chain":"Toom","name":"Toom Greifswald","at":"2026-06-26T14:48:00"},{"storeId":"obi-lehrte-b4a0bb","productId":"portasplit","chain":"OBI","name":"OBI Lehrte","at":"2026-06-26T14:43:00"},{"storeId":"bauhaus-fuldabrück-bbe205","productId":"universalhalterung","chain":"Bauhaus","name":"Bauhaus Fuldabrück","at":"2026-06-26T14:42:00"},{"storeId":"obi-detmold-e5c0d9","productId":"portasplit","chain":"OBI","name":"OBI Detmold","at":"2026-06-26T14:34:00"},{"storeId":"bauhaus-bonn-beuel-d2bd6c","productId":"portasplit-cool","chain":"Bauhaus","name":"Bauhaus Bonn-Beuel","at":"2026-06-26T14:33:00"},{"storeId":"bauhaus-magdeburg-c893ec","productId":"portasplit","chain":"Bauhaus","name":"Bauhaus Magdeburg","at":"2026-06-26T14:24:00"},{"storeId":"toom-stendal-d6a9c6","productId":"universalhalterung","chain":"Toom","name":"Toom Stendal","at":"2026-06-26T14:21:00"},{"storeId":"globus-baumarkt-weinheim-ab19ae","productId":"portasplit","chain":"Globus","name":"Globus Baumarkt Weinheim","at":"2026-06-26T14:17:00"},{"storeId":"obi-husum-ff12ca","productId":"universalhalterung","chain":"OBI","name":"OBI Husum","at":"2026-06-26T14:15:00"},{"storeId":"toom-saalfeld-f80b4f","productId":"universalhalterung","chain":"Toom","name":"Toom Saalfeld","at":"2026-06-26T14:15:00"},{"storeId":"obi-meerane-16e5e3","productId":"portasplit-cool","chain":"OBI","name":"OBI Meerane","at":"2026-06-26T14:11:00"},{"storeId":"obi-parsdorf-055a08","productId":"universalhalterung","chain":"OBI","name":"OBI Parsdorf","at":"2026-06-26T14:08:00"},{"storeId":"obi-cham-cfc5bd","productId":"universalhalterung","chain":"OBI","name":"OBI Cham","at":"2026-06-26T14:06:00"},{"storeId":"bauhaus-köln-porz-1c7731","productId":"portasplit","chain":"Bauhaus","name":"Bauhaus Köln-Porz","at":"2026-06-26T14:04:00"},{"storeId":"obi-lübeck-6d2afb","productId":"portasplit-cool","chain":"OBI","name":"OBI Lübeck","at":"2026-06-26T14:04:00"},{"storeId":"obi-zehdenick-610a94","productId":"universalhalterung","chain":"OBI","name":"OBI Zehdenick","at":"2026-06-26T14:04:00"},{"storeId":"obi-ronnenberg-empelde-553476","productId":"universalhalterung","chain":"OBI","name":"OBI Ronnenberg-Empelde","at":"2026-06-26T14:00:00"},{"storeId":"obi-nürnberg--leyher-straße-a03073","productId":"universalhalterung","chain":"OBI","name":"OBI Nürnberg, Leyher Straße","at":"2026-06-26T11:57:00"},{"storeId":"obi-rudolstadt-328192","productId":"universalhalterung","chain":"OBI","name":"OBI Rudolstadt","at":"2026-06-26T11:54:00"},{"storeId":"obi-garbsen-d78f21","productId":"universalhalterung","chain":"OBI","name":"OBI Garbsen","at":"2026-06-26T11:52:00"},{"storeId":"toom-alzenau-ebdb8b","productId":"universalhalterung","chain":"Toom","name":"Toom Alzenau","at":"2026-06-26T11:50:00"},{"storeId":"obi-neumünster--boostedter-str-13ca1c","productId":"portasplit","chain":"OBI","name":"OBI Neumünster, Boostedter Str.","at":"2026-06-26T11:50:00"},{"storeId":"obi-freiburg-nord-7a33d4","productId":"portasplit","chain":"OBI","name":"OBI Freiburg Nord","at":"2026-06-26T11:48:00"},{"storeId":"toom-oschersleben-784c66","productId":"universalhalterung","chain":"Toom","name":"Toom Oschersleben","at":"2026-06-26T11:42:00"},{"storeId":"obi-stadtbergen-041f8d","productId":"portasplit-cool","chain":"OBI","name":"OBI Stadtbergen","at":"2026-06-26T11:38:00"},{"storeId":"obi-elmshorn-76e5ad","productId":"portasplit","chain":"OBI","name":"OBI Elmshorn","at":"2026-06-26T11:34:00"},{"storeId":"toom-alfeld-leine-f28f2c","productId":"universalhalterung","chain":"Toom","name":"Toom Alfeld/Leine","at":"2026-06-26T11:18:00"},{"storeId":"hagebau-springe-37dbc5","productId":"portasplit","chain":"Hagebau","name":"Hagebau Springe","at":"2026-06-26T11:18:00"},{"storeId":"hornbach-ingolstadt-65d36b","productId":"portasplit","chain":"HornLine truncated
+1
View File
@@ -0,0 +1 @@
{"generatedAt": "2026-06-27T16:54:00", "predictions": [{"storeId": "bauhaus-aachen-425ab7", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.139, "prob7d": 0.13, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "bauhaus-aachen-425ab7", "productId": "portasplit-cool", "eventsLast30d": 1, "ratePerWeek": 0.084, "prob7d": 0.081, "typicalWeekday": 5, "typicalHour": 11, "lastSeenAvailable": "2026-05-30T11:41:00"}, {"storeId": "bauhaus-aachen-425ab7", "productId": "universalhalterung", "eventsLast30d": 2, "ratePerWeek": 0.277, "prob7d": 0.242, "typicalWeekday": 4, "typicalHour": 11, "lastSeenAvailable": "2026-06-13T11:37:00"}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.182, "prob7d": 0.166, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "portasplit-cool", "eventsLast30d": 3, "ratePerWeek": 0.261, "prob7d": 0.23, "typicalWeekday": 3, "typicalHour": 8, "lastSeenAvailable": "2026-06-25T14:49:00"}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "universalhalterung", "eventsLast30d": 3, "ratePerWeek": 0.62, "prob7d": 0.462, "typicalWeekday": 0, "typicalHour": 8, "lastSeenAvailable": "2026-06-23T08:19:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "portasplit", "eventsLast30d": 2, "ratePerWeek": 0.297, "prob7d": 0.257, "typicalWeekday": 0, "typicalHour": 14, "lastSeenAvailable": "2026-06-08T14:06:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "portasplit-cool", "eventsLast30d": 1, "ratePerWeek": 0.067, "prob7d": 0.065, "typicalWeekday": 1, "typicalHour": 8, "lastSeenAvailable": "2026-06-16T08:51:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "universalhalterung", "eventsLast30d": 3, "ratePerWeek": 0.59, "prob7d": 0.446, "typicalWeekday": 0, "typicalHour": 9, "lastSeenAvailable": "2026-06-21T09:03:00"}, {"storeId": "toom-aalen-2bc1a2", "productId": "portasplit", "eventsLast30d": 4, "ratePerWeek": 0.31, "prob7d": 0.267, "typicalWeekday": 2, "typicalHour": 8, "lastSeenAvailable": "2026-06-21T10:01:00"}, {"storeId": "toom-aalen-2bc1a2", "productId": "portasplit-cool", "eventsLast30d": 0, "ratePerWeek": 0.071, "prob7d": 0.069, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "toom-aalen-2bc1a2", "productId": "universalhalterung", "eventsLast30d": 1, "ratePerWeek": 0.134, "prob7d": 0.125, "typicalWeekday": 0, "typicalHour": 10, "lastSeenAvailable": "2026-06-01T10:27:00"}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.203, "prob7d": 0.184, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "portasplit-cool", "eventsLast30d": 0, "ratePerWeek": 0.025, "prob7d": 0.025, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "universalhalterung", "eventsLast30d": 3, "ratePerWeek": 0.43, "prob7d": 0.349, "typicalWeekday": 0, "typicalHour": 10, "lastSeenAvailable": "2026-06-21T10:08:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "portasplit", "eventsLast30d": 3, "ratePerWeek": 0.306, "prob7d": 0.264, "typicalWeekday": 3, "typicalHour": 9, "lastSeenAvailable": "2026-06-21T09:23:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "portasplit-cool", "eventsLast30d": 2, "ratePerWeek": 0.254, "prob7d": 0.224, "typicalWeekday": 1, "typicalHour": 9, "lastSeenAvailable": "2026-06-26T14:49:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "universalhalterung", "eventsLast30d": 2, "ratePerWeek": 0.223, "prob7d": 0.2, "typicalWeekday": 0, "typicalHour": 8, "lastSeenAvailable": "2026-06-15T08:13:00"}, {"storeId": "globus-baumarkt-achern-ebfdde", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.235, "prob7d": 0.209, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "globus-baumarkt-achern-ebfdde", "productId": "portasplit-cool", "eventsLast30d": 1, "ratePerWeek": 0.043, "prob7d": 0.042, "typicalWeekday": 1, "typicalHour": 9, "lastSeenAvailable": "2026-06-23T09:34:00"}, {"storeId": "hagebau-ahlbeck-590f2e", "productId": "portasplit", "eventsLast30d": 1, "ratePerWeek": 0.142, "prob7d": 0.132, "typicalWeekday": 1, "typicalHour": 8, "lastSeenAvailable": "2026-06-09T08:44:00"}, {"storeId": "toom-ahlen-bfb695", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.049, "prob7d": 0.048, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "toom-ahlen-bfb695", "productId": "portasplit-cool", "eventsLast30d": 0, "ratePerWeek": 0.08, "prob7d": 0.077, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "toom-ahlen-bfb695", "productId": "universalhalterung", "eventsLast30d": 0, "raLine truncated
+1
View File
@@ -0,0 +1 @@
{"generatedAt": "2026-06-27T16:54:00", "stats": {"storesTotal": 1167, "withPortaSplit": 39, "withPortaSplitCool": 28, "withHalterung": 67, "restocks24h": 0, "updated": "16:54"}, "offers": [{"storeId": "bauhaus-aachen-425ab7", "productId": "portasplit", "chain": "Bauhaus", "price": 749, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "bauhaus-aachen-425ab7", "productId": "portasplit-cool", "chain": "Bauhaus", "price": 599, "availability": "out_of_stock", "lastSeenAvailable": "2026-05-30T11:41:00"}, {"storeId": "bauhaus-aachen-425ab7", "productId": "universalhalterung", "chain": "Bauhaus", "price": 69, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-13T11:37:00"}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "portasplit", "chain": "OBI", "price": 799.99, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "portasplit-cool", "chain": "OBI", "price": 749.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-25T14:49:00"}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "universalhalterung", "chain": "OBI", "price": 69, "availability": "in_stock", "lastSeenAvailable": "2026-06-23T08:19:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "portasplit", "chain": "OBI", "price": 799.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-08T14:06:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "portasplit-cool", "chain": "OBI", "price": 749.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-16T08:51:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "universalhalterung", "chain": "OBI", "price": 69, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-21T09:03:00"}, {"storeId": "toom-aalen-2bc1a2", "productId": "portasplit", "chain": "Toom", "price": 799, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-21T10:01:00"}, {"storeId": "toom-aalen-2bc1a2", "productId": "portasplit-cool", "chain": "Toom", "price": 729, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-aalen-2bc1a2", "productId": "universalhalterung", "chain": "Toom", "price": 69.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-01T10:27:00"}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "portasplit", "chain": "Bauhaus", "price": 749, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "portasplit-cool", "chain": "Bauhaus", "price": 599, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "universalhalterung", "chain": "Bauhaus", "price": 69, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-21T10:08:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "portasplit", "chain": "OBI", "price": 799.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-21T09:23:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "portasplit-cool", "chain": "OBI", "price": 749.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-26T14:49:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "universalhalterung", "chain": "OBI", "price": 69, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-15T08:13:00"}, {"storeId": "globus-baumarkt-achern-ebfdde", "productId": "portasplit", "chain": "Globus", "price": 799, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "globus-baumarkt-achern-ebfdde", "productId": "portasplit-cool", "chain": "Globus", "price": 759, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-23T09:34:00"}, {"storeId": "hagebau-ahlbeck-590f2e", "productId": "portasplit", "chain": "Hagebau", "price": 849, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-09T08:44:00"}, {"storeId": "toom-ahlen-bfb695", "productId": "portasplit", "chain": "Toom", "price": 799, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-ahlen-bfb695", "productId": "portasplit-cool", "chain": "Toom", "price": 729, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-ahlen-bfb695", "productId": "universalhalterung", "chain": "Toom", "price": 69.99, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-ahrensburg-c563fd", "productId": "portasplit", "chain": "Toom", "price": 799, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-12T14:29:00"}, {"storeId": "toom-ahrensburg-c563fd", "productId": "portasplit-cool", "chain": "Toom", "price": 729, "availability": "out_of_stock", "lastSeenAvailable": null}, {"storeId": "toom-ahrensburg-c563fd", "productId": "universalhalterung", "chain": "Toom", "price": 69.99, "availability": "out_of_stock", "lastSeenAvailable": "2026-06-08T11:05:00"}, {"storeId": "obi-aichach-4fec7b", "productId": "portasplit", "chain": "OBI", "price": 799.99, "availaLine truncated
+1
View File
@@ -0,0 +1 @@
{"generatedAt": "2026-06-27T16:54:00", "predictions": [{"storeId": "bauhaus-aachen-425ab7", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.139, "prob7d": 0.13, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "bauhaus-aachen-425ab7", "productId": "portasplit-cool", "eventsLast30d": 1, "ratePerWeek": 0.084, "prob7d": 0.081, "typicalWeekday": 5, "typicalHour": 11, "lastSeenAvailable": "2026-05-30T11:41:00"}, {"storeId": "bauhaus-aachen-425ab7", "productId": "universalhalterung", "eventsLast30d": 2, "ratePerWeek": 0.277, "prob7d": 0.242, "typicalWeekday": 4, "typicalHour": 11, "lastSeenAvailable": "2026-06-13T11:37:00"}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.182, "prob7d": 0.166, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "portasplit-cool", "eventsLast30d": 3, "ratePerWeek": 0.261, "prob7d": 0.23, "typicalWeekday": 3, "typicalHour": 8, "lastSeenAvailable": "2026-06-25T14:49:00"}, {"storeId": "obi-aachen--debyestrasse-ef5da8", "productId": "universalhalterung", "eventsLast30d": 3, "ratePerWeek": 0.62, "prob7d": 0.462, "typicalWeekday": 0, "typicalHour": 8, "lastSeenAvailable": "2026-06-23T08:19:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "portasplit", "eventsLast30d": 2, "ratePerWeek": 0.297, "prob7d": 0.257, "typicalWeekday": 0, "typicalHour": 14, "lastSeenAvailable": "2026-06-08T14:06:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "portasplit-cool", "eventsLast30d": 1, "ratePerWeek": 0.067, "prob7d": 0.065, "typicalWeekday": 1, "typicalHour": 8, "lastSeenAvailable": "2026-06-16T08:51:00"}, {"storeId": "obi-aachen--roermonder-straße-2bf9d4", "productId": "universalhalterung", "eventsLast30d": 3, "ratePerWeek": 0.59, "prob7d": 0.446, "typicalWeekday": 0, "typicalHour": 9, "lastSeenAvailable": "2026-06-21T09:03:00"}, {"storeId": "toom-aalen-2bc1a2", "productId": "portasplit", "eventsLast30d": 4, "ratePerWeek": 0.31, "prob7d": 0.267, "typicalWeekday": 2, "typicalHour": 8, "lastSeenAvailable": "2026-06-21T10:01:00"}, {"storeId": "toom-aalen-2bc1a2", "productId": "portasplit-cool", "eventsLast30d": 0, "ratePerWeek": 0.071, "prob7d": 0.069, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "toom-aalen-2bc1a2", "productId": "universalhalterung", "eventsLast30d": 1, "ratePerWeek": 0.134, "prob7d": 0.125, "typicalWeekday": 0, "typicalHour": 10, "lastSeenAvailable": "2026-06-01T10:27:00"}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.203, "prob7d": 0.184, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "portasplit-cool", "eventsLast30d": 0, "ratePerWeek": 0.025, "prob7d": 0.025, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "bauhaus-aalen-essingen-ff51a6", "productId": "universalhalterung", "eventsLast30d": 3, "ratePerWeek": 0.43, "prob7d": 0.349, "typicalWeekday": 0, "typicalHour": 10, "lastSeenAvailable": "2026-06-21T10:08:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "portasplit", "eventsLast30d": 3, "ratePerWeek": 0.306, "prob7d": 0.264, "typicalWeekday": 3, "typicalHour": 9, "lastSeenAvailable": "2026-06-21T09:23:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "portasplit-cool", "eventsLast30d": 2, "ratePerWeek": 0.254, "prob7d": 0.224, "typicalWeekday": 1, "typicalHour": 9, "lastSeenAvailable": "2026-06-26T14:49:00"}, {"storeId": "obi-abensberg-09f3b6", "productId": "universalhalterung", "eventsLast30d": 2, "ratePerWeek": 0.223, "prob7d": 0.2, "typicalWeekday": 0, "typicalHour": 8, "lastSeenAvailable": "2026-06-15T08:13:00"}, {"storeId": "globus-baumarkt-achern-ebfdde", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.235, "prob7d": 0.209, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "globus-baumarkt-achern-ebfdde", "productId": "portasplit-cool", "eventsLast30d": 1, "ratePerWeek": 0.043, "prob7d": 0.042, "typicalWeekday": 1, "typicalHour": 9, "lastSeenAvailable": "2026-06-23T09:34:00"}, {"storeId": "hagebau-ahlbeck-590f2e", "productId": "portasplit", "eventsLast30d": 1, "ratePerWeek": 0.142, "prob7d": 0.132, "typicalWeekday": 1, "typicalHour": 8, "lastSeenAvailable": "2026-06-09T08:44:00"}, {"storeId": "toom-ahlen-bfb695", "productId": "portasplit", "eventsLast30d": 0, "ratePerWeek": 0.049, "prob7d": 0.048, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "toom-ahlen-bfb695", "productId": "portasplit-cool", "eventsLast30d": 0, "ratePerWeek": 0.08, "prob7d": 0.077, "typicalWeekday": null, "typicalHour": null, "lastSeenAvailable": null}, {"storeId": "toom-ahlen-bfb695", "productId": "universalhalterung", "eventsLast30d": 0, "raLine truncated
+1 -20
View File
@@ -1,20 +1 @@
[ [{"id": "bauhaus-aachen-425ab7", "chain": "Bauhaus", "name": "Bauhaus Aachen", "plz": "52070", "city": "Aachen", "lat": 50.7964, "lng": 6.1009}, {"id": "obi-aachen--debyestrasse-ef5da8", "chain": "OBI", "name": "OBI Aachen, Debyestrasse", "plz": "52078", "city": "Aachen Brand", "lat": 50.7624, "lng": 6.1409}, {"id": "obi-aachen--roermonder-straße-2bf9d4", "chain": "OBI", "name": "OBI Aachen, Roermonder Straße", "plz": "52072", "city": "Aachen", "lat": 50.7916, "lng": 6.0692}, {"id": "toom-aalen-2bc1a2", "chain": "Toom", "name": "Toom Aalen", "plz": "73430", "city": "Aalen", "lat": 48.8297, "lng": 10.0918}, {"id": "bauhaus-aalen-essingen-ff51a6", "chain": "Bauhaus", "name": "Bauhaus Aalen-Essingen", "plz": "73457", "city": "Essingen", "lat": 48.8132, "lng": 10.0241}, {"id": "obi-abensberg-09f3b6", "chain": "OBI", "name": "OBI Abensberg", "plz": "93326", "city": "Abensberg", "lat": 48.8172, "lng": 11.8369}, {"id": "globus-baumarkt-achern-ebfdde", "chain": "Globus", "name": "Globus Baumarkt Achern", "plz": "77855", "city": "Achern Nicht aktualisierbar seit 5 Tg. Nicht aktualisierbar seit 1 Tg.", "lat": 48.6169, "lng": 8.0826}, {"id": "hagebau-ahlbeck-590f2e", "chain": "Hagebau", "name": "Hagebau Ahlbeck", "plz": "17419", "city": "Ahlbeck", "lat": 53.8914, "lng": 14.1532}, {"id": "toom-ahlen-bfb695", "chain": "Toom", "name": "Toom Ahlen", "plz": "59229", "city": "Ahlen", "lat": 51.7409, "lng": 7.91}, {"id": "toom-ahrensburg-c563fd", "chain": "Toom", "name": "Toom Ahrensburg", "plz": "22926", "city": "Ahrensburg", "lat": 53.6622, "lng": 10.2182}, {"id": "obi-aichach-4fec7b", "chain": "OBI", "name": "OBI Aichach", "plz": "86551", "city": "Aichach", "lat": 48.4435, "lng": 11.1464}, {"id": "toom-albstadt-38c546", "chain": "Toom", "name": "Toom Albstadt", "plz": "72458", "city": "Albstadt", "lat": 48.1997, "lng": 9.0195}, {"id": "obi-alfeld-aef4d9", "chain": "OBI", "name": "OBI Alfeld", "plz": "31061", "city": "Alfeld", "lat": 51.9774, "lng": 9.8148}, {"id": "toom-alfeld-leine-f28f2c", "chain": "Toom", "name": "Toom Alfeld/Leine", "plz": "31061", "city": "Alfeld/Leine", "lat": 51.9731, "lng": 9.8253}, {"id": "obi-alfter-eb1309", "chain": "OBI", "name": "OBI Alfter", "plz": "53347", "city": "Alfter", "lat": 50.7439, "lng": 7.027}, {"id": "toom-alsdorf-39b1d6", "chain": "Toom", "name": "Toom Alsdorf", "plz": "52477", "city": "Alsdorf", "lat": 50.8645, "lng": 6.1713}, {"id": "obi-altenburg-0428cc", "chain": "OBI", "name": "OBI Altenburg", "plz": "04603", "city": "Windischleuba", "lat": 50.9693, "lng": 12.4273}, {"id": "toom-altenkirchen-34b7a6", "chain": "Toom", "name": "Toom Altenkirchen", "plz": "57610", "city": "Altenkirchen", "lat": 50.6918, "lng": 7.6725}, {"id": "hagebau-altenkunstadt-28ba12", "chain": "Hagebau", "name": "Hagebau Altenkunstadt", "plz": "96264", "city": "Altenkunstadt", "lat": 50.1375, "lng": 11.2634}, {"id": "hornbach-altötting-d40955", "chain": "Hornbach", "name": "Hornbach Altötting", "plz": "84503", "city": "Altötting Nicht aktualisierbar seit 4 Tg.", "lat": 48.235, "lng": 12.688}, {"id": "toom-alzenau-ebdb8b", "chain": "Toom", "name": "Toom Alzenau", "plz": "63755", "city": "Alzenau", "lat": 50.0936, "lng": 9.0547}, {"id": "toom-alzey-858a6d", "chain": "Toom", "name": "Toom Alzey", "plz": "55232", "city": "Alzey", "lat": 49.7828, "lng": 8.1079}, {"id": "obi-amberg-cd5c91", "chain": "OBI", "name": "OBI Amberg", "plz": "92224", "city": "Amberg", "lat": 49.4402, "lng": 11.8699}, {"id": "hagebau-amberg-c4c1ec", "chain": "Hagebau", "name": "Hagebau Amberg", "plz": "92224", "city": "Amberg", "lat": 49.4447, "lng": 11.8582}, {"id": "toom-angersdorf-d2a6e9", "chain": "Toom", "name": "Toom Angersdorf", "plz": "06179", "city": "Angersdorf", "lat": 51.4709, "lng": 11.842}, {"id": "toom-anklam-535489", "chain": "Toom", "name": "Toom Anklam", "plz": "17389", "city": "Anklam", "lat": 53.8456, "lng": 13.6935}, {"id": "obi-annaberg-buchholz-389840", "chain": "OBI", "name": "OBI Annaberg-Buchholz", "plz": "09456", "city": "Annaberg-Buchholz", "lat": 50.573, "lng": 13.0366}, {"id": "obi-ansbach-4e4966", "chain": "OBI", "name": "OBI Ansbach", "plz": "91522", "city": "Ansbach", "lat": 49.3139, "lng": 10.5805}, {"id": "toom-apolda-ea9331", "chain": "Toom", "name": "Toom Apolda", "plz": "99510", "city": "Apolda", "lat": 51.0393, "lng": 11.4695}, {"id": "obi-arnsberg-42a1fa", "chain": "OBI", "name": "OBI Arnsberg", "plz": "59759", "city": "Arnsberg", "lat": 51.4283, "lng": 8.0051}, {"id": "bauhaus-aschaffenburg-ebc8e9", "chain": "Bauhaus", "name": "Bauhaus Aschaffenburg", "plz": "63743", "city": "Aschaffenburg", "lat": 49.9526, "lng": 9.1763}, {"id": "bauhaus-augsburg-b789c7", "chain": "Bauhaus", "name": "Bauhaus Augsburg", "plz": "86159", "city": "Augsburg", "lat": 48.3431, "lng": 10.8963}, {"id": "bauhaus-augsburg---lechhausen-a680e3", "chain": "Bauhaus", "name": "Bauhaus Augsburg - Lechhausen", "plz":Line truncated
{"id":"obi-hamburg","chain":"OBI","name":"OBI Hamburg-Wandsbek","plz":"22041","city":"Hamburg","lat":53.573,"lng":10.085},
{"id":"hornbach-hamburg","chain":"Hornbach","name":"Hornbach Hamburg-Wandsbek","plz":"22047","city":"Hamburg","lat":53.585,"lng":10.103},
{"id":"bauhaus-berlin","chain":"Bauhaus","name":"Bauhaus Berlin-Halensee","plz":"10711","city":"Berlin","lat":52.497,"lng":13.290},
{"id":"obi-berlin","chain":"OBI","name":"OBI Berlin-Lichtenberg","plz":"10365","city":"Berlin","lat":52.512,"lng":13.500},
{"id":"toom-koeln","chain":"toom","name":"toom Köln-Ehrenfeld","plz":"50825","city":"Köln","lat":50.952,"lng":6.917},
{"id":"hornbach-koeln","chain":"Hornbach","name":"Hornbach Köln-Godorf","plz":"50997","city":"Köln","lat":50.864,"lng":6.987},
{"id":"bauhaus-muenchen","chain":"Bauhaus","name":"Bauhaus München-Laim","plz":"80687","city":"München","lat":48.140,"lng":11.500},
{"id":"obi-muenchen","chain":"OBI","name":"OBI München-Pasing","plz":"81241","city":"München","lat":48.146,"lng":11.460},
{"id":"hornbach-frankfurt","chain":"Hornbach","name":"Hornbach Frankfurt-Bornheim","plz":"60385","city":"Frankfurt","lat":50.131,"lng":8.713},
{"id":"toom-stuttgart","chain":"toom","name":"toom Stuttgart-Vaihingen","plz":"70563","city":"Stuttgart","lat":48.728,"lng":9.110},
{"id":"bauhaus-stuttgart","chain":"Bauhaus","name":"Bauhaus Stuttgart-Feuerbach","plz":"70469","city":"Stuttgart","lat":48.808,"lng":9.157},
{"id":"obi-hannover","chain":"OBI","name":"OBI Hannover-Vahrenheide","plz":"30179","city":"Hannover","lat":52.420,"lng":9.760},
{"id":"hornbach-dresden","chain":"Hornbach","name":"Hornbach Dresden-Kaditz","plz":"01139","city":"Dresden","lat":51.082,"lng":13.690},
{"id":"bauhaus-leipzig","chain":"Bauhaus","name":"Bauhaus Leipzig-Paunsdorf","plz":"04329","city":"Leipzig","lat":51.350,"lng":12.470},
{"id":"obi-dortmund","chain":"OBI","name":"OBI Dortmund-Aplerbeck","plz":"44287","city":"Dortmund","lat":51.490,"lng":7.560},
{"id":"toom-nuernberg","chain":"toom","name":"toom Nürnberg-Langwasser","plz":"90471","city":"Nürnberg","lat":49.400,"lng":11.130},
{"id":"bauhaus-karlsruhe","chain":"Bauhaus","name":"Bauhaus Karlsruhe-Durlach","plz":"76227","city":"Karlsruhe","lat":48.998,"lng":8.470},
{"id":"hornbach-bremen","chain":"Hornbach","name":"Hornbach Bremen-Habenhausen","plz":"28279","city":"Bremen","lat":53.040,"lng":8.840}
]
+226 -107
View File
@@ -1,134 +1,253 @@
--- ---
import Base from '../layouts/Base.astro'; import Base from '../layouts/Base.astro';
import { allProducts } from '../lib/pricing'; const jsonld = {'@context':'https://schema.org','@type':'WebApplication',name:'Kühlfinder Monitor',applicationCategory:'ShoppingApplication',
const products = allProducts.map(p => ({ slug:p.slug, name:p.name })); description:'Live-Verfügbarkeit der Midea PortaSplit, PortaSplit Cool und Universalhalterung in über 1.160 Baumärkten, mit Verfügbarkeits-Prognose.'};
--- ---
<Base title="Verfügbarkeits-Karte: portable Klimageräte in Baumärkten | Kühlfinder" description="Live-Karte: welche Baumärkte aktuell mobile Klimageräte auf Lager haben. Nach Gerät, Kette und Umkreis filtern."> <Base title="PortaSplit Monitor – Live-Verfügbarkeit + Prognose in 1.167 Baumärkten | Kühlfinder" description="Sieh auf einen Blick, welche OBI-, Hornbach-, Bauhaus-, Toom-, Hagebau- und Globus-Filialen Midea PortaSplit, Cool & Halterung auf Lager haben – mit Preis-Check, Wucher-Warnung und Verfügbarkeits-Prognose." jsonld={jsonld}>
<section class="wrap" style="margin-top:1.4rem"> <section class="mon-hero"><div class="wrap">
<p class="kicker"><span class="u"></span>Deutschlandkarte</p> <div class="mon-hero-row">
<h1>Wo gibt es heute ein Klimagerät?</h1> <div>
<p class="muted">Märkte mit aktuellem Bestand. Wähle ein Gerät, nutze deinen Standort und stell den Umkreis ein.</p> <p class="kicker"><span class="u"></span>PortaSplit Monitor · Live</p>
</section> <h1>Wo gibt es die Midea PortaSplit gerade?</h1>
<section class="wrap mapwrap"> <p class="lead">Live-Bestand in <span class="num" id="h-stores">1.167</span> Baumärkten – plus <strong>Verfügbarkeits-Prognose</strong>, die dir sagt, wo die Chance in den nächsten 7 Tagen am höchsten ist. Das kann braucheklima nicht.</p>
<aside class="panel card">
<label>Gerät
<select id="f-product">
<option value="all">Alle Geräte</option>
{products.map(p => <option value={p.slug}>{p.name}</option>)}
</select>
</label>
<button class="btn sm" id="locate">Meinen Standort nutzen</button>
<label>Umkreis: <span id="r-out">150 km</span>
<input type="range" id="f-radius" min="25" max="500" step="25" value="150" />
</label>
<div class="chains" id="f-chains">
<strong class="note">Ketten</strong>
<label><input type="checkbox" value="OBI" checked> OBI</label>
<label><input type="checkbox" value="Hornbach" checked> Hornbach</label>
<label><input type="checkbox" value="Bauhaus" checked> Bauhaus</label>
<label><input type="checkbox" value="toom" checked> toom</label>
</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>
<div class="stats" id="stats">
<div class="stat"><span class="note">Filialen</span><b class="num" data-k="storesTotal">–</b></div>
<div class="stat ok"><span class="note">mit PortaSplit</span><b class="num" data-k="withPortaSplit">–</b></div>
<div class="stat ok"><span class="note">mit Cool</span><b class="num" data-k="withPortaSplitCool">–</b></div>
<div class="stat ok"><span class="note">mit Halterung</span><b class="num" data-k="withHalterung">–</b></div>
<div class="stat"><span class="note">Restocks 7 Tg.</span><b class="num" id="s-restocks">–</b></div>
<div class="stat warn"><span class="note">Wucher-Alarm</span><b class="num" id="s-wucher">–</b></div>
</div>
</div></section>
<section class="wrap mon-grid">
<div class="mapcol">
<div id="map"></div>
<div class="legend"> <div class="legend">
<span><i style="background:#1d9e75"></i>verfügbar</span> <span><span class="dot ok"></span>verfügbar</span>
<span><i style="background:#ef9f27"></i>wenig</span> <span><span class="dot" style="background:var(--petrol)"></span>Filiale (Cluster)</span>
<span><i style="background:#b4b2a9"></i>leer</span> <span><i class="hot"></i>hohe Prognose</span>
</div> </div>
<p class="note" id="count">…</p> </div>
<p class="note">Hinweis: aktuelle Demo-Daten. Live-Anbindung der Ketten-Bestände folgt.</p> <aside class="feedcol card">
<h2 class="h-sm">Letzte Verfügbarkeiten</h2>
<p class="note">Echte Restock-Ereignisse, neueste zuerst.</p>
<ol class="feed" id="feed"><li class="skel" style="height:46px"></li><li class="skel" style="height:46px"></li><li class="skel" style="height:46px"></li></ol>
</aside> </aside>
<div id="map"></div> </section>
<section class="wrap">
<div class="toolbar card">
<div class="tb-row">
<input id="q" type="search" placeholder="Filiale, Ort oder PLZ suchen …" />
<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>
</div>
<div class="tb-row chips">
<span class="note">Produkt:</span>
<button class="chip on" data-p="ps">PortaSplit</button>
<button class="chip on" data-p="co">Cool</button>
<button class="chip on" data-p="ha">Halterung</button>
<span class="sep"></span>
<button class="chip ghost on" data-toggle="avail">Nur verfügbare</button>
<button class="chip ghost" data-toggle="chance">Beste Prognose zuerst</button>
<span class="sep"></span>
<span class="note">Kette:</span>
<span id="chainchips"></span>
<button class="chip ghost" id="reset" type="button">Zurücksetzen</button>
</div>
</div>
<div class="onlinebar card" id="onlinebar"></div>
<div class="tablewrap card">
<table class="mtab">
<thead><tr>
<th>Filiale</th>
<th class="ce" data-sort="ps">PortaSplit</th>
<th class="ce" data-sort="co">Cool</th>
<th class="ce" data-sort="ha">Halterung</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="6"><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 Filialen laden</button><p class="note" id="rowcount"></p></div>
</div>
<div class="alertcta card">
<div><h2 class="h-sm">Kein Bestand in deiner Nähe?</h2><p class="note" style="margin:0">Wir mailen dir, sobald PortaSplit & Co. in deinem Umkreis auftauchen – inkl. Prognose-Frühwarnung.</p></div>
<a class="btn" href="/alarm?produkt=midea-portasplit-12000">Verfügbarkeits-Alarm</a>
</div>
<p class="note" style="margin-top:.6rem">Datenstand: Snapshot aus öffentlich einsehbaren Filial-/Online-Beständen (Vorbild braucheklima.de). Prognose = statistische Schätzung aus der Restock-Historie, keine Garantie. Preise/Bestände beim Händler prüfen.</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>
.mapwrap{display:grid;grid-template-columns:280px 1fr;gap:1rem;margin-top:1rem;align-items:start} .mon-hero{background:linear-gradient(180deg,var(--petrol-soft),var(--light));padding:1.8rem 0 1.4rem}
.panel{display:flex;flex-direction:column;gap:.9rem;position:sticky;top:74px} .mon-hero-row{display:flex;justify-content:space-between;gap:1.2rem;align-items:flex-start}
.panel label{display:block;font-weight:500;font-size:.9rem} .mon-hero .lead{max-width:48rem;color:var(--ink-2);font-size:1.05rem}
.panel select,.panel input[type=range]{width:100%;margin-top:.3rem} .upd{min-width:140px;text-align:center}
.chains label{font-weight:400;display:flex;gap:.4rem;align-items:center;margin-top:.2rem} .upd-t{font-size:1.6rem;font-weight:600;color:var(--petrol-deep)}
.legend{display:flex;gap:.8rem;flex-wrap:wrap;font-size:.82rem;color:var(--slate)} .stats{display:grid;grid-template-columns:repeat(6,1fr);gap:.6rem;margin-top:1.2rem}
.legend i{display:inline-block;width:11px;height:11px;border-radius:50%;margin-right:.3rem;vertical-align:middle} .stat{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:.6rem .8rem;display:flex;flex-direction:column;gap:.1rem}
#map{height:600px;border-radius:var(--radius);overflow:hidden;border:1px solid var(--line)} .stat b{font-size:1.45rem;line-height:1.1}
.maplibregl-popup-content{font-family:var(--font-body);border-radius:10px;padding:.7rem .8rem} .stat.ok b{color:var(--ok-dot)} .stat.warn b{color:var(--danger)}
.pop strong{font-size:.98rem} .mon-grid{display:grid;grid-template-columns:1.7fr 1fr;gap:1rem;margin-top:1.2rem;align-items:start}
.pop .w{color:#a32d2d;font-weight:600} .mapcol{position:sticky;top:74px}
@media(max-width:820px){.mapwrap{grid-template-columns:1fr}.panel{position:static}#map{height:60vh}} #map{height:520px;border-radius:var(--radius);overflow:hidden;border:1px solid var(--line)}
.legend{display:flex;gap:1rem;flex-wrap:wrap;font-size:.8rem;color:var(--slate);margin-top:.5rem;align-items:center}
.legend i.hot{display:inline-block;width:11px;height:11px;border-radius:50%;border:2px solid var(--warm);margin-right:.3rem;vertical-align:middle}
.feedcol{max-height:560px;overflow:auto}
.h-sm{font-size:1.15rem;margin:.1rem 0 .2rem}
.feed{list-style:none;margin:.6rem 0 0;padding:0;display:flex;flex-direction:column;gap:.1rem}
.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:.92rem}
.feed .fl span{font-size:.8rem;color:var(--slate)}
.feed .ft{font-size:.8rem;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:200px;font-family:var(--font-body);font-size:.95rem;padding:.6rem .8rem;border:1px solid var(--line);border-radius:8px;background:var(--surface-2)}
#q:focus{border-color:var(--petrol);background:#fff}
.rad{display:flex;align-items:center;gap:.5rem;font-size:.85rem;color:var(--slate)}
.chips{font-size:.85rem}
.chip{font-family:var(--font-body);font-weight:500;font-size:.85rem;border:1px solid var(--line);background:var(--card);color:var(--ink);padding:.4rem .8rem;border-radius:999px;cursor:pointer;transition:transform .1s,background .15s,border-color .15s}
.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}
.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:8px;padding:.4rem .6rem;min-width:120px}
.osrc b{font-size:.85rem}.osrc .num{font-size:.9rem}
.osrc.wucher{border-color:var(--danger);background:var(--danger-bg)}
.osrc.wucher .num{color:var(--danger)}
.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:.75rem;text-transform:uppercase;letter-spacing:.04em;color:var(--slate);font-weight:600;padding:.7rem .8rem;border-bottom:1px solid var(--line);position:sticky;top:62px;background:var(--card);cursor:pointer;white-space:nowrap}
.mtab th.ce{text-align:center}
.mtab td{padding:.6rem .8rem;border-bottom:1px solid var(--line);vertical-align:middle}
.mtab tbody tr:hover{background:var(--petrol-soft)}
.mtab .fname{font-weight:600;font-size:.92rem}.mtab .faddr{font-size:.78rem;color:var(--slate)}
.cell{display:flex;flex-direction:column;align-items:center;gap:.15rem;line-height:1.2}
.cell .num{font-size:.85rem}
.cell .pa{font-size:.72rem;color:var(--ok-dot);font-weight:600}
.cell .pn{font-size:.72rem;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)}
.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}.mtab thead th{position:static}}
</style> </style>
<script> <script>
import maplibregl from 'maplibre-gl'; import maplibregl from 'maplibre-gl';
import storesData from '../data/stores.json'; const PNAME={ps:'PortaSplit',co:'PortaSplit Cool',ha:'Universalhalterung'};
const PKEYFEED={'portasplit':'ps','portasplit-cool':'co','universalhalterung':'ha'};
const euro=n=>n==null?'–':new Intl.NumberFormat('de-DE',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(n);
const FAIR={ps:849,co:779,ha:70};
let DATA=null, center=null, shown=120;
const state={products:{ps:true,co:true,ha:true},chains:new Set(),avail:true,chance:false,q:'',radius:500,sort:null};
type Offer = { productId:string; sourceType:string; sourceId:string; chain:string; price:number|null; availability:string; }; const $=s=>document.querySelector(s), $$=s=>Array.from(document.querySelectorAll(s));
const stores = storesData as any[]; 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));}
const AVAIL = ['in_stock','low_stock']; 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';}
const params = new URLSearchParams(location.search);
const map = new maplibregl.Map({ const map=new maplibregl.Map({container:'map',style:'https://tiles.openfreemap.org/styles/liberty',center:[10.2,51.1],zoom:5});
container:'map',
style:'https://tiles.openfreemap.org/styles/liberty',
center:[10.2,51.1], zoom:5.1
});
map.addControl(new maplibregl.NavigationControl(),'top-right'); map.addControl(new maplibregl.NavigationControl(),'top-right');
let offers: Offer[] = []; function activeProducts(){return Object.keys(state.products).filter(k=>state.products[k]);}
let center: [number,number] | null = null; function storeBest(s){ // returns {avail, bestProb, anyTracked}
let markers: maplibregl.Marker[] = []; let avail=false,bestProb=0,any=false;
for(const k of activeProducts()){ if(s[k]){any=true; if(s[k].a)avail=true; if(s[k].q>bestProb)bestProb=s[k].q;} }
return {avail,bestProb,any};
}
function passFilter(s){
const aps=activeProducts(); if(!aps.some(k=>s[k]))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(center && dist(center[1],center[0],s.la,s.lo)>state.radius)return false;
if(state.avail && !storeBest(s).avail)return false;
return true;
}
const elProd = document.getElementById('f-product') as HTMLSelectElement; function setMap(list){
const elRad = document.getElementById('f-radius') as HTMLInputElement; const all={type:'FeatureCollection',features:list.map(s=>({type:'Feature',geometry:{type:'Point',coordinates:[s.lo,s.la]},properties:{}}))};
const elROut = document.getElementById('r-out')!; const av={type:'FeatureCollection',features:list.filter(s=>storeBest(s).avail).map(s=>({type:'Feature',geometry:{type:'Point',coordinates:[s.lo,s.la]},properties:{n:s.n,ci:s.ci}}))};
const elCount = document.getElementById('count')!; map.getSource('all').setData(all); map.getSource('av').setData(av);
const chainBoxes = Array.from(document.querySelectorAll('#f-chains input')) as HTMLInputElement[];
if (params.get('produkt')) elProd.value = params.get('produkt')!;
function dist(aLat:number,aLng:number,bLat:number,bLng:number){
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));
} }
const COL:any={in_stock:'#1d9e75',low_stock:'#ef9f27',out_of_stock:'#b4b2a9'};
const fmt=(n:number|null)=> n==null?'–':new Intl.NumberFormat('de-DE',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(n);
const slugByProductId:any = {};
function render(){ function render(){
markers.forEach(m=>m.remove()); markers=[]; const list=DATA.stores.filter(passFilter);
const prod = elProd.value; if(center){list.sort((a,b)=>dist(center[1],center[0],a.la,a.lo)-dist(center[1],center[0],b.la,b.lo));}
const radius = +elRad.value; else if(state.chance){list.sort((a,b)=>storeBest(b).bestProb-storeBest(a).bestProb);}
elROut.textContent = radius+' km'; else {list.sort((a,b)=>(storeBest(b).avail?1:0)-(storeBest(a).avail?1:0)||storeBest(b).bestProb-storeBest(a).bestProb);}
const chains = chainBoxes.filter(c=>c.checked).map(c=>c.value); // map
let shown=0, avail=0; setMap(list);
for (const s of stores){ // table
if (!chains.includes(s.chain)) continue; const tb=$('#rows'); tb.innerHTML='';
if (center && dist(center[1],center[0],s.lat,s.lng)>radius) continue; const slice=list.slice(0,shown);
const here = offers.filter(o=>o.sourceId===s.id && o.sourceType==='store' && (prod==='all'|| o.productId===prod)); const cell=(o)=>{ if(!o)return '<td class="ce"><span class="pn">–</span></td>';
if (!here.length) continue; const dotc=o.a?'ok':(o.q>=15?'low':'off');
const best = here.find(o=>o.availability==='in_stock') || here.find(o=>o.availability==='low_stock') || here[0]; return `<td class="ce"><div class="cell"><span><span class="dot ${dotc}"></span> <span class="num">${euro(o.p)}</span></span>${o.a?'<span class="pa">verfügbar</span>':`<span class="pn">${o.q>0?o.q+'% / 7 Tg.':'—'}</span>`}</div></td>`;};
const isAvail = AVAIL.includes(best.availability); for(const s of slice){
shown++; if(isAvail) avail++; const b=storeBest(s);
const el=document.createElement('div'); const d=center?` · <span class="num">${dist(center[1],center[0],s.la,s.lo).toFixed(0)} km</span>`:'';
el.style.cssText=`width:16px;height:16px;border-radius:50%;border:2px solid #fff;box-shadow:0 1px 3px rgba(0,0,0,.4);background:${COL[best.availability]||'#b4b2a9'}`; const prog=`<td class="ce"><span class="prog"><span class="progbar"><i style="width:${b.bestProb}%"></i></span><span class="num">${b.bestProb}%</span></span></td>`;
const list = here.filter(o=>AVAIL.includes(o.availability)) const distTd=center?`<td class="ce num">${dist(center[1],center[0],s.la,s.lo).toFixed(0)} km</td>`:'';
.map(o=>`<div>${o.chain} · ${fmt(o.price)} · ${o.availability==='low_stock'?'wenig':'verfügbar'}</div>`).join('') || '<div class="muted">aktuell vergriffen</div>'; tb.insertAdjacentHTML('beforeend',`<tr><td><div class="fname">${s.n}</div><div class="faddr">${s.z} ${s.ci}${d}</div></td>${cell(s.ps)}${cell(s.co)}${cell(s.ha)}${prog}${distTd}</tr>`);
const popup=new maplibregl.Popup({offset:14}).setHTML(`<div class="pop"><strong>${s.chain} ${s.city}</strong><br><span style="color:#666">${s.name}</span><br>${list}</div>`);
const m=new maplibregl.Marker({element:el}).setLngLat([s.lng,s.lat]).setPopup(popup).addTo(map);
markers.push(m);
} }
elCount.textContent = `${avail} Märkte mit Bestand${center?` im Umkreis von ${radius} km`:''}`; $('#rowcount').textContent=`${slice.length} von ${list.length} Filialen`;
$('#more').style.display=list.length>shown?'inline-flex':'none';
$('#distcol').style.display=center?'table-cell':'none';
} }
document.getElementById('locate')!.addEventListener('click',()=>{ function fillStatic(){
if(!navigator.geolocation){elCount.textContent='Standort nicht verfügbar';return;} $('#h-updated').textContent=DATA.stats.updated;
navigator.geolocation.getCurrentPosition(pos=>{ $('#h-stores').textContent=DATA.stats.storesTotal.toLocaleString('de-DE');
center=[pos.coords.longitude,pos.coords.latitude]; $$('#stats b[data-k]').forEach(b=>b.textContent=(DATA.stats[b.dataset.k]??'–').toLocaleString('de-DE'));
map.flyTo({center,zoom:7}); const r7=DATA.feed.filter(f=>(Date.now()-new Date(f.at))/86400000<=7).length;
new maplibregl.Marker({color:'#185fa5'}).setLngLat(center).addTo(map); $('#s-restocks').textContent=r7+(r7>=DATA.feed.length?'+':'');
render(); $('#h-feednote').textContent='Feed live';
},()=>{elCount.textContent='Standort abgelehnt – zeige ganz Deutschland';}); const wuch=DATA.online.filter(o=>o.ps&&o.ps.wucher).length;
}); $('#s-wucher').textContent=wuch;
elProd.addEventListener('change',render); // feed
elRad.addEventListener('input',render); $('#feed').innerHTML=DATA.feed.slice(0,20).map(f=>`<li><div class="fl"><b>${f.name}</b><span>${PNAME[PKEYFEED[f.productId]]||f.productId}</span></div><span class="ft">${rel(f.at)}</span></li>`).join('');
chainBoxes.forEach(c=>c.addEventListener('change',render)); // online bar
$('#onlinebar').innerHTML='<span class="note" style="align-self:center">Online:</span>'+DATA.online.map(o=>{
const ps=o.ps; if(!ps)return '';
const w=ps.wucher;
return `<a class="osrc ${w?'wucher':''}" href="${o.url}" target="_blank" rel="nofollow noopener"><b>${o.src}${w?' ⚠':''}</b><span class="num">${euro(ps.p)}</span><span class="note">${ps.a?(w?'Wucher':'verfügbar'):'nicht verf.'}</span></a>`;
}).join('');
// chains
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;if(state.chains.has(c)){state.chains.delete(c);b.classList.remove('on');}else{state.chains.add(c);b.classList.add('on');}render();}));
}
fetch('/data/offers.json').then(r=>r.json()).then(d=>{offers=d.offers;render();}).catch(()=>{elCount.textContent='Daten konnten nicht geladen werden';}); map.on('load',()=>{
map.on('load',render); map.addSource('all',{type:'geojson',data:{type:'FeatureCollection',features:[]},cluster:true,clusterRadius:46,clusterMaxZoom:9});
map.addLayer({id:'cl',type:'circle',source:'all',filter:['has','point_count'],paint:{'circle-color':'#2b6a72','circle-opacity':.82,'circle-radius':['step',['get','point_count'],14,50,18,200,24,500,30]}});
map.addLayer({id:'cl-c',type:'symbol',source:'all',filter:['has','point_count'],layout:{'text-field':['get','point_count_abbreviated'],'text-size':12},paint:{'text-color':'#fff'}});
map.addLayer({id:'pt',type:'circle',source:'all',filter:['!',['has','point_count']],paint:{'circle-color':'#2b6a72','circle-opacity':.7,'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} – 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();
});
// controls
$$('.chip[data-p]').forEach(b=>b.addEventListener('click',()=>{const k=b.dataset.p;state.products[k]=!state.products[k];b.classList.toggle('on');render();}));
$$('.chip[data-toggle]').forEach(b=>b.addEventListener('click',()=>{const t=b.dataset.toggle;state[t]=!state[t];b.classList.toggle('on');render();}));
$('#q').addEventListener('input',e=>{state.q=e.target.value.trim();shown=120;render();});
$('#radius').addEventListener('input',e=>{state.radius=+e.target.value;$('#r-out').textContent=state.radius+' km';render();});
$('#more').addEventListener('click',()=>{shown+=120;render();});
$('#reset').addEventListener('click',()=>{location.reload();});
$$('.mtab th[data-sort]').forEach(th=>th.addEventListener('click',()=>{state.chance=(th.dataset.sort==='q');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();});});
fetch('/data/monitor.json').then(r=>r.json()).then(d=>{DATA=d;fillStatic();if(map.loaded())render();else render();});
</script> </script>
+19
View File
@@ -58,3 +58,22 @@ header.site nav a:hover{color:var(--petrol)}
footer.site{margin-top:4rem;border-top:1px solid var(--line);background:var(--petrol-soft)} footer.site{margin-top:4rem;border-top:1px solid var(--line);background:var(--petrol-soft)}
footer.site .wrap{padding-top:2rem;padding-bottom:2rem} footer.site .wrap{padding-top:2rem;padding-bottom:2rem}
@media(max-width:720px){header.site nav a{margin-left:.8rem;font-size:.85rem}} @media(max-width:720px){header.site nav a{margin-left:.8rem;font-size:.85rem}}
/* --- v2 additions: data-monitor craft (ui-ux-design-pro + design-taste-frontend) --- */
:root{
--mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
--ok-dot:oklch(0.62 0.13 155);
--low-dot:oklch(0.78 0.15 75);
--off-dot:oklch(0.80 0.01 240);
--ink-2:oklch(0.34 0.02 250);
--surface-2:oklch(0.975 0.006 230);
--shadow-sm:0 1px 2px oklch(0.4 0.03 250 / .06);
--shadow-md:0 6px 20px oklch(0.4 0.05 250 / .08),0 1px 3px oklch(0.4 0.05 250 / .05);
}
.num{font-family:var(--mono);font-variant-numeric:tabular-nums;letter-spacing:-.01em}
:focus-visible{outline:2px solid var(--petrol);outline-offset:2px;border-radius:4px}
.dot{display:inline-block;width:9px;height:9px;border-radius:50%;vertical-align:middle}
.dot.ok{background:var(--ok-dot)}.dot.low{background:var(--low-dot)}.dot.off{background:var(--off-dot)}
.skel{background:linear-gradient(90deg,var(--line),var(--surface-2),var(--line));background-size:200% 100%;animation:sh 1.2s infinite;border-radius:6px}
@keyframes sh{0%{background-position:200% 0}100%{background-position:-200% 0}}
@media (prefers-reduced-motion: reduce){*,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}}