移除 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:
@@ -87,7 +87,6 @@ npm run autostart:install # (可選)開機自動啟動
|
||||
| 左鍵點一下 | 揮手(若有非固定的氣泡,先關閉氣泡) |
|
||||
| 左鍵拖曳 | 搬家;往左播 running-left、往右播 running-right;放開後位置寫入設定檔 |
|
||||
| 右鍵 / 系統匣左鍵 | 開啟選單 |
|
||||
| 滑鼠靠近(40 px 內) | 她縮到 0.9、往下讓出位置,上方出現選單鈕;離開 56 px 外才復原(Codex 的 hover 版面) |
|
||||
| 滑鼠靠近 | 盯著游標看(選單開啟「跟著游標看」後才有) |
|
||||
|
||||
**選單功能**
|
||||
@@ -125,7 +124,6 @@ npm run autostart:install # (可選)開機自動啟動
|
||||
| `SessionEnd` | | 移除該 session | 最後一個結束時 👋 掰掰 |
|
||||
| (無事件) | 游標移動(追視開啟時) | 16 方向 `look`,覆蓋所有狀態 | |
|
||||
| (無事件) | 拖曳 | 單次位移 ≥ 4 px 才決定 `running-left` / `running-right` | |
|
||||
| (無事件) | 游標靠近 40 px 內 | 動畫不變;人物縮 0.9、顯示選單鈕 | |
|
||||
|
||||
規則:
|
||||
|
||||
@@ -259,7 +257,7 @@ Claude Code ──hook(stdin JSON)──▶ hook/claude-pet-hook.js ──PO
|
||||
| `review` | turn 完成且未讀,直到使用者回到該 thread | 直到該 session 下一個動作 |
|
||||
| `waving` | 只在 first-awake(每隻寵物一次,8 秒) | 相同(記在設定檔 `greetedPetIds`) |
|
||||
| `jumping` | 只在 `respondToHover`,而整個 app 沒人開啟它 | 只在選單「動作測試」 |
|
||||
| hover | 40 px 內進入、56 px 外退出;人物縮 0.9、下移 14 px、上方 24 px 控制鈕、200 ms | 相同;控制鈕是選單 |
|
||||
| hover | 40 px 內進入、56 px 外退出;人物縮 0.9、下移 14 px、上方 24 px 控制鈕、200 ms | 未採用——控制鈕的功能就是選單,右鍵已經有了 |
|
||||
| 追視 | 看 quick chat 文字游標/computer-use 游標,死區 1 px,覆蓋所有狀態 | 沒有對應來源;改為**選配**看滑鼠,預設關 |
|
||||
| 拖曳方向 | 單次取樣水平位移 ≥ 4 px | 相同 |
|
||||
| 拖曳放開 | 彈簧回彈(spring 220 / damping 26) | 無(直接停在放開處) |
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-pet",
|
||||
"version": "0.1.0",
|
||||
"version": "2.0.0",
|
||||
"description": "Codex Pets 相容的 Claude Code 桌面寵物(spriteVersionNumber 2:9 個動作列 + 16 方向追視)",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
|
||||
@@ -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
@@ -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 px(hoverControlGap)。
|
||||
// 人物以中心縮到 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()); });
|
||||
|
||||
|
||||
@@ -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 px(hoverControlSize),靠近時才出現 */
|
||||
#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 {
|
||||
|
||||
Reference in New Issue
Block a user