polats Claude Opus 4.8 (1M context) commited on
Commit
a0e5062
·
1 Parent(s): 044958d

Mobile: lock viewport (no pinch/double-tap/input-focus zoom) + hold-to-aim skills

Browse files

app.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>

Files changed (2) hide show
  1. app.py +22 -1
  2. 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
- HEAD = (_CSP
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.textContent = label;
6222
- const s = big ? 70 : 46;
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 = roundBtn(String(i + 1), () => {
6238
- req.skill = i + 1;
6239
- }, false);
 
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
- rings.ellipse(p.x, p.y, p.weapon.range, p.weapon.range * 0.6).stroke({ width: 1.5 / G, color: 16777215, alpha: 0.25 });
 
 
 
 
 
 
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 {