/* parts/partners.jsx — Partner / Verkaufsstellen: 146 echte Standorte (Quelle: Ice Cup Partnerliste) */
const { useState: usePState, useMemo: usePMemo, useRef: usePRef, useEffect: usePEffect } = React;

/* Real partner locations. x,y = position on the CH map box (0–100), projected
   from WGS84 lng/lat via an affine fit to known Swiss cities. */
const PARTNERS = [
{ "id": "p0", "name": "Smiley Group gmbh", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 52.74, "y": 28.08, "lat": 47.40146, "lng": 8.49951 },
{ "id": "p1", "name": "Universal Shop", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.42, "y": 17.46, "lat": 47.56227, "lng": 7.59725 },
{ "id": "p2", "name": "Clara Shop", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.4, "y": 17.45, "lat": 47.56241, "lng": 7.5964 },
{ "id": "p3", "name": "Gelter Shop", "city": "Gelterkinden", "kanton": "BL", "type": "Shop", "x": 34.87, "y": 23.11, "lat": 47.46558, "lng": 7.85338 },
{ "id": "p4", "name": "Züri KIOSK", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 54, "y": 28.86, "lat": 47.38829, "lng": 8.53897 },
{ "id": "p5", "name": "Old Town Shop", "city": "Schaffhausen", "kanton": "SH", "type": "Shop", "x": 50.45, "y": 13.08, "lat": 47.6988, "lng": 8.63197 },
{ "id": "p6", "name": "Wilden Shop", "city": "Kloten", "kanton": "ZH", "type": "Shop", "x": 53.81, "y": 25.6, "lat": 47.45361, "lng": 8.58012 },
{ "id": "p7", "name": "Seven Stars", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 53.74, "y": 27.77, "lat": 47.40965, "lng": 8.54464 },
{ "id": "p8", "name": "Central Shop GmbH", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 54.37, "y": 29.54, "lat": 47.37539, "lng": 8.54378 },
{ "id": "p9", "name": "Wettingen shop I 7 Days Open", "city": "Wettingen", "kanton": "AG", "type": "Shop", "x": 46.98, "y": 24.27, "lat": 47.4666, "lng": 8.32451 },
{ "id": "p10", "name": "Sami`s GmbH", "city": "Winterthur", "kanton": "ZH", "type": "Shop", "x": 56.69, "y": 23.57, "lat": 47.50017, "lng": 8.72677 },
{ "id": "p11", "name": "Bücheli Shop & Kiosk", "city": "Liestal", "kanton": "BL", "type": "Kiosk", "x": 31.5, "y": 21.87, "lat": 47.48369, "lng": 7.73593 },
{ "id": "p12", "name": "VOI Niederlenz", "city": "Niederlenz", "kanton": "AG", "type": "Supermarkt", "x": 44.49, "y": 27.4, "lat": 47.39865, "lng": 8.17718 },
{ "id": "p13", "name": "VOI Lenzburg", "city": "Lenzburg", "kanton": "AG", "type": "Supermarkt", "x": 44.5, "y": 27.86, "lat": 47.3894, "lng": 8.17065 },
{ "id": "p14", "name": "Helvetia Kiosk", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.88, "y": 29.06, "lat": 47.38412, "lng": 8.53125 },
{ "id": "p15", "name": "Kiosk Adina", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.96, "y": 28.71, "lat": 47.39124, "lng": 8.53941 },
{ "id": "p16", "name": "Viha Express", "city": "Zürich", "kanton": "ZH", "type": "Convenience", "x": 54.46, "y": 29.2, "lat": 47.38233, "lng": 8.5523 },
{ "id": "p17", "name": "44ER-CANDYSHOP", "city": "Liestal", "kanton": "BL", "type": "Convenience", "x": 31.53, "y": 21.92, "lat": 47.48285, "lng": 7.73636 },
{ "id": "p18", "name": "RL Shop Lebensmittel", "city": "Muttenz", "kanton": "BL", "type": "Lebensmittel", "x": 27.87, "y": 19.08, "lat": 47.53257, "lng": 7.63143 },
{ "id": "p19", "name": "Fast Shop Winterthur", "city": "Winterthur", "kanton": "ZH", "type": "Shop", "x": 56.67, "y": 23.56, "lat": 47.50028, "lng": 8.7258 },
{ "id": "p20", "name": "Fast Shop Baden", "city": "Baden", "kanton": "AG", "type": "Shop", "x": 46.39, "y": 23.84, "lat": 47.47404, "lng": 8.30689 },
{ "id": "p21", "name": "Fast Shop Zürich", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 54.19, "y": 29.65, "lat": 47.37287, "lng": 8.53481 },
{ "id": "p22", "name": "City Shop - Zurich Niderdorf", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 54.39, "y": 29.59, "lat": 47.37441, "lng": 8.54372 },
{ "id": "p23", "name": "STANDORT Ice Cup AG", "city": "Niederlenz", "kanton": "AG", "type": "Shop", "x": 44.46, "y": 27.35, "lat": 47.39944, "lng": 8.17656 },
{ "id": "p24", "name": "Dübi Shop Express", "city": "Dübendorf", "kanton": "ZH", "type": "Convenience", "x": 55.76, "y": 28.63, "lat": 47.39643, "lng": 8.61336 },
{ "id": "p25", "name": "Halk Pazari / Volks Bazar", "city": "Winterthur", "kanton": "ZH", "type": "Shop", "x": 57.29, "y": 23.48, "lat": 47.50318, "lng": 8.75217 },
{ "id": "p26", "name": "Fresh shop Seebach..Sila", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 53.63, "y": 27.32, "lat": 47.41852, "lng": 8.54684 },
{ "id": "p27", "name": "Kiosk Bernina", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.93, "y": 28.08, "lat": 47.4038, "lng": 8.54779 },
{ "id": "p28", "name": "Prathees Kiosk", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.69, "y": 27.61, "lat": 47.41283, "lng": 8.54509 },
{ "id": "p29", "name": "VOI Dottikon", "city": "Dottikon", "kanton": "AG", "type": "Supermarkt", "x": 46.47, "y": 28.38, "lat": 47.38283, "lng": 8.24217 },
{ "id": "p30", "name": "Swiss Food Express GmbH", "city": "Zürich", "kanton": "ZH", "type": "Lebensmittel", "x": 52.6, "y": 28.73, "lat": 47.38811, "lng": 8.48416 },
{ "id": "p31", "name": "Ada Market Dübendorf", "city": "Dübendorf", "kanton": "ZH", "type": "Lebensmittel", "x": 55.85, "y": 28.62, "lat": 47.3968, "lng": 8.61716 },
{ "id": "p32", "name": "Etzelpark Winti Benzin Tankstelle", "city": "Winterthur", "kanton": "ZH", "type": "Tankstelle", "x": 57.44, "y": 23.75, "lat": 47.49816, "lng": 8.75433 },
{ "id": "p33", "name": "Eritrean World", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.18, "y": 17.22, "lat": 47.56665, "lng": 7.5911 },
{ "id": "p34", "name": "Züri Market GmbH", "city": "Zürich", "kanton": "ZH", "type": "Lebensmittel", "x": 52.55, "y": 27.16, "lat": 47.41956, "lng": 8.50564 },
{ "id": "p35", "name": "Liquor House", "city": "Kloten", "kanton": "ZH", "type": "Shop", "x": 53.95, "y": 25.78, "lat": 47.45026, "lng": 8.58297 },
{ "id": "p36", "name": "Liquor House", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 52.78, "y": 28.82, "lat": 47.38667, "lng": 8.49021 },
{ "id": "p37", "name": "Sheyalino Kiosk + Lebensmittel", "city": "Zürich", "kanton": "ZH", "type": "Lebensmittel", "x": 53.78, "y": 29.38, "lat": 47.37749, "lng": 8.52239 },
{ "id": "p38", "name": "Ganashalingam Kiosk", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.79, "y": 29.39, "lat": 47.37733, "lng": 8.5227 },
{ "id": "p39", "name": "Halk Pazari", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 53.83, "y": 27.82, "lat": 47.40897, "lng": 8.54752 },
{ "id": "p40", "name": "Zürich Kiosk Langstrasse", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.89, "y": 29.31, "lat": 47.37908, "lng": 8.52755 },
{ "id": "p41", "name": "Helvatia Kiosk", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.88, "y": 29.06, "lat": 47.38412, "lng": 8.53125 },
{ "id": "p42", "name": "Take24 Winterthur (Take away)", "city": "Winterthur", "kanton": "ZH", "type": "Convenience", "x": 56.4, "y": 23.82, "lat": 47.49447, "lng": 8.71089 },
{ "id": "p43", "name": "Padrino's", "city": "Kloten", "kanton": "ZH", "type": "Shop", "x": 53.89, "y": 25.75, "lat": 47.45073, "lng": 8.5811 },
{ "id": "p44", "name": "House of Sweets", "city": "Weinfelden", "kanton": "TG", "type": "Convenience", "x": 65.24, "y": 21.15, "lat": 47.56607, "lng": 9.10752 },
{ "id": "p45", "name": "Ayaz Super Market", "city": "Frauenfeld", "kanton": "TG", "type": "Supermarkt", "x": 59.92, "y": 21.11, "lat": 47.55618, "lng": 8.89388 },
{ "id": "p46", "name": "Natna Shop", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.2, "y": 17.22, "lat": 47.56661, "lng": 7.59189 },
{ "id": "p47", "name": "Klybeck Shop", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.23, "y": 17.32, "lat": 47.56468, "lng": 7.59164 },
{ "id": "p48", "name": "Feldberg Lädeli", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.25, "y": 17.21, "lat": 47.56691, "lng": 7.59387 },
{ "id": "p49", "name": "Treffpunkt Unterdorf", "city": "Windisch", "kanton": "AG", "type": "Shop", "x": 44.18, "y": 23.18, "lat": 47.48281, "lng": 8.22769 },
{ "id": "p50", "name": "Kiosk Quellenstrasse", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.77, "y": 28.92, "lat": 47.38669, "lng": 8.52855 },
{ "id": "p51", "name": "Claras Tabak Shop Kesik", "city": "Basel", "kanton": "BS", "type": "Convenience", "x": 25.87, "y": 17.1, "lat": 47.56847, "lng": 7.58035 },
{ "id": "p52", "name": "Sam's Mini Shop", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.45, "y": 17.61, "lat": 47.55944, "lng": 7.59621 },
{ "id": "p53", "name": "Romy's Market", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 26.46, "y": 17.6, "lat": 47.55953, "lng": 7.59674 },
{ "id": "p54", "name": "Amaniel Supermarket", "city": "Basel", "kanton": "BS", "type": "Supermarkt", "x": 26.16, "y": 17.08, "lat": 47.56944, "lng": 7.59217 },
{ "id": "p55", "name": "Sangai Markt AG", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 25.83, "y": 17.05, "lat": 47.56939, "lng": 7.57945 },
{ "id": "p56", "name": "Elisha Market", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 25.88, "y": 17.12, "lat": 47.56799, "lng": 7.58032 },
{ "id": "p57", "name": "Red Sea Shop", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.22, "y": 17.25, "lat": 47.56608, "lng": 7.59212 },
{ "id": "p58", "name": "Kabala Shop", "city": "Pratteln", "kanton": "BL", "type": "Shop", "x": 29.65, "y": 19.78, "lat": 47.52221, "lng": 7.6928 },
{ "id": "p59", "name": "Orient Express Markt", "city": "Winterthur", "kanton": "ZH", "type": "Lebensmittel", "x": 57.19, "y": 23.55, "lat": 47.5016, "lng": 8.74699 },
{ "id": "p60", "name": "VOI Seengen", "city": "Seengen", "kanton": "AG", "type": "Supermarkt", "x": 46.58, "y": 31.27, "lat": 47.32491, "lng": 8.2036 },
{ "id": "p61", "name": "SUSA Market (schnäppli Rorschach)", "city": "Rorschach", "kanton": "SG", "type": "Lebensmittel", "x": 76.76, "y": 26.76, "lat": 47.47632, "lng": 9.48821 },
{ "id": "p62", "name": "St. Gallen Kiosk Shop", "city": "St. Gallen", "kanton": "SG", "type": "Kiosk", "x": 74.81, "y": 29.05, "lat": 47.42629, "lng": 9.37531 },
{ "id": "p63", "name": "Shop Altbach - Garage AG", "city": "Bassersdorf", "kanton": "ZH", "type": "Tankstelle", "x": 55.14, "y": 26.17, "lat": 47.44472, "lng": 8.62503 },
{ "id": "p64", "name": "Ela Market", "city": "Wildegg", "kanton": "AG", "type": "Lebensmittel", "x": 43.85, "y": 26.55, "lat": 47.41443, "lng": 8.1642 },
{ "id": "p65", "name": "Seematt Shop", "city": "Bülach", "kanton": "ZH", "type": "Shop", "x": 51.57, "y": 22.09, "lat": 47.51966, "lng": 8.54238 },
{ "id": "p66", "name": "kioskShop", "city": "Effretikon", "kanton": "ZH", "type": "Kiosk", "x": 57.2, "y": 27.26, "lat": 47.42695, "lng": 8.69186 },
{ "id": "p67", "name": "Shop 4900", "city": "Langenthal", "kanton": "BE", "type": "Shop", "x": 38.12, "y": 36.07, "lat": 47.2113, "lng": 7.79016 },
{ "id": "p68", "name": "Kölliken Shop", "city": "Kölliken", "kanton": "AG", "type": "Shop", "x": 41.74, "y": 30.19, "lat": 47.33683, "lng": 8.0244 },
{ "id": "p69", "name": "Madi Market", "city": "Oberenfelden", "kanton": "AG", "type": "Lebensmittel", "x": 41.9, "y": 29.14, "lat": 47.35834, "lng": 8.04661 },
{ "id": "p70", "name": "Ardianos Shop", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.12, "y": 17.19, "lat": 47.56709, "lng": 7.58902 },
{ "id": "p71", "name": "Xpressi by Yogesawary", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.59, "y": 18.23, "lat": 47.54709, "lng": 7.59242 },
{ "id": "p72", "name": "Tesko Lebensmittel", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 25.82, "y": 17.16, "lat": 47.56719, "lng": 7.57745 },
{ "id": "p73", "name": "Bahnhof Kiosk Amiri", "city": "Solothurn", "kanton": "SO", "type": "Kiosk", "x": 31.92, "y": 35.86, "lat": 47.20316, "lng": 7.54337 },
{ "id": "p74", "name": "Karsit K Shop", "city": "Egerkingen", "kanton": "SO", "type": "Shop", "x": 36.36, "y": 30.75, "lat": 47.31485, "lng": 7.79884 },
{ "id": "p75", "name": "Der Kiosk Momad", "city": "Niederlenz", "kanton": "AG", "type": "Kiosk", "x": 44.4, "y": 27.19, "lat": 47.40263, "lng": 8.1765 },
{ "id": "p76", "name": "SDM shop", "city": "Biel", "kanton": "BE", "type": "Shop", "x": 25.57, "y": 38.53, "lat": 47.13649, "lng": 7.24724 },
{ "id": "p77", "name": "Fenomen market sarl", "city": "Biel", "kanton": "BE", "type": "Lebensmittel", "x": 26.27, "y": 38.08, "lat": 47.14704, "lng": 7.28225 },
{ "id": "p78", "name": "Aare Primo", "city": "Büren an der Aare", "kanton": "BE", "type": "Lebensmittel", "x": 28.58, "y": 38.7, "lat": 47.13923, "lng": 7.36603 },
{ "id": "p79", "name": "K Shop", "city": "Solothurn", "kanton": "SO", "type": "Shop", "x": 31.57, "y": 35.65, "lat": 47.20659, "lng": 7.53236 },
{ "id": "p80", "name": "K SHOP Karsit GmbH", "city": "Hägendorf", "kanton": "SO", "type": "Shop", "x": 37.21, "y": 30.06, "lat": 47.33038, "lng": 7.84374 },
{ "id": "p81", "name": "YOYO Kiosk", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 54.23, "y": 29.15, "lat": 47.383, "lng": 8.54367 },
{ "id": "p82", "name": "Safi Coffee & Shop", "city": "Basel", "kanton": "BS", "type": "Convenience", "x": 26.41, "y": 17.19, "lat": 47.56778, "lng": 7.60066 },
{ "id": "p83", "name": "Volenergy AG Ruedi Rüssel", "city": "Basel", "kanton": "BS", "type": "Tankstelle", "x": 26.3, "y": 17.4, "lat": 47.56318, "lng": 7.59306 },
{ "id": "p84", "name": "Sana Markt", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 26.29, "y": 17.43, "lat": 47.56264, "lng": 7.59243 },
{ "id": "p85", "name": "Wabern Kiosk Shop Amiri", "city": "Köniz", "kanton": "BE", "type": "Kiosk", "x": 34.92, "y": 49.88, "lat": 46.92707, "lng": 7.4545 },
{ "id": "p86", "name": "Denner Partner", "city": "Nebikon", "kanton": "LU", "type": "Supermarkt", "x": 43.32, "y": 37.51, "lat": 47.19282, "lng": 7.97853 },
{ "id": "p87", "name": "Denner Satellit", "city": "Entlebuch", "kanton": "LU", "type": "Supermarkt", "x": 49.42, "y": 48.13, "lat": 46.99142, "lng": 8.06572 },
{ "id": "p88", "name": "Skitis Snack and More", "city": "Laufen", "kanton": "BL", "type": "Convenience", "x": 26.62, "y": 24.48, "lat": 47.42142, "lng": 7.49999 },
{ "id": "p89", "name": "SOCAR", "city": "Zürich", "kanton": "ZH", "type": "Tankstelle", "x": 55.07, "y": 31.61, "lat": 47.33521, "lng": 8.54095 },
{ "id": "p90", "name": "Enjoy Chill Out", "city": "Liestal", "kanton": "BL", "type": "Shop", "x": 31.46, "y": 21.84, "lat": 47.48426, "lng": 7.73487 },
{ "id": "p91", "name": "YumiHana Zürich", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 54.29, "y": 29.48, "lat": 47.37642, "lng": 8.54143 },
{ "id": "p92", "name": "YumiHana Basel", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 26.23, "y": 17.64, "lat": 47.55832, "lng": 7.58695 },
{ "id": "p93", "name": "YumiHana Bern", "city": "Bern", "kanton": "BE", "type": "Shop", "x": 34.34, "y": 48.8, "lat": 46.94745, "lng": 7.44705 },
{ "id": "p94", "name": "Gusto GmbH", "city": "Mellingen", "kanton": "AG", "type": "Shop", "x": 46.55, "y": 26.64, "lat": 47.41796, "lng": 8.27169 },
{ "id": "p95", "name": "Volta Market Mahinthan", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 25.76, "y": 17, "lat": 47.57026, "lng": 7.57758 },
{ "id": "p96", "name": "Shop Express", "city": "Baden", "kanton": "AG", "type": "Convenience", "x": 46.4, "y": 23.97, "lat": 47.47149, "lng": 8.30557 },
{ "id": "p97", "name": "YumiHana Beauty Bern", "city": "Bern", "kanton": "BE", "type": "Shop", "x": 34.28, "y": 48.76, "lat": 46.94821, "lng": 7.44523 },
{ "id": "p98", "name": "Kiosk Pacchetto", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.43, "y": 29.3, "lat": 47.37823, "lng": 8.50932 },
{ "id": "p99", "name": "Uster Express Shop", "city": "Uster", "kanton": "ZH", "type": "Convenience", "x": 59.39, "y": 31.27, "lat": 47.35059, "lng": 8.72021 },
{ "id": "p100", "name": "Metro Express (ONLINE)", "city": "Luzern", "kanton": "LU", "type": "Supermarkt", "x": 54.1, "y": 45.52, "lat": 47.05316, "lng": 8.29312 },
{ "id": "p101", "name": "nouristore Tankstelle", "city": "Fislisbach", "kanton": "AG", "type": "Tankstelle", "x": 46.71, "y": 25.71, "lat": 47.43708, "lng": 8.29185 },
{ "id": "p102", "name": "nouristore Tankstelle", "city": "Suhr", "kanton": "AG", "type": "Tankstelle", "x": 42.54, "y": 28.43, "lat": 47.37383, "lng": 8.08291 },
{ "id": "p103", "name": "nouristore Tankstelle", "city": "Zufikon", "kanton": "AG", "type": "Tankstelle", "x": 49.99, "y": 30.59, "lat": 47.34544, "lng": 8.35105 },
{ "id": "p104", "name": "Alima Market Basel", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 26.29, "y": 17.22, "lat": 47.56682, "lng": 7.59546 },
{ "id": "p105", "name": "Ziply GmbH (ONLINE)", "city": "Kriens", "kanton": "LU", "type": "Shop", "x": 53.9, "y": 46.42, "lat": 47.0347, "lng": 8.27187 },
{ "id": "p106", "name": "Aadi GmbH K2 Express", "city": "Zürich", "kanton": "ZH", "type": "Convenience", "x": 53.79, "y": 27.8, "lat": 47.40923, "lng": 8.54616 },
{ "id": "p107", "name": "Frisch TO GO Lädeli", "city": "Oberwil", "kanton": "BL", "type": "Shop", "x": 26.31, "y": 19.74, "lat": 47.51623, "lng": 7.55859 },
{ "id": "p108", "name": "SHOP ELEVEN", "city": "Solothurn", "kanton": "SO", "type": "Shop", "x": 31.64, "y": 35.65, "lat": 47.20678, "lng": 7.53523 },
{ "id": "p109", "name": "Züri Kiosk", "city": "Adliswil", "kanton": "ZH", "type": "Kiosk", "x": 55.02, "y": 32.24, "lat": 47.32236, "lng": 8.52926 },
{ "id": "p110", "name": "XYZ", "city": "Schindellegi", "kanton": "SZ", "type": "Shop", "x": 63.04, "y": 40.7, "lat": 47.1682, "lng": 8.72625 },
{ "id": "p111", "name": "Star Shop", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.4, "y": 17.99, "lat": 47.55167, "lng": 7.58848 },
{ "id": "p112", "name": "FAST SHOP FRESH", "city": "Opfikon", "kanton": "ZH", "type": "Shop", "x": 53.72, "y": 26.72, "lat": 47.43082, "lng": 8.55951 },
{ "id": "p113", "name": "Namira", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.24, "y": 17.23, "lat": 47.56656, "lng": 7.59337 },
{ "id": "p114", "name": "Candy Clouds", "city": "Aesch", "kanton": "BL", "type": "Convenience", "x": 28.19, "y": 22.24, "lat": 47.46975, "lng": 7.59712 },
{ "id": "p115", "name": "@ Kiosk", "city": "Oberenfelden", "kanton": "AG", "type": "Kiosk", "x": 41.94, "y": 29.25, "lat": 47.35619, "lng": 8.04649 },
{ "id": "p116", "name": "SPAR express AVIA Liestal", "city": "Liestal", "kanton": "BL", "type": "Tankstelle", "x": 30.92, "y": 21.15, "lat": 47.49701, "lng": 7.72324 },
{ "id": "p117", "name": "Dorfladen", "city": "Frenkendorf", "kanton": "BL", "type": "Lebensmittel", "x": 30.57, "y": 20.8, "lat": 47.50345, "lng": 7.71442 },
{ "id": "p118", "name": "SPAR express AVIA Zwingen", "city": "Zwingen", "kanton": "BL", "type": "Tankstelle", "x": 27.21, "y": 23.65, "lat": 47.43932, "lng": 7.53625 },
{ "id": "p119", "name": "Tukmachy", "city": "Schlieren", "kanton": "ZH", "type": "Shop", "x": 51.83, "y": 28.18, "lat": 47.39764, "lng": 8.46169 },
{ "id": "p120", "name": "THARANI KIOSK", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.31, "y": 29.48, "lat": 47.37444, "lng": 8.50182 },
{ "id": "p121", "name": "Topshop 3008", "city": "Bern", "kanton": "BE", "type": "Shop", "x": 33.88, "y": 48.85, "lat": 46.94554, "lng": 7.42776 },
{ "id": "p122", "name": "Global Crown", "city": "Grenchen", "kanton": "SO", "type": "Shop", "x": 28.4, "y": 36.16, "lat": 47.19, "lng": 7.39693 },
{ "id": "p123", "name": "S.K Market", "city": "Therwil", "kanton": "BL", "type": "Lebensmittel", "x": 26.59, "y": 20.61, "lat": 47.49919, "lng": 7.55674 },
{ "id": "p124", "name": "ASIA Shoppy", "city": "Bern", "kanton": "BE", "type": "Shop", "x": 32.51, "y": 48.74, "lat": 46.94508, "lng": 7.37414 },
{ "id": "p125", "name": "ASIA Shoppy Welle 7", "city": "Bern", "kanton": "BE", "type": "Shop", "x": 34.05, "y": 48.75, "lat": 46.94797, "lng": 7.43633 },
{ "id": "p126", "name": "Kiosk Fischermätteli", "city": "Bern", "kanton": "BE", "type": "Kiosk", "x": 33.73, "y": 49.08, "lat": 46.94072, "lng": 7.41839 },
{ "id": "p127", "name": "Tabak & Shop GmbH", "city": "Zürich", "kanton": "ZH", "type": "Convenience", "x": 53.67, "y": 29.47, "lat": 47.37539, "lng": 8.51649 },
{ "id": "p128", "name": "S.T.R. City Market", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 26.59, "y": 17.53, "lat": 47.56132, "lng": 7.60316 },
{ "id": "p129", "name": "Shop 4057", "city": "Basel", "kanton": "BS", "type": "Shop", "x": 26.25, "y": 17.35, "lat": 47.56424, "lng": 7.59187 },
{ "id": "p130", "name": "Babylon Import-Export Kiosk", "city": "Dietikon", "kanton": "ZH", "type": "Kiosk", "x": 50.14, "y": 27.42, "lat": 47.40945, "lng": 8.40459 },
{ "id": "p131", "name": "K. V-Mini Market", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 25.61, "y": 17.44, "lat": 47.56102, "lng": 7.56467 },
{ "id": "p132", "name": "Haimo Kiosk", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.16, "y": 29.67, "lat": 47.37032, "lng": 8.49266 },
{ "id": "p133", "name": "Amirian Kiosk", "city": "Zürich", "kanton": "ZH", "type": "Kiosk", "x": 53.93, "y": 27.91, "lat": 47.40732, "lng": 8.55021 },
{ "id": "p134", "name": "Vasa Lebensmittel", "city": "Basel", "kanton": "BS", "type": "Lebensmittel", "x": 26.16, "y": 17.24, "lat": 47.56625, "lng": 7.58982 },
{ "id": "p135", "name": "Pra Delal", "city": "Würenlos", "kanton": "AG", "type": "Shop", "x": 48.3, "y": 25.45, "lat": 47.44551, "lng": 8.36015 },
{ "id": "p136", "name": "YuMu", "city": "Aarburg", "kanton": "AG", "type": "Shop", "x": 38.85, "y": 30.75, "lat": 47.3198, "lng": 7.89942 },
{ "id": "p137", "name": "Jana Kiosk & Shop", "city": "Olten", "kanton": "SO", "type": "Kiosk", "x": 38.37, "y": 29.22, "lat": 47.34957, "lng": 7.90285 },
{ "id": "p138", "name": "Abis 24", "city": "Baden", "kanton": "AG", "type": "Shop", "x": 46.25, "y": 23.61, "lat": 47.47832, "lng": 8.30481 },
{ "id": "p139", "name": "Ötzi Shop", "city": "Zürich", "kanton": "ZH", "type": "Shop", "x": 52.72, "y": 28.04, "lat": 47.4023, "lng": 8.49965 },
{ "id": "p140", "name": "SnackPoint", "city": "Aarau", "kanton": "AG", "type": "Convenience", "x": 41.21, "y": 27.43, "lat": 47.39145, "lng": 8.04458 },
{ "id": "p141", "name": "Brot&Ware GmbH", "city": "Winterthur", "kanton": "ZH", "type": "Shop", "x": 57.57, "y": 23.47, "lat": 47.50387, "lng": 8.76346 },
{ "id": "p142", "name": "Persien Market & Restaurant", "city": "Neuenhof", "kanton": "AG", "type": "Lebensmittel", "x": 46.83, "y": 24.66, "lat": 47.45851, "lng": 8.31265 },
{ "id": "p143", "name": "Sweet Schaffhausen", "city": "Schaffhausen", "kanton": "SH", "type": "Convenience", "x": 50.7, "y": 13.25, "lat": 47.69575, "lng": 8.63953 },
{ "id": "p144", "name": "Fast Shop", "city": "Schaffhausen", "kanton": "SH", "type": "Shop", "x": 50.5, "y": 13.13, "lat": 47.69785, "lng": 8.63323 },
{ "id": "p145", "name": "Loru Market", "city": "Dietikon", "kanton": "ZH", "type": "Lebensmittel", "x": 50.67, "y": 28.15, "lat": 47.39581, "lng": 8.41492 }];

