Skip to content

Commit a060261

Browse files
claudemhanavan4-prog
authored andcommitted
Phone: three quick ways to the map, with nothing new on screen
Tap the health-and-level pill at the top left (it carries a faint map glyph); pinch the world with two fingers to open it and spread them on the map to put it away; or Map, now first in the menu. Taking the menu way tells you the short ways, once. A pinch is two fingers that came down on the world within a quarter second of each other, so a thumb already on the joystick and a camera finger never make one; while a pinch is being made the camera does not turn and you do not walk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014upby61fMmizgg2cMkKvqW
1 parent 95b8ed3 commit a060261

3 files changed

Lines changed: 97 additions & 4 deletions

File tree

‎client/main.js‎

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6922,6 +6922,9 @@ const joystickEl = document.getElementById('joystick');
69226922
const stickEl = document.getElementById('stick');
69236923
let joyVec = { x: 0, y: 0 };
69246924
let joyActive = false, joyOrigin = { x: 0, y: 0 }, joyTouchId = null;
6925+
// Pinching the world opens the map (see the pinch watcher after the camera's touch handlers).
6926+
const PINCH_RATIO_IN = 0.62, PINCH_RATIO_OUT = 1.45;
6927+
let pinch = null; // { a, b, d0 } while two fingers that came down together are on the world
69256928

