tfrere HF Staff Cursor commited on
Commit
0110fa1
Β·
1 Parent(s): 55c1238

feat: latch touch mode on first real touch, add ?touch=1 test hook

Browse files

(pointer: coarse) misses DevTools device modes and hybrid laptops, so any
real touchstart now arms the thumbs permanently (title page hands the
proof to controls/touch.js via __microduckTouched). ?touch=1 forces the
overlay on any device for desktop testing.

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (2) hide show
  1. controls/touch.js +13 -5
  2. index.html +14 -6
controls/touch.js CHANGED
@@ -15,10 +15,11 @@
15
  // holds the beak open (axes.jaw = 1) while held.
16
  //
17
  // The overlay DOM lives in index.html (#touch-ui); this module only wires
18
- // pointer events to it. `connected` mirrors (pointer: coarse) - rl.js
19
- // reflects it onto body.touch-mode, which is what actually shows the
20
- // overlay - so a desktop never sees the thumbs and a phone always does,
21
- // even before the first touch.
 
22
 
23
  const TOUCH_ALPHA = 0.18; // EMA smoothing toward the stick target
24
  const TOUCH_DEADZONE = 0.12; // normalized deflection ignored around center
@@ -44,9 +45,16 @@ export class TouchSource {
44
 
45
  init() {
46
  this.#mq = window.matchMedia("(pointer: coarse)");
47
- this.#onMq = () => { this.connected = this.#mq.matches; };
 
 
 
 
48
  this.#mq.addEventListener("change", this.#onMq);
49
  this.#onMq();
 
 
 
50
 
51
  const zone = document.getElementById("touch-zone");
52
  const stick = document.getElementById("touch-stick");
 
15
  // holds the beak open (axes.jaw = 1) while held.
16
  //
17
  // The overlay DOM lives in index.html (#touch-ui); this module only wires
18
+ // pointer events to it. `connected` (mirrored onto body.touch-mode by
19
+ // rl.js, which is what actually shows the overlay) arms two ways:
20
+ // - (pointer: coarse) matches: phones/tablets, before any touch;
21
+ // - OR the first real touch anywhere: catches DevTools device modes and
22
+ // hybrid laptops the media query misses. Latching, like a gamepad.
23
 
24
  const TOUCH_ALPHA = 0.18; // EMA smoothing toward the stick target
25
  const TOUCH_DEADZONE = 0.12; // normalized deflection ignored around center
 
45
 
46
  init() {
47
  this.#mq = window.matchMedia("(pointer: coarse)");
48
+ // Latching: once a device has proven it can touch, keep the thumbs.
49
+ // __microduckTouched carries a touch seen by the title page before
50
+ // this module booted.
51
+ if (window.__microduckTouched) this.connected = true;
52
+ this.#onMq = () => { this.connected = this.connected || this.#mq.matches; };
53
  this.#mq.addEventListener("change", this.#onMq);
54
  this.#onMq();
55
+ const armTouch = () => { this.connected = true; };
56
+ window.addEventListener("touchstart", armTouch, { once: true, passive: true });
57
+ this.#disposers.push(() => window.removeEventListener("touchstart", armTouch));
58
 
59
  const zone = document.getElementById("touch-zone");
60
  const stick = document.getElementById("touch-stick");
index.html CHANGED
@@ -1280,12 +1280,20 @@
1280
  pollPad();
1281
 
1282
  // Touch tutorial variant needs the class before rl.js boots; the
1283
- // game loop keeps mirroring the same media query afterwards.
 
 
 
1284
  const coarse = matchMedia("(pointer: coarse)");
1285
- const pollTouch = () =>
1286
- document.body.classList.toggle("touch-mode", coarse.matches);
1287
- coarse.addEventListener("change", pollTouch);
1288
- pollTouch();
 
 
 
 
 
1289
 
1290
  // ── Menu open / close ────────────────────────────────────────────
1291
  const open = () => {
@@ -1368,7 +1376,7 @@
1368
  appended: a plain src attribute can 401 when the browser blocks the
1369
  *.static.hf.space auth cookie inside the hub iframe. -->
1370
  <script type="module">
1371
- const V = "67"; // bump on rl.js changes to defeat heuristic caching
1372
  const sign = new URLSearchParams(location.search).get("__sign");
1373
  const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
1374
  import(`./rl.js?v=${V}${suffix}`);
 
1280
  pollPad();
1281
 
1282
  // Touch tutorial variant needs the class before rl.js boots; the
1283
+ // game loop keeps mirroring the touch source afterwards. Latching:
1284
+ // (pointer: coarse) OR any real touch proves the device (DevTools
1285
+ // device modes and hybrid laptops often fail the media query), and
1286
+ // the __microduckTouched flag hands the proof to controls/touch.js.
1287
  const coarse = matchMedia("(pointer: coarse)");
1288
+ const armTouch = () => {
1289
+ window.__microduckTouched = true;
1290
+ document.body.classList.add("touch-mode");
1291
+ };
1292
+ if (coarse.matches) armTouch();
1293
+ else coarse.addEventListener("change", () => coarse.matches && armTouch());
1294
+ window.addEventListener("touchstart", armTouch, { once: true, passive: true });
1295
+ // ?touch=1 forces the thumbs on any device - desktop testing hook.
1296
+ if (new URLSearchParams(location.search).get("touch") === "1") armTouch();
1297
 
1298
  // ── Menu open / close ────────────────────────────────────────────
1299
  const open = () => {
 
1376
  appended: a plain src attribute can 401 when the browser blocks the
1377
  *.static.hf.space auth cookie inside the hub iframe. -->
1378
  <script type="module">
1379
+ const V = "68"; // bump on rl.js changes to defeat heuristic caching
1380
  const sign = new URLSearchParams(location.search).get("__sign");
1381
  const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
1382
  import(`./rl.js?v=${V}${suffix}`);