{"id":28037,"date":"2025-07-07T13:44:00","date_gmt":"2025-07-07T11:44:00","guid":{"rendered":"https:\/\/huset.se\/?page_id=28037"},"modified":"2025-07-12T13:37:30","modified_gmt":"2025-07-12T11:37:30","slug":"lediga_fastigheter","status":"publish","type":"page","link":"https:\/\/huset.se\/fi\/lediga_fastigheter\/","title":{"rendered":"Tyhj\u00e4t kiinteist\u00f6t"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"28037\" class=\"elementor elementor-28037\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-3fcf4f0 elementor-section-content-middle elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"3fcf4f0\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-top-column elementor-element elementor-element-7e996f3\" data-id=\"7e996f3\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-d2369b2 elementor-widget elementor-widget-text-editor\" data-id=\"d2369b2\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Interaktiiviset ty\u00f6kalut<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c60138d elementor-widget elementor-widget-heading\" data-id=\"c60138d\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Tyhj\u00e4t kiinteist\u00f6t<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-cf32b9b elementor-widget-divider--view-line elementor-widget elementor-widget-divider\" data-id=\"cf32b9b\" data-element_type=\"widget\" data-widget_type=\"divider.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-divider\">\n\t\t\t<span class=\"elementor-divider-separator\">\n\t\t\t\t\t\t<\/span>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-top-column elementor-element elementor-element-390a2a6\" data-id=\"390a2a6\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-589ea35 elementor-widget elementor-widget-image\" data-id=\"589ea35\" data-element_type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/huset.se\/wp-content\/uploads\/2023\/07\/Officebuilding2-scaled.webp\" title=\"Toimistorakennus2\" alt=\"Toimistorakennus2\" class=\"elementor-animation-grow\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-4ec5bab elementor-reverse-tablet elementor-reverse-mobile elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"4ec5bab\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-021ff1f\" data-id=\"021ff1f\" data-element_type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-6f79a8c elementor-widget elementor-widget-spacer\" data-id=\"6f79a8c\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-450a2eb elementor-widget elementor-widget-heading\" data-id=\"450a2eb\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Saumaton kokemus - suoraan verkkosivustollasi<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ee77ebd elementor-widget-divider--view-line elementor-widget elementor-widget-divider\" data-id=\"ee77ebd\" data-element_type=\"widget\" data-widget_type=\"divider.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-divider\">\n\t\t\t<span class=\"elementor-divider-separator\">\n\t\t\t\t\t\t<\/span>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-44eb803 elementor-widget elementor-widget-text-editor\" data-id=\"44eb803\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p data-start=\"232\" data-end=\"579\">Kiinteist\u00f6jen omistajille ja v\u00e4litt\u00e4jille, joilla on useita kiinteist\u00f6j\u00e4, tarjoamme nyt r\u00e4\u00e4t\u00e4l\u00f6idyn ratkaisun, jolla voidaan esitell\u00e4 esill\u00e4 olevia kiinteist\u00f6j\u00e4 tyylikk\u00e4\u00e4sti ja helppok\u00e4ytt\u00f6isesti. Uuden ominaisuutemme avulla voit helposti integroida t\u00e4ydellisen kiinteist\u00f6hakumoduulin omalle verkkosivustollesi - siihen riitt\u00e4\u00e4 vain koodin kopiointi ja liitt\u00e4minen.<\/p><p data-start=\"581\" data-end=\"806\">Vierailijat p\u00e4\u00e4sev\u00e4t suoraan k\u00e4siksi vuokrattaviin tai myyt\u00e4viin kiinteist\u00f6ihisi ja voivat suodattaa alueittain, hinnoittain, pohjapiirustusten mukaan ja paljon muuta - ja kaikki t\u00e4m\u00e4 on kehystetty ainutlaatuiseen ja k\u00e4ytt\u00e4j\u00e4yst\u00e4v\u00e4lliseen esitystapaan.<\/p><p data-start=\"808\" data-end=\"873\">Kokemuksen lis\u00e4\u00e4miseksi moduuliin on yhdistetty:<\/p><ul data-start=\"875\" data-end=\"1179\"><li data-start=\"875\" data-end=\"980\"><p data-start=\"877\" data-end=\"980\"><strong data-start=\"877\" data-end=\"899\">Digitaaliset n\u00e4yt\u00f6t<\/strong> jonka ansiosta keinottelijat voivat kokea kiinteist\u00f6n ymp\u00e4ri vuorokauden sijainnista riippumatta.<\/p><\/li><li data-start=\"981\" data-end=\"1179\"><p data-start=\"983\" data-end=\"1179\"><strong data-start=\"983\" data-end=\"1007\">Teko\u00e4lyn myyntiassistenttimme<\/strong>, joka ei ainoastaan vastaa kysymyksiin useilla kielill\u00e4, vaan my\u00f6s k\u00e4sittelee kiinnostuksenilmaisuja, opastaa keinottelijoita ja vapauttaa sinut p\u00e4ivitt\u00e4isist\u00e4 teht\u00e4vist\u00e4 - tarkasti ja hienovaraisesti.<\/p><\/li><\/ul><p data-start=\"1181\" data-end=\"1329\">Tuloksena on \u00e4lyk\u00e4s ratkaisu, joka tehostaa ty\u00f6t\u00e4, parantaa br\u00e4ndikokemusta ja vapauttaa aikaa arvokkaimpiin asiakastapaamisiin.<\/p><p data-start=\"1331\" data-end=\"1387\"><strong data-start=\"1331\" data-end=\"1387\">\u00c4l\u00e4 vain v\u00e4lit\u00e4 kiinteist\u00f6\u00e4. V\u00e4lit\u00e4 tunne.<\/strong><\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-97a6100 elementor-widget elementor-widget-heading\" data-id=\"97a6100\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Saumaton kokemus - suoraan verkkosivustollasi<\/h2>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-93cab3b elementor-reverse-tablet elementor-reverse-mobile elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"93cab3b\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-636c3cc\" data-id=\"636c3cc\" data-element_type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-34166f8 elementor-widget elementor-widget-spacer\" data-id=\"34166f8\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-55d4c8f elementor-widget elementor-widget-html\" data-id=\"55d4c8f\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Kiinteist\u00f6n kerroksen saatavuus<\/title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <!-- Leaflet.js CSS -->\n  <link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet@1.7.1\/dist\/leaflet.css\" \/>\n  <!-- MarkerCluster CSS -->\n  <link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet.markercluster@1.4.1\/dist\/MarkerCluster.css\" \/>\n  <link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet.markercluster@1.4.1\/dist\/MarkerCluster.Default.css\" \/>\n  <!-- noUiSlider CSS -->\n  <link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/nouislider@15.7.1\/dist\/nouislider.min.css\" \/>\n  <style>\n    \/* CSS Reset for property listing component to prevent style conflicts *\/\n    #property-listing-component,\n    #property-listing-component * {\n      box-sizing: border-box !important;\n      margin: 0 !important;\n      padding: 0 !important;\n      font-family: Arial, sans-serif !important;\n      line-height: normal !important;\n      text-align: left !important;\n      font-size: 16px !important;\n      color: #333 !important;\n    }\n    \n    \/* Global styles - not affecting property listing *\/\n    body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }\n    \n    \/* Property Listing App - Isolated CSS with ID-based scoping and !important flags *\/\n    #property-listing-component {\n      font-family: Arial, sans-serif !important;\n      max-width: 900px !important;\n      margin: 0 auto !important;\n      padding: 1em !important;\n      background: #fff !important;\n      border-radius: 8px !important;\n      box-shadow: 0 2px 10px rgba(0,0,0,0.1) !important;\n      \/* Reset properties that might be inherited *\/\n      color: #333 !important;\n      line-height: 1.4 !important;\n      font-size: 16px !important;\n      text-align: left !important;\n      \/* Prevent external styles from affecting layout *\/\n      width: auto !important;\n      height: auto !important;\n      min-height: 0 !important;\n      min-width: 0 !important;\n      max-height: none !important;\n      position: relative !important;\n      top: auto !important;\n      left: auto !important;\n      right: auto !important;\n      bottom: auto !important;\n      float: none !important;\n      clear: none !important;\n      z-index: auto !important;\n    }\n    \/* Map container *\/\n    #property-listing-component #map {\n      height: 400px !important;\n      max-width: 600px !important;\n      margin: 2em auto !important;\n      border-radius: 12px !important;\n      box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;\n      z-index: 1 !important;\n    }\n    \n    \/* Leaflet map specific overrides to ensure proper display within the component *\/\n    #property-listing-component .leaflet-container {\n      font-family: Arial, sans-serif !important;\n      z-index: 1 !important;\n    }\n    \n    #property-listing-component .leaflet-popup-content-wrapper {\n      border-radius: 8px !important;\n      padding: 0 !important;\n    }\n    \n    #property-listing-component .leaflet-popup-content {\n      margin: 10px 12px !important;\n      line-height: 1.5 !important;\n      font-size: 14px !important;\n    }\n    \n    #property-listing-component .leaflet-popup-content p {\n      margin: 0 0 5px 0 !important;\n      padding: 0 !important;\n      line-height: 1.4 !important;\n    }\n    \n    #property-listing-component .leaflet-popup-content b {\n      font-weight: bold !important;\n      display: inline-block !important;\n      margin-bottom: 2px !important;\n    }\n    \n    #property-listing-component .leaflet-control-zoom a {\n      color: #000 !important;\n      text-decoration: none !important;\n      text-align: center !important;\n    }\n    \n    #property-listing-component .leaflet-marker-icon {\n      z-index: 10 !important;\n    }\n    \n    #property-listing-component .marker-cluster {\n      background-clip: padding-box !important;\n      border-radius: 20px !important;\n    }\n    \n    #property-listing-component .marker-cluster div {\n      width: 30px !important;\n      height: 30px !important;\n      margin-left: 5px !important;\n      margin-top: 5px !important;\n      text-align: center !important;\n      border-radius: 15px !important;\n      font-size: 12px !important;\n      color: #fff !important;\n      line-height: 30px !important;\n    }\n    \n    \/* Button styles *\/\n    #property-listing-component .dropdown-toggle, \n    #property-listing-component .action-button {\n      background-color: #f0f0f0 !important;\n      border: 1px solid #ddd !important;\n      padding: 8px 16px !important;\n      border-radius: 4px !important;\n      cursor: pointer !important;\n      margin-top: 10px !important;\n      margin-right: 5px !important;\n      display: inline-block !important;\n      text-decoration: none !important;\n      font-size: 14px !important;\n    }\n    \n    #property-listing-component .action-button {\n      background-color: #4CAF50 !important;\n      color: white !important;\n      border: none !important;\n    }\n    \n    #property-listing-component .action-button:hover {\n      background-color: #45a049 !important;\n    }\n    \n    #property-listing-component h1 { \n      color: #2c3e50 !important; \n      font-size: 24px !important;\n      margin-bottom: 15px !important;\n    }\n    \n    \/* Filter bar *\/\n    #property-listing-component .filter-bar { \n      background: #eafffa !important; \n      padding: 1em 1em 0.5em 1em !important; \n      border-radius: 12px !important; \n      margin-bottom: 1.5em !important; \n      box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important; \n      max-width: 600px !important; \n      margin-left: auto !important; \n      margin-right: auto !important; \n    }\n    \n    #property-listing-component .filter-group { \n      display: flex !important; \n      flex-wrap: wrap !important; \n      gap: 1em !important; \n      margin-bottom: 1em !important; \n    }\n    \n    #property-listing-component .slider-container { \n      margin-bottom: 10px !important; \n      flex: 1 1 0px !important; \n      min-width: 140px !important; \n      max-width: 100% !important; \n      height: auto !important; \n      min-height: 0 !important; \n    }\n    \n    #property-listing-component .dropdown-toggle { \n      background: #3ecf8e !important; \n      color: #fff !important; \n      border: none !important; \n      border-radius: 22px !important; \n      padding: 0.5em 1.5em !important; \n      font-size: 1em !important; \n      font-weight: bold !important; \n      cursor: pointer !important; \n      margin-left: 1em !important; \n    }\n    \n    #property-listing-component .dropdown-toggle:active, \n    #property-listing-component .dropdown-toggle:focus { \n      background: #29b673 !important; \n      outline: none !important; \n    }\n    \n    #property-listing-component .dropdown-filters { \n      width: 100% !important; \n      margin-top: 2px !important; \n      animation: dropdownFade 0.2s !important; \n    }\n    \n    @keyframes dropdownFade { \n      from { opacity: 0; } \n      to { opacity: 1; } \n    }\n    \n    #property-listing-component .slider-label { \n      display: block !important; \n      margin-bottom: 3px !important; \n      font-weight: bold !important; \n      font-size: 0.9em !important; \n    }\n    \n    #property-listing-component .slider-row { \n      display: block !important; \n      width: 100% !important; \n    }\n    \n    #property-listing-component .range-slider-container { \n      position: relative !important; \n      width: 100% !important; \n      height: 36px !important; \n      min-height: 0 !important; \n      margin-bottom: 6px !important; \n    }\n    #property-listing-component .range-slider { \n      width: calc(100% - 14px) !important; \n      height: 4px !important; \n      min-height: 0 !important; \n      background: #e0e0e0 !important; \n      position: absolute !important; \n      top: 50% !important; \n      left: 7px !important; \n      transform: translateY(-50%) !important; \n      border-radius: 2px !important; \n    }\n    \n    #property-listing-component .range-slider .range-selected { \n      height: 100% !important; \n      background-color: #3ecf8e !important; \n      position: absolute !important; \n    }\n    \n    #property-listing-component .range-slider .range-handle { \n      width: 14px !important; \n      height: 14px !important; \n      background: white !important; \n      border: 2px solid #3ecf8e !important; \n      border-radius: 50% !important; \n      position: absolute !important; \n      top: 50% !important; \n      cursor: pointer !important; \n      z-index: 2 !important; \n      box-shadow: 0 1px 3px rgba(0,0,0,0.2) !important; \n      transform: translateY(-50%) !important; \n    }\n    \n    #property-listing-component .range-slider .range-handle.left { \n      z-index: 3 !important;\n    }\n    \n    #property-listing-component .range-slider .range-handle.right { \n      z-index: 4 !important;\n    }\n    \n    #property-listing-component .range-slider .range-handle.left { \n      left: -7px !important; \n    }\n    \n    #property-listing-component .range-slider .range-handle.right { \n      right: -7px !important; \n    }\n    \n    #property-listing-component .range-values { \n      display: flex !important; \n      justify-content: space-between !important; \n      width: 100% !important; \n      margin-top: 0 !important; \n      margin-bottom: 8px !important; \n      font-size: 0.8em !important; \n      color: #666 !important; \n    }\n    \n    #property-listing-component .range-values .min-value { \n      display: block !important; \n      text-align: left !important; \n    }\n    \n    #property-listing-component .range-values .max-value { \n      display: block !important; \n      text-align: right !important; \n    }\n    \n    #property-listing-component #activeFilters { \n      margin-top: 1em !important; \n    }\n    \n    #property-listing-component .chip { \n      display: inline-block !important; \n      background: #f2f2f2 !important; \n      border-radius: 16px !important; \n      padding: 0.25em 0.75em !important; \n      margin: 0 0.5em 0.5em 0 !important; \n      font-size: 0.95em !important; \n    }\n    \n    #property-listing-component .chip button { \n      background: none !important; \n      border: none !important; \n      color: #888 !important; \n      cursor: pointer !important; \n      font-size: 1em !important; \n      margin-left: 0.5em !important; \n    }\n    \n    #property-listing-component .card-grid, #property-listing-component #cardGrid {\n      display: grid !important;\n      grid-template-columns: 1fr !important;\n      gap: 1.5em !important;\n    }\n    \n    #property-listing-component .card { \n      background: #fff !important; \n      border-radius: 8px !important; \n      border: 1px solid #e0e0e0 !important;\n      box-shadow: 0 1px 3px rgba(0,0,0,0.05) !important; \n      overflow: hidden !important; \n      min-height: 180px !important; \n      display: block !important;\n    }\n    \n    #property-listing-component .card-img { \n      width: 100% !important;\n      height: 160px !important;\n      background: #f5f5f5 !important; \n      display: flex !important; \n      align-items: center !important; \n      justify-content: center !important; \n    }\n    \n    #property-listing-component .card-img img { \n      width: 100% !important; \n      height: auto !important; \n      display: block !important; \n    }\n    \n    #property-listing-component .img-placeholder { \n      width: 80px !important; \n      height: 120px !important; \n      background: #d9e5ea !important; \n      border-radius: 8px !important; \n    }\n    \n    #property-listing-component .card-content { \n      padding: 1.2em !important; \n      display: flex !important; \n      flex-direction: column !important; \n      justify-content: space-between !important; \n    }\n    \n    #property-listing-component .card-status { \n      font-size: 0.9em !important; \n      margin-bottom: 0.5em !important; \n      display: flex !important; \n      align-items: center !important; \n      gap: 0.5em !important; \n      color: #333 !important;\n      font-weight: 500 !important;\n    }\n    \n    #property-listing-component .status-text {\n      font-size: 1.25em !important;\n      font-weight: bold !important;\n      margin-right: 0.5em;\n    }\n    \n    #property-listing-component .dot { \n      display: inline-block !important; \n      width: 10px !important; \n      height: 10px !important; \n      border-radius: 50% !important; \n      margin-right: 0.5em !important; \n    }\n    \n    #property-listing-component .dot-green { \n      background: #4caf50 !important; \n    }\n    \n    #property-listing-component .dot-yellow { \n      background: #ffb300 !important; \n    }\n    \n    #property-listing-component .dot-red { \n      background: #e53935 !important; \n    }\n    \n    #property-listing-component #property-listing-component .card-label,\n#property-listing-component .card-label {\n  font-weight: bold !important;\n  color: #222 !important;\n  margin-right: 0.2em !important;\n}\n    \n    #property-listing-component .card-title { \n      font-weight: bold !important; \n      font-size: 1.1em !important; \n      margin-bottom: 0.5em !important; \n      color: #333 !important;\n    }\n    \n    #property-listing-component .card-desc { \n      font-size: 0.97em !important; \n      color: #555 !important; \n      margin-bottom: 0.5em !important; \n    }\n    \n    #property-listing-component .card-info { \n      display: grid !important; \n      grid-template-columns: 1fr 1fr !important;\n      row-gap: 0.8em !important;\n      column-gap: 1em !important;\n      font-size: 0.9em !important; \n      margin-bottom: 1.2em !important; \n      color: #333 !important;\n    }\n    \n    #property-listing-component .card-info span { \n      white-space: nowrap !important; \n      display: block !important;\n    }\n    \n    #property-listing-component .card-info b {\n      display: block !important;\n      font-weight: 500 !important;\n      margin-top: 0.2em !important;\n    }\n    \n    #property-listing-component .show-btn { \n      background: #000 !important; \n      color: #fff !important; \n      border: none !important; \n      border-radius: 4px !important; \n      padding: 0.7em 1em !important; \n      font-size: 0.9em !important; \n      cursor: pointer !important; \n      text-align: center !important;\n      width: 100% !important;\n      transition: background 0.2s !important; \n      text-decoration: none !important;\n      display: block !important;\n    }\n    \n    #property-listing-component .show-btn:hover { \n      background: #333 !important; \n    }\n    \n    #property-listing-component .no-results { \n      padding: 2em !important; \n      text-align: center !important; \n      color: #888 !important; \n    }\n    \n    \/* Card buttons and interest button styles *\/\n    #property-listing-component .card-buttons { \n      display: flex !important; \n      gap: 0.5em !important; \n      margin-top: auto !important; \n    }\n    \n    #property-listing-component .interest-btn { \n      background: #fff !important; \n      color: #000 !important; \n      border: 1px solid #000 !important; \n      border-radius: 4px !important; \n      padding: 0.7em 1em !important; \n      font-size: 0.9em !important; \n      cursor: pointer !important; \n      text-decoration: none !important;\n      text-align: center !important;\n      width: 100% !important;\n      display: block !important;\n    }\n    \n    \/* Media queries - properly scoped to component *\/\n    @media (max-width: 600px) {\n      #property-listing-component .slider-label { \n        margin-bottom: 8px !important; \n      }\n      #property-listing-component .card-grid { \n        grid-template-columns: 1fr !important; \n      }\n      #property-listing-component .card { \n        flex-direction: column !important; \n        min-height: unset !important; \n      }\n      #property-listing-component .card-img, \n      #property-listing-component .img-placeholder { \n        min-width: 100% !important; \n        max-width: 100% !important; \n      }\n      #property-listing-component .card-content { \n        padding: 1em !important; \n      }\n      #property-listing-component .filter-bar { \n        padding: 0.7em 0.5em !important; \n        max-width: 100vw !important; \n        border-radius: 0 !important; \n        background: #f8fffd !important; \n      }\n      #property-listing-component .filter-group { \n        display: block !important; \n        gap: 32px !important; \n        margin-bottom: 8px !important; \n      }\n      #property-listing-component .slider-container { \n        display: block !important; \n        width: 100% !important; \n        min-width: 0 !important; \n        flex: unset !important; \n        margin-bottom: 0 !important; \n        background: #f4fcfa !important; \n        border-radius: 10px !important; \n        padding: 12px 10px 6px 10px !important; \n        box-shadow: 0 1px 6px rgba(0,0,0,0.1) !important; \n      }\n      #property-listing-component .dropdown-toggle { \n        margin-bottom: 4px !important; \n        margin-left: 0 !important; \n        width: 100% !important; \n      }\n    }\n    \/* Custom touch area for noUiSlider handles *\/\n    .slider-row-group {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 18px 24px;\n  margin-bottom: 12px;\n}\n@media (max-width: 700px) {\n  .slider-row-group {\n    grid-template-columns: 1fr;\n  }\n}\n.slider-row-group .slider-container {\n  min-width: 0;\n  max-width: 100%;\n}\n    .noUi-touch-area {\n      width: 24px !important;\n      height: 24px !important;\n    }\n    .noUi-handle {\n      width: 24px !important;\n      height: 24px !important;\n      box-sizing: border-box !important;\n    }\n  <\/style>\n<\/head>\n<body>\n  <div id=\"property-listing-component\">\n    <div id=\"map\"><\/div> <!-- Map container -->\n    <div class=\"filter-bar\">\n    <div class=\"filter-group\">\n        <div class=\"slider-container\">\n          <label class=\"slider-label\">Objektin tyyppi<\/label>\n          <div class=\"slider-row\">\n            <select id=\"typeFilter\"><option value=\"\">Kaikki<\/option><\/select>\n          <\/div>\n        <\/div>\n      <button id=\"toggleFiltersBtn\" class=\"dropdown-toggle\">N\u00e4yt\u00e4 lis\u00e4\u00e4 suodattimia<\/button>\n      <button id=\"showOnMapBtn\" class=\"action-button\">N\u00e4yt\u00e4 kartalla<\/button>\n    <\/div>\n    <div id=\"dropdownFilters\" class=\"dropdown-filters\" style=\"display:none;\">\n      <div class=\"filter-group\">\n        <div class=\"slider-row-group\">\n          <div class=\"slider-container\">\n            <label class=\"slider-label\">Koko (neli\u00f6metrein\u00e4)<\/label>\n            <div class=\"slider-row\">\n              <div id=\"sizeSlider\"><\/div>\n              <span id=\"sizeMinValue\" class=\"min-value\">0 neli\u00f6metri\u00e4<\/span>\n              <span id=\"sizeMaxValue\" class=\"max-value\">200 neli\u00f6metri\u00e4<\/span>\n            <\/div>\n          <\/div>\n          <div class=\"slider-container\">\n            <label class=\"slider-label\">Hinta (kr)<\/label>\n            <div class=\"slider-row\">\n              <div id=\"priceSlider\"><\/div>\n              <span id=\"priceMinValue\" class=\"min-value\">0 kr<\/span>\n              <span id=\"priceMaxValue\" class=\"max-value\">15 000 000 KRUUNUA<\/span>\n            <\/div>\n          <\/div>\n          <div class=\"slider-container\">\n            <label class=\"slider-label\">Maksu (SEK)<\/label>\n            <div class=\"slider-row\">\n              <div id=\"feeSlider\"><\/div>\n              <span id=\"feeMinValue\" class=\"min-value\">0 kr<\/span>\n              <span id=\"feeMaxValue\" class=\"max-value\">10 000 KRUUNUA<\/span>\n            <\/div>\n          <\/div>\n          <div class=\"slider-container\">\n            <label class=\"slider-label\">Lattia<\/label>\n            <div class=\"slider-row\">\n              <div id=\"floorSlider\"><\/div>\n              <span id=\"floorMinValue\" class=\"min-value\">0<\/span>\n              <span id=\"floorMaxValue\" class=\"max-value\">10<\/span>\n            <\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"slider-container\">\n          <label class=\"slider-label\">Tila<\/label>\n          <div class=\"slider-row\">\n            <select id=\"statusFilter\"><option value=\"\">Kaikki<\/option><\/select>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n  <div id=\"activeFilters\"><\/div>\n  <div id=\"cardGrid\" class=\"card-grid\"><\/div>\n  <!-- Leaflet.js -->\n  <script src=\"https:\/\/unpkg.com\/leaflet@1.7.1\/dist\/leaflet.js\"><\/script>\n  <!-- MarkerCluster JS -->\n  <script src=\"https:\/\/unpkg.com\/leaflet.markercluster@1.4.1\/dist\/leaflet.markercluster.js\"><\/script>\n<!-- noUiSlider JS -->\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/nouislider@15.7.1\/dist\/nouislider.min.js\"><\/script>\n  <script>\n    \/\/ Global variable to store all property units\n    let allUnits = [];\n    \n    \/\/ Debug function to log data structure\n    function debugLogData(data, label) {\n      console.log(`DEBUG ${label}:`, data);\n      if (Array.isArray(data) && data.length > 0) {\n        console.log('Sample item keys:', Object.keys(data[0]));\n      }\n    }\n    \n    \/\/ Function to fetch property data from secure API proxy\n    function fetchPropertyData() {\n      const status = document.getElementById('skickaStatus');\n      if (status) status.textContent = 'Fetching property data...';\n      \n      \/\/ Check if we're running locally (which would cause CORS issues)\n      const isLocalEnvironment = window.location.protocol === 'file:' || \n                               window.location.hostname === 'localhost' || \n                               window.location.hostname === '127.0.0.1';\n      \n      if (isLocalEnvironment) {\n        console.log('Running in local environment - using hardcoded data to avoid CORS issues');\n        if (status) status.textContent = 'Using local data for development';\n        loadHardcodedData();\n        setTimeout(() => { if (status) status.textContent = ''; }, 3000);\n        return Promise.resolve(allUnits);\n      }\n      \n      \/\/ Use our secure API proxy instead of directly accessing the external API\n      return fetch('https:\/\/husetexklusiv.se\/digitalvisning\/ledigafastigheter\/api\/properties.php?action=getProperties', {\n        method: 'GET',\n        headers: { \n          'Content-Type': 'application\/json'\n        }\n      })\n      .then(response => {\n        if (!response.ok) {\n          throw new Error(`HTTP error! Status: ${response.status}`);\n        }\n        return response.json();\n      })\n      .then(data => {\n        console.log('Received property data:', data);\n        if (status) status.textContent = 'Property data updated!';\n        \n        \/\/ Map the received data to our format\n        if (Array.isArray(data)) {\n          allUnits = data.map(u => ({\n            id: u.objekt_nr || u.id || '',\n            title: u.objekt_nr || '',\n            status: u.Availability || '',\n            size: u.storlek || '',\n            floor: u.plan || '',\n            rooms: u.rum || '',\n            fee: u.avgift || '',\n            price: u.pris || '',\n            type: u.objekttyp || '',\n            description: '', \n            image: '',\n            map_url: u.map_url || '',\n            latitude: u.latitude || null,\n            longitude: u.longitude || null,\n            visa_objekt: u.visa_objekt || ''\n          }));\n          \n          \/\/ Update the UI with new data\n          renderFilters(allUnits);\n          renderCards();\n          renderMap(allUnits);\n          setTimeout(() => { if (status) status.textContent = ''; }, 3000);\n          return allUnits;\n        } else {\n          throw new Error('Received data is not an array');\n        }\n      })\n      .catch(error => {\n        \/\/ Check if this is a CORS error\n        const isCorsError = error.message.includes('CORS') || \n                          error.message.includes('Failed to fetch') || \n                          error.message.includes('NetworkError');\n        \n        if (isCorsError) {\n          console.warn('CORS error detected - this is expected when running locally');\n          console.warn('Using hardcoded data instead');\n          if (status) status.textContent = 'Using local data (CORS restriction)';\n        } else {\n          console.error('Error fetching property data:', error);\n          if (status) status.textContent = 'Error fetching data: ' + error.message;\n        }\n        \n        \/\/ Fall back to hardcoded data if fetch fails\n        loadHardcodedData();\n        return allUnits;\n      });\n    }\n    \n    \/\/ Initialize the page\n    window.addEventListener('DOMContentLoaded', function() {\n      \/\/ Make sure the map container is visible and has dimensions\n      const mapDiv = document.getElementById('map');\n      if (mapDiv) {\n        mapDiv.style.display = 'block';\n        mapDiv.style.height = '400px';\n        mapDiv.style.width = '100%';\n        mapDiv.style.maxWidth = '600px';\n        mapDiv.style.margin = '2em auto';\n      }\n      \n      \/\/ Only load data from NocoDB\n      fetchNocoDBData();\n\n      \/\/ Dropdown toggle logic\n      const toggleBtn = document.getElementById('toggleFiltersBtn');\n      const dropdown = document.getElementById('dropdownFilters');\n      if (toggleBtn && dropdown) {\n        toggleBtn.addEventListener('click', function() {\n          if (dropdown.style.display === 'none' || dropdown.style.display === '') {\n            dropdown.style.display = 'block';\n            toggleBtn.textContent = 'D\u00f6lj filter';\n          } else {\n            dropdown.style.display = 'none';\n            toggleBtn.textContent = 'Visa fler filter';\n          }\n        });\n      }\n    });\n    \n    \/\/ Filter configuration\n    let filters = {\n      type: '',\n      size: { min: 0, max: 200 },\n      price: { min: 0, max: 15000000 },\n      fee: { min: 0, max: 10000 },\n      floor: { min: 0, max: 10 },\n      status: ''\n    };\n    function renderFilters(units) {\n      console.log('Setting up filters with units:', units);\n      \n      \/\/ Extract numeric values from strings for comparison\n      const extractNumber = (str) => {\n        if (typeof str === 'string') {\n          \/\/ Remove all non-numeric characters except for the first decimal point\n          const numStr = str.replace(\/[^0-9.]\/g, '');\n          return parseFloat(numStr) || 0;\n        }\n        return str || 0;\n      };\n      \n      \/\/ Extract floor number from strings like \"2 av 10\"\n      const extractFloor = (str) => {\n        if (typeof str === 'string') {\n          const floorMatch = str.match(\/^(\\d+)\/);\n          if (floorMatch) {\n            return parseInt(floorMatch[1]);\n          }\n        }\n        return 0;\n      };\n      \n      \/\/ Get min and max values from the data\n      const sizeValues = units.map(u => extractNumber(u.size)).filter(v => v > 0);\n      const priceValues = units.map(u => extractNumber(u.price)).filter(v => v > 0);\n      const feeValues = units.map(u => extractNumber(u.fee)).filter(v => v > 0);\n      const floorValues = units.map(u => extractFloor(u.floor)).filter(v => v > 0);\n      \n      console.log('Extracted values:', {\n        size: sizeValues,\n        price: priceValues,\n        fee: feeValues,\n        floor: floorValues\n      });\n      \n      \/\/ Helper to show\/hide slider rows based on valid range, returns true if visible\n    function showOrHideSlider(sliderId, min, max) {\n      const slider = document.getElementById(sliderId);\n      if (!slider) return false;\n      const sliderRow = slider.closest('.slider-row');\n      if (!sliderRow) return false;\n      if (min === max || max === 0) {\n        sliderRow.style.display = 'none';\n        return false;\n      } else {\n        sliderRow.style.display = '';\n        return true;\n      }\n    }\n\n    \/\/ Update filters with actual min\/max values from data\n      \/\/ Storlek (kvm)\n      filters.size.min = 0;\n      filters.size.max = sizeValues.length > 0 ? Math.max(...sizeValues) : 0;\n      \n      document.getElementById('sizeMaxValue').textContent = filters.size.max ? filters.size.max + ' kvm' : '\u2013';\n      if (showOrHideSlider('sizeSlider', filters.size.min, filters.size.max)) {\n        initRangeSlider('sizeSlider', 'size');\n      }\n\n      \/\/ Pris (kr)\n      filters.price.min = 0;\n      filters.price.max = priceValues.length > 0 ? Math.max(...priceValues) : 0;\n      \n      document.getElementById('priceMaxValue').textContent = filters.price.max\n        ? filters.price.max.toLocaleString('sv-SE') + ' kr'\n        : '\u2013';\n      if (showOrHideSlider('priceSlider', filters.price.min, filters.price.max)) {\n        initRangeSlider('priceSlider', 'price');\n      }\n\n      \/\/ Avgift (kr)\n      filters.fee.min = 0;\n      filters.fee.max = feeValues.length > 0 ? Math.max(...feeValues) : 0;\n      \n      document.getElementById('feeMaxValue').textContent = filters.fee.max\n        ? filters.fee.max.toLocaleString('sv-SE') + ' kr'\n        : '\u2013';\n      if (showOrHideSlider('feeSlider', filters.fee.min, filters.fee.max)) {\n        initRangeSlider('feeSlider', 'fee');\n      }\n\n      \/\/ V\u00e5ning\n      filters.floor.min = 0;\n      filters.floor.max = floorValues.length > 0 ? Math.max(...floorValues) : 0;\n      \n      document.getElementById('floorMaxValue').textContent = filters.floor.max\n        ? filters.floor.max\n        : '\u2013';\n      if (showOrHideSlider('floorSlider', filters.floor.min, filters.floor.max)) {\n        initRangeSlider('floorSlider', 'floor');\n      }\n      \n      \/\/ Populate type dropdown\n      const typeSelect = document.getElementById('typeFilter');\n      const typeValues = [...new Set(units.map(u => u.type).filter(Boolean))];\n      typeSelect.innerHTML = `<option value=\"\">Alla<\/option>` + \n        typeValues.map(v => `<option value=\"${v}\">${v}<\/option>`).join('');\n\n      \/\/ Populate status dropdown\n      const statusSelect = document.getElementById('statusFilter');\n      const statusValues = [...new Set(units.map(u => u.status).filter(Boolean))];\n      statusSelect.innerHTML = `<option value=\"\">Alla<\/option>` + \n        statusValues.map(v => `<option value=\"${v}\">${v}<\/option>`).join('');\n      \n      console.log('Updated filters:', filters);\n    }\n    function renderActiveFilters() {\n      const filterNames = {\n        type: 'objekttyp', size: 'storlek', price: 'pris', fee: 'avgift', floor: 'plan', status: 'Status'\n      };\n      \n      \/\/ Format values for display in filter chips\n      function formatChipValue(key, value) {\n        if (typeof value !== 'object') {\n          if (!value) return null;\n          return value;\n        }\n        \n        \/\/ Check if min and max are at their default values\n        const defaultValues = {\n          rooms: { min: 1, max: 6 },\n          size: { min: 0, max: 200 },\n          price: { min: 0, max: 15000000 },\n          fee: { min: 0, max: 10000 },\n          floor: { min: 0, max: 10 }\n        };\n        \n        if (value.min === defaultValues[key].min && value.max === defaultValues[key].max) {\n          return null; \/\/ Don't show chip if using default range\n        }\n        \n        \/\/ Format min-max values based on filter type\n        let minFormatted, maxFormatted;\n        switch(key) {\n          case 'price':\n            minFormatted = new Intl.NumberFormat('sv-SE').format(value.min);\n            maxFormatted = new Intl.NumberFormat('sv-SE').format(value.max);\n            return `${minFormatted} - ${maxFormatted} kr`;\n          case 'fee':\n            minFormatted = new Intl.NumberFormat('sv-SE').format(value.min);\n            maxFormatted = new Intl.NumberFormat('sv-SE').format(value.max);\n            return `${minFormatted} - ${maxFormatted} kr`;\n          case 'size':\n            return `${value.min} - ${value.max} kvm`;\n          case 'rooms':\n          case 'floor':\n            return `${value.min} - ${value.max}`;\n          default:\n            return value;\n        }\n      }\n      \n      const chips = Object.entries(filters)\n        .map(([k, v]) => {\n          const formattedValue = formatChipValue(k, v);\n          if (!formattedValue) return '';\n          return `<span class=\"chip\">${filterNames[k]}: ${formattedValue} <button onclick=\"removeFilter('${k}')\">&times;<\/button><\/span>`;\n        })\n        .filter(chip => chip !== '');\n        \n      document.getElementById('activeFilters').innerHTML = chips.join(' ');\n    }\n    function removeFilter(key) {\n      \/\/ Reset filter to default values\n      if (key === 'status') {\n        filters[key] = '';\n        document.getElementById(key + 'Filter').value = '';\n      } else {\n        \/\/ Reset to default min\/max values\n        const defaultValues = {\n          rooms: { min: 1, max: 6 },\n          size: { min: 0, max: 200 },\n          price: { min: 0, max: 15000000 },\n          fee: { min: 0, max: 10000 },\n          floor: { min: 0, max: 10 }\n        };\n        \n        filters[key] = { ...defaultValues[key] };\n        \n        \/\/ Reset slider positions\n        const slider = document.getElementById(key + 'Slider');\n        if (slider) {\n          const leftHandle = slider.querySelector('.range-handle.left');\n          const rightHandle = slider.querySelector('.range-handle.right');\n          const rangeSelected = slider.querySelector('.range-selected');\n          \n          if (leftHandle && rightHandle && rangeSelected) {\n            leftHandle.style.left = '0%';\n            rightHandle.style.left = '100%';\n            rangeSelected.style.left = '0%';\n            rangeSelected.style.width = '100%';\n            \n            leftHandle.setAttribute('data-value', defaultValues[key].min);\n            rightHandle.setAttribute('data-value', defaultValues[key].max);\n            \n            \/\/ Update min\/max value displays\n            document.getElementById(key + 'MinValue').textContent = formatSliderValue(key, defaultValues[key].min, 'min');\n            document.getElementById(key + 'MaxValue').textContent = formatSliderValue(key, defaultValues[key].max, 'max');\n          }\n        }\n      }\n      renderActiveFilters();\n      renderCards();\n    }\n    \/\/ Track if we're in a debounced operation to control logging\n    let isDebouncing = false;\n    \n    \/\/ Helper function to ensure URLs have proper protocol\n    function ensureAbsoluteUrl(url) {\n      if (!url) return '';\n      \n      \/\/ Extract just the www.example.com part if the URL contains it\n      if (url.includes('www.') && !url.startsWith('www.') && !url.startsWith('http')) {\n        url = url.replace(\/.*?(www\\.[^\\s\/]+)\/, '$1');\n      }\n      \n      \/\/ Add https:\/\/ to URLs starting with www.\n      if (url.startsWith('www.')) {\n        return 'https:\/\/' + url;\n      }\n      \n      \/\/ If URL already has http:\/\/ or https:\/\/, return as is\n      if (url.startsWith('http:\/\/') || url.startsWith('https:\/\/')) {\n        return url;\n      }\n      \n      \/\/ Otherwise, add https:\/\/ as default protocol\n      return 'https:\/\/' + url;\n    }\n    \n    function filterUnits(silent = false) {\n      \/\/ Only log if not in silent mode\n      if (!silent) {\n        console.log('Filtering with criteria:', {\n          type: filters.type || 'Alla',\n          size: `${filters.size.min}-${filters.size.max}`,\n          price: `${filters.price.min}-${filters.price.max}`,\n          fee: `${filters.fee.min}-${filters.fee.max}`,\n          floor: `${filters.floor.min}-${filters.floor.max}`,\n          status: filters.status || 'Alla'\n        });\n      }\n      \n      const filteredUnits = allUnits.filter(u => {\n        \/\/ Extract numeric values from strings for comparison\n        const extractNumber = (str) => {\n          if (typeof str === 'string') {\n            \/\/ Remove all non-numeric characters except for the first decimal point\n            const numStr = str.replace(\/[^0-9.]\/g, '');\n            return parseFloat(numStr) || 0;\n          }\n          return str || 0;\n        };\n        \n        \/\/ Get numeric values for comparison\n        const sizeValue = extractNumber(u.size);\n        const priceValue = extractNumber(u.price);\n        const feeValue = extractNumber(u.fee);\n        \n        \/\/ Extract floor number from strings like \"2 av 10\"\n        let floorValue = 0;\n        if (typeof u.floor === 'string') {\n          const floorMatch = u.floor.match(\/^(\\d+)\/);\n          if (floorMatch) {\n            floorValue = parseInt(floorMatch[1]);\n          }\n        }\n        \n        \/\/ Check if values are within the min-max ranges\n        const typeMatch = !filters.type || u.type === filters.type;\n        \/\/ Allow empty or 0 values to pass if filter is at its default range\n        const sizeMatch = (u.size === '' || sizeValue === 0) ? filters.size.min === 0 : (sizeValue >= filters.size.min && sizeValue <= filters.size.max);\n        const priceMatch = (u.price === '' || priceValue === 0) ? filters.price.min === 0 : (priceValue >= filters.price.min && priceValue <= filters.price.max);\n        const feeMatch = (u.fee === '' || feeValue === 0) ? filters.fee.min === 0 : (feeValue >= filters.fee.min && feeValue <= filters.fee.max);\n        const floorMatch = (u.floor === '' || floorValue === 0) ? filters.floor.min === 0 : (floorValue >= filters.floor.min && floorValue <= filters.floor.max);\n        const statusMatch = !filters.status || u.status === filters.status;\n        \n        return typeMatch && sizeMatch && priceMatch && feeMatch && floorMatch && statusMatch;\n      });\n      \n      return filteredUnits;\n    }\n    function renderCards(silent = false) {\n      const filteredUnits = filterUnits(silent);\n      const grid = document.getElementById('cardGrid');\n      if (filteredUnits.length === 0) {\n        grid.innerHTML = '<div class=\"no-results\">Inga bost\u00e4der matchar filtren.<\/div>';\n        return;\n      }\n      grid.innerHTML = filteredUnits.map(unit => {\n        return `\n        <div class=\"card\">\n          <div class=\"card-content\">\n            <div class=\"card-status\">\n  <span class=\"dot ${unit.status && unit.status.toLowerCase() === 's\u00e5ld' ? 'dot-red' : unit.status && unit.status.toLowerCase() === 'reserverad' ? 'dot-yellow' : 'dot-green'}\"><\/span>\n  <span class=\"status-text\">${unit.status || 'Ledig'}<\/span>\n  <span style=\"margin-left: 1.5em;\">Objekt nr: <b>${unit.id || unit.title || ''}<\/b><\/span>\n<\/div>\n            <div class=\"card-info\">\n              <span><span class=\"card-label\">Storlek<\/span> ${unit.size || ''} m\u00b2<\/span>\n              <span><span class=\"card-label\">Plan<\/span> ${unit.floor || ''}<\/span>\n              <span><span class=\"card-label\">Rum<\/span> ${unit.rooms || ''}<\/span>\n              <span><span class=\"card-label\">Avgift<\/span> ${unit.fee ? unit.fee.toString().replace(\/\\s*kr\\s*$\/i, '').trim() + ' kr' : ''}<\/span>\n              <span><span class=\"card-label\">Pris<\/span> ${unit.price ? unit.price.toString().replace(\/\\s*kr\\s*$\/i, '').trim() + ' kr' : ''}<\/span>\n              <span><span class=\"card-label\">Bostadstyp<\/span> ${unit.type || 'L\u00e4genhet'}<\/span>\n            <\/div>\n            <div class=\"card-buttons\">\n              <button class=\"interest-btn\">Anm\u00e4l intresse<\/button>\n              ${unit.viewUrl ? `<a href=\"${ensureAbsoluteUrl(unit.viewUrl)}\" class=\"show-btn\">Visa bostad<\/a>` : '<button class=\"show-btn\">Visa bostad<\/button>'}\n            <\/div>\n          <\/div>\n        <\/div>\n        `;\n      }).join('');\n    }\n    \/\/ Format slider values based on filter type\n    function formatSliderValue(key, value, position) {\n      if (value === 0 && position === 'min') return 'Min';\n      \n      switch(key) {\n        case 'price':\n          return new Intl.NumberFormat('sv-SE').format(value) + (position === 'max' ? ' kr' : '');\n        case 'fee':\n          return new Intl.NumberFormat('sv-SE').format(value) + (position === 'max' ? ' kr' : '');\n        case 'size':\n          return value + (position === 'max' ? ' kvm' : '');\n        default:\n          return value;\n      }\n    }\n    \n    \/\/ Debounce function to limit how often a function is called\n    function debounce(func, wait) {\n      let timeout;\n      return function(...args) {\n        const context = this;\n        clearTimeout(timeout);\n        timeout = setTimeout(() => func.apply(context, args), wait);\n      };\n    }\n    \n    \/\/ Initialize range sliders\n    function initRangeSlider(sliderId, key) {\n      if (filters[key].min === filters[key].max || filters[key].max === 0) {\n        \/\/ Don't initialize slider if no valid range\n        return;\n      }\n      const slider = document.getElementById(sliderId);\n      if (!slider) return;\n\n      const leftHandle = slider.querySelector('.range-handle.left');\n      const rightHandle = slider.querySelector('.range-handle.right');\n      const rangeSelected = slider.querySelector('.range-selected');\n      const minValueDisplay = document.getElementById(key + 'MinValue');\n      const maxValueDisplay = document.getElementById(key + 'MaxValue');\n\n      if (!leftHandle || !rightHandle || !rangeSelected) return;\n\n      const minValue = filters[key].min;\n      const maxValue = filters[key].max;\n      const valueRange = maxValue - minValue;\n\n      \/\/ Initial positions (as fractions 0-1)\n      let minPos = (filters[key].min - minValue) \/ valueRange;\n      let maxPos = (filters[key].max - minValue) \/ valueRange;\n\n      function updateHandles() {\n        leftHandle.style.left = (minPos * 100) + '%';\n        rightHandle.style.left = (maxPos * 100) + '%';\n        rangeSelected.style.left = (minPos * 100) + '%';\n        rangeSelected.style.width = ((maxPos - minPos) * 100) + '%';\n        const minVal = Math.round(minValue + minPos * valueRange);\n        const maxVal = Math.round(minValue + maxPos * valueRange);\n        if (minValueDisplay) minValueDisplay.textContent = formatSliderValue(key, minVal, 'min');\n        if (maxValueDisplay) maxValueDisplay.textContent = formatSliderValue(key, maxVal, 'max');\n        filters[key].min = minVal;\n        filters[key].max = maxVal;\n      }\n\n      let activeHandle = null;\n      let isDragging = false;\n      function startDrag(e, handle) {\n        e.preventDefault();\n        activeHandle = handle;\n        isDragging = true;\n        document.addEventListener('mousemove', drag);\n        document.addEventListener('mouseup', stopDrag);\n        document.addEventListener('touchmove', drag);\n        document.addEventListener('touchend', stopDrag);\n      }\n      function drag(e) {\n        if (!activeHandle) return;\n        const clientX = e.touches ? e.touches[0].clientX : e.clientX;\n        const sliderRect = slider.getBoundingClientRect();\n        let position = (clientX - sliderRect.left) \/ sliderRect.width;\n        position = Math.max(0, Math.min(1, position));\n        if (activeHandle === leftHandle) {\n          minPos = Math.min(position, maxPos - 0.01); \/\/ Prevent overlap\n          filters[key].min = Math.round(minValue + minPos * valueRange);\n        } else if (activeHandle === rightHandle) {\n          maxPos = Math.max(position, minPos + 0.01);\n          filters[key].max = Math.round(minValue + maxPos * valueRange);\n        }\n        \/\/ Recalculate positions from updated values to ensure accurate handle placement\n        minPos = (filters[key].min - minValue) \/ valueRange;\n        maxPos = (filters[key].max - minValue) \/ valueRange;\n        updateHandles();\n        renderActiveFilters();\n        renderCards(true);\n      }\n      function stopDrag() {\n        if (isDragging) {\n          renderActiveFilters();\n          renderCards(false);\n          isDragging = false;\n        }\n        \n        activeHandle = null;\n        document.removeEventListener('mousemove', drag);\n        document.removeEventListener('mouseup', stopDrag);\n        document.removeEventListener('touchmove', drag);\n        document.removeEventListener('touchend', stopDrag);\n      }\n      \n      \/\/ Add event listeners\n      leftHandle.addEventListener('mousedown', e => startDrag(e, leftHandle));\n      leftHandle.addEventListener('touchstart', e => startDrag(e, leftHandle));\n      rightHandle.addEventListener('mousedown', e => startDrag(e, rightHandle));\n      rightHandle.addEventListener('touchstart', e => startDrag(e, rightHandle));\n    }\n    \n    \/\/ Initialize noUiSlider for all filters\n    function initNoUiSlider(sliderId, key, unit, step) {\n      const slider = document.getElementById(sliderId);\n      if (!slider) return;\n      \/\/ Remove previous slider instance if any\n      if (slider.noUiSlider) slider.noUiSlider.destroy();\n      noUiSlider.create(slider, {\n        start: [filters[key].min, filters[key].max],\n        connect: true,\n        range: {\n          min: filters[key].min,\n          max: filters[key].max\n        },\n        step: step,\n        tooltips: false,\n        format: {\n          to: value => Math.round(value),\n          from: value => Number(value)\n        }\n      });\n      slider.noUiSlider.on('update', function(values, handle) {\n        document.getElementById(key + 'MinValue').textContent = values[0] + ' ' + unit;\n        document.getElementById(key + 'MaxValue').textContent = values[1] + ' ' + unit;\n        filters[key].min = Number(values[0]);\n        filters[key].max = Number(values[1]);\n        renderActiveFilters();\n        renderCards(true);\n      });\n    }\n    \/\/ Call for each slider\n    initNoUiSlider('sizeSlider', 'size', 'kvm', 1);\n    initNoUiSlider('priceSlider', 'price', 'kr', 1000);\n    initNoUiSlider('feeSlider', 'fee', 'kr', 100);\n    initNoUiSlider('floorSlider', 'floor', '', 1);\n\n    \n    \/\/ Function to show a temporary notification\n    function showNotification(message, duration = 3000) {\n      \/\/ Create notification element if it doesn't exist\n      let notification = document.getElementById('notification');\n      if (!notification) {\n        notification = document.createElement('div');\n        notification.id = 'notification';\n        notification.style.position = 'fixed';\n        notification.style.bottom = '20px';\n        notification.style.right = '20px';\n        notification.style.backgroundColor = '#4CAF50';\n        notification.style.color = 'white';\n        notification.style.padding = '10px 20px';\n        notification.style.borderRadius = '5px';\n        notification.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';\n        notification.style.zIndex = '1000';\n        notification.style.transition = 'opacity 0.3s';\n        document.body.appendChild(notification);\n      }\n      \n      \/\/ Set message and show notification\n      notification.textContent = message;\n      notification.style.opacity = '1';\n      \n      \/\/ Hide after duration\n      setTimeout(() => {\n        notification.style.opacity = '0';\n      }, duration);\n    }\n    \n    \/\/ Function to focus the map on filtered properties\n    function focusMapOnFilteredProperties() {\n      \/\/ Get the filtered units based on current filters\n      const filteredUnits = filterUnits();\n      \n      \/\/ Show a message if no properties match the filters\n      if (filteredUnits.length === 0) {\n        alert('Inga bost\u00e4der matchar de valda filtren.');\n        return;\n      }\n      \n      \/\/ Update button text to show loading state\n      const mapButton = document.getElementById('showOnMapBtn');\n      const originalText = mapButton.textContent;\n      mapButton.textContent = 'Uppdaterar karta...';\n      mapButton.disabled = true;\n      \n      \/\/ Scroll to the map section\n      document.getElementById('map').scrollIntoView({ behavior: 'smooth' });\n      \n      \/\/ Log all filtered units to debug\n      console.log('Filtered units before map validation:', filteredUnits);\n      \n      \/\/ Debug log each unit's map_url\n      filteredUnits.forEach(unit => {\n        console.log(`Unit ${unit.id || unit.objekt_nr || unit.title} map_url: ${unit.map_url || 'none'}`);\n      });\n      \n      \/\/ Only use units that have map_url or valid coordinates\n      const validUnits = filteredUnits.filter(unit => {\n        \/\/ Check if unit has map_url\n        if (unit.map_url && unit.map_url.trim() !== '') {\n          console.log(`Unit ${unit.id} has valid map_url: ${unit.map_url}`);\n          return true;\n        }\n        \/\/ Check if unit has valid direct coordinates\n        if (unit.latitude && unit.longitude && \n            !isNaN(parseFloat(unit.latitude)) && !isNaN(parseFloat(unit.longitude))) {\n          console.log(`Unit ${unit.id} has valid direct coordinates: ${unit.latitude}, ${unit.longitude}`);\n          return true;\n        }\n        console.log(`Unit ${unit.id} has no valid location data`);\n        return false;\n      });\n      \n      \/\/ Show a message if no properties have valid locations\n      if (validUnits.length === 0) {\n        alert('Inga bost\u00e4der med giltiga platskoordinater hittades.');\n        mapButton.textContent = originalText;\n        mapButton.disabled = false;\n        return;\n      }\n      \n      \/\/ Render the map with only units that have valid locations\n      renderMap(validUnits);\n      \n      \/\/ Show a message with the number of properties shown on the map\n      console.log(`Visar ${validUnits.length} bost\u00e4der p\u00e5 kartan`);\n      showNotification(`Visar ${validUnits.length} bost\u00e4der p\u00e5 kartan`);\n      \n      \/\/ Reset button after a short delay\n      setTimeout(() => {\n        mapButton.textContent = originalText;\n        mapButton.disabled = false;\n      }, 1000);\n    }\n    \n    \/\/ Add event listener for the 'Visa p\u00e5 kartan' button\n    document.getElementById('showOnMapBtn').addEventListener('click', focusMapOnFilteredProperties);\n    \n    \/\/ Global variable to store the map instance\n    let mapInstance = null;\n    \n    \/\/ Function to render map with property data\n    function renderMap(units) {\n      \/\/ Show loading state for map\n      const mapDiv = document.getElementById('map');\n      if (!mapDiv) return;\n      \n      mapDiv.innerHTML = '<div style=\"text-align:center;padding:2em;\">Laddar karta...<\/div>';\n      \n      \/\/ Make sure the map div is visible and has dimensions before initializing Leaflet\n      mapDiv.style.display = 'block';\n      mapDiv.style.visibility = 'visible';\n      \n      \/\/ If we already have a map instance, remove it to prevent memory leaks\n      if (mapInstance) {\n        mapInstance.remove();\n        mapInstance = null;\n      }\n      \n      \/\/ Helper function to extract coordinates from Google\/OpenStreetMap URLs\n      function extractCoordsFromUrl(url) {\n        if (!url) return null;\n        \n        console.log('Extracting coordinates from URL:', url);\n        \n        try {\n          \/\/ Check for place URLs with explicit coordinates in the URL path\n          \/\/ Format: \/place\/Name\/@lat,lng,zoom\n          let match = url.match(\/\\\/place\\\/.*?@(-?\\d+\\.\\d+),(-?\\d+\\.\\d+)\/);\n          if (match) {\n            console.log(`Found place format coordinates: ${match[1]}, ${match[2]}`);\n            return { lat: parseFloat(match[1]), lng: parseFloat(match[2]) };\n          }\n          \n          \/\/ Look for coordinates in Google Maps URLs with format !8m2!3d[LAT]!4d[LNG]\n          \/\/ This format is commonly used in Google Maps share URLs\n          match = url.match(\/!3d(-?\\d+\\.\\d+)!4d(-?\\d+\\.\\d+)\/);\n          if (match) {\n            console.log(`Found Google Maps format coordinates: ${match[1]}, ${match[2]}`);\n            return { lat: parseFloat(match[1]), lng: parseFloat(match[2]) };\n          }\n          \n          \/\/ Try to match coordinates in the format @lat,lng,zoom\n          match = url.match(\/\\\/@(-?\\d+\\.\\d+),(-?\\d+\\.\\d+)\/);\n          if (match) {\n            console.log(`Found @ format coordinates: ${match[1]}, ${match[2]}`);\n            return { lat: parseFloat(match[1]), lng: parseFloat(match[2]) };\n          }\n          \n          \/\/ Try to match coordinates in the format ?q=lat,lng\n          match = url.match(\/\\?q=(-?\\d+\\.\\d+),(-?\\d+\\.\\d+)\/);\n          if (match) {\n            console.log(`Found ?q= format coordinates: ${match[1]}, ${match[2]}`);\n            return { lat: parseFloat(match[1]), lng: parseFloat(match[2]) };\n          }\n          \n          \/\/ Try to match coordinates in the format lat=X&lon=Y\n          match = url.match(\/lat=(-?\\d+\\.\\d+).*?lon=(-?\\d+\\.\\d+)\/);\n          if (match) {\n            console.log(`Found lat\/lon format coordinates: ${match[1]}, ${match[2]}`);\n            return { lat: parseFloat(match[1]), lng: parseFloat(match[2]) };\n          }\n          \n          \/\/ Try to match any pair of coordinates in the URL\n          match = url.match(\/(-?\\d+\\.\\d+),\\s*(-?\\d+\\.\\d+)\/);\n          if (match) {\n            console.log(`Found generic coordinates: ${match[1]}, ${match[2]}`);\n            return { lat: parseFloat(match[1]), lng: parseFloat(match[2]) };\n          }\n          \n          console.log('No coordinate pattern matched in URL');\n          return null;\n        } catch (error) {\n          console.error('Error extracting coordinates from URL:', error);\n          return null;\n        }\n      }\n      \n      \/\/ Helper for geocoding addresses with retry and caching\n      const geocodeCache = {}; \/\/ Cache geocoding results\n      \n      function geocodeAddress(address) {\n        \/\/ Return from cache if available\n        if (geocodeCache[address]) {\n          console.log(`Using cached coordinates for address: ${address}`);\n          return Promise.resolve(geocodeCache[address]);\n        }\n        \n        \/\/ Use our secure API proxy instead of directly accessing the external API\n        return fetch(`api\/properties.php?action=geocode&address=${encodeURIComponent(address)}`)\n          .then(response => response.json())\n          .then(data => {\n            if (data && data.lat && data.lon) {\n              const result = {\n                lat: parseFloat(data.lat),\n                lon: parseFloat(data.lon)\n              };\n              \/\/ Cache the result\n              geocodeCache[address] = result;\n              return result;\n            }\n            return null;\n          })\n          .catch(error => {\n            console.error(`Geocoding error for address ${address}:`, error);\n            return null;\n          });\n      }\n      \n      \/\/ Process units to get valid coordinates\n      const validUnits = [];\n      const geocodePromises = [];\n      \n      console.log('Processing units for map:', units);\n      \n      units.forEach(unit => {\n        \/\/ Try to parse latitude and longitude\n        let lat = unit.latitude || null;\n        let lng = unit.longitude || null;\n        let coordsSource = 'direct';\n        \n        \/\/ Log each unit's coordinates and map_url for debugging\n        console.log(`Processing unit ${unit.id || unit.objekt_nr || unit.title}: lat=${lat}, lng=${lng}, map_url=${unit.map_url || 'none'}`);\n        \n        \/\/ If map_url exists, prioritize extracting coordinates from it\n        if (unit.map_url) {\n          console.log(`Extracting coordinates from map_url for ${unit.objekt_nr || unit.id || 'unknown'}`);\n          const coords = extractCoordsFromUrl(unit.map_url);\n          if (coords) {\n            console.log(`Successfully extracted coordinates from map_url: lat=${coords.lat}, lng=${coords.lng}`);\n            lat = coords.lat;\n            lng = coords.lng;\n            coordsSource = 'map_url';\n          } else {\n            console.log(`Failed to extract coordinates from map_url for ${unit.objekt_nr || unit.id || 'unknown'}`);\n          }\n        } else {\n          console.log(`No map_url found for unit ${unit.objekt_nr || unit.id || 'unknown'}`);\n        }\n        \n        \/\/ If we still don't have valid coordinates but have direct lat\/lng, use those\n        if ((!lat || !lng || isNaN(parseFloat(lat)) || isNaN(parseFloat(lng))) && \n            (unit.latitude && unit.longitude)) {\n          lat = parseFloat(unit.latitude);\n          lng = parseFloat(unit.longitude);\n          coordsSource = 'direct';\n          console.log(`Using direct coordinates: lat=${lat}, lng=${lng}`);\n        }\n        \n        \/\/ If we have valid coordinates, add to valid units\n        if (lat && lng && !isNaN(parseFloat(lat)) && !isNaN(parseFloat(lng))) {\n          console.log(`Adding unit ${unit.objekt_nr || unit.id || 'unknown'} with coordinates from ${coordsSource}`);\n          validUnits.push({...unit, latitude: parseFloat(lat), longitude: parseFloat(lng)});\n        }\n        \/\/ If we have an address but no coordinates, try geocoding\n        else if (unit.address) {\n          console.log(`Geocoding address for unit ${unit.objekt_nr || unit.id || 'unknown'}: ${unit.address}`);\n          geocodePromises.push(\n            geocodeAddress(unit.address).then(coords => {\n              if (coords) {\n                console.log(`Geocoded coordinates for ${unit.objekt_nr || unit.id || 'unknown'}: lat=${coords.lat}, lng=${coords.lon}`);\n                validUnits.push({...unit, latitude: coords.lat, longitude: coords.lon});\n              } else {\n                console.log(`Failed to geocode address for ${unit.objekt_nr || unit.id || 'unknown'}`);\n              }\n            })\n          );\n        } else {\n          console.log(`No valid coordinates or address for unit ${unit.objekt_nr || unit.id || 'unknown'}`);\n        }\n      });\n      \n      \/\/ Wait for all geocoding to finish\n      Promise.all(geocodePromises).then(() => {\n        \/\/ If no valid units with coordinates, show a message\n        if (validUnits.length === 0) {\n          console.log('No valid coordinates found for any units');\n          mapDiv.innerHTML = '<div style=\"text-align:center;padding:2em;\">Inga bost\u00e4der med giltiga platskoordinater hittades!<\/div>';\n          return;\n        }\n        \n        if (validUnits.length > 0) {\n          \/\/ Clear the loading message\n          mapDiv.innerHTML = '';\n          \n          \/\/ Add a small delay to ensure the DOM is ready\n          setTimeout(() => {\n            try {\n              \/\/ Make sure any existing map is properly removed first\n              if (mapInstance) {\n                console.log('Removing existing map instance');\n                try {\n                  mapInstance.remove();\n                } catch (e) {\n                  console.error('Error removing existing map:', e);\n                }\n                mapInstance = null;\n              }\n              \n              \/\/ Initialize the map with default view\n              console.log('Creating new map instance');\n              mapInstance = L.map('map', {\n                \/\/ Add these options to help with initialization\n                fadeAnimation: false,\n                zoomAnimation: false,\n                preferCanvas: true, \/\/ Use canvas for better performance\n                attributionControl: false \/\/ Remove attribution for cleaner look\n              });\n              \n              \/\/ If we have multiple units, fit the map to show all markers\n              if (validUnits.length > 1) {\n                console.log(`Fitting map to show all ${validUnits.length} properties`);\n                \/\/ Create bounds object to fit all markers\n                const bounds = L.latLngBounds(validUnits.map(unit => [unit.latitude, unit.longitude]));\n                \/\/ Fit the map to these bounds with some padding\n                mapInstance.fitBounds(bounds, {\n                  padding: [50, 50], \/\/ Add padding around the bounds\n                  maxZoom: 15 \/\/ Limit maximum zoom level\n                });\n              } else {\n                \/\/ If only one unit, center on it\n                console.log('Only one property, centering map on it');\n                mapInstance.setView([validUnits[0].latitude, validUnits[0].longitude], 13);\n              }\n          \n              \/\/ Add the tile layer (OpenStreetMap)\n              \/\/ Use a faster CDN for tiles\n              L.tileLayer('https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png', {\n                maxZoom: 19,\n                attribution: '\u00a9 OpenStreetMap contributors',\n                crossOrigin: true\n              }).addTo(mapInstance);\n          \n              \/\/ Create a marker cluster group for better performance with many markers\n              const markers = L.markerClusterGroup ? L.markerClusterGroup() : L.layerGroup();\n              \n              \/\/ Add markers for each unit\n              validUnits.forEach(unit => {\n                const marker = L.marker([unit.latitude, unit.longitude]);\n                \n                \/\/ Create popup content\n                let popupContent = `<b>${unit.title || unit.objekt_nr || ''}<\/b><br>`;\n                if (unit.address) popupContent += `Adress: ${unit.address}<br>`;\n                if (unit.status) popupContent += `Status: ${unit.status}<br>`;\n                if (unit.rooms) popupContent += `Rum: ${unit.rooms}<br>`;\n                if (unit.size) popupContent += `Storlek: ${unit.size} kvm<br>`;\n                if (unit.fee) popupContent += `Avgift: ${unit.fee} kr<br>`;\n                if (unit.price) {\n                  \/\/ Handle the 'kr' suffix properly, similar to the card display fix\n                  let priceText = unit.price.toString();\n                  \/\/ Remove any existing 'kr' suffix before adding it again\n                  priceText = priceText.replace(\/\\s*kr\\s*$\/i, '').trim();\n                  popupContent += `Pris: ${priceText} kr<br>`;\n                }\n                if (unit.type) popupContent += `Bostadstyp: ${unit.type}<br>`;\n                \n                marker.bindPopup(popupContent);\n                markers.addLayer(marker);\n              });\n              \n              \/\/ Add all markers at once for better performance\n              mapInstance.addLayer(markers);\n              \n              \/\/ Force a map redraw after initialization\n              setTimeout(() => {\n                mapInstance.invalidateSize();\n              }, 200);\n            } catch (error) {\n              console.error('Error initializing map:', error);\n              mapDiv.innerHTML = '<div style=\"text-align:center;padding:2em;\">Fel vid initialisering av kartan. F\u00f6rs\u00f6k igen senare.<\/div>';\n            }\n          }, 100); \/\/ Small delay before map initialization\n        } else {\n          mapDiv.innerHTML = '<div style=\"text-align:center;padding:2em;\">Inga l\u00e4genheter med giltiga koordinater hittades!<\/div>';\n        }\n      });\n    }\n    \n    \/\/ Fetch data from NocoDB to supplement the existing data\n    function fetchNocoDBData() {\n      fetch('https:\/\/husetexklusiv.se\/digitalvisning\/ledigafastigheter\/api\/properties.php?action=getNocoDBData', {\n        method: 'GET',\n        headers: { 'Content-Type': 'application\/json' }\n      })\n      .then(response => response.json())\n      .then(data => {\n        \/\/ Debug log to see the structure of the data\n        debugLogData(data, 'NocoDB raw data');\n        \n        if (data.list && data.list.length > 0) {\n          \/\/ Debug log a sample item to see its structure\n          debugLogData(data.list[0], 'Sample NocoDB item');\n          \n          \/\/ Map NocoDB data to our format and merge with existing units\n          const nocoUnits = data.list.map(u => ({\n            id: u.objekt_nr || u.Id || '',\n            status: u.Availability || '',\n            rooms: u.rum || '',\n            size: u.storlek || '',\n            fee: u.avgift || '',\n            price: u.pris || '',\n            type: u.objekttyp || '',\n            map_url: u.map_url || '',\n            mapUrl: u.map_url || '',\n            viewUrl: u.visa_objekt || '',\n            floor: u.plan || '',\n            title: '',\n            description: '',\n            image: '',\n            latitude: u.latitude || '',\n            longitude: u.longitude || '',\n            address: u.address || ''\n          }));\n          \n          \/\/ Merge with existing units if any\n          \/\/ Always use only the NocoDB data\n          allUnits = nocoUnits;\n          \n          \/\/ Debug log the mapped units\n          debugLogData(nocoUnits, 'Mapped NocoDB units');\n          \n          \/\/ Only use properties with real coordinates\n          const hasCoordinates = nocoUnits.some(unit => \n            (unit.latitude && unit.longitude) || unit.map_url\n          );\n          \n          if (!hasCoordinates) {\n            console.warn('No real coordinates found in any property.');\n          } else {\n            console.log('Using real coordinates from database');\n          }\n          renderFilters(allUnits);\n          renderCards();\n          renderMap(allUnits);\n        }\n      })\n      .catch(error => {\n        console.error('Error fetching data from NocoDB:', error);\n      });\n    }\n    \n    \/\/ Setup type filter\n    const typeFilter = document.getElementById('typeFilter');\n    if (typeFilter) {\n      typeFilter.addEventListener('change', function() {\n        filters.type = this.value;\n        renderActiveFilters();\n        renderCards();\n      });\n    } else {\n      console.warn('Type filter not found');\n    }\n    \/\/ Setup status filter\n    const statusFilter = document.getElementById('statusFilter');\n    if (statusFilter) {\n      statusFilter.addEventListener('change', function() {\n        filters.status = this.value;\n        renderActiveFilters();\n        renderCards();\n      });\n    } else {\n      console.warn('Status filter not found');\n    }\n    renderActiveFilters();\n    \n    \/\/ JavaScript style enforcer for CSS isolation\n    document.addEventListener('DOMContentLoaded', function() {\n      const component = document.getElementById('property-listing-component');\n      if (!component) return;\n      \n      const isolateStyles = function() {\n        const allElements = component.querySelectorAll('*');\n        allElements.forEach(function(el) {\n          if (el.tagName === 'BUTTON') {\n            el.style.setProperty('display', 'inline-block', 'important');\n            el.style.setProperty('cursor', 'pointer', 'important');\n          }\n          el.style.setProperty('font-family', 'Arial, sans-serif', 'important');\n        });\n      };\n      \n      \/\/ Apply styles on load\n      isolateStyles();\n      \n      \/\/ Watch for DOM changes and reapply styles\n      const observer = new MutationObserver(isolateStyles);\n      observer.observe(component, { childList: true, subtree: true });\n    });\n  <\/script>\n  <\/div> <!-- Close property-listing-component -->\n<\/body>\n<\/html>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-dfd05d6 elementor-reverse-tablet elementor-reverse-mobile elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"dfd05d6\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-b916744\" data-id=\"b916744\" data-element_type=\"column\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-590e047 elementor-widget elementor-widget-spacer\" data-id=\"590e047\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-84fa2ac elementor-widget elementor-widget-heading\" data-id=\"84fa2ac\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Toteutetut toiminnot<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-961bea9 elementor-widget-divider--view-line elementor-widget elementor-widget-divider\" data-id=\"961bea9\" data-element_type=\"widget\" data-widget_type=\"divider.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-divider\">\n\t\t\t<span class=\"elementor-divider-separator\">\n\t\t\t\t\t\t<\/span>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-48ce3b0 elementor-widget elementor-widget-text-editor\" data-id=\"48ce3b0\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<ul>\n<li>Virtuaalinen myyntiassistentti<\/li><li>Videoiden toisto on nyt mahdollista \u00e4lypuhelimilla, tableteilla, VR-kuulokkeilla ja tietokoneilla sek\u00e4 2D- ett\u00e4 360-muodossa.&nbsp;<br>- Henkil\u00f6kohtainen video(t). Henkil\u00f6kohtaisen toiston avulla asiakas voi saada henkil\u00f6kohtaisia tervehdyksi\u00e4 ja tietoja esimerkiksi v\u00e4litt\u00e4j\u00e4lt\u00e4.&nbsp;<br>- Kohteiden n\u00e4ytt\u00e4minen 2D- ja 360-videomuodossa.<\/li>\n<li>Seuraavia formaatteja tuetaan <br>.Word<br>.PDF <br>.PP(Microsoft PowerPoint)<br>.XL(Microsoft Excel)<\/li>\n<li>.<\/li>\n<li>3D-esineet<\/li>\n<li>YouTube<\/li>\n<li>Facebook<\/li>\n<li>Linkedin<\/li>\n<li>Twitter<\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-187a3ac elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"187a3ac\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-f74d0da\" data-id=\"f74d0da\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-5992af6 elementor-widget elementor-widget-spacer\" data-id=\"5992af6\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0044f35 elementor-widget elementor-widget-heading\" data-id=\"0044f35\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<span class=\"elementor-heading-title elementor-size-default\">Lue lis\u00e4\u00e4 konfiguraattoristamme uusia rakennuksia varten.<\/span>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d9e3ba3 elementor-align-center elementor-widget elementor-widget-button\" data-id=\"d9e3ba3\" data-element_type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"https:\/\/huset.se\/konfigurator\/\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Lue lis\u00e4\u00e4<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c869b12 elementor-widget elementor-widget-spacer\" data-id=\"c869b12\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>Vuorovaikutteiset ty\u00f6kalut K\u00e4ytett\u00e4viss\u00e4 olevat kiinteist\u00f6t Saumaton k\u00e4ytt\u00f6kokemus - suoraan verkkosivustollasi Kiinteist\u00f6nomistajille ja v\u00e4litt\u00e4jille, joilla on useita kiinteist\u00f6j\u00e4, tarjoamme nyt r\u00e4\u00e4t\u00e4l\u00f6idyn ratkaisun, jonka avulla esill\u00e4 olevat kiinteist\u00f6t voidaan esitell\u00e4 tyylikk\u00e4\u00e4sti ja helposti, mik\u00e4 aiemmin puuttui. Uuden ominaisuutemme avulla voit helposti integroida t\u00e4ydellisen kiinteist\u00f6hakumoduulin omaan [...]<\/p>","protected":false},"author":25,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template\/template-homepage.php","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","footnotes":""},"class_list":["post-28037","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/huset.se\/fi\/wp-json\/wp\/v2\/pages\/28037","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/huset.se\/fi\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/huset.se\/fi\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/huset.se\/fi\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/huset.se\/fi\/wp-json\/wp\/v2\/comments?post=28037"}],"version-history":[{"count":232,"href":"https:\/\/huset.se\/fi\/wp-json\/wp\/v2\/pages\/28037\/revisions"}],"predecessor-version":[{"id":28275,"href":"https:\/\/huset.se\/fi\/wp-json\/wp\/v2\/pages\/28037\/revisions\/28275"}],"wp:attachment":[{"href":"https:\/\/huset.se\/fi\/wp-json\/wp\/v2\/media?parent=28037"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}