() => { /** * Dual-skin boot: pick desktop vs phone layout, keep a single set of IDs in the DOM. * * Rules (first match wins for force flags): * ?ui=phone | ?ui=mobile → phone * ?ui=desktop | ?ui=pc → desktop * Mobile / phone UA → phone (HF embeds often report a WIDE iframe width) * max-width ≤ 768px → phone * else → desktop * * Unused skin is removed so getElementById stays unambiguous for gallery.js. */ function wantPhoneUI() { try { var q = new URLSearchParams(window.location.search || ""); var force = (q.get("ui") || "").toLowerCase(); if (force === "phone" || force === "mobile") return true; if (force === "desktop" || force === "pc") return false; } catch (e) {} // Real phones/tablets in portrait-ish embeds: don't trust iframe CSS width alone try { var ua = navigator.userAgent || ""; if (/Android.+Mobile|iPhone|iPod|webOS|BlackBerry|IEMobile|Opera Mini/i.test(ua)) { return true; } // iPad / Android tablet — treat as phone layout (single column) if (/iPad|Android(?!.*Mobile)|Tablet/i.test(ua)) { return true; } if (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1) { return true; // iPadOS desktop UA } } catch (e2) {} try { if (window.matchMedia("(max-width: 768px)").matches) return true; // Very short + narrow-ish viewport (mobile browser chrome) if (window.matchMedia("(max-width: 900px) and (max-height: 900px) and (pointer: coarse)").matches) { return true; } } catch (e3) { if ((window.innerWidth || 1024) <= 768) return true; } return false; } /** * Gradio / Spaces often inject maximum-scale=1 or user-scalable=no, which kills * pinch-zoom. Restore a viewport that allows zoom (a11y + your request). */ function ensureZoomableViewport() { try { var allow = "width=device-width, initial-scale=1, viewport-fit=cover, " + "minimum-scale=0.5, maximum-scale=5, user-scalable=yes"; var metas = document.querySelectorAll('meta[name="viewport"]'); if (!metas || !metas.length) { var m = document.createElement("meta"); m.setAttribute("name", "viewport"); m.setAttribute("content", allow); (document.head || document.documentElement).appendChild(m); return; } for (var i = 0; i < metas.length; i++) { metas[i].setAttribute("content", allow); } } catch (e) {} } /** * HF / Gradio wrap the app in transformed/overflow ancestors, so position:fixed * on .phone-run is relative to that box (often off-screen). Moving the bar to * document.body makes fixed = real viewport. Embeds often don't have those * ancestors — which is why floating Edit already worked there. */ function pinPhoneRunBar() { try { if (window.__qwenUiSkin !== "phone") return; var bar = document.querySelector(".phone-run"); if (!bar || !document.body) return; if (bar.parentNode === document.body) { bar.classList.add("phone-run--ported"); return; } // Marker so we can leave a spacer if needed (stack already has padding-bottom) bar.setAttribute("data-ported", "1"); bar.classList.add("phone-run--ported"); document.body.appendChild(bar); } catch (e) {} } function activate() { var phone = wantPhoneUI(); var desktop = document.getElementById("skin-desktop"); var phoneEl = document.getElementById("skin-phone"); // Skins not in DOM yet if (!desktop && !phoneEl) return false; // Always allow pinch/zoom — do not lock scale on phone or desktop ensureZoomableViewport(); if (phone) { if (desktop && desktop.parentNode) desktop.parentNode.removeChild(desktop); if (phoneEl) { phoneEl.hidden = false; phoneEl.removeAttribute("hidden"); phoneEl.style.display = ""; } document.documentElement.setAttribute("data-ui-skin", "phone"); try { document.body && document.body.setAttribute("data-ui-skin", "phone"); } catch (e4) {} window.__qwenUiSkin = "phone"; pinPhoneRunBar(); // DOM may still be settling on HF setTimeout(pinPhoneRunBar, 100); setTimeout(pinPhoneRunBar, 500); setTimeout(pinPhoneRunBar, 1500); } else { // If we previously ported the bar, remove orphan try { var orphan = document.querySelector("body > .phone-run"); if (orphan) orphan.parentNode.removeChild(orphan); } catch (e6) {} if (phoneEl && phoneEl.parentNode) phoneEl.parentNode.removeChild(phoneEl); if (desktop) { desktop.hidden = false; desktop.removeAttribute("hidden"); desktop.style.display = ""; } document.documentElement.setAttribute("data-ui-skin", "desktop"); try { document.body && document.body.setAttribute("data-ui-skin", "desktop"); } catch (e5) {} window.__qwenUiSkin = "desktop"; } return true; } // Expose for gallery.js / lora_demo.js if boot load order is flaky window.__qwenActivateUiSkin = activate; window.__qwenWantPhoneUI = wantPhoneUI; window.__qwenEnsureZoomableViewport = ensureZoomableViewport; window.__qwenPinPhoneRunBar = pinPhoneRunBar; function tryActivate(n) { // Fix zoom even before skins appear (Gradio may set a locked viewport early) ensureZoomableViewport(); if (activate()) { // Gradio sometimes rewrites viewport after load — re-assert zoom a few times setTimeout(ensureZoomableViewport, 200); setTimeout(ensureZoomableViewport, 1000); setTimeout(ensureZoomableViewport, 3000); return; } if (n < 60) setTimeout(function () { tryActivate(n + 1); }, 50); } tryActivate(0); }