{"id":11195,"date":"2026-06-13T17:02:17","date_gmt":"2026-06-13T16:02:17","guid":{"rendered":"https:\/\/paragliding-in-madeira.com\/weather\/?page_id=11195"},"modified":"2026-06-14T18:49:24","modified_gmt":"2026-06-14T17:49:24","slug":"roads","status":"publish","type":"page","link":"https:\/\/paragliding-in-madeira.com\/weather\/roads\/","title":{"rendered":"Madeira Roads"},"content":{"rendered":"\n<figure class=\"wp-block-image alignfull size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/paragliding-in-madeira.com\/weather\/wp-content\/uploads\/2025\/10\/Roads-banner-gpt-ext-1024x576.webp\" alt=\"Roads banner gpt ext\" class=\"wp-image-5169\" srcset=\"https:\/\/paragliding-in-madeira.com\/weather\/wp-content\/uploads\/2025\/10\/Roads-banner-gpt-ext-1024x576.webp 1024w, https:\/\/paragliding-in-madeira.com\/weather\/wp-content\/uploads\/2025\/10\/Roads-banner-gpt-ext-300x169.webp 300w, https:\/\/paragliding-in-madeira.com\/weather\/wp-content\/uploads\/2025\/10\/Roads-banner-gpt-ext-768x432.webp 768w, https:\/\/paragliding-in-madeira.com\/weather\/wp-content\/uploads\/2025\/10\/Roads-banner-gpt-ext-1536x864.webp 1536w, https:\/\/paragliding-in-madeira.com\/weather\/wp-content\/uploads\/2025\/10\/Roads-banner-gpt-ext.webp 1820w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<div class=\"wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-buttons is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-a89b3969 wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/paragliding-in-madeira.com\/weather\/\">Home<\/a><\/div>\n\n\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/paragliding-in-madeira.com\/weather\/weather-info\/\">Weather info<\/a><\/div>\n<\/div>\n\n\n\n<p class=\"has-text-align-center\"><a href=\"https:\/\/paragliding-in-madeira.com\/weather\/webcams\/\" title=\"\">Live<\/a> | <a href=\"https:\/\/paragliding-in-madeira.com\/weather\/all-cams\/\" title=\"\">Pictures<\/a> | <a href=\"https:\/\/paragliding-in-madeira.com\/weather\/freeway\" title=\"\">Freeway<\/a> | <strong>Roads<\/strong><\/p>\n\n\n\n<div style=\"height:8px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"has-text-align-center\"><a style=\"text-decoration:none\" href=\"https:\/\/madeiracams.pt\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Check the real time status of local traffic<\/a><br>Powered by <a href=\"https:\/\/www.viaexpresso.com\/rede-concessionada\/mapa-da-concessao.html\" target=\"_blank\" rel=\"noopener\" title=\"\">Via Expresso<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-default\"\/>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<div style=\"height:10px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<!-- first full version tests\n<div id=\"expresso-cam-container\">\n  <style>\n    \/* Container *\/\n    #expresso-cam-container {\n      max-width: 1344px;\n      margin: 0 auto;\n      padding: 1rem;\n      font-family: 'Inter', sans-serif;\n      background: transparent;\n    }\n    @media (min-width: 640px) { #expresso-cam-container { padding: 2rem; } }\n\n    \/* Header *\/\n    #expresso-cam-container .header { text-align: center; margin-bottom: 2rem; }\n    #expresso-cam-container .header .title { font-size: 1.7rem; font-weight: 600; color: #1f2937; margin-bottom: 0.4rem; }\n    #expresso-cam-container .header .subtitle { font-size: 1.125rem; color: #4b5563; margin-bottom: 1rem; }\n    \n    \/* Search Input and Filters Container *\/\n    #expresso-cam-container .search-and-filters {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      gap: 1rem;\n    }\n    \n    #expresso-cam-container .header .filter-input {\n      padding: 0.5rem 1rem;\n      width: 100%;\n      max-width: 32rem;\n      border: 1px solid #d1d5db;\n      border-radius: 0.25rem;\n    }\n\n    \/* Location Filter Buttons *\/\n    #expresso-cam-container .location-filters {\n      display: flex;\n      justify-content: center;\n      flex-wrap: wrap;\n      gap: 0.5rem;\n    }\n    #expresso-cam-container .location-filters button {\n      background-color: #f3f4f6;\n      color: #374151;\n      border: 1px solid #d1d5db;\n      padding: 0.5rem 1rem;\n      border-radius: 9999px;\n      cursor: pointer;\n      transition: background-color 0.2s, color 0.2s, transform 0.2s;\n      font-weight: 500;\n    }\n    #expresso-cam-container .location-filters button:hover {\n      background-color: #e5e7eb;\n      transform: translateY(-2px);\n    }\n    \/* Grounded color change for active elements *\/\n    #expresso-cam-container .location-filters button.active {\n      background-color: #15803d; \n      color: white;\n      border-color: #15803d;\n      transform: translateY(-2px);\n    }\n\n    \/* Grid *\/\n    #expresso-cam-container #camera-grid {\n      display: grid;\n      grid-template-columns: repeat(1, 1fr);\n      gap: 1.5rem;\n    }\n    @media (min-width: 640px) { #expresso-cam-container #camera-grid { grid-template-columns: repeat(2, 1fr); } }\n    @media (min-width: 1024px) { #expresso-cam-container #camera-grid { grid-template-columns: repeat(3, 1fr); } }\n\n    \/* Camera item *\/\n    #expresso-cam-container .camera-item {\n      position: relative;\n      border-radius: 6px;\n      box-shadow: 0 4px 6px rgba(0,0,0,0.1);\n      transition: transform .2s;\n      cursor: default;\n      background: transparent;\n      overflow: visible;\n    }\n    #expresso-cam-container .camera-item:hover { transform: translateY(-4px); }\n\n    \/* Video styling directly replacing the image *\/\n    #expresso-cam-container .camera-video {\n      width: 100%;\n      display: block;\n      min-height: 180px;\n      border-radius: 6px;\n      background-color: #111827; \/* Dark background while loading *\/\n      object-fit: cover;\n    }\n\n    #expresso-cam-container .camera-title {\n      padding: .5rem .75rem;\n      text-align: center;\n      font-weight: 500;\n      color: white;\n      background-color: #15803d; \/* Matching grounded color *\/\n      border-radius: 0 0 6px 6px;\n      margin-top: -3px;\n    }\n\n    #expresso-cam-container .fade-in { animation: fadeIn .5s ease-in forwards; }\n    @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n  <\/style>\n\n  <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/hls.js@latest\"><\/script>\n\n  <header class=\"header\">\n    <h1 class=\"title\">Via Expresso Cams<\/h1>\n    <p class=\"subtitle\">Live road conditions across the island<\/p>\n    <div class=\"search-and-filters\">\n        <input id=\"filterInput\" type=\"text\" placeholder=\"Filter by location...\" class=\"filter-input\" \/>\n        <div class=\"location-filters\">\n            <button id=\"all-btn\" class=\"active\">All<\/button>\n            <button id=\"ve1-btn\">VE1 (Machico-Santana)<\/button>\n            <button id=\"ve2-btn\">VE2 (Norte)<\/button>\n            <button id=\"ve3-btn\">VE3 (Oeste)<\/button>\n            <button id=\"ve4-btn\">VE4 (S\u00e3o Vicente)<\/button>\n        <\/div>\n    <\/div>\n  <\/header>\n\n  <div id=\"camera-grid\"><\/div>\n\n  <script>\n    const expressoTitles = {\n      'VE1-QTA-TVM001': 'Rotunda Machico Norte',\n      'VE1-NOR-TVM001': 'N\u00f3 dos Marocos',\n      'VE1-SER-TVM002': 'N\u00f3 Porto Cruz Este',\n      'VE1-CRZ-TVM001': 'N\u00f3 Porto Cruz Oeste',\n      'VE1-COR-TVM001': 'Rotunda do Faial',\n      'VE1-COR-TVM002': 'N\u00f3 da Achada - Santana',\n      'VE1-SAN-TVM002': 'Rotunda de Santana',\n      'VE1-RSJ-TVM002': 'T\u00fanel Ribeira S\u00e3o Jorge',\n      \n      'VE2-MAN-TVM002': 'Porto Moniz',\n      'VE2-RBJ-TVM001': 'N\u00f3 da Ribeira Janela',\n      'VE2-SEI-TVM002': 'N\u00f3 Seixal Oeste',\n      'VE2-SEI-TVM001': 'N\u00f3 Seixal Este',\n      \n      'VE4-CAR-TVM004': 'Rotunda de S\u00e3o Vicente',\n      'VE4-CAR-TVM002': 'Rotunda dos Cardais',\n      'VE4-SAR-TVM003': 'Rotunda do Laranjal',\n      'VE4-SAR-TVM001': 'Rotunda do Saramago',\n      'VE4-ENC-TVM002': 'Rotunda da Serra d\\'\u00c1gua',\n      'VE4-ENC-TVM001': 'N\u00f3 Serra d\\'\u00c1gua',\n      \n      'VE3-GES-TVM001': 'Rotunda Norte Ribeira Brava',\n      'VE3-RBT-TVM001': 'Rotunda Sul Ribeira Brava',\n      'VE3-RBT-TVM004': 'Rotunda da Tabua',\n      'VE3-LBX-TVM002': 'Rotunda da Ponta do Sol',\n      'VE3-PSM-TVM003': 'Rotunda da Madalena do Mar',\n      'VE3-ARC-TVM001': 'Rotunda do Arco da Calheta',\n      'VE3-DOU-TVM002': 'Rotunda da Calheta',\n      'VE3-IGR-TVM002': 'Rotunda do Estreito da Calheta',\n      'VE3-JPE-TVM002': 'Rotunda dos Prazeres',\n      'VE3-RAP-TVM001': 'N\u00f3 da Maloeira',\n      'VE3-RAP-TVM002': 'T\u00fanel da Raposeira'\n    };\n\n    \/\/ Convert object to array for mapping\n    const cameras = Object.keys(expressoTitles).map(id => ({\n        id: id,\n        title: expressoTitles[id],\n        region: id.substring(0, 3).toLowerCase() \/\/ Extracts ve1, ve2, ve3, ve4\n    }));\n\n    function createCameraElement(camera) {\n      const article = document.createElement('article');\n      article.className = 'camera-item fade-in';\n      article.dataset.id = camera.id;\n      article.dataset.title = camera.title.toLowerCase();\n      article.dataset.region = camera.region;\n      \n      \/\/ Native video element with autoplay, muted, and playsinline (no controls)\n      article.innerHTML = `\n        <video class=\"camera-video\" id=\"vid-${camera.id}\" autoplay muted playsinline><\/video>\n        <div class=\"camera-title\">${camera.title}<\/div>\n      `;\n      return article;\n    }\n\n    \/\/ Filter logic\n    function filterCameras(filterValue, isRegion) {\n        document.querySelectorAll('.camera-item').forEach(item => {\n            if (filterValue === 'all') {\n                item.style.display = '';\n            } else if (isRegion) {\n                item.style.display = item.dataset.region === filterValue ? '' : 'none';\n            } else {\n                item.style.display = item.dataset.title.includes(filterValue) ? '' : 'none';\n            }\n        });\n    }\n\n    document.addEventListener('DOMContentLoaded', () => {\n        const grid = document.getElementById('camera-grid');\n        const filterInput = document.getElementById('filterInput');\n        const locationButtons = document.querySelectorAll('.location-filters button');\n\n        \/\/ Create the intersection observer for gentle resource management\n        const observerOptions = {\n            rootMargin: '150px', \/\/ Start connecting slightly before it enters the screen\n            threshold: 0\n        };\n\n        const streamObserver = new IntersectionObserver((entries) => {\n            entries.forEach(entry => {\n                const video = entry.target.querySelector('video');\n                const cameraId = entry.target.dataset.id;\n                const streamUrl = `https:\/\/www.viaexpresso.com\/camaras\/${cameraId}\/stream.m3u8`;\n\n                if (entry.isIntersecting) {\n                    \/\/ Connect stream when visible\n                    if (Hls.isSupported()) {\n                        const hls = new Hls({ autoStartLoad: true });\n                        hls.loadSource(streamUrl);\n                        hls.attachMedia(video);\n                        video.hlsReference = hls; \/\/ Store reference to clear later\n                        \n                        hls.on(Hls.Events.MANIFEST_PARSED, function() {\n                            video.play().catch(() => { \/* Handle strict browser autoplay policies quietly *\/ });\n                        });\n                    } else if (video.canPlayType('application\/vnd.apple.mpegurl')) {\n                        \/\/ Native support for Safari\n                        video.src = streamUrl;\n                        video.play().catch(() => {});\n                    }\n                } else {\n                    \/\/ Disconnect stream when out of view\n                    if (video.hlsReference) {\n                        video.hlsReference.stopLoad();\n                        video.hlsReference.detachMedia();\n                        video.hlsReference = null;\n                    }\n                    video.removeAttribute('src');\n                    video.load(); \/\/ Reset the native player state\n                }\n            });\n        }, observerOptions);\n\n        \/\/ Build grid and attach observer\n        cameras.forEach(cam => {\n            const camElement = createCameraElement(cam);\n            grid.appendChild(camElement);\n            streamObserver.observe(camElement);\n        });\n\n        \/\/ Search Input Event\n        filterInput.addEventListener('input', e => {\n            const q = e.target.value.toLowerCase();\n            locationButtons.forEach(btn => btn.classList.remove('active'));\n            if (q === '') {\n                document.getElementById('all-btn').classList.add('active');\n            }\n            filterCameras(q, false);\n        });\n\n        \/\/ Location Buttons Event\n        locationButtons.forEach(button => {\n            button.addEventListener('click', () => {\n                filterInput.value = ''; \n                locationButtons.forEach(btn => btn.classList.remove('active'));\n                button.classList.add('active');\n\n                const regionId = button.id.replace('-btn', '');\n                filterCameras(regionId, regionId !== 'all');\n            });\n        });\n    });\n  <\/script>\n<\/div>\n-->\n\n\n\n<!-- removed reload button below\n<script>\n  function reloadIframe(iframeId) {\n    var iframe = document.getElementById(iframeId);\n    if (iframe) {\n      var currentSrc = iframe.src.split('?')[0];\n      iframe.src = currentSrc;\n      console.log('Iframe ' + iframeId + ' reloaded.');\n    } else {\n      console.error('Iframe with ID ' + iframeId + ' not found.');\n    }\n  }\n<\/script>\n\n<div style=\"width: 333px; height: 888px; transform: translatex(-10px); overflow: hidden; position: relative; clear: both;\">\n    \n\n    <iframe name=\"viaexpresso\" id=\"viaexpresso\"\n        src=\"https:\/\/www.viaexpresso.com\/rede-concessionada\/mapa-da-concessao.html\" \n        style=\"width: 333px; height: 1222px; position: absolute; top: -333px; left: 0; border: none;\"\n        scrolling=\"no\">\n    <\/iframe>\n<\/div>\n\n<div style=\"text-align: center; margin-top: 22px;\">\n  <button onclick=\"reloadIframe('viaexpresso')\" style=\"padding: 8px 18px; cursor: pointer; background-color: #0073aa; color: #fff; border: none; border-radius: 4px; font-weight: bold;\">\n    Reload ViaExpresso\n  <\/button>\n<\/div>\n-->\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary> Map<\/summary>\n<p><\/p>\n\n\n\n<!-- The Wrapper sets the exact crop area you want to show -->\n<div style=\"width: 333px; height: 888px; transform: translatex(-10px); overflow: hidden; position: relative; clear: both;\">\n    \n    <!-- The iframe is physically pulled up using absolute positioning -->\n    <iframe name=\"viaexpresso\" id=\"viaexpresso\"\n        src=\"https:\/\/www.viaexpresso.com\/rede-concessionada\/mapa-da-concessao.html\" \n        style=\"width: 333px; height: 1222px; position: absolute; top: -333px; left: 0; border: none;\"\n        scrolling=\"no\">\n    <\/iframe>\n<\/div>\n<\/details>\n\n\n\n<div style=\"height:7px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<!-- First working version fine tuned below \n<div id=\"expresso-cam-container\">\n  <style>\n    \/* Retaining your established styles for consistency *\/\n    #expresso-cam-container { max-width: 1344px; margin: 0 auto; padding: 1rem; font-family: 'Inter', sans-serif; }\n    .header { text-align: center; margin-bottom: 2rem; }\n    .search-and-filters { display: flex; flex-direction: column; align-items: center; gap: 1rem; }\n    .filter-input { padding: 0.5rem 1rem; width: 100%; max-width: 32rem; border: 1px solid #d1d5db; border-radius: 0.25rem; }\n    .location-filters { display: flex; justify-content: center; flex-wrap: wrap; gap: 0.5rem; }\n    .location-filters button { background-color: #f3f4f6; color: #374151; border: 1px solid #d1d5db; padding: 0.5rem 1rem; border-radius: 9999px; cursor: pointer; font-weight: 500; }\n    .location-filters button.active { background-color: #15803d; color: white; border-color: #15803d; }\n    \n    #camera-grid { display: grid; grid-template-columns: repeat(1, 1fr); gap: 1.5rem; }\n    @media (min-width: 640px) { #camera-grid { grid-template-columns: repeat(2, 1fr); } }\n    @media (min-width: 1024px) { #camera-grid { grid-template-columns: repeat(3, 1fr); } }\n\n    .camera-item { \n        position: relative; \/* Establishes a container for absolute children *\/\n        border-radius: 6px; \n        box-shadow: 0 4px 6px rgba(0,0,0,0.1); \n        background: #111827; \n        overflow: hidden; \n        height: 218px; \/* Adjust this to fit your preference *\/\n    }\n\n    .camera-iframe { \n        position: absolute; \/* Pulls the video out of the layout flow *\/\n        top: 0;\n        left: 0;\n        width: 154%; \n\/* scaling to compensate *\/\n        height: 225px; \n        border: none; \n        transform: scale(0.75); \n        transform-origin: 0 0; \n    }\n\n    .camera-title { \n     position: absolute; \n\/* Pins title to the bottom *\/\n     bottom: 0;\n     left: 0;\n     width: 100%;\n     padding: .75rem; \n     text-align: center; \n     font-weight: 500; \n     color: white; \n     background-color: #15803d;\n     box-sizing: border-box;\n     font-size: 1rem;\n    }\n\n  <\/style>\n\n  <header class=\"header\">\n    <h2 class=\"title\">Local roads<\/h2>\n    <p class=\"subtitle\">Real time across the island<\/p>\n    <div class=\"search-and-filters\">\n        <input id=\"filterInput\" type=\"text\" placeholder=\"Filter by location...\" class=\"filter-input\" \/>\n        <div class=\"location-filters\">\n            <button id=\"all-btn\" class=\"active\">All<\/button>\n            <button id=\"ve1-btn\">Machico to Santana<\/button>\n            <button id=\"ve2-btn\">Porto Moniz to Seixal<\/button>\n            <button id=\"ve3-btn\">Ribeira Brava to Prazeres<\/button> \n            <button id=\"ve4-btn\">S\u00e3o Vicente to Serra d'\u00c1gua<\/button>\n        <\/div>\n    <\/div>\n  <\/header>\n\n  <div id=\"camera-grid\"><\/div>\n\n  <script>\n    const expressoTitles = {\n      'VE1-QTA-TVM001': 'Rotunda Machico Norte',\n      'VE1-NOR-TVM001': 'N\u00f3 dos Maro\u00e7os',\n      'VE1-SER-TVM002': 'N\u00f3 Porto Cruz Este',\n      'VE1-CRZ-TVM001': 'N\u00f3 Porto Cruz Oeste',\n      'VE1-COR-TVM001': 'Rotunda do Faial',\n      'VE1-COR-TVM002': 'N\u00f3 da Achada - Santana',\n      'VE1-SAN-TVM002': 'Rotunda de Santana',\n      'VE1-RSJ-TVM002': 'T\u00fanel Ribeira S\u00e3o Jorge',\n      'VE2-MAN-TVM002': 'Porto Moniz',\n      'VE2-RBJ-TVM001': 'N\u00f3 da Ribeira Janela',\n      'VE2-SEI-TVM002': 'N\u00f3 Seixal Oeste',\n      'VE2-SEI-TVM001': 'N\u00f3 Seixal Este',\n      'VE4-CAR-TVM004': 'Rotunda de S\u00e3o Vicente',\n      'VE4-CAR-TVM002': 'Rotunda dos Cardais',\n      'VE4-SAR-TVM003': 'Rotunda do Laranjal',\n      'VE4-SAR-TVM001': 'Rotunda do Saramago',\n      'VE4-ENC-TVM002': 'Rotunda da Serra d\\'\u00c1gua',\n      'VE4-ENC-TVM001': 'N\u00f3 Serra d\\'\u00c1gua',\n      'VE3-GES-TVM001': 'Rotunda Norte Ribeira Brava',\n      'VE3-RBT-TVM001': 'Rotunda Sul Ribeira Brava',\n      'VE3-RBT-TVM004': 'Rotunda da Tabua',\n      'VE3-LBX-TVM002': 'Rotunda da Ponta do Sol',\n      'VE3-PSM-TVM003': 'Rotunda da Madalena do Mar',\n      'VE3-ARC-TVM001': 'Rotunda do Arco da Calheta',\n      'VE3-DOU-TVM002': 'Rotunda da Calheta',\n      'VE3-IGR-TVM002': 'Rotunda do Estreito da Calheta',\n      'VE3-JPE-TVM002': 'Rotunda dos Prazeres',\n      'VE3-RAP-TVM001': 'N\u00f3 da Maloeira',\n      'VE3-RAP-TVM002': 'T\u00fanel da Raposeira'\n    };\n\n    const grid = document.getElementById('camera-grid');\n    \n    \/\/ Create placeholders\n    Object.keys(expressoTitles).forEach(id => {\n        const div = document.createElement('div');\n        div.className = 'camera-item';\n        div.dataset.id = id;\n        div.dataset.region = id.substring(0, 3).toLowerCase();\n        div.dataset.title = expressoTitles[id].toLowerCase();\n        div.innerHTML = `<div class=\"camera-title\">${expressoTitles[id]}<\/div>`;\n        grid.appendChild(div);\n    });\n\n    \/\/ Observer to load iframe only when visible\n    const observer = new IntersectionObserver((entries) => {\n        entries.forEach(entry => {\n            if (entry.isIntersecting) {\n                const id = entry.target.dataset.id;\n                if (!entry.target.querySelector('iframe')) {\n                    entry.target.insertAdjacentHTML('afterbegin', \n                        `<iframe class=\"camera-iframe\" src=\"https:\/\/www.viaexpresso.com\/map\/iframe.php?camaraId=${id}\" frameborder=\"0\" allowfullscreen><\/iframe>`\n                    );\n                }\n            }\n        });\n    }, { rootMargin: '200px' });\n\n    document.querySelectorAll('.camera-item').forEach(item => observer.observe(item));\n\n    \/\/ Simple Filter Logic\n    document.getElementById('filterInput').addEventListener('input', e => {\n        const val = e.target.value.toLowerCase();\n        document.querySelectorAll('.camera-item').forEach(item => {\n            item.style.display = item.dataset.title.includes(val) ? '' : 'none';\n        });\n    });\n\n    document.querySelectorAll('.location-filters button').forEach(btn => {\n        btn.addEventListener('click', (e) => {\n            document.querySelectorAll('.location-filters button').forEach(b => b.classList.remove('active'));\n            e.target.classList.add('active');\n            const reg = e.target.id.replace('-btn', '');\n            document.querySelectorAll('.camera-item').forEach(item => {\n                item.style.display = (reg === 'all' || item.dataset.region === reg) ? '' : 'none';\n            });\n        });\n    });\n  <\/script>\n<\/div>\n-->\n\n\n\n<!-- First working version -->\n<div id=\"expresso-cam-container\">\n    <style>\n    \/* Container *\/\n    #expresso-cam-container { \n        max-width: 1344px; \n        margin: 0 auto; \n        padding: 1rem; \n        font-family: 'Inter', sans-serif; \n    }\n    @media (min-width: 640px) { #expresso-cam-container { padding: 2rem; } }\n\n    \/* Header *\/\n    .header { text-align: center; margin-bottom: 2rem; }\n    .header .title { font-size: 1.7rem; font-weight: 600; color: #1f2937; margin-bottom: 0.4rem; }\n    .header .subtitle { font-size: 1rem; color: #4b5563; margin-bottom: 1rem; }\n\n    \/* Search and Filters *\/\n    .search-and-filters { display: flex; flex-direction: column; align-items: center; gap: 1rem; }\n    .filter-input { padding: 0.5rem 1rem; width: 100%; max-width: 32rem; border: 1px solid #d1d5db; border-radius: 0.25rem; }\n\n    \/* Location Filter Buttons - Consistent Style *\/\n    .location-filters { display: flex; justify-content: center; flex-wrap: wrap; gap: 0.5rem; }\n    .location-filters button { \n        background-color: #f3f4f6; \n        color: #374151; \n        border: 1px solid #d1d5db; \n        padding: 0.5rem 1rem; \n        border-radius: 9999px; \n        cursor: pointer; \n        transition: background-color 0.2s, color 0.2s, transform 0.2s;\n        font-weight: 500;\n    }\n    .location-filters button:hover { \n        background-color: #e5e7eb; \n        transform: translateY(-2px);\n    }\n    .location-filters button.active { \n        background-color: #15803d; \n        color: white; \n        border-color: #15803d; \n        transform: translateY(-2px);\n    }\n\n    \/* Grid *\/\n    #camera-grid { display: grid; grid-template-columns: repeat(1, 1fr); gap: 1.5rem; }\n    @media (min-width: 640px) { #camera-grid { grid-template-columns: repeat(2, 1fr); } }\n    @media (min-width: 1024px) { #camera-grid { grid-template-columns: repeat(3, 1fr); } }\n\n    \/* Camera item *\/\n    .camera-item { \n        position: relative; \/* Establishes a container for absolute children *\/\n        border-radius: 6px; \n        box-shadow: 0 4px 6px rgba(0,0,0,0.1); \n        background: #111827; \n        overflow: hidden; \n        height: 218px; \/* Adjust this to make video fit within the space above title *\/\n        transition: transform .2s;\n    }\n    .camera-item:hover { transform: translateY(-4px); }\n\n    .camera-iframe { \n        position: absolute; \/* Pulls the video out of the layout flow *\/\n        top: 0;\n        left: 0;\n        width: 154%; \/* scaling to make video show in full *\/\n        height: 225px; \n        border: none; \n        transform: scale(0.75); \n        transform-origin: 0 0; \n    }\n\n    .camera-title { \n        position: absolute; \/* Pins title to the bottom *\/\n        bottom: 0;\n        left: 0;\n        width: 100%;\n        padding: .75rem; \n        text-align: center; \n        font-weight: 500; \n        color: white; \n        background-color: #15803d;\n        box-sizing: border-box;\n        font-size: 1rem;\n    }\n  <\/style>\n\n\n  <header class=\"header\">\n    <h2 class=\"title\">Local roads<\/h2>\n    <p class=\"subtitle\">Real time across the island<\/p>\n    <div class=\"search-and-filters\">\n        <input id=\"filterInput\" type=\"text\" placeholder=\"Filter by location...\" class=\"filter-input\" \/>\n        <div class=\"location-filters\">\n            <button id=\"all-btn\" class=\"active\">All<\/button>\n            <button id=\"ve1-btn\">Machico to Santana<\/button>\n            <button id=\"ve2-btn\">Porto Moniz to Seixal<\/button>\n            <button id=\"ve4-btn\">S\u00e3o Vicente to Serra d&#8217;\u00c1gua<\/button>\n            <button id=\"ve3-btn\">Ribeira Brava to Prazeres<\/button> \n        <\/div>\n    <\/div>\n  <\/header>\n\n  <div id=\"camera-grid\"><\/div>\n\n  <script>\n    const expressoTitles = {\n      'VE1-QTA-TVM001': 'Rotunda Machico Norte',\n      'VE1-NOR-TVM001': 'N\u00f3 dos Maro\u00e7os',\n      'VE1-SER-TVM002': 'N\u00f3 Porto Cruz Este',\n      'VE1-CRZ-TVM001': 'N\u00f3 Porto Cruz Oeste',\n      'VE1-COR-TVM001': 'Rotunda do Faial',\n      'VE1-COR-TVM002': 'N\u00f3 da Achada - Santana',\n      'VE1-SAN-TVM002': 'Rotunda de Santana',\n      'VE1-RSJ-TVM002': 'T\u00fanel Ribeira S\u00e3o Jorge',\n      'VE2-MAN-TVM002': 'Porto Moniz',\n      'VE2-RBJ-TVM001': 'N\u00f3 da Ribeira Janela',\n      'VE2-SEI-TVM002': 'N\u00f3 Seixal Oeste',\n      'VE2-SEI-TVM001': 'N\u00f3 Seixal Este',\n      'VE4-CAR-TVM004': 'Rotunda de S\u00e3o Vicente',\n      'VE4-CAR-TVM002': 'Rotunda dos Cardais',\n      'VE4-SAR-TVM003': 'Rotunda do Laranjal',\n      'VE4-SAR-TVM001': 'Rotunda do Saramago',\n      'VE4-ENC-TVM002': 'Rotunda da Serra d\\'\u00c1gua',\n      'VE4-ENC-TVM001': 'N\u00f3 Serra d\\'\u00c1gua',\n      'VE3-GES-TVM001': 'Rotunda Norte Ribeira Brava',\n      'VE3-RBT-TVM001': 'Rotunda Sul Ribeira Brava',\n      'VE3-RBT-TVM004': 'Rotunda da Tabua',\n      'VE3-LBX-TVM002': 'Rotunda da Ponta do Sol',\n      'VE3-PSM-TVM003': 'Rotunda da Madalena do Mar',\n      'VE3-ARC-TVM001': 'Rotunda do Arco da Calheta',\n      'VE3-DOU-TVM002': 'Rotunda da Calheta',\n      'VE3-IGR-TVM002': 'Rotunda do Estreito da Calheta',\n      'VE3-JPE-TVM002': 'Rotunda dos Prazeres',\n      'VE3-RAP-TVM001': 'N\u00f3 da Maloeira',\n      'VE3-RAP-TVM002': 'T\u00fanel da Raposeira'\n    };\n\n    const grid = document.getElementById('camera-grid');\n    \n    \/\/ Create placeholders\n    Object.keys(expressoTitles).forEach(id => {\n        const div = document.createElement('div');\n        div.className = 'camera-item';\n        div.dataset.id = id;\n        div.dataset.region = id.substring(0, 3).toLowerCase();\n        div.dataset.title = expressoTitles[id].toLowerCase();\n        div.innerHTML = `<div class=\"camera-title\">${expressoTitles[id]}<\/div>`;\n        grid.appendChild(div);\n    });\n\n    \/\/ Map to keep track of the timers for each camera item to prevent loading during rapid scroll\n    const loadTimers = new Map();\n\n    \/\/ Observer to load iframe only when visible and unload and stop video when out of view\n    const observer = new IntersectionObserver((entries) => {\n        entries.forEach(entry => {\n            if (entry.isIntersecting) {\n                \/\/ Start a 250ms softening delay before loading the iframe\n                const timerId = setTimeout(() => {\n                    const id = entry.target.dataset.id;\n                    if (!entry.target.querySelector('iframe')) {\n                        entry.target.insertAdjacentHTML('afterbegin', \n                            `<iframe class=\"camera-iframe\" src=\"https:\/\/www.viaexpresso.com\/map\/iframe.php?camaraId=${id}\" frameborder=\"0\" allowfullscreen><\/iframe>`\n                        );\n                    }\n                }, 250);\n                loadTimers.set(entry.target, timerId);\n            } else {\n                \/\/ If the item leaves the view before the delay finishes, clear the timer\n                if (loadTimers.has(entry.target)) {\n                    clearTimeout(loadTimers.get(entry.target));\n                    loadTimers.delete(entry.target);\n                }\n                \/\/ Safely remove iframe when out of sight to protect bandwidth & stop bg video play\n                const iframe = entry.target.querySelector('iframe');\n                if (iframe) {\n                    iframe.remove();\n                }\n            }\n        });\n    }, { rootMargin: '0px' });\n    \/\/ This root margin determines how soon a video loads before it's on screen view\n    document.querySelectorAll('.camera-item').forEach(item => observer.observe(item));\n\n    \/\/ Simple Filter Logic\n    document.getElementById('filterInput').addEventListener('input', e => {\n        const val = e.target.value.toLowerCase();\n        document.querySelectorAll('.camera-item').forEach(item => {\n            item.style.display = item.dataset.title.includes(val) ? '' : 'none';\n        });\n    });\n\n    document.querySelectorAll('.location-filters button').forEach(btn => {\n        btn.addEventListener('click', (e) => {\n            document.querySelectorAll('.location-filters button').forEach(b => b.classList.remove('active'));\n            e.target.classList.add('active');\n            const reg = e.target.id.replace('-btn', '');\n            document.querySelectorAll('.camera-item').forEach(item => {\n                item.style.display = (reg === 'all' || item.dataset.region === reg) ? '' : 'none';\n            });\n        });\n    });\n  <\/script>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Live | Pictures | Freeway | Roads Check the real time status of local trafficPowered by Via Expresso Local roads Real time across the island All Machico to Santana Porto Moniz to Seixal S\u00e3o Vicente to Serra d&#8217;\u00c1gua Ribeira Brava to Prazeres<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"slim_seo":{"title":"Madeira Roads live","description":"Live images on the local roads to check the real time status of traffic. Powered by Via Expresso"},"footnotes":""},"class_list":["post-11195","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/paragliding-in-madeira.com\/weather\/wp-json\/wp\/v2\/pages\/11195","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/paragliding-in-madeira.com\/weather\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/paragliding-in-madeira.com\/weather\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/paragliding-in-madeira.com\/weather\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/paragliding-in-madeira.com\/weather\/wp-json\/wp\/v2\/comments?post=11195"}],"version-history":[{"count":29,"href":"https:\/\/paragliding-in-madeira.com\/weather\/wp-json\/wp\/v2\/pages\/11195\/revisions"}],"predecessor-version":[{"id":11275,"href":"https:\/\/paragliding-in-madeira.com\/weather\/wp-json\/wp\/v2\/pages\/11195\/revisions\/11275"}],"wp:attachment":[{"href":"https:\/\/paragliding-in-madeira.com\/weather\/wp-json\/wp\/v2\/media?parent=11195"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}