Dynamic world map with Voxel stats • Online NC
  • CSS
  • HTML
  • JavaScript

Dynamic world map with Voxel stats

Dynamic and interactive world map that adapts in intensity based on the number of visits per country
  • Modifié il y a 5 mois
  • Share

🌍 This code allows you to display an interactive map that can dynamically collect statistical data from the Voxel theme.

💡 Principle
•    Loads an SVG map of the world.
•    Colours each country according to the number of visits (the more visits → the more opaque the colour).
•    Displays a tooltip with the country name and number of visits when hovering over or touching a country.

📌 In short:
A lively and interactive world map 🌍, where each country is coloured according to its visits, with zoom and instant info when hovering over or touching.

BEFORE STARTING 🌏

a. Download the map at : https://mapsvg.com/maps/world

b. Upload it in your Media Library

 

STEP 1 🛠️

a. Drag the “Action list (VX)” widget into your Elementor template.

b. Add an item and change the text by inserting the file « 1. Action list text.html » (you can also replace @site() with @post() depending on your needs) 

c. Loop the item in the « Loop repeater now » section 🔁

d. Assign an ID to the widget in the “Advanced” tab → name it countrystats

e. Add some styling in “Advanced” > CSS 🎨 see the file « 2. Action list styles.css »

 

STEP 2 : 💻

a. Drag an HTML widget right above and paste the file « 3. Dynamic world map with Voxel stats.html ».

b. Replace [YOUR SVG URL] if the internal URL of the map SVG file previously added

 

STEP 3 : 🚀

Save your template and preview the result! 👀

				
					// File : 1. Action list text.html
<div class="countrydata">
    <span class="countrycode">@site(visit_stats.countries.code)</span>
    <span class="countryname">@site(visit_stats.countries.name)</span>
    <span class="countryvisitscount">@site(visit_stats.countries.count)</span>
</div>

// File : 2. Action list styles.css
selector .countryvisitscount {
    background: white;
    font-size: 11px;
    border-radius: 50px;
    border: solid 1px black;
    color: black;
    font-weight: 600;
    padding: 2px 6px;
    display: flex;
    align-items: center;
    height: 18px;
    width: auto !important;
    justify-content: center;
    margin-left: 10px;
}

selector .countrycode {
    display: none;
}

selector .countrydata {
    display: flex;
}

