Back to Blog

Building an interactive masterplan for Sentiero Luxury Villas

Discover how I created an interactive masterplan for Sentiero Luxury Villas, blending architectural visualization with web interactivity to redefine real estate exploration.

By Marios Sofokleous
Published ·Updated
Interactive masterplan view showing a high-resolution site visualization with zoom, pan, and property detail popovers.

Introduction

For the Sentiero Luxury Villas landing page, I developed an interactive masterplan that blends architectural visualization with web interactivity. This feature showcases a luxury development of 26 off-plan villas, enabling users to explore property layouts, check availability, and view detailed specifications—all within an engaging, intuitive interface.

In this article, I'll walk through the process of creating this masterplan, highlight its core features, and discuss how it elevates both user experience and real estate marketing.

Vision and objectives

The interactive masterplan aims to give potential buyers a seamless, visually rich way to explore villa details while appreciating the overall scale and layout of the development.

Key objectives:

  • Present detailed property information in an accessible, interactive format.
  • Use color to clearly communicate the availability of each property.
  • Ensure responsiveness and usability across all device types.

Implementation layers

These layers form the technical foundation of the interactive masterplan.

1. Base layer: realistic visualization

The base layer is a composite image that merges a real aerial photograph of the plot with digital renderings of the villas. This grounds the masterplan in its real-world context and provides a visually compelling starting point.

Aerial photograph of the Sentiero Luxury Villas site with digitally rendered villas overlaid, illustrating the full development within its actual landscape. Aerial photograph of the Sentiero Luxury Villas site with digitally rendered villas overlaid, illustrating the full development within its actual landscape.

2. Secondary layer: focus enhancement

The secondary layer is a toggleable overlay that darkens the background. This makes the villas stand out and helps users concentrate on property details without distractions.

SVG with a single filled path covering the masterplan background, excluding villa shapes. SVG overlay of the masterplan background, with villa areas cut out.

3. Property layer: interactive SVG

Each villa is represented by a clickable SVG path. Users can interact with these paths to reveal detailed information about each property, such as reference number, plot size, internal space, bedrooms, bathrooms, and roof garden availability.

  • Reference number
  • Plot size
  • Internal space
  • Bedrooms and bathrooms
  • Roof garden availability

SVG diagram of Sentiero Luxury Villas, where each property is a distinct path with border and fill. Interactive SVG masterplan with each villa as a separate, bordered and filled path.

Features

1. Property popovers

Clicking or tapping on a villa brings up a popover with detailed specifications, including reference number, plot size, internal space, number of bedrooms and bathrooms, and roof garden availability. Only one popover can be visible at a time to avoid clutter and improve clarity.

2. Color-coded availability

Villa availability is visually represented using color coding:

  • Green: Available
  • Red: Sold

A toggle control lets users switch between the availability view and the standard view, providing flexibility in how information is displayed.

3. Zoom and panning controls

Users can zoom in, zoom out, and pan across the masterplan, making navigation smooth and intuitive—especially on touch devices. This ensures that users can explore the entire development easily, regardless of their device or viewport size.

4. Interactive focus mode

Focus mode allows users to toggle the secondary overlay, dimming the background and making the villas visually pop. This feature is especially useful for users who want to concentrate on the properties themselves without distraction from the surrounding context. The focus mode can be easily toggled via a dedicated control button, enhancing usability for both desktop and mobile users.

Implementation details

Libraries used

  • Panzoom: For panning and zooming functionality.
  • Bootstrap: For interactive UI components, including popovers.

HTML structure

The masterplan uses three main layers:

  • Base and secondary layers: embedded as <img> elements.
  • Property layer: inline SVG for precise control and interactivity.

Control buttons for zoom, focus, and availability toggles are included.