window.PARTNERS = PARTNERS;

/* canton BFS number -> 2-letter abbreviation (swisstopo ids) */
const CANTON_ABBR = { 1: 'ZH', 2: 'BE', 3: 'LU', 4: 'UR', 5: 'SZ', 6: 'OW', 7: 'NW', 8: 'GL', 9: 'ZG', 10: 'FR', 11: 'SO', 12: 'BS', 13: 'BL', 14: 'SH', 15: 'AR', 16: 'AI', 17: 'SG', 18: 'GR', 19: 'AG', 20: 'TG', 21: 'TI', 22: 'VD', 23: 'VS', 24: 'NE', 25: 'GE', 26: 'JU' };
const CANTON_NAME = { ZH: 'Zürich', BE: 'Bern', LU: 'Luzern', UR: 'Uri', SZ: 'Schwyz', OW: 'Obwalden', NW: 'Nidwalden', GL: 'Glarus', ZG: 'Zug', FR: 'Freiburg', SO: 'Solothurn', BS: 'Basel-Stadt', BL: 'Basel-Landschaft', SH: 'Schaffhausen', AR: 'Appenzell A.Rh.', AI: 'Appenzell I.Rh.', SG: 'St. Gallen', GR: 'Graubünden', AG: 'Aargau', TG: 'Thurgau', TI: 'Tessin', VD: 'Waadt', VS: 'Wallis', NE: 'Neuenburg', GE: 'Genf', JU: 'Jura' };

