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.

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.
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 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
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="<b>Plot Size</b>: 343 sqm<br /><b>Internal Space</b>: 153 sqm<br /><b>Bedrooms</b>: 3<br /><b>Bathrooms</b>: 4<br /><b>Roof Garden</b>: 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 viadata-bs-content
. If your content includes special characters like<
and>
, make sure to escape them as<
and>
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