// File : 3. Dynamic world map with Voxel stats.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Dynamic world map with Voxel stats</title>

  <style>
    
    #mapwrapper svg {
      width: 100%;
      height: auto;
      display: block;
    }

    #mapwrapper svg path {
      transition: fill-opacity 0.3s ease, stroke 0.2s ease;
      cursor: pointer;
      stroke: rgba(255,255,255,0.3);
      stroke-width: 0.5;
    }

    #mapwrapper svg path:hover {
      stroke: rgba(255,255,255,0.8);
      stroke-width: 1;
    }

    /* Slider zoom */
    #zoom-slider-container {
      position: absolute;
      bottom: 20px;
      right: 20px;
      z-index: 10;
    }

    #zoom-slider {
      writing-mode: bt-lr; /* IE */
      -webkit-appearance: slider-vertical; /* WebKit */
      width: 8px;
      height: 120px;
      background: white;
      outline: none;
      border-radius: 4px;
      cursor: pointer;
      border: 1px solid rgba(255, 255, 255, 0.8);
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    }

    #zoom-slider::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: white;
      border: 2px solid #333;
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
      transition: all 0.2s ease;
    }

    #zoom-slider::-webkit-slider-thumb:hover {
      transform: scale(1.1);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
    }

    #zoom-slider::-moz-range-thumb {
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: white;
      border: 2px solid #333;
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
    }

    /* Responsive */
    @media (max-width: 768px) {
      #zoom-slider-container {
        bottom: 15px;
        right: 15px;
      }
      #zoom-slider {
        height: 100px;
      }
    }

    /* Tooltip */
    #map-tooltip {
      position: absolute;
      z-index: 1000;
      background: linear-gradient(145deg, rgba(0,0,0,0.95), rgba(30,30,30,0.95));
      color: white;
      border-radius: 12px;
      box-shadow: 0 8px 25px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.1);
      padding: 0;
      pointer-events: none;
      backdrop-filter: blur(10px);
      transform-origin: bottom center;
      transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
      max-width: 200px;
    }

    .tooltip-hidden {
      opacity: 0;
      visibility: hidden;
      transform: scale(0.8) translateY(10px);
    }

    .tooltip-visible {
      opacity: 1;
      visibility: visible;
      transform: scale(1) translateY(0);
    }

    .tooltip-content {
      padding: 12px 16px;
      text-align: center;
    }

    .tooltip-count {
      font-size: 28px;
      font-weight: 700;
      line-height: 1;
      margin-bottom: 6px;
      display: block;
      background: linear-gradient(45deg, #64b5f6, #42a5f5);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      background-clip: text;
    }

    .tooltip-country {
      font-size: 14px;
      font-weight: 500;
      opacity: 0.9;
      display: block;
      letter-spacing: 0.5px;
    }
  </style>
</head>
<body>

  <div id="mapwrapper" style="width: 100%; position: relative; touch-action: none;">
    <!-- Zoom slider -->
    <div id="zoom-slider-container">
      <input type="range" id="zoom-slider" min="1" max="20" step="0.1" value="1" orient="vertical">
    </div>

    <!-- Tooltip -->
    <div id="map-tooltip" class="tooltip-hidden">
      <div class="tooltip-content">
        <span class="tooltip-count">0</span>
        <span class="tooltip-country">Country</span>
      </div>
    </div>
  </div>
 <!-- Librairie Pan & Zoom -->
  
 

<script type='application/javascript' >
  function adjustButtonState() {
    const isMobile = window.innerWidth <= 1024;
    const callBtn = document.querySelector('.callBtn');
    const iconContainer = document.querySelector('.iconContainer');
    const textContainer = document.querySelector('.textContainer');

    if (isMobile) {
      callBtn.style.width = "110px";
      callBtn.style.borderRadius = "40px";
      callBtn.style.paddingLeft = "10px";

      iconContainer.classList.remove("collapsed-mobile");
      iconContainer.classList.add("expanded-mobile");

      textContainer.classList.remove("collapsed-mobile");
      textContainer.classList.add("expanded-mobile");
    } else {
      callBtn.classList.remove("expanded-desktop");
      callBtn.style.width = "40px";
      callBtn.style.borderRadius = "50%";
      callBtn.style.paddingLeft = "5px";

      iconContainer.classList.remove("expanded-mobile");
      iconContainer.classList.add("collapsed-desktop");

      textContainer.classList.remove("expanded-mobile");
      textContainer.classList.add("collapsed-desktop");
    }
  }

  document.addEventListener("DOMContentLoaded", function () {
    adjustButtonState();
    window.addEventListener("resize", adjustButtonState);

    let lastScrollTop = 0;
    window.addEventListener("scroll", function () {
      const currentScroll = window.scrollY || document.documentElement.scrollTop;
      const callBtn = document.querySelector('.callBtn');

      if (currentScroll > lastScrollTop) {
        callBtn.classList.add("expanded-desktop");
      } else if (currentScroll === 0) {
        callBtn.classList.remove("expanded-desktop");
      }

      lastScrollTop = currentScroll;
    });
  });
</script>
<script type='application/javascript' >
    let panZoomInstance;
    let currentTooltipTimeout;

    document.addEventListener('DOMContentLoaded', () => {
      fetch('[YOUR SVG URL]') // REPLACE HERE THE INTERNAL URL OF MAP SVG FILE
        .then(response => response.text())
        .then(svgContent => {
          const wrapper = document.getElementById('mapwrapper');
          wrapper.insertAdjacentHTML('beforeend', svgContent);

          const svgEl = wrapper.querySelector('svg');
          svgEl.setAttribute('id', 'svg-map');

          panZoomInstance = svgPanZoom('#svg-map', {
            zoomEnabled: true,
            controlIconsEnabled: false,
            mouseWheelZoomEnabled: false,
            preventMouseEventsDefault: false,
            fit: true,
            center: true,
            minZoom: 0.5,
            maxZoom: 20,
            panEnabled: true,
            dblClickZoomEnabled: false 
          });

          panZoomInstance.setOnZoom(() => {
            hideTooltip();
          });
          
          panZoomInstance.setOnPan(() => {
            hideTooltip();
          });

          initializeZoomSlider();

          initializeTooltipSystem(svgEl);
        });
    });

    function initializeTooltipSystem(svgEl) {
      const tooltip = document.getElementById('map-tooltip');
      const tooltipCount = tooltip.querySelector('.tooltip-count');
      const tooltipCountry = tooltip.querySelector('.tooltip-country');

      const paths = svgEl.querySelectorAll('path');
      paths.forEach(path => {
        path.style.fill = 'var(--e-global-color-accent)'; // GET ACCENT COLOR SET IN ELEMENTOR OR REPLACE IF NECESSARY
        path.style.fillOpacity = 0.1;
      });

      const visitsByCountry = {};
      const namesByCountry = {};

      document.querySelectorAll('#countrystats .countrydata').forEach(el => {
        const code = el.querySelector('.countrycode')?.textContent.trim();
        const name = el.querySelector('.countryname')?.textContent.trim() || "Unknown country";
        const count = parseInt(el.querySelector('.countryvisitscount')?.textContent.trim() || "0", 10);

        if (code) {
          visitsByCountry[code] = count;
          namesByCountry[code] = name;
        }
      });

      const maxLog = Math.log10(Math.max(...Object.values(visitsByCountry)) + 1);

      paths.forEach(path => {
        const code = path.getAttribute('id');
        if (!code || !visitsByCountry[code]) return;

        const count = visitsByCountry[code];
        const name = namesByCountry[code] || "Unknown country";

        const opacity = 0.1 + (Math.log10(count + 1) / maxLog) * 0.9;
        path.style.fillOpacity = opacity.toFixed(2);

        setupPathEvents(path, count, name, tooltipCount, tooltipCountry, tooltip);
      });

      let initialDistance = 0;
      let initialZoom = 1;
      let currentlyTouchedPath = null;

      svgEl.addEventListener('touchstart', (e) => {
        if (e.touches.length === 2) {
          e.preventDefault();
          hideTooltip();
          initialDistance = getDistance(e.touches[0], e.touches[1]);
          initialZoom = panZoomInstance.getZoom();
        }
      }, { passive: false });

      svgEl.addEventListener('touchmove', (e) => {
        if (e.touches.length === 2) {
          e.preventDefault();
          const currentDistance = getDistance(e.touches[0], e.touches[1]);
          const zoomFactor = currentDistance / initialDistance;
          const newZoom = initialZoom * zoomFactor;

          if (newZoom >= panZoomInstance.getSizes().minZoom &&
              newZoom <= panZoomInstance.getSizes().maxZoom) {
            panZoomInstance.zoom(newZoom);
          }
        }
      }, { passive: false });

      function getDistance(touch1, touch2) {
        const dx = touch1.clientX - touch2.clientX;
        const dy = touch1.clientY - touch2.clientY;
        return Math.sqrt(dx * dx + dy * dy);
      }

      window.currentlyTouchedPath = currentlyTouchedPath;
    }

    function setupPathEvents(path, count, name, tooltipCount, tooltipCountry, tooltip) {
      // Desktop: survol
      path.addEventListener('mouseenter', (e) => {
        showTooltip(count, name, tooltipCount, tooltipCountry, tooltip, e);
      });

      path.addEventListener('mousemove', (e) => {
        updateTooltipPosition(tooltip, e);
      });

      path.addEventListener('mouseleave', () => {
        hideTooltip();
      });

     
      path.addEventListener('touchstart', (e) => {
        if (e.touches.length !== 1) return;
        if (window.currentlyTouchedPath && window.currentlyTouchedPath !== path) {
          e.preventDefault();
        }
        
        const touch = e.touches[0];
        if (window.currentlyTouchedPath !== path) {
          window.currentlyTouchedPath = path;
          showTooltip(count, name, tooltipCount, tooltipCountry, tooltip, touch);
        }
        if (currentTooltipTimeout) {
          clearTimeout(currentTooltipTimeout);
          currentTooltipTimeout = null;
        }
      }, { passive: false });

      path.addEventListener('touchend', () => {
      });
    }

    function showTooltip(count, name, tooltipCount, tooltipCountry, tooltip, event) {
      if (currentTooltipTimeout) {
        clearTimeout(currentTooltipTimeout);
      }

      tooltipCount.textContent = count;
      tooltipCountry.textContent = name;
      
      tooltip.classList.remove('tooltip-hidden');
      tooltip.classList.add('tooltip-visible');

      if (event) {
        updateTooltipPosition(tooltip, event);
      }
    }

    function updateTooltipPosition(tooltip, event) {
      const wrapper = document.getElementById('mapwrapper');
      const wrapperRect = wrapper.getBoundingClientRect();
      
      let clientX, clientY;
      if (event.touches && event.touches[0]) {
        clientX = event.touches[0].clientX;
        clientY = event.touches[0].clientY;
      } else {
        clientX = event.clientX;
        clientY = event.clientY;
      }

      const x = clientX - wrapperRect.left;
      const y = clientY - wrapperRect.top;

      const tooltipRect = tooltip.getBoundingClientRect();
      const tooltipWidth = tooltipRect.width || 160;
      const tooltipHeight = tooltipRect.height || 80;

      let left = x - (tooltipWidth / 2);
      let top = y - tooltipHeight - 15;

      const padding = 10;
      const wrapperWidth = wrapper.offsetWidth;
      const wrapperHeight = wrapper.offsetHeight;

      if (left < padding) {
        left = padding;
      } else if (left + tooltipWidth > wrapperWidth - padding) {
        left = wrapperWidth - tooltipWidth - padding;
      }

      if (top < padding) {
        top = y + 15;
      }

      if (top + tooltipHeight > wrapperHeight - padding) {
        top = wrapperHeight - tooltipHeight - padding;
      }

      tooltip.style.left = left + 'px';
      tooltip.style.top = top + 'px';
    }

    function hideTooltip() {
      const tooltip = document.getElementById('map-tooltip');
      tooltip.classList.remove('tooltip-visible');
      tooltip.classList.add('tooltip-hidden');
      
      // Réinitialiser le pays actuellement touché
      window.currentlyTouchedPath = null;
    }

    function initializeZoomSlider() {
      const slider = document.getElementById('zoom-slider');
      let isResetting = false;

      slider.addEventListener('input', (e) => {
        if (isResetting) return;
        
        const zoomLevel = parseFloat(e.target.value);

        if (zoomLevel === 1) {
          isResetting = true;
          
          if (panZoomInstance) {
            panZoomInstance.resetZoom();
            panZoomInstance.center();
            panZoomInstance.fit();
          }
          
          setTimeout(() => {
            isResetting = false;
          }, 300);
        } else {
          if (panZoomInstance) {
            panZoomInstance.zoom(zoomLevel);
          }
        }
      });

      if (panZoomInstance) {
        panZoomInstance.setOnZoom((newZoom) => {
          if (!isResetting) {
            slider.value = newZoom;
          }
        });
      }
    }

    function zoomIn() {
      const slider = document.getElementById('zoom-slider');
      const newValue = Math.min(20, parseFloat(slider.value) + 0.5);
      slider.value = newValue;
      slider.dispatchEvent(new Event('input'));
    }

    function zoomOut() {
      const slider = document.getElementById('zoom-slider');
      const newValue = Math.max(0.5, parseFloat(slider.value) - 0.5);
      slider.value = newValue;
      slider.dispatchEvent(new Event('input'));
    }

    function resetZoom() {
      const slider = document.getElementById('zoom-slider');
      slider.value = 1;
      slider.dispatchEvent(new Event('input'));
    }

  </script>
<script type='application/javascript' >
        document.addEventListener('voxel/search-form/init', e => {
            const { app, config, el } = e.detail;

            // Register membership plan filter component (clone of terms filter)
            app.component('filter-membership-plan', {
                template: '#sf-membership-plan-filter',
                name: 'membership-plan-filter',
                props: { filter: Object, repeaterId: String },
                data() {
                    return {
                        value: this.filter.props.selected || {},
                        search: '',
                        firstLabel: '',
                        remainingCount: 0
                    };
                },
                created() {
                    this.firstLabel = this._getFirstLabel();
                    this.remainingCount = this._getRemainingCount();
                },
                methods: {
                    saveValue() {
                        const newValue = this.isFilled() ? Object.keys(this.value).join(',') : null;
                        console.log('Membership Plan Filter - saveValue()');
                        console.log('  this.value:', this.value);
                        console.log('  Keys:', Object.keys(this.value));
                        console.log('  New filter value:', newValue);
                        this.filter.value = newValue;
                        this.firstLabel = this._getFirstLabel();
                        this.remainingCount = this._getRemainingCount();
                    },
                    onSave() {
                        console.log('Membership Plan Filter - onSave() called');
                        this.saveValue();
                        console.log('After saveValue, filter.value:', this.filter.value);
                        this.$refs.formGroup?.blur();
                    },
                    onBlur() {
                        console.log('Membership Plan Filter - onBlur() called');
                        this.saveValue();
                    },
                    onClear() {
                        this.value = {};
                        this.search = '';
                        this.$refs.searchInput?.focus();
                    },
                    isFilled() {
                        return Object.keys(this.value).length > 0;
                    },
                    _getFirstLabel() {
                        return Object.values(this.value)[0]?.label || '';
                    },
                    _getRemainingCount() {
                        return Object.values(this.value).length - 1;
                    },
                    selectPlan(plan) {
                        if (this.value[plan.key]) {
                            delete this.value[plan.key];
                        } else {
                            this.value[plan.key] = plan;
                        }

                        // For buttons display, save and submit immediately
                        if (this.filter.props.display_as === 'buttons') {
                            this.saveValue();
                        }
                    },
                    onReset() {
                        this.search = '';
                        this.value = {};

                        // Restore from resets_to if available (handles default value on reset)
                        if (this.filter.resets_to && this.filter.resets_to.length) {
                            // resets_to is an array of plan keys
                            let resetsTo = Array.isArray(this.filter.resets_to)
                                ? this.filter.resets_to
                                : this.filter.resets_to.split(',').map(k => k.trim());

                            resetsTo.forEach(key => {
                                if (this.filter.props.choices[key]) {
                                    this.value[key] = this.filter.props.choices[key];
                                }
                            });
                        }

                        this.saveValue();
                    }
                },
                computed: {
                    filteredPlans() {
                        const plans = Object.values(this.filter.props.choices);
                        if (!this.search.trim().length) {
                            return plans;
                        }
                        const searchTerm = this.search.trim().toLowerCase();
                        return plans.filter(plan =>
                            plan.label.toLowerCase().includes(searchTerm)
                        );
                    },
                    isPending() {
                        return false;
                    }
                }
            });

            // Register listing plan filter component
            app.component('filter-listing-plan', {
                template: '#sf-listing-plan-filter',
                name: 'listing-plan-filter',
                props: { filter: Object, repeaterId: String },
                data() {
                    return {
                        value: this.filter.props.selected || {},
                        search: '',
                        firstLabel: '',
                        remainingCount: 0
                    };
                },
                created() {
                    this.firstLabel = this._getFirstLabel();
                    this.remainingCount = this._getRemainingCount();
                },
                methods: {
                    saveValue() {
                        const newValue = this.isFilled() ? Object.keys(this.value).join(',') : null;
                        this.filter.value = newValue;
                        this.firstLabel = this._getFirstLabel();
                        this.remainingCount = this._getRemainingCount();
                    },
                    onSave() {
                        this.saveValue();
                        this.$refs.formGroup?.blur();
                    },
                    onBlur() {
                        this.saveValue();
                    },
                    onClear() {
                        this.value = {};
                        this.search = '';
                        this.$refs.searchInput?.focus();
                    },
                    isFilled() {
                        return Object.keys(this.value).length > 0;
                    },
                    _getFirstLabel() {
                        return Object.values(this.value)[0]?.label || '';
                    },
                    _getRemainingCount() {
                        return Object.values(this.value).length - 1;
                    },
                    selectPlan(plan) {
                        if (this.value[plan.key]) {
                            delete this.value[plan.key];
                        } else {
                            this.value[plan.key] = plan;
                        }

                        // For buttons display, save and submit immediately
                        if (this.filter.props.display_as === 'buttons') {
                            this.saveValue();
                        }
                    },
                    onReset() {
                        this.search = '';
                        this.value = {};

                        // Restore from resets_to if available (handles default value on reset)
                        if (this.filter.resets_to && this.filter.resets_to.length) {
                            let resetsTo = Array.isArray(this.filter.resets_to)
                                ? this.filter.resets_to
                                : this.filter.resets_to.split(',').map(k => k.trim());

                            resetsTo.forEach(key => {
                                if (this.filter.props.choices[key]) {
                                    this.value[key] = this.filter.props.choices[key];
                                }
                            });
                        }

                        this.saveValue();
                    }
                },
                computed: {
                    filteredPlans() {
                        const plans = Object.values(this.filter.props.choices);
                        if (!this.search.trim().length) {
                            return plans;
                        }
                        const searchTerm = this.search.trim().toLowerCase();
                        return plans.filter(plan =>
                            plan.label.toLowerCase().includes(searchTerm)
                        );
                    },
                    isPending() {
                        return false;
                    }
                }
            });

            // Register user role filter component (clone of membership plan filter)
            app.component('filter-user-role', {
                template: '#sf-user-role-filter',
                name: 'user-role-filter',
                props: { filter: Object, repeaterId: String },
                data() {
                    return {
                        value: {},
                        search: '',
                        firstLabel: '',
                        remainingCount: 0
                    };
                },
                created() {
                    // Initialize value from filter.value (comma-separated string)
                    console.log('User Role Filter - created()');
                    console.log('  filter.value:', this.filter.value);
                    console.log('  filter.props.choices:', this.filter.props.choices);

                    if (this.filter.value) {
                        const roleKeys = this.filter.value.split(',').map(k => k.trim());
                        console.log('  roleKeys:', roleKeys);
                        roleKeys.forEach(key => {
                            if (this.filter.props.choices[key]) {
                                this.value[key] = this.filter.props.choices[key];
                            }
                        });
                    }
                    console.log('  final this.value:', this.value);
                    this.firstLabel = this._getFirstLabel();
                    this.remainingCount = this._getRemainingCount();
                },
                methods: {
                    saveValue() {
                        const newValue = this.isFilled() ? Object.keys(this.value).join(',') : null;
                        this.filter.value = newValue;
                        this.firstLabel = this._getFirstLabel();
                        this.remainingCount = this._getRemainingCount();
                    },
                    onSave() {
                        this.saveValue();
                        this.$refs.formGroup?.blur();
                    },
                    onBlur() {
                        this.saveValue();
                    },
                    onClear() {
                        this.value = {};
                        this.search = '';
                        this.$refs.searchInput?.focus();
                    },
                    isFilled() {
                        return Object.keys(this.value).length > 0;
                    },
                    _getFirstLabel() {
                        return Object.values(this.value)[0]?.label || '';
                    },
                    _getRemainingCount() {
                        return Object.values(this.value).length - 1;
                    },
                    selectRole(role) {
                        if (this.value[role.key]) {
                            delete this.value[role.key];
                        } else {
                            this.value[role.key] = role;
                        }

                        // For buttons display, save and submit immediately
                        if (this.filter.props.display_as === 'buttons') {
                            this.saveValue();
                        }
                    },
                    onReset() {
                        this.search = '';
                        this.value = {};

                        // Restore from resets_to if available (handles default value on reset)
                        if (this.filter.resets_to && this.filter.resets_to.length) {
                            let resetsTo = Array.isArray(this.filter.resets_to)
                                ? this.filter.resets_to
                                : this.filter.resets_to.split(',').map(k => k.trim());

                            resetsTo.forEach(key => {
                                if (this.filter.props.choices[key]) {
                                    this.value[key] = this.filter.props.choices[key];
                                }
                            });
                        }

                        this.saveValue();
                    }
                },
                computed: {
                    filteredRoles() {
                        const roles = Object.values(this.filter.props.choices);
                        if (!this.search.trim().length) {
                            return roles;
                        }
                        const searchTerm = this.search.trim().toLowerCase();
                        return roles.filter(role =>
                            role.label.toLowerCase().includes(searchTerm)
                        );
                    },
                    isPending() {
                        return false;
                    }
                }
            });
        });
        </script>
<script type='application/javascript' >
				const lazyloadRunObserver = () => {
					const lazyloadBackgrounds = document.querySelectorAll( `.e-con.e-parent:not(.e-lazyloaded)` );
					const lazyloadBackgroundObserver = new IntersectionObserver( ( entries ) => {
						entries.forEach( ( entry ) => {
							if ( entry.isIntersecting ) {
								let lazyloadBackground = entry.target;
								if( lazyloadBackground ) {
									lazyloadBackground.classList.add( 'e-lazyloaded' );
								}
								lazyloadBackgroundObserver.unobserve( entry.target );
							}
						});
					}, { rootMargin: '200px 0px 200px 0px' } );
					lazyloadBackgrounds.forEach( ( lazyloadBackground ) => {
						lazyloadBackgroundObserver.observe( lazyloadBackground );
					} );
				};
				const events = [
					'DOMContentLoaded',
					'elementor/lazyload/observe',
				];
				events.forEach( ( event ) => {
					document.addEventListener( event, lazyloadRunObserver );
				} );
			</script>
<script type='application/javascript' >
        (function() {
            'use strict';

            // Wait for DOM to be ready
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', initCSSInjector);
            } else {
                initCSSInjector();
            }

            function initCSSInjector() {
                // Find all widgets with CSS data
                var widgets = document.querySelectorAll('[data-vt-css]');

                widgets.forEach(function(widget) {
                    var cssData = JSON.parse(widget.getAttribute('data-vt-css'));
                    if (!cssData || !cssData.length) return;

                    // Find the appropriate items based on widget type
                    var items = null;

                    // Navbar widget - select all direct li children of .ts-nav
                    if (widget.classList.contains('elementor-widget-ts-navbar')) {
                        var navContainer = widget.querySelector('.ts-nav');
                        if (navContainer) {
                            items = navContainer.querySelectorAll(':scope > li');
                        }
                    }
                    // User Bar widget - select all direct li children of .user-area-menu
                    else if (widget.classList.contains('elementor-widget-ts-user-bar')) {
                        var userMenu = widget.querySelector('.user-area-menu');
                        if (userMenu) {
                            items = userMenu.querySelectorAll(':scope > li');
                        }
                    }
                    // Advanced List widget - select all direct li children of .ts-advanced-list
                    else if (widget.classList.contains('elementor-widget-ts-advanced-list')) {
                        var actionList = widget.querySelector('.ts-advanced-list');
                        if (actionList) {
                            items = actionList.querySelectorAll(':scope > li');
                        }
                    }

                    if (!items || items.length === 0) return;

                    // Apply CSS classes and IDs to matching items
                    cssData.forEach(function(data) {
                        if (items[data.index]) {
                            if (data.class) {
                                var classes = data.class.split(' ');
                                classes.forEach(function(cls) {
                                    if (cls.trim()) {
                                        items[data.index].classList.add(cls.trim());
                                    }
                                });
                            }
                            if (data.id) {
                                items[data.index].id = data.id;
                            }
                        }
                    });
                });
            }
        })();
        </script>
