1(function(){ 2 const MARKER = 'MZ-PWA-INSTALL-WIDGET-V5'; 3 if (window.__mzPwaInstallWidgetLoaded) return; 4 window.__mzPwaInstallWidgetLoaded = true; 5 6 const cfg = window.MZ_PWA_CONFIG || {}; 7 const storageKey = 'mz_pwa_install_widget_closed_until'; 8 const appUrl = cfg.appUrl || '/app/'; 9 const manifestUrl = cfg.manifestUrl || '/manifest.webmanifest'; 10 const swUrl = cfg.swUrl || '/webpushr-sw.js'; 11 const scope = cfg.scope || '/'; 12 const forceShow = !!cfg.forceShow; 13 const showDelay = Number(cfg.showDelay || 900); 14 const statsEndpoint = cfg.statsEndpoint || '/app/pwa-install-counter.php'; 15 const idKey = 'mz_pwa_install_id_v1'; 16 17 let deferredPrompt = null; 18 let widgetShownTracked = false; 19 20 function isStandalone(){ return (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) || window.navigator.standalone === true; } 21 function isIOS(){ return /iphone|ipad|ipod/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); } 22 function now(){ return Date.now(); } 23 function closed(){ if (forceShow) return false; try { return Number(localStorage.getItem(storageKey) || 0) > now(); } catch(e){ return false; } } 24 function rememberClose(hours){ try { localStorage.setItem(storageKey, String(now() + hours*3600*1000)); } catch(e){} } 25 function source(){ return new URLSearchParams(location.search).get('source') || (location.pathname.startsWith('/app') ? 'app' : 'site'); } 26 function uid(){ 27 try { let v = localStorage.getItem(idKey); if (!v) { v = (crypto && crypto.randomUUID) ? crypto.randomUUID() : ('mz_' + Date.now() + '_' + Math.random().toString(16).slice(2)); localStorage.setItem(idKey, v); } return v; } 28 catch(e){ return 'no_storage_' + Math.random().toString(16).slice(2); } 29 } 30 function track(event){ 31 try { fetch(statsEndpoint, {method:'POST', headers:{'Content-Type':'application/json'}, keepalive:true, body:JSON.stringify({event, id:uid(), source:source(), path:location.pathname + location.search})}).catch(()=>{}); } catch(e) {} 32 } 33 function shouldShowHome(){ return !location.pathname.replace(/\/index\.php$/,'/').startsWith('/app/') && (isStandalone() || /pwa_hub|pwa|app/i.test(location.search) || cfg.forceHomeButton); } 34 35 function ensureHead(){ 36 if (!document.querySelector('link[rel="manifest"]')) { const l=document.createElement('link'); l.rel='manifest'; l.href=manifestUrl; document.head.appendChild(l); } 37 if (!document.querySelector('meta[name="theme-color"]')) { const m=document.createElement('meta'); m.name='theme-color'; m.content='#07111f'; document.head.appendChild(m); } 38 if (!document.querySelector('link[rel="apple-touch-icon"]')) { const a=document.createElement('link'); a.rel='apple-touch-icon'; a.href='/assets/pwa/icons/meteozentrale-app-apple-180.png'; document.head.appendChild(a); } 39 } 40 function registerSW(){ 41 if ('serviceWorker' in navigator) { 42 window.addEventListener('load', () => { navigator.serviceWorker.register(swUrl, {scope}).catch(err => console.warn('Meteozentrale PWA Service Worker:', err)); }, {once:true}); 43 } 44 } 45 function html(){ 46 return ` 47 <div class="mz-pwa-widget mz-pwa-widget-v5" id="mz-pwa-widget" hidden data-marker="${MARKER}"> 48 <div class="mz-pwa-card" role="region" aria-label="Meteozentrale App installieren"> 49 <div class="mz-pwa-dot"><span>ð¡</span></div> 50 <div class="mz-pwa-copy"><div class="mz-pwa-title">Meteozentrale als App nutzen</div><div class="mz-pwa-sub">SkyGuard, Wetternews, Regen, Sturm und Modelle direkt vom Startbildschirm öffnen.</div></div> 51 <div class="mz-pwa-actions"><button class="mz-pwa-install" type="button" id="mz-pwa-install-btn">App installieren</button><button class="mz-pwa-close" type="button" id="mz-pwa-close" aria-label="Hinweis schlieÃen">Ã</button></div> 52 </div> 53 </div> 54 <button class="mz-pwa-mini" id="mz-pwa-mini" type="button" hidden>ð² App</button> 55 <a class="mz-pwa-home" id="mz-pwa-home" href="${appUrl}" hidden aria-label="Zur Meteozentrale App-Zentrale">â App-Zentrale</a> 56 <div class="mz-pwa-modal" id="mz-pwa-modal" hidden role="dialog" aria-modal="true" aria-labelledby="mz-pwa-modal-title"> 57 <div class="mz-pwa-modal-card"> 58 <div class="mz-pwa-modal-head"><div><h2 id="mz-pwa-modal-title">Meteozentrale-App installieren</h2><p>Aktuelle Installationshilfe für Chrome, Edge und Safari. Falls ein Browser âveraltetâ meldet: Seite neu laden und in Chrome/Safari öffnen.</p></div><button class="mz-pwa-modal-close" id="mz-pwa-modal-close" type="button" aria-label="SchlieÃen">Ã</button></div> 59 <div class="mz-pwa-modal-body"> 60 <div class="mz-pwa-step"><span>ð±</span><div><b>
60Android / Chrome / Edge:</b><br>Browsermenü öffnen und âApp installierenâ oder âZum Startbildschirm hinzufügenâ wählen.</div></div> 61 <div class="mz-pwa-step"><span>ð</span><div><b>iPhone / iPad:</b><br>In Safari das Teilen-Symbol öffnen und âZum Home-Bildschirmâ antippen.</div></div> 62 <div class="mz-pwa-step"><span>ð»</span><div><b>Desktop:</b><br>Chrome oder Edge zeigen die Installation meist rechts in der Adressleiste oder im Menü an.</div></div> 63 <div class="mz-pwa-step mz-pwa-private"><span>ðµï¸</span><div><b>Privatfenster:</b><br>Dort wird Installation oft nicht angeboten. Bitte normal testen.</div></div> 64 </div> 65 <div class="mz-pwa-modal-foot">Direkter Installationslink: <b>${appUrl}</b> · Hinweis: In-App-Browser von Facebook können Installation und Scrollen blockieren.</div> 66 </div> 67 </div>`; 68 } 69 function inject(){ 70 if (document.getElementById('mz-pwa-widget')) return; 71 document.body.insertAdjacentHTML('beforeend', html()); 72 const widget = document.getElementById('mz-pwa-widget'); 73 const mini = document.getElementById('mz-pwa-mini'); 74 const modal = document.getElementById('mz-pwa-modal'); 75 const installBtn = document.getElementById('mz-pwa-install-btn'); 76 const closeBtn = document.getElementById('mz-pwa-close'); 77 const modalClose = document.getElementById('mz-pwa-modal-close'); 78 const home = document.getElementById('mz-pwa-home'); 79 80 function showModal(){ modal.hidden = false; } 81 function hideModal(){ modal.hidden = true; } 82 function showWidget(){ 83 if (isStandalone()) return; 84 if (closed()) { mini.hidden = false; return; } 85 widget.hidden = false; 86 if (!widgetShownTracked) { widgetShownTracked = true; track('prompt_shown'); } 87 } 88 function hideWidget(hours){ widget.hidden = true; mini.hidden = false; if (hours) { rememberClose(hours); track('install_dismiss'); } } 89 function showHome(){ if (shouldShowHome()) home.hidden = false; } 90 91 installBtn.addEventListener('click', async () => { 92 track('install_click'); 93 if (deferredPrompt) { 94 try { deferredPrompt.prompt(); await deferredPrompt.userChoice; } catch(e) {} 95 deferredPrompt = null; 96 } else { 97 showModal(); 98 } 99 }); 100 mini.addEventListener('click', () => { mini.hidden = true; widget.hidden = false; if (!widgetShownTracked) { widgetShownTracked = true; track('prompt_shown'); } }); 101 closeBtn.addEventListener('click', () => hideWidget(6)); 102 modalClose.addEventListener('click', hideModal); 103 modal.addEventListener('click', e => { if (e.target === modal) hideModal(); }); 104 document.addEventListener('keydown', e => { if (e.key === 'Escape') hideModal(); }); 105 home.addEventListener('click', () => track('app_home_click')); 106 107 if (isStandalone()) { showHome(); track('standalone_ping'); setInterval(() => track('standalone_ping'), 5*60*1000); return; } 108 setTimeout(showWidget, showDelay); 109 showHome(); 110 } 111 112 window.addEventListener('beforeinstallprompt', (event) => { 113 event.preventDefault(); 114 deferredPrompt = event; 115 const btn = document.getElementById('mz-pwa-install-btn'); if (btn) btn.textContent = 'App installieren'; 116 const widget = document.getElementById('mz-pwa-widget'); 117 if (widget && !closed() && !isStandalone()) { widget.hidden = false; if (!widgetShownTracked) { widgetShownTracked = true; track('prompt_shown'); } } 118 }); 119 window.addEventListener('appinstalled', () => { 120 rememberClose(24*365); 121 track('install_success'); 122 const widget = document.getElementById('mz-pwa-widget'); const mini = document.getElementById('mz-pwa-mini'); 123 if (widget) widget.hidden = true; if (mini) mini.hidden = true; 124 }); 125 126 ensureHead(); 127 registerSW(); 128 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', inject, {once:true}); else inject(); 129})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.