Mudanças entre as edições de "Widget:MapViewer.js"

De Wiki Gla
Ir para navegação Ir para pesquisar
 
(42 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
Linha 1: Linha 1:
<includeonly>
<includeonly><div id="mapa-viewer-<!--{$id|escape:'quotes'|default:'mapa1'}-->" style="width:<!--{$largura|escape:'quotes'|default:'100%'}-->; height:<!--{$altura|escape:'quotes'|default:'500px'}-->; background:#0f172a; border-radius:12px; overflow:hidden; position:relative;">
<script type="text/javascript">
    <div style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:#64748b; text-align:center;">
/**
        🗺️ Carregando mapa...
* MapViewer Widget para MediaWiki - Versão Simplificada
    </div>
* Uso: {{#widget:MapViewer|json=JSON_do_mapa|width=800|height=600}}
</div>
*/


<script>
// @noescape
(function() {
(function() {
     // Evitar múltiplas execuções
     var id = '<!--{$id|escape:'quotes'|default:'mapa1'}-->';
     if (window.MWMapViewerLoaded) return;
     var container = document.getElementById('mapa-viewer-' + id);
     window.MWMapViewerLoaded = true;
     if (!container) return;
      
      
     // ==================== ESTILOS ====================
     // JSON vem URL encoded para não quebrar o parser
     const styles = `
     var jsonEncoded = '<!--{$json|escape:'quotes'}-->';
        .mw-map-container {
    var jsonString = decodeURIComponent(jsonEncoded);
            position: relative;
   
            background: #1e1e2e;
    var mapConfig;
            border-radius: 8px;
    try {
            overflow: hidden;
         mapConfig = JSON.parse(jsonString);
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
    } catch(e) {
        }
         container.innerHTML = '<div style="padding:20px; text-align:center; color:#ef4444;">❌ Erro no JSON: ' + e.message + '</div>';
        .mw-map-container .mw-viewport {
         return;
            width: 100%;
    }
            height: 100%;
   
            overflow: auto;
    if (!mapConfig.layers || mapConfig.layers.length === 0) {
            cursor: grab;
         container.innerHTML = '<div style="padding:20px; text-align:center; color:#f59e0b;">⚠️ Nenhuma camada configurada</div>';
            background: #0f0f1a;
         return;
        }
    }
        .mw-map-container .mw-viewport:active {
   
            cursor: grabbing;
    // Iniciar o visualizador
        }
     iniciarMapaViewer(container, mapConfig);
        .mw-map-container .mw-layers {
            position: relative;
            min-width: 100%;
            min-height: 100%;
        }
        .mw-map-container .mw-layer {
            position: absolute;
            top: 0;
            left: 0;
        }
        .mw-map-container .mw-image {
            display: block;
            pointer-events: none;
        }
        .mw-map-container .mw-marker {
            position: absolute;
            cursor: pointer;
            transform: translate(-50%, -50%);
            transition: transform 0.1s;
            z-index: 100;
        }
        .mw-map-container .mw-marker:hover {
            transform: translate(-50%, -50%) scale(1.2);
            z-index: 101;
         }
        .mw-map-container .mw-marker-icon {
            font-size: 20px;
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
        }
        .mw-map-container .mw-marker-tooltip {
            position: absolute;
            bottom: 100%;
            left: 50%;
            transform: translateX(-50%);
            background: #1e1e2e;
            color: white;
            padding: 4px 8px;
            border-radius: 6px;
            font-size: 11px;
            white-space: nowrap;
            opacity: 0;
            visibility: hidden;
            transition: 0.2s;
            pointer-events: none;
            margin-bottom: 5px;
        }
        .mw-map-container .mw-marker:hover .mw-marker-tooltip {
            opacity: 1;
            visibility: visible;
        }
         .mw-map-container .mw-badge {
            position: absolute;
            top: -8px;
            right: -8px;
            background: #ef4444;
            color: white;
            font-size: 9px;
            min-width: 16px;
            height: 16px;
            border-radius: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 0 4px;
        }
        .mw-map-container .mw-badge.hidden {
            display: none;
        }
        .mw-map-container .mw-toolbar {
            position: absolute;
            top: 10px;
            left: 10px;
            right: 10px;
            display: flex;
            justify-content: space-between;
            z-index: 100;
            gap: 10px;
        }
        .mw-map-container .mw-controls {
            display: flex;
            gap: 5px;
            background: rgba(0,0,0,0.6);
            padding: 5px 10px;
            border-radius: 30px;
        }
        .mw-map-container .mw-controls button {
            background: #334155;
            border: none;
            color: white;
            width: 32px;
            height: 32px;
            border-radius: 50%;
            cursor: pointer;
            font-size: 16px;
        }
        .mw-map-container .mw-controls button:hover {
            background: #6366f1;
         }
        .mw-map-container .mw-floor-info {
            background: rgba(0,0,0,0.6);
            padding: 5px 15px;
            border-radius: 30px;
            color: white;
            font-size: 12px;
        }
        .mw-map-container .mw-zoom-level {
            background: rgba(0,0,0,0.6);
            padding: 5px 12px;
            border-radius: 30px;
            color: #a5b4fc;
            font-size: 12px;
        }
        .mw-map-container .mw-nav {
            position: absolute;
            bottom: 20px;
            left: 20px;
            display: flex;
            gap: 8px;
            z-index: 100;
        }
        .mw-map-container .mw-nav button {
            background: rgba(0,0,0,0.7);
            border: none;
            color: white;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            cursor: pointer;
            font-size: 18px;
        }
        .mw-map-container .mw-nav button:hover {
            background: #6366f1;
        }
         .mw-map-container .mw-coords {
            position: absolute;
            bottom: 10px;
            right: 10px;
            background: rgba(0,0,0,0.5);
            padding: 4px 10px;
            border-radius: 20px;
            color: #a5b4fc;
            font-size: 10px;
            font-family: monospace;
            z-index: 100;
        }
        .mw-map-popup {
            display: none;
            position: fixed;
            z-index: 10000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.7);
            justify-content: center;
            align-items: center;
        }
        .mw-map-popup .mw-popup-content {
            background: #1e1e2e;
            border-radius: 12px;
            max-width: 350px;
            width: 90%;
            padding: 20px;
            position: relative;
            border: 1px solid #334155;
        }
        .mw-map-popup .mw-popup-close {
            position: absolute;
            right: 15px;
            top: 10px;
            font-size: 24px;
            cursor: pointer;
            color: #94a3b8;
        }
        .mw-map-popup .mw-popup-close:hover {
            color: #ef4444;
         }
        .mw-map-popup h3 {
            color: #a5b4fc;
            margin: 0 0 10px 0;
        }
        .mw-map-popup p {
            color: #cbd5e1;
            margin: 0;
            line-height: 1.5;
        }
     `;
      
      
     // ==================== CLASSE DO MAPA ====================
     function iniciarMapaViewer(container, config) {
    class MWMap {
        container.innerHTML = '';
        constructor(container, config) {
        container.style.position = 'relative';
            this.container = container;
        container.style.overflow = 'hidden';
            this.config = config;
       
           
        // Toolbar
            // Configurações do mapa
        var toolbar = document.createElement('div');
            this.zoom = config.mapConfig?.defaultZoom || 1;
        toolbar.style.cssText = 'position:absolute; top:10px; left:10px; right:10px; z-index:100; display:flex; justify-content:space-between; gap:8px; flex-wrap:wrap;';
             this.currentFloor = config.mapConfig?.initialFloor || 0;
        toolbar.innerHTML = '<div style="display:flex; gap:5px; background:rgba(0,0,0,0.7); padding:5px 10px; border-radius:30px;">' +
             this.minZoom = config.mapConfig?.minZoom || 0.5;
             '<button id="zoom-in-' + id + '" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer;">+</button>' +
             this.maxZoom = config.mapConfig?.maxZoom || 3;
             '<button id="zoom-out-' + id + '" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer;">-</button>' +
             this.zoomStep = config.mapConfig?.zoomStep || 0.1;
             '<button id="reset-' + id + '" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer;">⟳</button>' +
           
             '</div>' +
            this.layers = [];
            '<div style="background:rgba(0,0,0,0.7); padding:5px 15px; border-radius:30px; color:white; font-size:12px;"><span id="floor-name-' + id + '">' + (config.layers[0]?.name || 'Mapa') + '</span></div>' +
            this.markers = [];
            '<div id="zoom-level-' + id + '" style="background:rgba(0,0,0,0.7); padding:5px 12px; border-radius:30px; color:#a5b4fc; font-size:12px;">100%</div>';
            this.isPanning = false;
       
            this.panStart = { x: 0, y: 0, left: 0, top: 0 };
        // Viewport
           
        var viewport = document.createElement('div');
             this.init();
        viewport.style.cssText = 'width:100%; height:100%; overflow:auto; cursor:grab; background:#0f172a;';
        }
        var camadasDiv = document.createElement('div');
        camadasDiv.style.cssText = 'position:relative; min-width:100%; min-height:100%;';
        viewport.appendChild(camadasDiv);
       
        // Navegação
        var navDiv = document.createElement('div');
        navDiv.style.cssText = 'position:absolute; bottom:20px; left:20px; z-index:100; display:flex; gap:8px; background:rgba(0,0,0,0.7); padding:8px; border-radius:40px;';
        navDiv.innerHTML = '<button id="prev-' + id + '" style="background:#334155; border:none; color:white; width:36px; height:36px; border-radius:50%; cursor:pointer;">▲</button>' +
             '<button id="next-' + id + '" style="background:#334155; border:none; color:white; width:36px; height:36px; border-radius:50%; cursor:pointer;">▼</button>';
          
          
         init() {
         // Coordenadas
            this.createDOM();
        var coordsDiv = document.createElement('div');
            this.render();
        coordsDiv.style.cssText = 'position:absolute; bottom:10px; right:10px; background:rgba(0,0,0,0.6); padding:4px 10px; border-radius:20px; color:#a5b4fc; font-size:10px; font-family:monospace;';
            this.setupEvents();
        coordsDiv.textContent = '📍 0, 0';
            this.updateMarkersPosition();
            this.createPopup();
        }
          
          
         createDOM() {
         container.appendChild(toolbar);
            // Limpar container
        container.appendChild(viewport);
            this.container.innerHTML = '';
        container.appendChild(coordsDiv);
            this.container.className = 'mw-map-container';
        container.appendChild(navDiv);
           
            // Criar estrutura
            this.container.innerHTML = `
                <div class="mw-toolbar">
                    <div class="mw-controls">
                        <button class="mw-zoom-in" title="Zoom In">+</button>
                        <button class="mw-zoom-out" title="Zoom Out">-</button>
                        <button class="mw-reset" title="Reset">⟳</button>
                    </div>
                    <div class="mw-floor-info">
                        <span class="mw-floor-name">Carregando...</span>
                    </div>
                    <div class="mw-zoom-level">
                        <span class="mw-zoom-text">100%</span>
                    </div>
                </div>
                <div class="mw-viewport">
                    <div class="mw-layers"></div>
                </div>
                <div class="mw-coords">
                    <span class="mw-coords-text">📍 0, 0</span>
                </div>
                <div class="mw-nav">
                    <button class="mw-prev-floor" title="Andar Anterior">▲</button>
                    <button class="mw-next-floor" title="Próximo Andar">▼</button>
                </div>
            `;
           
            this.viewport = this.container.querySelector('.mw-viewport');
            this.layersContainer = this.container.querySelector('.mw-layers');
            this.floorNameSpan = this.container.querySelector('.mw-floor-name');
            this.zoomTextSpan = this.container.querySelector('.mw-zoom-text');
            this.coordsSpan = this.container.querySelector('.mw-coords-text');
           
            // Botões
            this.container.querySelector('.mw-zoom-in')?.addEventListener('click', () => this.zoomIn());
            this.container.querySelector('.mw-zoom-out')?.addEventListener('click', () => this.zoomOut());
            this.container.querySelector('.mw-reset')?.addEventListener('click', () => this.reset());
            this.container.querySelector('.mw-prev-floor')?.addEventListener('click', () => this.prevFloor());
            this.container.querySelector('.mw-next-floor')?.addEventListener('click', () => this.nextFloor());
        }
          
          
         setupEvents() {
         // Variáveis
            // Pan
        var zoomAtual = config.mapConfig.defaultZoom || 1;
            this.viewport.addEventListener('mousedown', (e) => {
        var andarAtual = config.mapConfig.initialFloor || 0;
                if (e.target.closest('.mw-marker')) return;
        var zoomMin = config.mapConfig.minZoom || 0.5;
                this.isPanning = true;
        var zoomMax = config.mapConfig.maxZoom || 3;
                this.panStart = {
        var zoomPasso = config.mapConfig.zoomStep || 0.1;
                    x: e.clientX,
        var camadas = [];
                    y: e.clientY,
        var marcadores = [];
                    left: this.viewport.scrollLeft,
        var arrastando = false;
                    top: this.viewport.scrollTop
        var arrasteInicio = { x: 0, y: 0, scrollLeft: 0, scrollTop: 0 };
                };
                this.viewport.style.cursor = 'grabbing';
                e.preventDefault();
            });
           
            window.addEventListener('mousemove', (e) => {
                if (!this.isPanning) return;
                const dx = e.clientX - this.panStart.x;
                const dy = e.clientY - this.panStart.y;
                this.viewport.scrollLeft = this.panStart.left - dx;
                this.viewport.scrollTop = this.panStart.top - dy;
            });
           
            window.addEventListener('mouseup', () => {
                this.isPanning = false;
                this.viewport.style.cursor = 'grab';
            });
           
            // Zoom com scroll
            this.viewport.addEventListener('wheel', (e) => {
                if (e.ctrlKey) {
                    e.preventDefault();
                    e.deltaY < 0 ? this.zoomIn() : this.zoomOut();
                }
            });
           
            // Coordenadas
            this.viewport.addEventListener('mousemove', (e) => {
                const rect = this.viewport.getBoundingClientRect();
                const x = (e.clientX - rect.left + this.viewport.scrollLeft) / this.zoom;
                const y = (e.clientY - rect.top + this.viewport.scrollTop) / this.zoom;
                if (this.coordsSpan) {
                    this.coordsSpan.textContent = `📍 ${Math.round(x)}, ${Math.round(y)} | ${Math.round(this.zoom * 100)}%`;
                }
            });
           
            // Teclado
            document.addEventListener('keydown', (e) => {
                if (e.target.tagName === 'INPUT') return;
                switch(e.key) {
                    case '+': case '=': e.preventDefault(); this.zoomIn(); break;
                    case '-': case '_': e.preventDefault(); this.zoomOut(); break;
                    case 'r': case 'R': e.preventDefault(); this.reset(); break;
                    case 'ArrowUp': case 'PageUp': e.preventDefault(); this.prevFloor(); break;
                    case 'ArrowDown': case 'PageDown': e.preventDefault(); this.nextFloor(); break;
                }
            });
        }
          
          
         render() {
         function renderizarCamadas() {
             if (!this.layersContainer) return;
             camadasDiv.innerHTML = '';
            this.layersContainer.innerHTML = '';
             camadas = [];
             this.layers = [];
             marcadores = [];
             this.markers = [];
              
              
             if (!this.config.layers || this.config.layers.length === 0) {
             if (!config.layers || config.layers.length === 0) return;
                this.layersContainer.innerHTML = '<div style="padding:50px; text-align:center; color:#64748b;">Nenhum andar configurado</div>';
                return;
            }
              
              
             // Verificar se o andar atual existe
             var andarExiste = false;
             if (!this.config.layers.find(l => l.id === this.currentFloor)) {
             for (var i = 0; i < config.layers.length; i++) {
                 this.currentFloor = this.config.layers[0]?.id || 0;
                 if (config.layers[i].id === andarAtual) andarExiste = true;
             }
             }
            if (!andarExiste && config.layers.length > 0) andarAtual = config.layers[0].id;
              
              
             const currentFloorData = this.config.layers.find(l => l.id === this.currentFloor);
             var andarInfo = null;
            if (currentFloorData && this.floorNameSpan) {
            for (var i = 0; i < config.layers.length; i++) {
                this.floorNameSpan.textContent = currentFloorData.name;
                if (config.layers[i].id === andarAtual) andarInfo = config.layers[i];
             }
             }
            var nomeSpan = document.getElementById('floor-name-' + id);
            if (nomeSpan && andarInfo) nomeSpan.textContent = andarInfo.name;
              
              
             this.config.layers.forEach(layer => {
             for (var i = 0; i < config.layers.length; i++) {
                 const $layer = document.createElement('div');
                var layer = config.layers[i];
                 $layer.className = 'mw-layer';
                 var divLayer = document.createElement('div');
                 $layer.setAttribute('data-floor', layer.id);
                 divLayer.className = 'mapa-layer';
                 $layer.style.display = layer.id === this.currentFloor ? 'block' : 'none';
                 divLayer.setAttribute('data-floor', layer.id);
                 $layer.style.transform = `translate(${layer.alignment?.offsetX || 0}px, ${layer.alignment?.offsetY || 0}px)`;
                 divLayer.style.display = layer.id === andarAtual ? 'block' : 'none';
               
                 divLayer.style.position = 'absolute';
                const $img = document.createElement('img');
                 divLayer.style.top = '0';
                 $img.className = 'mw-image';
                 divLayer.style.left = '0';
                 $img.style.transform = `scale(${this.zoom})`;
                 divLayer.style.transform = 'translate(' + (layer.alignment?.offsetX || 0) + 'px, ' + (layer.alignment?.offsetY || 0) + 'px)';
                 $img.style.transformOrigin = '0 0';
                divLayer.style.opacity = (layer.opacity || 100) / 100;
               
                if (layer.imagePath) {
                    $img.src = layer.imagePath;
                } else {
                    // Imagem placeholder
                    const canvas = document.createElement('canvas');
                    canvas.width = 800;
                    canvas.height = 600;
                    const ctx = canvas.getContext('2d');
                    ctx.fillStyle = '#334155';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);
                    ctx.fillStyle = 'white';
                    ctx.font = '20px Arial';
                    ctx.fillText(layer.name || `Andar ${layer.id + 1}`, 50, 100);
                    $img.src = canvas.toDataURL();
                }
                  
                  
                 $img.onload = () => {
                 var img = document.createElement('img');
                    this.layersContainer.style.width = `${$img.width}px`;
                img.style.display = 'block';
                    this.layersContainer.style.height = `${$img.height}px`;
                img.style.transform = 'scale(' + zoomAtual + ')';
                 };
                img.style.transformOrigin = '0 0';
                img.src = layer.imagePath || '';
                img.onload = function() { camadasDiv.style.width = this.width + 'px'; camadasDiv.style.height = this.height + 'px'; };
                img.onerror = function() { this.src = 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22800%22 height=%22600%22%3E%3Crect width=%22100%25%22 height=%22100%25%22 fill=%22%23334155%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 fill=%22white%22%3ESem imagem%3C/text%3E%3C/svg%3E'; };
                 divLayer.appendChild(img);
                  
                  
                 $layer.appendChild($img);
                 var markersDiv = document.createElement('div');
               
                 markersDiv.style.position = 'absolute';
                // Adicionar marcadores
                 markersDiv.style.top = '0';
                const $markersDiv = document.createElement('div');
                 markersDiv.style.left = '0';
                 $markersDiv.style.position = 'absolute';
                 markersDiv.style.width = '100%';
                 $markersDiv.style.top = '0';
                 markersDiv.style.height = '100%';
                 $markersDiv.style.left = '0';
                 markersDiv.style.pointerEvents = 'none';
                 $markersDiv.style.width = '100%';
                 $markersDiv.style.height = '100%';
                 $markersDiv.style.pointerEvents = 'none';
                  
                  
                 if (layer.markers) {
                 if (layer.markers) {
                     layer.markers.forEach(marker => {
                     for (var j = 0; j < layer.markers.length; j++) {
                         const $marker = this.createMarker(marker, layer.id);
                         var marker = layer.markers[j];
                         $markersDiv.appendChild($marker);
                        var markerDiv = criarMarcador(marker, layer.id);
                         this.markers.push($marker);
                         markersDiv.appendChild(markerDiv);
                     });
                         marcadores.push({ el: markerDiv, data: marker });
                     }
                 }
                 }
                  
                 divLayer.appendChild(markersDiv);
                $layer.appendChild($markersDiv);
                 camadasDiv.appendChild(divLayer);
                 this.layersContainer.appendChild($layer);
                 camadas.push(divLayer);
                 this.layers.push($layer);
             }
             });
         }
         }
          
          
         createMarker(marker, floorId) {
         function criarMarcador(marker, floorId) {
             const $marker = document.createElement('div');
             var div = document.createElement('div');
             $marker.className = 'mw-marker';
             div.className = 'mapa-marker';
             $marker.setAttribute('data-marker-id', marker.id);
             div.setAttribute('data-marker-id', marker.id);
             $marker.setAttribute('data-floor', floorId);
             div.setAttribute('data-floor', floorId);
             $marker.setAttribute('data-x', marker.x);
             div.setAttribute('data-x', marker.x);
             $marker.setAttribute('data-y', marker.y);
             div.setAttribute('data-y', marker.y);
            div.style.position = 'absolute';
            div.style.left = (marker.x * zoomAtual) + 'px';
            div.style.top = (marker.y * zoomAtual) + 'px';
            div.style.zIndex = '100';
            div.style.transform = 'translate(-50%, -50%)';
            div.style.cursor = (marker.action && marker.action !== 'none') ? 'pointer' : 'default';
              
              
             $marker.style.left = `${marker.x * this.zoom}px`;
             var tamanhoIcone = Math.max(20, Math.floor(20 * zoomAtual));
             $marker.style.top = `${marker.y * this.zoom}px`;
             var htmlIcone = marker.iconBase64 ? '<img src="' + marker.iconBase64 + '" style="width:' + tamanhoIcone + 'px; height:' + tamanhoIcone + 'px;">' : '<span style="font-size:' + tamanhoIcone + 'px;">📍</span>';
              
              
             // Ícone
             var textoAcao = '';
             const iconSize = Math.max(20, Math.floor(20 * this.zoom));
             if (marker.action === 'popup') textoAcao = '📋 Informações';
             let iconHtml = marker.iconBase64 ?
            else if (marker.action === 'nextFloor') textoAcao = '⬆️ Próximo andar';
                `<img src="${marker.iconBase64}" style="width:${iconSize}px; height:${iconSize}px;">` :
            else if (marker.action === 'prevFloor') textoAcao = '⬇️ Andar anterior';
                `<span style="font-size:${iconSize}px;">📍</span>`;
             else if (marker.action === 'gotoFloor') textoAcao = '🎯 Ir para andar';
            else if (marker.action === 'link') textoAcao = '🔗 Link externo';
            else textoAcao = '📍 Clique';
              
              
             // Tooltip
             div.innerHTML = '<div style="display:flex; align-items:center; justify-content:center;">' + htmlIcone + '</div>' +
            let actionLabel = '';
                '<div style="position:absolute; bottom:100%; left:50%; transform:translateX(-50%); background:#1e293b; color:white; padding:4px 8px; border-radius:6px; font-size:10px; white-space:nowrap; opacity:0; visibility:hidden; transition:0.2s; pointer-events:none;">' +
            switch(marker.action) {
                '<strong>' + (marker.name || 'Marcador') + '</strong><br><small>' + textoAcao + '</small></div>' +
                case 'popup': actionLabel = '📋 Informações'; break;
                 '<div style="position:absolute; top:-8px; right:-8px; background:#ef4444; color:white; font-size:9px; min-width:16px; height:16px; border-radius:10px; display:flex; align-items:center; justify-content:center; padding:0 4px; ' + (!marker.hasBadge ? 'display:none;' : '') + '">' + (marker.hasBadge ? (marker.badgeNumber || marker.number || '') : '') + '</div>';
                 case 'nextFloor': actionLabel = '⬆️ Próximo andar'; break;
                case 'prevFloor': actionLabel = '⬇️ Andar anterior'; break;
                case 'gotoFloor': actionLabel = '🎯 Ir para andar'; break;
                case 'link': actionLabel = '🔗 Link'; break;
                default: actionLabel = 'Clique';
            }
              
              
             $marker.innerHTML = `
             div.addEventListener('mouseenter', function() { this.style.transform = 'translate(-50%, -50%) scale(1.15)'; var tp = this.children[1]; if (tp) { tp.style.opacity = '1'; tp.style.visibility = 'visible'; } });
                <div class="mw-marker-icon">${iconHtml}</div>
            div.addEventListener('mouseleave', function() { this.style.transform = 'translate(-50%, -50%) scale(1)'; var tp = this.children[1]; if (tp) { tp.style.opacity = '0'; tp.style.visibility = 'hidden'; } });
                <div class="mw-marker-tooltip">
                    <strong>${marker.name || 'Marcador'}</strong><br>
                    <small>${actionLabel}</small>
                </div>
                <div class="mw-badge ${!marker.hasBadge ? 'hidden' : ''}">${marker.hasBadge ? (marker.badgeNumber || marker.number || '') : ''}</div>
            `;
              
              
            // Evento de clique
             if (marker.action && marker.action !== 'none') {
             if (marker.action !== 'none') {
                 div.addEventListener('click', function(e) {
                 $marker.addEventListener('click', (e) => {
                     e.stopPropagation();
                     e.stopPropagation();
                     this.executeAction(marker);
                     if (marker.action === 'popup') alert(marker.name + '\n\n' + (marker.actionData?.text || 'Sem informações'));
                    else if (marker.action === 'nextFloor') irProximoAndar();
                    else if (marker.action === 'prevFloor') irAndarAnterior();
                    else if (marker.action === 'gotoFloor' && marker.actionData?.floorId !== undefined) irParaAndar(marker.actionData.floorId);
                    else if (marker.action === 'link' && marker.actionData?.url && marker.actionData.url !== '#') window.open(marker.actionData.url, marker.actionData.target || '_blank');
                 });
                 });
             }
             }
           
             return div;
             return $marker;
         }
         }
          
          
         executeAction(marker) {
         function atualizarPosicaoMarcadores() {
             console.log('Ação:', marker.action, marker.name);
             for (var i = 0; i < marcadores.length; i++) {
           
                var item = marcadores[i];
            switch(marker.action) {
                if (parseInt(item.el.getAttribute('data-floor')) === andarAtual) {
                case 'popup':
                     item.el.style.left = (item.data.x * zoomAtual) + 'px';
                     this.showPopup(marker);
                     item.el.style.top = (item.data.y * zoomAtual) + 'px';
                    break;
                     var ts = Math.max(20, Math.floor(20 * zoomAtual));
                case 'nextFloor':
                     var idiv = item.el.children[0];
                     this.nextFloor();
                     if (idiv) {
                    break;
                         if (item.data.iconBase64) idiv.innerHTML = '<img src="' + item.data.iconBase64 + '" style="width:' + ts + 'px; height:' + ts + 'px;">';
                case 'prevFloor':
                        else idiv.innerHTML = '<span style="font-size:' + ts + 'px;">📍</span>';
                     this.prevFloor();
                     break;
                case 'gotoFloor':
                     if (marker.actionData?.floorId !== undefined) {
                         this.goToFloor(marker.actionData.floorId);
                     }
                     }
                    break;
                 }
                 case 'link':
                    if (marker.actionData?.url && marker.actionData.url !== '#') {
                        window.open(marker.actionData.url, marker.actionData.target || '_blank');
                    }
                    break;
             }
             }
         }
         }
          
          
         createPopup() {
         function irProximoAndar() {
             if (document.querySelector('.mw-map-popup')) return;
             var idx = -1;
              
             for (var i = 0; i < config.layers.length; i++) {
            const popupHtml = `
                if (config.layers[i].id === andarAtual) idx = i;
                <div class="mw-map-popup">
             }
                    <div class="mw-popup-content">
             if (idx < config.layers.length - 1) irParaAndar(config.layers[idx + 1].id);
                        <span class="mw-popup-close">&times;</span>
                        <div class="mw-popup-body"></div>
                    </div>
                </div>
            `;
            document.body.insertAdjacentHTML('beforeend', popupHtml);
           
            this.popup = document.querySelector('.mw-map-popup');
            this.popupBody = this.popup?.querySelector('.mw-popup-body');
              
             this.popup?.querySelector('.mw-popup-close')?.addEventListener('click', () => this.closePopup());
            this.popup?.addEventListener('click', (e) => {
                if (e.target === this.popup) this.closePopup();
            });
         }
         }
          
          
         showPopup(marker) {
         function irAndarAnterior() {
             if (!this.popup) this.createPopup();
             var idx = -1;
           
            for (var i = 0; i < config.layers.length; i++) {
            const iconHtml = marker.iconBase64 ?
                 if (config.layers[i].id === andarAtual) idx = i;
                `<img src="${marker.iconBase64}" style="width:24px; height:24px; vertical-align:middle; margin-right:8px;">` :
            }
                `<span style="font-size:20px; margin-right:8px;">📍</span>`;
            if (idx > 0) irParaAndar(config.layers[idx - 1].id);
           
            this.popupBody.innerHTML = `
                 ${marker.actionData?.image ? `<img src="${marker.actionData.image}" style="max-width:100%; border-radius:8px; margin-bottom:15px;">` : ''}
                <div style="display:flex; align-items:center;">
                    ${iconHtml}
                    <h3 style="margin:0;">${marker.name}</h3>
                </div>
                <p style="margin-top:10px;">${marker.actionData?.text || 'Sem informações'}</p>
            `;
           
            this.popup.style.display = 'flex';
         }
         }
          
          
         closePopup() {
         function irParaAndar(floorId) {
             if (this.popup) this.popup.style.display = 'none';
             andarAtual = floorId;
            for (var i = 0; i < camadas.length; i++) {
                var lf = parseInt(camadas[i].getAttribute('data-floor'));
                camadas[i].style.display = lf === floorId ? 'block' : 'none';
            }
            var fd = null;
            for (var i = 0; i < config.layers.length; i++) {
                if (config.layers[i].id === floorId) fd = config.layers[i];
            }
            var nomeSpan = document.getElementById('floor-name-' + id);
            if (nomeSpan && fd) nomeSpan.textContent = fd.name;
            atualizarPosicaoMarcadores();
         }
         }
          
          
         updateMarkersPosition() {
         function zoomIn() {
             this.markers.forEach($marker => {
             if (zoomAtual < zoomMax) {
                 const floorId = parseInt($marker.getAttribute('data-floor'));
                 var old = zoomAtual;
                 if (floorId === this.currentFloor) {
                 zoomAtual = Math.min(zoomAtual + zoomPasso, zoomMax);
                    const x = parseFloat($marker.getAttribute('data-x'));
                aplicarZoom(old);
                    const y = parseFloat($marker.getAttribute('data-y'));
             }
                    $marker.style.left = `${x * this.zoom}px`;
                    $marker.style.top = `${y * this.zoom}px`;
                   
                    const iconSize = Math.max(20, Math.floor(20 * this.zoom));
                    const iconDiv = $marker.querySelector('.mw-marker-icon');
                    if (iconDiv) {
                        const hasImage = $marker.getAttribute('data-has-image');
                        if (hasImage) {
                            // manter imagem
                        } else {
                            iconDiv.innerHTML = `<span style="font-size:${iconSize}px;">📍</span>`;
                        }
                    }
                }
             });
         }
         }
          
          
         zoomIn() {
         function zoomOut() {
             if (this.zoom < this.maxZoom) {
             if (zoomAtual > zoomMin) {
                 const oldZoom = this.zoom;
                 var old = zoomAtual;
                 this.zoom = Math.min(this.zoom + this.zoomStep, this.maxZoom);
                 zoomAtual = Math.max(zoomAtual - zoomPasso, zoomMin);
                 this.applyZoom(oldZoom);
                 aplicarZoom(old);
             }
             }
         }
         }
          
          
         zoomOut() {
         function aplicarZoom(oldZoom) {
             if (this.zoom > this.minZoom) {
             var imgs = document.querySelectorAll('.mapa-image');
                const oldZoom = this.zoom;
            for (var i = 0; i < imgs.length; i++) imgs[i].style.transform = 'scale(' + zoomAtual + ')';
                this.zoom = Math.max(this.zoom - this.zoomStep, this.minZoom);
            var rect = viewport.getBoundingClientRect();
                this.applyZoom(oldZoom);
            var cx = rect.width / 2, cy = rect.height / 2;
             }
            var sx = (viewport.scrollLeft + cx) / oldZoom, sy = (viewport.scrollTop + cy) / oldZoom;
            viewport.scrollLeft = sx * zoomAtual - cx;
            viewport.scrollTop = sy * zoomAtual - cy;
            atualizarPosicaoMarcadores();
            var zoomSpan = document.getElementById('zoom-level-' + id);
             if (zoomSpan) zoomSpan.textContent = Math.round(zoomAtual * 100) + '%';
         }
         }
          
          
         applyZoom(oldZoom) {
         function resetarView() {
             // Aplicar zoom nas imagens
             zoomAtual = config.mapConfig.defaultZoom || 1;
             this.container.querySelectorAll('.mw-image').forEach(img => {
             var imgs = document.querySelectorAll('.mapa-image');
                img.style.transform = `scale(${this.zoom})`;
             for (var i = 0; i < imgs.length; i++) imgs[i].style.transform = 'scale(' + zoomAtual + ')';
            });
             viewport.scrollLeft = 0;
              
             viewport.scrollTop = 0;
            // Ajustar scroll para manter centro
             atualizarPosicaoMarcadores();
            const rect = this.viewport.getBoundingClientRect();
             var zoomSpan = document.getElementById('zoom-level-' + id);
            const cx = rect.width / 2;
             if (zoomSpan) zoomSpan.textContent = Math.round(zoomAtual * 100) + '%';
            const cy = rect.height / 2;
            const sx = (this.viewport.scrollLeft + cx) / oldZoom;
            const sy = (this.viewport.scrollTop + cy) / oldZoom;
              
            this.viewport.scrollLeft = sx * this.zoom - cx;
             this.viewport.scrollTop = sy * this.zoom - cy;
              
             this.updateMarkersPosition();
             if (this.zoomTextSpan) {
                this.zoomTextSpan.textContent = `${Math.round(this.zoom * 100)}%`;
            }
         }
         }
          
          
         reset() {
         // Conectar botões
            this.zoom = this.config.mapConfig?.defaultZoom || 1;
        document.getElementById('zoom-in-' + id).addEventListener('click', zoomIn);
            this.container.querySelectorAll('.mw-image').forEach(img => {
        document.getElementById('zoom-out-' + id).addEventListener('click', zoomOut);
                img.style.transform = `scale(${this.zoom})`;
        document.getElementById('reset-' + id).addEventListener('click', resetarView);
             });
        document.getElementById('prev-' + id).addEventListener('click', irAndarAnterior);
             this.viewport.scrollLeft = 0;
        document.getElementById('next-' + id).addEventListener('click', irProximoAndar);
            this.viewport.scrollTop = 0;
       
             this.updateMarkersPosition();
        // Arrastar
             if (this.zoomTextSpan) {
        viewport.addEventListener('mousedown', function(e) {
                this.zoomTextSpan.textContent = `${Math.round(this.zoom * 100)}%`;
            if (e.target.closest('.mapa-marker')) return;
            }
             arrastando = true;
         }
             arrasteInicio = { x: e.clientX, y: e.clientY, scrollLeft: viewport.scrollLeft, scrollTop: viewport.scrollTop };
             viewport.style.cursor = 'grabbing';
             e.preventDefault();
         });
          
          
         nextFloor() {
         window.addEventListener('mousemove', function(e) {
             const idx = this.config.layers.findIndex(l => l.id === this.currentFloor);
             if (!arrastando) return;
             if (idx < this.config.layers.length - 1) {
            viewport.scrollLeft = arrasteInicio.scrollLeft - (e.clientX - arrasteInicio.x);
                this.goToFloor(this.config.layers[idx + 1].id);
             viewport.scrollTop = arrasteInicio.scrollTop - (e.clientY - arrasteInicio.y);
            }
         });
         }
          
          
         prevFloor() {
         window.addEventListener('mouseup', function() { arrastando = false; viewport.style.cursor = 'grab'; });
            const idx = this.config.layers.findIndex(l => l.id === this.currentFloor);
            if (idx > 0) {
                this.goToFloor(this.config.layers[idx - 1].id);
            }
        }
          
          
         goToFloor(floorId) {
         viewport.addEventListener('wheel', function(e) {
            this.currentFloor = floorId;
             if (e.ctrlKey) { e.preventDefault(); e.deltaY < 0 ? zoomIn() : zoomOut(); }
           
         });
            this.layers.forEach(layer => {
                const layerFloor = parseInt(layer.getAttribute('data-floor'));
                layer.style.display = layerFloor === floorId ? 'block' : 'none';
            });
           
            const floorData = this.config.layers.find(l => l.id === floorId);
             if (floorData && this.floorNameSpan) {
                this.floorNameSpan.textContent = floorData.name;
            }
           
            setTimeout(() => this.updateMarkersPosition(), 50);
         }
    }
   
    // ==================== INICIALIZAÇÃO DO WIDGET ====================
    function initWidget() {
        // Procurar todos os containers do widget
        const containers = document.querySelectorAll('[data-mw-map]');
          
          
         containers.forEach(container => {
         viewport.addEventListener('mousemove', function(e) {
             const jsonStr = container.getAttribute('data-mw-config');
             var rect = viewport.getBoundingClientRect();
             if (!jsonStr) return;
             var x = (e.clientX - rect.left + viewport.scrollLeft) / zoomAtual;
              
             var y = (e.clientY - rect.top + viewport.scrollTop) / zoomAtual;
            try {
             coordsDiv.textContent = '📍 ' + Math.round(x) + ', ' + Math.round(y) + ' | ' + Math.round(zoomAtual * 100) + '%';
                const config = JSON.parse(jsonStr);
                new MWMap(container, config);
             } catch(e) {
                console.error('Erro ao criar mapa:', e);
                container.innerHTML = '<div style="padding:20px; text-align:center; color:#ef4444;">Erro ao carregar mapa</div>';
            }
         });
         });
       
        renderizarCamadas();
     }
     }
   
    // Injetar estilos
    if (!document.getElementById('mw-map-styles')) {
        const styleTag = document.createElement('style');
        styleTag.id = 'mw-map-styles';
        styleTag.textContent = styles;
        document.head.appendChild(styleTag);
    }
   
    // Aguardar DOM e inicializar
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initWidget);
    } else {
        initWidget();
    }
   
    // Expor para uso global
    window.MWMap = MWMap;
})();
})();
 
</script></includeonly>
</script>
</includeonly>

Edição atual tal como às 20h38min de 9 de abril de 2026