{"id":261,"date":"2026-01-28T09:13:44","date_gmt":"2026-01-28T09:13:44","guid":{"rendered":"https:\/\/plzprofi.de\/?page_id=261"},"modified":"2026-02-23T19:32:51","modified_gmt":"2026-02-23T19:32:51","slug":"es","status":"publish","type":"page","link":"https:\/\/plzprofi.de\/es\/eu-laender\/es\/","title":{"rendered":"Spanien Umkreissuche"},"content":{"rendered":"\n<link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet\/dist\/leaflet.css\" \/>\n\n<style>\n  :root { font-size: 90%; }\n\n  \/* === Grundlayout === *\/\n  body{\n    background:#050814;\n    color:#ffffff;\n    font-family:system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;\n  }\n\n  \/* =========================\n     HERO (ES)\n     ========================= *\/\n  .plzprofi-hero{\n    margin: 32px 16px 24px;\n    padding: 48px 32px 40px;\n    border-radius: 28px;\n    border: 1px solid rgba(148, 163, 184, 0.18);\n    box-shadow:\n      0 30px 80px rgba(15, 23, 42, 0.9),\n      0 0 0 1px rgba(15, 23, 42, 0.6);\n    overflow: hidden;\n    position: relative;\n    background: #050814;\n  }\n\n  \/* ES Flag as SVG background (stretched)\n     Spain: red \/ yellow \/ red (1\/4, 1\/2, 1\/4)\n  *\/\n  .plzprofi-hero::before{\n    content:\"\";\n    position:absolute;\n    inset:-2px;\n    background-image:\n      url(\"data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 900 600'%3E%3Crect width='900' height='150' fill='%23aa151b'\/%3E%3Crect y='150' width='900' height='300' fill='%23f1bf00'\/%3E%3Crect y='450' width='900' height='150' fill='%23aa151b'\/%3E%3C\/svg%3E\");\n    background-size: cover;\n    background-position: center;\n    opacity: 0.90;\n    filter: saturate(1.18) contrast(1.10);\n    transform: scale(1.02);\n  }\n\n  \/* Shadow\/Glow overlay *\/\n  .plzprofi-hero::after{\n    content:\"\";\n    position:absolute;\n    inset:0;\n    background:\n      radial-gradient(circle at top left, rgba(59,130,246,0.25), transparent 55%),\n      radial-gradient(circle at top right, rgba(168,85,247,0.18), transparent 55%),\n      linear-gradient(90deg, rgba(5,8,20,0.92) 0%, rgba(5,8,20,0.62) 45%, rgba(5,8,20,0.92) 100%);\n    pointer-events:none;\n  }\n\n  .plzprofi-hero-inner{\n    position: relative;\n    z-index: 1;\n    max-width: 1280px;\n    margin: 0 auto;\n    display: flex;\n    gap: 48px;\n    align-items: center;\n  }\n\n  .plzprofi-hero-left{ flex:1 1 52%; }\n  .plzprofi-hero-right{\n    flex:1 1 48%;\n    display:flex;\n    justify-content:center;\n  }\n\n  .hero-kicker{\n    display:inline-flex;\n    align-items:center;\n    gap: 8px;\n    padding: 4px 10px;\n    border-radius: 999px;\n    font-size: 11px;\n    letter-spacing: .08em;\n    text-transform: uppercase;\n    background: rgba(15, 23, 42, 0.8);\n    border: 1px solid rgba(56, 189, 248, 0.35);\n    color: #7dd3fc;\n    margin-bottom: 14px;\n  }\n\n  .hero-kicker::before{\n    content:\"\";\n    width: 6px;\n    height: 6px;\n    border-radius: 999px;\n    background:#22c55e;\n    box-shadow: 0 0 0 0 rgba(34,197,94,0.9);\n    animation: plzprofi-pulse-dot 1.6s infinite ease-out;\n  }\n\n  @keyframes plzprofi-pulse-dot{\n    0%   { box-shadow: 0 0 0 0 rgba(34,197,94,0.9); transform: scale(1); }\n    70%  { box-shadow: 0 0 0 10px rgba(34,197,94,0); transform: scale(1.1); }\n    100% { box-shadow: 0 0 0 0 rgba(34,197,94,0); transform: scale(1); }\n  }\n\n  .plzprofi-hero-left h1{\n    font-size: clamp(32px, 4vw, 40px);\n    line-height: 1.1;\n    margin: 0 0 14px;\n  }\n\n  .plzprofi-hero-left p{\n    margin: 0 0 24px;\n    font-size: 15px;\n    max-width: 42rem;\n    color: #e5e7eb;\n    opacity: 0.95;\n  }\n\n  .hero-btn{\n    display:inline-flex;\n    align-items:center;\n    justify-content:center;\n    gap: 8px;\n    padding: 12px 26px;\n    border-radius: 999px;\n    font-size: 14px;\n    font-weight: 500;\n    color:#ffffff;\n    text-decoration:none;\n    background: linear-gradient(135deg, #2563eb, #1d4ed8);\n    box-shadow: 0 18px 40px rgba(37,99,235,0.55);\n    border:none;\n    cursor:pointer;\n    transition: transform .15s ease, box-shadow .15s ease, filter .15s ease;\n  }\n  .hero-btn:hover{\n    transform: translateY(-1px);\n    filter: brightness(1.05);\n    box-shadow: 0 22px 46px rgba(37,99,235,0.75);\n  }\n\n  .hero-card{\n    width: 100%;\n    max-width: 520px;\n    aspect-ratio: 4 \/ 3;\n    border-radius: 24px;\n    position: relative;\n    overflow: hidden;\n    background:\n      radial-gradient(circle at center, rgba(59,130,246,0.20), rgba(15,23,42,0.98)),\n      #020617;\n    box-shadow:\n      0 24px 80px rgba(15,23,42,0.9),\n      0 0 0 1px rgba(148,163,184,0.28);\n  }\n\n  .hero-card::before{\n    content:\"\";\n    position:absolute;\n    inset:0;\n    background-image: url(\"https:\/\/plzprofi.de\/wp-content\/uploads\/media\/ESMAPV2.png\");\n    background-size: cover;\n    background-position: center;\n    opacity: 0.95;\n    mix-blend-mode: screen;\n    filter: contrast(1.05) saturate(1.05);\n  }\n\n  .hero-card::after{\n    content:\"\";\n    position:absolute;\n    inset:0;\n    background: radial-gradient(circle at center, rgba(56,189,248,0.38), transparent 60%);\n    mix-blend-mode: soft-light;\n  }\n\n  .hero-tag{\n    position:absolute;\n    left: 16px;\n    top: 16px;\n    padding: 6px 11px;\n    border-radius: 999px;\n    font-size: 11px;\n    text-transform: uppercase;\n    letter-spacing: .08em;\n    background: rgba(15,23,42,0.9);\n    border: 1px solid rgba(96,165,250,0.55);\n    color: #bfdbfe;\n    backdrop-filter: blur(6px);\n    z-index: 2;\n  }\n\n  @media (max-width: 960px){\n    .plzprofi-hero-inner{ flex-direction: column; align-items: flex-start; }\n    .plzprofi-hero-right{ width:100%; }\n    .hero-card{ max-width:100%; }\n  }\n\n  \/* === Tool Layout === *\/\n  .wrapper { max-width: 1280px; margin: 0 auto; padding: 16px 16px 40px; }\n\n  .panel {\n    position: relative; text-align: center; padding: 8px; display: flex; gap: 10px;\n    align-items: center; justify-content: center; flex-wrap: wrap;\n  }\n\n  label, input, button, select { font-size: 14px; }\n  input[type=\"text\"] { width: 240px; padding: 6px 8px; border: none; border-radius: 4px; }\n  input[type=\"number\"] { width: 64px; padding: 5px 6px; border-radius: 4px; border: none; }\n  input[type=\"range\"] { vertical-align: middle; width: 140px; -webkit-appearance: none; height: 6px; background: #444; border-radius: 3px; outline: none; accent-color: #0af; }\n  input[type=\"range\"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: #00bfff; border-radius: 50%; border: 2px solid #fff; cursor: pointer; }\n  #radiusLabel { margin: 0 5px; display: inline-block; min-width: 30px; }\n\n  button { padding: 6px 10px; background: #2a2a2a; color: #fff; border: none; cursor: pointer; border-radius: 6px; }\n  button:hover { background: #333; }\n  #btnSearch { background: #3b82f6; }\n  #btnSearch:hover { background: #2563eb; }\n\n  .seg { display: inline-flex; background: #2a2a2a; border-radius: 999px; overflow: hidden; }\n  .seg-btn { padding: 6px 12px; border-radius: 999px; background: transparent; border: none; color: #e5e7eb; cursor: pointer; display: inline-flex; align-items: center; gap: 8px;}\n  .seg-btn.active { background: #00bfff; color: #00121a; }\n  \n  #countryIndicator, #countryIndicator * { translate: no; cursor: default; }\n  #datasetBar{ display:none; } \/* Hidden for production *\/\n\n  #main { display: flex; justify-content: center; align-items: flex-start; gap: 16px; margin-top: 16px; }\n\n  \/* === Linke Spalte (Kompass & Verlauf) === *\/\n  #leftCol { width: 220px; display: flex; flex-direction: column; gap: 16px; flex-shrink: 0; }\n\n  .card { background: #111827; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); padding: 12px; }\n  .card h3 { margin: 0 0 10px 0; font-size: 15px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 6px; }\n\n  .compass-module {\n    display: flex; align-items: center; gap: 12px; background: #1f2937; padding: 10px;\n    border-radius: 8px; border: 1px solid rgba(148, 163, 184, 0.1); justify-content: flex-start;\n  }\n\n  .compass-canvas-wrap {\n    position: relative; width: 64px; height: 64px; border-radius: 50%;\n    background: radial-gradient(circle, #0f172a 0%, #020617 100%);\n    box-shadow: inset 0 0 10px rgba(0, 191, 255, 0.2); border: 2px solid #334155; cursor: pointer; flex-shrink: 0;\n  }\n  #compassCanvas { position: absolute; top: 0; left: 0; border-radius: 50%; width: 100%; height: 100%; }\n\n  .compass-controls { display: flex; flex-direction: column; gap: 8px; flex: 1; align-items: flex-start; }\n  \n  .compass-toggle-group {\n    display: inline-flex; background: #111827; border-radius: 999px; overflow: hidden; border: 1px solid rgba(148,163,184,0.2);\n  }\n  \n  .compass-toggle-btn {\n    display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; font-size: 11px; \n    border: none; background: transparent; color: #64748b; cursor: pointer; transition: 0.2s;\n  }\n  #btnCompOff.active { background: rgba(239, 68, 68, 0.15); color: #ef4444; font-weight: bold; }\n  #btnCompOn.active { background: rgba(34, 197, 94, 0.15); color: #22c55e; font-weight: bold; }\n\n  .compass-invert-btn {\n    display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; font-size: 11px; font-weight: 500;\n    background: #111827; color: #94a3b8; border: 1px solid rgba(148, 163, 184, 0.2); border-radius: 6px;\n    cursor: pointer; transition: all 0.2s;\n  }\n  .compass-invert-btn:hover { background: #334155; color: #fff; }\n  .compass-invert-btn.active { background: rgba(255, 68, 68, 0.15); border-color: #ff4444; color: #ff4444; }\n\n  .compass-quick-btns { display: flex; gap: 4px; margin-top: 10px; }\n  .compass-quick-btns button {\n    flex: 1; padding: 6px 0; background: #1f2937; font-size: 11px; font-weight: bold; color: #94a3b8;\n    border: 1px solid rgba(148, 163, 184, 0.2); transition: all 0.2s; border-radius: 4px;\n  }\n  .compass-quick-btns button:hover { background: #334155; color: #fff; }\n  .compass-quick-btns button.active { background: rgba(0, 191, 255, 0.15); border-color: #00bfff; color: #00bfff; }\n\n  #compassDegreeDisplay { font-size: 11px; color: #94a3b8; text-align: center; margin-top: 6px; background: #1f2937; padding: 4px; border-radius: 4px; }\n\n  #history-panel { width: 100%; }\n  #history-hint { margin: 0 0 8px; font-size: 11px; opacity: 0.65; }\n  #history-list { list-style: none; padding: 0; margin: 8px 0 0; }\n  #history-list li { \n    background: #1f2937; padding: 8px; border-radius: 6px; margin-bottom: 6px; \n    text-align: left; cursor: pointer; font-size: 13px;\n    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n  }\n  #history-list li:hover { background: #374151; }\n  #clear-history { width: 100%; background: #003366; margin-top: 10px; }\n\n  \/* === Map & Results (Mitte & Rechts) === *\/\n  #mapWrap { position: relative; width: 700px; height: 480px; border-radius: 10px; overflow: hidden; flex-shrink: 0; }\n  #map { width: 100%; height: 100%; }\n\n  #mapLoading {\n    position: absolute; inset: 0; background: rgba(0, 0, 0, 0.45);\n    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;\n    pointer-events: none; opacity: 0; transition: opacity 0.25s ease; z-index: 500;\n  }\n  #mapLoading.active { opacity: 1; pointer-events: auto; }\n  \n  #resultsCol { width: 300px; display: flex; flex-direction: column; gap: 10px; flex-shrink: 0; }\n  #results { height: 420px; overflow: auto; padding: 0; }\n  #results .header { position: sticky; top: 0; background: #020617; z-index: 1; padding: 10px 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }\n  .result-list { margin: 0; padding: 0; list-style: none; }\n  .result-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; gap: 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.06); }\n  .result-item:hover { background: #1f2937; }\n  .result-left { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px; }\n  .badge { padding: 2px 8px; border-radius: 999px; background: rgba(0, 191, 255, 0.12); border: 1px solid rgba(0, 191, 255, 0.35); color: #d7f6ff; font-size: 12px; white-space: nowrap; }\n\n  \/* Copy Button Style *\/\n  .copy-plz {\n    cursor: pointer; color: #38bdf8; position: relative; transition: color 0.2s;\n    display: inline-flex; align-items: center;\n  }\n  .copy-plz:hover { color: #bae6fd; text-decoration: underline; }\n  .copy-plz::before {\n    content: '\ud83d\udccb'; font-size: 11px; margin-right: 5px; opacity: 0.4; transition: opacity 0.2s;\n  }\n  .copy-plz:hover::before { opacity: 1; }\n  .copy-plz.copied { color: #22c55e; text-decoration: none; }\n  .copy-plz.copied::before { content: '\u2705'; opacity: 1; }\n\n  .exp-panel { display: none; margin-top: 6px; }\n  .exp-panel.open { display: block; }\n  #controls .row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 6px; font-size: 13px; }\n\n  #candidateBox {\n    position: absolute; left: 50%; transform: translateX(-50%); top: 56px; width: 380px; max-height: 300px;\n    overflow: auto; background: #222; border: 1px solid #333; border-radius: 8px; display: none; z-index: 9999; text-align: left;\n  }\n  #candidateBox.open { display: block; }\n  #candidateBox .cand { padding: 10px 12px; cursor: pointer; border-bottom: 1px solid rgba(255, 255, 255, 0.06); }\n  #candidateBox .cand:hover { background: #2b2b2b; }\n\n  @media (max-width: 1200px) {\n    #main { flex-direction: column; align-items: center; }\n    #leftCol, #resultsCol, #mapWrap { width: 100%; max-width: 800px; }\n    #leftCol { flex-direction: row; flex-wrap: wrap; }\n    .card { flex: 1; min-width: 300px; }\n  }\n\n  \/* =========================\n     INFO & FAQ FOOTER (ES)\n     ========================= *\/\n  .plzprofi-undersearch {\n    margin-top: 48px; padding: 40px 16px 40px; border-top: 1px solid rgba(148, 163, 184, 0.1);\n  }\n  .plzprofi-undersearch-head { margin: 0 0 32px 0; text-align: center; }\n  .plzprofi-undersearch-head h2 { margin: 0 0 8px 0; font-size: 28px; }\n  .plzprofi-undersearch-sub { margin: 0; color: #94a3b8; font-size: 15px; max-width: 600px; margin: 0 auto; line-height: 1.5; }\n\n  .plzprofi-undersearch-grid {\n    display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; margin-bottom: 48px; max-width: 1280px; margin-left: auto; margin-right: auto;\n  }\n  .plzprofi-undersearch-card {\n    border-radius: 16px; padding: 24px; border: 1px solid rgba(148, 163, 184, 0.15);\n    background: radial-gradient(circle at top left, rgba(59, 130, 246, 0.08), transparent 60%), #0b1220;\n    box-shadow: 0 10px 30px rgba(0,0,0,0.2); transition: transform 0.2s ease;\n  }\n  .plzprofi-undersearch-card:hover { transform: translateY(-3px); border-color: rgba(56, 189, 248, 0.3); }\n  .plzprofi-chip {\n    display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 999px;\n    font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase; background: rgba(56, 189, 248, 0.15);\n    color: #38bdf8; margin-bottom: 12px; font-weight: bold;\n  }\n  .plzprofi-undersearch-card h3 { margin: 0 0 12px 0; font-size: 18px; color: #f8fafc; }\n  .plzprofi-undersearch-card p { margin: 0; color: #cbd5e1; font-size: 14px; line-height: 1.6; }\n\n  \/* == EU L\u00c4NDER SEKTION == *\/\n  .plzprofi-eu-network {\n    background: #0b1220; border-radius: 20px; border: 1px solid rgba(148, 163, 184, 0.15);\n    padding: 32px; max-width: 1280px; margin: 0 auto 48px; text-align: center;\n  }\n  .plzprofi-eu-network h3 { margin: 0 0 12px 0; font-size: 22px; color: #ffffff; }\n  .plzprofi-eu-network p { color: #94a3b8; font-size: 15px; margin: 0 auto 24px; max-width: 600px; line-height: 1.5; }\n  \n  .eu-links-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }\n  .eu-country-btn {\n    display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px;\n    background: #1f2937; border: 1px solid rgba(148, 163, 184, 0.2); border-radius: 12px;\n    color: #e2e8f0; text-decoration: none; font-weight: 500; font-size: 14px; transition: all 0.2s ease;\n  }\n  .eu-country-btn:hover { background: #334155; border-color: #38bdf8; color: #fff; transform: translateY(-2px); }\n  .eu-country-btn.highlight { background: rgba(37, 99, 235, 0.2); border-color: rgba(37, 99, 235, 0.5); color: #60a5fa; }\n  .eu-country-btn.highlight:hover { background: rgba(37, 99, 235, 0.4); color: #fff; }\n\n  \/* == FAQ Sektion == *\/\n  .plzprofi-faq-section { max-width: 1280px; margin: 0 auto; background: transparent; }\n  .plzprofi-faq-section h3 { margin: 0 0 24px 0; font-size: 22px; text-align: center; }\n  .plzprofi-faq-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 16px; margin-bottom: 32px; }\n  \n  .plzprofi-faq-item {\n    border-radius: 12px; border: 1px solid rgba(148, 163, 184, 0.15); background: #0b1220; padding: 20px; transition: background 0.2s;\n  }\n  .plzprofi-faq-item:hover { background: #111827; }\n  .plzprofi-faq-item summary { cursor: pointer; font-weight: 600; list-style: none; color: #e2e8f0; font-size: 15px; position: relative; padding-right: 20px; }\n  .plzprofi-faq-item summary::-webkit-details-marker { display: none; }\n  .plzprofi-faq-item summary::after { content: \"\u25bc\"; position: absolute; right: 0; top: 0; font-size: 12px; color: #64748b; }\n  .plzprofi-faq-item[open] summary::after { content: \"\u25b2\"; color: #38bdf8; }\n  .plzprofi-faq-body { margin-top: 12px; color: #94a3b8; font-size: 14px; line-height: 1.6; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 12px; }\n  .faq-cta-wrap { text-align: center; }\n<\/style>\n\n<div class=\"plzprofi-hero\">\n  <div class=\"plzprofi-hero-inner\">\n    <div class=\"plzprofi-hero-left\">\n      <span class=\"hero-kicker\">PLZ Profi \u00b7 Umkreissuche<\/span>\n      <h1>Spanien (ES) \u00b7 Umkreissuche f\u00fcr Postleitzahlen &#038; Orte<\/h1>\n      <p>Suchen Sie in Spanien nach Orten und Postleitzahlen im gew\u00fcnschten Radius. Eingaben wie <strong>28001<\/strong> oder <strong>Madrid<\/strong> werden unterst\u00fctzt \u2013 Ortsnamen d\u00fcrfen auch Akzente enthalten (z.B. <strong>M\u00e1laga<\/strong>).<\/p>\n      <a class=\"hero-btn\" href=\"#plzprofi-tool\">Jetzt Suche starten<\/a>\n    <\/div>\n\n    <div class=\"plzprofi-hero-right\">\n      <div class=\"hero-card\" aria-hidden=\"true\">\n        <div class=\"hero-tag\">Kartensuche \u00b7 ES<\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<div id=\"plzprofi-tool\" class=\"wrapper\">\n  <div class=\"panel\">\n    <div class=\"seg notranslate\" id=\"countryIndicator\" translate=\"no\">\n      <button class=\"seg-btn active notranslate\" translate=\"no\" type=\"button\" aria-label=\"Aktives Land: ES\">\n        <span class=\"flag\" aria-hidden=\"true\">\ud83c\uddea\ud83c\uddf8<\/span> ES\n      <\/button>\n    <\/div>\n\n    <div id=\"datasetBar\">\n      <label for=\"datasetUrl\">GeoJSON URL:<\/label>\n      <input type=\"text\" id=\"datasetUrl\" value=\"https:\/\/plzprofi.de\/wp-content\/uploads\/geo\/plz-es-unique.geojson\" \/>\n      <button id=\"btnReload\" type=\"button\">Neu laden<\/button>\n    <\/div>\n\n    <label for=\"searchInput\">PLZ oder Ort:<\/label>\n    <input type=\"text\" id=\"searchInput\" placeholder=\"z.B. 28001 oder Madrid\" \/>\n    <button id=\"btnCandidates\" title=\"Ortsauswahl \u00f6ffnen\" type=\"button\">Ortsauswahl \u25bc<\/button>\n\n    <label for=\"radiusInput\">Radius (1\u2013100 km):<\/label>\n    <input type=\"number\" id=\"radiusNumber\" min=\"1\" max=\"100\" value=\"50\" \/>\n    <span id=\"radiusLabel\">50<\/span> km\n    <input type=\"range\" id=\"radiusInput\" min=\"1\" max=\"100\" value=\"50\" \/>\n    <button id=\"btnSearch\" type=\"button\">Suchen<\/button>\n\n    <div id=\"candidateBox\" aria-label=\"Ortsauswahl\"><\/div>\n  <\/div>\n\n  <div id=\"main\">\n    \n    <div id=\"leftCol\">\n      <div class=\"card\" id=\"compassCard\">\n        <h3>Kompass<\/h3>\n        <div class=\"compass-module\">\n          <div class=\"compass-canvas-wrap\" id=\"compassWrap\" title=\"Klicken &#038; Ziehen zum Rotieren\">\n            <canvas id=\"compassCanvas\" width=\"64\" height=\"64\"><\/canvas>\n          <\/div>\n          <div class=\"compass-controls\">\n            <div class=\"compass-toggle-group\">\n              <button type=\"button\" class=\"compass-toggle-btn active\" id=\"btnCompOff\">\n                <svg viewBox=\"0 0 24 24\" width=\"10\" height=\"10\" stroke=\"currentColor\" stroke-width=\"2.5\" fill=\"none\"><path d=\"M18.36 6.64a9 9 0 1 1-12.73 0\"><\/path><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"12\"><\/line><\/svg>\n                Aus\n              <\/button>\n              <button type=\"button\" class=\"compass-toggle-btn\" id=\"btnCompOn\">\n                <svg viewBox=\"0 0 24 24\" width=\"10\" height=\"10\" stroke=\"currentColor\" stroke-width=\"2.5\" fill=\"none\"><path d=\"M18.36 6.64a9 9 0 1 1-12.73 0\"><\/path><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"12\"><\/line><\/svg>\n                Ein\n              <\/button>\n            <\/div>\n            <button type=\"button\" class=\"compass-invert-btn\" id=\"btnCompInvert\">\n              <span>\ud83d\udd04<\/span> Umkehren\n            <\/button>\n          <\/div>\n        <\/div>\n        <div id=\"compassDegreeDisplay\">Winkel: 0\u00b0 (Norden)<\/div>\n        <div class=\"compass-quick-btns\">\n          <button type=\"button\" data-dir=\"0\">N<\/button>\n          <button type=\"button\" data-dir=\"45\">NO<\/button>\n          <button type=\"button\" data-dir=\"90\">O<\/button>\n          <button type=\"button\" data-dir=\"135\">SO<\/button>\n        <\/div>\n        <div class=\"compass-quick-btns\" style=\"margin-top:4px;\">\n          <button type=\"button\" data-dir=\"180\">S<\/button>\n          <button type=\"button\" data-dir=\"225\">SW<\/button>\n          <button type=\"button\" data-dir=\"270\">W<\/button>\n          <button type=\"button\" data-dir=\"315\">NW<\/button>\n        <\/div>\n      <\/div>\n\n      <div id=\"history-panel\" class=\"card\">\n        <h3>Suchverlauf<\/h3>\n        <div id=\"history-hint\">Wird lokal im Browser gespeichert<\/div>\n        <ul id=\"history-list\"><\/ul>\n        <button id=\"clear-history\">Verlauf l\u00f6schen<\/button>\n      <\/div>\n    <\/div>\n\n    <div id=\"mapWrap\">\n      <div id=\"map\"><\/div>\n      <div id=\"mapLoading\">\n        <span style=\"color:white; font-size:14px; background: rgba(0,0,0,0.8); padding: 8px 16px; border-radius: 8px;\">Kartendaten laden&#8230;<\/span>\n      <\/div>\n    <\/div>\n\n    <div id=\"resultsCol\">\n      <div id=\"controls\" class=\"card\">\n        <div class=\"row\">\n          <label for=\"sortSelect\">Sortierung:<\/label>\n          <select id=\"sortSelect\" style=\"flex:1;\">\n            <option value=\"dist-asc\" selected>Distanz \u2191<\/option>\n            <option value=\"plz-asc\">PLZ \u2191<\/option>\n            <option value=\"name-asc\">Ort A\u2013Z<\/option>\n          <\/select>\n        <\/div>\n        <div class=\"row\">\n          <label for=\"limitSelect\">Umfang:<\/label>\n          <select id=\"limitSelect\" style=\"flex:1;\">\n            <option value=\"top10\" selected>Top 10<\/option>\n            <option value=\"top20\">Top 20<\/option>\n            <option value=\"all\">Alle<\/option>\n          <\/select>\n        <\/div>\n        <div class=\"row exp-wrap\" style=\"margin-top: 10px;\">\n          <button class=\"exp-toggle\" id=\"expToggle\" style=\"width:100%\">Export \u25bc<\/button>\n          <div class=\"exp-panel\" id=\"expPanel\">\n            <div class=\"row\" style=\"margin-top:4px; justify-content: center;\">\n              <button id=\"btnExportCsv\">CSV<\/button>\n              <button id=\"btnExportXlsx\">XLSX<\/button>\n              <button id=\"btnExportPdf\">PDF<\/button>\n              <button id=\"btnExportTxt\">TXT<\/button>\n            <\/div>\n            <div style=\"font-size:11px; opacity:0.6; margin-top:6px; text-align:center;\">\u2022 Exportiert die sichtbare Liste<\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"results\" class=\"card\">\n        <div class=\"header\">\n          <div id=\"resTitle\"><strong>Ergebnisse<\/strong><\/div>\n          <div id=\"resMeta\" style=\"font-size: 12px; opacity: 0.8;\">Bitte Suche ausf\u00fchren.<\/div>\n        <\/div>\n        <ul class=\"result-list\" id=\"resList\"><\/ul>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <section class=\"plzprofi-undersearch\">\n    <header class=\"plzprofi-undersearch-head\">\n      <h2>Kurzinfos f\u00fcr die Spanien-Disposition<\/h2>\n      <p class=\"plzprofi-undersearch-sub\">Die wichtigsten Eckdaten zum PLZ-System und der Tourenplanung auf der Iberischen Halbinsel.<\/p>\n    <\/header>\n\n    <div class=\"plzprofi-undersearch-grid\">\n      <article class=\"plzprofi-undersearch-card\">\n        <span class=\"plzprofi-chip\">Format<\/span>\n        <h3>Das ES PLZ-System<\/h3>\n        <p>In Spanien ist das Standard-Format f\u00fcr Postleitzahlen exakt <strong>5-stellig<\/strong> (z.B. 28001). Die ersten zwei Ziffern stehen typischerweise f\u00fcr die Region bzw. Provinz. Unsere Suche ist robust genug, um auch Ortsnamen mit Akzenten (z.B. M\u00e1laga) korrekt zuzuordnen.<\/p>\n      <\/article>\n\n      <article class=\"plzprofi-undersearch-card\">\n        <span class=\"plzprofi-chip\">Transport<\/span>\n        <h3>Disposition &#038; Achsen<\/h3>\n        <p>Spanien hat enorme Logistik-Knotenpunkte. Besonders um <strong>Madrid, Barcelona und Valencia<\/strong> konzentrieren sich gro\u00dfe Verteilerzentren und Seeh\u00e4fen. F\u00fcr die schnelle Disposition hilft unsere Luftlinien-Radiusplanung, um Beiladungen im Umkreis der Hubs zu finden.<\/p>\n      <\/article>\n\n      <article class=\"plzprofi-undersearch-card\">\n        <span class=\"plzprofi-chip\">Workflow<\/span>\n        <h3>Schnell &#038; Pr\u00e4zise arbeiten<\/h3>\n        <p>Nutze die Kompass-Funktion, um deine Einzugsgebiete exakt einzugrenzen (z.B. nur Richtung Mittelmeerk\u00fcste). Die <strong>1-Klick-Kopie<\/strong> packt die PLZ direkt in deine Zwischenablage \u2013 ideal zum sofortigen Einf\u00fcgen in Timocom, Wtransnet oder dein TMS.<\/p>\n      <\/article>\n    <\/div>\n\n    <div class=\"plzprofi-eu-network\">\n      <h3>PLZ-Suche f\u00fcr ganz Europa<\/h3>\n      <p>Internationale Touren erfordern verl\u00e4ssliche Daten \u00fcber die Grenzen hinaus. Greife direkt auf unsere anderen L\u00e4ndersuchen zu.<\/p>\n      <div class=\"eu-links-grid\">\n        <a href=\"https:\/\/plzprofi.de\/\" class=\"eu-country-btn\"><span class=\"flag\">\ud83c\udde9\ud83c\uddea<\/span> Deutschland \/ AT<\/a>\n        <a href=\"https:\/\/plzprofi.de\/eu-laender\/cz\/\" class=\"eu-country-btn\"><span class=\"flag\">\ud83c\udde8\ud83c\uddff<\/span> Tschechien<\/a>\n        <a href=\"\/eu-laender\/\" class=\"eu-country-btn highlight\">\ud83c\udf0d Alle EU-L\u00e4nder ansehen<\/a>\n      <\/div>\n    <\/div>\n\n    <div class=\"plzprofi-faq-section\">\n      <h3>H\u00e4ufig gestellte Fragen<\/h3>\n      <div class=\"plzprofi-faq-grid\">\n        <details class=\"plzprofi-faq-item\">\n          <summary>Warum klappt die Suche nach &#8222;Malaga&#8220; auch ohne Akzent?<\/summary>\n          <div class=\"plzprofi-faq-body\">Unsere Such-Engine normalisiert deine Eingaben. Du musst also keine spanischen Akzente eintippen. Angezeigt wird in den Ergebnissen aber stets die amtlich korrekte Originalschreibweise.<\/div>\n        <\/details>\n\n        <details class=\"plzprofi-faq-item\">\n          <summary>Warum verschwinden die Marker, wenn ich &#8222;Alle&#8220; anw\u00e4hle?<\/summary>\n          <div class=\"plzprofi-faq-body\">Das ist eine Schutzfunktion. Wenn du 100 km Radius abfragst und auf &#8222;Alle&#8220; klickst, w\u00fcrden Hunderte Marker die Karte unleserlich machen. Die Karte zeigt dann nur den Startpunkt zur Orientierung, w\u00e4hrend die Liste dir alle Treffer exakt auflistet.<\/div>\n        <\/details>\n\n        <details class=\"plzprofi-faq-item\">\n          <summary>Was bedeutet &#8222;Top 10 \/ Top 20&#8220; im Spanien-Kontext?<\/summary>\n          <div class=\"plzprofi-faq-body\">Es wird je PLZ-Pr\u00e4fix (die ersten 2 Stellen der spanischen PLZ) ein Treffer gew\u00e4hlt. Da diese Pr\u00e4fixe oft Provinzen entsprechen, erh\u00e4ltst du damit einen perfekten und aufger\u00e4umten geografischen \u00dcberblick \u00fcber das Liefergebiet.<\/div>\n        <\/details>\n      <\/div>\n\n      <div class=\"faq-cta-wrap\" style=\"margin-top: 32px;\">\n        <a href=\"\/faq\/\" class=\"hero-btn\" style=\"background: transparent; border: 1px solid rgba(56, 189, 248, 0.4); box-shadow: none; color: #38bdf8;\">\n          <span>Zur vollst\u00e4ndigen FAQ<\/span>\n        <\/a>\n      <\/div>\n    <\/div>\n  <\/section>\n<\/div>\n\n<script src=\"https:\/\/unpkg.com\/leaflet\/dist\/leaflet.js\"><\/script>\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/xlsx@0.18.5\/dist\/xlsx.full.min.js\"><\/script>\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/jspdf@2.5.1\/dist\/jspdf.umd.min.js\"><\/script>\n\n<script>\n  \/\/ === GLOBALE KOPIER FUNKTION ===\n  window.copyPLZ = function(plz, element) {\n    navigator.clipboard.writeText(plz).then(() => {\n      element.classList.add('copied');\n      setTimeout(() => element.classList.remove('copied'), 1500);\n    }).catch(err => {\n      const ta = document.createElement('textarea'); ta.value = plz;\n      document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta);\n      element.classList.add('copied'); setTimeout(() => element.classList.remove('copied'), 1500);\n    });\n  };\n\n  \/\/ ==== SETUP ====\n  const DEFAULT_VIEW = { lat: 40.463667, lon: -3.74922, zoom: 6 };\n  const datasetUrlEl = document.getElementById(\"datasetUrl\");\n\n  let map = L.map(\"map\").setView([DEFAULT_VIEW.lat, DEFAULT_VIEW.lon], DEFAULT_VIEW.zoom);\n  L.tileLayer(\"https:\/\/{s}.tile.openstreetmap.org\/{z}\/{x}\/{y}.png\", { attribution: \"\u00a9 OpenStreetMap\" }).addTo(map);\n\n  let markerLayerGroup = L.layerGroup().addTo(map);\n  let circleLayer;\n\n  const resultIcon = new L.Icon({ iconUrl: \"https:\/\/unpkg.com\/leaflet@1.7.1\/dist\/images\/marker-icon.png\", iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowUrl: \"https:\/\/unpkg.com\/leaflet@1.7.1\/dist\/images\/marker-shadow.png\", shadowSize: [41, 41] });\n  const searchIcon = new L.Icon({ iconUrl: \"https:\/\/unpkg.com\/leaflet@1.7.1\/dist\/images\/marker-icon.png\", iconSize: [33, 54], iconAnchor: [16, 54], popupAnchor: [1, -40], shadowUrl: \"https:\/\/unpkg.com\/leaflet@1.7.1\/dist\/images\/marker-shadow.png\", shadowSize: [54, 54] });\n\n  let geoData = [], geoLoaded = false, allMatches = [], viewResults = [], lastCenter = null, searchActive = false;\n\n  \/\/ === KOMPASS LOGIK ===\n  const compassCanvas = document.getElementById(\"compassCanvas\"), ctx = compassCanvas.getContext(\"2d\");\n  const compassDegreeDisplay = document.getElementById(\"compassDegreeDisplay\");\n  const quickBtns = document.querySelectorAll(\".compass-quick-btns button\");\n  const btnCompOff = document.getElementById(\"btnCompOff\"), btnCompOn = document.getElementById(\"btnCompOn\"), btnCompInvert = document.getElementById(\"btnCompInvert\");\n\n  let isCompassActive = false, isCompassInverted = false, compassAngle = 0, isDraggingCompass = false, compassConeWidth = 90;\n\n  function setCompassActive(state) {\n    isCompassActive = state;\n    if(state) { btnCompOn.classList.add('active'); btnCompOff.classList.remove('active'); } \n    else { btnCompOff.classList.add('active'); btnCompOn.classList.remove('active'); }\n    drawCompass(); updateQuickBtnUI(); if(searchActive) applySortAndRender();\n  }\n\n  btnCompOn.addEventListener('click', () => setCompassActive(true));\n  btnCompOff.addEventListener('click', () => setCompassActive(false));\n  btnCompInvert.addEventListener('click', () => {\n    isCompassInverted = !isCompassInverted; btnCompInvert.classList.toggle('active', isCompassInverted);\n    drawCompass(); if(searchActive) applySortAndRender();\n  });\n\n  function getDirectionName(deg) {\n    if (deg >= 337.5 || deg < 22.5) return \"Norden\"; if (deg >= 22.5 && deg < 67.5) return \"Nord-Ost\";\n    if (deg >= 67.5 && deg < 112.5) return \"Osten\"; if (deg >= 112.5 && deg < 157.5) return \"S\u00fcd-Ost\";\n    if (deg >= 157.5 && deg < 202.5) return \"S\u00fcden\"; if (deg >= 202.5 && deg < 247.5) return \"S\u00fcd-West\";\n    if (deg >= 247.5 && deg < 292.5) return \"Westen\"; if (deg >= 292.5 && deg < 337.5) return \"Nord-West\"; return \"\";\n  }\n\n  function drawCompass() {\n    const w = compassCanvas.width, h = compassCanvas.height, cx = w\/2, cy = h\/2, r = w\/2 - 2;\n    ctx.clearRect(0, 0, w, h); ctx.strokeStyle = \"rgba(148, 163, 184, 0.2)\"; ctx.lineWidth = 1; ctx.beginPath();\n    ctx.moveTo(cx, 0); ctx.lineTo(cx, h); ctx.moveTo(0, cy); ctx.lineTo(w, cy); ctx.stroke();\n    ctx.fillStyle = \"rgba(148, 163, 184, 0.7)\"; ctx.font = \"10px sans-serif\"; ctx.textAlign = \"center\"; ctx.textBaseline = \"middle\";\n    ctx.fillText(\"N\", cx, 8); ctx.fillText(\"S\", cx, h - 8); ctx.fillText(\"W\", 8, cy); ctx.fillText(\"O\", w - 8, cy);\n\n    if (isCompassActive) {\n      const startAngle = (compassAngle - compassConeWidth \/ 2 - 90) * Math.PI \/ 180;\n      const endAngle = (compassAngle + compassConeWidth \/ 2 - 90) * Math.PI \/ 180;\n      ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, r, startAngle, endAngle); ctx.closePath();\n      const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);\n      grad.addColorStop(0, \"rgba(0, 191, 255, 0.1)\"); grad.addColorStop(1, \"rgba(0, 191, 255, 0.6)\");\n      ctx.fillStyle = isCompassInverted ? \"rgba(255, 68, 68, 0.4)\" : grad; ctx.fill();\n      ctx.strokeStyle = isCompassInverted ? \"#ff4444\" : \"#00bfff\"; ctx.lineWidth = 2; ctx.stroke();\n    }\n    compassDegreeDisplay.textContent = isCompassActive ? `Winkel: ${Math.round(compassAngle)}\u00b0 (${getDirectionName(compassAngle)})` : \"Kompass deaktiviert\";\n  }\n\n  function updateQuickBtnUI() { quickBtns.forEach(b => b.classList.toggle('active', isCompassActive && Math.abs(compassAngle - parseInt(b.dataset.dir)) < 2)); }\n  function handleCompassInteraction(e) {\n    if (!isDraggingCompass) return;\n    const rect = compassCanvas.getBoundingClientRect(), clientX = e.touches ? e.touches[0].clientX : e.clientX, clientY = e.touches ? e.touches[0].clientY : e.clientY;\n    compassAngle = (Math.atan2(clientX - rect.left - rect.width \/ 2, -(clientY - rect.top - rect.height \/ 2)) * 180 \/ Math.PI + 360) % 360;\n    if(!isCompassActive) setCompassActive(true); else { drawCompass(); updateQuickBtnUI(); if (searchActive) applySortAndRender(); }\n  }\n\n  compassCanvas.addEventListener('mousedown', (e) => { isDraggingCompass = true; handleCompassInteraction(e); });\n  window.addEventListener('mouseup', () => { isDraggingCompass = false; });\n  compassCanvas.addEventListener('mousemove', handleCompassInteraction);\n  quickBtns.forEach(btn => { btn.addEventListener('click', (e) => { compassAngle = parseInt(e.target.dataset.dir); setCompassActive(true); }); });\n  drawCompass();\n\n  \/\/ === ES SPEZIFISCHE FUNKTIONEN ===\n  function normalizeText(str) { return (str || \"\").toString().normalize(\"NFD\").replace(\/\\p{Diacritic}\/gu, \"\").toLowerCase().trim(); }\n  function normalizePostcodeES(input) {\n    const s = String(input || \"\").trim().replace(\/[\\s-]+\/g, \"\");\n    return \/^\\d{5}$\/.test(s) ? s : null;\n  }\n  function formatPostcodeES(plz) {\n    const s = String(plz || \"\").trim().replace(\/[\\s-]+\/g, \"\");\n    return \/^\\d{5}$\/.test(s) ? s : String(plz || \"\");\n  }\n  function safeOrt(f) {\n    const raw = (f.properties.ort || f.properties.note || f.properties.name || \"\").toString().trim(), plz = (f.properties.plz || \"\").toString();\n    return raw.replace(new RegExp(\"^\" + plz + \"\\\\s*\"), \"\").trim();\n  }\n\n  \/\/ === GEOMETRIE ===\n  function getCenter(coords, type) {\n    if (type === \"Point\") return [coords[1], coords[0]];\n    if (type === \"MultiPoint\") { let la=0, lo=0; coords.forEach(c => {lo+=c[0]; la+=c[1];}); return [la\/coords.length, lo\/coords.length]; }\n    let arr = (type === \"Polygon\") ? coords[0] || [] : (type === \"MultiPolygon\" ? (coords || []).flat(1) : (coords || []).flat(2));\n    let la = 0, lo = 0; arr.forEach(c => { lo += c[0]; la += c[1]; });\n    return [la \/ (arr.length || 1), lo \/ (arr.length || 1)];\n  }\n  function haversine(lat1, lon1, lat2, lon2) {\n    const R = 6371, toRad = x => x * Math.PI \/ 180, dLat = toRad(lat2 - lat1), dLon = toRad(lon2 - lon1);\n    const a = Math.sin(dLat \/ 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon \/ 2) ** 2;\n    return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n  }\n  function getBearing(lat1, lon1, lat2, lon2) {\n    const toRad = x => x * Math.PI \/ 180, toDeg = x => x * 180 \/ Math.PI;\n    const dLon = toRad(lon2 - lon1), y = Math.sin(dLon) * Math.cos(toRad(lat2));\n    const x = Math.cos(toRad(lat1)) * Math.sin(toRad(lat2)) - Math.sin(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.cos(dLon);\n    return (toDeg(Math.atan2(y, x)) + 360) % 360;\n  }\n\n  \/\/ === LADE LOGIK ===\n  async function loadDataset(url) {\n    geoLoaded = false; geoData = []; searchActive = false; lastCenter = null; allMatches = []; viewResults = [];\n    markerLayerGroup.clearLayers(); if (circleLayer) map.removeLayer(circleLayer);\n    document.getElementById(\"resTitle\").innerHTML = \"<strong>Ergebnisse<\/strong>\";\n    document.getElementById(\"resMeta\").textContent = \"Daten werden geladen\u2026\"; document.getElementById(\"resList\").innerHTML = \"\";\n    document.getElementById(\"mapLoading\").classList.add(\"active\");\n\n    try {\n      const r = await fetch(url, { cache: \"no-store\" });\n      if (!r.ok) throw new Error(\"HTTP \" + r.status);\n      const data = await r.json();\n      let feats;\n      if (Array.isArray(data)) feats = data; else if (Array.isArray(data.features)) feats = data.features;\n      else if (data.type === \"FeatureCollection\" && Array.isArray(data.features)) feats = data.features;\n      geoData = feats || []; geoLoaded = true; document.getElementById(\"resMeta\").textContent = \"Bitte Suche ausf\u00fchren.\";\n    } catch (err) {\n      console.error(\"GeoJSON-Fehler\", err); document.getElementById(\"resMeta\").textContent = \"Fehler beim Laden.\";\n    } finally { document.getElementById(\"mapLoading\").classList.remove(\"active\"); }\n  }\n  document.getElementById(\"btnReload\").addEventListener(\"click\", () => loadDataset(datasetUrlEl.value.trim()));\n\n  \/\/ === SUCHE ===\n  const candBox = document.getElementById(\"candidateBox\"), btnCandidates = document.getElementById(\"btnCandidates\");\n  let candidatesOpen = false;\n  function toggleCandidates(open) {\n    candidatesOpen = (open !== undefined) ? open : !candidatesOpen;\n    candBox.classList.toggle(\"open\", candidatesOpen); btnCandidates.textContent = candidatesOpen ? \"Ortsauswahl \u25b2\" : \"Ortsauswahl \u25bc\";\n  }\n  document.getElementById(\"searchInput\").addEventListener(\"keydown\", e => { if (e.key === \"Enter\") document.getElementById(\"btnSearch\").click(); });\n  btnCandidates.addEventListener(\"click\", () => { renderCandidates(candidatesFor(document.getElementById(\"searchInput\").value.trim())); toggleCandidates(); });\n\n  function candidatesFor(inputRaw) {\n    const plzNorm = normalizePostcodeES(inputRaw), qText = normalizeText(inputRaw);\n    return (geoData || []).filter(f => {\n      const normOrt = normalizeText(safeOrt(f));\n      if (plzNorm) return String(f.properties.plz || \"\").replace(\/[\\s-]+\/g, \"\") === plzNorm;\n      return normOrt === qText || normOrt.split(\/[\\s,]+\/).includes(qText);\n    }).sort((a, b) => String(a.properties.plz || \"\").localeCompare(String(b.properties.plz || \"\")));\n  }\n\n  function renderCandidates(list) {\n    if (!list.length) { candBox.classList.remove(\"open\"); btnCandidates.textContent = \"Ortsauswahl \u25bc\"; return; }\n    candBox.innerHTML = list.slice(0, 80).map((c, i) => `<div class=\"cand\" data-i=\"${i}\"><strong>${formatPostcodeES(c.properties.plz)}<\/strong> ${safeOrt(c)}<\/div>`).join(\"\");\n    candBox.querySelectorAll(\".cand\").forEach(el => el.addEventListener(\"click\", () => { toggleCandidates(false); runSearch(list[parseInt(el.getAttribute(\"data-i\"), 10)], getRadius(), document.getElementById(\"searchInput\").value.trim()); }));\n  }\n\n  function bestCandidate(cands, inputRaw) {\n    const plzNorm = normalizePostcodeES(inputRaw);\n    if (plzNorm) { const hit = cands.find(f => String(f.properties.plz || \"\").replace(\/[\\s-]+\/g, \"\") === plzNorm); if (hit) return hit; }\n    const inorm = normalizeText(inputRaw), exactOrt = cands.find(f => normalizeText(safeOrt(f)) === inorm); if (exactOrt) return exactOrt;\n    const tokenHit = cands.find(f => normalizeText(safeOrt(f)).split(\/[\\s,]+\/).includes(inorm)); return tokenHit || cands[0];\n  }\n\n  function getRadius() { return Math.min(100, Math.max(1, Number(document.getElementById(\"radiusInput\").value) || 50)); }\n\n  document.getElementById(\"btnSearch\").addEventListener(\"click\", () => {\n    toggleCandidates(false); const inputRaw = document.getElementById(\"searchInput\").value.trim(), radius = getRadius();\n    if (!inputRaw || isNaN(radius)) return alert(\"Bitte g\u00fcltige PLZ oder Ortsnamen eingeben.\");\n    if (!geoLoaded) return alert(\"Kartendaten laden noch...\");\n    const cands = candidatesFor(inputRaw); if (!cands.length) return alert(\"Ort nicht gefunden.\");\n    runSearch(bestCandidate(cands, inputRaw), radius, inputRaw);\n  });\n\n  function runSearch(refFeature, radius, inputRaw) {\n    const [centerLat, centerLon] = getCenter(refFeature.geometry.coordinates, refFeature.geometry.type);\n    lastCenter = { centerLat, centerLon, ref: refFeature };\n    if (circleLayer) map.removeLayer(circleLayer);\n    circleLayer = L.circle([centerLat, centerLon], { radius: radius * 1000, color: \"#1b5e20\", fillColor: \"#81c784\", fillOpacity: 0.15, weight: 2 }).addTo(map);\n    map.fitBounds(circleLayer.getBounds(), { padding: [24, 24] });\n    recomputeMatches(radius); applySortAndRender();\n    saveSearch({ q: inputRaw, plz: refFeature.properties.plz || \"\", ort: safeOrt(refFeature), radius: radius }); searchActive = true;\n  }\n\n  function recomputeMatches(radius) {\n    if (!lastCenter) return; const { centerLat, centerLon, ref } = lastCenter, targetNameNorm = normalizeText(safeOrt(ref));\n    allMatches = (geoData || []).map(f => {\n      const [lat, lon] = getCenter(f.geometry.coordinates, f.geometry.type); if (!isFinite(lat) || !isFinite(lon)) return null;\n      const dist = haversine(centerLat, centerLon, lat, lon), bearing = getBearing(centerLat, centerLon, lat, lon);\n      const rawPlz = String(f.properties.plz || \"\").replace(\/[\\s-]+\/g, \"\");\n      return { plz: rawPlz, ort: safeOrt(f), lat, lon, dist, bearing };\n    }).filter(p => p && p.dist <= radius).filter(p => normalizeText(p.ort) !== targetNameNorm);\n  }\n\n  function limitByPrefix(arr, n) {\n    const unique = new Map();\n    for (const x of arr) { const prefix = x.plz.slice(0, 2); if (!unique.has(prefix)) unique.set(prefix, x); if (unique.size >= n) break; }\n    return Array.from(unique.values());\n  }\n\n  function applySortAndRender() {\n    if (!lastCenter) return; let filteredMatches = allMatches;\n    if (isCompassActive) {\n      filteredMatches = allMatches.filter(p => {\n        let diff = Math.abs(p.bearing - compassAngle); if (diff > 180) diff = 360 - diff;\n        return isCompassInverted ? !(diff <= (compassConeWidth \/ 2)) : (diff <= (compassConeWidth \/ 2));\n      });\n    }\n\n    const sortVal = document.getElementById(\"sortSelect\").value;\n    if(sortVal === \"dist-asc\") filteredMatches.sort((a,b) => a.dist - b.dist);\n    else if(sortVal === \"plz-asc\") filteredMatches.sort((a,b) => a.plz.localeCompare(b.plz));\n    else if(sortVal === \"name-asc\") filteredMatches.sort((a,b) => a.ort.localeCompare(b.ort));\n\n    const limitVal = document.getElementById(\"limitSelect\").value;\n    viewResults = (limitVal === \"top10\") ? limitByPrefix(filteredMatches, 10) : ((limitVal === \"top20\") ? limitByPrefix(filteredMatches, 20) : filteredMatches);\n\n    const cityName = safeOrt(lastCenter.ref) || \"\", radiusNow = getRadius();\n    document.getElementById(\"resTitle\").innerHTML = `<strong>Ort: ${formatPostcodeES(lastCenter.ref.properties?.plz)} ${cityName}<\/strong>`;\n    \n    \/\/ VISUELLE KOMPASS-WARNUNG\n    let compassBadge = \"\";\n    if (isCompassActive) {\n      if (isCompassInverted) {\n        compassBadge = ` &nbsp;|&nbsp; <span style=\"color: #ef4444; font-weight: bold;\">\ud83e\udded Kompass: Umgekehrt<\/span>`;\n      } else {\n        compassBadge = ` &nbsp;|&nbsp; <span style=\"color: #22c55e; font-weight: bold;\">\ud83e\udded Kompass aktiv<\/span>`;\n      }\n    }\n    \n    document.getElementById(\"resMeta\").innerHTML = `${viewResults.length} Treffer im Umkreis (${radiusNow} km)${compassBadge}`;\n\n    document.getElementById(\"resList\").innerHTML = viewResults.map(p => \n      `<li class=\"result-item\">\n         <div class=\"result-left\">\n           <strong class=\"copy-plz\" title=\"PLZ kopieren\" onclick=\"copyPLZ('${p.plz}', this)\">${formatPostcodeES(p.plz)}<\/strong> ${p.ort}\n         <\/div>\n         <span class=\"badge\">${p.dist.toFixed(1)} km<\/span>\n       <\/li>`\n    ).join(\"\");\n\n    markerLayerGroup.clearLayers();\n    markerLayerGroup.addLayer(L.marker([lastCenter.centerLat, lastCenter.centerLon], { icon: searchIcon }).bindPopup(`Gesuchter Ort: <strong>${formatPostcodeES(lastCenter.ref.properties.plz)}<\/strong> ${cityName}`));\n    \n    if (limitVal !== 'all') {\n      viewResults.forEach(p => markerLayerGroup.addLayer(L.marker([p.lat, p.lon], { icon: resultIcon }).bindPopup(`<strong>${formatPostcodeES(p.plz)}<\/strong> ${p.ort}`)));\n    }\n  }\n\n  \/\/ === LIVE SYNC ===\n  document.getElementById(\"radiusInput\").addEventListener(\"input\", (e) => {\n    document.getElementById(\"radiusNumber\").value = e.target.value; document.getElementById(\"radiusLabel\").innerText = e.target.value;\n    if (searchActive && circleLayer) { circleLayer.setRadius(e.target.value * 1000); recomputeMatches(e.target.value); applySortAndRender(); }\n  });\n  document.getElementById(\"radiusNumber\").addEventListener(\"input\", (e) => {\n    let val = Math.min(100, Math.max(1, Number(e.target.value) || 50));\n    document.getElementById(\"radiusInput\").value = val; document.getElementById(\"radiusLabel\").innerText = val;\n    if (searchActive && circleLayer) { circleLayer.setRadius(val * 1000); recomputeMatches(val); applySortAndRender(); }\n  });\n  document.getElementById(\"sortSelect\").addEventListener(\"change\", () => { if(searchActive) applySortAndRender(); });\n  document.getElementById(\"limitSelect\").addEventListener(\"change\", () => { if(searchActive) applySortAndRender(); });\n\n  \/\/ === HISTORY ===\n  function historyKey() { return \"plzHistory_ES\"; }\n  function saveSearch(entry) {\n    if (!entry.plz && !entry.q) return; \n    let key = historyKey(), h = JSON.parse(localStorage.getItem(key) || \"[]\");\n    h = h.filter(e => !(e.plz === entry.plz && e.radius === entry.radius)); \n    h.unshift(entry); if (h.length > 10) h = h.slice(0, 10);\n    localStorage.setItem(key, JSON.stringify(h)); renderHistory();\n  }\n  function renderHistory() {\n    const list = document.getElementById(\"history-list\"); list.innerHTML = \"\"; \n    const h = JSON.parse(localStorage.getItem(historyKey()) || \"[]\");\n    for (const e of h) {\n      const li = document.createElement(\"li\"), dispName = `${formatPostcodeES(e.plz || e.q)} ${e.ort || ''}`.trim();\n      li.textContent = `${dispName} (${e.radius} km)`; li.title = li.textContent;\n      li.onclick = () => { \n        document.getElementById(\"searchInput\").value = e.plz || e.q; document.getElementById(\"radiusInput\").value = e.radius; \n        document.getElementById(\"radiusNumber\").value = e.radius; document.getElementById(\"radiusLabel\").innerText = e.radius; \n        document.getElementById(\"btnSearch\").click(); \n      };\n      list.appendChild(li);\n    }\n  }\n  document.getElementById(\"clear-history\").addEventListener(\"click\", () => { localStorage.removeItem(historyKey()); renderHistory(); });\n\n  \/\/ === EXPORT ===\n  document.getElementById(\"expToggle\").addEventListener(\"click\", () => { document.getElementById(\"expPanel\").classList.toggle(\"open\"); });\n  function downloadBlob(blob, filename) {\n    const url = URL.createObjectURL(blob), a = document.createElement(\"a\");\n    a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000);\n  }\n  function exportMeta() {\n    const r = getRadius(), limMap = { top10: \"Top 10\", top20: \"Top 20\", all: \"Alle\" };\n    let city = \"\"; if (lastCenter && lastCenter.ref) { city = (formatPostcodeES(lastCenter.ref.properties.plz) + \" \" + safeOrt(lastCenter.ref)).trim(); }\n    return { title: `PLZ Profi ES \u00b7 Radius ${r} km \u00b7 Umfang ${limMap[document.getElementById(\"limitSelect\").value] || \"Alle\"}`, city };\n  }\n  function visibleColumns() { return { byDist: (document.getElementById(\"sortSelect\").value === \"dist-asc\"), header: (document.getElementById(\"sortSelect\").value === \"dist-asc\") ? [\"PLZ\", \"Ort\", \"Distanz_km\"] : [\"PLZ\", \"Ort\"] }; }\n\n  document.getElementById(\"btnExportCsv\").addEventListener(\"click\", () => {\n    if (!viewResults.length) return; const meta = exportMeta(), cols = visibleColumns();\n    const rows = viewResults.map(r => cols.byDist ? [formatPostcodeES(r.plz), r.ort, r.dist.toFixed(2)] : [formatPostcodeES(r.plz), r.ort]);\n    const csv = [[meta.title], meta.city ? [`Gesuchter Ort: ${meta.city}`] : [], [], cols.header, ...rows].filter(r => r.length > 0).map(r => r.map(x => String(x).replace(\/\"\/g, '\"\"')).map(x => `\"${x}\"`).join(\",\")).join(\"\\n\");\n    downloadBlob(new Blob([csv], { type: \"text\/csv;charset=utf-8;\" }), `plzprofi_ES_${Date.now()}.csv`);\n  });\n  document.getElementById(\"btnExportXlsx\").addEventListener(\"click\", () => {\n    if (!viewResults.length) return; const meta = exportMeta(), cols = visibleColumns(), sheet = [[meta.title]];\n    if (meta.city) sheet.push([`Gesuchter Ort: ${meta.city}`]); sheet.push([]); sheet.push(cols.header);\n    viewResults.forEach(r => sheet.push(cols.byDist ? [formatPostcodeES(r.plz), r.ort, Number(r.dist.toFixed(2))] : [formatPostcodeES(r.plz), r.ort]));\n    const ws = XLSX.utils.aoa_to_sheet(sheet), wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, \"Ergebnisse\"); XLSX.writeFile(wb, `plzprofi_ES_${Date.now()}.xlsx`);\n  });\n  document.getElementById(\"btnExportPdf\").addEventListener(\"click\", () => {\n    if (!viewResults.length) return; const meta = exportMeta(), cols = visibleColumns(), { jsPDF } = window.jspdf, doc = new jsPDF({ unit: \"pt\", format: \"a4\" });\n    doc.setFontSize(12); doc.text(meta.title, 40, 40); let y = 58; if (meta.city) { doc.setFontSize(11); doc.text(`Gesuchter Ort: ${meta.city}`, 40, y); y += 18; }\n    doc.setFontSize(10); doc.text(cols.byDist ? \"PLZ     Ort                               Dist(km)\" : \"PLZ     Ort\", 40, y); y += 14;\n    for (const r of viewResults) {\n      const line = cols.byDist ? `${formatPostcodeES(r.plz).padEnd(6)}  ${(r.ort || \"\").padEnd(30)}  ${r.dist.toFixed(1).padStart(7)}` : `${formatPostcodeES(r.plz).padEnd(6)}  ${(r.ort || \"\")}`;\n      doc.text(line.substring(0, 95), 40, y); y += 14; if (y > 780) { doc.addPage(); y = 40; }\n    }\n    doc.save(`plzprofi_ES_${Date.now()}.pdf`);\n  });\n  document.getElementById(\"btnExportTxt\").addEventListener(\"click\", () => {\n    if (!viewResults.length) return; const meta = exportMeta(), cols = visibleColumns();\n    const lines = [meta.title, meta.city ? `Gesuchter Ort: ${meta.city}` : \"\", \"\", cols.byDist ? \"PLZ | Ort | Distanz(km)\" : \"PLZ | Ort\", ...viewResults.map(r => cols.byDist ? `${formatPostcodeES(r.plz)} | ${r.ort} | ${r.dist.toFixed(1)}` : `${formatPostcodeES(r.plz)} | ${r.ort}`)].filter(l => l !== \"\").join(\"\\n\");\n    downloadBlob(new Blob([lines], { type: \"text\/plain;charset=utf-8;\" }), `plzprofi_ES_${Date.now()}.txt`);\n  });\n\n  \/\/ init\n  (function init() { document.getElementById(\"radiusLabel\").innerText = getRadius(); loadDataset(datasetUrlEl.value.trim() || \"plz-es-unique.geojson\"); renderHistory(); })();\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>PLZ Profi \u00b7 Umkreissuche Spanien (ES) \u00b7 Umkreissuche f\u00fcr Postleitzahlen &#038; Orte Suchen Sie in Spanien nach Orten und Postleitzahlen im gew\u00fcnschten Radius. Eingaben wie 28001 oder Madrid werden unterst\u00fctzt \u2013 Ortsnamen d\u00fcrfen auch Akzente enthalten (z.B. M\u00e1laga). Jetzt Suche starten Kartensuche \u00b7 ES \ud83c\uddea\ud83c\uddf8 ES GeoJSON URL: Neu laden PLZ oder Ort: Ortsauswahl \u25bc [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":387,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-261","page","type-page","status-publish","hentry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/pages\/261","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/comments?post=261"}],"version-history":[{"count":12,"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/pages\/261\/revisions"}],"predecessor-version":[{"id":499,"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/pages\/261\/revisions\/499"}],"up":[{"embeddable":true,"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/pages\/387"}],"wp:attachment":[{"href":"https:\/\/plzprofi.de\/es\/wp-json\/wp\/v2\/media?parent=261"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}