const KANTONE = Array.from(new Set(PARTNERS.map((p) => p.kanton))).sort();
const TOPO_URL = 'https://unpkg.com/swiss-maps@4/2021/ch-combined.json';

function PartnersMap() {
  const [active, setActive] = usePState(PARTNERS[0].id);
  const [q, setQ] = usePState('');
  const [kanton, setKanton] = usePState('Alle');
  const [isMobile, setIsMobile] = usePState(false);
  const [visible, setVisible] = usePState(6);
  const [mapReady, setMapReady] = usePState(false);
  const scrollRef = usePRef(null);
  const mapElRef = usePRef(null);
  const mapRef = usePRef(null);
  const markersRef = usePRef({}); // id -> circleMarker
  const cantonLayerRef = usePRef(null);
  const activeRef = usePRef(active);
  const PAGE = 6;

  usePEffect(() => {activeRef.current = active;}, [active]);

  usePEffect(() => {
    const mq = window.matchMedia('(max-width: 860px)');
    const update = () => setIsMobile(mq.matches);
    update();
    mq.addEventListener ? mq.addEventListener('change', update) : mq.addListener(update);
    return () => mq.removeEventListener ? mq.removeEventListener('change', update) : mq.removeListener(update);
  }, []);

  const filtered = usePMemo(() => {
    const needle = q.trim().toLowerCase();
    return PARTNERS.filter((p) => {
      if (kanton !== 'Alle' && p.kanton !== kanton) return false;
      if (!needle) return true;
      return (p.name + ' ' + p.city + ' ' + p.kanton + ' ' + p.type).toLowerCase().includes(needle);
    });
  }, [q, kanton]);

  usePEffect(() => {setVisible(PAGE);}, [q, kanton]);

  const shown = isMobile ? filtered.slice(0, visible) : filtered;
  const remaining = filtered.length - shown.length;
  const sel = PARTNERS.find((p) => p.id === active) || PARTNERS[0];

  /* ---------- init Leaflet once ---------- */
  usePEffect(() => {
    if (!window.L || !mapElRef.current || mapRef.current) return;
    const L = window.L;
    const map = L.map(mapElRef.current, {
      center: [47.30, 8.10], zoom: 8, minZoom: 7, maxZoom: 13,
      zoomControl: true, scrollWheelZoom: false, attributionControl: false
    });
    mapRef.current = map;

    // brand-styled marker factory
    const mk = (p) => {
      const m = L.circleMarker([p.lat, p.lng], {
        radius: 6, weight: 2, color: '#ffffff', fillColor: '#1878C0', fillOpacity: 1,
        className: 'ic-marker'
      });
      m.bindTooltip(p.city + ' · ' + p.name, { direction: 'top', offset: [0, -6], className: 'ic-tip' });
      m.on('click', () => {selectPartner(p.id, false);});
      return m;
    };
    const markers = {};
    PARTNERS.forEach((p) => {const m = mk(p);markers[p.id] = m;m.addTo(map);});
    markersRef.current = markers;

    // load real canton borders
    fetch(TOPO_URL).then((r) => r.json()).then((topo) => {
      const obj = topo.objects.cantons || topo.objects.kantone || topo.objects.country;
      if (!obj || !window.topojson) return;
      const fc = window.topojson.feature(topo, obj);
      const layer = L.geoJSON(fc, {
        style: () => ({ color: '#1878C0', weight: 1, opacity: 0.45, fillColor: '#CFE6F4', fillOpacity: 0.22 }),
        onEachFeature: (feat, lyr) => {
          const ab = CANTON_ABBR[feat.id] || feat.properties && feat.properties.abbr;
          lyr.on('mouseover', () => lyr.setStyle({ fillOpacity: 0.42, weight: 1.6, opacity: 0.8 }));
          lyr.on('mouseout', () => cantonLayerRef.current && cantonLayerRef.current.resetStyle(lyr));
          if (ab) {
            lyr.bindTooltip(CANTON_NAME[ab] || ab, { sticky: true, className: 'ic-tip ct' });
            lyr.on('click', () => {setKanton((prev) => prev === ab ? 'Alle' : ab);});
          }
        }
      });
      layer.addTo(map);
      layer.bringToBack();
      cantonLayerRef.current = layer;
    }).catch(() => {});

    // background pane tint
    mapElRef.current.style.background = 'linear-gradient(160deg, oklch(0.97 0.012 232), oklch(0.93 0.03 230))';
    setTimeout(() => {map.invalidateSize();}, 200);
    setMapReady(true);

    // keep the map sized to its container (mobile column reflow, orientation change, resize)
    let rafId = 0;
    const refresh = () => { cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => map.invalidateSize()); };
    let ro;
    if (window.ResizeObserver) { ro = new ResizeObserver(refresh); ro.observe(mapElRef.current); }
    window.addEventListener('resize', refresh);
    window.addEventListener('orientationchange', refresh);

    return () => {
      if (ro) ro.disconnect();
      window.removeEventListener('resize', refresh);
      window.removeEventListener('orientationchange', refresh);
      cancelAnimationFrame(rafId);
      map.remove();mapRef.current = null;
    };
  }, []);

  /* keep markers in sync with the filter */
  usePEffect(() => {
    const map = mapRef.current;if (!map) return;
    const ids = new Set(filtered.map((p) => p.id));
    Object.entries(markersRef.current).forEach(([id, m]) => {
      const on = ids.has(id);
      if (on && !map.hasLayer(m)) m.addTo(map);
      if (!on && map.hasLayer(m)) map.removeLayer(m);
    });
    // fit to filtered set (only when a real filter is applied)
    if (filtered.length && (kanton !== 'Alle' || q.trim())) {
      const b = window.L.latLngBounds(filtered.map((p) => [p.lat, p.lng]));
      map.fitBounds(b.pad(0.25), { animate: true, maxZoom: 12 });
    }
  }, [filtered, kanton, q, mapReady]);

  /* highlight active marker */
  usePEffect(() => {
    const markers = markersRef.current;
    Object.entries(markers).forEach(([id, m]) => {
      if (id === active) m.setStyle({ radius: 9, fillColor: '#0B3A63', weight: 3 }).bringToFront();else
      m.setStyle({ radius: 6, fillColor: '#1878C0', weight: 2 });
    });
  }, [active, mapReady]);

  function selectPartner(id, fly) {
    setActive(id);
    const p = PARTNERS.find((x) => x.id === id);
    const map = mapRef.current;
    if (map && p) {
      if (fly) map.flyTo([p.lat, p.lng], Math.max(map.getZoom(), 11), { duration: 0.6 });
      const m = markersRef.current[id];if (m) m.openTooltip();
    }
    // scroll list row into view
    requestAnimationFrame(() => {
      const el = scrollRef.current && scrollRef.current.querySelector('[data-row="' + id + '"]');
      if (el && scrollRef.current) {
        const c = scrollRef.current,r = el;
        const top = r.offsetTop - c.offsetTop - c.clientHeight / 2 + r.clientHeight / 2;
        c.scrollTo({ top, behavior: 'smooth' });
      }
    });
  }

  return (
    <section className="section partners" id="partners">
      <div className="wrap">
        <div className="sec-head reveal">
          <span className="eyebrow">Partnernetz · Swiss Made</span>
          <h2 className="section-title">150+ Partner.<br />Eine Schweiz.</h2>
          <p className="lead">Bereits <b>150+ Betriebe</b> verkaufen Ice Cup in der Deutschschweiz. Zoome in die Karte, filtere nach Kanton oder durchsuche das Netz.</p>
        </div>

        <div className="partners-grid reveal">
          {/* REAL LEAFLET MAP */}
          <div className="map-card">
            <div className="map-stage">
              <div ref={mapElRef} className="leaflet-map"></div>
            </div>
          </div>

          {/* LIST + CONTROLS */}
          <div className="partner-list">
            <div className="partner-feature">
              <span className="pf-city">{sel.city} · {sel.kanton}</span>
              <b className="pf-name">{sel.name}</b>
              <span className="pf-type">{sel.type}</span>
            </div>

            <div className="partner-controls">
              <div className="search-field">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"><circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" /></svg>
                <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Ort, Händler, Kanton suchen…" />
                {q && <button className="search-clear" onClick={() => setQ('')} aria-label="Suche löschen"><IcClose s={14} /></button>}
              </div>
              <select className="kanton-select" value={kanton} onChange={(e) => setKanton(e.target.value)}>
                <option value="Alle">Alle Kantone</option>
                {KANTONE.map((k) => <option key={k} value={k}>{CANTON_NAME[k] ? CANTON_NAME[k] + ' (' + k + ')' : k}</option>)}
              </select>
            </div>

            <div className="partner-scroll" ref={scrollRef}>
              {filtered.length === 0 &&
              <div className="partner-empty">Keine Standorte gefunden.<br />Versuch einen anderen Ort oder Kanton.</div>
              }
              {shown.map((p) =>
              <button key={p.id} data-row={p.id}
              className={"partner-row" + (active === p.id ? " on" : "")}
              onClick={() => selectPartner(p.id, true)}>
                  <span className="pr-pin"></span>
                  <span className="pr-text">
                    <b>{p.name}</b>
                    <span>{p.city} · {p.kanton} · {p.type}</span>
                  </span>
                  <IcArrow s={15} />
                </button>
              )}
              {isMobile && remaining > 0 &&
              <button className="partner-more" onClick={() => setVisible((v) => v + PAGE)}>
                  Weitere laden <small>+{Math.min(PAGE, remaining)} von {remaining}</small>
                </button>
              }
            </div>
          </div>
        </div>
      </div>
    </section>);

}

Object.assign(window, { PartnersMap });