|
1 | 1 | (() => { |
2 | | - const runtimeVersion = "5"; |
| 2 | + const runtimeVersion = "6"; |
3 | 3 | const existing = window.__codexPlusPetRealMouseLook; |
4 | 4 | if (existing?.version === runtimeVersion && existing?.isReady?.()) return; |
5 | 5 | existing?.stop?.(); |
|
9 | 9 | const activationRadius = 480; |
10 | 10 | const movementThreshold = 2; |
11 | 11 | const movementHoldMs = 1400; |
| 12 | + const interactionOwner = "real-mouse"; |
| 13 | + |
| 14 | + const interaction = (() => { |
| 15 | + const key = "__codexPlusPetInteraction"; |
| 16 | + const current = window[key]; |
| 17 | + if (current?.version === 1 && typeof current.begin === "function") return current; |
| 18 | + const state = { pointerId: null, owner: "", captureTarget: null }; |
| 19 | + const next = { |
| 20 | + version: 1, |
| 21 | + begin(event, owner, captureTarget) { |
| 22 | + if (state.pointerId != null) return state.pointerId === event.pointerId && state.owner === owner; |
| 23 | + state.pointerId = event.pointerId; |
| 24 | + state.owner = owner; |
| 25 | + state.captureTarget = captureTarget || event.target; |
| 26 | + try { |
| 27 | + state.captureTarget?.setPointerCapture?.(event.pointerId); |
| 28 | + } catch { |
| 29 | + } |
| 30 | + return true; |
| 31 | + }, |
| 32 | + owns(event, owner) { |
| 33 | + return state.pointerId === event.pointerId && state.owner === owner; |
| 34 | + }, |
| 35 | + active() { |
| 36 | + return state.pointerId != null; |
| 37 | + }, |
| 38 | + end(event, owner) { |
| 39 | + if (!next.owns(event, owner)) return false; |
| 40 | + try { |
| 41 | + state.captureTarget?.releasePointerCapture?.(event.pointerId); |
| 42 | + } catch { |
| 43 | + } |
| 44 | + state.pointerId = null; |
| 45 | + state.owner = ""; |
| 46 | + state.captureTarget = null; |
| 47 | + return true; |
| 48 | + }, |
| 49 | + cancel(owner) { |
| 50 | + if (state.owner !== owner) return false; |
| 51 | + state.pointerId = null; |
| 52 | + state.owner = ""; |
| 53 | + state.captureTarget = null; |
| 54 | + return true; |
| 55 | + }, |
| 56 | + }; |
| 57 | + window[key] = next; |
| 58 | + return next; |
| 59 | + })(); |
12 | 60 |
|
13 | 61 | let stopped = false; |
14 | 62 | let acceptsUpdates = true; |
|
123 | 171 | if (stopped || !acceptsUpdates || updateInFlight) return; |
124 | 172 | const mascot = document.querySelector(mascotSelector); |
125 | 173 | runtime.mascotHovered = false; |
126 | | - if (!mascot || document.visibilityState !== "visible" || dragging || nativeCursorActive) { |
| 174 | + if (!mascot || document.visibilityState !== "visible" || interaction.active() || nativeCursorActive) { |
127 | 175 | if (!nativeCursorActive) clearSyntheticPoint(); |
128 | 176 | return; |
129 | 177 | } |
|
154 | 202 | lastMoveAt = Date.now(); |
155 | 203 | } |
156 | 204 | const active = distance <= activationRadius && Date.now() - lastMoveAt <= movementHoldMs; |
157 | | - if (!active || nativeCursorActive || dragging) { |
| 205 | + if (!active || nativeCursorActive || interaction.active()) { |
158 | 206 | clearSyntheticPoint(); |
159 | 207 | return; |
160 | 208 | } |
|
168 | 216 | } |
169 | 217 | } |
170 | 218 |
|
| 219 | + function mascotAtPoint(event) { |
| 220 | + if (!Number.isFinite(event?.clientX) || !Number.isFinite(event?.clientY)) return null; |
| 221 | + const mascot = document.querySelector(mascotSelector); |
| 222 | + if (!mascot) return null; |
| 223 | + const bounds = mascot.getBoundingClientRect(); |
| 224 | + return event.clientX >= bounds.left |
| 225 | + && event.clientX <= bounds.right |
| 226 | + && event.clientY >= bounds.top |
| 227 | + && event.clientY <= bounds.bottom |
| 228 | + ? mascot |
| 229 | + : null; |
| 230 | + } |
| 231 | + |
171 | 232 | function onPointerDown(event) { |
172 | | - if (!(event.target instanceof Element) || !event.target.closest(mascotSelector)) return; |
| 233 | + const target = event.target instanceof Element ? event.target : null; |
| 234 | + const mascot = target?.closest(mascotSelector) || mascotAtPoint(event); |
| 235 | + if (!mascot) return; |
| 236 | + const ownsPointer = interaction.begin(event, interactionOwner, target || mascot); |
| 237 | + if (!ownsPointer) return; |
173 | 238 | dragging = true; |
174 | 239 | clearSyntheticPoint(); |
| 240 | + event.preventDefault?.(); |
| 241 | + } |
| 242 | + |
| 243 | + function onPointerMove(event) { |
| 244 | + if (!dragging || !interaction.owns(event, interactionOwner)) return; |
| 245 | + event.preventDefault?.(); |
175 | 246 | } |
176 | 247 |
|
177 | | - function onPointerUp() { |
| 248 | + function releaseDragging(event) { |
| 249 | + if (!dragging) return; |
| 250 | + if (event) { |
| 251 | + if (interaction.owns(event, interactionOwner)) interaction.end(event, interactionOwner); |
| 252 | + } else { |
| 253 | + interaction.cancel(interactionOwner); |
| 254 | + } |
178 | 255 | dragging = false; |
179 | 256 | } |
180 | 257 |
|
| 258 | + function onPointerUp(event) { |
| 259 | + releaseDragging(event); |
| 260 | + } |
| 261 | + |
| 262 | + function onWindowBlur() { |
| 263 | + releaseDragging(); |
| 264 | + } |
| 265 | + |
181 | 266 | function stop() { |
182 | 267 | if (stopped) return; |
183 | 268 | stopped = true; |
|
195 | 280 | } |
196 | 281 | syntheticActive = false; |
197 | 282 | document.removeEventListener("pointerdown", onPointerDown, true); |
| 283 | + document.removeEventListener("pointermove", onPointerMove, true); |
198 | 284 | document.removeEventListener("pointerup", onPointerUp, true); |
199 | 285 | document.removeEventListener("pointercancel", onPointerUp, true); |
200 | 286 | document.removeEventListener("lostpointercapture", onPointerUp, true); |
201 | | - window.removeEventListener("blur", onPointerUp); |
| 287 | + window.removeEventListener("blur", onWindowBlur); |
| 288 | + interaction.cancel(interactionOwner); |
202 | 289 | unsubscribe?.(); |
203 | 290 | if (window.__codexPlusPetRealMouseLook?.version === runtimeVersion) { |
204 | 291 | delete window.__codexPlusPetRealMouseLook; |
205 | 292 | } |
206 | 293 | } |
207 | 294 |
|
208 | 295 | document.addEventListener("pointerdown", onPointerDown, true); |
| 296 | + document.addEventListener("pointermove", onPointerMove, true); |
209 | 297 | document.addEventListener("pointerup", onPointerUp, true); |
210 | 298 | document.addEventListener("pointercancel", onPointerUp, true); |
211 | 299 | document.addEventListener("lostpointercapture", onPointerUp, true); |
212 | | - window.addEventListener("blur", onPointerUp); |
| 300 | + window.addEventListener("blur", onWindowBlur); |
213 | 301 | const runtime = { |
214 | 302 | version: runtimeVersion, |
215 | 303 | transport: "cdp-push", |
|
219 | 307 | isReady() { |
220 | 308 | return !stopped && acceptsUpdates; |
221 | 309 | }, |
| 310 | + isVisualOverrideActive() { |
| 311 | + return nativeCursorActive || syntheticActive || interaction.active() || this.mascotHovered; |
| 312 | + }, |
222 | 313 | stop, |
223 | 314 | updateScreenPoint(point) { |
224 | 315 | if (!this.isReady()) return false; |
|
0 commit comments