<script type='application/javascript' >
var st_ajax_object = {"ajax_url":"https://online.nc/wp-admin/admin-ajax.php"};
//# sourceURL=st-trigger-button-script-js-extra
</script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/suretriggers/assets/js/st-trigger-button.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/themes/voxel/assets/vendor/vue/vue.prod.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/themes/voxel/assets/dist/commons.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/elementor/assets/js/webpack.runtime.min.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/elementor/assets/js/frontend-modules.min.js'></script>
<script type='application/javascript' src='https://online.nc/wp-includes/js/jquery/ui/core.min.js'></script>
<script type='application/javascript' >
var elementorFrontendConfig = {"environmentMode":{"edit":false,"wpPreview":false,"isScriptDebug":false},"i18n":{"shareOnFacebook":"Partager sur Facebook","shareOnTwitter":"Partager sur Twitter","pinIt":"L\u2019\u00e9pingler","download":"T\u00e9l\u00e9charger","downloadImage":"T\u00e9l\u00e9charger une image","fullscreen":"Plein \u00e9cran","zoom":"Zoom","share":"Partager","playVideo":"Lire la vid\u00e9o","previous":"Pr\u00e9c\u00e9dent","next":"Suivant","close":"Fermer","a11yCarouselPrevSlideMessage":"Diapositive pr\u00e9c\u00e9dente","a11yCarouselNextSlideMessage":"Diapositive suivante","a11yCarouselFirstSlideMessage":"Ceci est la premi\u00e8re diapositive","a11yCarouselLastSlideMessage":"Ceci est la derni\u00e8re diapositive","a11yCarouselPaginationBulletMessage":"Aller \u00e0 la diapositive"},"is_rtl":false,"breakpoints":{"xs":0,"sm":480,"md":768,"lg":1025,"xl":1440,"xxl":1600},"responsive":{"breakpoints":{"mobile":{"label":"Portrait mobile","value":767,"default_value":767,"direction":"max","is_enabled":true},"mobile_extra":{"label":"Mobile Paysage","value":880,"default_value":880,"direction":"max","is_enabled":false},"tablet":{"label":"Tablette en mode portrait","value":1024,"default_value":1024,"direction":"max","is_enabled":true},"tablet_extra":{"label":"Tablette en mode paysage","value":1200,"default_value":1200,"direction":"max","is_enabled":false},"laptop":{"label":"Portable","value":1366,"default_value":1366,"direction":"max","is_enabled":false},"widescreen":{"label":"\u00c9cran large","value":2400,"default_value":2400,"direction":"min","is_enabled":false}},"hasCustomBreakpoints":false},"version":"3.33.4","is_static":false,"experimentalFeatures":{"e_font_icon_svg":true,"container":true,"e_optimized_markup":true,"theme_builder_v2":true,"nested-elements":true,"home_screen":true,"global_classes_should_enforce_capabilities":true,"e_variables":true,"cloud-library":true,"e_opt_in_v4_page":true,"import-export-customization":true,"e_pro_variables":true},"urls":{"assets":"https:\/\/online.nc\/wp-content\/plugins\/elementor\/assets\/","ajaxurl":"https:\/\/online.nc\/wp-admin\/admin-ajax.php","uploadUrl":"https:\/\/online.nc\/wp-content\/uploads"},"nonces":{"floatingButtonsClickTracking":"c65832f1b9"},"swiperClass":"swiper","settings":{"page":[],"editorPreferences":[]},"kit":{"body_background_background":"classic","active_breakpoints":["viewport_mobile","viewport_tablet"],"global_image_lightbox":"yes","lightbox_enable_fullscreen":"yes","lightbox_enable_zoom":"yes","lightbox_description_src":"description"},"post":{"id":15755,"title":"Dynamic%20world%20map%20with%20Voxel%20stats%20%E2%80%A2%20Online%20NC","excerpt":"","featuredImage":"https:\/\/online.nc\/wp-content\/uploads\/2025\/08\/dynamic-world-map-Voxel-stats-1024x536.webp"}};
//# sourceURL=elementor-frontend-js-before
</script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/elementor/assets/js/frontend.min.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/elementor/assets/lib/swiper/v8/swiper.min.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/elementor-pro/assets/lib/sticky/jquery.sticky.min.js'></script>
<script type='application/javascript' >
var complianz = {"prefix":"cmplz_","user_banner_id":"1","set_cookies":[],"block_ajax_content":"","banner_version":"57","version":"7.4.4.1","store_consent":"","do_not_track_enabled":"1","consenttype":"optin","region":"eu","geoip":"","dismiss_timeout":"","disable_cookiebanner":"","soft_cookiewall":"","dismiss_on_scroll":"","cookie_expiry":"365","url":"https://online.nc/wp-json/complianz/v1/","locale":"lang=fr&locale=fr_FR","set_cookies_on_root":"","cookie_domain":"","current_policy_id":"37","cookie_path":"/","categories":{"statistics":"statistiques","marketing":"marketing"},"tcf_active":"","placeholdertext":"Cliquez pour accepter les cookies {category} et activer ce contenu","css_file":"https://online.nc/wp-content/uploads/complianz/css/banner-{banner_id}-{type}.css?v=57","page_links":{"eu":{"cookie-statement":{"title":"Politique de cookies","url":"https://online.nc/cookies/"}}},"tm_categories":"","forceEnableStats":"","preview":"","clean_cookies":"","aria_label":"Cliquez pour accepter les cookies {category} et activer ce contenu"};
//# sourceURL=cmplz-cookiebanner-js-extra
</script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/complianz-gdpr/cookiebanner/js/complianz.min.js'></script>
<script type='application/javascript' >
    
		if ('undefined' != typeof window.jQuery) {
			jQuery(document).ready(function ($) {
				$(document).on('elementor/popup/show', () => {
					let rev_cats = cmplz_categories.reverse();
					for (let key in rev_cats) {
						if (rev_cats.hasOwnProperty(key)) {
							let category = cmplz_categories[key];
							if (cmplz_has_consent(category)) {
								document.querySelectorAll('[data-category="' + category + '"]').forEach(obj => {
									cmplz_remove_placeholder(obj);
								});
							}
						}
					}

					let services = cmplz_get_services_on_page();
					for (let key in services) {
						if (services.hasOwnProperty(key)) {
							let service = services[key].service;
							let category = services[key].category;
							if (cmplz_has_service_consent(service, category)) {
								document.querySelectorAll('[data-service="' + service + '"]').forEach(obj => {
									cmplz_remove_placeholder(obj);
								});
							}
						}
					}
				});
			});
		}
    
    
		
			document.addEventListener("cmplz_enable_category", function(consentData) {
				var category = consentData.detail.category;
				var services = consentData.detail.services;
				var blockedContentContainers = [];
				let selectorVideo = '.cmplz-elementor-widget-video-playlist[data-category="'+category+'"],.elementor-widget-video[data-category="'+category+'"]';
				let selectorGeneric = '[data-cmplz-elementor-href][data-category="'+category+'"]';
				for (var skey in services) {
					if (services.hasOwnProperty(skey)) {
						let service = skey;
						selectorVideo +=',.cmplz-elementor-widget-video-playlist[data-service="'+service+'"],.elementor-widget-video[data-service="'+service+'"]';
						selectorGeneric +=',[data-cmplz-elementor-href][data-service="'+service+'"]';
					}
				}
				document.querySelectorAll(selectorVideo).forEach(obj => {
					let elementService = obj.getAttribute('data-service');
					if ( cmplz_is_service_denied(elementService) ) {
						return;
					}
					if (obj.classList.contains('cmplz-elementor-activated')) return;
					obj.classList.add('cmplz-elementor-activated');

					if ( obj.hasAttribute('data-cmplz_elementor_widget_type') ){
						let attr = obj.getAttribute('data-cmplz_elementor_widget_type');
						obj.classList.removeAttribute('data-cmplz_elementor_widget_type');
						obj.classList.setAttribute('data-widget_type', attr);
					}
					if (obj.classList.contains('cmplz-elementor-widget-video-playlist')) {
						obj.classList.remove('cmplz-elementor-widget-video-playlist');
						obj.classList.add('elementor-widget-video-playlist');
					}
					obj.setAttribute('data-settings', obj.getAttribute('data-cmplz-elementor-settings'));
					blockedContentContainers.push(obj);
				});

				document.querySelectorAll(selectorGeneric).forEach(obj => {
					let elementService = obj.getAttribute('data-service');
					if ( cmplz_is_service_denied(elementService) ) {
						return;
					}
					if (obj.classList.contains('cmplz-elementor-activated')) return;

					if (obj.classList.contains('cmplz-fb-video')) {
						obj.classList.remove('cmplz-fb-video');
						obj.classList.add('fb-video');
					}

					obj.classList.add('cmplz-elementor-activated');
					obj.setAttribute('data-href', obj.getAttribute('data-cmplz-elementor-href'));
					blockedContentContainers.push(obj.closest('.elementor-widget'));
				});

				/**
				 * Trigger the widgets in Elementor
				 */
				for (var key in blockedContentContainers) {
					if (blockedContentContainers.hasOwnProperty(key) && blockedContentContainers[key] !== undefined) {
						let blockedContentContainer = blockedContentContainers[key];
						if (elementorFrontend.elementsHandler) {
							elementorFrontend.elementsHandler.runReadyTrigger(blockedContentContainer)
						}
						var cssIndex = blockedContentContainer.getAttribute('data-placeholder_class_index');
						blockedContentContainer.classList.remove('cmplz-blocked-content-container');
						blockedContentContainer.classList.remove('cmplz-placeholder-' + cssIndex);
					}
				}

			});
		
		