69266929
const JOY_BASE = MOBILE_UI ? 128 : 110; // ring diameter (px)
69276930
const JOY_KNOB = MOBILE_UI ? 56 : 46;
@@ -6959,6 +6962,7 @@ if (joyZone && MOBILE_UI) {
69596962
e.preventDefault();
69606963
}, { passive: false });
69616964
joyZone.addEventListener('touchmove', (e) => {
6965+
if (pinch) return; // two fingers came down together: a pinch, not a walk
69626966
for (const t of e.changedTouches) {
69636967
if (t.identifier !== joyTouchId) continue;
69646968
let dx = t.clientX - joyOrigin.x, dy = t.clientY - joyOrigin.y;
@@ -7581,6 +7585,7 @@ canvas.addEventListener('touchstart', (e) => {
75817585
for (const t of e.changedTouches) {
75827586
touchLooks.set(t.identifier, { startX: t.clientX, startY: t.clientY, lastX: t.clientX, lastY: t.clientY, moved: 0 });
75837587
}
7588+
75847589
camGlide.velYaw = 0; camGlide.velPitch = 0; // finger down = direct control again
75857590
}, { passive: true });
75867591
canvas.addEventListener('touchmove', (e) => {
@@ -7590,6 +7595,7 @@ canvas.addEventListener('touchmove', (e) => {
75907595
const dx = t.clientX - tl.lastX, dy = t.clientY - tl.lastY;
75917596
tl.moved += Math.abs(dx) + Math.abs(dy);
75927597
tl.lastX = t.clientX; tl.lastY = t.clientY;
7598+
if (pinch) continue; // two fingers on the world: a pinch, not a turn of the camera (judged below)
75937599
if (MOBILE_UI && tl.moved >= CLICK_DRAG_THRESHOLD) {
75947600
// Faster than the first pass (+~50%) but fed through a short
75957601
// smoothing buffer (see updateCameraGlide) so speed reads as
@@ -7625,6 +7631,38 @@ const touchLookEnd = (e) => {
76257631
}
76267632
};
76277633
canvas.addEventListener('touchend', touchLookEnd, { passive: true });
7634+
// The pinch watcher. Two fingers that come down on the world (the scene, or
7635+
// the joystick's half of it) within a quarter of a second of each other and
7636+
// are drawn together open the map; it reads as zooming out. A thumb already
7637+
// holding the joystick when the camera finger lands is play, not a pinch.
7638+
if (MOBILE_UI) {
7639+
const PINCH_TOGETHER_MS = 250;
7640+
const downs = new Map(); // id -> { x, y, t }
7641+
const onWorld = (el) => el === canvas || (el && el.closest && !!el.closest('#joyZone'));
7642+
const span = () => { const a = downs.get(pinch.a), b = downs.get(pinch.b); return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : null; };
7643+
window.addEventListener('touchstart', (e) => {
7644+
for (const t of e.changedTouches) if (onWorld(t.target)) downs.set(t.identifier, { x: t.clientX, y: t.clientY, t: performance.now() });
7645+
pinch = null;
7646+
if (downs.size === 2 && !Modals.isOpen('mapModalOpen')) {
7647+
const [[a, A], [b, B]] = [...downs.entries()];
7648+
if (Math.abs(A.t - B.t) <= PINCH_TOGETHER_MS) { pinch = { a, b, d0: 0 }; pinch.d0 = span() || 0; }
7649+
}
7650+
}, { capture: true, passive: true });
7651+
window.addEventListener('touchmove', (e) => {
7652+
for (const t of e.changedTouches) { const d = downs.get(t.identifier); if (d) { d.x = t.clientX; d.y = t.clientY; } }
7653+
if (!pinch || pinch.d0 < 60) return;
7654+
const d = span();
7655+
if (d != null && d < pinch.d0 * PINCH_RATIO_IN) {
7656+
pinch = null;
7657+
for (const tl of touchLooks.values()) tl.moved = CLICK_DRAG_THRESHOLD; // neither finger is a tap on anything now
7658+
joyTouchId = null; joyActive = false; joyVec = { x: 0, y: 0 }; restJoystick(); // and she is not walking
7659+
if (typeof window.__openTownMap === 'function') window.__openTownMap();
7660+
}
7661+
}, { capture: true, passive: true });
7662+
const up = (e) => { for (const t of e.changedTouches) { downs.delete(t.identifier); if (pinch && (t.identifier === pinch.a || t.identifier === pinch.b)) pinch = null; } };
7663+
window.addEventListener('touchend', up, { capture: true, passive: true });
7664+
window.addEventListener('touchcancel', up, { capture: true, passive: true });
7665+
}
76287666
canvas.addEventListener('touchcancel', (e) => { for (const t of e.changedTouches) touchLooks.delete(t.identifier); }, { passive: true });
76297667

76307668
// ── Touch camera feel ───────────────────────────────────────────────────────
@@ -17756,7 +17794,37 @@ const { openNotifModal } = _notif;
1775617794
function toggle() { Modals.isOpen('mapModalOpen') ? close() : open(); }
1775717795
window.__toggleTownMap = toggle;
1775817796
window.__closeTownMap = close;
17759-
const mi = document.getElementById('menuMap'); if (mi) mi.addEventListener('click', open);
17797+
window.__openTownMap = open;
17798+
// Three ways to it on a phone, none of which puts anything new on the
17799+
// screen: tap the health-and-level pill at the top left; pinch the world
17800+
// (fingers together) — and spread them on the map to put it away; or Map,
17801+
// first in the ☰ menu. Someone who takes the long way through the menu is
17802+
// told the short ways, once.
17803+
const tellShortWays = () => {
17804+
if (!MOBILE_UI) return;
17805+
try { if (localStorage.getItem('tc_map_ways_told')) return; localStorage.setItem('tc_map_ways_told', '1'); } catch (_) { return; }
17806+
setTimeout(() => setUnlockToast('🗺️ Next time: tap your health and level at the top left, or pinch the world, to open the map.', { always: true, level: 'important' }), 400);
17807+
};
17808+
const mi = document.getElementById('menuMap'); if (mi) mi.addEventListener('click', () => { open(); tellShortWays(); });
17809+
const pill = document.getElementById('mobileVitals');
17810+
if (pill) {
17811+
// touchstart, like the action buttons: a second finger's tap gets no click while the thumb holds the joystick
17812+
pill.addEventListener('touchstart', (e) => { if (e.cancelable) e.preventDefault(); toggle(); }, { passive: false });
17813+
pill.addEventListener('click', (e) => { e.preventDefault(); toggle(); });
17814+
}
17815+
if (MOBILE_UI) { const h = document.getElementById('mapHint'); if (h) h.textContent = 'spread two fingers, or tap Close, to put it away'; }
17816+
// Spread two fingers on the open map to put it away.
17817+
const mm = document.getElementById('mapModal');
17818+
if (mm) {
17819+
let spread = null;
17820+
mm.addEventListener('touchstart', (e) => { spread = e.touches.length === 2 ? Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY) : null; }, { passive: true });
17821+
mm.addEventListener('touchmove', (e) => {
17822+
if (spread == null || e.touches.length !== 2) return;
17823+
const d = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
17824+
if (d > spread * PINCH_RATIO_OUT) { spread = null; close(); }
17825+
}, { passive: true });
17826+
mm.addEventListener('touchend', () => { spread = null; }, { passive: true });
17827+
}
1776017828
const cb = document.getElementById('mapCloseBtn'); if (cb) cb.addEventListener('click', close);
1776117829
window.addEventListener('resize', () => { if (Modals.isOpen('mapModalOpen')) render(); });
1776217830
})();

‎public/index.html‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,6 +1875,10 @@
18751875
color:#eafff0; font-size:13px; font-weight:700; padding:8px 14px; border-radius:18px;
18761876
backdrop-filter:blur(4px);
18771877
}
1878+
/* the pill is also the way to the map: tap it (client/main.js, the map) */
1879+
#mobileVitals { cursor:pointer; -webkit-tap-highlight-color:transparent; }
1880+
#mobileVitals:active { background:rgba(30,40,32,0.8); }
1881+
#mvMap { font-size:12px; opacity:0.6; margin-left:-4px; }
18781882
#mvHeart { color:#ff8a7a; }
18791883
#mvLevel { color:#ffe9a8; }
18801884
#menuBtn, #chatToggleBtn {
@@ -3024,7 +3028,7 @@
30243028
</div>
30253029

30263030
<div id="mobileTopBar" class="hidden">
3027-
<div id="mobileVitals"><span id="mvHeart"><span class="hudGi" data-hudicon="heart"></span><i id="mvHeartVal">100</i></span><span id="mvLevel">Lv 1</span><span id="mvClock" class="hudGi"></span></div>
3031+
<div id="mobileVitals"><span id="mvHeart"><span class="hudGi" data-hudicon="heart"></span><i id="mvHeartVal">100</i></span><span id="mvLevel">Lv 1</span><span id="mvClock" class="hudGi"></span><span id="mvMap" aria-hidden="true">🗺️</span></div>
30283032
<button id="chatToggleBtn" class="hidden"><span class="hudGi" data-hudicon="chat"></span><span id="chatUnread" class="hidden">0</span></button>
30293033
<button id="menuBtn">☰</button>
30303034
</div>
@@ -3033,6 +3037,7 @@
30333037
<div id="menuSheetCard">
30343038
<div id="menuSheetTitle">Thornreach</div>
30353039
<div id="menuGrid">
3040+
<button class="menuRow" id="menuMap" title="The map — see where everything is (M)">🗺️ Map</button>
30363041
<div class="menuSection">You</div>
30373042
<button class="menuRow" id="menuInventory">🎒 Inventory<span id="menuGoldVal" class="hidden" style="float:right;color:#ffd700;font-weight:800;"><span class="coin"></span> 0</span></button>
30383043
<button class="menuRow" id="menuSkills">🌟 Skills<span id="menuSkillsBadge"></span></button>
@@ -3067,7 +3072,6 @@
30673072
<button class="menuRow" id="menuGuise">👻 Disguise</button>
30683073

30693074
<div class="menuSection">Town</div>
3070-
<button class="menuRow" id="menuMap" title="Town map — see where everything is (M)">🗺️ Town Map</button>
30713075
<button class="menuRow" id="menuBonfire">🔥 Town Bonfire<span id="menuBonfireBadge" class="menuMiniBadge hidden"></span></button>
30723076
<button class="menuRow" id="menuBoard">🏆 Town Board</button>
30733077
<button class="menuRow" id="menuCrier">📣 Town Crier<span id="crierMenuBadge" class="menuMiniBadge hidden"></span></button>
@@ -4043,7 +4047,7 @@
40434047
#mapModal .mapBtns .btn{min-width:160px}
40444048
</style>
40454049
<div id="mapModal" class="hidden">
4046-
<div class="mapHead"><span id="mapTitle">🗺️ Thornreach — Town Map</span> <span class="mapHint">press M or Esc to close</span></div>
4050+
<div class="mapHead"><span id="mapTitle">🗺️ Thornreach — Town Map</span> <span class="mapHint" id="mapHint">press M or Esc to close</span></div>
40474051
<div id="mapCanvasWrap"><canvas id="mapCanvas"></canvas></div>
40484052
<div class="mapLegend">
40494053
<span><i style="background:#5ee7c0"></i>You</span>

‎test/map-quick.test.js‎

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)