<!-- Start masterplan -->
<div id="masterplan" class="masterplan">
  <div data-masterplan-container class="masterplan__container">
    <!-- Base layer -->
    <img class="masterplan__base-layer" src="/base-layer.jpg" width="4032" height="2268" alt="" />
    
    <!-- Secondary layer -->
    <img data-masterplan-secondary-layer class="masterplan__secondary-layer opacity-50" src="/secondary-layer.svg" width="4032" height="2268" alt="" />
    
    <!-- Property layer -->
    <svg class="masterplan__property-layer" width="2544" height="1432" viewBox="0 0 2544 1432" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path tabindex="0" role="button" class="masterplan__property"
        data-masterplan-property=""
        data-masterplan-status="sold"
        data-bs-html="true"
        data-bs-toggle="popover"
        title="Ref. D01"
        data-bs-content="&lt;b&gt;Plot Size&lt;/b&gt;: 343 sqm&lt;br /&gt;&lt;b&gt;Internal Space&lt;/b&gt;: 153 sqm&lt;br /&gt;&lt;b&gt;Bedrooms&lt;/b&gt;: 3&lt;br /&gt;&lt;b&gt;Bathrooms&lt;/b&gt;: 4&lt;br /&gt;&lt;b&gt;Roof Garden&lt;/b&gt;: No"
        d="M2384.34..."
        fill="#00B8CE"
        fill-opacity="0.2"
        stroke="#00B8CE"
        stroke-width="4"
      />
      <!-- Additional paths for other properties -->
    </svg>
  </div>

  <!-- Controls -->
  <button data-masterplan-zoom="in" class="masterplan__control masterplan__control_zoom-in" aria-label="Zoom in">
    <svg>...</svg>
  </button>
  <button data-masterplan-zoom="out" class="masterplan__control masterplan__control_zoom-out" aria-label="Zoom out">
    <svg>...</svg>
  </button>
  <button data-masterplan-focus-toggle class="masterplan__control masterplan__control_focus" aria-label="Exit focus mode">
    <span data-masterplan-focus-on-icon>
      <svg>...</svg>
    </span>
    <span data-masterplan-focus-off-icon class="d-none">
      <svg>...</svg>
    </span>
  </button>
  <button data-masterplan-availability-toggle class="masterplan__control masterplan__control_availability" aria-label="Switch to availability mode">
    <span data-masterplan-availability-on-icon class="d-none">
      <svg>...</svg>
    </span>
    <span data-masterplan-availability-off-icon>
      <svg>...</svg>
    </span>
  </button>
</div>
  • Popover titles are set via the title attribute, and popover contents are set via data-bs-content. If your content includes special characters like < and >, make sure to escape them as &lt; and &gt; respectively to ensure correct rendering.
  • Property availability is controlled via the data-masterplan-status attribute, which should be set to either 'sold' or 'available' for each property element.

JavaScript functionality

Importing the Bootstrap Popover component

Assuming you're using a modern build tool such as Vite or Webpack, you can directly import the Bootstrap Popover component in your JavaScript like so:

import { Popover } from 'bootstrap';

Panzoom initialization

const masterplan = document.getElementById('masterplan');
const masterplanContainer = masterplan.querySelector('[data-masterplan-container]');
const panzoom = Panzoom(masterplanContainer, {
  maxScale: 2,
  minScale: 1,
  contain: 'outside',
  cursor: 'grab'
});

Zoom controls

masterplan.querySelectorAll('[data-masterplan-zoom="in"], [data-masterplan-zoom="out"]').forEach(button => {
  button.addEventListener('click', function () {
    const zoomDirection = this.getAttribute('data-masterplan-zoom');
    if (zoomDirection === 'in') panzoom.zoomIn();
    else if (zoomDirection === 'out') panzoom.zoomOut();
  });
});

Focus mode toggle

const secondary = masterplan.querySelector('[data-masterplan-secondary-layer]');
const focusToggle = masterplan.querySelector('[data-masterplan-focus-toggle]');
const focusOnIcon = masterplan.querySelector('[data-masterplan-focus-on-icon]');
const focusOffIcon = masterplan.querySelector('[data-masterplan-focus-off-icon]');

focusToggle.addEventListener('click', function () {
  const isFocusMode = focusOffIcon.classList.contains('d-none');
  if (isFocusMode) {
    focusOffIcon.classList.remove('d-none');
    focusOnIcon.classList.add('d-none');
    secondary.classList.replace('opacity-50', 'opacity-0');
    focusToggle.setAttribute('aria-label', 'Switch to focus mode');
  } else {
    focusOnIcon.classList.remove('d-none');
    focusOffIcon.classList.add('d-none');
    secondary.classList.replace('opacity-0', 'opacity-50');
    focusToggle.setAttribute('aria-label', 'Exit focus mode');
  }
});

Popover initialization (Popper.js)