//# sourceURL=cmplz-cookiebanner-js-after
</script>
<script type='application/javascript' src='https://online.nc/wp-content/themes/voxel/assets/dist/quick-search.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/themes/voxel/assets/dist/user-bar.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/themes/voxel/assets/dist/share.js'></script>
<script type='application/javascript' >
window.VX_Track = {"done":false,"post_type":"coding","post_id":15755,"providers":[{"url":"https:\/\/get.geojs.io\/v1\/ip\/country.json","prop":"country"}],"index":0}
//# sourceURL=vx%3Avisit-tracker.js-js-before
</script>
<script type='application/javascript' src='https://online.nc/wp-content/themes/voxel/assets/dist/visit-tracker.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/elementor-pro/assets/js/webpack-pro.runtime.min.js'></script>
<script type='application/javascript' src='https://online.nc/wp-includes/js/dist/hooks.min.js'></script>
<script type='application/javascript' src='https://online.nc/wp-includes/js/dist/i18n.min.js'></script>
<script type='application/javascript' >
wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ 'ltr' ] } );
//# sourceURL=wp-i18n-js-after
</script>
<script type='application/javascript' >
var ElementorProFrontendConfig = {"ajaxurl":"https:\/\/online.nc\/wp-admin\/admin-ajax.php","nonce":"049c0769e4","urls":{"assets":"https:\/\/online.nc\/wp-content\/plugins\/elementor-pro\/assets\/","rest":"https:\/\/online.nc\/wp-json\/"},"settings":{"lazy_load_background_images":true},"popup":{"hasPopUps":true},"shareButtonsNetworks":{"facebook":{"title":"Facebook","has_counter":true},"twitter":{"title":"Twitter"},"linkedin":{"title":"LinkedIn","has_counter":true},"pinterest":{"title":"Pinterest","has_counter":true},"reddit":{"title":"Reddit","has_counter":true},"vk":{"title":"VK","has_counter":true},"odnoklassniki":{"title":"OK","has_counter":true},"tumblr":{"title":"Tumblr"},"digg":{"title":"Digg"},"skype":{"title":"Skype"},"stumbleupon":{"title":"StumbleUpon","has_counter":true},"mix":{"title":"Mix"},"telegram":{"title":"Telegram"},"pocket":{"title":"Pocket","has_counter":true},"xing":{"title":"XING","has_counter":true},"whatsapp":{"title":"WhatsApp"},"email":{"title":"Email"},"print":{"title":"Print"},"x-twitter":{"title":"X"},"threads":{"title":"Threads"}},"facebook_sdk":{"lang":"fr_FR","app_id":""},"lottie":{"defaultAnimationUrl":"https:\/\/online.nc\/wp-content\/plugins\/elementor-pro\/modules\/lottie\/assets\/animations\/default.json"}};
//# sourceURL=elementor-pro-frontend-js-before
</script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/elementor-pro/assets/js/frontend.min.js'></script>
<script type='application/javascript' src='https://online.nc/wp-content/plugins/elementor-pro/assets/js/elements-handlers.min.js'></script>
</body>
</html>


				
			
  • Kevin Ekelmans Presario