移除 hover 浮出的選單鈕,版本升到 2.0.0

摘要:
拿掉游標靠近時人物縮小讓位、上方浮出圓形選單鈕的整套 hover 版面,選單只留右鍵與系統匣。

根本原因:
上一版照 Codex 的 petControlsAppearance 做了 proximity 版面,但那顆控制鈕的作用就是開選單,
而本程式的右鍵與系統匣已經有同樣的選單,多一顆按鈕沒有增加任何功能,
反而讓游標經過時人物會縮小位移。

影響:
滑鼠靠近時人物無謂地縮小、下移,並蓋出一顆重複功能的按鈕。

修法:
- renderer:移除 HOVER_SCALE / HOVER_OFFSET_Y / PROXIMITY_ENTER / PROXIMITY_EXIT 常數,
  以及 updateProximity / setNear / overControls / distanceToRect 與 near 狀態、
  applyScale 裡的控制鈕定位、menu-btn 的事件繫結;mousemove 的點穿判定回到人物與氣泡兩者。
- index.html 移除 #controls;style.css 移除 body.near 的縮放位移與 #controls 樣式。
- package.json 版本 0.1.0 → 2.0.0。
- README 的日常使用、行為對照表、與 Codex 的對照三處同步更新,
  對照表標明此項未採用及原因。

保留不變:
右鍵與系統匣選單、idle 放慢 6 倍、狀態優先序與到期、look 列縮放補正、
first-awake 問候、拖曳 4 px 方向閾值、reduced motion。

驗證:
- 三個 js 檔語法檢查通過,全專案 grep 確認 controls / menu-btn / HOVER_ / PROXIMITY_ /
  body.near / setNear 等皆無殘留。
- 實機啟動:/health 正常,events.log 顯示 look rows rescaled 8 cells 仍正常觸發;
  把游標移到人物上截圖,人物維持原尺寸原位置、無按鈕浮出(測後游標已復位)。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar:版本 2.0.0、html 無 menu-btn、
  css 無 body.near、js 無 proximity 邏輯,且右鍵選單、idle 放慢、look 補正、
  狀態優先序都還在;exe 版本資訊為 2.0.0。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 15:18:30 +08:00
