(function () {
/* ── 1. Desktop: map column slide-in ── */
function initMapCol() {
var el = document.getElementById('map-col');
if (!el) { setTimeout(initMapCol, 300); return; }
new MutationObserver(function () {
var hasIframe = !!el.querySelector('iframe');
el.classList.toggle('map-active', hasIframe);
setMapReady(hasIframe);
}).observe(el, { childList: true, subtree: true });
}
setTimeout(initMapCol, 500);
/* ── 2. Chat active state (centered → bottom input) ── */
function initChatCenter() {
var chatCol = document.getElementById('chat-col-inner');
if (!chatCol) { setTimeout(initChatCenter, 300); return; }
function check() {
var chatbot = document.getElementById('main-chatbot');
if (!chatbot) return;
var log = chatbot.querySelector('[role="log"]');
var active = log ? log.children.length > 0 : false;
chatCol.classList.toggle('chat-active', active);
document.body.classList.toggle('chat-active', active);
}
// Observe the stable column — survives Gradio's DOM rebuilds on clear/reset
new MutationObserver(check).observe(chatCol, { childList: true, subtree: true });
}
setTimeout(initChatCenter, 300);
/* ── 3. Mobile: in-chat map button & fullscreen overlay ── */
var overlay = null;
var mapChatBtn = null;
var mapReady = false;
var reinjecting = false;
var imgLightbox = null;
function setMapReady(ready) {
mapReady = ready;
if (ready) {
ensureMapBtnLast();
// If overlay is open and map updated, refresh its content
if (overlay && overlay.classList.contains('active')) {
loadMapIntoOverlay();
}
} else {
if (mapChatBtn) { mapChatBtn.remove(); mapChatBtn = null; }
}
}
/* Keep the map button as the last child of [role="log"] */
function ensureMapBtnLast() {
if (reinjecting || !mapReady) return;
var chatbot = document.getElementById('main-chatbot');
var log = chatbot && chatbot.querySelector('[role="log"]');
if (!log) return;
if (!mapChatBtn) {
mapChatBtn = buildMapChatBtn();
}
if (log.lastElementChild !== mapChatBtn) {
reinjecting = true;
log.appendChild(mapChatBtn);
// Reset flag after microtasks + one macrotask so the observer
// callback triggered by appendChild is already processed
setTimeout(function () { reinjecting = false; }, 0);
}
}
function buildMapChatBtn() {
var wrap = document.createElement('div');
wrap.id = 'map-chat-btn';
var btn = document.createElement('button');
btn.setAttribute('aria-label', 'Karte anzeigen');
btn.innerHTML =
'' +
'Karte anzeigen';
btn.addEventListener('click', openMapOverlay);
wrap.appendChild(btn);
return wrap;
}
/* Observer on chatbot: keeps map button last during streaming */
function initChatBotObserver() {
var chatbot = document.getElementById('main-chatbot');
if (!chatbot) { setTimeout(initChatBotObserver, 400); return; }
new MutationObserver(function () {
if (!reinjecting) ensureMapBtnLast();
}).observe(chatbot, { childList: true, subtree: true });
}
setTimeout(initChatBotObserver, 600);
/* ── 4. Fullscreen overlay ── */
function buildOverlay() {
var chatCol = document.getElementById('chat-col-inner');
if (!chatCol) { setTimeout(buildOverlay, 400); return; }
overlay = document.createElement('div');
overlay.id = 'map-overlay';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
overlay.setAttribute('aria-label', 'Kartenansicht');
overlay.innerHTML =
'
' +
'Karte' +
'
' +
'';
document.body.appendChild(overlay);
overlay.querySelector('#map-overlay-close').addEventListener('click', closeMapOverlay);
}
setTimeout(buildOverlay, 600);
/* ── 5. Image lightbox for chatbot charts ── */
function buildImageLightbox() {
var chatbot = document.getElementById('main-chatbot');
if (!chatbot) { setTimeout(buildImageLightbox, 400); return; }
var lb = document.createElement('div');
lb.id = 'img-lightbox';
lb.setAttribute('role', 'dialog');
lb.setAttribute('aria-modal', 'true');
lb.setAttribute('aria-label', 'Full-size chart');
var lbImg = document.createElement('img');
lbImg.setAttribute('alt', '');
lb.appendChild(lbImg);
document.body.appendChild(lb);
imgLightbox = lb;
lb.addEventListener('click', function () {
lb.classList.remove('active');
});
lbImg.addEventListener('click', function (e) {
e.stopPropagation();
});
// Intercept clicks anywhere in .image-container (capture phase = before Gradio's broken handler)
chatbot.addEventListener('click', function (e) {
var container = e.target.closest('.image-container');
if (!container) return;
var img = container.querySelector('.image-frame img');
if (!img || !img.src) return;
lbImg.src = img.src;
lb.classList.add('active');
e.stopPropagation();
}, true);
}
setTimeout(buildImageLightbox, 700);
/* ── 6. Reset textarea height after submit clears the input ── */
function initTextareaReset() {
var chatCol = document.getElementById('chat-col-inner');
if (!chatCol) { setTimeout(initTextareaReset, 400); return; }
new MutationObserver(function () {
var ta = chatCol.querySelector('.multimodal-textbox textarea');
if (ta && !ta.value && ta.style.height) {
ta.style.height = '';
}
}).observe(chatCol, { childList: true, subtree: true, characterData: true });
}
setTimeout(initTextareaReset, 500);
/* ── 7. Info modal & dark-mode toggle (mobile footer) ── */
function initFooterBtns() {
var infoBtn = document.getElementById('footer-info-btn');
var backdrop = document.getElementById('info-modal-backdrop');
if (!infoBtn || !backdrop) { setTimeout(initFooterBtns, 400); return; }
infoBtn.addEventListener('click', function () {
backdrop.classList.add('active');
document.documentElement.style.overflow = 'hidden';
});
function closeInfoModal() {
backdrop.classList.remove('active');
document.documentElement.style.overflow = '';
}
backdrop.addEventListener('click', function (e) {
if (e.target === backdrop) closeInfoModal();
});
document.addEventListener('keydown', function (e) {
if (e.key !== 'Escape') return;
if (imgLightbox && imgLightbox.classList.contains('active')) { imgLightbox.classList.remove('active'); return; }
if (backdrop.classList.contains('active')) closeInfoModal();
else if (overlay && overlay.classList.contains('active')) closeMapOverlay();
});
}
setTimeout(initFooterBtns, 700);
function loadMapIntoOverlay() {
var mapCol = document.getElementById('map-col');
var srcIframe = mapCol && mapCol.querySelector('iframe');
if (!srcIframe) return;
var content = document.getElementById('map-overlay-content');
if (!content) return;
content.innerHTML = '';
var iframe = document.createElement('iframe');
var srcdoc = srcIframe.srcdoc;
if (srcdoc) {
iframe.srcdoc = srcdoc;
} else if (srcIframe.src) {
iframe.src = srcIframe.src;
}
var sandbox = srcIframe.getAttribute('sandbox');
if (sandbox) iframe.setAttribute('sandbox', sandbox);
iframe.style.cssText = 'width:100%;height:100%;border:none;display:block;';
content.appendChild(iframe);
}
function openMapOverlay() {
if (!overlay) return;
loadMapIntoOverlay();
overlay.classList.add('active');
document.documentElement.style.overflow = 'hidden';
}
function closeMapOverlay() {
if (!overlay) return;
overlay.classList.remove('active');
document.documentElement.style.overflow = '';
setTimeout(function () {
if (!overlay.classList.contains('active')) {
var content = document.getElementById('map-overlay-content');
if (content) content.innerHTML = '';
}
}, 380);
}
})();