const properties = masterplan.querySelectorAll('[data-masterplan-property]');
const popoverList = Array.from(properties).map(property => new Popover(property, {
  trigger: 'click',
  placement: 'top',
}));

Popover management

  • Hide all popovers on zoom (to prevent misalignment):
masterplanContainer.addEventListener('panzoomchange', function () {
  popoverList.forEach(function (popover) {
    if (popover._isShown()) popover.hide();
  });
});
  • Only one popover visible at a time:
properties.forEach(property => {
  property.addEventListener('click', function () {
    popoverList.forEach(popover => {
      if (popover._element !== property) popover.hide();
    });
  });
});
  • Add/remove 'active' class on popover show/hide:
function handlePopoverShow(event) {
  event.target.classList.add('active');
}

function handlePopoverHide(event) {
  event.target.classList.remove('active');
}

properties.forEach(property => {
  property.addEventListener('show.bs.popover', handlePopoverShow);
  property.addEventListener('hide.bs.popover', handlePopoverHide);
});

Availability toggle

const availabilityToggle = masterplan.querySelector('[data-masterplan-availability-toggle]');
const availabilityOnIcon = masterplan.querySelector('[data-masterplan-availability-on-icon]');
const availabilityOffIcon = masterplan.querySelector('[data-masterplan-availability-off-icon]');

availabilityToggle.addEventListener('click', function () {
  const isAvailabilityMode = availabilityOffIcon.classList.contains('d-none');
  if (isAvailabilityMode) {
    properties.forEach(property => {
      const status = property.getAttribute('data-masterplan-status');
      if (status === 'available') property.classList.remove('masterplan__property_available');
      if (status === 'sold') property.classList.remove('masterplan__property_sold');
    });
    availabilityOffIcon.classList.remove('d-none');
    availabilityOnIcon.classList.add('d-none');
    availabilityToggle.setAttribute('aria-label', 'Switch to availability mode');
  } else {
    properties.forEach(property => {
      const status = property.getAttribute('data-masterplan-status');
      if (status === 'available') property.classList.add('masterplan__property_available');
      if (status === 'sold') property.classList.add('masterplan__property_sold');
    });
    availabilityOnIcon.classList.remove('d-none');
    availabilityOffIcon.classList.add('d-none');
    availabilityToggle.setAttribute('aria-label', 'Exit availability mode');
  }
});

Styling

// Import all of Bootstrap's CSS
@import "bootstrap/scss/bootstrap";

.masterplan {
  position: relative;

  &__container {
    position: relative;
  }

  &__property-layer,
  &__secondary-layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
  }

  &__property {
    fill: none;
    stroke: none;
    pointer-events: all;
    cursor: pointer;
    outline: none;

    &:hover {
      stroke: $primary;
    }

    &:focus {
      stroke: blue;
    }

    &.active {
      fill: $primary;
      fill-opacity: 0.5;
      stroke: $primary;
    }

    &_sold {
      fill: #dc3545;
      fill-opacity: 0.5;
    }

    &_available {
      fill: #198754;
      fill-opacity: 0.5;
    }
  }

  &__control {
    position: absolute;
    background-color: #fff;
    border: 1px solid rgba(0, 0, 0, 0.2);
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;

    svg {
      display: block;
      fill: rgba(0, 0, 0, 0.75);
      width: 24px;
      height: 24px;
    }

    &:hover {
      svg {
        fill: rgba(0, 0, 0, 1);
      }
    }

    &_zoom-in {
      bottom: 44px;
      right: 12px;
    }

    &_zoom-out {
      bottom: 12px;
      right: 12px;
    }

    &_focus {
      bottom: 88px;
      right: 12px;
    }

    &_availability {
      bottom: 132px;
      right: 12px;
    }
  }
}

Conclusion

The interactive masterplan for Sentiero Luxury Villas combines real-world visuals, SVG interactivity, and modern web libraries to deliver a rich, intuitive experience for users exploring luxury real estate. By layering imagery, interactivity, and accessibility, this approach not only enhances user engagement but also sets a new standard for digital real estate presentations.


Interested in seeing this implementation live? Check out the interactive masterplan on the Sentiero Luxury Villas website to experience it firsthand, or explore my other projects. Let's connect on LinkedIn to discuss how I can bring innovative solutions to your team!


Share this page
Back to Blog

Let's connect

Interested in adding me to your development team?