co-authored by Claude Fable 5
parent f886df554e
commit 6da4775867
5 changed files with 3 additions and 97 deletions
-1
View File
@@ -9,7 +9,6 @@
<body>
<div id="stage">
<div id="bubble" class="hidden"><span id="bubble-text"></span></div>
<div id="controls"><button id="menu-btn" type="button" title="選單" aria-label="選單"></button></div>
<canvas id="sprite" width="126" height="137"></canvas>
</div>
<script src="renderer.js"></script>
+1 -45
View File
@@ -54,18 +54,12 @@ const EXPIRY = { failed: 3600e3, waiting: 1440 * 60e3, review: 10080 * 60e3 };
const RUNNING_SILENCE = 30 * 60e3;
const IDLE_PRUNE = 30 * 60e3; // idle 的 session 閒置多久後從清單移除(純清理,不影響顯示)
// hover 版面,數值與 Codex 的 petControlsAppearance 相同
const HOVER_SCALE = 0.9; // H_:游標靠近時人物縮到 0.9
const HOVER_OFFSET_Y = 10 + 8 / 2; // hoverOffsetY + hoverControlGap / 2:人物往下移,讓出上方放控制鈕
const PROXIMITY_ENTER = 40; // proximityEnterDistance:游標離人物多近算「靠近」
const PROXIMITY_EXIT = 56; // proximityExitDistance:離多遠才算離開(遲滯,避免邊界抖動)
const GREETING_MS = 8000; // first-awake 問候通知的存活時間(Pi = 8 s)
const canvas = document.getElementById("sprite");
const ctx = canvas.getContext("2d", { willReadFrequently: true });
const bubble = document.getElementById("bubble");
const bubbleText = document.getElementById("bubble-text");
const controls = document.getElementById("controls");
let sheet = null;
let petInfo = null;
@@ -130,7 +124,6 @@ function startAnim(anim, now) {
let bubbleTimer = null;
let bubbleSticky = false;
let ignoringMouse = true;
let near = false; // 游標是否在人物附近(Codex 的 proximity
let pressed = null;
let lastReported = "";
@@ -166,10 +159,6 @@ function applyScale(s) {
canvas.height = Math.round(PET_H * s);
canvas.style.width = `${canvas.width}px`;
canvas.style.height = `${canvas.height}px`;
// 控制鈕底邊 = 縮小後人物的頂端再往上 8 pxhoverControlGap)。
// 人物以中心縮到 0.9 再往下移 HOVER_OFFSET_Y,頂端會下移 h*(1-0.9)/2 + HOVER_OFFSET_Y。
const shiftedTop = canvas.height * (1 - HOVER_SCALE) / 2 + HOVER_OFFSET_Y;
controls.style.bottom = `${8 + canvas.height - shiftedTop + 8}px`;
}
// 量一格的人物輪廓(用來偵測圖檔裡整列被畫小的缺陷)
@@ -563,31 +552,6 @@ function overSprite(x, y) {
}
}
function overControls(x, y) {
if (!near) return false;
const r = controls.getBoundingClientRect();
return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
}
function distanceToRect(x, y, r) {
const dx = Math.max(r.left - x, 0, x - r.right);
const dy = Math.max(r.top - y, 0, y - r.bottom);
return Math.hypot(dx, dy);
}
// Codex 的 hover:游標進到離人物 40 px 內就切到 hover 版面——人物縮到 0.9、往下移 14 px、
// 上方出現 24 px 的控制鈕(200 ms 過渡);離開超過 56 px 才退出。動畫狀態本身不變。
function updateProximity(x, y) {
const d = distanceToRect(x, y, canvas.getBoundingClientRect());
setNear(near ? d <= PROXIMITY_EXIT : d <= PROXIMITY_ENTER);
}
function setNear(on) {
if (on === near) return;
near = on;
document.body.classList.toggle("near", on);
}
function setIgnore(ignore) {
if (ignore === ignoringMouse) return;
ignoringMouse = ignore;
@@ -605,18 +569,15 @@ window.addEventListener("mousemove", (e) => {
}
return;
}
updateProximity(e.clientX, e.clientY);
setIgnore(!(overSprite(e.clientX, e.clientY) || overBubble(e.clientX, e.clientY) || overControls(e.clientX, e.clientY)));
setIgnore(!(overSprite(e.clientX, e.clientY) || overBubble(e.clientX, e.clientY)));
});
document.addEventListener("mouseout", (e) => {
if (e.relatedTarget || pressed) return;
setNear(false);
setIgnore(true);
});
window.addEventListener("mousedown", (e) => {
if (e.target instanceof Element && e.target.closest("#controls")) return;
if (e.button === 0) {
pressed = { x: e.clientX, y: e.clientY, sx: e.screenX, sy: e.screenY, moved: false };
} else if (e.button === 2) {
@@ -667,11 +628,6 @@ window.pet.on("pet:settings", (st) => {
});
window.pet.on("pet:event", handleEvent);
document.getElementById("menu-btn").addEventListener("click", (e) => {
e.stopPropagation();
window.pet.send("pet:context-menu");
});
// 系統的「減少動態效果」切換時,讓目前的動畫重新開始(Codex 的 effect 也把 reducedMotion 列為相依)
reducedMotionQuery.addEventListener("change", () => { if (player.anim) startAnim(player.anim, performance.now()); });
-47
View File
@@ -56,53 +56,6 @@ html, body {
#sprite {
display: block;
/* Codex 的 hover 版面:以中心縮到 0.9 並往下移,200 ms 過渡(transitionDurationMs */
transform-origin: 50% 50%;
transition: transform 200ms ease;
}
body.near #sprite {
transform: translateY(14px) scale(0.9);
}
/* 靠近時氣泡讓開控制鈕 */
body.near #bubble:not(.hidden) {
transform: translateY(-22px);
}
/* 控制鈕:24 pxhoverControlSize),靠近時才出現 */
#controls {
position: absolute;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
opacity: 0;
pointer-events: none;
transition: opacity 200ms ease;
}
body.near #controls {
opacity: 1;
pointer-events: auto;
}
#controls button {
width: 24px;
height: 24px;
padding: 0;
border: none;
border-radius: 50%;
background: rgba(255, 255, 255, 0.97);
color: #222;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
font-size: 13px;
line-height: 24px;
cursor: pointer;
}
#controls button:hover {
background: #fff;
}
#sprite.grab {