Overview
Ficha Dubai supports 360° virtual tour integration, allowing property listings to include links to immersive virtual tour experiences. The tour button is strategically displayed on the main gallery image when available.
Property Data Structure
The 360° tour URL is stored in the property data:
const normalizeProperty = ( raw ) => {
const property = raw ?. property || raw ?. data ?. property || raw ;
// ... other properties
return {
titulo: property ?. titulo ,
descripcion: property ?. descripcion ,
url_360: property ?. url_360 , // 360° tour URL
images ,
// ... other fields
};
};
Code location: app.js:571-611
The url_360 property contains the full URL to an external 360° virtual tour service (e.g., Matterport, Kuula, or custom tour platforms).
Display Logic
The 360° tour button is displayed conditionally in the populatePage() function:
const populatePage = ( data ) => {
const property = normalizeProperty ( data );
// ... other rendering code
// Handle 360 tour button (main gallery)
const btn360Container = $ ( "#btn-360-container" );
const tour360 = $ ( "#property-360" );
const tour360Btn = $ ( "#property-360-btn" );
if ( property . url_360 ) {
if ( btn360Container ) {
btn360Container . style . display = "flex" ;
btn360Container . dataset . has360 = "true" ;
}
if ( tour360 ) tour360 . href = property . url_360 ;
if ( tour360Btn ) {
tour360Btn . href = property . url_360 ;
tour360Btn . style . display = "inline-flex" ;
}
} else {
if ( btn360Container ) {
btn360Container . style . display = "none" ;
btn360Container . dataset . has360 = "false" ;
}
if ( tour360Btn ) tour360Btn . style . display = "none" ;
}
// ... rest of rendering
};
Code location: app.js:631-652
The button is only displayed when a valid url_360 value exists in the property data.
Visibility Control
The 360° tour button on the main gallery image is only shown on the first image:
const setActive = ( index ) => {
galleryCurrentIndex = index ;
mainImage . src = resolvedImages [ index ];
mainImage . alt = `Imagen ${ index + 1 } ` ;
updateGalleryThumbHighlights ();
// Show 360 button only on first image
const btn360Container = $ ( "#btn-360-container" );
if ( btn360Container && btn360Container . dataset . has360 === "true" ) {
btn360Container . style . display = index === 0 ? "flex" : "none" ;
}
};
Code location: app.js:261-272
Displaying the button only on the first image prevents it from obscuring other property photos while keeping it prominently visible when the gallery loads.
HTML Structure
Main Gallery Button
The primary 360° tour button overlays the main gallery image:
< div class = "relative aspect-[16/9] w-full rounded-xl overflow-hidden mb-4 group" >
< img id = "gallery-main-image" alt = "Imagen principal" class = "w-full h-full object-cover" src = "" />
< div id = "btn-360-container" class = "absolute inset-0 flex items-center justify-center pointer-events-none" style = "display: none;" >
< a id = "property-360" href = "#" target = "_blank" rel = "noopener" class = "btn-360 pointer-events-auto flex items-center gap-3 px-8 py-4 rounded-full text-slate-900 font-display font-extrabold text-lg uppercase tracking-widest transition-all duration-300 border-2 border-white/20" >
< span class = "material-symbols-outlined text-3xl" > 360 </ span >
Vista 360°
</ a >
</ div >
<!-- Navigation arrows -->
<!-- ... -->
</ div >
Code location: index.html:101-115
A secondary button appears in the description section:
< section class = "bg-white dark:bg-slate-800 p-8 rounded-2xl shadow-sm border border-slate-100 dark:border-slate-700" >
< h3 class = "text-xl font-display font-bold mb-4 flex items-center gap-2" >
< span class = "material-icons-outlined text-primary" > description </ span >
Descripción
</ h3 >
< div class = "text-slate-600 dark:text-slate-400 leading-relaxed text-sm" >
< p id = "property-description" > Sin descripción </ p >
</ div >
< a id = "property-360-btn" href = "#" target = "_blank" rel = "noopener" class = "mt-6 flex items-center gap-2 px-5 py-2.5 rounded-full border-2 border-accent-teal text-slate-800 dark:text-accent-teal font-bold text-sm hover:bg-accent-teal hover:text-slate-900 transition-all" style = "display: none;" >
< span class = "material-symbols-outlined text-xl" > view_in_ar </ span >
Explorar Tour Virtual
</ a >
</ section >
Code location: index.html:141-153
The main 360° button has a distinctive gradient design:
.btn-360 {
background : linear-gradient ( 135 deg , #00f5d4 0 % , #00d1b2 100 % );
box-shadow : 0 10 px 25 px -5 px rgba ( 0 , 245 , 212 , 0.4 );
}
.btn-360:hover {
box-shadow : 0 15 px 30 px -5 px rgba ( 0 , 245 , 212 , 0.6 );
transform : translateY ( -2 px );
}
Code location: index.html:57-64
Eye-Catching Design Gradient teal-to-turquoise background with glowing shadow effect
Hover Animation Lifts up on hover with enhanced shadow for depth
Material Icon Uses Google Material Symbols 360 icon for instant recognition
Opens New Tab Links open in new window with security attributes (noopener)
Integration Examples
Matterport Integration
{
"property" : {
"titulo" : "Apartamento Moderno" ,
"url_360" : "https://my.matterport.com/show/?m=XXXXXX"
}
}
Kuula Integration
{
"property" : {
"titulo" : "Casa en Venta" ,
"url_360" : "https://kuula.co/share/XXXXXX"
}
}
{
"property" : {
"titulo" : "Oficina Ejecutiva" ,
"url_360" : "https://tours.example.com/property/12345"
}
}
User Flow
Property Loads
The system checks if url_360 exists in the property data
Button Appears
If URL exists, the 360° button is displayed on the first gallery image
User Clicks
Clicking the button opens the virtual tour in a new browser tab
Navigate Back
User can return to the property listing after viewing the tour
Best Practices
Verify that url_360 values are valid and accessible before publishing property listings.
Ensure tour URLs use HTTPS for security and browser compatibility.
Test 360° tours on mobile devices to ensure they work properly on all platforms.
Consider the file size and loading speed of external tour platforms.
Conditional Display Logic
Condition Button Display Data Attribute url_360 existsdisplay: flexdata-has360="true"url_360 is null/emptydisplay: nonedata-has360="false"Gallery index > 0 display: none(unchanged) Gallery index = 0 Inherits from data (unchanged)