Spaces:
Running
Running
Mobile: lock viewport (no pinch/double-tap/input-focus zoom) + hold-to-aim skills
Browse filesapp.py HEAD: re-assert a locked viewport (user-scalable=no, maximum-scale=1), inputs at
16px so iOS doesn't auto-zoom on focus, touch-action:manipulation to kill double-tap zoom,
and block iOS gesture (pinch) events — fixes the readability drift on mobile. Rebuilt
comboBattler with hold-to-aim/release-to-cast skill buttons + non-selectable HUD buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- app.py +22 -1
- web/comboBattler.js +45 -7
app.py
CHANGED
|
@@ -150,7 +150,28 @@ THEME = ('<style>'
|
|
| 150 |
# (SPACE_ID/SPACE_HOST are set there); local `python app.py` over http omits it and just works.
|
| 151 |
_CSP = ('<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">'
|
| 152 |
if (os.environ.get("SPACE_ID") or os.environ.get("SPACE_HOST")) else '')
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
+ HIDE_TABS + FONTS + THEME +
|
| 155 |
'<link rel="stylesheet" href="/web/shell/tokens.css">'
|
| 156 |
'<link rel="stylesheet" href="/web/shell/sidebar.css">'
|
|
|
|
| 150 |
# (SPACE_ID/SPACE_HOST are set there); local `python app.py` over http omits it and just works.
|
| 151 |
_CSP = ('<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">'
|
| 152 |
if (os.environ.get("SPACE_ID") or os.environ.get("SPACE_HOST")) else '')
|
| 153 |
+
|
| 154 |
+
# Mobile zoom is a readability hazard for a full-screen game: pinch-zoom + double-tap zoom drift
|
| 155 |
+
# the canvas, and focusing a text input auto-zooms on iOS. Lock the viewport (re-asserted, since
|
| 156 |
+
# Gradio sets its own), keep inputs at 16px (iOS only zooms on <16px focus), kill double-tap via
|
| 157 |
+
# touch-action, and block iOS gesture (pinch) events.
|
| 158 |
+
NOZOOM = (
|
| 159 |
+
'<style>'
|
| 160 |
+
'html,body,gradio-app,.gradio-container{touch-action:manipulation;}'
|
| 161 |
+
'.gradio-container input,.gradio-container textarea,.gradio-container select,'
|
| 162 |
+
'input,textarea,select{font-size:16px !important;}'
|
| 163 |
+
'</style>'
|
| 164 |
+
'<script>(function(){'
|
| 165 |
+
'function vp(){var m=document.querySelector("meta[name=viewport]");'
|
| 166 |
+
'if(!m){m=document.createElement("meta");m.setAttribute("name","viewport");document.head.appendChild(m);}'
|
| 167 |
+
'm.setAttribute("content","width=device-width, initial-scale=1, maximum-scale=1, '
|
| 168 |
+
'minimum-scale=1, user-scalable=no, viewport-fit=cover");}'
|
| 169 |
+
'vp();document.addEventListener("DOMContentLoaded",vp);setTimeout(vp,800);setTimeout(vp,2500);'
|
| 170 |
+
'["gesturestart","gesturechange","gestureend"].forEach(function(e){'
|
| 171 |
+
'document.addEventListener(e,function(ev){ev.preventDefault();},{passive:false});});'
|
| 172 |
+
'})();</script>'
|
| 173 |
+
)
|
| 174 |
+
HEAD = (_CSP + NOZOOM
|
| 175 |
+ HIDE_TABS + FONTS + THEME +
|
| 176 |
'<link rel="stylesheet" href="/web/shell/tokens.css">'
|
| 177 |
'<link rel="stylesheet" href="/web/shell/sidebar.css">'
|
web/comboBattler.js
CHANGED
|
@@ -6208,6 +6208,14 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6208 |
}
|
| 6209 |
let skillRowEl = null, adrFill = null, threatBadge = null;
|
| 6210 |
const skillBtns = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6211 |
function skillReady(a, s) {
|
| 6212 |
if (!a || !s) return false;
|
| 6213 |
if (s.cost?.adrenaline && a.adrenaline < s.cost.adrenaline) return false;
|
|
@@ -6218,15 +6226,24 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6218 |
}
|
| 6219 |
function roundBtn(label, on, big) {
|
| 6220 |
const b = document.createElement("button");
|
| 6221 |
-
b.
|
| 6222 |
-
|
| 6223 |
-
b.style.cssText = `pointer-events:auto;touch-action:manipulation;width:${s}px;height:${s}px;border-radius:50%;border:1px solid #20262e;background:rgba(20,24,33,.78);color:#e8e8e8;font:600 ${big ? 22 : 15}px monospace;cursor:pointer`;
|
| 6224 |
b.addEventListener("pointerdown", (ev) => {
|
| 6225 |
ev.preventDefault();
|
| 6226 |
on();
|
| 6227 |
});
|
| 6228 |
return b;
|
| 6229 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6230 |
function rebuildSkillButtons() {
|
| 6231 |
if (!skillRowEl) return;
|
| 6232 |
skillRowEl.replaceChildren();
|
|
@@ -6234,9 +6251,10 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6234 |
const bar = pa()?.bar || [];
|
| 6235 |
bar.forEach((s, i) => {
|
| 6236 |
const cost = s.cost?.adrenaline ? `${s.cost.adrenaline}adr` : s.cost?.energy ? `${s.cost.energy}en` : "";
|
| 6237 |
-
const b =
|
| 6238 |
-
|
| 6239 |
-
|
|
|
|
| 6240 |
b.title = `${s.name || ""}${cost ? " \xB7 " + cost : ""}`;
|
| 6241 |
skillRowEl.appendChild(b);
|
| 6242 |
skillBtns.push({ el: b, skill: s });
|
|
@@ -6349,9 +6367,22 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6349 |
threatBadge = document.createElement("div");
|
| 6350 |
threatBadge.style.cssText = "position:absolute;top:14px;left:50%;transform:translateX(-50%);pointer-events:none;padding:5px 12px;border-radius:999px;background:rgba(20,24,33,.8);border:1px solid #2a3340;color:#e8e8e8;font:700 12px monospace;letter-spacing:.04em";
|
| 6351 |
wrap.appendChild(threatBadge);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6352 |
wrap._cleanup = () => {
|
| 6353 |
window.removeEventListener("pointerup", up);
|
| 6354 |
window.removeEventListener("pointercancel", up);
|
|
|
|
|
|
|
| 6355 |
};
|
| 6356 |
host.appendChild(wrap);
|
| 6357 |
controlsEl = wrap;
|
|
@@ -6537,7 +6568,13 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6537 |
const p = pa();
|
| 6538 |
rings.clear();
|
| 6539 |
if (!p) return;
|
| 6540 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6541 |
}
|
| 6542 |
function drawMarkers() {
|
| 6543 |
if (!markers) return;
|
|
@@ -6604,6 +6641,7 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6604 |
skillBtns.length = 0;
|
| 6605 |
dash = null;
|
| 6606 |
dodgeCdUntil = 0;
|
|
|
|
| 6607 |
try {
|
| 6608 |
markers?.destroy();
|
| 6609 |
} catch {
|
|
|
|
| 6208 |
}
|
| 6209 |
let skillRowEl = null, adrFill = null, threatBadge = null;
|
| 6210 |
const skillBtns = [];
|
| 6211 |
+
let aimSkill = null, aimIdx = 0;
|
| 6212 |
+
const BTN_CSS = (s, big) => `pointer-events:auto;touch-action:none;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;width:${s}px;height:${s}px;border-radius:50%;border:1px solid #20262e;background:rgba(20,24,33,.78);color:#e8e8e8;font:600 ${big ? 22 : 15}px monospace;cursor:pointer;display:flex;align-items:center;justify-content:center`;
|
| 6213 |
+
const btnLabel = (text) => {
|
| 6214 |
+
const s = document.createElement("span");
|
| 6215 |
+
s.textContent = text;
|
| 6216 |
+
s.style.cssText = "pointer-events:none;user-select:none;-webkit-user-select:none";
|
| 6217 |
+
return s;
|
| 6218 |
+
};
|
| 6219 |
function skillReady(a, s) {
|
| 6220 |
if (!a || !s) return false;
|
| 6221 |
if (s.cost?.adrenaline && a.adrenaline < s.cost.adrenaline) return false;
|
|
|
|
| 6226 |
}
|
| 6227 |
function roundBtn(label, on, big) {
|
| 6228 |
const b = document.createElement("button");
|
| 6229 |
+
b.style.cssText = BTN_CSS(big ? 70 : 46, big);
|
| 6230 |
+
b.appendChild(btnLabel(label));
|
|
|
|
| 6231 |
b.addEventListener("pointerdown", (ev) => {
|
| 6232 |
ev.preventDefault();
|
| 6233 |
on();
|
| 6234 |
});
|
| 6235 |
return b;
|
| 6236 |
}
|
| 6237 |
+
function holdBtn(label, onDown) {
|
| 6238 |
+
const b = document.createElement("button");
|
| 6239 |
+
b.style.cssText = BTN_CSS(46, false);
|
| 6240 |
+
b.appendChild(btnLabel(label));
|
| 6241 |
+
b.addEventListener("pointerdown", (ev) => {
|
| 6242 |
+
ev.preventDefault();
|
| 6243 |
+
onDown();
|
| 6244 |
+
});
|
| 6245 |
+
return b;
|
| 6246 |
+
}
|
| 6247 |
function rebuildSkillButtons() {
|
| 6248 |
if (!skillRowEl) return;
|
| 6249 |
skillRowEl.replaceChildren();
|
|
|
|
| 6251 |
const bar = pa()?.bar || [];
|
| 6252 |
bar.forEach((s, i) => {
|
| 6253 |
const cost = s.cost?.adrenaline ? `${s.cost.adrenaline}adr` : s.cost?.energy ? `${s.cost.energy}en` : "";
|
| 6254 |
+
const b = holdBtn(String(i + 1), () => {
|
| 6255 |
+
aimSkill = s;
|
| 6256 |
+
aimIdx = i + 1;
|
| 6257 |
+
});
|
| 6258 |
b.title = `${s.name || ""}${cost ? " \xB7 " + cost : ""}`;
|
| 6259 |
skillRowEl.appendChild(b);
|
| 6260 |
skillBtns.push({ el: b, skill: s });
|
|
|
|
| 6367 |
threatBadge = document.createElement("div");
|
| 6368 |
threatBadge.style.cssText = "position:absolute;top:14px;left:50%;transform:translateX(-50%);pointer-events:none;padding:5px 12px;border-radius:999px;background:rgba(20,24,33,.8);border:1px solid #2a3340;color:#e8e8e8;font:700 12px monospace;letter-spacing:.04em";
|
| 6369 |
wrap.appendChild(threatBadge);
|
| 6370 |
+
const castOnRelease = () => {
|
| 6371 |
+
if (aimSkill) {
|
| 6372 |
+
req.skill = aimIdx;
|
| 6373 |
+
aimSkill = null;
|
| 6374 |
+
}
|
| 6375 |
+
};
|
| 6376 |
+
const cancelAim = () => {
|
| 6377 |
+
aimSkill = null;
|
| 6378 |
+
};
|
| 6379 |
+
window.addEventListener("pointerup", castOnRelease);
|
| 6380 |
+
window.addEventListener("pointercancel", cancelAim);
|
| 6381 |
wrap._cleanup = () => {
|
| 6382 |
window.removeEventListener("pointerup", up);
|
| 6383 |
window.removeEventListener("pointercancel", up);
|
| 6384 |
+
window.removeEventListener("pointerup", castOnRelease);
|
| 6385 |
+
window.removeEventListener("pointercancel", cancelAim);
|
| 6386 |
};
|
| 6387 |
host.appendChild(wrap);
|
| 6388 |
controlsEl = wrap;
|
|
|
|
| 6568 |
const p = pa();
|
| 6569 |
rings.clear();
|
| 6570 |
if (!p) return;
|
| 6571 |
+
const r = p.weapon.range;
|
| 6572 |
+
if (aimSkill) {
|
| 6573 |
+
const inRange = nearestFoeDist() <= r + p.radius;
|
| 6574 |
+
rings.ellipse(p.x, p.y, r, r * 0.6).stroke({ width: 2.5 / G, color: inRange ? 3461208 : 16738922, alpha: 0.7 });
|
| 6575 |
+
return;
|
| 6576 |
+
}
|
| 6577 |
+
rings.ellipse(p.x, p.y, r, r * 0.6).stroke({ width: 1.5 / G, color: 16777215, alpha: 0.25 });
|
| 6578 |
}
|
| 6579 |
function drawMarkers() {
|
| 6580 |
if (!markers) return;
|
|
|
|
| 6641 |
skillBtns.length = 0;
|
| 6642 |
dash = null;
|
| 6643 |
dodgeCdUntil = 0;
|
| 6644 |
+
aimSkill = null;
|
| 6645 |
try {
|
| 6646 |
markers?.destroy();
|
| 6647 |
} catch {
|