Skip to main content

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

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

Description Section Button

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

Button Styling

The main 360° button has a distinctive gradient design:
.btn-360 {
  background: linear-gradient(135deg, #00f5d4 0%, #00d1b2 100%);
  box-shadow: 0 10px 25px -5px rgba(0, 245, 212, 0.4);
}
.btn-360:hover {
  box-shadow: 0 15px 30px -5px rgba(0, 245, 212, 0.6);
  transform: translateY(-2px);
}
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"
  }
}

Custom Tour Platform

{
  "property": {
    "titulo": "Oficina Ejecutiva",
    "url_360": "https://tours.example.com/property/12345"
  }
}

User Flow

1

Property Loads

The system checks if url_360 exists in the property data
2

Button Appears

If URL exists, the 360° button is displayed on the first gallery image
3

User Clicks

Clicking the button opens the virtual tour in a new browser tab
4

Navigate Back

User can return to the property listing after viewing the tour

Best Practices

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

ConditionButton DisplayData Attribute
url_360 existsdisplay: flexdata-has360="true"
url_360 is null/emptydisplay: nonedata-has360="false"
Gallery index > 0display: none(unchanged)
Gallery index = 0Inherits from data(unchanged)

Build docs developers (and LLMs) love